1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-10 01:48:00 +00:00

Allow pre-storing host key fingerprints of all types.

verify_ssh_manual_host_key() now takes an array of all key
fingerprints instead of just the default type, which means that an
expected key fingerprint stored in the session configuration can now
be matched against any of them.
This commit is contained in:
Simon Tatham 2021-03-13 10:53:53 +00:00
parent 46b23c581a
commit 04758cb3ec
5 changed files with 58 additions and 42 deletions

33
misc.c
View File

@ -22,6 +22,10 @@
#include "putty.h" #include "putty.h"
#include "misc.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, ...) void seat_connection_fatal(Seat *seat, const char *fmt, ...)
{ {
va_list ap; va_list ap;
@ -129,22 +133,32 @@ bool validate_manual_hostkey(char *key)
* Now q is our word. * Now q is our word.
*/ */
if (strlen(q) == 16*3 - 1 && if (strstartswith(q, "SHA256:")) {
q[strspn(q, "0123456789abcdefABCDEF:")] == 0) { /* 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 * Test for a valid MD5 key fingerprint. Check the colons
* right places, and if so, return the same fingerprint * are in the right places, and if so, return the same
* canonicalised into lowercase. * fingerprint canonicalised into lowercase.
*/ */
int i; int i;
for (i = 0; i < 16; 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 */ goto not_fingerprint; /* sorry */
for (i = 0; i < 15; i++) for (i = 0; i < 15; i++)
if (q[3*i+2] != ':') if (r[3*i+2] != ':')
goto not_fingerprint; /* sorry */ goto not_fingerprint; /* sorry */
for (i = 0; i < 16*3 - 1; i++) for (i = 0; i < 16*3 - 1; i++)
key[i] = tolower(q[i]); key[i] = tolower(r[i]);
key[16*3 - 1] = '\0'; key[16*3 - 1] = '\0';
return true; return true;
} }
@ -161,8 +175,7 @@ bool validate_manual_hostkey(char *key)
*s = '\0'; *s = '\0';
if (strlen(q) % 4 == 0 && strlen(q) > 2*4 && if (strlen(q) % 4 == 0 && strlen(q) > 2*4 &&
q[strspn(q, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" q[strspn(q, BASE64_CHARS_ALL)] == 0) {
"abcdefghijklmnopqrstuvwxyz+/=")] == 0) {
/* /*
* Might be a base64-encoded SSH-2 public key blob. Check * Might be a base64-encoded SSH-2 public key blob. Check
* that it starts with a sensible algorithm string. No * that it starts with a sensible algorithm string. No

3
ssh.h
View File

@ -1686,8 +1686,7 @@ unsigned alloc_channel_id_general(tree234 *channels, size_t localid_offset);
void add_to_commasep(strbuf *buf, const char *data); void add_to_commasep(strbuf *buf, const char *data);
bool get_commasep_word(ptrlen *list, ptrlen *word); bool get_commasep_word(ptrlen *list, ptrlen *word);
int verify_ssh_manual_host_key( int verify_ssh_manual_host_key(Conf *conf, char **fingerprints, ssh_key *key);
Conf *conf, const char *fingerprint, ssh_key *key);
typedef struct ssh_transient_hostkey_cache ssh_transient_hostkey_cache; typedef struct ssh_transient_hostkey_cache ssh_transient_hostkey_cache;
ssh_transient_hostkey_cache *ssh_transient_hostkey_cache_new(void); ssh_transient_hostkey_cache *ssh_transient_hostkey_cache_new(void);

View File

@ -244,14 +244,13 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl)
/* /*
* First format the key into a string. * First format the key into a string.
*/ */
char *fingerprint;
char *keystr = rsastr_fmt(&s->hostkey); 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. */ /* 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 */ if (s->dlgret == 0) { /* did not match */
sfree(fingerprint); ssh2_free_all_fingerprints(fingerprints);
sfree(keystr); sfree(keystr);
ssh_proto_error(s->ppl.ssh, "Host key did not appear in manually " ssh_proto_error(s->ppl.ssh, "Host key did not appear in manually "
"configured list"); "configured list");
@ -259,8 +258,9 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl)
} else if (s->dlgret < 0) { /* none configured; use standard handling */ } else if (s->dlgret < 0) { /* none configured; use standard handling */
s->dlgret = seat_verify_ssh_host_key( s->dlgret = seat_verify_ssh_host_key(
s->ppl.seat, s->savedhost, s->savedport, s->ppl.seat, s->savedhost, s->savedport,
"rsa", keystr, fingerprint, ssh1_login_dialog_callback, s); "rsa", keystr, fingerprints[SSH_FPTYPE_DEFAULT],
sfree(fingerprint); ssh1_login_dialog_callback, s);
ssh2_free_all_fingerprints(fingerprints);
sfree(keystr); sfree(keystr);
#ifdef FUZZING #ifdef FUZZING
s->dlgret = 1; s->dlgret = 1;
@ -273,7 +273,7 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl)
return; return;
} }
} else { } else {
sfree(fingerprint); ssh2_free_all_fingerprints(fingerprints);
sfree(keystr); sfree(keystr);
} }
} }

