From 7777e68d0482c942f527e91c04adbcfb40bc8bef Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 22 May 2013 09:50:12 +0100 Subject: [PATCH] Implement unattended OpenPGP secret key import. * agent/command.c (cmd_import_key): Add option --unattended. * agent/cvt-openpgp.c (convert_transfer_key): New. (do_unprotect): Factor some code out to ... (prepare_unprotect): new function. (convert_from_openpgp): Factor all code out to ... (convert_from_openpgp_main): this. Add arg 'passphrase'. Implement openpgp-native protection modes. (convert_from_openpgp_native): New. * agent/t-protect.c (convert_from_openpgp_native): New dummy fucntion * agent/protect-tool.c (convert_from_openpgp_native): Ditto. * agent/protect.c (agent_unprotect): Add arg CTRL. Adjust all callers. Support openpgp-native protection. * g10/call-agent.c (agent_import_key): Add arg 'unattended'. * g10/import.c (transfer_secret_keys): Use unattended in batch mode. -- With the gpg-agent taking care of the secret keys, the user needs to migrate existing keys from secring.gpg to the agent. This and also the standard import of secret keys required the user to unprotect the secret keys first, so that gpg-agent was able to re-protected them using its own scheme. With many secret keys this is quite some usability hurdle. In particular if a passphrase is not instantly available. To make this migration smoother, this patch implements an unattended key import/migration which delays the conversion to the gpg-agent format until the key is actually used. For example: gpg2 --batch --import mysecretkey.gpg works without any user interaction due to the use of --batch. Now if a key is used (e.g. "gpg2 -su USERID_FROM_MYSECRETKEY foo"), gpg-agent has to ask for the passphrase anyway, converts the key from the openpgp format to the internal format, signs, re-encrypts the key and tries to store it in the gpg-agent format to the disk. The next time, the internal format of the key is used. This patch has only been tested with the old demo keys, more tests with other protection formats and no protection are needed. Signed-off-by: Werner Koch --- README | 57 ++++--- agent/agent.h | 3 +- agent/command.c | 22 ++- agent/cvt-openpgp.c | 365 ++++++++++++++++++++++++++++++++----------- agent/cvt-openpgp.h | 4 + agent/findkey.c | 9 +- agent/keyformat.txt | 120 +++++++++----- agent/protect-tool.c | 14 +- agent/protect.c | 32 +++- agent/t-protect.c | 11 ++ g10/call-agent.c | 5 +- g10/call-agent.h | 2 +- g10/import.c | 2 +- 13 files changed, 475 insertions(+), 171 deletions(-) diff --git a/README b/README index 03da25ef5..c64a14eff 100644 --- a/README +++ b/README @@ -5,7 +5,8 @@ THIS IS A DEVELOPMENT VERSION AND NOT INTENDED FOR REGULAR USE. Copyright 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, - 2006, 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc. + 2006, 2007, 2008, 2009, 2010, 2011, 2012, + 2013 Free Software Foundation, Inc. INTRODUCTION @@ -32,21 +33,22 @@ BUILD INSTRUCTIONS GnuPG 2.1 depends on the following packages: + npth (ftp://ftp.gnupg.org/gcrypt/npth/) libgpg-error (ftp://ftp.gnupg.org/gcrypt/libgpg-error/) libgcrypt (ftp://ftp.gnupg.org/gcrypt/libgcrypt/) libksba (ftp://ftp.gnupg.org/gcrypt/libksba/) libassuan (ftp://ftp.gnupg.org/gcrypt/libassuan/) -You also need the Pinentry package for most function of GnuPG; however -it is not a build requirement. Pinentry is available at -ftp://ftp.gnupg.org/gcrypt/pinentry/ . - You should get the latest versions of course, the GnuPG configure script complains if a version is not sufficient. +You also need the Pinentry package for most functions of GnuPG; +however it is not a build requirement. Pinentry is available at +ftp://ftp.gnupg.org/gcrypt/pinentry/ . + After building and installing the above packages in the order as given -above, you may now continue with GnuPG installation (you may also just -try to build GnuPG to see whether your already installed versions are +above, you may continue with GnuPG installation (you may also just try +to build GnuPG to see whether your already installed versions are sufficient). As with all packages, you just have to do @@ -62,7 +64,8 @@ S/MIME and smartcards. Note that there is no binary gpg but a gpg2 so that this package won't conflict with a GnuPG 1.4 installation. gpg2 behaves just like gpg. -In case of problem please ask on gnupg-users@gnupg.org for advise. +In case of problem please ask on gnupg-users@gnupg.org mailing list +for advise. Note that the PKITS tests are always skipped unless you copy the PKITS test data file into the tests/pkits directory. There is no need to @@ -79,21 +82,24 @@ to view the default directories used by GnuPG. MIGRATION FROM 1.4 or 2.0 to 2.1 ================================ -The major change in 2.1 is that gpg-agent now takes care of the -OpenPGP secret keys (those managed by GPG). The former secring.gpg -will not be used anymore. Newly generated keys are generated and -stored in the agent's key store (~/.gnupg/private-keys-v1.d/). To -migrate your existing keys to the agent you should run this command +The major change in 2.1 is gpg-agent taking care of the OpenPGP secret +keys (those managed by GPG). The former file "secring.gpg" will not +be used anymore. Newly generated keys are stored in the agent's key +store directory "~/.gnupg/private-keys-v1.d/". - gpg2 --import ~/.gnupg/secring.gpg +To migrate your existing keys you need to run the command -The agent will you ask for the passphrase of each key. You may use -the Cancel button of the Pinentry to skip importing this key. If you -want to stop the import process and you use one of the latest -pinentries, you should close the pinentry window instead of hitting -the cancel button. Secret keys already imported are skipped by the -import command. It is advisable to keep the secring.gpg for use with -older versions of GPG. + gpg2 --batch --import ~/.gnupg/secring.gpg + +Secret keys already imported are skipped by this command. It is +advisable to keep the secring.gpg for use with older versions of GPG. + +The use of "--batch" with "--import" is highly recommended. If you do +not use "--batch" the agent would ask for the passphrase of each key. +In this case you may use the Cancel button of the Pinentry to skip +importing this key. If you want to stop the enite import process and +you use a decent version of Pinentry, you should close the Pinentry +window instead of hitting the Cancel button. Note that gpg-agent now uses a fixed socket by default. All tools will start the gpg-agent as needed. In general there is no more need @@ -182,8 +188,13 @@ authors directly as we are busy working on improvements and bug fixes. The English and German mailing lists are watched by the authors and we try to answer questions when time allows us to do so. -Commercial grade support for GnuPG is available; please see -http://www.gnupg.org/service.html . +Commercial grade support for GnuPG is available; for a listing of +offers see http://www.gnupg.org/service.html . The driving force +behind the development of GnuPG is the company of its principal +author, Werner Koch. Maintenance and improvement of GnuPG and related +software takes up most of their resources. To allow him to continue +his work he asks to either purchase a support contract, engage them +for custom enhancements, or to donate money. See http://g10code.com . This file is Free Software; as a special exception the authors gives diff --git a/agent/agent.h b/agent/agent.h index 2fd0b8b8a..7445061ac 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -374,7 +374,8 @@ unsigned char get_standard_s2k_count_rfc4880 (void); int agent_protect (const unsigned char *plainkey, const char *passphrase, unsigned char **result, size_t *resultlen, unsigned long s2k_count); -int agent_unprotect (const unsigned char *protectedkey, const char *passphrase, +int agent_unprotect (ctrl_t ctrl, + const unsigned char *protectedkey, const char *passphrase, gnupg_isotime_t protected_at, unsigned char **result, size_t *resultlen); int agent_private_key_type (const unsigned char *privatekey); diff --git a/agent/command.c b/agent/command.c index e57c69d05..036486856 100644 --- a/agent/command.c +++ b/agent/command.c @@ -1,6 +1,7 @@ /* command.c - gpg-agent command handler * Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2008, 2009, 2010, * 2011 Free Software Foundation, Inc. + * Copyright (C) 2013 Werner Koch * * This file is part of GnuPG. * @@ -1807,18 +1808,21 @@ cmd_keywrap_key (assuan_context_t ctx, char *line) static const char hlp_import_key[] = - "IMPORT_KEY []\n" + "IMPORT_KEY [--unattended] []\n" "\n" "Import a secret key into the key store. The key is expected to be\n" "encrypted using the current session's key wrapping key (cf. command\n" "KEYWRAP_KEY) using the AESWRAP-128 algorithm. This function takes\n" "no arguments but uses the inquiry \"KEYDATA\" to ask for the actual\n" - "key data. The unwrapped key must be a canonical S-expression."; + "key data. The unwrapped key must be a canonical S-expression. The\n" + "option --unattended tries to import the key as-is without any\n" + "re-encryption"; static gpg_error_t cmd_import_key (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; + int opt_unattended; unsigned char *wrappedkey = NULL; size_t wrappedkeylen; gcry_cipher_hd_t cipherhd = NULL; @@ -1838,6 +1842,9 @@ cmd_import_key (assuan_context_t ctx, char *line) goto leave; } + opt_unattended = has_option (line, "--unattended"); + line = skip_options (line); + p = line; for (p=line; *p && *p != ' ' && *p != '\t'; p++) ; @@ -1921,7 +1928,7 @@ cmd_import_key (assuan_context_t ctx, char *line) key = NULL; err = convert_from_openpgp (ctrl, openpgp_sexp, grip, ctrl->server_local->keydesc, cache_nonce, - &key, &passphrase); + &key, opt_unattended? NULL : &passphrase); if (err) goto leave; realkeylen = gcry_sexp_canon_len (key, 0, NULL, &err); @@ -1929,6 +1936,7 @@ cmd_import_key (assuan_context_t ctx, char *line) goto leave; /* Invalid canonical encoded S-expression. */ if (passphrase) { + assert (!opt_unattended); if (!cache_nonce) { char buf[12]; @@ -1941,6 +1949,12 @@ cmd_import_key (assuan_context_t ctx, char *line) assuan_write_status (ctx, "CACHE_NONCE", cache_nonce); } } + else if (opt_unattended) + { + err = set_error (GPG_ERR_ASS_PARAMETER, + "\"--unattended\" may only be used with OpenPGP keys"); + goto leave; + } else { if (!agent_key_available (grip)) @@ -1957,7 +1971,7 @@ cmd_import_key (assuan_context_t ctx, char *line) if (passphrase) { err = agent_protect (key, passphrase, &finalkey, &finalkeylen, - ctrl->s2k_count); + ctrl->s2k_count); if (!err) err = agent_write_private_key (grip, finalkey, finalkeylen, 0); } diff --git a/agent/cvt-openpgp.c b/agent/cvt-openpgp.c index ec0fd0a89..205b9533a 100644 --- a/agent/cvt-openpgp.c +++ b/agent/cvt-openpgp.c @@ -1,6 +1,7 @@ /* cvt-openpgp.c - Convert an OpenPGP key to our internal format. * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2006, 2009, * 2010 Free Software Foundation, Inc. + * Copyright (C) 2013 Werner Koch * * This file is part of GnuPG. * @@ -160,6 +161,72 @@ convert_secret_key (gcry_sexp_t *r_key, int pubkey_algo, gcry_mpi_t *skey) } +/* Convert a secret key given as algorithm id, an array of key + parameters, and an S-expression of the original OpenPGP transfer + key into our s-expression based format. This is a variant of + convert_secret_key which is used for the openpgp-native protection + mode. Note that PUBKEY_ALGO has an gcrypt algorithm number. */ +static gpg_error_t +convert_transfer_key (gcry_sexp_t *r_key, int pubkey_algo, gcry_mpi_t *skey, + gcry_sexp_t transfer_key) +{ + gpg_error_t err; + gcry_sexp_t s_skey = NULL; + + *r_key = NULL; + + switch (pubkey_algo) + { + case GCRY_PK_DSA: + err = gcry_sexp_build + (&s_skey, NULL, + "(protected-private-key(dsa(p%m)(q%m)(g%m)(y%m)" + "(protected openpgp-native%S)))", + skey[0], skey[1], skey[2], skey[3], transfer_key); + break; + + case GCRY_PK_ELG: + case GCRY_PK_ELG_E: + err = gcry_sexp_build + (&s_skey, NULL, + "(protected-private-key(elg(p%m)(g%m)(y%m)" + "(protected openpgp-native%S)))", + skey[0], skey[1], skey[2], transfer_key); + break; + + + case GCRY_PK_RSA: + case GCRY_PK_RSA_E: + case GCRY_PK_RSA_S: + err = gcry_sexp_build + (&s_skey, NULL, + "(protected-private-key(rsa(n%m)(e%m)", + "(protected openpgp-native%S)))", + skey[0], skey[1], transfer_key ); + break; + + case GCRY_PK_ECDSA: + case GCRY_PK_ECDH: + /* Although our code would work with "ecc" we explicitly use + "ecdh" or "ecdsa" to implicitly set the key capabilities. */ + err = gcry_sexp_build + (&s_skey, NULL, + "(protected-private-key(%s(p%m)(a%m)(b%m)(g%m)(n%m)(q%m)" + "(protected openpgp-native%S)))", + pubkey_algo == GCRY_PK_ECDSA?"ecdsa":"ecdh", + skey[0], skey[1], skey[2], skey[3], skey[4], skey[5], transfer_key); + break; + + default: + err = gpg_error (GPG_ERR_PUBKEY_ALGO); + break; + } + + if (!err) + *r_key = s_skey; + return err; +} + /* Hash the passphrase and set the key. */ static gpg_error_t @@ -202,30 +269,19 @@ checksum (const unsigned char *p, unsigned int n) } -/* Note that this function modified SKEY. SKEYSIZE is the allocated - size of the array including the NULL item; this is used for a - bounds check. On success a converted key is stored at R_KEY. */ +/* Helper for do_unprotect. PUBKEY_ALOGO is the gcrypt algo number. + On success R_NPKEY and R_NSKEY receive the number or parameters for + the algorithm PUBKEY_ALGO and R_SKEYLEN the used length of + SKEY. */ static int -do_unprotect (const char *passphrase, - int pkt_version, int pubkey_algo, int is_protected, - gcry_mpi_t *skey, size_t skeysize, - int protect_algo, void *protect_iv, size_t protect_ivlen, - int s2k_mode, int s2k_algo, byte *s2k_salt, u32 s2k_count, - u16 desired_csum, gcry_sexp_t *r_key) +prepare_unprotect (int pubkey_algo, gcry_mpi_t *skey, size_t skeysize, + int s2k_mode, + unsigned int *r_npkey, unsigned int *r_nskey, + unsigned int *r_skeylen) { gpg_error_t err; size_t npkey, nskey, skeylen; - gcry_cipher_hd_t cipher_hd = NULL; - u16 actual_csum; - size_t nbytes; int i; - gcry_mpi_t tmpmpi; - - *r_key = NULL; - - /* Unfortunately, the OpenPGP PK algorithm numbers need to be - re-mapped for Libgcrypt. */ - pubkey_algo = map_pk_openpgp_to_gcry (pubkey_algo); /* Count the actual number of MPIs is in the array and set the remainder to NULL for easier processing later on. */ @@ -264,6 +320,54 @@ do_unprotect (const char *passphrase, if (nskey+1 >= skeysize) return gpg_error (GPG_ERR_BUFFER_TOO_SHORT); + /* Check that the public key parameters are all available and not + encrypted. */ + for (i=0; i < npkey; i++) + { + if (!skey[i] || gcry_mpi_get_flag (skey[i], GCRYMPI_FLAG_OPAQUE)) + return gpg_error (GPG_ERR_BAD_SECKEY); + } + + if (r_npkey) + *r_npkey = npkey; + if (r_nskey) + *r_nskey = nskey; + if (r_skeylen) + *r_skeylen = skeylen; + return 0; +} + + +/* Note that this function modifies SKEY. SKEYSIZE is the allocated + size of the array including the NULL item; this is used for a + bounds check. On success a converted key is stored at R_KEY. */ +static int +do_unprotect (const char *passphrase, + int pkt_version, int pubkey_algo, int is_protected, + gcry_mpi_t *skey, size_t skeysize, + int protect_algo, void *protect_iv, size_t protect_ivlen, + int s2k_mode, int s2k_algo, byte *s2k_salt, u32 s2k_count, + u16 desired_csum, gcry_sexp_t *r_key) +{ + gpg_error_t err; + unsigned int npkey, nskey, skeylen; + gcry_cipher_hd_t cipher_hd = NULL; + u16 actual_csum; + size_t nbytes; + int i; + gcry_mpi_t tmpmpi; + + *r_key = NULL; + + /* Unfortunately, the OpenPGP PK algorithm numbers need to be + re-mapped for Libgcrypt. */ + pubkey_algo = map_pk_openpgp_to_gcry (pubkey_algo); + + err = prepare_unprotect (pubkey_algo, skey, skeysize, s2k_mode, + &npkey, &nskey, &skeylen); + if (err) + return err; + /* Check whether SKEY is at all protected. If it is not protected merely verify the checksum. */ if (!is_protected) @@ -512,7 +616,7 @@ do_unprotect (const char *passphrase, } -/* Callback function to try the unprotection from the passpharse query +/* Callback function to try the unprotection from the passphrase query code. */ static int try_do_unprotect_cb (struct pin_entry_info_s *pi) @@ -536,24 +640,19 @@ try_do_unprotect_cb (struct pin_entry_info_s *pi) } -/* Convert an OpenPGP transfer key into our internal format. Before - asking for a passphrase we check whether the key already exists in - our key storage. S_PGP is the OpenPGP key in transfer format. If - CACHE_NONCE is given the passphrase will be looked up in the cache. - On success R_KEY will receive a canonical encoded S-expression with - the unprotected key in our internal format; the caller needs to - release that memory. The passphrase used to decrypt the OpenPGP - key will be returned at R_PASSPHRASE; the caller must release this - passphrase. The keygrip will be stored at the 20 byte buffer - pointed to by GRIP. On error NULL is stored at all return - arguments. */ -gpg_error_t -convert_from_openpgp (ctrl_t ctrl, gcry_sexp_t s_pgp, - unsigned char *grip, const char *prompt, - const char *cache_nonce, - unsigned char **r_key, char **r_passphrase) +/* See convert_from_openpgp for the core of the description. This + function adds an optional PASSPHRASE argument and uses this to + silently decrypt the key; CACHE_NONCE and R_PASSPHRASE must both be + NULL in this mode. */ +static gpg_error_t +convert_from_openpgp_main (ctrl_t ctrl, gcry_sexp_t s_pgp, + unsigned char *grip, const char *prompt, + const char *cache_nonce, const char *passphrase, + unsigned char **r_key, char **r_passphrase) { gpg_error_t err; + int unattended; + int from_native; gcry_sexp_t top_list; gcry_sexp_t list = NULL; const char *value; @@ -573,12 +672,13 @@ convert_from_openpgp (ctrl_t ctrl, gcry_sexp_t s_pgp, gcry_mpi_t skey[10]; /* We support up to 9 parameters. */ u16 desired_csum; int skeyidx = 0; - gcry_sexp_t s_skey; - struct pin_entry_info_s *pi; - struct try_do_unprotect_arg_s pi_arg; + gcry_sexp_t s_skey = NULL; *r_key = NULL; - *r_passphrase = NULL; + if (r_passphrase) + *r_passphrase = NULL; + unattended = !r_passphrase; + from_native = (!cache_nonce && passphrase && !r_passphrase); top_list = gcry_sexp_find_token (s_pgp, "openpgp-private-key", 0); if (!top_list) @@ -607,6 +707,7 @@ convert_from_openpgp (ctrl_t ctrl, gcry_sexp_t s_pgp, is_protected = 0; else goto bad_seckey; + if (is_protected) { string = gcry_sexp_nth_string (list, 2); @@ -755,64 +856,89 @@ convert_from_openpgp (ctrl_t ctrl, gcry_sexp_t s_pgp, if (err) goto leave; - if (!agent_key_available (grip)) + if (!from_native && !agent_key_available (grip)) { err = gpg_error (GPG_ERR_EEXIST); goto leave; } - pi = xtrycalloc_secure (1, sizeof (*pi) + 100); - if (!pi) - return gpg_error_from_syserror (); - pi->max_length = 100; - pi->min_digits = 0; /* We want a real passphrase. */ - pi->max_digits = 16; - pi->max_tries = 3; - pi->check_cb = try_do_unprotect_cb; - pi->check_cb_arg = &pi_arg; - pi_arg.is_v4 = is_v4; - pi_arg.is_protected = is_protected; - pi_arg.pubkey_algo = pubkey_algo; - pi_arg.protect_algo = protect_algo; - pi_arg.iv = iv; - pi_arg.ivlen = ivlen; - pi_arg.s2k_mode = s2k_mode; - pi_arg.s2k_algo = s2k_algo; - pi_arg.s2k_salt = s2k_salt; - pi_arg.s2k_count = s2k_count; - pi_arg.desired_csum = desired_csum; - pi_arg.skey = skey; - pi_arg.skeysize = DIM (skey); - pi_arg.skeyidx = skeyidx; - pi_arg.r_key = &s_skey; - - err = gpg_error (GPG_ERR_BAD_PASSPHRASE); - if (cache_nonce) + if (unattended && !from_native) { - char *cache_value; + int pubkey_g_algo = map_pk_openpgp_to_gcry (pubkey_algo); - cache_value = agent_get_cache (cache_nonce, CACHE_MODE_NONCE); - if (cache_value) + err = prepare_unprotect (pubkey_g_algo, skey, DIM(skey), s2k_mode, + NULL, NULL, NULL); + if (err) + goto leave; + + err = convert_transfer_key (&s_skey, pubkey_g_algo, skey, s_pgp); + if (err) + goto leave; + } + else + { + struct pin_entry_info_s *pi; + struct try_do_unprotect_arg_s pi_arg; + + pi = xtrycalloc_secure (1, sizeof (*pi) + 100); + if (!pi) + return gpg_error_from_syserror (); + pi->max_length = 100; + pi->min_digits = 0; /* We want a real passphrase. */ + pi->max_digits = 16; + pi->max_tries = 3; + pi->check_cb = try_do_unprotect_cb; + pi->check_cb_arg = &pi_arg; + pi_arg.is_v4 = is_v4; + pi_arg.is_protected = is_protected; + pi_arg.pubkey_algo = pubkey_algo; + pi_arg.protect_algo = protect_algo; + pi_arg.iv = iv; + pi_arg.ivlen = ivlen; + pi_arg.s2k_mode = s2k_mode; + pi_arg.s2k_algo = s2k_algo; + pi_arg.s2k_salt = s2k_salt; + pi_arg.s2k_count = s2k_count; + pi_arg.desired_csum = desired_csum; + pi_arg.skey = skey; + pi_arg.skeysize = DIM (skey); + pi_arg.skeyidx = skeyidx; + pi_arg.r_key = &s_skey; + + err = gpg_error (GPG_ERR_BAD_PASSPHRASE); + if (cache_nonce) { - if (strlen (cache_value) < pi->max_length) - strcpy (pi->pin, cache_value); - xfree (cache_value); + char *cache_value; + + cache_value = agent_get_cache (cache_nonce, CACHE_MODE_NONCE); + if (cache_value) + { + if (strlen (cache_value) < pi->max_length) + strcpy (pi->pin, cache_value); + xfree (cache_value); + } + if (*pi->pin) + err = try_do_unprotect_cb (pi); } - if (*pi->pin) - err = try_do_unprotect_cb (pi); + else if (from_native) + { + if (strlen (passphrase) < pi->max_length) + strcpy (pi->pin, passphrase); + err = try_do_unprotect_cb (pi); + } + if (gpg_err_code (err) == GPG_ERR_BAD_PASSPHRASE && !from_native) + err = agent_askpin (ctrl, prompt, NULL, NULL, pi); + skeyidx = pi_arg.skeyidx; + if (!err && r_passphrase) + { + *r_passphrase = xtrystrdup (pi->pin); + if (!*r_passphrase) + err = gpg_error_from_syserror (); + } + xfree (pi); + if (err) + goto leave; } - if (gpg_err_code (err) == GPG_ERR_BAD_PASSPHRASE) - err = agent_askpin (ctrl, prompt, NULL, NULL, pi); - skeyidx = pi_arg.skeyidx; - if (!err) - { - *r_passphrase = xtrystrdup (pi->pin); - if (!*r_passphrase) - err = gpg_error_from_syserror (); - } - xfree (pi); - if (err) - goto leave; /* Save some memory and get rid of the SKEY array now. */ for (idx=0; idx < skeyidx; idx++) @@ -820,16 +946,16 @@ convert_from_openpgp (ctrl_t ctrl, gcry_sexp_t s_pgp, skeyidx = 0; /* Note that the padding is not required - we use it only because - that function allows us to created the result in secure memory. */ + that function allows us to create the result in secure memory. */ err = make_canon_sexp_pad (s_skey, 1, r_key, NULL); - gcry_sexp_release (s_skey); leave: + gcry_sexp_release (s_skey); gcry_sexp_release (list); gcry_sexp_release (top_list); for (idx=0; idx < skeyidx; idx++) gcry_mpi_release (skey[idx]); - if (err) + if (err && r_passphrase) { xfree (*r_passphrase); *r_passphrase = NULL; @@ -847,6 +973,63 @@ convert_from_openpgp (ctrl_t ctrl, gcry_sexp_t s_pgp, } +/* Convert an OpenPGP transfer key into our internal format. Before + asking for a passphrase we check whether the key already exists in + our key storage. S_PGP is the OpenPGP key in transfer format. If + CACHE_NONCE is given the passphrase will be looked up in the cache. + On success R_KEY will receive a canonical encoded S-expression with + the unprotected key in our internal format; the caller needs to + release that memory. The passphrase used to decrypt the OpenPGP + key will be returned at R_PASSPHRASE; the caller must release this + passphrase. If R_PASSPHRASE is NULL the unattended conversion mode + will be used which uses the openpgp-native protection format for + the key. The keygrip will be stored at the 20 byte buffer pointed + to by GRIP. On error NULL is stored at all return arguments. */ +gpg_error_t +convert_from_openpgp (ctrl_t ctrl, gcry_sexp_t s_pgp, + unsigned char *grip, const char *prompt, + const char *cache_nonce, + unsigned char **r_key, char **r_passphrase) +{ + return convert_from_openpgp_main (ctrl, s_pgp, grip, prompt, + cache_nonce, NULL, + r_key, r_passphrase); +} + +/* This function is called by agent_unprotect to re-protect an + openpgp-native protected private-key into the standard private-key + protection format. */ +gpg_error_t +convert_from_openpgp_native (ctrl_t ctrl, + gcry_sexp_t s_pgp, const char *passphrase, + unsigned char **r_key) +{ + gpg_error_t err; + unsigned char grip[20]; + + if (!passphrase) + return gpg_error (GPG_ERR_INTERNAL); + + err = convert_from_openpgp_main (ctrl, s_pgp, grip, NULL, + NULL, passphrase, + r_key, NULL); + + /* On success try to re-write the key. */ + if (!err) + { + unsigned char *protectedkey = NULL; + size_t protectedkeylen; + + if (!agent_protect (*r_key, passphrase, &protectedkey, &protectedkeylen, + ctrl->s2k_count)) + agent_write_private_key (grip, protectedkey, protectedkeylen, 1); + xfree (protectedkey); + } + + return err; +} + + static gpg_error_t key_from_sexp (gcry_sexp_t sexp, const char *elems, gcry_mpi_t *array) diff --git a/agent/cvt-openpgp.h b/agent/cvt-openpgp.h index 3c48d0319..d27a776ef 100644 --- a/agent/cvt-openpgp.h +++ b/agent/cvt-openpgp.h @@ -23,6 +23,10 @@ gpg_error_t convert_from_openpgp (ctrl_t ctrl, gcry_sexp_t s_pgp, unsigned char *grip, const char *prompt, const char *cache_nonce, unsigned char **r_key, char **r_passphrase); +gpg_error_t convert_from_openpgp_native (ctrl_t ctrl, + gcry_sexp_t s_pgp, + const char *passphrase, + unsigned char **r_key); gpg_error_t convert_to_openpgp (ctrl_t ctrl, gcry_sexp_t s_key, const char *passphrase, diff --git a/agent/findkey.c b/agent/findkey.c index ebdcc038e..d11f0888a 100644 --- a/agent/findkey.c +++ b/agent/findkey.c @@ -66,6 +66,9 @@ agent_write_private_key (const unsigned char *grip, fname = make_filename (opt.homedir, GNUPG_PRIVATE_KEYS_DIR, hexgrip, NULL); + /* FIXME: Write to a temp file first so that write failures during + key updates won't lead to a key loss. */ + if (!force && !access (fname, F_OK)) { log_error ("secret key file '%s' already exists\n", fname); @@ -119,7 +122,7 @@ try_unprotect_cb (struct pin_entry_info_s *pi) assert (!arg->unprotected_key); arg->change_required = 0; - err = agent_unprotect (arg->protected_key, pi->pin, protected_at, + err = agent_unprotect (arg->ctrl, arg->protected_key, pi->pin, protected_at, &arg->unprotected_key, &dummy); if (err) return err; @@ -325,7 +328,7 @@ unprotect (ctrl_t ctrl, const char *cache_nonce, const char *desc_text, pw = agent_get_cache (cache_nonce, CACHE_MODE_NONCE); if (pw) { - rc = agent_unprotect (*keybuf, pw, NULL, &result, &resultlen); + rc = agent_unprotect (ctrl, *keybuf, pw, NULL, &result, &resultlen); if (!rc) { if (r_passphrase) @@ -350,7 +353,7 @@ unprotect (ctrl_t ctrl, const char *cache_nonce, const char *desc_text, pw = agent_get_cache (hexgrip, cache_mode); if (pw) { - rc = agent_unprotect (*keybuf, pw, NULL, &result, &resultlen); + rc = agent_unprotect (ctrl, *keybuf, pw, NULL, &result, &resultlen); if (!rc) { if (r_passphrase) diff --git a/agent/keyformat.txt b/agent/keyformat.txt index 7ba6af2fb..3f95dae03 100644 --- a/agent/keyformat.txt +++ b/agent/keyformat.txt @@ -84,56 +84,94 @@ encrypted_octet_string. The result of the decryption process is a list of the secret key parameters. The protected-at expression is optional; the isotimestamp is 15 bytes long (e.g. "19610711T172000"). -The only available protection mode for now is +The currently defined protection modes are: - openpgp-s2k3-sha1-aes-cbc +1. openpgp-s2k3-sha1-aes-cbc -which describes an algorithm using using AES in CBC mode for -encryption, SHA-1 for integrity protection and the String to Key -algorithm 3 from OpenPGP (rfc2440). + This describes an algorithm using using AES in CBC mode for + encryption, SHA-1 for integrity protection and the String to Key + algorithm 3 from OpenPGP (rfc2440). -Example: + Example: -(protected openpgp-s2k3-sha1-aes-cbc - ((sha1 16byte_salt no_of_iterations) 16byte_iv) - encrypted_octet_string -) + (protected openpgp-s2k3-sha1-aes-cbc + ((sha1 16byte_salt no_of_iterations) 16byte_iv) + encrypted_octet_string + ) -The encrypted_octet string should yield this S-Exp (in canonical -representation) after decryption: + The encrypted_octet string should yield this S-Exp (in canonical + representation) after decryption: -( - ( - (d #046129F..[some bytes not shown]..81#) - (p #00e861b..[some bytes not shown]..f1#) - (q #00f7a7c..[some bytes not shown]..61#) - (u #304559a..[some bytes not shown]..9b#) - ) - (hash sha1 #...[hashvalue]...#) -) + ( + ( + (d #046129F..[some bytes not shown]..81#) + (p #00e861b..[some bytes not shown]..f1#) + (q #00f7a7c..[some bytes not shown]..61#) + (u #304559a..[some bytes not shown]..9b#) + ) + (hash sha1 #...[hashvalue]...#) + ) -For padding reasons, random bytes are appended to this list - they can -easily be stripped by looking for the end of the list. + For padding reasons, random bytes are appended to this list - they can + easily be stripped by looking for the end of the list. -The hash is calculated on the concatenation of the public key and -secret key parameter lists: i.e it is required to hash the -concatenation of these 6 canonical encoded lists for RSA, including -the parenthesis, the algorithm keyword and (if used) the protected-at -list. + The hash is calculated on the concatenation of the public key and + secret key parameter lists: i.e it is required to hash the + concatenation of these 6 canonical encoded lists for RSA, including + the parenthesis, the algorithm keyword and (if used) the protected-at + list. -(rsa - (n #00e0ce9..[some bytes not shown]..51#) - (e #010001#) - (d #046129F..[some bytes not shown]..81#) - (p #00e861b..[some bytes not shown]..f1#) - (q #00f7a7c..[some bytes not shown]..61#) - (u #304559a..[some bytes not shown]..9b#) - (protected-at "18950523T000000") -) + (rsa + (n #00e0ce9..[some bytes not shown]..51#) + (e #010001#) + (d #046129F..[some bytes not shown]..81#) + (p #00e861b..[some bytes not shown]..f1#) + (q #00f7a7c..[some bytes not shown]..61#) + (u #304559a..[some bytes not shown]..9b#) + (protected-at "18950523T000000") + ) + + After decryption the hash must be recalculated and compared against + the stored one - If they don't match the integrity of the key is not + given. + +2. openpgp-native + + This is a wrapper around the OpenPGP Private Key Transport format + which resembles the standard OpenPGP format and allows the use of an + existing key without re-encrypting to the default protection format. + + Example: + + (protected openpgp-native + (openpgp-private-key + (version V) + (algo PUBKEYALGO) + (skey _ P1 _ P2 _ P3 ... e PN) + (csum n) + (protection PROTTYPE PROTALGO IV S2KMODE S2KHASH S2KSALT S2KCOUNT))) + + Note that the public key paramaters in SKEY are duplicated and + should be identical to their copies in the standard parameter + elements. Here is an example of an entire protected private key + using this format: + + (protected-private-key + (rsa + (n #00e0ce9..[some bytes not shown]..51#) + (e #010001#) + (protected openpgp-native + (openpgp-private-key + (version 4) + (algo rsa) + (skey _ #00e0ce9..[some bytes not shown]..51# + _ #010001# + e #.........................#) + (protection sha1 aes #aabbccddeeff00112233445566778899# + 3 sha1 #2596f93e85f41e53# 3:190)))) + (uri http://foo.bar x-foo:whatever_you_want) + (comment whatever)) -After decryption the hash must be recalculated and compared against -the stored one - If they don't match the integrity of the key is not -given. Shadowed Private Key Format @@ -184,7 +222,7 @@ This format is used to transfer keys between gpg and gpg-agent. the secrect key parameters are encrypted if the "protection" list is given. To make this more explicit each parameter is preceded by a flag "_" for cleartext or "e" for encrypted text. -* CSUM is the depreciated 16 bit checksum as defined by OpenPGP. This +* CSUM is the deprecated 16 bit checksum as defined by OpenPGP. This is an optional element. * If PROTTYPE is "sha1" the new style SHA1 checksum is used if it is "sum" the old 16 bit checksum (above) is used and if it is "none" no diff --git a/agent/protect-tool.c b/agent/protect-tool.c index d59f5f0fa..faa0e2458 100644 --- a/agent/protect-tool.c +++ b/agent/protect-tool.c @@ -372,7 +372,7 @@ read_and_unprotect (const char *fname) if (!key) return; - rc = agent_unprotect (key, (pw=get_passphrase (1)), + rc = agent_unprotect (NULL, key, (pw=get_passphrase (1)), protected_at, &result, &resultlen); release_passphrase (pw); xfree (key); @@ -728,3 +728,15 @@ release_passphrase (char *pw) xfree (pw); } } + + +/* Stub function. */ +gpg_error_t +convert_from_openpgp_native (gcry_sexp_t s_pgp, const char *passphrase, + unsigned char **r_key) +{ + (void)s_pgp; + (void)passphrase; + (void)r_key; + return gpg_error (GPG_ERR_BUG); +} diff --git a/agent/protect.c b/agent/protect.c index 3e2cbb94e..cb2c098a9 100644 --- a/agent/protect.c +++ b/agent/protect.c @@ -1,6 +1,7 @@ /* protect.c - Un/Protect a secret key * Copyright (C) 1998, 1999, 2000, 2001, 2002, * 2003, 2007, 2009, 2011 Free Software Foundation, Inc. + * Copyright (C) 2013 Werner Koch * * This file is part of GnuPG. * @@ -604,6 +605,7 @@ agent_protect (const unsigned char *plainkey, const char *passphrase, return 0; } + /* Do the actual decryption and check the return list for consistency. */ static int @@ -832,9 +834,10 @@ merge_lists (const unsigned char *protectedkey, /* Unprotect the key encoded in canonical format. We assume a valid S-Exp here. If a protected-at item is available, its value will - be stored at protocted_at unless this is NULL. */ + be stored at protected_at unless this is NULL. */ int -agent_unprotect (const unsigned char *protectedkey, const char *passphrase, +agent_unprotect (ctrl_t ctrl, + const unsigned char *protectedkey, const char *passphrase, gnupg_isotime_t protected_at, unsigned char **result, size_t *resultlen) { @@ -938,7 +941,30 @@ agent_unprotect (const unsigned char *protectedkey, const char *passphrase, if (!n) return gpg_error (GPG_ERR_INV_SEXP); if (!smatch (&s, n, "openpgp-s2k3-sha1-" PROT_CIPHER_STRING "-cbc")) - return gpg_error (GPG_ERR_UNSUPPORTED_PROTECTION); + { + if (smatch (&s, n, "openpgp-native")) + { + gcry_sexp_t s_prot_begin; + + rc = gcry_sexp_sscan (&s_prot_begin, NULL, + prot_begin, + gcry_sexp_canon_len (prot_begin, 0,NULL,NULL)); + if (rc) + return rc; + + rc = convert_from_openpgp_native (ctrl, + s_prot_begin, passphrase, &final); + gcry_sexp_release (s_prot_begin); + if (!rc) + { + *result = final; + *resultlen = gcry_sexp_canon_len (final, 0, NULL, NULL); + } + return rc; + } + else + return gpg_error (GPG_ERR_UNSUPPORTED_PROTECTION); + } if (*s != '(' || s[1] != '(') return gpg_error (GPG_ERR_INV_SEXP); s += 2; diff --git a/agent/t-protect.c b/agent/t-protect.c index 02b614a38..9096cb2e0 100644 --- a/agent/t-protect.c +++ b/agent/t-protect.c @@ -337,3 +337,14 @@ main (int argc, char **argv) return 0; } + +/* Stub function. */ +gpg_error_t +convert_from_openpgp_native (gcry_sexp_t s_pgp, const char *passphrase, + unsigned char **r_key) +{ + (void)s_pgp; + (void)passphrase; + (void)r_key; + return gpg_error (GPG_ERR_BUG); +} diff --git a/g10/call-agent.c b/g10/call-agent.c index cb965e9ad..17290ec1a 100644 --- a/g10/call-agent.c +++ b/g10/call-agent.c @@ -1998,7 +1998,7 @@ inq_import_key_parms (void *opaque, const char *line) /* Call the agent to import a key into the agent. */ gpg_error_t agent_import_key (ctrl_t ctrl, const char *desc, char **cache_nonce_addr, - const void *key, size_t keylen) + const void *key, size_t keylen, int unattended) { gpg_error_t err; struct import_key_parm_s parm; @@ -2028,7 +2028,8 @@ agent_import_key (ctrl_t ctrl, const char *desc, char **cache_nonce_addr, parm.key = key; parm.keylen = keylen; - snprintf (line, sizeof line, "IMPORT_KEY%s%s", + snprintf (line, sizeof line, "IMPORT_KEY%s%s%s", + unattended? " --unattended":"", cache_nonce_addr && *cache_nonce_addr? " ":"", cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:""); cn_parm.cache_nonce_addr = cache_nonce_addr; diff --git a/g10/call-agent.h b/g10/call-agent.h index ab1d41a53..cce8304bc 100644 --- a/g10/call-agent.h +++ b/g10/call-agent.h @@ -177,7 +177,7 @@ gpg_error_t agent_keywrap_key (ctrl_t ctrl, int forexport, /* Send a key to the agent. */ gpg_error_t agent_import_key (ctrl_t ctrl, const char *desc, char **cache_nonce_addr, - const void *key, size_t keylen); + const void *key, size_t keylen, int unattended); /* Receive a key from the agent. */ gpg_error_t agent_export_key (ctrl_t ctrl, const char *keygrip, diff --git a/g10/import.c b/g10/import.c index a57b32e3f..3846c213f 100644 --- a/g10/import.c +++ b/g10/import.c @@ -1432,7 +1432,7 @@ transfer_secret_keys (ctrl_t ctrl, struct stats_s *stats, kbnode_t sec_keyblock) { char *desc = gpg_format_keydesc (pk, 1, 1); err = agent_import_key (ctrl, desc, &cache_nonce, - wrappedkey, wrappedkeylen); + wrappedkey, wrappedkeylen, opt.batch); xfree (desc); } if (!err)