diff options
Diffstat (limited to '')
-rw-r--r-- | agent/ChangeLog | 44 | ||||
-rw-r--r-- | agent/agent.h | 10 | ||||
-rw-r--r-- | agent/call-pinentry.c | 38 | ||||
-rw-r--r-- | agent/command-ssh.c | 4 | ||||
-rw-r--r-- | agent/command.c | 151 | ||||
-rw-r--r-- | agent/cvt-openpgp.c | 273 | ||||
-rw-r--r-- | agent/cvt-openpgp.h | 12 | ||||
-rw-r--r-- | agent/divert-scd.c | 6 | ||||
-rw-r--r-- | agent/findkey.c | 62 | ||||
-rw-r--r-- | agent/genkey.c | 14 | ||||
-rw-r--r-- | agent/gpg-agent.c | 28 | ||||
-rw-r--r-- | agent/keyformat.txt | 114 | ||||
-rw-r--r-- | agent/pkdecrypt.c | 4 | ||||
-rw-r--r-- | agent/pksign.c | 4 | ||||
-rw-r--r-- | agent/protect.c | 27 | ||||
-rw-r--r-- | agent/t-protect.c | 8 |
16 files changed, 705 insertions, 94 deletions
diff --git a/agent/ChangeLog b/agent/ChangeLog index db77fe014..16871b20f 100644 --- a/agent/ChangeLog +++ b/agent/ChangeLog @@ -1,3 +1,47 @@ +2010-09-30 Werner Koch <[email protected]> + + * gpg-agent.c (agent_exit): Run cleanup. + (cleanup): Run only once. + + * call-pinentry.c (close_button_status_cb): New. + (agent_askpin): Add arg R_CANCEL_ALL. Change all callers. + * genkey.c (agent_ask_new_passphrase): Ditto. + * findkey.c (unprotect): Return GPG_ERR_FULLY_CANCELED if needed. + + * command.c (cmd_export_key): Add support for OpenPGP keys. + * findkey.c (unprotect): Add optional arg R_PASSPHRASE. + (agent_key_from_file): Ditto. Change all callers. + + * findkey.c (unprotect): Do not put the passphrase into the cache + if it has been changed. + + * cvt-openpgp.c (convert_to_openpgp, apply_protection) + (key_from_sexp): New. + +2010-09-29 Werner Koch <[email protected]> + + * cvt-openpgp.c (convert_openpgp): Rename to convert_from_openpgp. + + * command.c (has_option): Stop at "--". + (has_option_name, option_value): Ditto. + (skip_options): Skip initial spaces. + +2010-09-24 Werner Koch <[email protected]> + + * gpg-agent.c (main, reread_configuration): Always test whether + the default configuration file has been created in the meantime. + Fixes bug#1285. + +2010-09-17 Werner Koch <[email protected]> + + * command.c (cmd_havekey): Allow testing of several keygrips. + +2010-09-15 Werner Koch <[email protected]> + + * protect.c (calculate_mic): Take care of shared secret format. + + * agent.h (PROTECTED_SHARED_SECRET): New. + 2010-09-02 Werner Koch <[email protected]> * cache.c (new_data): Change arg and callers to use a string and diff --git a/agent/agent.h b/agent/agent.h index 6c2e7c65e..517df1351 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -182,7 +182,8 @@ enum PRIVATE_KEY_UNKNOWN = 0, PRIVATE_KEY_CLEAR = 1, PRIVATE_KEY_PROTECTED = 2, - PRIVATE_KEY_SHADOWED = 3 + PRIVATE_KEY_SHADOWED = 3, + PROTECTED_SHARED_SECRET = 4 }; @@ -233,7 +234,8 @@ gpg_error_t agent_key_from_file (ctrl_t ctrl, unsigned char **shadow_info, cache_mode_t cache_mode, lookup_ttl_t lookup_ttl, - gcry_sexp_t *result); + gcry_sexp_t *result, + char **r_passphrase); gpg_error_t agent_public_key_from_file (ctrl_t ctrl, const unsigned char *grip, gcry_sexp_t *result); @@ -251,7 +253,7 @@ int pinentry_active_p (ctrl_t ctrl, int waitseconds); int agent_askpin (ctrl_t ctrl, const char *desc_text, const char *prompt_text, const char *inital_errtext, - struct pin_entry_info_s *pininfo); + struct pin_entry_info_s *pininfo, int *r_cancelall); int agent_get_passphrase (ctrl_t ctrl, char **retpass, const char *desc, const char *prompt, const char *errtext, int with_qualitybar); @@ -289,7 +291,7 @@ int agent_pkdecrypt (ctrl_t ctrl, const char *desc_text, /*-- genkey.c --*/ int check_passphrase_constraints (ctrl_t ctrl, const char *pw, int silent); gpg_error_t agent_ask_new_passphrase (ctrl_t ctrl, const char *prompt, - char **r_passphrase); + char **r_passphrase, int *r_cancelall); int agent_genkey (ctrl_t ctrl, const char *cache_nonce, const char *keyparam, size_t keyparmlen, membuf_t *outbuf); int agent_protect_and_store (ctrl_t ctrl, gcry_sexp_t s_skey); diff --git a/agent/call-pinentry.c b/agent/call-pinentry.c index 6ab845a0c..4436b4f48 100644 --- a/agent/call-pinentry.c +++ b/agent/call-pinentry.c @@ -696,6 +696,29 @@ setup_qualitybar (void) } +/* Check the button_info line for a close action. */ +static gpg_error_t +close_button_status_cb (void *opaque, const char *line) +{ + int *flag = opaque; + const char *keyword = line; + int keywordlen; + + for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) + ; + while (spacep (line)) + line++; + if (keywordlen == 11 && !memcmp (keyword, "BUTTON_INFO", keywordlen)) + { + if ( !strcmp (line, "close") ) + *flag = 1; + } + + return 0; +} + + + /* Call the Entry and ask for the PIN. We do check for a valid PIN number here and repeat it as long as we have invalid formed @@ -704,7 +727,7 @@ int agent_askpin (ctrl_t ctrl, const char *desc_text, const char *prompt_text, const char *initial_errtext, - struct pin_entry_info_s *pininfo) + struct pin_entry_info_s *pininfo, int *r_cancel_all) { int rc; char line[ASSUAN_LINELENGTH]; @@ -712,6 +735,10 @@ agent_askpin (ctrl_t ctrl, const char *errtext = NULL; int is_pin = 0; int saveflag; + int close_button; + + if (r_cancel_all) + *r_cancel_all = 0; if (opt.batch) return 0; /* fixme: we should return BAD PIN */ @@ -791,8 +818,10 @@ agent_askpin (ctrl_t ctrl, saveflag = assuan_get_flag (entry_ctx, ASSUAN_CONFIDENTIAL); assuan_begin_confidential (entry_ctx); + close_button = 0; rc = assuan_transact (entry_ctx, "GETPIN", getpin_cb, &parm, - inq_quality, entry_ctx, NULL, NULL); + inq_quality, entry_ctx, + close_button_status_cb, &close_button); assuan_set_flag (entry_ctx, ASSUAN_CONFIDENTIAL, saveflag); /* Most pinentries out in the wild return the old Assuan error code for canceled which gets translated to an assuan Cancel error and @@ -801,6 +830,11 @@ agent_askpin (ctrl_t ctrl, && gpg_err_code (rc) == GPG_ERR_ASS_CANCELED) rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED); + /* Set a flag in case the window close button was clicked to + cancel the operation. */ + if (close_button && r_cancel_all && gpg_err_code (rc) == GPG_ERR_CANCELED) + *r_cancel_all = 1; + if (gpg_err_code (rc) == GPG_ERR_ASS_TOO_MUCH_DATA) errtext = is_pin? _("PIN too long") : _("Passphrase too long"); diff --git a/agent/command-ssh.c b/agent/command-ssh.c index ec1c73e6a..3829d4836 100644 --- a/agent/command-ssh.c +++ b/agent/command-ssh.c @@ -2425,7 +2425,7 @@ ssh_identity_register (ctrl_t ctrl, gcry_sexp_t key, int ttl) pi2->check_cb_arg = pi->pin; next_try: - err = agent_askpin (ctrl, description, NULL, initial_errtext, pi); + err = agent_askpin (ctrl, description, NULL, initial_errtext, pi, NULL); initial_errtext = NULL; if (err) goto out; @@ -2433,7 +2433,7 @@ ssh_identity_register (ctrl_t ctrl, gcry_sexp_t key, int ttl) /* Unless the passphrase is empty, ask to confirm it. */ if (pi->pin && *pi->pin) { - err = agent_askpin (ctrl, description2, NULL, NULL, pi2); + err = agent_askpin (ctrl, description2, NULL, NULL, pi2, NULL); if (err == -1) { /* The re-entered one did not match and the user did not hit cancel. */ diff --git a/agent/command.c b/agent/command.c index a8827e5de..e8af3ecf6 100644 --- a/agent/command.c +++ b/agent/command.c @@ -169,6 +169,23 @@ reset_notify (assuan_context_t ctx, char *line) } +/* Skip over options. + Blanks after the options are also removed. */ +static char * +skip_options (const char *line) +{ + while (spacep (line)) + line++; + while ( *line == '-' && line[1] == '-' ) + { + while (*line && !spacep (line)) + line++; + while (spacep (line)) + line++; + } + return (char*)line; +} + /* Check whether the option NAME appears in LINE */ static int has_option (const char *line, const char *name) @@ -177,6 +194,8 @@ has_option (const char *line, const char *name) int n = strlen (name); s = strstr (line, name); + if (s && s >= skip_options (line)) + return 0; return (s && (s == line || spacep (s-1)) && (!s[n] || spacep (s+n))); } @@ -190,6 +209,8 @@ has_option_name (const char *line, const char *name) int n = strlen (name); s = strstr (line, name); + if (s && s >= skip_options (line)) + return 0; return (s && (s == line || spacep (s-1)) && (!s[n] || spacep (s+n) || s[n] == '=')); } @@ -203,6 +224,8 @@ option_value (const char *line, const char *name) int n = strlen (name); s = strstr (line, name); + if (s && s >= skip_options (line)) + return NULL; if (s && (s == line || spacep (s-1)) && s[n] && (spacep (s+n) || s[n] == '=')) { @@ -215,23 +238,6 @@ option_value (const char *line, const char *name) } -/* Skip over options. It is assumed that leading spaces have been - removed (this is the case for lines passed to a handler from - assuan). Blanks after the options are also removed. */ -static char * -skip_options (char *line) -{ - while ( *line == '-' && line[1] == '-' ) - { - while (*line && !spacep (line)) - line++; - while (spacep (line)) - line++; - } - return line; -} - - /* Replace all '+' by a blank. */ static void plus_to_blank (char *s) @@ -530,23 +536,35 @@ cmd_marktrusted (assuan_context_t ctx, char *line) static const char hlp_havekey[] = - "HAVEKEY <hexstring_with_keygrip>\n" + "HAVEKEY <hexstrings_with_keygrips>\n" "\n" - "Return success when the secret key is available."; + "Return success if at least one of the secret keys with the given\n" + "keygrips is available."; static gpg_error_t cmd_havekey (assuan_context_t ctx, char *line) { - int rc; + gpg_error_t err; unsigned char buf[20]; - rc = parse_keygrip (ctx, line, buf); - if (rc) - return rc; - - if (agent_key_available (buf)) - return gpg_error (GPG_ERR_NO_SECKEY); + do + { + err = parse_keygrip (ctx, line, buf); + if (err) + return err; + + if (!agent_key_available (buf)) + return 0; /* Found. */ - return 0; + while (*line && *line != ' ' && *line != '\t') + line++; + while (*line == ' ' || *line == '\t') + line++; + } + while (*line); + + /* No leave_cmd() here because errors are expected and would clutter + the log. */ + return gpg_error (GPG_ERR_NO_SECKEY); } @@ -1316,9 +1334,14 @@ cmd_passwd (assuan_context_t ctx, char *line) ctrl->in_passwd++; rc = agent_key_from_file (ctrl, NULL, ctrl->server_local->keydesc, grip, &shadow_info, CACHE_MODE_IGNORE, NULL, - &s_skey); + &s_skey, NULL); if (rc) - ; + { + /* Not all users of gpg-agent know about fully cancled; thus we + map it back. */ + if (gpg_err_code (rc) == GPG_ERR_FULLY_CANCELED) + rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED); + } else if (!s_skey) { log_error ("changing a smartcard PIN is not yet supported\n"); @@ -1590,9 +1613,9 @@ cmd_import_key (assuan_context_t ctx, char *line) used to protect the key using the same code as for regular key import. */ - err = convert_openpgp (ctrl, openpgp_sexp, grip, - ctrl->server_local->keydesc, cache_nonce, - &key, &passphrase); + err = convert_from_openpgp (ctrl, openpgp_sexp, grip, + ctrl->server_local->keydesc, cache_nonce, + &key, &passphrase); if (err) goto leave; realkeylen = gcry_sexp_canon_len (key, keylen, NULL, &err); @@ -1620,7 +1643,7 @@ cmd_import_key (assuan_context_t ctx, char *line) err = agent_ask_new_passphrase (ctrl, _("Please enter the passphrase to protect the " "imported object within the GnuPG system."), - &passphrase); + &passphrase, NULL); if (err) goto leave; } @@ -1650,7 +1673,7 @@ cmd_import_key (assuan_context_t ctx, char *line) static const char hlp_export_key[] = - "EXPORT_KEY <hexstring_with_keygrip>\n" + "EXPORT_KEY [--cache-nonce=<nonce>] [--openpgp] <hexstring_with_keygrip>\n" "\n" "Export a secret key from the key store. The key will be encrypted\n" "using the current session's key wrapping key (cf. command KEYWRAP_KEY)\n" @@ -1668,6 +1691,26 @@ cmd_export_key (assuan_context_t ctx, char *line) gcry_cipher_hd_t cipherhd = NULL; unsigned char *wrappedkey = NULL; size_t wrappedkeylen; + int openpgp; + char *cache_nonce; + char *passphrase = NULL; + + openpgp = has_option (line, "--openpgp"); + cache_nonce = option_value (line, "--cache-nonce"); + if (cache_nonce) + { + for (; *line && !spacep (line); line++) + ; + if (*line) + *line++ = '\0'; + cache_nonce = xtrystrdup (cache_nonce); + if (!cache_nonce) + { + err = gpg_error_from_syserror (); + goto leave; + } + } + line = skip_options (line); if (!ctrl->server_local->export_key) { @@ -1685,8 +1728,11 @@ cmd_export_key (assuan_context_t ctx, char *line) goto leave; } + /* Get the key from the file. With the openpgp flag we also ask for + the passphrase so that we can use it to re-encrypt it. */ err = agent_key_from_file (ctrl, NULL, ctrl->server_local->keydesc, grip, - NULL, CACHE_MODE_IGNORE, NULL, &s_skey); + NULL, CACHE_MODE_IGNORE, NULL, &s_skey, + openpgp ? &passphrase : NULL); if (err) goto leave; if (!s_skey) @@ -1697,8 +1743,33 @@ cmd_export_key (assuan_context_t ctx, char *line) err = gpg_error (GPG_ERR_UNUSABLE_SECKEY); goto leave; } - - err = make_canon_sexp_pad (s_skey, 1, &key, &keylen); + + if (openpgp) + { + /* The openpgp option changes the key format into the OpenPGP + key transfer format. The result is already a padded + canonical S-expression. */ + if (!passphrase) + { + int fully_canceled; + err = agent_ask_new_passphrase + (ctrl, _("This key (or subkey) is not protected with a passphrase." + " Please enter a new passphrase to export it."), + &passphrase, &fully_canceled); + if (err) + { + if (fully_canceled) + err = gpg_error (GPG_ERR_FULLY_CANCELED); + goto leave; + } + } + err = convert_to_openpgp (ctrl, s_skey, passphrase, &key, &keylen); + } + else + { + /* Convert into a canonical S-expression and wrap that. */ + err = make_canon_sexp_pad (s_skey, 1, &key, &keylen); + } if (err) goto leave; gcry_sexp_release (s_skey); @@ -1735,12 +1806,18 @@ cmd_export_key (assuan_context_t ctx, char *line) leave: + xfree (passphrase); xfree (wrappedkey); gcry_cipher_close (cipherhd); xfree (key); gcry_sexp_release (s_skey); xfree (ctrl->server_local->keydesc); ctrl->server_local->keydesc = NULL; + + /* Not all users of gpg-agent know about fully cancled; thus we map + it back unless we know that it is okay. */ + if (!openpgp && gpg_err_code (err) == GPG_ERR_FULLY_CANCELED) + err = gpg_err_make (gpg_err_source (err), GPG_ERR_CANCELED); return leave_cmd (ctx, err); } diff --git a/agent/cvt-openpgp.c b/agent/cvt-openpgp.c index fee93e27d..8105ae6f6 100644 --- a/agent/cvt-openpgp.c +++ b/agent/cvt-openpgp.c @@ -525,10 +525,10 @@ try_do_unprotect_cb (struct pin_entry_info_s *pi) pointed to by GRIP. On error NULL is stored at all return arguments. */ gpg_error_t -convert_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) +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 err; gcry_sexp_t top_list; @@ -779,7 +779,7 @@ convert_openpgp (ctrl_t ctrl, gcry_sexp_t s_pgp, err = try_do_unprotect_cb (pi); } if (gpg_err_code (err) == GPG_ERR_BAD_PASSPHRASE) - err = agent_askpin (ctrl, prompt, NULL, NULL, pi); + err = agent_askpin (ctrl, prompt, NULL, NULL, pi, NULL); skeyidx = pi_arg.skeyidx; if (!err) { @@ -824,4 +824,267 @@ convert_openpgp (ctrl_t ctrl, gcry_sexp_t s_pgp, } + +static gpg_error_t +key_from_sexp (gcry_sexp_t sexp, const char *elems, gcry_mpi_t *array) +{ + gpg_error_t err = 0; + gcry_sexp_t l2; + int idx; + + for (idx=0; *elems; elems++, idx++) + { + l2 = gcry_sexp_find_token (sexp, elems, 1); + if (!l2) + { + err = gpg_error (GPG_ERR_NO_OBJ); /* Required parameter not found. */ + goto leave; + } + array[idx] = gcry_sexp_nth_mpi (l2, 1, GCRYMPI_FMT_USG); + gcry_sexp_release (l2); + if (!array[idx]) + { + err = gpg_error (GPG_ERR_INV_OBJ); /* Required parameter invalid. */ + goto leave; + } + } + + leave: + if (err) + { + int i; + + for (i=0; i < idx; i++) + { + gcry_mpi_release (array[i]); + array[i] = NULL; + } + } + return err; +} + + +/* Given an ARRAY of mpis with the key parameters, protect the secret + parameters in that array and replace them by one opaque encoded + mpi. NPKEY is the number of public key parameters and NSKEY is + the number of secret key parameters (including the public ones). + On success the array will have NPKEY+1 elements. */ +static gpg_error_t +apply_protection (gcry_mpi_t *array, int npkey, int nskey, + const char *passphrase, + int protect_algo, void *protect_iv, size_t protect_ivlen, + int s2k_mode, int s2k_algo, byte *s2k_salt, u32 s2k_count) +{ + gpg_error_t err; + int i, j; + gcry_cipher_hd_t cipherhd; + unsigned char *bufarr[10]; + size_t narr[10]; + unsigned int nbits[10]; + int ndata; + unsigned char *p, *data; + + assert (npkey < nskey); + assert (nskey < DIM (bufarr)); + + /* Collect only the secret key parameters into BUFARR et al and + compute the required size of the data buffer. */ + ndata = 20; /* Space for the SHA-1 checksum. */ + for (i = npkey, j = 0; i < nskey; i++, j++ ) + { + err = gcry_mpi_aprint (GCRYMPI_FMT_USG, bufarr+j, narr+j, array[i]); + if (err) + { + err = gpg_error_from_syserror (); + for (i = 0; i < j; i++) + xfree (bufarr[i]); + return err; + } + nbits[j] = gcry_mpi_get_nbits (array[i]); + ndata += 2 + narr[j]; + } + + /* Allocate data buffer and stuff it with the secret key parameters. */ + data = xtrymalloc_secure (ndata); + if (!data) + { + err = gpg_error_from_syserror (); + for (i = 0; i < (nskey-npkey); i++ ) + xfree (bufarr[i]); + return err; + } + p = data; + for (i = 0; i < (nskey-npkey); i++ ) + { + *p++ = nbits[i] >> 8 ; + *p++ = nbits[i]; + memcpy (p, bufarr[i], narr[i]); + p += narr[i]; + xfree (bufarr[i]); + bufarr[i] = NULL; + } + assert (p == data + ndata - 20); + + /* Append a hash of the secret key parameters. */ + gcry_md_hash_buffer (GCRY_MD_SHA1, p, data, ndata - 20); + + /* Encrypt it. */ + err = gcry_cipher_open (&cipherhd, protect_algo, + GCRY_CIPHER_MODE_CFB, GCRY_CIPHER_SECURE); + if (!err) + err = hash_passphrase_and_set_key (passphrase, cipherhd, protect_algo, + s2k_mode, s2k_algo, s2k_salt, s2k_count); + if (!err) + err = gcry_cipher_setiv (cipherhd, protect_iv, protect_ivlen); + if (!err) + err = gcry_cipher_encrypt (cipherhd, data, ndata, NULL, 0); + gcry_cipher_close (cipherhd); + if (err) + { + xfree (data); + return err; + } + + /* Replace the secret key parameters in the array by one opaque value. */ + for (i = npkey; i < nskey; i++ ) + { + gcry_mpi_release (array[i]); + array[i] = NULL; + } + array[npkey] = gcry_mpi_set_opaque (NULL, data, ndata*8); + return 0; +} + + +/* Convert our key S_KEY into an OpenPGP key transfer format. On + success a canonical encoded S-expression is stored at R_TRANSFERKEY + and its length at R_TRANSFERKEYLEN; this S-expression is also + padded to a multiple of 64 bits. */ +gpg_error_t +convert_to_openpgp (ctrl_t ctrl, gcry_sexp_t s_key, const char *passphrase, + unsigned char **r_transferkey, size_t *r_transferkeylen) +{ + gpg_error_t err; + gcry_sexp_t list, l2; + char *name; + int algo; + const char *algoname; + const char *elems; + int npkey, nskey; + gcry_mpi_t array[10]; + char protect_iv[16]; + char salt[8]; + unsigned long s2k_count; + int i, j; + + (void)ctrl; + + *r_transferkey = NULL; + + for (i=0; i < DIM (array); i++) + array[i] = NULL; + + list = gcry_sexp_find_token (s_key, "private-key", 0); + if (!list) + return gpg_error (GPG_ERR_NO_OBJ); /* Does not contain a key object. */ + l2 = gcry_sexp_cadr (list); + gcry_sexp_release (list); + list = l2; + name = gcry_sexp_nth_string (list, 0); + if (!name) + { + gcry_sexp_release (list); + return gpg_error (GPG_ERR_INV_OBJ); /* Invalid structure of object. */ + } + + algo = gcry_pk_map_name (name); + xfree (name); + + switch (algo) + { + case GCRY_PK_RSA: algoname = "rsa"; npkey = 2; elems = "nedpqu"; break; + case GCRY_PK_ELG: algoname = "elg"; npkey = 3; elems = "pgyx"; break; + case GCRY_PK_ELG_E: algoname = "elg"; npkey = 3; elems = "pgyx"; break; + case GCRY_PK_DSA: algoname = "dsa"; npkey = 4; elems = "pqgyx"; break; + case GCRY_PK_ECDSA: algoname = "ecdsa"; npkey = 6; elems = "pabgnqd"; break; + default: algoname = ""; npkey = 0; elems = NULL; break; + } + assert (!elems || strlen (elems) < DIM (array) ); + nskey = elems? strlen (elems) : 0; + + if (!elems) + err = gpg_error (GPG_ERR_PUBKEY_ALGO); + else + err = key_from_sexp (list, elems, array); + gcry_sexp_release (list); + if (err) + return err; + + gcry_create_nonce (protect_iv, sizeof protect_iv); + gcry_create_nonce (salt, sizeof salt); + s2k_count = get_standard_s2k_count (); + err = apply_protection (array, npkey, nskey, passphrase, + GCRY_CIPHER_AES, protect_iv, sizeof protect_iv, + 3, GCRY_MD_SHA1, salt, s2k_count); + /* Turn it into the transfer key S-expression. Note that we always + return a protected key. */ + if (!err) + { + char countbuf[35]; + membuf_t mbuf; + void *format_args_buf_ptr[1]; + int format_args_buf_int[1]; + void *format_args[10+2]; + size_t n; + gcry_sexp_t tmpkey, tmpsexp; + + snprintf (countbuf, sizeof countbuf, "%lu", s2k_count); + + init_membuf (&mbuf, 50); + put_membuf_str (&mbuf, "(skey"); + for (i=j=0; i < npkey; i++) + { + put_membuf_str (&mbuf, " _ %m"); + format_args[j++] = array + i; + } + put_membuf_str (&mbuf, " e %b"); + format_args_buf_ptr[0] = gcry_mpi_get_opaque (array[npkey], &n); + format_args_buf_int[0] = (n+7)/8; + format_args[j++] = format_args_buf_int; + format_args[j++] = format_args_buf_ptr; + put_membuf_str (&mbuf, ")\n"); + put_membuf (&mbuf, "", 1); + + tmpkey = NULL; + { + char *format = get_membuf (&mbuf, NULL); + if (!format) + err = gpg_error_from_syserror (); + else + err = gcry_sexp_build_array (&tmpkey, NULL, format, format_args); + xfree (format); + } + if (!err) + err = gcry_sexp_build (&tmpsexp, NULL, + "(openpgp-private-key\n" + " (version 1:4)\n" + " (algo %s)\n" + " %S\n" + " (protection sha1 aes %b 1:3 sha1 %b %s))\n", + algoname, + tmpkey, + (int)sizeof protect_iv, protect_iv, + (int)sizeof salt, salt, + countbuf); + gcry_sexp_release (tmpkey); + if (!err) + err = make_canon_sexp_pad (tmpsexp, 0, r_transferkey, r_transferkeylen); + gcry_sexp_release (tmpsexp); + } + + for (i=0; i < DIM (array); i++) + gcry_mpi_release (array[i]); + + return err; +} diff --git a/agent/cvt-openpgp.h b/agent/cvt-openpgp.h index 8dafbc454..db06a3f6a 100644 --- a/agent/cvt-openpgp.h +++ b/agent/cvt-openpgp.h @@ -19,10 +19,14 @@ #ifndef GNUPG_AGENT_CVT_OPENPGP_H #define GNUPG_AGENT_CVT_OPENPGP_H -gpg_error_t convert_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 (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_to_openpgp (ctrl_t ctrl, gcry_sexp_t s_key, + const char *passphrase, + unsigned char **r_transferkey, + size_t *r_transferkeylen); #endif /*GNUPG_AGENT_CVT_OPENPGP_H*/ diff --git a/agent/divert-scd.c b/agent/divert-scd.c index bf07d0785..0986f3bd3 100644 --- a/agent/divert-scd.c +++ b/agent/divert-scd.c @@ -266,7 +266,7 @@ getpin_cb (void *opaque, const char *info, char *buf, size_t maxbuf) if (any_flags) { - rc = agent_askpin (ctrl, info, prompt, again_text, pi); + rc = agent_askpin (ctrl, info, prompt, again_text, pi, NULL); again_text = NULL; if (!rc && newpin) { @@ -288,7 +288,7 @@ getpin_cb (void *opaque, const char *info, char *buf, size_t maxbuf) is_puk? _("Repeat this PUK"): _("Repeat this PIN")), - prompt, NULL, pi2); + prompt, NULL, pi2, NULL); if (!rc && strcmp (pi->pin, pi2->pin)) { again_text = (resetcode? @@ -312,7 +312,7 @@ getpin_cb (void *opaque, const char *info, char *buf, size_t maxbuf) info? info:"", info? "')":"") < 0) desc = NULL; - rc = agent_askpin (ctrl, desc?desc:info, prompt, NULL, pi); + rc = agent_askpin (ctrl, desc?desc:info, prompt, NULL, pi, NULL); xfree (desc); } diff --git a/agent/findkey.c b/agent/findkey.c index 02aea24e5..63b24a5d5 100644 --- a/agent/findkey.c +++ b/agent/findkey.c @@ -104,7 +104,7 @@ agent_write_private_key (const unsigned char *grip, } -/* Callback function to try the unprotection from the passpharse query +/* Callback function to try the unprotection from the passphrase query code. */ static int try_unprotect_cb (struct pin_entry_info_s *pi) @@ -273,11 +273,16 @@ modify_description (const char *in, const char *comment, char **result) should be the hex encoded keygrip of that key to be used with the caching mechanism. DESC_TEXT may be set to override the default description used for the pinentry. If LOOKUP_TTL is given this - function is used to lookup the default ttl. */ + function is used to lookup the default ttl. If R_PASSPHRASE is not + NULL, the function succeeded and the key was protected the used + passphrase (entered or from the cache) is stored there; if not NULL + will be stored. The caller needs to free the returned + passphrase. */ static int unprotect (ctrl_t ctrl, const char *cache_nonce, const char *desc_text, unsigned char **keybuf, const unsigned char *grip, - cache_mode_t cache_mode, lookup_ttl_t lookup_ttl) + cache_mode_t cache_mode, lookup_ttl_t lookup_ttl, + char **r_passphrase) { struct pin_entry_info_s *pi; struct try_unprotect_arg_s arg; @@ -285,6 +290,10 @@ unprotect (ctrl_t ctrl, const char *cache_nonce, const char *desc_text, unsigned char *result; size_t resultlen; char hexgrip[40+1]; + int fully_canceled; + + if (r_passphrase) + *r_passphrase = NULL; bin2hex (grip, 20, hexgrip); @@ -297,13 +306,17 @@ unprotect (ctrl_t ctrl, const char *cache_nonce, const char *desc_text, if (pw) { rc = agent_unprotect (*keybuf, pw, NULL, &result, &resultlen); - xfree (pw); if (!rc) { + if (r_passphrase) + *r_passphrase = pw; + else + xfree (pw); xfree (*keybuf); *keybuf = result; return 0; } + xfree (pw); } } @@ -318,13 +331,17 @@ unprotect (ctrl_t ctrl, const char *cache_nonce, const char *desc_text, if (pw) { rc = agent_unprotect (*keybuf, pw, NULL, &result, &resultlen); - xfree (pw); if (!rc) { + if (r_passphrase) + *r_passphrase = pw; + else + xfree (pw); xfree (*keybuf); *keybuf = result; return 0; } + xfree (pw); rc = 0; } @@ -366,7 +383,9 @@ unprotect (ctrl_t ctrl, const char *cache_nonce, const char *desc_text, arg.change_required = 0; pi->check_cb_arg = &arg; - rc = agent_askpin (ctrl, desc_text, NULL, NULL, pi); + rc = agent_askpin (ctrl, desc_text, NULL, NULL, pi, &fully_canceled); + if (gpg_err_code (rc) == GPG_ERR_CANCELED && fully_canceled) + rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_FULLY_CANCELED); if (!rc) { assert (arg.unprotected_key); @@ -400,8 +419,13 @@ unprotect (ctrl_t ctrl, const char *cache_nonce, const char *desc_text, return rc; } } - agent_put_cache (hexgrip, cache_mode, pi->pin, - lookup_ttl? lookup_ttl (hexgrip) : 0); + else + { + agent_put_cache (hexgrip, cache_mode, pi->pin, + lookup_ttl? lookup_ttl (hexgrip) : 0); + if (r_passphrase && *pi->pin) + *r_passphrase = xtrystrdup (pi->pin); + } xfree (*keybuf); *keybuf = arg.unprotected_key; } @@ -501,13 +525,17 @@ read_key_file (const unsigned char *grip, gcry_sexp_t *result) not simply pass the TTL value because the value is only needed if an unprotect action was needed and looking up the TTL may have some overhead (e.g. scanning the sshcontrol file). If a CACHE_NONCE is - given that cache item is first tried to get a passphrase. */ + given that cache item is first tried to get a passphrase. If + R_PASSPHRASE is not NULL, the function succeeded and the key was + protected the used passphrase (entered or from the cache) is stored + there; if not NULL will be stored. The caller needs to free the + returned passphrase. */ gpg_error_t agent_key_from_file (ctrl_t ctrl, const char *cache_nonce, const char *desc_text, const unsigned char *grip, unsigned char **shadow_info, cache_mode_t cache_mode, lookup_ttl_t lookup_ttl, - gcry_sexp_t *result) + gcry_sexp_t *result, char **r_passphrase) { int rc; unsigned char *buf; @@ -518,6 +546,8 @@ agent_key_from_file (ctrl_t ctrl, const char *cache_nonce, *result = NULL; if (shadow_info) *shadow_info = NULL; + if (r_passphrase) + *r_passphrase = NULL; rc = read_key_file (grip, &s_skey); if (rc) @@ -579,7 +609,7 @@ agent_key_from_file (ctrl_t ctrl, const char *cache_nonce, if (!rc) { rc = unprotect (ctrl, cache_nonce, desc_text_final, &buf, grip, - cache_mode, lookup_ttl); + cache_mode, lookup_ttl, r_passphrase); if (rc) log_error ("failed to unprotect the secret key: %s\n", gpg_strerror (rc)); @@ -626,6 +656,11 @@ agent_key_from_file (ctrl_t ctrl, const char *cache_nonce, if (rc || got_shadow_info) { xfree (buf); + if (r_passphrase) + { + xfree (*r_passphrase); + *r_passphrase = NULL; + } return rc; } @@ -637,6 +672,11 @@ agent_key_from_file (ctrl_t ctrl, const char *cache_nonce, { log_error ("failed to build S-Exp (off=%u): %s\n", (unsigned int)erroff, gpg_strerror (rc)); + if (r_passphrase) + { + xfree (*r_passphrase); + *r_passphrase = NULL; + } return rc; } diff --git a/agent/genkey.c b/agent/genkey.c index 0a35643e5..c26785204 100644 --- a/agent/genkey.c +++ b/agent/genkey.c @@ -290,10 +290,12 @@ reenter_compare_cb (struct pin_entry_info_s *pi) function returns 0 and store the passphrase at R_PASSPHRASE; if the user opted not to use a passphrase NULL will be stored there. The user needs to free the returned string. In case of an error and - error code is returned and NULL stored at R_PASSPHRASE. */ + error code is returned and NULL stored at R_PASSPHRASE. If + R_CANCEL_ALL is not NULL and the user canceled by directly closing + the window true will be stored at this address. */ gpg_error_t agent_ask_new_passphrase (ctrl_t ctrl, const char *prompt, - char **r_passphrase) + char **r_passphrase, int *r_cancel_all) { gpg_error_t err; const char *text1 = prompt; @@ -314,7 +316,7 @@ agent_ask_new_passphrase (ctrl_t ctrl, const char *prompt, pi2->check_cb_arg = pi->pin; next_try: - err = agent_askpin (ctrl, text1, NULL, initial_errtext, pi); + err = agent_askpin (ctrl, text1, NULL, initial_errtext, pi, r_cancel_all); initial_errtext = NULL; if (!err) { @@ -327,7 +329,7 @@ agent_ask_new_passphrase (ctrl_t ctrl, const char *prompt, /* Unless the passphrase is empty, ask to confirm it. */ if (pi->pin && *pi->pin) { - err = agent_askpin (ctrl, text2, NULL, NULL, pi2); + err = agent_askpin (ctrl, text2, NULL, NULL, pi2, NULL); if (err == -1) { /* The re-entered one did not match and the user did not hit cancel. */ @@ -379,7 +381,7 @@ agent_genkey (ctrl_t ctrl, const char *cache_nonce, rc = agent_ask_new_passphrase (ctrl, _("Please enter the passphrase to%0A" "to protect your new key"), - &passphrase); + &passphrase, NULL); if (rc) return rc; @@ -471,7 +473,7 @@ agent_protect_and_store (ctrl_t ctrl, gcry_sexp_t s_skey) rc = agent_ask_new_passphrase (ctrl, _("Please enter the new passphrase"), - &passphrase); + &passphrase, NULL); if (!rc) { rc = store_key (s_skey, passphrase, 1); diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c index 0e64939f5..a0e018ea3 100644 --- a/agent/gpg-agent.c +++ b/agent/gpg-agent.c @@ -440,6 +440,11 @@ remove_socket (char *name) static void cleanup (void) { + static int done; + + if (done) + return; + done = 1; deinitialize_module_cache (); remove_socket (socket_name); remove_socket (socket_name_ssh); @@ -724,6 +729,12 @@ main (int argc, char **argv ) if( parse_debug ) log_info (_("NOTE: no default option file `%s'\n"), configname ); + /* Save the default conf file name so that + reread_configuration is able to test whether the + config file has been created in the meantime. */ + xfree (config_filename); + config_filename = configname; + configname = NULL; } else { @@ -811,10 +822,15 @@ main (int argc, char **argv ) fclose( configfp ); configfp = NULL; /* Keep a copy of the name so that it can be read on SIGHUP. */ - config_filename = configname; + if (config_filename != configname) + { + xfree (config_filename); + config_filename = configname; + } configname = NULL; goto next_pass; } + xfree (configname); configname = NULL; if (log_get_errorcount(0)) @@ -1262,6 +1278,12 @@ void agent_exit (int rc) { /*FIXME: update_random_seed_file();*/ + + /* We run our cleanup handler because that may close cipher contexts + stored in secure memory and thus this needs to be done before we + explicitly terminate secure memory. */ + cleanup (); + #if 1 /* at this time a bit annoying */ if (opt.debug & DBG_MEMSTAT_VALUE) @@ -1337,8 +1359,8 @@ reread_configuration (void) fp = fopen (config_filename, "r"); if (!fp) { - log_error (_("option file `%s': %s\n"), - config_filename, strerror(errno) ); + log_info (_("option file `%s': %s\n"), + config_filename, strerror(errno) ); return; } diff --git a/agent/keyformat.txt b/agent/keyformat.txt index 841e5840f..da93f0c50 100644 --- a/agent/keyformat.txt +++ b/agent/keyformat.txt @@ -14,7 +14,8 @@ the ~/.gnupg home directory. This directory is named and should have permissions 700. The secret keys are stored in files with a name matching the -hexadecimal representation of the keygrip[2]. +hexadecimal representation of the keygrip[2] and suffixed with ".key". + Unprotected Private Key Format ============================== @@ -166,21 +167,23 @@ This format is used to transfer keys between gpg and gpg-agent. (openpgp-private-key (version V) - (protection PROTTYPE PROTALGO IV S2KMODE S2KHASH S2KSALT S2KCOUNT) (algo PUBKEYALGO) - (skey CSUM c P1 c P2 c P3 ... e PN)) + (skey _ P1 _ P2 _ P3 ... e PN) + (csum n) + (protection PROTTYPE PROTALGO IV S2KMODE S2KHASH S2KSALT S2KCOUNT)) * V is the packet version number (3 or 4). * PUBKEYALGO is a Libgcrypt algo name -* CSUM is the 16 bit checksum as defined by OpenPGP. * P1 .. PN are the parameters; the public parameters are never encrypted 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 + is an optional element. * If PROTTYPE is "sha1" the new style SHA1 checksum is used if it is "sum" - the old 16 bit checksum is used and if it is "none" no protection at - all is used. + the old 16 bit checksum (above) is used and if it is "none" no + protection at all is used. * PROTALGO is a Libgcrypt style cipher algorithm name * IV is the initialization verctor. * S2KMODE is the value from RFC-4880. @@ -189,6 +192,105 @@ This format is used to transfer keys between gpg and gpg-agent. * S2KCOUNT is the count value from RFC-4880. +Persistent Passphrase Format +============================ + +To allow persistent storage of cached passphrases we use a scheme +similar to the private-key storage format. This is a master +passphrase format where each file may protect several secrets under +one master passphrase. It is possible to have several of those files +each protected by a dedicated master passphrase. Clear text keywords +allow to list the available protected passphrases. + +The name of the files with these protected secrets have this form: +pw-<string>.dat. STRING may be an arbitrary string, as a default name +for the passphrase storage the name "pw-default.dat" is suggested. + + +(protected-shared-secret + ((desc descriptive_text) + (key [key_1] (keyword_1 keyword_2 keyword_n)) + (key [key_2] (keyword_21 keyword_22 keyword_2n)) + (key [key_n] (keyword_n1 keyword_n2 keyword_nn)) + (protected mode (parms) encrypted_octet_string) + (protected-at <isotimestamp>) + ) +) + +After decryption the encrypted_octet_string yields this S-expression: + +( + ( + (value key_1 value_1) + (value key_2 value_2) + (value key_n value_n) + ) + (hash sha1 #...[hashvalue]...#) +) + +The "descriptive_text" is displayed with the prompt to enter the +unprotection passphrase. + +KEY_1 to KEY_N are unique identifiers for the shared secret, for +example an URI. In case this information should be kept confidential +as well, they may not appear in the unprotected part; however they are +mandatory in the encrypted_octet_string. The list of keywords is +optional. The oder of the "key" lists and the order of the "value" +lists mut match, that is the first "key"-list is associated with the +first "value" list in the encrypted_octet_string. + +The protection mode etc. is indentical to the protection mode as +decribed for the private key format. + +list of the secret key parameters. The protected-at expression is +optional; the isotimestamp is 15 bytes long (e.g. "19610711T172000"). + +The "hash" in the encrypted_octet_string is calculated on the +concatenation of the key list and value lists: i.e it is required to +hash the concatenation of all these lists, including the +parenthesis and (if used) the protected-at list. + +Example: + +(protected-shared-secret + ((desc "List of system passphrases") + (key "uid-1002" ("Knuth" "Donald Ervin Knuth")) + (key "uid-1001" ("Dijkstra" "Edsgar Wybe Dijkstra")) + (key) + (protected mode (parms) encrypted_octet_string) + (protected-at "20100915T111722") + ) +) + +with "encrypted_octet_string" decoding to: + +( + ( + (value 4:1002 "signal flags at the lock") + (value 4:1001 "taocp") + (value 1:0 "premature optimization is the root of all evil") + ) + (hash sha1 #0102030405060708091011121314151617181920#) +) + +To compute the hash this S-expression (in canoncical format) was +hashed: + + ((desc "List of system passphrases") + (key "uid-1002" ("Knuth" "Donald Ervin Knuth")) + (key "uid-1001" ("Dijkstra" "Edsgar Wybe Dijkstra")) + (key) + (value 4:1002 "signal flags at the lock") + (value 4:1001 "taocp") + (value 1:0 "premature optimization is the root of all evil") + (protected-at "20100915T111722") + ) + + + + + + Notes: ====== diff --git a/agent/pkdecrypt.c b/agent/pkdecrypt.c index 11d297fbf..11d0cc375 100644 --- a/agent/pkdecrypt.c +++ b/agent/pkdecrypt.c @@ -66,7 +66,9 @@ agent_pkdecrypt (ctrl_t ctrl, const char *desc_text, } rc = agent_key_from_file (ctrl, NULL, desc_text, ctrl->keygrip, &shadow_info, - CACHE_MODE_NORMAL, NULL, &s_skey); + CACHE_MODE_NORMAL, NULL, &s_skey, NULL); + if (gpg_err_code (rc) == GPG_ERR_FULLY_CANCELED) + rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED); if (rc) { if (gpg_err_code (rc) == GPG_ERR_ENOENT) diff --git a/agent/pksign.c b/agent/pksign.c index d31a687ce..12f4420be 100644 --- a/agent/pksign.c +++ b/agent/pksign.c @@ -255,7 +255,9 @@ agent_pksign_do (ctrl_t ctrl, const char *cache_nonce, rc = agent_key_from_file (ctrl, cache_nonce, desc_text, ctrl->keygrip, &shadow_info, cache_mode, lookup_ttl, - &s_skey); + &s_skey, NULL); + if (gpg_err_code (rc) == GPG_ERR_FULLY_CANCELED) + rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED); if (rc) { log_error ("failed to read the secret key\n"); diff --git a/agent/protect.c b/agent/protect.c index 3a983e2bd..795d06231 100644 --- a/agent/protect.c +++ b/agent/protect.c @@ -191,14 +191,16 @@ get_standard_s2k_count (void) -/* Calculate the MIC for a private key S-Exp. SHA1HASH should point to - a 20 byte buffer. This function is suitable for any algorithms. */ +/* Calculate the MIC for a private key or shared secret S-expression. + SHA1HASH should point to a 20 byte buffer. This function is + suitable for all algorithms. */ static int calculate_mic (const unsigned char *plainkey, unsigned char *sha1hash) { const unsigned char *hash_begin, *hash_end; const unsigned char *s; size_t n; + int is_shared_secret; s = plainkey; if (*s != '(') @@ -207,16 +209,23 @@ calculate_mic (const unsigned char *plainkey, unsigned char *sha1hash) n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); - if (!smatch (&s, n, "private-key")) + if (smatch (&s, n, "private-key")) + is_shared_secret = 0; + else if (smatch (&s, n, "shared-secret")) + is_shared_secret = 1; + else return gpg_error (GPG_ERR_UNKNOWN_SEXP); if (*s != '(') return gpg_error (GPG_ERR_UNKNOWN_SEXP); hash_begin = s; - s++; - n = snext (&s); - if (!n) - return gpg_error (GPG_ERR_INV_SEXP); - s += n; /* skip over the algorithm name */ + if (!is_shared_secret) + { + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + s += n; /* Skip the algorithm name. */ + } while (*s == '(') { @@ -955,7 +964,7 @@ agent_unprotect (const unsigned char *protectedkey, const char *passphrase, xfree (final); return rc; } - /* Now remove tha part which is included in the MIC but should not + /* Now remove the part which is included in the MIC but should not go into the final thing. */ if (cutlen) { diff --git a/agent/t-protect.c b/agent/t-protect.c index 0e29cafe2..7b80bcbbb 100644 --- a/agent/t-protect.c +++ b/agent/t-protect.c @@ -289,6 +289,13 @@ test_agent_get_shadow_info (void) } +static void +test_agent_protect_shared_secret (void) +{ + +} + + int @@ -305,6 +312,7 @@ main (int argc, char **argv) test_make_shadow_info (); test_agent_shadow_key (); test_agent_get_shadow_info (); + test_agent_protect_shared_secret (); return 0; } |