X-Git-Url: https://asedeno.scripts.mit.edu/gitweb/?a=blobdiff_plain;f=cmdgen.c;h=d6c1f72115ac3080b17e7750aa1ee15350637e08;hb=4ff22863d895cb7ebfced4cf923a012a614adaa8;hp=5e9d2d675666b2f1a383f9ead17725b446c2fbc0;hpb=509e84ed7715e2ad6803e16da242d32dbd1f4d25;p=PuTTY.git diff --git a/cmdgen.c b/cmdgen.c index 5e9d2d67..d6c1f721 100644 --- a/cmdgen.c +++ b/cmdgen.c @@ -2,18 +2,6 @@ * cmdgen.c - command-line form of PuTTYgen */ -/* - * TODO: - * - * - Test thoroughly. - * + a neat way to do this might be to have a -DTESTMODE for - * this file, which #defines console_get_line and - * get_random_noise to different names in order to be able to - * link them to test stubs rather than the real ones. That - * way I can have a test rig which checks whether passphrases - * are being prompted for. - */ - #define PUTTY_DO_GLOBALS #include @@ -22,18 +10,57 @@ #include #include #include +#include +#include #include "putty.h" #include "ssh.h" -#ifdef TESTMODE +#ifdef TEST_CMDGEN +/* + * This section overrides some definitions below for test purposes. + * When compiled with -DTEST_CMDGEN (as cgtest.c will do): + * + * - Calls to get_random_data() are replaced with the diagnostic + * function below (I #define the name so that I can still link + * with the original set of modules without symbol clash), in + * order to avoid depleting the test system's /dev/random + * unnecessarily. + * + * - Calls to console_get_userpass_input() are replaced with the + * diagnostic function below, so that I can run tests in an + * automated manner and provide their interactive passphrase + * inputs. + * + * - main() is renamed to cmdgen_main(); at the bottom of the file + * I define another main() which calls the former repeatedly to + * run tests. + */ #define get_random_data get_random_data_diagnostic -char *get_random_data(int len) +char *get_random_data(int len, const char *device) { char *buf = snewn(len, char); memset(buf, 'x', len); return buf; } +#define console_get_userpass_input console_get_userpass_input_diagnostic +int nprompts, promptsgot; +const char *prompts[3]; +int console_get_userpass_input(prompts_t *p, unsigned char *in, int inlen) +{ + size_t i; + int ret = 1; + for (i = 0; i < p->n_prompts; i++) { + if (promptsgot < nprompts) { + p->prompts[i]->result = dupstr(prompts[promptsgot++]); + } else { + promptsgot++; /* track number of requests anyway */ + ret = 0; + } + } + return ret; +} +#define main cmdgen_main #endif struct progress { @@ -65,7 +92,7 @@ static void no_progress(void *param, int action, int phase, int iprogress) { } -void modalfatalbox(char *p, ...) +void modalfatalbox(const char *p, ...) { va_list ap; fprintf(stderr, "FATAL ERROR: "); @@ -76,6 +103,16 @@ void modalfatalbox(char *p, ...) cleanup_exit(1); } +void nonfatal(const char *p, ...) +{ + va_list ap; + fprintf(stderr, "ERROR: "); + va_start(ap, p); + vfprintf(stderr, p, ap); + va_end(ap); + fputc('\n', stderr); +} + /* * Stubs to let everything else link sensibly. */ @@ -92,19 +129,21 @@ void sk_cleanup(void) void showversion(void) { - char *verstr = dupstr(ver); - verstr[0] = tolower(verstr[0]); - printf("PuTTYgen %s\n", verstr); - sfree(verstr); + char *buildinfo_text = buildinfo("\n"); + printf("puttygen: %s\n%s", ver, buildinfo_text); + sfree(buildinfo_text); } -void usage(void) +void usage(int standalone) { fprintf(stderr, "Usage: puttygen ( keyfile | -t type [ -b bits ] )\n" - " [ -C comment ] [ -P ]\n" + " [ -C comment ] [ -P ] [ -q ]\n" " [ -o output-keyfile ] [ -O type | -l | -L" " | -p ]\n"); + if (standalone) + fprintf(stderr, + "Use \"puttygen --help\" for more detail.\n"); } void help(void) @@ -114,77 +153,37 @@ void help(void) * start with that, plus a version heading. */ showversion(); - usage(); + usage(FALSE); fprintf(stderr, - " -t specify key type when generating (rsa, dsa, rsa1)\n" + " -t specify key type when generating (ed25519, ecdsa, rsa, " + "dsa, rsa1)\n" " -b specify number of bits when generating key\n" " -C change or specify key comment\n" " -P change key passphrase\n" + " -q quiet: do not display progress bar\n" " -O specify output type:\n" " private output PuTTY private key format\n" " private-openssh export OpenSSH private key\n" + " private-openssh-new export OpenSSH private key " + "(force new file format)\n" " private-sshcom export ssh.com private key\n" - " public standard / ssh.com public key\n" + " public RFC 4716 / ssh.com public key\n" " public-openssh OpenSSH public key\n" " fingerprint output the key fingerprint\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" + " --old-passphrase file\n" + " specify file containing old key passphrase\n" + " --new-passphrase file\n" + " specify file containing new key passphrase\n" + " --random-device device\n" + " specify device to read entropy from (e.g. /dev/urandom)\n" ); } -static int save_ssh2_pubkey(char *filename, char *comment, - void *v_pub_blob, int pub_len) -{ - unsigned char *pub_blob = (unsigned char *)v_pub_blob; - char *p; - int i, column; - FILE *fp; - - if (filename) { - fp = fopen(filename, "wb"); - if (!fp) - return 0; - } else - fp = stdout; - - fprintf(fp, "---- BEGIN SSH2 PUBLIC KEY ----\n"); - - if (comment) { - fprintf(fp, "Comment: \""); - for (p = comment; *p; p++) { - if (*p == '\\' || *p == '\"') - fputc('\\', fp); - fputc(*p, fp); - } - fprintf(fp, "\"\n"); - } - - i = 0; - column = 0; - while (i < pub_len) { - char buf[5]; - int n = (pub_len - i < 3 ? pub_len - i : 3); - base64_encode_atom(pub_blob + i, n, buf); - i += n; - buf[4] = '\0'; - fputs(buf, fp); - if (++column >= 16) { - fputc('\n', fp); - column = 0; - } - } - if (column > 0) - fputc('\n', fp); - - fprintf(fp, "---- END SSH2 PUBLIC KEY ----\n"); - if (filename) - fclose(fp); - return 1; -} - -static void move(char *from, char *to) +static int move(char *from, char *to) { int ret; @@ -198,40 +197,45 @@ static void move(char *from, char *to) } if (ret) { perror("puttygen: cannot move new file on to old one"); - exit(1); + return FALSE; } + return TRUE; } -static char *blobfp(char *alg, int bits, char *blob, int bloblen) +static char *readpassphrase(const char *filename) { - char buffer[128]; - unsigned char digest[16]; - struct MD5Context md5c; - int i; - - MD5Init(&md5c); - MD5Update(&md5c, blob, bloblen); - MD5Final(digest, &md5c); - - sprintf(buffer, "%s ", alg); - if (bits > 0) - sprintf(buffer + strlen(buffer), "%d ", bits); - for (i = 0; i < 16; i++) - sprintf(buffer + strlen(buffer), "%s%02x", i ? ":" : "", - digest[i]); + FILE *fp; + char *line; - return dupstr(buffer); + fp = fopen(filename, "r"); + if (!fp) { + fprintf(stderr, "puttygen: cannot open %s: %s\n", + filename, strerror(errno)); + return NULL; + } + line = fgetline(fp); + if (line) + line[strcspn(line, "\r\n")] = '\0'; + else if (ferror(fp)) + fprintf(stderr, "puttygen: error reading from %s: %s\n", + filename, strerror(errno)); + else /* empty file */ + line = dupstr(""); + fclose(fp); + return line; } +#define DEFAULT_RSADSA_BITS 2048 + int main(int argc, char **argv) { char *infile = NULL; - Filename infilename; - enum { NOKEYGEN, RSA1, RSA2, DSA } keytype = NOKEYGEN; + Filename *infilename = NULL, *outfilename = NULL; + enum { NOKEYGEN, RSA1, RSA2, DSA, ECDSA, ED25519 } keytype = NOKEYGEN; char *outfile = NULL, *outfiletmp = NULL; - Filename outfilename; - enum { PRIVATE, PUBLIC, PUBLICO, FP, OPENSSH, SSHCOM } outtype = PRIVATE; - int bits = 1024; + enum { PRIVATE, PUBLIC, PUBLICO, FP, OPENSSH_AUTO, + OPENSSH_NEW, SSHCOM } outtype = PRIVATE; + int bits = -1; char *comment = NULL, *origcomment = NULL; int change_passphrase = FALSE; int errs = FALSE, nogo = FALSE; @@ -239,12 +243,14 @@ int main(int argc, char **argv) int sshver = 0; struct ssh2_userkey *ssh2key = NULL; struct RSAKey *ssh1key = NULL; - char *ssh2blob = NULL, *ssh2alg = NULL; + unsigned char *ssh2blob = NULL; + char *ssh2alg = NULL; const struct ssh_signkey *ssh2algf = NULL; int ssh2bloblen; - char *passphrase = NULL; + char *old_passphrase = NULL, *new_passphrase = NULL; int load_encrypted; progfn_t progressfn = is_interactive() ? progress_update : no_progress; + const char *random_device = NULL; /* ------------------------------------------------------------------ * Parse the command line to figure out what we've been asked to do. @@ -255,7 +261,7 @@ int main(int argc, char **argv) * return success. */ if (argc <= 1) { - usage(); + usage(TRUE); return 0; } @@ -284,28 +290,74 @@ int main(int argc, char **argv) *p++ = '\0'; val = p; } else - val = NULL; + val = NULL; + if (!strcmp(opt, "-help")) { - 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")) { - showversion(); - nogo = TRUE; - } - /* - * A sample option requiring an argument: - * - * else if (!strcmp(opt, "-output")) { - * if (!val) - * errs = TRUE, error(err_optnoarg, opt); - * else - * ofile = val; - * } - */ - else { + 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; + } + } 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) + 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) + 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; + } + } else { errs = TRUE; fprintf(stderr, - "puttygen: no such option `--%s'\n", opt); + "puttygen: no such option `-%s'\n", opt); } } p = NULL; @@ -373,6 +425,10 @@ int main(int argc, char **argv) keytype = RSA1, sshver = 1; else if (!strcmp(p, "dsa") || !strcmp(p, "dss")) keytype = DSA, sshver = 2; + else if (!strcmp(p, "ecdsa")) + keytype = ECDSA, sshver = 2; + else if (!strcmp(p, "ed25519")) + keytype = ED25519, sshver = 2; else { fprintf(stderr, "puttygen: unknown key type `%s'\n", p); @@ -395,7 +451,9 @@ int main(int argc, char **argv) else if (!strcmp(p, "fingerprint")) outtype = FP; else if (!strcmp(p, "private-openssh")) - outtype = OPENSSH, sshver = 2; + outtype = OPENSSH_AUTO, sshver = 2; + else if (!strcmp(p, "private-openssh-new")) + outtype = OPENSSH_NEW, sshver = 2; else if (!strcmp(p, "private-sshcom")) outtype = SSHCOM, sshver = 2; else { @@ -433,6 +491,47 @@ int main(int argc, char **argv) } } + if (bits == -1) { + /* + * No explicit key size was specified. Default varies + * depending on key type. + */ + switch (keytype) { + case ECDSA: + bits = 384; + break; + case ED25519: + bits = 256; + break; + default: + bits = DEFAULT_RSADSA_BITS; + break; + } + } + + if (keytype == ECDSA && (bits != 256 && bits != 384 && bits != 521)) { + fprintf(stderr, "puttygen: invalid bits for ECDSA, choose 256, 384 or 521\n"); + errs = TRUE; + } + + if (keytype == ED25519 && (bits != 256)) { + fprintf(stderr, "puttygen: invalid bits for ED25519, choose 256\n"); + errs = TRUE; + } + + if (keytype == RSA2 || keytype == RSA1 || keytype == DSA) { + if (bits < 256) { + fprintf(stderr, "puttygen: cannot generate %s keys shorter than" + " 256 bits\n", (keytype == DSA ? "DSA" : "RSA")); + errs = TRUE; + } else if (bits < DEFAULT_RSADSA_BITS) { + fprintf(stderr, "puttygen: warning: %s keys shorter than" + " %d bits are probably not secure\n", + (keytype == DSA ? "DSA" : "RSA"), DEFAULT_RSADSA_BITS); + /* but this is just a warning, so proceed anyway */ + } + } + if (errs) return 1; @@ -444,7 +543,7 @@ int main(int argc, char **argv) * ones, print the usage message and return failure. */ if (!infile && keytype == NOKEYGEN) { - usage(); + usage(TRUE); return 1; } @@ -456,11 +555,22 @@ int main(int argc, char **argv) * Bomb out if we've been asked to both load and generate a * key. */ - if (keytype != NOKEYGEN && intype) { + if (keytype != NOKEYGEN && infile) { fprintf(stderr, "puttygen: cannot both load and generate a key\n"); return 1; } + /* + * We must save the private part when generating a new key. + */ + if (keytype != NOKEYGEN && + (outtype != PRIVATE && outtype != OPENSSH_AUTO && + outtype != OPENSSH_NEW && outtype != SSHCOM)) { + fprintf(stderr, "puttygen: this would generate a new key but " + "discard the private part\n"); + return 1; + } + /* * Analyse the type of the input file, in case this affects our * course of action. @@ -468,31 +578,9 @@ int main(int argc, char **argv) if (infile) { infilename = filename_from_str(infile); - intype = key_type(&infilename); + intype = key_type(infilename); switch (intype) { - /* - * It would be nice here to be able to load _public_ - * key files, in any of a number of forms, and (a) - * convert them to other public key types, (b) print - * out their fingerprints. Or, I suppose, for real - * orthogonality, (c) change their comment! - * - * In fact this opens some interesting possibilities. - * Suppose ssh2_userkey_loadpub() were able to load - * public key files as well as extracting the public - * key from private ones. And suppose I did the thing - * I've been wanting to do, where specifying a - * particular private key file for authentication - * causes any _other_ key in the agent to be discarded. - * Then, if you had an agent forwarded to the machine - * you were running Unix PuTTY or Plink on, and you - * needed to specify which of the keys in the agent it - * should use, you could do that by supplying a - * _public_ key file, thus not needing to trust even - * your encrypted private key file to the network. Ooh! - */ - case SSH_KEYTYPE_UNOPENABLE: case SSH_KEYTYPE_UNKNOWN: fprintf(stderr, "puttygen: unable to load file `%s': %s\n", @@ -500,8 +588,9 @@ int main(int argc, char **argv) return 1; case SSH_KEYTYPE_SSH1: + case SSH_KEYTYPE_SSH1_PUBLIC: if (sshver == 2) { - fprintf(stderr, "puttygen: conversion from SSH1 to SSH2 keys" + fprintf(stderr, "puttygen: conversion from SSH-1 to SSH-2 keys" " not supported\n"); return 1; } @@ -509,15 +598,22 @@ int main(int argc, char **argv) break; case SSH_KEYTYPE_SSH2: - case SSH_KEYTYPE_OPENSSH: + case SSH_KEYTYPE_SSH2_PUBLIC_RFC4716: + case SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH: + case SSH_KEYTYPE_OPENSSH_PEM: + case SSH_KEYTYPE_OPENSSH_NEW: case SSH_KEYTYPE_SSHCOM: if (sshver == 1) { - fprintf(stderr, "puttygen: conversion from SSH2 to SSH1 keys" + fprintf(stderr, "puttygen: conversion from SSH-2 to SSH-1 keys" " not supported\n"); return 1; } sshver = 2; break; + + case SSH_KEYTYPE_OPENSSH_AUTO: + default: + assert(0 && "Should never see these types on an input file"); } } @@ -533,11 +629,12 @@ int main(int argc, char **argv) */ if ((intype == SSH_KEYTYPE_SSH1 && outtype == PRIVATE) || (intype == SSH_KEYTYPE_SSH2 && outtype == PRIVATE) || - (intype == SSH_KEYTYPE_OPENSSH && outtype == OPENSSH) || + (intype == SSH_KEYTYPE_OPENSSH_PEM && outtype == OPENSSH_AUTO) || + (intype == SSH_KEYTYPE_OPENSSH_NEW && outtype == OPENSSH_NEW) || (intype == SSH_KEYTYPE_SSHCOM && outtype == SSHCOM)) { if (!outfile) { outfile = infile; - outfiletmp = dupcat(outfile, ".tmp"); + outfiletmp = dupcat(outfile, ".tmp", NULL); } if (!change_passphrase && !comment) { @@ -551,7 +648,8 @@ int main(int argc, char **argv) * Bomb out rather than automatically choosing to write * a private key file to stdout. */ - if (outtype==PRIVATE || outtype==OPENSSH || outtype==SSHCOM) { + if (outtype == PRIVATE || outtype == OPENSSH_AUTO || + outtype == OPENSSH_NEW || outtype == SSHCOM) { fprintf(stderr, "puttygen: need to specify an output file\n"); return 1; } @@ -564,12 +662,23 @@ int main(int argc, char **argv) * out a private key format, or (b) the entire input key file * is encrypted. */ - if (outtype == PRIVATE || outtype == OPENSSH || outtype == SSHCOM || - intype == SSH_KEYTYPE_OPENSSH || intype == SSH_KEYTYPE_SSHCOM) + if (outtype == PRIVATE || outtype == OPENSSH_AUTO || + outtype == OPENSSH_NEW || outtype == SSHCOM || + intype == SSH_KEYTYPE_OPENSSH_PEM || + intype == SSH_KEYTYPE_OPENSSH_NEW || + intype == SSH_KEYTYPE_SSHCOM) load_encrypted = TRUE; else load_encrypted = FALSE; + if (load_encrypted && (intype == SSH_KEYTYPE_SSH1_PUBLIC || + intype == SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 || + intype == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH)) { + fprintf(stderr, "puttygen: cannot perform this action on a " + "public-key-only input file\n"); + return 1; + } + /* ------------------------------------------------------------------ * Now we're ready to actually do some stuff. */ @@ -580,24 +689,31 @@ int main(int argc, char **argv) if (keytype != NOKEYGEN) { char *entropy; char default_comment[80]; - time_t t; - struct tm *tm; + struct tm tm; struct progress prog; prog.phase = -1; prog.current = -1; - time(&t); - tm = localtime(&t); + tm = ltime(); if (keytype == DSA) - strftime(default_comment, 30, "dsa-key-%Y%m%d", tm); + strftime(default_comment, 30, "dsa-key-%Y%m%d", &tm); + else if (keytype == ECDSA) + strftime(default_comment, 30, "ecdsa-key-%Y%m%d", &tm); + else if (keytype == ED25519) + strftime(default_comment, 30, "ed25519-key-%Y%m%d", &tm); else - strftime(default_comment, 30, "rsa-key-%Y%m%d", tm); + strftime(default_comment, 30, "rsa-key-%Y%m%d", &tm); - random_init(); - entropy = get_random_data(bits / 8); + random_ref(); + entropy = get_random_data(bits / 8, random_device); + if (!entropy) { + fprintf(stderr, "puttygen: failed to collect entropy, " + "could not generate key\n"); + return 1; + } random_add_heavynoise(entropy, bits / 8); - memset(entropy, 0, bits/8); + smemclr(entropy, bits/8); sfree(entropy); if (keytype == DSA) { @@ -607,9 +723,24 @@ int main(int argc, char **argv) ssh2key->data = dsskey; ssh2key->alg = &ssh_dss; ssh1key = NULL; + } else if (keytype == ECDSA) { + struct ec_key *ec = snew(struct ec_key); + ec_generate(ec, bits, progressfn, &prog); + ssh2key = snew(struct ssh2_userkey); + ssh2key->data = ec; + ssh2key->alg = ec->signalg; + ssh1key = NULL; + } else if (keytype == ED25519) { + struct ec_key *ec = snew(struct ec_key); + ec_edgenerate(ec, bits, progressfn, &prog); + ssh2key = snew(struct ssh2_userkey); + ssh2key->data = ec; + ssh2key->alg = &ssh_ecdsa_ed25519; + ssh1key = NULL; } else { struct RSAKey *rsakey = snew(struct RSAKey); rsa_generate(rsakey, bits, progressfn, &prog); + rsakey->comment = NULL; if (keytype == RSA1) { ssh1key = rsakey; } else { @@ -635,65 +766,102 @@ int main(int argc, char **argv) * Find out whether the input key is encrypted. */ if (intype == SSH_KEYTYPE_SSH1) - encrypted = rsakey_encrypted(&infilename, &origcomment); + encrypted = rsakey_encrypted(infilename, &origcomment); else if (intype == SSH_KEYTYPE_SSH2) - encrypted = ssh2_userkey_encrypted(&infilename, &origcomment); + encrypted = ssh2_userkey_encrypted(infilename, &origcomment); else - encrypted = import_encrypted(&infilename, intype, &origcomment); + encrypted = import_encrypted(infilename, intype, &origcomment); /* * If so, ask for a passphrase. */ if (encrypted && load_encrypted) { - passphrase = snewn(512, char); - if (!console_get_line("Enter passphrase to load key: ", - passphrase, 512, TRUE)) { - perror("puttygen: unable to read passphrase"); - return 1; + if (!old_passphrase) { + prompts_t *p = new_prompts(NULL); + int ret; + p->to_server = FALSE; + p->name = dupstr("SSH key passphrase"); + add_prompt(p, dupstr("Enter passphrase to load key: "), FALSE); + ret = console_get_userpass_input(p, NULL, 0); + assert(ret >= 0); + if (!ret) { + free_prompts(p); + perror("puttygen: unable to read passphrase"); + return 1; + } else { + old_passphrase = dupstr(p->prompts[0]->result); + free_prompts(p); + } } } else { - passphrase = NULL; + old_passphrase = NULL; } switch (intype) { int ret; case SSH_KEYTYPE_SSH1: + case SSH_KEYTYPE_SSH1_PUBLIC: ssh1key = snew(struct RSAKey); if (!load_encrypted) { void *vblob; - char *blob; - int n, bloblen; + unsigned char *blob; + int n, l, bloblen; - ret = rsakey_pubblob(&infilename, &vblob, &bloblen, &error); - blob = (char *)vblob; + ret = rsakey_pubblob(infilename, &vblob, &bloblen, + &origcomment, &error); + blob = (unsigned char *)vblob; n = 4; /* skip modulus bits */ - n += ssh1_read_bignum(blob + n, &ssh1key->exponent); - n += ssh1_read_bignum(blob + n, &ssh1key->modulus); - ssh1key->comment = NULL; + + l = ssh1_read_bignum(blob + n, bloblen - n, + &ssh1key->exponent); + if (l < 0) { + error = "SSH-1 public key blob was too short"; + } else { + n += l; + l = ssh1_read_bignum(blob + n, bloblen - n, + &ssh1key->modulus); + if (l < 0) { + error = "SSH-1 public key blob was too short"; + } else + n += l; + } + ssh1key->comment = dupstr(origcomment); + ssh1key->private_exponent = NULL; + ssh1key->p = NULL; + ssh1key->q = NULL; + ssh1key->iqmp = NULL; } else { - ret = loadrsakey(&infilename, ssh1key, passphrase, &error); + ret = loadrsakey(infilename, ssh1key, old_passphrase, &error); } - if (ret) + if (ret > 0) error = NULL; else if (!error) error = "unknown error"; break; case SSH_KEYTYPE_SSH2: + case SSH_KEYTYPE_SSH2_PUBLIC_RFC4716: + case SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH: if (!load_encrypted) { - ssh2blob = ssh2_userkey_loadpub(&infilename, &ssh2alg, - &ssh2bloblen, &error); - ssh2algf = find_pubkey_alg(ssh2alg); - if (ssh2algf) - bits = ssh2algf->pubkey_bits(ssh2blob, ssh2bloblen); - else - bits = -1; + ssh2blob = ssh2_userkey_loadpub(infilename, &ssh2alg, + &ssh2bloblen, &origcomment, + &error); + if (ssh2blob) { + ssh2algf = find_pubkey_alg(ssh2alg); + if (ssh2algf) + bits = ssh2algf->pubkey_bits(ssh2algf, + ssh2blob, ssh2bloblen); + else + bits = -1; + } + sfree(ssh2alg); } else { - ssh2key = ssh2_load_userkey(&infilename, passphrase, &error); + ssh2key = ssh2_load_userkey(infilename, old_passphrase, + &error); } - if (ssh2key || ssh2blob) + if ((ssh2key && ssh2key != SSH2_WRONG_PASSPHRASE) || ssh2blob) error = NULL; else if (!error) { if (ssh2key == SSH2_WRONG_PASSPHRASE) @@ -703,17 +871,17 @@ int main(int argc, char **argv) } break; - case SSH_KEYTYPE_OPENSSH: + case SSH_KEYTYPE_OPENSSH_PEM: + case SSH_KEYTYPE_OPENSSH_NEW: case SSH_KEYTYPE_SSHCOM: - ssh2key = import_ssh2(&infilename, intype, passphrase); - if (ssh2key) - error = NULL; - else if (!error) { - if (ssh2key == SSH2_WRONG_PASSPHRASE) - error = "wrong passphrase"; + ssh2key = import_ssh2(infilename, intype, old_passphrase, &error); + if (ssh2key) { + if (ssh2key != SSH2_WRONG_PASSPHRASE) + error = NULL; else - error = "unknown error"; - } + error = "wrong passphrase"; + } else if (!error) + error = "unknown error"; break; default: @@ -742,38 +910,45 @@ int main(int argc, char **argv) } } + /* + * Unless we're changing the passphrase, the old one (if any) is a + * reasonable default. + */ + if (!change_passphrase && old_passphrase && !new_passphrase) + new_passphrase = dupstr(old_passphrase); + /* * Prompt for a new passphrase if we have been asked to, or if * we have just generated a key. */ - if (change_passphrase || keytype != NOKEYGEN) { - char *passphrase2; - - if (passphrase) { - memset(passphrase, 0, strlen(passphrase)); - sfree(passphrase); - } + if (!new_passphrase && (change_passphrase || keytype != NOKEYGEN)) { + prompts_t *p = new_prompts(NULL); + int ret; - passphrase = snewn(512, char); - passphrase2 = snewn(512, char); - if (!console_get_line("Enter passphrase to save key: ", - passphrase, 512, TRUE) || - !console_get_line("Re-enter passphrase to verify: ", - passphrase2, 512, TRUE)) { + p->to_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, NULL, 0); + assert(ret >= 0); + if (!ret) { + free_prompts(p); perror("puttygen: unable to read new passphrase"); return 1; - } - if (strcmp(passphrase, passphrase2)) { - fprintf(stderr, "puttygen: passphrases do not match\n"); - return 1; - } - memset(passphrase2, 0, strlen(passphrase2)); - sfree(passphrase2); - if (!*passphrase) { - sfree(passphrase); - passphrase = NULL; + } else { + if (strcmp(p->prompts[0]->result, p->prompts[1]->result)) { + free_prompts(p); + fprintf(stderr, "puttygen: passphrases do not match\n"); + return 1; + } + new_passphrase = dupstr(p->prompts[0]->result); + free_prompts(p); } } + if (new_passphrase && !*new_passphrase) { + sfree(new_passphrase); + new_passphrase = NULL; + } /* * Write output. @@ -789,104 +964,59 @@ int main(int argc, char **argv) outfilename = filename_from_str(outfile ? outfile : ""); switch (outtype) { - int ret; + int ret, real_outtype; case PRIVATE: if (sshver == 1) { assert(ssh1key); - ret = saversakey(&outfilename, ssh1key, passphrase); + ret = saversakey(outfilename, ssh1key, new_passphrase); if (!ret) { - fprintf(stderr, "puttygen: unable to save SSH1 private key\n"); + fprintf(stderr, "puttygen: unable to save SSH-1 private key\n"); return 1; } } else { assert(ssh2key); - ret = ssh2_save_userkey(&outfilename, ssh2key, passphrase); + ret = ssh2_save_userkey(outfilename, ssh2key, new_passphrase); if (!ret) { - fprintf(stderr, "puttygen: unable to save SSH2 private key\n"); + fprintf(stderr, "puttygen: unable to save SSH-2 private key\n"); return 1; } } - if (outfiletmp) - move(outfiletmp, outfile); + if (outfiletmp) { + if (!move(outfiletmp, outfile)) + return 1; /* rename failed */ + } break; case PUBLIC: case PUBLICO: - if (sshver == 1) { - FILE *fp; - char *dec1, *dec2; - - assert(ssh1key); - - if (outfile) - fp = f_open(outfilename, "w"); - else - fp = stdout; - dec1 = bignum_decimal(ssh1key->exponent); - dec2 = bignum_decimal(ssh1key->modulus); - fprintf(fp, "%d %s %s %s\n", bignum_bitcount(ssh1key->modulus), - dec1, dec2, ssh1key->comment); - sfree(dec1); - sfree(dec2); - if (outfile) - fclose(fp); - } else if (outtype == PUBLIC) { - if (!ssh2blob) { - assert(ssh2key); - ssh2blob = ssh2key->alg->public_blob(ssh2key->data, - &ssh2bloblen); - } - save_ssh2_pubkey(outfile, ssh2key ? ssh2key->comment : origcomment, - ssh2blob, ssh2bloblen); - } else if (outtype == PUBLICO) { - char *buffer, *p; - int i; - FILE *fp; + { + FILE *fp; + + if (outfile) + fp = f_open(outfilename, "w", FALSE); + else + fp = stdout; + + if (sshver == 1) { + ssh1_write_pubkey(fp, ssh1key); + } else { + if (!ssh2blob) { + assert(ssh2key); + ssh2blob = ssh2key->alg->public_blob(ssh2key->data, + &ssh2bloblen); + } + + ssh2_write_pubkey(fp, ssh2key ? ssh2key->comment : origcomment, + ssh2blob, ssh2bloblen, + (outtype == PUBLIC ? + SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 : + SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH)); + } - if (!ssh2blob) { - assert(ssh2key); - ssh2blob = ssh2key->alg->public_blob(ssh2key->data, - &ssh2bloblen); - } - if (!ssh2alg) { - assert(ssh2key); - ssh2alg = ssh2key->alg->name; - } - if (ssh2key) - comment = ssh2key->comment; - else - comment = origcomment; - - buffer = snewn(strlen(ssh2alg) + - 4 * ((ssh2bloblen+2) / 3) + - strlen(comment) + 3, char); - strcpy(buffer, ssh2alg); - p = buffer + strlen(buffer); - *p++ = ' '; - i = 0; - while (i < ssh2bloblen) { - int n = (ssh2bloblen - i < 3 ? ssh2bloblen - i : 3); - base64_encode_atom(ssh2blob + i, n, p); - i += n; - p += 4; - } - if (*comment) { - *p++ = ' '; - strcpy(p, comment); - } else - *p++ = '\0'; - - if (outfile) - fp = f_open(outfilename, "w"); - else - fp = stdout; - fprintf(fp, "%s\n", buffer); if (outfile) fclose(fp); - - sfree(buffer); - } + } break; case FP: @@ -900,15 +1030,16 @@ int main(int argc, char **argv) rsa_fingerprint(fingerprint, 128, ssh1key); } else { if (ssh2key) { - fingerprint = ssh2key->alg->fingerprint(ssh2key->data); + fingerprint = ssh2_fingerprint(ssh2key->alg, + ssh2key->data); } else { assert(ssh2blob); - fingerprint = blobfp(ssh2alg, bits, ssh2blob, ssh2bloblen); + fingerprint = ssh2_fingerprint_blob(ssh2blob, ssh2bloblen); } } if (outfile) - fp = f_open(outfilename, "w"); + fp = f_open(outfilename, "w", FALSE); else fp = stdout; fprintf(fp, "%s\n", fingerprint); @@ -919,23 +1050,45 @@ int main(int argc, char **argv) } break; - case OPENSSH: + case OPENSSH_AUTO: + case OPENSSH_NEW: case SSHCOM: assert(sshver == 2); assert(ssh2key); - ret = export_ssh2(&outfilename, outtype, ssh2key, passphrase); + random_ref(); /* both foreign key types require randomness, + * for IV or padding */ + switch (outtype) { + case OPENSSH_AUTO: + real_outtype = SSH_KEYTYPE_OPENSSH_AUTO; + break; + case OPENSSH_NEW: + real_outtype = SSH_KEYTYPE_OPENSSH_NEW; + break; + case SSHCOM: + real_outtype = SSH_KEYTYPE_SSHCOM; + break; + default: + assert(0 && "control flow goof"); + } + ret = export_ssh2(outfilename, real_outtype, ssh2key, new_passphrase); if (!ret) { fprintf(stderr, "puttygen: unable to export key\n"); return 1; } - if (outfiletmp) - move(outfiletmp, outfile); + if (outfiletmp) { + if (!move(outfiletmp, outfile)) + return 1; /* rename failed */ + } break; } - if (passphrase) { - memset(passphrase, 0, strlen(passphrase)); - sfree(passphrase); + if (old_passphrase) { + smemclr(old_passphrase, strlen(old_passphrase)); + sfree(old_passphrase); + } + if (new_passphrase) { + smemclr(new_passphrase, strlen(new_passphrase)); + sfree(new_passphrase); } if (ssh1key) @@ -947,3 +1100,550 @@ int main(int argc, char **argv) return 0; } + +#ifdef TEST_CMDGEN + +#undef main + +#include + +int passes, fails; + +void setup_passphrases(char *first, ...) +{ + va_list ap; + char *next; + + nprompts = 0; + if (first) { + prompts[nprompts++] = first; + va_start(ap, first); + while ((next = va_arg(ap, char *)) != NULL) { + assert(nprompts < lenof(prompts)); + prompts[nprompts++] = next; + } + va_end(ap); + } +} + +void test(int retval, ...) +{ + va_list ap; + int i, argc, ret; + char **argv; + + argc = 0; + va_start(ap, retval); + while (va_arg(ap, char *) != NULL) + argc++; + va_end(ap); + + argv = snewn(argc+1, char *); + va_start(ap, retval); + for (i = 0; i <= argc; i++) + argv[i] = va_arg(ap, char *); + va_end(ap); + + promptsgot = 0; + ret = cmdgen_main(argc, argv); + + if (ret != retval) { + printf("FAILED retval (exp %d got %d):", retval, ret); + for (i = 0; i < argc; i++) + printf(" %s", argv[i]); + printf("\n"); + fails++; + } else if (promptsgot != nprompts) { + printf("FAILED nprompts (exp %d got %d):", nprompts, promptsgot); + for (i = 0; i < argc; i++) + printf(" %s", argv[i]); + printf("\n"); + fails++; + } else { + passes++; + } +} + +void filecmp(char *file1, char *file2, char *fmt, ...) +{ + /* + * Ideally I should do file comparison myself, to maximise the + * portability of this test suite once this application begins + * running on non-Unix platforms. For the moment, though, + * calling Unix diff is perfectly adequate. + */ + char *buf; + int ret; + + buf = dupprintf("diff -q '%s' '%s'", file1, file2); + ret = system(buf); + sfree(buf); + + if (ret) { + va_list ap; + + printf("FAILED diff (ret=%d): ", ret); + + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + + printf("\n"); + + fails++; + } else + passes++; +} + +char *cleanup_fp(char *s) +{ + char *p; + + if (!strncmp(s, "ssh-", 4)) { + s += strcspn(s, " \n\t"); + s += strspn(s, " \n\t"); + } + + p = s; + s += strcspn(s, " \n\t"); + s += strspn(s, " \n\t"); + s += strcspn(s, " \n\t"); + + return dupprintf("%.*s", (int)(s - p), p); +} + +char *get_fp(char *filename) +{ + FILE *fp; + char buf[256], *ret; + + fp = fopen(filename, "r"); + if (!fp) + return NULL; + ret = fgets(buf, sizeof(buf), fp); + fclose(fp); + if (!ret) + return NULL; + return cleanup_fp(buf); +} + +void check_fp(char *filename, char *fp, char *fmt, ...) +{ + char *newfp; + + if (!fp) + return; + + newfp = get_fp(filename); + + if (!strcmp(fp, newfp)) { + passes++; + } else { + va_list ap; + + printf("FAILED check_fp ['%s' != '%s']: ", newfp, fp); + + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + + printf("\n"); + + fails++; + } + + sfree(newfp); +} + +int main(int argc, char **argv) +{ + int i; + static char *const keytypes[] = { "rsa1", "dsa", "rsa" }; + + /* + * Even when this thing is compiled for automatic test mode, + * it's helpful to be able to invoke it with command-line + * options for _manual_ tests. + */ + if (argc > 1) + return cmdgen_main(argc, argv); + + passes = fails = 0; + + for (i = 0; i < lenof(keytypes); i++) { + char filename[128], osfilename[128], scfilename[128]; + char pubfilename[128], tmpfilename1[128], tmpfilename2[128]; + char *fp; + + sprintf(filename, "test-%s.ppk", keytypes[i]); + sprintf(pubfilename, "test-%s.pub", keytypes[i]); + sprintf(osfilename, "test-%s.os", keytypes[i]); + sprintf(scfilename, "test-%s.sc", keytypes[i]); + sprintf(tmpfilename1, "test-%s.tmp1", keytypes[i]); + sprintf(tmpfilename2, "test-%s.tmp2", keytypes[i]); + + /* + * Create an encrypted key. + */ + setup_passphrases("sponge", "sponge", NULL); + test(0, "puttygen", "-t", keytypes[i], "-o", filename, NULL); + + /* + * List the public key in OpenSSH format. + */ + setup_passphrases(NULL); + test(0, "puttygen", "-L", filename, "-o", pubfilename, NULL); + { + char cmdbuf[256]; + fp = NULL; + sprintf(cmdbuf, "ssh-keygen -l -f '%s' > '%s'", + pubfilename, tmpfilename1); + if (system(cmdbuf) || + (fp = get_fp(tmpfilename1)) == NULL) { + printf("UNABLE to test fingerprint matching against OpenSSH"); + } + } + + /* + * List the public key in IETF/ssh.com format. + */ + setup_passphrases(NULL); + test(0, "puttygen", "-p", filename, NULL); + + /* + * List the fingerprint of the key. + */ + setup_passphrases(NULL); + test(0, "puttygen", "-l", filename, "-o", tmpfilename1, NULL); + if (!fp) { + /* + * If we can't test fingerprints against OpenSSH, we + * can at the very least test equality of all the + * fingerprints we generate of this key throughout + * testing. + */ + fp = get_fp(tmpfilename1); + } else { + check_fp(tmpfilename1, fp, "%s initial fp", keytypes[i]); + } + + /* + * Change the comment of the key; this _does_ require a + * passphrase owing to the tamperproofing. + * + * NOTE: In SSH-1, this only requires a passphrase because + * of inadequacies of the loading and saving mechanisms. In + * _principle_, it should be perfectly possible to modify + * the comment on an SSH-1 key without requiring a + * passphrase; the only reason I can't do it is because my + * loading and saving mechanisms don't include a method of + * loading all the key data without also trying to decrypt + * the private section. + * + * I don't consider this to be a problem worth solving, + * because (a) to fix it would probably end up bloating + * PuTTY proper, and (b) SSH-1 is on the way out anyway so + * it shouldn't be highly significant. If it seriously + * bothers anyone then perhaps I _might_ be persuadable. + */ + setup_passphrases("sponge", NULL); + test(0, "puttygen", "-C", "new-comment", filename, NULL); + + /* + * Change the passphrase to nothing. + */ + setup_passphrases("sponge", "", "", NULL); + test(0, "puttygen", "-P", filename, NULL); + + /* + * Change the comment of the key again; this time we expect no + * passphrase to be required. + */ + setup_passphrases(NULL); + test(0, "puttygen", "-C", "new-comment-2", filename, NULL); + + /* + * Export the private key into OpenSSH format; no passphrase + * should be required since the key is currently unencrypted. + * For RSA1 keys, this should give an error. + */ + setup_passphrases(NULL); + test((i==0), "puttygen", "-O", "private-openssh", "-o", osfilename, + filename, NULL); + + if (i) { + /* + * List the fingerprint of the OpenSSH-formatted key. + */ + setup_passphrases(NULL); + test(0, "puttygen", "-l", osfilename, "-o", tmpfilename1, NULL); + check_fp(tmpfilename1, fp, "%s openssh clear fp", keytypes[i]); + + /* + * List the public half of the OpenSSH-formatted key in + * OpenSSH format. + */ + setup_passphrases(NULL); + test(0, "puttygen", "-L", osfilename, NULL); + + /* + * List the public half of the OpenSSH-formatted key in + * IETF/ssh.com format. + */ + setup_passphrases(NULL); + test(0, "puttygen", "-p", osfilename, NULL); + } + + /* + * Export the private key into ssh.com format; no passphrase + * should be required since the key is currently unencrypted. + * For RSA1 keys, this should give an error. + */ + setup_passphrases(NULL); + test((i==0), "puttygen", "-O", "private-sshcom", "-o", scfilename, + filename, NULL); + + if (i) { + /* + * List the fingerprint of the ssh.com-formatted key. + */ + setup_passphrases(NULL); + test(0, "puttygen", "-l", scfilename, "-o", tmpfilename1, NULL); + check_fp(tmpfilename1, fp, "%s ssh.com clear fp", keytypes[i]); + + /* + * List the public half of the ssh.com-formatted key in + * OpenSSH format. + */ + setup_passphrases(NULL); + test(0, "puttygen", "-L", scfilename, NULL); + + /* + * List the public half of the ssh.com-formatted key in + * IETF/ssh.com format. + */ + setup_passphrases(NULL); + test(0, "puttygen", "-p", scfilename, NULL); + } + + if (i) { + /* + * Convert from OpenSSH into ssh.com. + */ + setup_passphrases(NULL); + test(0, "puttygen", osfilename, "-o", tmpfilename1, + "-O", "private-sshcom", NULL); + + /* + * Convert from ssh.com back into a PuTTY key, + * supplying the same comment as we had before we + * started to ensure the comparison works. + */ + setup_passphrases(NULL); + test(0, "puttygen", tmpfilename1, "-C", "new-comment-2", + "-o", tmpfilename2, NULL); + + /* + * See if the PuTTY key thus generated is the same as + * the original. + */ + filecmp(filename, tmpfilename2, + "p->o->s->p clear %s", keytypes[i]); + + /* + * Convert from ssh.com to OpenSSH. + */ + setup_passphrases(NULL); + test(0, "puttygen", scfilename, "-o", tmpfilename1, + "-O", "private-openssh", NULL); + + /* + * Convert from OpenSSH back into a PuTTY key, + * supplying the same comment as we had before we + * started to ensure the comparison works. + */ + setup_passphrases(NULL); + test(0, "puttygen", tmpfilename1, "-C", "new-comment-2", + "-o", tmpfilename2, NULL); + + /* + * See if the PuTTY key thus generated is the same as + * the original. + */ + filecmp(filename, tmpfilename2, + "p->s->o->p clear %s", keytypes[i]); + + /* + * Finally, do a round-trip conversion between PuTTY + * and ssh.com without involving OpenSSH, to test that + * the key comment is preserved in that case. + */ + setup_passphrases(NULL); + test(0, "puttygen", "-O", "private-sshcom", "-o", tmpfilename1, + filename, NULL); + setup_passphrases(NULL); + test(0, "puttygen", tmpfilename1, "-o", tmpfilename2, NULL); + filecmp(filename, tmpfilename2, + "p->s->p clear %s", keytypes[i]); + } + + /* + * Check that mismatched passphrases cause an error. + */ + setup_passphrases("sponge2", "sponge3", NULL); + test(1, "puttygen", "-P", filename, NULL); + + /* + * Put a passphrase back on. + */ + setup_passphrases("sponge2", "sponge2", NULL); + test(0, "puttygen", "-P", filename, NULL); + + /* + * Export the private key into OpenSSH format, this time + * while encrypted. For RSA1 keys, this should give an + * error. + */ + if (i == 0) + setup_passphrases(NULL); /* error, hence no passphrase read */ + else + setup_passphrases("sponge2", NULL); + test((i==0), "puttygen", "-O", "private-openssh", "-o", osfilename, + filename, NULL); + + if (i) { + /* + * List the fingerprint of the OpenSSH-formatted key. + */ + setup_passphrases("sponge2", NULL); + test(0, "puttygen", "-l", osfilename, "-o", tmpfilename1, NULL); + check_fp(tmpfilename1, fp, "%s openssh encrypted fp", keytypes[i]); + + /* + * List the public half of the OpenSSH-formatted key in + * OpenSSH format. + */ + setup_passphrases("sponge2", NULL); + test(0, "puttygen", "-L", osfilename, NULL); + + /* + * List the public half of the OpenSSH-formatted key in + * IETF/ssh.com format. + */ + setup_passphrases("sponge2", NULL); + test(0, "puttygen", "-p", osfilename, NULL); + } + + /* + * Export the private key into ssh.com format, this time + * while encrypted. For RSA1 keys, this should give an + * error. + */ + if (i == 0) + setup_passphrases(NULL); /* error, hence no passphrase read */ + else + setup_passphrases("sponge2", NULL); + test((i==0), "puttygen", "-O", "private-sshcom", "-o", scfilename, + filename, NULL); + + if (i) { + /* + * List the fingerprint of the ssh.com-formatted key. + */ + setup_passphrases("sponge2", NULL); + test(0, "puttygen", "-l", scfilename, "-o", tmpfilename1, NULL); + check_fp(tmpfilename1, fp, "%s ssh.com encrypted fp", keytypes[i]); + + /* + * List the public half of the ssh.com-formatted key in + * OpenSSH format. + */ + setup_passphrases("sponge2", NULL); + test(0, "puttygen", "-L", scfilename, NULL); + + /* + * List the public half of the ssh.com-formatted key in + * IETF/ssh.com format. + */ + setup_passphrases("sponge2", NULL); + test(0, "puttygen", "-p", scfilename, NULL); + } + + if (i) { + /* + * Convert from OpenSSH into ssh.com. + */ + setup_passphrases("sponge2", NULL); + test(0, "puttygen", osfilename, "-o", tmpfilename1, + "-O", "private-sshcom", NULL); + + /* + * Convert from ssh.com back into a PuTTY key, + * supplying the same comment as we had before we + * started to ensure the comparison works. + */ + setup_passphrases("sponge2", NULL); + test(0, "puttygen", tmpfilename1, "-C", "new-comment-2", + "-o", tmpfilename2, NULL); + + /* + * See if the PuTTY key thus generated is the same as + * the original. + */ + filecmp(filename, tmpfilename2, + "p->o->s->p encrypted %s", keytypes[i]); + + /* + * Convert from ssh.com to OpenSSH. + */ + setup_passphrases("sponge2", NULL); + test(0, "puttygen", scfilename, "-o", tmpfilename1, + "-O", "private-openssh", NULL); + + /* + * Convert from OpenSSH back into a PuTTY key, + * supplying the same comment as we had before we + * started to ensure the comparison works. + */ + setup_passphrases("sponge2", NULL); + test(0, "puttygen", tmpfilename1, "-C", "new-comment-2", + "-o", tmpfilename2, NULL); + + /* + * See if the PuTTY key thus generated is the same as + * the original. + */ + filecmp(filename, tmpfilename2, + "p->s->o->p encrypted %s", keytypes[i]); + + /* + * Finally, do a round-trip conversion between PuTTY + * and ssh.com without involving OpenSSH, to test that + * the key comment is preserved in that case. + */ + setup_passphrases("sponge2", NULL); + test(0, "puttygen", "-O", "private-sshcom", "-o", tmpfilename1, + filename, NULL); + setup_passphrases("sponge2", NULL); + test(0, "puttygen", tmpfilename1, "-o", tmpfilename2, NULL); + filecmp(filename, tmpfilename2, + "p->s->p encrypted %s", keytypes[i]); + } + + /* + * Load with the wrong passphrase. + */ + setup_passphrases("sponge8", NULL); + test(1, "puttygen", "-C", "spurious-new-comment", filename, NULL); + + /* + * Load a totally bogus file. + */ + setup_passphrases(NULL); + test(1, "puttygen", "-C", "spurious-new-comment", pubfilename, NULL); + } + printf("%d passes, %d fails\n", passes, fails); + return 0; +} + +#endif