aboutsummaryrefslogtreecommitdiffstats
path: root/agent/command-ssh.c
diff options
context:
space:
mode:
authorWerner Koch <[email protected]>2023-02-01 08:27:28 +0000
committerWerner Koch <[email protected]>2023-02-01 08:27:28 +0000
commit8b8a8b246c443d5631a88ec59b88edf00aa0ff51 (patch)
treee4ca830daaae89ce7ec0e7089c99b4042d3db145 /agent/command-ssh.c
parentgpg: New pseudo option full-help for --list-options et al. (diff)
downloadgnupg-8b8a8b246c443d5631a88ec59b88edf00aa0ff51.tar.gz
gnupg-8b8a8b246c443d5631a88ec59b88edf00aa0ff51.zip
ssh: Allow to define the order in which keys are returned.
* agent/findkey.c (public_key_from_file): Add arg r_sshorder. (agent_ssh_key_from_file): Ditto. * agent/command-ssh.c (struct key_collection_item_s): New. (struct key_collection_s): New. (search_control_file): Add art r_lnr. (add_to_key_array): New. (free_key_array): New. (compare_key_collection_items): New. (ssh_send_available_keys): Rewrite to return the keys in the user given order. -- GnuPG-bug-id: 6212 We now first return the keys from active cards, followed by keys listed in sshcontrol, finally from those with the "Use-for-ssh" key attribute. Keys from active cards are returned sorted by their S/N. Keys from sshcontrol are returned in the order they are given in that file. Use-for-ssh keys are ordered by the value assigned to that key attribute. The values for the latter are clamped at 99999.
Diffstat (limited to '')
-rw-r--r--agent/command-ssh.c227
1 files changed, 171 insertions, 56 deletions
diff --git a/agent/command-ssh.c b/agent/command-ssh.c
index f237f9355..7621e7c2f 100644
--- a/agent/command-ssh.c
+++ b/agent/command-ssh.c
@@ -231,6 +231,22 @@ struct ssh_control_file_s
};
+/* Two objects definition to hold keys for later sorting. */
+struct key_collection_item_s
+{
+ gcry_sexp_t key; /* Public key. (owned by us) */
+ char *cardsn; /* Serial number of a card or NULL. (owned by us) */
+ int order; /* Computed ordinal */
+};
+
+struct key_collection_s
+{
+ struct key_collection_item_s *items;
+ size_t allocated;
+ size_t nitems;
+};
+
+
/* Prototypes. */
static gpg_error_t ssh_handler_request_identities (ctrl_t ctrl,
estream_t request,
@@ -1030,10 +1046,11 @@ read_control_file_item (ssh_control_file_t cf)
HEXGRIP is found; return success in this case and store true at
DISABLED if the found key has been disabled. If R_TTL is not NULL
a specified TTL for that key is stored there. If R_CONFIRM is not
- NULL it is set to 1 if the key has the confirm flag set. */
+ NULL it is set to 1 if the key has the confirm flag set. The line
+ number where the item was found is stored at R_LNR. */
static gpg_error_t
search_control_file (ssh_control_file_t cf, const char *hexgrip,
- int *r_disabled, int *r_ttl, int *r_confirm)
+ int *r_disabled, int *r_ttl, int *r_confirm, int *r_lnr)
{
gpg_error_t err;
@@ -1045,6 +1062,8 @@ search_control_file (ssh_control_file_t cf, const char *hexgrip,
*r_ttl = 0;
if (r_confirm)
*r_confirm = 0;
+ if (r_lnr)
+ *r_lnr = -1;
rewind_control_file (cf);
while (!(err=read_control_file_item (cf)))
@@ -1062,6 +1081,8 @@ search_control_file (ssh_control_file_t cf, const char *hexgrip,
*r_ttl = cf->item.ttl;
if (r_confirm)
*r_confirm = cf->item.confirm;
+ if (r_lnr)
+ *r_lnr = cf->lnr;
}
return err;
}
@@ -1090,7 +1111,7 @@ add_control_entry (ctrl_t ctrl, ssh_key_type_spec_t *spec,
if (err)
return err;
- err = search_control_file (cf, hexgrip, &disabled, NULL, NULL);
+ err = search_control_file (cf, hexgrip, &disabled, NULL, NULL, NULL);
if (err && gpg_err_code(err) == GPG_ERR_EOF)
{
struct tm *tp;
@@ -1141,7 +1162,7 @@ ttl_from_sshcontrol (const char *hexgrip)
if (open_control_file (&cf, 0))
return 0; /* Error: Use the global default TTL. */
- if (search_control_file (cf, hexgrip, &disabled, &ttl, NULL)
+ if (search_control_file (cf, hexgrip, &disabled, &ttl, NULL, NULL)
|| disabled)
ttl = 0; /* Use the global default if not found or disabled. */
@@ -1164,7 +1185,7 @@ confirm_flag_from_sshcontrol (const char *hexgrip)
if (open_control_file (&cf, 0))
return 1; /* Error: Better ask for confirmation. */
- if (search_control_file (cf, hexgrip, &disabled, NULL, &confirm)
+ if (search_control_file (cf, hexgrip, &disabled, NULL, &confirm, NULL)
|| disabled)
confirm = 0; /* If not found or disabled, there is no reason to
ask for confirmation. */
@@ -1250,7 +1271,8 @@ ssh_search_control_file (ssh_control_file_t cf,
if (i != 40)
err = gpg_error (GPG_ERR_INV_LENGTH);
else
- err = search_control_file (cf, uphexgrip, r_disabled, r_ttl, r_confirm);
+ err = search_control_file (cf, uphexgrip, r_disabled, r_ttl, r_confirm,
+ NULL);
if (gpg_err_code (err) == GPG_ERR_EOF)
err = gpg_error (GPG_ERR_NOT_FOUND);
return err;
@@ -2483,8 +2505,71 @@ get_ssh_keyinfo_on_cards (ctrl_t ctrl)
return keyinfo_on_cards;
}
+
+/* Append (KEY,CARDSN,LNR,ORDER) to ARRAY. The array must initially
+ * be passed as a cleared struct. ARRAY takes ownership of KEY and
+ * CARDSN. */
static gpg_error_t
-ssh_send_available_keys (ctrl_t ctrl, estream_t key_blobs, u32 *key_counter_p)
+add_to_key_array (struct key_collection_s *array, gcry_sexp_t key,
+ char *cardsn, int order)
+{
+ if (array->nitems == array->allocated)
+ {
+ struct key_collection_item_s *newitems;
+ size_t newsize = ((array->allocated + 63)/64 + 1) * 64;
+
+ newitems = xtryreallocarray (array->items, array->allocated, newsize+1,
+ sizeof *newitems);
+ if (!newitems)
+ return gpg_error_from_syserror ();
+ array->allocated = newsize;
+ array->items = newitems;
+ }
+ array->items[array->nitems].key = key;
+ array->items[array->nitems].cardsn = cardsn;
+ array->items[array->nitems].order = order;
+ array->nitems++;
+ return 0;
+}
+
+/* Release the content of ARRAY. */
+static void
+free_key_array (struct key_collection_s *array)
+{
+ if (array && array->items)
+ {
+ unsigned int n;
+
+ for (n = 0; n < array->nitems; n++)
+ {
+ gcry_sexp_release (array->items[n].key);
+ xfree (array->items[n].cardsn);
+ }
+ xfree (array->items);
+ }
+}
+
+
+/* Helper for the qsort in ssh_send_available_keys. */
+static int
+compare_key_collection_items (const void *arg_a, const void *arg_b)
+{
+ const struct key_collection_item_s *a
+ = (const struct key_collection_item_s *)arg_a;
+ const struct key_collection_item_s *b
+ = (const struct key_collection_item_s *)arg_b;
+ int res;
+
+ res = a->order - b->order;
+ /* If we are comparing two cards we sort by serial number. */
+ if (!res && a->order == 1)
+ res = strcmp (a->cardsn?a->cardsn:"", b->cardsn?b->cardsn:"");
+ return res;
+}
+
+
+static gpg_error_t
+ssh_send_available_keys (ctrl_t ctrl, estream_t key_blobs, u32 *r_key_counter)
{
gpg_error_t err;
char *dirname;
@@ -2495,6 +2580,8 @@ ssh_send_available_keys (ctrl_t ctrl, estream_t key_blobs, u32 *key_counter_p)
struct card_key_info_s *keyinfo_on_cards, *l;
char *cardsn;
gcry_sexp_t key_public = NULL;
+ int count;
+ struct key_collection_s keyarray = { NULL };
err = open_control_file (&cf, 0);
if (err)
@@ -2526,7 +2613,7 @@ ssh_send_available_keys (ctrl_t ctrl, estream_t key_blobs, u32 *key_counter_p)
while ( (dir_entry = gnupg_readdir (dir)) )
{
struct card_key_info_s *l_prev = NULL;
- int disabled, is_ssh;
+ int disabled, is_ssh, lnr, order;
unsigned char grip[20];
cardsn = NULL;
@@ -2548,15 +2635,32 @@ ssh_send_available_keys (ctrl_t ctrl, estream_t key_blobs, u32 *key_counter_p)
/* Check if it's listed in "ssh_control" file. */
disabled = is_ssh = 0;
- err = search_control_file (cf, hexgrip, &disabled, NULL, NULL);
+ err = search_control_file (cf, hexgrip, &disabled, NULL, NULL, &lnr);
if (!err)
{
if (!disabled)
- is_ssh = 1;
+ {
+ is_ssh = 1;
+ }
}
else if (gpg_err_code (err) != GPG_ERR_EOF)
break;
+ /* Clamp LNR value and set the ordinal.
+ * Current use of ordinals:
+ * 1..99999 - inserted cards (right now only 1)
+ * 100000..199999 - listed in sshcontrol
+ * 200000..299999 - order taken from Use-for-ssh
+ */
+ if (is_ssh)
+ {
+ if (lnr < 1)
+ lnr = 0;
+ else if (lnr > 99999)
+ lnr = 99999;
+ order = lnr + 100000;
+ }
+
if (l)
{
err = card_key_available (ctrl, l, &key_public, &cardsn);
@@ -2570,81 +2674,92 @@ ssh_send_available_keys (ctrl_t ctrl, estream_t key_blobs, u32 *key_counter_p)
xfree (l->usage);
xfree (l);
l = NULL;
+ /* If we want to allow that the user to change the sorting
+ * order of card keys (which are sorted by their s/n), we
+ * would need to get the use-for-ssh: value from the stub
+ * file and set an appropriate ordinal. */
+ order = 1;
}
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);
+ else /* Examine the file if it's suitable for SSH. */
+ {
+ err = agent_ssh_key_from_file (ctrl, grip, &key_public, &order);
+ if (order < 0 || err)
+ order = 0;
+ else if (order > 99999)
+ order = 99999;
+ order += 200000;
+ }
if (err)
{
- /* Clear ERR, skiping the key in question. */
+ /* Clear ERR, skipping the key in question. */
err = 0;
continue;
}
- err = ssh_send_key_public (key_blobs, key_public, cardsn);
- xfree (cardsn);
+ err = add_to_key_array (&keyarray, key_public, cardsn, order);
if (err)
{
- if (opt.debug)
- 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; /* the readdir loop. */
- }
+ gcry_sexp_release (key_public);
+ xfree (cardsn);
+ goto leave;
}
- else /* Success */
- (*key_counter_p)++;
-
- gcry_sexp_release (key_public);
}
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)
+ for (l = keyinfo_on_cards, count=0; l; l = l->next, count++)
{
cardsn = NULL;
if (card_key_available (ctrl, l, &key_public, &cardsn))
continue;
- err = ssh_send_key_public (key_blobs, key_public, cardsn);
- xfree (cardsn);
+ err = add_to_key_array (&keyarray, key_public, cardsn, 300000+count);
if (err)
{
- if (opt.debug)
- 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);
+ xfree (cardsn);
+ goto leave;
}
- else /* Success. */
- (*key_counter_p)++;
-
- gcry_sexp_release (key_public);
}
- agent_card_free_keyinfo (keyinfo_on_cards);
+ /* Sort the array. */
+ qsort (keyarray.items, keyarray.nitems, sizeof *keyarray.items,
+ compare_key_collection_items);
+ if (opt.debug)
+ for (count=0; count < keyarray.nitems; count++)
+ log_debug ("sshkeys[%d]: order=%d, pubkey=%p sn=%s\n",
+ count, keyarray.items[count].order,
+ keyarray.items[count].key, keyarray.items[count].cardsn);
+
+ /* And print the keys. */
+ for (count=0; count < keyarray.nitems; count++)
+ {
+ err = ssh_send_key_public (key_blobs, keyarray.items[count].key,
+ keyarray.items[count].cardsn);
+ if (err)
+ {
+ if (opt.debug)
+ gcry_log_debugsxp ("pubkey", keyarray.items[count].key);
+ 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
+ goto leave;
+ }
+ }
+ *r_key_counter = count;
+ leave:
+ agent_card_free_keyinfo (keyinfo_on_cards);
+ free_key_array (&keyarray);
return err;
}