agent: Create and use Token entries to track the display s/n.

* agent/divert-scd.c (linefeed_to_percent0A): New.
(ask_for_card): Add arg grip.  Read Token and Label items and use
them.
(divert_pksign, divert_pkdecrypt): Pass down grip.
* agent/findkey.c (write_extended_private_key): Add args serialno,
keyref, and dispserialno.  Writen Token item.
(agent_write_private_key): Add args serialno, keyref, and
dispserialno.
(read_key_file): Add arg r_keymeta.
(agent_keymeta_from_file): New.
(agent_write_shadow_key): Remove leading spaces from serialno and keyid.
* agent/protect-tool.c (agent_write_private_key): Ditto.
* agent/learncard.c (agent_handle_learn): Get DISPSERIALNO and pass to
agent_write_shadow_key.
* agent/command-ssh.c (card_key_available): Ditto.
--

GnuPG-bug-id: 6135

This patch backports some changes from master but also adds the
Display-S/N tracking.
This commit is contained in:
Werner Koch 2022-08-15 12:49:56 +02:00
parent 706adf6691
commit dc9b242628
No known key found for this signature in database
GPG Key ID: E3FDFF218E45B72B
10 changed files with 272 additions and 36 deletions

View File

@ -36,6 +36,7 @@
#include "../common/sysutils.h" /* (gnupg_fd_t) */
#include "../common/session-env.h"
#include "../common/shareddefs.h"
#include "../common/name-value.h"
/* To convey some special hash algorithms we use algorithm numbers
reserved for application use. */
@ -429,7 +430,9 @@ gpg_error_t agent_modify_description (const char *in, const char *comment,
const gcry_sexp_t key, char **result);
int agent_write_private_key (const unsigned char *grip,
const void *buffer, size_t length, int force,
time_t timestamp);
time_t timestamp,
const char *serialno, const char *keyref,
const char *dispserialno);
gpg_error_t agent_key_from_file (ctrl_t ctrl,
const char *cache_nonce,
const char *desc_text,
@ -441,6 +444,8 @@ gpg_error_t agent_key_from_file (ctrl_t ctrl,
char **r_passphrase);
gpg_error_t agent_raw_key_from_file (ctrl_t ctrl, const unsigned char *grip,
gcry_sexp_t *result);
gpg_error_t agent_keymeta_from_file (ctrl_t ctrl, const unsigned char *grip,
nvc_t *r_keymeta);
gpg_error_t agent_public_key_from_file (ctrl_t ctrl,
const unsigned char *grip,
gcry_sexp_t *result);
@ -549,7 +554,8 @@ gpg_error_t s2k_hash_passphrase (const char *passphrase, int hashalgo,
unsigned char *key, size_t keylen);
gpg_error_t agent_write_shadow_key (const unsigned char *grip,
const char *serialno, const char *keyid,
const unsigned char *pkbuf, int force);
const unsigned char *pkbuf, int force,
const char *dispserialno);
/*-- trustlist.c --*/

View File

@ -2495,8 +2495,13 @@ card_key_available (ctrl_t ctrl, gcry_sexp_t *r_pk, char **cardsn)
if ( agent_key_available (grip) )
{
char *dispserialno;
/* (Shadow)-key is not available in our key storage. */
err = agent_write_shadow_key (grip, serialno, authkeyid, pkbuf, 0);
agent_card_getattr (ctrl, "$DISPSERIALNO", &dispserialno);
err = agent_write_shadow_key (grip, serialno, authkeyid, pkbuf, 0,
dispserialno);
xfree (dispserialno);
if (err)
{
xfree (pkbuf);
@ -3154,7 +3159,8 @@ 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, 0);
err = agent_write_private_key (key_grip_raw, buffer, buffer_n, 0, 0,
NULL, NULL, NULL);
if (err)
goto out;

View File

@ -1040,7 +1040,12 @@ cmd_readkey (assuan_context_t ctx, char *line)
if (agent_key_available (grip))
{
/* (Shadow)-key is not available in our key storage. */
rc = agent_write_shadow_key (grip, serialno, keyid, pkbuf, 0);
char *dispserialno;
agent_card_getattr (ctrl, "$DISPSERIALNO", &dispserialno);
rc = agent_write_shadow_key (grip, serialno, keyid, pkbuf, 0,
dispserialno);
xfree (dispserialno);
if (rc)
goto leave;
}
@ -2425,11 +2430,11 @@ cmd_import_key (assuan_context_t ctx, char *line)
ctrl->s2k_count, -1);
if (!err)
err = agent_write_private_key (grip, finalkey, finalkeylen, force,
opt_timestamp);
opt_timestamp, NULL, NULL, NULL);
}
else
err = agent_write_private_key (grip, key, realkeylen, force,
opt_timestamp);
opt_timestamp, NULL, NULL, NULL);
leave:
gcry_sexp_release (openpgp_sexp);

