mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-01-09 09:27:59 +00:00
Introduce OpenSSH-compatible SHA256 key fingerprinting.
There's a new enumeration of fingerprint types, and you tell ssh2_fingerprint() or ssh2_fingerprint_blob() which of them to use. So far, this is only implemented behind the scenes, and exposed for testcrypt to test. All the call sites of ssh2_fingerprint pass a fixed default fptype, which is still set to the old MD5. That will change shortly.
This commit is contained in:
parent
0bc78dea68
commit
1da353e649
4
cmdgen.c
4
cmdgen.c
@ -1182,11 +1182,11 @@ int main(int argc, char **argv)
|
||||
fingerprint = rsa_ssh1_fingerprint(ssh1key);
|
||||
} else {
|
||||
if (ssh2key) {
|
||||
fingerprint = ssh2_fingerprint(ssh2key->key);
|
||||
fingerprint = ssh2_fingerprint(ssh2key->key, SSH_FPTYPE_DEFAULT);
|
||||
} else {
|
||||
assert(ssh2blob);
|
||||
fingerprint = ssh2_fingerprint_blob(
|
||||
ptrlen_from_strbuf(ssh2blob));
|
||||
ptrlen_from_strbuf(ssh2blob), SSH_FPTYPE_DEFAULT);
|
||||
}
|
||||
}
|
||||
|
||||
|
22
pageant.c
22
pageant.c
@ -703,7 +703,8 @@ static PageantAsyncOp *pageant_make_op(
|
||||
int i;
|
||||
ssh2_userkey *skey;
|
||||
for (i = 0; NULL != (skey = pageant_nth_ssh2_key(i)); i++) {
|
||||
char *fingerprint = ssh2_fingerprint(skey->key);
|
||||
char *fingerprint = ssh2_fingerprint(
|
||||
skey->key, SSH_FPTYPE_DEFAULT);
|
||||
pageant_client_log(pc, reqid, "returned key: %s %s",
|
||||
fingerprint, skey->comment);
|
||||
sfree(fingerprint);
|
||||
@ -812,7 +813,8 @@ static PageantAsyncOp *pageant_make_op(
|
||||
have_flags = true;
|
||||
|
||||
if (!pc->suppress_logging) {
|
||||
char *fingerprint = ssh2_fingerprint_blob(keyblob);
|
||||
char *fingerprint = ssh2_fingerprint_blob(
|
||||
keyblob, SSH_FPTYPE_DEFAULT);
|
||||
pageant_client_log(pc, reqid, "requested key: %s", fingerprint);
|
||||
sfree(fingerprint);
|
||||
}
|
||||
@ -927,7 +929,7 @@ static PageantAsyncOp *pageant_make_op(
|
||||
}
|
||||
|
||||
if (!pc->suppress_logging) {
|
||||
char *fingerprint = ssh2_fingerprint(key->key);
|
||||
char *fingerprint = ssh2_fingerprint(key->key, SSH_FPTYPE_DEFAULT);
|
||||
pageant_client_log(pc, reqid, "submitted key: %s %s",
|
||||
fingerprint, key->comment);
|
||||
sfree(fingerprint);
|
||||
@ -1019,7 +1021,8 @@ static PageantAsyncOp *pageant_make_op(
|
||||
}
|
||||
|
||||
if (!pc->suppress_logging) {
|
||||
char *fingerprint = ssh2_fingerprint_blob(blob);
|
||||
char *fingerprint = ssh2_fingerprint_blob(
|
||||
blob, SSH_FPTYPE_DEFAULT);
|
||||
pageant_client_log(pc, reqid, "unwanted key: %s", fingerprint);
|
||||
sfree(fingerprint);
|
||||
}
|
||||
@ -1132,7 +1135,7 @@ static PageantAsyncOp *pageant_make_op(
|
||||
|
||||
if (!pc->suppress_logging) {
|
||||
char *fingerprint = ssh2_fingerprint_blob(
|
||||
ptrlen_from_strbuf(public_blob));
|
||||
ptrlen_from_strbuf(public_blob), SSH_FPTYPE_DEFAULT);
|
||||
pageant_client_log(pc, reqid, "add-ppk: %s %s",
|
||||
fingerprint, comment);
|
||||
sfree(fingerprint);
|
||||
@ -1234,7 +1237,8 @@ static PageantAsyncOp *pageant_make_op(
|
||||
}
|
||||
|
||||
if (!pc->suppress_logging) {
|
||||
char *fingerprint = ssh2_fingerprint_blob(blob);
|
||||
char *fingerprint = ssh2_fingerprint_blob(
|
||||
blob, SSH_FPTYPE_DEFAULT);
|
||||
pageant_client_log(pc, reqid, "key to re-encrypt: %s",
|
||||
fingerprint);
|
||||
sfree(fingerprint);
|
||||
@ -1316,7 +1320,8 @@ static PageantAsyncOp *pageant_make_op(
|
||||
int i;
|
||||
ssh2_userkey *skey;
|
||||
for (i = 0; NULL != (skey = pageant_nth_ssh2_key(i)); i++) {
|
||||
char *fingerprint = ssh2_fingerprint(skey->key);
|
||||
char *fingerprint = ssh2_fingerprint(
|
||||
skey->key, SSH_FPTYPE_DEFAULT);
|
||||
pageant_client_log(pc, reqid, "returned key: %s %s",
|
||||
fingerprint, skey->comment);
|
||||
sfree(fingerprint);
|
||||
@ -2224,7 +2229,8 @@ int pageant_enum_keys(pageant_key_enum_fn_t callback, void *callback_ctx,
|
||||
cbkey.comment = mkstr(kl2->keys[i].comment);
|
||||
cbkey.ssh_version = 2;
|
||||
|
||||
char *fingerprint = ssh2_fingerprint_blob(kl2->keys[i].blob);
|
||||
char *fingerprint = ssh2_fingerprint_blob(kl2->keys[i].blob,
|
||||
SSH_FPTYPE_DEFAULT);
|
||||
|
||||
callback(callback_ctx, fingerprint, cbkey.comment,
|
||||
kl2->keys[i].flags, &cbkey);
|
||||
|
13
ssh.h
13
ssh.h
@ -1328,14 +1328,23 @@ enum {
|
||||
SSH_KEYTYPE_SSH2_PUBLIC_RFC4716,
|
||||
SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
SSH_FPTYPE_MD5,
|
||||
SSH_FPTYPE_SHA256,
|
||||
} FingerprintType;
|
||||
|
||||
#define SSH_FPTYPE_DEFAULT SSH_FPTYPE_MD5
|
||||
#define SSH_N_FPTYPES (SSH_FPTYPE_SHA256 + 1)
|
||||
|
||||
char *ssh1_pubkey_str(RSAKey *ssh1key);
|
||||
void ssh1_write_pubkey(FILE *fp, RSAKey *ssh1key);
|
||||
char *ssh2_pubkey_openssh_str(ssh2_userkey *key);
|
||||
void ssh2_write_pubkey(FILE *fp, const char *comment,
|
||||
const void *v_pub_blob, int pub_len,
|
||||
int keytype);
|
||||
char *ssh2_fingerprint_blob(ptrlen);
|
||||
char *ssh2_fingerprint(ssh_key *key);
|
||||
char *ssh2_fingerprint_blob(ptrlen, FingerprintType);
|
||||
char *ssh2_fingerprint(ssh_key *key, FingerprintType);
|
||||
int key_type(const Filename *filename);
|
||||
int key_type_s(BinarySource *src);
|
||||
const char *key_type_to_str(int type);
|
||||
|
@ -721,7 +721,7 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
|
||||
* host key, store it.
|
||||
*/
|
||||
if (s->hkey) {
|
||||
s->fingerprint = ssh2_fingerprint(s->hkey);
|
||||
s->fingerprint = ssh2_fingerprint(s->hkey, SSH_FPTYPE_DEFAULT);
|
||||
ppl_logevent("GSS kex provided fallback host key:");
|
||||
ppl_logevent("%s", s->fingerprint);
|
||||
sfree(s->fingerprint);
|
||||
@ -779,7 +779,7 @@ 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 */
|
||||
s->fingerprint = ssh2_fingerprint(s->hkey);
|
||||
s->fingerprint = ssh2_fingerprint(s->hkey, SSH_FPTYPE_DEFAULT);
|
||||
|
||||
if (s->need_gss_transient_hostkey) {
|
||||
ppl_logevent("Post-GSS rekey provided fallback host key:");
|
||||
@ -843,7 +843,7 @@ 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.)
|
||||
*/
|
||||
s->fingerprint = ssh2_fingerprint(s->hkey);
|
||||
s->fingerprint = ssh2_fingerprint(s->hkey, SSH_FPTYPE_DEFAULT);
|
||||
ppl_logevent("Host key fingerprint is:");
|
||||
ppl_logevent("%s", s->fingerprint);
|
||||
/* First check against manually configured host keys. */
|
||||
@ -882,7 +882,7 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
|
||||
assert(s->hkey);
|
||||
assert(ssh_key_alg(s->hkey) == s->cross_certifying);
|
||||
|
||||
s->fingerprint = ssh2_fingerprint(s->hkey);
|
||||
s->fingerprint = ssh2_fingerprint(s->hkey, SSH_FPTYPE_DEFAULT);
|
||||
ppl_logevent("Storing additional host key for this host:");
|
||||
ppl_logevent("%s", s->fingerprint);
|
||||
sfree(s->fingerprint);
|
||||
|
72
sshpubk.c
72
sshpubk.c
@ -1732,51 +1732,73 @@ void ssh2_write_pubkey(FILE *fp, const char *comment,
|
||||
/* ----------------------------------------------------------------------
|
||||
* Utility functions to compute SSH-2 fingerprints in a uniform way.
|
||||
*/
|
||||
char *ssh2_fingerprint_blob(ptrlen blob)
|
||||
static void ssh2_fingerprint_blob_md5(ptrlen blob, strbuf *sb)
|
||||
{
|
||||
unsigned char digest[16];
|
||||
char fingerprint_str[16*3];
|
||||
ptrlen algname;
|
||||
const ssh_keyalg *alg;
|
||||
int i;
|
||||
BinarySource src[1];
|
||||
|
||||
/*
|
||||
* The fingerprint hash itself is always just the MD5 of the blob.
|
||||
*/
|
||||
hash_simple(&ssh_md5, blob, digest);
|
||||
for (i = 0; i < 16; i++)
|
||||
sprintf(fingerprint_str + i*3, "%02x%s", digest[i], i==15 ? "" : ":");
|
||||
for (unsigned i = 0; i < 16; i++)
|
||||
strbuf_catf(sb, "%02x%s", digest[i], i==15 ? "" : ":");
|
||||
}
|
||||
|
||||
static void ssh2_fingerprint_blob_sha256(ptrlen blob, strbuf *sb)
|
||||
{
|
||||
unsigned char digest[32];
|
||||
hash_simple(&ssh_sha256, blob, digest);
|
||||
|
||||
put_datapl(sb, PTRLEN_LITERAL("SHA256:"));
|
||||
|
||||
for (unsigned i = 0; i < 32; i += 3) {
|
||||
char buf[5];
|
||||
unsigned len = 32-i;
|
||||
if (len > 3)
|
||||
len = 3;
|
||||
base64_encode_atom(digest + i, len, buf);
|
||||
put_data(sb, buf, 4);
|
||||
}
|
||||
strbuf_chomp(sb, '=');
|
||||
}
|
||||
|
||||
char *ssh2_fingerprint_blob(ptrlen blob, FingerprintType fptype)
|
||||
{
|
||||
strbuf *sb = strbuf_new();
|
||||
|
||||
/*
|
||||
* Identify the key algorithm, if possible.
|
||||
*
|
||||
* If we can't do that, then we have a seriously confused key
|
||||
* blob, in which case we return only the hash.
|
||||
*/
|
||||
BinarySource src[1];
|
||||
BinarySource_BARE_INIT_PL(src, blob);
|
||||
algname = get_string(src);
|
||||
ptrlen algname = get_string(src);
|
||||
if (!get_err(src)) {
|
||||
alg = find_pubkey_alg_len(algname);
|
||||
const ssh_keyalg *alg = find_pubkey_alg_len(algname);
|
||||
if (alg) {
|
||||
int bits = ssh_key_public_bits(alg, blob);
|
||||
return dupprintf("%.*s %d %s", PTRLEN_PRINTF(algname),
|
||||
bits, fingerprint_str);
|
||||
strbuf_catf(sb, "%.*s %d ", PTRLEN_PRINTF(algname), bits);
|
||||
} else {
|
||||
return dupprintf("%.*s %s", PTRLEN_PRINTF(algname),
|
||||
fingerprint_str);
|
||||
strbuf_catf(sb, "%.*s ", PTRLEN_PRINTF(algname));
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* No algorithm available (which means a seriously confused
|
||||
* key blob, but there we go). Return only the hash.
|
||||
*/
|
||||
return dupstr(fingerprint_str);
|
||||
}
|
||||
|
||||
switch (fptype) {
|
||||
case SSH_FPTYPE_MD5:
|
||||
ssh2_fingerprint_blob_md5(blob, sb);
|
||||
break;
|
||||
case SSH_FPTYPE_SHA256:
|
||||
ssh2_fingerprint_blob_sha256(blob, sb);
|
||||
break;
|
||||
}
|
||||
|
||||
return strbuf_to_str(sb);
|
||||
}
|
||||
|
||||
char *ssh2_fingerprint(ssh_key *data)
|
||||
char *ssh2_fingerprint(ssh_key *data, FingerprintType fptype)
|
||||
{
|
||||
strbuf *blob = strbuf_new();
|
||||
ssh_key_public_blob(data, BinarySink_UPCAST(blob));
|
||||
char *ret = ssh2_fingerprint_blob(ptrlen_from_strbuf(blob));
|
||||
char *ret = ssh2_fingerprint_blob(ptrlen_from_strbuf(blob), fptype);
|
||||
strbuf_free(blob);
|
||||
return ret;
|
||||
}
|
||||
|
@ -1146,6 +1146,36 @@ class crypt(MyTestBase):
|
||||
self.assertEqual(
|
||||
fp, b"768 96:12:c8:bc:e6:03:75:86:e8:c7:b9:af:d8:0c:15:75")
|
||||
|
||||
def testSSH2Fingerprints(self):
|
||||
# A sensible key blob that we can make sense of.
|
||||
sensible_blob = base64.decodebytes(
|
||||
b'AAAAC3NzaC1lZDI1NTE5AAAAICWiV0VAD4lQ7taUN7vZ5Rkc'
|
||||
b'SLJBW5ubn6ZINwCOzpn3')
|
||||
self.assertEqual(ssh2_fingerprint_blob(sensible_blob, "sha256"),
|
||||
b'ssh-ed25519 255 SHA256:'
|
||||
b'E4VmaHW0sUF7SUgSEOmMJ8WBtt0e/j3zbsKvyqfFnu4')
|
||||
self.assertEqual(ssh2_fingerprint_blob(sensible_blob, "md5"),
|
||||
b'ssh-ed25519 255 '
|
||||
b'35:73:80:df:a3:2c:1a:f2:2c:a6:5c:84:ce:48:6a:7e')
|
||||
|
||||
# A key blob with an unknown algorithm name, so that we can't
|
||||
# extract the bit count.
|
||||
silly_blob = ssh_string(b'foo') + ssh_string(b'key data')
|
||||
self.assertEqual(ssh2_fingerprint_blob(silly_blob, "sha256"),
|
||||
b'foo SHA256:'
|
||||
b'mvfJTB4PaRI7hxYaYwn0sH8G6zW1HbLkbWnZE2YIKc4')
|
||||
self.assertEqual(ssh2_fingerprint_blob(silly_blob, "md5"),
|
||||
b'foo '
|
||||
b'5f:5f:97:94:97:be:01:5c:f6:3f:e3:6e:55:46:ea:52')
|
||||
|
||||
# A key blob without even a valid algorithm-name string at the start.
|
||||
very_silly_blob = b'foo'
|
||||
self.assertEqual(ssh2_fingerprint_blob(very_silly_blob, "sha256"),
|
||||
b'SHA256:'
|
||||
b'LCa0a2j/xo/5m0U8HTBBNBNCLXBkg7+g+YpeiGJm564')
|
||||
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')
|
||||
|
||||
def testAES(self):
|
||||
# My own test cases, generated by a mostly independent
|
||||
# reference implementation of AES in Python. ('Mostly'
|
||||
|
@ -178,7 +178,7 @@ def make_argword(arg, argtype, fnname, argindex, to_preserve):
|
||||
if typename in {
|
||||
"hashalg", "macalg", "keyalg", "cipheralg",
|
||||
"dh_group", "ecdh_alg", "rsaorder", "primegenpolicy",
|
||||
"argon2flavour"}:
|
||||
"argon2flavour", "fptype"}:
|
||||
arg = coerce_to_bytes(arg)
|
||||
if isinstance(arg, bytes) and b" " not in arg:
|
||||
return arg
|
||||
|
19
testcrypt.c
19
testcrypt.c
@ -433,6 +433,24 @@ static Argon2Flavour get_argon2flavour(BinarySource *in)
|
||||
fatal_error("Argon2 flavour '%.*s': not found", PTRLEN_PRINTF(name));
|
||||
}
|
||||
|
||||
static FingerprintType get_fptype(BinarySource *in)
|
||||
{
|
||||
static const struct {
|
||||
const char *key;
|
||||
FingerprintType value;
|
||||
} ids[] = {
|
||||
{"md5", SSH_FPTYPE_MD5},
|
||||
{"sha256", SSH_FPTYPE_SHA256},
|
||||
};
|
||||
|
||||
ptrlen name = get_word(in);
|
||||
for (size_t i = 0; i < lenof(ids); i++)
|
||||
if (ptrlen_eq_string(name, ids[i].key))
|
||||
return ids[i].value;
|
||||
|
||||
fatal_error("fingerprint type '%.*s': not found", PTRLEN_PRINTF(name));
|
||||
}
|
||||
|
||||
static uintmax_t get_uint(BinarySource *in)
|
||||
{
|
||||
ptrlen word = get_word(in);
|
||||
@ -1310,6 +1328,7 @@ typedef const PrimeGenerationPolicy *TD_primegenpolicy;
|
||||
typedef struct mpint_list TD_mpint_list;
|
||||
typedef PockleStatus TD_pocklestatus;
|
||||
typedef Argon2Flavour TD_argon2flavour;
|
||||
typedef FingerprintType TD_fptype;
|
||||
|
||||
#define FUNC0(rettype, function) \
|
||||
static void handle_##function(BinarySource *in, strbuf *out) { \
|
||||
|
@ -261,6 +261,8 @@ FUNC5(int, rsa1_load_s, val_string_binarysource, val_rsa, out_opt_val_string_asc
|
||||
FUNC8(val_string, ppk_save_sb, val_key, opt_val_string_asciz, opt_val_string_asciz, uint, argon2flavour, uint, uint, uint)
|
||||
FUNC3(val_string, rsa1_save_sb, val_rsa, opt_val_string_asciz, opt_val_string_asciz)
|
||||
|
||||
FUNC2(val_string_asciz, ssh2_fingerprint_blob, val_string_ptrlen, fptype)
|
||||
|
||||
/*
|
||||
* Password hashing.
|
||||
*/
|
||||
|
@ -1023,7 +1023,7 @@ void load_key_file(HWND hwnd, struct MainDlgState *state,
|
||||
|
||||
savecomment = state->ssh2key.comment;
|
||||
state->ssh2key.comment = NULL;
|
||||
fp = ssh2_fingerprint(state->ssh2key.key);
|
||||
fp = ssh2_fingerprint(state->ssh2key.key, SSH_FPTYPE_DEFAULT);
|
||||
state->ssh2key.comment = savecomment;
|
||||
|
||||
SetDlgItemText(hwnd, IDC_FINGERPRINT, fp);
|
||||
@ -1796,7 +1796,7 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg,
|
||||
savecomment = *state->commentptr;
|
||||
*state->commentptr = NULL;
|
||||
if (state->ssh2)
|
||||
fp = ssh2_fingerprint(state->ssh2key.key);
|
||||
fp = ssh2_fingerprint(state->ssh2key.key, SSH_FPTYPE_DEFAULT);
|
||||
else
|
||||
fp = rsa_ssh1_fingerprint(&state->key);
|
||||
SetDlgItemText(hwnd, IDC_FINGERPRINT, fp);
|
||||
|
@ -353,7 +353,7 @@ void keylist_update(void)
|
||||
* stop and leave out a tab character. Urgh.
|
||||
*/
|
||||
|
||||
p = ssh2_fingerprint(skey->key);
|
||||
p = ssh2_fingerprint(skey->key, SSH_FPTYPE_DEFAULT);
|
||||
listentry = dupprintf("%s\t%s", p, skey->comment);
|
||||
sfree(p);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user