diff --git a/cmdgen.c b/cmdgen.c index 8d82362f..25078b9a 100644 --- a/cmdgen.c +++ b/cmdgen.c @@ -614,6 +614,10 @@ int main(int argc, char **argv) fptype = SSH_FPTYPE_MD5; else if (!strcmp(p, "sha256")) fptype = SSH_FPTYPE_SHA256; + else if (!strcmp(p, "md5-cert")) + fptype = SSH_FPTYPE_MD5_CERT; + else if (!strcmp(p, "sha256-cert")) + fptype = SSH_FPTYPE_SHA256_CERT; else { fprintf(stderr, "puttygen: unknown fingerprint " "type `%s'\n", p); diff --git a/crypto/openssh-certs.c b/crypto/openssh-certs.c index e37e42b2..07f412b9 100644 --- a/crypto/openssh-certs.c +++ b/crypto/openssh-certs.c @@ -868,9 +868,9 @@ static SeatDialogText *opensshcert_cert_info(ssh_key *key) seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "%s", fp); sfree(fp); - fp = ssh2_fingerprint(ck->basekey, SSH_FPTYPE_DEFAULT); + fp = ssh2_fingerprint(key, ssh_fptype_to_cert(SSH_FPTYPE_DEFAULT)); seat_dialog_text_append(text, SDT_MORE_INFO_KEY, - "Fingerprint of underlying key"); + "Fingerprint including certificate"); seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "%s", fp); sfree(fp); diff --git a/pageant.c b/pageant.c index 72e61f8c..425e7a19 100644 --- a/pageant.c +++ b/pageant.c @@ -729,7 +729,7 @@ static PageantAsyncOp *pageant_make_op( int i; PageantKey *pk; for (i = 0; NULL != (pk = pageant_nth_key(2, i)); i++) { - char *fingerprint = ssh2_fingerprint_blob( + char *fingerprint = ssh2_double_fingerprint_blob( ptrlen_from_strbuf(pk->public_blob), SSH_FPTYPE_DEFAULT); pageant_client_log(pc, reqid, "returned key: %s %s", fingerprint, pk->comment); @@ -839,7 +839,7 @@ static PageantAsyncOp *pageant_make_op( have_flags = true; if (!pc->suppress_logging) { - char *fingerprint = ssh2_fingerprint_blob( + char *fingerprint = ssh2_double_fingerprint_blob( keyblob, SSH_FPTYPE_DEFAULT); pageant_client_log(pc, reqid, "requested key: %s", fingerprint); sfree(fingerprint); @@ -1047,7 +1047,7 @@ static PageantAsyncOp *pageant_make_op( } if (!pc->suppress_logging) { - char *fingerprint = ssh2_fingerprint_blob( + char *fingerprint = ssh2_double_fingerprint_blob( blob, SSH_FPTYPE_DEFAULT); pageant_client_log(pc, reqid, "unwanted key: %s", fingerprint); sfree(fingerprint); @@ -1160,7 +1160,7 @@ static PageantAsyncOp *pageant_make_op( } if (!pc->suppress_logging) { - char *fingerprint = ssh2_fingerprint_blob( + char *fingerprint = ssh2_double_fingerprint_blob( ptrlen_from_strbuf(public_blob), SSH_FPTYPE_DEFAULT); pageant_client_log(pc, reqid, "add-ppk: %s %s", fingerprint, comment); @@ -1263,7 +1263,7 @@ static PageantAsyncOp *pageant_make_op( } if (!pc->suppress_logging) { - char *fingerprint = ssh2_fingerprint_blob( + char *fingerprint = ssh2_double_fingerprint_blob( blob, SSH_FPTYPE_DEFAULT); pageant_client_log(pc, reqid, "key to re-encrypt: %s", fingerprint); @@ -1348,7 +1348,7 @@ static PageantAsyncOp *pageant_make_op( int i; PageantKey *pk; for (i = 0; NULL != (pk = pageant_nth_key(2, i)); i++) { - char *fingerprint = ssh2_fingerprint_blob( + char *fingerprint = ssh2_double_fingerprint_blob( ptrlen_from_strbuf(pk->public_blob), SSH_FPTYPE_DEFAULT); pageant_client_log(pc, reqid, "returned key: %s %s", diff --git a/ssh.h b/ssh.h index 139acb20..bd2e6961 100644 --- a/ssh.h +++ b/ssh.h @@ -1454,12 +1454,36 @@ enum { }; typedef enum { + /* Default fingerprint types strip off a certificate to show you + * the fingerprint of the underlying public key */ SSH_FPTYPE_MD5, SSH_FPTYPE_SHA256, + /* Non-default version of each fingerprint type which is 'raw', + * giving you the true hash of the public key blob even if it + * includes a certificate */ + SSH_FPTYPE_MD5_CERT, + SSH_FPTYPE_SHA256_CERT, } FingerprintType; +static inline bool ssh_fptype_is_cert(FingerprintType fptype) +{ + return fptype >= SSH_FPTYPE_MD5_CERT; +} +static inline FingerprintType ssh_fptype_from_cert(FingerprintType fptype) +{ + if (ssh_fptype_is_cert(fptype)) + fptype -= (SSH_FPTYPE_MD5_CERT - SSH_FPTYPE_MD5); + return fptype; +} +static inline FingerprintType ssh_fptype_to_cert(FingerprintType fptype) +{ + if (!ssh_fptype_is_cert(fptype)) + fptype += (SSH_FPTYPE_MD5_CERT - SSH_FPTYPE_MD5); + return fptype; +} + +#define SSH_N_FPTYPES (SSH_FPTYPE_SHA256_CERT + 1) #define SSH_FPTYPE_DEFAULT SSH_FPTYPE_SHA256 -#define SSH_N_FPTYPES (SSH_FPTYPE_SHA256 + 1) FingerprintType ssh2_pick_fingerprint(char **fingerprints, FingerprintType preferred_type); @@ -1473,6 +1497,8 @@ void ssh2_write_pubkey(FILE *fp, const char *comment, int keytype); char *ssh2_fingerprint_blob(ptrlen, FingerprintType); char *ssh2_fingerprint(ssh_key *key, FingerprintType); +char *ssh2_double_fingerprint_blob(ptrlen, FingerprintType); +char *ssh2_double_fingerprint(ssh_key *key, FingerprintType); char **ssh2_all_fingerprints_for_blob(ptrlen); char **ssh2_all_fingerprints(ssh_key *key); void ssh2_free_all_fingerprints(char **); diff --git a/ssh/kex2-client.c b/ssh/kex2-client.c index ccc62c3c..5935ef29 100644 --- a/ssh/kex2-client.c +++ b/ssh/kex2-client.c @@ -732,7 +732,7 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) * host key, store it. */ if (s->hkey) { - char *fingerprint = ssh2_fingerprint( + char *fingerprint = ssh2_double_fingerprint( s->hkey, SSH_FPTYPE_DEFAULT); ppl_logevent("GSS kex provided fallback host key:"); ppl_logevent("%s", fingerprint); @@ -791,7 +791,8 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) * triggered on purpose to populate the transient cache. */ assert(s->hkey); /* only KEXTYPE_GSS lets this be null */ - char *fingerprint = ssh2_fingerprint(s->hkey, SSH_FPTYPE_DEFAULT); + char *fingerprint = ssh2_double_fingerprint( + s->hkey, SSH_FPTYPE_DEFAULT); if (s->need_gss_transient_hostkey) { ppl_logevent("Post-GSS rekey provided fallback host key:"); @@ -867,10 +868,10 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) * hash.) */ if (ssh_key_alg(s->hkey)->is_certificate) { - char *base_fp = ssh2_fingerprint(ssh_key_base_key(s->hkey), - fptype_default); - ppl_logevent("Host key is a certificate, whose base key has " - "fingerprint:"); + char *base_fp = ssh2_fingerprint( + s->hkey, ssh_fptype_to_cert(fptype_default)); + ppl_logevent("Host key is a certificate. " + "Hash including certificate:"); ppl_logevent("%s", base_fp); sfree(base_fp); @@ -972,7 +973,8 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) assert(s->hkey); assert(ssh_key_alg(s->hkey) == s->cross_certifying); - char *fingerprint = ssh2_fingerprint(s->hkey, SSH_FPTYPE_DEFAULT); + char *fingerprint = ssh2_double_fingerprint( + s->hkey, SSH_FPTYPE_DEFAULT); ppl_logevent("Storing additional host key for this host:"); ppl_logevent("%s", fingerprint); sfree(fingerprint); diff --git a/sshpubk.c b/sshpubk.c index efc3f2e5..0648b4c4 100644 --- a/sshpubk.c +++ b/sshpubk.c @@ -1752,6 +1752,7 @@ static void ssh2_fingerprint_blob_sha256(ptrlen blob, strbuf *sb) char *ssh2_fingerprint_blob(ptrlen blob, FingerprintType fptype) { strbuf *sb = strbuf_new(); + strbuf *tmp = NULL; /* * Identify the key algorithm, if possible. @@ -1767,23 +1768,62 @@ char *ssh2_fingerprint_blob(ptrlen blob, FingerprintType fptype) if (alg) { int bits = ssh_key_public_bits(alg, blob); put_fmt(sb, "%.*s %d ", PTRLEN_PRINTF(algname), bits); + + if (!ssh_fptype_is_cert(fptype) && alg->is_certificate) { + ssh_key *key = ssh_key_new_pub(alg, blob); + if (key) { + tmp = strbuf_new(); + ssh_key_public_blob(ssh_key_base_key(key), + BinarySink_UPCAST(tmp)); + blob = ptrlen_from_strbuf(tmp); + ssh_key_free(key); + } + } } else { put_fmt(sb, "%.*s ", PTRLEN_PRINTF(algname)); } } - switch (fptype) { + switch (ssh_fptype_from_cert(fptype)) { case SSH_FPTYPE_MD5: ssh2_fingerprint_blob_md5(blob, sb); break; case SSH_FPTYPE_SHA256: ssh2_fingerprint_blob_sha256(blob, sb); break; + default: + unreachable("ssh_fptype_from_cert ruled out the other values"); } + if (tmp) + strbuf_free(tmp); + return strbuf_to_str(sb); } +char *ssh2_double_fingerprint_blob(ptrlen blob, FingerprintType fptype) +{ + if (ssh_fptype_is_cert(fptype)) + fptype = ssh_fptype_from_cert(fptype); + + char *fp = ssh2_fingerprint_blob(blob, fptype); + char *p = strrchr(fp, ' '); + char *hash = p ? p + 1 : fp; + + char *fpc = ssh2_fingerprint_blob(blob, ssh_fptype_to_cert(fptype)); + char *pc = strrchr(fpc, ' '); + char *hashc = pc ? pc + 1 : fpc; + + if (strcmp(hash, hashc)) { + char *tmp = dupprintf("%s (with certificate: %s)", fp, hashc); + sfree(fp); + fp = tmp; + } + + sfree(fpc); + return fp; +} + char **ssh2_all_fingerprints_for_blob(ptrlen blob) { char **fps = snewn(SSH_N_FPTYPES, char *); @@ -1801,6 +1841,15 @@ char *ssh2_fingerprint(ssh_key *data, FingerprintType fptype) return ret; } +char *ssh2_double_fingerprint(ssh_key *data, FingerprintType fptype) +{ + strbuf *blob = strbuf_new(); + ssh_key_public_blob(data, BinarySink_UPCAST(blob)); + char *ret = ssh2_double_fingerprint_blob(ptrlen_from_strbuf(blob), fptype); + strbuf_free(blob); + return ret; +} + char **ssh2_all_fingerprints(ssh_key *data) { strbuf *blob = strbuf_new(); diff --git a/test/cryptsuite.py b/test/cryptsuite.py index 1512444c..04450dd0 100755 --- a/test/cryptsuite.py +++ b/test/cryptsuite.py @@ -1410,6 +1410,35 @@ class crypt(MyTestBase): self.assertEqual(ssh2_fingerprint_blob(very_silly_blob, "md5"), b'ac:bd:18:db:4c:c2:f8:5c:ed:ef:65:4f:cc:c4:a4:d8') + # A certified key. + cert_blob = b64( + 'AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIJ4Ds9YwRHxs' + 'xdtUitRbZGz0MgKGZSBVrTHI1AbvetofAAAAIMt0/CMBL+64GQ/r/JyGxo6oHs86' + 'i9bOHhMJYbDbxEJfAAAAAAAAAG8AAAABAAAAAmlkAAAADAAAAAh1c2VybmFtZQAA' + 'AAAAAAPoAAAAAAAAB9AAAAAAAAAAAAAAAAAAAAE+AAAAIHNzaC1lZDI1NTE5LWNl' + 'cnQtdjAxQG9wZW5zc2guY29tAAAAICl5MiUNt8hoAAHT0v00JYOkWe2UW31+Qq5Q' + 'HYKWGyVjAAAAIMUJEFAmSV/qtoxSmVOHUgTMKYjqkDy8fTfsfCKV+sN7AAAAAAAA' + 'AG8AAAABAAAAAmlkAAAAEgAAAA5kb2Vzbid0IG1hdHRlcgAAAAAAAAPoAAAAAAAA' + 'B9AAAAAAAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIMUJEFAmSV/qtoxS' + 'mVOHUgTMKYjqkDy8fTfsfCKV+sN7AAAAUwAAAAtzc2gtZWQyNTUxOQAAAEAXbRz3' + 'lBmoU4FVge29jn04MfubF6U0CoPG1nbeZSgDN2iz7qtZ75XIk5O/Z/W9nA8jwsiz' + 'iSEMItjvR7HEN8MIAAAAUwAAAAtzc2gtZWQyNTUxOQAAAECszhkY8bUbSCjmHEMP' + 'LjcOX6OaeBzPIYYYXJzpLn+m+CIwDXRIxyvON5/d/TomgAFNJutfOEsqIzy5OAvl' + 'p5IO') + self.assertEqual(ssh2_fingerprint_blob(cert_blob, "sha256"), + b'ssh-ed25519-cert-v01@openssh.com 255 ' + b'SHA256:42JaqhHUNa5CoKxGWqtKXF0Awz7b0aPrtgBZ9VLLHfY') + self.assertEqual(ssh2_fingerprint_blob(cert_blob, "md5"), + b'ssh-ed25519-cert-v01@openssh.com 255 ' + b'8e:40:00:e0:1f:4a:9c:b3:c8:e9:05:59:04:03:44:b3') + self.assertEqual(ssh2_fingerprint_blob(cert_blob, "sha256-cert"), + b'ssh-ed25519-cert-v01@openssh.com 255 ' + b'SHA256:W/+SDEg7S+/dAn4SrodJ2c8bYvt13XXA7YYlQ6E8R5U') + self.assertEqual(ssh2_fingerprint_blob(cert_blob, "md5-cert"), + b'ssh-ed25519-cert-v01@openssh.com 255 ' + b'03:cf:aa:8e:aa:c3:a0:97:bb:2e:7e:57:9d:08:b5:be') + + def testAES(self): # My own test cases, generated by a mostly independent # reference implementation of AES in Python. ('Mostly' diff --git a/test/testcrypt-enum.h b/test/testcrypt-enum.h index f1977c3e..ac73a766 100644 --- a/test/testcrypt-enum.h +++ b/test/testcrypt-enum.h @@ -136,6 +136,8 @@ END_ENUM_TYPE(argon2flavour) BEGIN_ENUM_TYPE(fptype) ENUM_VALUE("md5", SSH_FPTYPE_MD5) ENUM_VALUE("sha256", SSH_FPTYPE_SHA256) + ENUM_VALUE("md5-cert", SSH_FPTYPE_MD5_CERT) + ENUM_VALUE("sha256-cert", SSH_FPTYPE_SHA256_CERT) END_ENUM_TYPE(fptype) /* diff --git a/unix/pageant.c b/unix/pageant.c index 5db797a8..cef57b5b 100644 --- a/unix/pageant.c +++ b/unix/pageant.c @@ -612,6 +612,7 @@ static bool match_fingerprint_string( switch (fptype) { case SSH_FPTYPE_MD5: + case SSH_FPTYPE_MD5_CERT: /* MD5 fingerprints are in hex, so disregard case differences. */ case_sensitive = false; /* And we don't really need to force the user to type the @@ -620,6 +621,7 @@ static bool match_fingerprint_string( ignore_chars = ":"; break; case SSH_FPTYPE_SHA256: + case SSH_FPTYPE_SHA256_CERT: /* Skip over the "SHA256:" prefix, which we don't really * want to force the user to type. On the other hand, * tolerate it on the input string. */ @@ -713,6 +715,18 @@ struct pageant_pubkey *find_key(const char *string, char **retstr) try_comment = false; try_all_fptypes = false; fptype = SSH_FPTYPE_SHA256; + } else if (!strnicmp(string, "md5-cert:", 9)) { + string += 9; + try_file = false; + try_comment = false; + try_all_fptypes = false; + fptype = SSH_FPTYPE_MD5_CERT; + } else if (!strncmp(string, "sha256-cert:", 12)) { + string += 12; + try_file = false; + try_comment = false; + try_all_fptypes = false; + fptype = SSH_FPTYPE_SHA256_CERT; } /* @@ -1402,6 +1416,10 @@ int main(int argc, char **argv) key_list_fptype = SSH_FPTYPE_MD5; else if (!strcmp(keyword, "sha256")) key_list_fptype = SSH_FPTYPE_SHA256; + else if (!strcmp(keyword, "md5-cert")) + key_list_fptype = SSH_FPTYPE_MD5_CERT; + else if (!strcmp(keyword, "sha256-cert")) + key_list_fptype = SSH_FPTYPE_SHA256_CERT; else { fprintf(stderr, "pageant: unknown fingerprint type `%s'\n", keyword); diff --git a/windows/pageant.c b/windows/pageant.c index 507e5c96..60040310 100644 --- a/windows/pageant.c +++ b/windows/pageant.c @@ -600,6 +600,8 @@ static INT_PTR CALLBACK KeyListProc(HWND hwnd, UINT msg, } fptypes[] = { {"SHA256", SSH_FPTYPE_SHA256}, {"MD5", SSH_FPTYPE_MD5}, + {"SHA256 including certificate", SSH_FPTYPE_SHA256_CERT}, + {"MD5 including certificate", SSH_FPTYPE_MD5_CERT}, }; switch (msg) { diff --git a/windows/pageant.rc b/windows/pageant.rc index 57fdbcf4..1bea78b4 100644 --- a/windows/pageant.rc +++ b/windows/pageant.rc @@ -60,7 +60,7 @@ BEGIN PUSHBUTTON "&Help", IDC_KEYLIST_HELP, 10, 212, 50, 14 DEFPUSHBUTTON "&Close", IDOK, 390, 212, 50, 14 LTEXT "&Fingerprint type:", IDC_KEYLIST_FPTYPE_STATIC, 10, 172, 60, 8 - COMBOBOX IDC_KEYLIST_FPTYPE, 70, 170, 60, 12, CBS_DROPDOWNLIST + COMBOBOX IDC_KEYLIST_FPTYPE, 70, 170, 100, 12, CBS_DROPDOWNLIST END /* Accelerators used: cl */