From 44b25040110a224a79ff371ee548be9a10ba8bfa Mon Sep 17 00:00:00 2001 From: Damien Miller Date: Fri, 2 Jul 2010 13:35:01 +1000 Subject: [PATCH] - djm@cvs.openbsd.org 2010/06/29 23:15:30 [ssh-keygen.1 ssh-keygen.c] allow import (-i) and export (-e) of PEM and PKCS#8 encoded keys; bz#1749; ok markus@ --- ChangeLog | 4 + ssh-keygen.1 | 47 ++++++--- ssh-keygen.c | 281 +++++++++++++++++++++++++++++++++++++++++---------- 3 files changed, 266 insertions(+), 66 deletions(-) diff --git a/ChangeLog b/ChangeLog index 20b39f0de..ee40f10eb 100644 --- a/ChangeLog +++ b/ChangeLog @@ -6,6 +6,10 @@ - djm@cvs.openbsd.org 2010/06/26 23:04:04 [ssh.c] oops, forgot to #include ; spotted and patch from chl@ + - djm@cvs.openbsd.org 2010/06/29 23:15:30 + [ssh-keygen.1 ssh-keygen.c] + allow import (-i) and export (-e) of PEM and PKCS#8 encoded keys; + bz#1749; ok markus@ 20100627 - (tim) [openbsd-compat/port-uw.c] Reorder includes. auth-options.h now needs diff --git a/ssh-keygen.1 b/ssh-keygen.1 index 26ae31f5e..0d62255ba 100644 --- a/ssh-keygen.1 +++ b/ssh-keygen.1 @@ -1,4 +1,4 @@ -.\" $OpenBSD: ssh-keygen.1,v 1.94 2010/04/16 06:47:04 jmc Exp $ +.\" $OpenBSD: ssh-keygen.1,v 1.95 2010/06/29 23:15:30 djm Exp $ .\" .\" -*- nroff -*- .\" @@ -37,7 +37,7 @@ .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.Dd $Mdocdate: April 16 2010 $ +.Dd $Mdocdate: June 29 2010 $ .Dt SSH-KEYGEN 1 .Os .Sh NAME @@ -59,9 +59,11 @@ .Op Fl f Ar keyfile .Nm ssh-keygen .Fl i +.Op Fl m Ar key_format .Op Fl f Ar input_keyfile .Nm ssh-keygen .Fl e +.Op Fl m Ar key_format .Op Fl f Ar input_keyfile .Nm ssh-keygen .Fl y @@ -215,11 +217,13 @@ Download the RSA public keys provided by the PKCS#11 shared library .Ar pkcs11 . .It Fl e This option will read a private or public OpenSSH key file and -print the key in -RFC 4716 SSH Public Key File Format -to stdout. -This option allows exporting keys for use by several commercial -SSH implementations. +print to stdout the key in one of the formats specified by the +.Fl m +option. +The default export format is +.Dq RFC4716 . +This option allows exporting OpenSSH key for use by other programs, including +several commercial SSH implementations. .It Fl F Ar hostname Search for the specified .Ar hostname @@ -270,13 +274,14 @@ Please see the section for details. .It Fl i This option will read an unencrypted private (or public) key file -in SSH2-compatible format and print an OpenSSH compatible private +in the format specified by the +.Fl m +option and print an OpenSSH compatible private (or public) key to stdout. -.Nm -also reads the -RFC 4716 SSH Public Key File Format. -This option allows importing keys from several commercial -SSH implementations. +This option allows importing keys from other software, including several +commercial SSH implementations. +The default import format is +.Dq RFC4716 . .It Fl L Prints the contents of a certificate. .It Fl l @@ -288,6 +293,22 @@ tries to find the matching public key file and prints its fingerprint. If combined with .Fl v , an ASCII art representation of the key is supplied with the fingerprint. +.It Fl m Ar key_format +Specify a key format for the +.Fl i +(import) or +.Fl e +(export) coversion options. +The supported key formats are: +.Dq RFC4716 +(RFC4716/SSH2 public or private key), +.Dq PKCS8 +(PEM PKCS8 public key) +or +.Dq PEM +(PEM public key). +The default conversion format is +.Dq RFC4716 . .It Fl M Ar memory Specify the amount of memory to use (in megabytes) when generating candidate moduli for DH-GEX. diff --git a/ssh-keygen.c b/ssh-keygen.c index de7c4409d..be08fbda6 100644 --- a/ssh-keygen.c +++ b/ssh-keygen.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh-keygen.c,v 1.192 2010/06/23 02:59:02 djm Exp $ */ +/* $OpenBSD: ssh-keygen.c,v 1.193 2010/06/29 23:15:30 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1994 Tatu Ylonen , Espoo, Finland @@ -133,14 +133,20 @@ u_int32_t certflags_flags = CERTOPT_DEFAULT; char *certflags_command = NULL; char *certflags_src_addr = NULL; -/* Dump public key file in format used by real and the original SSH 2 */ -int convert_to_ssh2 = 0; -int convert_from_ssh2 = 0; +/* Conversion to/from various formats */ +int convert_to = 0; +int convert_from = 0; +enum { + FMT_RFC4716, + FMT_PKCS8, + FMT_PEM +} convert_format = FMT_RFC4716; int print_public = 0; int print_generic = 0; char *key_type_name = NULL; + /* argv0 */ extern char *__progname; @@ -215,30 +221,12 @@ load_identity(char *filename) #define SSH_COM_PRIVATE_KEY_MAGIC 0x3f6ff9eb static void -do_convert_to_ssh2(struct passwd *pw) +do_convert_to_ssh2(struct passwd *pw, Key *k) { - Key *k; u_int len; u_char *blob; char comment[61]; - struct stat st; - if (!have_identity) - ask_filename(pw, "Enter file in which the key is"); - if (stat(identity_file, &st) < 0) { - perror(identity_file); - exit(1); - } - if ((k = key_load_public(identity_file, NULL)) == NULL) { - if ((k = load_identity(identity_file)) == NULL) { - fprintf(stderr, "load failed\n"); - exit(1); - } - } - if (k->type == KEY_RSA1) { - fprintf(stderr, "version 1 keys are not supported\n"); - exit(1); - } if (key_to_blob(k, &blob, &len) <= 0) { fprintf(stderr, "key_to_blob failed\n"); exit(1); @@ -258,6 +246,81 @@ do_convert_to_ssh2(struct passwd *pw) exit(0); } +static void +do_convert_to_pkcs8(Key *k) +{ + switch (key_type_plain(k->type)) { + case KEY_RSA: + if (!PEM_write_RSA_PUBKEY(stdout, k->rsa)) + fatal("PEM_write_RSA_PUBKEY failed"); + break; + case KEY_DSA: + if (!PEM_write_DSA_PUBKEY(stdout, k->dsa)) + fatal("PEM_write_DSA_PUBKEY failed"); + break; + default: + fatal("%s: unsupported key type %s", __func__, key_type(k)); + } + exit(0); +} + +static void +do_convert_to_pem(Key *k) +{ + switch (key_type_plain(k->type)) { + case KEY_RSA: + if (!PEM_write_RSAPublicKey(stdout, k->rsa)) + fatal("PEM_write_RSAPublicKey failed"); + break; +#if notyet /* OpenSSH 0.9.8 lacks this function */ + case KEY_DSA: + if (!PEM_write_DSAPublicKey(stdout, k->dsa)) + fatal("PEM_write_DSAPublicKey failed"); + break; +#endif + default: + fatal("%s: unsupported key type %s", __func__, key_type(k)); + } + exit(0); +} + +static void +do_convert_to(struct passwd *pw) +{ + Key *k; + struct stat st; + + if (!have_identity) + ask_filename(pw, "Enter file in which the key is"); + if (stat(identity_file, &st) < 0) + fatal("%s: %s: %s", __progname, identity_file, strerror(errno)); + if ((k = key_load_public(identity_file, NULL)) == NULL) { + if ((k = load_identity(identity_file)) == NULL) { + fprintf(stderr, "load failed\n"); + exit(1); + } + } + if (k->type == KEY_RSA1) { + fprintf(stderr, "version 1 keys are not supported\n"); + exit(1); + } + + switch (convert_format) { + case FMT_RFC4716: + do_convert_to_ssh2(pw, k); + break; + case FMT_PKCS8: + do_convert_to_pkcs8(k); + break; + case FMT_PEM: + do_convert_to_pem(k); + break; + default: + fatal("%s: unknown key format %d", __func__, convert_format); + } + exit(0); +} + static void buffer_get_bignum_bits(Buffer *b, BIGNUM *value) { @@ -396,24 +459,16 @@ get_line(FILE *fp, char *line, size_t len) } static void -do_convert_from_ssh2(struct passwd *pw) +do_convert_from_ssh2(struct passwd *pw, Key **k, int *private) { - Key *k; int blen; u_int len; char line[1024]; u_char blob[8096]; char encoded[8096]; - struct stat st; - int escaped = 0, private = 0, ok; + int escaped = 0; FILE *fp; - if (!have_identity) - ask_filename(pw, "Enter file in which the key is"); - if (stat(identity_file, &st) < 0) { - perror(identity_file); - exit(1); - } if ((fp = fopen(identity_file, "r")) == NULL) fatal("%s: %s: %s", __progname, identity_file, strerror(errno)); encoded[0] = '\0'; @@ -423,7 +478,7 @@ do_convert_from_ssh2(struct passwd *pw) if (strncmp(line, "----", 4) == 0 || strstr(line, ": ") != NULL) { if (strstr(line, SSH_COM_PRIVATE_BEGIN) != NULL) - private = 1; + *private = 1; if (strstr(line, " END ") != NULL) { break; } @@ -448,26 +503,130 @@ do_convert_from_ssh2(struct passwd *pw) fprintf(stderr, "uudecode failed.\n"); exit(1); } - k = private ? + *k = *private ? do_convert_private_ssh2_from_blob(blob, blen) : key_from_blob(blob, blen); - if (k == NULL) { + if (*k == NULL) { fprintf(stderr, "decode blob failed.\n"); exit(1); } - ok = private ? - (k->type == KEY_DSA ? - PEM_write_DSAPrivateKey(stdout, k->dsa, NULL, NULL, 0, NULL, NULL) : - PEM_write_RSAPrivateKey(stdout, k->rsa, NULL, NULL, 0, NULL, NULL)) : - key_write(k, stdout); + fclose(fp); +} + +static void +do_convert_from_pkcs8(Key **k, int *private) +{ + EVP_PKEY *pubkey; + FILE *fp; + + if ((fp = fopen(identity_file, "r")) == NULL) + fatal("%s: %s: %s", __progname, identity_file, strerror(errno)); + if ((pubkey = PEM_read_PUBKEY(fp, NULL, NULL, NULL)) == NULL) { + fatal("%s: %s is not a recognised public key format", __func__, + identity_file); + } + fclose(fp); + switch (EVP_PKEY_type(pubkey->type)) { + case EVP_PKEY_RSA: + *k = key_new(KEY_UNSPEC); + (*k)->type = KEY_RSA; + (*k)->rsa = EVP_PKEY_get1_RSA(pubkey); + break; + case EVP_PKEY_DSA: + *k = key_new(KEY_UNSPEC); + (*k)->type = KEY_DSA; + (*k)->dsa = EVP_PKEY_get1_DSA(pubkey); + break; + default: + fatal("%s: unsupported pubkey type %d", __func__, + EVP_PKEY_type(pubkey->type)); + } + EVP_PKEY_free(pubkey); + return; +} + +static void +do_convert_from_pem(Key **k, int *private) +{ + FILE *fp; + RSA *rsa; +#ifdef notyet + DSA *dsa; +#endif + + if ((fp = fopen(identity_file, "r")) == NULL) + fatal("%s: %s: %s", __progname, identity_file, strerror(errno)); + if ((rsa = PEM_read_RSAPublicKey(fp, NULL, NULL, NULL)) != NULL) { + *k = key_new(KEY_UNSPEC); + (*k)->type = KEY_RSA; + (*k)->rsa = rsa; + fclose(fp); + return; + } +#if notyet /* OpenSSH 0.9.8 lacks this function */ + rewind(fp); + if ((dsa = PEM_read_DSAPublicKey(fp, NULL, NULL, NULL)) != NULL) { + *k = key_new(KEY_UNSPEC); + (*k)->type = KEY_DSA; + (*k)->dsa = dsa; + fclose(fp); + return; + } +#endif + fatal("%s: unrecognised raw private key format", __func__); +} + +static void +do_convert_from(struct passwd *pw) +{ + Key *k = NULL; + int private = 0, ok; + struct stat st; + + if (!have_identity) + ask_filename(pw, "Enter file in which the key is"); + if (stat(identity_file, &st) < 0) + fatal("%s: %s: %s", __progname, identity_file, strerror(errno)); + + switch (convert_format) { + case FMT_RFC4716: + do_convert_from_ssh2(pw, &k, &private); + break; + case FMT_PKCS8: + do_convert_from_pkcs8(&k, &private); + break; + case FMT_PEM: + do_convert_from_pem(&k, &private); + break; + default: + fatal("%s: unknown key format %d", __func__, convert_format); + } + + if (!private) + ok = key_write(k, stdout); + if (ok) + fprintf(stdout, "\n"); + else { + switch (k->type) { + case KEY_DSA: + ok = PEM_write_DSAPrivateKey(stdout, k->dsa, NULL, + NULL, 0, NULL, NULL); + break; + case KEY_RSA: + ok = PEM_write_RSAPrivateKey(stdout, k->rsa, NULL, + NULL, 0, NULL, NULL); + break; + default: + fatal("%s: unsupported key type %s", __func__, + key_type(k)); + } + } + if (!ok) { fprintf(stderr, "key write failed\n"); exit(1); } key_free(k); - if (!private) - fprintf(stdout, "\n"); - fclose(fp); exit(0); } @@ -1525,7 +1684,7 @@ usage(void) #ifdef ENABLE_PKCS11 fprintf(stderr, " -D pkcs11 Download public key from pkcs11 token.\n"); #endif - fprintf(stderr, " -e Convert OpenSSH to RFC 4716 key file.\n"); + fprintf(stderr, " -e Export OpenSSH to foreign format key file.\n"); fprintf(stderr, " -F hostname Find hostname in known hosts file.\n"); fprintf(stderr, " -f filename Filename of the key file.\n"); fprintf(stderr, " -G file Generate candidates for DH-GEX moduli.\n"); @@ -1533,9 +1692,10 @@ usage(void) fprintf(stderr, " -H Hash names in known_hosts file.\n"); fprintf(stderr, " -h Generate host certificate instead of a user certificate.\n"); fprintf(stderr, " -I key_id Key identifier to include in certificate.\n"); - fprintf(stderr, " -i Convert RFC 4716 to OpenSSH key file.\n"); + fprintf(stderr, " -i Import foreign format to OpenSSH key file.\n"); fprintf(stderr, " -L Print the contents of a certificate.\n"); fprintf(stderr, " -l Show fingerprint of key file.\n"); + fprintf(stderr, " -m key_fmt Conversion format for -e/-i (PEM|PKCS8|RFC4716).\n"); fprintf(stderr, " -M memory Amount of memory (MB) to use for generating DH-GEX moduli.\n"); fprintf(stderr, " -n name,... User/host principal names to include in certificate\n"); fprintf(stderr, " -N phrase Provide new passphrase.\n"); @@ -1603,7 +1763,7 @@ main(int argc, char **argv) exit(1); } - while ((opt = getopt(argc, argv, "degiqpclBHLhvxXyF:b:f:t:D:I:P:N:n:" + while ((opt = getopt(argc, argv, "degiqpclBHLhvxXyF:b:f:t:D:I:P:m:N:n:" "O:C:r:g:R:T:G:M:S:s:a:V:W:z:")) != -1) { switch (opt) { case 'b': @@ -1635,6 +1795,21 @@ main(int argc, char **argv) case 'B': print_bubblebabble = 1; break; + case 'm': + if (strcasecmp(optarg, "RFC4716") == 0 || + strcasecmp(optarg, "ssh2") == 0) { + convert_format = FMT_RFC4716; + break; + } + if (strcasecmp(optarg, "PKCS8") == 0) { + convert_format = FMT_PKCS8; + break; + } + if (strcasecmp(optarg, "PEM") == 0) { + convert_format = FMT_PEM; + break; + } + fatal("Unsupported conversion format \"%s\"", optarg); case 'n': cert_principals = optarg; break; @@ -1671,7 +1846,7 @@ main(int argc, char **argv) case 'e': case 'x': /* export key */ - convert_to_ssh2 = 1; + convert_to = 1; break; case 'h': cert_key_type = SSH2_CERT_TYPE_HOST; @@ -1680,7 +1855,7 @@ main(int argc, char **argv) case 'i': case 'X': /* import key */ - convert_from_ssh2 = 1; + convert_from = 1; break; case 'y': print_public = 1; @@ -1796,10 +1971,10 @@ main(int argc, char **argv) do_change_passphrase(pw); if (change_comment) do_change_comment(pw); - if (convert_to_ssh2) - do_convert_to_ssh2(pw); - if (convert_from_ssh2) - do_convert_from_ssh2(pw); + if (convert_to) + do_convert_to(pw); + if (convert_from) + do_convert_from(pw); if (print_public) do_print_public(pw); if (rr_hostname != NULL) {