1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-09 09:27:59 +00:00

cmdgen: add a --dump option.

Also spelled '-O text', this takes a public or private key as input,
and produces on standard output a dump of all the actual numbers
involved in the key: the exponent and modulus for RSA, the p,q,g,y
parameters for DSA, the affine x and y coordinates of the public
elliptic curve point for ECC keys, and all the extra bits and pieces
in the private keys too.

Partly I expect this to be useful to me for debugging: I've had to
paste key files a few too many times through base64 decoders and hex
dump tools, then manually decode SSH marshalling and paste the result
into the Python REPL to get an integer object. Now I should be able to
get _straight_ to text I can paste into Python.

But also, it's a way that other applications can use the key
generator: if you need to generate, say, an RSA key in some format I
don't support (I've recently heard of an XML-based one, for example),
then you can run 'puttygen -t rsa --dump' and have it print the
elements of a freshly generated keypair on standard output, and then
all you have to do is understand the output format.
This commit is contained in:
Simon Tatham 2020-02-17 19:53:19 +00:00
parent 96f1fb9456
commit c18e5dc8fb
13 changed files with 317 additions and 8 deletions

2
Recipe
View File

@ -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

View File

@ -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:

3
misc.h
View File

@ -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);
/*

22
ssh.h
View File

@ -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)

View File

@ -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,

View File

@ -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,

View File

@ -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);
}

View File

@ -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,

View File

@ -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":

View File

@ -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) { \

View File

@ -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

View File

@ -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

24
utils.c
View File

@ -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);
}
}