From 0fadffe0cbd191c3125834a1445ebe1885a88295 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 17 Mar 2016 18:42:46 +0000 Subject: [PATCH] Add command-line passphrase-file options to command-line PuTTYgen. Patch due to Colin Watson. Putting the passphrase in a file avoids exposing it to 'ps' which can print out every process's command line, while at the same time not being as platform-specific as the approach of providing an fd number (since cmdgen.c is in principle a potential cross-platform PuTTYgen, not just a Unix one, which is why it's not in the 'unix' directory). Of course it introduces its own risks if someone can read the file from your disk after you delete it; probably the best approach to avoiding this, if possible, is to point the option at a file on an in-memory tmpfs type file system. Or better still, use bash-style /dev/fd options such as puttygen --new-passphrase <(echo -n "my passphrase") [options] Failing that, try a secure file-wipe utility, as the man page change mentions. (And a use case not to be overlooked, of course, is the one where you actually want to generate an unprotected key - in which case, just pass /dev/null as the filename.) --- cmdgen.c | 149 ++++++++++++++++++++++++++++++++----------------- doc/man-pg.but | 20 +++++-- 2 files changed, 114 insertions(+), 55 deletions(-) diff --git a/cmdgen.c b/cmdgen.c index 12e1ac9e..4722a8ad 100644 --- a/cmdgen.c +++ b/cmdgen.c @@ -10,6 +10,8 @@ #include #include #include +#include +#include #include "putty.h" #include "ssh.h" @@ -171,6 +173,10 @@ void help(void) " -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" ); } @@ -193,6 +199,29 @@ static int move(char *from, char *to) return TRUE; } +static char *readpassphrase(const char *filename) +{ + FILE *fp; + char *line; + + 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; +} + int main(int argc, char **argv) { char *infile = NULL; @@ -213,7 +242,7 @@ int main(int argc, char **argv) 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; @@ -285,21 +314,31 @@ int main(int argc, char **argv) pgp_fingerprints(); nogo = TRUE; } - } - /* - * For long options requiring an argument, add - * code along the lines of - * - * else if (!strcmp(opt, "-output")) { - * if (!val) { - * errs = TRUE; - * fprintf(stderr, "puttygen: option `-%s'" - * " expects an argument\n", opt); - * } else - * ofile = val; - * } - */ - else { + } 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 { errs = TRUE; fprintf(stderr, "puttygen: no such option `-%s'\n", opt); @@ -708,23 +747,25 @@ int main(int argc, char **argv) * If so, ask for a passphrase. */ if (encrypted && load_encrypted) { - 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 { - passphrase = dupstr(p->prompts[0]->result); - free_prompts(p); + 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) { @@ -763,7 +804,7 @@ int main(int argc, char **argv) ssh1key->q = NULL; ssh1key->iqmp = NULL; } else { - ret = loadrsakey(infilename, ssh1key, passphrase, &error); + ret = loadrsakey(infilename, ssh1key, old_passphrase, &error); } if (ret > 0) error = NULL; @@ -788,7 +829,8 @@ int main(int argc, char **argv) } sfree(ssh2alg); } else { - ssh2key = ssh2_load_userkey(infilename, passphrase, &error); + ssh2key = ssh2_load_userkey(infilename, old_passphrase, + &error); } if ((ssh2key && ssh2key != SSH2_WRONG_PASSPHRASE) || ssh2blob) error = NULL; @@ -803,7 +845,7 @@ int main(int argc, char **argv) case SSH_KEYTYPE_OPENSSH_PEM: case SSH_KEYTYPE_OPENSSH_NEW: case SSH_KEYTYPE_SSHCOM: - ssh2key = import_ssh2(infilename, intype, passphrase, &error); + ssh2key = import_ssh2(infilename, intype, old_passphrase, &error); if (ssh2key) { if (ssh2key != SSH2_WRONG_PASSPHRASE) error = NULL; @@ -839,11 +881,18 @@ 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) { + if (!new_passphrase && (change_passphrase || keytype != NOKEYGEN)) { prompts_t *p = new_prompts(NULL); int ret; @@ -863,18 +912,14 @@ int main(int argc, char **argv) fprintf(stderr, "puttygen: passphrases do not match\n"); return 1; } - if (passphrase) { - smemclr(passphrase, strlen(passphrase)); - sfree(passphrase); - } - passphrase = dupstr(p->prompts[0]->result); + new_passphrase = dupstr(p->prompts[0]->result); free_prompts(p); - if (!*passphrase) { - sfree(passphrase); - passphrase = NULL; - } } } + if (new_passphrase && !*new_passphrase) { + sfree(new_passphrase); + new_passphrase = NULL; + } /* * Write output. @@ -895,14 +940,14 @@ int main(int argc, char **argv) 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 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 SSH-2 private key\n"); return 1; @@ -996,7 +1041,7 @@ int main(int argc, char **argv) default: assert(0 && "control flow goof"); } - ret = export_ssh2(outfilename, real_outtype, ssh2key, passphrase); + ret = export_ssh2(outfilename, real_outtype, ssh2key, new_passphrase); if (!ret) { fprintf(stderr, "puttygen: unable to export key\n"); return 1; @@ -1008,9 +1053,13 @@ int main(int argc, char **argv) break; } - if (passphrase) { - smemclr(passphrase, 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) diff --git a/doc/man-pg.but b/doc/man-pg.but index 6ee37c99..b6e4ef2c 100644 --- a/doc/man-pg.but +++ b/doc/man-pg.but @@ -64,6 +64,13 @@ and \c{rsa1} (to generate SSH-1 keys). \dd Suppress the progress display when generating a new key. +\dt \cw{\-\-old\-passphrase} \e{file} + +\dd Specify a file name; the first line will be read from this file +(removing any trailing newline) and used as the old passphrase. +\s{CAUTION:} If the passphrase is important, the file should be stored +on a temporary filesystem or else securely erased after use. + In the second phase, \c{puttygen} optionally alters properties of the key it has loaded or generated. The options to control this are: @@ -156,6 +163,14 @@ fingerprint. Otherwise, the \c{\-o} option is required. \dd Synonym for \q{\cw{-O public}}. +\dt \cw{\-\-new\-passphrase} \e{file} + +\dd Specify a file name; the first line will be read from this file +(removing any trailing newline) and used as the new passphrase. If the +file is empty then the saved key will be unencrypted. \s{CAUTION:} If +the passphrase is important, the file should be stored on a temporary +filesystem or else securely erased after use. + The following options do not run PuTTYgen as normal, but print informational messages and then quit: @@ -210,8 +225,3 @@ To add the OpenSSH-format public half of a key to your authorised keys file: \c puttygen -L mykey.ppk >> $HOME/.ssh/authorized_keys - -\S{puttygen-manpage-bugs} BUGS - -There's currently no way to supply passphrases in batch mode, or -even just to specify that you don't want a passphrase at all. -- 2.45.2