summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNIIBE Yutaka <gniibe@fsij.org>2022-05-26 10:10:54 +0200
committerNIIBE Yutaka <gniibe@fsij.org>2022-05-26 10:11:52 +0200
commit193fcc2f7a8cca5240ce50499c54f99235a87e1c (patch)
treefc99d3f0fa56f274c931007d73d6f12e36c96152
parentagent: Fix get_keyinfo_on_cards. (diff)
downloadgnupg2-193fcc2f7a8cca5240ce50499c54f99235a87e1c.tar.xz
gnupg2-193fcc2f7a8cca5240ce50499c54f99235a87e1c.zip
agent,ssh: Make not-inserted OpenPGP.3 keys available for SSH.
* agent/agent.h (agent_ssh_key_from_file): New. * agent/command-ssh.c (get_ssh_keyinfo_on_cards): New. (ssh_send_available_keys): Loop on the GNUPG_PRIVATE_KEYS_DIR. Support keys by agent_ssh_key_from_file. (ssh_handler_request_identities): Move card key handling to ssh_send_available_keys. * agent/findkey.c (public_key_from_file): New. Adding handling for SSH. (agent_public_key_from_file): Use public_key_from_file. (agent_ssh_key_from_file): New. -- GnuPG-bug-id: 5996 Signed-off-by: NIIBE Yutaka <gniibe@fsij.org>
-rw-r--r--agent/agent.h3
-rw-r--r--agent/command-ssh.c240
-rw-r--r--agent/findkey.c48
3 files changed, 206 insertions, 85 deletions
diff --git a/agent/agent.h b/agent/agent.h
index d32a756f6..9eccdeacf 100644
--- a/agent/agent.h
+++ b/agent/agent.h
@@ -468,6 +468,9 @@ gpg_error_t agent_raw_key_from_file (ctrl_t ctrl, const unsigned char *grip,
gpg_error_t agent_public_key_from_file (ctrl_t ctrl,
const unsigned char *grip,
gcry_sexp_t *result);
+gpg_error_t agent_ssh_key_from_file (ctrl_t ctrl,
+ const unsigned char *grip,
+ gcry_sexp_t *result);
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);
diff --git a/agent/command-ssh.c b/agent/command-ssh.c
index e5c50a63e..8634ec01a 100644
--- a/agent/command-ssh.c
+++ b/agent/command-ssh.c
@@ -2449,48 +2449,194 @@ card_key_available (ctrl_t ctrl, const struct card_key_info_s *keyinfo,
return 0;
}
+static struct card_key_info_s *
+get_ssh_keyinfo_on_cards (ctrl_t ctrl)
+{
+ struct card_key_info_s *keyinfo_on_cards = NULL;
+ gpg_error_t err;
+ char *serialno;
+
+ if (opt.disable_daemon[DAEMON_SCD])
+ return NULL;
+
+ /* Scan for new device(s). */
+ err = agent_card_serialno (ctrl, &serialno, NULL);
+ if (err)
+ {
+ if (opt.verbose)
+ log_info (_("error getting list of cards: %s\n"),
+ gpg_strerror (err));
+ return NULL;
+ }
+
+ xfree (serialno);
+
+ err = agent_card_keyinfo (ctrl, NULL, GCRY_PK_USAGE_AUTH, &keyinfo_on_cards);
+ if (err)
+ return NULL;
+
+ return keyinfo_on_cards;
+}
+
static gpg_error_t
ssh_send_available_keys (ctrl_t ctrl, estream_t key_blobs, u32 *key_counter_p)
{
gpg_error_t err;
+ char *dirname;
+ gnupg_dir_t dir = NULL;
+ gnupg_dirent_t dir_entry;
+ char hexgrip[41];
ssh_control_file_t cf = NULL;
+ struct card_key_info_s *keyinfo_on_cards, *l;
+ char *cardsn;
+ gcry_sexp_t key_public = NULL;
err = open_control_file (&cf, 0);
if (err)
return err;
- while (!read_control_file_item (cf))
+ /* First, get information keys available on card(s). */
+ keyinfo_on_cards = get_ssh_keyinfo_on_cards (ctrl);
+
+ /* Then, look at all keys with "OPENPGP.3" idstring. */
+ /* Look at all the registered and non-disabled keys, in sshcontrol. */
+ dirname = make_filename_try (gnupg_homedir (),
+ GNUPG_PRIVATE_KEYS_DIR, NULL);
+ if (!dirname)
{
+ err = gpg_error_from_syserror ();
+ agent_card_free_keyinfo (keyinfo_on_cards);
+ return err;
+ }
+ dir = gnupg_opendir (dirname);
+ if (!dir)
+ {
+ err = gpg_error_from_syserror ();
+ xfree (dirname);
+ agent_card_free_keyinfo (keyinfo_on_cards);
+ return err;
+ }
+ xfree (dirname);
+
+ while ( (dir_entry = gnupg_readdir (dir)) )
+ {
+ struct card_key_info_s *l_prev = NULL;
+ int disabled, is_ssh;
unsigned char grip[20];
- gcry_sexp_t key_public = NULL;
- if (!cf->item.valid)
- continue; /* Should not happen. */
- if (cf->item.disabled)
+ cardsn = NULL;
+ if (strlen (dir_entry->d_name) != 44
+ || strcmp (dir_entry->d_name + 40, ".key"))
continue;
- log_assert (strlen (cf->item.hexgrip) == 40);
- hex2bin (cf->item.hexgrip, grip, sizeof (grip));
+ strncpy (hexgrip, dir_entry->d_name, 40);
+ hexgrip[40] = 0;
- err = agent_public_key_from_file (ctrl, grip, &key_public);
+ if ( hex2bin (hexgrip, grip, 20) < 0 )
+ continue; /* Bad hex string. */
+
+ /* Check if it's a key on card. */
+ for (l = keyinfo_on_cards; l; l = l->next)
+ if (!memcmp (l->keygrip, hexgrip, 40))
+ break;
+ else
+ l_prev = l;
+
+ /* Check if it's listed in "ssh_control" file. */
+ disabled = is_ssh = 0;
+ err = search_control_file (cf, hexgrip, &disabled, NULL, NULL);
+ if (!err)
+ {
+ if (!disabled)
+ is_ssh = 1;
+ }
+ else if (gpg_err_code (err) != GPG_ERR_EOF)
+ break;
+
+ if (l)
+ {
+ err = card_key_available (ctrl, l, &key_public, &cardsn);
+ /* Remove the entry from the list of KEYINFO_ON_CARD */
+ if (l_prev)
+ l_prev->next = l->next;
+ else
+ keyinfo_on_cards = l->next;
+ xfree (l->serialno);
+ xfree (l->idstr);
+ xfree (l->usage);
+ xfree (l);
+ l = NULL;
+ }
+ else if (is_ssh)
+ err = agent_public_key_from_file (ctrl, grip, &key_public);
+ else
+ /* Examine the file if it's suitable for SSH. */
+ err = agent_ssh_key_from_file (ctrl, grip, &key_public);
if (err)
{
- log_error ("%s:%d: key '%s' skipped: %s\n",
- cf->fname, cf->lnr, cf->item.hexgrip,
- gpg_strerror (err));
/* Clear ERR, skiping the key in question. */
err = 0;
continue;
}
- err = ssh_send_key_public (key_blobs, key_public, NULL);
+ err = ssh_send_key_public (key_blobs, key_public, cardsn);
+ xfree (cardsn);
if (err)
- break;
+ {
+ if (opt.verbose)
+ gcry_log_debugsxp ("pubkey", key_public);
+ if (gpg_err_code (err) == GPG_ERR_UNKNOWN_CURVE
+ || gpg_err_code (err) == GPG_ERR_INV_CURVE)
+ {
+ /* For example a Brainpool curve or a curve we don't
+ * support at all but a smartcard lists that curve.
+ * We ignore them. */
+ }
+ else
+ {
+ gcry_sexp_release (key_public);
+ break;
+ }
+ }
gcry_sexp_release (key_public);
(*key_counter_p)++;
}
- close_control_file (cf);
+ gnupg_closedir (dir);
+ ssh_close_control_file (cf);
+
+ /* Lastly, handle remaining keys which don't have the stub files. */
+ for (l = keyinfo_on_cards; l; l = l->next)
+ {
+ cardsn = NULL;
+ if (card_key_available (ctrl, l, &key_public, &cardsn))
+ continue;
+
+ err = ssh_send_key_public (key_blobs, key_public, cardsn);
+ xfree (cardsn);
+ if (err)
+ {
+ if (opt.verbose)
+ gcry_log_debugsxp ("pubkey", key_public);
+ if (gpg_err_code (err) == GPG_ERR_UNKNOWN_CURVE
+ || gpg_err_code (err) == GPG_ERR_INV_CURVE)
+ {
+ /* For example a Brainpool curve or a curve we don't
+ * support at all but a smartcard lists that curve.
+ * We ignore them. */
+ }
+ else
+ {
+ gcry_sexp_release (key_public);
+ break;
+ }
+ }
+ else
+ (*key_counter_p)++;
+ }
+
+ agent_card_free_keyinfo (keyinfo_on_cards);
+
return err;
}
@@ -2528,72 +2674,6 @@ ssh_handler_request_identities (ctrl_t ctrl,
goto out;
}
- /* First check whether a key is currently available in the card
- reader - this should be allowed even without being listed in
- sshcontrol. */
-
- if (!opt.disable_daemon[DAEMON_SCD])
- {
- char *serialno;
- struct card_key_info_s *keyinfo_list;
- struct card_key_info_s *keyinfo;
-
- /* Scan device(s), and get list of KEYGRIP. */
- err = agent_card_serialno (ctrl, &serialno, NULL);
- if (!err)
- {
- xfree (serialno);
- err = agent_card_keyinfo (ctrl, NULL, GCRY_PK_USAGE_AUTH,
- &keyinfo_list);
- }
-
- if (err)
- {
- if (opt.verbose)
- log_info (_("error getting list of cards: %s\n"),
- gpg_strerror (err));
- goto scd_out;
- }
-
- for (keyinfo = keyinfo_list; keyinfo; keyinfo = keyinfo->next)
- {
- char *cardsn;
- gcry_sexp_t key_public = NULL;
-
- if (card_key_available (ctrl, keyinfo, &key_public, &cardsn))
- continue;
-
- err = ssh_send_key_public (key_blobs, key_public, cardsn);
- xfree (cardsn);
- if (err)
- {
- if (opt.verbose)
- gcry_log_debugsxp ("pubkey", key_public);
- if (gpg_err_code (err) == GPG_ERR_UNKNOWN_CURVE
- || gpg_err_code (err) == GPG_ERR_INV_CURVE)
- {
- /* For example a Brainpool curve or a curve we don't
- * support at all but a smartcard lists that curve.
- * We ignore them. */
- }
- else
- {
- agent_card_free_keyinfo (keyinfo_list);
- gcry_sexp_release (key_public);
- goto out;
- }
- }
- else
- key_counter++;
-
- gcry_sexp_release (key_public);
- }
-
- agent_card_free_keyinfo (keyinfo_list);
- }
-
- scd_out:
- /* Then look at all the registered and non-disabled keys. */
err = ssh_send_available_keys (ctrl, key_blobs, &key_counter);
if (!err)
{
diff --git a/agent/findkey.c b/agent/findkey.c
index 4499cae22..21af16fb1 100644
--- a/agent/findkey.c
+++ b/agent/findkey.c
@@ -1351,14 +1351,14 @@ agent_raw_key_from_file (ctrl_t ctrl, const unsigned char *grip,
at RESULT. This function extracts the public key from the private
key database. On failure an error code is returned and NULL stored
at RESULT. */
-gpg_error_t
-agent_public_key_from_file (ctrl_t ctrl,
- const unsigned char *grip,
- gcry_sexp_t *result)
+static gpg_error_t
+public_key_from_file (ctrl_t ctrl, const unsigned char *grip,
+ gcry_sexp_t *result, int for_ssh)
{
gpg_error_t err;
int i, idx;
gcry_sexp_t s_skey;
+ nvc_t keymeta = NULL;
const char *algoname, *elems;
int npkey;
gcry_mpi_t array[10];
@@ -1380,10 +1380,32 @@ agent_public_key_from_file (ctrl_t ctrl,
*result = NULL;
- err = read_key_file (grip, &s_skey, NULL);
+ err = read_key_file (grip, &s_skey, for_ssh? &keymeta : NULL);
if (err)
return err;
+ if (keymeta)
+ {
+ /* Token: <SERIALNO> <IDSTR> */
+ const char *p = nvc_get_string (keymeta, "Token:");
+
+ if (!p)
+ return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
+
+ while (*p && !spacep (p))
+ p++;
+
+ if (!*p)
+ return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
+
+ p++;
+ if (strcmp (p, "OPENPGP.3"))
+ return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
+
+ nvc_release (keymeta);
+ keymeta = NULL;
+ }
+
for (i=0; i < DIM (array); i++)
array[i] = NULL;
@@ -1472,6 +1494,22 @@ agent_public_key_from_file (ctrl_t ctrl,
return err;
}
+gpg_error_t
+agent_public_key_from_file (ctrl_t ctrl,
+ const unsigned char *grip,
+ gcry_sexp_t *result)
+{
+ return public_key_from_file (ctrl, grip, result, 0);
+}
+
+gpg_error_t
+agent_ssh_key_from_file (ctrl_t ctrl,
+ const unsigned char *grip,
+ gcry_sexp_t *result)
+{
+ return public_key_from_file (ctrl, grip, result, 1);
+}
+
/* Check whether the secret key identified by GRIP is available.
Returns 0 is the key is available. */