View File

@ -1069,7 +1069,8 @@ convert_from_openpgp_native (ctrl_t ctrl,
if (!agent_protect (*r_key, passphrase,
&protectedkey, &protectedkeylen,
ctrl->s2k_count, -1))
agent_write_private_key (grip, protectedkey, protectedkeylen, 1, 0);
agent_write_private_key (grip, protectedkey, protectedkeylen,
1, 0, NULL, NULL, NULL);
xfree (protectedkey);
}
else
@ -1078,7 +1079,7 @@ convert_from_openpgp_native (ctrl_t ctrl,
agent_write_private_key (grip,
*r_key,
gcry_sexp_canon_len (*r_key, 0, NULL,NULL),
1, 0);
1, 0, NULL, NULL, NULL);
}
}

View File

@ -31,16 +31,51 @@
#include "../common/i18n.h"
#include "../common/sexp-parse.h"
/* Replace all linefeeds in STRING by "%0A" and return a new malloced
* string. May return NULL on memory error. */
static char *
linefeed_to_percent0A (const char *string)
{
const char *s;
size_t n;
char *buf, *p;
for (n=0, s=string; *s; s++)
if (*s == '\n')
n += 3;
else
n++;
p = buf = xtrymalloc (n+1);
if (!buf)
return NULL;
for (s=string; *s; s++)
if (*s == '\n')
{
memcpy (p, "%0A", 3);
p += 3;
}
else
*p++ = *s;
*p = 0;
return buf;
}
/* Ask for the card using SHADOW_INFO. If GRIP is not NULL, the
* function also tries to find additional information from the shadow
* key file. */
static int
ask_for_card (ctrl_t ctrl, const unsigned char *shadow_info, char **r_kid)
ask_for_card (ctrl_t ctrl, const unsigned char *shadow_info,
const unsigned char *grip, char **r_kid)
{
int rc, i;
char *serialno;
int no_card = 0;
char *desc;
char *want_sn, *want_kid, *want_sn_disp;
int got_sn_disp_from_meta = 0;
int len;
char *comment = NULL;
*r_kid = NULL;
@ -53,11 +88,62 @@ ask_for_card (ctrl_t ctrl, const unsigned char *shadow_info, char **r_kid)
rc = gpg_error_from_syserror ();
xfree (want_sn);
xfree (want_kid);
xfree (comment);
return rc;
}
if (grip)
{
nvc_t keymeta;
const char *s;
size_t snlen;
nve_t item;
char **tokenfields = NULL;
rc = agent_keymeta_from_file (ctrl, grip, &keymeta);
if (!rc)
{
snlen = strlen (want_sn);
s = NULL;
for (item = nvc_lookup (keymeta, "Token:");
item;
item = nve_next_value (item, "Token:"))
if ((s = nve_value (item)) && !strncmp (s, want_sn, snlen))
break;
if (s && (tokenfields = strtokenize (s, " \t\n")))
{
if (tokenfields[0] && tokenfields[1] && tokenfields[2]
&& tokenfields[3] && strlen (tokenfields[3]) > 1)
{
xfree (want_sn_disp);
want_sn_disp = percent_plus_unescape (tokenfields[3], 0xff);
if (!want_sn_disp)
{
rc = gpg_error_from_syserror ();
xfree (tokenfields);
nvc_release (keymeta);
xfree (want_sn);
xfree (want_kid);
xfree (comment);
return rc;
}
got_sn_disp_from_meta = 1;
}
xfree (tokenfields);
}
if ((s = nvc_get_string (keymeta, "Label:")))
comment = linefeed_to_percent0A (s);
nvc_release (keymeta);
}
}
len = strlen (want_sn_disp);
if (len == 32 && !strncmp (want_sn_disp, "D27600012401", 12))
if (got_sn_disp_from_meta)
; /* We got the the display S/N from the key file. */
else if (len == 32 && !strncmp (want_sn_disp, "D27600012401", 12))
{
/* This is an OpenPGP card - reformat */
memmove (want_sn_disp, want_sn_disp+16, 4);
@ -87,6 +173,7 @@ ask_for_card (ctrl_t ctrl, const unsigned char *shadow_info, char **r_kid)
{
xfree (want_sn_disp);
xfree (want_sn);
xfree (comment);
*r_kid = want_kid;
return 0; /* yes, we have the correct card */
}
@ -112,12 +199,13 @@ ask_for_card (ctrl_t ctrl, const unsigned char *shadow_info, char **r_kid)
{
if (asprintf (&desc,
"%s:%%0A%%0A"
" %s%%0A"
" %s",
no_card
? L_("Please insert the card with serial number")
: L_("Please remove the current card and "
"insert the one with serial number"),
want_sn_disp) < 0)
want_sn_disp, comment? comment:"") < 0)
{
rc = out_of_core ();
}
@ -136,6 +224,7 @@ ask_for_card (ctrl_t ctrl, const unsigned char *shadow_info, char **r_kid)
xfree (want_sn_disp);
xfree (want_sn);
xfree (want_kid);
xfree (comment);
return rc;
}
}
@ -447,7 +536,7 @@ divert_pksign (ctrl_t ctrl, const char *desc_text,
(void)desc_text;
rc = ask_for_card (ctrl, shadow_info, &kid);
rc = ask_for_card (ctrl, shadow_info, grip, &kid);
if (rc)
return rc;
@ -599,7 +688,7 @@ divert_pkdecrypt (ctrl_t ctrl, const char *desc_text,
ciphertext = s;
ciphertextlen = n;
rc = ask_for_card (ctrl, shadow_info, &kid);
rc = ask_for_card (ctrl, shadow_info, grip, &kid);
if (rc)
return rc;

View File

@ -55,12 +55,16 @@ struct try_unprotect_arg_s
/* Note: Ownership of FNAME and FP are moved to this function. */
static gpg_error_t
write_extended_private_key (char *fname, estream_t fp, int update, int newkey,
const void *buf, size_t len, time_t timestamp)
const void *buf, size_t len, time_t timestamp,
const char *serialno, const char *keyref,
const char *dispserialno)
{
gpg_error_t err;
nvc_t pk = NULL;
gcry_sexp_t key = NULL;
int remove = 0;
char *token0 = NULL;
char *token = NULL;
if (update)
{
@ -106,6 +110,59 @@ write_extended_private_key (char *fname, estream_t fp, int update, int newkey,
goto leave;
}
/* If requested write a Token line. */
if (serialno && keyref)
{
nve_t item;
const char *s;
size_t token0len;
char *tmpdistsn = NULL;
if (dispserialno)
{
tmpdistsn = percent_plus_escape (dispserialno);
if (!tmpdistsn)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
token0 = strconcat (serialno, " ", keyref, NULL);
if (token0)
token = strconcat (token0, " - ", tmpdistsn? tmpdistsn:"-", NULL);
xfree (tmpdistsn);
if (!token0 || !token)
{
err = gpg_error_from_syserror ();
goto leave;
}
token0len = strlen (token0);
for (item = nvc_lookup (pk, "Token:");
item;
item = nve_next_value (item, "Token:"))
if ((s = nve_value (item)) && !strncmp (s, token0, token0len))
break;
if (!item)
{
/* No token or no token with that value exists. Add a new
* one so that keys which have been stored on several cards
* are well supported. */
err = nvc_add (pk, "Token:", token);
if (err)
goto leave;
}
else
{
/* Token exists: Update the display s/n. It may have
* changed due to changes in a newer software version. */
err = nve_set (item, token);
if (err)
goto leave;
}
}
err = es_fseek (fp, 0, SEEK_SET);
if (err)
goto leave;
@ -145,6 +202,8 @@ write_extended_private_key (char *fname, estream_t fp, int update, int newkey,
if (remove)
gnupg_remove (fname);
xfree (fname);
xfree (token);
xfree (token0);
gcry_sexp_release (key);
nvc_release (pk);
return err;
@ -153,11 +212,14 @@ write_extended_private_key (char *fname, estream_t fp, int update, int newkey,
/* Write an S-expression formatted key to our key storage. With FORCE
* passed as true an existing key with the given GRIP will get
* overwritten. If TIMESTAMP is not zero and the key does not yet
* exists it will be recorded as creation date. */
* exists it will be recorded as creation date. If SERIALNO, KEYREF,
* of DISPSERIALNO are not NULL they will be recorded as well. */
int
agent_write_private_key (const unsigned char *grip,
const void *buffer, size_t length,
int force, time_t timestamp)
int force, time_t timestamp,
const char *serialno, const char *keyref,
const char *dispserialno)
{
char *fname;
estream_t fp;
@ -226,19 +288,22 @@ agent_write_private_key (const unsigned char *grip,
{
/* Key is already in the extended format. */
return write_extended_private_key (fname, fp, 1, 0, buffer, length,
timestamp);
timestamp, serialno, keyref,
dispserialno);
}
if (first == '(' && opt.enable_extended_key_format)
{
/* Key is in the old format - but we want the extended format. */
return write_extended_private_key (fname, fp, 0, 0, buffer, length,
timestamp);
timestamp, serialno, keyref,
dispserialno);
}
}
if (opt.enable_extended_key_format)
return write_extended_private_key (fname, fp, 0, 1, buffer, length,
timestamp);
timestamp, serialno, keyref,
dispserialno);
if (es_fwrite (buffer, length, 1, fp) != 1)
{
@ -715,10 +780,12 @@ unprotect (ctrl_t ctrl, const char *cache_nonce, const char *desc_text,
/* Read the key identified by GRIP from the private key directory and
return it as an gcrypt S-expression object in RESULT. On failure
returns an error code and stores NULL at RESULT. */
* return it as an gcrypt S-expression object in RESULT. If R_KEYMETA
* is not NULl and the extended key format is used, the meta data
* items are stored there. However the "Key:" item is removed from
* it. On failure returns an error code and stores NULL at RESULT. */
static gpg_error_t
read_key_file (const unsigned char *grip, gcry_sexp_t *result)
read_key_file (const unsigned char *grip, gcry_sexp_t *result, nvc_t *r_keymeta)
{
gpg_error_t err;
char *fname;
@ -731,6 +798,8 @@ read_key_file (const unsigned char *grip, gcry_sexp_t *result)
char first;
*result = NULL;
if (r_keymeta)
*r_keymeta = NULL;
bin2hex (grip, 20, hexgrip);
strcpy (hexgrip+40, ".key");
@ -781,12 +850,17 @@ read_key_file (const unsigned char *grip, gcry_sexp_t *result)
else
{
err = nvc_get_private_key (pk, result);
nvc_release (pk);
if (err)
log_error ("error getting private key from '%s': %s\n",
fname, gpg_strerror (err));
else
nvc_delete_named (pk, "Key:");
}
if (!err && r_keymeta)
*r_keymeta = pk;
else
nvc_release (pk);
xfree (fname);
return err;
}
@ -893,7 +967,7 @@ agent_key_from_file (ctrl_t ctrl, const char *cache_nonce,
if (r_passphrase)
*r_passphrase = NULL;
err = read_key_file (grip, &s_skey);
err = read_key_file (grip, &s_skey, NULL);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_ENOENT)
@ -1223,13 +1297,28 @@ agent_raw_key_from_file (ctrl_t ctrl, const unsigned char *grip,
*result = NULL;
err = read_key_file (grip, &s_skey);
err = read_key_file (grip, &s_skey, NULL);
if (!err)
*result = s_skey;
return err;
}
gpg_error_t
agent_keymeta_from_file (ctrl_t ctrl, const unsigned char *grip,
nvc_t *r_keymeta)
{
gpg_error_t err;
gcry_sexp_t s_skey;
(void)ctrl;
err = read_key_file (grip, &s_skey, r_keymeta);
gcry_sexp_release (s_skey);
return err;
}
/* Return the public key for the keygrip GRIP. The result is stored
at RESULT. This function extracts the public key from the private
key database. On failure an error code is returned and NULL stored
@ -1262,7 +1351,7 @@ agent_public_key_from_file (ctrl_t ctrl,
*result = NULL;
err = read_key_file (grip, &s_skey);
err = read_key_file (grip, &s_skey, NULL);
if (err)
return err;
@ -1409,7 +1498,7 @@ agent_key_info_from_file (ctrl_t ctrl, const unsigned char *grip,
{
gcry_sexp_t sexp;
err = read_key_file (grip, &sexp);
err = read_key_file (grip, &sexp, NULL);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_ENOENT)
@ -1493,7 +1582,7 @@ agent_delete_key (ctrl_t ctrl, const char *desc_text,
char *default_desc = NULL;
int key_type;
err = read_key_file (grip, &s_skey);
err = read_key_file (grip, &s_skey, NULL);
if (gpg_err_code (err) == GPG_ERR_ENOENT)
err = gpg_error (GPG_ERR_NO_SECKEY);
if (err)
@ -1592,17 +1681,27 @@ agent_delete_key (ctrl_t ctrl, const char *desc_text,
/* Write an S-expression formatted shadow key to our key storage.
Shadow key is created by an S-expression public key in PKBUF and
card's SERIALNO and the IDSTRING. With FORCE passed as true an
existing key with the given GRIP will get overwritten. */
existing key with the given GRIP will get overwritten. If
DISPSERIALNO is not NULL the human readable s/n will also be
recorded in the key file. */
gpg_error_t
agent_write_shadow_key (const unsigned char *grip,
const char *serialno, const char *keyid,
const unsigned char *pkbuf, int force)
const unsigned char *pkbuf, int force,
const char *dispserialno)
{
gpg_error_t err;
unsigned char *shadow_info;
unsigned char *shdkey;
size_t len;
/* Just in case some caller did not parse the stuff correctly, skip
* leading spaces. */
while (spacep (serialno))
serialno++;
while (spacep (keyid))
keyid++;
shadow_info = make_shadow_info (serialno, keyid);
if (!shadow_info)
return gpg_error_from_syserror ();
@ -1616,7 +1715,8 @@ 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, 0);
err = agent_write_private_key (grip, shdkey, len, force, 0,
serialno, keyid, dispserialno);
xfree (shdkey);
if (err)
log_error ("error writing key: %s\n", gpg_strerror (err));

View File

@ -69,7 +69,8 @@ store_key (gcry_sexp_t private, const char *passphrase, int force,
buf = p;
}
rc = agent_write_private_key (grip, buf, len, force, timestamp);
rc = agent_write_private_key (grip, buf, len, force, timestamp,
NULL, NULL, NULL);
xfree (buf);
return rc;
}

View File

@ -78,7 +78,7 @@ of a continuation line encodes a newline.
Lines containing only whitespace, and lines starting with whitespace
followed by '#' are considered to be comments and are ignored.
** Well defined names
** Well known names
*** Description
This is a human readable string describing the key.
@ -108,6 +108,22 @@ This takes a base64 encoded string wrapped so that this
key file can be easily edited with a standard editor. Several of such
items can be used.
*** Token
If such an item exists it overrides the info given by the "shadow"
parameter in the S-expression. Using this item makes it possible to
describe a key which is stored on several tokens and also makes it
easy to update this info using a standard editor. The syntax is
similar to the "shadow" parameter:
- Serialnumber of the token.
- Key reference from the token in full format (e.g. "OpenPGP.2").
- An optional fixed length of the PIN or "-".
- The human readable serial number of a card. This is usually what is
printed on the actual card. This value is taken directly from the
card but when asking to insert a card it is useful to have this
value available. GnuPG takes care of creating and possibly updating
this entry. This is percent-plus-escaped.
*** Use-for-ssh
If given and the value is "yes" or "1" the key is allowed for use by
gpg-agent's ssh-agent implementation. This is thus the same as

View File

@ -409,7 +409,14 @@ agent_handle_learn (ctrl_t ctrl, int send, void *assuan_context, int force)
goto leave;
}
rc = agent_write_shadow_key (grip, serialno, item->id, pubkey, force);
{
char *dispserialno;
agent_card_getattr (ctrl, "$DISPSERIALNO", &dispserialno);
rc = agent_write_shadow_key (grip, serialno, item->id, pubkey, force,
dispserialno);
xfree (dispserialno);
}
xfree (pubkey);
if (rc)
goto leave;

View File

@ -810,13 +810,18 @@ agent_askpin (ctrl_t ctrl,
int
agent_write_private_key (const unsigned char *grip,
const void *buffer, size_t length, int force,
time_t timestamp)
time_t timestamp,
const char *serialno, const char *keyref,
const char *dispserialno)
{
char hexgrip[40+4+1];
char *p;
(void)force;
(void)timestamp;
(void)serialno;
(void)keyref;
(void)dispserialno;
bin2hex (grip, 20, hexgrip);
strcpy (hexgrip+40, ".key");