aboutsummaryrefslogtreecommitdiffstats
path: root/tools/card-keys.c
diff options
context:
space:
mode:
authorWerner Koch <[email protected]>2019-02-25 08:28:22 +0000
committerWerner Koch <[email protected]>2019-02-25 08:34:30 +0000
commit28de5c0ea53373c56a4405fe6b08d194682dd1de (patch)
treeb720cb2577450d8f28641fada45e590d775241db /tools/card-keys.c
parentagent: Fix for suggested Libgcrypt use. (diff)
downloadgnupg-28de5c0ea53373c56a4405fe6b08d194682dd1de.tar.gz
gnupg-28de5c0ea53373c56a4405fe6b08d194682dd1de.zip
card: Rename gpg-card-tool to gpg-card.
* tools/card-tool-keys.c: Rename to card-keys.c. * tools/card-tool-misc.c: Rename to card-misc.c. * tools/card-tool-yubikey.c: Rename to card-yubikey.c. * tools/card-tool.h: Rename to gpg-card.h. * tools/gpg-card-tool-w32info.rc: Rename to gpg-card-w32info.rc * doc/card-tool.texi: Rename top gpg-card.texi Signed-off-by: Werner Koch <[email protected]>
Diffstat (limited to 'tools/card-keys.c')
-rw-r--r--tools/card-keys.c555
1 files changed, 555 insertions, 0 deletions
diff --git a/tools/card-keys.c b/tools/card-keys.c
new file mode 100644
index 000000000..ad06f2ff7
--- /dev/null
+++ b/tools/card-keys.c
@@ -0,0 +1,555 @@
+/* card-keys.c - OpenPGP and CMS related functions for gpg-card
+ * Copyright (C) 2019 g10 Code GmbH
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuPG is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../common/util.h"
+#include "../common/i18n.h"
+#include "../common/ccparray.h"
+#include "../common/exectool.h"
+#include "../common/openpgpdefs.h"
+#include "gpg-card.h"
+
+
+/* It is quite common that all keys of an OpenPGP card belong to the
+ * the same OpenPGP keyblock. To avoid running several queries
+ * despite that we already got the information with the previous
+ * keyblock, we keep a small cache of of previous done queries. */
+static struct
+{
+ unsigned int lru;
+ keyblock_t keyblock;
+} keyblock_cache[5];
+
+
+
+/* Helper for release_keyblock. */
+static void
+do_release_keyblock (keyblock_t keyblock)
+{
+ pubkey_t pubkey;
+ userid_t uid;
+
+ while (keyblock)
+ {
+ keyblock_t keyblocknext = keyblock->next;
+ pubkey = keyblock->keys;
+ while (pubkey)
+ {
+ pubkey_t pubkeynext = pubkey->next;
+ xfree (pubkey);
+ pubkey = pubkeynext;
+ }
+ uid = keyblock->uids;
+ while (uid)
+ {
+ userid_t uidnext = uid->next;
+ xfree (uid->value);
+ xfree (uid);
+ uid = uidnext;
+ }
+ xfree (keyblock);
+ keyblock = keyblocknext;
+ }
+}
+
+
+/* Release a keyblock object. */
+void
+release_keyblock (keyblock_t keyblock)
+{
+ static unsigned int lru_counter;
+ unsigned int lru;
+ int i, lru_idx;
+
+ if (!keyblock)
+ return;
+
+ lru = (unsigned int)(-1);
+ lru_idx = 0;
+ for (i=0; i < DIM (keyblock_cache); i++)
+ {
+ if (!keyblock_cache[i].keyblock)
+ {
+ keyblock_cache[i].keyblock = keyblock;
+ keyblock_cache[i].lru = ++lru_counter;
+ goto leave;
+ }
+ if (keyblock_cache[i].lru < lru)
+ {
+ lru = keyblock_cache[i].lru;
+ lru_idx = i;
+ }
+ }
+
+ /* No free slot. Replace one. */
+ do_release_keyblock (keyblock_cache[lru_idx].keyblock);
+ keyblock_cache[lru_idx].keyblock = keyblock;
+ keyblock_cache[lru_idx].lru = ++lru_counter;
+
+ leave:
+ if (!lru_counter)
+ {
+ /* Wrapped around. We simply clear the entire cache. */
+ flush_keyblock_cache ();
+ }
+}
+
+
+/* Flush the enire keyblock cache. */
+void
+flush_keyblock_cache (void)
+{
+ int i;
+
+ for (i=0; i < DIM (keyblock_cache); i++)
+ {
+ do_release_keyblock (keyblock_cache[i].keyblock);
+ keyblock_cache[i].keyblock = NULL;
+ }
+}
+
+
+
+/* Object to communicate with the status_cb. */
+struct status_cb_s
+{
+ const char *pgm; /* Name of the program for debug purposes. */
+ int no_pubkey; /* Result flag. */
+};
+
+
+/* Status callback helper for the exec functions. */
+static void
+status_cb (void *opaque, const char *keyword, char *args)
+{
+ struct status_cb_s *c = opaque;
+ const char *s;
+
+ if (DBG_EXTPROG)
+ log_debug ("%s: status: %s %s\n", c->pgm, keyword, args);
+
+ if (!strcmp (keyword, "ERROR")
+ && (s=has_leading_keyword (args, "keylist.getkey"))
+ && gpg_err_code (atoi (s)) == GPG_ERR_NO_PUBKEY)
+ {
+ /* No public key was found. gpg terminates with an error in
+ * this case and we can't change that behaviour. Instead we
+ * detect this status and carry that error forward. */
+ c->no_pubkey = 1;
+ }
+
+}
+
+
+/* Helper for get_matching_keys to parse "pub" style records. */
+static gpg_error_t
+parse_key_record (char **fields, int nfields, pubkey_t *r_pubkey)
+{
+ pubkey_t pubkey;
+
+ (void)fields; /* Not yet used. */
+ (void)nfields;
+
+ pubkey = xtrycalloc (1, sizeof *pubkey);
+ if (!pubkey)
+ return gpg_error_from_syserror ();
+ *r_pubkey = pubkey;
+ return 0;
+}
+
+
+/* Run gpg or gpgsm to get a list of all keys matching the 20 byte
+ * KEYGRIP. PROTOCOL is one of or a combination of
+ * GNUPG_PROTOCOL_OPENPGP and GNUPG_PROTOCOL_CMS. On success a new
+ * keyblock is stored at R_KEYBLOCK; on error NULL is stored there. */
+gpg_error_t
+get_matching_keys (const unsigned char *keygrip, int protocol,
+ keyblock_t *r_keyblock)
+{
+ gpg_error_t err;
+ ccparray_t ccp;
+ const char **argv;
+ estream_t listing;
+ char hexgrip[1 + (2*KEYGRIP_LEN) + 1];
+ char *line = NULL;
+ size_t length_of_line = 0;
+ size_t maxlen;
+ ssize_t len;
+ char **fields = NULL;
+ int nfields;
+ int first_seen;
+ int i;
+ keyblock_t keyblock_head, *keyblock_tail, kb;
+ pubkey_t pubkey, pk;
+ size_t n;
+ struct status_cb_s status_cb_parm;
+
+ *r_keyblock = NULL;
+
+ keyblock_head = NULL;
+ keyblock_tail = &keyblock_head;
+ kb = NULL;
+
+ /* Shortcut to run a listing on both protocols. */
+ if ((protocol & GNUPG_PROTOCOL_OPENPGP) && (protocol & GNUPG_PROTOCOL_CMS))
+ {
+ err = get_matching_keys (keygrip, GNUPG_PROTOCOL_OPENPGP, &kb);
+ if (!err || gpg_err_code (err) == GPG_ERR_NO_PUBKEY)
+ {
+ if (!err)
+ {
+ *keyblock_tail = kb;
+ keyblock_tail = &kb->next;
+ kb = NULL;
+ }
+ err = get_matching_keys (keygrip, GNUPG_PROTOCOL_CMS, &kb);
+ if (!err)
+ {
+ *keyblock_tail = kb;
+ keyblock_tail = &kb->next;
+ kb = NULL;
+ }
+ else if (gpg_err_code (err) == GPG_ERR_NO_PUBKEY)
+ err = 0;
+ }
+ if (err)
+ release_keyblock (keyblock_head);
+ else
+ *r_keyblock = keyblock_head;
+ return err;
+ }
+
+ /* Check that we have only one protocol. */
+ if (protocol != GNUPG_PROTOCOL_OPENPGP && protocol != GNUPG_PROTOCOL_CMS)
+ return gpg_error (GPG_ERR_UNSUPPORTED_PROTOCOL);
+
+ /* Try to get it from our cache. */
+ for (i=0; i < DIM (keyblock_cache); i++)
+ for (kb = keyblock_cache[i].keyblock; kb; kb = kb->next)
+ if (kb->protocol == protocol)
+ for (pk = kb->keys; pk; pk = pk->next)
+ if (pk->grip_valid && !memcmp (pk->grip, keygrip, KEYGRIP_LEN))
+ {
+ *r_keyblock = keyblock_cache[i].keyblock;
+ keyblock_cache[i].keyblock = NULL;
+ return 0;
+ }
+
+ /* Open a memory stream. */
+ listing = es_fopenmem (0, "w+b");
+ if (!listing)
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
+ return err;
+ }
+
+ status_cb_parm.pgm = protocol == GNUPG_PROTOCOL_OPENPGP? "gpg":"gpgsm";
+ status_cb_parm.no_pubkey = 0;
+
+ hexgrip[0] = '&';
+ bin2hex (keygrip, KEYGRIP_LEN, hexgrip+1);
+
+ ccparray_init (&ccp, 0);
+
+ if (opt.verbose > 1 || DBG_EXTPROG)
+ ccparray_put (&ccp, "--verbose");
+ else
+ ccparray_put (&ccp, "--quiet");
+ ccparray_put (&ccp, "--no-options");
+ ccparray_put (&ccp, "--batch");
+ ccparray_put (&ccp, "--status-fd=2");
+ ccparray_put (&ccp, "--with-colons");
+ ccparray_put (&ccp, "--with-keygrip");
+ ccparray_put (&ccp, "--list-keys");
+ ccparray_put (&ccp, hexgrip);
+
+ ccparray_put (&ccp, NULL);
+ argv = ccparray_get (&ccp, NULL);
+ if (!argv)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ err = gnupg_exec_tool_stream (protocol == GNUPG_PROTOCOL_OPENPGP?
+ opt.gpg_program : opt.gpgsm_program,
+ argv, NULL, NULL, listing, status_cb,
+ &status_cb_parm);
+ if (err)
+ {
+ if (status_cb_parm.no_pubkey)
+ err = gpg_error (GPG_ERR_NO_PUBKEY);
+ else if (gpg_err_code (err) != GPG_ERR_GENERAL)
+ log_error ("key listing failed: %s\n", gpg_strerror (err));
+ goto leave;
+ }
+
+ es_rewind (listing);
+ first_seen = 0;
+ maxlen = 8192; /* Set limit large enough for all escaped UIDs. */
+ while ((len = es_read_line (listing, &line, &length_of_line, &maxlen)) > 0)
+ {
+ if (!maxlen)
+ {
+ log_error ("received line too long\n");
+ err = gpg_error (GPG_ERR_LINE_TOO_LONG);
+ goto leave;
+ }
+ /* Strip newline and carriage return, if present. */
+ while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r'))
+ line[--len] = '\0';
+
+ xfree (fields);
+ fields = strtokenize (line, ":");
+ if (!fields)
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("strtokenize failed: %s\n", gpg_strerror (err));
+ goto leave;
+ }
+ for (nfields = 0; fields[nfields]; nfields++)
+ ;
+ if (!nfields)
+ {
+ err = gpg_error (GPG_ERR_INV_ENGINE);
+ goto leave;
+ }
+
+ /* Skip over all records until we reach a pub or sec. */
+ if (!first_seen
+ && (!strcmp (fields[0], "pub") || !strcmp (fields[0], "sec")
+ || !strcmp (fields[0], "crt") || !strcmp (fields[0], "crs")))
+ first_seen = 1;
+ if (!first_seen)
+ continue;
+
+ if (!strcmp (fields[0], "pub") || !strcmp (fields[0], "sec")
+ || !strcmp (fields[0], "crt") || !strcmp (fields[0], "crs"))
+ {
+ if (kb) /* Finish the current keyblock. */
+ {
+ *keyblock_tail = kb;
+ keyblock_tail = &kb->next;
+ }
+ kb = xtrycalloc (1, sizeof *kb);
+ if (!kb)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ kb->protocol = protocol;
+ err = parse_key_record (fields, nfields, &pubkey);
+ if (err)
+ goto leave;
+ kb->keys = pubkey;
+ pubkey = NULL;
+ }
+ else if (!strcmp (fields[0], "sub") || !strcmp (fields[0], "ssb"))
+ {
+ log_assert (kb && kb->keys);
+ err = parse_key_record (fields, nfields, &pubkey);
+ if (err)
+ goto leave;
+ for (pk = kb->keys; pk->next; pk = pk->next)
+ ;
+ pk->next = pubkey;
+ pubkey = NULL;
+ }
+ else if (!strcmp (fields[0], "fpr") && nfields > 9)
+ {
+ log_assert (kb && kb->keys);
+ n = strlen (fields[9]);
+ if (n != 64 && n != 40 && n != 32)
+ {
+ log_debug ("bad length (%zu) in fpr record\n", n);
+ err = gpg_error (GPG_ERR_INV_ENGINE);
+ goto leave;
+ }
+ n /= 2;
+
+ for (pk = kb->keys; pk->next; pk = pk->next)
+ ;
+ if (pk->fprlen)
+ {
+ log_debug ("too many fpr records\n");
+ err = gpg_error (GPG_ERR_INV_ENGINE);
+ goto leave;
+ }
+ log_assert (n <= sizeof pk->fpr);
+ pk->fprlen = n;
+ if (hex2bin (fields[9], pk->fpr, n) < 0)
+ {
+ log_debug ("bad chars in fpr record\n");
+ err = gpg_error (GPG_ERR_INV_ENGINE);
+ goto leave;
+ }
+ }
+ else if (!strcmp (fields[0], "grp") && nfields > 9)
+ {
+ log_assert (kb && kb->keys);
+ n = strlen (fields[9]);
+ if (n != 2*KEYGRIP_LEN)
+ {
+ log_debug ("bad length (%zu) in grp record\n", n);
+ err = gpg_error (GPG_ERR_INV_ENGINE);
+ goto leave;
+ }
+ n /= 2;
+
+ for (pk = kb->keys; pk->next; pk = pk->next)
+ ;
+ if (pk->grip_valid)
+ {
+ log_debug ("too many grp records\n");
+ err = gpg_error (GPG_ERR_INV_ENGINE);
+ goto leave;
+ }
+ if (hex2bin (fields[9], pk->grip, KEYGRIP_LEN) < 0)
+ {
+ log_debug ("bad chars in fpr record\n");
+ err = gpg_error (GPG_ERR_INV_ENGINE);
+ goto leave;
+ }
+ pk->grip_valid = 1;
+ if (!memcmp (pk->grip, keygrip, KEYGRIP_LEN))
+ pk->requested = 1;
+ }
+ else if (!strcmp (fields[0], "uid") && nfields > 9)
+ {
+ userid_t uid, u;
+
+ uid = xtrycalloc (1, sizeof *uid);
+ if (!uid)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ uid->value = decode_c_string (fields[9]);
+ if (!uid->value)
+ {
+ err = gpg_error_from_syserror ();
+ xfree (uid);
+ goto leave;
+ }
+ if (!kb->uids)
+ kb->uids = uid;
+ else
+ {
+ for (u = kb->uids; u->next; u = u->next)
+ ;
+ u->next = uid;
+ }
+ }
+ }
+ if (len < 0 || es_ferror (listing))
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("error reading memory stream\n");
+ goto leave;
+ }
+
+ if (kb) /* Finish the current keyblock. */
+ {
+ *keyblock_tail = kb;
+ keyblock_tail = &kb->next;
+ kb = NULL;
+ }
+
+ if (!keyblock_head)
+ err = gpg_error (GPG_ERR_NO_PUBKEY);
+
+ leave:
+ if (err)
+ release_keyblock (keyblock_head);
+ else
+ *r_keyblock = keyblock_head;
+ xfree (kb);
+ xfree (fields);
+ es_free (line);
+ xfree (argv);
+ es_fclose (listing);
+ return err;
+}
+
+
+void
+dump_keyblock (keyblock_t keyblock)
+{
+ keyblock_t kb;
+ pubkey_t pubkey;
+ userid_t uid;
+
+ for (kb = keyblock; kb; kb = kb->next)
+ {
+ log_info ("%s key:\n",
+ kb->protocol == GNUPG_PROTOCOL_OPENPGP? "OpenPGP":"X.509");
+ for (pubkey = kb->keys; pubkey; pubkey = pubkey->next)
+ {
+ log_info (" grip: ");
+ if (pubkey->grip_valid)
+ log_printhex (pubkey->grip, KEYGRIP_LEN, NULL);
+ log_printf ("%s\n", pubkey->requested? " (*)":"");
+
+ log_info (" fpr: ");
+ log_printhex (pubkey->fpr, pubkey->fprlen, "");
+ }
+ for (uid = kb->uids; uid; uid = uid->next)
+ {
+ log_info (" uid: %s\n", uid->value);
+ }
+ }
+}
+
+
+
+gpg_error_t
+test_get_matching_keys (const char *hexgrip)
+{
+ gpg_error_t err;
+ unsigned char grip[KEYGRIP_LEN];
+ keyblock_t keyblock;
+
+ if (strlen (hexgrip) != 40)
+ {
+ log_error ("error: invalid keygrip\n");
+ return 0;
+ }
+ if (hex2bin (hexgrip, grip, sizeof grip) < 0)
+ {
+ log_error ("error: bad kegrip\n");
+ return 0;
+ }
+ err = get_matching_keys (grip,
+ (GNUPG_PROTOCOL_OPENPGP | GNUPG_PROTOCOL_CMS),
+ &keyblock);
+ if (err)
+ {
+ log_error ("get_matching_keys failed: %s\n", gpg_strerror (err));
+ return err;
+ }
+
+ dump_keyblock (keyblock);
+ release_keyblock (keyblock);
+ return 0;
+}