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*/