Download raw body.
nc/libtls: peer cert pubkey fingerprint printing
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 <netinet/in.h>
+
+#include <resolv.h>
#include <stdio.h>
#include <string.h>
@@ -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);
nc/libtls: peer cert pubkey fingerprint printing