diff --git a/misc.c b/misc.c index ae146748..84de046d 100644 --- a/misc.c +++ b/misc.c @@ -22,6 +22,10 @@ #include "putty.h" #include "misc.h" +#define BASE64_CHARS_NOEQ \ + "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/" +#define BASE64_CHARS_ALL BASE64_CHARS_NOEQ "=" + void seat_connection_fatal(Seat *seat, const char *fmt, ...) { va_list ap; @@ -129,22 +133,32 @@ bool validate_manual_hostkey(char *key) * Now q is our word. */ - if (strlen(q) == 16*3 - 1 && - q[strspn(q, "0123456789abcdefABCDEF:")] == 0) { + if (strstartswith(q, "SHA256:")) { + /* Test for a valid SHA256 key fingerprint. */ + r = q + 7; + if (strlen(r) == 43 && r[strspn(r, BASE64_CHARS_NOEQ)] == 0) + return true; + } + + r = q; + if (strstartswith(r, "MD5:")) + r += 4; + if (strlen(r) == 16*3 - 1 && + r[strspn(r, "0123456789abcdefABCDEF:")] == 0) { /* - * Might be a key fingerprint. Check the colons are in the - * right places, and if so, return the same fingerprint - * canonicalised into lowercase. + * Test for a valid MD5 key fingerprint. Check the colons + * are in the right places, and if so, return the same + * fingerprint canonicalised into lowercase. */ int i; for (i = 0; i < 16; i++) - if (q[3*i] == ':' || q[3*i+1] == ':') + if (r[3*i] == ':' || r[3*i+1] == ':') goto not_fingerprint; /* sorry */ for (i = 0; i < 15; i++) - if (q[3*i+2] != ':') + if (r[3*i+2] != ':') goto not_fingerprint; /* sorry */ for (i = 0; i < 16*3 - 1; i++) - key[i] = tolower(q[i]); + key[i] = tolower(r[i]); key[16*3 - 1] = '\0'; return true; } @@ -161,8 +175,7 @@ bool validate_manual_hostkey(char *key) *s = '\0'; if (strlen(q) % 4 == 0 && strlen(q) > 2*4 && - q[strspn(q, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz+/=")] == 0) { + q[strspn(q, BASE64_CHARS_ALL)] == 0) { /* * Might be a base64-encoded SSH-2 public key blob. Check * that it starts with a sensible algorithm string. No diff --git a/ssh.h b/ssh.h index acffbe32..3e46b814 100644 --- a/ssh.h +++ b/ssh.h @@ -1686,8 +1686,7 @@ unsigned alloc_channel_id_general(tree234 *channels, size_t localid_offset); void add_to_commasep(strbuf *buf, const char *data); bool get_commasep_word(ptrlen *list, ptrlen *word); -int verify_ssh_manual_host_key( - Conf *conf, const char *fingerprint, ssh_key *key); +int verify_ssh_manual_host_key(Conf *conf, char **fingerprints, ssh_key *key); typedef struct ssh_transient_hostkey_cache ssh_transient_hostkey_cache; ssh_transient_hostkey_cache *ssh_transient_hostkey_cache_new(void); diff --git a/ssh1login.c b/ssh1login.c index cf191831..5556320b 100644 --- a/ssh1login.c +++ b/ssh1login.c @@ -244,14 +244,13 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl) /* * First format the key into a string. */ - char *fingerprint; char *keystr = rsastr_fmt(&s->hostkey); - fingerprint = rsa_ssh1_fingerprint(&s->hostkey); + char **fingerprints = rsa_ssh1_fake_all_fingerprints(&s->hostkey); /* First check against manually configured host keys. */ - s->dlgret = verify_ssh_manual_host_key(s->conf, fingerprint, NULL); + s->dlgret = verify_ssh_manual_host_key(s->conf, fingerprints, NULL); if (s->dlgret == 0) { /* did not match */ - sfree(fingerprint); + ssh2_free_all_fingerprints(fingerprints); sfree(keystr); ssh_proto_error(s->ppl.ssh, "Host key did not appear in manually " "configured list"); @@ -259,8 +258,9 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl) } else if (s->dlgret < 0) { /* none configured; use standard handling */ s->dlgret = seat_verify_ssh_host_key( s->ppl.seat, s->savedhost, s->savedport, - "rsa", keystr, fingerprint, ssh1_login_dialog_callback, s); - sfree(fingerprint); + "rsa", keystr, fingerprints[SSH_FPTYPE_DEFAULT], + ssh1_login_dialog_callback, s); + ssh2_free_all_fingerprints(fingerprints); sfree(keystr); #ifdef FUZZING s->dlgret = 1; @@ -273,7 +273,7 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl) return; } } else { - sfree(fingerprint); + ssh2_free_all_fingerprints(fingerprints); sfree(keystr); } } diff --git a/ssh2kex-client.c b/ssh2kex-client.c index ca1ee0c1..91452e55 100644 --- a/ssh2kex-client.c +++ b/ssh2kex-client.c @@ -843,13 +843,16 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) * Authenticate remote host: verify host key. (We've already * checked the signature of the exchange hash.) */ - char *fingerprint = ssh2_fingerprint(s->hkey, SSH_FPTYPE_DEFAULT); + char **fingerprints = ssh2_all_fingerprints(s->hkey); + FingerprintType fptype_default = + ssh2_pick_default_fingerprint(fingerprints); ppl_logevent("Host key fingerprint is:"); - ppl_logevent("%s", fingerprint); + ppl_logevent("%s", fingerprints[fptype_default]); /* First check against manually configured host keys. */ s->dlgret = verify_ssh_manual_host_key( - s->conf, fingerprint, s->hkey); + s->conf, fingerprints, s->hkey); if (s->dlgret == 0) { /* did not match */ + ssh2_free_all_fingerprints(fingerprints); ssh_sw_abort(s->ppl.ssh, "Host key did not appear in manually " "configured list"); *aborted = true; @@ -857,9 +860,10 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) } else if (s->dlgret < 0) { /* none configured; use standard handling */ s->dlgret = seat_verify_ssh_host_key( s->ppl.seat, s->savedhost, s->savedport, - ssh_key_cache_id(s->hkey), s->keystr, fingerprint, + ssh_key_cache_id(s->hkey), s->keystr, + fingerprints[SSH_FPTYPE_DEFAULT], ssh2_transport_dialog_callback, s); - sfree(fingerprint); + ssh2_free_all_fingerprints(fingerprints); #ifdef FUZZING s->dlgret = 1; #endif diff --git a/sshcommon.c b/sshcommon.c index 9cf2cf6f..5485e33a 100644 --- a/sshcommon.c +++ b/sshcommon.c @@ -826,28 +826,28 @@ bool ssh2_bpp_check_unimplemented(BinaryPacketProtocol *bpp, PktIn *pktin) * Function to check a host key against any manually configured in Conf. */ -int verify_ssh_manual_host_key( - Conf *conf, const char *fingerprint, ssh_key *key) +int verify_ssh_manual_host_key(Conf *conf, char **fingerprints, ssh_key *key) { if (!conf_get_str_nthstrkey(conf, CONF_ssh_manual_hostkeys, 0)) return -1; /* no manual keys configured */ - if (fingerprint) { - /* - * The fingerprint string we've been given will have things - * like 'ssh-rsa 2048' at the front of it. Strip those off and - * narrow down to just the colon-separated hex block at the - * end of the string. - */ - const char *p = strrchr(fingerprint, ' '); - fingerprint = p ? p+1 : fingerprint; - /* Quick sanity checks, including making sure it's in lowercase */ - assert(strlen(fingerprint) == 16*3 - 1); - assert(fingerprint[2] == ':'); - assert(fingerprint[strspn(fingerprint, "0123456789abcdef:")] == 0); - - if (conf_get_str_str_opt(conf, CONF_ssh_manual_hostkeys, fingerprint)) - return 1; /* success */ + if (fingerprints) { + for (size_t i = 0; i < SSH_N_FPTYPES; i++) { + /* + * Each fingerprint string we've been given will have + * things like 'ssh-rsa 2048' at the front of it. Strip + * those off and narrow down to just the hash at the end + * of the string. + */ + const char *fingerprint = fingerprints[i]; + if (!fingerprint) + continue; + const char *p = strrchr(fingerprint, ' '); + fingerprint = p ? p+1 : fingerprint; + if (conf_get_str_str_opt(conf, CONF_ssh_manual_hostkeys, + fingerprint)) + return 1; /* success */ + } } if (key) {