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