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