47a6b94c |
1 | /* |
2 | * cmdgen.c - command-line form of PuTTYgen |
3 | */ |
4 | |
5 | /* |
6 | * TODO: |
7 | * |
8 | * - Test thoroughly. |
9 | * + a neat way to do this might be to have a -DTESTMODE for |
10 | * this file, which #defines console_get_line and |
11 | * get_random_noise to different names in order to be able to |
12 | * link them to test stubs rather than the real ones. That |
13 | * way I can have a test rig which checks whether passphrases |
14 | * are being prompted for. |
15 | */ |
16 | |
17 | #define PUTTY_DO_GLOBALS |
18 | |
19 | #include <stdio.h> |
20 | #include <stdlib.h> |
21 | #include <ctype.h> |
22 | #include <limits.h> |
23 | #include <assert.h> |
24 | #include <time.h> |
25 | |
26 | #include "putty.h" |
27 | #include "ssh.h" |
28 | |
29 | #ifdef TESTMODE |
30 | #define get_random_data get_random_data_diagnostic |
31 | char *get_random_data(int len) |
32 | { |
33 | char *buf = snewn(len, char); |
34 | memset(buf, 'x', len); |
35 | return buf; |
36 | } |
37 | #endif |
38 | |
39 | struct progress { |
40 | int phase, current; |
41 | }; |
42 | |
43 | static void progress_update(void *param, int action, int phase, int iprogress) |
44 | { |
45 | struct progress *p = (struct progress *)param; |
46 | if (action != PROGFN_PROGRESS) |
47 | return; |
48 | if (phase > p->phase) { |
49 | if (p->phase >= 0) |
50 | fputc('\n', stderr); |
51 | p->phase = phase; |
52 | if (iprogress >= 0) |
53 | p->current = iprogress - 1; |
54 | else |
55 | p->current = iprogress; |
56 | } |
57 | while (p->current < iprogress) { |
58 | fputc('+', stdout); |
59 | p->current++; |
60 | } |
61 | fflush(stdout); |
62 | } |
63 | |
64 | static void no_progress(void *param, int action, int phase, int iprogress) |
65 | { |
66 | } |
67 | |
68 | void modalfatalbox(char *p, ...) |
69 | { |
70 | va_list ap; |
71 | fprintf(stderr, "FATAL ERROR: "); |
72 | va_start(ap, p); |
73 | vfprintf(stderr, p, ap); |
74 | va_end(ap); |
75 | fputc('\n', stderr); |
76 | cleanup_exit(1); |
77 | } |
78 | |
79 | /* |
80 | * Stubs to let everything else link sensibly. |
81 | */ |
82 | void log_eventlog(void *handle, const char *event) |
83 | { |
84 | } |
85 | char *x_get_default(const char *key) |
86 | { |
87 | return NULL; |
88 | } |
89 | void sk_cleanup(void) |
90 | { |
91 | } |
92 | |
93 | void showversion(void) |
94 | { |
95 | char *verstr = dupstr(ver); |
96 | verstr[0] = tolower(verstr[0]); |
97 | printf("PuTTYgen %s\n", verstr); |
98 | sfree(verstr); |
99 | } |
100 | |
101 | void usage(void) |
102 | { |
103 | fprintf(stderr, |
104 | "Usage: puttygen ( keyfile | -t type [ -b bits ] )\n" |
105 | " [ -C comment ] [ -P ]\n" |
106 | " [ -o output-keyfile ] [ -O type ]\n"); |
107 | } |
108 | |
109 | void help(void) |
110 | { |
111 | /* |
112 | * Help message is an extended version of the usage message. So |
113 | * start with that, plus a version heading. |
114 | */ |
115 | showversion(); |
116 | usage(); |
117 | fprintf(stderr, |
118 | " -t specify key type when generating (rsa, dsa, rsa1)\n" |
119 | " -C change or specify key comment\n" |
120 | " -P change key passphrase\n" |
121 | " -O specify output type:\n" |
122 | " private output PuTTY private key format\n" |
123 | " private-openssh export OpenSSH private key\n" |
124 | " private-sshcom export ssh.com private key\n" |
125 | " public standard / ssh.com public key\n" |
126 | " public-openssh OpenSSH public key\n" |
127 | " fingerprint output the key fingerprint\n" |
128 | " -l equivalent to `-O fingerprint'\n" |
129 | " -L equivalent to `-O public-openssh'\n" |
130 | " -p equivalent to `-O public'\n" |
131 | ); |
132 | } |
133 | |
134 | static int save_ssh2_pubkey(char *filename, char *comment, |
135 | void *v_pub_blob, int pub_len) |
136 | { |
137 | unsigned char *pub_blob = (unsigned char *)v_pub_blob; |
138 | char *p; |
139 | int i, column; |
140 | FILE *fp; |
141 | |
142 | if (filename) { |
143 | fp = fopen(filename, "wb"); |
144 | if (!fp) |
145 | return 0; |
146 | } else |
147 | fp = stdout; |
148 | |
149 | fprintf(fp, "---- BEGIN SSH2 PUBLIC KEY ----\n"); |
150 | |
151 | if (comment) { |
152 | fprintf(fp, "Comment: \""); |
153 | for (p = comment; *p; p++) { |
154 | if (*p == '\\' || *p == '\"') |
155 | fputc('\\', fp); |
156 | fputc(*p, fp); |
157 | } |
158 | fprintf(fp, "\"\n"); |
159 | } |
160 | |
161 | i = 0; |
162 | column = 0; |
163 | while (i < pub_len) { |
164 | char buf[5]; |
165 | int n = (pub_len - i < 3 ? pub_len - i : 3); |
166 | base64_encode_atom(pub_blob + i, n, buf); |
167 | i += n; |
168 | buf[4] = '\0'; |
169 | fputs(buf, fp); |
170 | if (++column >= 16) { |
171 | fputc('\n', fp); |
172 | column = 0; |
173 | } |
174 | } |
175 | if (column > 0) |
176 | fputc('\n', fp); |
177 | |
178 | fprintf(fp, "---- END SSH2 PUBLIC KEY ----\n"); |
179 | if (filename) |
180 | fclose(fp); |
181 | return 1; |
182 | } |
183 | |
184 | static void move(char *from, char *to) |
185 | { |
186 | int ret; |
187 | |
188 | ret = rename(from, to); |
189 | if (ret) { |
190 | /* |
191 | * This OS may require us to remove the original file first. |
192 | */ |
193 | remove(to); |
194 | ret = rename(from, to); |
195 | } |
196 | if (ret) { |
197 | perror("puttygen: cannot move new file on to old one"); |
198 | exit(1); |
199 | } |
200 | } |
201 | |
202 | static char *blobfp(char *alg, int bits, char *blob, int bloblen) |
203 | { |
204 | char buffer[128]; |
205 | unsigned char digest[16]; |
206 | struct MD5Context md5c; |
207 | int i; |
208 | |
209 | MD5Init(&md5c); |
210 | MD5Update(&md5c, blob, bloblen); |
211 | MD5Final(digest, &md5c); |
212 | |
213 | sprintf(buffer, "%s ", alg); |
214 | if (bits > 0) |
215 | sprintf(buffer + strlen(buffer), "%d ", bits); |
216 | for (i = 0; i < 16; i++) |
217 | sprintf(buffer + strlen(buffer), "%s%02x", i ? ":" : "", |
218 | digest[i]); |
219 | |
220 | return dupstr(buffer); |
221 | } |
222 | |
223 | int main(int argc, char **argv) |
224 | { |
225 | char *infile = NULL; |
226 | Filename infilename; |
227 | enum { NOKEYGEN, RSA1, RSA2, DSA } keytype = NOKEYGEN; |
228 | char *outfile = NULL, *outfiletmp = NULL; |
229 | Filename outfilename; |
230 | enum { PRIVATE, PUBLIC, PUBLICO, FP, OPENSSH, SSHCOM } outtype = PRIVATE; |
231 | int bits = 1024; |
232 | char *comment = NULL, *origcomment = NULL; |
233 | int change_passphrase = FALSE; |
234 | int errs = FALSE, nogo = FALSE; |
235 | int intype = SSH_KEYTYPE_UNOPENABLE; |
236 | int sshver = 0; |
237 | struct ssh2_userkey *ssh2key = NULL; |
238 | struct RSAKey *ssh1key = NULL; |
239 | char *ssh2blob = NULL, *ssh2alg = NULL; |
240 | const struct ssh_signkey *ssh2algf = NULL; |
241 | int ssh2bloblen; |
242 | char *passphrase = NULL; |
243 | int load_encrypted; |
244 | progfn_t progressfn = is_interactive() ? progress_update : no_progress; |
245 | |
246 | /* ------------------------------------------------------------------ |
247 | * Parse the command line to figure out what we've been asked to do. |
248 | */ |
249 | |
250 | /* |
251 | * If run with no arguments at all, print the usage message and |
252 | * return success. |
253 | */ |
254 | if (argc <= 1) { |
255 | usage(); |
256 | return 0; |
257 | } |
258 | |
259 | /* |
260 | * Parse command line arguments. |
261 | */ |
262 | while (--argc) { |
263 | char *p = *++argv; |
264 | if (*p == '-') { |
265 | /* |
266 | * An option. |
267 | */ |
268 | while (p && *++p) { |
269 | char c = *p; |
270 | switch (c) { |
271 | case '-': |
272 | /* |
273 | * Long option. |
274 | */ |
275 | { |
276 | char *opt, *val; |
277 | opt = p++; /* opt will have _one_ leading - */ |
278 | while (*p && *p != '=') |
279 | p++; /* find end of option */ |
280 | if (*p == '=') { |
281 | *p++ = '\0'; |
282 | val = p; |
283 | } else |
284 | val = NULL; |
285 | if (!strcmp(opt, "-help")) { |
286 | help(); |
287 | nogo = TRUE; |
288 | } else if (!strcmp(opt, "-version")) { |
289 | showversion(); |
290 | nogo = TRUE; |
291 | } |
292 | /* |
293 | * A sample option requiring an argument: |
294 | * |
295 | * else if (!strcmp(opt, "-output")) { |
296 | * if (!val) |
297 | * errs = TRUE, error(err_optnoarg, opt); |
298 | * else |
299 | * ofile = val; |
300 | * } |
301 | */ |
302 | else { |
303 | errs = TRUE; |
304 | fprintf(stderr, |
305 | "puttygen: no such option `--%s'\n", opt); |
306 | } |
307 | } |
308 | p = NULL; |
309 | break; |
310 | case 'h': |
311 | case 'V': |
312 | case 'P': |
313 | case 'l': |
314 | case 'L': |
315 | case 'p': |
316 | case 'q': |
317 | /* |
318 | * Option requiring no parameter. |
319 | */ |
320 | switch (c) { |
321 | case 'h': |
322 | help(); |
323 | nogo = TRUE; |
324 | break; |
325 | case 'V': |
326 | showversion(); |
327 | nogo = TRUE; |
328 | break; |
329 | case 'P': |
330 | change_passphrase = TRUE; |
331 | break; |
332 | case 'l': |
333 | outtype = FP; |
334 | break; |
335 | case 'L': |
336 | outtype = PUBLICO; |
337 | break; |
338 | case 'p': |
339 | outtype = PUBLIC; |
340 | break; |
341 | case 'q': |
342 | progressfn = no_progress; |
343 | break; |
344 | } |
345 | break; |
346 | case 't': |
347 | case 'b': |
348 | case 'C': |
349 | case 'O': |
350 | case 'o': |
351 | /* |
352 | * Option requiring parameter. |
353 | */ |
354 | p++; |
355 | if (!*p && argc > 1) |
356 | --argc, p = *++argv; |
357 | else if (!*p) { |
358 | fprintf(stderr, "puttygen: option `-%c' expects a" |
359 | " parameter\n", c); |
360 | errs = TRUE; |
361 | } |
362 | /* |
363 | * Now c is the option and p is the parameter. |
364 | */ |
365 | switch (c) { |
366 | case 't': |
367 | if (!strcmp(p, "rsa") || !strcmp(p, "rsa2")) |
368 | keytype = RSA2, sshver = 2; |
369 | else if (!strcmp(p, "rsa1")) |
370 | keytype = RSA1, sshver = 1; |
371 | else if (!strcmp(p, "dsa") || !strcmp(p, "dss")) |
372 | keytype = DSA, sshver = 2; |
373 | else { |
374 | fprintf(stderr, |
375 | "puttygen: unknown key type `%s'\n", p); |
376 | errs = TRUE; |
377 | } |
378 | break; |
379 | case 'b': |
380 | bits = atoi(p); |
381 | break; |
382 | case 'C': |
383 | comment = p; |
384 | break; |
385 | case 'O': |
386 | if (!strcmp(p, "public")) |
387 | outtype = PUBLIC; |
388 | else if (!strcmp(p, "public-openssh")) |
389 | outtype = PUBLICO; |
390 | else if (!strcmp(p, "private")) |
391 | outtype = PRIVATE; |
392 | else if (!strcmp(p, "fingerprint")) |
393 | outtype = FP; |
394 | else if (!strcmp(p, "private-openssh")) |
395 | outtype = OPENSSH, sshver = 2; |
396 | else if (!strcmp(p, "private-sshcom")) |
397 | outtype = SSHCOM, sshver = 2; |
398 | else { |
399 | fprintf(stderr, |
400 | "puttygen: unknown output type `%s'\n", p); |
401 | errs = TRUE; |
402 | } |
403 | break; |
404 | case 'o': |
405 | outfile = p; |
406 | break; |
407 | } |
408 | p = NULL; /* prevent continued processing */ |
409 | break; |
410 | default: |
411 | /* |
412 | * Unrecognised option. |
413 | */ |
414 | errs = TRUE; |
415 | fprintf(stderr, "puttygen: no such option `-%c'\n", c); |
416 | break; |
417 | } |
418 | } |
419 | } else { |
420 | /* |
421 | * A non-option argument. |
422 | */ |
423 | if (!infile) |
424 | infile = p; |
425 | else { |
426 | errs = TRUE; |
427 | fprintf(stderr, "puttygen: cannot handle more than one" |
428 | " input file\n"); |
429 | } |
430 | } |
431 | } |
432 | |
433 | if (errs) |
434 | return 1; |
435 | |
436 | if (nogo) |
437 | return 0; |
438 | |
439 | /* |
440 | * If run with at least one argument _but_ not the required |
441 | * ones, print the usage message and return failure. |
442 | */ |
443 | if (!infile && keytype == NOKEYGEN) { |
444 | usage(); |
445 | return 1; |
446 | } |
447 | |
448 | /* ------------------------------------------------------------------ |
449 | * Figure out further details of exactly what we're going to do. |
450 | */ |
451 | |
452 | /* |
453 | * Bomb out if we've been asked to both load and generate a |
454 | * key. |
455 | */ |
456 | if (keytype != NOKEYGEN && intype) { |
457 | fprintf(stderr, "puttygen: cannot both load and generate a key\n"); |
458 | return 1; |
459 | } |
460 | |
461 | /* |
462 | * Analyse the type of the input file, in case this affects our |
463 | * course of action. |
464 | */ |
465 | if (infile) { |
466 | infilename = filename_from_str(infile); |
467 | |
468 | intype = key_type(&infilename); |
469 | |
470 | switch (intype) { |
471 | /* |
472 | * It would be nice here to be able to load _public_ |
473 | * key files, in any of a number of forms, and (a) |
474 | * convert them to other public key types, (b) print |
475 | * out their fingerprints. Or, I suppose, for real |
476 | * orthogonality, (c) change their comment! |
477 | * |
478 | * In fact this opens some interesting possibilities. |
479 | * Suppose ssh2_userkey_loadpub() were able to load |
480 | * public key files as well as extracting the public |
481 | * key from private ones. And suppose I did the thing |
482 | * I've been wanting to do, where specifying a |
483 | * particular private key file for authentication |
484 | * causes any _other_ key in the agent to be discarded. |
485 | * Then, if you had an agent forwarded to the machine |
486 | * you were running Unix PuTTY or Plink on, and you |
487 | * needed to specify which of the keys in the agent it |
488 | * should use, you could do that by supplying a |
489 | * _public_ key file, thus not needing to trust even |
490 | * your encrypted private key file to the network. Ooh! |
491 | */ |
492 | |
493 | case SSH_KEYTYPE_UNOPENABLE: |
494 | case SSH_KEYTYPE_UNKNOWN: |
495 | fprintf(stderr, "puttygen: unable to load file `%s': %s\n", |
496 | infile, key_type_to_str(intype)); |
497 | return 1; |
498 | |
499 | case SSH_KEYTYPE_SSH1: |
500 | if (sshver == 2) { |
501 | fprintf(stderr, "puttygen: conversion from SSH1 to SSH2 keys" |
502 | " not supported\n"); |
503 | return 1; |
504 | } |
505 | sshver = 1; |
506 | break; |
507 | |
508 | case SSH_KEYTYPE_SSH2: |
509 | case SSH_KEYTYPE_OPENSSH: |
510 | case SSH_KEYTYPE_SSHCOM: |
511 | if (sshver == 1) { |
512 | fprintf(stderr, "puttygen: conversion from SSH2 to SSH1 keys" |
513 | " not supported\n"); |
514 | return 1; |
515 | } |
516 | sshver = 2; |
517 | break; |
518 | } |
519 | } |
520 | |
521 | /* |
522 | * Determine the default output file, if none is provided. |
523 | * |
524 | * This will usually be equal to stdout, except that if the |
525 | * input and output file formats are the same then the default |
526 | * output is to overwrite the input. |
527 | * |
528 | * Also in this code, we bomb out if the input and output file |
529 | * formats are the same and no other action is performed. |
530 | */ |
531 | if ((intype == SSH_KEYTYPE_SSH1 && outtype == PRIVATE) || |
532 | (intype == SSH_KEYTYPE_SSH2 && outtype == PRIVATE) || |
533 | (intype == SSH_KEYTYPE_OPENSSH && outtype == OPENSSH) || |
534 | (intype == SSH_KEYTYPE_SSHCOM && outtype == SSHCOM)) { |
535 | if (!outfile) { |
536 | outfile = infile; |
537 | outfiletmp = dupcat(outfile, ".tmp"); |
538 | } |
539 | |
540 | if (!change_passphrase && !comment) { |
541 | fprintf(stderr, "puttygen: this command would perform no useful" |
542 | " action\n"); |
543 | return 1; |
544 | } |
545 | } else { |
546 | if (!outfile) { |
547 | /* |
548 | * Bomb out rather than automatically choosing to write |
549 | * a private key file to stdout. |
550 | */ |
551 | if (outtype==PRIVATE || outtype==OPENSSH || outtype==SSHCOM) { |
552 | fprintf(stderr, "puttygen: need to specify an output file\n"); |
553 | return 1; |
554 | } |
555 | } |
556 | } |
557 | |
558 | /* |
559 | * Figure out whether we need to load the encrypted part of the |
560 | * key. This will be the case if either (a) we need to write |
561 | * out a private key format, or (b) the entire input key file |
562 | * is encrypted. |
563 | */ |
564 | if (outtype == PRIVATE || outtype == OPENSSH || outtype == SSHCOM || |
565 | intype == SSH_KEYTYPE_OPENSSH || intype == SSH_KEYTYPE_SSHCOM) |
566 | load_encrypted = TRUE; |
567 | else |
568 | load_encrypted = FALSE; |
569 | |
570 | /* ------------------------------------------------------------------ |
571 | * Now we're ready to actually do some stuff. |
572 | */ |
573 | |
574 | /* |
575 | * Either load or generate a key. |
576 | */ |
577 | if (keytype != NOKEYGEN) { |
578 | char *entropy; |
579 | char default_comment[80]; |
580 | time_t t; |
581 | struct tm *tm; |
582 | struct progress prog; |
583 | |
584 | prog.phase = -1; |
585 | prog.current = -1; |
586 | |
587 | time(&t); |
588 | tm = localtime(&t); |
589 | if (keytype == DSA) |
590 | strftime(default_comment, 30, "dsa-key-%Y%m%d", tm); |
591 | else |
592 | strftime(default_comment, 30, "rsa-key-%Y%m%d", tm); |
593 | |
594 | random_init(); |
595 | entropy = get_random_data(bits / 8); |
596 | random_add_heavynoise(entropy, bits / 8); |
597 | memset(entropy, 0, bits/8); |
598 | sfree(entropy); |
599 | |
600 | if (keytype == DSA) { |
601 | struct dss_key *dsskey = snew(struct dss_key); |
602 | dsa_generate(dsskey, bits, progressfn, &prog); |
603 | ssh2key = snew(struct ssh2_userkey); |
604 | ssh2key->data = dsskey; |
605 | ssh2key->alg = &ssh_dss; |
606 | ssh1key = NULL; |
607 | } else { |
608 | struct RSAKey *rsakey = snew(struct RSAKey); |
609 | rsa_generate(rsakey, bits, progressfn, &prog); |
610 | if (keytype == RSA1) { |
611 | ssh1key = rsakey; |
612 | } else { |
613 | ssh2key = snew(struct ssh2_userkey); |
614 | ssh2key->data = rsakey; |
615 | ssh2key->alg = &ssh_rsa; |
616 | } |
617 | } |
618 | progressfn(&prog, PROGFN_PROGRESS, INT_MAX, -1); |
619 | |
620 | if (ssh2key) |
621 | ssh2key->comment = dupstr(default_comment); |
622 | if (ssh1key) |
623 | ssh1key->comment = dupstr(default_comment); |
624 | |
625 | } else { |
626 | const char *error = NULL; |
627 | int encrypted; |
628 | |
629 | assert(infile != NULL); |
630 | |
631 | /* |
632 | * Find out whether the input key is encrypted. |
633 | */ |
634 | if (intype == SSH_KEYTYPE_SSH1) |
635 | encrypted = rsakey_encrypted(&infilename, &origcomment); |
636 | else if (intype == SSH_KEYTYPE_SSH2) |
637 | encrypted = ssh2_userkey_encrypted(&infilename, &origcomment); |
638 | else |
639 | encrypted = import_encrypted(&infilename, intype, &origcomment); |
640 | |
641 | /* |
642 | * If so, ask for a passphrase. |
643 | */ |
644 | if (encrypted && load_encrypted) { |
645 | passphrase = snewn(512, char); |
646 | if (!console_get_line("Enter passphrase to load key: ", |
647 | passphrase, 512, TRUE)) { |
648 | perror("puttygen: unable to read passphrase"); |
649 | return 1; |
650 | } |
651 | } else { |
652 | passphrase = NULL; |
653 | } |
654 | |
655 | switch (intype) { |
656 | int ret; |
657 | |
658 | case SSH_KEYTYPE_SSH1: |
659 | ssh1key = snew(struct RSAKey); |
660 | if (!load_encrypted) { |
661 | void *vblob; |
662 | char *blob; |
663 | int n, bloblen; |
664 | |
665 | ret = rsakey_pubblob(&infilename, &vblob, &bloblen, &error); |
666 | blob = (char *)vblob; |
667 | |
668 | n = 4; /* skip modulus bits */ |
669 | n += ssh1_read_bignum(blob + n, &ssh1key->exponent); |
670 | n += ssh1_read_bignum(blob + n, &ssh1key->modulus); |
671 | ssh1key->comment = NULL; |
672 | } else { |
673 | ret = loadrsakey(&infilename, ssh1key, passphrase, &error); |
674 | } |
675 | if (ret) |
676 | error = NULL; |
677 | else if (!error) |
678 | error = "unknown error"; |
679 | break; |
680 | |
681 | case SSH_KEYTYPE_SSH2: |
682 | if (!load_encrypted) { |
683 | ssh2blob = ssh2_userkey_loadpub(&infilename, &ssh2alg, |
684 | &ssh2bloblen, &error); |
685 | ssh2algf = find_pubkey_alg(ssh2alg); |
686 | if (ssh2algf) |
687 | bits = ssh2algf->pubkey_bits(ssh2blob, ssh2bloblen); |
688 | else |
689 | bits = -1; |
690 | } else { |
691 | ssh2key = ssh2_load_userkey(&infilename, passphrase, &error); |
692 | } |
693 | if (ssh2key || ssh2blob) |
694 | error = NULL; |
695 | else if (!error) { |
696 | if (ssh2key == SSH2_WRONG_PASSPHRASE) |
697 | error = "wrong passphrase"; |
698 | else |
699 | error = "unknown error"; |
700 | } |
701 | break; |
702 | |
703 | case SSH_KEYTYPE_OPENSSH: |
704 | case SSH_KEYTYPE_SSHCOM: |
705 | ssh2key = import_ssh2(&infilename, intype, passphrase); |
706 | if (ssh2key) |
707 | error = NULL; |
708 | else if (!error) { |
709 | if (ssh2key == SSH2_WRONG_PASSPHRASE) |
710 | error = "wrong passphrase"; |
711 | else |
712 | error = "unknown error"; |
713 | } |
714 | break; |
715 | |
716 | default: |
717 | assert(0); |
718 | } |
719 | |
720 | if (error) { |
721 | fprintf(stderr, "puttygen: error loading `%s': %s\n", |
722 | infile, error); |
723 | return 1; |
724 | } |
725 | } |
726 | |
727 | /* |
728 | * Change the comment if asked to. |
729 | */ |
730 | if (comment) { |
731 | if (sshver == 1) { |
732 | assert(ssh1key); |
733 | sfree(ssh1key->comment); |
734 | ssh1key->comment = dupstr(comment); |
735 | } else { |
736 | assert(ssh2key); |
737 | sfree(ssh2key->comment); |
738 | ssh2key->comment = dupstr(comment); |
739 | } |
740 | } |
741 | |
742 | /* |
743 | * Prompt for a new passphrase if we have been asked to, or if |
744 | * we have just generated a key. |
745 | */ |
746 | if (change_passphrase || keytype != NOKEYGEN) { |
747 | char *passphrase2; |
748 | |
749 | if (passphrase) { |
750 | memset(passphrase, 0, strlen(passphrase)); |
751 | sfree(passphrase); |
752 | } |
753 | |
754 | passphrase = snewn(512, char); |
755 | passphrase2 = snewn(512, char); |
756 | if (!console_get_line("Enter passphrase to save key: ", |
757 | passphrase, 512, TRUE) || |
758 | !console_get_line("Re-enter passphrase to verify: ", |
759 | passphrase2, 512, TRUE)) { |
760 | perror("puttygen: unable to read new passphrase"); |
761 | return 1; |
762 | } |
763 | if (strcmp(passphrase, passphrase2)) { |
764 | fprintf(stderr, "puttygen: passphrases do not match\n"); |
765 | return 1; |
766 | } |
767 | memset(passphrase2, 0, strlen(passphrase2)); |
768 | sfree(passphrase2); |
769 | if (!*passphrase) { |
770 | sfree(passphrase); |
771 | passphrase = NULL; |
772 | } |
773 | } |
774 | |
775 | /* |
776 | * Write output. |
777 | * |
778 | * (In the case where outfile and outfiletmp are both NULL, |
779 | * there is no semantic reason to initialise outfilename at |
780 | * all; but we have to write _something_ to it or some compiler |
781 | * will probably complain that it might be used uninitialised.) |
782 | */ |
783 | if (outfiletmp) |
784 | outfilename = filename_from_str(outfiletmp); |
785 | else |
786 | outfilename = filename_from_str(outfile ? outfile : ""); |
787 | |
788 | switch (outtype) { |
789 | int ret; |
790 | |
791 | case PRIVATE: |
792 | if (sshver == 1) { |
793 | assert(ssh1key); |
794 | ret = saversakey(&outfilename, ssh1key, passphrase); |
795 | if (!ret) { |
796 | fprintf(stderr, "puttygen: unable to save SSH1 private key\n"); |
797 | return 1; |
798 | } |
799 | } else { |
800 | assert(ssh2key); |
801 | ret = ssh2_save_userkey(&outfilename, ssh2key, passphrase); |
802 | if (!ret) { |
803 | fprintf(stderr, "puttygen: unable to save SSH2 private key\n"); |
804 | return 1; |
805 | } |
806 | } |
807 | if (outfiletmp) |
808 | move(outfiletmp, outfile); |
809 | break; |
810 | |
811 | case PUBLIC: |
812 | case PUBLICO: |
813 | if (sshver == 1) { |
814 | FILE *fp; |
815 | char *dec1, *dec2; |
816 | |
817 | assert(ssh1key); |
818 | |
819 | if (outfile) |
820 | fp = f_open(outfilename, "w"); |
821 | else |
822 | fp = stdout; |
823 | dec1 = bignum_decimal(ssh1key->exponent); |
824 | dec2 = bignum_decimal(ssh1key->modulus); |
825 | fprintf(fp, "%d %s %s %s\n", bignum_bitcount(ssh1key->modulus), |
826 | dec1, dec2, ssh1key->comment); |
827 | sfree(dec1); |
828 | sfree(dec2); |
829 | if (outfile) |
830 | fclose(fp); |
831 | } else if (outtype == PUBLIC) { |
832 | if (!ssh2blob) { |
833 | assert(ssh2key); |
834 | ssh2blob = ssh2key->alg->public_blob(ssh2key->data, |
835 | &ssh2bloblen); |
836 | } |
837 | save_ssh2_pubkey(outfile, ssh2key ? ssh2key->comment : origcomment, |
838 | ssh2blob, ssh2bloblen); |
839 | } else if (outtype == PUBLICO) { |
840 | char *buffer, *p; |
841 | int i; |
842 | FILE *fp; |
843 | |
844 | if (!ssh2blob) { |
845 | assert(ssh2key); |
846 | ssh2blob = ssh2key->alg->public_blob(ssh2key->data, |
847 | &ssh2bloblen); |
848 | } |
849 | if (!ssh2alg) { |
850 | assert(ssh2key); |
851 | ssh2alg = ssh2key->alg->name; |
852 | } |
853 | if (ssh2key) |
854 | comment = ssh2key->comment; |
855 | else |
856 | comment = origcomment; |
857 | |
858 | buffer = snewn(strlen(ssh2alg) + |
859 | 4 * ((ssh2bloblen+2) / 3) + |
860 | strlen(comment) + 3, char); |
861 | strcpy(buffer, ssh2alg); |
862 | p = buffer + strlen(buffer); |
863 | *p++ = ' '; |
864 | i = 0; |
865 | while (i < ssh2bloblen) { |
866 | int n = (ssh2bloblen - i < 3 ? ssh2bloblen - i : 3); |
867 | base64_encode_atom(ssh2blob + i, n, p); |
868 | i += n; |
869 | p += 4; |
870 | } |
871 | if (*comment) { |
872 | *p++ = ' '; |
873 | strcpy(p, comment); |
874 | } else |
875 | *p++ = '\0'; |
876 | |
877 | if (outfile) |
878 | fp = f_open(outfilename, "w"); |
879 | else |
880 | fp = stdout; |
881 | fprintf(fp, "%s\n", buffer); |
882 | if (outfile) |
883 | fclose(fp); |
884 | |
885 | sfree(buffer); |
886 | } |
887 | break; |
888 | |
889 | case FP: |
890 | { |
891 | FILE *fp; |
892 | char *fingerprint; |
893 | |
894 | if (sshver == 1) { |
895 | assert(ssh1key); |
896 | fingerprint = snewn(128, char); |
897 | rsa_fingerprint(fingerprint, 128, ssh1key); |
898 | } else { |
899 | if (ssh2key) { |
900 | fingerprint = ssh2key->alg->fingerprint(ssh2key->data); |
901 | } else { |
902 | assert(ssh2blob); |
903 | fingerprint = blobfp(ssh2alg, bits, ssh2blob, ssh2bloblen); |
904 | } |
905 | } |
906 | |
907 | if (outfile) |
908 | fp = f_open(outfilename, "w"); |
909 | else |
910 | fp = stdout; |
911 | fprintf(fp, "%s\n", fingerprint); |
912 | if (outfile) |
913 | fclose(fp); |
914 | |
915 | sfree(fingerprint); |
916 | } |
917 | break; |
918 | |
919 | case OPENSSH: |
920 | case SSHCOM: |
921 | assert(sshver == 2); |
922 | assert(ssh2key); |
923 | ret = export_ssh2(&outfilename, outtype, ssh2key, passphrase); |
924 | if (!ret) { |
925 | fprintf(stderr, "puttygen: unable to export key\n"); |
926 | return 1; |
927 | } |
928 | if (outfiletmp) |
929 | move(outfiletmp, outfile); |
930 | break; |
931 | } |
932 | |
933 | if (passphrase) { |
934 | memset(passphrase, 0, strlen(passphrase)); |
935 | sfree(passphrase); |
936 | } |
937 | |
938 | if (ssh1key) |
939 | freersakey(ssh1key); |
940 | if (ssh2key) { |
941 | ssh2key->alg->freekey(ssh2key->data); |
942 | sfree(ssh2key); |
943 | } |
944 | |
945 | return 0; |
946 | } |