diff --git a/agent/agent.h b/agent/agent.h index 531fad210..f0b2a334e 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -225,6 +225,17 @@ typedef struct ssh_control_file_s *ssh_control_file_t; /* Forward reference for local definitions in call-scd.c. */ struct daemon_local_s; +/* Object to hold ephemeral secret keys. */ +struct ephemeral_private_key_s +{ + struct ephemeral_private_key_s *next; + unsigned char grip[KEYGRIP_LEN]; + unsigned char *keybuf; /* Canon-s-exp with the private key (malloced). */ + size_t keybuflen; +}; +typedef struct ephemeral_private_key_s *ephemeral_private_key_t; + + /* Collection of data per session (aka connection). */ struct server_control_s { @@ -246,6 +257,12 @@ struct server_control_s /* Private data of the daemon (call-XXX.c). */ struct daemon_local_s *d_local[DAEMON_MAX_TYPE]; + /* Linked list with ephemeral stored private keys. */ + ephemeral_private_key_t ephemeral_keys; + + /* If set functions will lookup keys in the ephemeral_keys list. */ + int ephemeral_mode; + /* Environment settings for the connection. */ session_env_t session_env; char *lc_ctype; @@ -452,7 +469,8 @@ void start_command_handler_ssh (ctrl_t, gnupg_fd_t); /*-- findkey.c --*/ gpg_error_t agent_modify_description (const char *in, const char *comment, const gcry_sexp_t key, char **result); -gpg_error_t agent_write_private_key (const unsigned char *grip, +gpg_error_t agent_write_private_key (ctrl_t ctrl, + const unsigned char *grip, const void *buffer, size_t length, int force, const char *serialno, const char *keyref, @@ -477,7 +495,7 @@ gpg_error_t agent_ssh_key_from_file (ctrl_t ctrl, gcry_sexp_t *result, int *r_order); int agent_pk_get_algo (gcry_sexp_t s_key); int agent_is_tpm2_key(gcry_sexp_t s_key); -int agent_key_available (const unsigned char *grip); +int agent_key_available (ctrl_t ctrl, const unsigned char *grip); gpg_error_t agent_key_info_from_file (ctrl_t ctrl, const unsigned char *grip, int *r_keytype, unsigned char **r_shadow_info, @@ -485,7 +503,8 @@ gpg_error_t agent_key_info_from_file (ctrl_t ctrl, const unsigned char *grip, gpg_error_t agent_delete_key (ctrl_t ctrl, const char *desc_text, const unsigned char *grip, int force, int only_stubs); -gpg_error_t agent_update_private_key (const unsigned char *grip, nvc_t pk); +gpg_error_t agent_update_private_key (ctrl_t ctrl, + const unsigned char *grip, nvc_t pk); /*-- call-pinentry.c --*/ void initialize_module_call_pinentry (void); @@ -541,15 +560,21 @@ gpg_error_t agent_pkdecrypt (ctrl_t ctrl, const char *desc_text, #define CHECK_CONSTRAINTS_NOT_EMPTY 1 #define CHECK_CONSTRAINTS_NEW_SYMKEY 2 +#define GENKEY_FLAG_NO_PROTECTION 1 +#define GENKEY_FLAG_PRESET 2 + +void clear_ephemeral_keys (ctrl_t ctrl); + int check_passphrase_constraints (ctrl_t ctrl, const char *pw, unsigned int flags, char **failed_constraint); gpg_error_t agent_ask_new_passphrase (ctrl_t ctrl, const char *prompt, char **r_passphrase); -int agent_genkey (ctrl_t ctrl, const char *cache_nonce, time_t timestamp, +int agent_genkey (ctrl_t ctrl, unsigned int flags, + const char *cache_nonce, time_t timestamp, const char *keyparam, size_t keyparmlen, - int no_protection, const char *override_passphrase, - int preset, membuf_t *outbuf); + const char *override_passphrase, + membuf_t *outbuf); gpg_error_t agent_protect_and_store (ctrl_t ctrl, gcry_sexp_t s_skey, char **passphrase_addr); @@ -587,7 +612,7 @@ gpg_error_t s2k_hash_passphrase (const char *passphrase, int hashalgo, const unsigned char *s2ksalt, unsigned int s2kcount, unsigned char *key, size_t keylen); -gpg_error_t agent_write_shadow_key (const unsigned char *grip, +gpg_error_t agent_write_shadow_key (ctrl_t ctrl, const unsigned char *grip, const char *serialno, const char *keyid, const unsigned char *pkbuf, int force, const char *dispserialno); diff --git a/agent/command-ssh.c b/agent/command-ssh.c index ca3993321..189acd7f8 100644 --- a/agent/command-ssh.c +++ b/agent/command-ssh.c @@ -2430,14 +2430,14 @@ card_key_available (ctrl_t ctrl, const struct card_key_info_s *keyinfo, } hex2bin (keyinfo->keygrip, grip, sizeof (grip)); - if ( agent_key_available (grip) ) + if (!ctrl->ephemeral_mode && agent_key_available (ctrl, grip) ) { char *dispserialno; /* (Shadow)-key is not available in our key storage. */ agent_card_getattr (ctrl, "$DISPSERIALNO", &dispserialno, keyinfo->keygrip); - err = agent_write_shadow_key (grip, keyinfo->serialno, + err = agent_write_shadow_key (ctrl, grip, keyinfo->serialno, keyinfo->idstr, pkbuf, 0, dispserialno); xfree (dispserialno); if (err) @@ -3222,7 +3222,7 @@ ssh_identity_register (ctrl_t ctrl, ssh_key_type_spec_t *spec, /* Check whether the key is already in our key storage. Don't do anything then besides (re-)adding it to sshcontrol. */ - if ( !agent_key_available (key_grip_raw) ) + if ( !agent_key_available (ctrl, key_grip_raw) ) goto key_exists; /* Yes, key is available. */ err = ssh_key_extract_comment (key, &comment); @@ -3286,7 +3286,7 @@ ssh_identity_register (ctrl_t ctrl, ssh_key_type_spec_t *spec, /* Store this key to our key storage. We do not store a creation * timestamp because we simply do not know. */ - err = agent_write_private_key (key_grip_raw, buffer, buffer_n, 0, + err = agent_write_private_key (ctrl, key_grip_raw, buffer, buffer_n, 0, NULL, NULL, NULL, 0); if (err) goto out; diff --git a/agent/command.c b/agent/command.c index da91053d8..20ae08e9f 100644 --- a/agent/command.c +++ b/agent/command.c @@ -251,6 +251,9 @@ reset_notify (assuan_context_t ctx, char *line) clear_nonce_cache (ctrl); + /* Note that a RESET does not clear the ephemeral store becuase + * clients are used to issue a RESET on a connection. */ + return 0; } @@ -643,15 +646,15 @@ static const char hlp_havekey[] = static gpg_error_t cmd_havekey (assuan_context_t ctx, char *line) { - ctrl_t ctrl; + ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; unsigned char grip[20]; char *p; int list_mode = 0; /* Less than 0 for no limit. */ int info_mode = 0; int counter; - char *dirname; - gnupg_dir_t dir; + char *dirname = NULL; + gnupg_dir_t dir = NULL; gnupg_dirent_t dir_entry; char hexgrip[41]; struct card_key_info_s *keyinfo_on_cards, *l; @@ -668,14 +671,11 @@ cmd_havekey (assuan_context_t ctx, char *line) line = skip_options (line); - if (info_mode) { int keytype; const char *infostring; - ctrl = assuan_get_pointer (ctx); - err = parse_keygrip (ctx, line, grip); if (err) goto leave; @@ -706,7 +706,7 @@ cmd_havekey (assuan_context_t ctx, char *line) if (err) return err; - if (!agent_key_available (grip)) + if (!agent_key_available (ctrl, grip)) return 0; /* Found. */ while (*line && *line != ' ' && *line != '\t') @@ -724,7 +724,6 @@ cmd_havekey (assuan_context_t ctx, char *line) /* List mode. */ dir = NULL; dirname = NULL; - ctrl = assuan_get_pointer (ctx); if (ctrl->restricted) { @@ -1117,26 +1116,27 @@ cmd_genkey (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; - int no_protection; unsigned char *value = NULL; size_t valuelen; unsigned char *newpasswd = NULL; membuf_t outbuf; char *cache_nonce = NULL; char *passwd_nonce = NULL; - int opt_preset; int opt_inq_passwd; size_t n; char *p, *pend; const char *s; time_t opt_timestamp; int c; + unsigned int flags = 0; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); - no_protection = has_option (line, "--no-protection"); - opt_preset = has_option (line, "--preset"); + if (has_option (line, "--no-protection")) + flags |= GENKEY_FLAG_NO_PROTECTION; + if (has_option (line, "--preset")) + flags |= GENKEY_FLAG_PRESET; opt_inq_passwd = has_option (line, "--inq-passwd"); passwd_nonce = option_value (line, "--passwd-nonce"); if (passwd_nonce) @@ -1191,7 +1191,7 @@ cmd_genkey (assuan_context_t ctx, char *line) /* If requested, ask for the password to be used for the key. If this is not used the regular Pinentry mechanism is used. */ - if (opt_inq_passwd && !no_protection) + if (opt_inq_passwd && !(flags & GENKEY_FLAG_NO_PROTECTION)) { /* (N is used as a dummy) */ assuan_begin_confidential (ctx); @@ -1204,16 +1204,17 @@ cmd_genkey (assuan_context_t ctx, char *line) /* Empty password given - switch to no-protection mode. */ xfree (newpasswd); newpasswd = NULL; - no_protection = 1; + flags |= GENKEY_FLAG_NO_PROTECTION; } } else if (passwd_nonce) newpasswd = agent_get_cache (ctrl, passwd_nonce, CACHE_MODE_NONCE); - rc = agent_genkey (ctrl, cache_nonce, opt_timestamp, - (char*)value, valuelen, no_protection, - newpasswd, opt_preset, &outbuf); + + rc = agent_genkey (ctrl, flags, cache_nonce, opt_timestamp, + (char*)value, valuelen, + newpasswd, &outbuf); leave: if (newpasswd) @@ -1317,7 +1318,7 @@ cmd_keyattr (assuan_context_t ctx, char *line) if (!err) err = nvc_set_private_key (keymeta, s_key); if (!err) - err = agent_update_private_key (grip, keymeta); + err = agent_update_private_key (ctrl, grip, keymeta); } nvc_release (keymeta); @@ -1327,6 +1328,8 @@ cmd_keyattr (assuan_context_t ctx, char *line) leave: return leave_cmd (ctx, err); } + + static const char hlp_readkey[] = "READKEY [--no-data] [--format=ssh] \n" @@ -1390,7 +1393,7 @@ cmd_readkey (assuan_context_t ctx, char *line) goto leave; } - if (agent_key_available (grip)) + if (!ctrl->ephemeral_mode && agent_key_available (ctrl, grip)) { /* (Shadow)-key is not available in our key storage. */ char *dispserialno; @@ -1398,7 +1401,7 @@ cmd_readkey (assuan_context_t ctx, char *line) bin2hex (grip, 20, hexgrip); agent_card_getattr (ctrl, "$DISPSERIALNO", &dispserialno, hexgrip); - rc = agent_write_shadow_key (grip, serialno, keyid, pkbuf, 0, + rc = agent_write_shadow_key (ctrl, grip, serialno, keyid, pkbuf, 0, dispserialno); xfree (dispserialno); if (rc) @@ -2934,7 +2937,7 @@ cmd_import_key (assuan_context_t ctx, char *line) } else { - if (!force && !agent_key_available (grip)) + if (!force && !agent_key_available (ctrl, grip)) err = gpg_error (GPG_ERR_EEXIST); else { @@ -2956,11 +2959,11 @@ cmd_import_key (assuan_context_t ctx, char *line) err = agent_protect (key, passphrase, &finalkey, &finalkeylen, ctrl->s2k_count); if (!err) - err = agent_write_private_key (grip, finalkey, finalkeylen, force, + err = agent_write_private_key (ctrl, grip, finalkey, finalkeylen, force, NULL, NULL, NULL, opt_timestamp); } else - err = agent_write_private_key (grip, key, realkeylen, force, + err = agent_write_private_key (ctrl, grip, key, realkeylen, force, NULL, NULL, NULL, opt_timestamp); leave: @@ -2979,7 +2982,8 @@ cmd_import_key (assuan_context_t ctx, char *line) static const char hlp_export_key[] = - "EXPORT_KEY [--cache-nonce=] [--openpgp|--mode1003] \n" + "EXPORT_KEY [--cache-nonce=] \\\n" + " [--openpgp|--mode1003] \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" @@ -2987,8 +2991,8 @@ static const char hlp_export_key[] = "prior to using this command. The function takes the keygrip as argument.\n" "\n" "If --openpgp is used, the secret key material will be exported in RFC 4880\n" - "compatible passphrase-protected form. If --mode1003 is use the secret key\n" - "is exported as s-expression as storred locally. Without those options,\n" + "compatible passphrase-protected form. In --mode1003 the secret key\n" + "is exported as s-expression as stored locally. Without those options,\n" "the secret key material will be exported in the clear (after prompting\n" "the user to unlock it, if needed).\n"; static gpg_error_t @@ -3045,7 +3049,7 @@ cmd_export_key (assuan_context_t ctx, char *line) if (err) goto leave; - if (agent_key_available (grip)) + if (agent_key_available (ctrl, grip)) { err = gpg_error (GPG_ERR_NO_SECKEY); goto leave; @@ -3257,9 +3261,9 @@ cmd_keytocard (assuan_context_t ctx, char *line) if (err) goto leave; - if (agent_key_available (grip)) + if (agent_key_available (ctrl, grip)) { - err =gpg_error (GPG_ERR_NO_SECKEY); + err = gpg_error (GPG_ERR_NO_SECKEY); goto leave; } @@ -3577,7 +3581,7 @@ cmd_keytotpm (assuan_context_t ctx, char *line) if (err) goto leave; - if (agent_key_available (grip)) + if (agent_key_available (ctrl, grip)) { err =gpg_error (GPG_ERR_NO_SECKEY); goto leave; @@ -3869,6 +3873,7 @@ static const char hlp_getinfo[] = " getenv NAME - Return value of envvar NAME.\n" " connections - Return number of active connections.\n" " jent_active - Returns OK if Libgcrypt's JENT is active.\n" + " ephemeral - Returns OK if the connection is in ephemeral mode.\n" " restricted - Returns OK if the connection is in restricted mode.\n" " cmd_has_option CMD OPT\n" " - Returns OK if command CMD has option OPT.\n"; @@ -3922,6 +3927,10 @@ cmd_getinfo (assuan_context_t ctx, char *line) snprintf (numbuf, sizeof numbuf, "%lu", get_standard_s2k_count ()); rc = assuan_send_data (ctx, numbuf, strlen (numbuf)); } + else if (!strcmp (line, "ephemeral")) + { + rc = ctrl->ephemeral_mode? 0 : gpg_error (GPG_ERR_FALSE); + } else if (!strcmp (line, "restricted")) { rc = ctrl->restricted? 0 : gpg_error (GPG_ERR_FALSE); @@ -4078,6 +4087,10 @@ option_handler (assuan_context_t ctx, const char *key, const char *value) ctrl->server_local->allow_fully_canceled = gnupg_compare_version (value, "2.1.0"); } + else if (!strcmp (key, "ephemeral")) + { + ctrl->ephemeral_mode = *value? atoi (value) : 0; + } else if (ctrl->restricted) { err = gpg_error (GPG_ERR_FORBIDDEN); diff --git a/agent/cvt-openpgp.c b/agent/cvt-openpgp.c index 6aad35bff..50755c0fd 100644 --- a/agent/cvt-openpgp.c +++ b/agent/cvt-openpgp.c @@ -969,7 +969,7 @@ convert_from_openpgp_main (ctrl_t ctrl, gcry_sexp_t s_pgp, int dontcare_exist, if (err) goto leave; - if (!dontcare_exist && !from_native && !agent_key_available (grip)) + if (!dontcare_exist && !from_native && !agent_key_available (ctrl, grip)) { err = gpg_error (GPG_ERR_EEXIST); goto leave; @@ -1147,14 +1147,16 @@ convert_from_openpgp_native (ctrl_t ctrl, if (!agent_protect (*r_key, passphrase, &protectedkey, &protectedkeylen, ctrl->s2k_count)) - agent_write_private_key (grip, protectedkey, protectedkeylen, 1, - NULL, NULL, NULL, 0); + agent_write_private_key (ctrl, grip, + protectedkey, + protectedkeylen, + 1, NULL, NULL, NULL, 0); xfree (protectedkey); } else { /* Empty passphrase: write key without protection. */ - agent_write_private_key (grip, + agent_write_private_key (ctrl, grip, *r_key, gcry_sexp_canon_len (*r_key, 0, NULL,NULL), 1, NULL, NULL, NULL, 0); diff --git a/agent/divert-tpm2.c b/agent/divert-tpm2.c index e7c6a8aae..2496d091a 100644 --- a/agent/divert-tpm2.c +++ b/agent/divert-tpm2.c @@ -57,7 +57,7 @@ agent_write_tpm2_shadow_key (ctrl_t ctrl, const unsigned char *grip, } len = gcry_sexp_canon_len (shdkey, 0, NULL, NULL); - err = agent_write_private_key (grip, shdkey, len, 1 /*force*/, + err = agent_write_private_key (ctrl, grip, shdkey, len, 1 /*force*/, NULL, NULL, NULL, 0); xfree (shdkey); if (err) @@ -70,7 +70,7 @@ agent_write_tpm2_shadow_key (ctrl_t ctrl, const unsigned char *grip, return GPG_ERR_ENOMEM; gcry_sexp_sprint(s_key, GCRYSEXP_FMT_CANON, pkbuf, len); - err1 = agent_write_private_key (grip, pkbuf, len, 1 /*force*/, + err1 = agent_write_private_key (ctrl, grip, pkbuf, len, 1 /*force*/, NULL, NULL, NULL, 0); xfree(pkbuf); if (err1) diff --git a/agent/findkey.c b/agent/findkey.c index 4e55119e3..1f2938ea3 100644 --- a/agent/findkey.c +++ b/agent/findkey.c @@ -40,7 +40,7 @@ #endif -static gpg_error_t read_key_file (const unsigned char *grip, +static gpg_error_t read_key_file (ctrl_t ctrl, const unsigned char *grip, gcry_sexp_t *result, nvc_t *r_keymeta, char **r_orig_key_value); static gpg_error_t is_shadowed_key (gcry_sexp_t s_skey); @@ -73,6 +73,30 @@ fname_from_keygrip (const unsigned char *grip, int for_new) } +/* Helper until we have a "wipe" mode flag in es_fopen. */ +static void +wipe_and_fclose (estream_t fp) +{ + void *blob; + size_t blob_size; + + if (!fp) + ; + else if (es_fclose_snatch (fp, &blob, &blob_size)) + { + log_error ("error wiping buffer during fclose\n"); + es_fclose (fp); + } + else if (blob) + { + wipememory (blob, blob_size); + gpgrt_free (blob); + } +} + + + + /* Replace all linefeeds in STRING by "%0A" and return a new malloced * string. May return NULL on memory error. */ static char * @@ -110,7 +134,8 @@ linefeed_to_percent0A (const char *string) * TIMESTAMP is not zero and the key does not yet exists it will be * recorded as creation date. */ gpg_error_t -agent_write_private_key (const unsigned char *grip, +agent_write_private_key (ctrl_t ctrl, + const unsigned char *grip, const void *buffer, size_t length, int force, const char *serialno, const char *keyref, const char *dispserialno, @@ -120,7 +145,7 @@ agent_write_private_key (const unsigned char *grip, char *fname = NULL; char *tmpfname = NULL; estream_t fp = NULL; - int newkey; + int newkey = 0; nvc_t pk = NULL; gcry_sexp_t key = NULL; int removetmp = 0; @@ -134,11 +159,13 @@ agent_write_private_key (const unsigned char *grip, const char *s; int force_modify = 0; - fname = fname_from_keygrip (grip, 0); + fname = (ctrl->ephemeral_mode + ? xtrystrdup ("[ephemeral key store]") + : fname_from_keygrip (grip, 0)); if (!fname) return gpg_error_from_syserror (); - err = read_key_file (grip, &key, &pk, &orig_key_value); + err = read_key_file (ctrl, grip, &key, &pk, &orig_key_value); if (err) { if (gpg_err_code (err) == GPG_ERR_ENOENT) @@ -289,43 +316,99 @@ agent_write_private_key (const unsigned char *grip, goto leave; } - /* Create a temporary file for writing. */ - tmpfname = fname_from_keygrip (grip, 1); - fp = tmpfname ? es_fopen (tmpfname, "wbx,mode=-rw") : NULL; - if (!fp) + if (ctrl->ephemeral_mode) { - err = gpg_error_from_syserror (); - log_error ("can't create '%s': %s\n", tmpfname, gpg_strerror (err)); - goto leave; - } + ephemeral_private_key_t ek; + void *blob; + size_t blobsize; - err = nvc_write (pk, fp); - if (!err && es_fflush (fp)) - err = gpg_error_from_syserror (); - if (err) - { - log_error ("error writing '%s': %s\n", tmpfname, gpg_strerror (err)); - removetmp = 1; - goto leave; - } + for (ek = ctrl->ephemeral_keys; ek; ek = ek->next) + if (!memcmp (ek->grip, grip, KEYGRIP_LEN)) + break; + if (!ek) + { + ek = xtrycalloc (1, sizeof *ek); + if (!ek) + { + err = gpg_error_from_syserror (); + goto leave; + } + memcpy (ek->grip, grip, KEYGRIP_LEN); + ek->next = ctrl->ephemeral_keys; + ctrl->ephemeral_keys = ek; + } - if (es_fclose (fp)) - { - err = gpg_error_from_syserror (); - log_error ("error closing '%s': %s\n", tmpfname, gpg_strerror (err)); - removetmp = 1; - goto leave; + fp = es_fopenmem (0, "wb,wipe"); + if (!fp) + { + err = gpg_error_from_syserror (); + log_error ("can't open memory stream: %s\n", gpg_strerror (err)); + goto leave; + } + + err = nvc_write (pk, fp); + if (err) + { + log_error ("error writing to memory stream: %s\n",gpg_strerror (err)); + goto leave; + } + + if (es_fclose_snatch (fp, &blob, &blobsize) || !blob) + { + err = gpg_error_from_syserror (); + log_error ("error getting memory stream buffer: %s\n", + gpg_strerror (err)); + /* Closing right away so that we don't try another snatch in + * the cleanup. */ + es_fclose (fp); + fp = NULL; + goto leave; + } + fp = NULL; + xfree (ek->keybuf); + ek->keybuf = blob; + ek->keybuflen = blobsize; } else - fp = NULL; - - err = gnupg_rename_file (tmpfname, fname, &blocksigs); - if (err) { - err = gpg_error_from_syserror (); - log_error ("error renaming '%s': %s\n", tmpfname, gpg_strerror (err)); - removetmp = 1; - goto leave; + /* Create a temporary file for writing. */ + tmpfname = fname_from_keygrip (grip, 1); + fp = tmpfname ? es_fopen (tmpfname, "wbx,mode=-rw") : NULL; + if (!fp) + { + err = gpg_error_from_syserror (); + log_error ("can't create '%s': %s\n", tmpfname, gpg_strerror (err)); + goto leave; + } + + err = nvc_write (pk, fp); + if (!err && es_fflush (fp)) + err = gpg_error_from_syserror (); + if (err) + { + log_error ("error writing '%s': %s\n", tmpfname, gpg_strerror (err)); + removetmp = 1; + goto leave; + } + + if (es_fclose (fp)) + { + err = gpg_error_from_syserror (); + log_error ("error closing '%s': %s\n", tmpfname, gpg_strerror (err)); + removetmp = 1; + goto leave; + } + else + fp = NULL; + + err = gnupg_rename_file (tmpfname, fname, &blocksigs); + if (err) + { + err = gpg_error_from_syserror (); + log_error ("error renaming '%s': %s\n", tmpfname, gpg_strerror (err)); + removetmp = 1; + goto leave; + } } bump_key_eventcounter (); @@ -333,7 +416,10 @@ agent_write_private_key (const unsigned char *grip, leave: if (blocksigs) gnupg_unblock_all_signals (); - es_fclose (fp); + if (ctrl->ephemeral_mode) + wipe_and_fclose (fp); + else + es_fclose (fp); if (removetmp && tmpfname) gnupg_remove (tmpfname); xfree (orig_key_value); @@ -350,7 +436,7 @@ agent_write_private_key (const unsigned char *grip, gpg_error_t -agent_update_private_key (const unsigned char *grip, nvc_t pk) +agent_update_private_key (ctrl_t ctrl, const unsigned char *grip, nvc_t pk) { gpg_error_t err; char *fname0 = NULL; /* The existing file name. */ @@ -359,6 +445,57 @@ agent_update_private_key (const unsigned char *grip, nvc_t pk) int removetmp = 0; int blocksigs = 0; + if (ctrl->ephemeral_mode) + { + ephemeral_private_key_t ek; + void *blob; + size_t blobsize; + + for (ek = ctrl->ephemeral_keys; ek; ek = ek->next) + if (!memcmp (ek->grip, grip, KEYGRIP_LEN)) + break; + if (!ek) + { + err = gpg_error (GPG_ERR_ENOENT); + goto leave; + } + + fp = es_fopenmem (0, "wbx,wipe"); + if (!fp) + { + err = gpg_error_from_syserror (); + log_error ("can't open memory stream: %s\n", gpg_strerror (err)); + goto leave; + } + + err = nvc_write (pk, fp); + if (err) + { + log_error ("error writing to memory stream: %s\n",gpg_strerror (err)); + goto leave; + } + + if (es_fclose_snatch (fp, &blob, &blobsize) || !blob) + { + err = gpg_error_from_syserror (); + log_error ("error getting memory stream buffer: %s\n", + gpg_strerror (err)); + /* Closing right away so that we don't try another snatch in + * the cleanup. */ + es_fclose (fp); + fp = NULL; + goto leave; + } + fp = NULL; + /* No need to revisit the linked list because the found EK is + * not expected to change due to the other syscalls above. */ + xfree (ek->keybuf); + ek->keybuf = blob; + ek->keybuflen = blobsize; + goto leave; + } + + fname0 = fname_from_keygrip (grip, 0); if (!fname0) { @@ -404,7 +541,10 @@ agent_update_private_key (const unsigned char *grip, nvc_t pk) leave: if (blocksigs) gnupg_unblock_all_signals (); - es_fclose (fp); + if (ctrl->ephemeral_mode) + wipe_and_fclose (fp); + else + es_fclose (fp); if (removetmp && fname) gnupg_remove (fname); xfree (fname); @@ -888,17 +1028,17 @@ unprotect (ctrl_t ctrl, const char *cache_nonce, const char *desc_text, * caller must free it. On failure returns an error code and stores * NULL at RESULT and R_KEYMETA. */ static gpg_error_t -read_key_file (const unsigned char *grip, gcry_sexp_t *result, nvc_t *r_keymeta, - char **r_orig_key_value) +read_key_file (ctrl_t ctrl, const unsigned char *grip, + gcry_sexp_t *result, nvc_t *r_keymeta, char **r_orig_key_value) { gpg_error_t err; char *fname; - estream_t fp; - struct stat st; - unsigned char *buf; + estream_t fp = NULL; + unsigned char *buf = NULL; size_t buflen, erroff; - gcry_sexp_t s_skey; + nvc_t pk = NULL; char first; + size_t keybuflen; *result = NULL; if (r_keymeta) @@ -906,19 +1046,42 @@ read_key_file (const unsigned char *grip, gcry_sexp_t *result, nvc_t *r_keymeta, if (r_orig_key_value) *r_orig_key_value = NULL; - fname = fname_from_keygrip (grip, 0); + fname = (ctrl->ephemeral_mode + ? xtrystrdup ("[ephemeral key store]") + : fname_from_keygrip (grip, 0)); if (!fname) { - return gpg_error_from_syserror (); + err = gpg_error_from_syserror (); + goto leave; + } + + if (ctrl->ephemeral_mode) + { + ephemeral_private_key_t ek; + + for (ek = ctrl->ephemeral_keys; ek; ek = ek->next) + if (!memcmp (ek->grip, grip, KEYGRIP_LEN) + && ek->keybuf && ek->keybuflen) + break; + if (!ek || !ek->keybuf || !ek->keybuflen) + { + err = gpg_error (GPG_ERR_ENOENT); + goto leave; + } + keybuflen = ek->keybuflen; + fp = es_fopenmem_init (0, "rb", ek->keybuf, ek->keybuflen); + } + else + { + keybuflen = 0; /* Indicates that this is not ephemeral mode. */ + fp = es_fopen (fname, "rb"); } - fp = es_fopen (fname, "rb"); if (!fp) { err = gpg_error_from_syserror (); if (gpg_err_code (err) != GPG_ERR_ENOENT) log_error ("can't open '%s': %s\n", fname, gpg_strerror (err)); - xfree (fname); - return err; + goto leave; } if (es_fread (&first, 1, 1, fp) != 1) @@ -926,28 +1089,22 @@ read_key_file (const unsigned char *grip, gcry_sexp_t *result, nvc_t *r_keymeta, err = gpg_error_from_syserror (); log_error ("error reading first byte from '%s': %s\n", fname, gpg_strerror (err)); - xfree (fname); - es_fclose (fp); - return err; + goto leave; } if (es_fseek (fp, 0, SEEK_SET)) { err = gpg_error_from_syserror (); log_error ("error seeking in '%s': %s\n", fname, gpg_strerror (err)); - xfree (fname); - es_fclose (fp); - return err; + goto leave; } if (first != '(') { /* Key is in extended format. */ - nvc_t pk = NULL; int line; err = nvc_parse_private_key (&pk, &line, fp); - es_fclose (fp); if (err) log_error ("error parsing '%s' line %d: %s\n", @@ -969,9 +1126,7 @@ read_key_file (const unsigned char *grip, gcry_sexp_t *result, nvc_t *r_keymeta, if (!*r_orig_key_value) { err = gpg_error_from_syserror (); - nvc_release (pk); - xfree (fname); - return err; + goto leave; } } } @@ -979,35 +1134,31 @@ read_key_file (const unsigned char *grip, gcry_sexp_t *result, nvc_t *r_keymeta, } } - if (!err && r_keymeta) - *r_keymeta = pk; - else - nvc_release (pk); - xfree (fname); - return err; + goto leave; /* Ready. */ } - if (fstat (es_fileno (fp), &st)) + if (keybuflen) + buflen = keybuflen; + else { - err = gpg_error_from_syserror (); - log_error ("can't stat '%s': %s\n", fname, gpg_strerror (err)); - xfree (fname); - es_fclose (fp); - return err; + struct stat st; + + if (fstat (es_fileno (fp), &st)) + { + err = gpg_error_from_syserror (); + log_error ("can't stat '%s': %s\n", fname, gpg_strerror (err)); + goto leave; + } + buflen = st.st_size; } - buflen = st.st_size; buf = xtrymalloc (buflen+1); if (!buf) { err = gpg_error_from_syserror (); log_error ("error allocating %zu bytes for '%s': %s\n", buflen, fname, gpg_strerror (err)); - xfree (fname); - es_fclose (fp); - xfree (buf); - return err; - + goto leave; } if (es_fread (buf, buflen, 1, fp) != 1) @@ -1015,25 +1166,32 @@ read_key_file (const unsigned char *grip, gcry_sexp_t *result, nvc_t *r_keymeta, err = gpg_error_from_syserror (); log_error ("error reading %zu bytes from '%s': %s\n", buflen, fname, gpg_strerror (err)); - xfree (fname); - es_fclose (fp); - xfree (buf); - return err; + goto leave; } /* Convert the file into a gcrypt S-expression object. */ - err = gcry_sexp_sscan (&s_skey, &erroff, (char*)buf, buflen); - xfree (fname); - es_fclose (fp); - xfree (buf); - if (err) - { + { + gcry_sexp_t s_skey; + + err = gcry_sexp_sscan (&s_skey, &erroff, (char*)buf, buflen); + if (err) log_error ("failed to build S-Exp (off=%u): %s\n", (unsigned int)erroff, gpg_strerror (err)); - return err; - } - *result = s_skey; - return 0; + else + *result = s_skey; + } + + leave: + if (!err && r_keymeta) + *r_keymeta = pk; + else + nvc_release (pk); + if (ctrl->ephemeral_mode) + wipe_and_fclose (fp); + else + es_fclose (fp); + xfree (fname); + return err; } @@ -1226,7 +1384,8 @@ agent_key_from_file (ctrl_t ctrl, const char *cache_nonce, if (!grip && !ctrl->have_keygrip) return gpg_error (GPG_ERR_NO_SECKEY); - err = read_key_file (grip? grip : ctrl->keygrip, &s_skey, &keymeta, NULL); + err = read_key_file (ctrl, grip? grip : ctrl->keygrip, + &s_skey, &keymeta, NULL); if (err) { if (gpg_err_code (err) == GPG_ERR_ENOENT) @@ -1485,7 +1644,7 @@ agent_raw_key_from_file (ctrl_t ctrl, const unsigned char *grip, *result = NULL; - err = read_key_file (grip, &s_skey, r_keymeta, NULL); + err = read_key_file (ctrl, grip, &s_skey, r_keymeta, NULL); if (!err) *result = s_skey; return err; @@ -1528,7 +1687,7 @@ public_key_from_file (ctrl_t ctrl, const unsigned char *grip, if (r_sshorder) *r_sshorder = 0; - err = read_key_file (grip, &s_skey, for_ssh? &keymeta : NULL, NULL); + err = read_key_file (ctrl, grip, &s_skey, for_ssh? &keymeta : NULL, NULL); if (err) return err; @@ -1656,13 +1815,23 @@ agent_ssh_key_from_file (ctrl_t ctrl, /* Check whether the secret key identified by GRIP is available. - Returns 0 is the key is available. */ + Returns 0 is the key is available. */ int -agent_key_available (const unsigned char *grip) +agent_key_available (ctrl_t ctrl, const unsigned char *grip) { int result; char *fname; char hexgrip[40+4+1]; + ephemeral_private_key_t ek; + + if (ctrl && ctrl->ephemeral_mode) + { + for (ek = ctrl->ephemeral_keys; ek; ek = ek->next) + if (!memcmp (ek->grip, grip, KEYGRIP_LEN) + && ek->keybuf && ek->keybuflen) + return 0; + return -1; + } bin2hex (grip, 20, hexgrip); strcpy (hexgrip+40, ".key"); @@ -1675,7 +1844,6 @@ agent_key_available (const unsigned char *grip) } - /* Return the information about the secret key specified by the binary keygrip GRIP. If the key is a shadowed one the shadow information will be stored at the address R_SHADOW_INFO as an allocated @@ -1700,7 +1868,7 @@ agent_key_info_from_file (ctrl_t ctrl, const unsigned char *grip, { gcry_sexp_t sexp; - err = read_key_file (grip, &sexp, NULL, NULL); + err = read_key_file (ctrl, grip, &sexp, NULL, NULL); if (err) { if (gpg_err_code (err) == GPG_ERR_ENOENT) @@ -1784,7 +1952,13 @@ agent_delete_key (ctrl_t ctrl, const char *desc_text, char *default_desc = NULL; int key_type; - err = read_key_file (grip, &s_skey, NULL, NULL); + if (ctrl->ephemeral_mode) + { + err = gpg_error (GPG_ERR_NO_SECKEY); + goto leave; + } + + err = read_key_file (ctrl, grip, &s_skey, NULL, NULL); if (gpg_err_code (err) == GPG_ERR_ENOENT) err = gpg_error (GPG_ERR_NO_SECKEY); if (err) @@ -1885,7 +2059,7 @@ agent_delete_key (ctrl_t ctrl, const char *desc_text, card's SERIALNO and the IDSTRING. With FORCE passed as true an existing key with the given GRIP will get overwritten. */ gpg_error_t -agent_write_shadow_key (const unsigned char *grip, +agent_write_shadow_key (ctrl_t ctrl, const unsigned char *grip, const char *serialno, const char *keyid, const unsigned char *pkbuf, int force, const char *dispserialno) @@ -1915,7 +2089,7 @@ agent_write_shadow_key (const unsigned char *grip, } len = gcry_sexp_canon_len (shdkey, 0, NULL, NULL); - err = agent_write_private_key (grip, shdkey, len, force, + err = agent_write_private_key (ctrl, grip, shdkey, len, force, serialno, keyid, dispserialno, 0); xfree (shdkey); if (err) diff --git a/agent/genkey.c b/agent/genkey.c index 741c05f4f..444f89f79 100644 --- a/agent/genkey.c +++ b/agent/genkey.c @@ -30,14 +30,36 @@ #include "../common/exechelp.h" #include "../common/sysutils.h" -static int -store_key (gcry_sexp_t private, const char *passphrase, int force, + +void +clear_ephemeral_keys (ctrl_t ctrl) +{ + while (ctrl->ephemeral_keys) + { + ephemeral_private_key_t next = ctrl->ephemeral_keys->next; + if (ctrl->ephemeral_keys->keybuf) + { + wipememory (ctrl->ephemeral_keys->keybuf, + ctrl->ephemeral_keys->keybuflen); + xfree (ctrl->ephemeral_keys->keybuf); + } + xfree (ctrl->ephemeral_keys); + ctrl->ephemeral_keys = next; + } +} + + +/* Store the key either to a file, or in ctrl->ephemeral_mode in the + * session data. */ +static gpg_error_t +store_key (ctrl_t ctrl, gcry_sexp_t private, + const char *passphrase, int force, unsigned long s2k_count, time_t timestamp) { - int rc; + gpg_error_t err; unsigned char *buf; size_t len; - unsigned char grip[20]; + unsigned char grip[KEYGRIP_LEN]; if ( !gcry_pk_get_keygrip (private, grip) ) { @@ -49,7 +71,10 @@ store_key (gcry_sexp_t private, const char *passphrase, int force, log_assert (len); buf = gcry_malloc_secure (len); if (!buf) - return out_of_core (); + { + err = gpg_error_from_syserror (); + goto leave; + } len = gcry_sexp_sprint (private, GCRYSEXP_FMT_CANON, buf, len); log_assert (len); @@ -57,20 +82,56 @@ store_key (gcry_sexp_t private, const char *passphrase, int force, { unsigned char *p; - rc = agent_protect (buf, passphrase, &p, &len, s2k_count); - if (rc) - { - xfree (buf); - return rc; - } + err = agent_protect (buf, passphrase, &p, &len, s2k_count); + if (err) + goto leave; xfree (buf); buf = p; } - rc = agent_write_private_key (grip, buf, len, force, - NULL, NULL, NULL, timestamp); + if (ctrl->ephemeral_mode) + { + ephemeral_private_key_t ek; + + for (ek = ctrl->ephemeral_keys; ek; ek = ek->next) + if (!memcmp (ek->grip, grip, KEYGRIP_LEN)) + break; + if (!ek) + { + ek = xtrycalloc (1, sizeof *ek); + if (!ek) + { + err = gpg_error_from_syserror (); + goto leave; + } + memcpy (ek->grip, grip, KEYGRIP_LEN); + ek->next = ctrl->ephemeral_keys; + ctrl->ephemeral_keys = ek; + } + if (ek->keybuf) + { + wipememory (ek->keybuf, ek->keybuflen); + xfree (ek->keybuf); + } + ek->keybuf = buf; + buf = NULL; + ek->keybuflen = len; + } + else + err = agent_write_private_key (ctrl, grip, buf, len, force, + NULL, NULL, NULL, timestamp); + + if (!err) + { + char hexgrip[2*KEYGRIP_LEN+1]; + + bin2hex (grip, KEYGRIP_LEN, hexgrip); + agent_write_status (ctrl, "KEYGRIP", hexgrip, NULL); + } + + leave: xfree (buf); - return rc; + return err; } @@ -450,16 +511,19 @@ agent_ask_new_passphrase (ctrl_t ctrl, const char *prompt, /* Generate a new keypair according to the parameters given in - KEYPARAM. If CACHE_NONCE is given first try to lookup a passphrase - using the cache nonce. If NO_PROTECTION is true the key will not - be protected by a passphrase. If OVERRIDE_PASSPHRASE is true that - passphrase will be used for the new key. If TIMESTAMP is not zero - it will be recorded as creation date of the key (unless extended - format is disabled) . */ + * KEYPARAM. If CACHE_NONCE is given first try to lookup a passphrase + * using the cache nonce. If NO_PROTECTION is true the key will not + * be protected by a passphrase. If OVERRIDE_PASSPHRASE is true that + * passphrase will be used for the new key. If TIMESTAMP is not zero + * it will be recorded as creation date of the key (unless extended + * format is disabled). In ctrl_ephemeral_mode the key is stored in + * the session data and an identifier is returned using a status + * line. */ int -agent_genkey (ctrl_t ctrl, const char *cache_nonce, time_t timestamp, - const char *keyparam, size_t keyparamlen, int no_protection, - const char *override_passphrase, int preset, membuf_t *outbuf) +agent_genkey (ctrl_t ctrl, unsigned int flags, + const char *cache_nonce, time_t timestamp, + const char *keyparam, size_t keyparamlen, + const char *override_passphrase, membuf_t *outbuf) { gcry_sexp_t s_keyparam, s_key, s_private, s_public; char *passphrase_buffer = NULL; @@ -478,7 +542,7 @@ agent_genkey (ctrl_t ctrl, const char *cache_nonce, time_t timestamp, /* Get the passphrase now, cause key generation may take a while. */ if (override_passphrase) passphrase = override_passphrase; - else if (no_protection || !cache_nonce) + else if ((flags & GENKEY_FLAG_NO_PROTECTION) || !cache_nonce) passphrase = NULL; else { @@ -486,8 +550,8 @@ agent_genkey (ctrl_t ctrl, const char *cache_nonce, time_t timestamp, passphrase = passphrase_buffer; } - if (passphrase || no_protection) - ; + if (passphrase || (flags & GENKEY_FLAG_NO_PROTECTION)) + ; /* No need to ask for a passphrase. */ else { rc = agent_ask_new_passphrase (ctrl, @@ -532,11 +596,14 @@ agent_genkey (ctrl_t ctrl, const char *cache_nonce, time_t timestamp, gcry_sexp_release (s_key); s_key = NULL; /* store the secret key */ - if (DBG_CRYPTO) - log_debug ("storing private key\n"); - rc = store_key (s_private, passphrase, 0, ctrl->s2k_count, timestamp); - if (!rc) + if (opt.verbose) + log_info ("storing %sprivate key\n", + ctrl->ephemeral_mode?"ephemeral ":""); + rc = store_key (ctrl, s_private, passphrase, 0, ctrl->s2k_count, timestamp); + if (!rc && !ctrl->ephemeral_mode) { + /* FIXME: or does it make sense to also cache passphrases in + * ephemeral mode using a dedicated cache? */ if (!cache_nonce) { char tmpbuf[12]; @@ -544,21 +611,23 @@ agent_genkey (ctrl_t ctrl, const char *cache_nonce, time_t timestamp, cache_nonce = bin2hex (tmpbuf, 12, NULL); } if (cache_nonce - && !no_protection + && !(flags & GENKEY_FLAG_NO_PROTECTION) && !agent_put_cache (ctrl, cache_nonce, CACHE_MODE_NONCE, passphrase, ctrl->cache_ttl_opt_preset)) agent_write_status (ctrl, "CACHE_NONCE", cache_nonce, NULL); - if (preset && !no_protection) - { - unsigned char grip[20]; - char hexgrip[40+1]; - if (gcry_pk_get_keygrip (s_private, grip)) - { - bin2hex(grip, 20, hexgrip); - rc = agent_put_cache (ctrl, hexgrip, CACHE_MODE_ANY, passphrase, + if ((flags & GENKEY_FLAG_PRESET) + && !(flags & GENKEY_FLAG_NO_PROTECTION)) + { + unsigned char grip[20]; + char hexgrip[40+1]; + if (gcry_pk_get_keygrip (s_private, grip)) + { + bin2hex(grip, 20, hexgrip); + rc = agent_put_cache (ctrl, hexgrip, + CACHE_MODE_ANY, passphrase, ctrl->cache_ttl_opt_preset); - } - } + } + } } xfree (passphrase_buffer); passphrase_buffer = NULL; @@ -607,7 +676,8 @@ agent_protect_and_store (ctrl_t ctrl, gcry_sexp_t s_skey, if (passphrase_addr && *passphrase_addr) { /* Take an empty string as request not to protect the key. */ - err = store_key (s_skey, **passphrase_addr? *passphrase_addr:NULL, 1, + err = store_key (ctrl, s_skey, + **passphrase_addr? *passphrase_addr:NULL, 1, ctrl->s2k_count, 0); } else @@ -623,7 +693,7 @@ agent_protect_and_store (ctrl_t ctrl, gcry_sexp_t s_skey, L_("Please enter the new passphrase"), &pass); if (!err) - err = store_key (s_skey, pass, 1, ctrl->s2k_count, 0); + err = store_key (ctrl, s_skey, pass, 1, ctrl->s2k_count, 0); if (!err && passphrase_addr) *passphrase_addr = pass; else diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c index b0150031d..a5448ac38 100644 --- a/agent/gpg-agent.c +++ b/agent/gpg-agent.c @@ -1989,6 +1989,7 @@ agent_deinit_default_ctrl (ctrl_t ctrl) { unregister_progress_cb (); session_env_release (ctrl->session_env); + clear_ephemeral_keys (ctrl); xfree (ctrl->digest.data); ctrl->digest.data = NULL; diff --git a/agent/learncard.c b/agent/learncard.c index 8d80b809d..83945b8be 100644 --- a/agent/learncard.c +++ b/agent/learncard.c @@ -397,7 +397,7 @@ agent_handle_learn (ctrl_t ctrl, int send, void *assuan_context, int force) for (p=item->hexgrip, i=0; i < 20; p += 2, i++) grip[i] = xtoi_2 (p); - if (!force && !agent_key_available (grip)) + if (!force && !agent_key_available (ctrl, grip)) continue; /* The key is already available. */ /* Unknown key - store it. */ @@ -408,15 +408,17 @@ agent_handle_learn (ctrl_t ctrl, int send, void *assuan_context, int force) goto leave; } - { - char *dispserialno; + if (!ctrl->ephemeral_mode) + { + char *dispserialno; - agent_card_getattr (ctrl, "$DISPSERIALNO", &dispserialno, - item->hexgrip); - rc = agent_write_shadow_key (grip, serialno, item->id, pubkey, force, - dispserialno); - xfree (dispserialno); - } + agent_card_getattr (ctrl, "$DISPSERIALNO", &dispserialno, + item->hexgrip); + rc = agent_write_shadow_key (ctrl, + grip, serialno, item->id, pubkey, force, + dispserialno); + xfree (dispserialno); + } xfree (pubkey); if (rc) goto leave; diff --git a/agent/pksign.c b/agent/pksign.c index a7b5c579f..322918969 100644 --- a/agent/pksign.c +++ b/agent/pksign.c @@ -371,13 +371,14 @@ agent_pksign_do (ctrl_t ctrl, const char *cache_nonce, goto leave; } - if (keyref) + if (keyref && !ctrl->ephemeral_mode) { char *dispserialno; agent_card_getattr (ctrl, "$DISPSERIALNO", &dispserialno, hexgrip); - agent_write_shadow_key (ctrl->keygrip, serialno, keyref, pkbuf, + agent_write_shadow_key (ctrl, + ctrl->keygrip, serialno, keyref, pkbuf, 0, dispserialno); xfree (dispserialno); } diff --git a/agent/protect-tool.c b/agent/protect-tool.c index 17f6fd559..c6450a20e 100644 --- a/agent/protect-tool.c +++ b/agent/protect-tool.c @@ -755,8 +755,9 @@ release_passphrase (char *pw) /* Stub function. */ int -agent_key_available (const unsigned char *grip) +agent_key_available (ctrl_t ctrl, const unsigned char *grip) { + (void)ctrl; (void)grip; return -1; /* Not available. */ } @@ -814,7 +815,7 @@ agent_askpin (ctrl_t ctrl, /* Replacement for the function in findkey.c. Here we write the key * to stdout. */ gpg_error_t -agent_write_private_key (const unsigned char *grip, +agent_write_private_key (ctrl_t ctrl, const unsigned char *grip, const void *buffer, size_t length, int force, const char *serialno, const char *keyref, const char *dispserialno, time_t timestamp) @@ -822,6 +823,7 @@ agent_write_private_key (const unsigned char *grip, char hexgrip[40+4+1]; char *p; + (void)ctrl; (void)force; (void)serialno; (void)keyref;