]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - cmdgen.c
In Unix PuTTYgen, existing SSH-1 key comments were coming out as "(null)"
[PuTTY.git] / cmdgen.c
1 /*
2  * cmdgen.c - command-line form of PuTTYgen
3  */
4
5 #define PUTTY_DO_GLOBALS
6
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <ctype.h>
10 #include <limits.h>
11 #include <assert.h>
12 #include <time.h>
13
14 #include "putty.h"
15 #include "ssh.h"
16
17 #ifdef TEST_CMDGEN
18 /*
19  * This section overrides some definitions below for test purposes.
20  * When compiled with -DTEST_CMDGEN:
21  * 
22  *  - Calls to get_random_data() are replaced with the diagnostic
23  *    function below (I #define the name so that I can still link
24  *    with the original set of modules without symbol clash), in
25  *    order to avoid depleting the test system's /dev/random
26  *    unnecessarily.
27  * 
28  *  - Calls to console_get_line() are replaced with the diagnostic
29  *    function below, so that I can run tests in an automated
30  *    manner and provide their interactive passphrase inputs.
31  * 
32  *  - main() is renamed to cmdgen_main(); at the bottom of the file
33  *    I define another main() which calls the former repeatedly to
34  *    run tests.
35  */
36 #define get_random_data get_random_data_diagnostic
37 char *get_random_data(int len)
38 {
39     char *buf = snewn(len, char);
40     memset(buf, 'x', len);
41     return buf;
42 }
43 #define console_get_line console_get_line_diagnostic
44 int nprompts, promptsgot;
45 const char *prompts[3];
46 int console_get_line(const char *prompt, char *str, int maxlen, int is_pw)
47 {
48     if (promptsgot < nprompts) {
49         assert(strlen(prompts[promptsgot]) < maxlen);
50         strcpy(str, prompts[promptsgot++]);
51         return TRUE;
52     } else {
53         promptsgot++;                  /* track number of requests anyway */
54         return FALSE;
55     }
56 }
57 #define main cmdgen_main
58 #endif
59
60 struct progress {
61     int phase, current;
62 };
63
64 static void progress_update(void *param, int action, int phase, int iprogress)
65 {
66     struct progress *p = (struct progress *)param;
67     if (action != PROGFN_PROGRESS)
68         return;
69     if (phase > p->phase) {
70         if (p->phase >= 0)
71             fputc('\n', stderr);
72         p->phase = phase;
73         if (iprogress >= 0)
74             p->current = iprogress - 1;
75         else
76             p->current = iprogress;
77     }
78     while (p->current < iprogress) {
79         fputc('+', stdout);
80         p->current++;
81     }
82     fflush(stdout);
83 }
84
85 static void no_progress(void *param, int action, int phase, int iprogress)
86 {
87 }
88
89 void modalfatalbox(char *p, ...)
90 {
91     va_list ap;
92     fprintf(stderr, "FATAL ERROR: ");
93     va_start(ap, p);
94     vfprintf(stderr, p, ap);
95     va_end(ap);
96     fputc('\n', stderr);
97     cleanup_exit(1);
98 }
99
100 /*
101  * Stubs to let everything else link sensibly.
102  */
103 void log_eventlog(void *handle, const char *event)
104 {
105 }
106 char *x_get_default(const char *key)
107 {
108     return NULL;
109 }
110 void sk_cleanup(void)
111 {
112 }
113
114 void showversion(void)
115 {
116     char *verstr = dupstr(ver);
117     verstr[0] = tolower(verstr[0]);
118     printf("PuTTYgen %s\n", verstr);
119     sfree(verstr);
120 }
121
122 void usage(void)
123 {
124     fprintf(stderr,
125             "Usage: puttygen ( keyfile | -t type [ -b bits ] )\n"
126             "                [ -C comment ] [ -P ]\n"
127             "                [ -o output-keyfile ] [ -O type | -l | -L"
128             " | -p ]\n");
129 }
130
131 void help(void)
132 {
133     /*
134      * Help message is an extended version of the usage message. So
135      * start with that, plus a version heading.
136      */
137     showversion();
138     usage();
139     fprintf(stderr,
140             "  -t    specify key type when generating (rsa, dsa, rsa1)\n"
141             "  -b    specify number of bits when generating key\n"
142             "  -C    change or specify key comment\n"
143             "  -P    change key passphrase\n"
144             "  -O    specify output type:\n"
145             "           private             output PuTTY private key format\n"
146             "           private-openssh     export OpenSSH private key\n"
147             "           private-sshcom      export ssh.com private key\n"
148             "           public              standard / ssh.com public key\n"
149             "           public-openssh      OpenSSH public key\n"
150             "           fingerprint         output the key fingerprint\n"
151             "  -o    specify output file\n"
152             "  -l    equivalent to `-O fingerprint'\n"
153             "  -L    equivalent to `-O public-openssh'\n"
154             "  -p    equivalent to `-O public'\n"
155             );
156 }
157
158 static int save_ssh2_pubkey(char *filename, char *comment,
159                             void *v_pub_blob, int pub_len)
160 {
161     unsigned char *pub_blob = (unsigned char *)v_pub_blob;
162     char *p;
163     int i, column;
164     FILE *fp;
165
166     if (filename) {
167         fp = fopen(filename, "wb");
168         if (!fp)
169             return 0;
170     } else
171         fp = stdout;
172
173     fprintf(fp, "---- BEGIN SSH2 PUBLIC KEY ----\n");
174
175     if (comment) {
176         fprintf(fp, "Comment: \"");
177         for (p = comment; *p; p++) {
178             if (*p == '\\' || *p == '\"')
179                 fputc('\\', fp);
180             fputc(*p, fp);
181         }
182         fprintf(fp, "\"\n");
183     }
184
185     i = 0;
186     column = 0;
187     while (i < pub_len) {
188         char buf[5];
189         int n = (pub_len - i < 3 ? pub_len - i : 3);
190         base64_encode_atom(pub_blob + i, n, buf);
191         i += n;
192         buf[4] = '\0';
193         fputs(buf, fp);
194         if (++column >= 16) {
195             fputc('\n', fp);
196             column = 0;
197         }
198     }
199     if (column > 0)
200         fputc('\n', fp);
201     
202     fprintf(fp, "---- END SSH2 PUBLIC KEY ----\n");
203     if (filename)
204         fclose(fp);
205     return 1;
206 }
207
208 static int move(char *from, char *to)
209 {
210     int ret;
211
212     ret = rename(from, to);
213     if (ret) {
214         /*
215          * This OS may require us to remove the original file first.
216          */
217         remove(to);
218         ret = rename(from, to);
219     }
220     if (ret) {
221         perror("puttygen: cannot move new file on to old one");
222         return FALSE;
223     }
224     return TRUE;
225 }
226
227 static char *blobfp(char *alg, int bits, char *blob, int bloblen)
228 {
229     char buffer[128];
230     unsigned char digest[16];
231     struct MD5Context md5c;
232     int i;
233
234     MD5Init(&md5c);
235     MD5Update(&md5c, blob, bloblen);
236     MD5Final(digest, &md5c);
237
238     sprintf(buffer, "%s ", alg);
239     if (bits > 0)
240         sprintf(buffer + strlen(buffer), "%d ", bits);
241     for (i = 0; i < 16; i++)
242         sprintf(buffer + strlen(buffer), "%s%02x", i ? ":" : "",
243                 digest[i]);
244
245     return dupstr(buffer);
246 }
247
248 int main(int argc, char **argv)
249 {
250     char *infile = NULL;
251     Filename infilename;
252     enum { NOKEYGEN, RSA1, RSA2, DSA } keytype = NOKEYGEN;    
253     char *outfile = NULL, *outfiletmp = NULL;
254     Filename outfilename;
255     enum { PRIVATE, PUBLIC, PUBLICO, FP, OPENSSH, SSHCOM } outtype = PRIVATE;
256     int bits = 1024;
257     char *comment = NULL, *origcomment = NULL;
258     int change_passphrase = FALSE;
259     int errs = FALSE, nogo = FALSE;
260     int intype = SSH_KEYTYPE_UNOPENABLE;
261     int sshver = 0;
262     struct ssh2_userkey *ssh2key = NULL;
263     struct RSAKey *ssh1key = NULL;
264     char *ssh2blob = NULL, *ssh2alg = NULL;
265     const struct ssh_signkey *ssh2algf = NULL;
266     int ssh2bloblen;
267     char *passphrase = NULL;
268     int load_encrypted;
269     progfn_t progressfn = is_interactive() ? progress_update : no_progress;
270
271     /* ------------------------------------------------------------------
272      * Parse the command line to figure out what we've been asked to do.
273      */
274
275     /*
276      * If run with no arguments at all, print the usage message and
277      * return success.
278      */
279     if (argc <= 1) {
280         usage();
281         return 0;
282     }
283
284     /*
285      * Parse command line arguments.
286      */
287     while (--argc) {
288         char *p = *++argv;
289         if (*p == '-') {
290             /*
291              * An option.
292              */
293             while (p && *++p) {
294                 char c = *p;
295                 switch (c) {
296                   case '-':
297                     /*
298                      * Long option.
299                      */
300                     {
301                         char *opt, *val;
302                         opt = p++;     /* opt will have _one_ leading - */
303                         while (*p && *p != '=')
304                             p++;               /* find end of option */
305                         if (*p == '=') {
306                             *p++ = '\0';
307                             val = p;
308                         } else
309                             val = NULL;
310                         if (!strcmp(opt, "-help")) {
311                             help();
312                             nogo = TRUE;
313                         } else if (!strcmp(opt, "-version")) {
314                             showversion();
315                             nogo = TRUE;
316                         } else if (!strcmp(opt, "-pgpfp")) {
317                             /* support "-pgpfp" for consistency with others */
318                             pgp_fingerprints();
319                             nogo = TRUE;
320                         }
321                         /*
322                          * A sample option requiring an argument:
323                          * 
324                          * else if (!strcmp(opt, "-output")) {
325                          *     if (!val)
326                          *         errs = TRUE, error(err_optnoarg, opt);
327                          *     else
328                          *         ofile = val;
329                          * }
330                          */
331                         else {
332                             errs = TRUE;
333                             fprintf(stderr,
334                                     "puttygen: no such option `--%s'\n", opt);
335                         }
336                     }
337                     p = NULL;
338                     break;
339                   case 'h':
340                   case 'V':
341                   case 'P':
342                   case 'l':
343                   case 'L':
344                   case 'p':
345                   case 'q':
346                     /*
347                      * Option requiring no parameter.
348                      */
349                     switch (c) {
350                       case 'h':
351                         help();
352                         nogo = TRUE;
353                         break;
354                       case 'V':
355                         showversion();
356                         nogo = TRUE;
357                         break;
358                       case 'P':
359                         change_passphrase = TRUE;
360                         break;
361                       case 'l':
362                         outtype = FP;
363                         break;
364                       case 'L':
365                         outtype = PUBLICO;
366                         break;
367                       case 'p':
368                         outtype = PUBLIC;
369                         break;
370                       case 'q':
371                         progressfn = no_progress;
372                         break;
373                     }
374                     break;
375                   case 't':
376                   case 'b':
377                   case 'C':
378                   case 'O':
379                   case 'o':
380                     /*
381                      * Option requiring parameter.
382                      */
383                     p++;
384                     if (!*p && argc > 1)
385                         --argc, p = *++argv;
386                     else if (!*p) {
387                         fprintf(stderr, "puttygen: option `-%c' expects a"
388                                 " parameter\n", c);
389                         errs = TRUE;
390                     }
391                     /*
392                      * Now c is the option and p is the parameter.
393                      */
394                     switch (c) {
395                       case 't':
396                         if (!strcmp(p, "rsa") || !strcmp(p, "rsa2"))
397                             keytype = RSA2, sshver = 2;
398                         else if (!strcmp(p, "rsa1"))
399                             keytype = RSA1, sshver = 1;
400                         else if (!strcmp(p, "dsa") || !strcmp(p, "dss"))
401                             keytype = DSA, sshver = 2;
402                         else {
403                             fprintf(stderr,
404                                     "puttygen: unknown key type `%s'\n", p);
405                             errs = TRUE;
406                         }
407                         break;
408                       case 'b':
409                         bits = atoi(p);
410                         break;
411                       case 'C':
412                         comment = p;
413                         break;
414                       case 'O':
415                         if (!strcmp(p, "public"))
416                             outtype = PUBLIC;
417                         else if (!strcmp(p, "public-openssh"))
418                             outtype = PUBLICO;
419                         else if (!strcmp(p, "private"))
420                             outtype = PRIVATE;
421                         else if (!strcmp(p, "fingerprint"))
422                             outtype = FP;
423                         else if (!strcmp(p, "private-openssh"))
424                             outtype = OPENSSH, sshver = 2;
425                         else if (!strcmp(p, "private-sshcom"))
426                             outtype = SSHCOM, sshver = 2;
427                         else {
428                             fprintf(stderr,
429                                     "puttygen: unknown output type `%s'\n", p);
430                             errs = TRUE;
431                         }
432                         break;
433                       case 'o':
434                         outfile = p;
435                         break;
436                     }
437                     p = NULL;          /* prevent continued processing */
438                     break;
439                   default:
440                     /*
441                      * Unrecognised option.
442                      */
443                     errs = TRUE;
444                     fprintf(stderr, "puttygen: no such option `-%c'\n", c);
445                     break;
446                 }
447             }
448         } else {
449             /*
450              * A non-option argument.
451              */
452             if (!infile)
453                 infile = p;
454             else {
455                 errs = TRUE;
456                 fprintf(stderr, "puttygen: cannot handle more than one"
457                         " input file\n");
458             }
459         }
460     }
461
462     if (errs)
463         return 1;
464
465     if (nogo)
466         return 0;
467
468     /*
469      * If run with at least one argument _but_ not the required
470      * ones, print the usage message and return failure.
471      */
472     if (!infile && keytype == NOKEYGEN) {
473         usage();
474         return 1;
475     }
476
477     /* ------------------------------------------------------------------
478      * Figure out further details of exactly what we're going to do.
479      */
480
481     /*
482      * Bomb out if we've been asked to both load and generate a
483      * key.
484      */
485     if (keytype != NOKEYGEN && intype) {
486         fprintf(stderr, "puttygen: cannot both load and generate a key\n");
487         return 1;
488     }
489
490     /*
491      * Analyse the type of the input file, in case this affects our
492      * course of action.
493      */
494     if (infile) {
495         infilename = filename_from_str(infile);
496
497         intype = key_type(&infilename);
498
499         switch (intype) {
500             /*
501              * It would be nice here to be able to load _public_
502              * key files, in any of a number of forms, and (a)
503              * convert them to other public key types, (b) print
504              * out their fingerprints. Or, I suppose, for real
505              * orthogonality, (c) change their comment!
506              * 
507              * In fact this opens some interesting possibilities.
508              * Suppose ssh2_userkey_loadpub() were able to load
509              * public key files as well as extracting the public
510              * key from private ones. And suppose I did the thing
511              * I've been wanting to do, where specifying a
512              * particular private key file for authentication
513              * causes any _other_ key in the agent to be discarded.
514              * Then, if you had an agent forwarded to the machine
515              * you were running Unix PuTTY or Plink on, and you
516              * needed to specify which of the keys in the agent it
517              * should use, you could do that by supplying a
518              * _public_ key file, thus not needing to trust even
519              * your encrypted private key file to the network. Ooh!
520              */
521
522           case SSH_KEYTYPE_UNOPENABLE:
523           case SSH_KEYTYPE_UNKNOWN:
524             fprintf(stderr, "puttygen: unable to load file `%s': %s\n",
525                     infile, key_type_to_str(intype));
526             return 1;
527
528           case SSH_KEYTYPE_SSH1:
529             if (sshver == 2) {
530                 fprintf(stderr, "puttygen: conversion from SSH-1 to SSH-2 keys"
531                         " not supported\n");
532                 return 1;
533             }
534             sshver = 1;
535             break;
536
537           case SSH_KEYTYPE_SSH2:
538           case SSH_KEYTYPE_OPENSSH:
539           case SSH_KEYTYPE_SSHCOM:
540             if (sshver == 1) {
541                 fprintf(stderr, "puttygen: conversion from SSH-2 to SSH-1 keys"
542                         " not supported\n");
543                 return 1;
544             }
545             sshver = 2;
546             break;
547         }
548     }
549
550     /*
551      * Determine the default output file, if none is provided.
552      * 
553      * This will usually be equal to stdout, except that if the
554      * input and output file formats are the same then the default
555      * output is to overwrite the input.
556      * 
557      * Also in this code, we bomb out if the input and output file
558      * formats are the same and no other action is performed.
559      */
560     if ((intype == SSH_KEYTYPE_SSH1 && outtype == PRIVATE) ||
561         (intype == SSH_KEYTYPE_SSH2 && outtype == PRIVATE) ||
562         (intype == SSH_KEYTYPE_OPENSSH && outtype == OPENSSH) ||
563         (intype == SSH_KEYTYPE_SSHCOM && outtype == SSHCOM)) {
564         if (!outfile) {
565             outfile = infile;
566             outfiletmp = dupcat(outfile, ".tmp", NULL);
567         }
568
569         if (!change_passphrase && !comment) {
570             fprintf(stderr, "puttygen: this command would perform no useful"
571                     " action\n");
572             return 1;
573         }
574     } else {
575         if (!outfile) {
576             /*
577              * Bomb out rather than automatically choosing to write
578              * a private key file to stdout.
579              */
580             if (outtype==PRIVATE || outtype==OPENSSH || outtype==SSHCOM) {
581                 fprintf(stderr, "puttygen: need to specify an output file\n");
582                 return 1;
583             }
584         }
585     }
586
587     /*
588      * Figure out whether we need to load the encrypted part of the
589      * key. This will be the case if either (a) we need to write
590      * out a private key format, or (b) the entire input key file
591      * is encrypted.
592      */
593     if (outtype == PRIVATE || outtype == OPENSSH || outtype == SSHCOM ||
594         intype == SSH_KEYTYPE_OPENSSH || intype == SSH_KEYTYPE_SSHCOM)
595         load_encrypted = TRUE;
596     else
597         load_encrypted = FALSE;
598
599     /* ------------------------------------------------------------------
600      * Now we're ready to actually do some stuff.
601      */
602
603     /*
604      * Either load or generate a key.
605      */
606     if (keytype != NOKEYGEN) {
607         char *entropy;
608         char default_comment[80];
609         struct tm tm;
610         struct progress prog;
611
612         prog.phase = -1;
613         prog.current = -1;
614
615         tm = ltime();
616         if (keytype == DSA)
617             strftime(default_comment, 30, "dsa-key-%Y%m%d", &tm);
618         else
619             strftime(default_comment, 30, "rsa-key-%Y%m%d", &tm);
620
621         random_ref();
622         entropy = get_random_data(bits / 8);
623         random_add_heavynoise(entropy, bits / 8);
624         memset(entropy, 0, bits/8);
625         sfree(entropy);
626
627         if (keytype == DSA) {
628             struct dss_key *dsskey = snew(struct dss_key);
629             dsa_generate(dsskey, bits, progressfn, &prog);
630             ssh2key = snew(struct ssh2_userkey);
631             ssh2key->data = dsskey;
632             ssh2key->alg = &ssh_dss;
633             ssh1key = NULL;
634         } else {
635             struct RSAKey *rsakey = snew(struct RSAKey);
636             rsa_generate(rsakey, bits, progressfn, &prog);
637             rsakey->comment = NULL;
638             if (keytype == RSA1) {
639                 ssh1key = rsakey;
640             } else {
641                 ssh2key = snew(struct ssh2_userkey);
642                 ssh2key->data = rsakey;
643                 ssh2key->alg = &ssh_rsa;
644             }
645         }
646         progressfn(&prog, PROGFN_PROGRESS, INT_MAX, -1);
647
648         if (ssh2key)
649             ssh2key->comment = dupstr(default_comment);
650         if (ssh1key)
651             ssh1key->comment = dupstr(default_comment);
652
653     } else {
654         const char *error = NULL;
655         int encrypted;
656
657         assert(infile != NULL);
658
659         /*
660          * Find out whether the input key is encrypted.
661          */
662         if (intype == SSH_KEYTYPE_SSH1)
663             encrypted = rsakey_encrypted(&infilename, &origcomment);
664         else if (intype == SSH_KEYTYPE_SSH2)
665             encrypted = ssh2_userkey_encrypted(&infilename, &origcomment);
666         else
667             encrypted = import_encrypted(&infilename, intype, &origcomment);
668
669         /*
670          * If so, ask for a passphrase.
671          */
672         if (encrypted && load_encrypted) {
673             passphrase = snewn(512, char);
674             if (!console_get_line("Enter passphrase to load key: ",
675                                   passphrase, 512, TRUE)) {
676                 perror("puttygen: unable to read passphrase");
677                 return 1;
678             }
679         } else {
680             passphrase = NULL;
681         }
682
683         switch (intype) {
684             int ret;
685
686           case SSH_KEYTYPE_SSH1:
687             ssh1key = snew(struct RSAKey);
688             if (!load_encrypted) {
689                 void *vblob;
690                 char *blob;
691                 int n, l, bloblen;
692
693                 ret = rsakey_pubblob(&infilename, &vblob, &bloblen,
694                                      &origcomment, &error);
695                 blob = (char *)vblob;
696
697                 n = 4;                 /* skip modulus bits */
698                 
699                 l = ssh1_read_bignum(blob + n, bloblen - n,
700                                      &ssh1key->exponent);
701                 if (l < 0) {
702                     error = "SSH-1 public key blob was too short";
703                 } else {
704                     n += l;
705                     l = ssh1_read_bignum(blob + n, bloblen - n,
706                                          &ssh1key->modulus);
707                     if (l < 0) {
708                         error = "SSH-1 public key blob was too short";
709                     } else
710                         n += l;
711                 }
712                 ssh1key->comment = dupstr(origcomment);
713                 ssh1key->private_exponent = NULL;
714             } else {
715                 ret = loadrsakey(&infilename, ssh1key, passphrase, &error);
716             }
717             if (ret > 0)
718                 error = NULL;
719             else if (!error)
720                 error = "unknown error";
721             break;
722
723           case SSH_KEYTYPE_SSH2:
724             if (!load_encrypted) {
725                 ssh2blob = ssh2_userkey_loadpub(&infilename, &ssh2alg,
726                                                 &ssh2bloblen, NULL, &error);
727                 ssh2algf = find_pubkey_alg(ssh2alg);
728                 if (ssh2algf)
729                     bits = ssh2algf->pubkey_bits(ssh2blob, ssh2bloblen);
730                 else
731                     bits = -1;
732             } else {
733                 ssh2key = ssh2_load_userkey(&infilename, passphrase, &error);
734             }
735             if ((ssh2key && ssh2key != SSH2_WRONG_PASSPHRASE) || ssh2blob)
736                 error = NULL;
737             else if (!error) {
738                 if (ssh2key == SSH2_WRONG_PASSPHRASE)
739                     error = "wrong passphrase";
740                 else
741                     error = "unknown error";
742             }
743             break;
744
745           case SSH_KEYTYPE_OPENSSH:
746           case SSH_KEYTYPE_SSHCOM:
747             ssh2key = import_ssh2(&infilename, intype, passphrase, &error);
748             if (ssh2key) {
749                 if (ssh2key != SSH2_WRONG_PASSPHRASE)
750                     error = NULL;
751                 else
752                     error = "wrong passphrase";
753             } else if (!error)
754                 error = "unknown error";
755             break;
756
757           default:
758             assert(0);
759         }
760
761         if (error) {
762             fprintf(stderr, "puttygen: error loading `%s': %s\n",
763                     infile, error);
764             return 1;
765         }
766     }
767
768     /*
769      * Change the comment if asked to.
770      */
771     if (comment) {
772         if (sshver == 1) {
773             assert(ssh1key);
774             sfree(ssh1key->comment);
775             ssh1key->comment = dupstr(comment);
776         } else {
777             assert(ssh2key);
778             sfree(ssh2key->comment);
779             ssh2key->comment = dupstr(comment);
780         }
781     }
782
783     /*
784      * Prompt for a new passphrase if we have been asked to, or if
785      * we have just generated a key.
786      */
787     if (change_passphrase || keytype != NOKEYGEN) {
788         char *passphrase2;
789
790         if (passphrase) {
791             memset(passphrase, 0, strlen(passphrase));
792             sfree(passphrase);
793         }
794
795         passphrase = snewn(512, char);
796         passphrase2 = snewn(512, char);
797         if (!console_get_line("Enter passphrase to save key: ",
798                               passphrase, 512, TRUE) ||
799             !console_get_line("Re-enter passphrase to verify: ",
800                               passphrase2, 512, TRUE)) {
801             perror("puttygen: unable to read new passphrase");
802             return 1;
803         }
804         if (strcmp(passphrase, passphrase2)) {
805             fprintf(stderr, "puttygen: passphrases do not match\n");
806             return 1;
807         }
808         memset(passphrase2, 0, strlen(passphrase2));
809         sfree(passphrase2);
810         if (!*passphrase) {
811             sfree(passphrase);
812             passphrase = NULL;
813         }
814     }
815
816     /*
817      * Write output.
818      * 
819      * (In the case where outfile and outfiletmp are both NULL,
820      * there is no semantic reason to initialise outfilename at
821      * all; but we have to write _something_ to it or some compiler
822      * will probably complain that it might be used uninitialised.)
823      */
824     if (outfiletmp)
825         outfilename = filename_from_str(outfiletmp);
826     else
827         outfilename = filename_from_str(outfile ? outfile : "");
828
829     switch (outtype) {
830         int ret;
831
832       case PRIVATE:
833         if (sshver == 1) {
834             assert(ssh1key);
835             ret = saversakey(&outfilename, ssh1key, passphrase);
836             if (!ret) {
837                 fprintf(stderr, "puttygen: unable to save SSH-1 private key\n");
838                 return 1;
839             }
840         } else {
841             assert(ssh2key);
842             ret = ssh2_save_userkey(&outfilename, ssh2key, passphrase);
843             if (!ret) {
844                 fprintf(stderr, "puttygen: unable to save SSH-2 private key\n");
845                 return 1;
846             }
847         }
848         if (outfiletmp) {
849             if (!move(outfiletmp, outfile))
850                 return 1;              /* rename failed */
851         }
852         break;
853
854       case PUBLIC:
855       case PUBLICO:
856         if (sshver == 1) {
857             FILE *fp;
858             char *dec1, *dec2;
859
860             assert(ssh1key);
861
862             if (outfile)
863                 fp = f_open(outfilename, "w");
864             else
865                 fp = stdout;
866             dec1 = bignum_decimal(ssh1key->exponent);
867             dec2 = bignum_decimal(ssh1key->modulus);
868             fprintf(fp, "%d %s %s %s\n", bignum_bitcount(ssh1key->modulus),
869                     dec1, dec2, ssh1key->comment);
870             sfree(dec1);
871             sfree(dec2);
872             if (outfile)
873                 fclose(fp);
874         } else if (outtype == PUBLIC) {
875             if (!ssh2blob) {
876                 assert(ssh2key);
877                 ssh2blob = ssh2key->alg->public_blob(ssh2key->data,
878                                                      &ssh2bloblen);
879             }
880             save_ssh2_pubkey(outfile, ssh2key ? ssh2key->comment : origcomment,
881                              ssh2blob, ssh2bloblen);
882         } else if (outtype == PUBLICO) {
883             char *buffer, *p;
884             int i;
885             FILE *fp;
886
887             if (!ssh2blob) {
888                 assert(ssh2key);
889                 ssh2blob = ssh2key->alg->public_blob(ssh2key->data,
890                                                      &ssh2bloblen);
891             }
892             if (!ssh2alg) {
893                 assert(ssh2key);
894                 ssh2alg = ssh2key->alg->name;
895             }
896             if (ssh2key)
897                 comment = ssh2key->comment;
898             else
899                 comment = origcomment;
900
901             buffer = snewn(strlen(ssh2alg) +
902                            4 * ((ssh2bloblen+2) / 3) +
903                            strlen(comment) + 3, char);
904             strcpy(buffer, ssh2alg);
905             p = buffer + strlen(buffer);
906             *p++ = ' ';
907             i = 0;
908             while (i < ssh2bloblen) {
909                 int n = (ssh2bloblen - i < 3 ? ssh2bloblen - i : 3);
910                 base64_encode_atom(ssh2blob + i, n, p);
911                 i += n;
912                 p += 4;
913             }
914             if (*comment) {
915                 *p++ = ' ';
916                 strcpy(p, comment);
917             } else
918                 *p++ = '\0';
919
920             if (outfile)
921                 fp = f_open(outfilename, "w");
922             else
923                 fp = stdout;
924             fprintf(fp, "%s\n", buffer);
925             if (outfile)
926                 fclose(fp);
927
928             sfree(buffer);
929         }
930         break;
931
932       case FP:
933         {
934             FILE *fp;
935             char *fingerprint;
936
937             if (sshver == 1) {
938                 assert(ssh1key);
939                 fingerprint = snewn(128, char);
940                 rsa_fingerprint(fingerprint, 128, ssh1key);
941             } else {
942                 if (ssh2key) {
943                     fingerprint = ssh2key->alg->fingerprint(ssh2key->data);
944                 } else {
945                     assert(ssh2blob);
946                     fingerprint = blobfp(ssh2alg, bits, ssh2blob, ssh2bloblen);
947                 }
948             }
949
950             if (outfile)
951                 fp = f_open(outfilename, "w");
952             else
953                 fp = stdout;
954             fprintf(fp, "%s\n", fingerprint);
955             if (outfile)
956                 fclose(fp);
957
958             sfree(fingerprint);
959         }
960         break;
961         
962       case OPENSSH:
963       case SSHCOM:
964         assert(sshver == 2);
965         assert(ssh2key);
966         ret = export_ssh2(&outfilename, outtype, ssh2key, passphrase);
967         if (!ret) {
968             fprintf(stderr, "puttygen: unable to export key\n");
969             return 1;
970         }
971         if (outfiletmp) {
972             if (!move(outfiletmp, outfile))
973                 return 1;              /* rename failed */
974         }
975         break;
976     }
977
978     if (passphrase) {
979         memset(passphrase, 0, strlen(passphrase));
980         sfree(passphrase);
981     }
982
983     if (ssh1key)
984         freersakey(ssh1key);
985     if (ssh2key) {
986         ssh2key->alg->freekey(ssh2key->data);
987         sfree(ssh2key);
988     }
989
990     return 0;
991 }
992
993 #ifdef TEST_CMDGEN
994
995 #undef main
996
997 #include <stdarg.h>
998
999 int passes, fails;
1000
1001 void setup_passphrases(char *first, ...)
1002 {
1003     va_list ap;
1004     char *next;
1005
1006     nprompts = 0;
1007     if (first) {
1008         prompts[nprompts++] = first;
1009         va_start(ap, first);
1010         while ((next = va_arg(ap, char *)) != NULL) {
1011             assert(nprompts < lenof(prompts));
1012             prompts[nprompts++] = next;
1013         }
1014         va_end(ap);
1015     }
1016 }
1017
1018 void test(int retval, ...)
1019 {
1020     va_list ap;
1021     int i, argc, ret;
1022     char **argv;
1023
1024     argc = 0;
1025     va_start(ap, retval);
1026     while (va_arg(ap, char *) != NULL)
1027         argc++;
1028     va_end(ap);
1029
1030     argv = snewn(argc+1, char *);
1031     va_start(ap, retval);
1032     for (i = 0; i <= argc; i++)
1033         argv[i] = va_arg(ap, char *);
1034     va_end(ap);
1035
1036     promptsgot = 0;
1037     ret = cmdgen_main(argc, argv);
1038
1039     if (ret != retval) {
1040         printf("FAILED retval (exp %d got %d):", retval, ret);
1041         for (i = 0; i < argc; i++)
1042             printf(" %s", argv[i]);
1043         printf("\n");
1044         fails++;
1045     } else if (promptsgot != nprompts) {
1046         printf("FAILED nprompts (exp %d got %d):", nprompts, promptsgot);
1047         for (i = 0; i < argc; i++)
1048             printf(" %s", argv[i]);
1049         printf("\n");
1050         fails++;
1051     } else {
1052         passes++;
1053     }
1054 }
1055
1056 void filecmp(char *file1, char *file2, char *fmt, ...)
1057 {
1058     /*
1059      * Ideally I should do file comparison myself, to maximise the
1060      * portability of this test suite once this application begins
1061      * running on non-Unix platforms. For the moment, though,
1062      * calling Unix diff is perfectly adequate.
1063      */
1064     char *buf;
1065     int ret;
1066
1067     buf = dupprintf("diff -q '%s' '%s'", file1, file2);
1068     ret = system(buf);
1069     sfree(buf);
1070
1071     if (ret) {
1072         va_list ap;
1073
1074         printf("FAILED diff (ret=%d): ", ret);
1075
1076         va_start(ap, fmt);
1077         vprintf(fmt, ap);
1078         va_end(ap);
1079
1080         printf("\n");
1081
1082         fails++;
1083     } else
1084         passes++;
1085 }
1086
1087 char *cleanup_fp(char *s)
1088 {
1089     char *p;
1090
1091     if (!strncmp(s, "ssh-", 4)) {
1092         s += strcspn(s, " \n\t");
1093         s += strspn(s, " \n\t");
1094     }
1095
1096     p = s;
1097     s += strcspn(s, " \n\t");
1098     s += strspn(s, " \n\t");
1099     s += strcspn(s, " \n\t");
1100
1101     return dupprintf("%.*s", s - p, p);
1102 }
1103
1104 char *get_fp(char *filename)
1105 {
1106     FILE *fp;
1107     char buf[256], *ret;
1108
1109     fp = fopen(filename, "r");
1110     if (!fp)
1111         return NULL;
1112     ret = fgets(buf, sizeof(buf), fp);
1113     fclose(fp);
1114     if (!ret)
1115         return NULL;
1116     return cleanup_fp(buf);
1117 }
1118
1119 void check_fp(char *filename, char *fp, char *fmt, ...)
1120 {
1121     char *newfp;
1122
1123     if (!fp)
1124         return;
1125
1126     newfp = get_fp(filename);
1127
1128     if (!strcmp(fp, newfp)) {
1129         passes++;
1130     } else {
1131         va_list ap;
1132
1133         printf("FAILED check_fp ['%s' != '%s']: ", newfp, fp);
1134
1135         va_start(ap, fmt);
1136         vprintf(fmt, ap);
1137         va_end(ap);
1138
1139         printf("\n");
1140
1141         fails++;
1142     }
1143
1144     sfree(newfp);
1145 }
1146
1147 int main(int argc, char **argv)
1148 {
1149     int i;
1150     static char *const keytypes[] = { "rsa1", "dsa", "rsa" };
1151
1152     /*
1153      * Even when this thing is compiled for automatic test mode,
1154      * it's helpful to be able to invoke it with command-line
1155      * options for _manual_ tests.
1156      */
1157     if (argc > 1)
1158         return cmdgen_main(argc, argv);
1159
1160     passes = fails = 0;
1161
1162     for (i = 0; i < lenof(keytypes); i++) {
1163         char filename[128], osfilename[128], scfilename[128];
1164         char pubfilename[128], tmpfilename1[128], tmpfilename2[128];
1165         char *fp;
1166
1167         sprintf(filename, "test-%s.ppk", keytypes[i]);
1168         sprintf(pubfilename, "test-%s.pub", keytypes[i]);
1169         sprintf(osfilename, "test-%s.os", keytypes[i]);
1170         sprintf(scfilename, "test-%s.sc", keytypes[i]);
1171         sprintf(tmpfilename1, "test-%s.tmp1", keytypes[i]);
1172         sprintf(tmpfilename2, "test-%s.tmp2", keytypes[i]);
1173
1174         /*
1175          * Create an encrypted key.
1176          */
1177         setup_passphrases("sponge", "sponge", NULL);
1178         test(0, "puttygen", "-t", keytypes[i], "-o", filename, NULL);
1179
1180         /*
1181          * List the public key in OpenSSH format.
1182          */
1183         setup_passphrases(NULL);
1184         test(0, "puttygen", "-L", filename, "-o", pubfilename, NULL);
1185         {
1186             char cmdbuf[256];
1187             fp = NULL;
1188             sprintf(cmdbuf, "ssh-keygen -l -f '%s' > '%s'",
1189                     pubfilename, tmpfilename1);
1190             if (system(cmdbuf) ||
1191                 (fp = get_fp(tmpfilename1)) == NULL) {
1192                 printf("UNABLE to test fingerprint matching against OpenSSH");
1193             }
1194         }
1195
1196         /*
1197          * List the public key in IETF/ssh.com format.
1198          */
1199         setup_passphrases(NULL);
1200         test(0, "puttygen", "-p", filename, NULL);
1201
1202         /*
1203          * List the fingerprint of the key.
1204          */
1205         setup_passphrases(NULL);
1206         test(0, "puttygen", "-l", filename, "-o", tmpfilename1, NULL);
1207         if (!fp) {
1208             /*
1209              * If we can't test fingerprints against OpenSSH, we
1210              * can at the very least test equality of all the
1211              * fingerprints we generate of this key throughout
1212              * testing.
1213              */
1214             fp = get_fp(tmpfilename1);
1215         } else {
1216             check_fp(tmpfilename1, fp, "%s initial fp", keytypes[i]);
1217         }
1218
1219         /*
1220          * Change the comment of the key; this _does_ require a
1221          * passphrase owing to the tamperproofing.
1222          * 
1223          * NOTE: In SSH-1, this only requires a passphrase because
1224          * of inadequacies of the loading and saving mechanisms. In
1225          * _principle_, it should be perfectly possible to modify
1226          * the comment on an SSH-1 key without requiring a
1227          * passphrase; the only reason I can't do it is because my
1228          * loading and saving mechanisms don't include a method of
1229          * loading all the key data without also trying to decrypt
1230          * the private section.
1231          * 
1232          * I don't consider this to be a problem worth solving,
1233          * because (a) to fix it would probably end up bloating
1234          * PuTTY proper, and (b) SSH-1 is on the way out anyway so
1235          * it shouldn't be highly significant. If it seriously
1236          * bothers anyone then perhaps I _might_ be persuadable.
1237          */
1238         setup_passphrases("sponge", NULL);
1239         test(0, "puttygen", "-C", "new-comment", filename, NULL);
1240
1241         /*
1242          * Change the passphrase to nothing.
1243          */
1244         setup_passphrases("sponge", "", "", NULL);
1245         test(0, "puttygen", "-P", filename, NULL);
1246
1247         /*
1248          * Change the comment of the key again; this time we expect no
1249          * passphrase to be required.
1250          */
1251         setup_passphrases(NULL);
1252         test(0, "puttygen", "-C", "new-comment-2", filename, NULL);
1253
1254         /*
1255          * Export the private key into OpenSSH format; no passphrase
1256          * should be required since the key is currently unencrypted.
1257          * For RSA1 keys, this should give an error.
1258          */
1259         setup_passphrases(NULL);
1260         test((i==0), "puttygen", "-O", "private-openssh", "-o", osfilename,
1261              filename, NULL);
1262
1263         if (i) {
1264             /*
1265              * List the fingerprint of the OpenSSH-formatted key.
1266              */
1267             setup_passphrases(NULL);
1268             test(0, "puttygen", "-l", osfilename, "-o", tmpfilename1, NULL);
1269             check_fp(tmpfilename1, fp, "%s openssh clear fp", keytypes[i]);
1270
1271             /*
1272              * List the public half of the OpenSSH-formatted key in
1273              * OpenSSH format.
1274              */
1275             setup_passphrases(NULL);
1276             test(0, "puttygen", "-L", osfilename, NULL);
1277
1278             /*
1279              * List the public half of the OpenSSH-formatted key in
1280              * IETF/ssh.com format.
1281              */
1282             setup_passphrases(NULL);
1283             test(0, "puttygen", "-p", osfilename, NULL);
1284         }
1285
1286         /*
1287          * Export the private key into ssh.com format; no passphrase
1288          * should be required since the key is currently unencrypted.
1289          * For RSA1 keys, this should give an error.
1290          */
1291         setup_passphrases(NULL);
1292         test((i==0), "puttygen", "-O", "private-sshcom", "-o", scfilename,
1293              filename, NULL);
1294
1295         if (i) {
1296             /*
1297              * List the fingerprint of the ssh.com-formatted key.
1298              */
1299             setup_passphrases(NULL);
1300             test(0, "puttygen", "-l", scfilename, "-o", tmpfilename1, NULL);
1301             check_fp(tmpfilename1, fp, "%s ssh.com clear fp", keytypes[i]);
1302
1303             /*
1304              * List the public half of the ssh.com-formatted key in
1305              * OpenSSH format.
1306              */
1307             setup_passphrases(NULL);
1308             test(0, "puttygen", "-L", scfilename, NULL);
1309
1310             /*
1311              * List the public half of the ssh.com-formatted key in
1312              * IETF/ssh.com format.
1313              */
1314             setup_passphrases(NULL);
1315             test(0, "puttygen", "-p", scfilename, NULL);
1316         }
1317
1318         if (i) {
1319             /*
1320              * Convert from OpenSSH into ssh.com.
1321              */
1322             setup_passphrases(NULL);
1323             test(0, "puttygen", osfilename, "-o", tmpfilename1,
1324                  "-O", "private-sshcom", NULL);
1325
1326             /*
1327              * Convert from ssh.com back into a PuTTY key,
1328              * supplying the same comment as we had before we
1329              * started to ensure the comparison works.
1330              */
1331             setup_passphrases(NULL);
1332             test(0, "puttygen", tmpfilename1, "-C", "new-comment-2",
1333                  "-o", tmpfilename2, NULL);
1334
1335             /*
1336              * See if the PuTTY key thus generated is the same as
1337              * the original.
1338              */
1339             filecmp(filename, tmpfilename2,
1340                     "p->o->s->p clear %s", keytypes[i]);
1341
1342             /*
1343              * Convert from ssh.com to OpenSSH.
1344              */
1345             setup_passphrases(NULL);
1346             test(0, "puttygen", scfilename, "-o", tmpfilename1,
1347                  "-O", "private-openssh", NULL);
1348
1349             /*
1350              * Convert from OpenSSH back into a PuTTY key,
1351              * supplying the same comment as we had before we
1352              * started to ensure the comparison works.
1353              */
1354             setup_passphrases(NULL);
1355             test(0, "puttygen", tmpfilename1, "-C", "new-comment-2",
1356                  "-o", tmpfilename2, NULL);
1357
1358             /*
1359              * See if the PuTTY key thus generated is the same as
1360              * the original.
1361              */
1362             filecmp(filename, tmpfilename2,
1363                     "p->s->o->p clear %s", keytypes[i]);
1364
1365             /*
1366              * Finally, do a round-trip conversion between PuTTY
1367              * and ssh.com without involving OpenSSH, to test that
1368              * the key comment is preserved in that case.
1369              */
1370             setup_passphrases(NULL);
1371             test(0, "puttygen", "-O", "private-sshcom", "-o", tmpfilename1,
1372                  filename, NULL);
1373             setup_passphrases(NULL);
1374             test(0, "puttygen", tmpfilename1, "-o", tmpfilename2, NULL);
1375             filecmp(filename, tmpfilename2,
1376                     "p->s->p clear %s", keytypes[i]);
1377         }
1378
1379         /*
1380          * Check that mismatched passphrases cause an error.
1381          */
1382         setup_passphrases("sponge2", "sponge3", NULL);
1383         test(1, "puttygen", "-P", filename, NULL);
1384
1385         /*
1386          * Put a passphrase back on.
1387          */
1388         setup_passphrases("sponge2", "sponge2", NULL);
1389         test(0, "puttygen", "-P", filename, NULL);
1390
1391         /*
1392          * Export the private key into OpenSSH format, this time
1393          * while encrypted. For RSA1 keys, this should give an
1394          * error.
1395          */
1396         if (i == 0)
1397             setup_passphrases(NULL);   /* error, hence no passphrase read */
1398         else
1399             setup_passphrases("sponge2", NULL);
1400         test((i==0), "puttygen", "-O", "private-openssh", "-o", osfilename,
1401              filename, NULL);
1402
1403         if (i) {
1404             /*
1405              * List the fingerprint of the OpenSSH-formatted key.
1406              */
1407             setup_passphrases("sponge2", NULL);
1408             test(0, "puttygen", "-l", osfilename, "-o", tmpfilename1, NULL);
1409             check_fp(tmpfilename1, fp, "%s openssh encrypted fp", keytypes[i]);
1410
1411             /*
1412              * List the public half of the OpenSSH-formatted key in
1413              * OpenSSH format.
1414              */
1415             setup_passphrases("sponge2", NULL);
1416             test(0, "puttygen", "-L", osfilename, NULL);
1417
1418             /*
1419              * List the public half of the OpenSSH-formatted key in
1420              * IETF/ssh.com format.
1421              */
1422             setup_passphrases("sponge2", NULL);
1423             test(0, "puttygen", "-p", osfilename, NULL);
1424         }
1425
1426         /*
1427          * Export the private key into ssh.com format, this time
1428          * while encrypted. For RSA1 keys, this should give an
1429          * error.
1430          */
1431         if (i == 0)
1432             setup_passphrases(NULL);   /* error, hence no passphrase read */
1433         else
1434             setup_passphrases("sponge2", NULL);
1435         test((i==0), "puttygen", "-O", "private-sshcom", "-o", scfilename,
1436              filename, NULL);
1437
1438         if (i) {
1439             /*
1440              * List the fingerprint of the ssh.com-formatted key.
1441              */
1442             setup_passphrases("sponge2", NULL);
1443             test(0, "puttygen", "-l", scfilename, "-o", tmpfilename1, NULL);
1444             check_fp(tmpfilename1, fp, "%s ssh.com encrypted fp", keytypes[i]);
1445
1446             /*
1447              * List the public half of the ssh.com-formatted key in
1448              * OpenSSH format.
1449              */
1450             setup_passphrases("sponge2", NULL);
1451             test(0, "puttygen", "-L", scfilename, NULL);
1452
1453             /*
1454              * List the public half of the ssh.com-formatted key in
1455              * IETF/ssh.com format.
1456              */
1457             setup_passphrases("sponge2", NULL);
1458             test(0, "puttygen", "-p", scfilename, NULL);
1459         }
1460
1461         if (i) {
1462             /*
1463              * Convert from OpenSSH into ssh.com.
1464              */
1465             setup_passphrases("sponge2", NULL);
1466             test(0, "puttygen", osfilename, "-o", tmpfilename1,
1467                  "-O", "private-sshcom", NULL);
1468
1469             /*
1470              * Convert from ssh.com back into a PuTTY key,
1471              * supplying the same comment as we had before we
1472              * started to ensure the comparison works.
1473              */
1474             setup_passphrases("sponge2", NULL);
1475             test(0, "puttygen", tmpfilename1, "-C", "new-comment-2",
1476                  "-o", tmpfilename2, NULL);
1477
1478             /*
1479              * See if the PuTTY key thus generated is the same as
1480              * the original.
1481              */
1482             filecmp(filename, tmpfilename2,
1483                     "p->o->s->p encrypted %s", keytypes[i]);
1484
1485             /*
1486              * Convert from ssh.com to OpenSSH.
1487              */
1488             setup_passphrases("sponge2", NULL);
1489             test(0, "puttygen", scfilename, "-o", tmpfilename1,
1490                  "-O", "private-openssh", NULL);
1491
1492             /*
1493              * Convert from OpenSSH back into a PuTTY key,
1494              * supplying the same comment as we had before we
1495              * started to ensure the comparison works.
1496              */
1497             setup_passphrases("sponge2", NULL);
1498             test(0, "puttygen", tmpfilename1, "-C", "new-comment-2",
1499                  "-o", tmpfilename2, NULL);
1500
1501             /*
1502              * See if the PuTTY key thus generated is the same as
1503              * the original.
1504              */
1505             filecmp(filename, tmpfilename2,
1506                     "p->s->o->p encrypted %s", keytypes[i]);
1507
1508             /*
1509              * Finally, do a round-trip conversion between PuTTY
1510              * and ssh.com without involving OpenSSH, to test that
1511              * the key comment is preserved in that case.
1512              */
1513             setup_passphrases("sponge2", NULL);
1514             test(0, "puttygen", "-O", "private-sshcom", "-o", tmpfilename1,
1515                  filename, NULL);
1516             setup_passphrases("sponge2", NULL);
1517             test(0, "puttygen", tmpfilename1, "-o", tmpfilename2, NULL);
1518             filecmp(filename, tmpfilename2,
1519                     "p->s->p encrypted %s", keytypes[i]);
1520         }
1521
1522         /*
1523          * Load with the wrong passphrase.
1524          */
1525         setup_passphrases("sponge8", NULL);
1526         test(1, "puttygen", "-C", "spurious-new-comment", filename, NULL);
1527
1528         /*
1529          * Load a totally bogus file.
1530          */
1531         setup_passphrases(NULL);
1532         test(1, "puttygen", "-C", "spurious-new-comment", pubfilename, NULL);
1533     }
1534     printf("%d passes, %d fails\n", passes, fails);
1535     return 0;
1536 }
1537
1538 #endif