diff --git a/Recipe b/Recipe index fdf988f2..468ad715 100644 --- a/Recipe +++ b/Recipe @@ -400,7 +400,7 @@ testcrypt : [UT] testcrypt SSHCRYPTO sshprng sshprime sshpubk marshal utils testcrypt : [C] testcrypt SSHCRYPTO sshprng sshprime sshpubk marshal utils + memory tree234 winmiscs KEYGEN testsc : [UT] testsc SSHCRYPTO marshal utils memory tree234 wildcard - + sshmac uxutils + + sshmac uxutils sshpubk testzlib : [UT] testzlib sshzlib utils marshal memory uppity : [UT] uxserver SSHSERVER UXMISC uxsignal uxnoise uxgss uxnogtk diff --git a/cmdgen.c b/cmdgen.c index 0d2c0c8b..c4340ecc 100644 --- a/cmdgen.c +++ b/cmdgen.c @@ -13,6 +13,7 @@ #include "putty.h" #include "ssh.h" +#include "mpint.h" struct progress { int phase, current; @@ -97,6 +98,8 @@ void help(void) " public RFC 4716 / ssh.com public key\n" " public-openssh OpenSSH public key\n" " fingerprint output the key fingerprint\n" + " text output the key components as " + "'name=0x####'\n" " -o specify output file\n" " -l equivalent to `-O fingerprint'\n" " -L equivalent to `-O public-openssh'\n" @@ -166,7 +169,7 @@ int main(int argc, char **argv) enum { NOKEYGEN, RSA1, RSA2, DSA, ECDSA, ED25519 } keytype = NOKEYGEN; char *outfile = NULL, *outfiletmp = NULL; enum { PRIVATE, PUBLIC, PUBLICO, FP, OPENSSH_AUTO, - OPENSSH_NEW, SSHCOM } outtype = PRIVATE; + OPENSSH_NEW, SSHCOM, TEXT } outtype = PRIVATE; int bits = -1; const char *comment = NULL; char *origcomment = NULL; @@ -287,6 +290,8 @@ int main(int argc, char **argv) } else { random_device = val; } + } else if (!strcmp(opt, "-dump")) { + outtype = TEXT; } else { errs = true; fprintf(stderr, @@ -389,6 +394,8 @@ int main(int argc, char **argv) outtype = OPENSSH_NEW, sshver = 2; else if (!strcmp(p, "private-sshcom")) outtype = SSHCOM, sshver = 2; + else if (!strcmp(p, "text")) + outtype = TEXT; else { fprintf(stderr, "puttygen: unknown output type `%s'\n", p); @@ -498,7 +505,7 @@ int main(int argc, char **argv) */ if (keytype != NOKEYGEN && (outtype != PRIVATE && outtype != OPENSSH_AUTO && - outtype != OPENSSH_NEW && outtype != SSHCOM)) { + outtype != OPENSSH_NEW && outtype != SSHCOM && outtype != TEXT)) { fprintf(stderr, "puttygen: this would generate a new key but " "discard the private part\n"); RETURN(1); @@ -867,8 +874,13 @@ int main(int argc, char **argv) /* * Prompt for a new passphrase if we have been asked to, or if * we have just generated a key. + * + * In the latter case, an exception is if we're producing text + * output, because that output format doesn't support encryption + * in any case. */ - if (!new_passphrase && (change_passphrase || keytype != NOKEYGEN)) { + if (!new_passphrase && (change_passphrase || + (keytype != NOKEYGEN && outtype != TEXT))) { prompts_t *p = new_prompts(); int ret; @@ -1039,6 +1051,65 @@ int main(int argc, char **argv) RETURN(1); /* rename failed */ } break; + + case TEXT: { + key_components *kc; + if (sshver == 1) { + assert(ssh1key); + kc = rsa_components(ssh1key); + } else { + if (ssh2key) { + kc = ssh_key_components(ssh2key->key); + } else { + assert(ssh2blob); + + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(ssh2blob)); + ptrlen algname = get_string(src); + const ssh_keyalg *alg = find_pubkey_alg_len(algname); + if (!alg) { + fprintf(stderr, "puttygen: cannot extract key components " + "from public key of unknown type '%.*s'\n", + PTRLEN_PRINTF(algname)); + RETURN(1); + } + ssh_key *sk = ssh_key_new_pub( + alg, ptrlen_from_strbuf(ssh2blob)); + kc = ssh_key_components(sk); + ssh_key_free(sk); + } + } + + FILE *fp; + if (outfile) { + fp = f_open(outfilename, "w", false); + if (!fp) { + fprintf(stderr, "unable to open output file\n"); + exit(1); + } + } else { + fp = stdout; + } + + for (size_t i = 0; i < kc->ncomponents; i++) { + if (kc->components[i].is_mp_int) { + char *hex = mp_get_hex(kc->components[i].mp); + fprintf(fp, "%s=0x%s\n", kc->components[i].name, hex); + smemclr(hex, strlen(hex)); + sfree(hex); + } else { + fprintf(fp, "%s=\"", kc->components[i].name); + write_c_string_literal(fp, ptrlen_from_asciz( + kc->components[i].text)); + fputs("\"\n", fp); + } + } + + if (outfile) + fclose(fp); + key_components_free(kc); + break; + } } out: diff --git a/misc.h b/misc.h index e31a3ec7..7b4945ef 100644 --- a/misc.h +++ b/misc.h @@ -215,6 +215,9 @@ bool smemeq(const void *av, const void *bv, size_t len); * been removed. */ size_t encode_utf8(void *output, unsigned long ch); +/* Write a string out in C string-literal format. */ +void write_c_string_literal(FILE *fp, ptrlen str); + char *buildinfo(const char *newline); /* diff --git a/ssh.h b/ssh.h index 2395c325..d8bbf465 100644 --- a/ssh.h +++ b/ssh.h @@ -528,6 +528,24 @@ struct eddsa_key { WeierstrassPoint *ecdsa_public(mp_int *private_key, const ssh_keyalg *alg); EdwardsPoint *eddsa_public(mp_int *private_key, const ssh_keyalg *alg); +typedef struct key_components { + size_t ncomponents, componentsize; + struct { + char *name; + bool is_mp_int; + union { + char *text; + mp_int *mp; + }; + } *components; +} key_components; +key_components *key_components_new(void); +void key_components_add_text(key_components *kc, + const char *name, const char *value); +void key_components_add_mp(key_components *kc, + const char *name, mp_int *value); +void key_components_free(key_components *kc); + /* * SSH-1 never quite decided which order to store the two components * of an RSA key. During connection setup, the server sends its host @@ -554,6 +572,7 @@ int rsa_ssh1_public_blob_len(ptrlen data); void rsa_ssh1_private_blob_agent(BinarySink *bs, RSAKey *key); void freersapriv(RSAKey *key); void freersakey(RSAKey *key); +key_components *rsa_components(RSAKey *key); uint32_t crc32_rfc1662(ptrlen data); uint32_t crc32_ssh1(ptrlen data); @@ -801,6 +820,7 @@ struct ssh_keyalg { void (*private_blob)(ssh_key *key, BinarySink *); void (*openssh_blob) (ssh_key *key, BinarySink *); char *(*cache_str) (ssh_key *key); + key_components *(*components) (ssh_key *key); /* 'Class methods' that don't deal with an ssh_key at all */ int (*pubkey_bits) (const ssh_keyalg *self, ptrlen blob); @@ -837,6 +857,8 @@ static inline void ssh_key_openssh_blob(ssh_key *key, BinarySink *bs) { key->vt->openssh_blob(key, bs); } static inline char *ssh_key_cache_str(ssh_key *key) { return key->vt->cache_str(key); } +static inline key_components *ssh_key_components(ssh_key *key) +{ return key->vt->components(key); } static inline int ssh_key_public_bits(const ssh_keyalg *self, ptrlen blob) { return self->pubkey_bits(self, blob); } static inline const ssh_keyalg *ssh_key_alg(ssh_key *key) diff --git a/sshdss.c b/sshdss.c index 9cb78c69..549e717e 100644 --- a/sshdss.c +++ b/sshdss.c @@ -83,6 +83,23 @@ static char *dss_cache_str(ssh_key *key) return strbuf_to_str(sb); } +static key_components *dss_components(ssh_key *key) +{ + struct dss_key *dss = container_of(key, struct dss_key, sshk); + key_components *kc = key_components_new(); + + key_components_add_text(kc, "key_type", "DSA"); + assert(dss->p); + key_components_add_mp(kc, "p", dss->p); + key_components_add_mp(kc, "q", dss->q); + key_components_add_mp(kc, "g", dss->g); + key_components_add_mp(kc, "public_y", dss->y); + if (dss->x) + key_components_add_mp(kc, "private_x", dss->x); + + return kc; +} + static char *dss_invalid(ssh_key *key, unsigned flags) { /* No validity criterion will stop us from using a DSA key at all */ @@ -478,6 +495,7 @@ const ssh_keyalg ssh_dss = { dss_private_blob, dss_openssh_blob, dss_cache_str, + dss_components, dss_pubkey_bits, diff --git a/sshecc.c b/sshecc.c index 1e047c64..de94eedb 100644 --- a/sshecc.c +++ b/sshecc.c @@ -640,6 +640,27 @@ static char *ecdsa_cache_str(ssh_key *key) return toret; } +static key_components *ecdsa_components(ssh_key *key) +{ + struct ecdsa_key *ek = container_of(key, struct ecdsa_key, sshk); + key_components *kc = key_components_new(); + + key_components_add_text(kc, "key_type", "ECDSA"); + key_components_add_text(kc, "curve_name", ek->curve->textname); + + mp_int *x, *y; + ecc_weierstrass_get_affine(ek->publicKey, &x, &y); + key_components_add_mp(kc, "public_affine_x", x); + key_components_add_mp(kc, "public_affine_y", y); + mp_free(x); + mp_free(y); + + if (ek->privateKey) + key_components_add_mp(kc, "private_exponent", ek->privateKey); + + return kc; +} + static char *eddsa_cache_str(ssh_key *key) { struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk); @@ -652,6 +673,27 @@ static char *eddsa_cache_str(ssh_key *key) return toret; } +static key_components *eddsa_components(ssh_key *key) +{ + struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk); + key_components *kc = key_components_new(); + + key_components_add_text(kc, "key_type", "EdDSA"); + key_components_add_text(kc, "curve_name", ek->curve->textname); + + mp_int *x, *y; + ecc_edwards_get_affine(ek->publicKey, &x, &y); + key_components_add_mp(kc, "affine_x", x); + key_components_add_mp(kc, "affine_y", y); + mp_free(x); + mp_free(y); + + if (ek->privateKey) + key_components_add_mp(kc, "private_exponent", ek->privateKey); + + return kc; +} + static void ecdsa_public_blob(ssh_key *key, BinarySink *bs) { struct ecdsa_key *ek = container_of(key, struct ecdsa_key, sshk); @@ -1141,6 +1183,7 @@ const ssh_keyalg ssh_ecdsa_ed25519 = { eddsa_private_blob, eddsa_openssh_blob, eddsa_cache_str, + eddsa_components, ec_shared_pubkey_bits, @@ -1171,6 +1214,7 @@ const ssh_keyalg ssh_ecdsa_nistp256 = { ecdsa_private_blob, ecdsa_openssh_blob, ecdsa_cache_str, + ecdsa_components, ec_shared_pubkey_bits, @@ -1201,6 +1245,7 @@ const ssh_keyalg ssh_ecdsa_nistp384 = { ecdsa_private_blob, ecdsa_openssh_blob, ecdsa_cache_str, + ecdsa_components, ec_shared_pubkey_bits, @@ -1231,6 +1276,7 @@ const ssh_keyalg ssh_ecdsa_nistp521 = { ecdsa_private_blob, ecdsa_openssh_blob, ecdsa_cache_str, + ecdsa_components, ec_shared_pubkey_bits, diff --git a/sshpubk.c b/sshpubk.c index ac995b69..cbf42c93 100644 --- a/sshpubk.c +++ b/sshpubk.c @@ -1730,3 +1730,47 @@ const char *key_type_to_str(int type) unreachable("bad key type in key_type_to_str"); } } + +key_components *key_components_new(void) +{ + key_components *kc = snew(key_components); + kc->ncomponents = 0; + kc->componentsize = 0; + kc->components = NULL; + return kc; +} + +void key_components_add_text(key_components *kc, + const char *name, const char *value) +{ + sgrowarray(kc->components, kc->componentsize, kc->ncomponents); + size_t n = kc->ncomponents++; + kc->components[n].name = dupstr(name); + kc->components[n].is_mp_int = false; + kc->components[n].text = dupstr(value); +} + +void key_components_add_mp(key_components *kc, + const char *name, mp_int *value) +{ + sgrowarray(kc->components, kc->componentsize, kc->ncomponents); + size_t n = kc->ncomponents++; + kc->components[n].name = dupstr(name); + kc->components[n].is_mp_int = true; + kc->components[n].mp = mp_copy(value); +} + +void key_components_free(key_components *kc) +{ + for (size_t i = 0; i < kc->ncomponents; i++) { + sfree(kc->components[i].name); + if (kc->components[i].is_mp_int) { + mp_free(kc->components[i].mp); + } else { + smemclr(kc->components[i].text, strlen(kc->components[i].text)); + sfree(kc->components[i].text); + } + } + sfree(kc->components); + sfree(kc); +} diff --git a/sshrsa.c b/sshrsa.c index 4634763f..eec76da8 100644 --- a/sshrsa.c +++ b/sshrsa.c @@ -43,6 +43,21 @@ void BinarySource_get_rsa_ssh1_priv( rsa->private_exponent = get_mp_ssh1(src); } +key_components *rsa_components(RSAKey *rsa) +{ + key_components *kc = key_components_new(); + key_components_add_text(kc, "key_type", "RSA"); + key_components_add_mp(kc, "public_modulus", rsa->modulus); + key_components_add_mp(kc, "public_exponent", rsa->exponent); + if (rsa->private_exponent) { + key_components_add_mp(kc, "private_exponent", rsa->private_exponent); + key_components_add_mp(kc, "private_p", rsa->p); + key_components_add_mp(kc, "private_q", rsa->q); + key_components_add_mp(kc, "private_inverse_q_mod_p", rsa->iqmp); + } + return kc; +} + RSAKey *BinarySource_get_rsa_ssh1_priv_agent(BinarySource *src) { RSAKey *rsa = snew(RSAKey); @@ -484,6 +499,12 @@ static char *rsa2_cache_str(ssh_key *key) return rsastr_fmt(rsa); } +static key_components *rsa2_components(ssh_key *key) +{ + RSAKey *rsa = container_of(key, RSAKey, sshk); + return rsa_components(rsa); +} + static void rsa2_public_blob(ssh_key *key, BinarySink *bs) { RSAKey *rsa = container_of(key, RSAKey, sshk); @@ -808,6 +829,7 @@ const ssh_keyalg ssh_rsa = { rsa2_private_blob, rsa2_openssh_blob, rsa2_cache_str, + rsa2_components, rsa2_pubkey_bits, diff --git a/test/testcrypt.py b/test/testcrypt.py index 999453bc..85e0b044 100644 --- a/test/testcrypt.py +++ b/test/testcrypt.py @@ -163,17 +163,41 @@ def make_argword(arg, argtype, fnname, argindex, to_preserve): "Can't convert {}() argument {:d} to {} (value was {!r})".format( fnname, argindex, typename, arg)) +def unpack_string(identifier): + retwords = childprocess.funcall("getstring", [identifier]) + childprocess.funcall("free", [identifier]) + return re.sub(b"%[0-9A-F][0-9A-F]", + lambda m: valbytes([int(m.group(0)[1:], 16)]), + retwords[0]) + +def unpack_mp(identifier): + retwords = childprocess.funcall("mp_dump", [identifier]) + childprocess.funcall("free", [identifier]) + return int(retwords[0], 16) + def make_retval(rettype, word, unpack_strings): if rettype.startswith("opt_"): if word == b"NULL": return None rettype = rettype[4:] if rettype == "val_string" and unpack_strings: - retwords = childprocess.funcall("getstring", [word]) + return unpack_string(word) + if rettype == "val_keycomponents": + kc = {} + retwords = childprocess.funcall("key_components_count", [word]) + for i in range(int(retwords[0], 0)): + args = [word, "{:d}".format(i)] + retwords = childprocess.funcall("key_components_nth_name", args) + kc_key = unpack_string(retwords[0]) + retwords = childprocess.funcall("key_components_nth_str", args) + if retwords[0] != b"NULL": + kc_value = unpack_string(retwords[0]).decode("ASCII") + else: + retwords = childprocess.funcall("key_components_nth_mp", args) + kc_value = unpack_mp(retwords[0]) + kc[kc_key.decode("ASCII")] = kc_value childprocess.funcall("free", [word]) - return re.sub(b"%[0-9A-F][0-9A-F]", - lambda m: valbytes([int(m.group(0)[1:], 16)]), - retwords[0]) + return kc if rettype.startswith("val_"): return Value(rettype, word) elif rettype == "int" or rettype == "uint": diff --git a/testcrypt.c b/testcrypt.c index 417f091e..94d9baaf 100644 --- a/testcrypt.c +++ b/testcrypt.c @@ -87,6 +87,7 @@ uint64_t prng_reseed_time_ms(void) X(rsakex, RSAKey *, ssh_rsakex_freekey(v)) \ X(rsa, RSAKey *, rsa_free(v)) \ X(prng, prng *, prng_free(v)) \ + X(keycomponents, key_components *, key_components_free(v)) \ /* end of list */ typedef struct Value Value; @@ -576,6 +577,7 @@ static void return_val_string_asciz(strbuf *out, char *s) NULLABLE_RETURN_WRAPPER(val_string, strbuf *) NULLABLE_RETURN_WRAPPER(val_string_asciz, char *) +NULLABLE_RETURN_WRAPPER(val_string_asciz_const, const char *) NULLABLE_RETURN_WRAPPER(val_cipher, ssh_cipher *) NULLABLE_RETURN_WRAPPER(val_hash, ssh_hash *) NULLABLE_RETURN_WRAPPER(val_key, ssh_key *) @@ -1077,6 +1079,25 @@ ssh_key *eddsa_generate_wrapper(int bits) } #define eddsa_generate eddsa_generate_wrapper +size_t key_components_count(key_components *kc) { return kc->ncomponents; } +const char *key_components_nth_name(key_components *kc, size_t n) +{ + return (n >= kc->ncomponents ? NULL : + kc->components[n].name); +} +const char *key_components_nth_str(key_components *kc, size_t n) +{ + return (n >= kc->ncomponents ? NULL : + kc->components[n].is_mp_int ? NULL : + kc->components[n].text); +} +mp_int *key_components_nth_mp(key_components *kc, size_t n) +{ + return (n >= kc->ncomponents ? NULL : + !kc->components[n].is_mp_int ? NULL : + mp_copy(kc->components[n].mp)); +} + #define VALTYPE_TYPEDEF(n,t,f) \ typedef t TD_val_##n; \ typedef t *TD_out_val_##n; @@ -1113,6 +1134,7 @@ typedef const ssh_cipheralg *TD_cipheralg; typedef const ssh_kex *TD_dh_group; typedef const ssh_kex *TD_ecdh_alg; typedef RsaSsh1Order TD_rsaorder; +typedef key_components *TD_keycomponents; #define FUNC0(rettype, function) \ static void handle_##function(BinarySource *in, strbuf *out) { \ diff --git a/testcrypt.h b/testcrypt.h index 8b2c91ae..41bee353 100644 --- a/testcrypt.h +++ b/testcrypt.h @@ -157,8 +157,17 @@ FUNC2(void, ssh_key_public_blob, val_key, out_val_string_binarysink) FUNC2(void, ssh_key_private_blob, val_key, out_val_string_binarysink) FUNC2(void, ssh_key_openssh_blob, val_key, out_val_string_binarysink) FUNC1(val_string_asciz, ssh_key_cache_str, val_key) +FUNC1(val_keycomponents, ssh_key_components, val_key) FUNC2(uint, ssh_key_public_bits, keyalg, val_string_ptrlen) +/* + * Accessors to retrieve the innards of a 'key_components'. + */ +FUNC1(uint, key_components_count, val_keycomponents) +FUNC2(opt_val_string_asciz_const, key_components_nth_name, val_keycomponents, uint) +FUNC2(opt_val_string_asciz_const, key_components_nth_str, val_keycomponents, uint) +FUNC2(opt_val_mpint, key_components_nth_mp, val_keycomponents, uint) + /* * The ssh_cipher abstraction. The in-place encrypt and decrypt * functions are wrapped to replace them with versions that take one diff --git a/testsc.c b/testsc.c index 50bda39f..bbe15faf 100644 --- a/testsc.c +++ b/testsc.c @@ -93,6 +93,10 @@ static NORETURN PRINTF_LIKE(1, 2) void fatal_error(const char *p, ...) } void out_of_memory(void) { fatal_error("out of memory"); } +FILE *f_open(const Filename *filename, char const *mode, bool is_private) +{ unreachable("this is a stub needed to link, and should never be called"); } +void old_keyfile_warning(void) +{ unreachable("this is a stub needed to link, and should never be called"); } /* * A simple deterministic PRNG, without any of the Fortuna diff --git a/utils.c b/utils.c index b65f852a..61786d0a 100644 --- a/utils.c +++ b/utils.c @@ -1049,3 +1049,27 @@ size_t encode_utf8(void *output, unsigned long ch) } return p - start; } + +void write_c_string_literal(FILE *fp, ptrlen str) +{ + for (const char *p = str.ptr; p < (const char *)str.ptr + str.len; p++) { + char c = *p; + + if (c == '\n') + fputs("\\n", fp); + else if (c == '\r') + fputs("\\r", fp); + else if (c == '\t') + fputs("\\t", fp); + else if (c == '\b') + fputs("\\b", fp); + else if (c == '\\') + fputs("\\\\", fp); + else if (c == '"') + fputs("\\\"", fp); + else if (c >= 32 && c <= 126) + fputc(c, fp); + else + fprintf(fp, "\\%03o", (unsigned char)c); + } +}