diff --git a/ssh.h b/ssh.h index 45675ce8..99c51da1 100644 --- a/ssh.h +++ b/ssh.h @@ -1235,6 +1235,11 @@ int rsa1_load_f(const Filename *filename, RSAKey *key, const char *passphrase, const char **errorstr); typedef struct ppk_save_parameters { + unsigned fmt_version; /* currently 2 or 3 */ + + /* + * Parameters for fmt_version == 3 + */ Argon2Flavour argon2_flavour; uint32_t argon2_mem; /* in Kb */ bool argon2_passes_auto; diff --git a/sshpubk.c b/sshpubk.c index fc840f44..d5f5c70d 100644 --- a/sshpubk.c +++ b/sshpubk.c @@ -1410,6 +1410,8 @@ void base64_encode(FILE *fp, const unsigned char *data, int datalen, int cpl) } const ppk_save_parameters ppk_save_default_parameters = { + .fmt_version = 3, + /* * The Argon2 spec recommends the hybrid variant Argon2id, where * you don't have a good reason to go with the pure Argon2d or @@ -1505,19 +1507,25 @@ strbuf *ppk_save_sb(ssh2_userkey *key, const char *passphrase, * copy for us to retrieve. */ ppk_save_parameters params = *params_orig; - /* Invent a salt for the password hash. */ strbuf *passphrase_salt = strbuf_new(); - if (params.salt) - put_data(passphrase_salt, params.salt, params.saltlen); - else - random_read(strbuf_append(passphrase_salt, 16), 16); + + if (params.fmt_version == 3) { + /* Invent a salt for the password hash. */ + if (params.salt) + put_data(passphrase_salt, params.salt, params.saltlen); + else + random_read(strbuf_append(passphrase_salt, 16), 16); + } cipher_mac_keys_blob = strbuf_new(); - ssh2_ppk_derive_keys(3, ciphertype, + ssh2_ppk_derive_keys(params.fmt_version, ciphertype, ptrlen_from_asciz(passphrase ? passphrase : ""), cipher_mac_keys_blob, &cipherkey, &cipheriv, &mackey, ptrlen_from_strbuf(passphrase_salt), ¶ms); + const ssh2_macalg *macalg = (params.fmt_version == 2 ? + &ssh_hmac_sha1 : &ssh_hmac_sha256); + /* Now create the MAC. */ { strbuf *macdata; @@ -1529,8 +1537,7 @@ strbuf *ppk_save_sb(ssh2_userkey *key, const char *passphrase, put_string(macdata, pub_blob->s, pub_blob->len); put_string(macdata, priv_blob_encrypted, priv_encrypted_len); - mac_simple(&ssh_hmac_sha256, mackey, - ptrlen_from_strbuf(macdata), priv_mac); + mac_simple(macalg, mackey, ptrlen_from_strbuf(macdata), priv_mac); strbuf_free(macdata); } @@ -1541,12 +1548,13 @@ strbuf *ppk_save_sb(ssh2_userkey *key, const char *passphrase, } strbuf *out = strbuf_new_nm(); - strbuf_catf(out, "PuTTY-User-Key-File-3: %s\n", ssh_key_ssh_id(key->key)); + strbuf_catf(out, "PuTTY-User-Key-File-%u: %s\n", + params.fmt_version, ssh_key_ssh_id(key->key)); strbuf_catf(out, "Encryption: %s\n", cipherstr); strbuf_catf(out, "Comment: %s\n", key->comment); strbuf_catf(out, "Public-Lines: %d\n", base64_lines(pub_blob->len)); base64_encode_s(BinarySink_UPCAST(out), pub_blob->u, pub_blob->len, 64); - if (ciphertype->keylen != 0) { + if (params.fmt_version == 3 && ciphertype->keylen != 0) { strbuf_catf(out, "Key-Derivation: %s\n", params.argon2_flavour == Argon2d ? "Argon2d" : params.argon2_flavour == Argon2i ? "Argon2i" : "Argon2id"); @@ -1564,7 +1572,7 @@ strbuf *ppk_save_sb(ssh2_userkey *key, const char *passphrase, base64_encode_s(BinarySink_UPCAST(out), priv_blob_encrypted, priv_encrypted_len, 64); strbuf_catf(out, "Private-MAC: "); - for (i = 0; i < 32; i++) + for (i = 0; i < macalg->len; i++) strbuf_catf(out, "%02x", priv_mac[i]); strbuf_catf(out, "\n"); diff --git a/test/cryptsuite.py b/test/cryptsuite.py index 09ee5e8d..3aaf4a04 100755 --- a/test/cryptsuite.py +++ b/test/cryptsuite.py @@ -2163,17 +2163,21 @@ Private-MAC: 6f5e588e475e55434106ec2c3569695b03f423228b44993a9e97d52ffe7be5a8 salt = unhex('37c3911bfefc8c1d11ec579627d2b3d9') with queued_specific_random_data(salt): - self.assertEqual(ppk_save_sb(k1, comment, None, 'id', 8192, 13, 1), + self.assertEqual(ppk_save_sb(k1, comment, None, + 3, 'id', 8192, 13, 1), input_clear_key) with queued_specific_random_data(salt): - self.assertEqual(ppk_save_sb(k2, comment, None, 'id', 8192, 13, 1), + self.assertEqual(ppk_save_sb(k2, comment, None, + 3, 'id', 8192, 13, 1), input_clear_key) with queued_specific_random_data(salt): - self.assertEqual(ppk_save_sb(k1, comment, pp, 'id', 8192, 13, 1), + self.assertEqual(ppk_save_sb(k1, comment, pp, + 3, 'id', 8192, 13, 1), input_encrypted_key) with queued_specific_random_data(salt): - self.assertEqual(ppk_save_sb(k2, comment, pp, 'id', 8192, 13, 1), + self.assertEqual(ppk_save_sb(k2, comment, pp, + 3, 'id', 8192, 13, 1), input_encrypted_key) # And check we can still handle v2 key files. @@ -2219,6 +2223,13 @@ Private-MAC: 5b1f6f4cc43eb0060d2c3e181bc0129343adba2b self.assertEqual(ssh_key_private_blob(k1), privblob) self.assertEqual(ssh_key_private_blob(k2), privblob) + self.assertEqual(ppk_save_sb(k2, comment, None, + 2, 'id', 8192, 13, 1), + v2_clear_key) + self.assertEqual(ppk_save_sb(k1, comment, pp, + 2, 'id', 8192, 13, 1), + v2_encrypted_key) + def testRSA1LoadSave(self): # Stability test of SSH-1 RSA key-file load/save functions. input_clear_key = unhex( diff --git a/testcrypt.c b/testcrypt.c index bb05e1e1..6dc063ce 100644 --- a/testcrypt.c +++ b/testcrypt.c @@ -1144,9 +1144,10 @@ int rsa1_load_s_wrapper(BinarySource *src, RSAKey *rsa, char **comment, } #define rsa1_load_s rsa1_load_s_wrapper -strbuf *ppk_save_sb_wrapper(ssh_key *key, const char *comment, - const char *passphrase, Argon2Flavour flavour, - uint32_t mem, uint32_t passes, uint32_t parallel) +strbuf *ppk_save_sb_wrapper( + ssh_key *key, const char *comment, const char *passphrase, + unsigned fmt_version, Argon2Flavour flavour, + uint32_t mem, uint32_t passes, uint32_t parallel) { /* * For repeatable testing purposes, we never want a timing-dependent @@ -1154,6 +1155,7 @@ strbuf *ppk_save_sb_wrapper(ssh_key *key, const char *comment, */ ppk_save_parameters save_params; memset(&save_params, 0, sizeof(save_params)); + save_params.fmt_version = fmt_version; save_params.argon2_flavour = flavour; save_params.argon2_mem = mem; save_params.argon2_passes_auto = false; diff --git a/testcrypt.h b/testcrypt.h index 5918f179..be58d9b8 100644 --- a/testcrypt.h +++ b/testcrypt.h @@ -258,7 +258,7 @@ FUNC5(boolean, ppk_loadpub_s, val_string_binarysource, out_opt_val_string_asciz, FUNC4(int, rsa1_loadpub_s, val_string_binarysource, out_val_string_binarysink, out_opt_val_string_asciz, out_opt_val_string_asciz_const) FUNC4(opt_val_key, ppk_load_s, val_string_binarysource, out_opt_val_string_asciz, opt_val_string_asciz, out_opt_val_string_asciz_const) FUNC5(int, rsa1_load_s, val_string_binarysource, val_rsa, out_opt_val_string_asciz, opt_val_string_asciz, out_opt_val_string_asciz_const) -FUNC7(val_string, ppk_save_sb, val_key, opt_val_string_asciz, opt_val_string_asciz, argon2flavour, uint, uint, uint) +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) /*