View File

@ -843,13 +843,16 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
* Authenticate remote host: verify host key. (We've already * Authenticate remote host: verify host key. (We've already
* checked the signature of the exchange hash.) * 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("Host key fingerprint is:");
ppl_logevent("%s", fingerprint); ppl_logevent("%s", fingerprints[fptype_default]);
/* First check against manually configured host keys. */ /* First check against manually configured host keys. */
s->dlgret = verify_ssh_manual_host_key( s->dlgret = verify_ssh_manual_host_key(
s->conf, fingerprint, s->hkey); s->conf, fingerprints, s->hkey);
if (s->dlgret == 0) { /* did not match */ 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 " ssh_sw_abort(s->ppl.ssh, "Host key did not appear in manually "
"configured list"); "configured list");
*aborted = true; *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 */ } else if (s->dlgret < 0) { /* none configured; use standard handling */
s->dlgret = seat_verify_ssh_host_key( s->dlgret = seat_verify_ssh_host_key(
s->ppl.seat, s->savedhost, s->savedport, 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); ssh2_transport_dialog_callback, s);
sfree(fingerprint); ssh2_free_all_fingerprints(fingerprints);
#ifdef FUZZING #ifdef FUZZING
s->dlgret = 1; s->dlgret = 1;
#endif #endif

View File

@ -826,29 +826,29 @@ bool ssh2_bpp_check_unimplemented(BinaryPacketProtocol *bpp, PktIn *pktin)
* Function to check a host key against any manually configured in Conf. * Function to check a host key against any manually configured in Conf.
*/ */
int verify_ssh_manual_host_key( int verify_ssh_manual_host_key(Conf *conf, char **fingerprints, ssh_key *key)
Conf *conf, const char *fingerprint, ssh_key *key)
{ {
if (!conf_get_str_nthstrkey(conf, CONF_ssh_manual_hostkeys, 0)) if (!conf_get_str_nthstrkey(conf, CONF_ssh_manual_hostkeys, 0))
return -1; /* no manual keys configured */ return -1; /* no manual keys configured */
if (fingerprint) { if (fingerprints) {
for (size_t i = 0; i < SSH_N_FPTYPES; i++) {
/* /*
* The fingerprint string we've been given will have things * Each fingerprint string we've been given will have
* like 'ssh-rsa 2048' at the front of it. Strip those off and * things like 'ssh-rsa 2048' at the front of it. Strip
* narrow down to just the colon-separated hex block at the * those off and narrow down to just the hash at the end
* end of the string. * of the string.
*/ */
const char *fingerprint = fingerprints[i];
if (!fingerprint)
continue;
const char *p = strrchr(fingerprint, ' '); const char *p = strrchr(fingerprint, ' ');
fingerprint = p ? p+1 : fingerprint; fingerprint = p ? p+1 : fingerprint;
/* Quick sanity checks, including making sure it's in lowercase */ if (conf_get_str_str_opt(conf, CONF_ssh_manual_hostkeys,
assert(strlen(fingerprint) == 16*3 - 1); fingerprint))
assert(fingerprint[2] == ':');
assert(fingerprint[strspn(fingerprint, "0123456789abcdef:")] == 0);
if (conf_get_str_str_opt(conf, CONF_ssh_manual_hostkeys, fingerprint))
return 1; /* success */ return 1; /* success */
} }
}
if (key) { if (key) {
/* /*