card: Print matching OpenPGP and X.509 data.

* tools/card-tool-keys.c: New.
* tools/Makefile.am (gpg_card_tool_SOURCES): Add file.
* tools/card-tool.h (struct pubkey_s, pubkey_t): New.
(struct userid_s, userid_t): New.
(struct keyblock_s, keyblock_t): New.
* common/util.h (GNUPG_PROTOCOL_): New const
* tools/gpg-card-tool.c (aTest): Add temporary command.
(list_one_kinfo): Print info from gpg and gpgsm.

Signed-off-by: Werner Koch <wk@gnupg.org>
This commit is contained in:
Werner Koch 2019-01-30 15:01:34 +01:00
parent 140fda8c61
commit 833f27a6a7
No known key found for this signature in database
GPG Key ID: E3FDFF218E45B72B
5 changed files with 605 additions and 10 deletions

View File

@ -262,6 +262,13 @@ void gnupg_module_name_flush_some (void);
void gnupg_set_builddir (const char *newdir);
/* A list of constants to identify protocols. This is used by tools
* which need to distinguish between the different protocols
* implemented by GnuPG. May be used as bit flags. */
#define GNUPG_PROTOCOL_OPENPGP 1 /* The one and only (gpg). */
#define GNUPG_PROTOCOL_CMS 2 /* The core of S/MIME (gpgsm) */
#define GNUPG_PROTOCOL_SSH_AGENT 4 /* Out ssh-agent implementation */
/*-- gpgrlhelp.c --*/
void gnupg_rl_initialize (void);

View File

@ -128,6 +128,7 @@ gpg_card_tool_SOURCES = \
gpg-card-tool.c \
card-tool.h \
card-call-scd.c \
card-tool-keys.c \
card-tool-misc.c
gpg_card_tool_LDADD = ../common/libgpgrl.a $(common_libs) \

467
tools/card-tool-keys.c Normal file
View File

