diff --git a/NEWS b/NEWS index da771f115..ba140796d 100644 --- a/NEWS +++ b/NEWS @@ -42,6 +42,9 @@ Noteworthy changes in version 2.1.0-betaN (unreleased) * Protect against rogue keyservers sending secret keys. + * GPGSM can now be used to export a secret RSA key in PKCS#1 or + PKCS#8 format. + Noteworthy changes in version 2.1.0beta3 (2011-12-20) ----------------------------------------------------- diff --git a/doc/gpgsm.texi b/doc/gpgsm.texi index 3d2594f68..b38ad4d72 100644 --- a/doc/gpgsm.texi +++ b/doc/gpgsm.texi @@ -259,13 +259,26 @@ certificate are only exported if all @var{pattern} are given as fingerprints or keygrips. @item --export-secret-key-p12 @var{key-id} -@opindex export +@opindex export-secret-key-p12 Export the private key and the certificate identified by @var{key-id} in -a PKCS#12 format. When using along with the @code{--armor} option a few +a PKCS#12 format. When used with the @code{--armor} option a few informational lines are prepended to the output. Note, that the PKCS#12 format is not very secure and this command is only provided if there is no other way to exchange the private key. (@pxref{option --p12-charset}) +@ifset gpgtwoone +@item --export-secret-key-p8 @var{key-id} +@itemx --export-secret-key-raw @var{key-id} +@opindex export-secret-key-p8 +@opindex export-secret-key-raw +Export the private key of the certificate identified by @var{key-id} +with any encryption stripped. The @code{...-raw} command exports in +PKCS#1 format; the @code{...-p8} command exports in PKCS#8 format. +When used with the @code{--armor} option a few informational lines are +prepended to the output. These commands are useful to prepare a key +for use on a TLS server. +@end ifset + @item --import [@var{files}] @opindex import Import the certificates from the PEM or binary encoded files as well as diff --git a/sm/export.c b/sm/export.c index 0403fe2f5..1dce106a1 100644 --- a/sm/export.c +++ b/sm/export.c @@ -60,6 +60,7 @@ static void print_short_info (ksba_cert_t cert, estream_t stream); static gpg_error_t export_p12 (ctrl_t ctrl, const unsigned char *certimg, size_t certimglen, const char *prompt, const char *keygrip, + int rawmode, void **r_result, size_t *r_resultlen); @@ -315,9 +316,14 @@ gpgsm_export (ctrl_t ctrl, strlist_t names, estream_t stream) } -/* Export a certificate and its private key. */ +/* Export a certificate and its private key. RAWMODE controls the + actual output: + 0 - Private key and certifciate in PKCS#12 format + 1 - Only unencrypted private key in PKCS#8 format + 2 - Only unencrypted private key in PKCS#1 format + */ void -gpgsm_p12_export (ctrl_t ctrl, const char *name, estream_t stream) +gpgsm_p12_export (ctrl_t ctrl, const char *name, estream_t stream, int rawmode) { gpg_error_t err = 0; KEYDB_HANDLE hd; @@ -416,13 +422,18 @@ gpgsm_p12_export (ctrl_t ctrl, const char *name, estream_t stream) es_putc ('\n', stream); } - if (opt.p12_charset && ctrl->create_pem) + if (opt.p12_charset && ctrl->create_pem && !rawmode) { es_fprintf (stream, "The passphrase is %s encoded.\n\n", opt.p12_charset); } - ctrl->pem_name = "PKCS12"; + if (rawmode == 0) + ctrl->pem_name = "PKCS12"; + else if (rawmode == 1) + ctrl->pem_name = "PRIVATE KEY"; + else + ctrl->pem_name = "RSA PRIVATE KEY"; err = gpgsm_create_writer (&b64writer, ctrl, stream, &writer); if (err) { @@ -431,7 +442,8 @@ gpgsm_p12_export (ctrl_t ctrl, const char *name, estream_t stream) } prompt = gpgsm_format_keydesc (cert); - err = export_p12 (ctrl, image, imagelen, prompt, keygrip, &data, &datalen); + err = export_p12 (ctrl, image, imagelen, prompt, keygrip, rawmode, + &data, &datalen); xfree (prompt); if (err) goto leave; @@ -513,12 +525,19 @@ print_short_info (ksba_cert_t cert, estream_t stream) xfree (p); } es_putc ('\n', stream); + + p = gpgsm_get_keygrip_hexstring (cert); + if (p) + { + es_fprintf (stream, "Keygrip ..: %s\n", p); + xfree (p); + } } -/* Parse a private key S-expression and retutn a malloced array with - the RSA paramaters in pkcs#12 order. The caller needs to +/* Parse a private key S-expression and return a malloced array with + the RSA parameters in pkcs#12 order. The caller needs to deep-release this array. */ static gcry_mpi_t * sexp_to_kparms (gcry_sexp_t sexp) @@ -587,7 +606,7 @@ sexp_to_kparms (gcry_sexp_t sexp) static gpg_error_t export_p12 (ctrl_t ctrl, const unsigned char *certimg, size_t certimglen, - const char *prompt, const char *keygrip, + const char *prompt, const char *keygrip, int rawmode, void **r_result, size_t *r_resultlen) { gpg_error_t err = 0; @@ -671,20 +690,30 @@ export_p12 (ctrl_t ctrl, const unsigned char *certimg, size_t certimglen, goto leave; } - err = gpgsm_agent_ask_passphrase - (ctrl, - i18n_utf8 ("Please enter the passphrase to protect the " - "new PKCS#12 object."), - 1, &passphrase); - if (err) - goto leave; + if (rawmode) + { + /* Export in raw mode, that is only the pkcs#1/#8 private key. */ + result = p12_raw_build (kparms, rawmode, &resultlen); + if (!result) + err = gpg_error (GPG_ERR_GENERAL); + } + else + { + err = gpgsm_agent_ask_passphrase + (ctrl, + i18n_utf8 ("Please enter the passphrase to protect the " + "new PKCS#12 object."), + 1, &passphrase); + if (err) + goto leave; - result = p12_build (kparms, certimg, certimglen, passphrase, - opt.p12_charset, &resultlen); - xfree (passphrase); - passphrase = NULL; - if (!result) - err = gpg_error (GPG_ERR_GENERAL); + result = p12_build (kparms, certimg, certimglen, passphrase, + opt.p12_charset, &resultlen); + xfree (passphrase); + passphrase = NULL; + if (!result) + err = gpg_error (GPG_ERR_GENERAL); + } leave: xfree (key); diff --git a/sm/gpgsm.c b/sm/gpgsm.c index 3822717ca..01f33e3ae 100644 --- a/sm/gpgsm.c +++ b/sm/gpgsm.c @@ -74,6 +74,8 @@ enum cmd_and_opt_values { aRecvKeys, aExport, aExportSecretKeyP12, + aExportSecretKeyP8, + aExportSecretKeyRaw, aServer, aLearnCard, aCallDirmngr, @@ -208,7 +210,13 @@ static ARGPARSE_OPTS opts[] = { /*ARGPARSE_c (aRecvKeys, "recv-keys", N_("import keys from a key server")),*/ ARGPARSE_c (aImport, "import", N_("import certificates")), ARGPARSE_c (aExport, "export", N_("export certificates")), + + /* We use -raw and not -p1 for pkcs#1 secret key export so that it + won't accidently be used in case -p12 was intended. */ ARGPARSE_c (aExportSecretKeyP12, "export-secret-key-p12", "@"), + ARGPARSE_c (aExportSecretKeyP8, "export-secret-key-p8", "@"), + ARGPARSE_c (aExportSecretKeyRaw, "export-secret-key-raw", "@"), + ARGPARSE_c (aLearnCard, "learn-card", N_("register a smartcard")), ARGPARSE_c (aServer, "server", N_("run in server mode")), ARGPARSE_c (aCallDirmngr, "call-dirmngr", @@ -1084,6 +1092,8 @@ main ( int argc, char **argv) case aRecvKeys: case aExport: case aExportSecretKeyP12: + case aExportSecretKeyP8: + case aExportSecretKeyRaw: case aDumpKeys: case aDumpChain: case aDumpExternalKeys: @@ -1888,7 +1898,7 @@ main ( int argc, char **argv) estream_t fp = open_es_fwrite (opt.outfile?opt.outfile:"-"); if (argc == 1) - gpgsm_p12_export (&ctrl, *argv, fp); + gpgsm_p12_export (&ctrl, *argv, fp, 0); else wrong_args ("--export-secret-key-p12 KEY-ID"); if (fp != es_stdout) @@ -1896,6 +1906,32 @@ main ( int argc, char **argv) } break; + case aExportSecretKeyP8: + { + estream_t fp = open_es_fwrite (opt.outfile?opt.outfile:"-"); + + if (argc == 1) + gpgsm_p12_export (&ctrl, *argv, fp, 1); + else + wrong_args ("--export-secret-key-p8 KEY-ID"); + if (fp != es_stdout) + es_fclose (fp); + } + break; + + case aExportSecretKeyRaw: + { + estream_t fp = open_es_fwrite (opt.outfile?opt.outfile:"-"); + + if (argc == 1) + gpgsm_p12_export (&ctrl, *argv, fp, 2); + else + wrong_args ("--export-secret-key-raw KEY-ID"); + if (fp != es_stdout) + es_fclose (fp); + } + break; + case aSendKeys: case aRecvKeys: log_error ("this command has not yet been implemented\n"); diff --git a/sm/gpgsm.h b/sm/gpgsm.h index 6c68af746..7c7ca7a20 100644 --- a/sm/gpgsm.h +++ b/sm/gpgsm.h @@ -348,7 +348,8 @@ int gpgsm_import_files (ctrl_t ctrl, int nfiles, char **files, /*-- export.c --*/ void gpgsm_export (ctrl_t ctrl, strlist_t names, estream_t stream); -void gpgsm_p12_export (ctrl_t ctrl, const char *name, estream_t stream); +void gpgsm_p12_export (ctrl_t ctrl, const char *name, estream_t stream, + int rawmode); /*-- delete.c --*/ int gpgsm_delete (ctrl_t ctrl, strlist_t names); diff --git a/sm/minip12.c b/sm/minip12.c index c91ef226a..01b91b710 100644 --- a/sm/minip12.c +++ b/sm/minip12.c @@ -1,5 +1,6 @@ /* minip12.c - A minimal pkcs-12 implementation. * Copyright (C) 2002, 2003, 2004, 2006, 2011 Free Software Foundation, Inc. + * Copyright (C) 2014 Werner Koch * * This file is part of GnuPG. * @@ -1891,10 +1892,15 @@ create_final (struct buffer_s *sequences, const char *pw, size_t *r_length) } } } + + MODE controls what is being generated: + 0 - As described above + 1 - Ditto but without the padding + 2 - Only the inner part (pkcs#1) */ static unsigned char * -build_key_sequence (gcry_mpi_t *kparms, size_t *r_length) +build_key_sequence (gcry_mpi_t *kparms, int mode, size_t *r_length) { int rc, i; size_t needed, n; @@ -1902,7 +1908,7 @@ build_key_sequence (gcry_mpi_t *kparms, size_t *r_length) size_t plainlen; size_t outseqlen, oidseqlen, octstrlen, inseqlen; - needed = 3; /* The version(?) integer of value 0. */ + needed = 3; /* The version integer with value 0. */ for (i=0; kparms[i]; i++) { n = 0; @@ -1929,23 +1935,27 @@ build_key_sequence (gcry_mpi_t *kparms, size_t *r_length) if (!n) return NULL; needed += n; - /* Encapsulate all into an octet string. */ - octstrlen = needed; - n = compute_tag_length (needed); - if (!n) - return NULL; - needed += n; - /* Prepend the object identifier sequence. */ - oidseqlen = 2 + DIM (oid_rsaEncryption) + 2; - needed += 2 + oidseqlen; - /* The version number. */ - needed += 3; - /* And finally put the whole thing into a sequence. */ - outseqlen = needed; - n = compute_tag_length (needed); - if (!n) - return NULL; - needed += n; + + if (mode != 2) + { + /* Encapsulate all into an octet string. */ + octstrlen = needed; + n = compute_tag_length (needed); + if (!n) + return NULL; + needed += n; + /* Prepend the object identifier sequence. */ + oidseqlen = 2 + DIM (oid_rsaEncryption) + 2; + needed += 2 + oidseqlen; + /* The version number. */ + needed += 3; + /* And finally put the whole thing into a sequence. */ + outseqlen = needed; + n = compute_tag_length (needed); + if (!n) + return NULL; + needed += n; + } /* allocate 8 extra bytes for padding */ plain = gcry_malloc_secure (needed+8); @@ -1957,20 +1967,24 @@ build_key_sequence (gcry_mpi_t *kparms, size_t *r_length) /* And now fill the plaintext buffer. */ p = plain; - p = store_tag_length (p, TAG_SEQUENCE, outseqlen); - /* Store version. */ - *p++ = TAG_INTEGER; - *p++ = 1; - *p++ = 0; - /* Store object identifier sequence. */ - p = store_tag_length (p, TAG_SEQUENCE, oidseqlen); - p = store_tag_length (p, TAG_OBJECT_ID, DIM (oid_rsaEncryption)); - memcpy (p, oid_rsaEncryption, DIM (oid_rsaEncryption)); - p += DIM (oid_rsaEncryption); - *p++ = TAG_NULL; - *p++ = 0; - /* Start with the octet string. */ - p = store_tag_length (p, TAG_OCTET_STRING, octstrlen); + if (mode != 2) + { + p = store_tag_length (p, TAG_SEQUENCE, outseqlen); + /* Store version. */ + *p++ = TAG_INTEGER; + *p++ = 1; + *p++ = 0; + /* Store object identifier sequence. */ + p = store_tag_length (p, TAG_SEQUENCE, oidseqlen); + p = store_tag_length (p, TAG_OBJECT_ID, DIM (oid_rsaEncryption)); + memcpy (p, oid_rsaEncryption, DIM (oid_rsaEncryption)); + p += DIM (oid_rsaEncryption); + *p++ = TAG_NULL; + *p++ = 0; + /* Start with the octet string. */ + p = store_tag_length (p, TAG_OCTET_STRING, octstrlen); + } + p = store_tag_length (p, TAG_SEQUENCE, inseqlen); /* Store the key parameters. */ *p++ = TAG_INTEGER; @@ -2003,10 +2017,14 @@ build_key_sequence (gcry_mpi_t *kparms, size_t *r_length) plainlen = p - plain; assert (needed == plainlen); - /* Append some pad characters; we already allocated extra space. */ - n = 8 - plainlen % 8; - for (i=0; i < n; i++, plainlen++) - *p++ = n; + + if (!mode) + { + /* Append some pad characters; we already allocated extra space. */ + n = 8 - plainlen % 8; + for (i=0; i < n; i++, plainlen++) + *p++ = n; + } *r_length = plainlen; return plain; @@ -2459,7 +2477,7 @@ p12_build (gcry_mpi_t *kparms, const void *cert, size_t certlen, if (kparms) { /* Encode the key. */ - buffer = build_key_sequence (kparms, &buflen); + buffer = build_key_sequence (kparms, 0, &buflen); if (!buffer) goto failure; @@ -2502,6 +2520,24 @@ p12_build (gcry_mpi_t *kparms, const void *cert, size_t certlen, } +/* This is actually not a pkcs#12 function but one which creates an + unencrypted a pkcs#1 private key. */ +unsigned char * +p12_raw_build (gcry_mpi_t *kparms, int rawmode, size_t *r_length) +{ + unsigned char *buffer; + size_t buflen; + + assert (rawmode == 1 || rawmode == 2); + buffer = build_key_sequence (kparms, rawmode, &buflen); + if (!buffer) + return NULL; + + *r_length = buflen; + return buffer; +} + + #ifdef TEST static void diff --git a/sm/minip12.h b/sm/minip12.h index 27f24f5a1..7a1950fb0 100644 --- a/sm/minip12.h +++ b/sm/minip12.h @@ -31,6 +31,9 @@ unsigned char *p12_build (gcry_mpi_t *kparms, const void *cert, size_t certlen, const char *pw, const char *charset, size_t *r_length); +unsigned char *p12_raw_build (gcry_mpi_t *kparms, + int rawmode, + size_t *r_length); #endif /*MINIP12_H*/