1
0
Fork 0
mirror of git://git.gnupg.org/gnupg.git synced 2025-07-03 22:56:33 +02: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

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