From dc9b2426288e4eb6ab42aa7f731a35bc8d383b46 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 15 Aug 2022 12:49:56 +0200 Subject: [PATCH] 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. --- agent/agent.h | 10 +++- agent/command-ssh.c | 10 +++- agent/command.c | 11 +++- agent/cvt-openpgp.c | 5 +- agent/divert-scd.c | 99 +++++++++++++++++++++++++++++-- agent/findkey.c | 136 +++++++++++++++++++++++++++++++++++++------ agent/genkey.c | 3 +- agent/keyformat.txt | 18 +++++- agent/learncard.c | 9 ++- agent/protect-tool.c | 7 ++- 10 files changed, 272 insertions(+), 36 deletions(-) diff --git a/agent/agent.h b/agent/agent.h index 09ccc90c1..d0e8619f2 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -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 --*/ diff --git a/agent/command-ssh.c b/agent/command-ssh.c index 6836fb2e9..dee9ee3df 100644 --- a/agent/command-ssh.c +++ b/agent/command-ssh.c @@ -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; diff --git a/agent/command.c b/agent/command.c index 5da5dbcc0..a3e17d7d2 100644 --- a/agent/command.c +++ b/agent/command.c @@ -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); diff --git a/agent/cvt-openpgp.c b/agent/cvt-openpgp.c index 130468667..78f2989e7 100644 --- a/agent/cvt-openpgp.c +++ b/agent/cvt-openpgp.c @@ -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); } } diff --git a/agent/divert-scd.c b/agent/divert-scd.c index 70a71e991..b9ea7e745 100644 --- a/agent/divert-scd.c +++ b/agent/divert-scd.c @@ -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; diff --git a/agent/findkey.c b/agent/findkey.c index 64a54262c..dc4653531 100644 --- a/agent/findkey.c +++ b/agent/findkey.c @@ -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)); diff --git a/agent/genkey.c b/agent/genkey.c index eb2f25e4e..a944ac7c6 100644 --- a/agent/genkey.c +++ b/agent/genkey.c @@ -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; } diff --git a/agent/keyformat.txt b/agent/keyformat.txt index 259166aba..97ab87d58 100644 --- a/agent/keyformat.txt +++ b/agent/keyformat.txt @@ -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 diff --git a/agent/learncard.c b/agent/learncard.c index abe1dd0bf..c66ee198a 100644 --- a/agent/learncard.c +++ b/agent/learncard.c @@ -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; diff --git a/agent/protect-tool.c b/agent/protect-tool.c index b04836aaf..fb2c71dbd 100644 --- a/agent/protect-tool.c +++ b/agent/protect-tool.c @@ -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");