From 47a6b94c2389f0e2a4f42706818a12369746b713 Mon Sep 17 00:00:00 2001 From: simon Date: Thu, 22 Jan 2004 19:15:32 +0000 Subject: [PATCH] Added a command-line key generation tool. Currently builds and runs on Linux, but the (very few) platform-specific bits are already abstracted out of the main code, so it should port to other platforms with a minimum of fuss. git-svn-id: svn://svn.tartarus.org/sgt/putty@3762 cda61777-01e9-0310-a592-d414129be87e --- Recipe | 4 + cmdgen.c | 946 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ putty.h | 2 + ssh.h | 2 + sshdss.c | 13 + sshpubk.c | 29 +- sshrsa.c | 13 + unix/.cvsignore | 2 +- unix/uxcons.c | 5 + unix/uxgen.c | 36 +++ 10 files changed, 1040 insertions(+), 12 deletions(-) create mode 100644 cmdgen.c create mode 100644 unix/uxgen.c diff --git a/Recipe b/Recipe index 64b1cb95..0b57a544 100644 --- a/Recipe +++ b/Recipe @@ -158,6 +158,10 @@ puttytel : [X] UXTERM uxmisc misc ldisc settings pty uxsel be_nossh uxstore plink : [U] uxplink uxcons NONSSH UXSSH be_all logging UXMISC signal ux_x11 +puttygen : [U] cmdgen sshrsag sshdssg sshprime sshdes sshbn sshmd5 version + + sshrand uxnoise sshsha misc sshrsa sshdss uxcons uxstore uxmisc + + sshpubk sshaes sshsh512 import puttygen.res tree234 uxgen + pscp : [U] scp uxsftp uxcons UXSSH be_none SFTP wildcard UXMISC psftp : [U] psftp uxsftp uxcons UXSSH be_none SFTP UXMISC diff --git a/cmdgen.c b/cmdgen.c new file mode 100644 index 00000000..f845c626 --- /dev/null +++ b/cmdgen.c @@ -0,0 +1,946 @@ +/* + * 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 +#include +#include +#include +#include +#include + +#include "putty.h" +#include "ssh.h" + +#ifdef TESTMODE +#define get_random_data get_random_data_diagnostic +char *get_random_data(int len) +{ + char *buf = snewn(len, char); + memset(buf, 'x', len); + return buf; +} +#endif + +struct progress { + int phase, current; +}; + +static void progress_update(void *param, int action, int phase, int iprogress) +{ + struct progress *p = (struct progress *)param; + if (action != PROGFN_PROGRESS) + return; + if (phase > p->phase) { + if (p->phase >= 0) + fputc('\n', stderr); + p->phase = phase; + if (iprogress >= 0) + p->current = iprogress - 1; + else + p->current = iprogress; + } + while (p->current < iprogress) { + fputc('+', stdout); + p->current++; + } + fflush(stdout); +} + +static void no_progress(void *param, int action, int phase, int iprogress) +{ +} + +void modalfatalbox(char *p, ...) +{ + va_list ap; + fprintf(stderr, "FATAL ERROR: "); + va_start(ap, p); + vfprintf(stderr, p, ap); + va_end(ap); + fputc('\n', stderr); + cleanup_exit(1); +} + +/* + * Stubs to let everything else link sensibly. + */ +void log_eventlog(void *handle, const char *event) +{ +} +char *x_get_default(const char *key) +{ + return NULL; +} +void sk_cleanup(void) +{ +} + +void showversion(void) +{ + char *verstr = dupstr(ver); + verstr[0] = tolower(verstr[0]); + printf("PuTTYgen %s\n", verstr); + sfree(verstr); +} + +void usage(void) +{ + fprintf(stderr, + "Usage: puttygen ( keyfile | -t type [ -b bits ] )\n" + " [ -C comment ] [ -P ]\n" + " [ -o output-keyfile ] [ -O type ]\n"); +} + +void help(void) +{ + /* + * Help message is an extended version of the usage message. So + * start with that, plus a version heading. + */ + showversion(); + usage(); + fprintf(stderr, + " -t specify key type when generating (rsa, dsa, rsa1)\n" + " -C change or specify key comment\n" + " -P change key passphrase\n" + " -O specify output type:\n" + " private output PuTTY private key format\n" + " private-openssh export OpenSSH private key\n" + " private-sshcom export ssh.com private key\n" + " public standard / ssh.com public key\n" + " public-openssh OpenSSH public key\n" + " fingerprint output the key fingerprint\n" + " -l equivalent to `-O fingerprint'\n" + " -L equivalent to `-O public-openssh'\n" + " -p equivalent to `-O public'\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) +{ + int ret; + + ret = rename(from, to); + if (ret) { + /* + * This OS may require us to remove the original file first. + */ + remove(to); + ret = rename(from, to); + } + if (ret) { + perror("puttygen: cannot move new file on to old one"); + exit(1); + } +} + +static char *blobfp(char *alg, int bits, char *blob, int bloblen) +{ + 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]); + + return dupstr(buffer); +} + +int main(int argc, char **argv) +{ + char *infile = NULL; + Filename infilename; + enum { NOKEYGEN, RSA1, RSA2, DSA } keytype = NOKEYGEN; + char *outfile = NULL, *outfiletmp = NULL; + Filename outfilename; + enum { PRIVATE, PUBLIC, PUBLICO, FP, OPENSSH, SSHCOM } outtype = PRIVATE; + int bits = 1024; + char *comment = NULL, *origcomment = NULL; + int change_passphrase = FALSE; + int errs = FALSE, nogo = FALSE; + int intype = SSH_KEYTYPE_UNOPENABLE; + int sshver = 0; + struct ssh2_userkey *ssh2key = NULL; + struct RSAKey *ssh1key = NULL; + char *ssh2blob = NULL, *ssh2alg = NULL; + const struct ssh_signkey *ssh2algf = NULL; + int ssh2bloblen; + char *passphrase = NULL; + int load_encrypted; + progfn_t progressfn = is_interactive() ? progress_update : no_progress; + + /* ------------------------------------------------------------------ + * Parse the command line to figure out what we've been asked to do. + */ + + /* + * If run with no arguments at all, print the usage message and + * return success. + */ + if (argc <= 1) { + usage(); + return 0; + } + + /* + * Parse command line arguments. + */ + while (--argc) { + char *p = *++argv; + if (*p == '-') { + /* + * An option. + */ + while (p && *++p) { + char c = *p; + switch (c) { + case '-': + /* + * Long option. + */ + { + char *opt, *val; + opt = p++; /* opt will have _one_ leading - */ + while (*p && *p != '=') + p++; /* find end of option */ + if (*p == '=') { + *p++ = '\0'; + val = p; + } else + val = NULL; + if (!strcmp(opt, "-help")) { + 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 { + errs = TRUE; + fprintf(stderr, + "puttygen: no such option `--%s'\n", opt); + } + } + p = NULL; + break; + case 'h': + case 'V': + case 'P': + case 'l': + case 'L': + case 'p': + case 'q': + /* + * Option requiring no parameter. + */ + switch (c) { + case 'h': + help(); + nogo = TRUE; + break; + case 'V': + showversion(); + nogo = TRUE; + break; + case 'P': + change_passphrase = TRUE; + break; + case 'l': + outtype = FP; + break; + case 'L': + outtype = PUBLICO; + break; + case 'p': + outtype = PUBLIC; + break; + case 'q': + progressfn = no_progress; + break; + } + break; + case 't': + case 'b': + case 'C': + case 'O': + case 'o': + /* + * Option requiring parameter. + */ + p++; + if (!*p && argc > 1) + --argc, p = *++argv; + else if (!*p) { + fprintf(stderr, "puttygen: option `-%c' expects a" + " parameter\n", c); + errs = TRUE; + } + /* + * Now c is the option and p is the parameter. + */ + switch (c) { + case 't': + if (!strcmp(p, "rsa") || !strcmp(p, "rsa2")) + keytype = RSA2, sshver = 2; + else if (!strcmp(p, "rsa1")) + keytype = RSA1, sshver = 1; + else if (!strcmp(p, "dsa") || !strcmp(p, "dss")) + keytype = DSA, sshver = 2; + else { + fprintf(stderr, + "puttygen: unknown key type `%s'\n", p); + errs = TRUE; + } + break; + case 'b': + bits = atoi(p); + break; + case 'C': + comment = p; + break; + case 'O': + if (!strcmp(p, "public")) + outtype = PUBLIC; + else if (!strcmp(p, "public-openssh")) + outtype = PUBLICO; + else if (!strcmp(p, "private")) + outtype = PRIVATE; + else if (!strcmp(p, "fingerprint")) + outtype = FP; + else if (!strcmp(p, "private-openssh")) + outtype = OPENSSH, sshver = 2; + else if (!strcmp(p, "private-sshcom")) + outtype = SSHCOM, sshver = 2; + else { + fprintf(stderr, + "puttygen: unknown output type `%s'\n", p); + errs = TRUE; + } + break; + case 'o': + outfile = p; + break; + } + p = NULL; /* prevent continued processing */ + break; + default: + /* + * Unrecognised option. + */ + errs = TRUE; + fprintf(stderr, "puttygen: no such option `-%c'\n", c); + break; + } + } + } else { + /* + * A non-option argument. + */ + if (!infile) + infile = p; + else { + errs = TRUE; + fprintf(stderr, "puttygen: cannot handle more than one" + " input file\n"); + } + } + } + + if (errs) + return 1; + + if (nogo) + return 0; + + /* + * If run with at least one argument _but_ not the required + * ones, print the usage message and return failure. + */ + if (!infile && keytype == NOKEYGEN) { + usage(); + return 1; + } + + /* ------------------------------------------------------------------ + * Figure out further details of exactly what we're going to do. + */ + + /* + * Bomb out if we've been asked to both load and generate a + * key. + */ + if (keytype != NOKEYGEN && intype) { + fprintf(stderr, "puttygen: cannot both load and generate a key\n"); + return 1; + } + + /* + * Analyse the type of the input file, in case this affects our + * course of action. + */ + if (infile) { + infilename = filename_from_str(infile); + + 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", + infile, key_type_to_str(intype)); + return 1; + + case SSH_KEYTYPE_SSH1: + if (sshver == 2) { + fprintf(stderr, "puttygen: conversion from SSH1 to SSH2 keys" + " not supported\n"); + return 1; + } + sshver = 1; + break; + + case SSH_KEYTYPE_SSH2: + case SSH_KEYTYPE_OPENSSH: + case SSH_KEYTYPE_SSHCOM: + if (sshver == 1) { + fprintf(stderr, "puttygen: conversion from SSH2 to SSH1 keys" + " not supported\n"); + return 1; + } + sshver = 2; + break; + } + } + + /* + * Determine the default output file, if none is provided. + * + * This will usually be equal to stdout, except that if the + * input and output file formats are the same then the default + * output is to overwrite the input. + * + * Also in this code, we bomb out if the input and output file + * formats are the same and no other action is performed. + */ + if ((intype == SSH_KEYTYPE_SSH1 && outtype == PRIVATE) || + (intype == SSH_KEYTYPE_SSH2 && outtype == PRIVATE) || + (intype == SSH_KEYTYPE_OPENSSH && outtype == OPENSSH) || + (intype == SSH_KEYTYPE_SSHCOM && outtype == SSHCOM)) { + if (!outfile) { + outfile = infile; + outfiletmp = dupcat(outfile, ".tmp"); + } + + if (!change_passphrase && !comment) { + fprintf(stderr, "puttygen: this command would perform no useful" + " action\n"); + return 1; + } + } else { + if (!outfile) { + /* + * Bomb out rather than automatically choosing to write + * a private key file to stdout. + */ + if (outtype==PRIVATE || outtype==OPENSSH || outtype==SSHCOM) { + fprintf(stderr, "puttygen: need to specify an output file\n"); + return 1; + } + } + } + + /* + * Figure out whether we need to load the encrypted part of the + * key. This will be the case if either (a) we need to write + * 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) + load_encrypted = TRUE; + else + load_encrypted = FALSE; + + /* ------------------------------------------------------------------ + * Now we're ready to actually do some stuff. + */ + + /* + * Either load or generate a key. + */ + if (keytype != NOKEYGEN) { + char *entropy; + char default_comment[80]; + time_t t; + struct tm *tm; + struct progress prog; + + prog.phase = -1; + prog.current = -1; + + time(&t); + tm = localtime(&t); + if (keytype == DSA) + strftime(default_comment, 30, "dsa-key-%Y%m%d", tm); + else + strftime(default_comment, 30, "rsa-key-%Y%m%d", tm); + + random_init(); + entropy = get_random_data(bits / 8); + random_add_heavynoise(entropy, bits / 8); + memset(entropy, 0, bits/8); + sfree(entropy); + + if (keytype == DSA) { + struct dss_key *dsskey = snew(struct dss_key); + dsa_generate(dsskey, bits, progressfn, &prog); + ssh2key = snew(struct ssh2_userkey); + ssh2key->data = dsskey; + ssh2key->alg = &ssh_dss; + ssh1key = NULL; + } else { + struct RSAKey *rsakey = snew(struct RSAKey); + rsa_generate(rsakey, bits, progressfn, &prog); + if (keytype == RSA1) { + ssh1key = rsakey; + } else { + ssh2key = snew(struct ssh2_userkey); + ssh2key->data = rsakey; + ssh2key->alg = &ssh_rsa; + } + } + progressfn(&prog, PROGFN_PROGRESS, INT_MAX, -1); + + if (ssh2key) + ssh2key->comment = dupstr(default_comment); + if (ssh1key) + ssh1key->comment = dupstr(default_comment); + + } else { + const char *error = NULL; + int encrypted; + + assert(infile != NULL); + + /* + * Find out whether the input key is encrypted. + */ + if (intype == SSH_KEYTYPE_SSH1) + encrypted = rsakey_encrypted(&infilename, &origcomment); + else if (intype == SSH_KEYTYPE_SSH2) + encrypted = ssh2_userkey_encrypted(&infilename, &origcomment); + else + 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; + } + } else { + passphrase = NULL; + } + + switch (intype) { + int ret; + + case SSH_KEYTYPE_SSH1: + ssh1key = snew(struct RSAKey); + if (!load_encrypted) { + void *vblob; + char *blob; + int n, bloblen; + + ret = rsakey_pubblob(&infilename, &vblob, &bloblen, &error); + blob = (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; + } else { + ret = loadrsakey(&infilename, ssh1key, passphrase, &error); + } + if (ret) + error = NULL; + else if (!error) + error = "unknown error"; + break; + + case SSH_KEYTYPE_SSH2: + 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; + } else { + ssh2key = ssh2_load_userkey(&infilename, passphrase, &error); + } + if (ssh2key || ssh2blob) + error = NULL; + else if (!error) { + if (ssh2key == SSH2_WRONG_PASSPHRASE) + error = "wrong passphrase"; + else + error = "unknown error"; + } + break; + + case SSH_KEYTYPE_OPENSSH: + 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"; + else + error = "unknown error"; + } + break; + + default: + assert(0); + } + + if (error) { + fprintf(stderr, "puttygen: error loading `%s': %s\n", + infile, error); + return 1; + } + } + + /* + * Change the comment if asked to. + */ + if (comment) { + if (sshver == 1) { + assert(ssh1key); + sfree(ssh1key->comment); + ssh1key->comment = dupstr(comment); + } else { + assert(ssh2key); + sfree(ssh2key->comment); + ssh2key->comment = dupstr(comment); + } + } + + /* + * 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); + } + + 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)) { + 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; + } + } + + /* + * Write output. + * + * (In the case where outfile and outfiletmp are both NULL, + * there is no semantic reason to initialise outfilename at + * all; but we have to write _something_ to it or some compiler + * will probably complain that it might be used uninitialised.) + */ + if (outfiletmp) + outfilename = filename_from_str(outfiletmp); + else + outfilename = filename_from_str(outfile ? outfile : ""); + + switch (outtype) { + int ret; + + case PRIVATE: + if (sshver == 1) { + assert(ssh1key); + ret = saversakey(&outfilename, ssh1key, passphrase); + if (!ret) { + fprintf(stderr, "puttygen: unable to save SSH1 private key\n"); + return 1; + } + } else { + assert(ssh2key); + ret = ssh2_save_userkey(&outfilename, ssh2key, passphrase); + if (!ret) { + fprintf(stderr, "puttygen: unable to save SSH2 private key\n"); + return 1; + } + } + if (outfiletmp) + move(outfiletmp, outfile); + 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; + + 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: + { + FILE *fp; + char *fingerprint; + + if (sshver == 1) { + assert(ssh1key); + fingerprint = snewn(128, char); + rsa_fingerprint(fingerprint, 128, ssh1key); + } else { + if (ssh2key) { + fingerprint = ssh2key->alg->fingerprint(ssh2key->data); + } else { + assert(ssh2blob); + fingerprint = blobfp(ssh2alg, bits, ssh2blob, ssh2bloblen); + } + } + + if (outfile) + fp = f_open(outfilename, "w"); + else + fp = stdout; + fprintf(fp, "%s\n", fingerprint); + if (outfile) + fclose(fp); + + sfree(fingerprint); + } + break; + + case OPENSSH: + case SSHCOM: + assert(sshver == 2); + assert(ssh2key); + ret = export_ssh2(&outfilename, outtype, ssh2key, passphrase); + if (!ret) { + fprintf(stderr, "puttygen: unable to export key\n"); + return 1; + } + if (outfiletmp) + move(outfiletmp, outfile); + break; + } + + if (passphrase) { + memset(passphrase, 0, strlen(passphrase)); + sfree(passphrase); + } + + if (ssh1key) + freersakey(ssh1key); + if (ssh2key) { + ssh2key->alg->freekey(ssh2key->data); + sfree(ssh2key); + } + + return 0; +} diff --git a/putty.h b/putty.h index de1b770b..af3768d8 100644 --- a/putty.h +++ b/putty.h @@ -794,6 +794,7 @@ int askappend(void *frontend, Filename filename); extern int console_batch_mode; int console_get_line(const char *prompt, char *str, int maxlen, int is_pw); void console_provide_logctx(void *logctx); +int is_interactive(void); /* * Exports from printing.c. @@ -848,5 +849,6 @@ const char *filename_to_str(const Filename *fn); int filename_equal(Filename f1, Filename f2); int filename_is_null(Filename fn); char *get_username(void); /* return value needs freeing */ +char *get_random_data(int bytes); /* used in cmdgen.c */ #endif diff --git a/ssh.h b/ssh.h index 3320098b..90f77d10 100644 --- a/ssh.h +++ b/ssh.h @@ -186,6 +186,7 @@ struct ssh_signkey { unsigned char *priv_blob, int priv_len); void *(*openssh_createkey) (unsigned char **blob, int *len); int (*openssh_fmtkey) (void *key, unsigned char *blob, int len); + int (*pubkey_bits) (void *blob, int len); char *(*fingerprint) (void *key); int (*verifysig) (void *key, char *sig, int siglen, char *data, int datalen); @@ -350,6 +351,7 @@ char *ssh2_userkey_loadpub(const Filename *filename, char **algorithm, int *pub_blob_len, const char **errorstr); int ssh2_save_userkey(const Filename *filename, struct ssh2_userkey *key, char *passphrase); +const struct ssh_signkey *find_pubkey_alg(const char *name); enum { SSH_KEYTYPE_UNOPENABLE, diff --git a/sshdss.c b/sshdss.c index 73500101..fa3ac384 100644 --- a/sshdss.c +++ b/sshdss.c @@ -473,6 +473,18 @@ static int dss_openssh_fmtkey(void *key, unsigned char *blob, int len) return bloblen; } +static int dss_pubkey_bits(void *blob, int len) +{ + struct dss_key *dss; + int ret; + + dss = dss_newkey((char *) blob, len); + ret = bignum_bitcount(dss->p); + dss_freekey(dss); + + return ret; +} + static unsigned char *dss_sign(void *key, char *data, int datalen, int *siglen) { /* @@ -630,6 +642,7 @@ const struct ssh_signkey ssh_dss = { dss_createkey, dss_openssh_createkey, dss_openssh_fmtkey, + dss_pubkey_bits, dss_fingerprint, dss_verifysig, dss_sign, diff --git a/sshpubk.c b/sshpubk.c index f87a0814..ed178fe3 100644 --- a/sshpubk.c +++ b/sshpubk.c @@ -186,6 +186,7 @@ int loadrsakey(const Filename *filename, struct RSAKey *key, char *passphrase, * This routine will take care of calling fclose() for us. */ ret = loadrsakey_main(fp, key, FALSE, NULL, passphrase, &error); + fp = NULL; goto end; } @@ -195,7 +196,8 @@ int loadrsakey(const Filename *filename, struct RSAKey *key, char *passphrase, error = "not an SSH-1 RSA file"; end: - fclose(fp); + if (fp) + fclose(fp); if ((ret != 1) && errorstr) *errorstr = error; return ret; @@ -264,6 +266,7 @@ int rsakey_pubblob(const Filename *filename, void **blob, int *bloblen, *blob = rsa_public_blob(&key, bloblen); freersakey(&key); ret = 1; + fp = NULL; } } else { error = "not an SSH-1 RSA file"; @@ -612,6 +615,16 @@ struct ssh2_userkey ssh2_wrong_passphrase = { NULL, NULL, NULL }; +const struct ssh_signkey *find_pubkey_alg(const char *name) +{ + if (!strcmp(name, "ssh-rsa")) + return &ssh_rsa; + else if (!strcmp(name, "ssh-dss")) + return &ssh_dss; + else + return NULL; +} + struct ssh2_userkey *ssh2_load_userkey(const Filename *filename, char *passphrase, const char **errorstr) { @@ -653,11 +666,8 @@ struct ssh2_userkey *ssh2_load_userkey(const Filename *filename, if ((b = read_body(fp)) == NULL) goto error; /* Select key algorithm structure. */ - if (!strcmp(b, "ssh-rsa")) - alg = &ssh_rsa; - else if (!strcmp(b, "ssh-dss")) - alg = &ssh_dss; - else { + alg = find_pubkey_alg(b); + if (!alg) { sfree(b); goto error; } @@ -897,11 +907,8 @@ char *ssh2_userkey_loadpub(const Filename *filename, char **algorithm, if ((b = read_body(fp)) == NULL) goto error; /* Select key algorithm structure. Currently only ssh-rsa. */ - if (!strcmp(b, "ssh-rsa")) - alg = &ssh_rsa; - else if (!strcmp(b, "ssh-dss")) - alg = &ssh_dss; - else { + alg = find_pubkey_alg(b); + if (!alg) { sfree(b); goto error; } diff --git a/sshrsa.c b/sshrsa.c index a735517f..b7732105 100644 --- a/sshrsa.c +++ b/sshrsa.c @@ -629,6 +629,18 @@ static int rsa2_openssh_fmtkey(void *key, unsigned char *blob, int len) return bloblen; } +static int rsa2_pubkey_bits(void *blob, int len) +{ + struct RSAKey *rsa; + int ret; + + rsa = rsa2_newkey((char *) blob, len); + ret = bignum_bitcount(rsa->modulus); + rsa2_freekey(rsa); + + return ret; +} + static char *rsa2_fingerprint(void *key) { struct RSAKey *rsa = (struct RSAKey *) key; @@ -794,6 +806,7 @@ const struct ssh_signkey ssh_rsa = { rsa2_createkey, rsa2_openssh_createkey, rsa2_openssh_fmtkey, + rsa2_pubkey_bits, rsa2_fingerprint, rsa2_verifysig, rsa2_sign, diff --git a/unix/.cvsignore b/unix/.cvsignore index 532f0223..6fb7149c 100644 --- a/unix/.cvsignore +++ b/unix/.cvsignore @@ -1,4 +1,4 @@ Makefile.gtk Makefile local -plink pterm putty puttytel psftp pscp +plink pterm putty puttytel psftp pscp puttygen *.log diff --git a/unix/uxcons.c b/unix/uxcons.c index 5e3949b3..3c79ad63 100644 --- a/unix/uxcons.c +++ b/unix/uxcons.c @@ -313,3 +313,8 @@ void frontend_keypress(void *handle) */ return; } + +int is_interactive(void) +{ + return isatty(0); +} diff --git a/unix/uxgen.c b/unix/uxgen.c new file mode 100644 index 00000000..6c579174 --- /dev/null +++ b/unix/uxgen.c @@ -0,0 +1,36 @@ +/* + * uxgen.c: Unix implementation of get_heavy_noise() from cmdgen.c. + */ + +#include +#include +#include + +#include "putty.h" + +char *get_random_data(int len) +{ + char *buf = snewn(len, char); + int fd; + int ngot, ret; + + fd = open("/dev/random", O_RDONLY); + if (fd < 0) { + sfree(buf); + perror("puttygen: unable to open /dev/random"); + return NULL; + } + + ngot = 0; + while (ngot < len) { + ret = read(fd, buf+ngot, len-ngot); + if (ret < 0) { + close(fd); + perror("puttygen: unable to read /dev/random"); + return NULL; + } + ngot += ret; + } + + return buf; +} -- 2.11.0