diff options
author | Werner Koch <wk@gnupg.org> | 2013-05-22 10:50:12 +0200 |
---|---|---|
committer | Werner Koch <wk@gnupg.org> | 2013-05-22 10:14:57 +0200 |
commit | 7777e68d0482c942f527e91c04adbcfb40bc8bef (patch) | |
tree | a739b575f0495cf34116f11abaa1f85e86f747e2 /agent | |
parent | New debug functions log_printcanon and log_printsexp. (diff) | |
download | gnupg2-7777e68d0482c942f527e91c04adbcfb40bc8bef.tar.xz gnupg2-7777e68d0482c942f527e91c04adbcfb40bc8bef.zip |
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 <wk@gnupg.org>
Diffstat (limited to 'agent')
-rw-r--r-- | agent/agent.h | 3 | ||||
-rw-r--r-- | agent/command.c | 22 | ||||
-rw-r--r-- | agent/cvt-openpgp.c | 365 | ||||
-rw-r--r-- | agent/cvt-openpgp.h | 4 | ||||
-rw-r--r-- | agent/findkey.c | 9 | ||||
-rw-r--r-- | agent/keyformat.txt | 126 | ||||
-rw-r--r-- | agent/protect-tool.c | 14 | ||||
-rw-r--r-- | agent/protect.c | 32 | ||||
-rw-r--r-- | agent/t-protect.c | 11 |
9 files changed, 439 insertions, 147 deletions
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 [<cache_nonce>]\n" + "IMPORT_KEY [--unattended] [<cache_nonce>]\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) - { - if (strlen (cache_value) < pi->max_length) - strcpy (pi->pin, cache_value); - xfree (cache_value); - } - if (*pi->pin) - err = try_do_unprotect_cb (pi); + 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; } - if (gpg_err_code (err) == GPG_ERR_BAD_PASSPHRASE) - err = agent_askpin (ctrl, prompt, NULL, NULL, pi); - skeyidx = pi_arg.skeyidx; - if (!err) + else { - *r_passphrase = xtrystrdup (pi->pin); - if (!*r_passphrase) - err = gpg_error_from_syserror (); + 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) + { + 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); + } + 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; } - 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: - -(protected openpgp-s2k3-sha1-aes-cbc - ((sha1 16byte_salt no_of_iterations) 16byte_iv) - encrypted_octet_string -) + Example: -The encrypted_octet string should yield this S-Exp (in canonical -representation) after decryption: + (protected openpgp-s2k3-sha1-aes-cbc + ((sha1 16byte_salt no_of_iterations) 16byte_iv) + encrypted_octet_string + ) -( - ( - (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]...#) -) + The encrypted_octet string should yield this S-Exp (in canonical + representation) after decryption: -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. - -(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") -) + ( + ( + (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. + + 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") + ) + + 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); +} |