2 * cmdgen.c - command-line form of PuTTYgen
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.
17 #define PUTTY_DO_GLOBALS
30 #define get_random_data get_random_data_diagnostic
31 char *get_random_data(int len
)
33 char *buf
= snewn(len
, char);
34 memset(buf
, 'x', len
);
43 static void progress_update(void *param
, int action
, int phase
, int iprogress
)
45 struct progress
*p
= (struct progress
*)param
;
46 if (action
!= PROGFN_PROGRESS
)
48 if (phase
> p
->phase
) {
53 p
->current
= iprogress
- 1;
55 p
->current
= iprogress
;
57 while (p
->current
< iprogress
) {
64 static void no_progress(void *param
, int action
, int phase
, int iprogress
)
68 void modalfatalbox(char *p
, ...)
71 fprintf(stderr
, "FATAL ERROR: ");
73 vfprintf(stderr
, p
, ap
);
80 * Stubs to let everything else link sensibly.
82 void log_eventlog(void *handle
, const char *event
)
85 char *x_get_default(const char *key
)
93 void showversion(void)
95 char *verstr
= dupstr(ver
);
96 verstr
[0] = tolower(verstr
[0]);
97 printf("PuTTYgen %s\n", verstr
);
104 "Usage: puttygen ( keyfile | -t type [ -b bits ] )\n"
105 " [ -C comment ] [ -P ]\n"
106 " [ -o output-keyfile ] [ -O type ]\n");
112 * Help message is an extended version of the usage message. So
113 * start with that, plus a version heading.
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"
134 static int save_ssh2_pubkey(char *filename
, char *comment
,
135 void *v_pub_blob
, int pub_len
)
137 unsigned char *pub_blob
= (unsigned char *)v_pub_blob
;
143 fp
= fopen(filename
, "wb");
149 fprintf(fp
, "---- BEGIN SSH2 PUBLIC KEY ----\n");
152 fprintf(fp
, "Comment: \"");
153 for (p
= comment
; *p
; p
++) {
154 if (*p
== '\\' || *p
== '\"')
163 while (i
< pub_len
) {
165 int n
= (pub_len
- i
< 3 ? pub_len
- i
: 3);
166 base64_encode_atom(pub_blob
+ i
, n
, buf
);
170 if (++column
>= 16) {
178 fprintf(fp
, "---- END SSH2 PUBLIC KEY ----\n");
184 static void move(char *from
, char *to
)
188 ret
= rename(from
, to
);
191 * This OS may require us to remove the original file first.
194 ret
= rename(from
, to
);
197 perror("puttygen: cannot move new file on to old one");
202 static char *blobfp(char *alg
, int bits
, char *blob
, int bloblen
)
205 unsigned char digest
[16];
206 struct MD5Context md5c
;
210 MD5Update(&md5c
, blob
, bloblen
);
211 MD5Final(digest
, &md5c
);
213 sprintf(buffer
, "%s ", alg
);
215 sprintf(buffer
+ strlen(buffer
), "%d ", bits
);
216 for (i
= 0; i
< 16; i
++)
217 sprintf(buffer
+ strlen(buffer
), "%s%02x", i ?
":" : "",
220 return dupstr(buffer
);
223 int main(int argc
, char **argv
)
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
;
232 char *comment
= NULL
, *origcomment
= NULL
;
233 int change_passphrase
= FALSE
;
234 int errs
= FALSE
, nogo
= FALSE
;
235 int intype
= SSH_KEYTYPE_UNOPENABLE
;
237 struct ssh2_userkey
*ssh2key
= NULL
;
238 struct RSAKey
*ssh1key
= NULL
;
239 char *ssh2blob
= NULL
, *ssh2alg
= NULL
;
240 const struct ssh_signkey
*ssh2algf
= NULL
;
242 char *passphrase
= NULL
;
244 progfn_t progressfn
= is_interactive() ? progress_update
: no_progress
;
246 /* ------------------------------------------------------------------
247 * Parse the command line to figure out what we've been asked to do.
251 * If run with no arguments at all, print the usage message and
260 * Parse command line arguments.
277 opt
= p
++; /* opt will have _one_ leading - */
278 while (*p
&& *p
!= '=')
279 p
++; /* find end of option */
285 if (!strcmp(opt
, "-help")) {
288 } else if (!strcmp(opt
, "-version")) {
293 * A sample option requiring an argument:
295 * else if (!strcmp(opt, "-output")) {
297 * errs = TRUE, error(err_optnoarg, opt);
305 "puttygen: no such option `--%s'\n", opt
);
318 * Option requiring no parameter.
330 change_passphrase
= TRUE
;
342 progressfn
= no_progress
;
352 * Option requiring parameter.
358 fprintf(stderr
, "puttygen: option `-%c' expects a"
363 * Now c is the option and p is the parameter.
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;
375 "puttygen: unknown key type `%s'\n", p
);
386 if (!strcmp(p
, "public"))
388 else if (!strcmp(p
, "public-openssh"))
390 else if (!strcmp(p
, "private"))
392 else if (!strcmp(p
, "fingerprint"))
394 else if (!strcmp(p
, "private-openssh"))
395 outtype
= OPENSSH
, sshver
= 2;
396 else if (!strcmp(p
, "private-sshcom"))
397 outtype
= SSHCOM
, sshver
= 2;
400 "puttygen: unknown output type `%s'\n", p
);
408 p
= NULL
; /* prevent continued processing */
412 * Unrecognised option.
415 fprintf(stderr
, "puttygen: no such option `-%c'\n", c
);
421 * A non-option argument.
427 fprintf(stderr
, "puttygen: cannot handle more than one"
440 * If run with at least one argument _but_ not the required
441 * ones, print the usage message and return failure.
443 if (!infile
&& keytype
== NOKEYGEN
) {
448 /* ------------------------------------------------------------------
449 * Figure out further details of exactly what we're going to do.
453 * Bomb out if we've been asked to both load and generate a
456 if (keytype
!= NOKEYGEN
&& intype
) {
457 fprintf(stderr
, "puttygen: cannot both load and generate a key\n");
462 * Analyse the type of the input file, in case this affects our
466 infilename
= filename_from_str(infile
);
468 intype
= key_type(&infilename
);
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!
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!
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
));
499 case SSH_KEYTYPE_SSH1
:
501 fprintf(stderr
, "puttygen: conversion from SSH1 to SSH2 keys"
508 case SSH_KEYTYPE_SSH2
:
509 case SSH_KEYTYPE_OPENSSH
:
510 case SSH_KEYTYPE_SSHCOM
:
512 fprintf(stderr
, "puttygen: conversion from SSH2 to SSH1 keys"
522 * Determine the default output file, if none is provided.
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.
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.
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
)) {
537 outfiletmp
= dupcat(outfile
, ".tmp");
540 if (!change_passphrase
&& !comment
) {
541 fprintf(stderr
, "puttygen: this command would perform no useful"
548 * Bomb out rather than automatically choosing to write
549 * a private key file to stdout.
551 if (outtype
==PRIVATE
|| outtype
==OPENSSH
|| outtype
==SSHCOM
) {
552 fprintf(stderr
, "puttygen: need to specify an output file\n");
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
564 if (outtype
== PRIVATE
|| outtype
== OPENSSH
|| outtype
== SSHCOM
||
565 intype
== SSH_KEYTYPE_OPENSSH
|| intype
== SSH_KEYTYPE_SSHCOM
)
566 load_encrypted
= TRUE
;
568 load_encrypted
= FALSE
;
570 /* ------------------------------------------------------------------
571 * Now we're ready to actually do some stuff.
575 * Either load or generate a key.
577 if (keytype
!= NOKEYGEN
) {
579 char default_comment
[80];
582 struct progress prog
;
590 strftime(default_comment
, 30, "dsa-key-%Y%m%d", tm
);
592 strftime(default_comment
, 30, "rsa-key-%Y%m%d", tm
);
595 entropy
= get_random_data(bits
/ 8);
596 random_add_heavynoise(entropy
, bits
/ 8);
597 memset(entropy
, 0, bits
/8);
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
;
608 struct RSAKey
*rsakey
= snew(struct RSAKey
);
609 rsa_generate(rsakey
, bits
, progressfn
, &prog
);
610 if (keytype
== RSA1
) {
613 ssh2key
= snew(struct ssh2_userkey
);
614 ssh2key
->data
= rsakey
;
615 ssh2key
->alg
= &ssh_rsa
;
618 progressfn(&prog
, PROGFN_PROGRESS
, INT_MAX
, -1);
621 ssh2key
->comment
= dupstr(default_comment
);
623 ssh1key
->comment
= dupstr(default_comment
);
626 const char *error
= NULL
;
629 assert(infile
!= NULL
);
632 * Find out whether the input key is encrypted.
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
);
639 encrypted
= import_encrypted(&infilename
, intype
, &origcomment
);
642 * If so, ask for a passphrase.
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");
658 case SSH_KEYTYPE_SSH1
:
659 ssh1key
= snew(struct RSAKey
);
660 if (!load_encrypted
) {
665 ret
= rsakey_pubblob(&infilename
, &vblob
, &bloblen
, &error
);
666 blob
= (char *)vblob
;
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
;
673 ret
= loadrsakey(&infilename
, ssh1key
, passphrase
, &error
);
678 error
= "unknown error";
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
);
687 bits
= ssh2algf
->pubkey_bits(ssh2blob
, ssh2bloblen
);
691 ssh2key
= ssh2_load_userkey(&infilename
, passphrase
, &error
);
693 if (ssh2key
|| ssh2blob
)
696 if (ssh2key
== SSH2_WRONG_PASSPHRASE
)
697 error
= "wrong passphrase";
699 error
= "unknown error";
703 case SSH_KEYTYPE_OPENSSH
:
704 case SSH_KEYTYPE_SSHCOM
:
705 ssh2key
= import_ssh2(&infilename
, intype
, passphrase
);
709 if (ssh2key
== SSH2_WRONG_PASSPHRASE
)
710 error
= "wrong passphrase";
712 error
= "unknown error";
721 fprintf(stderr
, "puttygen: error loading `%s': %s\n",
728 * Change the comment if asked to.
733 sfree(ssh1key
->comment
);
734 ssh1key
->comment
= dupstr(comment
);
737 sfree(ssh2key
->comment
);
738 ssh2key
->comment
= dupstr(comment
);
743 * Prompt for a new passphrase if we have been asked to, or if
744 * we have just generated a key.
746 if (change_passphrase
|| keytype
!= NOKEYGEN
) {
750 memset(passphrase
, 0, strlen(passphrase
));
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");
763 if (strcmp(passphrase
, passphrase2
)) {
764 fprintf(stderr
, "puttygen: passphrases do not match\n");
767 memset(passphrase2
, 0, strlen(passphrase2
));
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.)
784 outfilename
= filename_from_str(outfiletmp
);
786 outfilename
= filename_from_str(outfile ? outfile
: "");
794 ret
= saversakey(&outfilename
, ssh1key
, passphrase
);
796 fprintf(stderr
, "puttygen: unable to save SSH1 private key\n");
801 ret
= ssh2_save_userkey(&outfilename
, ssh2key
, passphrase
);
803 fprintf(stderr
, "puttygen: unable to save SSH2 private key\n");
808 move(outfiletmp
, outfile
);
820 fp
= f_open(outfilename
, "w");
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
);
831 } else if (outtype
== PUBLIC
) {
834 ssh2blob
= ssh2key
->alg
->public_blob(ssh2key
->data
,
837 save_ssh2_pubkey(outfile
, ssh2key ? ssh2key
->comment
: origcomment
,
838 ssh2blob
, ssh2bloblen
);
839 } else if (outtype
== PUBLICO
) {
846 ssh2blob
= ssh2key
->alg
->public_blob(ssh2key
->data
,
851 ssh2alg
= ssh2key
->alg
->name
;
854 comment
= ssh2key
->comment
;
856 comment
= origcomment
;
858 buffer
= snewn(strlen(ssh2alg
) +
859 4 * ((ssh2bloblen
+2) / 3) +
860 strlen(comment
) + 3, char);
861 strcpy(buffer
, ssh2alg
);
862 p
= buffer
+ strlen(buffer
);
865 while (i
< ssh2bloblen
) {
866 int n
= (ssh2bloblen
- i
< 3 ? ssh2bloblen
- i
: 3);
867 base64_encode_atom(ssh2blob
+ i
, n
, p
);
878 fp
= f_open(outfilename
, "w");
881 fprintf(fp
, "%s\n", buffer
);
896 fingerprint
= snewn(128, char);
897 rsa_fingerprint(fingerprint
, 128, ssh1key
);
900 fingerprint
= ssh2key
->alg
->fingerprint(ssh2key
->data
);
903 fingerprint
= blobfp(ssh2alg
, bits
, ssh2blob
, ssh2bloblen
);
908 fp
= f_open(outfilename
, "w");
911 fprintf(fp
, "%s\n", fingerprint
);
923 ret
= export_ssh2(&outfilename
, outtype
, ssh2key
, passphrase
);
925 fprintf(stderr
, "puttygen: unable to export key\n");
929 move(outfiletmp
, outfile
);
934 memset(passphrase
, 0, strlen(passphrase
));
941 ssh2key
->alg
->freekey(ssh2key
->data
);