From: Theo Buehler Subject: nc/libtls: peer cert pubkey fingerprint printing To: tech@openbsd.org Cc: beck@openbsd.org, jsing@openbsd.org Date: Wed, 6 Nov 2024 08:27:04 +0100 For things like github you may want to pin its certificate in a .gitconfig stanza like this one: [http "https://github.com/"] pinnedpubkey = sha256//Gs+dT9kUC17nDYZXH52mKzGnlUU/Q5mS0UruTQW3H0U= As you can see, this uses a somewhat weird libcurlism with that sha256// prefixed base64 blob (thus the entire thing is a valid base64 string, but that's probably a coincidence). It is somewhat annoying to compute this hash with what used to be the world's most terrible command line interface before git was inflicted on us. So here's a diff that adds a getter to libtls to compute this thing and uses this getter in nc(1) to display it like so $ nc -cvz github.com 443 [...] Pubkey Fingerprint: sha256//Gs+dT9kUC17nDYZXH52mKzGnlUU/Q5mS0UruTQW3H0U= When we discussed it in Melbourne, it was suggested I do a SHA256:$hex version of this, which will be easy enough to do, but I have no need for this, and I forgot the details, so I need to be told again, preferably with suggested naming for the public API. Of note, this hashes the entire SPKI, not only the bitstring like in the SKI, so X509_pubkey_digest() is of no use. Also, I tried to be consistent by mentioning pubkey in the public facing parts to avoid confusion with the -fingerprint option that computes the hash of the entire cert. Of course this needs a minor bump (not included). This isn't urgent and I'll wait for the next bump, e.g., when bluhm and henning finish what they're cooking. Index: lib/libtls/Symbols.list =================================================================== RCS file: /cvs/src/lib/libtls/Symbols.list,v diff -u -p -r1.16 Symbols.list --- lib/libtls/Symbols.list 24 Mar 2022 15:56:34 -0000 1.16 +++ lib/libtls/Symbols.list 6 Nov 2024 06:14:59 -0000 @@ -71,6 +71,7 @@ tls_ocsp_process_response tls_peer_cert_chain_pem tls_peer_cert_contains_name tls_peer_cert_hash +tls_peer_cert_pubkey_fingerprint tls_peer_cert_issuer tls_peer_cert_notafter tls_peer_cert_notbefore Index: lib/libtls/tls.c =================================================================== RCS file: /cvs/src/lib/libtls/tls.c,v diff -u -p -r1.104 tls.c --- lib/libtls/tls.c 8 Apr 2024 20:47:32 -0000 1.104 +++ lib/libtls/tls.c 6 Nov 2024 06:19:38 -0000 @@ -336,6 +336,42 @@ tls_cert_pubkey_hash(X509 *cert, char ** return (rv); } +int +tls_cert_pubkey_fingerprint(X509 *cert, char **fingerprint) +{ + X509_PUBKEY *pubkey; + char d[EVP_MAX_MD_SIZE], *dbase64 = NULL; + unsigned char *der = NULL; + int derlen = 0, dlen = 0, rv = -1; + + free(*fingerprint); + *fingerprint = NULL; + + if ((pubkey = X509_get_X509_PUBKEY(cert)) == NULL) + goto err; + if ((derlen = i2d_X509_PUBKEY(pubkey, &der)) <= 0) { + derlen = 0; + goto err; + } + if (!EVP_Digest(der, derlen, d, &dlen, EVP_sha256(), NULL)) + goto err; + if (tls_base64_string(d, dlen, &dbase64, NULL) != 0) + goto err; + + if (asprintf(fingerprint, "sha256//%s", dbase64) == -1) { + *fingerprint = NULL; + goto err; + } + + rv = 0; + + err: + freezero(der, derlen); + free(dbase64); + + return rv; +} + static int tls_keypair_to_pkey(struct tls *ctx, struct tls_keypair *keypair, EVP_PKEY **pkey) { Index: lib/libtls/tls.h =================================================================== RCS file: /cvs/src/lib/libtls/tls.h,v diff -u -p -r1.67 tls.h --- lib/libtls/tls.h 2 Aug 2024 15:00:01 -0000 1.67 +++ lib/libtls/tls.h 6 Nov 2024 06:15:29 -0000 @@ -201,6 +201,7 @@ int tls_peer_cert_provided(struct tls *_ int tls_peer_cert_contains_name(struct tls *_ctx, const char *_name); const char *tls_peer_cert_hash(struct tls *_ctx); +const char *tls_peer_cert_pubkey_fingerprint(struct tls *_ctx); const char *tls_peer_cert_issuer(struct tls *_ctx); const char *tls_peer_cert_subject(struct tls *_ctx); time_t tls_peer_cert_notbefore(struct tls *_ctx); Index: lib/libtls/tls_conninfo.c =================================================================== RCS file: /cvs/src/lib/libtls/tls_conninfo.c,v diff -u -p -r1.27 tls_conninfo.c --- lib/libtls/tls_conninfo.c 26 Mar 2024 06:31:22 -0000 1.27 +++ lib/libtls/tls_conninfo.c 6 Nov 2024 06:13:03 -0000 @@ -16,6 +16,9 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include + +#include #include #include @@ -42,6 +45,43 @@ tls_convert_notafter(struct tm *tm, time } int +tls_base64_string(const unsigned char *in, size_t inlen, char **out, + size_t *outlen) +{ + char *base64 = NULL; + size_t base64_len = 0; + int ret = -1; + + if (outlen != NULL) + *outlen = 0; + if (*out != NULL) + goto err; + + if (inlen > INT_MAX / 4) + goto err; + + base64_len = ((inlen + 2) / 3) * 4 + 1; + if ((base64 = calloc(1, base64_len)) == NULL) + goto err; + + if (b64_ntop(in, inlen, base64, base64_len) == -1) + goto err; + + *out = base64; + base64 = NULL; + + if (outlen != NULL) + *outlen = base64_len; + + ret = 0; + + err: + freezero(base64, base64_len); + + return (ret); +} + +int tls_hex_string(const unsigned char *in, size_t inlen, char **out, size_t *outlen) { @@ -87,6 +127,21 @@ tls_get_peer_cert_hash(struct tls *ctx, } static int +tls_get_peer_cert_fingerprint(struct tls *ctx, char **fingerprint) +{ + *fingerprint = NULL; + if (ctx->ssl_peer_cert == NULL) + return (0); + + if (tls_cert_pubkey_fingerprint(ctx->ssl_peer_cert, fingerprint) == -1) { + tls_set_errorx(ctx, TLS_ERROR_OUT_OF_MEMORY, "out of memory"); + *fingerprint = NULL; + return (-1); + } + return (0); +} + +static int tls_get_peer_cert_issuer(struct tls *ctx, char **issuer) { X509_NAME *name = NULL; @@ -152,6 +207,8 @@ tls_get_peer_cert_info(struct tls *ctx) if (ctx->ssl_peer_cert == NULL) return (0); + if (tls_get_peer_cert_fingerprint(ctx, &ctx->conninfo->fingerprint) == -1) + goto err; if (tls_get_peer_cert_hash(ctx, &ctx->conninfo->hash) == -1) goto err; if (tls_get_peer_cert_subject(ctx, &ctx->conninfo->subject) == -1) @@ -298,6 +355,7 @@ tls_conninfo_free(struct tls_conninfo *c free(conninfo->servername); free(conninfo->version); + free(conninfo->fingerprint); free(conninfo->hash); free(conninfo->issuer); free(conninfo->subject); Index: lib/libtls/tls_internal.h =================================================================== RCS file: /cvs/src/lib/libtls/tls_internal.h,v diff -u -p -r1.85 tls_internal.h --- lib/libtls/tls_internal.h 26 Mar 2024 06:24:52 -0000 1.85 +++ lib/libtls/tls_internal.h 6 Nov 2024 06:08:18 -0000 @@ -129,6 +129,7 @@ struct tls_conninfo { int session_resumed; char *version; + char *fingerprint; char *hash; char *issuer; char *subject; @@ -291,10 +292,13 @@ int tls_ocsp_verify_cb(SSL *ssl, void *a int tls_ocsp_stapling_cb(SSL *ssl, void *arg); void tls_ocsp_free(struct tls_ocsp *ctx); struct tls_ocsp *tls_ocsp_setup_from_peer(struct tls *ctx); +int tls_base64_string(const unsigned char *_in, size_t _inlen, char **_out, + size_t *_outlen); int tls_hex_string(const unsigned char *_in, size_t _inlen, char **_out, size_t *_outlen); int tls_cert_hash(X509 *_cert, char **_hash); int tls_cert_pubkey_hash(X509 *_cert, char **_hash); +int tls_cert_pubkey_fingerprint(X509 *_cert, char **_fingerprint); int tls_password_cb(char *_buf, int _size, int _rwflag, void *_u); Index: lib/libtls/tls_peer.c =================================================================== RCS file: /cvs/src/lib/libtls/tls_peer.c,v diff -u -p -r1.8 tls_peer.c --- lib/libtls/tls_peer.c 10 Apr 2017 17:11:13 -0000 1.8 +++ lib/libtls/tls_peer.c 6 Nov 2024 06:15:08 -0000 @@ -30,6 +30,15 @@ tls_peer_cert_hash(struct tls *ctx) return (NULL); return (ctx->conninfo->hash); } + +const char * +tls_peer_cert_pubkey_fingerprint(struct tls *ctx) +{ + if (ctx->conninfo == NULL) + return (NULL); + return (ctx->conninfo->fingerprint); +} + const char * tls_peer_cert_issuer(struct tls *ctx) { Index: lib/libtls/man/tls_conn_version.3 =================================================================== RCS file: /cvs/src/lib/libtls/man/tls_conn_version.3,v diff -u -p -r1.10 tls_conn_version.3 --- lib/libtls/man/tls_conn_version.3 2 Nov 2019 13:43:14 -0000 1.10 +++ lib/libtls/man/tls_conn_version.3 6 Nov 2024 06:50:31 -0000 @@ -31,6 +31,7 @@ .Nm tls_peer_cert_issuer , .Nm tls_peer_cert_subject , .Nm tls_peer_cert_hash , +.Nm tls_peer_cert_pubkey_fingerprint , .Nm tls_peer_cert_notbefore , .Nm tls_peer_cert_notafter .Nd inspect an established TLS connection @@ -66,6 +67,8 @@ .Fn tls_peer_cert_subject "struct tls *ctx" .Ft const char * .Fn tls_peer_cert_hash "struct tls *ctx" +.Ft const char * +.Fn tls_peer_cert_pubkey_fingerprint "struct tls *ctx" .Ft time_t .Fn tls_peer_cert_notbefore "struct tls *ctx" .Ft time_t @@ -149,6 +152,22 @@ h=$(openssl x509 -outform der -in mycert printf "SHA256:${h}\\n" .Ed .Pp +.Fn tls_peer_cert_pubkey_fingerprint +returns a string +corresponding to a hash of the raw certificate's subject public key info from +.Ar ctx +prefixed by a hash name followed by two slashes. +The hash currently used is SHA256, though this +could change in the future. +The public key fingerprint for a certificate in file +.Ar mycert.crt +can be generated using the commands: +.Bd -literal -offset indent +f=$(openssl x509 -pubkey -in mycert.crt | + openssl pkey -pubin -outform der | sha256 -b) +printf "sha256//${f}\\n" +.Ed +.Pp .Fn tls_peer_cert_notbefore returns the time corresponding to the start of the validity period of the peer certificate from @@ -209,6 +228,9 @@ appeared in .Fn tls_conn_cipher_strength appeared in .Ox 6.7 . +.Fn tls_peer_cert_pubkey_fingerprint +appeared in +.Ox 7.7 . .Sh AUTHORS .An Bob Beck Aq Mt beck@openbsd.org .An Joel Sing Aq Mt jsing@openbsd.org Index: regress/lib/libtls/gotls/tls.go =================================================================== RCS file: /cvs/src/regress/lib/libtls/gotls/tls.go,v diff -u -p -r1.16 tls.go --- regress/lib/libtls/gotls/tls.go 2 Aug 2024 15:02:22 -0000 1.16 +++ regress/lib/libtls/gotls/tls.go 6 Nov 2024 06:25:21 -0000 @@ -220,6 +220,15 @@ func (t *TLS) PeerCertHash() (string, er return C.GoString(hash), nil } +// PeerCertPubkeyFingerprint returns a hash of the peer certificate's public key. +func (t *TLS) PeerCertPubkeyFingerprint() (string, error) { + hash := C.tls_peer_cert_pubkey_fingerprint(t.ctx) + if hash == nil { + return "", errors.New("no hash returned") + } + return C.GoString(hash), nil +} + // PeerCertNotBefore returns the notBefore time from the peer // certificate. func (t *TLS) PeerCertNotBefore() (time.Time, error) { Index: regress/lib/libtls/gotls/tls_test.go =================================================================== RCS file: /cvs/src/regress/lib/libtls/gotls/tls_test.go,v diff -u -p -r1.10 tls_test.go --- regress/lib/libtls/gotls/tls_test.go 2 Jul 2023 06:37:27 -0000 1.10 +++ regress/lib/libtls/gotls/tls_test.go 6 Nov 2024 06:28:03 -0000 @@ -18,6 +18,7 @@ const ( httpContent = "Hello, TLS!" certHash = "SHA256:1153aa0230ee0481b36bdd83ddb04b607340dbda35f3a4fff0615e4d9292d687" + certFingerprint = "sha256//ybtCgKrAwBjEc3yEHzrre0RnI/Yre8JIF1cF4vxY4Cg=" ) var ( @@ -421,6 +422,9 @@ func TestTLSInfo(t *testing.T) { if _, err := tls.PeerCertHash(); err == nil { t.Error("PeerCertHash() returned nil error, want error") } + if _, err := tls.PeerCertPubkeyFingerprint(); err == nil { + t.Error("PeerCertHash() returned nil error, want error") + } if _, err := tls.PeerCertNotBefore(); err == nil { t.Error("PeerCertNotBefore() returned nil error, want error") } @@ -474,6 +478,13 @@ func TestTLSInfo(t *testing.T) { t.Errorf("Got cert hash %q, want %q", hash, certHash) } else { t.Logf("Hash: %v", hash) + } + if fingerprint, err := tls.PeerCertPubkeyFingerprint(); err != nil { + t.Errorf("PeerCertPubkeyFingerprint() returned error: %v", err) + } else if fingerprint != certFingerprint { + t.Errorf("Got cert hash %q, want %q", fingerprint, certFingerprint) + } else { + t.Logf("Pubkey Fingerprint: %v", fingerprint) } if notBefore, err := tls.PeerCertNotBefore(); err != nil { t.Errorf("PeerCertNotBefore() returned error: %v", err) Index: usr.bin/nc/netcat.c =================================================================== RCS file: /cvs/src/usr.bin/nc/netcat.c,v diff -u -p -r1.229 netcat.c --- usr.bin/nc/netcat.c 2 Nov 2024 17:19:27 -0000 1.229 +++ usr.bin/nc/netcat.c 6 Nov 2024 06:21:42 -0000 @@ -1737,6 +1737,9 @@ report_tls(struct tls *tls_ctx, char *ho if (tls_peer_cert_hash(tls_ctx)) fprintf(stderr, "Cert Hash: %s\n", tls_peer_cert_hash(tls_ctx)); + if (tls_peer_cert_pubkey_fingerprint(tls_ctx)) + fprintf(stderr, "Pubkey Fingerprint: %s\n", + tls_peer_cert_pubkey_fingerprint(tls_ctx)); ocsp_url = tls_peer_ocsp_url(tls_ctx); if (ocsp_url != NULL) fprintf(stderr, "OCSP URL: %s\n", ocsp_url);