Loïc
2020-Apr-25 00:58 UTC
[PATCH 1/3] Add private key protection information extraction to ssh-keygen
Add private key protection information extraction to shh-keygen using -v option on top of -y option which is already parsing the private key. Technically, the passphrase isn't necessary to do this, but it is the most logical thing to do for me. Adding this to -l option is not appropriate because fingerprinting is using the .pub file when available. An other idea is to add a new option, I can do it if you prefer. Also, I'm laking information for information extraction from PEM and PKCS8 file format, I'm OK to have a pointer to implement this correctly. This patch is also adding a regression test for the functionnality. --- ?authfile.c??????????????????????????? |? 16 ++-- ?authfile.h??????????????????????????? |?? 7 +- ?regress/Makefile????????????????????? |?? 3 +- ?regress/keygen-private-information.sh |? 81 +++++++++++++++++++++ ?ssh-keygen.c????????????????????????? |? 44 +++++++---- ?ssh-keysign.c???????????????????????? |?? 2 +- ?sshconnect2.c???????????????????????? |?? 2 +- ?sshd.c??????????????????????????????? |?? 2 +- ?sshkey.c????????????????????????????? | 101 +++++++++++++++++++++++--- ?sshkey.h????????????????????????????? |? 14 +++- ?10 files changed, 234 insertions(+), 38 deletions(-) ?create mode 100644 regress/keygen-private-information.sh diff --git a/authfile.c b/authfile.c index 35ccf576c2b5..6c79369ebfc1 100644 --- a/authfile.c +++ b/authfile.c @@ -116,7 +116,7 @@ sshkey_perm_ok(int fd, const char *filename) ? ?int ?sshkey_load_private_type(int type, const char *filename, const char *passphrase, -??? struct sshkey **keyp, char **commentp) +??? struct sshkey **keyp, char **commentp, struct sshkey_vault **vault_infop) ?{ ??? ?int fd, r; ? @@ -124,6 +124,8 @@ sshkey_load_private_type(int type, const char *filename, const char *passphrase, ??? ??? ?*keyp = NULL; ??? ?if (commentp != NULL) ??? ??? ?*commentp = NULL; +?? ?if (vault_infop != NULL) +?? ??? ?*vault_infop = NULL; ? ??? ?if ((fd = open(filename, O_RDONLY)) == -1) ??? ??? ?return SSH_ERR_SYSTEM_ERROR; @@ -132,7 +134,7 @@ sshkey_load_private_type(int type, const char *filename, const char *passphrase, ??? ?if (r != 0) ??? ??? ?goto out; ? -?? ?r = sshkey_load_private_type_fd(fd, type, passphrase, keyp, commentp); +?? ?r = sshkey_load_private_type_fd(fd, type, passphrase, keyp, commentp, vault_infop); ??? ?if (r == 0 && keyp && *keyp) ??? ??? ?r = sshkey_set_filename(*keyp, filename); ? out: @@ -142,15 +144,15 @@ sshkey_load_private_type(int type, const char *filename, const char *passphrase, ? ?int ?sshkey_load_private(const char *filename, const char *passphrase, -??? struct sshkey **keyp, char **commentp) +??? struct sshkey **keyp, char **commentp, struct sshkey_vault **vault_infop) ?{ ??? ?return sshkey_load_private_type(KEY_UNSPEC, filename, passphrase, -?? ???? keyp, commentp); +?? ???? keyp, commentp, vault_infop); ?} ? ?int ?sshkey_load_private_type_fd(int fd, int type, const char *passphrase, -??? struct sshkey **keyp, char **commentp) +??? struct sshkey **keyp, char **commentp, struct sshkey_vault **vault_infop) ?{ ??? ?struct sshbuf *buffer = NULL; ??? ?int r; @@ -159,7 +161,7 @@ sshkey_load_private_type_fd(int fd, int type, const char *passphrase, ??? ??? ?*keyp = NULL; ??? ?if ((r = sshbuf_load_fd(fd, &buffer)) != 0 || ??? ???? (r = sshkey_parse_private_fileblob_type(buffer, type, -?? ???? passphrase, keyp, commentp)) != 0) +?? ???? passphrase, keyp, commentp, vault_infop)) != 0) ??? ??? ?goto out; ? ??? ?/* success */ @@ -334,7 +336,7 @@ sshkey_load_private_cert(int type, const char *filename, const char *passphrase, ??? ?} ? ??? ?if ((r = sshkey_load_private_type(type, filename, -?? ???? passphrase, &key, NULL)) != 0 || +?? ???? passphrase, &key, NULL, NULL)) != 0 || ??? ???? (r = sshkey_load_cert(filename, &cert)) != 0) ??? ??? ?goto out; ? diff --git a/authfile.h b/authfile.h index 1db067a813a1..85f78ac78edf 100644 --- a/authfile.h +++ b/authfile.h @@ -29,6 +29,7 @@ ? ?struct sshbuf; ?struct sshkey; +struct sshkey_vault; ? ?/* XXX document these */ ?/* XXX some of these could probably be merged/retired */ @@ -37,13 +38,13 @@ int sshkey_save_private(struct sshkey *, const char *, ???? const char *, const char *, int, const char *, int); ?int sshkey_load_cert(const char *, struct sshkey **); ?int sshkey_load_public(const char *, struct sshkey **, char **); -int sshkey_load_private(const char *, const char *, struct sshkey **, char **); +int sshkey_load_private(const char *, const char *, struct sshkey **, char **, struct sshkey_vault **); ?int sshkey_load_private_cert(int, const char *, const char *, ???? struct sshkey **); ?int sshkey_load_private_type(int, const char *, const char *, -??? struct sshkey **, char **); +??? struct sshkey **, char **, struct sshkey_vault **); ?int sshkey_load_private_type_fd(int fd, int type, const char *passphrase, -??? struct sshkey **keyp, char **commentp); +??? struct sshkey **keyp, char **commentp, struct sshkey_vault **vault_infop); ?int sshkey_perm_ok(int, const char *); ?int sshkey_in_file(struct sshkey *, const char *, int, int); ?int sshkey_check_revoked(struct sshkey *key, const char *revoked_keys_file); diff --git a/regress/Makefile b/regress/Makefile index 62794d25fc42..ae6f4dd09edc 100644 --- a/regress/Makefile +++ b/regress/Makefile @@ -92,7 +92,8 @@ LTESTS= ?? ?connect \ ??? ??? ?allow-deny-users \ ??? ??? ?authinfo \ ??? ??? ?sshsig \ -?? ??? ?keygen-comment +?? ??? ?keygen-comment \ +??????? keygen-private-information ? ? ?INTEROP_TESTS=?? ?putty-transfer putty-ciphers putty-kex conch-ciphers diff --git a/regress/keygen-private-information.sh b/regress/keygen-private-information.sh new file mode 100644 index 000000000000..a9959e919fd1 --- /dev/null +++ b/regress/keygen-private-information.sh @@ -0,0 +1,81 @@ +#??? Placed in the Public Domain. + +tid="Test information extraction from private key" + +check_private_key () { +?? ?file="$1" +?? ?format="$2" +?? ?comment="$3" +?? ?secret="$4" +?? ?rounds="$5" + +?? ?# construct expected output in $exp file +?? ?exp=$OBJ/$t-expected +?? ?# default format is RFC4716 +?? ?test -z "$format" && format="RFC4716" +?? ?# Currently PKCS8 is detected as PEM, should be fixed in ssh-keygen +?? ?test "$format" = "PKCS8" && format="PEM" +?? ?cat > $exp << EOF +$comment +Key protection details: +File format: $format +EOF +?? ?if [ -z "$secret" -o "$format" = "PEM" ]; then +?? ??? ?# For PEM format, passphrase is not detected yet, should be fixed in ssh-keygen +?? ??? ?echo "no passphrase" >> $exp +?? ?else +?? ??? ?cat >> $exp << EOF +cipher: aes256-ctr +kdf: bcrypt +rounds: $rounds +EOF +?? ?fi + +?? ?if ! ${SSHKEYGEN} -yv -P "${secret}" -f $file > $OBJ/$t-pub ; then +?? ??? ?fail "ssh-keygen -y failed for $t-key" +?? ?fi +?? ?if ! sed '1 s/[^ ]* [^ ]* \?//' $OBJ/$t-pub > $OBJ/$t-tmp ; then +?? ??? ?fail "sed failed for $t-key" +?? ?fi +?? ?if ! cmp $OBJ/$t-tmp $exp > /dev/null 2>&1; then +?? ??? ?fail "ssh-keygen -yv output is not the expected value for $t-key" +?? ??? ?diff $exp $OBJ/$t-tmp +?? ?fi +?? ?rm -f $OBJ/$t-pub $OBJ/$t-tmp $exp +} + +for fmt in '' PKCS8 PEM ; do +?? ?for secret in '' 'secret1'; do +?? ??? ?rounds_list="0" +?? ??? ?test -n "$secret" -a -z "$fmt" && rounds_list="2 16" +?? ??? ?for rounds in $rounds_list; do +?? ??? ??? ?for t in $SSH_KEYTYPES; do +?? ??? ??? ??? ?trace "generating $t key in '$fmt' format with '$secret' passphrase and '$rounds' rounds" +?? ??? ??? ??? ?rm -f $OBJ/$t-key* +?? ??? ??? ??? ?oldfmt="" +?? ??? ??? ??? ?case "$fmt" in +?? ??? ??? ??? ?PKCS8|PEM) oldfmt=1 ;; +?? ??? ??? ??? ?esac +?? ??? ??? ??? ?# Some key types like ssh-ed25519 and *@openssh.com are never +?? ??? ??? ??? ?# stored in old formats. +?? ??? ??? ??? ?case "$t" in +?? ??? ??? ??? ?ssh-ed25519|*openssh.com) test -z "$oldfmt" || continue ;; +?? ??? ??? ??? ?esac +?? ??? ??? ??? ?comment="foo bar" +?? ??? ??? ??? ?fmtarg="" +?? ??? ??? ??? ?test -z "$fmt" || fmtarg="-m $fmt" +?? ??? ??? ??? ?test "$rounds" = "0" || roundarg="-a $rounds" +?? ??? ??? ??? ?${SSHKEYGEN} $fmtarg $roundarg -N "${secret}" -C "${comment}" \ +?? ??? ??? ??? ???? -t $t -f $OBJ/$t-key >/dev/null 2>&1 || \ +?? ??? ??? ??? ??? ?fatal "keygen of $t in format $fmt failed" +?? ??? ??? ??? ?rm -f $OBJ/$t-key.pub # .pub file not used, remove it to be sure it is not used +?? ??? ??? ??? ?if [ ! -z "$oldfmt" ] ; then +?? ??? ??? ??? ??? ?# Comment cannot be recovered from old format keys. +?? ??? ??? ??? ??? ?comment="" +?? ??? ??? ??? ?fi +?? ??? ??? ??? ?check_private_key $OBJ/$t-key "${fmt}" "${comment}" "${secret}" "${rounds}" +?? ??? ??? ??? ?rm -f $OBJ/$t-key* +?? ??? ??? ?done +?? ??? ?done +?? ?done +done diff --git a/ssh-keygen.c b/ssh-keygen.c index d50ca5f28c51..6dd17c48be5e 100644 --- a/ssh-keygen.c +++ b/ssh-keygen.c @@ -310,7 +310,7 @@ ask_filename(struct passwd *pw, const char *prompt) ?} ? ?static struct sshkey * -load_identity(const char *filename, char **commentp) +load_identity(const char *filename, char **commentp, struct sshkey_vault **vault_infop) ?{ ??? ?char *pass; ??? ?struct sshkey *prv; @@ -318,7 +318,9 @@ load_identity(const char *filename, char **commentp) ? ??? ?if (commentp != NULL) ??? ??? ?*commentp = NULL; -?? ?if ((r = sshkey_load_private(filename, "", &prv, commentp)) == 0) +?? ?if (vault_infop != NULL) +?? ??? ?*vault_infop = NULL; +?? ?if ((r = sshkey_load_private(filename, "", &prv, commentp, vault_infop)) == 0) ??? ??? ?return prv; ??? ?if (r != SSH_ERR_KEY_WRONG_PASSPHRASE) ??? ??? ?fatal("Load key \"%s\": %s", filename, ssh_err(r)); @@ -326,7 +328,7 @@ load_identity(const char *filename, char **commentp) ??? ??? ?pass = xstrdup(identity_passphrase); ??? ?else ??? ??? ?pass = read_passphrase("Enter passphrase: ", RP_ALLOW_STDIN); -?? ?r = sshkey_load_private(filename, pass, &prv, commentp); +?? ?r = sshkey_load_private(filename, pass, &prv, commentp, vault_infop); ??? ?freezero(pass, strlen(pass)); ??? ?if (r != 0) ??? ??? ?fatal("Load key \"%s\": %s", filename, ssh_err(r)); @@ -429,7 +431,7 @@ do_convert_to(struct passwd *pw) ??? ?if (stat(identity_file, &st) == -1) ??? ??? ?fatal("%s: %s: %s", __progname, identity_file, strerror(errno)); ??? ?if ((r = sshkey_load_public(identity_file, &k, NULL)) != 0) -?? ??? ?k = load_identity(identity_file, NULL); +?? ??? ?k = load_identity(identity_file, NULL, NULL); ??? ?switch (convert_format) { ??? ?case FMT_RFC4716: ??? ??? ?do_convert_to_ssh2(pw, k); @@ -806,19 +808,33 @@ do_print_public(struct passwd *pw) ??? ?struct stat st; ??? ?int r; ??? ?char *comment = NULL; +?? ?struct sshkey_vault *vault_info = NULL; ? ??? ?if (!have_identity) ??? ??? ?ask_filename(pw, "Enter file in which the key is"); ??? ?if (stat(identity_file, &st) == -1) ??? ??? ?fatal("%s: %s", identity_file, strerror(errno)); -?? ?prv = load_identity(identity_file, &comment); +?? ?prv = load_identity(identity_file, &comment, &vault_info); ??? ?if ((r = sshkey_write(prv, stdout)) != 0) ??? ??? ?error("sshkey_write failed: %s", ssh_err(r)); ??? ?sshkey_free(prv); ??? ?if (comment != NULL && *comment != '\0') -?? ??? ?fprintf(stdout, " %s", comment); -?? ?fprintf(stdout, "\n"); +?? ??? ?printf(" %s", comment); +?? ?printf("\n"); +?? ?if (log_level_get() >= SYSLOG_LEVEL_VERBOSE) { +?? ??? ?printf("Key protection details:\n"); +?? ??? ?printf("File format: %s\n", sshkey_format_name(vault_info->format)); +?? ??? ?if ( (vault_info->ciphername == NULL || strcmp(vault_info->ciphername, "none") == 0) +?? ??? ?? || (vault_info->kdfname == NULL || strcmp(vault_info->kdfname, "none") == 0)) { +?? ??? ??? ?printf("no passphrase\n"); +?? ??? ?} else { +?? ??? ??? ?printf("cipher: %s\n", vault_info->ciphername); +?? ??? ??? ?printf("kdf: %s\n", vault_info->kdfname); +?? ??? ??? ?printf("rounds: %d\n", vault_info->rounds); +?? ??? ?} +?? ?} ??? ?free(comment); +?? ?sshkey_vault_free(vault_info); ??? ?exit(0); ?} ? @@ -920,7 +936,7 @@ fingerprint_private(const char *path) ??? ?if (pubkey == NULL || comment == NULL || *comment == '\0') { ??? ??? ?free(comment); ??? ??? ?if ((r = sshkey_load_private(path, NULL, -?? ??? ???? &privkey, &comment)) != 0) +?? ??? ???? &privkey, &comment, NULL)) != 0) ??? ??? ??? ?debug("load private \"%s\": %s", path, ssh_err(r)); ??? ?} ??? ?if (pubkey == NULL && privkey == NULL) @@ -1416,7 +1432,7 @@ do_change_passphrase(struct passwd *pw) ??? ?if (stat(identity_file, &st) == -1) ??? ??? ?fatal("%s: %s", identity_file, strerror(errno)); ??? ?/* Try to load the file with empty passphrase. */ -?? ?r = sshkey_load_private(identity_file, "", &private, &comment); +?? ?r = sshkey_load_private(identity_file, "", &private, &comment, NULL); ??? ?if (r == SSH_ERR_KEY_WRONG_PASSPHRASE) { ??? ??? ?if (identity_passphrase) ??? ??? ??? ?old_passphrase = xstrdup(identity_passphrase); @@ -1425,7 +1441,7 @@ do_change_passphrase(struct passwd *pw) ??? ??? ??? ???? read_passphrase("Enter old passphrase: ", ??? ??? ??? ???? RP_ALLOW_STDIN); ??? ??? ?r = sshkey_load_private(identity_file, old_passphrase, -?? ??? ???? &private, &comment); +?? ??? ???? &private, &comment, NULL); ??? ??? ?freezero(old_passphrase, strlen(old_passphrase)); ??? ??? ?if (r != 0) ??? ??? ??? ?goto badkey; @@ -1525,7 +1541,7 @@ do_change_comment(struct passwd *pw, const char *identity_comment) ??? ?if (stat(identity_file, &st) == -1) ??? ??? ?fatal("%s: %s", identity_file, strerror(errno)); ??? ?if ((r = sshkey_load_private(identity_file, "", -?? ???? &private, &comment)) == 0) +?? ???? &private, &comment, NULL)) == 0) ??? ??? ?passphrase = xstrdup(""); ??? ?else if (r != SSH_ERR_KEY_WRONG_PASSPHRASE) ??? ??? ?fatal("Cannot load private key \"%s\": %s.", @@ -1540,7 +1556,7 @@ do_change_comment(struct passwd *pw, const char *identity_comment) ??? ??? ??? ???? RP_ALLOW_STDIN); ??? ??? ?/* Try to load using the passphrase. */ ??? ??? ?if ((r = sshkey_load_private(identity_file, passphrase, -?? ??? ???? &private, &comment)) != 0) { +?? ??? ???? &private, &comment, NULL)) != 0) { ??? ??? ??? ?freezero(passphrase, strlen(passphrase)); ??? ??? ??? ?fatal("Cannot load private key \"%s\": %s.", ??? ??? ??? ???? identity_file, ssh_err(r)); @@ -1785,7 +1801,7 @@ do_ca_sign(struct passwd *pw, const char *ca_key_path, int prefer_agent, ??? ??? ?ca->flags |= SSHKEY_FLAG_EXT; ??? ?} else { ??? ??? ?/* CA key is assumed to be a private key on the filesystem */ -?? ??? ?ca = load_identity(tmp, NULL); +?? ??? ?ca = load_identity(tmp, NULL, NULL); ??? ?} ??? ?free(tmp); ? @@ -2494,7 +2510,7 @@ load_sign_key(const char *keypath, const struct sshkey *pubkey) ??? ??? ?debug("%s: %s looks like a public key, using private key " ??? ??? ???? "path %s instead", __func__, keypath, privpath); ??? ?} -?? ?if ((privkey = load_identity(privpath, NULL)) == NULL) { +?? ?if ((privkey = load_identity(privpath, NULL, NULL)) == NULL) { ??? ??? ?error("Couldn't load identity %s", keypath); ??? ??? ?goto done; ??? ?} diff --git a/ssh-keysign.c b/ssh-keysign.c index 3e3ea3e1481d..c9c20483b9a5 100644 --- a/ssh-keysign.c +++ b/ssh-keysign.c @@ -225,7 +225,7 @@ main(int argc, char **argv) ??? ??? ?if (key_fd[i] == -1) ??? ??? ??? ?continue; ??? ??? ?r = sshkey_load_private_type_fd(key_fd[i], KEY_UNSPEC, -?? ??? ???? NULL, &key, NULL); +?? ??? ???? NULL, &key, NULL, NULL); ??? ??? ?close(key_fd[i]); ??? ??? ?if (r != 0) ??? ??? ??? ?debug("parse key %d: %s", i, ssh_err(r)); diff --git a/sshconnect2.c b/sshconnect2.c index 1a6545edf026..7947f2da6584 100644 --- a/sshconnect2.c +++ b/sshconnect2.c @@ -1472,7 +1472,7 @@ load_identity_file(Identity *id) ??? ??? ??? ?} ??? ??? ?} ??? ??? ?switch ((r = sshkey_load_private_type(KEY_UNSPEC, id->filename, -?? ??? ???? passphrase, &private, &comment))) { +?? ??? ???? passphrase, &private, &comment, NULL))) { ??? ??? ?case 0: ??? ??? ??? ?break; ??? ??? ?case SSH_ERR_KEY_WRONG_PASSPHRASE: diff --git a/sshd.c b/sshd.c index 6f8f11a3bdac..42c19089a225 100644 --- a/sshd.c +++ b/sshd.c @@ -1789,7 +1789,7 @@ main(int ac, char **av) ??? ??? ?if (options.host_key_files[i] == NULL) ??? ??? ??? ?continue; ??? ??? ?if ((r = sshkey_load_private(options.host_key_files[i], "", -?? ??? ???? &key, NULL)) != 0 && r != SSH_ERR_SYSTEM_ERROR) +?? ??? ???? &key, NULL, NULL)) != 0 && r != SSH_ERR_SYSTEM_ERROR) ??? ??? ??? ?do_log2(ll, "Unable to load host key \"%s\": %s", ??? ??? ??? ???? options.host_key_files[i], ssh_err(r)); ??? ??? ?if (sshkey_is_sk(key) && diff --git a/sshkey.c b/sshkey.c index 1571e3d93878..4c1948a3752e 100644 --- a/sshkey.c +++ b/sshkey.c @@ -93,6 +93,26 @@ int?? ?sshkey_private_serialize_opt(struct sshkey *key, ?static int sshkey_from_blob_internal(struct sshbuf *buf, ???? struct sshkey **keyp, int allow_cert); ? +/* Supported format types */ +const char * +sshkey_format_name(enum sshkey_private_format format) { +?? ?const char *format_str; +?? ?switch (format) { +?? ?case SSHKEY_PRIVATE_OPENSSH: +?? ??? ?format_str = "RFC4716"; +?? ??? ?break; +?? ?case SSHKEY_PRIVATE_PKCS8: +?? ??? ?format_str = "PKCS8"; +?? ??? ?break; +?? ?case SSHKEY_PRIVATE_PEM: +?? ??? ?format_str = "PEM"; +?? ??? ?break; +?? ?default: +?? ??? ?format_str = "unknown"; +?? ?} +?? ?return format_str; +} + ?/* Supported key types */ ?struct keytype { ??? ?const char *name; @@ -679,6 +699,29 @@ sshkey_free(struct sshkey *k) ??? ?freezero(k, sizeof(*k)); ?} ? +struct sshkey_vault * +sshkey_vault_new() +{ +?? ?struct sshkey_vault *k = calloc(1, sizeof(*k)); +?? ?if (k == NULL) +?? ??? ?return NULL; +?? ?k->format = SSHKEY_PRIVATE_OPENSSH; +?? ?k->ciphername = NULL; +?? ?k->kdfname = NULL; +?? ?k->rounds = -1; +?? ?return k; +} + +void +sshkey_vault_free(struct sshkey_vault *k) +{ +?? ?if (k == NULL) +?? ??? ?return; +?? ?free(k->kdfname); +?? ?free(k); +?? ?return; +} + ?static int ?cert_compare(struct sshkey_cert *a, struct sshkey_cert *b) ?{ @@ -4029,7 +4072,7 @@ private2_uudecode(struct sshbuf *blob, struct sshbuf **decodedp) ? ?static int ?private2_decrypt(struct sshbuf *decoded, const char *passphrase, -??? struct sshbuf **decryptedp, struct sshkey **pubkeyp) +??? struct sshbuf **decryptedp, struct sshkey **pubkeyp, struct sshkey_vault **vault_infop) ?{ ??? ?char *ciphername = NULL, *kdfname = NULL; ??? ?const struct sshcipher *cipher = NULL; @@ -4038,12 +4081,21 @@ private2_decrypt(struct sshbuf *decoded, const char *passphrase, ??? ?struct sshbuf *kdf = NULL, *decrypted = NULL; ??? ?struct sshcipher_ctx *ciphercontext = NULL; ??? ?struct sshkey *pubkey = NULL; +?? ?struct sshkey_vault *vault_info = NULL; ??? ?u_char *key = NULL, *salt = NULL, *dp; ??? ?u_int blocksize, rounds, nkeys, encrypted_len, check1, check2; ? ??? ?if (decoded == NULL || decryptedp == NULL || pubkeyp == NULL) ??? ??? ?return SSH_ERR_INVALID_ARGUMENT; ? +?? ?if (vault_infop != NULL) { +?? ??? ?*vault_infop = NULL; +?? ?} +?? ?if ((vault_info = sshkey_vault_new()) == NULL) { +?? ??? ?r = SSH_ERR_ALLOC_FAIL; +?? ??? ?goto out; +?? ?} + ??? ?*decryptedp = NULL; ??? ?*pubkeyp = NULL; ? @@ -4074,10 +4126,18 @@ private2_decrypt(struct sshbuf *decoded, const char *passphrase, ??? ??? ?r = SSH_ERR_KEY_UNKNOWN_CIPHER; ??? ??? ?goto out; ??? ?} +?? ?if ((vault_info->ciphername = strdup(ciphername)) == NULL) { +?? ??? ?r = SSH_ERR_ALLOC_FAIL; +?? ??? ?goto out; +?? ?} ??? ?if (strcmp(kdfname, "none") != 0 && strcmp(kdfname, "bcrypt") != 0) { ??? ??? ?r = SSH_ERR_KEY_UNKNOWN_CIPHER; ??? ??? ?goto out; ??? ?} +?? ?if ((vault_info->kdfname = strdup(kdfname)) == NULL) { +?? ??? ?r = SSH_ERR_ALLOC_FAIL; +?? ??? ?goto out; +?? ?} ??? ?if (strcmp(kdfname, "none") == 0 && strcmp(ciphername, "none") != 0) { ??? ??? ?r = SSH_ERR_INVALID_FORMAT; ??? ??? ?goto out; @@ -4108,6 +4168,7 @@ private2_decrypt(struct sshbuf *decoded, const char *passphrase, ??? ??? ?if ((r = sshbuf_get_string(kdf, &salt, &slen)) != 0 || ??? ??? ???? (r = sshbuf_get_u32(kdf, &rounds)) != 0) ??? ??? ??? ?goto out; +?? ??? ?vault_info->rounds = rounds; ??? ??? ?if (bcrypt_pbkdf(passphrase, strlen(passphrase), salt, slen, ??? ??? ???? key, keylen + ivlen, rounds) < 0) { ??? ??? ??? ?r = SSH_ERR_INVALID_FORMAT; @@ -4155,6 +4216,10 @@ private2_decrypt(struct sshbuf *decoded, const char *passphrase, ??? ?decrypted = NULL; ??? ?*pubkeyp = pubkey; ??? ?pubkey = NULL; +?? ?if (vault_infop != NULL) { +?? ??? ?*vault_infop = vault_info; +?? ??? ?vault_info = NULL; +?? ?} ??? ?r = 0; ? out: ??? ?cipher_free(ciphercontext); @@ -4171,6 +4236,7 @@ private2_decrypt(struct sshbuf *decoded, const char *passphrase, ??? ?} ??? ?sshbuf_free(kdf); ??? ?sshbuf_free(decrypted); +?? ?sshkey_vault_free(vault_info); ??? ?return r; ?} ? @@ -4201,7 +4267,7 @@ private2_check_padding(struct sshbuf *decrypted) ? ?static int ?sshkey_parse_private2(struct sshbuf *blob, int type, const char *passphrase, -??? struct sshkey **keyp, char **commentp) +??? struct sshkey **keyp, char **commentp, struct sshkey_vault **vault_infop) ?{ ??? ?char *comment = NULL; ??? ?int r = SSH_ERR_INTERNAL_ERROR; @@ -4216,7 +4282,7 @@ sshkey_parse_private2(struct sshbuf *blob, int type, const char *passphrase, ??? ?/* Undo base64 encoding and decrypt the private section */ ??? ?if ((r = private2_uudecode(blob, &decoded)) != 0 || ??? ???? (r = private2_decrypt(decoded, passphrase, -?? ???? &decrypted, &pubkey)) != 0) +?? ???? &decrypted, &pubkey, vault_infop)) != 0) ??? ??? ?goto out; ? ??? ?if (type != KEY_UNSPEC && @@ -4521,15 +4587,18 @@ pem_passphrase_cb(char *buf, int size, int rwflag, void *u) ? ?static int ?sshkey_parse_private_pem_fileblob(struct sshbuf *blob, int type, -??? const char *passphrase, struct sshkey **keyp) +??? const char *passphrase, struct sshkey **keyp, struct sshkey_vault **vault_infop) ?{ ??? ?EVP_PKEY *pk = NULL; ??? ?struct sshkey *prv = NULL; +?? ?struct sshkey_vault *vault_info = NULL; ??? ?BIO *bio = NULL; ??? ?int r; ? ??? ?if (keyp != NULL) ??? ??? ?*keyp = NULL; +?? ?if (vault_infop != NULL) +?? ??? ?*vault_infop = NULL; ? ??? ?if ((bio = BIO_new(BIO_s_mem())) == NULL || sshbuf_len(blob) > INT_MAX) ??? ??? ?return SSH_ERR_ALLOC_FAIL; @@ -4538,6 +4607,13 @@ sshkey_parse_private_pem_fileblob(struct sshbuf *blob, int type, ??? ??? ?r = SSH_ERR_ALLOC_FAIL; ??? ??? ?goto out; ??? ?} +?? ?if ((vault_info = sshkey_vault_new()) == NULL) { +?? ??? ?r = SSH_ERR_ALLOC_FAIL; +?? ??? ?goto out; +?? ?} +?? ?// TODO: identify correctly PEM and PKCS8 format +?? ?vault_info->format = SSHKEY_PRIVATE_PEM; +?? ?// TODO: put the correct ciphername, kdfname and round if a passphrase is used ? ??? ?clear_libcrypto_errors(); ??? ?if ((pk = PEM_read_bio_PrivateKey(bio, NULL, pem_passphrase_cb, @@ -4614,17 +4690,22 @@ sshkey_parse_private_pem_fileblob(struct sshbuf *blob, int type, ??? ??? ?*keyp = prv; ??? ??? ?prv = NULL; ??? ?} +?? ?if (vault_infop != NULL) { +?? ??? ?*vault_infop = vault_info; +?? ??? ?vault_info = NULL; +?? ?} ? out: ??? ?BIO_free(bio); ??? ?EVP_PKEY_free(pk); ??? ?sshkey_free(prv); +?? ?sshkey_vault_free(vault_info); ??? ?return r; ?} ?#endif /* WITH_OPENSSL */ ? ?int ?sshkey_parse_private_fileblob_type(struct sshbuf *blob, int type, -??? const char *passphrase, struct sshkey **keyp, char **commentp) +??? const char *passphrase, struct sshkey **keyp, char **commentp, struct sshkey_vault **vault_infop) ?{ ??? ?int r = SSH_ERR_INTERNAL_ERROR; ? @@ -4632,22 +4713,24 @@ sshkey_parse_private_fileblob_type(struct sshbuf *blob, int type, ??? ??? ?*keyp = NULL; ??? ?if (commentp != NULL) ??? ??? ?*commentp = NULL; +?? ?if (vault_infop != NULL) +?? ??? ?*vault_infop = NULL; ? ??? ?switch (type) { ??? ?case KEY_ED25519: ??? ?case KEY_XMSS: ??? ??? ?/* No fallback for new-format-only keys */ ??? ??? ?return sshkey_parse_private2(blob, type, passphrase, -?? ??? ???? keyp, commentp); +?? ??? ???? keyp, commentp, vault_infop); ??? ?default: ??? ??? ?r = sshkey_parse_private2(blob, type, passphrase, keyp, -?? ??? ???? commentp); +?? ??? ???? commentp, vault_infop); ??? ??? ?/* Only fallback to PEM parser if a format error occurred. */ ??? ??? ?if (r != SSH_ERR_INVALID_FORMAT) ??? ??? ??? ?return r; ?#ifdef WITH_OPENSSL ??? ??? ?return sshkey_parse_private_pem_fileblob(blob, type, -?? ??? ???? passphrase, keyp); +?? ??? ???? passphrase, keyp, vault_infop); ?#else ??? ??? ?return SSH_ERR_INVALID_FORMAT; ?#endif /* WITH_OPENSSL */ @@ -4664,7 +4747,7 @@ sshkey_parse_private_fileblob(struct sshbuf *buffer, const char *passphrase, ??? ??? ?*commentp = NULL; ? ??? ?return sshkey_parse_private_fileblob_type(buffer, KEY_UNSPEC, -?? ???? passphrase, keyp, commentp); +?? ???? passphrase, keyp, commentp, NULL); ?} ? ?void diff --git a/sshkey.h b/sshkey.h index 9c1d4f6372f6..7b7de828d9ce 100644 --- a/sshkey.h +++ b/sshkey.h @@ -99,6 +99,8 @@ enum sshkey_private_format { ??? ?SSHKEY_PRIVATE_PEM = 1, ??? ?SSHKEY_PRIVATE_PKCS8 = 2, ?}; +const char * +sshkey_format_name(enum sshkey_private_format); ? ?/* key is stored in external hardware */ ?#define SSHKEY_FLAG_EXT?? ??? ?0x0001 @@ -162,6 +164,16 @@ struct sshkey_sig_details { ??? ?uint8_t sk_flags;?? ?/* U2F signature flags; see ssh-sk.h */ ?}; ? +/* Key storage parameters in private key file */ +struct sshkey_vault { +?? ?enum sshkey_private_format format; +?? ?char *ciphername; +?? ?char *kdfname; +?? ?int rounds; +}; +struct sshkey_vault?? ?*sshkey_vault_new(); +void?? ??? ?sshkey_vault_free(struct sshkey_vault *); + ?struct sshkey?? ?*sshkey_new(int); ?void?? ??? ? sshkey_free(struct sshkey *); ?int?? ??? ? sshkey_equal_public(const struct sshkey *, @@ -258,7 +270,7 @@ int?? ?sshkey_private_to_fileblob(struct sshkey *key, struct sshbuf *blob, ?int?? ?sshkey_parse_private_fileblob(struct sshbuf *buffer, ???? const char *passphrase, struct sshkey **keyp, char **commentp); ?int?? ?sshkey_parse_private_fileblob_type(struct sshbuf *blob, int type, -??? const char *passphrase, struct sshkey **keyp, char **commentp); +??? const char *passphrase, struct sshkey **keyp, char **commentp, struct sshkey_vault **vault_infop); ?int?? ?sshkey_parse_pubkey_from_private_fileblob_type(struct sshbuf *blob, ???? int type, struct sshkey **pubkeyp); ? -- 2.17.1
On 25/04/2020 at 02:58, Lo?c wrote :> Add private key protection information extraction to shh-keygen using -v > option on top of -y option which is already parsing the private key. > > Technically, the passphrase isn't necessary to do this, but it is the > most logical thing to do for me. > > Adding this to -l option is not appropriate because fingerprinting is > using the .pub file when available. > > An other idea is to add a new option, I can do it if you prefer. > > Also, I'm laking information for information extraction from PEM and > PKCS8 file format, I'm OK to have a pointer to implement this correctly. > > This patch is also adding a regression test for the functionnality. > > --- > > ?authfile.c??????????????????????????? |? 16 ++-- > ?authfile.h??????????????????????????? |?? 7 +- > ?regress/Makefile????????????????????? |?? 3 +- > ?regress/keygen-private-information.sh |? 81 +++++++++++++++++++++ > ?ssh-keygen.c????????????????????????? |? 44 +++++++---- > ?ssh-keysign.c???????????????????????? |?? 2 +- > ?sshconnect2.c???????????????????????? |?? 2 +- > ?sshd.c??????????????????????????????? |?? 2 +- > ?sshkey.c????????????????????????????? | 101 +++++++++++++++++++++++--- > ?sshkey.h????????????????????????????? |? 14 +++- > ?10 files changed, 234 insertions(+), 38 deletions(-) > ?create mode 100644 regress/keygen-private-information.sh >In since I discovered the -Z option, I'm adding here a regression test for this option, the patch below applies on top on the upper one I'm replying to. Hope it is useful. --- ?regress/keygen-private-information.sh | 82 ++++++++++++++++----------- ?1 file changed, 50 insertions(+), 32 deletions(-) diff --git a/regress/keygen-private-information.sh b/regress/keygen-private-information.sh index a9959e919fd1..ddf74eb95c3c 100644 --- a/regress/keygen-private-information.sh +++ b/regress/keygen-private-information.sh @@ -7,7 +7,8 @@ check_private_key () { ???? format="$2" ???? comment="$3" ???? secret="$4" -??? rounds="$5" +??? cipher="$5" +??? rounds="$6" ? ???? # construct expected output in $exp file ???? exp=$OBJ/$t-expected @@ -25,7 +26,7 @@ EOF ???? ??? echo "no passphrase" >> $exp ???? else ???? ??? cat >> $exp << EOF -cipher: aes256-ctr +cipher: $cipher ?kdf: bcrypt ?rounds: $rounds ?EOF @@ -44,37 +45,54 @@ EOF ???? rm -f $OBJ/$t-pub $OBJ/$t-tmp $exp ?} ? -for fmt in '' PKCS8 PEM ; do +for fmt in '' RFC4716 PKCS8 PEM ; do ???? for secret in '' 'secret1'; do -??? ??? rounds_list="0" -??? ??? test -n "$secret" -a -z "$fmt" && rounds_list="2 16" -??? ??? for rounds in $rounds_list; do -??? ??? ??? for t in $SSH_KEYTYPES; do -??? ??? ??? ??? trace "generating $t key in '$fmt' format with '$secret' passphrase and '$rounds' rounds" -??? ??? ??? ??? rm -f $OBJ/$t-key* -??? ??? ??? ??? oldfmt="" -??? ??? ??? ??? case "$fmt" in -??? ??? ??? ??? PKCS8|PEM) oldfmt=1 ;; -??? ??? ??? ??? esac -??? ??? ??? ??? # Some key types like ssh-ed25519 and *@openssh.com are never -??? ??? ??? ??? # stored in old formats. -??? ??? ??? ??? case "$t" in -??? ??? ??? ??? ssh-ed25519|*openssh.com) test -z "$oldfmt" || continue ;; -??? ??? ??? ??? esac -??? ??? ??? ??? comment="foo bar" -??? ??? ??? ??? fmtarg="" -??? ??? ??? ??? test -z "$fmt" || fmtarg="-m $fmt" -??? ??? ??? ??? test "$rounds" = "0" || roundarg="-a $rounds" -??? ??? ??? ??? ${SSHKEYGEN} $fmtarg $roundarg -N "${secret}" -C "${comment}" \ -??? ??? ??? ??? ??? -t $t -f $OBJ/$t-key >/dev/null 2>&1 || \ -??? ??? ??? ??? ??? fatal "keygen of $t in format $fmt failed" -??? ??? ??? ??? rm -f $OBJ/$t-key.pub # .pub file not used, remove it to be sure it is not used -??? ??? ??? ??? if [ ! -z "$oldfmt" ] ; then -??? ??? ??? ??? ??? # Comment cannot be recovered from old format keys. -??? ??? ??? ??? ??? comment="" -??? ??? ??? ??? fi -??? ??? ??? ??? check_private_key $OBJ/$t-key "${fmt}" "${comment}" "${secret}" "${rounds}" -??? ??? ??? ??? rm -f $OBJ/$t-key* +??? ??? cipher_list="default" +??? ??? test -n "$secret" -a -z "$fmt" && cipher_list=`${SSH} -Q cipher` +??? ??? for cipher in $cipher_list; do +??? ??? ??? rounds_list="default" +??? ??? ??? test -n "$secret" -a -z "$fmt" && rounds_list="2 16" +??? ??? ??? for rounds in $rounds_list; do +??? ??? ??? ??? for t in $SSH_KEYTYPES; do +??? ??? ??? ??? ??? trace "generating $t key in '$fmt' format with '$secret' passphrase, '$cipher' cipher and '$rounds' rounds" +??? ??? ??? ??? ??? rm -f $OBJ/$t-key* +??? ??? ??? ??? ??? oldfmt="" +??? ??? ??? ??? ??? case "$fmt" in +??? ??? ??? ??? ??? PKCS8|PEM) oldfmt=1 ;; +??? ??? ??? ??? ??? esac +??? ??? ??? ??? ??? # Some key types like ssh-ed25519 and *@openssh.com are never +??? ??? ??? ??? ??? # stored in old formats. +??? ??? ??? ??? ??? case "$t" in +??? ??? ??? ??? ??? ssh-ed25519|*openssh.com) test -z "$oldfmt" || continue ;; +??? ??? ??? ??? ??? esac +??? ??? ??? ??? ??? comment="foo bar" +??? ??? ??? ??? ??? fmtarg="" +??? ??? ??? ??? ??? test -z "$fmt" || fmtarg="-m $fmt" +??? ??? ??? ??? ??? if test "$rounds" = "default" ; then +??? ??? ??? ??? ??? ??? rounds=16; +??? ??? ??? ??? ??? ??? roundarg="" +??? ??? ??? ??? ??? else +??? ??? ??? ??? ??? ??? roundarg="-a $rounds"; +??? ??? ??? ??? ??? fi +??? ??? ??? ??? ??? if test "$cipher" = "default" ; then +??? ??? ??? ??? ??? ??? cipher="aes256-ctr" ; +??? ??? ??? ??? ??? ??? cipherarg="" +??? ??? ??? ??? ??? else +??? ??? ??? ??? ??? ??? cipherarg="-Z $cipher"; +??? ??? ??? ??? ??? fi +??? ??? ??? ??? ??? ${SSHKEYGEN} $fmtarg $cipherarg $roundarg \ +??? ??? ??? ??? ??? ??? -N "${secret}" -C "${comment}" \ +??? ??? ??? ??? ??? ??? -t $t -f $OBJ/$t-key >/dev/null 2>&1 || \ +??? ??? ??? ??? ??? ??? fatal "keygen of $t in format $fmt failed" +??? ??? ??? ??? ??? rm -f $OBJ/$t-key.pub # .pub file not used, remove it to be sure it is not used +??? ??? ??? ??? ??? if [ ! -z "$oldfmt" ] ; then +??? ??? ??? ??? ??? ??? # Comment cannot be recovered from old format keys. +??? ??? ??? ??? ??? ??? comment="" +??? ??? ??? ??? ??? fi +??? ??? ??? ??? ??? check_private_key $OBJ/$t-key "${fmt}" "${comment}" \ +??? ??? ??? ??? ??? ??? "${secret}" "${cipher}" "${rounds}" +??? ??? ??? ??? ??? rm -f $OBJ/$t-key* +??? ??? ??? ??? done ???? ??? ??? done ???? ??? done ???? done -- 2.17.1
Loïc
2020-Apr-25 21:48 UTC
[PATCH 5/3] ssh-keygen: -Z cipher can be "none" test it in regression and, report it correctly in -yv option
On 25/04/2020 at 23:35, Lo?c wrote :> On 25/04/2020 at 02:58, Lo?c wrote : >> Add private key protection information extraction to shh-keygen using -v >> option on top of -y option which is already parsing the private key. >> >> Technically, the passphrase isn't necessary to do this, but it is the >> most logical thing to do for me. >> >> Adding this to -l option is not appropriate because fingerprinting is >> using the .pub file when available. >> >> An other idea is to add a new option, I can do it if you prefer. >> >> Also, I'm laking information for information extraction from PEM and >> PKCS8 file format, I'm OK to have a pointer to implement this correctly. >> >> This patch is also adding a regression test for the functionnality. >> >> --- >> >> ?authfile.c??????????????????????????? |? 16 ++-- >> ?authfile.h??????????????????????????? |?? 7 +- >> ?regress/Makefile????????????????????? |?? 3 +- >> ?regress/keygen-private-information.sh |? 81 +++++++++++++++++++++ >> ?ssh-keygen.c????????????????????????? |? 44 +++++++---- >> ?ssh-keysign.c???????????????????????? |?? 2 +- >> ?sshconnect2.c???????????????????????? |?? 2 +- >> ?sshd.c??????????????????????????????? |?? 2 +- >> ?sshkey.c????????????????????????????? | 101 +++++++++++++++++++++++--- >> ?sshkey.h????????????????????????????? |? 14 +++- >> ?10 files changed, 234 insertions(+), 38 deletions(-) >> ?create mode 100644 regress/keygen-private-information.sh >> > In since I discovered the -Z option, I'm adding here a regression test > for this option, the patch below applies on top on the upper one I'm > replying to. >In fact "none" cypher is allowed here is a patch to test it in regression and report it correctly in -yv option --- ?regress/keygen-private-information.sh | 2 +- ?ssh-keygen.c????????????????????????? | 3 +-- ?2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/regress/keygen-private-information.sh b/regress/keygen-private-information.sh index ddf74eb95c3c..22ad6429a079 100644 --- a/regress/keygen-private-information.sh +++ b/regress/keygen-private-information.sh @@ -48,7 +48,7 @@ EOF ?for fmt in '' RFC4716 PKCS8 PEM ; do ???? for secret in '' 'secret1'; do ???? ??? cipher_list="default" -??? ??? test -n "$secret" -a -z "$fmt" && cipher_list=`${SSH} -Q cipher` +??? ??? test -n "$secret" -a -z "$fmt" && cipher_list=`${SSH} -Q cipher`" none" ???? ??? for cipher in $cipher_list; do ???? ??? ??? rounds_list="default" ???? ??? ??? test -n "$secret" -a -z "$fmt" && rounds_list="2 16" diff --git a/ssh-keygen.c b/ssh-keygen.c index a848edc33b5d..030b12e5b897 100644 --- a/ssh-keygen.c +++ b/ssh-keygen.c @@ -824,8 +824,7 @@ do_print_public(struct passwd *pw) ???? if (log_level_get() >= SYSLOG_LEVEL_VERBOSE) { ???? ??? printf("Key protection details:\n"); ???? ??? printf("File format: %s\n", sshkey_format_name(vault_info->format)); -??? ??? if ( (vault_info->ciphername == NULL || strcmp(vault_info->ciphername, "none") == 0) -??? ??? ? || (vault_info->kdfname == NULL || strcmp(vault_info->kdfname, "none") == 0)) { +??? ??? if (vault_info->kdfname == NULL || strcmp(vault_info->kdfname, "none") == 0) { ???? ??? ??? printf("no passphrase\n"); ???? ??? } else { ???? ??? ??? printf("cipher: %s\n", vault_info->ciphername); -- 2.17.1