From 28de5c0ea53373c56a4405fe6b08d194682dd1de Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 25 Feb 2019 09:28:22 +0100 Subject: 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 --- doc/Makefile.am | 6 +- doc/card-tool.texi | 126 -- doc/gnupg.texi | 2 +- doc/gpg-card.texi | 126 ++ po/POTFILES.in | 5 +- tools/Makefile.am | 31 +- tools/card-call-scd.c | 2 +- tools/card-keys.c | 555 +++++++ tools/card-misc.c | 113 ++ tools/card-tool-keys.c | 552 ------- tools/card-tool-misc.c | 113 -- tools/card-tool-yubikey.c | 438 ----- tools/card-tool.h | 229 --- tools/card-yubikey.c | 438 +++++ tools/gpg-card-tool-w32info.rc | 51 - tools/gpg-card-tool.c | 3452 ---------------------------------------- tools/gpg-card-w32info.rc | 51 + tools/gpg-card.c | 3452 ++++++++++++++++++++++++++++++++++++++++ tools/gpg-card.h | 229 +++ 19 files changed, 4989 insertions(+), 4982 deletions(-) delete mode 100644 doc/card-tool.texi create mode 100644 doc/gpg-card.texi create mode 100644 tools/card-keys.c create mode 100644 tools/card-misc.c delete mode 100644 tools/card-tool-keys.c delete mode 100644 tools/card-tool-misc.c delete mode 100644 tools/card-tool-yubikey.c delete mode 100644 tools/card-tool.h create mode 100644 tools/card-yubikey.c delete mode 100644 tools/gpg-card-tool-w32info.rc delete mode 100644 tools/gpg-card-tool.c create mode 100644 tools/gpg-card-w32info.rc create mode 100644 tools/gpg-card.c create mode 100644 tools/gpg-card.h diff --git a/doc/Makefile.am b/doc/Makefile.am index 19b3825b1..0720dd366 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -69,7 +69,7 @@ nobase_dist_doc_DATA = FAQ DETAILS HACKING DCO TRANSLATE OpenPGP KEYSERVER \ gnupg_TEXINFOS = \ gpg.texi gpgsm.texi gpg-agent.texi scdaemon.texi instguide.texi \ tools.texi debugging.texi glossary.texi contrib.texi gpl.texi \ - sysnotes.texi dirmngr.texi wks.texi card-tool.texi \ + sysnotes.texi dirmngr.texi wks.texi gpg-card.texi \ gnupg-module-overview.svg \ gnupg-card-architecture.fig \ howtos.texi howto-create-a-server-cert.texi @@ -90,12 +90,12 @@ YAT2M_OPTIONS = -I $(srcdir) \ myman_sources = gnupg7.texi gpg.texi gpgsm.texi gpg-agent.texi \ dirmngr.texi scdaemon.texi tools.texi wks.texi \ - card-tool.texi + gpg-card.texi myman_pages = gpgsm.1 gpg-agent.1 dirmngr.8 scdaemon.1 \ watchgnupg.1 gpgconf.1 addgnupghome.8 gpg-preset-passphrase.1 \ gpg-connect-agent.1 gpgparsemail.1 symcryptrun.1 gpgtar.1 \ applygnupgdefaults.8 gpg-wks-client.1 gpg-wks-server.1 \ - dirmngr-client.1 gpg-card-tool.1 + dirmngr-client.1 gpg-card.1 if USE_GPG2_HACK myman_pages += gpg2.1 gpgv2.1 else diff --git a/doc/card-tool.texi b/doc/card-tool.texi deleted file mode 100644 index b84f20721..000000000 --- a/doc/card-tool.texi +++ /dev/null @@ -1,126 +0,0 @@ -@c card-tool.texi - man page for gpg-card-tool -@c Copyright (C) 2019 g10 Code GmbH -@c This is part of the GnuPG manual. -@c For copying conditions, see the file GnuPG.texi. - -@include defs.inc - -@node Smart Card Tool -@chapter Smart Card Tool - -GnuPG comes with tool to administrate smart cards and USB tokens. This -tool is an extension of the @option{--edit-key} command available with -@command{gpg}. - -@menu -* gpg-card-tool:: Administrate smart cards. -@end menu - -@c -@c GPG-CARD-TOOL -@c -@manpage gpg-card-tool.1 -@node gpg-card-tool -@section Administrate smart cards. -@ifset manverb -.B gpg-card-tool -\- Administrate Smart Cards -@end ifset - -@mansect synopsis -@ifset manverb -.B gpg-card-tool -.RI [ options ] -.br -.B gpg-card-tool -.RI [ options ] -.I command -.RI { -.B -- -.I command -.RI } -@end ifset - -@mansect description -The @command{gpg-card-tool} is used to administrate smart cards and USB -tokens. It provides a superset of features from @command{gpg ---card-edit} an can be considered a frontend to @command{scdaemon} -which is a daemon started by @command{gpg-agent} to handle smart -cards. - -If @command{gpg-card-tool} is invoked without commands an interactive -mode is used. - -If @command{gpg-card-tool} is invoked with one or more commands the -same commands as available in the interactive mode are run from the -command line. These commands need to be delimited with a double-dash. -If a double-dash or a shell specific character is required as part of -a command the entire command needs to be put in quotes. If one of -those commands returns an error the remaining commands are mot anymore -run unless the command was prefixed with a single dash. - -A list of commands is available by using the command @code{help} and a -detailed description of each command is printed by using @code{help -COMMAND}. - - -@mansect options -@noindent -@command{gpg-card-tool} understands these options: - -@table @gnupgtabopt - -@item --with-colons -@opindex with-colons -This option has currently no effect. - -@item --status-fd @var{n} -@opindex status-fd -Write special status strings to the file descriptor @var{n}. This -program returns only the status messages SUCCESS or FAILURE which are -helpful when the caller uses a double fork approach and can't easily -get the return code of the process. - -@item --verbose -@opindex verbose -Enable extra informational output. - -@item --quiet -@opindex quiet -Disable almost all informational output. - -@item --version -@opindex version -Print version of the program and exit. - -@item --help -@opindex help -Display a brief help page and exit. - -@item --no-autostart -@opindex no-autostart -Do not start the gpg-agent if it has not yet been started and its -service is required. This option is mostly useful on machines where -the connection to gpg-agent has been redirected to another machines. - -@item --agent-program @var{file} -@opindex agent-program -Specify the agent program to be started if none is running. The -default value is determined by running @command{gpgconf} with the -option @option{--list-dirs}. - -@item --gpg-program @var{file} -@opindex gpg-program -Specify a non-default gpg binary to be used by certain commands. - -@item --gpgsm-program @var{file} -@opindex gpgsm-program -Specify a non-default gpgsm binary to be used by certain commands. - -@end table - - -@mansect see also -@ifset isman -@command{scdaemon}(1) -@end ifset diff --git a/doc/gnupg.texi b/doc/gnupg.texi index 6210486a0..78d4669da 100644 --- a/doc/gnupg.texi +++ b/doc/gnupg.texi @@ -187,7 +187,7 @@ the administration and the architecture. @cindex trust values @include trust-values.texi -@include card-tool.texi +@include gpg-card.texi @include tools.texi @include wks.texi diff --git a/doc/gpg-card.texi b/doc/gpg-card.texi new file mode 100644 index 000000000..92bc12362 --- /dev/null +++ b/doc/gpg-card.texi @@ -0,0 +1,126 @@ +@c card-tool.texi - man page for gpg-card-tool +@c Copyright (C) 2019 g10 Code GmbH +@c This is part of the GnuPG manual. +@c For copying conditions, see the file GnuPG.texi. + +@include defs.inc + +@node Smart Card Tool +@chapter Smart Card Tool + +GnuPG comes with tool to administrate smart cards and USB tokens. This +tool is an extension of the @option{--edit-key} command available with +@command{gpg}. + +@menu +* gpg-card:: Administrate smart cards. +@end menu + +@c +@c GPG-CARD-TOOL +@c +@manpage gpg-card.1 +@node gpg-card +@section Administrate smart cards. +@ifset manverb +.B gpg-card +\- Administrate Smart Cards +@end ifset + +@mansect synopsis +@ifset manverb +.B gpg-card +.RI [ options ] +.br +.B gpg-card +.RI [ options ] +.I command +.RI { +.B -- +.I command +.RI } +@end ifset + +@mansect description +The @command{gpg-card} is used to administrate smart cards and USB +tokens. It provides a superset of features from @command{gpg +--card-edit} an can be considered a frontend to @command{scdaemon} +which is a daemon started by @command{gpg-agent} to handle smart +cards. + +If @command{gpg-card} is invoked without commands an interactive +mode is used. + +If @command{gpg-card} is invoked with one or more commands the +same commands as available in the interactive mode are run from the +command line. These commands need to be delimited with a double-dash. +If a double-dash or a shell specific character is required as part of +a command the entire command needs to be put in quotes. If one of +those commands returns an error the remaining commands are mot anymore +run unless the command was prefixed with a single dash. + +A list of commands is available by using the command @code{help} and a +detailed description of each command is printed by using @code{help +COMMAND}. + + +@mansect options +@noindent +@command{gpg-card} understands these options: + +@table @gnupgtabopt + +@item --with-colons +@opindex with-colons +This option has currently no effect. + +@item --status-fd @var{n} +@opindex status-fd +Write special status strings to the file descriptor @var{n}. This +program returns only the status messages SUCCESS or FAILURE which are +helpful when the caller uses a double fork approach and can't easily +get the return code of the process. + +@item --verbose +@opindex verbose +Enable extra informational output. + +@item --quiet +@opindex quiet +Disable almost all informational output. + +@item --version +@opindex version +Print version of the program and exit. + +@item --help +@opindex help +Display a brief help page and exit. + +@item --no-autostart +@opindex no-autostart +Do not start the gpg-agent if it has not yet been started and its +service is required. This option is mostly useful on machines where +the connection to gpg-agent has been redirected to another machines. + +@item --agent-program @var{file} +@opindex agent-program +Specify the agent program to be started if none is running. The +default value is determined by running @command{gpgconf} with the +option @option{--list-dirs}. + +@item --gpg-program @var{file} +@opindex gpg-program +Specify a non-default gpg binary to be used by certain commands. + +@item --gpgsm-program @var{file} +@opindex gpgsm-program +Specify a non-default gpgsm binary to be used by certain commands. + +@end table + + +@mansect see also +@ifset isman +@command{scdaemon}(1) +@end ifset diff --git a/po/POTFILES.in b/po/POTFILES.in index 53a7dc8a5..da5581168 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -127,5 +127,8 @@ tools/gpgconf.c tools/no-libgcrypt.c tools/symcryptrun.c tools/gpg-check-pattern.c -tools/gpg-card-tool.c +tools/gpg-card.c +tools/card-misc.c +tools/card-keys.c +tools/card-yubikey.c tools/card-call-scd.c diff --git a/tools/Makefile.am b/tools/Makefile.am index 69f4098ca..fb37c05e7 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -22,14 +22,14 @@ EXTRA_DIST = \ lspgpot mail-signed-keys convert-from-106 sockprox.c \ ccidmon.c ChangeLog-2011 \ gpg-connect-agent-w32info.rc \ - gpg-card-tool-w32info.rc + gpg-card-w32info.rc AM_CPPFLAGS = include $(top_srcdir)/am/cmacros.am if HAVE_W32_SYSTEM gpg_connect_agent_rc_objs = gpg-connect-agent-w32info.o -gpg_card_tool_rc_objs = gpg-card-tool-w32info.o +gpg_card_tool_rc_objs = gpg-card-w32info.o resource_objs += $(gpg_connect_agent_rc_objs) $(gpg_card_tool_rc_objs) endif @@ -51,7 +51,7 @@ endif libexec_PROGRAMS = gpg-wks-client gpg-pair-tool -bin_PROGRAMS = gpgconf gpg-connect-agent gpg-card-tool ${symcryptrun} +bin_PROGRAMS = gpgconf gpg-connect-agent gpg-card ${symcryptrun} if !HAVE_W32_SYSTEM bin_PROGRAMS += watchgnupg gpgparsemail ${gpg_wks_server} endif @@ -124,19 +124,20 @@ gpg_connect_agent_LDADD = ../common/libgpgrl.a $(common_libs) \ $(gpg_connect_agent_rc_objs) -gpg_card_tool_SOURCES = \ - gpg-card-tool.c \ - card-tool.h \ +gpg_card_SOURCES = \ + gpg-card.c \ + gpg-card.h \ card-call-scd.c \ - card-tool-keys.c \ - card-tool-yubikey.c \ - card-tool-misc.c - -gpg_card_tool_LDADD = ../common/libgpgrl.a $(common_libs) \ - $(LIBASSUAN_LIBS) $(LIBGCRYPT_LIBS) \ - $(GPG_ERROR_LIBS) \ - $(LIBREADLINE) $(LIBINTL) $(NETLIBS) $(LIBICONV) \ - $(gpg_card_tool_rc_objs) + card-keys.c \ + card-yubikey.c \ + card-misc.c + +gpg_card_LDADD = \ + ../common/libgpgrl.a $(common_libs) \ + $(LIBASSUAN_LIBS) $(LIBGCRYPT_LIBS) \ + $(GPG_ERROR_LIBS) \ + $(LIBREADLINE) $(LIBINTL) $(NETLIBS) $(LIBICONV) \ + $(gpg_card_tool_rc_objs) if !DISABLE_REGEX diff --git a/tools/card-call-scd.c b/tools/card-call-scd.c index 83e9ba099..55ecf126e 100644 --- a/tools/card-call-scd.c +++ b/tools/card-call-scd.c @@ -39,7 +39,7 @@ #include "../common/status.h" #include "../common/host2net.h" #include "../common/openpgpdefs.h" -#include "card-tool.h" +#include "gpg-card.h" #define CONTROL_D ('D' - 'A' + 1) 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 . + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include +#include +#include +#include + +#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; +} diff --git a/tools/card-misc.c b/tools/card-misc.c new file mode 100644 index 000000000..bccdbda9d --- /dev/null +++ b/tools/card-misc.c @@ -0,0 +1,113 @@ +/* card-misc.c - Helper 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 . + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include +#include +#include +#include +#include +#include + +#include "../common/util.h" +#include "../common/i18n.h" +#include "../common/openpgpdefs.h" +#include "gpg-card.h" + +/* Return the key info object for the key KEYREF. If it is not found + * NULL is returned. */ +key_info_t +find_kinfo (card_info_t info, const char *keyref) +{ + key_info_t kinfo; + + for (kinfo = info->kinfo; kinfo; kinfo = kinfo->next) + if (!strcmp (kinfo->keyref, keyref)) + return kinfo; + return NULL; +} + + +/* Convert STRING into a newly allocated buffer while translating the + * hex numbers. Blanks and colons are allowed to separate pairs of + * hex digits. Returns NULL on error or a newly malloced buffer and + * its length in LENGTH. */ +void * +hex_to_buffer (const char *string, size_t *r_length) +{ + unsigned char *buffer; + const char *s; + size_t n; + + buffer = xtrymalloc (strlen (string)+1); + if (!buffer) + return NULL; + for (s=string, n=0; *s; s++) + { + if (ascii_isspace (*s) || *s == ':') + continue; + if (hexdigitp (s) && hexdigitp (s+1)) + { + buffer[n++] = xtoi_2 (s); + s++; + } + else + { + xfree (buffer); + gpg_err_set_errno (EINVAL); + return NULL; + } + } + *r_length = n; + return buffer; +} + + +/* Direct sending of an hex encoded APDU with error printing. This is + * a simple wrapper around scd_apdu. */ +gpg_error_t +send_apdu (const char *hexapdu, const char *desc, unsigned int ignore, + unsigned char **r_data, size_t *r_datalen) +{ + gpg_error_t err; + unsigned int sw; + + err = scd_apdu (hexapdu, &sw, r_data, r_datalen); + if (err) + log_error ("sending card command %s failed: %s\n", desc, + gpg_strerror (err)); + else if (!hexapdu || !strcmp (hexapdu, "undefined")) + ; + else if (ignore == 0xffff) + ; /* Ignore all status words. */ + else if (sw != 0x9000) + { + switch (sw) + { + case 0x6285: err = gpg_error (GPG_ERR_OBJ_TERM_STATE); break; + case 0x6982: err = gpg_error (GPG_ERR_BAD_PIN); break; + case 0x6985: err = gpg_error (GPG_ERR_USE_CONDITIONS); break; + default: err = gpg_error (GPG_ERR_CARD); + } + if (!(ignore && ignore == sw)) + log_error ("card command %s failed: %s (0x%04x)\n", desc, + gpg_strerror (err), sw); + } + return err; +} diff --git a/tools/card-tool-keys.c b/tools/card-tool-keys.c deleted file mode 100644 index 4e057ad94..000000000 --- a/tools/card-tool-keys.c +++ /dev/null @@ -1,552 +0,0 @@ -/* 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 . - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#include -#include -#include -#include - -#include "../common/util.h" -#include "../common/i18n.h" -#include "../common/ccparray.h" -#include "../common/exectool.h" -#include "../common/openpgpdefs.h" -#include "card-tool.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; - - 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; -} diff --git a/tools/card-tool-misc.c b/tools/card-tool-misc.c deleted file mode 100644 index d0fb55dab..000000000 --- a/tools/card-tool-misc.c +++ /dev/null @@ -1,113 +0,0 @@ -/* card-tool-misc.c - Helper 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 . - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#include -#include -#include -#include -#include -#include - -#include "../common/util.h" -#include "../common/i18n.h" -#include "../common/openpgpdefs.h" -#include "card-tool.h" - -/* Return the key info object for the key KEYREF. If it is not found - * NULL is returned. */ -key_info_t -find_kinfo (card_info_t info, const char *keyref) -{ - key_info_t kinfo; - - for (kinfo = info->kinfo; kinfo; kinfo = kinfo->next) - if (!strcmp (kinfo->keyref, keyref)) - return kinfo; - return NULL; -} - - -/* Convert STRING into a newly allocated buffer while translating the - * hex numbers. Blanks and colons are allowed to separate pairs of - * hex digits. Returns NULL on error or a newly malloced buffer and - * its length in LENGTH. */ -void * -hex_to_buffer (const char *string, size_t *r_length) -{ - unsigned char *buffer; - const char *s; - size_t n; - - buffer = xtrymalloc (strlen (string)+1); - if (!buffer) - return NULL; - for (s=string, n=0; *s; s++) - { - if (ascii_isspace (*s) || *s == ':') - continue; - if (hexdigitp (s) && hexdigitp (s+1)) - { - buffer[n++] = xtoi_2 (s); - s++; - } - else - { - xfree (buffer); - gpg_err_set_errno (EINVAL); - return NULL; - } - } - *r_length = n; - return buffer; -} - - -/* Direct sending of an hex encoded APDU with error printing. This is - * a simple wrapper around scd_apdu. */ -gpg_error_t -send_apdu (const char *hexapdu, const char *desc, unsigned int ignore, - unsigned char **r_data, size_t *r_datalen) -{ - gpg_error_t err; - unsigned int sw; - - err = scd_apdu (hexapdu, &sw, r_data, r_datalen); - if (err) - log_error ("sending card command %s failed: %s\n", desc, - gpg_strerror (err)); - else if (!hexapdu || !strcmp (hexapdu, "undefined")) - ; - else if (ignore == 0xffff) - ; /* Ignore all status words. */ - else if (sw != 0x9000) - { - switch (sw) - { - case 0x6285: err = gpg_error (GPG_ERR_OBJ_TERM_STATE); break; - case 0x6982: err = gpg_error (GPG_ERR_BAD_PIN); break; - case 0x6985: err = gpg_error (GPG_ERR_USE_CONDITIONS); break; - default: err = gpg_error (GPG_ERR_CARD); - } - if (!(ignore && ignore == sw)) - log_error ("card command %s failed: %s (0x%04x)\n", desc, - gpg_strerror (err), sw); - } - return err; -} diff --git a/tools/card-tool-yubikey.c b/tools/card-tool-yubikey.c deleted file mode 100644 index 996bbf041..000000000 --- a/tools/card-tool-yubikey.c +++ /dev/null @@ -1,438 +0,0 @@ -/* card-tool-yubikey.c - Yubikey specific functions. - * 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 . - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#include -#include -#include -#include -#include -#include - -#include "../common/util.h" -#include "../common/i18n.h" -#include "../common/tlv.h" -#include "../common/ttyio.h" -#include "card-tool.h" - - -/* Object to describe requested interface options. */ -struct iface_s { - unsigned int usb:1; - unsigned int nfc:1; -}; - - -/* Bit flags as used by the fields in struct ykapps_s. */ -#define YKAPP_USB_SUPPORTED 0x01 -#define YKAPP_USB_ENABLED 0x02 -#define YKAPP_NFC_SUPPORTED 0x04 -#define YKAPP_NFC_ENABLED 0x08 -#define YKAPP_SELECTED 0x80 /* Selected by the command. */ - -/* An object to describe the applications on a Yubikey. Each field - * has 8 bits to hold the above flag values. */ -struct ykapps_s { - unsigned int otp:8; - unsigned int u2f:8; - unsigned int opgp:8; - unsigned int piv:8; - unsigned int oath:8; - unsigned int fido2:8; -}; - - - -/* Helper to parse an unsigned integer config value consisting of bit - * flags. TAG select the config item and MASK is the mask ORed into - * the value for a set bit. The function modifies YK. */ -static gpg_error_t -parse_ul_config_value (struct ykapps_s *yk, - const unsigned char *config, size_t configlen, - int tag, unsigned int mask) -{ - const unsigned char *s; - size_t n; - unsigned long ul = 0; - int i; - - s = find_tlv (config, configlen, tag, &n); - if (s && n) - { - if (n > sizeof ul) - { - log_error ("too large integer in Yubikey config tag %02x detected\n", - tag); - if (opt.verbose) - log_printhex (config, configlen, "config:"); - return gpg_error (GPG_ERR_CARD); - } - for (i=0; i < n; i++) - { - ul <<=8; - ul |= s[i]; - } - if (ul & 0x01) - yk->otp |= mask; - if (ul & 0x02) - yk->u2f |= mask; - if (ul & 0x08) - yk->opgp |= mask; - if (ul & 0x10) - yk->piv |= mask; - if (ul & 0x20) - yk->oath |= mask; - if (ul & 0x200) - yk->fido2 |= mask; - } - return 0; -} - - -/* Create an unsigned integer config value for TAG from the data in YK - * and store it the provided 4 byte buffer RESULT. If ENABLE is true - * the respective APP_SELECTED bit in YK sets the corresponding bit - * flags, it is is false that bit flag is cleared. IF APP_SELECTED is - * not set the bit flag is not changed. */ -static void -set_ul_config_value (struct ykapps_s *yk, - unsigned int bitflag, int tag, unsigned int enable, - unsigned char *result) -{ - unsigned long ul = 0; - - /* First set the current values. */ - if ((yk->otp & bitflag)) - ul |= 0x01; - if ((yk->u2f & bitflag)) - ul |= 0x02; - if ((yk->opgp & bitflag)) - ul |= 0x08; - if ((yk->piv & bitflag)) - ul |= 0x10; - if ((yk->oath & bitflag)) - ul |= 0x20; - if ((yk->fido2 & bitflag)) - ul |= 0x200; - - /* Then enable or disable the bits according to the selection flag. */ - if (enable) - { - if ((yk->otp & YKAPP_SELECTED)) - ul |= 0x01; - if ((yk->u2f & YKAPP_SELECTED)) - ul |= 0x02; - if ((yk->opgp & YKAPP_SELECTED)) - ul |= 0x08; - if ((yk->piv & YKAPP_SELECTED)) - ul |= 0x10; - if ((yk->oath & YKAPP_SELECTED)) - ul |= 0x20; - if ((yk->fido2 & YKAPP_SELECTED)) - ul |= 0x200; - } - else - { - if ((yk->otp & YKAPP_SELECTED)) - ul &= ~0x01; - if ((yk->u2f & YKAPP_SELECTED)) - ul &= ~0x02; - if ((yk->opgp & YKAPP_SELECTED)) - ul &= ~0x08; - if ((yk->piv & YKAPP_SELECTED)) - ul &= ~0x10; - if ((yk->oath & YKAPP_SELECTED)) - ul &= ~0x20; - if ((yk->fido2 & YKAPP_SELECTED)) - ul &= ~0x200; - } - - /* Make sure that we do not disable the CCID transport. Without - * CCID we won't have any way to change the configuration again. We - * would instead need one of the other Yubikey tools to enable an - * application and thus its transport again. */ - if (bitflag == YKAPP_USB_ENABLED && !(ul & (0x08|0x10|0x20))) - { - log_info ("Enabling PIV to have at least one CCID transport\n"); - ul |= 0x10; - } - - result[0] = tag; - result[1] = 2; - result[2] = ul >> 8; - result[3] = ul; -} - - -/* Print the info from YK. */ -static void -yk_list (estream_t fp, struct ykapps_s *yk) -{ - if (opt.interactive) - tty_fprintf (fp, ("Application USB NFC\n" - "-----------------------\n")); - tty_fprintf (fp, "OTP %s %s\n", - (yk->otp & YKAPP_USB_SUPPORTED)? - (yk->otp & YKAPP_USB_ENABLED? "yes" : "no ") : "- ", - (yk->otp & YKAPP_NFC_SUPPORTED)? - (yk->otp & YKAPP_NFC_ENABLED? "yes" : "no ") : "- "); - tty_fprintf (fp, "U2F %s %s\n", - (yk->otp & YKAPP_USB_SUPPORTED)? - (yk->otp & YKAPP_USB_ENABLED? "yes" : "no ") : "- ", - (yk->otp & YKAPP_NFC_SUPPORTED)? - (yk->otp & YKAPP_NFC_ENABLED? "yes" : "no ") : "- "); - tty_fprintf (fp, "OPGP %s %s\n", - (yk->opgp & YKAPP_USB_SUPPORTED)? - (yk->opgp & YKAPP_USB_ENABLED? "yes" : "no ") : "- ", - (yk->opgp & YKAPP_NFC_SUPPORTED)? - (yk->opgp & YKAPP_NFC_ENABLED? "yes" : "no ") : "- "); - tty_fprintf (fp, "PIV %s %s\n", - (yk->piv & YKAPP_USB_SUPPORTED)? - (yk->piv & YKAPP_USB_ENABLED? "yes" : "no ") : "- ", - (yk->piv & YKAPP_NFC_SUPPORTED)? - (yk->piv & YKAPP_NFC_ENABLED? "yes" : "no ") : "- "); - tty_fprintf (fp, "OATH %s %s\n", - (yk->oath & YKAPP_USB_SUPPORTED)? - (yk->oath & YKAPP_USB_ENABLED? "yes" : "no ") : "- ", - (yk->oath & YKAPP_NFC_SUPPORTED)? - (yk->oath & YKAPP_NFC_ENABLED? "yes" : "no ") : "- "); - tty_fprintf (fp, "FIDO2 %s %s\n", - (yk->fido2 & YKAPP_USB_SUPPORTED)? - (yk->fido2 & YKAPP_USB_ENABLED? "yes" : "no ") : "- ", - (yk->fido2 & YKAPP_NFC_SUPPORTED)? - (yk->fido2 & YKAPP_NFC_ENABLED? "yes" : "no ") : "- "); -} - - -/* Enable disable the apps as marked in YK with flag YKAPP_SELECTED. */ -static gpg_error_t -yk_enable_disable (struct ykapps_s *yk, struct iface_s *iface, - const unsigned char *config, size_t configlen, int enable) -{ - gpg_error_t err = 0; - unsigned char apdu[100]; - unsigned int apdulen; - /* const unsigned char *s; */ - /* size_t n; */ - char *hexapdu = NULL; - - apdulen = 0; - apdu[apdulen++] = 0x00; - apdu[apdulen++] = 0x1c; /* Write Config instruction. */ - apdu[apdulen++] = 0x00; - apdu[apdulen++] = 0x00; - apdu[apdulen++] = 0x00; /* Lc will be fixed up later. */ - apdu[apdulen++] = 0x00; /* Length of data will also be fixed up later. */ - - /* The ykman tool has no way to set NFC and USB flags in one go. - * Reasoning about the Yubikey's firmware it seems plausible that - * combining should work. Let's try it here if the user called for - * setting both interfaces. */ - if (iface->nfc) - { - set_ul_config_value (yk, YKAPP_NFC_ENABLED, 0x0e, enable, apdu+apdulen); - apdulen += 4; - } - if (iface->usb) - { - set_ul_config_value (yk, YKAPP_USB_ENABLED, 0x03, enable, apdu+apdulen); - apdulen += 4; - /* Yubikey's ykman copies parts of the config data when writing - * the config for USB. Below is a commented example on how that - * can be done. */ - (void)config; - (void)configlen; - /* Copy the device flags. */ - /* s = find_tlv (config, configlen, 0x08, &n); */ - /* if (s && n) */ - /* { */ - /* s -= 2; */ - /* n += 2; */ - /* if (apdulen + n > sizeof apdu) */ - /* { */ - /* err = gpg_error (GPG_ERR_BUFFER_TOO_SHORT); */ - /* goto leave; */ - /* } */ - /* memcpy (apdu+apdulen, s, n); */ - /* apdulen += n; */ - /* } */ - } - if (iface->nfc || iface->usb) - { - if (apdulen + 2 > sizeof apdu) - { - err = gpg_error (GPG_ERR_BUFFER_TOO_SHORT); - goto leave; - } - /* Disable the next two lines to let the card reboot. Not doing - * this is however more convenient for this tool because further - * commands don't end up with an error. It seems to be better - * that a "reset" command from gpg-card-tool is run at the - * user's discretion. */ - /* apdu[apdulen++] = 0x0c; /\* Reboot tag *\/ */ - /* apdu[apdulen++] = 0; /\* No data for reboot. *\/ */ - /* Fixup the lngth bytes. */ - apdu[4] = apdulen - 6 + 1; - apdu[5] = apdulen - 6; - - hexapdu = bin2hex (apdu, apdulen, NULL); - if (!hexapdu) - err = gpg_error_from_syserror (); - else - err = send_apdu (hexapdu, "YK.write_config", 0, NULL, NULL); - } - - leave: - xfree (hexapdu); - return err; -} - - -/* Implementation part of cmd_yubikey. ARGV is an array of size ARGc - * with the argumets given to the yubikey command. Note that ARGV has - * no terminating NULL so that ARGC must be considred. FP is the - * stream to output information. This function must only be called on - * Yubikeys. */ -gpg_error_t -yubikey_commands (estream_t fp, int argc, char *argv[]) -{ - gpg_error_t err; - enum {ykLIST, ykENABLE, ykDISABLE } cmd; - struct iface_s iface = {0,0}; - struct ykapps_s ykapps = {0}; - unsigned char *config = NULL; - size_t configlen; - int i; - - if (!argc) - return gpg_error (GPG_ERR_SYNTAX); - - /* Parse command. */ - if (!ascii_strcasecmp (argv[0], "list")) - cmd = ykLIST; - else if (!ascii_strcasecmp (argv[0], "enable")) - cmd = ykENABLE; - else if (!ascii_strcasecmp (argv[0], "disable")) - cmd = ykDISABLE; - else - { - err = gpg_error (GPG_ERR_UNKNOWN_COMMAND); - goto leave; - } - - /* Parse interface if needed. */ - if (cmd == ykLIST) - iface.usb = iface.nfc = 1; - else if (argc < 2) - { - err = gpg_error (GPG_ERR_SYNTAX); - goto leave; - } - else if (!ascii_strcasecmp (argv[1], "usb")) - iface.usb = 1; - else if (!ascii_strcasecmp (argv[1], "nfc")) - iface.nfc = 1; - else if (!ascii_strcasecmp (argv[1], "all") || !strcmp (argv[1], "*")) - iface.usb = iface.nfc = 1; - else - { - err = gpg_error (GPG_ERR_SYNTAX); - goto leave; - } - - /* Parse list of applications. */ - for (i=2; i < argc; i++) - { - if (!ascii_strcasecmp (argv[i], "otp")) - ykapps.otp = 0x80; - else if (!ascii_strcasecmp (argv[i], "u2f")) - ykapps.u2f = 0x80; - else if (!ascii_strcasecmp (argv[i], "opgp") - ||!ascii_strcasecmp (argv[i], "openpgp")) - ykapps.opgp = 0x80; - else if (!ascii_strcasecmp (argv[i], "piv")) - ykapps.piv = 0x80; - else if (!ascii_strcasecmp (argv[i], "oath") - || !ascii_strcasecmp (argv[i], "oauth")) - ykapps.oath = 0x80; - else if (!ascii_strcasecmp (argv[i], "fido2")) - ykapps.fido2 = 0x80; - else if (!ascii_strcasecmp (argv[i], "all")|| !strcmp (argv[i], "*")) - { - ykapps.otp = ykapps.u2f = ykapps.opgp = ykapps.piv = ykapps.oath - = ykapps.fido2 = 0x80; - } - else - { - err = gpg_error (GPG_ERR_SYNTAX); - goto leave; - } - } - - /* Select the Yubikey Manager application. */ - err = send_apdu ("00A4040008a000000527471117", "Select.YK-Manager", 0, - NULL, NULL); - if (err) - goto leave; - /* Send the read config command. */ - err = send_apdu ("001D000000", "YK.read_config", 0, &config, &configlen); - if (err) - goto leave; - if (!configlen || *config > configlen - 1) - { - /* The length byte is shorter than the actual length. */ - log_error ("Yubikey returned improper config data\n"); - log_printhex (config, configlen, "config:"); - err = gpg_error (GPG_ERR_CARD); - goto leave; - } - if (configlen-1 > *config) - { - log_info ("Extra config data ignored\n"); - log_printhex (config, configlen, "config:"); - } - configlen = *config; - - err = parse_ul_config_value (&ykapps, config+1, configlen, - 0x01, YKAPP_USB_SUPPORTED); - if (!err) - err = parse_ul_config_value (&ykapps, config+1, configlen, - 0x03, YKAPP_USB_ENABLED); - if (!err) - err = parse_ul_config_value (&ykapps, config+1, configlen, - 0x0d, YKAPP_NFC_SUPPORTED); - if (!err) - err = parse_ul_config_value (&ykapps, config+1, configlen, - 0x0e, YKAPP_NFC_ENABLED); - if (err) - goto leave; - - switch (cmd) - { - case ykLIST: yk_list (fp, &ykapps); break; - case ykENABLE: err = yk_enable_disable (&ykapps, &iface, - config+1, configlen, 1); break; - case ykDISABLE: err = yk_enable_disable (&ykapps, &iface, - config+1, configlen, 0); break; - } - - leave: - xfree (config); - return err; -} diff --git a/tools/card-tool.h b/tools/card-tool.h deleted file mode 100644 index 5598ae5fd..000000000 --- a/tools/card-tool.h +++ /dev/null @@ -1,229 +0,0 @@ -/* card-tool.h - Common definitions for the gpg-card-tool - * Copyright (C) 2019 g10 Code GmbH - * - * This file is part of GnuPG. - * - * This file 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. - * - * This file 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 Lesser 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 . - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#ifndef GNUPG_CARD_TOOL_H -#define GNUPG_CARD_TOOL_H - -#include "../common/session-env.h" - - -/* We keep all global options in the structure OPT. */ -struct -{ - int interactive; - int verbose; - unsigned int debug; - int quiet; - int with_colons; - const char *gpg_program; - const char *gpgsm_program; - const char *agent_program; - int autostart; - - /* Options passed to the gpg-agent: */ - session_env_t session_env; - char *lc_ctype; - char *lc_messages; - -} opt; - -/* Debug values and macros. */ -#define DBG_IPC_VALUE 1024 /* Debug assuan communication. */ -#define DBG_EXTPROG_VALUE 16384 /* Debug external program calls */ - -#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 - { - APP_TYPE_NONE, /* Not yet known or for direct APDU sending. */ - APP_TYPE_OPENPGP, - APP_TYPE_NKS, - APP_TYPE_DINSIG, - APP_TYPE_P15, - APP_TYPE_GELDKARTE, - APP_TYPE_SC_HSM, - APP_TYPE_PIV, - APP_TYPE_UNKNOWN /* Unknown by this tool. */ - } app_type_t; - - -/* OpenPGP card key attributes. */ -struct key_attr -{ - int algo; /* Algorithm identifier. */ - union { - unsigned int nbits; /* Supported keysize. */ - const char *curve; /* Name of curve. */ - }; -}; - -/* 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; - - unsigned char grip[20];/* The keygrip. */ - - unsigned char xflag; /* Temporary flag to help processing a list. */ - - /* The three next items are mostly useful for OpenPGP cards. */ - unsigned char fprlen; /* Use length of the next item. */ - unsigned char fpr[32]; /* The binary fingerprint of length FPRLEN. */ - u32 created; /* The time the key was created. */ - unsigned int usage; /* Usage flags. (GCRY_PK_USAGE_*) */ - char keyref[1]; /* String with the keyref (e.g. OPENPGP.1). */ -}; -typedef struct key_info_s *key_info_t; - - -/* - * The object used to store information about a card. - */ -struct card_info_s -{ - int initialized; /* True if a learn command was successful. */ - int error; /* private. */ - char *reader; /* Reader information. */ - char *cardtype; /* NULL or type of the card. */ - char *apptypestr; /* Malloced application type string. */ - app_type_t apptype;/* Translated from APPTYPESTR. */ - char *serialno; /* malloced hex string. */ - char *dispserialno;/* malloced string. */ - char *disp_name; /* malloced. */ - char *disp_lang; /* malloced. */ - int disp_sex; /* 0 = unspecified, 1 = male, 2 = female */ - char *pubkey_url; /* malloced. */ - char *login_data; /* malloced. */ - char *private_do[4]; /* malloced. */ - char cafpr1len; /* Length of the CA-fingerprint or 0 if invalid. */ - char cafpr2len; - char cafpr3len; - char cafpr1[20]; - char cafpr2[20]; - char cafpr3[20]; - key_info_t kinfo; /* Linked list with all keypair related data. */ - unsigned long sig_counter; - int chv1_cached; /* For openpgp this is true if a PIN is not - required for each signing. Note that the - gpg-agent might cache it anyway. */ - int is_v2; /* True if this is a v2 openpgp card. */ - int chvmaxlen[3]; /* Maximum allowed length of a CHV. */ - int chvinfo[3]; /* Allowed retries for the CHV; 0 = blocked. */ - unsigned char chvusage[2]; /* Data object 5F2F */ - struct key_attr key_attr[3]; /* OpenPGP card key attributes. */ - struct { - unsigned int ki:1; /* Key import available. */ - unsigned int aac:1; /* Algorithm attributes are changeable. */ - unsigned int kdf:1; /* KDF object to support PIN hashing available. */ - unsigned int bt:1; /* Button for confirmation available. */ - } extcap; - unsigned int status_indicator; - int kdf_do_enabled; /* True if card has a KDF object. */ - int uif[3]; /* True if User Interaction Flag is on. */ -}; -typedef struct card_info_s *card_info_t; - - -/*-- card-tool-keys.c --*/ -void release_keyblock (keyblock_t keyblock); -void flush_keyblock_cache (void); -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); -void *hex_to_buffer (const char *string, size_t *r_length); -gpg_error_t send_apdu (const char *hexapdu, const char *desc, - unsigned int ignore, - unsigned char **r_data, size_t *r_datalen); - -/*-- card-call-scd.c --*/ -void release_card_info (card_info_t info); -const char *app_type_string (app_type_t app_type); - -gpg_error_t scd_apdu (const char *hexapdu, unsigned int *r_sw, - unsigned char **r_data, size_t *r_datalen); -gpg_error_t scd_learn (card_info_t info); -gpg_error_t scd_getattr (const char *name, struct card_info_s *info); -gpg_error_t scd_setattr (const char *name, - const unsigned char *value, size_t valuelen); -gpg_error_t scd_writecert (const char *certidstr, - const unsigned char *certdata, size_t certdatalen); -gpg_error_t scd_writekey (int keyno, - const unsigned char *keydata, size_t keydatalen); -gpg_error_t scd_genkey (const char *keyref, int force, const char *algo, - u32 *createtime); -gpg_error_t scd_serialno (char **r_serialno, const char *demand); -gpg_error_t scd_readcert (const char *certidstr, - void **r_buf, size_t *r_buflen); -gpg_error_t scd_readkey (const char *keyrefstr, gcry_sexp_t *r_result); -gpg_error_t scd_cardlist (strlist_t *result); -gpg_error_t scd_change_pin (const char *pinref, int reset_mode); -gpg_error_t scd_checkpin (const char *serialno); - -unsigned long agent_get_s2k_count (void); - -/*-- card-tool-yubikey.c --*/ -gpg_error_t yubikey_commands (estream_t fp, int argc, char *argv[]); - - -#endif /*GNUPG_CARD_TOOL_H*/ diff --git a/tools/card-yubikey.c b/tools/card-yubikey.c new file mode 100644 index 000000000..f9d130988 --- /dev/null +++ b/tools/card-yubikey.c @@ -0,0 +1,438 @@ +/* card-yubikey.c - Yubikey specific functions. + * 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 . + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include +#include +#include +#include +#include +#include + +#include "../common/util.h" +#include "../common/i18n.h" +#include "../common/tlv.h" +#include "../common/ttyio.h" +#include "gpg-card.h" + + +/* Object to describe requested interface options. */ +struct iface_s { + unsigned int usb:1; + unsigned int nfc:1; +}; + + +/* Bit flags as used by the fields in struct ykapps_s. */ +#define YKAPP_USB_SUPPORTED 0x01 +#define YKAPP_USB_ENABLED 0x02 +#define YKAPP_NFC_SUPPORTED 0x04 +#define YKAPP_NFC_ENABLED 0x08 +#define YKAPP_SELECTED 0x80 /* Selected by the command. */ + +/* An object to describe the applications on a Yubikey. Each field + * has 8 bits to hold the above flag values. */ +struct ykapps_s { + unsigned int otp:8; + unsigned int u2f:8; + unsigned int opgp:8; + unsigned int piv:8; + unsigned int oath:8; + unsigned int fido2:8; +}; + + + +/* Helper to parse an unsigned integer config value consisting of bit + * flags. TAG select the config item and MASK is the mask ORed into + * the value for a set bit. The function modifies YK. */ +static gpg_error_t +parse_ul_config_value (struct ykapps_s *yk, + const unsigned char *config, size_t configlen, + int tag, unsigned int mask) +{ + const unsigned char *s; + size_t n; + unsigned long ul = 0; + int i; + + s = find_tlv (config, configlen, tag, &n); + if (s && n) + { + if (n > sizeof ul) + { + log_error ("too large integer in Yubikey config tag %02x detected\n", + tag); + if (opt.verbose) + log_printhex (config, configlen, "config:"); + return gpg_error (GPG_ERR_CARD); + } + for (i=0; i < n; i++) + { + ul <<=8; + ul |= s[i]; + } + if (ul & 0x01) + yk->otp |= mask; + if (ul & 0x02) + yk->u2f |= mask; + if (ul & 0x08) + yk->opgp |= mask; + if (ul & 0x10) + yk->piv |= mask; + if (ul & 0x20) + yk->oath |= mask; + if (ul & 0x200) + yk->fido2 |= mask; + } + return 0; +} + + +/* Create an unsigned integer config value for TAG from the data in YK + * and store it the provided 4 byte buffer RESULT. If ENABLE is true + * the respective APP_SELECTED bit in YK sets the corresponding bit + * flags, it is is false that bit flag is cleared. IF APP_SELECTED is + * not set the bit flag is not changed. */ +static void +set_ul_config_value (struct ykapps_s *yk, + unsigned int bitflag, int tag, unsigned int enable, + unsigned char *result) +{ + unsigned long ul = 0; + + /* First set the current values. */ + if ((yk->otp & bitflag)) + ul |= 0x01; + if ((yk->u2f & bitflag)) + ul |= 0x02; + if ((yk->opgp & bitflag)) + ul |= 0x08; + if ((yk->piv & bitflag)) + ul |= 0x10; + if ((yk->oath & bitflag)) + ul |= 0x20; + if ((yk->fido2 & bitflag)) + ul |= 0x200; + + /* Then enable or disable the bits according to the selection flag. */ + if (enable) + { + if ((yk->otp & YKAPP_SELECTED)) + ul |= 0x01; + if ((yk->u2f & YKAPP_SELECTED)) + ul |= 0x02; + if ((yk->opgp & YKAPP_SELECTED)) + ul |= 0x08; + if ((yk->piv & YKAPP_SELECTED)) + ul |= 0x10; + if ((yk->oath & YKAPP_SELECTED)) + ul |= 0x20; + if ((yk->fido2 & YKAPP_SELECTED)) + ul |= 0x200; + } + else + { + if ((yk->otp & YKAPP_SELECTED)) + ul &= ~0x01; + if ((yk->u2f & YKAPP_SELECTED)) + ul &= ~0x02; + if ((yk->opgp & YKAPP_SELECTED)) + ul &= ~0x08; + if ((yk->piv & YKAPP_SELECTED)) + ul &= ~0x10; + if ((yk->oath & YKAPP_SELECTED)) + ul &= ~0x20; + if ((yk->fido2 & YKAPP_SELECTED)) + ul &= ~0x200; + } + + /* Make sure that we do not disable the CCID transport. Without + * CCID we won't have any way to change the configuration again. We + * would instead need one of the other Yubikey tools to enable an + * application and thus its transport again. */ + if (bitflag == YKAPP_USB_ENABLED && !(ul & (0x08|0x10|0x20))) + { + log_info ("Enabling PIV to have at least one CCID transport\n"); + ul |= 0x10; + } + + result[0] = tag; + result[1] = 2; + result[2] = ul >> 8; + result[3] = ul; +} + + +/* Print the info from YK. */ +static void +yk_list (estream_t fp, struct ykapps_s *yk) +{ + if (opt.interactive) + tty_fprintf (fp, ("Application USB NFC\n" + "-----------------------\n")); + tty_fprintf (fp, "OTP %s %s\n", + (yk->otp & YKAPP_USB_SUPPORTED)? + (yk->otp & YKAPP_USB_ENABLED? "yes" : "no ") : "- ", + (yk->otp & YKAPP_NFC_SUPPORTED)? + (yk->otp & YKAPP_NFC_ENABLED? "yes" : "no ") : "- "); + tty_fprintf (fp, "U2F %s %s\n", + (yk->otp & YKAPP_USB_SUPPORTED)? + (yk->otp & YKAPP_USB_ENABLED? "yes" : "no ") : "- ", + (yk->otp & YKAPP_NFC_SUPPORTED)? + (yk->otp & YKAPP_NFC_ENABLED? "yes" : "no ") : "- "); + tty_fprintf (fp, "OPGP %s %s\n", + (yk->opgp & YKAPP_USB_SUPPORTED)? + (yk->opgp & YKAPP_USB_ENABLED? "yes" : "no ") : "- ", + (yk->opgp & YKAPP_NFC_SUPPORTED)? + (yk->opgp & YKAPP_NFC_ENABLED? "yes" : "no ") : "- "); + tty_fprintf (fp, "PIV %s %s\n", + (yk->piv & YKAPP_USB_SUPPORTED)? + (yk->piv & YKAPP_USB_ENABLED? "yes" : "no ") : "- ", + (yk->piv & YKAPP_NFC_SUPPORTED)? + (yk->piv & YKAPP_NFC_ENABLED? "yes" : "no ") : "- "); + tty_fprintf (fp, "OATH %s %s\n", + (yk->oath & YKAPP_USB_SUPPORTED)? + (yk->oath & YKAPP_USB_ENABLED? "yes" : "no ") : "- ", + (yk->oath & YKAPP_NFC_SUPPORTED)? + (yk->oath & YKAPP_NFC_ENABLED? "yes" : "no ") : "- "); + tty_fprintf (fp, "FIDO2 %s %s\n", + (yk->fido2 & YKAPP_USB_SUPPORTED)? + (yk->fido2 & YKAPP_USB_ENABLED? "yes" : "no ") : "- ", + (yk->fido2 & YKAPP_NFC_SUPPORTED)? + (yk->fido2 & YKAPP_NFC_ENABLED? "yes" : "no ") : "- "); +} + + +/* Enable disable the apps as marked in YK with flag YKAPP_SELECTED. */ +static gpg_error_t +yk_enable_disable (struct ykapps_s *yk, struct iface_s *iface, + const unsigned char *config, size_t configlen, int enable) +{ + gpg_error_t err = 0; + unsigned char apdu[100]; + unsigned int apdulen; + /* const unsigned char *s; */ + /* size_t n; */ + char *hexapdu = NULL; + + apdulen = 0; + apdu[apdulen++] = 0x00; + apdu[apdulen++] = 0x1c; /* Write Config instruction. */ + apdu[apdulen++] = 0x00; + apdu[apdulen++] = 0x00; + apdu[apdulen++] = 0x00; /* Lc will be fixed up later. */ + apdu[apdulen++] = 0x00; /* Length of data will also be fixed up later. */ + + /* The ykman tool has no way to set NFC and USB flags in one go. + * Reasoning about the Yubikey's firmware it seems plausible that + * combining should work. Let's try it here if the user called for + * setting both interfaces. */ + if (iface->nfc) + { + set_ul_config_value (yk, YKAPP_NFC_ENABLED, 0x0e, enable, apdu+apdulen); + apdulen += 4; + } + if (iface->usb) + { + set_ul_config_value (yk, YKAPP_USB_ENABLED, 0x03, enable, apdu+apdulen); + apdulen += 4; + /* Yubikey's ykman copies parts of the config data when writing + * the config for USB. Below is a commented example on how that + * can be done. */ + (void)config; + (void)configlen; + /* Copy the device flags. */ + /* s = find_tlv (config, configlen, 0x08, &n); */ + /* if (s && n) */ + /* { */ + /* s -= 2; */ + /* n += 2; */ + /* if (apdulen + n > sizeof apdu) */ + /* { */ + /* err = gpg_error (GPG_ERR_BUFFER_TOO_SHORT); */ + /* goto leave; */ + /* } */ + /* memcpy (apdu+apdulen, s, n); */ + /* apdulen += n; */ + /* } */ + } + if (iface->nfc || iface->usb) + { + if (apdulen + 2 > sizeof apdu) + { + err = gpg_error (GPG_ERR_BUFFER_TOO_SHORT); + goto leave; + } + /* Disable the next two lines to let the card reboot. Not doing + * this is however more convenient for this tool because further + * commands don't end up with an error. It seems to be better + * that a "reset" command from gpg-card-tool is run at the + * user's discretion. */ + /* apdu[apdulen++] = 0x0c; /\* Reboot tag *\/ */ + /* apdu[apdulen++] = 0; /\* No data for reboot. *\/ */ + /* Fixup the lngth bytes. */ + apdu[4] = apdulen - 6 + 1; + apdu[5] = apdulen - 6; + + hexapdu = bin2hex (apdu, apdulen, NULL); + if (!hexapdu) + err = gpg_error_from_syserror (); + else + err = send_apdu (hexapdu, "YK.write_config", 0, NULL, NULL); + } + + leave: + xfree (hexapdu); + return err; +} + + +/* Implementation part of cmd_yubikey. ARGV is an array of size ARGc + * with the argumets given to the yubikey command. Note that ARGV has + * no terminating NULL so that ARGC must be considred. FP is the + * stream to output information. This function must only be called on + * Yubikeys. */ +gpg_error_t +yubikey_commands (estream_t fp, int argc, char *argv[]) +{ + gpg_error_t err; + enum {ykLIST, ykENABLE, ykDISABLE } cmd; + struct iface_s iface = {0,0}; + struct ykapps_s ykapps = {0}; + unsigned char *config = NULL; + size_t configlen; + int i; + + if (!argc) + return gpg_error (GPG_ERR_SYNTAX); + + /* Parse command. */ + if (!ascii_strcasecmp (argv[0], "list")) + cmd = ykLIST; + else if (!ascii_strcasecmp (argv[0], "enable")) + cmd = ykENABLE; + else if (!ascii_strcasecmp (argv[0], "disable")) + cmd = ykDISABLE; + else + { + err = gpg_error (GPG_ERR_UNKNOWN_COMMAND); + goto leave; + } + + /* Parse interface if needed. */ + if (cmd == ykLIST) + iface.usb = iface.nfc = 1; + else if (argc < 2) + { + err = gpg_error (GPG_ERR_SYNTAX); + goto leave; + } + else if (!ascii_strcasecmp (argv[1], "usb")) + iface.usb = 1; + else if (!ascii_strcasecmp (argv[1], "nfc")) + iface.nfc = 1; + else if (!ascii_strcasecmp (argv[1], "all") || !strcmp (argv[1], "*")) + iface.usb = iface.nfc = 1; + else + { + err = gpg_error (GPG_ERR_SYNTAX); + goto leave; + } + + /* Parse list of applications. */ + for (i=2; i < argc; i++) + { + if (!ascii_strcasecmp (argv[i], "otp")) + ykapps.otp = 0x80; + else if (!ascii_strcasecmp (argv[i], "u2f")) + ykapps.u2f = 0x80; + else if (!ascii_strcasecmp (argv[i], "opgp") + ||!ascii_strcasecmp (argv[i], "openpgp")) + ykapps.opgp = 0x80; + else if (!ascii_strcasecmp (argv[i], "piv")) + ykapps.piv = 0x80; + else if (!ascii_strcasecmp (argv[i], "oath") + || !ascii_strcasecmp (argv[i], "oauth")) + ykapps.oath = 0x80; + else if (!ascii_strcasecmp (argv[i], "fido2")) + ykapps.fido2 = 0x80; + else if (!ascii_strcasecmp (argv[i], "all")|| !strcmp (argv[i], "*")) + { + ykapps.otp = ykapps.u2f = ykapps.opgp = ykapps.piv = ykapps.oath + = ykapps.fido2 = 0x80; + } + else + { + err = gpg_error (GPG_ERR_SYNTAX); + goto leave; + } + } + + /* Select the Yubikey Manager application. */ + err = send_apdu ("00A4040008a000000527471117", "Select.YK-Manager", 0, + NULL, NULL); + if (err) + goto leave; + /* Send the read config command. */ + err = send_apdu ("001D000000", "YK.read_config", 0, &config, &configlen); + if (err) + goto leave; + if (!configlen || *config > configlen - 1) + { + /* The length byte is shorter than the actual length. */ + log_error ("Yubikey returned improper config data\n"); + log_printhex (config, configlen, "config:"); + err = gpg_error (GPG_ERR_CARD); + goto leave; + } + if (configlen-1 > *config) + { + log_info ("Extra config data ignored\n"); + log_printhex (config, configlen, "config:"); + } + configlen = *config; + + err = parse_ul_config_value (&ykapps, config+1, configlen, + 0x01, YKAPP_USB_SUPPORTED); + if (!err) + err = parse_ul_config_value (&ykapps, config+1, configlen, + 0x03, YKAPP_USB_ENABLED); + if (!err) + err = parse_ul_config_value (&ykapps, config+1, configlen, + 0x0d, YKAPP_NFC_SUPPORTED); + if (!err) + err = parse_ul_config_value (&ykapps, config+1, configlen, + 0x0e, YKAPP_NFC_ENABLED); + if (err) + goto leave; + + switch (cmd) + { + case ykLIST: yk_list (fp, &ykapps); break; + case ykENABLE: err = yk_enable_disable (&ykapps, &iface, + config+1, configlen, 1); break; + case ykDISABLE: err = yk_enable_disable (&ykapps, &iface, + config+1, configlen, 0); break; + } + + leave: + xfree (config); + return err; +} diff --git a/tools/gpg-card-tool-w32info.rc b/tools/gpg-card-tool-w32info.rc deleted file mode 100644 index 6937c3e34..000000000 --- a/tools/gpg-card-tool-w32info.rc +++ /dev/null @@ -1,51 +0,0 @@ -/* gpg-card-toolt-w32info.rc -*- c -*- - * Copyright (C) 2019 g10 Code GmbH - * - * This file is free software; as a special exception the author gives - * unlimited permission to copy and/or distribute it, with or without - * modifications, as long as this notice is preserved. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY, to the extent permitted by law; without even the - * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - */ - -#include "afxres.h" -#include "../common/w32info-rc.h" - -1 ICON "../common/gnupg.ico" - -1 VERSIONINFO - FILEVERSION W32INFO_VI_FILEVERSION - PRODUCTVERSION W32INFO_VI_PRODUCTVERSION - FILEFLAGSMASK 0x3fL -#ifdef _DEBUG - FILEFLAGS 0x01L /* VS_FF_DEBUG (0x1)*/ -#else - FILEFLAGS 0x00L -#endif - FILEOS 0x40004L /* VOS_NT (0x40000) | VOS__WINDOWS32 (0x4) */ - FILETYPE 0x1L /* VFT_APP (0x1) */ - FILESUBTYPE 0x0L /* VFT2_UNKNOWN */ - BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "040904b0" /* US English (0409), Unicode (04b0) */ - BEGIN - VALUE "FileDescription", L"GnuPG\x2019s card tool \ -to the agent\0" - VALUE "InternalName", "gpg-card-tool\0" - VALUE "OriginalFilename", "gpg-card-tool.exe\0" - VALUE "ProductName", W32INFO_PRODUCTNAME - VALUE "ProductVersion", W32INFO_PRODUCTVERSION - VALUE "CompanyName", W32INFO_COMPANYNAME - VALUE "FileVersion", W32INFO_FILEVERSION - VALUE "LegalCopyright", W32INFO_LEGALCOPYRIGHT - VALUE "Comments", W32INFO_COMMENTS - END - END - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x409, 0x4b0 - END - END diff --git a/tools/gpg-card-tool.c b/tools/gpg-card-tool.c deleted file mode 100644 index eb723d7ab..000000000 --- a/tools/gpg-card-tool.c +++ /dev/null @@ -1,3452 +0,0 @@ -/* gpg-card-tool.c - An interactive tool to work with cards. - * Copyright (C) 2019 g10 Code GmbH - * - * This file is part of GnuPG. - * - * This file 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. - * - * This file 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 Lesser 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 . - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#include -#include -#include -#include -#ifdef HAVE_LIBREADLINE -# define GNUPG_LIBREADLINE_H_INCLUDED -# include -#endif /*HAVE_LIBREADLINE*/ - -#include "../common/util.h" -#include "../common/status.h" -#include "../common/i18n.h" -#include "../common/init.h" -#include "../common/sysutils.h" -#include "../common/asshelp.h" -#include "../common/userids.h" -#include "../common/ccparray.h" -#include "../common/exectool.h" -#include "../common/ttyio.h" -#include "../common/server-help.h" -#include "../common/openpgpdefs.h" - -#include "card-tool.h" - - -#define CONTROL_D ('D' - 'A' + 1) - -/* Constants to identify the commands and options. */ -enum opt_values - { - aNull = 0, - - oQuiet = 'q', - oVerbose = 'v', - - oDebug = 500, - - oGpgProgram, - oGpgsmProgram, - oStatusFD, - oWithColons, - oNoAutostart, - oAgentProgram, - - oDisplay, - oTTYname, - oTTYtype, - oXauthority, - oLCctype, - oLCmessages, - - oDummy - }; - - -/* The list of commands and options. */ -static ARGPARSE_OPTS opts[] = { - ARGPARSE_group (301, ("@\nOptions:\n ")), - - ARGPARSE_s_n (oVerbose, "verbose", ("verbose")), - ARGPARSE_s_n (oQuiet, "quiet", ("be somewhat more quiet")), - ARGPARSE_s_s (oDebug, "debug", "@"), - ARGPARSE_s_s (oGpgProgram, "gpg", "@"), - ARGPARSE_s_s (oGpgsmProgram, "gpgsm", "@"), - ARGPARSE_s_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")), - ARGPARSE_s_n (oWithColons, "with-colons", "@"), - ARGPARSE_s_n (oNoAutostart, "no-autostart", "@"), - ARGPARSE_s_s (oAgentProgram, "agent-program", "@"), - ARGPARSE_s_s (oDisplay, "display", "@"), - ARGPARSE_s_s (oTTYname, "ttyname", "@"), - ARGPARSE_s_s (oTTYtype, "ttytype", "@"), - ARGPARSE_s_s (oXauthority, "xauthority", "@"), - ARGPARSE_s_s (oLCctype, "lc-ctype", "@"), - ARGPARSE_s_s (oLCmessages, "lc-messages","@"), - - ARGPARSE_end () -}; - -/* The list of supported debug flags. */ -static struct debug_flags_s debug_flags [] = - { - { DBG_IPC_VALUE , "ipc" }, - { DBG_EXTPROG_VALUE, "extprog" }, - { 0, NULL } - }; - - -/* An object to create lists of labels and keyrefs. */ -struct keyinfolabel_s -{ - const char *label; - const char *keyref; -}; -typedef struct keyinfolabel_s *keyinfolabel_t; - - -/* Limit of size of data we read from a file for certain commands. */ -#define MAX_GET_DATA_FROM_FILE 16384 - -/* Constants for OpenPGP cards. */ -#define OPENPGP_USER_PIN_DEFAULT "123456" -#define OPENPGP_ADMIN_PIN_DEFAULT "12345678" -#define OPENPGP_KDF_DATA_LENGTH_MIN 90 -#define OPENPGP_KDF_DATA_LENGTH_MAX 110 - - - - -/* Local prototypes. */ -static gpg_error_t dispatch_command (card_info_t info, const char *command); -static void interactive_loop (void); -#ifdef HAVE_LIBREADLINE -static char **command_completion (const char *text, int start, int end); -#endif /*HAVE_LIBREADLINE*/ - - - -/* Print usage information and provide strings for help. */ -static const char * -my_strusage( int level ) -{ - const char *p; - - switch (level) - { - case 11: p = "gpg-card-tool"; break; - case 12: p = "@GNUPG@"; break; - case 13: p = VERSION; break; - case 17: p = PRINTABLE_OS_NAME; break; - case 19: p = ("Please report bugs to <@EMAIL@>.\n"); break; - - case 1: - case 40: - p = ("Usage: gpg-card-tool" - " [options] [{[--] command [args]}] (-h for help)"); - break; - case 41: - p = ("Syntax: gpg-card-tool" - " [options] [command [args] {-- command [args]}]\n\n" - "Tool to manage cards and tokens. With a command an interactive\n" - "mode is used. Use command \"help\" to list all commands."); - break; - - default: p = NULL; break; - } - return p; -} - - -static void -set_opt_session_env (const char *name, const char *value) -{ - gpg_error_t err; - - err = session_env_setenv (opt.session_env, name, value); - if (err) - log_fatal ("error setting session environment: %s\n", - gpg_strerror (err)); -} - - - -/* Command line parsing. */ -static void -parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts) -{ - while (optfile_parse (NULL, NULL, NULL, pargs, popts)) - { - switch (pargs->r_opt) - { - case oQuiet: opt.quiet = 1; break; - case oVerbose: opt.verbose++; break; - case oDebug: - if (parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags)) - { - pargs->r_opt = ARGPARSE_INVALID_ARG; - pargs->err = ARGPARSE_PRINT_ERROR; - } - break; - - case oGpgProgram: opt.gpg_program = pargs->r.ret_str; break; - case oGpgsmProgram: opt.gpgsm_program = pargs->r.ret_str; break; - case oAgentProgram: opt.agent_program = pargs->r.ret_str; break; - - case oStatusFD: - gnupg_set_status_fd (translate_sys2libc_fd_int (pargs->r.ret_int, 1)); - break; - - case oWithColons: opt.with_colons = 1; break; - case oNoAutostart: opt.autostart = 0; break; - - case oDisplay: set_opt_session_env ("DISPLAY", pargs->r.ret_str); break; - case oTTYname: set_opt_session_env ("GPG_TTY", pargs->r.ret_str); break; - case oTTYtype: set_opt_session_env ("TERM", pargs->r.ret_str); break; - case oXauthority: set_opt_session_env ("XAUTHORITY", - pargs->r.ret_str); break; - case oLCctype: opt.lc_ctype = pargs->r.ret_str; break; - case oLCmessages: opt.lc_messages = pargs->r.ret_str; break; - - default: pargs->err = 2; break; - } - } -} - - - -/* gpg-card-tool main. */ -int -main (int argc, char **argv) -{ - gpg_error_t err; - ARGPARSE_ARGS pargs; - char **command_list = NULL; - int cmdidx; - char *command; - - gnupg_reopen_std ("gpg-card-tool"); - set_strusage (my_strusage); - gnupg_rl_initialize (); - log_set_prefix ("gpg-card-tool", GPGRT_LOG_WITH_PREFIX); - - /* Make sure that our subsystems are ready. */ - i18n_init(); - init_common_subsystems (&argc, &argv); - - assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT); - setup_libassuan_logging (&opt.debug, NULL); - - /* Setup default options. */ - opt.autostart = 1; - opt.session_env = session_env_new (); - if (!opt.session_env) - log_fatal ("error allocating session environment block: %s\n", - gpg_strerror (gpg_error_from_syserror ())); - - - /* Parse the command line. */ - pargs.argc = &argc; - pargs.argv = &argv; - pargs.flags = ARGPARSE_FLAG_KEEP; - parse_arguments (&pargs, opts); - - if (log_get_errorcount (0)) - exit (2); - - /* Set defaults for non given options. */ - if (!opt.gpg_program) - opt.gpg_program = gnupg_module_name (GNUPG_MODULE_NAME_GPG); - if (!opt.gpgsm_program) - opt.gpgsm_program = gnupg_module_name (GNUPG_MODULE_NAME_GPGSM); - - /* Now build the list of commands. We guess the size of the array - * by assuming each item is a complete command. Obviously this will - * be rarely the case, but it is less code to allocate a possible - * too large array. */ - command_list = xcalloc (argc+1, sizeof *command_list); - cmdidx = 0; - command = NULL; - while (argc) - { - for ( ; argc && strcmp (*argv, "--"); argc--, argv++) - { - if (!command) - command = xstrdup (*argv); - else - { - char *tmp = xstrconcat (command, " ", *argv, NULL); - xfree (command); - command = tmp; - } - } - if (argc) - { /* Skip the double dash. */ - argc--; - argv++; - } - if (command) - { - command_list[cmdidx++] = command; - command = NULL; - } - } - opt.interactive = !cmdidx; - - if (opt.interactive) - { - interactive_loop (); - err = 0; - } - else - { - struct card_info_s info_buffer = { 0 }; - card_info_t info = &info_buffer; - - err = 0; - for (cmdidx=0; (command = command_list[cmdidx]); cmdidx++) - { - err = dispatch_command (info, command); - if (err) - break; - } - if (gpg_err_code (err) == GPG_ERR_EOF) - err = 0; /* This was a "quit". */ - else if (command && !opt.quiet) - log_info ("stopped at command '%s'\n", command); - } - - flush_keyblock_cache (); - if (command_list) - { - for (cmdidx=0; command_list[cmdidx]; cmdidx++) - xfree (command_list[cmdidx]); - xfree (command_list); - } - if (err) - gnupg_status_printf (STATUS_FAILURE, "- %u", err); - else if (log_get_errorcount (0)) - gnupg_status_printf (STATUS_FAILURE, "- %u", GPG_ERR_GENERAL); - else - gnupg_status_printf (STATUS_SUCCESS, NULL); - return log_get_errorcount (0)? 1:0; -} - - -/* Read data from file FNAME up to MAX_GET_DATA_FROM_FILE characters. - * On error return an error code and stores NULL at R_BUFFER; on - * success returns 0 and stores the number of bytes read at R_BUFLEN - * and the address of a newly allocated buffer at R_BUFFER. A - * complementary nul byte is always appended to the data but not - * counted; this allows to pass NULL for R-BUFFER and consider the - * returned data as a string. */ -static gpg_error_t -get_data_from_file (const char *fname, char **r_buffer, size_t *r_buflen) -{ - gpg_error_t err; - estream_t fp; - char *data; - int n; - - *r_buffer = NULL; - if (r_buflen) - *r_buflen = 0; - - fp = es_fopen (fname, "rb"); - if (!fp) - { - err = gpg_error_from_syserror (); - log_error (_("can't open '%s': %s\n"), fname, gpg_strerror (err)); - return err; - } - - data = xtrymalloc (MAX_GET_DATA_FROM_FILE); - if (!data) - { - err = gpg_error_from_syserror (); - log_error (_("error allocating enough memory: %s\n"), gpg_strerror (err)); - es_fclose (fp); - return err; - } - - n = es_fread (data, 1, MAX_GET_DATA_FROM_FILE - 1, fp); - es_fclose (fp); - if (n < 0) - { - err = gpg_error_from_syserror (); - tty_printf (_("error reading '%s': %s\n"), fname, gpg_strerror (err)); - xfree (data); - return err; - } - data[n] = 0; - - *r_buffer = data; - if (r_buflen) - *r_buflen = n; - return 0; -} - - -/* Write LENGTH bytes from BUFFER to file FNAME. Return 0 on - * success. */ -static gpg_error_t -put_data_to_file (const char *fname, const void *buffer, size_t length) -{ - gpg_error_t err; - estream_t fp; - - fp = es_fopen (fname, "wb"); - if (!fp) - { - err = gpg_error_from_syserror (); - log_error (_("can't create '%s': %s\n"), fname, gpg_strerror (err)); - return err; - } - - if (length && es_fwrite (buffer, length, 1, fp) != 1) - { - err = gpg_error_from_syserror (); - log_error (_("error writing '%s': %s\n"), fname, gpg_strerror (err)); - es_fclose (fp); - return err; - } - if (es_fclose (fp)) - { - err = gpg_error_from_syserror (); - log_error (_("error writing '%s': %s\n"), fname, gpg_strerror (err)); - return err; - } - return 0; -} - - - -/* Simply prints TEXT to the output. Returns 0 as a convenience. - * This is a separate fucntion so that it can be extended to run - * less(1) or so. The extra arguments are int values terminated by a - * 0 to indicate card application types supported with this command. - * If none are given (just teh final 0), this is a general - * command. */ -static gpg_error_t -print_help (const char *text, ...) -{ - estream_t fp; - va_list arg_ptr; - int value; - int any = 0; - - fp = opt.interactive? NULL : es_stdout; - tty_fprintf (fp, "%s\n", text); - - va_start (arg_ptr, text); - while ((value = va_arg (arg_ptr, int))) - { - if (!any) - tty_fprintf (fp, "[Supported by: "); - tty_fprintf (fp, "%s%s", any?", ":"", app_type_string (value)); - any = 1; - } - if (any) - tty_fprintf (fp, "]\n"); - - va_end (arg_ptr); - return 0; -} - - -/* Return the OpenPGP card manufacturer name. */ -static const char * -get_manufacturer (unsigned int no) -{ - /* Note: Make sure that there is no colon or linefeed in the string. */ - switch (no) - { - case 0x0001: return "PPC Card Systems"; - case 0x0002: return "Prism"; - case 0x0003: return "OpenFortress"; - case 0x0004: return "Wewid"; - case 0x0005: return "ZeitControl"; - case 0x0006: return "Yubico"; - case 0x0007: return "OpenKMS"; - case 0x0008: return "LogoEmail"; - case 0x0009: return "Fidesmo"; - case 0x000A: return "Dangerous Things"; - - case 0x002A: return "Magrathea"; - case 0x0042: return "GnuPG e.V."; - - case 0x1337: return "Warsaw Hackerspace"; - case 0x2342: return "warpzone"; /* hackerspace Muenster. */ - case 0x4354: return "Confidential Technologies"; /* cotech.de */ - case 0x63AF: return "Trustica"; - case 0xBD0E: return "Paranoidlabs"; - case 0xF517: return "FSIJ"; - - /* 0x0000 and 0xFFFF are defined as test cards per spec, - * 0xFF00 to 0xFFFE are assigned for use with randomly created - * serial numbers. */ - case 0x0000: - case 0xffff: return "test card"; - default: return (no & 0xff00) == 0xff00? "unmanaged S/N range":"unknown"; - } -} - -/* Print an (OpenPGP) fingerprint. */ -static void -print_shax_fpr (estream_t fp, const unsigned char *fpr, unsigned int fprlen) -{ - int i; - - if (fpr) - { - /* FIXME: Fix formatting for FPRLEN != 20 */ - for (i=0; i < fprlen ; i+=2, fpr += 2 ) - { - if (i == 10 ) - tty_fprintf (fp, " "); - tty_fprintf (fp, " %02X%02X", *fpr, fpr[1]); - } - } - else - tty_fprintf (fp, " [none]"); - tty_fprintf (fp, "\n"); -} - -/* Print the keygrip GRP. */ -static void -print_keygrip (estream_t fp, const unsigned char *grp) -{ - int i; - - for (i=0; i < 20 ; i++, grp++) - tty_fprintf (fp, "%02X", *grp); - tty_fprintf (fp, "\n"); -} - - -/* Print a string but avoid printing control characters. */ -static void -print_string (estream_t fp, const char *text, const char *name) -{ - tty_fprintf (fp, "%s", text); - - /* FIXME: tty_printf_utf8_string2 eats everything after and - including an @ - e.g. when printing an url. */ - if (name && *name) - { - if (fp) - print_utf8_buffer2 (fp, name, strlen (name), '\n'); - else - tty_print_utf8_string2 (NULL, name, strlen (name), 0); - } - else - tty_fprintf (fp, _("[not set]")); - tty_fprintf (fp, "\n"); -} - - -/* Print an ISO formatted name or "[not set]". */ -static void -print_isoname (estream_t fp, const char *name) -{ - if (name && *name) - { - char *p, *given, *buf; - - buf = xstrdup (name); - given = strstr (buf, "<<"); - for (p=buf; *p; p++) - if (*p == '<') - *p = ' '; - if (given && given[2]) - { - *given = 0; - given += 2; - if (fp) - print_utf8_buffer2 (fp, given, strlen (given), '\n'); - else - tty_print_utf8_string2 (NULL, given, strlen (given), 0); - - if (*buf) - tty_fprintf (fp, " "); - } - - if (fp) - print_utf8_buffer2 (fp, buf, strlen (buf), '\n'); - else - tty_print_utf8_string2 (NULL, buf, strlen (buf), 0); - - xfree (buf); - } - else - { - tty_fprintf (fp, _("[not set]")); - } - - tty_fprintf (fp, "\n"); -} - - -/* Return true if the buffer MEM of length memlen consists only of zeroes. */ -static int -mem_is_zero (const char *mem, unsigned int memlen) -{ - int i; - - for (i=0; i < memlen && !mem[i]; i++) - ; - return (i == memlen); -} - - - -/* Helper to list a single keyref. */ -static void -list_one_kinfo (key_info_t firstkinfo, key_info_t kinfo, estream_t fp) -{ - gpg_error_t err; - keyblock_t keyblock = NULL; - keyblock_t kb; - pubkey_t pubkey; - userid_t uid; - key_info_t ki; - const char *s; - gcry_sexp_t s_pkey; - int any; - - if (firstkinfo && kinfo) - { - tty_fprintf (fp, " "); - if (mem_is_zero (kinfo->grip, sizeof kinfo->grip)) - { - tty_fprintf (fp, "[none]\n"); - goto leave; - } - - print_keygrip (fp, kinfo->grip); - tty_fprintf (fp, " keyref .....: %s", kinfo->keyref); - if (kinfo->usage) - { - any = 0; - tty_fprintf (fp, " ("); - if ((kinfo->usage & GCRY_PK_USAGE_SIGN)) - { tty_fprintf (fp, "sign"); any=1; } - if ((kinfo->usage & GCRY_PK_USAGE_CERT)) - { tty_fprintf (fp, "%scert", any?",":""); any=1; } - if ((kinfo->usage & GCRY_PK_USAGE_AUTH)) - { tty_fprintf (fp, "%sauth", any?",":""); any=1; } - if ((kinfo->usage & GCRY_PK_USAGE_ENCR)) - { tty_fprintf (fp, "%sencr", any?",":""); any=1; } - tty_fprintf (fp, ")"); - } - tty_fprintf (fp, "\n"); - - if (!scd_readkey (kinfo->keyref, &s_pkey)) - { - char *tmp = pubkey_algo_string (s_pkey); - tty_fprintf (fp, " algorithm ..: %s\n", tmp); - xfree (tmp); - gcry_sexp_release (s_pkey); - s_pkey = NULL; - } - - if (kinfo->fprlen && kinfo->created) - { - tty_fprintf (fp, " fingerprint :"); - print_shax_fpr (fp, kinfo->fpr, kinfo->fprlen); - 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, " \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); -} - - -/* List all keyinfo in INFO using the list of LABELS. */ -static void -list_all_kinfo (card_info_t info, keyinfolabel_t labels, estream_t fp) -{ - key_info_t kinfo; - int idx, i; - - /* Print the keyinfo. We first print those we known and then all - * remaining item. */ - for (kinfo = info->kinfo; kinfo; kinfo = kinfo->next) - kinfo->xflag = 0; - if (labels) - { - for (idx=0; labels[idx].label; idx++) - { - tty_fprintf (fp, "%s", labels[idx].label); - kinfo = find_kinfo (info, labels[idx].keyref); - list_one_kinfo (info->kinfo, kinfo, fp); - if (kinfo) - kinfo->xflag = 1; - } - } - for (kinfo = info->kinfo; kinfo; kinfo = kinfo->next) - { - if (kinfo->xflag) - continue; - tty_fprintf (fp, "Key %s ", kinfo->keyref); - for (i=5+strlen (kinfo->keyref); i < 18; i++) - tty_fprintf (fp, "."); - tty_fprintf (fp, ":"); - list_one_kinfo (info->kinfo, kinfo, fp); - } -} - - -/* List OpenPGP card specific data. */ -static void -list_openpgp (card_info_t info, estream_t fp) -{ - static struct keyinfolabel_s keyinfolabels[] = { - { "Signature key ....:", "OPENPGP.1" }, - { "Encryption key....:", "OPENPGP.2" }, - { "Authentication key:", "OPENPGP.3" }, - { NULL, NULL } - }; - int i; - - if (!info->serialno - || strncmp (info->serialno, "D27600012401", 12) - || strlen (info->serialno) != 32 ) - { - tty_fprintf (fp, "invalid OpenPGP card\n"); - return; - } - - tty_fprintf (fp, "Version ..........: %.1s%c.%.1s%c\n", - info->serialno[12] == '0'?"":info->serialno+12, - info->serialno[13], - info->serialno[14] == '0'?"":info->serialno+14, - info->serialno[15]); - tty_fprintf (fp, "Manufacturer .....: %s\n", - get_manufacturer (xtoi_2(info->serialno+16)*256 - + xtoi_2 (info->serialno+18))); - tty_fprintf (fp, "Name of cardholder: "); - print_isoname (fp, info->disp_name); - - print_string (fp, "Language prefs ...: ", info->disp_lang); - tty_fprintf (fp, "Salutation .......: %s\n", - info->disp_sex == 1? _("Mr."): - info->disp_sex == 2? _("Mrs.") : ""); - print_string (fp, "URL of public key : ", info->pubkey_url); - print_string (fp, "Login data .......: ", info->login_data); - if (info->private_do[0]) - print_string (fp, "Private DO 1 .....: ", info->private_do[0]); - if (info->private_do[1]) - print_string (fp, "Private DO 2 .....: ", info->private_do[1]); - if (info->private_do[2]) - print_string (fp, "Private DO 3 .....: ", info->private_do[2]); - if (info->private_do[3]) - print_string (fp, "Private DO 4 .....: ", info->private_do[3]); - if (info->cafpr1len) - { - tty_fprintf (fp, "CA fingerprint %d .:", 1); - print_shax_fpr (fp, info->cafpr1, info->cafpr1len); - } - if (info->cafpr2len) - { - tty_fprintf (fp, "CA fingerprint %d .:", 2); - print_shax_fpr (fp, info->cafpr2, info->cafpr2len); - } - if (info->cafpr3len) - { - tty_fprintf (fp, "CA fingerprint %d .:", 3); - print_shax_fpr (fp, info->cafpr3, info->cafpr3len); - } - tty_fprintf (fp, "Signature PIN ....: %s\n", - info->chv1_cached? _("not forced"): _("forced")); - if (info->key_attr[0].algo) - { - tty_fprintf (fp, "Key attributes ...:"); - for (i=0; i < DIM (info->key_attr); i++) - if (info->key_attr[i].algo == PUBKEY_ALGO_RSA) - tty_fprintf (fp, " rsa%u", info->key_attr[i].nbits); - else if (info->key_attr[i].algo == PUBKEY_ALGO_ECDH - || info->key_attr[i].algo == PUBKEY_ALGO_ECDSA - || info->key_attr[i].algo == PUBKEY_ALGO_EDDSA) - { - const char *curve_for_print = "?"; - const char *oid; - - if (info->key_attr[i].curve - && (oid = openpgp_curve_to_oid (info->key_attr[i].curve, NULL))) - curve_for_print = openpgp_oid_to_curve (oid, 0); - tty_fprintf (fp, " %s", curve_for_print); - } - tty_fprintf (fp, "\n"); - } - tty_fprintf (fp, "Max. PIN lengths .: %d %d %d\n", - info->chvmaxlen[0], info->chvmaxlen[1], info->chvmaxlen[2]); - tty_fprintf (fp, "PIN retry counter : %d %d %d\n", - info->chvinfo[0], info->chvinfo[1], info->chvinfo[2]); - tty_fprintf (fp, "Signature counter : %lu\n", info->sig_counter); - if (info->extcap.kdf) - { - tty_fprintf (fp, "KDF setting ......: %s\n", - info->kdf_do_enabled ? "on" : "off"); - } - if (info->extcap.bt) - { - tty_fprintf (fp, "UIF setting ......: Sign=%s Decrypt=%s Auth=%s\n", - info->uif[0] ? "on" : "off", info->uif[1] ? "on" : "off", - info->uif[2] ? "on" : "off"); - } - - list_all_kinfo (info, keyinfolabels, fp); - - /* tty_fprintf (fp, "General key info->.: "); */ - /* thefpr = (info->fpr1len? info->fpr1 : info->fpr2len? info->fpr2 : */ - /* info->fpr3len? info->fpr3 : NULL); */ - /* thefprlen = (info->fpr1len? info->fpr1len : info->fpr2len? info->fpr2len : */ - /* info->fpr3len? info->fpr3len : 0); */ - /* If the fingerprint is all 0xff, the key has no associated - OpenPGP certificate. */ - /* if ( thefpr && !mem_is_ff (thefpr, thefprlen) */ - /* && !get_pubkey_byfprint (ctrl, pk, &keyblock, thefpr, thefprlen)) */ - /* { */ - /* print_pubkey_info (ctrl, fp, pk); */ - /* if (keyblock) */ - /* print_card_key_info (fp, keyblock); */ - /* } */ - /* else */ - /* tty_fprintf (fp, "[none]\n"); */ -} - - -/* List PIV card specific data. */ -static void -list_piv (card_info_t info, estream_t fp) -{ - static struct keyinfolabel_s keyinfolabels[] = { - { "PIV authentication:", "PIV.9A" }, - { "Card authenticat. :", "PIV.9E" }, - { "Digital signature :", "PIV.9C" }, - { "Key management ...:", "PIV.9D" }, - { NULL, NULL } - }; - const char *s; - int i; - - if (info->chvusage[0] || info->chvusage[1]) - { - tty_fprintf (fp, "PIN usage policy .:"); - if ((info->chvusage[0] & 0x40)) - tty_fprintf (fp, " app-pin"); - if ((info->chvusage[0] & 0x20)) - tty_fprintf (fp, " global-pin"); - if ((info->chvusage[0] & 0x10)) - tty_fprintf (fp, " occ"); - if ((info->chvusage[0] & 0x08)) - tty_fprintf (fp, " vci"); - if ((info->chvusage[0] & 0x08) && !(info->chvusage[0] & 0x04)) - tty_fprintf (fp, " pairing"); - - if (info->chvusage[1] == 0x10) - tty_fprintf (fp, " primary:card"); - else if (info->chvusage[1] == 0x20) - tty_fprintf (fp, " primary:global"); - - tty_fprintf (fp, "\n"); - } - - tty_fprintf (fp, "PIN retry counter :"); - for (i=0; i < DIM (info->chvinfo); i++) - { - if (info->chvinfo[i] > 0) - tty_fprintf (fp, " %d", info->chvinfo[i]); - else - { - switch (info->chvinfo[i]) - { - case -1: s = "[error]"; break; - case -2: s = "-"; break; /* No such PIN or info not available. */ - case -3: s = "[blocked]"; break; - case -5: s = "[verified]"; break; - default: s = "[?]"; break; - } - tty_fprintf (fp, " %s", s); - } - } - tty_fprintf (fp, "\n"); - list_all_kinfo (info, keyinfolabels, fp); - -} - - -/* Print all available information about the current card. */ -static void -list_card (card_info_t info) -{ - estream_t fp = opt.interactive? NULL : es_stdout; - - tty_fprintf (fp, "Reader ...........: %s\n", - info->reader? info->reader : "[none]"); - if (info->cardtype) - tty_fprintf (fp, "Card type ........: %s\n", info->cardtype); - tty_fprintf (fp, "Serial number ....: %s\n", - info->serialno? info->serialno : "[none]"); - tty_fprintf (fp, "Application type .: %s%s%s%s\n", - app_type_string (info->apptype), - info->apptype == APP_TYPE_UNKNOWN && info->apptypestr? "(":"", - info->apptype == APP_TYPE_UNKNOWN && info->apptypestr - ? info->apptypestr:"", - info->apptype == APP_TYPE_UNKNOWN && info->apptypestr? ")":""); - if (info->serialno && info->dispserialno - && strcmp (info->serialno, info->dispserialno)) - tty_fprintf (fp, "Displayed S/N ....: %s\n", info->dispserialno); - - switch (info->apptype) - { - case APP_TYPE_OPENPGP: list_openpgp (info, fp); break; - case APP_TYPE_PIV: list_piv (info, fp); break; - default: break; - } -} - - - -/* The VERIFY command. */ -static gpg_error_t -cmd_verify (card_info_t info, char *argstr) -{ - gpg_error_t err; - const char *pinref; - - if (!info) - return print_help ("verify [chvid]", 0); - - if (*argstr) - pinref = argstr; - else if (info->apptype == APP_TYPE_OPENPGP) - pinref = info->serialno; - else if (info->apptype == APP_TYPE_PIV) - pinref = "PIV.80"; - else - return gpg_error (GPG_ERR_MISSING_VALUE); - - err = scd_checkpin (pinref); - if (err) - log_error ("verify failed: %s <%s>\n", - gpg_strerror (err), gpg_strsource (err)); - return err; -} - - -static gpg_error_t -cmd_authenticate (card_info_t info, char *argstr) -{ - gpg_error_t err; - int opt_setkey; - int opt_raw; - char *string = NULL; - char *key = NULL; - size_t keylen; - - if (!info) - return print_help - ("AUTHENTICATE [--setkey] [--raw] [< FILE]|KEY\n\n" - "Perform a mutual autentication either by reading the key\n" - "from FILE or by taking it from the command line. Without\n" - "the option --raw the key is expected to be hex encoded.\n" - "To install a new administration key --setkey is used; this\n" - "requires a prior authentication with the old key.", - APP_TYPE_PIV, 0); - - if (info->apptype != APP_TYPE_PIV) - { - log_info ("Note: This is a PIV only command.\n"); - return gpg_error (GPG_ERR_NOT_SUPPORTED); - } - - opt_setkey = has_leading_option (argstr, "--setkey"); - opt_raw = has_leading_option (argstr, "--raw"); - argstr = skip_options (argstr); - - if (*argstr == '<') /* Read key from a file. */ - { - for (argstr++; spacep (argstr); argstr++) - ; - err = get_data_from_file (argstr, &string, NULL); - if (err) - goto leave; - } - - if (opt_raw) - { - key = string? string : xstrdup (argstr); - string = NULL; - keylen = strlen (key); - } - else - { - key = hex_to_buffer (string? string: argstr, &keylen); - if (!key) - { - err = gpg_error_from_syserror (); - goto leave; - } - } - err = scd_setattr (opt_setkey? "SET-ADM-KEY":"AUTH-ADM-KEY", key, keylen); - - leave: - if (key) - { - wipememory (key, keylen); - xfree (key); - } - xfree (string); - return err; -} - - -/* Helper for cmd_name to qyery a part of name. */ -static char * -ask_one_name (const char *prompt) -{ - char *name; - int i; - - for (;;) - { - name = tty_get (prompt); - trim_spaces (name); - tty_kill_prompt (); - if (!*name || *name == CONTROL_D) - { - if (*name == CONTROL_D) - tty_fprintf (NULL, "\n"); - xfree (name); - return NULL; - } - for (i=0; name[i] && name[i] >= ' ' && name[i] <= 126; i++) - ; - - /* The name must be in Latin-1 and not UTF-8 - lacking the code - * to ensure this we restrict it to ASCII. */ - if (name[i]) - tty_printf (_("Error: Only plain ASCII is currently allowed.\n")); - else if (strchr (name, '<')) - tty_printf (_("Error: The \"<\" character may not be used.\n")); - else if (strstr (name, " ")) - tty_printf (_("Error: Double spaces are not allowed.\n")); - else - return name; - xfree (name); - } -} - - -/* The NAME command. */ -static gpg_error_t -cmd_name (card_info_t info, const char *argstr) -{ - gpg_error_t err; - char *surname, *givenname; - char *isoname, *p; - - if (!info) - return print_help - ("name [--clear]\n\n" - "Set the name field of an OpenPGP card. With --clear the stored\n" - "name is cleared off the card.", APP_TYPE_OPENPGP, APP_TYPE_NKS, 0); - - if (info->apptype != APP_TYPE_OPENPGP) - { - log_info ("Note: This is an OpenPGP only command.\n"); - return gpg_error (GPG_ERR_NOT_SUPPORTED); - } - - again: - if (!strcmp (argstr, "--clear")) - isoname = xstrdup (" "); /* No real way to clear; set to space instead. */ - else - { - surname = ask_one_name (_("Cardholder's surname: ")); - givenname = ask_one_name (_("Cardholder's given name: ")); - if (!surname || !givenname || (!*surname && !*givenname)) - { - xfree (surname); - xfree (givenname); - return gpg_error (GPG_ERR_CANCELED); - } - - isoname = xstrconcat (surname, "<<", givenname, NULL); - xfree (surname); - xfree (givenname); - for (p=isoname; *p; p++) - if (*p == ' ') - *p = '<'; - - if (strlen (isoname) > 39 ) - { - log_info (_("Error: Combined name too long " - "(limit is %d characters).\n"), 39); - xfree (isoname); - goto again; - } - } - - err = scd_setattr ("DISP-NAME", isoname, strlen (isoname)); - - xfree (isoname); - return err; -} - - -static gpg_error_t -cmd_url (card_info_t info, const char *argstr) -{ - gpg_error_t err; - char *url; - - if (!info) - return print_help - ("URL [--clear]\n\n" - "Set the URL data object. That data object can be used by\n" - "the FETCH command to retrieve the full public key. The\n" - "option --clear deletes the content of that data object.", - APP_TYPE_OPENPGP, 0); - - if (info->apptype != APP_TYPE_OPENPGP) - { - log_info ("Note: This is an OpenPGP only command.\n"); - return gpg_error (GPG_ERR_NOT_SUPPORTED); - } - - if (!strcmp (argstr, "--clear")) - url = xstrdup (" "); /* No real way to clear; set to space instead. */ - else - { - url = tty_get (_("URL to retrieve public key: ")); - trim_spaces (url); - tty_kill_prompt (); - if (!*url || *url == CONTROL_D) - { - err = gpg_error (GPG_ERR_CANCELED); - goto leave; - } - } - - err = scd_setattr ("PUBKEY-URL", url, strlen (url)); - - leave: - xfree (url); - return err; -} - - -/* Fetch the key from the URL given on the card or try to get it from - * the default keyserver. */ -static gpg_error_t -cmd_fetch (card_info_t info) -{ - gpg_error_t err; - key_info_t kinfo; - - if (!info) - return print_help - ("FETCH\n\n" - "Retrieve a key using the URL data object or if that is missing\n" - "using the fingerprint.", APP_TYPE_OPENPGP, 0); - - if (info->pubkey_url && *info->pubkey_url) - { - /* strlist_t sl = NULL; */ - - /* add_to_strlist (&sl, info.pubkey_url); */ - /* err = keyserver_fetch (ctrl, sl, KEYORG_URL); */ - /* free_strlist (sl); */ - err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */ - } - else if ((kinfo = find_kinfo (info, "OPENPGP.1")) && kinfo->fprlen) - { - /* rc = keyserver_import_fprint (ctrl, info.fpr1, info.fpr1len, */ - /* opt.keyserver, 0); */ - err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */ - } - else - err = gpg_error (GPG_ERR_NO_DATA); - - return err; -} - - -static gpg_error_t -cmd_login (card_info_t info, char *argstr) -{ - gpg_error_t err; - char *data; - size_t datalen; - - if (!info) - return print_help - ("LOGIN [--clear] [< FILE]\n\n" - "Set the login data object. If FILE is given the data is\n" - "is read from that file. This allows for binary data.\n" - "The option --clear deletes the login data.", - APP_TYPE_OPENPGP, 0); - - if (!strcmp (argstr, "--clear")) - { - data = xstrdup (" "); /* kludge. */ - datalen = 1; - } - else if (*argstr == '<') /* Read it from a file */ - { - for (argstr++; spacep (argstr); argstr++) - ; - err = get_data_from_file (argstr, &data, &datalen); - if (err) - goto leave; - } - else - { - data = tty_get (_("Login data (account name): ")); - trim_spaces (data); - tty_kill_prompt (); - if (!*data || *data == CONTROL_D) - { - err = gpg_error (GPG_ERR_CANCELED); - goto leave; - } - datalen = strlen (data); - } - - err = scd_setattr ("LOGIN-DATA", data, datalen); - - leave: - xfree (data); - return err; -} - - -static gpg_error_t -cmd_lang (card_info_t info, const char *argstr) -{ - gpg_error_t err; - char *data, *p; - - if (!info) - return print_help - ("LANG [--clear]\n\n" - "Change the language info for the card. This info can be used\n" - "by applications for a personalized greeting. Up to 4 two-digit\n" - "language identifiers can be entered as a preference. The option\n" - "--clear removes all identifiers. GnuPG does not use this info.", - APP_TYPE_OPENPGP, 0); - - if (!strcmp (argstr, "--clear")) - data = xstrdup (" "); /* Note that we need two spaces here. */ - else - { - again: - data = tty_get (_("Language preferences: ")); - trim_spaces (data); - tty_kill_prompt (); - if (!*data || *data == CONTROL_D) - { - err = gpg_error (GPG_ERR_CANCELED); - goto leave; - } - - if (strlen (data) > 8 || (strlen (data) & 1)) - { - log_info (_("Error: invalid length of preference string.\n")); - xfree (data); - goto again; - } - - for (p=data; *p && *p >= 'a' && *p <= 'z'; p++) - ; - if (*p) - { - log_info (_("Error: invalid characters in preference string.\n")); - xfree (data); - goto again; - } - } - - err = scd_setattr ("DISP-LANG", data, strlen (data)); - - leave: - xfree (data); - return err; -} - - -static gpg_error_t -cmd_salut (card_info_t info, const char *argstr) -{ - gpg_error_t err; - char *data = NULL; - const char *str; - - if (!info) - return print_help - ("SALUT [--clear]\n\n" - "Change the salutation info for the card. This info can be used\n" - "by applications for a personalized greeting. The option --clear\n" - "removes this data object. GnuPG does not use this info.", - APP_TYPE_OPENPGP, 0); - - again: - if (!strcmp (argstr, "--clear")) - str = "9"; - else - { - data = tty_get (_("Salutation (M = Mr., F = Mrs., or space): ")); - trim_spaces (data); - tty_kill_prompt (); - if (*data == CONTROL_D) - { - err = gpg_error (GPG_ERR_CANCELED); - goto leave; - } - - if (!*data) - str = "9"; - else if ((*data == 'M' || *data == 'm') && !data[1]) - str = "1"; - else if ((*data == 'F' || *data == 'f') && !data[1]) - str = "2"; - else - { - tty_printf (_("Error: invalid response.\n")); - xfree (data); - goto again; - } - } - - err = scd_setattr ("DISP-SEX", str, 1); - leave: - xfree (data); - return err; -} - - -static gpg_error_t -cmd_cafpr (card_info_t info, char *argstr) -{ - gpg_error_t err; - char *data = NULL; - const char *s; - int i, c; - unsigned char fpr[32]; - int fprlen; - int fprno; - int opt_clear = 0; - - if (!info) - return print_help - ("CAFPR [--clear] N\n\n" - "Change the CA fingerprint number N. N must be in the\n" - "range 1 to 3. The option --clear clears the specified\n" - "CA fingerprint N or all of them if N is 0 or not given.", - APP_TYPE_OPENPGP, 0); - - - opt_clear = has_leading_option (argstr, "--clear"); - argstr = skip_options (argstr); - - if (digitp (argstr)) - { - fprno = atoi (argstr); - while (digitp (argstr)) - argstr++; - while (spacep (argstr)) - argstr++; - } - else - fprno = 0; - - if (opt_clear && !fprno) - ; /* Okay: clear all fprs. */ - else if (fprno < 1 || fprno > 3) - { - err = gpg_error (GPG_ERR_INV_ARG); - goto leave; - } - - again: - if (opt_clear) - { - memset (fpr, 0, 20); - fprlen = 20; - } - else - { - xfree (data); - data = tty_get (_("CA fingerprint: ")); - trim_spaces (data); - tty_kill_prompt (); - if (!*data || *data == CONTROL_D) - { - err = gpg_error (GPG_ERR_CANCELED); - goto leave; - } - - for (i=0, s=data; i < sizeof fpr && *s; ) - { - while (spacep(s)) - s++; - if (*s == ':') - s++; - while (spacep(s)) - s++; - c = hextobyte (s); - if (c == -1) - break; - fpr[i++] = c; - s += 2; - } - fprlen = i; - if ((fprlen != 20 && fprlen != 32) || *s) - { - log_error (_("Error: invalid formatted fingerprint.\n")); - goto again; - } - } - - if (!fprno) - { - log_assert (opt_clear); - err = scd_setattr ("CA-FPR-1", fpr, fprlen); - if (!err) - err = scd_setattr ("CA-FPR-2", fpr, fprlen); - if (!err) - err = scd_setattr ("CA-FPR-3", fpr, fprlen); - } - else - err = scd_setattr (fprno==1?"CA-FPR-1": - fprno==2?"CA-FPR-2": - fprno==3?"CA-FPR-3":"x", fpr, fprlen); - - leave: - xfree (data); - return err; -} - - -static gpg_error_t -cmd_privatedo (card_info_t info, char *argstr) -{ - gpg_error_t err; - int opt_clear; - char *do_name = NULL; - char *data = NULL; - size_t datalen; - int do_no; - - if (!info) - return print_help - ("PRIVATEDO [--clear] N [< FILE]\n\n" - "Change the private data object N. N must be in the\n" - "range 1 to 4. If FILE is given the data is is read\n" - "from that file. The option --clear clears the data.", - APP_TYPE_OPENPGP, 0); - - opt_clear = has_leading_option (argstr, "--clear"); - argstr = skip_options (argstr); - - if (digitp (argstr)) - { - do_no = atoi (argstr); - while (digitp (argstr)) - argstr++; - while (spacep (argstr)) - argstr++; - } - else - do_no = 0; - - if (do_no < 1 || do_no > 4) - { - err = gpg_error (GPG_ERR_INV_ARG); - goto leave; - } - do_name = xasprintf ("PRIVATE-DO-%d", do_no); - - if (opt_clear) - { - data = xstrdup (" "); - datalen = 1; - } - else if (*argstr == '<') /* Read it from a file */ - { - for (argstr++; spacep (argstr); argstr++) - ; - err = get_data_from_file (argstr, &data, &datalen); - if (err) - goto leave; - } - else if (*argstr) - { - err = gpg_error (GPG_ERR_INV_ARG); - goto leave; - } - else - { - data = tty_get (_("Private DO data: ")); - trim_spaces (data); - tty_kill_prompt (); - datalen = strlen (data); - if (!*data || *data == CONTROL_D) - { - err = gpg_error (GPG_ERR_CANCELED); - goto leave; - } - } - - err = scd_setattr (do_name, data, datalen); - - leave: - xfree (do_name); - xfree (data); - return err; -} - - -static gpg_error_t -cmd_writecert (card_info_t info, char *argstr) -{ - gpg_error_t err; - int opt_clear; - char *certref_buffer = NULL; - char *certref; - char *data = NULL; - size_t datalen; - - if (!info) - return print_help - ("WRITECERT [--clear] CERTREF < FILE\n\n" - "Write a certificate for key 3. Unless --clear is given\n" - "the file argument is mandatory. The option --clear removes\n" - "the certificate from the card.", - APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); - - opt_clear = has_leading_option (argstr, "--clear"); - argstr = skip_options (argstr); - - certref = argstr; - if ((argstr = strchr (certref, ' '))) - { - *argstr++ = 0; - trim_spaces (certref); - trim_spaces (argstr); - } - else /* Let argstr point to an empty string. */ - argstr = certref + strlen (certref); - - if (info->apptype == APP_TYPE_OPENPGP) - { - if (ascii_strcasecmp (certref, "OPENPGP.3") && strcmp (certref, "3")) - { - err = gpg_error (GPG_ERR_INV_ID); - log_error ("Error: CERTREF must be \"3\" or \"OPENPGP.3\"\n"); - goto leave; - } - certref = certref_buffer = xstrdup ("OPENPGP.3"); - } - else /* Upcase the certref; prepend cardtype if needed. */ - { - if (!strchr (certref, '.')) - certref_buffer = xstrconcat (app_type_string (info->apptype), ".", - certref, NULL); - else - certref_buffer = xstrdup (certref); - ascii_strupr (certref_buffer); - certref = certref_buffer; - } - - if (opt_clear) - { - data = xstrdup (" "); - datalen = 1; - } - else if (*argstr == '<') /* Read it from a file */ - { - for (argstr++; spacep (argstr); argstr++) - ; - err = get_data_from_file (argstr, &data, &datalen); - if (err) - goto leave; - } - else - { - err = gpg_error (GPG_ERR_INV_ARG); - goto leave; - } - - err = scd_writecert (certref, data, datalen); - - leave: - xfree (data); - xfree (certref_buffer); - return err; -} - - -static gpg_error_t -cmd_readcert (card_info_t info, char *argstr) -{ - gpg_error_t err; - char *certref_buffer = NULL; - char *certref; - void *data = NULL; - size_t datalen; - const char *fname; - - if (!info) - return print_help - ("READCERT CERTREF > FILE\n\n" - "Read the certificate for key 3 and store it in FILE.", - APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); - - argstr = skip_options (argstr); - - certref = argstr; - if ((argstr = strchr (certref, ' '))) - { - *argstr++ = 0; - trim_spaces (certref); - trim_spaces (argstr); - } - else /* Let argstr point to an empty string. */ - argstr = certref + strlen (certref); - - if (info->apptype == APP_TYPE_OPENPGP) - { - if (ascii_strcasecmp (certref, "OPENPGP.3") && strcmp (certref, "3")) - { - err = gpg_error (GPG_ERR_INV_ID); - log_error ("Error: CERTREF must be \"3\" or \"OPENPGP.3\"\n"); - goto leave; - } - certref = certref_buffer = xstrdup ("OPENPGP.3"); - } - - if (*argstr == '>') /* Write it to a file */ - { - for (argstr++; spacep (argstr); argstr++) - ; - fname = argstr; - } - else - { - err = gpg_error (GPG_ERR_INV_ARG); - goto leave; - } - - err = scd_readcert (certref, &data, &datalen); - if (err) - goto leave; - - err = put_data_to_file (fname, data, datalen); - - leave: - xfree (data); - xfree (certref_buffer); - return err; -} - - -static gpg_error_t -cmd_forcesig (card_info_t info) -{ - gpg_error_t err; - int newstate; - - if (!info) - return print_help - ("FORCESIG\n\n" - "Toggle the forcesig flag of an OpenPGP card.", - APP_TYPE_OPENPGP, 0); - - if (info->apptype != APP_TYPE_OPENPGP) - { - log_info ("Note: This is an OpenPGP only command.\n"); - return gpg_error (GPG_ERR_NOT_SUPPORTED); - } - - newstate = !info->chv1_cached; - - err = scd_setattr ("CHV-STATUS-1", newstate? "\x01":"", 1); - if (err) - goto leave; - - /* Read it back to be sure we have the right toggle state the next - * time. */ - err = scd_getattr ("CHV-STATUS", info); - - leave: - return err; -} - - - -/* Helper for cmd_generate_openpgp. Noe that either 0 or 1 is stored at - * FORCED_CHV1. */ -static gpg_error_t -check_pin_for_key_operation (card_info_t info, int *forced_chv1) -{ - gpg_error_t err = 0; - - *forced_chv1 = !info->chv1_cached; - if (*forced_chv1) - { /* Switch off the forced mode so that during key generation we - * don't get bothered with PIN queries for each self-signature. */ - err = scd_setattr ("CHV-STATUS-1", "\x01", 1); - if (err) - { - log_error ("error clearing forced signature PIN flag: %s\n", - gpg_strerror (err)); - *forced_chv1 = -1; /* Not changed. */ - goto leave; - } - } - - /* Check the PIN now, so that we won't get asked later for each - * binding signature. */ - err = scd_checkpin (info->serialno); - if (err) - log_error ("error checking the PIN: %s\n", gpg_strerror (err)); - - leave: - return err; -} - - -/* Helper for cmd_generate_openpgp. */ -static void -restore_forced_chv1 (int *forced_chv1) -{ - gpg_error_t err; - - /* Note the possible values stored at FORCED_CHV1: - * 0 - forcesig was not enabled. - * 1 - forcesig was enabled - enable it again. - * -1 - We have not changed anything. */ - if (*forced_chv1 == 1) - { /* Switch back to forced state. */ - err = scd_setattr ("CHV-STATUS-1", "", 1); - if (err) - log_error ("error setting forced signature PIN flag: %s\n", - gpg_strerror (err)); - *forced_chv1 = 0; - } -} - - -/* Implementation of cmd_generate for OpenPGP cards. */ -static gpg_error_t -generate_openpgp (card_info_t info) -{ - gpg_error_t err; - int forced_chv1 = -1; - int want_backup; - char *answer = NULL; - key_info_t kinfo1, kinfo2, kinfo3; - - if (info->extcap.ki) - { - xfree (answer); - answer = tty_get (_("Make off-card backup of encryption key? (Y/n) ")); - want_backup = answer_is_yes_no_default (answer, 1/*(default to Yes)*/); - tty_kill_prompt (); - if (*answer == CONTROL_D) - { - err = gpg_error (GPG_ERR_CANCELED); - goto leave; - } - } - else - want_backup = 0; - - kinfo1 = find_kinfo (info, "OPENPGP.1"); - kinfo2 = find_kinfo (info, "OPENPGP.2"); - kinfo3 = find_kinfo (info, "OPENPGP.3"); - - if ((kinfo1 && kinfo1->fprlen && !mem_is_zero (kinfo1->fpr,kinfo1->fprlen)) - || (kinfo2 && kinfo2->fprlen && !mem_is_zero (kinfo2->fpr,kinfo2->fprlen)) - || (kinfo3 && kinfo3->fprlen && !mem_is_zero (kinfo3->fpr,kinfo3->fprlen)) - ) - { - tty_printf ("\n"); - log_info (_("Note: keys are already stored on the card!\n")); - tty_printf ("\n"); - answer = tty_get (_("Replace existing keys? (y/N) ")); - tty_kill_prompt (); - if (*answer == CONTROL_D) - { - err = gpg_error (GPG_ERR_CANCELED); - goto leave; - } - - if (!answer_is_yes_no_default (answer, 0/*(default to No)*/)) - { - err = gpg_error (GPG_ERR_CANCELED); - goto leave; - } - } - - /* If no displayed name has been set, we assume that this is a fresh - * card and print a hint about the default PINs. */ - if (!info->disp_name || !*info->disp_name) - { - tty_printf ("\n"); - tty_printf (_("Please note that the factory settings of the PINs are\n" - " PIN = '%s' Admin PIN = '%s'\n" - "You should change them using the command --change-pin\n"), - OPENPGP_USER_PIN_DEFAULT, OPENPGP_ADMIN_PIN_DEFAULT); - tty_printf ("\n"); - } - - err = check_pin_for_key_operation (info, &forced_chv1); - if (err) - goto leave; - - /* FIXME: We need to divert to a function which spwans gpg which - * will then create the key. This also requires new features in - * gpg. We might also first create the keys on the card and then - * tell gpg to use them to create the OpenPGP keyblock. */ - /* generate_keypair (ctrl, 1, NULL, info.serialno, want_backup); */ - (void)want_backup; - err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); - - leave: - restore_forced_chv1 (&forced_chv1); - xfree (answer); - return err; -} - - -/* Generic implementation of cmd_generate. */ -static gpg_error_t -generate_generic (card_info_t info, const char *keyref, int force, - const char *algo) -{ - gpg_error_t err; - - (void)info; - - err = scd_genkey (keyref, force, algo, NULL); - - return err; -} - - -static gpg_error_t -cmd_generate (card_info_t info, char *argstr) -{ - static char * const valid_algos[] = - { "rsa2048", "rsa3072", "rsa4096", - "nistp256", "nistp384", "nistp521", - "ed25519", "cv25519", - NULL - }; - gpg_error_t err; - int opt_force; - char *opt_algo = NULL; /* Malloced. */ - char *keyref_buffer = NULL; /* Malloced. */ - char *keyref; /* Points into argstr or keyref_buffer. */ - int i; - - if (!info) - return print_help - ("GENERATE [--force] [--algo=ALGO] KEYREF\n\n" - "Create a new key on a card. For OpenPGP cards are menu is used\n" - "and KEYREF is ignored. Use --force to overwrite an existing key.", - APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); - - if (opt.interactive || opt.verbose) - log_info (_("%s card no. %s detected\n"), - app_type_string (info->apptype), - info->dispserialno? info->dispserialno : info->serialno); - - opt_force = has_leading_option (argstr, "--force"); - err = get_option_value (argstr, "--algo", &opt_algo); - if (err) - goto leave; - argstr = skip_options (argstr); - - keyref = argstr; - if ((argstr = strchr (keyref, ' '))) - { - *argstr++ = 0; - trim_spaces (keyref); - trim_spaces (argstr); - } - else /* Let argstr point to an empty string. */ - argstr = keyref + strlen (keyref); - - if (!*keyref) - keyref = NULL; - - if (*argstr) - { - /* Extra arguments found. */ - err = gpg_error (GPG_ERR_INV_ARG); - goto leave; - } - - if (opt_algo) - { - for (i=0; valid_algos[i]; i++) - if (!strcmp (valid_algos[i], opt_algo)) - break; - if (!valid_algos[i]) - { - err = gpg_error (GPG_ERR_PUBKEY_ALGO); - log_info ("Invalid algorithm '%s' given. Use one:\n", opt_algo); - for (i=0; valid_algos[i]; i++) - if (!(i%5)) - log_info (" %s%s", valid_algos[i], valid_algos[i+1]?",":"."); - else - log_printf (" %s%s", valid_algos[i], valid_algos[i+1]?",":"."); - log_info ("Note that the card may not support all of them.\n"); - goto leave; - } - } - - /* Upcase the keyref; if it misses the cardtype, prepend it. */ - if (keyref) - { - if (!strchr (keyref, '.')) - keyref_buffer = xstrconcat (app_type_string (info->apptype), ".", - keyref, NULL); - else - keyref_buffer = xstrdup (keyref); - ascii_strupr (keyref_buffer); - keyref = keyref_buffer; - } - - /* Divert to dedicated functions. */ - if (info->apptype == APP_TYPE_OPENPGP) - { - if (opt_force || opt_algo || keyref) - log_info ("Note: Options are ignored for OpenPGP cards.\n"); - err = generate_openpgp (info); - } - else if (!keyref) - err = gpg_error (GPG_ERR_INV_ID); - else - err = generate_generic (info, keyref, opt_force, opt_algo); - - leave: - xfree (opt_algo); - xfree (keyref_buffer); - return err; -} - - - -/* Sub-menu to change a PIN. The presented options may depend on the - * the ALLOW_ADMIN flag. */ -static gpg_error_t -cmd_passwd (card_info_t info, int allow_admin, char *argstr) -{ - gpg_error_t err; - char *answer = NULL; - const char *pinref; - - if (!info) - return print_help - ("PASSWD [PINREF]\n\n" - "Menu to change or unblock the PINs. Note that the\n" - "presented menu options depend on the type of card\n" - "and whether the admin mode is enabled. For OpenPGP\n" - "and PIV cards defaults for PINREF are available.", - 0); - - if (opt.interactive || opt.verbose) - log_info (_("%s card no. %s detected\n"), - app_type_string (info->apptype), - info->dispserialno? info->dispserialno : info->serialno); - - if (!allow_admin || info->apptype != APP_TYPE_OPENPGP) - { - if (*argstr) - pinref = argstr; - else if (info->apptype == APP_TYPE_OPENPGP) - pinref = "OPENPGP.1"; - else if (info->apptype == APP_TYPE_PIV) - pinref = "PIV.80"; - else - { - err = gpg_error (GPG_ERR_MISSING_VALUE); - goto leave; - } - err = scd_change_pin (pinref, 0); - if (err) - goto leave; - - if (info->apptype == APP_TYPE_PIV - && !ascii_strcasecmp (pinref, "PIV.81")) - log_info ("PUK changed.\n"); - else - log_info ("PIN changed.\n"); - } - else if (info->apptype == APP_TYPE_OPENPGP) - { - for (;;) - { - tty_printf ("\n"); - tty_printf ("1 - change PIN\n" - "2 - unblock and set new PIN\n" - "3 - change Admin PIN\n" - "4 - set the Reset Code\n" - "Q - quit\n"); - tty_printf ("\n"); - - err = 0; - xfree (answer); - answer = tty_get (_("Your selection? ")); - tty_kill_prompt (); - if (*answer == CONTROL_D) - break; /* Quit. */ - if (strlen (answer) != 1) - continue; - if (*answer == 'q' || *answer == 'Q') - break; /* Quit. */ - - if (*answer == '1') - { - /* Change PIN (same as the direct thing in non-admin mode). */ - err = scd_change_pin ("OPENPGP.1", 0); - if (err) - log_error ("Error changing the PIN: %s\n", gpg_strerror (err)); - else - log_info ("PIN changed.\n"); - } - else if (*answer == '2') - { - /* Unblock PIN by setting a new PIN. */ - err = scd_change_pin ("OPENPGP.1", 1); - if (err) - log_error ("Error unblocking the PIN: %s\n", gpg_strerror(err)); - else - log_info ("PIN unblocked and new PIN set.\n"); - } - else if (*answer == '3') - { - /* Change Admin PIN. */ - err = scd_change_pin ("OPENPGP.3", 0); - if (err) - log_error ("Error changing the PIN: %s\n", gpg_strerror (err)); - else - log_info ("PIN changed.\n"); - } - else if (*answer == '4') - { - /* Set a new Reset Code. */ - err = scd_change_pin ("OPENPGP.2", 1); - if (err) - log_error ("Error setting the Reset Code: %s\n", - gpg_strerror (err)); - else - log_info ("Reset Code set.\n"); - } - - } /*end for loop*/ - } - else - { - log_info ("Admin related passwd options not yet supported for '%s'\n", - app_type_string (info->apptype)); - err = gpg_error (GPG_ERR_NOT_SUPPORTED); - } - - leave: - xfree (answer); - return err; -} - - -static gpg_error_t -cmd_unblock (card_info_t info) -{ - gpg_error_t err = 0; - - if (!info) - return print_help - ("UNBLOCK\n\n" - "Unblock a PIN using a PUK or Reset Code. Note that OpenPGP\n" - "cards prior to version 2 can't use this; instead the PASSWD\n" - "command can be used to set a new PIN.", - 0); - - if (opt.interactive || opt.verbose) - log_info (_("%s card no. %s detected\n"), - app_type_string (info->apptype), - info->dispserialno? info->dispserialno : info->serialno); - - if (info->apptype == APP_TYPE_OPENPGP) - { - if (!info->is_v2) - { - log_error (_("This command is only available for version 2 cards\n")); - err = gpg_error (GPG_ERR_NOT_SUPPORTED); - } - else if (!info->chvinfo[1]) - { - log_error (_("Reset Code not or not anymore available\n")); - err = gpg_error (GPG_ERR_PIN_BLOCKED); - } - else - { - err = scd_change_pin ("OPENPGP.2", 0); - if (!err) - log_info ("PIN changed.\n"); - } - } - else if (info->apptype == APP_TYPE_PIV) - { - /* Unblock the Application PIN. */ - err = scd_change_pin ("PIV.80", 1); - if (!err) - log_info ("PIN unblocked and changed.\n"); - } - else - { - log_info ("Unblocking not yet supported for '%s'\n", - app_type_string (info->apptype)); - err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); - } - - return err; -} - - -/* Note: On successful execution a redisplay should be scheduled. If - * this function fails the card may be in an unknown state. */ -static gpg_error_t -cmd_factoryreset (card_info_t info) -{ - gpg_error_t err; - char *answer = NULL; - int termstate = 0; - int any_apdu = 0; - int is_yubikey = 0; - int i; - - - if (!info) - return print_help - ("FACTORY-RESET\n\n" - "Do a complete reset of some OpenPGP and PIV cards. This\n" - "deletes all data and keys and resets the PINs to their default.\n" - "This is mainly used by developers with scratch cards. Don't\n" - "worry, you need to confirm before the command proceeds.", - APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); - - /* We support the factory reset for most OpenPGP cards and Yubikeys - * with the PIV application. */ - if (info->apptype == APP_TYPE_OPENPGP) - ; - else if (info->apptype == APP_TYPE_PIV - && info->cardtype && !strcmp (info->cardtype, "yubikey")) - is_yubikey = 1; - else - - return gpg_error (GPG_ERR_NOT_SUPPORTED); - - /* For an OpenPGP card the code below basically does the same what - * this gpg-connect-agent script does: - * - * scd reset - * scd serialno undefined - * scd apdu 00 A4 04 00 06 D2 76 00 01 24 01 - * scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40 - * scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40 - * scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40 - * scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40 - * scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40 - * scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40 - * scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40 - * scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40 - * scd apdu 00 e6 00 00 - * scd apdu 00 44 00 00 - * scd reset - * /echo Card has been reset to factory defaults - * - * For a PIV application on a Yubikey it merely issues the Yubikey - * specific resset command. - */ - - err = scd_learn (info); - if (gpg_err_code (err) == GPG_ERR_OBJ_TERM_STATE - && gpg_err_source (err) == GPG_ERR_SOURCE_SCD) - termstate = 1; - else if (err) - { - log_error (_("OpenPGP card not available: %s\n"), gpg_strerror (err)); - goto leave; - } - - if (opt.interactive || opt.verbose) - log_info (_("%s card no. %s detected\n"), - app_type_string (info->apptype), - info->dispserialno? info->dispserialno : info->serialno); - - if (!termstate || is_yubikey) - { - if (!is_yubikey) - { - if (!(info->status_indicator == 3 || info->status_indicator == 5)) - { - /* Note: We won't see status-indicator 3 here because it - * is not possible to select a card application in - * termination state. */ - log_error (_("This command is not supported by this card\n")); - err = gpg_error (GPG_ERR_NOT_SUPPORTED); - goto leave; - } - } - - tty_printf ("\n"); - log_info - (_("Note: This command destroys all keys stored on the card!\n")); - tty_printf ("\n"); - xfree (answer); - answer = tty_get (_("Continue? (y/N) ")); - tty_kill_prompt (); - trim_spaces (answer); - if (*answer == CONTROL_D - || !answer_is_yes_no_default (answer, 0/*(default to no)*/)) - { - err = gpg_error (GPG_ERR_CANCELED); - goto leave; - } - - xfree (answer); - answer = tty_get (_("Really do a factory reset? (enter \"yes\") ")); - tty_kill_prompt (); - trim_spaces (answer); - if (strcmp (answer, "yes") && strcmp (answer,_("yes"))) - { - err = gpg_error (GPG_ERR_CANCELED); - goto leave; - } - - - if (is_yubikey) - { - /* The PIV application si already selected, we only need to - * send the special reset APDU after having blocked PIN and - * PUK. Note that blocking the PUK is done using the - * unblock PIN command. */ - any_apdu = 1; - for (i=0; i < 5; i++) - send_apdu ("0020008008FFFFFFFFFFFFFFFF", "VERIFY", 0xffff, - NULL, NULL); - for (i=0; i < 5; i++) - send_apdu ("002C008010FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", - "RESET RETRY COUNTER", 0xffff, NULL, NULL); - err = send_apdu ("00FB000001FF", "YUBIKEY RESET", 0, NULL, NULL); - if (err) - goto leave; - } - else /* OpenPGP card. */ - { - any_apdu = 1; - /* We need to select a card application before we can send APDUs - * to the card without scdaemon doing anything on its own. */ - err = send_apdu (NULL, "RESET", 0, NULL, NULL); - if (err) - goto leave; - err = send_apdu ("undefined", "dummy select ", 0, NULL, NULL); - if (err) - goto leave; - /* Select the OpenPGP application. */ - err = send_apdu ("00A4040006D27600012401", "SELECT AID", 0, - NULL, NULL); - if (err) - goto leave; - - /* Do some dummy verifies with wrong PINs to set the retry - * counter to zero. We can't easily use the card version 2.1 - * feature of presenting the admin PIN to allow the terminate - * command because there is no machinery in scdaemon to catch - * the verify command and ask for the PIN when the "APDU" - * command is used. - * Here, the length of dummy wrong PIN is 32-byte, also - * supporting authentication with KDF DO. */ - for (i=0; i < 4; i++) - send_apdu ("0020008120" - "40404040404040404040404040404040" - "40404040404040404040404040404040", "VERIFY", 0xffff, - NULL, NULL); - for (i=0; i < 4; i++) - send_apdu ("0020008320" - "40404040404040404040404040404040" - "40404040404040404040404040404040", "VERIFY", 0xffff, - NULL, NULL); - - /* Send terminate datafile command. */ - err = send_apdu ("00e60000", "TERMINATE DF", 0x6985, NULL, NULL); - if (err) - goto leave; - } - } - - if (!is_yubikey) - { - any_apdu = 1; - /* Send activate datafile command. This is used without - * confirmation if the card is already in termination state. */ - err = send_apdu ("00440000", "ACTIVATE DF", 0, NULL, NULL); - if (err) - goto leave; - } - - /* Finally we reset the card reader once more. */ - err = send_apdu (NULL, "RESET", 0, NULL, NULL); - if (err) - goto leave; - - /* Then, connect the card again (answer used as a dummy). */ - xfree (answer); answer = NULL; - err = scd_serialno (&answer, NULL); - - leave: - if (err && any_apdu && !is_yubikey) - { - log_info ("Due to an error the card might be in an inconsistent state\n" - "You should run the LIST command to check this.\n"); - /* FIXME: We need a better solution in the case that the card is - * in a termination state, i.e. the card was removed before the - * activate was sent. The best solution I found with v2.1 - * Zeitcontrol card was to kill scdaemon and the issue this - * sequence with gpg-connect-agent: - * scd reset - * scd serialno undefined - * scd apdu 00A4040006D27600012401 (returns error) - * scd apdu 00440000 - * Then kill scdaemon again and issue: - * scd reset - * scd serialno openpgp - */ - } - xfree (answer); - return err; -} - - -/* Generate KDF data. This is a helper for cmd_kdfsetup. */ -static gpg_error_t -gen_kdf_data (unsigned char *data, int single_salt) -{ - gpg_error_t err; - const unsigned char h0[] = { 0x81, 0x01, 0x03, - 0x82, 0x01, 0x08, - 0x83, 0x04 }; - const unsigned char h1[] = { 0x84, 0x08 }; - const unsigned char h2[] = { 0x85, 0x08 }; - const unsigned char h3[] = { 0x86, 0x08 }; - const unsigned char h4[] = { 0x87, 0x20 }; - const unsigned char h5[] = { 0x88, 0x20 }; - unsigned char *p, *salt_user, *salt_admin; - unsigned char s2k_char; - unsigned int iterations; - unsigned char count_4byte[4]; - - p = data; - - s2k_char = encode_s2k_iterations (agent_get_s2k_count ()); - iterations = S2K_DECODE_COUNT (s2k_char); - count_4byte[0] = (iterations >> 24) & 0xff; - count_4byte[1] = (iterations >> 16) & 0xff; - count_4byte[2] = (iterations >> 8) & 0xff; - count_4byte[3] = (iterations & 0xff); - - memcpy (p, h0, sizeof h0); - p += sizeof h0; - memcpy (p, count_4byte, sizeof count_4byte); - p += sizeof count_4byte; - memcpy (p, h1, sizeof h1); - salt_user = (p += sizeof h1); - gcry_randomize (p, 8, GCRY_STRONG_RANDOM); - p += 8; - - if (single_salt) - salt_admin = salt_user; - else - { - memcpy (p, h2, sizeof h2); - p += sizeof h2; - gcry_randomize (p, 8, GCRY_STRONG_RANDOM); - p += 8; - memcpy (p, h3, sizeof h3); - salt_admin = (p += sizeof h3); - gcry_randomize (p, 8, GCRY_STRONG_RANDOM); - p += 8; - } - - memcpy (p, h4, sizeof h4); - p += sizeof h4; - err = gcry_kdf_derive (OPENPGP_USER_PIN_DEFAULT, - strlen (OPENPGP_USER_PIN_DEFAULT), - GCRY_KDF_ITERSALTED_S2K, GCRY_MD_SHA256, - salt_user, 8, iterations, 32, p); - p += 32; - if (!err) - { - memcpy (p, h5, sizeof h5); - p += sizeof h5; - err = gcry_kdf_derive (OPENPGP_ADMIN_PIN_DEFAULT, - strlen (OPENPGP_ADMIN_PIN_DEFAULT), - GCRY_KDF_ITERSALTED_S2K, GCRY_MD_SHA256, - salt_admin, 8, iterations, 32, p); - } - - return err; -} - - -static gpg_error_t -cmd_kdfsetup (card_info_t info, char *argstr) -{ - gpg_error_t err; - unsigned char kdf_data[OPENPGP_KDF_DATA_LENGTH_MAX]; - int single = (*argstr != 0); - - if (!info) - return print_help - ("KDF-SETUP\n\n" - "Prepare the OpenPGP card KDF feature for this card.", - APP_TYPE_OPENPGP, 0); - - if (info->apptype != APP_TYPE_OPENPGP) - { - log_info ("Note: This is an OpenPGP only command.\n"); - return gpg_error (GPG_ERR_NOT_SUPPORTED); - } - - if (!info->extcap.kdf) - { - log_error (_("This command is not supported by this card\n")); - err = gpg_error (GPG_ERR_NOT_SUPPORTED); - goto leave; - } - - err = gen_kdf_data (kdf_data, single); - if (err) - goto leave; - - err = scd_setattr ("KDF", kdf_data, - single ? OPENPGP_KDF_DATA_LENGTH_MIN - /* */ : OPENPGP_KDF_DATA_LENGTH_MAX); - if (err) - goto leave; - - err = scd_getattr ("KDF", info); - - leave: - return err; -} - - - -static void -show_keysize_warning (void) -{ - static int shown; - - if (shown) - return; - shown = 1; - tty_printf - (_("Note: There is no guarantee that the card supports the requested\n" - " key type or size. If the key generation does not succeed,\n" - " please check the documentation of your card to see which\n" - " key types and sizes are supported.\n") - ); -} - - -/* Ask for the size of a card key. NBITS is the current size - * configured for the card. Returns 0 on success and stored the - * chosen key size at R_KEYSIZE; 0 is stored to indicate that the - * default size shall be used. */ -static gpg_error_t -ask_card_rsa_keysize (unsigned int nbits, unsigned int *r_keysize) -{ - unsigned int min_nbits = 1024; - unsigned int max_nbits = 4096; - char*answer; - unsigned int req_nbits; - - for (;;) - { - answer = tty_getf (_("What keysize do you want? (%u) "), nbits); - trim_spaces (answer); - tty_kill_prompt (); - if (*answer == CONTROL_D) - { - xfree (answer); - return gpg_error (GPG_ERR_CANCELED); - } - req_nbits = *answer? atoi (answer): nbits; - xfree (answer); - - if (req_nbits != nbits && (req_nbits % 32) ) - { - req_nbits = ((req_nbits + 31) / 32) * 32; - tty_printf (_("rounded up to %u bits\n"), req_nbits); - } - - if (req_nbits == nbits) - { - /* Use default. */ - *r_keysize = 0; - return 0; - } - - if (req_nbits < min_nbits || req_nbits > max_nbits) - { - tty_printf (_("%s keysizes must be in the range %u-%u\n"), - "RSA", min_nbits, max_nbits); - } - else - { - *r_keysize = req_nbits; - return 0; - } - } -} - - -/* Ask for the key attribute of a card key. CURRENT is the current - * attribute configured for the card. KEYNO is the number of the key - * used to select the prompt. Stores NULL at result to use the - * default attribute or stores the selected attribute structure at - * RESULT. On error an error code is returned. */ -static gpg_error_t -ask_card_keyattr (int keyno, const struct key_attr *current, - struct key_attr **result) -{ - gpg_error_t err; - struct key_attr *key_attr = NULL; - char *answer = NULL; - int selection; - - *result = NULL; - - key_attr = xcalloc (1, sizeof *key_attr); - - tty_printf (_("Changing card key attribute for: ")); - if (keyno == 0) - tty_printf (_("Signature key\n")); - else if (keyno == 1) - tty_printf (_("Encryption key\n")); - else - tty_printf (_("Authentication key\n")); - - tty_printf (_("Please select what kind of key you want:\n")); - tty_printf (_(" (%d) RSA\n"), 1 ); - tty_printf (_(" (%d) ECC\n"), 2 ); - - for (;;) - { - xfree (answer); - answer = tty_get (_("Your selection? ")); - trim_spaces (answer); - tty_kill_prompt (); - if (!*answer || *answer == CONTROL_D) - { - err = gpg_error (GPG_ERR_CANCELED); - goto leave; - } - selection = *answer? atoi (answer) : 0; - - if (selection == 1 || selection == 2) - break; - else - tty_printf (_("Invalid selection.\n")); - } - - - if (selection == 1) - { - unsigned int nbits, result_nbits; - - if (current->algo == PUBKEY_ALGO_RSA) - nbits = current->nbits; - else - nbits = 2048; - - err = ask_card_rsa_keysize (nbits, &result_nbits); - if (err) - goto leave; - if (result_nbits == 0) - { - if (current->algo == PUBKEY_ALGO_RSA) - { - xfree (key_attr); - key_attr = NULL; - } - else - result_nbits = nbits; - } - - if (key_attr) - { - key_attr->algo = PUBKEY_ALGO_RSA; - key_attr->nbits = result_nbits; - } - } - else if (selection == 2) - { - const char *curve; - /* const char *oid_str; */ - int algo; - - if (current->algo == PUBKEY_ALGO_RSA) - { - if (keyno == 1) /* Encryption key */ - algo = PUBKEY_ALGO_ECDH; - else /* Signature key or Authentication key */ - algo = PUBKEY_ALGO_ECDSA; - curve = NULL; - } - else - { - algo = current->algo; - curve = current->curve; - } - - (void)curve; - (void)algo; - err = GPG_ERR_NOT_IMPLEMENTED; - goto leave; - /* FIXME: We need to mve the ask_cure code out to common or - * provide another sultion. */ - /* curve = ask_curve (&algo, NULL, curve); */ - /* if (curve) */ - /* { */ - /* key_attr->algo = algo; */ - /* oid_str = openpgp_curve_to_oid (curve, NULL); */ - /* key_attr->curve = openpgp_oid_to_curve (oid_str, 0); */ - /* } */ - /* else */ - /* { */ - /* xfree (key_attr); */ - /* key_attr = NULL; */ - /* } */ - } - else - { - err = gpg_error (GPG_ERR_BUG); - goto leave; - } - - /* Tell the user what we are going to do. */ - if (key_attr->algo == PUBKEY_ALGO_RSA) - { - tty_printf (_("The card will now be re-configured" - " to generate a key of %u bits\n"), key_attr->nbits); - } - else if (key_attr->algo == PUBKEY_ALGO_ECDH - || key_attr->algo == PUBKEY_ALGO_ECDSA - || key_attr->algo == PUBKEY_ALGO_EDDSA) - { - tty_printf (_("The card will now be re-configured" - " to generate a key of type: %s\n"), key_attr->curve); - } - show_keysize_warning (); - - *result = key_attr; - key_attr = NULL; - - leave: - xfree (key_attr); - xfree (answer); - return err; -} - - -/* Change the key attribute of key KEYNO (0..2) and show an error - * message if that fails. */ -static gpg_error_t -do_change_keyattr (int keyno, const struct key_attr *key_attr) -{ - gpg_error_t err = 0; - char args[100]; - - if (key_attr->algo == PUBKEY_ALGO_RSA) - snprintf (args, sizeof args, "--force %d 1 rsa%u", keyno+1, - key_attr->nbits); - else if (key_attr->algo == PUBKEY_ALGO_ECDH - || key_attr->algo == PUBKEY_ALGO_ECDSA - || key_attr->algo == PUBKEY_ALGO_EDDSA) - snprintf (args, sizeof args, "--force %d %d %s", - keyno+1, key_attr->algo, key_attr->curve); - else - { - /* FIXME: Above we use opnepgp algo names but in the error - * message we use the gcrypt names. We should settle for a - * consistent solution. */ - log_error (_("public key algorithm %d (%s) is not supported\n"), - key_attr->algo, gcry_pk_algo_name (key_attr->algo)); - err = gpg_error (GPG_ERR_PUBKEY_ALGO); - goto leave; - } - - err = scd_setattr ("KEY-ATTR", args, strlen (args)); - if (err) - log_error (_("error changing key attribute for key %d: %s\n"), - keyno+1, gpg_strerror (err)); - leave: - return err; -} - - -static gpg_error_t -cmd_keyattr (card_info_t info, char *argstr) -{ - gpg_error_t err = 0; - int keyno; - struct key_attr *key_attr = NULL; - - (void)argstr; - - if (!info) - return print_help - ("KEY-ATTR\n\n" - "Menu to change the key attributes of an OpenPGP card.", - APP_TYPE_OPENPGP, 0); - - if (info->apptype != APP_TYPE_OPENPGP) - { - log_info ("Note: This is an OpenPGP only command.\n"); - return gpg_error (GPG_ERR_NOT_SUPPORTED); - } - - if (!(info->is_v2 && info->extcap.aac)) - { - log_error (_("This command is not supported by this card\n")); - err = gpg_error (GPG_ERR_NOT_SUPPORTED); - goto leave; - } - - for (keyno = 0; keyno < DIM (info->key_attr); keyno++) - { - xfree (key_attr); - key_attr = NULL; - err = ask_card_keyattr (keyno, &info->key_attr[keyno], &key_attr); - if (err) - goto leave; - - err = do_change_keyattr (keyno, key_attr); - if (err) - { - /* Error: Better read the default key attribute again. */ - log_debug ("FIXME\n"); - /* Ask again for this key. */ - keyno--; - } - } - - leave: - xfree (key_attr); - return err; -} - - -static gpg_error_t -cmd_uif (card_info_t info, char *argstr) -{ - gpg_error_t err; - int keyno; - - if (!info) - return print_help - ("UIF N [on|off|permanent]\n\n" - "Change the User Interaction Flag. N must in the range 1 to 3.", - APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); - - argstr = skip_options (argstr); - - if (digitp (argstr)) - { - keyno = atoi (argstr); - while (digitp (argstr)) - argstr++; - while (spacep (argstr)) - argstr++; - } - else - keyno = 0; - - if (keyno < 1 || keyno > 3) - { - err = gpg_error (GPG_ERR_INV_ARG); - goto leave; - } - - - err = GPG_ERR_NOT_IMPLEMENTED; - - leave: - return err; -} - - -static gpg_error_t -cmd_yubikey (card_info_t info, char *argstr) -{ - gpg_error_t err, err2; - estream_t fp = opt.interactive? NULL : es_stdout; - char *words[20]; - int nwords; - - if (!info) - return print_help - ("YUBIKEY args\n\n" - "Various commands pertaining to Yubikey tokens with being:\n" - "\n" - " LIST \n" - "\n" - "List supported and enabled applications.\n" - "\n" - " ENABLE usb|nfc|all [otp|u2f|opgp|piv|oath|fido2|all]\n" - " DISABLE usb|nfc|all [otp|u2f|opgp|piv|oath|fido2|all]\n" - "\n" - "Enable or disable the specified or all applications on the\n" - "given interface.", - 0); - - argstr = skip_options (argstr); - - if (!info->cardtype || strcmp (info->cardtype, "yubikey")) - { - log_info ("This command can only be used with Yubikeys.\n"); - err = gpg_error (GPG_ERR_NOT_SUPPORTED); - goto leave; - } - - nwords = split_fields (argstr, words, DIM (words)); - if (nwords < 1) - { - err = gpg_error (GPG_ERR_SYNTAX); - goto leave; - } - - - /* Note that we always do a learn to get a chance to the card back - * into a usable state. */ - err = yubikey_commands (fp, nwords, words); - err2 = scd_learn (info); - if (err2) - log_error ("Error re-reading card: %s\n", gpg_strerror (err)); - - leave: - return err; -} - - - -/* Data used by the command parser. This needs to be outside of the - * function scope to allow readline based command completion. */ -enum cmdids - { - cmdNOP = 0, - cmdQUIT, cmdADMIN, cmdHELP, cmdLIST, cmdRESET, cmdVERIFY, - cmdNAME, cmdURL, cmdFETCH, cmdLOGIN, cmdLANG, cmdSALUT, cmdCAFPR, - cmdFORCESIG, cmdGENERATE, cmdPASSWD, cmdPRIVATEDO, cmdWRITECERT, - cmdREADCERT, cmdUNBLOCK, cmdFACTORYRESET, cmdKDFSETUP, - cmdKEYATTR, cmdUIF, cmdAUTHENTICATE, cmdYUBIKEY, - cmdINVCMD - }; - -static struct -{ - const char *name; - enum cmdids id; - int admin_only; - const char *desc; -} cmds[] = { - { "quit" , cmdQUIT , 0, N_("quit this menu")}, - { "q" , cmdQUIT , 0, NULL }, - { "admin" , cmdADMIN , 0, N_("show admin commands")}, - { "help" , cmdHELP , 0, N_("show this help")}, - { "?" , cmdHELP , 0, NULL }, - { "list" , cmdLIST , 0, N_("list all available data")}, - { "l" , cmdLIST , 0, NULL }, - { "name" , cmdNAME , 1, N_("change card holder's name")}, - { "url" , cmdURL , 1, N_("change URL to retrieve key")}, - { "fetch" , cmdFETCH , 0, N_("fetch the key specified in the card URL")}, - { "login" , cmdLOGIN , 1, N_("change the login name")}, - { "lang" , cmdLANG , 1, N_("change the language preferences")}, - { "salutation",cmdSALUT, 1, N_("change card holder's salutation")}, - { "salut" , cmdSALUT, 1, NULL }, - { "cafpr" , cmdCAFPR , 1, N_("change a CA fingerprint")}, - { "forcesig", cmdFORCESIG, 1, N_("toggle the signature force PIN flag")}, - { "generate", cmdGENERATE, 1, N_("generate new keys")}, - { "passwd" , cmdPASSWD, 0, N_("menu to change or unblock the PIN")}, - { "verify" , cmdVERIFY, 0, N_("verify the PIN and list all data")}, - { "unblock" , cmdUNBLOCK,0, N_("unblock the PIN using a Reset Code")}, - { "authenticate",cmdAUTHENTICATE, 0,N_("authenticate to the card")}, - { "auth" , cmdAUTHENTICATE, 0, NULL }, - { "reset" , cmdRESET, 0, N_("send a reset to the card daemon")}, - { "factory-reset", cmdFACTORYRESET, 1, N_("destroy all keys and data")}, - { "kdf-setup", cmdKDFSETUP, 1, N_("setup KDF for PIN authentication")}, - { "key-attr", cmdKEYATTR, 1, N_("change the key attribute")}, - { "uif", cmdUIF, 1, N_("change the User Interaction Flag")}, - { "privatedo", cmdPRIVATEDO, 0, N_("change a private data object")}, - { "readcert", cmdREADCERT, 0, N_("read a certificate from a data object")}, - { "writecert", cmdWRITECERT, 1, N_("store a certificate to a data object")}, - { "yubikey", cmdYUBIKEY, 0, N_("Yubikey management commands")}, - { NULL, cmdINVCMD, 0, NULL } -}; - - -/* The command line command dispatcher. */ -static gpg_error_t -dispatch_command (card_info_t info, const char *orig_command) -{ - gpg_error_t err = 0; - enum cmdids cmd; /* The command. */ - char *command; /* A malloced copy of ORIG_COMMAND. */ - char *argstr; /* The argument as a string. */ - int i; - int ignore_error; - - if ((ignore_error = *orig_command == '-')) - orig_command++; - command = xstrdup (orig_command); - argstr = NULL; - if ((argstr = strchr (command, ' '))) - { - *argstr++ = 0; - trim_spaces (command); - trim_spaces (argstr); - } - - for (i=0; cmds[i].name; i++ ) - if (!ascii_strcasecmp (command, cmds[i].name )) - break; - cmd = cmds[i].id; /* (If not found this will be cmdINVCMD). */ - - /* Make sure we have valid strings for the args. They are allowed - * to be modified and must thus point to a buffer. */ - if (!argstr) - argstr = command + strlen (command); - - /* For most commands we need to make sure that we have a card. */ - if (!info) - ; /* Help mode */ - else if (!(cmd == cmdNOP || cmd == cmdQUIT || cmd == cmdHELP - || cmd == cmdINVCMD) - && !info->initialized) - { - err = scd_learn (info); - if (err) - { - log_error ("Error reading card: %s\n", gpg_strerror (err)); - goto leave; - } - } - - switch (cmd) - { - case cmdNOP: - if (!info) - print_help ("NOP\n\n" - "Dummy command.", 0); - break; - - case cmdQUIT: - if (!info) - print_help ("QUIT\n\n" - "Stop processing.", 0); - else - { - err = gpg_error (GPG_ERR_EOF); - goto leave; - } - break; - - case cmdHELP: - if (!info) - print_help ("HELP [command]\n\n" - "Show all commands. With an argument show help\n" - "for that command.", 0); - else if (*argstr) - dispatch_command (NULL, argstr); - else - { - es_printf - ("List of commands (\"help \" for details):\n"); - for (i=0; cmds[i].name; i++ ) - if(cmds[i].desc) - es_printf("%-14s %s\n", cmds[i].name, _(cmds[i].desc) ); - es_printf ("Prefix a command with a dash to ignore its error.\n"); - } - break; - - case cmdLIST: - if (!info) - print_help ("LIST\n\n" - "Show content of the card.", 0); - else - { - err = scd_learn (info); - if (err) - log_error ("Error reading card: %s\n", gpg_strerror (err)); - else - list_card (info); - } - break; - - case cmdRESET: - if (!info) - print_help ("RESET\n\n" - "Send a RESET to the card daemon.", 0); - else - { - flush_keyblock_cache (); - err = scd_apdu (NULL, NULL, NULL, NULL); - } - break; - - case cmdADMIN: - /* This is a NOP in non-interactive mode. */ - break; - - case cmdVERIFY: err = cmd_verify (info, argstr); break; - case cmdAUTHENTICATE: err = cmd_authenticate (info, argstr); break; - case cmdNAME: err = cmd_name (info, argstr); break; - case cmdURL: err = cmd_url (info, argstr); break; - case cmdFETCH: err = cmd_fetch (info); break; - case cmdLOGIN: err = cmd_login (info, argstr); break; - case cmdLANG: err = cmd_lang (info, argstr); break; - case cmdSALUT: err = cmd_salut (info, argstr); break; - case cmdCAFPR: err = cmd_cafpr (info, argstr); break; - case cmdPRIVATEDO: err = cmd_privatedo (info, argstr); break; - case cmdWRITECERT: err = cmd_writecert (info, argstr); break; - case cmdREADCERT: err = cmd_readcert (info, argstr); break; - case cmdFORCESIG: err = cmd_forcesig (info); break; - case cmdGENERATE: err = cmd_generate (info, argstr); break; - case cmdPASSWD: err = cmd_passwd (info, 1, argstr); break; - case cmdUNBLOCK: err = cmd_unblock (info); break; - case cmdFACTORYRESET: err = cmd_factoryreset (info); break; - case cmdKDFSETUP: err = cmd_kdfsetup (info, argstr); break; - case cmdKEYATTR: err = cmd_keyattr (info, argstr); break; - case cmdUIF: err = cmd_uif (info, argstr); break; - case cmdYUBIKEY: err = cmd_yubikey (info, argstr); break; - - case cmdINVCMD: - default: - log_error (_("Invalid command (try \"help\")\n")); - break; - } /* End command switch. */ - - - leave: - /* Return GPG_ERR_EOF only if its origin was "quit". */ - es_fflush (es_stdout); - if (gpg_err_code (err) == GPG_ERR_EOF && cmd != cmdQUIT) - err = gpg_error (GPG_ERR_GENERAL); - if (err && gpg_err_code (err) != GPG_ERR_EOF) - { - if (ignore_error) - { - log_info ("Command '%s' failed: %s\n", command, gpg_strerror (err)); - err = 0; - } - else - log_error ("Command '%s' failed: %s\n", command, gpg_strerror (err)); - } - xfree (command); - - return err; -} - - -/* The interactive main loop. */ -static void -interactive_loop (void) -{ - gpg_error_t err; - char *answer = NULL; /* The input line. */ - enum cmdids cmd = cmdNOP; /* The command. */ - int cmd_admin_only; /* The command is an admin only command. */ - char *argstr; /* The argument as a string. */ - int redisplay = 1; /* Whether to redisplay the main info. */ - int allow_admin = 0; /* Whether admin commands are allowed. */ - char *help_arg = NULL; /* Argument of the HELP command. */ - struct card_info_s info_buffer = { 0 }; - card_info_t info = &info_buffer; - char *p; - int i; - - /* In the interactive mode we do not want to print the program prefix. */ - log_set_prefix (NULL, 0); - - for (;;) - { - if (help_arg) - { - /* Clear info to indicate helpmode */ - info = NULL; - } - else if (!info) - { - /* Get out of help. */ - info = &info_buffer; - help_arg = NULL; - redisplay = 0; - } - else if (redisplay) - { - err = scd_learn (info); - if (err) - { - log_error ("Error reading card: %s\n", gpg_strerror (err)); - } - else - { - list_card (info); - tty_printf("\n"); - redisplay = 0; - } - } - - if (!info) - { - /* Copy the pending help arg into our answer. Noe that - * help_arg points into answer. */ - p = xstrdup (help_arg); - help_arg = NULL; - xfree (answer); - answer = p; - } - else - { - do - { - xfree (answer); - tty_enable_completion (command_completion); - answer = tty_get (_("gpg/card> ")); - tty_kill_prompt(); - tty_disable_completion (); - trim_spaces(answer); - } - while ( *answer == '#' ); - } - - argstr = NULL; - cmd_admin_only = 0; - if (!*answer) - cmd = cmdLIST; /* We default to the list command */ - else if (*answer == CONTROL_D) - cmd = cmdQUIT; - else - { - if ((argstr = strchr (answer,' '))) - { - *argstr++ = 0; - trim_spaces (answer); - trim_spaces (argstr); - } - - for (i=0; cmds[i].name; i++ ) - if (!ascii_strcasecmp (answer, cmds[i].name )) - break; - - cmd = cmds[i].id; - cmd_admin_only = cmds[i].admin_only; - } - - /* Make sure we have valid strings for the args. They are - * allowed to be modified and must thus point to a buffer. */ - if (!argstr) - argstr = answer + strlen (answer); - - if (!(cmd == cmdNOP || cmd == cmdQUIT || cmd == cmdHELP - || cmd == cmdINVCMD)) - { - /* If redisplay is set we know that there was an error reading - * the card. In this case we force a LIST command to retry. */ - if (!info) - ; /* In help mode. */ - else if (redisplay) - { - cmd = cmdLIST; - cmd_admin_only = 0; - } - else if (!info->serialno) - { - /* Without a serial number most commands won't work. - * Catch it here. */ - tty_printf ("\n"); - tty_printf ("Serial number missing\n"); - continue; - } - else if (!allow_admin && cmd_admin_only) - { - tty_printf ("\n"); - tty_printf (_("Admin-only command\n")); - continue; - } - } - - err = 0; - switch (cmd) - { - case cmdNOP: - if (!info) - print_help ("NOP\n\n" - "Dummy command.", 0); - break; - - case cmdQUIT: - if (!info) - print_help ("QUIT\n\n" - "Leave this tool.", 0); - else - { - tty_printf ("\n"); - goto leave; - } - break; - - case cmdHELP: - if (!info) - print_help ("HELP [command]\n\n" - "Show all commands. With an argument show help\n" - "for that command.", 0); - else if (*argstr) - help_arg = argstr; /* Trigger help for a command. */ - else - { - tty_printf - ("List of commands (\"help \" for details):\n"); - for (i=0; cmds[i].name; i++ ) - if(cmds[i].desc - && (!cmds[i].admin_only - || (cmds[i].admin_only && allow_admin))) - tty_printf("%-14s %s\n", cmds[i].name, _(cmds[i].desc) ); - } - break; - - case cmdLIST: - if (!info) - print_help ("LIST\n\n" - "Show content of the card.", 0); - else - { - /* Actual work is done by the redisplay code block. */ - redisplay = 1; - } - break; - - case cmdRESET: - if (!info) - print_help ("RESET\n\n" - "Send a RESET to the card daemon.", 0); - else - { - flush_keyblock_cache (); - err = scd_apdu (NULL, NULL, NULL, NULL); - } - break; - - case cmdADMIN: - if ( !strcmp (argstr, "on") ) - allow_admin = 1; - else if ( !strcmp (argstr, "off") ) - allow_admin = 0; - else if ( !strcmp (argstr, "verify") ) - { - /* Force verification of the Admin Command. However, - this is only done if the retry counter is at initial - state. */ - /* FIXME: Must depend on the type of the card. */ - /* char *tmp = xmalloc (strlen (serialnobuf) + 6 + 1); */ - /* strcpy (stpcpy (tmp, serialnobuf), "[CHV3]"); */ - /* allow_admin = !agent_scd_checkpin (tmp); */ - /* xfree (tmp); */ - } - else /* Toggle. */ - allow_admin=!allow_admin; - if(allow_admin) - tty_printf(_("Admin commands are allowed\n")); - else - tty_printf(_("Admin commands are not allowed\n")); - break; - - case cmdVERIFY: - err = cmd_verify (info, argstr); - if (!err) - redisplay = 1; - break; - case cmdAUTHENTICATE: err = cmd_authenticate (info, argstr); break; - case cmdNAME: err = cmd_name (info, argstr); break; - case cmdURL: err = cmd_url (info, argstr); break; - case cmdFETCH: err = cmd_fetch (info); break; - case cmdLOGIN: err = cmd_login (info, argstr); break; - case cmdLANG: err = cmd_lang (info, argstr); break; - case cmdSALUT: err = cmd_salut (info, argstr); break; - case cmdCAFPR: err = cmd_cafpr (info, argstr); break; - case cmdPRIVATEDO: err = cmd_privatedo (info, argstr); break; - case cmdWRITECERT: err = cmd_writecert (info, argstr); break; - case cmdREADCERT: err = cmd_readcert (info, argstr); break; - case cmdFORCESIG: err = cmd_forcesig (info); break; - case cmdGENERATE: err = cmd_generate (info, argstr); break; - case cmdPASSWD: err = cmd_passwd (info, allow_admin, argstr); break; - case cmdUNBLOCK: err = cmd_unblock (info); break; - case cmdFACTORYRESET: - err = cmd_factoryreset (info); - if (!err) - redisplay = 1; - break; - case cmdKDFSETUP: err = cmd_kdfsetup (info, argstr); break; - case cmdKEYATTR: err = cmd_keyattr (info, argstr); break; - case cmdUIF: err = cmd_uif (info, argstr); break; - case cmdYUBIKEY: err = cmd_yubikey (info, argstr); break; - - case cmdINVCMD: - default: - tty_printf ("\n"); - tty_printf (_("Invalid command (try \"help\")\n")); - break; - } /* End command switch. */ - - if (gpg_err_code (err) == GPG_ERR_CANCELED) - tty_fprintf (NULL, "\n"); - else if (err) - { - const char *s = "?"; - for (i=0; cmds[i].name; i++ ) - if (cmd == cmds[i].id) - { - s = cmds[i].name; - break; - } - log_error ("Command '%s' failed: %s\n", s, gpg_strerror (err)); - } - - } /* End of main menu loop. */ - - leave: - release_card_info (info); - xfree (answer); -} - -#ifdef HAVE_LIBREADLINE -/* Helper function for readline's command completion. */ -static char * -command_generator (const char *text, int state) -{ - static int list_index, len; - const char *name; - - /* If this is a new word to complete, initialize now. This includes - * saving the length of TEXT for efficiency, and initializing the - index variable to 0. */ - if (!state) - { - list_index = 0; - len = strlen(text); - } - - /* Return the next partial match */ - while ((name = cmds[list_index].name)) - { - /* Only complete commands that have help text. */ - if (cmds[list_index++].desc && !strncmp (name, text, len)) - return strdup(name); - } - - return NULL; -} - -/* Second helper function for readline's command completion. */ -static char ** -command_completion (const char *text, int start, int end) -{ - (void)end; - - /* If we are at the start of a line, we try and command-complete. - * If not, just do nothing for now. The support for help completion - * needs to be more smarter. */ - if (!start) - return rl_completion_matches (text, command_generator); - else if (start == 5 && !ascii_strncasecmp (rl_line_buffer, "help ", 5)) - return rl_completion_matches (text, command_generator); - - rl_attempted_completion_over = 1; - - return NULL; -} -#endif /*HAVE_LIBREADLINE*/ diff --git a/tools/gpg-card-w32info.rc b/tools/gpg-card-w32info.rc new file mode 100644 index 000000000..b35ff4ce2 --- /dev/null +++ b/tools/gpg-card-w32info.rc @@ -0,0 +1,51 @@ +/* gpg-card-w32info.rc -*- c -*- + * Copyright (C) 2019 g10 Code GmbH + * + * This file is free software; as a special exception the author gives + * unlimited permission to copy and/or distribute it, with or without + * modifications, as long as this notice is preserved. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY, to the extent permitted by law; without even the + * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + */ + +#include "afxres.h" +#include "../common/w32info-rc.h" + +1 ICON "../common/gnupg.ico" + +1 VERSIONINFO + FILEVERSION W32INFO_VI_FILEVERSION + PRODUCTVERSION W32INFO_VI_PRODUCTVERSION + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x01L /* VS_FF_DEBUG (0x1)*/ +#else + FILEFLAGS 0x00L +#endif + FILEOS 0x40004L /* VOS_NT (0x40000) | VOS__WINDOWS32 (0x4) */ + FILETYPE 0x1L /* VFT_APP (0x1) */ + FILESUBTYPE 0x0L /* VFT2_UNKNOWN */ + BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" /* US English (0409), Unicode (04b0) */ + BEGIN + VALUE "FileDescription", L"GnuPG\x2019s card tool \ +to the agent\0" + VALUE "InternalName", "gpg-card\0" + VALUE "OriginalFilename", "gpg-card.exe\0" + VALUE "ProductName", W32INFO_PRODUCTNAME + VALUE "ProductVersion", W32INFO_PRODUCTVERSION + VALUE "CompanyName", W32INFO_COMPANYNAME + VALUE "FileVersion", W32INFO_FILEVERSION + VALUE "LegalCopyright", W32INFO_LEGALCOPYRIGHT + VALUE "Comments", W32INFO_COMMENTS + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 0x4b0 + END + END diff --git a/tools/gpg-card.c b/tools/gpg-card.c new file mode 100644 index 000000000..f1d0dc8fc --- /dev/null +++ b/tools/gpg-card.c @@ -0,0 +1,3452 @@ +/* gpg-card.c - An interactive tool to work with cards. + * Copyright (C) 2019 g10 Code GmbH + * + * This file is part of GnuPG. + * + * This file 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. + * + * This file 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 Lesser 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 . + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include +#include +#include +#include +#ifdef HAVE_LIBREADLINE +# define GNUPG_LIBREADLINE_H_INCLUDED +# include +#endif /*HAVE_LIBREADLINE*/ + +#include "../common/util.h" +#include "../common/status.h" +#include "../common/i18n.h" +#include "../common/init.h" +#include "../common/sysutils.h" +#include "../common/asshelp.h" +#include "../common/userids.h" +#include "../common/ccparray.h" +#include "../common/exectool.h" +#include "../common/ttyio.h" +#include "../common/server-help.h" +#include "../common/openpgpdefs.h" + +#include "gpg-card.h" + + +#define CONTROL_D ('D' - 'A' + 1) + +/* Constants to identify the commands and options. */ +enum opt_values + { + aNull = 0, + + oQuiet = 'q', + oVerbose = 'v', + + oDebug = 500, + + oGpgProgram, + oGpgsmProgram, + oStatusFD, + oWithColons, + oNoAutostart, + oAgentProgram, + + oDisplay, + oTTYname, + oTTYtype, + oXauthority, + oLCctype, + oLCmessages, + + oDummy + }; + + +/* The list of commands and options. */ +static ARGPARSE_OPTS opts[] = { + ARGPARSE_group (301, ("@\nOptions:\n ")), + + ARGPARSE_s_n (oVerbose, "verbose", ("verbose")), + ARGPARSE_s_n (oQuiet, "quiet", ("be somewhat more quiet")), + ARGPARSE_s_s (oDebug, "debug", "@"), + ARGPARSE_s_s (oGpgProgram, "gpg", "@"), + ARGPARSE_s_s (oGpgsmProgram, "gpgsm", "@"), + ARGPARSE_s_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")), + ARGPARSE_s_n (oWithColons, "with-colons", "@"), + ARGPARSE_s_n (oNoAutostart, "no-autostart", "@"), + ARGPARSE_s_s (oAgentProgram, "agent-program", "@"), + ARGPARSE_s_s (oDisplay, "display", "@"), + ARGPARSE_s_s (oTTYname, "ttyname", "@"), + ARGPARSE_s_s (oTTYtype, "ttytype", "@"), + ARGPARSE_s_s (oXauthority, "xauthority", "@"), + ARGPARSE_s_s (oLCctype, "lc-ctype", "@"), + ARGPARSE_s_s (oLCmessages, "lc-messages","@"), + + ARGPARSE_end () +}; + +/* The list of supported debug flags. */ +static struct debug_flags_s debug_flags [] = + { + { DBG_IPC_VALUE , "ipc" }, + { DBG_EXTPROG_VALUE, "extprog" }, + { 0, NULL } + }; + + +/* An object to create lists of labels and keyrefs. */ +struct keyinfolabel_s +{ + const char *label; + const char *keyref; +}; +typedef struct keyinfolabel_s *keyinfolabel_t; + + +/* Limit of size of data we read from a file for certain commands. */ +#define MAX_GET_DATA_FROM_FILE 16384 + +/* Constants for OpenPGP cards. */ +#define OPENPGP_USER_PIN_DEFAULT "123456" +#define OPENPGP_ADMIN_PIN_DEFAULT "12345678" +#define OPENPGP_KDF_DATA_LENGTH_MIN 90 +#define OPENPGP_KDF_DATA_LENGTH_MAX 110 + + + + +/* Local prototypes. */ +static gpg_error_t dispatch_command (card_info_t info, const char *command); +static void interactive_loop (void); +#ifdef HAVE_LIBREADLINE +static char **command_completion (const char *text, int start, int end); +#endif /*HAVE_LIBREADLINE*/ + + + +/* Print usage information and provide strings for help. */ +static const char * +my_strusage( int level ) +{ + const char *p; + + switch (level) + { + case 11: p = "gpg-card"; break; + case 12: p = "@GNUPG@"; break; + case 13: p = VERSION; break; + case 17: p = PRINTABLE_OS_NAME; break; + case 19: p = ("Please report bugs to <@EMAIL@>.\n"); break; + + case 1: + case 40: + p = ("Usage: gpg-card" + " [options] [{[--] command [args]}] (-h for help)"); + break; + case 41: + p = ("Syntax: gpg-card" + " [options] [command [args] {-- command [args]}]\n\n" + "Tool to manage cards and tokens. With a command an interactive\n" + "mode is used. Use command \"help\" to list all commands."); + break; + + default: p = NULL; break; + } + return p; +} + + +static void +set_opt_session_env (const char *name, const char *value) +{ + gpg_error_t err; + + err = session_env_setenv (opt.session_env, name, value); + if (err) + log_fatal ("error setting session environment: %s\n", + gpg_strerror (err)); +} + + + +/* Command line parsing. */ +static void +parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts) +{ + while (optfile_parse (NULL, NULL, NULL, pargs, popts)) + { + switch (pargs->r_opt) + { + case oQuiet: opt.quiet = 1; break; + case oVerbose: opt.verbose++; break; + case oDebug: + if (parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags)) + { + pargs->r_opt = ARGPARSE_INVALID_ARG; + pargs->err = ARGPARSE_PRINT_ERROR; + } + break; + + case oGpgProgram: opt.gpg_program = pargs->r.ret_str; break; + case oGpgsmProgram: opt.gpgsm_program = pargs->r.ret_str; break; + case oAgentProgram: opt.agent_program = pargs->r.ret_str; break; + + case oStatusFD: + gnupg_set_status_fd (translate_sys2libc_fd_int (pargs->r.ret_int, 1)); + break; + + case oWithColons: opt.with_colons = 1; break; + case oNoAutostart: opt.autostart = 0; break; + + case oDisplay: set_opt_session_env ("DISPLAY", pargs->r.ret_str); break; + case oTTYname: set_opt_session_env ("GPG_TTY", pargs->r.ret_str); break; + case oTTYtype: set_opt_session_env ("TERM", pargs->r.ret_str); break; + case oXauthority: set_opt_session_env ("XAUTHORITY", + pargs->r.ret_str); break; + case oLCctype: opt.lc_ctype = pargs->r.ret_str; break; + case oLCmessages: opt.lc_messages = pargs->r.ret_str; break; + + default: pargs->err = 2; break; + } + } +} + + + +/* gpg-card main. */ +int +main (int argc, char **argv) +{ + gpg_error_t err; + ARGPARSE_ARGS pargs; + char **command_list = NULL; + int cmdidx; + char *command; + + gnupg_reopen_std ("gpg-card"); + set_strusage (my_strusage); + gnupg_rl_initialize (); + log_set_prefix ("gpg-card", GPGRT_LOG_WITH_PREFIX); + + /* Make sure that our subsystems are ready. */ + i18n_init(); + init_common_subsystems (&argc, &argv); + + assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT); + setup_libassuan_logging (&opt.debug, NULL); + + /* Setup default options. */ + opt.autostart = 1; + opt.session_env = session_env_new (); + if (!opt.session_env) + log_fatal ("error allocating session environment block: %s\n", + gpg_strerror (gpg_error_from_syserror ())); + + + /* Parse the command line. */ + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags = ARGPARSE_FLAG_KEEP; + parse_arguments (&pargs, opts); + + if (log_get_errorcount (0)) + exit (2); + + /* Set defaults for non given options. */ + if (!opt.gpg_program) + opt.gpg_program = gnupg_module_name (GNUPG_MODULE_NAME_GPG); + if (!opt.gpgsm_program) + opt.gpgsm_program = gnupg_module_name (GNUPG_MODULE_NAME_GPGSM); + + /* Now build the list of commands. We guess the size of the array + * by assuming each item is a complete command. Obviously this will + * be rarely the case, but it is less code to allocate a possible + * too large array. */ + command_list = xcalloc (argc+1, sizeof *command_list); + cmdidx = 0; + command = NULL; + while (argc) + { + for ( ; argc && strcmp (*argv, "--"); argc--, argv++) + { + if (!command) + command = xstrdup (*argv); + else + { + char *tmp = xstrconcat (command, " ", *argv, NULL); + xfree (command); + command = tmp; + } + } + if (argc) + { /* Skip the double dash. */ + argc--; + argv++; + } + if (command) + { + command_list[cmdidx++] = command; + command = NULL; + } + } + opt.interactive = !cmdidx; + + if (opt.interactive) + { + interactive_loop (); + err = 0; + } + else + { + struct card_info_s info_buffer = { 0 }; + card_info_t info = &info_buffer; + + err = 0; + for (cmdidx=0; (command = command_list[cmdidx]); cmdidx++) + { + err = dispatch_command (info, command); + if (err) + break; + } + if (gpg_err_code (err) == GPG_ERR_EOF) + err = 0; /* This was a "quit". */ + else if (command && !opt.quiet) + log_info ("stopped at command '%s'\n", command); + } + + flush_keyblock_cache (); + if (command_list) + { + for (cmdidx=0; command_list[cmdidx]; cmdidx++) + xfree (command_list[cmdidx]); + xfree (command_list); + } + if (err) + gnupg_status_printf (STATUS_FAILURE, "- %u", err); + else if (log_get_errorcount (0)) + gnupg_status_printf (STATUS_FAILURE, "- %u", GPG_ERR_GENERAL); + else + gnupg_status_printf (STATUS_SUCCESS, NULL); + return log_get_errorcount (0)? 1:0; +} + + +/* Read data from file FNAME up to MAX_GET_DATA_FROM_FILE characters. + * On error return an error code and stores NULL at R_BUFFER; on + * success returns 0 and stores the number of bytes read at R_BUFLEN + * and the address of a newly allocated buffer at R_BUFFER. A + * complementary nul byte is always appended to the data but not + * counted; this allows to pass NULL for R-BUFFER and consider the + * returned data as a string. */ +static gpg_error_t +get_data_from_file (const char *fname, char **r_buffer, size_t *r_buflen) +{ + gpg_error_t err; + estream_t fp; + char *data; + int n; + + *r_buffer = NULL; + if (r_buflen) + *r_buflen = 0; + + fp = es_fopen (fname, "rb"); + if (!fp) + { + err = gpg_error_from_syserror (); + log_error (_("can't open '%s': %s\n"), fname, gpg_strerror (err)); + return err; + } + + data = xtrymalloc (MAX_GET_DATA_FROM_FILE); + if (!data) + { + err = gpg_error_from_syserror (); + log_error (_("error allocating enough memory: %s\n"), gpg_strerror (err)); + es_fclose (fp); + return err; + } + + n = es_fread (data, 1, MAX_GET_DATA_FROM_FILE - 1, fp); + es_fclose (fp); + if (n < 0) + { + err = gpg_error_from_syserror (); + tty_printf (_("error reading '%s': %s\n"), fname, gpg_strerror (err)); + xfree (data); + return err; + } + data[n] = 0; + + *r_buffer = data; + if (r_buflen) + *r_buflen = n; + return 0; +} + + +/* Write LENGTH bytes from BUFFER to file FNAME. Return 0 on + * success. */ +static gpg_error_t +put_data_to_file (const char *fname, const void *buffer, size_t length) +{ + gpg_error_t err; + estream_t fp; + + fp = es_fopen (fname, "wb"); + if (!fp) + { + err = gpg_error_from_syserror (); + log_error (_("can't create '%s': %s\n"), fname, gpg_strerror (err)); + return err; + } + + if (length && es_fwrite (buffer, length, 1, fp) != 1) + { + err = gpg_error_from_syserror (); + log_error (_("error writing '%s': %s\n"), fname, gpg_strerror (err)); + es_fclose (fp); + return err; + } + if (es_fclose (fp)) + { + err = gpg_error_from_syserror (); + log_error (_("error writing '%s': %s\n"), fname, gpg_strerror (err)); + return err; + } + return 0; +} + + + +/* Simply prints TEXT to the output. Returns 0 as a convenience. + * This is a separate fucntion so that it can be extended to run + * less(1) or so. The extra arguments are int values terminated by a + * 0 to indicate card application types supported with this command. + * If none are given (just teh final 0), this is a general + * command. */ +static gpg_error_t +print_help (const char *text, ...) +{ + estream_t fp; + va_list arg_ptr; + int value; + int any = 0; + + fp = opt.interactive? NULL : es_stdout; + tty_fprintf (fp, "%s\n", text); + + va_start (arg_ptr, text); + while ((value = va_arg (arg_ptr, int))) + { + if (!any) + tty_fprintf (fp, "[Supported by: "); + tty_fprintf (fp, "%s%s", any?", ":"", app_type_string (value)); + any = 1; + } + if (any) + tty_fprintf (fp, "]\n"); + + va_end (arg_ptr); + return 0; +} + + +/* Return the OpenPGP card manufacturer name. */ +static const char * +get_manufacturer (unsigned int no) +{ + /* Note: Make sure that there is no colon or linefeed in the string. */ + switch (no) + { + case 0x0001: return "PPC Card Systems"; + case 0x0002: return "Prism"; + case 0x0003: return "OpenFortress"; + case 0x0004: return "Wewid"; + case 0x0005: return "ZeitControl"; + case 0x0006: return "Yubico"; + case 0x0007: return "OpenKMS"; + case 0x0008: return "LogoEmail"; + case 0x0009: return "Fidesmo"; + case 0x000A: return "Dangerous Things"; + + case 0x002A: return "Magrathea"; + case 0x0042: return "GnuPG e.V."; + + case 0x1337: return "Warsaw Hackerspace"; + case 0x2342: return "warpzone"; /* hackerspace Muenster. */ + case 0x4354: return "Confidential Technologies"; /* cotech.de */ + case 0x63AF: return "Trustica"; + case 0xBD0E: return "Paranoidlabs"; + case 0xF517: return "FSIJ"; + + /* 0x0000 and 0xFFFF are defined as test cards per spec, + * 0xFF00 to 0xFFFE are assigned for use with randomly created + * serial numbers. */ + case 0x0000: + case 0xffff: return "test card"; + default: return (no & 0xff00) == 0xff00? "unmanaged S/N range":"unknown"; + } +} + +/* Print an (OpenPGP) fingerprint. */ +static void +print_shax_fpr (estream_t fp, const unsigned char *fpr, unsigned int fprlen) +{ + int i; + + if (fpr) + { + /* FIXME: Fix formatting for FPRLEN != 20 */ + for (i=0; i < fprlen ; i+=2, fpr += 2 ) + { + if (i == 10 ) + tty_fprintf (fp, " "); + tty_fprintf (fp, " %02X%02X", *fpr, fpr[1]); + } + } + else + tty_fprintf (fp, " [none]"); + tty_fprintf (fp, "\n"); +} + +/* Print the keygrip GRP. */ +static void +print_keygrip (estream_t fp, const unsigned char *grp) +{ + int i; + + for (i=0; i < 20 ; i++, grp++) + tty_fprintf (fp, "%02X", *grp); + tty_fprintf (fp, "\n"); +} + + +/* Print a string but avoid printing control characters. */ +static void +print_string (estream_t fp, const char *text, const char *name) +{ + tty_fprintf (fp, "%s", text); + + /* FIXME: tty_printf_utf8_string2 eats everything after and + including an @ - e.g. when printing an url. */ + if (name && *name) + { + if (fp) + print_utf8_buffer2 (fp, name, strlen (name), '\n'); + else + tty_print_utf8_string2 (NULL, name, strlen (name), 0); + } + else + tty_fprintf (fp, _("[not set]")); + tty_fprintf (fp, "\n"); +} + + +/* Print an ISO formatted name or "[not set]". */ +static void +print_isoname (estream_t fp, const char *name) +{ + if (name && *name) + { + char *p, *given, *buf; + + buf = xstrdup (name); + given = strstr (buf, "<<"); + for (p=buf; *p; p++) + if (*p == '<') + *p = ' '; + if (given && given[2]) + { + *given = 0; + given += 2; + if (fp) + print_utf8_buffer2 (fp, given, strlen (given), '\n'); + else + tty_print_utf8_string2 (NULL, given, strlen (given), 0); + + if (*buf) + tty_fprintf (fp, " "); + } + + if (fp) + print_utf8_buffer2 (fp, buf, strlen (buf), '\n'); + else + tty_print_utf8_string2 (NULL, buf, strlen (buf), 0); + + xfree (buf); + } + else + { + tty_fprintf (fp, _("[not set]")); + } + + tty_fprintf (fp, "\n"); +} + + +/* Return true if the buffer MEM of length memlen consists only of zeroes. */ +static int +mem_is_zero (const char *mem, unsigned int memlen) +{ + int i; + + for (i=0; i < memlen && !mem[i]; i++) + ; + return (i == memlen); +} + + + +/* Helper to list a single keyref. */ +static void +list_one_kinfo (key_info_t firstkinfo, key_info_t kinfo, estream_t fp) +{ + gpg_error_t err; + keyblock_t keyblock = NULL; + keyblock_t kb; + pubkey_t pubkey; + userid_t uid; + key_info_t ki; + const char *s; + gcry_sexp_t s_pkey; + int any; + + if (firstkinfo && kinfo) + { + tty_fprintf (fp, " "); + if (mem_is_zero (kinfo->grip, sizeof kinfo->grip)) + { + tty_fprintf (fp, "[none]\n"); + goto leave; + } + + print_keygrip (fp, kinfo->grip); + tty_fprintf (fp, " keyref .....: %s", kinfo->keyref); + if (kinfo->usage) + { + any = 0; + tty_fprintf (fp, " ("); + if ((kinfo->usage & GCRY_PK_USAGE_SIGN)) + { tty_fprintf (fp, "sign"); any=1; } + if ((kinfo->usage & GCRY_PK_USAGE_CERT)) + { tty_fprintf (fp, "%scert", any?",":""); any=1; } + if ((kinfo->usage & GCRY_PK_USAGE_AUTH)) + { tty_fprintf (fp, "%sauth", any?",":""); any=1; } + if ((kinfo->usage & GCRY_PK_USAGE_ENCR)) + { tty_fprintf (fp, "%sencr", any?",":""); any=1; } + tty_fprintf (fp, ")"); + } + tty_fprintf (fp, "\n"); + + if (!scd_readkey (kinfo->keyref, &s_pkey)) + { + char *tmp = pubkey_algo_string (s_pkey); + tty_fprintf (fp, " algorithm ..: %s\n", tmp); + xfree (tmp); + gcry_sexp_release (s_pkey); + s_pkey = NULL; + } + + if (kinfo->fprlen && kinfo->created) + { + tty_fprintf (fp, " fingerprint :"); + print_shax_fpr (fp, kinfo->fpr, kinfo->fprlen); + 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, " \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); +} + + +/* List all keyinfo in INFO using the list of LABELS. */ +static void +list_all_kinfo (card_info_t info, keyinfolabel_t labels, estream_t fp) +{ + key_info_t kinfo; + int idx, i; + + /* Print the keyinfo. We first print those we known and then all + * remaining item. */ + for (kinfo = info->kinfo; kinfo; kinfo = kinfo->next) + kinfo->xflag = 0; + if (labels) + { + for (idx=0; labels[idx].label; idx++) + { + tty_fprintf (fp, "%s", labels[idx].label); + kinfo = find_kinfo (info, labels[idx].keyref); + list_one_kinfo (info->kinfo, kinfo, fp); + if (kinfo) + kinfo->xflag = 1; + } + } + for (kinfo = info->kinfo; kinfo; kinfo = kinfo->next) + { + if (kinfo->xflag) + continue; + tty_fprintf (fp, "Key %s ", kinfo->keyref); + for (i=5+strlen (kinfo->keyref); i < 18; i++) + tty_fprintf (fp, "."); + tty_fprintf (fp, ":"); + list_one_kinfo (info->kinfo, kinfo, fp); + } +} + + +/* List OpenPGP card specific data. */ +static void +list_openpgp (card_info_t info, estream_t fp) +{ + static struct keyinfolabel_s keyinfolabels[] = { + { "Signature key ....:", "OPENPGP.1" }, + { "Encryption key....:", "OPENPGP.2" }, + { "Authentication key:", "OPENPGP.3" }, + { NULL, NULL } + }; + int i; + + if (!info->serialno + || strncmp (info->serialno, "D27600012401", 12) + || strlen (info->serialno) != 32 ) + { + tty_fprintf (fp, "invalid OpenPGP card\n"); + return; + } + + tty_fprintf (fp, "Version ..........: %.1s%c.%.1s%c\n", + info->serialno[12] == '0'?"":info->serialno+12, + info->serialno[13], + info->serialno[14] == '0'?"":info->serialno+14, + info->serialno[15]); + tty_fprintf (fp, "Manufacturer .....: %s\n", + get_manufacturer (xtoi_2(info->serialno+16)*256 + + xtoi_2 (info->serialno+18))); + tty_fprintf (fp, "Name of cardholder: "); + print_isoname (fp, info->disp_name); + + print_string (fp, "Language prefs ...: ", info->disp_lang); + tty_fprintf (fp, "Salutation .......: %s\n", + info->disp_sex == 1? _("Mr."): + info->disp_sex == 2? _("Mrs.") : ""); + print_string (fp, "URL of public key : ", info->pubkey_url); + print_string (fp, "Login data .......: ", info->login_data); + if (info->private_do[0]) + print_string (fp, "Private DO 1 .....: ", info->private_do[0]); + if (info->private_do[1]) + print_string (fp, "Private DO 2 .....: ", info->private_do[1]); + if (info->private_do[2]) + print_string (fp, "Private DO 3 .....: ", info->private_do[2]); + if (info->private_do[3]) + print_string (fp, "Private DO 4 .....: ", info->private_do[3]); + if (info->cafpr1len) + { + tty_fprintf (fp, "CA fingerprint %d .:", 1); + print_shax_fpr (fp, info->cafpr1, info->cafpr1len); + } + if (info->cafpr2len) + { + tty_fprintf (fp, "CA fingerprint %d .:", 2); + print_shax_fpr (fp, info->cafpr2, info->cafpr2len); + } + if (info->cafpr3len) + { + tty_fprintf (fp, "CA fingerprint %d .:", 3); + print_shax_fpr (fp, info->cafpr3, info->cafpr3len); + } + tty_fprintf (fp, "Signature PIN ....: %s\n", + info->chv1_cached? _("not forced"): _("forced")); + if (info->key_attr[0].algo) + { + tty_fprintf (fp, "Key attributes ...:"); + for (i=0; i < DIM (info->key_attr); i++) + if (info->key_attr[i].algo == PUBKEY_ALGO_RSA) + tty_fprintf (fp, " rsa%u", info->key_attr[i].nbits); + else if (info->key_attr[i].algo == PUBKEY_ALGO_ECDH + || info->key_attr[i].algo == PUBKEY_ALGO_ECDSA + || info->key_attr[i].algo == PUBKEY_ALGO_EDDSA) + { + const char *curve_for_print = "?"; + const char *oid; + + if (info->key_attr[i].curve + && (oid = openpgp_curve_to_oid (info->key_attr[i].curve, NULL))) + curve_for_print = openpgp_oid_to_curve (oid, 0); + tty_fprintf (fp, " %s", curve_for_print); + } + tty_fprintf (fp, "\n"); + } + tty_fprintf (fp, "Max. PIN lengths .: %d %d %d\n", + info->chvmaxlen[0], info->chvmaxlen[1], info->chvmaxlen[2]); + tty_fprintf (fp, "PIN retry counter : %d %d %d\n", + info->chvinfo[0], info->chvinfo[1], info->chvinfo[2]); + tty_fprintf (fp, "Signature counter : %lu\n", info->sig_counter); + if (info->extcap.kdf) + { + tty_fprintf (fp, "KDF setting ......: %s\n", + info->kdf_do_enabled ? "on" : "off"); + } + if (info->extcap.bt) + { + tty_fprintf (fp, "UIF setting ......: Sign=%s Decrypt=%s Auth=%s\n", + info->uif[0] ? "on" : "off", info->uif[1] ? "on" : "off", + info->uif[2] ? "on" : "off"); + } + + list_all_kinfo (info, keyinfolabels, fp); + + /* tty_fprintf (fp, "General key info->.: "); */ + /* thefpr = (info->fpr1len? info->fpr1 : info->fpr2len? info->fpr2 : */ + /* info->fpr3len? info->fpr3 : NULL); */ + /* thefprlen = (info->fpr1len? info->fpr1len : info->fpr2len? info->fpr2len : */ + /* info->fpr3len? info->fpr3len : 0); */ + /* If the fingerprint is all 0xff, the key has no associated + OpenPGP certificate. */ + /* if ( thefpr && !mem_is_ff (thefpr, thefprlen) */ + /* && !get_pubkey_byfprint (ctrl, pk, &keyblock, thefpr, thefprlen)) */ + /* { */ + /* print_pubkey_info (ctrl, fp, pk); */ + /* if (keyblock) */ + /* print_card_key_info (fp, keyblock); */ + /* } */ + /* else */ + /* tty_fprintf (fp, "[none]\n"); */ +} + + +/* List PIV card specific data. */ +static void +list_piv (card_info_t info, estream_t fp) +{ + static struct keyinfolabel_s keyinfolabels[] = { + { "PIV authentication:", "PIV.9A" }, + { "Card authenticat. :", "PIV.9E" }, + { "Digital signature :", "PIV.9C" }, + { "Key management ...:", "PIV.9D" }, + { NULL, NULL } + }; + const char *s; + int i; + + if (info->chvusage[0] || info->chvusage[1]) + { + tty_fprintf (fp, "PIN usage policy .:"); + if ((info->chvusage[0] & 0x40)) + tty_fprintf (fp, " app-pin"); + if ((info->chvusage[0] & 0x20)) + tty_fprintf (fp, " global-pin"); + if ((info->chvusage[0] & 0x10)) + tty_fprintf (fp, " occ"); + if ((info->chvusage[0] & 0x08)) + tty_fprintf (fp, " vci"); + if ((info->chvusage[0] & 0x08) && !(info->chvusage[0] & 0x04)) + tty_fprintf (fp, " pairing"); + + if (info->chvusage[1] == 0x10) + tty_fprintf (fp, " primary:card"); + else if (info->chvusage[1] == 0x20) + tty_fprintf (fp, " primary:global"); + + tty_fprintf (fp, "\n"); + } + + tty_fprintf (fp, "PIN retry counter :"); + for (i=0; i < DIM (info->chvinfo); i++) + { + if (info->chvinfo[i] > 0) + tty_fprintf (fp, " %d", info->chvinfo[i]); + else + { + switch (info->chvinfo[i]) + { + case -1: s = "[error]"; break; + case -2: s = "-"; break; /* No such PIN or info not available. */ + case -3: s = "[blocked]"; break; + case -5: s = "[verified]"; break; + default: s = "[?]"; break; + } + tty_fprintf (fp, " %s", s); + } + } + tty_fprintf (fp, "\n"); + list_all_kinfo (info, keyinfolabels, fp); + +} + + +/* Print all available information about the current card. */ +static void +list_card (card_info_t info) +{ + estream_t fp = opt.interactive? NULL : es_stdout; + + tty_fprintf (fp, "Reader ...........: %s\n", + info->reader? info->reader : "[none]"); + if (info->cardtype) + tty_fprintf (fp, "Card type ........: %s\n", info->cardtype); + tty_fprintf (fp, "Serial number ....: %s\n", + info->serialno? info->serialno : "[none]"); + tty_fprintf (fp, "Application type .: %s%s%s%s\n", + app_type_string (info->apptype), + info->apptype == APP_TYPE_UNKNOWN && info->apptypestr? "(":"", + info->apptype == APP_TYPE_UNKNOWN && info->apptypestr + ? info->apptypestr:"", + info->apptype == APP_TYPE_UNKNOWN && info->apptypestr? ")":""); + if (info->serialno && info->dispserialno + && strcmp (info->serialno, info->dispserialno)) + tty_fprintf (fp, "Displayed S/N ....: %s\n", info->dispserialno); + + switch (info->apptype) + { + case APP_TYPE_OPENPGP: list_openpgp (info, fp); break; + case APP_TYPE_PIV: list_piv (info, fp); break; + default: break; + } +} + + + +/* The VERIFY command. */ +static gpg_error_t +cmd_verify (card_info_t info, char *argstr) +{ + gpg_error_t err; + const char *pinref; + + if (!info) + return print_help ("verify [chvid]", 0); + + if (*argstr) + pinref = argstr; + else if (info->apptype == APP_TYPE_OPENPGP) + pinref = info->serialno; + else if (info->apptype == APP_TYPE_PIV) + pinref = "PIV.80"; + else + return gpg_error (GPG_ERR_MISSING_VALUE); + + err = scd_checkpin (pinref); + if (err) + log_error ("verify failed: %s <%s>\n", + gpg_strerror (err), gpg_strsource (err)); + return err; +} + + +static gpg_error_t +cmd_authenticate (card_info_t info, char *argstr) +{ + gpg_error_t err; + int opt_setkey; + int opt_raw; + char *string = NULL; + char *key = NULL; + size_t keylen; + + if (!info) + return print_help + ("AUTHENTICATE [--setkey] [--raw] [< FILE]|KEY\n\n" + "Perform a mutual autentication either by reading the key\n" + "from FILE or by taking it from the command line. Without\n" + "the option --raw the key is expected to be hex encoded.\n" + "To install a new administration key --setkey is used; this\n" + "requires a prior authentication with the old key.", + APP_TYPE_PIV, 0); + + if (info->apptype != APP_TYPE_PIV) + { + log_info ("Note: This is a PIV only command.\n"); + return gpg_error (GPG_ERR_NOT_SUPPORTED); + } + + opt_setkey = has_leading_option (argstr, "--setkey"); + opt_raw = has_leading_option (argstr, "--raw"); + argstr = skip_options (argstr); + + if (*argstr == '<') /* Read key from a file. */ + { + for (argstr++; spacep (argstr); argstr++) + ; + err = get_data_from_file (argstr, &string, NULL); + if (err) + goto leave; + } + + if (opt_raw) + { + key = string? string : xstrdup (argstr); + string = NULL; + keylen = strlen (key); + } + else + { + key = hex_to_buffer (string? string: argstr, &keylen); + if (!key) + { + err = gpg_error_from_syserror (); + goto leave; + } + } + err = scd_setattr (opt_setkey? "SET-ADM-KEY":"AUTH-ADM-KEY", key, keylen); + + leave: + if (key) + { + wipememory (key, keylen); + xfree (key); + } + xfree (string); + return err; +} + + +/* Helper for cmd_name to qyery a part of name. */ +static char * +ask_one_name (const char *prompt) +{ + char *name; + int i; + + for (;;) + { + name = tty_get (prompt); + trim_spaces (name); + tty_kill_prompt (); + if (!*name || *name == CONTROL_D) + { + if (*name == CONTROL_D) + tty_fprintf (NULL, "\n"); + xfree (name); + return NULL; + } + for (i=0; name[i] && name[i] >= ' ' && name[i] <= 126; i++) + ; + + /* The name must be in Latin-1 and not UTF-8 - lacking the code + * to ensure this we restrict it to ASCII. */ + if (name[i]) + tty_printf (_("Error: Only plain ASCII is currently allowed.\n")); + else if (strchr (name, '<')) + tty_printf (_("Error: The \"<\" character may not be used.\n")); + else if (strstr (name, " ")) + tty_printf (_("Error: Double spaces are not allowed.\n")); + else + return name; + xfree (name); + } +} + + +/* The NAME command. */ +static gpg_error_t +cmd_name (card_info_t info, const char *argstr) +{ + gpg_error_t err; + char *surname, *givenname; + char *isoname, *p; + + if (!info) + return print_help + ("name [--clear]\n\n" + "Set the name field of an OpenPGP card. With --clear the stored\n" + "name is cleared off the card.", APP_TYPE_OPENPGP, APP_TYPE_NKS, 0); + + if (info->apptype != APP_TYPE_OPENPGP) + { + log_info ("Note: This is an OpenPGP only command.\n"); + return gpg_error (GPG_ERR_NOT_SUPPORTED); + } + + again: + if (!strcmp (argstr, "--clear")) + isoname = xstrdup (" "); /* No real way to clear; set to space instead. */ + else + { + surname = ask_one_name (_("Cardholder's surname: ")); + givenname = ask_one_name (_("Cardholder's given name: ")); + if (!surname || !givenname || (!*surname && !*givenname)) + { + xfree (surname); + xfree (givenname); + return gpg_error (GPG_ERR_CANCELED); + } + + isoname = xstrconcat (surname, "<<", givenname, NULL); + xfree (surname); + xfree (givenname); + for (p=isoname; *p; p++) + if (*p == ' ') + *p = '<'; + + if (strlen (isoname) > 39 ) + { + log_info (_("Error: Combined name too long " + "(limit is %d characters).\n"), 39); + xfree (isoname); + goto again; + } + } + + err = scd_setattr ("DISP-NAME", isoname, strlen (isoname)); + + xfree (isoname); + return err; +} + + +static gpg_error_t +cmd_url (card_info_t info, const char *argstr) +{ + gpg_error_t err; + char *url; + + if (!info) + return print_help + ("URL [--clear]\n\n" + "Set the URL data object. That data object can be used by\n" + "the FETCH command to retrieve the full public key. The\n" + "option --clear deletes the content of that data object.", + APP_TYPE_OPENPGP, 0); + + if (info->apptype != APP_TYPE_OPENPGP) + { + log_info ("Note: This is an OpenPGP only command.\n"); + return gpg_error (GPG_ERR_NOT_SUPPORTED); + } + + if (!strcmp (argstr, "--clear")) + url = xstrdup (" "); /* No real way to clear; set to space instead. */ + else + { + url = tty_get (_("URL to retrieve public key: ")); + trim_spaces (url); + tty_kill_prompt (); + if (!*url || *url == CONTROL_D) + { + err = gpg_error (GPG_ERR_CANCELED); + goto leave; + } + } + + err = scd_setattr ("PUBKEY-URL", url, strlen (url)); + + leave: + xfree (url); + return err; +} + + +/* Fetch the key from the URL given on the card or try to get it from + * the default keyserver. */ +static gpg_error_t +cmd_fetch (card_info_t info) +{ + gpg_error_t err; + key_info_t kinfo; + + if (!info) + return print_help + ("FETCH\n\n" + "Retrieve a key using the URL data object or if that is missing\n" + "using the fingerprint.", APP_TYPE_OPENPGP, 0); + + if (info->pubkey_url && *info->pubkey_url) + { + /* strlist_t sl = NULL; */ + + /* add_to_strlist (&sl, info.pubkey_url); */ + /* err = keyserver_fetch (ctrl, sl, KEYORG_URL); */ + /* free_strlist (sl); */ + err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */ + } + else if ((kinfo = find_kinfo (info, "OPENPGP.1")) && kinfo->fprlen) + { + /* rc = keyserver_import_fprint (ctrl, info.fpr1, info.fpr1len, */ + /* opt.keyserver, 0); */ + err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */ + } + else + err = gpg_error (GPG_ERR_NO_DATA); + + return err; +} + + +static gpg_error_t +cmd_login (card_info_t info, char *argstr) +{ + gpg_error_t err; + char *data; + size_t datalen; + + if (!info) + return print_help + ("LOGIN [--clear] [< FILE]\n\n" + "Set the login data object. If FILE is given the data is\n" + "is read from that file. This allows for binary data.\n" + "The option --clear deletes the login data.", + APP_TYPE_OPENPGP, 0); + + if (!strcmp (argstr, "--clear")) + { + data = xstrdup (" "); /* kludge. */ + datalen = 1; + } + else if (*argstr == '<') /* Read it from a file */ + { + for (argstr++; spacep (argstr); argstr++) + ; + err = get_data_from_file (argstr, &data, &datalen); + if (err) + goto leave; + } + else + { + data = tty_get (_("Login data (account name): ")); + trim_spaces (data); + tty_kill_prompt (); + if (!*data || *data == CONTROL_D) + { + err = gpg_error (GPG_ERR_CANCELED); + goto leave; + } + datalen = strlen (data); + } + + err = scd_setattr ("LOGIN-DATA", data, datalen); + + leave: + xfree (data); + return err; +} + + +static gpg_error_t +cmd_lang (card_info_t info, const char *argstr) +{ + gpg_error_t err; + char *data, *p; + + if (!info) + return print_help + ("LANG [--clear]\n\n" + "Change the language info for the card. This info can be used\n" + "by applications for a personalized greeting. Up to 4 two-digit\n" + "language identifiers can be entered as a preference. The option\n" + "--clear removes all identifiers. GnuPG does not use this info.", + APP_TYPE_OPENPGP, 0); + + if (!strcmp (argstr, "--clear")) + data = xstrdup (" "); /* Note that we need two spaces here. */ + else + { + again: + data = tty_get (_("Language preferences: ")); + trim_spaces (data); + tty_kill_prompt (); + if (!*data || *data == CONTROL_D) + { + err = gpg_error (GPG_ERR_CANCELED); + goto leave; + } + + if (strlen (data) > 8 || (strlen (data) & 1)) + { + log_info (_("Error: invalid length of preference string.\n")); + xfree (data); + goto again; + } + + for (p=data; *p && *p >= 'a' && *p <= 'z'; p++) + ; + if (*p) + { + log_info (_("Error: invalid characters in preference string.\n")); + xfree (data); + goto again; + } + } + + err = scd_setattr ("DISP-LANG", data, strlen (data)); + + leave: + xfree (data); + return err; +} + + +static gpg_error_t +cmd_salut (card_info_t info, const char *argstr) +{ + gpg_error_t err; + char *data = NULL; + const char *str; + + if (!info) + return print_help + ("SALUT [--clear]\n\n" + "Change the salutation info for the card. This info can be used\n" + "by applications for a personalized greeting. The option --clear\n" + "removes this data object. GnuPG does not use this info.", + APP_TYPE_OPENPGP, 0); + + again: + if (!strcmp (argstr, "--clear")) + str = "9"; + else + { + data = tty_get (_("Salutation (M = Mr., F = Mrs., or space): ")); + trim_spaces (data); + tty_kill_prompt (); + if (*data == CONTROL_D) + { + err = gpg_error (GPG_ERR_CANCELED); + goto leave; + } + + if (!*data) + str = "9"; + else if ((*data == 'M' || *data == 'm') && !data[1]) + str = "1"; + else if ((*data == 'F' || *data == 'f') && !data[1]) + str = "2"; + else + { + tty_printf (_("Error: invalid response.\n")); + xfree (data); + goto again; + } + } + + err = scd_setattr ("DISP-SEX", str, 1); + leave: + xfree (data); + return err; +} + + +static gpg_error_t +cmd_cafpr (card_info_t info, char *argstr) +{ + gpg_error_t err; + char *data = NULL; + const char *s; + int i, c; + unsigned char fpr[32]; + int fprlen; + int fprno; + int opt_clear = 0; + + if (!info) + return print_help + ("CAFPR [--clear] N\n\n" + "Change the CA fingerprint number N. N must be in the\n" + "range 1 to 3. The option --clear clears the specified\n" + "CA fingerprint N or all of them if N is 0 or not given.", + APP_TYPE_OPENPGP, 0); + + + opt_clear = has_leading_option (argstr, "--clear"); + argstr = skip_options (argstr); + + if (digitp (argstr)) + { + fprno = atoi (argstr); + while (digitp (argstr)) + argstr++; + while (spacep (argstr)) + argstr++; + } + else + fprno = 0; + + if (opt_clear && !fprno) + ; /* Okay: clear all fprs. */ + else if (fprno < 1 || fprno > 3) + { + err = gpg_error (GPG_ERR_INV_ARG); + goto leave; + } + + again: + if (opt_clear) + { + memset (fpr, 0, 20); + fprlen = 20; + } + else + { + xfree (data); + data = tty_get (_("CA fingerprint: ")); + trim_spaces (data); + tty_kill_prompt (); + if (!*data || *data == CONTROL_D) + { + err = gpg_error (GPG_ERR_CANCELED); + goto leave; + } + + for (i=0, s=data; i < sizeof fpr && *s; ) + { + while (spacep(s)) + s++; + if (*s == ':') + s++; + while (spacep(s)) + s++; + c = hextobyte (s); + if (c == -1) + break; + fpr[i++] = c; + s += 2; + } + fprlen = i; + if ((fprlen != 20 && fprlen != 32) || *s) + { + log_error (_("Error: invalid formatted fingerprint.\n")); + goto again; + } + } + + if (!fprno) + { + log_assert (opt_clear); + err = scd_setattr ("CA-FPR-1", fpr, fprlen); + if (!err) + err = scd_setattr ("CA-FPR-2", fpr, fprlen); + if (!err) + err = scd_setattr ("CA-FPR-3", fpr, fprlen); + } + else + err = scd_setattr (fprno==1?"CA-FPR-1": + fprno==2?"CA-FPR-2": + fprno==3?"CA-FPR-3":"x", fpr, fprlen); + + leave: + xfree (data); + return err; +} + + +static gpg_error_t +cmd_privatedo (card_info_t info, char *argstr) +{ + gpg_error_t err; + int opt_clear; + char *do_name = NULL; + char *data = NULL; + size_t datalen; + int do_no; + + if (!info) + return print_help + ("PRIVATEDO [--clear] N [< FILE]\n\n" + "Change the private data object N. N must be in the\n" + "range 1 to 4. If FILE is given the data is is read\n" + "from that file. The option --clear clears the data.", + APP_TYPE_OPENPGP, 0); + + opt_clear = has_leading_option (argstr, "--clear"); + argstr = skip_options (argstr); + + if (digitp (argstr)) + { + do_no = atoi (argstr); + while (digitp (argstr)) + argstr++; + while (spacep (argstr)) + argstr++; + } + else + do_no = 0; + + if (do_no < 1 || do_no > 4) + { + err = gpg_error (GPG_ERR_INV_ARG); + goto leave; + } + do_name = xasprintf ("PRIVATE-DO-%d", do_no); + + if (opt_clear) + { + data = xstrdup (" "); + datalen = 1; + } + else if (*argstr == '<') /* Read it from a file */ + { + for (argstr++; spacep (argstr); argstr++) + ; + err = get_data_from_file (argstr, &data, &datalen); + if (err) + goto leave; + } + else if (*argstr) + { + err = gpg_error (GPG_ERR_INV_ARG); + goto leave; + } + else + { + data = tty_get (_("Private DO data: ")); + trim_spaces (data); + tty_kill_prompt (); + datalen = strlen (data); + if (!*data || *data == CONTROL_D) + { + err = gpg_error (GPG_ERR_CANCELED); + goto leave; + } + } + + err = scd_setattr (do_name, data, datalen); + + leave: + xfree (do_name); + xfree (data); + return err; +} + + +static gpg_error_t +cmd_writecert (card_info_t info, char *argstr) +{ + gpg_error_t err; + int opt_clear; + char *certref_buffer = NULL; + char *certref; + char *data = NULL; + size_t datalen; + + if (!info) + return print_help + ("WRITECERT [--clear] CERTREF < FILE\n\n" + "Write a certificate for key 3. Unless --clear is given\n" + "the file argument is mandatory. The option --clear removes\n" + "the certificate from the card.", + APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); + + opt_clear = has_leading_option (argstr, "--clear"); + argstr = skip_options (argstr); + + certref = argstr; + if ((argstr = strchr (certref, ' '))) + { + *argstr++ = 0; + trim_spaces (certref); + trim_spaces (argstr); + } + else /* Let argstr point to an empty string. */ + argstr = certref + strlen (certref); + + if (info->apptype == APP_TYPE_OPENPGP) + { + if (ascii_strcasecmp (certref, "OPENPGP.3") && strcmp (certref, "3")) + { + err = gpg_error (GPG_ERR_INV_ID); + log_error ("Error: CERTREF must be \"3\" or \"OPENPGP.3\"\n"); + goto leave; + } + certref = certref_buffer = xstrdup ("OPENPGP.3"); + } + else /* Upcase the certref; prepend cardtype if needed. */ + { + if (!strchr (certref, '.')) + certref_buffer = xstrconcat (app_type_string (info->apptype), ".", + certref, NULL); + else + certref_buffer = xstrdup (certref); + ascii_strupr (certref_buffer); + certref = certref_buffer; + } + + if (opt_clear) + { + data = xstrdup (" "); + datalen = 1; + } + else if (*argstr == '<') /* Read it from a file */ + { + for (argstr++; spacep (argstr); argstr++) + ; + err = get_data_from_file (argstr, &data, &datalen); + if (err) + goto leave; + } + else + { + err = gpg_error (GPG_ERR_INV_ARG); + goto leave; + } + + err = scd_writecert (certref, data, datalen); + + leave: + xfree (data); + xfree (certref_buffer); + return err; +} + + +static gpg_error_t +cmd_readcert (card_info_t info, char *argstr) +{ + gpg_error_t err; + char *certref_buffer = NULL; + char *certref; + void *data = NULL; + size_t datalen; + const char *fname; + + if (!info) + return print_help + ("READCERT CERTREF > FILE\n\n" + "Read the certificate for key 3 and store it in FILE.", + APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); + + argstr = skip_options (argstr); + + certref = argstr; + if ((argstr = strchr (certref, ' '))) + { + *argstr++ = 0; + trim_spaces (certref); + trim_spaces (argstr); + } + else /* Let argstr point to an empty string. */ + argstr = certref + strlen (certref); + + if (info->apptype == APP_TYPE_OPENPGP) + { + if (ascii_strcasecmp (certref, "OPENPGP.3") && strcmp (certref, "3")) + { + err = gpg_error (GPG_ERR_INV_ID); + log_error ("Error: CERTREF must be \"3\" or \"OPENPGP.3\"\n"); + goto leave; + } + certref = certref_buffer = xstrdup ("OPENPGP.3"); + } + + if (*argstr == '>') /* Write it to a file */ + { + for (argstr++; spacep (argstr); argstr++) + ; + fname = argstr; + } + else + { + err = gpg_error (GPG_ERR_INV_ARG); + goto leave; + } + + err = scd_readcert (certref, &data, &datalen); + if (err) + goto leave; + + err = put_data_to_file (fname, data, datalen); + + leave: + xfree (data); + xfree (certref_buffer); + return err; +} + + +static gpg_error_t +cmd_forcesig (card_info_t info) +{ + gpg_error_t err; + int newstate; + + if (!info) + return print_help + ("FORCESIG\n\n" + "Toggle the forcesig flag of an OpenPGP card.", + APP_TYPE_OPENPGP, 0); + + if (info->apptype != APP_TYPE_OPENPGP) + { + log_info ("Note: This is an OpenPGP only command.\n"); + return gpg_error (GPG_ERR_NOT_SUPPORTED); + } + + newstate = !info->chv1_cached; + + err = scd_setattr ("CHV-STATUS-1", newstate? "\x01":"", 1); + if (err) + goto leave; + + /* Read it back to be sure we have the right toggle state the next + * time. */ + err = scd_getattr ("CHV-STATUS", info); + + leave: + return err; +} + + + +/* Helper for cmd_generate_openpgp. Noe that either 0 or 1 is stored at + * FORCED_CHV1. */ +static gpg_error_t +check_pin_for_key_operation (card_info_t info, int *forced_chv1) +{ + gpg_error_t err = 0; + + *forced_chv1 = !info->chv1_cached; + if (*forced_chv1) + { /* Switch off the forced mode so that during key generation we + * don't get bothered with PIN queries for each self-signature. */ + err = scd_setattr ("CHV-STATUS-1", "\x01", 1); + if (err) + { + log_error ("error clearing forced signature PIN flag: %s\n", + gpg_strerror (err)); + *forced_chv1 = -1; /* Not changed. */ + goto leave; + } + } + + /* Check the PIN now, so that we won't get asked later for each + * binding signature. */ + err = scd_checkpin (info->serialno); + if (err) + log_error ("error checking the PIN: %s\n", gpg_strerror (err)); + + leave: + return err; +} + + +/* Helper for cmd_generate_openpgp. */ +static void +restore_forced_chv1 (int *forced_chv1) +{ + gpg_error_t err; + + /* Note the possible values stored at FORCED_CHV1: + * 0 - forcesig was not enabled. + * 1 - forcesig was enabled - enable it again. + * -1 - We have not changed anything. */ + if (*forced_chv1 == 1) + { /* Switch back to forced state. */ + err = scd_setattr ("CHV-STATUS-1", "", 1); + if (err) + log_error ("error setting forced signature PIN flag: %s\n", + gpg_strerror (err)); + *forced_chv1 = 0; + } +} + + +/* Implementation of cmd_generate for OpenPGP cards. */ +static gpg_error_t +generate_openpgp (card_info_t info) +{ + gpg_error_t err; + int forced_chv1 = -1; + int want_backup; + char *answer = NULL; + key_info_t kinfo1, kinfo2, kinfo3; + + if (info->extcap.ki) + { + xfree (answer); + answer = tty_get (_("Make off-card backup of encryption key? (Y/n) ")); + want_backup = answer_is_yes_no_default (answer, 1/*(default to Yes)*/); + tty_kill_prompt (); + if (*answer == CONTROL_D) + { + err = gpg_error (GPG_ERR_CANCELED); + goto leave; + } + } + else + want_backup = 0; + + kinfo1 = find_kinfo (info, "OPENPGP.1"); + kinfo2 = find_kinfo (info, "OPENPGP.2"); + kinfo3 = find_kinfo (info, "OPENPGP.3"); + + if ((kinfo1 && kinfo1->fprlen && !mem_is_zero (kinfo1->fpr,kinfo1->fprlen)) + || (kinfo2 && kinfo2->fprlen && !mem_is_zero (kinfo2->fpr,kinfo2->fprlen)) + || (kinfo3 && kinfo3->fprlen && !mem_is_zero (kinfo3->fpr,kinfo3->fprlen)) + ) + { + tty_printf ("\n"); + log_info (_("Note: keys are already stored on the card!\n")); + tty_printf ("\n"); + answer = tty_get (_("Replace existing keys? (y/N) ")); + tty_kill_prompt (); + if (*answer == CONTROL_D) + { + err = gpg_error (GPG_ERR_CANCELED); + goto leave; + } + + if (!answer_is_yes_no_default (answer, 0/*(default to No)*/)) + { + err = gpg_error (GPG_ERR_CANCELED); + goto leave; + } + } + + /* If no displayed name has been set, we assume that this is a fresh + * card and print a hint about the default PINs. */ + if (!info->disp_name || !*info->disp_name) + { + tty_printf ("\n"); + tty_printf (_("Please note that the factory settings of the PINs are\n" + " PIN = '%s' Admin PIN = '%s'\n" + "You should change them using the command --change-pin\n"), + OPENPGP_USER_PIN_DEFAULT, OPENPGP_ADMIN_PIN_DEFAULT); + tty_printf ("\n"); + } + + err = check_pin_for_key_operation (info, &forced_chv1); + if (err) + goto leave; + + /* FIXME: We need to divert to a function which spwans gpg which + * will then create the key. This also requires new features in + * gpg. We might also first create the keys on the card and then + * tell gpg to use them to create the OpenPGP keyblock. */ + /* generate_keypair (ctrl, 1, NULL, info.serialno, want_backup); */ + (void)want_backup; + err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); + + leave: + restore_forced_chv1 (&forced_chv1); + xfree (answer); + return err; +} + + +/* Generic implementation of cmd_generate. */ +static gpg_error_t +generate_generic (card_info_t info, const char *keyref, int force, + const char *algo) +{ + gpg_error_t err; + + (void)info; + + err = scd_genkey (keyref, force, algo, NULL); + + return err; +} + + +static gpg_error_t +cmd_generate (card_info_t info, char *argstr) +{ + static char * const valid_algos[] = + { "rsa2048", "rsa3072", "rsa4096", + "nistp256", "nistp384", "nistp521", + "ed25519", "cv25519", + NULL + }; + gpg_error_t err; + int opt_force; + char *opt_algo = NULL; /* Malloced. */ + char *keyref_buffer = NULL; /* Malloced. */ + char *keyref; /* Points into argstr or keyref_buffer. */ + int i; + + if (!info) + return print_help + ("GENERATE [--force] [--algo=ALGO] KEYREF\n\n" + "Create a new key on a card. For OpenPGP cards are menu is used\n" + "and KEYREF is ignored. Use --force to overwrite an existing key.", + APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); + + if (opt.interactive || opt.verbose) + log_info (_("%s card no. %s detected\n"), + app_type_string (info->apptype), + info->dispserialno? info->dispserialno : info->serialno); + + opt_force = has_leading_option (argstr, "--force"); + err = get_option_value (argstr, "--algo", &opt_algo); + if (err) + goto leave; + argstr = skip_options (argstr); + + keyref = argstr; + if ((argstr = strchr (keyref, ' '))) + { + *argstr++ = 0; + trim_spaces (keyref); + trim_spaces (argstr); + } + else /* Let argstr point to an empty string. */ + argstr = keyref + strlen (keyref); + + if (!*keyref) + keyref = NULL; + + if (*argstr) + { + /* Extra arguments found. */ + err = gpg_error (GPG_ERR_INV_ARG); + goto leave; + } + + if (opt_algo) + { + for (i=0; valid_algos[i]; i++) + if (!strcmp (valid_algos[i], opt_algo)) + break; + if (!valid_algos[i]) + { + err = gpg_error (GPG_ERR_PUBKEY_ALGO); + log_info ("Invalid algorithm '%s' given. Use one:\n", opt_algo); + for (i=0; valid_algos[i]; i++) + if (!(i%5)) + log_info (" %s%s", valid_algos[i], valid_algos[i+1]?",":"."); + else + log_printf (" %s%s", valid_algos[i], valid_algos[i+1]?",":"."); + log_info ("Note that the card may not support all of them.\n"); + goto leave; + } + } + + /* Upcase the keyref; if it misses the cardtype, prepend it. */ + if (keyref) + { + if (!strchr (keyref, '.')) + keyref_buffer = xstrconcat (app_type_string (info->apptype), ".", + keyref, NULL); + else + keyref_buffer = xstrdup (keyref); + ascii_strupr (keyref_buffer); + keyref = keyref_buffer; + } + + /* Divert to dedicated functions. */ + if (info->apptype == APP_TYPE_OPENPGP) + { + if (opt_force || opt_algo || keyref) + log_info ("Note: Options are ignored for OpenPGP cards.\n"); + err = generate_openpgp (info); + } + else if (!keyref) + err = gpg_error (GPG_ERR_INV_ID); + else + err = generate_generic (info, keyref, opt_force, opt_algo); + + leave: + xfree (opt_algo); + xfree (keyref_buffer); + return err; +} + + + +/* Sub-menu to change a PIN. The presented options may depend on the + * the ALLOW_ADMIN flag. */ +static gpg_error_t +cmd_passwd (card_info_t info, int allow_admin, char *argstr) +{ + gpg_error_t err; + char *answer = NULL; + const char *pinref; + + if (!info) + return print_help + ("PASSWD [PINREF]\n\n" + "Menu to change or unblock the PINs. Note that the\n" + "presented menu options depend on the type of card\n" + "and whether the admin mode is enabled. For OpenPGP\n" + "and PIV cards defaults for PINREF are available.", + 0); + + if (opt.interactive || opt.verbose) + log_info (_("%s card no. %s detected\n"), + app_type_string (info->apptype), + info->dispserialno? info->dispserialno : info->serialno); + + if (!allow_admin || info->apptype != APP_TYPE_OPENPGP) + { + if (*argstr) + pinref = argstr; + else if (info->apptype == APP_TYPE_OPENPGP) + pinref = "OPENPGP.1"; + else if (info->apptype == APP_TYPE_PIV) + pinref = "PIV.80"; + else + { + err = gpg_error (GPG_ERR_MISSING_VALUE); + goto leave; + } + err = scd_change_pin (pinref, 0); + if (err) + goto leave; + + if (info->apptype == APP_TYPE_PIV + && !ascii_strcasecmp (pinref, "PIV.81")) + log_info ("PUK changed.\n"); + else + log_info ("PIN changed.\n"); + } + else if (info->apptype == APP_TYPE_OPENPGP) + { + for (;;) + { + tty_printf ("\n"); + tty_printf ("1 - change PIN\n" + "2 - unblock and set new PIN\n" + "3 - change Admin PIN\n" + "4 - set the Reset Code\n" + "Q - quit\n"); + tty_printf ("\n"); + + err = 0; + xfree (answer); + answer = tty_get (_("Your selection? ")); + tty_kill_prompt (); + if (*answer == CONTROL_D) + break; /* Quit. */ + if (strlen (answer) != 1) + continue; + if (*answer == 'q' || *answer == 'Q') + break; /* Quit. */ + + if (*answer == '1') + { + /* Change PIN (same as the direct thing in non-admin mode). */ + err = scd_change_pin ("OPENPGP.1", 0); + if (err) + log_error ("Error changing the PIN: %s\n", gpg_strerror (err)); + else + log_info ("PIN changed.\n"); + } + else if (*answer == '2') + { + /* Unblock PIN by setting a new PIN. */ + err = scd_change_pin ("OPENPGP.1", 1); + if (err) + log_error ("Error unblocking the PIN: %s\n", gpg_strerror(err)); + else + log_info ("PIN unblocked and new PIN set.\n"); + } + else if (*answer == '3') + { + /* Change Admin PIN. */ + err = scd_change_pin ("OPENPGP.3", 0); + if (err) + log_error ("Error changing the PIN: %s\n", gpg_strerror (err)); + else + log_info ("PIN changed.\n"); + } + else if (*answer == '4') + { + /* Set a new Reset Code. */ + err = scd_change_pin ("OPENPGP.2", 1); + if (err) + log_error ("Error setting the Reset Code: %s\n", + gpg_strerror (err)); + else + log_info ("Reset Code set.\n"); + } + + } /*end for loop*/ + } + else + { + log_info ("Admin related passwd options not yet supported for '%s'\n", + app_type_string (info->apptype)); + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + } + + leave: + xfree (answer); + return err; +} + + +static gpg_error_t +cmd_unblock (card_info_t info) +{ + gpg_error_t err = 0; + + if (!info) + return print_help + ("UNBLOCK\n\n" + "Unblock a PIN using a PUK or Reset Code. Note that OpenPGP\n" + "cards prior to version 2 can't use this; instead the PASSWD\n" + "command can be used to set a new PIN.", + 0); + + if (opt.interactive || opt.verbose) + log_info (_("%s card no. %s detected\n"), + app_type_string (info->apptype), + info->dispserialno? info->dispserialno : info->serialno); + + if (info->apptype == APP_TYPE_OPENPGP) + { + if (!info->is_v2) + { + log_error (_("This command is only available for version 2 cards\n")); + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + } + else if (!info->chvinfo[1]) + { + log_error (_("Reset Code not or not anymore available\n")); + err = gpg_error (GPG_ERR_PIN_BLOCKED); + } + else + { + err = scd_change_pin ("OPENPGP.2", 0); + if (!err) + log_info ("PIN changed.\n"); + } + } + else if (info->apptype == APP_TYPE_PIV) + { + /* Unblock the Application PIN. */ + err = scd_change_pin ("PIV.80", 1); + if (!err) + log_info ("PIN unblocked and changed.\n"); + } + else + { + log_info ("Unblocking not yet supported for '%s'\n", + app_type_string (info->apptype)); + err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); + } + + return err; +} + + +/* Note: On successful execution a redisplay should be scheduled. If + * this function fails the card may be in an unknown state. */ +static gpg_error_t +cmd_factoryreset (card_info_t info) +{ + gpg_error_t err; + char *answer = NULL; + int termstate = 0; + int any_apdu = 0; + int is_yubikey = 0; + int i; + + + if (!info) + return print_help + ("FACTORY-RESET\n\n" + "Do a complete reset of some OpenPGP and PIV cards. This\n" + "deletes all data and keys and resets the PINs to their default.\n" + "This is mainly used by developers with scratch cards. Don't\n" + "worry, you need to confirm before the command proceeds.", + APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); + + /* We support the factory reset for most OpenPGP cards and Yubikeys + * with the PIV application. */ + if (info->apptype == APP_TYPE_OPENPGP) + ; + else if (info->apptype == APP_TYPE_PIV + && info->cardtype && !strcmp (info->cardtype, "yubikey")) + is_yubikey = 1; + else + + return gpg_error (GPG_ERR_NOT_SUPPORTED); + + /* For an OpenPGP card the code below basically does the same what + * this gpg-connect-agent script does: + * + * scd reset + * scd serialno undefined + * scd apdu 00 A4 04 00 06 D2 76 00 01 24 01 + * scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40 + * scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40 + * scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40 + * scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40 + * scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40 + * scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40 + * scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40 + * scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40 + * scd apdu 00 e6 00 00 + * scd apdu 00 44 00 00 + * scd reset + * /echo Card has been reset to factory defaults + * + * For a PIV application on a Yubikey it merely issues the Yubikey + * specific resset command. + */ + + err = scd_learn (info); + if (gpg_err_code (err) == GPG_ERR_OBJ_TERM_STATE + && gpg_err_source (err) == GPG_ERR_SOURCE_SCD) + termstate = 1; + else if (err) + { + log_error (_("OpenPGP card not available: %s\n"), gpg_strerror (err)); + goto leave; + } + + if (opt.interactive || opt.verbose) + log_info (_("%s card no. %s detected\n"), + app_type_string (info->apptype), + info->dispserialno? info->dispserialno : info->serialno); + + if (!termstate || is_yubikey) + { + if (!is_yubikey) + { + if (!(info->status_indicator == 3 || info->status_indicator == 5)) + { + /* Note: We won't see status-indicator 3 here because it + * is not possible to select a card application in + * termination state. */ + log_error (_("This command is not supported by this card\n")); + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + goto leave; + } + } + + tty_printf ("\n"); + log_info + (_("Note: This command destroys all keys stored on the card!\n")); + tty_printf ("\n"); + xfree (answer); + answer = tty_get (_("Continue? (y/N) ")); + tty_kill_prompt (); + trim_spaces (answer); + if (*answer == CONTROL_D + || !answer_is_yes_no_default (answer, 0/*(default to no)*/)) + { + err = gpg_error (GPG_ERR_CANCELED); + goto leave; + } + + xfree (answer); + answer = tty_get (_("Really do a factory reset? (enter \"yes\") ")); + tty_kill_prompt (); + trim_spaces (answer); + if (strcmp (answer, "yes") && strcmp (answer,_("yes"))) + { + err = gpg_error (GPG_ERR_CANCELED); + goto leave; + } + + + if (is_yubikey) + { + /* The PIV application si already selected, we only need to + * send the special reset APDU after having blocked PIN and + * PUK. Note that blocking the PUK is done using the + * unblock PIN command. */ + any_apdu = 1; + for (i=0; i < 5; i++) + send_apdu ("0020008008FFFFFFFFFFFFFFFF", "VERIFY", 0xffff, + NULL, NULL); + for (i=0; i < 5; i++) + send_apdu ("002C008010FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", + "RESET RETRY COUNTER", 0xffff, NULL, NULL); + err = send_apdu ("00FB000001FF", "YUBIKEY RESET", 0, NULL, NULL); + if (err) + goto leave; + } + else /* OpenPGP card. */ + { + any_apdu = 1; + /* We need to select a card application before we can send APDUs + * to the card without scdaemon doing anything on its own. */ + err = send_apdu (NULL, "RESET", 0, NULL, NULL); + if (err) + goto leave; + err = send_apdu ("undefined", "dummy select ", 0, NULL, NULL); + if (err) + goto leave; + /* Select the OpenPGP application. */ + err = send_apdu ("00A4040006D27600012401", "SELECT AID", 0, + NULL, NULL); + if (err) + goto leave; + + /* Do some dummy verifies with wrong PINs to set the retry + * counter to zero. We can't easily use the card version 2.1 + * feature of presenting the admin PIN to allow the terminate + * command because there is no machinery in scdaemon to catch + * the verify command and ask for the PIN when the "APDU" + * command is used. + * Here, the length of dummy wrong PIN is 32-byte, also + * supporting authentication with KDF DO. */ + for (i=0; i < 4; i++) + send_apdu ("0020008120" + "40404040404040404040404040404040" + "40404040404040404040404040404040", "VERIFY", 0xffff, + NULL, NULL); + for (i=0; i < 4; i++) + send_apdu ("0020008320" + "40404040404040404040404040404040" + "40404040404040404040404040404040", "VERIFY", 0xffff, + NULL, NULL); + + /* Send terminate datafile command. */ + err = send_apdu ("00e60000", "TERMINATE DF", 0x6985, NULL, NULL); + if (err) + goto leave; + } + } + + if (!is_yubikey) + { + any_apdu = 1; + /* Send activate datafile command. This is used without + * confirmation if the card is already in termination state. */ + err = send_apdu ("00440000", "ACTIVATE DF", 0, NULL, NULL); + if (err) + goto leave; + } + + /* Finally we reset the card reader once more. */ + err = send_apdu (NULL, "RESET", 0, NULL, NULL); + if (err) + goto leave; + + /* Then, connect the card again (answer used as a dummy). */ + xfree (answer); answer = NULL; + err = scd_serialno (&answer, NULL); + + leave: + if (err && any_apdu && !is_yubikey) + { + log_info ("Due to an error the card might be in an inconsistent state\n" + "You should run the LIST command to check this.\n"); + /* FIXME: We need a better solution in the case that the card is + * in a termination state, i.e. the card was removed before the + * activate was sent. The best solution I found with v2.1 + * Zeitcontrol card was to kill scdaemon and the issue this + * sequence with gpg-connect-agent: + * scd reset + * scd serialno undefined + * scd apdu 00A4040006D27600012401 (returns error) + * scd apdu 00440000 + * Then kill scdaemon again and issue: + * scd reset + * scd serialno openpgp + */ + } + xfree (answer); + return err; +} + + +/* Generate KDF data. This is a helper for cmd_kdfsetup. */ +static gpg_error_t +gen_kdf_data (unsigned char *data, int single_salt) +{ + gpg_error_t err; + const unsigned char h0[] = { 0x81, 0x01, 0x03, + 0x82, 0x01, 0x08, + 0x83, 0x04 }; + const unsigned char h1[] = { 0x84, 0x08 }; + const unsigned char h2[] = { 0x85, 0x08 }; + const unsigned char h3[] = { 0x86, 0x08 }; + const unsigned char h4[] = { 0x87, 0x20 }; + const unsigned char h5[] = { 0x88, 0x20 }; + unsigned char *p, *salt_user, *salt_admin; + unsigned char s2k_char; + unsigned int iterations; + unsigned char count_4byte[4]; + + p = data; + + s2k_char = encode_s2k_iterations (agent_get_s2k_count ()); + iterations = S2K_DECODE_COUNT (s2k_char); + count_4byte[0] = (iterations >> 24) & 0xff; + count_4byte[1] = (iterations >> 16) & 0xff; + count_4byte[2] = (iterations >> 8) & 0xff; + count_4byte[3] = (iterations & 0xff); + + memcpy (p, h0, sizeof h0); + p += sizeof h0; + memcpy (p, count_4byte, sizeof count_4byte); + p += sizeof count_4byte; + memcpy (p, h1, sizeof h1); + salt_user = (p += sizeof h1); + gcry_randomize (p, 8, GCRY_STRONG_RANDOM); + p += 8; + + if (single_salt) + salt_admin = salt_user; + else + { + memcpy (p, h2, sizeof h2); + p += sizeof h2; + gcry_randomize (p, 8, GCRY_STRONG_RANDOM); + p += 8; + memcpy (p, h3, sizeof h3); + salt_admin = (p += sizeof h3); + gcry_randomize (p, 8, GCRY_STRONG_RANDOM); + p += 8; + } + + memcpy (p, h4, sizeof h4); + p += sizeof h4; + err = gcry_kdf_derive (OPENPGP_USER_PIN_DEFAULT, + strlen (OPENPGP_USER_PIN_DEFAULT), + GCRY_KDF_ITERSALTED_S2K, GCRY_MD_SHA256, + salt_user, 8, iterations, 32, p); + p += 32; + if (!err) + { + memcpy (p, h5, sizeof h5); + p += sizeof h5; + err = gcry_kdf_derive (OPENPGP_ADMIN_PIN_DEFAULT, + strlen (OPENPGP_ADMIN_PIN_DEFAULT), + GCRY_KDF_ITERSALTED_S2K, GCRY_MD_SHA256, + salt_admin, 8, iterations, 32, p); + } + + return err; +} + + +static gpg_error_t +cmd_kdfsetup (card_info_t info, char *argstr) +{ + gpg_error_t err; + unsigned char kdf_data[OPENPGP_KDF_DATA_LENGTH_MAX]; + int single = (*argstr != 0); + + if (!info) + return print_help + ("KDF-SETUP\n\n" + "Prepare the OpenPGP card KDF feature for this card.", + APP_TYPE_OPENPGP, 0); + + if (info->apptype != APP_TYPE_OPENPGP) + { + log_info ("Note: This is an OpenPGP only command.\n"); + return gpg_error (GPG_ERR_NOT_SUPPORTED); + } + + if (!info->extcap.kdf) + { + log_error (_("This command is not supported by this card\n")); + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + goto leave; + } + + err = gen_kdf_data (kdf_data, single); + if (err) + goto leave; + + err = scd_setattr ("KDF", kdf_data, + single ? OPENPGP_KDF_DATA_LENGTH_MIN + /* */ : OPENPGP_KDF_DATA_LENGTH_MAX); + if (err) + goto leave; + + err = scd_getattr ("KDF", info); + + leave: + return err; +} + + + +static void +show_keysize_warning (void) +{ + static int shown; + + if (shown) + return; + shown = 1; + tty_printf + (_("Note: There is no guarantee that the card supports the requested\n" + " key type or size. If the key generation does not succeed,\n" + " please check the documentation of your card to see which\n" + " key types and sizes are supported.\n") + ); +} + + +/* Ask for the size of a card key. NBITS is the current size + * configured for the card. Returns 0 on success and stored the + * chosen key size at R_KEYSIZE; 0 is stored to indicate that the + * default size shall be used. */ +static gpg_error_t +ask_card_rsa_keysize (unsigned int nbits, unsigned int *r_keysize) +{ + unsigned int min_nbits = 1024; + unsigned int max_nbits = 4096; + char*answer; + unsigned int req_nbits; + + for (;;) + { + answer = tty_getf (_("What keysize do you want? (%u) "), nbits); + trim_spaces (answer); + tty_kill_prompt (); + if (*answer == CONTROL_D) + { + xfree (answer); + return gpg_error (GPG_ERR_CANCELED); + } + req_nbits = *answer? atoi (answer): nbits; + xfree (answer); + + if (req_nbits != nbits && (req_nbits % 32) ) + { + req_nbits = ((req_nbits + 31) / 32) * 32; + tty_printf (_("rounded up to %u bits\n"), req_nbits); + } + + if (req_nbits == nbits) + { + /* Use default. */ + *r_keysize = 0; + return 0; + } + + if (req_nbits < min_nbits || req_nbits > max_nbits) + { + tty_printf (_("%s keysizes must be in the range %u-%u\n"), + "RSA", min_nbits, max_nbits); + } + else + { + *r_keysize = req_nbits; + return 0; + } + } +} + + +/* Ask for the key attribute of a card key. CURRENT is the current + * attribute configured for the card. KEYNO is the number of the key + * used to select the prompt. Stores NULL at result to use the + * default attribute or stores the selected attribute structure at + * RESULT. On error an error code is returned. */ +static gpg_error_t +ask_card_keyattr (int keyno, const struct key_attr *current, + struct key_attr **result) +{ + gpg_error_t err; + struct key_attr *key_attr = NULL; + char *answer = NULL; + int selection; + + *result = NULL; + + key_attr = xcalloc (1, sizeof *key_attr); + + tty_printf (_("Changing card key attribute for: ")); + if (keyno == 0) + tty_printf (_("Signature key\n")); + else if (keyno == 1) + tty_printf (_("Encryption key\n")); + else + tty_printf (_("Authentication key\n")); + + tty_printf (_("Please select what kind of key you want:\n")); + tty_printf (_(" (%d) RSA\n"), 1 ); + tty_printf (_(" (%d) ECC\n"), 2 ); + + for (;;) + { + xfree (answer); + answer = tty_get (_("Your selection? ")); + trim_spaces (answer); + tty_kill_prompt (); + if (!*answer || *answer == CONTROL_D) + { + err = gpg_error (GPG_ERR_CANCELED); + goto leave; + } + selection = *answer? atoi (answer) : 0; + + if (selection == 1 || selection == 2) + break; + else + tty_printf (_("Invalid selection.\n")); + } + + + if (selection == 1) + { + unsigned int nbits, result_nbits; + + if (current->algo == PUBKEY_ALGO_RSA) + nbits = current->nbits; + else + nbits = 2048; + + err = ask_card_rsa_keysize (nbits, &result_nbits); + if (err) + goto leave; + if (result_nbits == 0) + { + if (current->algo == PUBKEY_ALGO_RSA) + { + xfree (key_attr); + key_attr = NULL; + } + else + result_nbits = nbits; + } + + if (key_attr) + { + key_attr->algo = PUBKEY_ALGO_RSA; + key_attr->nbits = result_nbits; + } + } + else if (selection == 2) + { + const char *curve; + /* const char *oid_str; */ + int algo; + + if (current->algo == PUBKEY_ALGO_RSA) + { + if (keyno == 1) /* Encryption key */ + algo = PUBKEY_ALGO_ECDH; + else /* Signature key or Authentication key */ + algo = PUBKEY_ALGO_ECDSA; + curve = NULL; + } + else + { + algo = current->algo; + curve = current->curve; + } + + (void)curve; + (void)algo; + err = GPG_ERR_NOT_IMPLEMENTED; + goto leave; + /* FIXME: We need to mve the ask_cure code out to common or + * provide another sultion. */ + /* curve = ask_curve (&algo, NULL, curve); */ + /* if (curve) */ + /* { */ + /* key_attr->algo = algo; */ + /* oid_str = openpgp_curve_to_oid (curve, NULL); */ + /* key_attr->curve = openpgp_oid_to_curve (oid_str, 0); */ + /* } */ + /* else */ + /* { */ + /* xfree (key_attr); */ + /* key_attr = NULL; */ + /* } */ + } + else + { + err = gpg_error (GPG_ERR_BUG); + goto leave; + } + + /* Tell the user what we are going to do. */ + if (key_attr->algo == PUBKEY_ALGO_RSA) + { + tty_printf (_("The card will now be re-configured" + " to generate a key of %u bits\n"), key_attr->nbits); + } + else if (key_attr->algo == PUBKEY_ALGO_ECDH + || key_attr->algo == PUBKEY_ALGO_ECDSA + || key_attr->algo == PUBKEY_ALGO_EDDSA) + { + tty_printf (_("The card will now be re-configured" + " to generate a key of type: %s\n"), key_attr->curve); + } + show_keysize_warning (); + + *result = key_attr; + key_attr = NULL; + + leave: + xfree (key_attr); + xfree (answer); + return err; +} + + +/* Change the key attribute of key KEYNO (0..2) and show an error + * message if that fails. */ +static gpg_error_t +do_change_keyattr (int keyno, const struct key_attr *key_attr) +{ + gpg_error_t err = 0; + char args[100]; + + if (key_attr->algo == PUBKEY_ALGO_RSA) + snprintf (args, sizeof args, "--force %d 1 rsa%u", keyno+1, + key_attr->nbits); + else if (key_attr->algo == PUBKEY_ALGO_ECDH + || key_attr->algo == PUBKEY_ALGO_ECDSA + || key_attr->algo == PUBKEY_ALGO_EDDSA) + snprintf (args, sizeof args, "--force %d %d %s", + keyno+1, key_attr->algo, key_attr->curve); + else + { + /* FIXME: Above we use opnepgp algo names but in the error + * message we use the gcrypt names. We should settle for a + * consistent solution. */ + log_error (_("public key algorithm %d (%s) is not supported\n"), + key_attr->algo, gcry_pk_algo_name (key_attr->algo)); + err = gpg_error (GPG_ERR_PUBKEY_ALGO); + goto leave; + } + + err = scd_setattr ("KEY-ATTR", args, strlen (args)); + if (err) + log_error (_("error changing key attribute for key %d: %s\n"), + keyno+1, gpg_strerror (err)); + leave: + return err; +} + + +static gpg_error_t +cmd_keyattr (card_info_t info, char *argstr) +{ + gpg_error_t err = 0; + int keyno; + struct key_attr *key_attr = NULL; + + (void)argstr; + + if (!info) + return print_help + ("KEY-ATTR\n\n" + "Menu to change the key attributes of an OpenPGP card.", + APP_TYPE_OPENPGP, 0); + + if (info->apptype != APP_TYPE_OPENPGP) + { + log_info ("Note: This is an OpenPGP only command.\n"); + return gpg_error (GPG_ERR_NOT_SUPPORTED); + } + + if (!(info->is_v2 && info->extcap.aac)) + { + log_error (_("This command is not supported by this card\n")); + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + goto leave; + } + + for (keyno = 0; keyno < DIM (info->key_attr); keyno++) + { + xfree (key_attr); + key_attr = NULL; + err = ask_card_keyattr (keyno, &info->key_attr[keyno], &key_attr); + if (err) + goto leave; + + err = do_change_keyattr (keyno, key_attr); + if (err) + { + /* Error: Better read the default key attribute again. */ + log_debug ("FIXME\n"); + /* Ask again for this key. */ + keyno--; + } + } + + leave: + xfree (key_attr); + return err; +} + + +static gpg_error_t +cmd_uif (card_info_t info, char *argstr) +{ + gpg_error_t err; + int keyno; + + if (!info) + return print_help + ("UIF N [on|off|permanent]\n\n" + "Change the User Interaction Flag. N must in the range 1 to 3.", + APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); + + argstr = skip_options (argstr); + + if (digitp (argstr)) + { + keyno = atoi (argstr); + while (digitp (argstr)) + argstr++; + while (spacep (argstr)) + argstr++; + } + else + keyno = 0; + + if (keyno < 1 || keyno > 3) + { + err = gpg_error (GPG_ERR_INV_ARG); + goto leave; + } + + + err = GPG_ERR_NOT_IMPLEMENTED; + + leave: + return err; +} + + +static gpg_error_t +cmd_yubikey (card_info_t info, char *argstr) +{ + gpg_error_t err, err2; + estream_t fp = opt.interactive? NULL : es_stdout; + char *words[20]; + int nwords; + + if (!info) + return print_help + ("YUBIKEY args\n\n" + "Various commands pertaining to Yubikey tokens with being:\n" + "\n" + " LIST \n" + "\n" + "List supported and enabled applications.\n" + "\n" + " ENABLE usb|nfc|all [otp|u2f|opgp|piv|oath|fido2|all]\n" + " DISABLE usb|nfc|all [otp|u2f|opgp|piv|oath|fido2|all]\n" + "\n" + "Enable or disable the specified or all applications on the\n" + "given interface.", + 0); + + argstr = skip_options (argstr); + + if (!info->cardtype || strcmp (info->cardtype, "yubikey")) + { + log_info ("This command can only be used with Yubikeys.\n"); + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + goto leave; + } + + nwords = split_fields (argstr, words, DIM (words)); + if (nwords < 1) + { + err = gpg_error (GPG_ERR_SYNTAX); + goto leave; + } + + + /* Note that we always do a learn to get a chance to the card back + * into a usable state. */ + err = yubikey_commands (fp, nwords, words); + err2 = scd_learn (info); + if (err2) + log_error ("Error re-reading card: %s\n", gpg_strerror (err)); + + leave: + return err; +} + + + +/* Data used by the command parser. This needs to be outside of the + * function scope to allow readline based command completion. */ +enum cmdids + { + cmdNOP = 0, + cmdQUIT, cmdADMIN, cmdHELP, cmdLIST, cmdRESET, cmdVERIFY, + cmdNAME, cmdURL, cmdFETCH, cmdLOGIN, cmdLANG, cmdSALUT, cmdCAFPR, + cmdFORCESIG, cmdGENERATE, cmdPASSWD, cmdPRIVATEDO, cmdWRITECERT, + cmdREADCERT, cmdUNBLOCK, cmdFACTORYRESET, cmdKDFSETUP, + cmdKEYATTR, cmdUIF, cmdAUTHENTICATE, cmdYUBIKEY, + cmdINVCMD + }; + +static struct +{ + const char *name; + enum cmdids id; + int admin_only; + const char *desc; +} cmds[] = { + { "quit" , cmdQUIT , 0, N_("quit this menu")}, + { "q" , cmdQUIT , 0, NULL }, + { "admin" , cmdADMIN , 0, N_("show admin commands")}, + { "help" , cmdHELP , 0, N_("show this help")}, + { "?" , cmdHELP , 0, NULL }, + { "list" , cmdLIST , 0, N_("list all available data")}, + { "l" , cmdLIST , 0, NULL }, + { "name" , cmdNAME , 1, N_("change card holder's name")}, + { "url" , cmdURL , 1, N_("change URL to retrieve key")}, + { "fetch" , cmdFETCH , 0, N_("fetch the key specified in the card URL")}, + { "login" , cmdLOGIN , 1, N_("change the login name")}, + { "lang" , cmdLANG , 1, N_("change the language preferences")}, + { "salutation",cmdSALUT, 1, N_("change card holder's salutation")}, + { "salut" , cmdSALUT, 1, NULL }, + { "cafpr" , cmdCAFPR , 1, N_("change a CA fingerprint")}, + { "forcesig", cmdFORCESIG, 1, N_("toggle the signature force PIN flag")}, + { "generate", cmdGENERATE, 1, N_("generate new keys")}, + { "passwd" , cmdPASSWD, 0, N_("menu to change or unblock the PIN")}, + { "verify" , cmdVERIFY, 0, N_("verify the PIN and list all data")}, + { "unblock" , cmdUNBLOCK,0, N_("unblock the PIN using a Reset Code")}, + { "authenticate",cmdAUTHENTICATE, 0,N_("authenticate to the card")}, + { "auth" , cmdAUTHENTICATE, 0, NULL }, + { "reset" , cmdRESET, 0, N_("send a reset to the card daemon")}, + { "factory-reset", cmdFACTORYRESET, 1, N_("destroy all keys and data")}, + { "kdf-setup", cmdKDFSETUP, 1, N_("setup KDF for PIN authentication")}, + { "key-attr", cmdKEYATTR, 1, N_("change the key attribute")}, + { "uif", cmdUIF, 1, N_("change the User Interaction Flag")}, + { "privatedo", cmdPRIVATEDO, 0, N_("change a private data object")}, + { "readcert", cmdREADCERT, 0, N_("read a certificate from a data object")}, + { "writecert", cmdWRITECERT, 1, N_("store a certificate to a data object")}, + { "yubikey", cmdYUBIKEY, 0, N_("Yubikey management commands")}, + { NULL, cmdINVCMD, 0, NULL } +}; + + +/* The command line command dispatcher. */ +static gpg_error_t +dispatch_command (card_info_t info, const char *orig_command) +{ + gpg_error_t err = 0; + enum cmdids cmd; /* The command. */ + char *command; /* A malloced copy of ORIG_COMMAND. */ + char *argstr; /* The argument as a string. */ + int i; + int ignore_error; + + if ((ignore_error = *orig_command == '-')) + orig_command++; + command = xstrdup (orig_command); + argstr = NULL; + if ((argstr = strchr (command, ' '))) + { + *argstr++ = 0; + trim_spaces (command); + trim_spaces (argstr); + } + + for (i=0; cmds[i].name; i++ ) + if (!ascii_strcasecmp (command, cmds[i].name )) + break; + cmd = cmds[i].id; /* (If not found this will be cmdINVCMD). */ + + /* Make sure we have valid strings for the args. They are allowed + * to be modified and must thus point to a buffer. */ + if (!argstr) + argstr = command + strlen (command); + + /* For most commands we need to make sure that we have a card. */ + if (!info) + ; /* Help mode */ + else if (!(cmd == cmdNOP || cmd == cmdQUIT || cmd == cmdHELP + || cmd == cmdINVCMD) + && !info->initialized) + { + err = scd_learn (info); + if (err) + { + log_error ("Error reading card: %s\n", gpg_strerror (err)); + goto leave; + } + } + + switch (cmd) + { + case cmdNOP: + if (!info) + print_help ("NOP\n\n" + "Dummy command.", 0); + break; + + case cmdQUIT: + if (!info) + print_help ("QUIT\n\n" + "Stop processing.", 0); + else + { + err = gpg_error (GPG_ERR_EOF); + goto leave; + } + break; + + case cmdHELP: + if (!info) + print_help ("HELP [command]\n\n" + "Show all commands. With an argument show help\n" + "for that command.", 0); + else if (*argstr) + dispatch_command (NULL, argstr); + else + { + es_printf + ("List of commands (\"help \" for details):\n"); + for (i=0; cmds[i].name; i++ ) + if(cmds[i].desc) + es_printf("%-14s %s\n", cmds[i].name, _(cmds[i].desc) ); + es_printf ("Prefix a command with a dash to ignore its error.\n"); + } + break; + + case cmdLIST: + if (!info) + print_help ("LIST\n\n" + "Show content of the card.", 0); + else + { + err = scd_learn (info); + if (err) + log_error ("Error reading card: %s\n", gpg_strerror (err)); + else + list_card (info); + } + break; + + case cmdRESET: + if (!info) + print_help ("RESET\n\n" + "Send a RESET to the card daemon.", 0); + else + { + flush_keyblock_cache (); + err = scd_apdu (NULL, NULL, NULL, NULL); + } + break; + + case cmdADMIN: + /* This is a NOP in non-interactive mode. */ + break; + + case cmdVERIFY: err = cmd_verify (info, argstr); break; + case cmdAUTHENTICATE: err = cmd_authenticate (info, argstr); break; + case cmdNAME: err = cmd_name (info, argstr); break; + case cmdURL: err = cmd_url (info, argstr); break; + case cmdFETCH: err = cmd_fetch (info); break; + case cmdLOGIN: err = cmd_login (info, argstr); break; + case cmdLANG: err = cmd_lang (info, argstr); break; + case cmdSALUT: err = cmd_salut (info, argstr); break; + case cmdCAFPR: err = cmd_cafpr (info, argstr); break; + case cmdPRIVATEDO: err = cmd_privatedo (info, argstr); break; + case cmdWRITECERT: err = cmd_writecert (info, argstr); break; + case cmdREADCERT: err = cmd_readcert (info, argstr); break; + case cmdFORCESIG: err = cmd_forcesig (info); break; + case cmdGENERATE: err = cmd_generate (info, argstr); break; + case cmdPASSWD: err = cmd_passwd (info, 1, argstr); break; + case cmdUNBLOCK: err = cmd_unblock (info); break; + case cmdFACTORYRESET: err = cmd_factoryreset (info); break; + case cmdKDFSETUP: err = cmd_kdfsetup (info, argstr); break; + case cmdKEYATTR: err = cmd_keyattr (info, argstr); break; + case cmdUIF: err = cmd_uif (info, argstr); break; + case cmdYUBIKEY: err = cmd_yubikey (info, argstr); break; + + case cmdINVCMD: + default: + log_error (_("Invalid command (try \"help\")\n")); + break; + } /* End command switch. */ + + + leave: + /* Return GPG_ERR_EOF only if its origin was "quit". */ + es_fflush (es_stdout); + if (gpg_err_code (err) == GPG_ERR_EOF && cmd != cmdQUIT) + err = gpg_error (GPG_ERR_GENERAL); + if (err && gpg_err_code (err) != GPG_ERR_EOF) + { + if (ignore_error) + { + log_info ("Command '%s' failed: %s\n", command, gpg_strerror (err)); + err = 0; + } + else + log_error ("Command '%s' failed: %s\n", command, gpg_strerror (err)); + } + xfree (command); + + return err; +} + + +/* The interactive main loop. */ +static void +interactive_loop (void) +{ + gpg_error_t err; + char *answer = NULL; /* The input line. */ + enum cmdids cmd = cmdNOP; /* The command. */ + int cmd_admin_only; /* The command is an admin only command. */ + char *argstr; /* The argument as a string. */ + int redisplay = 1; /* Whether to redisplay the main info. */ + int allow_admin = 0; /* Whether admin commands are allowed. */ + char *help_arg = NULL; /* Argument of the HELP command. */ + struct card_info_s info_buffer = { 0 }; + card_info_t info = &info_buffer; + char *p; + int i; + + /* In the interactive mode we do not want to print the program prefix. */ + log_set_prefix (NULL, 0); + + for (;;) + { + if (help_arg) + { + /* Clear info to indicate helpmode */ + info = NULL; + } + else if (!info) + { + /* Get out of help. */ + info = &info_buffer; + help_arg = NULL; + redisplay = 0; + } + else if (redisplay) + { + err = scd_learn (info); + if (err) + { + log_error ("Error reading card: %s\n", gpg_strerror (err)); + } + else + { + list_card (info); + tty_printf("\n"); + redisplay = 0; + } + } + + if (!info) + { + /* Copy the pending help arg into our answer. Noe that + * help_arg points into answer. */ + p = xstrdup (help_arg); + help_arg = NULL; + xfree (answer); + answer = p; + } + else + { + do + { + xfree (answer); + tty_enable_completion (command_completion); + answer = tty_get (_("gpg/card> ")); + tty_kill_prompt(); + tty_disable_completion (); + trim_spaces(answer); + } + while ( *answer == '#' ); + } + + argstr = NULL; + cmd_admin_only = 0; + if (!*answer) + cmd = cmdLIST; /* We default to the list command */ + else if (*answer == CONTROL_D) + cmd = cmdQUIT; + else + { + if ((argstr = strchr (answer,' '))) + { + *argstr++ = 0; + trim_spaces (answer); + trim_spaces (argstr); + } + + for (i=0; cmds[i].name; i++ ) + if (!ascii_strcasecmp (answer, cmds[i].name )) + break; + + cmd = cmds[i].id; + cmd_admin_only = cmds[i].admin_only; + } + + /* Make sure we have valid strings for the args. They are + * allowed to be modified and must thus point to a buffer. */ + if (!argstr) + argstr = answer + strlen (answer); + + if (!(cmd == cmdNOP || cmd == cmdQUIT || cmd == cmdHELP + || cmd == cmdINVCMD)) + { + /* If redisplay is set we know that there was an error reading + * the card. In this case we force a LIST command to retry. */ + if (!info) + ; /* In help mode. */ + else if (redisplay) + { + cmd = cmdLIST; + cmd_admin_only = 0; + } + else if (!info->serialno) + { + /* Without a serial number most commands won't work. + * Catch it here. */ + tty_printf ("\n"); + tty_printf ("Serial number missing\n"); + continue; + } + else if (!allow_admin && cmd_admin_only) + { + tty_printf ("\n"); + tty_printf (_("Admin-only command\n")); + continue; + } + } + + err = 0; + switch (cmd) + { + case cmdNOP: + if (!info) + print_help ("NOP\n\n" + "Dummy command.", 0); + break; + + case cmdQUIT: + if (!info) + print_help ("QUIT\n\n" + "Leave this tool.", 0); + else + { + tty_printf ("\n"); + goto leave; + } + break; + + case cmdHELP: + if (!info) + print_help ("HELP [command]\n\n" + "Show all commands. With an argument show help\n" + "for that command.", 0); + else if (*argstr) + help_arg = argstr; /* Trigger help for a command. */ + else + { + tty_printf + ("List of commands (\"help \" for details):\n"); + for (i=0; cmds[i].name; i++ ) + if(cmds[i].desc + && (!cmds[i].admin_only + || (cmds[i].admin_only && allow_admin))) + tty_printf("%-14s %s\n", cmds[i].name, _(cmds[i].desc) ); + } + break; + + case cmdLIST: + if (!info) + print_help ("LIST\n\n" + "Show content of the card.", 0); + else + { + /* Actual work is done by the redisplay code block. */ + redisplay = 1; + } + break; + + case cmdRESET: + if (!info) + print_help ("RESET\n\n" + "Send a RESET to the card daemon.", 0); + else + { + flush_keyblock_cache (); + err = scd_apdu (NULL, NULL, NULL, NULL); + } + break; + + case cmdADMIN: + if ( !strcmp (argstr, "on") ) + allow_admin = 1; + else if ( !strcmp (argstr, "off") ) + allow_admin = 0; + else if ( !strcmp (argstr, "verify") ) + { + /* Force verification of the Admin Command. However, + this is only done if the retry counter is at initial + state. */ + /* FIXME: Must depend on the type of the card. */ + /* char *tmp = xmalloc (strlen (serialnobuf) + 6 + 1); */ + /* strcpy (stpcpy (tmp, serialnobuf), "[CHV3]"); */ + /* allow_admin = !agent_scd_checkpin (tmp); */ + /* xfree (tmp); */ + } + else /* Toggle. */ + allow_admin=!allow_admin; + if(allow_admin) + tty_printf(_("Admin commands are allowed\n")); + else + tty_printf(_("Admin commands are not allowed\n")); + break; + + case cmdVERIFY: + err = cmd_verify (info, argstr); + if (!err) + redisplay = 1; + break; + case cmdAUTHENTICATE: err = cmd_authenticate (info, argstr); break; + case cmdNAME: err = cmd_name (info, argstr); break; + case cmdURL: err = cmd_url (info, argstr); break; + case cmdFETCH: err = cmd_fetch (info); break; + case cmdLOGIN: err = cmd_login (info, argstr); break; + case cmdLANG: err = cmd_lang (info, argstr); break; + case cmdSALUT: err = cmd_salut (info, argstr); break; + case cmdCAFPR: err = cmd_cafpr (info, argstr); break; + case cmdPRIVATEDO: err = cmd_privatedo (info, argstr); break; + case cmdWRITECERT: err = cmd_writecert (info, argstr); break; + case cmdREADCERT: err = cmd_readcert (info, argstr); break; + case cmdFORCESIG: err = cmd_forcesig (info); break; + case cmdGENERATE: err = cmd_generate (info, argstr); break; + case cmdPASSWD: err = cmd_passwd (info, allow_admin, argstr); break; + case cmdUNBLOCK: err = cmd_unblock (info); break; + case cmdFACTORYRESET: + err = cmd_factoryreset (info); + if (!err) + redisplay = 1; + break; + case cmdKDFSETUP: err = cmd_kdfsetup (info, argstr); break; + case cmdKEYATTR: err = cmd_keyattr (info, argstr); break; + case cmdUIF: err = cmd_uif (info, argstr); break; + case cmdYUBIKEY: err = cmd_yubikey (info, argstr); break; + + case cmdINVCMD: + default: + tty_printf ("\n"); + tty_printf (_("Invalid command (try \"help\")\n")); + break; + } /* End command switch. */ + + if (gpg_err_code (err) == GPG_ERR_CANCELED) + tty_fprintf (NULL, "\n"); + else if (err) + { + const char *s = "?"; + for (i=0; cmds[i].name; i++ ) + if (cmd == cmds[i].id) + { + s = cmds[i].name; + break; + } + log_error ("Command '%s' failed: %s\n", s, gpg_strerror (err)); + } + + } /* End of main menu loop. */ + + leave: + release_card_info (info); + xfree (answer); +} + +#ifdef HAVE_LIBREADLINE +/* Helper function for readline's command completion. */ +static char * +command_generator (const char *text, int state) +{ + static int list_index, len; + const char *name; + + /* If this is a new word to complete, initialize now. This includes + * saving the length of TEXT for efficiency, and initializing the + index variable to 0. */ + if (!state) + { + list_index = 0; + len = strlen(text); + } + + /* Return the next partial match */ + while ((name = cmds[list_index].name)) + { + /* Only complete commands that have help text. */ + if (cmds[list_index++].desc && !strncmp (name, text, len)) + return strdup(name); + } + + return NULL; +} + +/* Second helper function for readline's command completion. */ +static char ** +command_completion (const char *text, int start, int end) +{ + (void)end; + + /* If we are at the start of a line, we try and command-complete. + * If not, just do nothing for now. The support for help completion + * needs to be more smarter. */ + if (!start) + return rl_completion_matches (text, command_generator); + else if (start == 5 && !ascii_strncasecmp (rl_line_buffer, "help ", 5)) + return rl_completion_matches (text, command_generator); + + rl_attempted_completion_over = 1; + + return NULL; +} +#endif /*HAVE_LIBREADLINE*/ diff --git a/tools/gpg-card.h b/tools/gpg-card.h new file mode 100644 index 000000000..03bad7530 --- /dev/null +++ b/tools/gpg-card.h @@ -0,0 +1,229 @@ +/* gpg-card.h - Common definitions for the gpg-card-tool + * Copyright (C) 2019 g10 Code GmbH + * + * This file is part of GnuPG. + * + * This file 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. + * + * This file 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 Lesser 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 . + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#ifndef GNUPG_GPG_CARD_H +#define GNUPG_GPG_CARD_H + +#include "../common/session-env.h" + + +/* We keep all global options in the structure OPT. */ +struct +{ + int interactive; + int verbose; + unsigned int debug; + int quiet; + int with_colons; + const char *gpg_program; + const char *gpgsm_program; + const char *agent_program; + int autostart; + + /* Options passed to the gpg-agent: */ + session_env_t session_env; + char *lc_ctype; + char *lc_messages; + +} opt; + +/* Debug values and macros. */ +#define DBG_IPC_VALUE 1024 /* Debug assuan communication. */ +#define DBG_EXTPROG_VALUE 16384 /* Debug external program calls */ + +#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 + { + APP_TYPE_NONE, /* Not yet known or for direct APDU sending. */ + APP_TYPE_OPENPGP, + APP_TYPE_NKS, + APP_TYPE_DINSIG, + APP_TYPE_P15, + APP_TYPE_GELDKARTE, + APP_TYPE_SC_HSM, + APP_TYPE_PIV, + APP_TYPE_UNKNOWN /* Unknown by this tool. */ + } app_type_t; + + +/* OpenPGP card key attributes. */ +struct key_attr +{ + int algo; /* Algorithm identifier. */ + union { + unsigned int nbits; /* Supported keysize. */ + const char *curve; /* Name of curve. */ + }; +}; + +/* 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; + + unsigned char grip[20];/* The keygrip. */ + + unsigned char xflag; /* Temporary flag to help processing a list. */ + + /* The three next items are mostly useful for OpenPGP cards. */ + unsigned char fprlen; /* Use length of the next item. */ + unsigned char fpr[32]; /* The binary fingerprint of length FPRLEN. */ + u32 created; /* The time the key was created. */ + unsigned int usage; /* Usage flags. (GCRY_PK_USAGE_*) */ + char keyref[1]; /* String with the keyref (e.g. OPENPGP.1). */ +}; +typedef struct key_info_s *key_info_t; + + +/* + * The object used to store information about a card. + */ +struct card_info_s +{ + int initialized; /* True if a learn command was successful. */ + int error; /* private. */ + char *reader; /* Reader information. */ + char *cardtype; /* NULL or type of the card. */ + char *apptypestr; /* Malloced application type string. */ + app_type_t apptype;/* Translated from APPTYPESTR. */ + char *serialno; /* malloced hex string. */ + char *dispserialno;/* malloced string. */ + char *disp_name; /* malloced. */ + char *disp_lang; /* malloced. */ + int disp_sex; /* 0 = unspecified, 1 = male, 2 = female */ + char *pubkey_url; /* malloced. */ + char *login_data; /* malloced. */ + char *private_do[4]; /* malloced. */ + char cafpr1len; /* Length of the CA-fingerprint or 0 if invalid. */ + char cafpr2len; + char cafpr3len; + char cafpr1[20]; + char cafpr2[20]; + char cafpr3[20]; + key_info_t kinfo; /* Linked list with all keypair related data. */ + unsigned long sig_counter; + int chv1_cached; /* For openpgp this is true if a PIN is not + required for each signing. Note that the + gpg-agent might cache it anyway. */ + int is_v2; /* True if this is a v2 openpgp card. */ + int chvmaxlen[3]; /* Maximum allowed length of a CHV. */ + int chvinfo[3]; /* Allowed retries for the CHV; 0 = blocked. */ + unsigned char chvusage[2]; /* Data object 5F2F */ + struct key_attr key_attr[3]; /* OpenPGP card key attributes. */ + struct { + unsigned int ki:1; /* Key import available. */ + unsigned int aac:1; /* Algorithm attributes are changeable. */ + unsigned int kdf:1; /* KDF object to support PIN hashing available. */ + unsigned int bt:1; /* Button for confirmation available. */ + } extcap; + unsigned int status_indicator; + int kdf_do_enabled; /* True if card has a KDF object. */ + int uif[3]; /* True if User Interaction Flag is on. */ +}; +typedef struct card_info_s *card_info_t; + + +/*-- card-keys.c --*/ +void release_keyblock (keyblock_t keyblock); +void flush_keyblock_cache (void); +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-misc.c --*/ +key_info_t find_kinfo (card_info_t info, const char *keyref); +void *hex_to_buffer (const char *string, size_t *r_length); +gpg_error_t send_apdu (const char *hexapdu, const char *desc, + unsigned int ignore, + unsigned char **r_data, size_t *r_datalen); + +/*-- card-call-scd.c --*/ +void release_card_info (card_info_t info); +const char *app_type_string (app_type_t app_type); + +gpg_error_t scd_apdu (const char *hexapdu, unsigned int *r_sw, + unsigned char **r_data, size_t *r_datalen); +gpg_error_t scd_learn (card_info_t info); +gpg_error_t scd_getattr (const char *name, struct card_info_s *info); +gpg_error_t scd_setattr (const char *name, + const unsigned char *value, size_t valuelen); +gpg_error_t scd_writecert (const char *certidstr, + const unsigned char *certdata, size_t certdatalen); +gpg_error_t scd_writekey (int keyno, + const unsigned char *keydata, size_t keydatalen); +gpg_error_t scd_genkey (const char *keyref, int force, const char *algo, + u32 *createtime); +gpg_error_t scd_serialno (char **r_serialno, const char *demand); +gpg_error_t scd_readcert (const char *certidstr, + void **r_buf, size_t *r_buflen); +gpg_error_t scd_readkey (const char *keyrefstr, gcry_sexp_t *r_result); +gpg_error_t scd_cardlist (strlist_t *result); +gpg_error_t scd_change_pin (const char *pinref, int reset_mode); +gpg_error_t scd_checkpin (const char *serialno); + +unsigned long agent_get_s2k_count (void); + +/*-- card-yubikey.c --*/ +gpg_error_t yubikey_commands (estream_t fp, int argc, char *argv[]); + + +#endif /*GNUPG_GPG_CARD_H*/ -- cgit v1.2.3