diff options
author | Werner Koch <[email protected]> | 2019-02-25 08:28:22 +0000 |
---|---|---|
committer | Werner Koch <[email protected]> | 2019-02-25 08:34:30 +0000 |
commit | 28de5c0ea53373c56a4405fe6b08d194682dd1de (patch) | |
tree | b720cb2577450d8f28641fada45e590d775241db /tools/card-keys.c | |
parent | agent: Fix for suggested Libgcrypt use. (diff) | |
download | gnupg-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.c | 555 |
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; +} |