diff options
Diffstat (limited to 'cmdgen.c')
-rw-r--r-- | cmdgen.c | 527 |
1 files changed, 410 insertions, 117 deletions
@@ -130,13 +130,19 @@ void help(void) " public RFC 4716 / ssh.com public key\n" " public-openssh OpenSSH public key\n" " fingerprint output the key fingerprint\n" + " cert-info print certificate information\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" " -p equivalent to `-O public'\n" + " --cert-info equivalent to `-O cert-info'\n" " --dump equivalent to `-O text'\n" + " -E fptype specify fingerprint output type:\n" + " sha256, md5, sha256-cert, md5-cert\n" + " --certificate file incorporate a certificate into the key\n" + " --remove-certificate remove any certificate from the key\n" " --reencrypt load a key and save it with fresh " "encryption\n" " --old-passphrase file\n" @@ -214,6 +220,15 @@ static char *readpassphrase(const char *filename) #define DEFAULT_RSADSA_BITS 2048 +static void spr_error(SeatPromptResult spr) +{ + if (spr.kind == SPRK_SW_ABORT) { + char *err = spr_get_error_message(spr); + fprintf(stderr, "puttygen: unable to read passphrase: %s", err); + sfree(err); + } +} + /* For Unix in particular, but harmless if this main() is reused elsewhere */ const bool buildinfo_gtk_relevant = false; @@ -226,7 +241,7 @@ int main(int argc, char **argv) enum { NOKEYGEN, RSA1, RSA2, DSA, ECDSA, EDDSA } keytype = NOKEYGEN; char *outfile = NULL, *outfiletmp = NULL; enum { PRIVATE, PUBLIC, PUBLICO, FP, OPENSSH_AUTO, - OPENSSH_NEW, SSHCOM, TEXT } outtype = PRIVATE; + OPENSSH_NEW, SSHCOM, TEXT, CERTINFO } outtype = PRIVATE; int bits = -1; const char *comment = NULL; char *origcomment = NULL; @@ -241,6 +256,8 @@ int main(int argc, char **argv) char *old_passphrase = NULL, *new_passphrase = NULL; bool load_encrypted; const char *random_device = NULL; + char *certfile = NULL; + bool remove_cert = false; int exit_status = 0; const PrimeGenerationPolicy *primegen = &primegen_probabilistic; bool strong_rsa = false; @@ -286,75 +303,79 @@ int main(int argc, char **argv) while (*p && *p != '=') p++; /* find end of option */ if (*p == '=') { - *p++ = '\0'; - val = p; + *p++ = '\0'; + val = p; } else val = NULL; if (!strcmp(opt, "-help")) { - if (val) { - errs = true; - fprintf(stderr, "puttygen: option `-%s'" - " expects no argument\n", opt); - } else { - help(); - nogo = true; - } + if (val) { + errs = true; + fprintf(stderr, "puttygen: option `-%s'" + " expects no argument\n", opt); + } else { + help(); + nogo = true; + } } else if (!strcmp(opt, "-version")) { - if (val) { - errs = true; - fprintf(stderr, "puttygen: option `-%s'" - " expects no argument\n", opt); - } else { - showversion(); - nogo = true; - } + if (val) { + errs = true; + fprintf(stderr, "puttygen: option `-%s'" + " expects no argument\n", opt); + } else { + showversion(); + nogo = true; + } } else if (!strcmp(opt, "-pgpfp")) { - if (val) { - errs = true; - fprintf(stderr, "puttygen: option `-%s'" - " expects no argument\n", opt); - } else { - /* support --pgpfp for consistency */ - pgp_fingerprints(); - nogo = true; - } + if (val) { + errs = true; + fprintf(stderr, "puttygen: option `-%s'" + " expects no argument\n", opt); + } else { + /* support --pgpfp for consistency */ + pgp_fingerprints(); + nogo = true; + } } else if (!strcmp(opt, "-old-passphrase")) { - if (!val && argc > 1) - --argc, val = *++argv; - if (!val) { - errs = true; - fprintf(stderr, "puttygen: option `-%s'" - " expects an argument\n", opt); - } else { - old_passphrase = readpassphrase(val); - if (!old_passphrase) + if (!val && argc > 1) + --argc, val = *++argv; + if (!val) { errs = true; - } + fprintf(stderr, "puttygen: option `-%s'" + " expects an argument\n", opt); + } else { + old_passphrase = readpassphrase(val); + if (!old_passphrase) + errs = true; + } } else if (!strcmp(opt, "-new-passphrase")) { - if (!val && argc > 1) - --argc, val = *++argv; - if (!val) { - errs = true; - fprintf(stderr, "puttygen: option `-%s'" - " expects an argument\n", opt); - } else { - new_passphrase = readpassphrase(val); - if (!new_passphrase) + if (!val && argc > 1) + --argc, val = *++argv; + if (!val) { errs = true; - } + fprintf(stderr, "puttygen: option `-%s'" + " expects an argument\n", opt); + } else { + new_passphrase = readpassphrase(val); + if (!new_passphrase) + errs = true; + } } else if (!strcmp(opt, "-random-device")) { - if (!val && argc > 1) - --argc, val = *++argv; - if (!val) { - errs = true; - fprintf(stderr, "puttygen: option `-%s'" - " expects an argument\n", opt); - } else { - random_device = val; - } + if (!val && argc > 1) + --argc, val = *++argv; + if (!val) { + errs = true; + fprintf(stderr, "puttygen: option `-%s'" + " expects an argument\n", opt); + } else { + random_device = val; + } } else if (!strcmp(opt, "-dump")) { outtype = TEXT; + } else if (!strcmp(opt, "-cert-info") || + !strcmp(opt, "-certinfo") || + !strcmp(opt, "-cert_info")) { + outtype = CERTINFO; } else if (!strcmp(opt, "-primes")) { if (!val && argc > 1) --argc, val = *++argv; @@ -383,6 +404,18 @@ int main(int argc, char **argv) } } else if (!strcmp(opt, "-strong-rsa")) { strong_rsa = true; + } else if (!strcmp(opt, "-certificate")) { + if (!val && argc > 1) + --argc, val = *++argv; + if (!val) { + errs = true; + fprintf(stderr, "puttygen: option `-%s'" + " expects an argument\n", opt); + } else { + certfile = val; + } + } else if (!strcmp(opt, "-remove-certificate")) { + remove_cert = true; } else if (!strcmp(opt, "-reencrypt")) { reencrypt = true; } else if (!strcmp(opt, "-ppk-param") || @@ -461,9 +494,9 @@ int main(int argc, char **argv) } } } else { - errs = true; - fprintf(stderr, - "puttygen: no such option `-%s'\n", opt); + errs = true; + fprintf(stderr, + "puttygen: no such option `-%s'\n", opt); } p = NULL; break; @@ -569,6 +602,8 @@ int main(int argc, char **argv) outtype = SSHCOM, sshver = 2; else if (!strcmp(p, "text")) outtype = TEXT; + else if (!strcmp(p, "cert-info")) + outtype = CERTINFO; else { fprintf(stderr, "puttygen: unknown output type `%s'\n", p); @@ -583,6 +618,10 @@ int main(int argc, char **argv) fptype = SSH_FPTYPE_MD5; else if (!strcmp(p, "sha256")) fptype = SSH_FPTYPE_SHA256; + else if (!strcmp(p, "md5-cert")) + fptype = SSH_FPTYPE_MD5_CERT; + else if (!strcmp(p, "sha256-cert")) + fptype = SSH_FPTYPE_SHA256_CERT; else { fprintf(stderr, "puttygen: unknown fingerprint " "type `%s'\n", p); @@ -790,7 +829,8 @@ int main(int argc, char **argv) outfiletmp = dupcat(outfile, ".tmp"); } - if (!change_passphrase && !comment && !reencrypt) { + if (!change_passphrase && !comment && !reencrypt && !certfile && + !remove_cert) { fprintf(stderr, "puttygen: this command would perform no useful" " action\n"); RETURN(1); @@ -840,6 +880,26 @@ int main(int argc, char **argv) RETURN(1); } + /* + * Check consistency properties relating to certificates. + */ + if (certfile && !(sshver == 2 && intype_has_private && + outtype_has_private && infile)) { + fprintf(stderr, "puttygen: certificates can only be added to " + "existing SSH-2 private key files\n"); + RETURN(1); + } + if (remove_cert && !(sshver == 2 && infile)) { + fprintf(stderr, "puttygen: certificates can only be removed from " + "existing SSH-2 key files\n"); + RETURN(1); + } + if (certfile && remove_cert) { + fprintf(stderr, "puttygen: cannot both add and remove a " + "certificate\n"); + RETURN(1); + } + /* ------------------------------------------------------------------ * Now we're ready to actually do some stuff. */ @@ -878,10 +938,10 @@ int main(int argc, char **argv) PrimeGenerationContext *pgc = primegen_new_context(primegen); if (keytype == DSA) { - struct dss_key *dsskey = snew(struct dss_key); - dsa_generate(dsskey, bits, pgc, &cmdgen_progress); + struct dsa_key *dsakey = snew(struct dsa_key); + dsa_generate(dsakey, bits, pgc, &cmdgen_progress); ssh2key = snew(ssh2_userkey); - ssh2key->key = &dsskey->sshk; + ssh2key->key = &dsakey->sshk; ssh1key = NULL; } else if (keytype == ECDSA) { struct ecdsa_key *ek = snew(struct ecdsa_key); @@ -941,16 +1001,16 @@ int main(int argc, char **argv) if (encrypted && load_encrypted) { if (!old_passphrase) { prompts_t *p = new_prompts(); - int ret; + SeatPromptResult spr; p->to_server = false; p->from_server = false; p->name = dupstr("SSH key passphrase"); add_prompt(p, dupstr("Enter passphrase to load key: "), false); - ret = console_get_userpass_input(p); - assert(ret >= 0); - if (!ret) { + spr = console_get_userpass_input(p); + assert(spr.kind != SPRK_INCOMPLETE); + if (spr_is_abort(spr)) { free_prompts(p); - perror("puttygen: unable to read passphrase"); + spr_error(spr); RETURN(1); } else { old_passphrase = prompt_get_result(p->prompts[0]); @@ -1073,6 +1133,124 @@ int main(int argc, char **argv) } /* + * Swap out the public key for a different one, if asked to. + */ + if (certfile) { + Filename *certfilename = filename_from_str(certfile); + LoadedFile *certfile_lf; + const char *error = NULL; + + if (!strcmp(certfile, "-")) + certfile_lf = lf_load_keyfile_fp(stdin, &error); + else + certfile_lf = lf_load_keyfile(certfilename, &error); + + filename_free(certfilename); + + if (!certfile_lf) { + fprintf(stderr, "puttygen: unable to load certificate file `%s': " + "%s\n", certfile, error); + RETURN(1); + } + + char *algname = NULL; + char *comment = NULL; + strbuf *pub = strbuf_new(); + if (!ppk_loadpub_s(BinarySource_UPCAST(certfile_lf), &algname, + BinarySink_UPCAST(pub), &comment, &error)) { + fprintf(stderr, "puttygen: unable to load certificate file `%s': " + "%s\n", certfile, error); + strbuf_free(pub); + sfree(algname); + sfree(comment); + lf_free(certfile_lf); + RETURN(1); + } + + lf_free(certfile_lf); + sfree(comment); + + const ssh_keyalg *alg = find_pubkey_alg(algname); + if (!alg) { + fprintf(stderr, "puttygen: certificate file `%s' has unsupported " + "algorithm name `%s'\n", certfile, algname); + strbuf_free(pub); + sfree(algname); + RETURN(1); + } + + sfree(algname); + + /* Check the two public keys match apart from certificates */ + strbuf *old_basepub = strbuf_new(); + ssh_key_public_blob(ssh_key_base_key(ssh2key->key), + BinarySink_UPCAST(old_basepub)); + + ssh_key *new_pubkey = ssh_key_new_pub(alg, ptrlen_from_strbuf(pub)); + strbuf *new_basepub = strbuf_new(); + ssh_key_public_blob(ssh_key_base_key(new_pubkey), + BinarySink_UPCAST(new_basepub)); + ssh_key_free(new_pubkey); + + bool match = ptrlen_eq_ptrlen(ptrlen_from_strbuf(old_basepub), + ptrlen_from_strbuf(new_basepub)); + strbuf_free(old_basepub); + strbuf_free(new_basepub); + + if (!match) { + fprintf(stderr, "puttygen: certificate in `%s' does not match " + "public key in `%s'\n", certfile, infile); + strbuf_free(pub); + RETURN(1); + } + + strbuf *priv = strbuf_new_nm(); + ssh_key_private_blob(ssh2key->key, BinarySink_UPCAST(priv)); + ssh_key *newkey = ssh_key_new_priv( + alg, ptrlen_from_strbuf(pub), ptrlen_from_strbuf(priv)); + strbuf_free(pub); + strbuf_free(priv); + + if (!newkey) { + fprintf(stderr, "puttygen: unable to combine certificate in `%s' " + "with private key\n", certfile); + RETURN(1); + } + + ssh_key_free(ssh2key->key); + ssh2key->key = newkey; + } else if (remove_cert) { + /* + * Removing a certificate can be meaningfully done to a pure + * public key blob, as well as a full key pair. + */ + if (ssh2key) { + ssh_key *newkey = ssh_key_clone(ssh_key_base_key(ssh2key->key)); + ssh_key_free(ssh2key->key); + ssh2key->key = newkey; + } else if (ssh2blob) { + ptrlen algname = pubkey_blob_to_alg_name( + ptrlen_from_strbuf(ssh2blob)); + + const ssh_keyalg *alg = find_pubkey_alg_len(algname); + + if (!alg) { + fprintf(stderr, "puttygen: input file `%s' has unsupported " + "algorithm name `%.*s'\n", infile, + PTRLEN_PRINTF(algname)); + RETURN(1); + } + + ssh_key *tmpkey = ssh_key_new_pub( + alg, ptrlen_from_strbuf(ssh2blob)); + strbuf_clear(ssh2blob); + ssh_key_public_blob(ssh_key_base_key(tmpkey), + BinarySink_UPCAST(ssh2blob)); + ssh_key_free(tmpkey); + } + } + + /* * Unless we're changing the passphrase, the old one (if any) is a * reasonable default. */ @@ -1090,18 +1268,18 @@ int main(int argc, char **argv) if (!new_passphrase && (change_passphrase || (keytype != NOKEYGEN && outtype != TEXT))) { prompts_t *p = new_prompts(); - int ret; + SeatPromptResult spr; p->to_server = false; p->from_server = false; p->name = dupstr("New SSH key passphrase"); add_prompt(p, dupstr("Enter passphrase to save key: "), false); add_prompt(p, dupstr("Re-enter passphrase to verify: "), false); - ret = console_get_userpass_input(p); - assert(ret >= 0); - if (!ret) { + spr = console_get_userpass_input(p); + assert(spr.kind != SPRK_INCOMPLETE); + if (spr_is_abort(spr)) { free_prompts(p); - perror("puttygen: unable to read new passphrase"); + spr_error(spr); RETURN(1); } else { if (strcmp(prompt_get_result_ref(p->prompts[0]), @@ -1164,29 +1342,29 @@ int main(int argc, char **argv) FILE *fp; if (outfile) { - fp = f_open(outfilename, "w", false); - if (!fp) { - fprintf(stderr, "unable to open output file\n"); - exit(1); - } + fp = f_open(outfilename, "w", false); + if (!fp) { + fprintf(stderr, "unable to open output file\n"); + exit(1); + } } else { - fp = stdout; + fp = stdout; } if (sshver == 1) { - ssh1_write_pubkey(fp, ssh1key); + ssh1_write_pubkey(fp, ssh1key); } else { - if (!ssh2blob) { - assert(ssh2key); - ssh2blob = strbuf_new(); - ssh_key_public_blob(ssh2key->key, BinarySink_UPCAST(ssh2blob)); - } - - ssh2_write_pubkey(fp, ssh2key ? ssh2key->comment : origcomment, - ssh2blob->s, ssh2blob->len, - (outtype == PUBLIC ? - SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 : - SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH)); + if (!ssh2blob) { + assert(ssh2key); + ssh2blob = strbuf_new(); + ssh_key_public_blob(ssh2key->key, BinarySink_UPCAST(ssh2blob)); + } + + ssh2_write_pubkey(fp, ssh2key ? ssh2key->comment : origcomment, + ssh2blob->s, ssh2blob->len, + (outtype == PUBLIC ? + SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 : + SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH)); } if (outfile) @@ -1200,26 +1378,26 @@ int main(int argc, char **argv) char *fingerprint; if (sshver == 1) { - assert(ssh1key); - fingerprint = rsa_ssh1_fingerprint(ssh1key); + assert(ssh1key); + fingerprint = rsa_ssh1_fingerprint(ssh1key); } else { - if (ssh2key) { - fingerprint = ssh2_fingerprint(ssh2key->key, fptype); - } else { - assert(ssh2blob); - fingerprint = ssh2_fingerprint_blob( - ptrlen_from_strbuf(ssh2blob), fptype); - } + if (ssh2key) { + fingerprint = ssh2_fingerprint(ssh2key->key, fptype); + } else { + assert(ssh2blob); + fingerprint = ssh2_fingerprint_blob( + ptrlen_from_strbuf(ssh2blob), fptype); + } } if (outfile) { - fp = f_open(outfilename, "w", false); - if (!fp) { - fprintf(stderr, "unable to open output file\n"); - exit(1); - } + fp = f_open(outfilename, "w", false); + if (!fp) { + fprintf(stderr, "unable to open output file\n"); + exit(1); + } } else { - fp = stdout; + fp = stdout; } fprintf(fp, "%s\n", fingerprint); if (outfile) @@ -1271,9 +1449,8 @@ int main(int argc, char **argv) } else { assert(ssh2blob); - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(ssh2blob)); - ptrlen algname = get_string(src); + ptrlen algname = pubkey_blob_to_alg_name( + ptrlen_from_strbuf(ssh2blob)); const ssh_keyalg *alg = find_pubkey_alg_len(algname); if (!alg) { fprintf(stderr, "puttygen: cannot extract key components " @@ -1304,16 +1481,55 @@ int main(int argc, char **argv) } 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); + key_component *comp = &kc->components[i]; + fprintf(fp, "%s=", comp->name); + switch (comp->type) { + case KCT_MPINT: { + char *hex = mp_get_hex(comp->mp); + fprintf(fp, "0x%s\n", 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)); + break; + } + case KCT_TEXT: + fputs("\"", fp); + write_c_string_literal(fp, ptrlen_from_strbuf(comp->str)); fputs("\"\n", fp); + break; + case KCT_BINARY: { + /* + * Display format for binary key components is to show + * them as base64, with a wrapper so that the actual + * printed string is along the lines of + * 'b64("aGVsbG8sIHdvcmxkCg==")'. + * + * That's a compromise between not being too verbose + * for a human reader, and still being reasonably + * friendly to people pasting the output of this + * 'puttygen --dump' option into Python code (which + * the format is designed to permit in general). + * + * Python users pasting a dump containing one of these + * will have to define a function 'b64' in advance + * which takes a string, which you can do most easily + * using this import statement, as seen in + * cryptsuite.py: + * + * from base64 import b64decode as b64 + */ + fputs("b64(\"", fp); + char b64[4]; + for (size_t j = 0; j < comp->str->len; j += 3) { + size_t len = comp->str->len - j; + if (len > 3) len = 3; + base64_encode_atom(comp->str->u + j, len, b64); + fwrite(b64, 1, 4, fp); + } + fputs("\")\n", fp); + break; + } + default: + unreachable("bad key component type"); } } @@ -1322,6 +1538,83 @@ int main(int argc, char **argv) key_components_free(kc); break; } + + case CERTINFO: { + if (sshver == 1) { + fprintf(stderr, "puttygen: SSH-1 keys cannot contain " + "certificates\n"); + RETURN(1); + } + + const ssh_keyalg *alg; + ssh_key *sk; + bool sk_allocated = false; + + if (ssh2key) { + sk = ssh2key->key; + alg = ssh_key_alg(sk); + } else { + assert(ssh2blob); + ptrlen algname = pubkey_blob_to_alg_name( + ptrlen_from_strbuf(ssh2blob)); + alg = find_pubkey_alg_len(algname); + if (!alg) { + fprintf(stderr, "puttygen: cannot extract certificate info " + "from public key of unknown type '%.*s'\n", + PTRLEN_PRINTF(algname)); + RETURN(1); + } + sk = ssh_key_new_pub(alg, ptrlen_from_strbuf(ssh2blob)); + if (!sk) { + fprintf(stderr, "puttygen: unable to decode public key\n"); + RETURN(1); + } + sk_allocated = true; + } + + if (!alg->is_certificate) { + fprintf(stderr, "puttygen: key is not a certificate\n"); + } else { + SeatDialogText *text = ssh_key_cert_info(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 (SeatDialogTextItem *item = text->items, + *end = item+text->nitems; item < end; item++) { + switch (item->type) { + case SDT_MORE_INFO_KEY: + fprintf(fp, "%s", item->text); + break; + case SDT_MORE_INFO_VALUE_SHORT: + fprintf(fp, ": %s\n", item->text); + break; + case SDT_MORE_INFO_VALUE_BLOB: + fprintf(fp, ":\n%s\n", item->text); + break; + default: + break; + } + } + + if (outfile) + fclose(fp); + + seat_dialog_text_free(text); + } + + if (sk_allocated) + ssh_key_free(sk); + break; + } } out: |