diff --git a/po/POTFILES.in b/po/POTFILES.in index fe8d45f7e..53a7dc8a5 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -1,130 +1,131 @@ # List of files with translatable strings agent/call-pinentry.c agent/command-ssh.c agent/divert-scd.c agent/genkey.c agent/gpg-agent.c agent/preset-passphrase.c agent/protect-tool.c agent/trustlist.c agent/findkey.c agent/pksign.c agent/cvt-openpgp.c common/exechelp-posix.c common/exechelp-w32.c common/exechelp-w32ce.c common/simple-pwquery.c common/sysutils.c common/yesno.c common/miscellaneous.c common/asshelp.c common/audit.c common/helpfile.c common/gettime.c common/ksba-io-support.c common/argparse.c common/utf8conv.c common/dotlock.c common/init.c g10/armor.c g10/build-packet.c g10/call-agent.c g10/card-util.c g10/dearmor.c g10/decrypt.c g10/delkey.c g10/encrypt.c g10/decrypt-data.c g10/exec.c g10/export.c g10/getkey.c g10/gpg.c g10/gpgv.c g10/helptext.c g10/import.c g10/keydb.c g10/keyedit.c g10/keygen.c g10/keyid.c g10/keylist.c g10/keyring.c g10/keyserver.c g10/mainproc.c g10/misc.c g10/openfile.c g10/parse-packet.c g10/passphrase.c g10/photoid.c g10/pkclist.c g10/plaintext.c g10/pubkey-enc.c g10/revoke.c g10/seskey.c g10/sig-check.c g10/sign.c g10/skclist.c g10/tdbdump.c g10/tdbio.c g10/textfilter.c g10/tofu.c g10/trustdb.c g10/trust.c g10/verify.c g10/cipher-cfb.c g10/cipher-aead.c kbx/kbxutil.c scd/app-nks.c scd/app-openpgp.c scd/app-dinsig.c scd/scdaemon.c sm/call-agent.c sm/call-dirmngr.c sm/certchain.c sm/certcheck.c sm/certdump.c sm/certlist.c sm/certreqgen.c sm/certreqgen-ui.c sm/decrypt.c sm/delete.c sm/encrypt.c sm/export.c sm/gpgsm.c sm/import.c sm/keydb.c sm/keylist.c sm/misc.c sm/qualified.c sm/sign.c sm/verify.c dirmngr/certcache.c dirmngr/crlcache.c dirmngr/crlfetch.c dirmngr/dirmngr-client.c dirmngr/dirmngr.c dirmngr/dirmngr_ldap.c dirmngr/http.c dirmngr/ldap-wrapper-ce.c dirmngr/ldap-wrapper.c dirmngr/ldap.c dirmngr/ldapserver.c dirmngr/misc.c dirmngr/ocsp.c dirmngr/server.c dirmngr/validate.c tools/gpg-connect-agent.c tools/gpgconf-comp.c tools/gpgconf.c tools/no-libgcrypt.c tools/symcryptrun.c tools/gpg-check-pattern.c - +tools/gpg-card-tool.c +tools/card-call-scd.c diff --git a/tools/Makefile.am b/tools/Makefile.am index 4833bff5e..e29e6a281 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -1,192 +1,192 @@ # Makefile.am - Tools directory # Copyright (C) 2003, 2007 Free Software Foundation, Inc. # # 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 . EXTRA_DIST = \ Manifest watchgnupg.c no-libgcrypt.c \ addgnupghome applygnupgdefaults \ lspgpot mail-signed-keys convert-from-106 sockprox.c \ ccidmon.c ChangeLog-2011 \ gpg-connect-agent-w32info.rc \ gpg-card-tool-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 resource_objs += $(gpg_connect_agent_rc_objs) $(gpg_card_tool_rc_objs) endif AM_CFLAGS = $(LIBGCRYPT_CFLAGS) $(GPG_ERROR_CFLAGS) $(LIBASSUAN_CFLAGS) sbin_SCRIPTS = addgnupghome applygnupgdefaults if BUILD_SYMCRYPTRUN symcryptrun = symcryptrun else symcryptrun = endif if BUILD_WKS_TOOLS gpg_wks_server = gpg-wks-server else gpg_wks_server = endif libexec_PROGRAMS = gpg-wks-client gpg-pair-tool bin_PROGRAMS = gpgconf gpg-connect-agent gpg-card-tool ${symcryptrun} if !HAVE_W32_SYSTEM bin_PROGRAMS += watchgnupg gpgparsemail ${gpg_wks_server} endif if !DISABLE_REGEX libexec_PROGRAMS += gpg-check-pattern endif if !HAVE_W32CE_SYSTEM noinst_PROGRAMS = clean-sat make-dns-cert gpgsplit endif if !HAVE_W32CE_SYSTEM if BUILD_GPGTAR bin_PROGRAMS += gpgtar else noinst_PROGRAMS += gpgtar endif endif common_libs = $(libcommon) commonpth_libs = $(libcommonpth) # Some modules require PTH under W32CE. if HAVE_W32CE_SYSTEM maybe_commonpth_libs = $(commonpth_libs) else maybe_commonpth_libs = $(common_libs) endif if HAVE_W32CE_SYSTEM pwquery_libs = else pwquery_libs = ../common/libsimple-pwquery.a endif if HAVE_W32CE_SYSTEM opt_libassuan_libs = $(LIBASSUAN_LIBS) endif gpgsplit_LDADD = $(common_libs) \ $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \ $(ZLIBS) $(LIBINTL) $(NETLIBS) $(LIBICONV) gpgconf_SOURCES = gpgconf.c gpgconf.h gpgconf-comp.c # common sucks in gpg-error, will they, nil they (some compilers # do not eliminate the supposed-to-be-unused-inline-functions). gpgconf_LDADD = $(maybe_commonpth_libs) $(opt_libassuan_libs) \ $(LIBINTL) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) $(NETLIBS) \ $(LIBICONV) $(W32SOCKLIBS) gpgconf_LDFLAGS = $(extra_bin_ldflags) gpgparsemail_SOURCES = gpgparsemail.c rfc822parse.c rfc822parse.h gpgparsemail_LDADD = symcryptrun_SOURCES = symcryptrun.c symcryptrun_LDADD = $(LIBUTIL_LIBS) $(common_libs) $(pwquery_libs) \ $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) $(LIBINTL) \ $(LIBICONV) $(NETLIBS) $(W32SOCKLIBS) $(LIBASSUAN_LIBS) watchgnupg_SOURCES = watchgnupg.c watchgnupg_LDADD = $(NETLIBS) gpg_connect_agent_SOURCES = gpg-connect-agent.c gpg_connect_agent_LDADD = ../common/libgpgrl.a $(common_libs) \ $(LIBASSUAN_LIBS) $(LIBGCRYPT_LIBS) \ $(GPG_ERROR_LIBS) \ $(LIBREADLINE) $(LIBINTL) $(NETLIBS) $(LIBICONV) \ $(gpg_connect_agent_rc_objs) -gpg_card_tool_SOURCES = gpg-card-tool.c +gpg_card_tool_SOURCES = gpg-card-tool.c card-tool.h card-call-scd.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) if !DISABLE_REGEX gpg_check_pattern_SOURCES = gpg-check-pattern.c gpg_check_pattern_CFLAGS = $(LIBGCRYPT_CFLAGS) $(GPG_ERROR_CFLAGS) $(INCICONV) gpg_check_pattern_LDADD = $(common_libs) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \ $(LIBINTL) $(NETLIBS) $(LIBICONV) $(W32SOCKLIBS) \ $(LIBICONV) endif gpgtar_SOURCES = \ gpgtar.c gpgtar.h \ gpgtar-create.c \ gpgtar-extract.c \ gpgtar-list.c gpgtar_CFLAGS = $(GPG_ERROR_CFLAGS) gpgtar_LDADD = $(libcommon) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \ $(LIBINTL) $(NETLIBS) $(LIBICONV) $(W32SOCKLIBS) gpg_wks_server_SOURCES = \ gpg-wks-server.c \ gpg-wks.h \ wks-util.c \ wks-receive.c \ rfc822parse.c rfc822parse.h \ mime-parser.c mime-parser.h \ mime-maker.c mime-maker.h \ send-mail.c send-mail.h gpg_wks_server_CFLAGS = $(GPG_ERROR_CFLAGS) $(INCICONV) gpg_wks_server_LDADD = $(libcommon) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \ $(LIBINTL) $(LIBICONV) gpg_wks_client_SOURCES = \ gpg-wks-client.c \ gpg-wks.h \ wks-util.c \ wks-receive.c \ rfc822parse.c rfc822parse.h \ mime-parser.c mime-parser.h \ mime-maker.h mime-maker.c \ send-mail.c send-mail.h \ call-dirmngr.c call-dirmngr.h gpg_wks_client_CFLAGS = $(LIBASSUAN_CFLAGS) $(GPG_ERROR_CFLAGS) $(INCICONV) gpg_wks_client_LDADD = $(libcommon) \ $(LIBASSUAN_LIBS) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \ $(LIBINTL) $(LIBICONV) gpg_pair_tool_SOURCES = \ gpg-pair-tool.c gpg_pair_tool_CFLAGS = $(GPG_ERROR_CFLAGS) $(INCICONV) gpg_pair_tool_LDADD = $(libcommon) \ $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \ $(LIBINTL) $(LIBICONV) $(W32SOCKLIBS) # Make sure that all libs are build before we use them. This is # important for things like make -j2. $(PROGRAMS): $(common_libs) $(pwquery_libs) ../common/libgpgrl.a diff --git a/tools/card-call-scd.c b/tools/card-call-scd.c new file mode 100644 index 000000000..7df7861d3 --- /dev/null +++ b/tools/card-call-scd.c @@ -0,0 +1,1355 @@ +/* card-call-scd.c - IPC calls to scdaemon. + * Copyright (C) 2019 g10 Code GmbH + * Copyright (C) 2001-2003, 2006-2011, 2013 Free Software Foundation, Inc. + * Copyright (C) 2013-2015 Werner Koch + * + * 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 +#ifdef HAVE_LOCALE_H +#include +#endif + +#include "../common/util.h" +#include "../common/membuf.h" +#include "../common/i18n.h" +#include "../common/asshelp.h" +#include "../common/sysutils.h" +#include "../common/status.h" +#include "../common/host2net.h" +#include "../common/openpgpdefs.h" +#include "card-tool.h" + +#define CONTROL_D ('D' - 'A' + 1) + +#define START_AGENT_NO_STARTUP_CMDS 1 +#define START_AGENT_SUPPRESS_ERRORS 2 + +struct default_inq_parm_s +{ + assuan_context_t ctx; + struct { + u32 *keyid; + u32 *mainkeyid; + int pubkey_algo; + } keyinfo; +}; + +struct cipher_parm_s +{ + struct default_inq_parm_s *dflt; + assuan_context_t ctx; + unsigned char *ciphertext; + size_t ciphertextlen; +}; + +struct writecert_parm_s +{ + struct default_inq_parm_s *dflt; + const unsigned char *certdata; + size_t certdatalen; +}; + +struct writekey_parm_s +{ + struct default_inq_parm_s *dflt; + const unsigned char *keydata; + size_t keydatalen; +}; + +struct genkey_parm_s +{ + struct default_inq_parm_s *dflt; + const char *keyparms; + const char *passphrase; +}; + +struct card_cardlist_parm_s +{ + gpg_error_t error; + strlist_t list; +}; + +struct import_key_parm_s +{ + struct default_inq_parm_s *dflt; + const void *key; + size_t keylen; +}; + + +struct cache_nonce_parm_s +{ + char **cache_nonce_addr; + char **passwd_nonce_addr; +}; + + + +/* + * File local variables + */ + +/* The established context to the agent. Note that all calls to + * scdaemon are routed via the agent and thus we only need to care + * about the IPC with the agent. */ +static assuan_context_t agent_ctx; + + + +/* + * Local prototypes + */ +static gpg_error_t learn_status_cb (void *opaque, const char *line); + + + + +/* Release the card info structure INFO. */ +void +release_card_info (card_info_t info) +{ + int i; + + if (!info) + return; + + xfree (info->reader); info->reader = NULL; + xfree (info->serialno); info->serialno = NULL; + xfree (info->dispserialno); info->dispserialno = NULL; + xfree (info->apptypestr); info->apptypestr = NULL; + info->apptype = APP_TYPE_NONE; + xfree (info->disp_name); info->disp_name = NULL; + xfree (info->disp_lang); info->disp_lang = NULL; + xfree (info->pubkey_url); info->pubkey_url = NULL; + xfree (info->login_data); info->login_data = NULL; + info->cafpr1len = info->cafpr2len = info->cafpr3len = 0; + info->fpr1len = info->fpr2len = info->fpr3len = 0; + for (i=0; i < DIM(info->private_do); i++) + { + xfree (info->private_do[i]); + info->private_do[i] = NULL; + } +} + + +/* Map an application type string to an integer. */ +static app_type_t +map_apptypestr (const char *string) +{ + app_type_t result; + + if (!string) + result = APP_TYPE_NONE; + else if (!ascii_strcasecmp (string, "OPENPGP")) + result = APP_TYPE_OPENPGP; + else if (!ascii_strcasecmp (string, "NKS")) + result = APP_TYPE_NKS; + else if (!ascii_strcasecmp (string, "DINSIG")) + result = APP_TYPE_DINSIG; + else if (!ascii_strcasecmp (string, "P15")) + result = APP_TYPE_P15; + else if (!ascii_strcasecmp (string, "GELDKARTE")) + result = APP_TYPE_GELDKARTE; + else if (!ascii_strcasecmp (string, "SC-HSM")) + result = APP_TYPE_SC_HSM; + else if (!ascii_strcasecmp (string, "PIV")) + result = APP_TYPE_PIV; + else + result = APP_TYPE_UNKNOWN; + + return result; +} + + +/* Return a string representation of the application type. */ +const char * +app_type_string (app_type_t app_type) +{ + const char *result = "?"; + switch (app_type) + { + case APP_TYPE_NONE: result = "None"; break; + case APP_TYPE_OPENPGP: result = "OpenPGP"; break; + case APP_TYPE_NKS: result = "NetKey"; break; + case APP_TYPE_DINSIG: result = "DINSIG"; break; + case APP_TYPE_P15: result = "PKCS#15"; break; + case APP_TYPE_GELDKARTE: result = "Geldkarte"; break; + case APP_TYPE_SC_HSM: result = "SC-HSM"; break; + case APP_TYPE_PIV: result = "PIV"; break; + case APP_TYPE_UNKNOWN: result = "Unknown"; break; + } + return result; +} + + + +/* If RC is not 0, write an appropriate status message. */ +static gpg_error_t +status_sc_op_failure (gpg_error_t err) +{ + switch (gpg_err_code (err)) + { + case 0: + break; + case GPG_ERR_CANCELED: + case GPG_ERR_FULLY_CANCELED: + gnupg_status_printf (STATUS_SC_OP_FAILURE, "1"); + break; + case GPG_ERR_BAD_PIN: + gnupg_status_printf (STATUS_SC_OP_FAILURE, "2"); + break; + default: + gnupg_status_printf (STATUS_SC_OP_FAILURE, NULL); + break; + } + return err; +} + + +/* This is the default inquiry callback. It mainly handles the + Pinentry notifications. */ +static gpg_error_t +default_inq_cb (void *opaque, const char *line) +{ + gpg_error_t err = 0; + struct default_inq_parm_s *parm = opaque; + + (void)parm; + + if (has_leading_keyword (line, "PINENTRY_LAUNCHED")) + { + /* err = gpg_proxy_pinentry_notify (parm->ctrl, line); */ + /* if (err) */ + /* log_error (_("failed to proxy %s inquiry to client\n"), */ + /* "PINENTRY_LAUNCHED"); */ + /* We do not pass errors to avoid breaking other code. */ + } + else + log_debug ("ignoring gpg-agent inquiry '%s'\n", line); + + return err; +} + + +/* Print a warning if the server's version number is less than our + version number. Returns an error code on a connection problem. */ +static gpg_error_t +warn_version_mismatch (assuan_context_t ctx, const char *servername, int mode) +{ + gpg_error_t err; + char *serverversion; + const char *myversion = strusage (13); + + err = get_assuan_server_version (ctx, mode, &serverversion); + if (err) + log_log (gpg_err_code (err) == GPG_ERR_NOT_SUPPORTED? + GPGRT_LOGLVL_INFO : GPGRT_LOGLVL_ERROR, + _("error getting version from '%s': %s\n"), + servername, gpg_strerror (err)); + else if (compare_version_strings (serverversion, myversion) < 0) + { + char *warn; + + warn = xtryasprintf (_("server '%s' is older than us (%s < %s)"), + servername, serverversion, myversion); + if (!warn) + err = gpg_error_from_syserror (); + else + { + log_info (_("WARNING: %s\n"), warn); + if (!opt.quiet) + { + log_info (_("Note: Outdated servers may lack important" + " security fixes.\n")); + log_info (_("Note: Use the command \"%s\" to restart them.\n"), + "gpgconf --kill all"); + } + gnupg_status_printf (STATUS_WARNING, "server_version_mismatch 0 %s", + warn); + xfree (warn); + } + } + xfree (serverversion); + return err; +} + + +/* Try to connect to the agent via socket or fork it off and work by + * pipes. Handle the server's initial greeting. */ +static gpg_error_t +start_agent (unsigned int flags) +{ + gpg_error_t err; + + if (agent_ctx) + err = 0; + else + { + err = start_new_gpg_agent (&agent_ctx, + GPG_ERR_SOURCE_DEFAULT, + opt.agent_program, + opt.lc_ctype, opt.lc_messages, + opt.session_env, + opt.autostart, opt.verbose, DBG_IPC, + NULL, NULL); + if (!opt.autostart && gpg_err_code (err) == GPG_ERR_NO_AGENT) + { + static int shown; + + if (!shown) + { + shown = 1; + log_info (_("no gpg-agent running in this session\n")); + } + } + else if (!err + && !(err = warn_version_mismatch (agent_ctx, GPG_AGENT_NAME, 0))) + { + /* Tell the agent that we support Pinentry notifications. + No error checking so that it will work also with older + agents. */ + assuan_transact (agent_ctx, "OPTION allow-pinentry-notify", + NULL, NULL, NULL, NULL, NULL, NULL); + /* Tell the agent about what version we are aware. This is + here used to indirectly enable GPG_ERR_FULLY_CANCELED. */ + assuan_transact (agent_ctx, "OPTION agent-awareness=2.1.0", + NULL, NULL, NULL, NULL, NULL, NULL); + } + } + + if (!err && !(flags & START_AGENT_NO_STARTUP_CMDS)) + { + /* Request the serial number of the card for an early test. */ + struct card_info_s info; + + memset (&info, 0, sizeof info); + + if (!(flags & START_AGENT_SUPPRESS_ERRORS)) + err = warn_version_mismatch (agent_ctx, SCDAEMON_NAME, 2); + + if (!err) + err = assuan_transact (agent_ctx, "SCD SERIALNO", + NULL, NULL, NULL, NULL, + learn_status_cb, &info); + if (err && !(flags & START_AGENT_SUPPRESS_ERRORS)) + { + switch (gpg_err_code (err)) + { + case GPG_ERR_NOT_SUPPORTED: + case GPG_ERR_NO_SCDAEMON: + gnupg_status_printf (STATUS_CARDCTRL, "6"); /* No card support. */ + break; + case GPG_ERR_OBJ_TERM_STATE: + /* Card is in termination state. */ + gnupg_status_printf (STATUS_CARDCTRL, "7"); + break; + default: + gnupg_status_printf (STATUS_CARDCTRL, "4"); /* No card. */ + break; + } + } + + if (!err && info.serialno) + gnupg_status_printf (STATUS_CARDCTRL, "3 %s", info.serialno); + + release_card_info (&info); + } + + return err; +} + + +/* Return a new malloced string by unescaping the string S. Escaping + * is percent escaping and '+'/space mapping. A binary nul will + * silently be replaced by a 0xFF. Function returns NULL to indicate + * an out of memory status. */ +static char * +unescape_status_string (const unsigned char *s) +{ + return percent_plus_unescape (s, 0xff); +} + + +/* Take a 20 or 32 byte hexencoded string and put it into the provided + * FPRLEN byte long buffer FPR in binary format. Returns the actual + * used length of the FPR buffer or 0 on error. */ +static unsigned int +unhexify_fpr (const char *hexstr, unsigned char *fpr, unsigned int fprlen) +{ + const char *s; + int n; + + for (s=hexstr, n=0; hexdigitp (s); s++, n++) + ; + if ((*s && *s != ' ') || !(n == 40 || n == 64)) + return 0; /* no fingerprint (invalid or wrong length). */ + for (s=hexstr, n=0; *s && n < fprlen; s += 2, n++) + fpr[n] = xtoi_2 (s); + + return (n == 20 || n == 32)? n : 0; +} + + +/* Take the serial number from LINE and return it verbatim in a newly + * allocated string. We make sure that only hex characters are + * returned. Returns NULL on error. */ +static char * +store_serialno (const char *line) +{ + const char *s; + char *p; + + for (s=line; hexdigitp (s); s++) + ; + p = xtrymalloc (s + 1 - line); + if (p) + { + memcpy (p, line, s-line); + p[s-line] = 0; + } + return p; +} + + + +/* Send an APDU to the current card. On success the status word is + * stored at R_SW inless R_SW is NULL. With HEXAPDU being NULL only a + * RESET command is send to scd. With HEXAPDU being the string + * "undefined" the command "SERIALNO undefined" is send to scd. */ +gpg_error_t +scd_apdu (const char *hexapdu, unsigned int *r_sw) +{ + gpg_error_t err; + + err = start_agent (START_AGENT_NO_STARTUP_CMDS); + if (err) + return err; + + if (!hexapdu) + { + err = assuan_transact (agent_ctx, "SCD RESET", + NULL, NULL, NULL, NULL, NULL, NULL); + + } + else if (!strcmp (hexapdu, "undefined")) + { + err = assuan_transact (agent_ctx, "SCD SERIALNO undefined", + NULL, NULL, NULL, NULL, NULL, NULL); + } + else + { + char line[ASSUAN_LINELENGTH]; + membuf_t mb; + unsigned char *data; + size_t datalen; + + init_membuf (&mb, 256); + + snprintf (line, DIM(line), "SCD APDU %s", hexapdu); + err = assuan_transact (agent_ctx, line, + put_membuf_cb, &mb, NULL, NULL, NULL, NULL); + if (!err) + { + data = get_membuf (&mb, &datalen); + if (!data) + err = gpg_error_from_syserror (); + else if (datalen < 2) /* Ooops */ + err = gpg_error (GPG_ERR_CARD); + else + { + if (r_sw) + *r_sw = buf16_to_uint (data+datalen-2); + } + xfree (data); + } + } + + return err; +} + + +/* This is a dummy data line callback. */ +static gpg_error_t +dummy_data_cb (void *opaque, const void *buffer, size_t length) +{ + (void)opaque; + (void)buffer; + (void)length; + return 0; +} + +/* A simple callback used to return the serialnumber of a card. */ +static gpg_error_t +get_serialno_cb (void *opaque, const char *line) +{ + char **serialno = opaque; + const char *keyword = line; + const char *s; + int keywordlen, n; + + for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) + ; + while (spacep (line)) + line++; + + /* FIXME: Should we use has_leading_keyword? */ + if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen)) + { + if (*serialno) + return gpg_error (GPG_ERR_CONFLICT); /* Unexpected status line. */ + for (n=0,s=line; hexdigitp (s); s++, n++) + ; + if (!n || (n&1)|| !(spacep (s) || !*s) ) + return gpg_error (GPG_ERR_ASS_PARAMETER); + *serialno = xtrymalloc (n+1); + if (!*serialno) + return out_of_core (); + memcpy (*serialno, line, n); + (*serialno)[n] = 0; + } + + return 0; +} + + +/* The status callback to handle the LEARN and GETATTR commands. */ +static gpg_error_t +learn_status_cb (void *opaque, const char *line) +{ + struct card_info_s *parm = opaque; + const char *keyword = line; + int keywordlen; + int i; + + for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) + ; + while (spacep (line)) + line++; + + switch (keywordlen) + { + case 3: + if (!memcmp (keyword, "KDF", 3)) + { + parm->kdf_do_enabled = 1; + } + break; + + case 5: + if (!memcmp (keyword, "UIF-", 4) + && strchr("123", keyword[4])) + { + unsigned char *data; + int no = keyword[4] - '1'; + + log_assert (no >= 0 && no <= 2); + data = unescape_status_string (line); + parm->uif[no] = (data[0] != 0xff); + xfree (data); + } + break; + + case 6: + if (!memcmp (keyword, "READER", keywordlen)) + { + xfree (parm->reader); + parm->reader = unescape_status_string (line); + } + else if (!memcmp (keyword, "EXTCAP", keywordlen)) + { + char *p, *p2, *buf; + int abool; + + buf = p = unescape_status_string (line); + if (buf) + { + for (p = strtok (buf, " "); p; p = strtok (NULL, " ")) + { + p2 = strchr (p, '='); + if (p2) + { + *p2++ = 0; + abool = (*p2 == '1'); + if (!strcmp (p, "ki")) + parm->extcap.ki = abool; + else if (!strcmp (p, "aac")) + parm->extcap.aac = abool; + else if (!strcmp (p, "bt")) + parm->extcap.bt = abool; + else if (!strcmp (p, "kdf")) + parm->extcap.kdf = abool; + else if (!strcmp (p, "si")) + parm->status_indicator = strtoul (p2, NULL, 10); + } + } + xfree (buf); + } + } + else if (!memcmp (keyword, "CA-FPR", keywordlen)) + { + int no = atoi (line); + while (*line && !spacep (line)) + line++; + while (spacep (line)) + line++; + if (no == 1) + parm->cafpr1len = unhexify_fpr (line, parm->cafpr1, + sizeof parm->cafpr1); + else if (no == 2) + parm->cafpr2len = unhexify_fpr (line, parm->cafpr2, + sizeof parm->cafpr2); + else if (no == 3) + parm->cafpr3len = unhexify_fpr (line, parm->cafpr3, + sizeof parm->cafpr3); + } + break; + + case 7: + if (!memcmp (keyword, "APPTYPE", keywordlen)) + { + xfree (parm->apptypestr); + parm->apptypestr = unescape_status_string (line); + parm->apptype = map_apptypestr (parm->apptypestr); + } + else if (!memcmp (keyword, "KEY-FPR", keywordlen)) + { + int no = atoi (line); + + while (*line && !spacep (line)) + line++; + while (spacep (line)) + line++; + if (no == 1) + parm->fpr1len = unhexify_fpr (line, parm->fpr1, sizeof parm->fpr1); + else if (no == 2) + parm->fpr2len = unhexify_fpr (line, parm->fpr2, sizeof parm->fpr2); + else if (no == 3) + parm->fpr3len = unhexify_fpr (line, parm->fpr3, sizeof parm->fpr3); + } + break; + + case 8: + if (!memcmp (keyword, "SERIALNO", keywordlen)) + { + xfree (parm->serialno); + parm->serialno = store_serialno (line); + parm->is_v2 = (strlen (parm->serialno) >= 16 + && xtoi_2 (parm->serialno+12) >= 2 ); + } + else if (!memcmp (keyword, "DISP-SEX", keywordlen)) + { + parm->disp_sex = *line == '1'? 1 : *line == '2' ? 2: 0; + } + else if (!memcmp (keyword, "KEY-TIME", keywordlen)) + { + int no = atoi (line); + while (* line && !spacep (line)) + line++; + while (spacep (line)) + line++; + if (no == 1) + parm->fpr1time = strtoul (line, NULL, 10); + else if (no == 2) + parm->fpr2time = strtoul (line, NULL, 10); + else if (no == 3) + parm->fpr3time = strtoul (line, NULL, 10); + } + else if (!memcmp (keyword, "KEY-ATTR", keywordlen)) + { + int keyno = 0; + int algo = GCRY_PK_RSA; + int n = 0; + + sscanf (line, "%d %d %n", &keyno, &algo, &n); + keyno--; + if (keyno < 0 || keyno >= DIM (parm->key_attr)) + ; /* Out of range - ignore. */ + else + { + parm->key_attr[keyno].algo = algo; + if (algo == PUBKEY_ALGO_RSA) + parm->key_attr[keyno].nbits = strtoul (line+n+3, NULL, 10); + else if (algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_ECDSA + || algo == PUBKEY_ALGO_EDDSA) + { + parm->key_attr[keyno].curve = + openpgp_is_curve_supported (line + n, NULL, NULL); + } + } + } + break; + + case 9: + if (!memcmp (keyword, "DISP-NAME", keywordlen)) + { + xfree (parm->disp_name); + parm->disp_name = unescape_status_string (line); + } + else if (!memcmp (keyword, "DISP-LANG", keywordlen)) + { + xfree (parm->disp_lang); + parm->disp_lang = unescape_status_string (line); + } + break; + + case 10: + if (!memcmp (keyword, "PUBKEY-URL", keywordlen)) + { + xfree (parm->pubkey_url); + parm->pubkey_url = unescape_status_string (line); + } + else if (!memcmp (keyword, "LOGIN-DATA", keywordlen)) + { + xfree (parm->login_data); + parm->login_data = unescape_status_string (line); + } + else if (!memcmp (keyword, "CHV-STATUS", keywordlen)) + { + char *p, *buf; + + buf = p = unescape_status_string (line); + if (buf) + { + while (spacep (p)) + p++; + parm->chv1_cached = atoi (p); + while (*p && !spacep (p)) + p++; + while (spacep (p)) + p++; + for (i=0; *p && i < 3; i++) + { + parm->chvmaxlen[i] = atoi (p); + while (*p && !spacep (p)) + p++; + while (spacep (p)) + p++; + } + for (i=0; *p && i < 3; i++) + { + parm->chvretry[i] = atoi (p); + while (*p && !spacep (p)) + p++; + while (spacep (p)) + p++; + } + xfree (buf); + } + } + break; + + case 11: + if (!memcmp (keyword, "SIG-COUNTER", keywordlen)) + { + parm->sig_counter = strtoul (line, NULL, 0); + } + else if (!memcmp (keyword, "KEYPAIRINFO", keywordlen)) + { + const char *hexgrp = line; + int no; + + while (*line && !spacep (line)) + line++; + while (spacep (line)) + line++; + if (strncmp (line, "OPENPGP.", 8)) + ; + else if ((no = atoi (line+8)) == 1) + unhexify_fpr (hexgrp, parm->grp1, sizeof parm->grp1); + else if (no == 2) + unhexify_fpr (hexgrp, parm->grp2, sizeof parm->grp2); + else if (no == 3) + unhexify_fpr (hexgrp, parm->grp3, sizeof parm->grp3); + } + break; + + case 12: + if (!memcmp (keyword, "PRIVATE-DO-", 11) + && strchr("1234", keyword[11])) + { + int no = keyword[11] - '1'; + log_assert (no >= 0 && no <= 3); + xfree (parm->private_do[no]); + parm->private_do[no] = unescape_status_string (line); + } + break; + + case 13: + if (!memcmp (keyword, "$DISPSERIALNO", keywordlen)) + { + xfree (parm->dispserialno); + parm->dispserialno = unescape_status_string (line); + } + break; + + default: + /* Unknown. */ + break; + } + + return 0; +} + + +/* Call the scdaemon to learn about a smartcard. This fills INFO + * wioth data from the card. */ +gpg_error_t +scd_learn (card_info_t info) +{ + gpg_error_t err; + struct default_inq_parm_s parm; + struct card_info_s dummyinfo; + + if (!info) + info = &dummyinfo; + + memset (info, 0, sizeof *info); + memset (&parm, 0, sizeof parm); + + err = start_agent (0); + if (err) + return err; + + parm.ctx = agent_ctx; + err = assuan_transact (agent_ctx, "SCD LEARN --force", + dummy_data_cb, NULL, default_inq_cb, &parm, + learn_status_cb, info); + /* Also try to get some other key attributes. */ + if (!err) + { + err = scd_getattr ("KEY-ATTR", info); + if (gpg_err_code (err) == GPG_ERR_INV_NAME + || gpg_err_code (err) == GPG_ERR_UNSUPPORTED_OPERATION) + err = 0; /* Not implemented or GETATTR not supported. */ + err = scd_getattr ("$DISPSERIALNO", info); + if (gpg_err_code (err) == GPG_ERR_INV_NAME + || gpg_err_code (err) == GPG_ERR_UNSUPPORTED_OPERATION) + err = 0; /* Not implemented or GETATTR not supported. */ + + } + + if (info == &dummyinfo) + release_card_info (info); + + return err; +} + + +/* Call the agent to retrieve a data object. This function returns + * the data in the same structure as used by the learn command. It is + * allowed to update such a structure using this command. */ +gpg_error_t +scd_getattr (const char *name, struct card_info_s *info) +{ + gpg_error_t err; + char line[ASSUAN_LINELENGTH]; + struct default_inq_parm_s parm; + + memset (&parm, 0, sizeof parm); + + if (!*name) + return gpg_error (GPG_ERR_INV_VALUE); + + /* We assume that NAME does not need escaping. */ + if (12 + strlen (name) > DIM(line)-1) + return gpg_error (GPG_ERR_TOO_LARGE); + stpcpy (stpcpy (line, "SCD GETATTR "), name); + + err = start_agent (0); + if (err) + return err; + + parm.ctx = agent_ctx; + err = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &parm, + learn_status_cb, info); + + return err; +} + + +/* Send an setattr command to the SCdaemon. */ +gpg_error_t +scd_setattr (const char *name, + const unsigned char *value, size_t valuelen) +{ + gpg_error_t err; + char *tmp; + char *line = NULL; + struct default_inq_parm_s parm; + + + if (!*name || !valuelen) + return gpg_error (GPG_ERR_INV_VALUE); + + tmp = strconcat ("SCD SETATTR ", name, " ", NULL); + if (!tmp) + { + err = gpg_error_from_syserror (); + goto leave; + } + line = percent_data_escape (1, tmp, value, valuelen); + xfree (tmp); + if (!line) + { + err = gpg_error_from_syserror (); + goto leave; + } + + if (strlen (line) + 10 > ASSUAN_LINELENGTH) + { + err = gpg_error (GPG_ERR_TOO_LARGE); + goto leave; + } + + err = start_agent (0); + if (err ) + goto leave; + + memset (&parm, 0, sizeof parm); + parm.ctx = agent_ctx; + err = assuan_transact (agent_ctx, line, NULL, NULL, + default_inq_cb, &parm, NULL, NULL); + + leave: + xfree (line); + return status_sc_op_failure (err); +} + + + +/* Handle a CERTDATA inquiry. Note, we only send the data, + * assuan_transact takes care of flushing and writing the END + * command. */ +static gpg_error_t +inq_writecert_parms (void *opaque, const char *line) +{ + gpg_error_t err; + struct writecert_parm_s *parm = opaque; + + if (has_leading_keyword (line, "CERTDATA")) + { + err = assuan_send_data (parm->dflt->ctx, + parm->certdata, parm->certdatalen); + } + else + err = default_inq_cb (parm->dflt, line); + + return err; +} + + +/* Send a WRITECERT command to the SCdaemon. */ +gpg_error_t +scd_writecert (const char *certidstr, + const unsigned char *certdata, size_t certdatalen) +{ + gpg_error_t err; + char line[ASSUAN_LINELENGTH]; + struct writecert_parm_s parms; + struct default_inq_parm_s dfltparm; + + memset (&dfltparm, 0, sizeof dfltparm); + + err = start_agent (0); + if (err) + return err; + + memset (&parms, 0, sizeof parms); + + snprintf (line, sizeof line, "SCD WRITECERT %s", certidstr); + dfltparm.ctx = agent_ctx; + parms.dflt = &dfltparm; + parms.certdata = certdata; + parms.certdatalen = certdatalen; + + err = assuan_transact (agent_ctx, line, NULL, NULL, + inq_writecert_parms, &parms, NULL, NULL); + + return status_sc_op_failure (err); +} + + + +/* Handle a KEYDATA inquiry. Note, we only send the data, + assuan_transact takes care of flushing and writing the end */ +static gpg_error_t +inq_writekey_parms (void *opaque, const char *line) +{ + gpg_error_t err; + struct writekey_parm_s *parm = opaque; + + if (has_leading_keyword (line, "KEYDATA")) + { + err = assuan_send_data (parm->dflt->ctx, parm->keydata, parm->keydatalen); + } + else + err = default_inq_cb (parm->dflt, line); + + return err; +} + + +/* Send a WRITEKEY command to the SCdaemon. */ +gpg_error_t +scd_writekey (int keyno, const unsigned char *keydata, size_t keydatalen) +{ + gpg_error_t err; + char line[ASSUAN_LINELENGTH]; + struct writekey_parm_s parms; + struct default_inq_parm_s dfltparm; + + memset (&parms, 0, sizeof parms); + memset (&dfltparm, 0, sizeof dfltparm); + + err = start_agent (0); + if (err) + return err; + + snprintf (line, sizeof line, "SCD WRITEKEY --force OPENPGP.%d", keyno); + dfltparm.ctx = agent_ctx; + parms.dflt = &dfltparm; + parms.keydata = keydata; + parms.keydatalen = keydatalen; + + err = assuan_transact (agent_ctx, line, NULL, NULL, + inq_writekey_parms, &parms, NULL, NULL); + + return status_sc_op_failure (err); +} + + + +/* Status callback for the SCD GENKEY command. */ +static gpg_error_t +scd_genkey_cb (void *opaque, const char *line) +{ + u32 *createtime = opaque; + const char *keyword = line; + int keywordlen; + + for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) + ; + while (spacep (line)) + line++; + + if (keywordlen == 14 && !memcmp (keyword,"KEY-CREATED-AT", keywordlen)) + { + *createtime = (u32)strtoul (line, NULL, 10); + } + else if (keywordlen == 8 && !memcmp (keyword, "PROGRESS", keywordlen)) + { + gnupg_status_printf (STATUS_PROGRESS, "%s", line); + } + + return 0; +} + +/* Send a GENKEY command to the SCdaemon. If *CREATETIME is not 0, + * the value will be passed to SCDAEMON with --timestamp option so that + * the key is created with this. Otherwise, timestamp was generated by + * SCDEAMON. On success, creation time is stored back to + * CREATETIME. */ +gpg_error_t +scd_genkey (int keyno, int force, u32 *createtime) +{ + gpg_error_t err; + char line[ASSUAN_LINELENGTH]; + gnupg_isotime_t tbuf; + struct default_inq_parm_s dfltparm; + + memset (&dfltparm, 0, sizeof dfltparm); + + err = start_agent (0); + if (err) + return err; + + if (*createtime) + epoch2isotime (tbuf, *createtime); + else + *tbuf = 0; + + snprintf (line, sizeof line, "SCD GENKEY %s%s %s %d", + *tbuf? "--timestamp=":"", tbuf, + force? "--force":"", + keyno); + + dfltparm.ctx = agent_ctx; + err = assuan_transact (agent_ctx, line, + NULL, NULL, default_inq_cb, &dfltparm, + scd_genkey_cb, createtime); + + return status_sc_op_failure (err); +} + + + +/* Return the serial number of the card or an appropriate error. The + * serial number is returned as a hexstring. If DEMAND is not NULL + * the reader with the a card of the serilanumber DEMAND is + * requested. */ +gpg_error_t +scd_serialno (char **r_serialno, const char *demand) +{ + int err; + char *serialno = NULL; + char line[ASSUAN_LINELENGTH]; + + err = start_agent (START_AGENT_SUPPRESS_ERRORS); + if (err) + return err; + + if (!demand) + strcpy (line, "SCD SERIALNO"); + else + snprintf (line, DIM(line), "SCD SERIALNO --demand=%s", demand); + + err = assuan_transact (agent_ctx, line, + NULL, NULL, NULL, NULL, + get_serialno_cb, &serialno); + if (err) + { + xfree (serialno); + return err; + } + + *r_serialno = serialno; + return 0; +} + + + +/* Send a READCERT command to the SCdaemon. */ +gpg_error_t +scd_readcert (const char *certidstr, void **r_buf, size_t *r_buflen) +{ + gpg_error_t err; + char line[ASSUAN_LINELENGTH]; + membuf_t data; + size_t len; + struct default_inq_parm_s dfltparm; + + memset (&dfltparm, 0, sizeof dfltparm); + + *r_buf = NULL; + err = start_agent (0); + if (err) + return err; + + dfltparm.ctx = agent_ctx; + + init_membuf (&data, 2048); + + snprintf (line, sizeof line, "SCD READCERT %s", certidstr); + err = assuan_transact (agent_ctx, line, + put_membuf_cb, &data, + default_inq_cb, &dfltparm, + NULL, NULL); + if (err) + { + xfree (get_membuf (&data, &len)); + return err; + } + + *r_buf = get_membuf (&data, r_buflen); + if (!*r_buf) + return gpg_error_from_syserror (); + + return 0; +} + + + +/* Callback function for card_cardlist. */ +static gpg_error_t +card_cardlist_cb (void *opaque, const char *line) +{ + struct card_cardlist_parm_s *parm = opaque; + const char *keyword = line; + int keywordlen; + + for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) + ; + while (spacep (line)) + line++; + + if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen)) + { + const char *s; + int n; + + for (n=0,s=line; hexdigitp (s); s++, n++) + ; + + if (!n || (n&1) || *s) + parm->error = gpg_error (GPG_ERR_ASS_PARAMETER); + else + add_to_strlist (&parm->list, line); + } + + return 0; +} + + +/* Return the serial numbers of all cards currently inserted. */ +gpg_error_t +scd_cardlist (strlist_t *result) +{ + gpg_error_t err; + struct card_cardlist_parm_s parm; + + memset (&parm, 0, sizeof parm); + *result = NULL; + + err = start_agent (START_AGENT_SUPPRESS_ERRORS); + if (err) + return err; + + err = assuan_transact (agent_ctx, "SCD GETINFO card_list", + NULL, NULL, NULL, NULL, + card_cardlist_cb, &parm); + if (!err && parm.error) + err = parm.error; + + if (!err) + *result = parm.list; + else + free_strlist (parm.list); + + return err; +} + + + +/* Change the PIN of an OpenPGP card or reset the retry counter. + * CHVNO 1: Change the PIN + * 2: For v1 cards: Same as 1. + * For v2 cards: Reset the PIN using the Reset Code. + * 3: Change the admin PIN + * 101: Set a new PIN and reset the retry counter + * 102: For v1 cars: Same as 101. + * For v2 cards: Set a new Reset Code. + * SERIALNO is not used. + */ +gpg_error_t +scd_change_pin (int chvno) +{ + gpg_error_t err; + char line[ASSUAN_LINELENGTH]; + const char *reset = ""; + struct default_inq_parm_s dfltparm; + + memset (&dfltparm, 0, sizeof dfltparm); + + if (chvno >= 100) + reset = "--reset"; + chvno %= 100; + + err = start_agent (0); + if (err) + return err; + dfltparm.ctx = agent_ctx; + + snprintf (line, sizeof line, "SCD PASSWD %s %d", reset, chvno); + err = assuan_transact (agent_ctx, line, + NULL, NULL, + default_inq_cb, &dfltparm, + NULL, NULL); + + return status_sc_op_failure (err); +} + + +/* Perform a CHECKPIN operation. SERIALNO should be the serial + * number of the card - optionally followed by the fingerprint; + * however the fingerprint is ignored here. */ +gpg_error_t +scd_checkpin (const char *serialno) +{ + gpg_error_t err; + char line[ASSUAN_LINELENGTH]; + struct default_inq_parm_s dfltparm; + + memset (&dfltparm, 0, sizeof dfltparm); + + err = start_agent (0); + if (err) + return err; + dfltparm.ctx = agent_ctx; + + snprintf (line, sizeof line, "SCD CHECKPIN %s", serialno); + err = assuan_transact (agent_ctx, line, + NULL, NULL, + default_inq_cb, &dfltparm, + NULL, NULL); + return status_sc_op_failure (err); +} + + +/* Return the S2K iteration count as computed by gpg-agent. On error + * print a warning and return a default value. */ +unsigned long +agent_get_s2k_count (void) +{ + gpg_error_t err; + membuf_t data; + char *buf; + unsigned long count = 0; + + err = start_agent (0); + if (err) + goto leave; + + init_membuf (&data, 32); + err = assuan_transact (agent_ctx, "GETINFO s2k_count", + put_membuf_cb, &data, + NULL, NULL, NULL, NULL); + if (err) + xfree (get_membuf (&data, NULL)); + else + { + put_membuf (&data, "", 1); + buf = get_membuf (&data, NULL); + if (!buf) + err = gpg_error_from_syserror (); + else + { + count = strtoul (buf, NULL, 10); + xfree (buf); + } + } + + leave: + if (err || count < 65536) + { + /* Don't print an error if an older agent is used. */ + if (err && gpg_err_code (err) != GPG_ERR_ASS_PARAMETER) + log_error (_("problem with the agent: %s\n"), gpg_strerror (err)); + + /* Default to 65536 which was used up to 2.0.13. */ + count = 65536; + } + + return count; +} diff --git a/tools/card-tool.h b/tools/card-tool.h new file mode 100644 index 000000000..d51698c76 --- /dev/null +++ b/tools/card-tool.h @@ -0,0 +1,161 @@ +/* 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 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) + + +/* 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. */ + }; +}; + + +/* + * The object used to store information about a card. + */ +struct card_info_s +{ + int error; /* private. */ + char *reader; /* Reader information. */ + 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]; + unsigned char fpr1len; /* Length of the fingerprint or 0 if invalid. */ + unsigned char fpr2len; + unsigned char fpr3len; + char fpr1[20]; + char fpr2[20]; + char fpr3[20]; + u32 fpr1time; + u32 fpr2time; + u32 fpr3time; + char grp1[20]; /* The keygrip for OPENPGP.1 */ + char grp2[20]; /* The keygrip for OPENPGP.2 */ + char grp3[20]; /* The keygrip for OPENPGP.3 */ + unsigned long sig_counter; + int chv1_cached; /* 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 card. */ + int chvmaxlen[3]; /* Maximum allowed length of a CHV. */ + int chvretry[3]; /* Allowed retries for the CHV; 0 = blocked. */ + 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-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); +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 (int keyno, int force, 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_cardlist (strlist_t *result); +gpg_error_t scd_change_pin (int chvno); +gpg_error_t scd_checkpin (const char *serialno); + +unsigned long agent_get_s2k_count (void); + + + +#endif /*GNUPG_CARD_TOOL_H*/ diff --git a/tools/gpg-card-tool.c b/tools/gpg-card-tool.c index 91f04108e..b40914a6f 100644 --- a/tools/gpg-card-tool.c +++ b/tools/gpg-card-tool.c @@ -1,869 +1,2729 @@ /* gpg-card-tool.c - An interactive tool to work with cards. - * Copyright (C) 2019 g10 Code GmbH Werner Koch + * 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+ + * 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 cmd_and_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 (300, ("@Commands:\n ")), 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 () }; -/* Debug values and macros. */ -#define DBG_IPC_VALUE 1024 /* Debug assuan communication. */ -#define DBG_EXTPROG_VALUE 16384 /* debug external program calls */ - - /* The list of supported debug flags. */ static struct debug_flags_s debug_flags [] = { { DBG_IPC_VALUE , "ipc" }, { DBG_EXTPROG_VALUE, "extprog" }, { 0, NULL } }; +/* Limit of size of data we read from a file for certain commands. */ +#define MAX_GET_DATA_FROM_FILE 16384 + +/* Constats 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 + -/* We keep all global options in the structure OPT. */ -struct -{ - int verbose; - unsigned int debug; - int quiet; - int with_colons; - const char *gpg_program; - const char *gpgsm_program; -} opt; +/* Local prototypes. */ static void wrong_args (const char *text) GPGRT_ATTR_NORETURN; 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 [command] [options] [args] (-h for help)"); break; case 41: p = ("Syntax: gpg-card-tool [command] [options] [args]\n" "Tool to configure cards and tokens\n"); break; default: p = NULL; break; } return p; } static void wrong_args (const char *text) { es_fprintf (es_stderr, _("usage: %s [options] %s\n"), strusage (11), text); exit (2); } +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 enum cmd_and_opt_values parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts) { enum cmd_and_opt_values cmd = 0; int no_more_options = 0; while (!no_more_options && 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 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 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; } } return cmd; } /* gpg-card-tool main. */ int main (int argc, char **argv) { gpg_error_t err; ARGPARSE_ARGS pargs; enum cmd_and_opt_values cmd; 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; cmd = parse_arguments (&pargs, opts); if (log_get_errorcount (0)) exit (2); /* Print a warning if an argument looks like an option. */ if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN)) { int i; for (i=0; i < argc; i++) if (argv[i][0] == '-' && argv[i][1] == '-') log_info (("NOTE: '%s' is not considered an option\n"), argv[i]); } /* 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); /* Run the selected command. */ switch (cmd) { default: interactive_loop (); err = 0; break; } 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, stpres the number of bytes read at R_BUFLEN and + * the address of a newly allocated buffer at R_BUFFER. */ +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; -/* Print all available information about the current card. */ -static void -print_card_status (char *serialno, size_t serialnobuflen) -{ - /* struct agent_card_info_s info; */ - /* PKT_public_key *pk = xcalloc (1, sizeof *pk); */ - /* kbnode_t keyblock = NULL; */ - /* int rc; */ - /* unsigned int uval; */ - /* const unsigned char *thefpr; */ - /* unsigned int thefprlen; */ - /* int i; */ - - /* if (serialno && serialnobuflen) */ - /* *serialno = 0; */ - - /* rc = agent_scd_learn (&info, 0); */ - /* if (rc) */ - /* { */ - /* if (opt.with_colons) */ - /* es_fputs ("AID:::\n", fp); */ - /* log_error (_("OpenPGP card not available: %s\n"), gpg_strerror (rc)); */ - /* xfree (pk); */ - /* return; */ - /* } */ - - /* if (opt.with_colons) */ - /* es_fprintf (fp, "Reader:%s:", info.reader? info.reader : ""); */ - /* else */ - /* tty_fprintf (fp, "Reader ...........: %s\n", */ - /* info.reader? info.reader : "[none]"); */ - /* if (opt.with_colons) */ - /* es_fprintf (fp, "AID:%s:", info.serialno? info.serialno : ""); */ - /* else */ - /* tty_fprintf (fp, "Application ID ...: %s\n", */ - /* info.serialno? info.serialno : "[none]"); */ - /* if (!info.serialno || strncmp (info.serialno, "D27600012401", 12) */ - /* || strlen (info.serialno) != 32 ) */ - /* { */ - /* if (info.apptype && !strcmp (info.apptype, "NKS")) */ - /* { */ - /* if (opt.with_colons) */ - /* es_fputs ("netkey-card:\n", fp); */ - /* log_info ("this is a NetKey card\n"); */ - /* } */ - /* else if (info.apptype && !strcmp (info.apptype, "DINSIG")) */ - /* { */ - /* if (opt.with_colons) */ - /* es_fputs ("dinsig-card:\n", fp); */ - /* log_info ("this is a DINSIG compliant card\n"); */ - /* } */ - /* else if (info.apptype && !strcmp (info.apptype, "P15")) */ - /* { */ - /* if (opt.with_colons) */ - /* es_fputs ("pkcs15-card:\n", fp); */ - /* log_info ("this is a PKCS#15 compliant card\n"); */ - /* } */ - /* else if (info.apptype && !strcmp (info.apptype, "GELDKARTE")) */ - /* { */ - /* if (opt.with_colons) */ - /* es_fputs ("geldkarte-card:\n", fp); */ - /* log_info ("this is a Geldkarte compliant card\n"); */ - /* } */ - /* else */ - /* { */ - /* if (opt.with_colons) */ - /* es_fputs ("unknown:\n", fp); */ - /* } */ - /* log_info ("not an OpenPGP card\n"); */ - /* agent_release_card_info (&info); */ - /* xfree (pk); */ - /* return; */ - /* } */ + *r_buffer = NULL; + *r_buflen = 0; - /* if (!serialno) */ - /* ; */ - /* else if (strlen (info.serialno)+1 > serialnobuflen) */ - /* log_error ("serial number longer than expected\n"); */ - /* else */ - /* strcpy (serialno, info.serialno); */ - - /* if (opt.with_colons) */ - /* es_fputs ("openpgp-card:\n", fp); */ - - - /* 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, "Serial number ....: %.8s\n", info.serialno+20); */ - - /* print_isoname (fp, "Name of cardholder: ", "name", info.disp_name); */ - /* print_name (fp, "Language prefs ...: ", info.disp_lang); */ - /* tty_fprintf (fp, "Salutation .......: %s\n", */ - /* info.disp_sex == 1? _("Mr."): */ - /* info.disp_sex == 2? _("Mrs.") : ""); */ - /* print_name (fp, "URL of public key : ", info.pubkey_url); */ - /* print_name (fp, "Login data .......: ", info.login_data); */ - /* if (info.private_do[0]) */ - /* print_name (fp, "Private DO 1 .....: ", info.private_do[0]); */ - /* if (info.private_do[1]) */ - /* print_name (fp, "Private DO 2 .....: ", info.private_do[1]); */ - /* if (info.private_do[2]) */ - /* print_name (fp, "Private DO 3 .....: ", info.private_do[2]); */ - /* if (info.private_do[3]) */ - /* print_name (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 = "?"; */ - - /* if (info.key_attr[i].curve) */ - /* { */ - /* const char *oid; */ - /* oid = openpgp_curve_to_oid (info.key_attr[i].curve, NULL); */ - /* if (oid) */ - /* 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.chvretry[0], info.chvretry[1], info.chvretry[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"); */ - /* } */ - /* tty_fprintf (fp, "Signature key ....:"); */ - /* print_shax_fpr (fp, info.fpr1len? info.fpr1:NULL, info.fpr1len); */ - /* if (info.fpr1len && info.fpr1time) */ - /* { */ - /* tty_fprintf (fp, " created ....: %s\n", */ - /* isotimestamp (info.fpr1time)); */ - /* print_keygrip (fp, info.grp1); */ - /* } */ - /* tty_fprintf (fp, "Encryption key....:"); */ - /* print_shax_fpr (fp, info.fpr2len? info.fpr2:NULL, info.fpr2len); */ - /* if (info.fpr2len && info.fpr2time) */ - /* { */ - /* tty_fprintf (fp, " created ....: %s\n", */ - /* isotimestamp (info.fpr2time)); */ - /* print_keygrip (fp, info.grp2); */ - /* } */ - /* tty_fprintf (fp, "Authentication key:"); */ - /* print_shax_fpr (fp, info.fpr3len? info.fpr3:NULL, info.fpr3len); */ - /* if (info.fpr3len && info.fpr3time) */ - /* { */ - /* tty_fprintf (fp, " created ....: %s\n", */ - /* isotimestamp (info.fpr3time)); */ - /* print_keygrip (fp, info.grp3); */ - /* } */ - /* 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 && !fpr_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"); */ - - /* release_kbnode (keyblock); */ - /* free_public_key (pk); */ - /* agent_release_card_info (&info); */ -} + 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; + } - -static void -cmd_verify (void) -{ - /* agent_scd_checkpin (serialnobuf); */ + n = es_fread (data, 1, MAX_GET_DATA_FROM_FILE, 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; + } + *r_buffer = data; + *r_buflen = n; + return 0; } -static void -cmd_name (void) +/* 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) { - /* change_name (); */ -} + 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; + } -static void -cmd_url (void) -{ - /* change_url (); */ + 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; } -static void -cmd_fetch (void) + +/* 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, ...) { - /* fetch_url (); */ -} + va_list arg_ptr; + int value; + int any = 0; + tty_fprintf (NULL, "%s\n", text); -static void -cmd_login (char *arg_string) -{ - /* change_login (arg_string); */ -} - + va_start (arg_ptr, text); + while ((value = va_arg (arg_ptr, int))) + { + if (!any) + tty_fprintf (NULL, "[Supported by: "); + tty_fprintf (NULL, "%s%s", any?", ":"", app_type_string (value)); + any = 1; + } + if (any) + tty_fprintf (NULL, "]\n"); -static void -cmd_lang (void) -{ - /* change_lang (); */ + va_end (arg_ptr); + return 0; } -static void -cmd_salut (void) +/* Return the OpenPGP card manufacturer name. */ +static const char * +get_manufacturer (unsigned int no) { - /* change_salut (); */ + /* 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 -cmd_cafpr (int arg_number) +print_shax_fpr (estream_t fp, const unsigned char *fpr, unsigned int fprlen) { - if ( arg_number < 1 || arg_number > 3 ) - tty_printf ("usage: cafpr N\n" - " 1 <= N <= 3\n"); - /* else */ - /* change_cafpr (arg_number); */ -} - + int i; -static void -cmd_privatedo (int arg_number, char *arg_string) -{ - if ( arg_number < 1 || arg_number > 4 ) - tty_printf ("usage: privatedo N\n" - " 1 <= N <= 4\n"); - /* else */ - /* change_private_do (arg_string, arg_number); */ + 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 -cmd_writecert (int arg_number, char *arg_rest) +print_keygrip (estream_t fp, const unsigned char *grp) { - if ( arg_number != 3 ) - tty_printf ("usage: writecert 3 < FILE\n"); - /* else */ - /* change_cert (arg_rest); */ + 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 -cmd_readcert (int arg_number, char *arg_rest) +print_string (estream_t fp, const char *text, const char *name) { - if ( arg_number != 3 ) - tty_printf ("usage: readcert 3 > FILE\n"); - /* else */ - /* read_cert (arg_rest); */ + 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 -cmd_forcesig (void) +print_isoname (estream_t fp, const char *name) { - /* toggle_forcesig (); */ -} + 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); -static void -cmd_generate (void) -{ - /* generate_card_keys (); */ + xfree (buf); + } + else + { + tty_fprintf (fp, _("[not set]")); + } + + tty_fprintf (fp, "\n"); } -static void -cmd_passwd (int allow_admin) +/* Return true if the SHA1 fingerprint FPR consists only of zeroes. */ +static int +fpr_is_zero (const char *fpr, unsigned int fprlen) { - /* change_pin (0, allow_admin); */ + int i; + + for (i=0; i < fprlen && !fpr[i]; i++) + ; + return (i == fprlen); } -static void -cmd_unblock (int allow_admin) +/* Return true if the fingerprint FPR consists only of 0xFF. */ +static int +fpr_is_ff (const char *fpr, unsigned int fprlen) { - /* change_pin (1, allow_admin); */ + int i; + + for (i=0; i < fprlen && fpr[i] == '\xff'; i++) + ; + return (i == fprlen); } + +/* List OpenPGP card specific data. */ static void -cmd_factoryreset (void) +list_openpgp (card_info_t info, estream_t fp) { - /* factory_reset (); */ -} + int i; + if (!info->serialno + || strncmp (info->serialno, "D27600012401", 12) + || strlen (info->serialno) != 32 ) + { + tty_fprintf (fp, "invalid OpenPGP card\n"); + return; + } -static void -cmd_kdfsetup (char *argstring) -{ - /* kdf_setup (arg_string); */ + 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->chvretry[0], info->chvretry[1], info->chvretry[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"); + } + tty_fprintf (fp, "Signature key ....:"); + print_shax_fpr (fp, info->fpr1len? info->fpr1:NULL, info->fpr1len); + if (info->fpr1len && info->fpr1time) + { + tty_fprintf (fp, " created ....: %s\n", + isotimestamp (info->fpr1time)); + tty_fprintf (fp, " keygrip ....: "); + print_keygrip (fp, info->grp1); + } + tty_fprintf (fp, "Encryption key....:"); + print_shax_fpr (fp, info->fpr2len? info->fpr2:NULL, info->fpr2len); + if (info->fpr2len && info->fpr2time) + { + tty_fprintf (fp, " created ....: %s\n", + isotimestamp (info->fpr2time)); + tty_fprintf (fp, " keygrip ....: "); + print_keygrip (fp, info->grp2); + } + tty_fprintf (fp, "Authentication key:"); + print_shax_fpr (fp, info->fpr3len? info->fpr3:NULL, info->fpr3len); + if (info->fpr3len && info->fpr3time) + { + tty_fprintf (fp, " created ....: %s\n", + isotimestamp (info->fpr3time)); + tty_fprintf (fp, " keygrip ....: "); + print_keygrip (fp, info->grp3); + } + + /* 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 && !fpr_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"); */ } +/* Print all available information about the current card. */ static void -cmd_keyattr (void) +list_card (card_info_t info) { - /* key_attr (); */ + estream_t fp = NULL; + + tty_fprintf (fp, "Reader ...........: %s\n", + info->reader? info->reader : "[none]"); + 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; + default: break; + } } -static void -cmd_uif (int arg_number, char *arg_rest) + +/* The VERIFY command. */ +static gpg_error_t +cmd_verify (card_info_t info, char *argstr) { - if ( arg_number < 1 || arg_number > 3 ) - tty_printf ("usage: uif N [on|off|permanent]\n" - " 1 <= N <= 3\n"); - /* else */ - /* uif (arg_number, arg_rest); */ -} + gpg_error_t err; + if (!info) + return print_help ("verify [chvid]", 0); - -/* 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, cmdDEBUG, cmdVERIFY, - cmdNAME, cmdURL, cmdFETCH, cmdLOGIN, cmdLANG, cmdSALUT, cmdCAFPR, - cmdFORCESIG, cmdGENERATE, cmdPASSWD, cmdPRIVATEDO, cmdWRITECERT, - cmdREADCERT, cmdUNBLOCK, cmdFACTORYRESET, cmdKDFSETUP, - cmdKEYATTR, cmdUIF, - cmdINVCMD - }; + if (info->apptype == APP_TYPE_OPENPGP) + err = scd_checkpin (info->serialno); + else + err = scd_checkpin (argstr); -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 }, - { "debug" , cmdDEBUG , 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")}, - { "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")}, - { "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")}, - /* Note, that we do not announce these command yet. */ - { "privatedo", cmdPRIVATEDO, 0, NULL }, - { "readcert", cmdREADCERT, 0, NULL }, - { "writecert", cmdWRITECERT, 1, NULL }, - { NULL, cmdINVCMD, 0, NULL } -}; + if (err) + log_error ("verify failed: %s <%s>\n", + gpg_strerror (err), gpg_strsource (err)); + return err; +} -/* The main loop. */ -static void -interactive_loop (void) +/* Helper for cmd_name to qyery a part of name. */ +static char * +ask_one_name (const char *prompt) { - char *answer = NULL; /* The input line. */ - enum cmdids cmd = cmdNOP; /* The command. */ - int cmd_admin_only; /* The command is an admin only command. */ - int arg_number; /* The first argument as a number. */ - char *arg_string = ""; /* The first argument as a string. */ - char *arg_rest = ""; /* The remaining arguments. */ - int redisplay = 1; /* Whether to redisplay the main info. */ - int allow_admin = 0; /* Whether admin commands are allowed. */ - char serialnobuf[50]; - char *p; + char *name; int i; for (;;) { - - tty_printf ("\n"); - if (redisplay) + name = tty_get (prompt); + trim_spaces (name); + tty_kill_prompt (); + if (!*name || *name == CONTROL_D) { - print_card_status (serialnobuf, DIM (serialnobuf)); - tty_printf("\n"); - redisplay = 0; - } + 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); + } +} - do - { - xfree (answer); - tty_enable_completion (command_completion); - answer = tty_get (_("gpg/card> ")); - tty_kill_prompt(); - tty_disable_completion (); - trim_spaces(answer); - } - while ( *answer == '#' ); - arg_number = 0; - cmd_admin_only = 0; - if (!*answer) - cmd = cmdLIST; /* We default to the list command */ - else if (*answer == CONTROL_D) - cmd = cmdQUIT; - else - { - if ((p=strchr (answer,' '))) - { - *p++ = 0; - trim_spaces (answer); - trim_spaces (p); - arg_number = atoi (p); - arg_string = p; - arg_rest = p; - while (digitp (arg_rest)) - arg_rest++; - while (spacep (arg_rest)) - arg_rest++; - } +/* 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; - for (i=0; cmds[i].name; i++ ) - if (!ascii_strcasecmp (answer, cmds[i].name )) - break; + 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); - cmd = cmds[i].id; - cmd_admin_only = cmds[i].admin_only; - } + if (info->apptype != APP_TYPE_OPENPGP) + { + log_info ("Note: This is an OpenPGP only command.\n"); + return gpg_error (GPG_ERR_NOT_SUPPORTED); + } - if (!allow_admin && cmd_admin_only) - { - tty_printf ("\n"); - tty_printf (_("Admin-only command\n")); - continue; + 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); } - switch (cmd) + isoname = xstrconcat (surname, "<<", givenname, NULL); + xfree (surname); + xfree (givenname); + for (p=isoname; *p; p++) + if (*p == ' ') + *p = '<'; + + if (strlen (isoname) > 39 ) { - case cmdNOP: - break; + log_info (_("Error: Combined name too long " + "(limit is %d characters).\n"), 39); + xfree (isoname); + goto again; + } + } - case cmdQUIT: - goto leave; + err = scd_setattr ("DISP-NAME", isoname, strlen (isoname)); - case cmdHELP: - 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; + xfree (isoname); + return err; +} - case cmdADMIN: - if ( !strcmp (arg_string, "on") ) + +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; + + 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 (info->fpr1len) + { + /* 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; + int do_no; + char *data = NULL; + size_t datalen; + + if (!info) + return print_help + ("WRITECERT [--clear] 3 < FILE\n\n" + "Write a certificate for key 3. Unless --clear is given\n" + "the file argement is mandatory. The option --clear removes\n" + "the certificate from the card.", + 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 != 3) + { + err = gpg_error (GPG_ERR_INV_ARG); + goto leave; + } + + 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 ("OPENPGP.3", data, datalen); + + leave: + xfree (data); + return err; +} + + +static gpg_error_t +cmd_readcert (card_info_t info, char *argstr) +{ + gpg_error_t err; + int do_no; + void *data = NULL; + size_t datalen; + const char *fname; + + if (!info) + return print_help + ("READCERT 3 > FILE\n\n" + "Read the certificate for key 3 and store it in FILE.", + APP_TYPE_OPENPGP, 0); + + 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 != 3) + { + err = gpg_error (GPG_ERR_INV_ARG); + goto leave; + } + + if (*argstr == '>') /* Read it from a file */ + { + for (argstr++; spacep (argstr); argstr++) + ; + fname = argstr; + } + else + { + err = gpg_error (GPG_ERR_INV_ARG); + goto leave; + } + + err = scd_readcert ("OPENPGP.3", &data, &datalen); + if (err) + goto leave; + + err = put_data_to_file (fname, data, datalen); + + leave: + xfree (data); + 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. 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. */ +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; + } +} + + +static gpg_error_t +cmd_generate (card_info_t info) +{ + gpg_error_t err; + int forced_chv1 = -1; + int want_backup; + char *answer = NULL; + + if (!info) + return print_help + ("GENERATE\n\n" + "Menu to generate a new keys.", + 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.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; + + if ( (info->fpr1len && !fpr_is_zero (info->fpr1, info->fpr1len)) + || (info->fpr2len && !fpr_is_zero (info->fpr2, info->fpr2len)) + || (info->fpr3len && !fpr_is_zero (info->fpr3, info->fpr3len))) + { + 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); */ + err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); + + leave: + restore_forced_chv1 (&forced_chv1); + xfree (answer); + 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) +{ + gpg_error_t err; + char *answer = NULL; + + if (!info) + return print_help + ("PASSWD\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.", + 0); + + /* Convenience message because we did this in gpg --card-edit too. */ + if (info->apptype == APP_TYPE_OPENPGP) + log_info (_("OpenPGP card no. %s detected\n"), + info->dispserialno? info->dispserialno : info->serialno); + + if (!allow_admin) + { + err = scd_change_pin (1); + if (err) + goto leave; + 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 (1); + 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 (101); + 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 (3); + 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 (102); + 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 (info->apptype == APP_TYPE_OPENPGP) + log_info (_("OpenPGP card no. %s detected\n"), + info->dispserialno? info->dispserialno : info->serialno); + + if (info->apptype == APP_TYPE_OPENPGP && !info->is_v2) + log_error (_("This command is only available for version 2 cards\n")); + else if (info->apptype == APP_TYPE_OPENPGP && !info->chvretry[1]) + log_error (_("Reset Code not or not anymore available\n")); + else if (info->apptype == APP_TYPE_OPENPGP) + { + err = scd_change_pin (2); + if (!err) + log_info ("PIN changed.\n"); + } + else + log_info ("Unblocking not yet supported for '%s'\n", + app_type_string (info->apptype)); + + return err; +} + + +/* Direct sending of an hex encoded APDU with error printing. */ +static gpg_error_t +send_apdu (const char *hexapdu, const char *desc, unsigned int ignore) +{ + gpg_error_t err; + unsigned int sw; + + err = scd_apdu (hexapdu, &sw); + 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; +} + + +/* 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 i; + + + if (!info) + return print_help + ("FACTORY-RESET\n\n" + "Do a complete reset of an OpenPGP card. This deletes all\n" + "data and keys and resets the PINs to their default. This\n" + "mainly used by developers with scratch cards. Don't worry,\n" + "you need to confirm before the command proceeds.", + 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); + } + + /* 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 + * + * but tries to find out something about the card first. + */ + + 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 (!termstate) + { + log_info (_("OpenPGP card no. %s detected\n"), + info->dispserialno? info->dispserialno : info->serialno); + 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; + } + + 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); + if (err) + goto leave; + err = send_apdu ("undefined", "dummy select ", 0); + if (err) + goto leave; + + /* Select the OpenPGP application. */ + err = send_apdu ("00A4040006D27600012401", "SELECT AID", 0); + 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); + for (i=0; i < 4; i++) + send_apdu ("0020008320" + "40404040404040404040404040404040" + "40404040404040404040404040404040", "VERIFY", 0xffff); + + /* Send terminate datafile command. */ + err = send_apdu ("00e60000", "TERMINATE DF", 0x6985); + if (err) + goto leave; + } + + 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); + if (err) + goto leave; + + /* Finally we reset the card reader once more. */ + err = send_apdu (NULL, "RESET", 0); + 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) + { + 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; + } + + 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; +} + + + +/* 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, + 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")}, + { "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")}, + /* Note, that we do not announce these command yet. */ + { "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")}, + { NULL, cmdINVCMD, 0, NULL } +}; + + +/* The 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; + 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 modifed and must thus point to a buffer. */ + if (!argstr) + argstr = answer + strlen (answer); + + if (!(cmd == cmdNOP || cmd == cmdQUIT || cmd == cmdHELP)) + { + /* 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 + { + err = scd_apdu (NULL, NULL); + } + break; + + case cmdADMIN: + if ( !strcmp (argstr, "on") ) allow_admin = 1; - else if ( !strcmp (arg_string, "off") ) + else if ( !strcmp (argstr, "off") ) allow_admin = 0; - else if ( !strcmp (arg_string, "verify") ) + 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: cmd_verify (); redisplay = 1; break; - case cmdLIST: redisplay = 1; break; - case cmdNAME: cmd_name (); break; - case cmdURL: cmd_url (); break; - case cmdFETCH: cmd_fetch (); break; - case cmdLOGIN: cmd_login (arg_string); break; - case cmdLANG: cmd_lang (); break; - case cmdSALUT: cmd_salut (); break; - case cmdCAFPR: cmd_cafpr (arg_number); break; - case cmdPRIVATEDO: cmd_privatedo (arg_number, arg_string); break; - case cmdWRITECERT: cmd_writecert (arg_number, arg_rest); break; - case cmdREADCERT: cmd_readcert (arg_number, arg_rest); break; - case cmdFORCESIG: cmd_forcesig (); break; - case cmdGENERATE: cmd_generate (); break; - case cmdPASSWD: cmd_passwd (allow_admin); break; - case cmdUNBLOCK: cmd_unblock (allow_admin); break; - case cmdFACTORYRESET: cmd_factoryreset (); break; - case cmdKDFSETUP: cmd_kdfsetup (arg_string); break; - case cmdKEYATTR: cmd_keyattr (); break; - case cmdUIF: cmd_uif (arg_number, arg_rest); break; + case cmdVERIFY: + err = cmd_verify (info, argstr); + if (!err) + redisplay = 1; + 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); break; + case cmdPASSWD: err = cmd_passwd (info, allow_admin); 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 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. */ if (!start) return rl_completion_matches (text, command_generator); rl_attempted_completion_over = 1; return NULL; } #endif /*HAVE_LIBREADLINE*/