mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-01-09 17:38:00 +00:00
Certificate-aware handling of key fingerprints.
OpenSSH, when called on to give the fingerprint of a certified public key, will in many circumstances generate the hash of the public blob of the _underlying_ key, rather than the hash of the full certificate. I think the hash of the certificate is also potentially useful (if nothing else, it provides a way to tell apart multiple certificates on the same key). But I can also see that it's useful to be able to recognise a key as the same one 'really' (since all certificates on the same key share a private key, so they're unavoidably related). So I've dealt with this by introducing an extra pair of fingerprint types, giving the cross product of {MD5, SHA-256} x {base key only, full certificate}. You can manually select which one you want to see in some circumstances (notably PuTTYgen), and in others (such as diagnostics) both fingerprints will be emitted side by side via the new functions ssh2_double_fingerprint[_blob]. The default, following OpenSSH, is to just fingerprint the base key.
This commit is contained in:
parent
e711a08daf
commit
cd7f6c4407
4
cmdgen.c
4
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);
|
||||
|
@ -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);
|
||||
|
||||
|
12
pageant.c
12
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",
|
||||
|
28
ssh.h
28
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 **);
|
||||
|
@ -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);
|
||||
|
51
sshpubk.c
51
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();
|
||||
|
@ -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'
|
||||
|
@ -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)
|
||||
|
||||
/*
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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 */
|
||||
|
Loading…
Reference in New Issue
Block a user