@ -0,0 +1,467 @@
/* card-tool-keys.c - OpenPGP and CMS related functions for gpg-card-tool
* 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 "card-tool.h"
/* Release a keyblocm object. */
void
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;
}
}
/* 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;
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;
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)
{
*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);
/* 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;
}

View File

@ -50,6 +50,41 @@ struct
#define DBG_IPC (opt.debug & DBG_IPC_VALUE)
#define DBG_EXTPROG (opt.debug & DBG_EXTPROG_VALUE)
/* The maximum length of a binary fingerprint. */
#define MAX_FINGERPRINT_LEN 32
/*
* Data structures to store keyblocks (aka certificates).
*/
struct pubkey_s
{
struct pubkey_s *next; /* The next key. */
unsigned char grip[KEYGRIP_LEN];
unsigned char fpr[MAX_FINGERPRINT_LEN];
unsigned char fprlen; /* The used length of a FPR. */
unsigned int grip_valid:1;/* The grip is valid. */
unsigned int requested: 1;/* This is the requested grip. */
};
typedef struct pubkey_s *pubkey_t;
struct userid_s
{
struct userid_s *next;
char *value; /* Malloced. */
};
typedef struct userid_s *userid_t;
struct keyblock_s
{
struct keyblock_s *next; /* Allow to link several keyblocks. */
int protocol; /* GPGME_PROTOCOL_OPENPGP or _CMS. */
pubkey_t keys; /* The key. For OpenPGP primary + list of subkeys. */
userid_t uids; /* The list of user ids. */
};
typedef struct keyblock_s *keyblock_t;
/* Enumeration of the known card application types. */
typedef enum
@ -76,9 +111,9 @@ struct key_attr
};
};
/* An object to store information pertaining to a key pair. This is
* commonly used as a linked list with all keys known for the current
* card. */
/* An object to store information pertaining to a key pair as stored
* on a card. This is commonly used as a linked list with all keys
* known for the current card. */
struct key_info_s
{
struct key_info_s *next;
@ -144,6 +179,13 @@ struct card_info_s
typedef struct card_info_s *card_info_t;
/*-- card-tool-keys.c --*/
void release_keyblock (keyblock_t keyblock);
gpg_error_t get_matching_keys (const unsigned char *keygrip, int protocol,
keyblock_t *r_keyblock);
gpg_error_t test_get_matching_keys (const char *hexgrip);
/*-- card-tool-misc.c --*/
key_info_t find_kinfo (card_info_t info, const char *keyref);

View File

@ -69,6 +69,9 @@ enum cmd_and_opt_values
oLCctype,
oLCmessages,
aTest,
oDummy
};
@ -76,6 +79,7 @@ enum cmd_and_opt_values
/* The list of commands and options. */
static ARGPARSE_OPTS opts[] = {
ARGPARSE_group (300, ("@Commands:\n ")),
ARGPARSE_c (aTest, "test", "test command"),
ARGPARSE_group (301, ("@\nOptions:\n ")),
@ -227,6 +231,10 @@ parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts)
case oLCctype: opt.lc_ctype = pargs->r.ret_str; break;
case oLCmessages: opt.lc_messages = pargs->r.ret_str; break;
case aTest:
cmd = pargs->r_opt;
break;
default: pargs->err = 2; break;
}
}
@ -292,6 +300,12 @@ main (int argc, char **argv)
/* Run the selected command. */
switch (cmd)
{
case aTest:
if (!argc)
wrong_args ("--test KEYGRIP");
err = test_get_matching_keys (*argv);
break;
default:
interactive_loop ();
err = 0;
@ -580,15 +594,25 @@ mem_is_ff (const char *mem, unsigned int memlen)
/* Helper to list a single keyref. */
static void
list_one_kinfo (key_info_t kinfo, estream_t fp)
list_one_kinfo (key_info_t firstkinfo, key_info_t kinfo, estream_t fp)
{
if (kinfo)
gpg_error_t err;
keyblock_t keyblock = NULL;
keyblock_t kb;
pubkey_t pubkey;
userid_t uid;
key_info_t ki;
const char *s;
if (firstkinfo && kinfo)
{
tty_fprintf (fp, " ");
if (mem_is_zero (kinfo->grip, sizeof kinfo->grip))
tty_fprintf (fp, "[none]\n");
else
print_keygrip (fp, kinfo->grip);
{
tty_fprintf (fp, "[none]\n");
goto leave;
}
print_keygrip (fp, kinfo->grip);
if (kinfo->fprlen && kinfo->created)
{
@ -597,9 +621,63 @@ list_one_kinfo (key_info_t kinfo, estream_t fp)
tty_fprintf (fp, " created ....: %s\n",
isotimestamp (kinfo->created));
}
err = get_matching_keys (kinfo->grip,
(GNUPG_PROTOCOL_OPENPGP | GNUPG_PROTOCOL_CMS),
&keyblock);
if (err)
{
if (gpg_err_code (err) != GPG_ERR_NO_PUBKEY)
tty_fprintf (fp, " error ......: %s\n", gpg_strerror (err));
goto leave;
}
for (kb = keyblock; kb; kb = kb->next)
{
tty_fprintf (fp, " used for ...: %s\n",
kb->protocol == GNUPG_PROTOCOL_OPENPGP? "OpenPGP" :
kb->protocol == GNUPG_PROTOCOL_CMS? "X.509" : "?");
pubkey = kb->keys;
/* If this is not the primary key print the primary key's
* fingerprint or a reference to it. */
if (kb->protocol == GNUPG_PROTOCOL_OPENPGP)
{
tty_fprintf (fp, " main key .:");
for (ki=firstkinfo; ki; ki = ki->next)
if (pubkey->grip_valid
&& !memcmp (ki->grip, pubkey->grip, KEYGRIP_LEN))
break;
if (ki)
{
/* Fixme: Replace mapping by a table lookup. */
if (!memcmp (kinfo->grip, pubkey->grip, KEYGRIP_LEN))
s = "this";
else if (!strcmp (ki->keyref, "OPENPGP.1"))
s = "Signature key";
else if (!strcmp (ki->keyref, "OPENPGP.2"))
s = "Encryption key";
else if (!strcmp (ki->keyref, "OPENPGP.3"))
s = "Authentication key";
else
s = NULL;
if (s)
tty_fprintf (fp, " <%s>\n", s);
else
tty_fprintf (fp, " <Key %s>\n", ki->keyref);
}
else
print_shax_fpr (fp, pubkey->fpr, pubkey->fprlen);
}
for (uid = kb->uids; uid; uid = uid->next)
{
print_string (fp, " user id ..: ", uid->value);
}
}
}
else
tty_fprintf (fp, " [none]\n");
leave:
release_keyblock (keyblock);
}
@ -620,7 +698,7 @@ list_all_kinfo (card_info_t info, keyinfolabel_t labels, estream_t fp)
{
tty_fprintf (fp, "%s", labels[idx].label);
kinfo = find_kinfo (info, labels[idx].keyref);
list_one_kinfo (kinfo, fp);
list_one_kinfo (info->kinfo, kinfo, fp);
if (kinfo)
kinfo->xflag = 1;
}
@ -633,7 +711,7 @@ list_all_kinfo (card_info_t info, keyinfolabel_t labels, estream_t fp)
for (i=5+strlen (kinfo->keyref); i < 18; i++)
tty_fprintf (fp, ".");
tty_fprintf (fp, ":");
list_one_kinfo (kinfo, fp);
list_one_kinfo (info->kinfo, kinfo, fp);
}
}