1
0
mirror of git://git.gnupg.org/gnupg.git synced 2024-12-22 10:19:57 +01:00

agent: Add "ephemeral" Assuan option.

* agent/agent.h (struct ephemeral_private_key_s): New.
(struct server_control_s): Add ephemeral_mode and ephemeral_keys.
(GENKEY_FLAG_NO_PROTECTION, GENKEY_FLAG_PRESET): New.
* agent/genkey.c (clear_ephemeral_keys): New.
(store_key): Add arg ctrl and implement ephemeral_mode.  Change all
callers.
(agent_genkey): Replace args no_protection and preset by a generic new
flags arg.
* agent/findkey.c (wipe_and_fclose): New.
(agent_write_private_key): Add arg ctrl and implement ephemeral_mode.
Change all callers.
(agent_update_private_key): Ditto
(read_key_file): Ditto.
(agent_key_available): Ditto.
* agent/command-ssh.c (card_key_available): Do not update display s/n
in ephemeral mode.  This is however enver triggred.
* agent/gpg-agent.c (agent_deinit_default_ctrl): Cleanup ephemeral
keys.
* agent/command.c (cmd_genkey): Use the new flags instead of separate
vars.
(cmd_readkey): Create a shadow key only in non-ephemeral_mode.
(cmd_getinfo): Add sub-command "ephemeral".
(option_handler): Add option "ephemeral".
--

The idea here that a session can be switched in an ephemeral mode
which does not store or read keys from disk but keeps them local to
the session.

GnuPG-bug-id: 6944
This commit is contained in:
Werner Koch 2024-01-22 13:22:44 +01:00
parent 18320d692c
commit 434a641d40
No known key found for this signature in database
GPG Key ID: E3FDFF218E45B72B
11 changed files with 496 additions and 206 deletions

View File

@ -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);

View File

@ -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;

View File

@ -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] <hexstring_with_keygrip>\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=<nonce>] [--openpgp|--mode1003] <hexkeygrip>\n"
"EXPORT_KEY [--cache-nonce=<nonce>] \\\n"
" [--openpgp|--mode1003] <hexkeygrip>\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);

View File

@ -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);

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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);
}

View File

@ -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;