aboutsummaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/Makefile.am44
-rwxr-xr-xtools/addgnupghome2
-rw-r--r--tools/card-call-scd.c1550
-rw-r--r--tools/card-keys.c555
-rw-r--r--tools/card-misc.c113
-rw-r--r--tools/card-yubikey.c438
-rw-r--r--tools/gpg-card-w32info.rc51
-rw-r--r--tools/gpg-card.c3497
-rw-r--r--tools/gpg-card.h230
-rw-r--r--tools/gpg-check-pattern.c2
-rw-r--r--tools/gpg-connect-agent-w32info.rc2
-rw-r--r--tools/gpg-connect-agent.c6
-rw-r--r--tools/gpg-pair-tool.c2020
-rw-r--r--tools/gpg-wks-client.c322
-rw-r--r--tools/gpg-wks-server.c351
-rw-r--r--tools/gpg-wks.h8
-rw-r--r--tools/gpg-zip.in148
-rw-r--r--tools/gpgconf-comp.c28
-rw-r--r--tools/gpgconf.c23
-rw-r--r--tools/gpgconf.h28
-rw-r--r--tools/gpgtar-create.c8
-rw-r--r--tools/gpgtar-extract.c22
-rw-r--r--tools/gpgtar-list.c49
-rw-r--r--tools/gpgtar.c37
-rw-r--r--tools/gpgtar.h28
-rw-r--r--tools/mime-maker.c47
-rw-r--r--tools/mime-parser.c2
-rw-r--r--tools/no-libgcrypt.c2
-rw-r--r--tools/rfc822parse.c113
-rw-r--r--tools/rfc822parse.h2
-rw-r--r--tools/wks-util.c400
31 files changed, 9421 insertions, 707 deletions
diff --git a/tools/Makefile.am b/tools/Makefile.am
index 0c828a7bd..fb37c05e7 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -20,25 +20,23 @@ 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
-
+ ccidmon.c ChangeLog-2011 \
+ gpg-connect-agent-w32info.rc \
+ gpg-card-w32info.rc
AM_CPPFLAGS =
include $(top_srcdir)/am/cmacros.am
if HAVE_W32_SYSTEM
-resource_objs += gpg-connect-agent-w32info.o
+gpg_connect_agent_rc_objs = gpg-connect-agent-w32info.o
+gpg_card_tool_rc_objs = gpg-card-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 HAVE_USTAR
-# bin_SCRIPTS += gpg-zip
-noinst_SCRIPTS = gpg-zip
-endif
-
if BUILD_SYMCRYPTRUN
symcryptrun = symcryptrun
else
@@ -51,9 +49,9 @@ else
gpg_wks_server =
endif
-libexec_PROGRAMS = gpg-wks-client
+libexec_PROGRAMS = gpg-wks-client gpg-pair-tool
-bin_PROGRAMS = gpgconf gpg-connect-agent ${symcryptrun}
+bin_PROGRAMS = gpgconf gpg-connect-agent gpg-card ${symcryptrun}
if !HAVE_W32_SYSTEM
bin_PROGRAMS += watchgnupg gpgparsemail ${gpg_wks_server}
endif
@@ -123,7 +121,23 @@ gpg_connect_agent_LDADD = ../common/libgpgrl.a $(common_libs) \
$(LIBASSUAN_LIBS) $(LIBGCRYPT_LIBS) \
$(GPG_ERROR_LIBS) \
$(LIBREADLINE) $(LIBINTL) $(NETLIBS) $(LIBICONV) \
- $(resource_objs)
+ $(gpg_connect_agent_rc_objs)
+
+
+gpg_card_SOURCES = \
+ gpg-card.c \
+ gpg-card.h \
+ card-call-scd.c \
+ card-keys.c \
+ card-yubikey.c \
+ card-misc.c
+
+gpg_card_LDADD = \
+ ../common/libgpgrl.a $(common_libs) \
+ $(LIBASSUAN_LIBS) $(LIBGCRYPT_LIBS) \
+ $(GPG_ERROR_LIBS) \
+ $(LIBREADLINE) $(LIBINTL) $(NETLIBS) $(LIBICONV) \
+ $(gpg_card_tool_rc_objs)
if !DISABLE_REGEX
@@ -173,6 +187,14 @@ 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.
diff --git a/tools/addgnupghome b/tools/addgnupghome
index e13c3cd01..718b2226c 100755
--- a/tools/addgnupghome
+++ b/tools/addgnupghome
@@ -107,7 +107,7 @@ if [ ! -d /etc/skel/.gnupg ]; then
exit 1
fi
cd "/etc/skel/.gnupg" || (error "error cd-ing to \`/etc/skel/.gnupg'"; exit 1)
-filelist=$(find . \( -type f -or -type d \) -not -name '*~' -not -name . -print)
+filelist=$(find . \( -type f -o -type d \) '!' -name '*~' '!' -name . -print)
if ! umask 0077 ; then
diff --git a/tools/card-call-scd.c b/tools/card-call-scd.c
new file mode 100644
index 000000000..0a01bf5ca
--- /dev/null
+++ b/tools/card-call-scd.c
@@ -0,0 +1,1550 @@
+/* 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 <https://www.gnu.org/licenses/>.
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <time.h>
+#ifdef HAVE_LOCALE_H
+#include <locale.h>
+#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 "gpg-card.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->cardtype); info->cardtype = 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;
+ for (i=0; i < DIM(info->private_do); i++)
+ {
+ xfree (info->private_do[i]);
+ info->private_do[i] = NULL;
+ }
+ while (info->kinfo)
+ {
+ key_info_t kinfo = info->kinfo->next;
+ xfree (info->kinfo);
+ info->kinfo = kinfo;
+ }
+ info->chvusage[0] = info->chvusage[1] = 0;
+}
+
+
+/* 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 = "P15"; 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. If
+ * R_DATA is not NULL the data is without the status code is stored
+ * there. Caller must release it. */
+gpg_error_t
+scd_apdu (const char *hexapdu, unsigned int *r_sw,
+ unsigned char **r_data, size_t *r_datalen)
+{
+ gpg_error_t err;
+
+ if (r_data)
+ *r_data = NULL;
+ if (r_datalen)
+ *r_datalen = 0;
+
+ 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);
+ if (r_data && r_datalen)
+ {
+ *r_data = data;
+ *r_datalen = datalen - 2;
+ data = NULL;
+ }
+ }
+ 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;
+}
+
+
+
+/* For historical reasons OpenPGP cards simply use the numbers 1 to 3
+ * for the <keyref>. Other cards and future versions of
+ * scd/app-openpgp.c may print the full keyref; i.e. "OpenPGP.1"
+ * instead of "1". This is a helper to cope with that. */
+static const char *
+parse_keyref_helper (const char *string)
+{
+ if (*string == '1' && spacep (string+1))
+ return "OPENPGP.1";
+ else if (*string == '2' && spacep (string+1))
+ return "OPENPGP.2";
+ else if (*string == '3' && spacep (string+1))
+ return "OPENPGP.3";
+ else
+ return string;
+}
+
+
+/* Create a new key info object with KEYREF. All fields but the
+ * keyref are zeroed out. Never returns NULL. The created object is
+ * appended to the list at INFO. */
+static key_info_t
+create_kinfo (card_info_t info, const char *keyref)
+{
+ key_info_t kinfo, ki;
+
+ kinfo = xcalloc (1, sizeof *kinfo + strlen (keyref));
+ strcpy (kinfo->keyref, keyref);
+
+ if (!info->kinfo)
+ info->kinfo = kinfo;
+ else
+ {
+ for (ki=info->kinfo; ki->next; ki = ki->next)
+ ;
+ ki->next = kinfo;
+ }
+ return kinfo;
+}
+
+
+/* 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;
+ char *line_buffer = NULL; /* In case we need a copy. */
+ char *pline;
+ key_info_t kinfo;
+ const char *keyref;
+ 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))
+ {
+ /* The format of such a line is:
+ * KEY-FPR <keyref> <fingerprintinhex>
+ */
+ const char *fpr;
+
+ line_buffer = pline = xstrdup (line);
+
+ keyref = parse_keyref_helper (pline);
+ while (*pline && !spacep (pline))
+ pline++;
+ if (*pline)
+ *pline++ = 0; /* Terminate keyref. */
+ while (spacep (pline)) /* Skip to the fingerprint. */
+ pline++;
+ fpr = pline;
+
+ /* Check whether we already have an item for the keyref. */
+ kinfo = find_kinfo (parm, keyref);
+ if (!kinfo) /* No: new entry. */
+ kinfo = create_kinfo (parm, keyref);
+ else /* Existing entry - clear the fpr. */
+ memset (kinfo->fpr, 0, sizeof kinfo->fpr);
+
+ /* Set or update or the fingerprint. */
+ kinfo->fprlen = unhexify_fpr (fpr, kinfo->fpr, sizeof kinfo->fpr);
+ }
+ 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, "CARDTYPE", keywordlen))
+ {
+ xfree (parm->cardtype);
+ parm->cardtype = unescape_status_string (line);
+ }
+ else if (!memcmp (keyword, "DISP-SEX", keywordlen))
+ {
+ parm->disp_sex = *line == '1'? 1 : *line == '2' ? 2: 0;
+ }
+ else if (!memcmp (keyword, "KEY-TIME", keywordlen))
+ {
+ /* The format of such a line is:
+ * KEY-TIME <keyref> <timestamp>
+ */
+ const char *timestamp;
+
+ line_buffer = pline = xstrdup (line);
+
+ keyref = parse_keyref_helper (pline);
+ while (*pline && !spacep (pline))
+ pline++;
+ if (*pline)
+ *pline++ = 0; /* Terminate keyref. */
+ while (spacep (pline)) /* Skip to the timestamp. */
+ pline++;
+ timestamp = pline;
+
+ /* Check whether we already have an item for the keyref. */
+ kinfo = find_kinfo (parm, keyref);
+ if (!kinfo) /* No: new entry. */
+ kinfo = create_kinfo (parm, keyref);
+
+ kinfo->created = strtoul (timestamp, 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);
+ }
+ else if (!memcmp (keyword, "CHV-USAGE", keywordlen))
+ {
+ unsigned int byte1, byte2;
+
+ byte1 = byte2 = 0;
+ sscanf (line, "%x %x", &byte1, &byte2);
+ parm->chvusage[0] = byte1;
+ parm->chvusage[1] = byte2;
+ }
+ 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++;
+
+ if (!buf)
+ ;
+ else if (parm->apptype == APP_TYPE_OPENPGP)
+ {
+ 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->chvinfo[i] = atoi (p);
+ while (*p && !spacep (p))
+ p++;
+ while (spacep (p))
+ p++;
+ }
+ }
+ else if (parm->apptype == APP_TYPE_PIV)
+ {
+ for (i=0; *p && i < DIM (parm->chvinfo); i++)
+ {
+ parm->chvinfo[i] = atoi (p);
+ while (*p && !spacep (p))
+ p++;
+ while (spacep (p))
+ p++;
+ }
+ }
+
+ xfree (buf);
+ }
+ else if (!memcmp (keyword, "APPVERSION", keywordlen))
+ {
+ unsigned int val = 0;
+
+ sscanf (line, "%x", &val);
+ parm->appversion = val;
+ }
+ break;
+
+ case 11:
+ if (!memcmp (keyword, "SIG-COUNTER", keywordlen))
+ {
+ parm->sig_counter = strtoul (line, NULL, 0);
+ }
+ else if (!memcmp (keyword, "KEYPAIRINFO", keywordlen))
+ {
+ /* The format of such a line is:
+ * KEYPAIRINFO <hexgrip> <keyref> [usage]
+ */
+ char *hexgrp, *usage;
+
+ line_buffer = pline = xstrdup (line);
+
+ hexgrp = pline;
+ while (*pline && !spacep (pline))
+ pline++;
+ while (spacep (pline))
+ pline++;
+
+ keyref = pline;
+ while (*pline && !spacep (pline))
+ pline++;
+ if (*pline)
+ {
+ *pline++ = 0;
+ while (spacep (pline))
+ pline++;
+ usage = pline;
+ while (*pline && !spacep (pline))
+ pline++;
+ *pline = 0;
+ }
+ else
+ usage = "";
+
+ /* Check whether we already have an item for the keyref. */
+ kinfo = find_kinfo (parm, keyref);
+ if (!kinfo) /* New entry. */
+ kinfo = create_kinfo (parm, keyref);
+ else /* Existing entry - clear grip and usage */
+ {
+ memset (kinfo->grip, 0, sizeof kinfo->grip);
+ kinfo->usage = 0;
+ }
+
+ /* Set or update the grip. Note that due to the
+ * calloc/memset an erroneous too short grip will be nul
+ * padded on the right. */
+ unhexify_fpr (hexgrp, kinfo->grip, sizeof kinfo->grip);
+ /* Parse and set the usage. */
+ for (; *usage; usage++)
+ {
+ switch (*usage)
+ {
+ case 's': kinfo->usage |= GCRY_PK_USAGE_SIGN; break;
+ case 'c': kinfo->usage |= GCRY_PK_USAGE_CERT; break;
+ case 'a': kinfo->usage |= GCRY_PK_USAGE_AUTH; break;
+ case 'e': kinfo->usage |= GCRY_PK_USAGE_ENCR; break;
+ }
+ }
+ }
+ else if (!memcmp (keyword, "CARDVERSION", keywordlen))
+ {
+ unsigned int val = 0;
+
+ sscanf (line, "%x", &val);
+ parm->cardversion = val;
+ }
+ 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;
+ }
+
+ xfree (line_buffer);
+ 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)
+ {
+ info->initialized = 1;
+
+ 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);
+}
+
+
+
+/* Send a WRITEKEY command to the agent (so that the agent can fetch
+ * the key to write). KEYGRIP is the hexified keygrip of the source
+ * key which will be written to tye slot KEYREF. FORCE must be true
+ * to overwrite an existing key. */
+gpg_error_t
+scd_writekey (const char *keyref, int force, const char *keygrip)
+{
+ gpg_error_t err;
+ struct default_inq_parm_s parm;
+ char line[ASSUAN_LINELENGTH];
+
+ memset (&parm, 0, sizeof parm);
+
+ err = start_agent (0);
+ if (err)
+ return err;
+
+ /* Note: We don't send the s/n but "-" because gpg-agent has
+ * currently no use for it. */
+ /* FIXME: For OpenPGP we should provide the creation time. */
+ snprintf (line, sizeof line, "KEYTOCARD%s %s - %s",
+ force? " --force":"", keygrip, keyref);
+ err = assuan_transact (agent_ctx, line, NULL, NULL,
+ default_inq_cb, &parm, 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))
+ {
+ if (createtime)
+ *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 (const char *keyref, int force, const char *algo, 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 && *createtime)
+ epoch2isotime (tbuf, *createtime);
+ else
+ *tbuf = 0;
+
+ snprintf (line, sizeof line, "SCD GENKEY %s%s %s %s%s -- %s",
+ *tbuf? "--timestamp=":"", tbuf,
+ force? "--force":"",
+ algo? "--algo=":"",
+ algo? algo:"",
+ keyref);
+
+ 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;
+}
+
+
+
+/* Send a READKEY command to the SCdaemon. On success a new
+ * s-expression is stored at R_RESULT. */
+gpg_error_t
+scd_readkey (const char *keyrefstr, gcry_sexp_t *r_result)
+{
+ gpg_error_t err;
+ char line[ASSUAN_LINELENGTH];
+ membuf_t data;
+ unsigned char *buf;
+ size_t len, buflen;
+
+ *r_result = NULL;
+ err = start_agent (0);
+ if (err)
+ return err;
+
+ init_membuf (&data, 1024);
+ snprintf (line, DIM(line), "SCD READKEY %s", keyrefstr);
+ err = assuan_transact (agent_ctx, line,
+ put_membuf_cb, &data,
+ NULL, NULL,
+ NULL, NULL);
+ if (err)
+ {
+ xfree (get_membuf (&data, &len));
+ return err;
+ }
+ buf = get_membuf (&data, &buflen);
+ if (!buf)
+ return gpg_error_from_syserror ();
+
+ err = gcry_sexp_new (r_result, buf, buflen, 0);
+ xfree (buf);
+
+ return err;
+}
+
+
+
+/* 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.
+ */
+gpg_error_t
+scd_change_pin (const char *pinref, int reset_mode)
+{
+ 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 PASSWD%s %s",
+ reset_mode? " --reset":"", pinref);
+ 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-keys.c b/tools/card-keys.c
new file mode 100644
index 000000000..ad06f2ff7
--- /dev/null
+++ b/tools/card-keys.c
@@ -0,0 +1,555 @@
+/* card-keys.c - OpenPGP and CMS related functions for gpg-card
+ * Copyright (C) 2019 g10 Code GmbH
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuPG is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../common/util.h"
+#include "../common/i18n.h"
+#include "../common/ccparray.h"
+#include "../common/exectool.h"
+#include "../common/openpgpdefs.h"
+#include "gpg-card.h"
+
+
+/* It is quite common that all keys of an OpenPGP card belong to the
+ * the same OpenPGP keyblock. To avoid running several queries
+ * despite that we already got the information with the previous
+ * keyblock, we keep a small cache of of previous done queries. */
+static struct
+{
+ unsigned int lru;
+ keyblock_t keyblock;
+} keyblock_cache[5];
+
+
+
+/* Helper for release_keyblock. */
+static void
+do_release_keyblock (keyblock_t keyblock)
+{
+ pubkey_t pubkey;
+ userid_t uid;
+
+ while (keyblock)
+ {
+ keyblock_t keyblocknext = keyblock->next;
+ pubkey = keyblock->keys;
+ while (pubkey)
+ {
+ pubkey_t pubkeynext = pubkey->next;
+ xfree (pubkey);
+ pubkey = pubkeynext;
+ }
+ uid = keyblock->uids;
+ while (uid)
+ {
+ userid_t uidnext = uid->next;
+ xfree (uid->value);
+ xfree (uid);
+ uid = uidnext;
+ }
+ xfree (keyblock);
+ keyblock = keyblocknext;
+ }
+}
+
+
+/* Release a keyblock object. */
+void
+release_keyblock (keyblock_t keyblock)
+{
+ static unsigned int lru_counter;
+ unsigned int lru;
+ int i, lru_idx;
+
+ if (!keyblock)
+ return;
+
+ lru = (unsigned int)(-1);
+ lru_idx = 0;
+ for (i=0; i < DIM (keyblock_cache); i++)
+ {
+ if (!keyblock_cache[i].keyblock)
+ {
+ keyblock_cache[i].keyblock = keyblock;
+ keyblock_cache[i].lru = ++lru_counter;
+ goto leave;
+ }
+ if (keyblock_cache[i].lru < lru)
+ {
+ lru = keyblock_cache[i].lru;
+ lru_idx = i;
+ }
+ }
+
+ /* No free slot. Replace one. */
+ do_release_keyblock (keyblock_cache[lru_idx].keyblock);
+ keyblock_cache[lru_idx].keyblock = keyblock;
+ keyblock_cache[lru_idx].lru = ++lru_counter;
+
+ leave:
+ if (!lru_counter)
+ {
+ /* Wrapped around. We simply clear the entire cache. */
+ flush_keyblock_cache ();
+ }
+}
+
+
+/* Flush the enire keyblock cache. */
+void
+flush_keyblock_cache (void)
+{
+ int i;
+
+ for (i=0; i < DIM (keyblock_cache); i++)
+ {
+ do_release_keyblock (keyblock_cache[i].keyblock);
+ keyblock_cache[i].keyblock = NULL;
+ }
+}
+
+
+
+/* Object to communicate with the status_cb. */
+struct status_cb_s
+{
+ const char *pgm; /* Name of the program for debug purposes. */
+ int no_pubkey; /* Result flag. */
+};
+
+
+/* Status callback helper for the exec functions. */
+static void
+status_cb (void *opaque, const char *keyword, char *args)
+{
+ struct status_cb_s *c = opaque;
+ const char *s;
+
+ if (DBG_EXTPROG)
+ log_debug ("%s: status: %s %s\n", c->pgm, keyword, args);
+
+ if (!strcmp (keyword, "ERROR")
+ && (s=has_leading_keyword (args, "keylist.getkey"))
+ && gpg_err_code (atoi (s)) == GPG_ERR_NO_PUBKEY)
+ {
+ /* No public key was found. gpg terminates with an error in
+ * this case and we can't change that behaviour. Instead we
+ * detect this status and carry that error forward. */
+ c->no_pubkey = 1;
+ }
+
+}
+
+
+/* Helper for get_matching_keys to parse "pub" style records. */
+static gpg_error_t
+parse_key_record (char **fields, int nfields, pubkey_t *r_pubkey)
+{
+ pubkey_t pubkey;
+
+ (void)fields; /* Not yet used. */
+ (void)nfields;
+
+ pubkey = xtrycalloc (1, sizeof *pubkey);
+ if (!pubkey)
+ return gpg_error_from_syserror ();
+ *r_pubkey = pubkey;
+ return 0;
+}
+
+
+/* Run gpg or gpgsm to get a list of all keys matching the 20 byte
+ * KEYGRIP. PROTOCOL is one of or a combination of
+ * GNUPG_PROTOCOL_OPENPGP and GNUPG_PROTOCOL_CMS. On success a new
+ * keyblock is stored at R_KEYBLOCK; on error NULL is stored there. */
+gpg_error_t
+get_matching_keys (const unsigned char *keygrip, int protocol,
+ keyblock_t *r_keyblock)
+{
+ gpg_error_t err;
+ ccparray_t ccp;
+ const char **argv;
+ estream_t listing;
+ char hexgrip[1 + (2*KEYGRIP_LEN) + 1];
+ char *line = NULL;
+ size_t length_of_line = 0;
+ size_t maxlen;
+ ssize_t len;
+ char **fields = NULL;
+ int nfields;
+ int first_seen;
+ int i;
+ keyblock_t keyblock_head, *keyblock_tail, kb;
+ pubkey_t pubkey, pk;
+ size_t n;
+ struct status_cb_s status_cb_parm;
+
+ *r_keyblock = NULL;
+
+ keyblock_head = NULL;
+ keyblock_tail = &keyblock_head;
+ kb = NULL;
+
+ /* Shortcut to run a listing on both protocols. */
+ if ((protocol & GNUPG_PROTOCOL_OPENPGP) && (protocol & GNUPG_PROTOCOL_CMS))
+ {
+ err = get_matching_keys (keygrip, GNUPG_PROTOCOL_OPENPGP, &kb);
+ if (!err || gpg_err_code (err) == GPG_ERR_NO_PUBKEY)
+ {
+ if (!err)
+ {
+ *keyblock_tail = kb;
+ keyblock_tail = &kb->next;
+ kb = NULL;
+ }
+ err = get_matching_keys (keygrip, GNUPG_PROTOCOL_CMS, &kb);
+ if (!err)
+ {
+ *keyblock_tail = kb;
+ keyblock_tail = &kb->next;
+ kb = NULL;
+ }
+ else if (gpg_err_code (err) == GPG_ERR_NO_PUBKEY)
+ err = 0;
+ }
+ if (err)
+ release_keyblock (keyblock_head);
+ else
+ *r_keyblock = keyblock_head;
+ return err;
+ }
+
+ /* Check that we have only one protocol. */
+ if (protocol != GNUPG_PROTOCOL_OPENPGP && protocol != GNUPG_PROTOCOL_CMS)
+ return gpg_error (GPG_ERR_UNSUPPORTED_PROTOCOL);
+
+ /* Try to get it from our cache. */
+ for (i=0; i < DIM (keyblock_cache); i++)
+ for (kb = keyblock_cache[i].keyblock; kb; kb = kb->next)
+ if (kb->protocol == protocol)
+ for (pk = kb->keys; pk; pk = pk->next)
+ if (pk->grip_valid && !memcmp (pk->grip, keygrip, KEYGRIP_LEN))
+ {
+ *r_keyblock = keyblock_cache[i].keyblock;
+ keyblock_cache[i].keyblock = NULL;
+ return 0;
+ }
+
+ /* Open a memory stream. */
+ listing = es_fopenmem (0, "w+b");
+ if (!listing)
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
+ return err;
+ }
+
+ status_cb_parm.pgm = protocol == GNUPG_PROTOCOL_OPENPGP? "gpg":"gpgsm";
+ status_cb_parm.no_pubkey = 0;
+
+ hexgrip[0] = '&';
+ bin2hex (keygrip, KEYGRIP_LEN, hexgrip+1);
+
+ ccparray_init (&ccp, 0);
+
+ if (opt.verbose > 1 || DBG_EXTPROG)
+ ccparray_put (&ccp, "--verbose");
+ else
+ ccparray_put (&ccp, "--quiet");
+ ccparray_put (&ccp, "--no-options");
+ ccparray_put (&ccp, "--batch");
+ ccparray_put (&ccp, "--status-fd=2");
+ ccparray_put (&ccp, "--with-colons");
+ ccparray_put (&ccp, "--with-keygrip");
+ ccparray_put (&ccp, "--list-keys");
+ ccparray_put (&ccp, hexgrip);
+
+ ccparray_put (&ccp, NULL);
+ argv = ccparray_get (&ccp, NULL);
+ if (!argv)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ err = gnupg_exec_tool_stream (protocol == GNUPG_PROTOCOL_OPENPGP?
+ opt.gpg_program : opt.gpgsm_program,
+ argv, NULL, NULL, listing, status_cb,
+ &status_cb_parm);
+ if (err)
+ {
+ if (status_cb_parm.no_pubkey)
+ err = gpg_error (GPG_ERR_NO_PUBKEY);
+ else if (gpg_err_code (err) != GPG_ERR_GENERAL)
+ log_error ("key listing failed: %s\n", gpg_strerror (err));
+ goto leave;
+ }
+
+ es_rewind (listing);
+ first_seen = 0;
+ maxlen = 8192; /* Set limit large enough for all escaped UIDs. */
+ while ((len = es_read_line (listing, &line, &length_of_line, &maxlen)) > 0)
+ {
+ if (!maxlen)
+ {
+ log_error ("received line too long\n");
+ err = gpg_error (GPG_ERR_LINE_TOO_LONG);
+ goto leave;
+ }
+ /* Strip newline and carriage return, if present. */
+ while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r'))
+ line[--len] = '\0';
+
+ xfree (fields);
+ fields = strtokenize (line, ":");
+ if (!fields)
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("strtokenize failed: %s\n", gpg_strerror (err));
+ goto leave;
+ }
+ for (nfields = 0; fields[nfields]; nfields++)
+ ;
+ if (!nfields)
+ {
+ err = gpg_error (GPG_ERR_INV_ENGINE);
+ goto leave;
+ }
+
+ /* Skip over all records until we reach a pub or sec. */
+ if (!first_seen
+ && (!strcmp (fields[0], "pub") || !strcmp (fields[0], "sec")
+ || !strcmp (fields[0], "crt") || !strcmp (fields[0], "crs")))
+ first_seen = 1;
+ if (!first_seen)
+ continue;
+
+ if (!strcmp (fields[0], "pub") || !strcmp (fields[0], "sec")
+ || !strcmp (fields[0], "crt") || !strcmp (fields[0], "crs"))
+ {
+ if (kb) /* Finish the current keyblock. */
+ {
+ *keyblock_tail = kb;
+ keyblock_tail = &kb->next;
+ }
+ kb = xtrycalloc (1, sizeof *kb);
+ if (!kb)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ kb->protocol = protocol;
+ err = parse_key_record (fields, nfields, &pubkey);
+ if (err)
+ goto leave;
+ kb->keys = pubkey;
+ pubkey = NULL;
+ }
+ else if (!strcmp (fields[0], "sub") || !strcmp (fields[0], "ssb"))
+ {
+ log_assert (kb && kb->keys);
+ err = parse_key_record (fields, nfields, &pubkey);
+ if (err)
+ goto leave;
+ for (pk = kb->keys; pk->next; pk = pk->next)
+ ;
+ pk->next = pubkey;
+ pubkey = NULL;
+ }
+ else if (!strcmp (fields[0], "fpr") && nfields > 9)
+ {
+ log_assert (kb && kb->keys);
+ n = strlen (fields[9]);
+ if (n != 64 && n != 40 && n != 32)
+ {
+ log_debug ("bad length (%zu) in fpr record\n", n);
+ err = gpg_error (GPG_ERR_INV_ENGINE);
+ goto leave;
+ }
+ n /= 2;
+
+ for (pk = kb->keys; pk->next; pk = pk->next)
+ ;
+ if (pk->fprlen)
+ {
+ log_debug ("too many fpr records\n");
+ err = gpg_error (GPG_ERR_INV_ENGINE);
+ goto leave;
+ }
+ log_assert (n <= sizeof pk->fpr);
+ pk->fprlen = n;
+ if (hex2bin (fields[9], pk->fpr, n) < 0)
+ {
+ log_debug ("bad chars in fpr record\n");
+ err = gpg_error (GPG_ERR_INV_ENGINE);
+ goto leave;
+ }
+ }
+ else if (!strcmp (fields[0], "grp") && nfields > 9)
+ {
+ log_assert (kb && kb->keys);
+ n = strlen (fields[9]);
+ if (n != 2*KEYGRIP_LEN)
+ {
+ log_debug ("bad length (%zu) in grp record\n", n);
+ err = gpg_error (GPG_ERR_INV_ENGINE);
+ goto leave;
+ }
+ n /= 2;
+
+ for (pk = kb->keys; pk->next; pk = pk->next)
+ ;
+ if (pk->grip_valid)
+ {
+ log_debug ("too many grp records\n");
+ err = gpg_error (GPG_ERR_INV_ENGINE);
+ goto leave;
+ }
+ if (hex2bin (fields[9], pk->grip, KEYGRIP_LEN) < 0)
+ {
+ log_debug ("bad chars in fpr record\n");
+ err = gpg_error (GPG_ERR_INV_ENGINE);
+ goto leave;
+ }
+ pk->grip_valid = 1;
+ if (!memcmp (pk->grip, keygrip, KEYGRIP_LEN))
+ pk->requested = 1;
+ }
+ else if (!strcmp (fields[0], "uid") && nfields > 9)
+ {
+ userid_t uid, u;
+
+ uid = xtrycalloc (1, sizeof *uid);
+ if (!uid)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ uid->value = decode_c_string (fields[9]);
+ if (!uid->value)
+ {
+ err = gpg_error_from_syserror ();
+ xfree (uid);
+ goto leave;
+ }
+ if (!kb->uids)
+ kb->uids = uid;
+ else
+ {
+ for (u = kb->uids; u->next; u = u->next)
+ ;
+ u->next = uid;
+ }
+ }
+ }
+ if (len < 0 || es_ferror (listing))
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("error reading memory stream\n");
+ goto leave;
+ }
+
+ if (kb) /* Finish the current keyblock. */
+ {
+ *keyblock_tail = kb;
+ keyblock_tail = &kb->next;
+ kb = NULL;
+ }
+
+ if (!keyblock_head)
+ err = gpg_error (GPG_ERR_NO_PUBKEY);
+
+ leave:
+ if (err)
+ release_keyblock (keyblock_head);
+ else
+ *r_keyblock = keyblock_head;
+ xfree (kb);
+ xfree (fields);
+ es_free (line);
+ xfree (argv);
+ es_fclose (listing);
+ return err;
+}
+
+
+void
+dump_keyblock (keyblock_t keyblock)
+{
+ keyblock_t kb;
+ pubkey_t pubkey;
+ userid_t uid;
+
+ for (kb = keyblock; kb; kb = kb->next)
+ {
+ log_info ("%s key:\n",
+ kb->protocol == GNUPG_PROTOCOL_OPENPGP? "OpenPGP":"X.509");
+ for (pubkey = kb->keys; pubkey; pubkey = pubkey->next)
+ {
+ log_info (" grip: ");
+ if (pubkey->grip_valid)
+ log_printhex (pubkey->grip, KEYGRIP_LEN, NULL);
+ log_printf ("%s\n", pubkey->requested? " (*)":"");
+
+ log_info (" fpr: ");
+ log_printhex (pubkey->fpr, pubkey->fprlen, "");
+ }
+ for (uid = kb->uids; uid; uid = uid->next)
+ {
+ log_info (" uid: %s\n", uid->value);
+ }
+ }
+}
+
+
+
+gpg_error_t
+test_get_matching_keys (const char *hexgrip)
+{
+ gpg_error_t err;
+ unsigned char grip[KEYGRIP_LEN];
+ keyblock_t keyblock;
+
+ if (strlen (hexgrip) != 40)
+ {
+ log_error ("error: invalid keygrip\n");
+ return 0;
+ }
+ if (hex2bin (hexgrip, grip, sizeof grip) < 0)
+ {
+ log_error ("error: bad kegrip\n");
+ return 0;
+ }
+ err = get_matching_keys (grip,
+ (GNUPG_PROTOCOL_OPENPGP | GNUPG_PROTOCOL_CMS),
+ &keyblock);
+ if (err)
+ {
+ log_error ("get_matching_keys failed: %s\n", gpg_strerror (err));
+ return err;
+ }
+
+ dump_keyblock (keyblock);
+ release_keyblock (keyblock);
+ return 0;
+}
diff --git a/tools/card-misc.c b/tools/card-misc.c
new file mode 100644
index 000000000..bccdbda9d
--- /dev/null
+++ b/tools/card-misc.c
@@ -0,0 +1,113 @@
+/* card-misc.c - Helper functions for gpg-card
+ * Copyright (C) 2019 g10 Code GmbH
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuPG is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include "../common/util.h"
+#include "../common/i18n.h"
+#include "../common/openpgpdefs.h"
+#include "gpg-card.h"
+
+/* Return the key info object for the key KEYREF. If it is not found
+ * NULL is returned. */
+key_info_t
+find_kinfo (card_info_t info, const char *keyref)
+{
+ key_info_t kinfo;
+
+ for (kinfo = info->kinfo; kinfo; kinfo = kinfo->next)
+ if (!strcmp (kinfo->keyref, keyref))
+ return kinfo;
+ return NULL;
+}
+
+
+/* Convert STRING into a newly allocated buffer while translating the
+ * hex numbers. Blanks and colons are allowed to separate pairs of
+ * hex digits. Returns NULL on error or a newly malloced buffer and
+ * its length in LENGTH. */
+void *
+hex_to_buffer (const char *string, size_t *r_length)
+{
+ unsigned char *buffer;
+ const char *s;
+ size_t n;
+
+ buffer = xtrymalloc (strlen (string)+1);
+ if (!buffer)
+ return NULL;
+ for (s=string, n=0; *s; s++)
+ {
+ if (ascii_isspace (*s) || *s == ':')
+ continue;
+ if (hexdigitp (s) && hexdigitp (s+1))
+ {
+ buffer[n++] = xtoi_2 (s);
+ s++;
+ }
+ else
+ {
+ xfree (buffer);
+ gpg_err_set_errno (EINVAL);
+ return NULL;
+ }
+ }
+ *r_length = n;
+ return buffer;
+}
+
+
+/* Direct sending of an hex encoded APDU with error printing. This is
+ * a simple wrapper around scd_apdu. */
+gpg_error_t
+send_apdu (const char *hexapdu, const char *desc, unsigned int ignore,
+ unsigned char **r_data, size_t *r_datalen)
+{
+ gpg_error_t err;
+ unsigned int sw;
+
+ err = scd_apdu (hexapdu, &sw, r_data, r_datalen);
+ if (err)
+ log_error ("sending card command %s failed: %s\n", desc,
+ gpg_strerror (err));
+ else if (!hexapdu || !strcmp (hexapdu, "undefined"))
+ ;
+ else if (ignore == 0xffff)
+ ; /* Ignore all status words. */
+ else if (sw != 0x9000)
+ {
+ switch (sw)
+ {
+ case 0x6285: err = gpg_error (GPG_ERR_OBJ_TERM_STATE); break;
+ case 0x6982: err = gpg_error (GPG_ERR_BAD_PIN); break;
+ case 0x6985: err = gpg_error (GPG_ERR_USE_CONDITIONS); break;
+ default: err = gpg_error (GPG_ERR_CARD);
+ }
+ if (!(ignore && ignore == sw))
+ log_error ("card command %s failed: %s (0x%04x)\n", desc,
+ gpg_strerror (err), sw);
+ }
+ return err;
+}
diff --git a/tools/card-yubikey.c b/tools/card-yubikey.c
new file mode 100644
index 000000000..f9d130988
--- /dev/null
+++ b/tools/card-yubikey.c
@@ -0,0 +1,438 @@
+/* card-yubikey.c - Yubikey specific functions.
+ * Copyright (C) 2019 g10 Code GmbH
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuPG is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include "../common/util.h"
+#include "../common/i18n.h"
+#include "../common/tlv.h"
+#include "../common/ttyio.h"
+#include "gpg-card.h"
+
+
+/* Object to describe requested interface options. */
+struct iface_s {
+ unsigned int usb:1;
+ unsigned int nfc:1;
+};
+
+
+/* Bit flags as used by the fields in struct ykapps_s. */
+#define YKAPP_USB_SUPPORTED 0x01
+#define YKAPP_USB_ENABLED 0x02
+#define YKAPP_NFC_SUPPORTED 0x04
+#define YKAPP_NFC_ENABLED 0x08
+#define YKAPP_SELECTED 0x80 /* Selected by the command. */
+
+/* An object to describe the applications on a Yubikey. Each field
+ * has 8 bits to hold the above flag values. */
+struct ykapps_s {
+ unsigned int otp:8;
+ unsigned int u2f:8;
+ unsigned int opgp:8;
+ unsigned int piv:8;
+ unsigned int oath:8;
+ unsigned int fido2:8;
+};
+
+
+
+/* Helper to parse an unsigned integer config value consisting of bit
+ * flags. TAG select the config item and MASK is the mask ORed into
+ * the value for a set bit. The function modifies YK. */
+static gpg_error_t
+parse_ul_config_value (struct ykapps_s *yk,
+ const unsigned char *config, size_t configlen,
+ int tag, unsigned int mask)
+{
+ const unsigned char *s;
+ size_t n;
+ unsigned long ul = 0;
+ int i;
+
+ s = find_tlv (config, configlen, tag, &n);
+ if (s && n)
+ {
+ if (n > sizeof ul)
+ {
+ log_error ("too large integer in Yubikey config tag %02x detected\n",
+ tag);
+ if (opt.verbose)
+ log_printhex (config, configlen, "config:");
+ return gpg_error (GPG_ERR_CARD);
+ }
+ for (i=0; i < n; i++)
+ {
+ ul <<=8;
+ ul |= s[i];
+ }
+ if (ul & 0x01)
+ yk->otp |= mask;
+ if (ul & 0x02)
+ yk->u2f |= mask;
+ if (ul & 0x08)
+ yk->opgp |= mask;
+ if (ul & 0x10)
+ yk->piv |= mask;
+ if (ul & 0x20)
+ yk->oath |= mask;
+ if (ul & 0x200)
+ yk->fido2 |= mask;
+ }
+ return 0;
+}
+
+
+/* Create an unsigned integer config value for TAG from the data in YK
+ * and store it the provided 4 byte buffer RESULT. If ENABLE is true
+ * the respective APP_SELECTED bit in YK sets the corresponding bit
+ * flags, it is is false that bit flag is cleared. IF APP_SELECTED is
+ * not set the bit flag is not changed. */
+static void
+set_ul_config_value (struct ykapps_s *yk,
+ unsigned int bitflag, int tag, unsigned int enable,
+ unsigned char *result)
+{
+ unsigned long ul = 0;
+
+ /* First set the current values. */
+ if ((yk->otp & bitflag))
+ ul |= 0x01;
+ if ((yk->u2f & bitflag))
+ ul |= 0x02;
+ if ((yk->opgp & bitflag))
+ ul |= 0x08;
+ if ((yk->piv & bitflag))
+ ul |= 0x10;
+ if ((yk->oath & bitflag))
+ ul |= 0x20;
+ if ((yk->fido2 & bitflag))
+ ul |= 0x200;
+
+ /* Then enable or disable the bits according to the selection flag. */
+ if (enable)
+ {
+ if ((yk->otp & YKAPP_SELECTED))
+ ul |= 0x01;
+ if ((yk->u2f & YKAPP_SELECTED))
+ ul |= 0x02;
+ if ((yk->opgp & YKAPP_SELECTED))
+ ul |= 0x08;
+ if ((yk->piv & YKAPP_SELECTED))
+ ul |= 0x10;
+ if ((yk->oath & YKAPP_SELECTED))
+ ul |= 0x20;
+ if ((yk->fido2 & YKAPP_SELECTED))
+ ul |= 0x200;
+ }
+ else
+ {
+ if ((yk->otp & YKAPP_SELECTED))
+ ul &= ~0x01;
+ if ((yk->u2f & YKAPP_SELECTED))
+ ul &= ~0x02;
+ if ((yk->opgp & YKAPP_SELECTED))
+ ul &= ~0x08;
+ if ((yk->piv & YKAPP_SELECTED))
+ ul &= ~0x10;
+ if ((yk->oath & YKAPP_SELECTED))
+ ul &= ~0x20;
+ if ((yk->fido2 & YKAPP_SELECTED))
+ ul &= ~0x200;
+ }
+
+ /* Make sure that we do not disable the CCID transport. Without
+ * CCID we won't have any way to change the configuration again. We
+ * would instead need one of the other Yubikey tools to enable an
+ * application and thus its transport again. */
+ if (bitflag == YKAPP_USB_ENABLED && !(ul & (0x08|0x10|0x20)))
+ {
+ log_info ("Enabling PIV to have at least one CCID transport\n");
+ ul |= 0x10;
+ }
+
+ result[0] = tag;
+ result[1] = 2;
+ result[2] = ul >> 8;
+ result[3] = ul;
+}
+
+
+/* Print the info from YK. */
+static void
+yk_list (estream_t fp, struct ykapps_s *yk)
+{
+ if (opt.interactive)
+ tty_fprintf (fp, ("Application USB NFC\n"
+ "-----------------------\n"));
+ tty_fprintf (fp, "OTP %s %s\n",
+ (yk->otp & YKAPP_USB_SUPPORTED)?
+ (yk->otp & YKAPP_USB_ENABLED? "yes" : "no ") : "- ",
+ (yk->otp & YKAPP_NFC_SUPPORTED)?
+ (yk->otp & YKAPP_NFC_ENABLED? "yes" : "no ") : "- ");
+ tty_fprintf (fp, "U2F %s %s\n",
+ (yk->otp & YKAPP_USB_SUPPORTED)?
+ (yk->otp & YKAPP_USB_ENABLED? "yes" : "no ") : "- ",
+ (yk->otp & YKAPP_NFC_SUPPORTED)?
+ (yk->otp & YKAPP_NFC_ENABLED? "yes" : "no ") : "- ");
+ tty_fprintf (fp, "OPGP %s %s\n",
+ (yk->opgp & YKAPP_USB_SUPPORTED)?
+ (yk->opgp & YKAPP_USB_ENABLED? "yes" : "no ") : "- ",
+ (yk->opgp & YKAPP_NFC_SUPPORTED)?
+ (yk->opgp & YKAPP_NFC_ENABLED? "yes" : "no ") : "- ");
+ tty_fprintf (fp, "PIV %s %s\n",
+ (yk->piv & YKAPP_USB_SUPPORTED)?
+ (yk->piv & YKAPP_USB_ENABLED? "yes" : "no ") : "- ",
+ (yk->piv & YKAPP_NFC_SUPPORTED)?
+ (yk->piv & YKAPP_NFC_ENABLED? "yes" : "no ") : "- ");
+ tty_fprintf (fp, "OATH %s %s\n",
+ (yk->oath & YKAPP_USB_SUPPORTED)?
+ (yk->oath & YKAPP_USB_ENABLED? "yes" : "no ") : "- ",
+ (yk->oath & YKAPP_NFC_SUPPORTED)?
+ (yk->oath & YKAPP_NFC_ENABLED? "yes" : "no ") : "- ");
+ tty_fprintf (fp, "FIDO2 %s %s\n",
+ (yk->fido2 & YKAPP_USB_SUPPORTED)?
+ (yk->fido2 & YKAPP_USB_ENABLED? "yes" : "no ") : "- ",
+ (yk->fido2 & YKAPP_NFC_SUPPORTED)?
+ (yk->fido2 & YKAPP_NFC_ENABLED? "yes" : "no ") : "- ");
+}
+
+
+/* Enable disable the apps as marked in YK with flag YKAPP_SELECTED. */
+static gpg_error_t
+yk_enable_disable (struct ykapps_s *yk, struct iface_s *iface,
+ const unsigned char *config, size_t configlen, int enable)
+{
+ gpg_error_t err = 0;
+ unsigned char apdu[100];
+ unsigned int apdulen;
+ /* const unsigned char *s; */
+ /* size_t n; */
+ char *hexapdu = NULL;
+
+ apdulen = 0;
+ apdu[apdulen++] = 0x00;
+ apdu[apdulen++] = 0x1c; /* Write Config instruction. */
+ apdu[apdulen++] = 0x00;
+ apdu[apdulen++] = 0x00;
+ apdu[apdulen++] = 0x00; /* Lc will be fixed up later. */
+ apdu[apdulen++] = 0x00; /* Length of data will also be fixed up later. */
+
+ /* The ykman tool has no way to set NFC and USB flags in one go.
+ * Reasoning about the Yubikey's firmware it seems plausible that
+ * combining should work. Let's try it here if the user called for
+ * setting both interfaces. */
+ if (iface->nfc)
+ {
+ set_ul_config_value (yk, YKAPP_NFC_ENABLED, 0x0e, enable, apdu+apdulen);
+ apdulen += 4;
+ }
+ if (iface->usb)
+ {
+ set_ul_config_value (yk, YKAPP_USB_ENABLED, 0x03, enable, apdu+apdulen);
+ apdulen += 4;
+ /* Yubikey's ykman copies parts of the config data when writing
+ * the config for USB. Below is a commented example on how that
+ * can be done. */
+ (void)config;
+ (void)configlen;
+ /* Copy the device flags. */
+ /* s = find_tlv (config, configlen, 0x08, &n); */
+ /* if (s && n) */
+ /* { */
+ /* s -= 2; */
+ /* n += 2; */
+ /* if (apdulen + n > sizeof apdu) */
+ /* { */
+ /* err = gpg_error (GPG_ERR_BUFFER_TOO_SHORT); */
+ /* goto leave; */
+ /* } */
+ /* memcpy (apdu+apdulen, s, n); */
+ /* apdulen += n; */
+ /* } */
+ }
+ if (iface->nfc || iface->usb)
+ {
+ if (apdulen + 2 > sizeof apdu)
+ {
+ err = gpg_error (GPG_ERR_BUFFER_TOO_SHORT);
+ goto leave;
+ }
+ /* Disable the next two lines to let the card reboot. Not doing
+ * this is however more convenient for this tool because further
+ * commands don't end up with an error. It seems to be better
+ * that a "reset" command from gpg-card-tool is run at the
+ * user's discretion. */
+ /* apdu[apdulen++] = 0x0c; /\* Reboot tag *\/ */
+ /* apdu[apdulen++] = 0; /\* No data for reboot. *\/ */
+ /* Fixup the lngth bytes. */
+ apdu[4] = apdulen - 6 + 1;
+ apdu[5] = apdulen - 6;
+
+ hexapdu = bin2hex (apdu, apdulen, NULL);
+ if (!hexapdu)
+ err = gpg_error_from_syserror ();
+ else
+ err = send_apdu (hexapdu, "YK.write_config", 0, NULL, NULL);
+ }
+
+ leave:
+ xfree (hexapdu);
+ return err;
+}
+
+
+/* Implementation part of cmd_yubikey. ARGV is an array of size ARGc
+ * with the argumets given to the yubikey command. Note that ARGV has
+ * no terminating NULL so that ARGC must be considred. FP is the
+ * stream to output information. This function must only be called on
+ * Yubikeys. */
+gpg_error_t
+yubikey_commands (estream_t fp, int argc, char *argv[])
+{
+ gpg_error_t err;
+ enum {ykLIST, ykENABLE, ykDISABLE } cmd;
+ struct iface_s iface = {0,0};
+ struct ykapps_s ykapps = {0};
+ unsigned char *config = NULL;
+ size_t configlen;
+ int i;
+
+ if (!argc)
+ return gpg_error (GPG_ERR_SYNTAX);
+
+ /* Parse command. */
+ if (!ascii_strcasecmp (argv[0], "list"))
+ cmd = ykLIST;
+ else if (!ascii_strcasecmp (argv[0], "enable"))
+ cmd = ykENABLE;
+ else if (!ascii_strcasecmp (argv[0], "disable"))
+ cmd = ykDISABLE;
+ else
+ {
+ err = gpg_error (GPG_ERR_UNKNOWN_COMMAND);
+ goto leave;
+ }
+
+ /* Parse interface if needed. */
+ if (cmd == ykLIST)
+ iface.usb = iface.nfc = 1;
+ else if (argc < 2)
+ {
+ err = gpg_error (GPG_ERR_SYNTAX);
+ goto leave;
+ }
+ else if (!ascii_strcasecmp (argv[1], "usb"))
+ iface.usb = 1;
+ else if (!ascii_strcasecmp (argv[1], "nfc"))
+ iface.nfc = 1;
+ else if (!ascii_strcasecmp (argv[1], "all") || !strcmp (argv[1], "*"))
+ iface.usb = iface.nfc = 1;
+ else
+ {
+ err = gpg_error (GPG_ERR_SYNTAX);
+ goto leave;
+ }
+
+ /* Parse list of applications. */
+ for (i=2; i < argc; i++)
+ {
+ if (!ascii_strcasecmp (argv[i], "otp"))
+ ykapps.otp = 0x80;
+ else if (!ascii_strcasecmp (argv[i], "u2f"))
+ ykapps.u2f = 0x80;
+ else if (!ascii_strcasecmp (argv[i], "opgp")
+ ||!ascii_strcasecmp (argv[i], "openpgp"))
+ ykapps.opgp = 0x80;
+ else if (!ascii_strcasecmp (argv[i], "piv"))
+ ykapps.piv = 0x80;
+ else if (!ascii_strcasecmp (argv[i], "oath")
+ || !ascii_strcasecmp (argv[i], "oauth"))
+ ykapps.oath = 0x80;
+ else if (!ascii_strcasecmp (argv[i], "fido2"))
+ ykapps.fido2 = 0x80;
+ else if (!ascii_strcasecmp (argv[i], "all")|| !strcmp (argv[i], "*"))
+ {
+ ykapps.otp = ykapps.u2f = ykapps.opgp = ykapps.piv = ykapps.oath
+ = ykapps.fido2 = 0x80;
+ }
+ else
+ {
+ err = gpg_error (GPG_ERR_SYNTAX);
+ goto leave;
+ }
+ }
+
+ /* Select the Yubikey Manager application. */
+ err = send_apdu ("00A4040008a000000527471117", "Select.YK-Manager", 0,
+ NULL, NULL);
+ if (err)
+ goto leave;
+ /* Send the read config command. */
+ err = send_apdu ("001D000000", "YK.read_config", 0, &config, &configlen);
+ if (err)
+ goto leave;
+ if (!configlen || *config > configlen - 1)
+ {
+ /* The length byte is shorter than the actual length. */
+ log_error ("Yubikey returned improper config data\n");
+ log_printhex (config, configlen, "config:");
+ err = gpg_error (GPG_ERR_CARD);
+ goto leave;
+ }
+ if (configlen-1 > *config)
+ {
+ log_info ("Extra config data ignored\n");
+ log_printhex (config, configlen, "config:");
+ }
+ configlen = *config;
+
+ err = parse_ul_config_value (&ykapps, config+1, configlen,
+ 0x01, YKAPP_USB_SUPPORTED);
+ if (!err)
+ err = parse_ul_config_value (&ykapps, config+1, configlen,
+ 0x03, YKAPP_USB_ENABLED);
+ if (!err)
+ err = parse_ul_config_value (&ykapps, config+1, configlen,
+ 0x0d, YKAPP_NFC_SUPPORTED);
+ if (!err)
+ err = parse_ul_config_value (&ykapps, config+1, configlen,
+ 0x0e, YKAPP_NFC_ENABLED);
+ if (err)
+ goto leave;
+
+ switch (cmd)
+ {
+ case ykLIST: yk_list (fp, &ykapps); break;
+ case ykENABLE: err = yk_enable_disable (&ykapps, &iface,
+ config+1, configlen, 1); break;
+ case ykDISABLE: err = yk_enable_disable (&ykapps, &iface,
+ config+1, configlen, 0); break;
+ }
+
+ leave:
+ xfree (config);
+ return err;
+}
diff --git a/tools/gpg-card-w32info.rc b/tools/gpg-card-w32info.rc
new file mode 100644
index 000000000..b35ff4ce2
--- /dev/null
+++ b/tools/gpg-card-w32info.rc
@@ -0,0 +1,51 @@
+/* gpg-card-w32info.rc -*- c -*-
+ * Copyright (C) 2019 g10 Code GmbH
+ *
+ * This file is free software; as a special exception the author gives
+ * unlimited permission to copy and/or distribute it, with or without
+ * modifications, as long as this notice is preserved.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
+ * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+#include "afxres.h"
+#include "../common/w32info-rc.h"
+
+1 ICON "../common/gnupg.ico"
+
+1 VERSIONINFO
+ FILEVERSION W32INFO_VI_FILEVERSION
+ PRODUCTVERSION W32INFO_VI_PRODUCTVERSION
+ FILEFLAGSMASK 0x3fL
+#ifdef _DEBUG
+ FILEFLAGS 0x01L /* VS_FF_DEBUG (0x1)*/
+#else
+ FILEFLAGS 0x00L
+#endif
+ FILEOS 0x40004L /* VOS_NT (0x40000) | VOS__WINDOWS32 (0x4) */
+ FILETYPE 0x1L /* VFT_APP (0x1) */
+ FILESUBTYPE 0x0L /* VFT2_UNKNOWN */
+ BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0" /* US English (0409), Unicode (04b0) */
+ BEGIN
+ VALUE "FileDescription", L"GnuPG\x2019s card tool \
+to the agent\0"
+ VALUE "InternalName", "gpg-card\0"
+ VALUE "OriginalFilename", "gpg-card.exe\0"
+ VALUE "ProductName", W32INFO_PRODUCTNAME
+ VALUE "ProductVersion", W32INFO_PRODUCTVERSION
+ VALUE "CompanyName", W32INFO_COMPANYNAME
+ VALUE "FileVersion", W32INFO_FILEVERSION
+ VALUE "LegalCopyright", W32INFO_LEGALCOPYRIGHT
+ VALUE "Comments", W32INFO_COMMENTS
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 0x4b0
+ END
+ END
diff --git a/tools/gpg-card.c b/tools/gpg-card.c
new file mode 100644
index 000000000..e2d728dab
--- /dev/null
+++ b/tools/gpg-card.c
@@ -0,0 +1,3497 @@
+/* gpg-card.c - An interactive tool to work with cards.
+ * Copyright (C) 2019 g10 Code GmbH
+ *
+ * This file is part of GnuPG.
+ *
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://gnu.org/licenses/>.
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_LIBREADLINE
+# define GNUPG_LIBREADLINE_H_INCLUDED
+# include <readline/readline.h>
+#endif /*HAVE_LIBREADLINE*/
+
+#include "../common/util.h"
+#include "../common/status.h"
+#include "../common/i18n.h"
+#include "../common/init.h"
+#include "../common/sysutils.h"
+#include "../common/asshelp.h"
+#include "../common/userids.h"
+#include "../common/ccparray.h"
+#include "../common/exectool.h"
+#include "../common/ttyio.h"
+#include "../common/server-help.h"
+#include "../common/openpgpdefs.h"
+
+#include "gpg-card.h"
+
+
+#define CONTROL_D ('D' - 'A' + 1)
+
+/* Constants to identify the commands and options. */
+enum opt_values
+ {
+ aNull = 0,
+
+ oQuiet = 'q',
+ oVerbose = 'v',
+
+ oDebug = 500,
+
+ oGpgProgram,
+ oGpgsmProgram,
+ oStatusFD,
+ oWithColons,
+ oNoAutostart,
+ oAgentProgram,
+
+ oDisplay,
+ oTTYname,
+ oTTYtype,
+ oXauthority,
+ oLCctype,
+ oLCmessages,
+
+ oDummy
+ };
+
+
+/* The list of commands and options. */
+static ARGPARSE_OPTS opts[] = {
+ ARGPARSE_group (301, ("@\nOptions:\n ")),
+
+ ARGPARSE_s_n (oVerbose, "verbose", ("verbose")),
+ ARGPARSE_s_n (oQuiet, "quiet", ("be somewhat more quiet")),
+ ARGPARSE_s_s (oDebug, "debug", "@"),
+ ARGPARSE_s_s (oGpgProgram, "gpg", "@"),
+ ARGPARSE_s_s (oGpgsmProgram, "gpgsm", "@"),
+ ARGPARSE_s_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")),
+ ARGPARSE_s_n (oWithColons, "with-colons", "@"),
+ ARGPARSE_s_n (oNoAutostart, "no-autostart", "@"),
+ ARGPARSE_s_s (oAgentProgram, "agent-program", "@"),
+ ARGPARSE_s_s (oDisplay, "display", "@"),
+ ARGPARSE_s_s (oTTYname, "ttyname", "@"),
+ ARGPARSE_s_s (oTTYtype, "ttytype", "@"),
+ ARGPARSE_s_s (oXauthority, "xauthority", "@"),
+ ARGPARSE_s_s (oLCctype, "lc-ctype", "@"),
+ ARGPARSE_s_s (oLCmessages, "lc-messages","@"),
+
+ ARGPARSE_end ()
+};
+
+/* The list of supported debug flags. */
+static struct debug_flags_s debug_flags [] =
+ {
+ { DBG_IPC_VALUE , "ipc" },
+ { DBG_EXTPROG_VALUE, "extprog" },
+ { 0, NULL }
+ };
+
+
+/* An object to create lists of labels and keyrefs. */
+struct keyinfolabel_s
+{
+ const char *label;
+ const char *keyref;
+};
+typedef struct keyinfolabel_s *keyinfolabel_t;
+
+
+/* Limit of size of data we read from a file for certain commands. */
+#define MAX_GET_DATA_FROM_FILE 16384
+
+/* Constants for OpenPGP cards. */
+#define OPENPGP_USER_PIN_DEFAULT "123456"
+#define OPENPGP_ADMIN_PIN_DEFAULT "12345678"
+#define OPENPGP_KDF_DATA_LENGTH_MIN 90
+#define OPENPGP_KDF_DATA_LENGTH_MAX 110
+
+
+
+
+/* Local prototypes. */
+static gpg_error_t dispatch_command (card_info_t info, const char *command);
+static void interactive_loop (void);
+#ifdef HAVE_LIBREADLINE
+static char **command_completion (const char *text, int start, int end);
+#endif /*HAVE_LIBREADLINE*/
+
+
+
+/* Print usage information and provide strings for help. */
+static const char *
+my_strusage( int level )
+{
+ const char *p;
+
+ switch (level)
+ {
+ case 11: p = "gpg-card"; break;
+ case 12: p = "@GNUPG@"; break;
+ case 13: p = VERSION; break;
+ case 17: p = PRINTABLE_OS_NAME; break;
+ case 19: p = ("Please report bugs to <@EMAIL@>.\n"); break;
+
+ case 1:
+ case 40:
+ p = ("Usage: gpg-card"
+ " [options] [{[--] command [args]}] (-h for help)");
+ break;
+ case 41:
+ p = ("Syntax: gpg-card"
+ " [options] [command [args] {-- command [args]}]\n\n"
+ "Tool to manage cards and tokens. With a command an interactive\n"
+ "mode is used. Use command \"help\" to list all commands.");
+ break;
+
+ default: p = NULL; break;
+ }
+ return p;
+}
+
+
+static void
+set_opt_session_env (const char *name, const char *value)
+{
+ gpg_error_t err;
+
+ err = session_env_setenv (opt.session_env, name, value);
+ if (err)
+ log_fatal ("error setting session environment: %s\n",
+ gpg_strerror (err));
+}
+
+
+
+/* Command line parsing. */
+static void
+parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts)
+{
+ while (optfile_parse (NULL, NULL, NULL, pargs, popts))
+ {
+ switch (pargs->r_opt)
+ {
+ case oQuiet: opt.quiet = 1; break;
+ case oVerbose: opt.verbose++; break;
+ case oDebug:
+ if (parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags))
+ {
+ pargs->r_opt = ARGPARSE_INVALID_ARG;
+ pargs->err = ARGPARSE_PRINT_ERROR;
+ }
+ break;
+
+ case oGpgProgram: opt.gpg_program = pargs->r.ret_str; break;
+ case oGpgsmProgram: opt.gpgsm_program = pargs->r.ret_str; break;
+ case oAgentProgram: opt.agent_program = pargs->r.ret_str; break;
+
+ case oStatusFD:
+ gnupg_set_status_fd (translate_sys2libc_fd_int (pargs->r.ret_int, 1));
+ break;
+
+ case oWithColons: opt.with_colons = 1; break;
+ case oNoAutostart: opt.autostart = 0; break;
+
+ case oDisplay: set_opt_session_env ("DISPLAY", pargs->r.ret_str); break;
+ case oTTYname: set_opt_session_env ("GPG_TTY", pargs->r.ret_str); break;
+ case oTTYtype: set_opt_session_env ("TERM", pargs->r.ret_str); break;
+ case oXauthority: set_opt_session_env ("XAUTHORITY",
+ pargs->r.ret_str); break;
+ case oLCctype: opt.lc_ctype = pargs->r.ret_str; break;
+ case oLCmessages: opt.lc_messages = pargs->r.ret_str; break;
+
+ default: pargs->err = 2; break;
+ }
+ }
+}
+
+
+
+/* gpg-card main. */
+int
+main (int argc, char **argv)
+{
+ gpg_error_t err;
+ ARGPARSE_ARGS pargs;
+ char **command_list = NULL;
+ int cmdidx;
+ char *command;
+
+ gnupg_reopen_std ("gpg-card");
+ set_strusage (my_strusage);
+ gnupg_rl_initialize ();
+ log_set_prefix ("gpg-card", GPGRT_LOG_WITH_PREFIX);
+
+ /* Make sure that our subsystems are ready. */
+ i18n_init();
+ init_common_subsystems (&argc, &argv);
+
+ assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
+ setup_libassuan_logging (&opt.debug, NULL);
+
+ /* Setup default options. */
+ opt.autostart = 1;
+ opt.session_env = session_env_new ();
+ if (!opt.session_env)
+ log_fatal ("error allocating session environment block: %s\n",
+ gpg_strerror (gpg_error_from_syserror ()));
+
+
+ /* Parse the command line. */
+ pargs.argc = &argc;
+ pargs.argv = &argv;
+ pargs.flags = ARGPARSE_FLAG_KEEP;
+ parse_arguments (&pargs, opts);
+
+ if (log_get_errorcount (0))
+ exit (2);
+
+ /* Set defaults for non given options. */
+ if (!opt.gpg_program)
+ opt.gpg_program = gnupg_module_name (GNUPG_MODULE_NAME_GPG);
+ if (!opt.gpgsm_program)
+ opt.gpgsm_program = gnupg_module_name (GNUPG_MODULE_NAME_GPGSM);
+
+ /* Now build the list of commands. We guess the size of the array
+ * by assuming each item is a complete command. Obviously this will
+ * be rarely the case, but it is less code to allocate a possible
+ * too large array. */
+ command_list = xcalloc (argc+1, sizeof *command_list);
+ cmdidx = 0;
+ command = NULL;
+ while (argc)
+ {
+ for ( ; argc && strcmp (*argv, "--"); argc--, argv++)
+ {
+ if (!command)
+ command = xstrdup (*argv);
+ else
+ {
+ char *tmp = xstrconcat (command, " ", *argv, NULL);
+ xfree (command);
+ command = tmp;
+ }
+ }
+ if (argc)
+ { /* Skip the double dash. */
+ argc--;
+ argv++;
+ }
+ if (command)
+ {
+ command_list[cmdidx++] = command;
+ command = NULL;
+ }
+ }
+ opt.interactive = !cmdidx;
+
+ if (opt.interactive)
+ {
+ interactive_loop ();
+ err = 0;
+ }
+ else
+ {
+ struct card_info_s info_buffer = { 0 };
+ card_info_t info = &info_buffer;
+
+ err = 0;
+ for (cmdidx=0; (command = command_list[cmdidx]); cmdidx++)
+ {
+ err = dispatch_command (info, command);
+ if (err)
+ break;
+ }
+ if (gpg_err_code (err) == GPG_ERR_EOF)
+ err = 0; /* This was a "quit". */
+ else if (command && !opt.quiet)
+ log_info ("stopped at command '%s'\n", command);
+ }
+
+ flush_keyblock_cache ();
+ if (command_list)
+ {
+ for (cmdidx=0; command_list[cmdidx]; cmdidx++)
+ xfree (command_list[cmdidx]);
+ xfree (command_list);
+ }
+ if (err)
+ gnupg_status_printf (STATUS_FAILURE, "- %u", err);
+ else if (log_get_errorcount (0))
+ gnupg_status_printf (STATUS_FAILURE, "- %u", GPG_ERR_GENERAL);
+ else
+ gnupg_status_printf (STATUS_SUCCESS, NULL);
+ return log_get_errorcount (0)? 1:0;
+}
+
+
+/* Read data from file FNAME up to MAX_GET_DATA_FROM_FILE characters.
+ * On error return an error code and stores NULL at R_BUFFER; on
+ * success returns 0 and stores the number of bytes read at R_BUFLEN
+ * and the address of a newly allocated buffer at R_BUFFER. A
+ * complementary nul byte is always appended to the data but not
+ * counted; this allows to pass NULL for R-BUFFER and consider the
+ * returned data as a string. */
+static gpg_error_t
+get_data_from_file (const char *fname, char **r_buffer, size_t *r_buflen)
+{
+ gpg_error_t err;
+ estream_t fp;
+ char *data;
+ int n;
+
+ *r_buffer = NULL;
+ if (r_buflen)
+ *r_buflen = 0;
+
+ fp = es_fopen (fname, "rb");
+ if (!fp)
+ {
+ err = gpg_error_from_syserror ();
+ log_error (_("can't open '%s': %s\n"), fname, gpg_strerror (err));
+ return err;
+ }
+
+ data = xtrymalloc (MAX_GET_DATA_FROM_FILE);
+ if (!data)
+ {
+ err = gpg_error_from_syserror ();
+ log_error (_("error allocating enough memory: %s\n"), gpg_strerror (err));
+ es_fclose (fp);
+ return err;
+ }
+
+ n = es_fread (data, 1, MAX_GET_DATA_FROM_FILE - 1, fp);
+ es_fclose (fp);
+ if (n < 0)
+ {
+ err = gpg_error_from_syserror ();
+ tty_printf (_("error reading '%s': %s\n"), fname, gpg_strerror (err));
+ xfree (data);
+ return err;
+ }
+ data[n] = 0;
+
+ *r_buffer = data;
+ if (r_buflen)
+ *r_buflen = n;
+ return 0;
+}
+
+
+/* Write LENGTH bytes from BUFFER to file FNAME. Return 0 on
+ * success. */
+static gpg_error_t
+put_data_to_file (const char *fname, const void *buffer, size_t length)
+{
+ gpg_error_t err;
+ estream_t fp;
+
+ fp = es_fopen (fname, "wb");
+ if (!fp)
+ {
+ err = gpg_error_from_syserror ();
+ log_error (_("can't create '%s': %s\n"), fname, gpg_strerror (err));
+ return err;
+ }
+
+ if (length && es_fwrite (buffer, length, 1, fp) != 1)
+ {
+ err = gpg_error_from_syserror ();
+ log_error (_("error writing '%s': %s\n"), fname, gpg_strerror (err));
+ es_fclose (fp);
+ return err;
+ }
+ if (es_fclose (fp))
+ {
+ err = gpg_error_from_syserror ();
+ log_error (_("error writing '%s': %s\n"), fname, gpg_strerror (err));
+ return err;
+ }
+ return 0;
+}
+
+
+
+/* Simply prints TEXT to the output. Returns 0 as a convenience.
+ * This is a separate fucntion so that it can be extended to run
+ * less(1) or so. The extra arguments are int values terminated by a
+ * 0 to indicate card application types supported with this command.
+ * If none are given (just teh final 0), this is a general
+ * command. */
+static gpg_error_t
+print_help (const char *text, ...)
+{
+ estream_t fp;
+ va_list arg_ptr;
+ int value;
+ int any = 0;
+
+ fp = opt.interactive? NULL : es_stdout;
+ tty_fprintf (fp, "%s\n", text);
+
+ va_start (arg_ptr, text);
+ while ((value = va_arg (arg_ptr, int)))
+ {
+ if (!any)
+ tty_fprintf (fp, "[Supported by: ");
+ tty_fprintf (fp, "%s%s", any?", ":"", app_type_string (value));
+ any = 1;
+ }
+ if (any)
+ tty_fprintf (fp, "]\n");
+
+ va_end (arg_ptr);
+ return 0;
+}
+
+
+/* Return the OpenPGP card manufacturer name. */
+static const char *
+get_manufacturer (unsigned int no)
+{
+ /* Note: Make sure that there is no colon or linefeed in the string. */
+ switch (no)
+ {
+ case 0x0001: return "PPC Card Systems";
+ case 0x0002: return "Prism";
+ case 0x0003: return "OpenFortress";
+ case 0x0004: return "Wewid";
+ case 0x0005: return "ZeitControl";
+ case 0x0006: return "Yubico";
+ case 0x0007: return "OpenKMS";
+ case 0x0008: return "LogoEmail";
+ case 0x0009: return "Fidesmo";
+ case 0x000A: return "Dangerous Things";
+
+ case 0x002A: return "Magrathea";
+ case 0x0042: return "GnuPG e.V.";
+
+ case 0x1337: return "Warsaw Hackerspace";
+ case 0x2342: return "warpzone"; /* hackerspace Muenster. */
+ case 0x4354: return "Confidential Technologies"; /* cotech.de */
+ case 0x63AF: return "Trustica";
+ case 0xBD0E: return "Paranoidlabs";
+ case 0xF517: return "FSIJ";
+
+ /* 0x0000 and 0xFFFF are defined as test cards per spec,
+ * 0xFF00 to 0xFFFE are assigned for use with randomly created
+ * serial numbers. */
+ case 0x0000:
+ case 0xffff: return "test card";
+ default: return (no & 0xff00) == 0xff00? "unmanaged S/N range":"unknown";
+ }
+}
+
+/* Print an (OpenPGP) fingerprint. */
+static void
+print_shax_fpr (estream_t fp, const unsigned char *fpr, unsigned int fprlen)
+{
+ int i;
+
+ if (fpr)
+ {
+ /* FIXME: Fix formatting for FPRLEN != 20 */
+ for (i=0; i < fprlen ; i+=2, fpr += 2 )
+ {
+ if (i == 10 )
+ tty_fprintf (fp, " ");
+ tty_fprintf (fp, " %02X%02X", *fpr, fpr[1]);
+ }
+ }
+ else
+ tty_fprintf (fp, " [none]");
+ tty_fprintf (fp, "\n");
+}
+
+/* Print the keygrip GRP. */
+static void
+print_keygrip (estream_t fp, const unsigned char *grp)
+{
+ int i;
+
+ for (i=0; i < 20 ; i++, grp++)
+ tty_fprintf (fp, "%02X", *grp);
+ tty_fprintf (fp, "\n");
+}
+
+
+/* Print a string but avoid printing control characters. */
+static void
+print_string (estream_t fp, const char *text, const char *name)
+{
+ tty_fprintf (fp, "%s", text);
+
+ /* FIXME: tty_printf_utf8_string2 eats everything after and
+ including an @ - e.g. when printing an url. */
+ if (name && *name)
+ {
+ if (fp)
+ print_utf8_buffer2 (fp, name, strlen (name), '\n');
+ else
+ tty_print_utf8_string2 (NULL, name, strlen (name), 0);
+ }
+ else
+ tty_fprintf (fp, _("[not set]"));
+ tty_fprintf (fp, "\n");
+}
+
+
+/* Print an ISO formatted name or "[not set]". */
+static void
+print_isoname (estream_t fp, const char *name)
+{
+ if (name && *name)
+ {
+ char *p, *given, *buf;
+
+ buf = xstrdup (name);
+ given = strstr (buf, "<<");
+ for (p=buf; *p; p++)
+ if (*p == '<')
+ *p = ' ';
+ if (given && given[2])
+ {
+ *given = 0;
+ given += 2;
+ if (fp)
+ print_utf8_buffer2 (fp, given, strlen (given), '\n');
+ else
+ tty_print_utf8_string2 (NULL, given, strlen (given), 0);
+
+ if (*buf)
+ tty_fprintf (fp, " ");
+ }
+
+ if (fp)
+ print_utf8_buffer2 (fp, buf, strlen (buf), '\n');
+ else
+ tty_print_utf8_string2 (NULL, buf, strlen (buf), 0);
+
+ xfree (buf);
+ }
+ else
+ {
+ tty_fprintf (fp, _("[not set]"));
+ }
+
+ tty_fprintf (fp, "\n");
+}
+
+
+/* Return true if the buffer MEM of length memlen consists only of zeroes. */
+static int
+mem_is_zero (const char *mem, unsigned int memlen)
+{
+ int i;
+
+ for (i=0; i < memlen && !mem[i]; i++)
+ ;
+ return (i == memlen);
+}
+
+
+
+/* Helper to list a single keyref. LABEL_KEYREF is a fallback key
+ * reference if no info is available; it may be NULL. */
+static void
+list_one_kinfo (key_info_t firstkinfo, key_info_t kinfo,
+ const char *label_keyref, estream_t fp)
+{
+ gpg_error_t err;
+ keyblock_t keyblock = NULL;
+ keyblock_t kb;
+ pubkey_t pubkey;
+ userid_t uid;
+ key_info_t ki;
+ const char *s;
+ gcry_sexp_t s_pkey;
+ int any;
+
+ if (firstkinfo && kinfo)
+ {
+ tty_fprintf (fp, " ");
+ if (mem_is_zero (kinfo->grip, sizeof kinfo->grip))
+ {
+ tty_fprintf (fp, "[none]\n");
+ tty_fprintf (fp, " keyref .....: %s\n", kinfo->keyref);
+ goto leave;
+ }
+
+ print_keygrip (fp, kinfo->grip);
+ tty_fprintf (fp, " keyref .....: %s", kinfo->keyref);
+ if (kinfo->usage)
+ {
+ any = 0;
+ tty_fprintf (fp, " (");
+ if ((kinfo->usage & GCRY_PK_USAGE_SIGN))
+ { tty_fprintf (fp, "sign"); any=1; }
+ if ((kinfo->usage & GCRY_PK_USAGE_CERT))
+ { tty_fprintf (fp, "%scert", any?",":""); any=1; }
+ if ((kinfo->usage & GCRY_PK_USAGE_AUTH))
+ { tty_fprintf (fp, "%sauth", any?",":""); any=1; }
+ if ((kinfo->usage & GCRY_PK_USAGE_ENCR))
+ { tty_fprintf (fp, "%sencr", any?",":""); any=1; }
+ tty_fprintf (fp, ")");
+ }
+ tty_fprintf (fp, "\n");
+
+ if (!scd_readkey (kinfo->keyref, &s_pkey))
+ {
+ char *tmp = pubkey_algo_string (s_pkey);
+ tty_fprintf (fp, " algorithm ..: %s\n", tmp);
+ xfree (tmp);
+ gcry_sexp_release (s_pkey);
+ s_pkey = NULL;
+ }
+
+ if (kinfo->fprlen && kinfo->created)
+ {
+ tty_fprintf (fp, " fingerprint :");
+ print_shax_fpr (fp, kinfo->fpr, kinfo->fprlen);
+ tty_fprintf (fp, " created ....: %s\n",
+ isotimestamp (kinfo->created));
+ }
+ err = get_matching_keys (kinfo->grip,
+ (GNUPG_PROTOCOL_OPENPGP | GNUPG_PROTOCOL_CMS),
+ &keyblock);
+ if (err)
+ {
+ if (gpg_err_code (err) != GPG_ERR_NO_PUBKEY)
+ tty_fprintf (fp, " error ......: %s\n", gpg_strerror (err));
+ goto leave;
+ }
+ for (kb = keyblock; kb; kb = kb->next)
+ {
+ tty_fprintf (fp, " used for ...: %s\n",
+ kb->protocol == GNUPG_PROTOCOL_OPENPGP? "OpenPGP" :
+ kb->protocol == GNUPG_PROTOCOL_CMS? "X.509" : "?");
+ pubkey = kb->keys;
+ /* If this is not the primary key print the primary key's
+ * fingerprint or a reference to it. */
+ if (kb->protocol == GNUPG_PROTOCOL_OPENPGP)
+ {
+ tty_fprintf (fp, " main key .:");
+ for (ki=firstkinfo; ki; ki = ki->next)
+ if (pubkey->grip_valid
+ && !memcmp (ki->grip, pubkey->grip, KEYGRIP_LEN))
+ break;
+ if (ki)
+ {
+ /* Fixme: Replace mapping by a table lookup. */
+ if (!memcmp (kinfo->grip, pubkey->grip, KEYGRIP_LEN))
+ s = "this";
+ else if (!strcmp (ki->keyref, "OPENPGP.1"))
+ s = "Signature key";
+ else if (!strcmp (ki->keyref, "OPENPGP.2"))
+ s = "Encryption key";
+ else if (!strcmp (ki->keyref, "OPENPGP.3"))
+ s = "Authentication key";
+ else
+ s = NULL;
+ if (s)
+ tty_fprintf (fp, " <%s>\n", s);
+ else
+ tty_fprintf (fp, " <Key %s>\n", ki->keyref);
+ }
+ else
+ print_shax_fpr (fp, pubkey->fpr, pubkey->fprlen);
+ }
+ for (uid = kb->uids; uid; uid = uid->next)
+ {
+ print_string (fp, " user id ..: ", uid->value);
+ }
+
+ }
+ }
+ else
+ {
+ tty_fprintf (fp, " [none]\n");
+ if (label_keyref)
+ tty_fprintf (fp, " keyref .....: %s\n", label_keyref);
+ }
+
+ leave:
+ release_keyblock (keyblock);
+}
+
+
+/* List all keyinfo in INFO using the list of LABELS. */
+static void
+list_all_kinfo (card_info_t info, keyinfolabel_t labels, estream_t fp)
+{
+ key_info_t kinfo;
+ int idx, i;
+
+ /* Print the keyinfo. We first print those we known and then all
+ * remaining item. */
+ for (kinfo = info->kinfo; kinfo; kinfo = kinfo->next)
+ kinfo->xflag = 0;
+ if (labels)
+ {
+ for (idx=0; labels[idx].label; idx++)
+ {
+ tty_fprintf (fp, "%s", labels[idx].label);
+ kinfo = find_kinfo (info, labels[idx].keyref);
+ list_one_kinfo (info->kinfo, kinfo, labels[idx].keyref, fp);
+ if (kinfo)
+ kinfo->xflag = 1;
+ }
+ }
+ for (kinfo = info->kinfo; kinfo; kinfo = kinfo->next)
+ {
+ if (kinfo->xflag)
+ continue;
+ tty_fprintf (fp, "Key %s ", kinfo->keyref);
+ for (i=5+strlen (kinfo->keyref); i < 18; i++)
+ tty_fprintf (fp, ".");
+ tty_fprintf (fp, ":");
+ list_one_kinfo (info->kinfo, kinfo, NULL, fp);
+ }
+}
+
+
+/* List OpenPGP card specific data. */
+static void
+list_openpgp (card_info_t info, estream_t fp)
+{
+ static struct keyinfolabel_s keyinfolabels[] = {
+ { "Signature key ....:", "OPENPGP.1" },
+ { "Encryption key....:", "OPENPGP.2" },
+ { "Authentication key:", "OPENPGP.3" },
+ { NULL, NULL }
+ };
+ int i;
+
+ if (!info->serialno
+ || strncmp (info->serialno, "D27600012401", 12)
+ || strlen (info->serialno) != 32 )
+ {
+ tty_fprintf (fp, "invalid OpenPGP card\n");
+ return;
+ }
+
+ tty_fprintf (fp, "Manufacturer .....: %s\n",
+ get_manufacturer (xtoi_2(info->serialno+16)*256
+ + xtoi_2 (info->serialno+18)));
+ tty_fprintf (fp, "Name of cardholder: ");
+ print_isoname (fp, info->disp_name);
+
+ print_string (fp, "Language prefs ...: ", info->disp_lang);
+ tty_fprintf (fp, "Salutation .......: %s\n",
+ info->disp_sex == 1? _("Mr."):
+ info->disp_sex == 2? _("Mrs.") : "");
+ print_string (fp, "URL of public key : ", info->pubkey_url);
+ print_string (fp, "Login data .......: ", info->login_data);
+ if (info->private_do[0])
+ print_string (fp, "Private DO 1 .....: ", info->private_do[0]);
+ if (info->private_do[1])
+ print_string (fp, "Private DO 2 .....: ", info->private_do[1]);
+ if (info->private_do[2])
+ print_string (fp, "Private DO 3 .....: ", info->private_do[2]);
+ if (info->private_do[3])
+ print_string (fp, "Private DO 4 .....: ", info->private_do[3]);
+ if (info->cafpr1len)
+ {
+ tty_fprintf (fp, "CA fingerprint %d .:", 1);
+ print_shax_fpr (fp, info->cafpr1, info->cafpr1len);
+ }
+ if (info->cafpr2len)
+ {
+ tty_fprintf (fp, "CA fingerprint %d .:", 2);
+ print_shax_fpr (fp, info->cafpr2, info->cafpr2len);
+ }
+ if (info->cafpr3len)
+ {
+ tty_fprintf (fp, "CA fingerprint %d .:", 3);
+ print_shax_fpr (fp, info->cafpr3, info->cafpr3len);
+ }
+ tty_fprintf (fp, "Signature PIN ....: %s\n",
+ info->chv1_cached? _("not forced"): _("forced"));
+ if (info->key_attr[0].algo)
+ {
+ tty_fprintf (fp, "Key attributes ...:");
+ for (i=0; i < DIM (info->key_attr); i++)
+ if (info->key_attr[i].algo == PUBKEY_ALGO_RSA)
+ tty_fprintf (fp, " rsa%u", info->key_attr[i].nbits);
+ else if (info->key_attr[i].algo == PUBKEY_ALGO_ECDH
+ || info->key_attr[i].algo == PUBKEY_ALGO_ECDSA
+ || info->key_attr[i].algo == PUBKEY_ALGO_EDDSA)
+ {
+ const char *curve_for_print = "?";
+ const char *oid;
+
+ if (info->key_attr[i].curve
+ && (oid = openpgp_curve_to_oid (info->key_attr[i].curve, NULL)))
+ curve_for_print = openpgp_oid_to_curve (oid, 0);
+ tty_fprintf (fp, " %s", curve_for_print);
+ }
+ tty_fprintf (fp, "\n");
+ }
+ tty_fprintf (fp, "Max. PIN lengths .: %d %d %d\n",
+ info->chvmaxlen[0], info->chvmaxlen[1], info->chvmaxlen[2]);
+ tty_fprintf (fp, "PIN retry counter : %d %d %d\n",
+ info->chvinfo[0], info->chvinfo[1], info->chvinfo[2]);
+ tty_fprintf (fp, "Signature counter : %lu\n", info->sig_counter);
+ if (info->extcap.kdf)
+ {
+ tty_fprintf (fp, "KDF setting ......: %s\n",
+ info->kdf_do_enabled ? "on" : "off");
+ }
+ if (info->extcap.bt)
+ {
+ tty_fprintf (fp, "UIF setting ......: Sign=%s Decrypt=%s Auth=%s\n",
+ info->uif[0] ? "on" : "off", info->uif[1] ? "on" : "off",
+ info->uif[2] ? "on" : "off");
+ }
+
+ list_all_kinfo (info, keyinfolabels, fp);
+
+}
+
+
+/* List PIV card specific data. */
+static void
+list_piv (card_info_t info, estream_t fp)
+{
+ static struct keyinfolabel_s keyinfolabels[] = {
+ { "PIV authentication:", "PIV.9A" },
+ { "Card authenticat. :", "PIV.9E" },
+ { "Digital signature :", "PIV.9C" },
+ { "Key management ...:", "PIV.9D" },
+ { NULL, NULL }
+ };
+ const char *s;
+ int i;
+
+ if (info->chvusage[0] || info->chvusage[1])
+ {
+ tty_fprintf (fp, "PIN usage policy .:");
+ if ((info->chvusage[0] & 0x40))
+ tty_fprintf (fp, " app-pin");
+ if ((info->chvusage[0] & 0x20))
+ tty_fprintf (fp, " global-pin");
+ if ((info->chvusage[0] & 0x10))
+ tty_fprintf (fp, " occ");
+ if ((info->chvusage[0] & 0x08))
+ tty_fprintf (fp, " vci");
+ if ((info->chvusage[0] & 0x08) && !(info->chvusage[0] & 0x04))
+ tty_fprintf (fp, " pairing");
+
+ if (info->chvusage[1] == 0x10)
+ tty_fprintf (fp, " primary:card");
+ else if (info->chvusage[1] == 0x20)
+ tty_fprintf (fp, " primary:global");
+
+ tty_fprintf (fp, "\n");
+ }
+
+ tty_fprintf (fp, "PIN retry counter :");
+ for (i=0; i < DIM (info->chvinfo); i++)
+ {
+ if (info->chvinfo[i] > 0)
+ tty_fprintf (fp, " %d", info->chvinfo[i]);
+ else
+ {
+ switch (info->chvinfo[i])
+ {
+ case -1: s = "[error]"; break;
+ case -2: s = "-"; break; /* No such PIN or info not available. */
+ case -3: s = "[blocked]"; break;
+ case -5: s = "[verified]"; break;
+ default: s = "[?]"; break;
+ }
+ tty_fprintf (fp, " %s", s);
+ }
+ }
+ tty_fprintf (fp, "\n");
+ list_all_kinfo (info, keyinfolabels, fp);
+
+}
+
+
+
+static void
+print_a_version (estream_t fp, const char *prefix, unsigned int value)
+{
+ unsigned int a, b, c, d;
+ a = ((value >> 24) & 0xff);
+ b = ((value >> 16) & 0xff);
+ c = ((value >> 8) & 0xff);
+ d = ((value ) & 0xff);
+
+ if (a)
+ tty_fprintf (fp, "%s %u.%u.%u.%u\n", prefix, a, b, c, d);
+ else if (b)
+ tty_fprintf (fp, "%s %u.%u.%u\n", prefix, b, c, d);
+ else
+ tty_fprintf (fp, "%s %u.%u\n", prefix, c, d);
+}
+
+
+/* Print all available information about the current card. */
+static void
+list_card (card_info_t info)
+{
+ estream_t fp = opt.interactive? NULL : es_stdout;
+
+ tty_fprintf (fp, "Reader ...........: %s\n",
+ info->reader? info->reader : "[none]");
+ if (info->cardtype)
+ tty_fprintf (fp, "Card type ........: %s\n", info->cardtype);
+ if (info->cardversion)
+ print_a_version (fp, "Card firmware ....:", info->cardversion);
+ 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->appversion)
+ print_a_version (fp, "Version ..........:", info->appversion);
+ if (info->serialno && info->dispserialno
+ && strcmp (info->serialno, info->dispserialno))
+ tty_fprintf (fp, "Displayed s/n ....: %s\n", info->dispserialno);
+
+ switch (info->apptype)
+ {
+ case APP_TYPE_OPENPGP: list_openpgp (info, fp); break;
+ case APP_TYPE_PIV: list_piv (info, fp); break;
+ default: break;
+ }
+}
+
+
+
+/* The VERIFY command. */
+static gpg_error_t
+cmd_verify (card_info_t info, char *argstr)
+{
+ gpg_error_t err;
+ const char *pinref;
+
+ if (!info)
+ return print_help ("verify [chvid]", 0);
+
+ if (*argstr)
+ pinref = argstr;
+ else if (info->apptype == APP_TYPE_OPENPGP)
+ pinref = info->serialno;
+ else if (info->apptype == APP_TYPE_PIV)
+ pinref = "PIV.80";
+ else
+ return gpg_error (GPG_ERR_MISSING_VALUE);
+
+ err = scd_checkpin (pinref);
+ if (err)
+ log_error ("verify failed: %s <%s>\n",
+ gpg_strerror (err), gpg_strsource (err));
+ return err;
+}
+
+
+static gpg_error_t
+cmd_authenticate (card_info_t info, char *argstr)
+{
+ gpg_error_t err;
+ int opt_setkey;
+ int opt_raw;
+ char *string = NULL;
+ char *key = NULL;
+ size_t keylen;
+
+ if (!info)
+ return print_help
+ ("AUTHENTICATE [--setkey] [--raw] [< FILE]|KEY\n\n"
+ "Perform a mutual autentication either by reading the key\n"
+ "from FILE or by taking it from the command line. Without\n"
+ "the option --raw the key is expected to be hex encoded.\n"
+ "To install a new administration key --setkey is used; this\n"
+ "requires a prior authentication with the old key.",
+ APP_TYPE_PIV, 0);
+
+ if (info->apptype != APP_TYPE_PIV)
+ {
+ log_info ("Note: This is a PIV only command.\n");
+ return gpg_error (GPG_ERR_NOT_SUPPORTED);
+ }
+
+ opt_setkey = has_leading_option (argstr, "--setkey");
+ opt_raw = has_leading_option (argstr, "--raw");
+ argstr = skip_options (argstr);
+
+ if (*argstr == '<') /* Read key from a file. */
+ {
+ for (argstr++; spacep (argstr); argstr++)
+ ;
+ err = get_data_from_file (argstr, &string, NULL);
+ if (err)
+ goto leave;
+ }
+
+ if (opt_raw)
+ {
+ key = string? string : xstrdup (argstr);
+ string = NULL;
+ keylen = strlen (key);
+ }
+ else
+ {
+ key = hex_to_buffer (string? string: argstr, &keylen);
+ if (!key)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ }
+ err = scd_setattr (opt_setkey? "SET-ADM-KEY":"AUTH-ADM-KEY", key, keylen);
+
+ leave:
+ if (key)
+ {
+ wipememory (key, keylen);
+ xfree (key);
+ }
+ xfree (string);
+ return err;
+}
+
+
+/* Helper for cmd_name to qyery a part of name. */
+static char *
+ask_one_name (const char *prompt)
+{
+ char *name;
+ int i;
+
+ for (;;)
+ {
+ name = tty_get (prompt);
+ trim_spaces (name);
+ tty_kill_prompt ();
+ if (!*name || *name == CONTROL_D)
+ {
+ if (*name == CONTROL_D)
+ tty_fprintf (NULL, "\n");
+ xfree (name);
+ return NULL;
+ }
+ for (i=0; name[i] && name[i] >= ' ' && name[i] <= 126; i++)
+ ;
+
+ /* The name must be in Latin-1 and not UTF-8 - lacking the code
+ * to ensure this we restrict it to ASCII. */
+ if (name[i])
+ tty_printf (_("Error: Only plain ASCII is currently allowed.\n"));
+ else if (strchr (name, '<'))
+ tty_printf (_("Error: The \"<\" character may not be used.\n"));
+ else if (strstr (name, " "))
+ tty_printf (_("Error: Double spaces are not allowed.\n"));
+ else
+ return name;
+ xfree (name);
+ }
+}
+
+
+/* The NAME command. */
+static gpg_error_t
+cmd_name (card_info_t info, const char *argstr)
+{
+ gpg_error_t err;
+ char *surname, *givenname;
+ char *isoname, *p;
+
+ if (!info)
+ return print_help
+ ("name [--clear]\n\n"
+ "Set the name field of an OpenPGP card. With --clear the stored\n"
+ "name is cleared off the card.", APP_TYPE_OPENPGP, APP_TYPE_NKS, 0);
+
+ if (info->apptype != APP_TYPE_OPENPGP)
+ {
+ log_info ("Note: This is an OpenPGP only command.\n");
+ return gpg_error (GPG_ERR_NOT_SUPPORTED);
+ }
+
+ again:
+ if (!strcmp (argstr, "--clear"))
+ isoname = xstrdup (" "); /* No real way to clear; set to space instead. */
+ else
+ {
+ surname = ask_one_name (_("Cardholder's surname: "));
+ givenname = ask_one_name (_("Cardholder's given name: "));
+ if (!surname || !givenname || (!*surname && !*givenname))
+ {
+ xfree (surname);
+ xfree (givenname);
+ return gpg_error (GPG_ERR_CANCELED);
+ }
+
+ isoname = xstrconcat (surname, "<<", givenname, NULL);
+ xfree (surname);
+ xfree (givenname);
+ for (p=isoname; *p; p++)
+ if (*p == ' ')
+ *p = '<';
+
+ if (strlen (isoname) > 39 )
+ {
+ log_info (_("Error: Combined name too long "
+ "(limit is %d characters).\n"), 39);
+ xfree (isoname);
+ goto again;
+ }
+ }
+
+ err = scd_setattr ("DISP-NAME", isoname, strlen (isoname));
+
+ xfree (isoname);
+ return err;
+}
+
+
+static gpg_error_t
+cmd_url (card_info_t info, const char *argstr)
+{
+ gpg_error_t err;
+ char *url;
+
+ if (!info)
+ return print_help
+ ("URL [--clear]\n\n"
+ "Set the URL data object. That data object can be used by\n"
+ "the FETCH command to retrieve the full public key. The\n"
+ "option --clear deletes the content of that data object.",
+ APP_TYPE_OPENPGP, 0);
+
+ if (info->apptype != APP_TYPE_OPENPGP)
+ {
+ log_info ("Note: This is an OpenPGP only command.\n");
+ return gpg_error (GPG_ERR_NOT_SUPPORTED);
+ }
+
+ if (!strcmp (argstr, "--clear"))
+ url = xstrdup (" "); /* No real way to clear; set to space instead. */
+ else
+ {
+ url = tty_get (_("URL to retrieve public key: "));
+ trim_spaces (url);
+ tty_kill_prompt ();
+ if (!*url || *url == CONTROL_D)
+ {
+ err = gpg_error (GPG_ERR_CANCELED);
+ goto leave;
+ }
+ }
+
+ err = scd_setattr ("PUBKEY-URL", url, strlen (url));
+
+ leave:
+ xfree (url);
+ return err;
+}
+
+
+/* Fetch the key from the URL given on the card or try to get it from
+ * the default keyserver. */
+static gpg_error_t
+cmd_fetch (card_info_t info)
+{
+ gpg_error_t err;
+ key_info_t kinfo;
+
+ if (!info)
+ return print_help
+ ("FETCH\n\n"
+ "Retrieve a key using the URL data object or if that is missing\n"
+ "using the fingerprint.", APP_TYPE_OPENPGP, 0);
+
+ if (info->pubkey_url && *info->pubkey_url)
+ {
+ /* strlist_t sl = NULL; */
+
+ /* add_to_strlist (&sl, info.pubkey_url); */
+ /* err = keyserver_fetch (ctrl, sl, KEYORG_URL); */
+ /* free_strlist (sl); */
+ err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */
+ }
+ else if ((kinfo = find_kinfo (info, "OPENPGP.1")) && kinfo->fprlen)
+ {
+ /* rc = keyserver_import_fprint (ctrl, info.fpr1, info.fpr1len, */
+ /* opt.keyserver, 0); */
+ err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */
+ }
+ else
+ err = gpg_error (GPG_ERR_NO_DATA);
+
+ return err;
+}
+
+
+static gpg_error_t
+cmd_login (card_info_t info, char *argstr)
+{
+ gpg_error_t err;
+ char *data;
+ size_t datalen;
+
+ if (!info)
+ return print_help
+ ("LOGIN [--clear] [< FILE]\n\n"
+ "Set the login data object. If FILE is given the data is\n"
+ "is read from that file. This allows for binary data.\n"
+ "The option --clear deletes the login data.",
+ APP_TYPE_OPENPGP, 0);
+
+ if (!strcmp (argstr, "--clear"))
+ {
+ data = xstrdup (" "); /* kludge. */
+ datalen = 1;
+ }
+ else if (*argstr == '<') /* Read it from a file */
+ {
+ for (argstr++; spacep (argstr); argstr++)
+ ;
+ err = get_data_from_file (argstr, &data, &datalen);
+ if (err)
+ goto leave;
+ }
+ else
+ {
+ data = tty_get (_("Login data (account name): "));
+ trim_spaces (data);
+ tty_kill_prompt ();
+ if (!*data || *data == CONTROL_D)
+ {
+ err = gpg_error (GPG_ERR_CANCELED);
+ goto leave;
+ }
+ datalen = strlen (data);
+ }
+
+ err = scd_setattr ("LOGIN-DATA", data, datalen);
+
+ leave:
+ xfree (data);
+ return err;
+}
+
+
+static gpg_error_t
+cmd_lang (card_info_t info, const char *argstr)
+{
+ gpg_error_t err;
+ char *data, *p;
+
+ if (!info)
+ return print_help
+ ("LANG [--clear]\n\n"
+ "Change the language info for the card. This info can be used\n"
+ "by applications for a personalized greeting. Up to 4 two-digit\n"
+ "language identifiers can be entered as a preference. The option\n"
+ "--clear removes all identifiers. GnuPG does not use this info.",
+ APP_TYPE_OPENPGP, 0);
+
+ if (!strcmp (argstr, "--clear"))
+ data = xstrdup (" "); /* Note that we need two spaces here. */
+ else
+ {
+ again:
+ data = tty_get (_("Language preferences: "));
+ trim_spaces (data);
+ tty_kill_prompt ();
+ if (!*data || *data == CONTROL_D)
+ {
+ err = gpg_error (GPG_ERR_CANCELED);
+ goto leave;
+ }
+
+ if (strlen (data) > 8 || (strlen (data) & 1))
+ {
+ log_info (_("Error: invalid length of preference string.\n"));
+ xfree (data);
+ goto again;
+ }
+
+ for (p=data; *p && *p >= 'a' && *p <= 'z'; p++)
+ ;
+ if (*p)
+ {
+ log_info (_("Error: invalid characters in preference string.\n"));
+ xfree (data);
+ goto again;
+ }
+ }
+
+ err = scd_setattr ("DISP-LANG", data, strlen (data));
+
+ leave:
+ xfree (data);
+ return err;
+}
+
+
+static gpg_error_t
+cmd_salut (card_info_t info, const char *argstr)
+{
+ gpg_error_t err;
+ char *data = NULL;
+ const char *str;
+
+ if (!info)
+ return print_help
+ ("SALUT [--clear]\n\n"
+ "Change the salutation info for the card. This info can be used\n"
+ "by applications for a personalized greeting. The option --clear\n"
+ "removes this data object. GnuPG does not use this info.",
+ APP_TYPE_OPENPGP, 0);
+
+ again:
+ if (!strcmp (argstr, "--clear"))
+ str = "9";
+ else
+ {
+ data = tty_get (_("Salutation (M = Mr., F = Mrs., or space): "));
+ trim_spaces (data);
+ tty_kill_prompt ();
+ if (*data == CONTROL_D)
+ {
+ err = gpg_error (GPG_ERR_CANCELED);
+ goto leave;
+ }
+
+ if (!*data)
+ str = "9";
+ else if ((*data == 'M' || *data == 'm') && !data[1])
+ str = "1";
+ else if ((*data == 'F' || *data == 'f') && !data[1])
+ str = "2";
+ else
+ {
+ tty_printf (_("Error: invalid response.\n"));
+ xfree (data);
+ goto again;
+ }
+ }
+
+ err = scd_setattr ("DISP-SEX", str, 1);
+ leave:
+ xfree (data);
+ return err;
+}
+
+
+static gpg_error_t
+cmd_cafpr (card_info_t info, char *argstr)
+{
+ gpg_error_t err;
+ char *data = NULL;
+ const char *s;
+ int i, c;
+ unsigned char fpr[32];
+ int fprlen;
+ int fprno;
+ int opt_clear = 0;
+
+ if (!info)
+ return print_help
+ ("CAFPR [--clear] N\n\n"
+ "Change the CA fingerprint number N. N must be in the\n"
+ "range 1 to 3. The option --clear clears the specified\n"
+ "CA fingerprint N or all of them if N is 0 or not given.",
+ APP_TYPE_OPENPGP, 0);
+
+
+ opt_clear = has_leading_option (argstr, "--clear");
+ argstr = skip_options (argstr);
+
+ if (digitp (argstr))
+ {
+ fprno = atoi (argstr);
+ while (digitp (argstr))
+ argstr++;
+ while (spacep (argstr))
+ argstr++;
+ }
+ else
+ fprno = 0;
+
+ if (opt_clear && !fprno)
+ ; /* Okay: clear all fprs. */
+ else if (fprno < 1 || fprno > 3)
+ {
+ err = gpg_error (GPG_ERR_INV_ARG);
+ goto leave;
+ }
+
+ again:
+ if (opt_clear)
+ {
+ memset (fpr, 0, 20);
+ fprlen = 20;
+ }
+ else
+ {
+ xfree (data);
+ data = tty_get (_("CA fingerprint: "));
+ trim_spaces (data);
+ tty_kill_prompt ();
+ if (!*data || *data == CONTROL_D)
+ {
+ err = gpg_error (GPG_ERR_CANCELED);
+ goto leave;
+ }
+
+ for (i=0, s=data; i < sizeof fpr && *s; )
+ {
+ while (spacep(s))
+ s++;
+ if (*s == ':')
+ s++;
+ while (spacep(s))
+ s++;
+ c = hextobyte (s);
+ if (c == -1)
+ break;
+ fpr[i++] = c;
+ s += 2;
+ }
+ fprlen = i;
+ if ((fprlen != 20 && fprlen != 32) || *s)
+ {
+ log_error (_("Error: invalid formatted fingerprint.\n"));
+ goto again;
+ }
+ }
+
+ if (!fprno)
+ {
+ log_assert (opt_clear);
+ err = scd_setattr ("CA-FPR-1", fpr, fprlen);
+ if (!err)
+ err = scd_setattr ("CA-FPR-2", fpr, fprlen);
+ if (!err)
+ err = scd_setattr ("CA-FPR-3", fpr, fprlen);
+ }
+ else
+ err = scd_setattr (fprno==1?"CA-FPR-1":
+ fprno==2?"CA-FPR-2":
+ fprno==3?"CA-FPR-3":"x", fpr, fprlen);
+
+ leave:
+ xfree (data);
+ return err;
+}
+
+
+static gpg_error_t
+cmd_privatedo (card_info_t info, char *argstr)
+{
+ gpg_error_t err;
+ int opt_clear;
+ char *do_name = NULL;
+ char *data = NULL;
+ size_t datalen;
+ int do_no;
+
+ if (!info)
+ return print_help
+ ("PRIVATEDO [--clear] N [< FILE]\n\n"
+ "Change the private data object N. N must be in the\n"
+ "range 1 to 4. If FILE is given the data is is read\n"
+ "from that file. The option --clear clears the data.",
+ APP_TYPE_OPENPGP, 0);
+
+ opt_clear = has_leading_option (argstr, "--clear");
+ argstr = skip_options (argstr);
+
+ if (digitp (argstr))
+ {
+ do_no = atoi (argstr);
+ while (digitp (argstr))
+ argstr++;
+ while (spacep (argstr))
+ argstr++;
+ }
+ else
+ do_no = 0;
+
+ if (do_no < 1 || do_no > 4)
+ {
+ err = gpg_error (GPG_ERR_INV_ARG);
+ goto leave;
+ }
+ do_name = xasprintf ("PRIVATE-DO-%d", do_no);
+
+ if (opt_clear)
+ {
+ data = xstrdup (" ");
+ datalen = 1;
+ }
+ else if (*argstr == '<') /* Read it from a file */
+ {
+ for (argstr++; spacep (argstr); argstr++)
+ ;
+ err = get_data_from_file (argstr, &data, &datalen);
+ if (err)
+ goto leave;
+ }
+ else if (*argstr)
+ {
+ err = gpg_error (GPG_ERR_INV_ARG);
+ goto leave;
+ }
+ else
+ {
+ data = tty_get (_("Private DO data: "));
+ trim_spaces (data);
+ tty_kill_prompt ();
+ datalen = strlen (data);
+ if (!*data || *data == CONTROL_D)
+ {
+ err = gpg_error (GPG_ERR_CANCELED);
+ goto leave;
+ }
+ }
+
+ err = scd_setattr (do_name, data, datalen);
+
+ leave:
+ xfree (do_name);
+ xfree (data);
+ return err;
+}
+
+
+static gpg_error_t
+cmd_writecert (card_info_t info, char *argstr)
+{
+ gpg_error_t err;
+ int opt_clear;
+ char *certref_buffer = NULL;
+ char *certref;
+ char *data = NULL;
+ size_t datalen;
+
+ if (!info)
+ return print_help
+ ("WRITECERT [--clear] CERTREF < FILE\n\n"
+ "Write a certificate for key 3. Unless --clear is given\n"
+ "the file argument is mandatory. The option --clear removes\n"
+ "the certificate from the card.",
+ APP_TYPE_OPENPGP, APP_TYPE_PIV, 0);
+
+ opt_clear = has_leading_option (argstr, "--clear");
+ argstr = skip_options (argstr);
+
+ certref = argstr;
+ if ((argstr = strchr (certref, ' ')))
+ {
+ *argstr++ = 0;
+ trim_spaces (certref);
+ trim_spaces (argstr);
+ }
+ else /* Let argstr point to an empty string. */
+ argstr = certref + strlen (certref);
+
+ if (info->apptype == APP_TYPE_OPENPGP)
+ {
+ if (ascii_strcasecmp (certref, "OPENPGP.3") && strcmp (certref, "3"))
+ {
+ err = gpg_error (GPG_ERR_INV_ID);
+ log_error ("Error: CERTREF must be \"3\" or \"OPENPGP.3\"\n");
+ goto leave;
+ }
+ certref = certref_buffer = xstrdup ("OPENPGP.3");
+ }
+ else /* Upcase the certref; prepend cardtype if needed. */
+ {
+ if (!strchr (certref, '.'))
+ certref_buffer = xstrconcat (app_type_string (info->apptype), ".",
+ certref, NULL);
+ else
+ certref_buffer = xstrdup (certref);
+ ascii_strupr (certref_buffer);
+ certref = certref_buffer;
+ }
+
+ if (opt_clear)
+ {
+ data = xstrdup (" ");
+ datalen = 1;
+ }
+ else if (*argstr == '<') /* Read it from a file */
+ {
+ for (argstr++; spacep (argstr); argstr++)
+ ;
+ err = get_data_from_file (argstr, &data, &datalen);
+ if (err)
+ goto leave;
+ if (ascii_memistr (data, datalen, "-----BEGIN CERTIFICATE-----")
+ && ascii_memistr (data, datalen, "-----END CERTIFICATE-----")
+ && !memchr (data, 0, datalen) && !memchr (data, 1, datalen))
+ {
+ struct b64state b64;
+
+ err = b64dec_start (&b64, "");
+ if (!err)
+ err = b64dec_proc (&b64, data, datalen, &datalen);
+ if (!err)
+ err = b64dec_finish (&b64);
+ if (err)
+ goto leave;
+ }
+ }
+ else
+ {
+ err = gpg_error (GPG_ERR_INV_ARG);
+ goto leave;
+ }
+
+ err = scd_writecert (certref, data, datalen);
+
+ leave:
+ xfree (data);
+ xfree (certref_buffer);
+ return err;
+}
+
+
+static gpg_error_t
+cmd_readcert (card_info_t info, char *argstr)
+{
+ gpg_error_t err;
+ char *certref_buffer = NULL;
+ char *certref;
+ void *data = NULL;
+ size_t datalen;
+ const char *fname;
+
+ if (!info)
+ return print_help
+ ("READCERT CERTREF > FILE\n\n"
+ "Read the certificate for key CERTREF and store it in FILE.",
+ APP_TYPE_OPENPGP, APP_TYPE_PIV, 0);
+
+ argstr = skip_options (argstr);
+
+ certref = argstr;
+ if ((argstr = strchr (certref, ' ')))
+ {
+ *argstr++ = 0;
+ trim_spaces (certref);
+ trim_spaces (argstr);
+ }
+ else /* Let argstr point to an empty string. */
+ argstr = certref + strlen (certref);
+
+ if (info->apptype == APP_TYPE_OPENPGP)
+ {
+ if (ascii_strcasecmp (certref, "OPENPGP.3") && strcmp (certref, "3"))
+ {
+ err = gpg_error (GPG_ERR_INV_ID);
+ log_error ("Error: CERTREF must be \"3\" or \"OPENPGP.3\"\n");
+ goto leave;
+ }
+ certref = certref_buffer = xstrdup ("OPENPGP.3");
+ }
+
+ if (*argstr == '>') /* Write it to a file */
+ {
+ for (argstr++; spacep (argstr); argstr++)
+ ;
+ fname = argstr;
+ }
+ else
+ {
+ err = gpg_error (GPG_ERR_INV_ARG);
+ goto leave;
+ }
+
+ err = scd_readcert (certref, &data, &datalen);
+ if (err)
+ goto leave;
+
+ err = put_data_to_file (fname, data, datalen);
+
+ leave:
+ xfree (data);
+ xfree (certref_buffer);
+ return err;
+}
+
+
+static gpg_error_t
+cmd_writekey (card_info_t info, char *argstr)
+{
+ gpg_error_t err;
+ int opt_force;
+ char *argv[2];
+ int argc;
+ char *keyref_buffer = NULL;
+ char *keyref;
+ char *keygrip;
+
+ if (!info)
+ return print_help
+ ("WRITEKEY [--force] KEYREF KEYGRIP\n\n"
+ "Write a private key object identified by KEYGRIP to slot KEYREF.\n"
+ "Use --force to overwrite an existing key.",
+ APP_TYPE_OPENPGP, APP_TYPE_PIV, 0);
+
+ opt_force = has_leading_option (argstr, "--force");
+ argstr = skip_options (argstr);
+
+ argc = split_fields (argstr, argv, DIM (argv));
+ if (argc < 2)
+ {
+ err = gpg_error (GPG_ERR_INV_ARG);
+ goto leave;
+ }
+
+ /* Upcase the keyref; prepend cardtype if needed. */
+ keyref = argv[0];
+ if (!strchr (keyref, '.'))
+ keyref_buffer = xstrconcat (app_type_string (info->apptype), ".",
+ keyref, NULL);
+ else
+ keyref_buffer = xstrdup (keyref);
+ ascii_strupr (keyref_buffer);
+ keyref = keyref_buffer;
+
+ /* Get the keygrip. */
+ keygrip = argv[1];
+ if (strlen (keygrip) != 40
+ && !(keygrip[0] == '&' && strlen (keygrip+1) == 40))
+ {
+ log_error (_("Not a valid keygrip (expecting 40 hex digits)\n"));
+ err = gpg_error (GPG_ERR_INV_ARG);
+ goto leave;
+ }
+
+ err = scd_writekey (keyref, opt_force, keygrip);
+
+ leave:
+ xfree (keyref_buffer);
+ return err;
+}
+
+
+static gpg_error_t
+cmd_forcesig (card_info_t info)
+{
+ gpg_error_t err;
+ int newstate;
+
+ if (!info)
+ return print_help
+ ("FORCESIG\n\n"
+ "Toggle the forcesig flag of an OpenPGP card.",
+ APP_TYPE_OPENPGP, 0);
+
+ if (info->apptype != APP_TYPE_OPENPGP)
+ {
+ log_info ("Note: This is an OpenPGP only command.\n");
+ return gpg_error (GPG_ERR_NOT_SUPPORTED);
+ }
+
+ newstate = !info->chv1_cached;
+
+ err = scd_setattr ("CHV-STATUS-1", newstate? "\x01":"", 1);
+ if (err)
+ goto leave;
+
+ /* Read it back to be sure we have the right toggle state the next
+ * time. */
+ err = scd_getattr ("CHV-STATUS", info);
+
+ leave:
+ return err;
+}
+
+
+
+/* Helper for cmd_generate_openpgp. Noe that either 0 or 1 is stored at
+ * FORCED_CHV1. */
+static gpg_error_t
+check_pin_for_key_operation (card_info_t info, int *forced_chv1)
+{
+ gpg_error_t err = 0;
+
+ *forced_chv1 = !info->chv1_cached;
+ if (*forced_chv1)
+ { /* Switch off the forced mode so that during key generation we
+ * don't get bothered with PIN queries for each self-signature. */
+ err = scd_setattr ("CHV-STATUS-1", "\x01", 1);
+ if (err)
+ {
+ log_error ("error clearing forced signature PIN flag: %s\n",
+ gpg_strerror (err));
+ *forced_chv1 = -1; /* Not changed. */
+ goto leave;
+ }
+ }
+
+ /* Check the PIN now, so that we won't get asked later for each
+ * binding signature. */
+ err = scd_checkpin (info->serialno);
+ if (err)
+ log_error ("error checking the PIN: %s\n", gpg_strerror (err));
+
+ leave:
+ return err;
+}
+
+
+/* Helper for cmd_generate_openpgp. */
+static void
+restore_forced_chv1 (int *forced_chv1)
+{
+ gpg_error_t err;
+
+ /* Note the possible values stored at FORCED_CHV1:
+ * 0 - forcesig was not enabled.
+ * 1 - forcesig was enabled - enable it again.
+ * -1 - We have not changed anything. */
+ if (*forced_chv1 == 1)
+ { /* Switch back to forced state. */
+ err = scd_setattr ("CHV-STATUS-1", "", 1);
+ if (err)
+ log_error ("error setting forced signature PIN flag: %s\n",
+ gpg_strerror (err));
+ *forced_chv1 = 0;
+ }
+}
+
+
+/* Implementation of cmd_generate for OpenPGP cards. */
+static gpg_error_t
+generate_openpgp (card_info_t info)
+{
+ gpg_error_t err;
+ int forced_chv1 = -1;
+ int want_backup;
+ char *answer = NULL;
+ key_info_t kinfo1, kinfo2, kinfo3;
+
+ if (info->extcap.ki)
+ {
+ xfree (answer);
+ answer = tty_get (_("Make off-card backup of encryption key? (Y/n) "));
+ want_backup = answer_is_yes_no_default (answer, 1/*(default to Yes)*/);
+ tty_kill_prompt ();
+ if (*answer == CONTROL_D)
+ {
+ err = gpg_error (GPG_ERR_CANCELED);
+ goto leave;
+ }
+ }
+ else
+ want_backup = 0;
+
+ kinfo1 = find_kinfo (info, "OPENPGP.1");
+ kinfo2 = find_kinfo (info, "OPENPGP.2");
+ kinfo3 = find_kinfo (info, "OPENPGP.3");
+
+ if ((kinfo1 && kinfo1->fprlen && !mem_is_zero (kinfo1->fpr,kinfo1->fprlen))
+ || (kinfo2 && kinfo2->fprlen && !mem_is_zero (kinfo2->fpr,kinfo2->fprlen))
+ || (kinfo3 && kinfo3->fprlen && !mem_is_zero (kinfo3->fpr,kinfo3->fprlen))
+ )
+ {
+ tty_printf ("\n");
+ log_info (_("Note: keys are already stored on the card!\n"));
+ tty_printf ("\n");
+ answer = tty_get (_("Replace existing keys? (y/N) "));
+ tty_kill_prompt ();
+ if (*answer == CONTROL_D)
+ {
+ err = gpg_error (GPG_ERR_CANCELED);
+ goto leave;
+ }
+
+ if (!answer_is_yes_no_default (answer, 0/*(default to No)*/))
+ {
+ err = gpg_error (GPG_ERR_CANCELED);
+ goto leave;
+ }
+ }
+
+ /* If no displayed name has been set, we assume that this is a fresh
+ * card and print a hint about the default PINs. */
+ if (!info->disp_name || !*info->disp_name)
+ {
+ tty_printf ("\n");
+ tty_printf (_("Please note that the factory settings of the PINs are\n"
+ " PIN = '%s' Admin PIN = '%s'\n"
+ "You should change them using the command --change-pin\n"),
+ OPENPGP_USER_PIN_DEFAULT, OPENPGP_ADMIN_PIN_DEFAULT);
+ tty_printf ("\n");
+ }
+
+ err = check_pin_for_key_operation (info, &forced_chv1);
+ if (err)
+ goto leave;
+
+ /* FIXME: We need to divert to a function which spwans gpg which
+ * will then create the key. This also requires new features in
+ * gpg. We might also first create the keys on the card and then
+ * tell gpg to use them to create the OpenPGP keyblock. */
+ /* generate_keypair (ctrl, 1, NULL, info.serialno, want_backup); */
+ (void)want_backup;
+ err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+
+ leave:
+ restore_forced_chv1 (&forced_chv1);
+ xfree (answer);
+ return err;
+}
+
+
+/* Generic implementation of cmd_generate. */
+static gpg_error_t
+generate_generic (card_info_t info, const char *keyref, int force,
+ const char *algo)
+{
+ gpg_error_t err;
+
+ (void)info;
+
+ err = scd_genkey (keyref, force, algo, NULL);
+
+ return err;
+}
+
+
+static gpg_error_t
+cmd_generate (card_info_t info, char *argstr)
+{
+ static char * const valid_algos[] =
+ { "rsa2048", "rsa3072", "rsa4096",
+ "nistp256", "nistp384", "nistp521",
+ "ed25519", "cv25519",
+ NULL
+ };
+ gpg_error_t err;
+ int opt_force;
+ char *opt_algo = NULL; /* Malloced. */
+ char *keyref_buffer = NULL; /* Malloced. */
+ char *keyref; /* Points into argstr or keyref_buffer. */
+ int i;
+
+ if (!info)
+ return print_help
+ ("GENERATE [--force] [--algo=ALGO] KEYREF\n\n"
+ "Create a new key on a card. For OpenPGP cards are menu is used\n"
+ "and KEYREF is ignored. Use --force to overwrite an existing key.",
+ APP_TYPE_OPENPGP, APP_TYPE_PIV, 0);
+
+ if (opt.interactive || opt.verbose)
+ log_info (_("%s card no. %s detected\n"),
+ app_type_string (info->apptype),
+ info->dispserialno? info->dispserialno : info->serialno);
+
+ opt_force = has_leading_option (argstr, "--force");
+ err = get_option_value (argstr, "--algo", &opt_algo);
+ if (err)
+ goto leave;
+ argstr = skip_options (argstr);
+
+ keyref = argstr;
+ if ((argstr = strchr (keyref, ' ')))
+ {
+ *argstr++ = 0;
+ trim_spaces (keyref);
+ trim_spaces (argstr);
+ }
+ else /* Let argstr point to an empty string. */
+ argstr = keyref + strlen (keyref);
+
+ if (!*keyref)
+ keyref = NULL;
+
+ if (*argstr)
+ {
+ /* Extra arguments found. */
+ err = gpg_error (GPG_ERR_INV_ARG);
+ goto leave;
+ }
+
+ if (opt_algo)
+ {
+ for (i=0; valid_algos[i]; i++)
+ if (!strcmp (valid_algos[i], opt_algo))
+ break;
+ if (!valid_algos[i])
+ {
+ err = gpg_error (GPG_ERR_PUBKEY_ALGO);
+ log_info ("Invalid algorithm '%s' given. Use one:\n", opt_algo);
+ for (i=0; valid_algos[i]; i++)
+ if (!(i%5))
+ log_info (" %s%s", valid_algos[i], valid_algos[i+1]?",":".");
+ else
+ log_printf (" %s%s", valid_algos[i], valid_algos[i+1]?",":".");
+ log_info ("Note that the card may not support all of them.\n");
+ goto leave;
+ }
+ }
+
+ /* Upcase the keyref; if it misses the cardtype, prepend it. */
+ if (keyref)
+ {
+ if (!strchr (keyref, '.'))
+ keyref_buffer = xstrconcat (app_type_string (info->apptype), ".",
+ keyref, NULL);
+ else
+ keyref_buffer = xstrdup (keyref);
+ ascii_strupr (keyref_buffer);
+ keyref = keyref_buffer;
+ }
+
+ /* Special checks. */
+ if ((info->cardtype && !strcmp (info->cardtype, "yubikey"))
+ && info->cardversion >= 0x040200 && info->cardversion < 0x040305)
+ {
+ log_error ("On-chip key generation on this YubiKey has been blocked.\n");
+ log_info ("Please see <https://yubi.co/ysa201701> for details\n");
+ err = gpg_error (GPG_ERR_NOT_SUPPORTED);
+ goto leave;
+ }
+
+ /* Divert to dedicated functions. */
+ if (info->apptype == APP_TYPE_OPENPGP)
+ {
+ if (opt_force || opt_algo || keyref)
+ log_info ("Note: Options are ignored for OpenPGP cards.\n");
+ err = generate_openpgp (info);
+ }
+ else if (!keyref)
+ err = gpg_error (GPG_ERR_INV_ID);
+ else
+ err = generate_generic (info, keyref, opt_force, opt_algo);
+
+ leave:
+ xfree (opt_algo);
+ xfree (keyref_buffer);
+ return err;
+}
+
+
+
+/* Sub-menu to change a PIN. */
+static gpg_error_t
+cmd_passwd (card_info_t info, char *argstr)
+{
+ gpg_error_t err;
+ char *answer = NULL;
+ const char *pinref;
+
+ if (!info)
+ return print_help
+ ("PASSWD [PINREF]\n\n"
+ "Menu to change or unblock the PINs. Note that the\n"
+ "presented menu options depend on the type of card\n"
+ "and whether the admin mode is enabled. For OpenPGP\n"
+ "and PIV cards defaults for PINREF are available.",
+ 0);
+
+ if (opt.interactive || opt.verbose)
+ log_info (_("%s card no. %s detected\n"),
+ app_type_string (info->apptype),
+ info->dispserialno? info->dispserialno : info->serialno);
+
+ if (!*argstr && info->apptype == APP_TYPE_OPENPGP)
+ {
+ /* For an OpenPGP card we present the well known menu if no
+ * argument is given. */
+ for (;;)
+ {
+ tty_printf ("\n");
+ tty_printf ("1 - change PIN\n"
+ "2 - unblock and set new PIN\n"
+ "3 - change Admin PIN\n"
+ "4 - set the Reset Code\n"
+ "Q - quit\n");
+ tty_printf ("\n");
+
+ err = 0;
+ xfree (answer);
+ answer = tty_get (_("Your selection? "));
+ tty_kill_prompt ();
+ if (*answer == CONTROL_D)
+ break; /* Quit. */
+ if (strlen (answer) != 1)
+ continue;
+ if (*answer == 'q' || *answer == 'Q')
+ break; /* Quit. */
+
+ if (*answer == '1')
+ {
+ /* Change PIN (same as the direct thing in non-admin mode). */
+ err = scd_change_pin ("OPENPGP.1", 0);
+ if (err)
+ log_error ("Error changing the PIN: %s\n", gpg_strerror (err));
+ else
+ log_info ("PIN changed.\n");
+ }
+ else if (*answer == '2')
+ {
+ /* Unblock PIN by setting a new PIN. */
+ err = scd_change_pin ("OPENPGP.1", 1);
+ if (err)
+ log_error ("Error unblocking the PIN: %s\n", gpg_strerror(err));
+ else
+ log_info ("PIN unblocked and new PIN set.\n");
+ }
+ else if (*answer == '3')
+ {
+ /* Change Admin PIN. */
+ err = scd_change_pin ("OPENPGP.3", 0);
+ if (err)
+ log_error ("Error changing the PIN: %s\n", gpg_strerror (err));
+ else
+ log_info ("PIN changed.\n");
+ }
+ else if (*answer == '4')
+ {
+ /* Set a new Reset Code. */
+ err = scd_change_pin ("OPENPGP.2", 1);
+ if (err)
+ log_error ("Error setting the Reset Code: %s\n",
+ gpg_strerror (err));
+ else
+ log_info ("Reset Code set.\n");
+ }
+
+ } /*end for loop*/
+ }
+ else
+ {
+ if (*argstr)
+ pinref = argstr;
+ else if (info->apptype == APP_TYPE_PIV)
+ pinref = "PIV.80";
+ else
+ {
+ /* Note that we do not have a default value for OpenPGP
+ * because we want to be mostly compatible to "gpg
+ * --card-edit" and show a menu in that case (above). */
+ err = gpg_error (GPG_ERR_MISSING_VALUE);
+ goto leave;
+ }
+ err = scd_change_pin (pinref, 0);
+ if (err)
+ goto leave;
+
+ if (info->apptype == APP_TYPE_PIV
+ && !ascii_strcasecmp (pinref, "PIV.81"))
+ log_info ("PUK changed.\n");
+ else
+ log_info ("PIN changed.\n");
+ }
+
+ leave:
+ xfree (answer);
+ return err;
+}
+
+
+static gpg_error_t
+cmd_unblock (card_info_t info)
+{
+ gpg_error_t err = 0;
+
+ if (!info)
+ return print_help
+ ("UNBLOCK\n\n"
+ "Unblock a PIN using a PUK or Reset Code. Note that OpenPGP\n"
+ "cards prior to version 2 can't use this; instead the PASSWD\n"
+ "command can be used to set a new PIN.",
+ 0);
+
+ if (opt.interactive || opt.verbose)
+ log_info (_("%s card no. %s detected\n"),
+ app_type_string (info->apptype),
+ info->dispserialno? info->dispserialno : info->serialno);
+
+ if (info->apptype == APP_TYPE_OPENPGP)
+ {
+ if (!info->is_v2)
+ {
+ log_error (_("This command is only available for version 2 cards\n"));
+ err = gpg_error (GPG_ERR_NOT_SUPPORTED);
+ }
+ else if (!info->chvinfo[1])
+ {
+ log_error (_("Reset Code not or not anymore available\n"));
+ err = gpg_error (GPG_ERR_PIN_BLOCKED);
+ }
+ else
+ {
+ err = scd_change_pin ("OPENPGP.2", 0);
+ if (!err)
+ log_info ("PIN changed.\n");
+ }
+ }
+ else if (info->apptype == APP_TYPE_PIV)
+ {
+ /* Unblock the Application PIN. */
+ err = scd_change_pin ("PIV.80", 1);
+ if (!err)
+ log_info ("PIN unblocked and changed.\n");
+ }
+ else
+ {
+ log_info ("Unblocking not yet supported for '%s'\n",
+ app_type_string (info->apptype));
+ err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+ }
+
+ return err;
+}
+
+
+/* Note: On successful execution a redisplay should be scheduled. If
+ * this function fails the card may be in an unknown state. */
+static gpg_error_t
+cmd_factoryreset (card_info_t info)
+{
+ gpg_error_t err;
+ char *answer = NULL;
+ int termstate = 0;
+ int any_apdu = 0;
+ int is_yubikey = 0;
+ int i;
+
+
+ if (!info)
+ return print_help
+ ("FACTORY-RESET\n\n"
+ "Do a complete reset of some OpenPGP and PIV cards. This\n"
+ "deletes all data and keys and resets the PINs to their default.\n"
+ "This is mainly used by developers with scratch cards. Don't\n"
+ "worry, you need to confirm before the command proceeds.",
+ APP_TYPE_OPENPGP, APP_TYPE_PIV, 0);
+
+ /* We support the factory reset for most OpenPGP cards and Yubikeys
+ * with the PIV application. */
+ if (info->apptype == APP_TYPE_OPENPGP)
+ ;
+ else if (info->apptype == APP_TYPE_PIV
+ && info->cardtype && !strcmp (info->cardtype, "yubikey"))
+ is_yubikey = 1;
+ else
+
+ return gpg_error (GPG_ERR_NOT_SUPPORTED);
+
+ /* For an OpenPGP card the code below basically does the same what
+ * this gpg-connect-agent script does:
+ *
+ * scd reset
+ * scd serialno undefined
+ * scd apdu 00 A4 04 00 06 D2 76 00 01 24 01
+ * scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
+ * scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
+ * scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
+ * scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
+ * scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
+ * scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
+ * scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
+ * scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
+ * scd apdu 00 e6 00 00
+ * scd apdu 00 44 00 00
+ * scd reset
+ * /echo Card has been reset to factory defaults
+ *
+ * For a PIV application on a Yubikey it merely issues the Yubikey
+ * specific resset command.
+ */
+
+ err = scd_learn (info);
+ if (gpg_err_code (err) == GPG_ERR_OBJ_TERM_STATE
+ && gpg_err_source (err) == GPG_ERR_SOURCE_SCD)
+ termstate = 1;
+ else if (err)
+ {
+ log_error (_("OpenPGP card not available: %s\n"), gpg_strerror (err));
+ goto leave;
+ }
+
+ if (opt.interactive || opt.verbose)
+ log_info (_("%s card no. %s detected\n"),
+ app_type_string (info->apptype),
+ info->dispserialno? info->dispserialno : info->serialno);
+
+ if (!termstate || is_yubikey)
+ {
+ if (!is_yubikey)
+ {
+ if (!(info->status_indicator == 3 || info->status_indicator == 5))
+ {
+ /* Note: We won't see status-indicator 3 here because it
+ * is not possible to select a card application in
+ * termination state. */
+ log_error (_("This command is not supported by this card\n"));
+ err = gpg_error (GPG_ERR_NOT_SUPPORTED);
+ goto leave;
+ }
+ }
+
+ tty_printf ("\n");
+ log_info
+ (_("Note: This command destroys all keys stored on the card!\n"));
+ tty_printf ("\n");
+ xfree (answer);
+ answer = tty_get (_("Continue? (y/N) "));
+ tty_kill_prompt ();
+ trim_spaces (answer);
+ if (*answer == CONTROL_D
+ || !answer_is_yes_no_default (answer, 0/*(default to no)*/))
+ {
+ err = gpg_error (GPG_ERR_CANCELED);
+ goto leave;
+ }
+
+ xfree (answer);
+ answer = tty_get (_("Really do a factory reset? (enter \"yes\") "));
+ tty_kill_prompt ();
+ trim_spaces (answer);
+ if (strcmp (answer, "yes") && strcmp (answer,_("yes")))
+ {
+ err = gpg_error (GPG_ERR_CANCELED);
+ goto leave;
+ }
+
+
+ if (is_yubikey)
+ {
+ /* The PIV application si already selected, we only need to
+ * send the special reset APDU after having blocked PIN and
+ * PUK. Note that blocking the PUK is done using the
+ * unblock PIN command. */
+ any_apdu = 1;
+ for (i=0; i < 5; i++)
+ send_apdu ("0020008008FFFFFFFFFFFFFFFF", "VERIFY", 0xffff,
+ NULL, NULL);
+ for (i=0; i < 5; i++)
+ send_apdu ("002C008010FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
+ "RESET RETRY COUNTER", 0xffff, NULL, NULL);
+ err = send_apdu ("00FB000001FF", "YUBIKEY RESET", 0, NULL, NULL);
+ if (err)
+ goto leave;
+ }
+ else /* OpenPGP card. */
+ {
+ any_apdu = 1;
+ /* We need to select a card application before we can send APDUs
+ * to the card without scdaemon doing anything on its own. */
+ err = send_apdu (NULL, "RESET", 0, NULL, NULL);
+ if (err)
+ goto leave;
+ err = send_apdu ("undefined", "dummy select ", 0, NULL, NULL);
+ if (err)
+ goto leave;
+ /* Select the OpenPGP application. */
+ err = send_apdu ("00A4040006D27600012401", "SELECT AID", 0,
+ NULL, NULL);
+ if (err)
+ goto leave;
+
+ /* Do some dummy verifies with wrong PINs to set the retry
+ * counter to zero. We can't easily use the card version 2.1
+ * feature of presenting the admin PIN to allow the terminate
+ * command because there is no machinery in scdaemon to catch
+ * the verify command and ask for the PIN when the "APDU"
+ * command is used.
+ * Here, the length of dummy wrong PIN is 32-byte, also
+ * supporting authentication with KDF DO. */
+ for (i=0; i < 4; i++)
+ send_apdu ("0020008120"
+ "40404040404040404040404040404040"
+ "40404040404040404040404040404040", "VERIFY", 0xffff,
+ NULL, NULL);
+ for (i=0; i < 4; i++)
+ send_apdu ("0020008320"
+ "40404040404040404040404040404040"
+ "40404040404040404040404040404040", "VERIFY", 0xffff,
+ NULL, NULL);
+
+ /* Send terminate datafile command. */
+ err = send_apdu ("00e60000", "TERMINATE DF", 0x6985, NULL, NULL);
+ if (err)
+ goto leave;
+ }
+ }
+
+ if (!is_yubikey)
+ {
+ any_apdu = 1;
+ /* Send activate datafile command. This is used without
+ * confirmation if the card is already in termination state. */
+ err = send_apdu ("00440000", "ACTIVATE DF", 0, NULL, NULL);
+ if (err)
+ goto leave;
+ }
+
+ /* Finally we reset the card reader once more. */
+ err = send_apdu (NULL, "RESET", 0, NULL, NULL);
+ if (err)
+ goto leave;
+
+ /* Then, connect the card again (answer used as a dummy). */
+ xfree (answer); answer = NULL;
+ err = scd_serialno (&answer, NULL);
+
+ leave:
+ if (err && any_apdu && !is_yubikey)
+ {
+ log_info ("Due to an error the card might be in an inconsistent state\n"
+ "You should run the LIST command to check this.\n");
+ /* FIXME: We need a better solution in the case that the card is
+ * in a termination state, i.e. the card was removed before the
+ * activate was sent. The best solution I found with v2.1
+ * Zeitcontrol card was to kill scdaemon and the issue this
+ * sequence with gpg-connect-agent:
+ * scd reset
+ * scd serialno undefined
+ * scd apdu 00A4040006D27600012401 (returns error)
+ * scd apdu 00440000
+ * Then kill scdaemon again and issue:
+ * scd reset
+ * scd serialno openpgp
+ */
+ }
+ xfree (answer);
+ return err;
+}
+
+
+/* Generate KDF data. This is a helper for cmd_kdfsetup. */
+static gpg_error_t
+gen_kdf_data (unsigned char *data, int single_salt)
+{
+ gpg_error_t err;
+ const unsigned char h0[] = { 0x81, 0x01, 0x03,
+ 0x82, 0x01, 0x08,
+ 0x83, 0x04 };
+ const unsigned char h1[] = { 0x84, 0x08 };
+ const unsigned char h2[] = { 0x85, 0x08 };
+ const unsigned char h3[] = { 0x86, 0x08 };
+ const unsigned char h4[] = { 0x87, 0x20 };
+ const unsigned char h5[] = { 0x88, 0x20 };
+ unsigned char *p, *salt_user, *salt_admin;
+ unsigned char s2k_char;
+ unsigned int iterations;
+ unsigned char count_4byte[4];
+
+ p = data;
+
+ s2k_char = encode_s2k_iterations (agent_get_s2k_count ());
+ iterations = S2K_DECODE_COUNT (s2k_char);
+ count_4byte[0] = (iterations >> 24) & 0xff;
+ count_4byte[1] = (iterations >> 16) & 0xff;
+ count_4byte[2] = (iterations >> 8) & 0xff;
+ count_4byte[3] = (iterations & 0xff);
+
+ memcpy (p, h0, sizeof h0);
+ p += sizeof h0;
+ memcpy (p, count_4byte, sizeof count_4byte);
+ p += sizeof count_4byte;
+ memcpy (p, h1, sizeof h1);
+ salt_user = (p += sizeof h1);
+ gcry_randomize (p, 8, GCRY_STRONG_RANDOM);
+ p += 8;
+
+ if (single_salt)
+ salt_admin = salt_user;
+ else
+ {
+ memcpy (p, h2, sizeof h2);
+ p += sizeof h2;
+ gcry_randomize (p, 8, GCRY_STRONG_RANDOM);
+ p += 8;
+ memcpy (p, h3, sizeof h3);
+ salt_admin = (p += sizeof h3);
+ gcry_randomize (p, 8, GCRY_STRONG_RANDOM);
+ p += 8;
+ }
+
+ memcpy (p, h4, sizeof h4);
+ p += sizeof h4;
+ err = gcry_kdf_derive (OPENPGP_USER_PIN_DEFAULT,
+ strlen (OPENPGP_USER_PIN_DEFAULT),
+ GCRY_KDF_ITERSALTED_S2K, GCRY_MD_SHA256,
+ salt_user, 8, iterations, 32, p);
+ p += 32;
+ if (!err)
+ {
+ memcpy (p, h5, sizeof h5);
+ p += sizeof h5;
+ err = gcry_kdf_derive (OPENPGP_ADMIN_PIN_DEFAULT,
+ strlen (OPENPGP_ADMIN_PIN_DEFAULT),
+ GCRY_KDF_ITERSALTED_S2K, GCRY_MD_SHA256,
+ salt_admin, 8, iterations, 32, p);
+ }
+
+ return err;
+}
+
+
+static gpg_error_t
+cmd_kdfsetup (card_info_t info, char *argstr)
+{
+ gpg_error_t err;
+ unsigned char kdf_data[OPENPGP_KDF_DATA_LENGTH_MAX];
+ int single = (*argstr != 0);
+
+ if (!info)
+ return print_help
+ ("KDF-SETUP\n\n"
+ "Prepare the OpenPGP card KDF feature for this card.",
+ APP_TYPE_OPENPGP, 0);
+
+ if (info->apptype != APP_TYPE_OPENPGP)
+ {
+ log_info ("Note: This is an OpenPGP only command.\n");
+ return gpg_error (GPG_ERR_NOT_SUPPORTED);
+ }
+
+ if (!info->extcap.kdf)
+ {
+ log_error (_("This command is not supported by this card\n"));
+ err = gpg_error (GPG_ERR_NOT_SUPPORTED);
+ goto leave;
+ }
+
+ err = gen_kdf_data (kdf_data, single);
+ if (err)
+ goto leave;
+
+ err = scd_setattr ("KDF", kdf_data,
+ single ? OPENPGP_KDF_DATA_LENGTH_MIN
+ /* */ : OPENPGP_KDF_DATA_LENGTH_MAX);
+ if (err)
+ goto leave;
+
+ err = scd_getattr ("KDF", info);
+
+ leave:
+ return err;
+}
+
+
+
+static void
+show_keysize_warning (void)
+{
+ static int shown;
+
+ if (shown)
+ return;
+ shown = 1;
+ tty_printf
+ (_("Note: There is no guarantee that the card supports the requested\n"
+ " key type or size. If the key generation does not succeed,\n"
+ " please check the documentation of your card to see which\n"
+ " key types and sizes are supported.\n")
+ );
+}
+
+
+/* Ask for the size of a card key. NBITS is the current size
+ * configured for the card. Returns 0 on success and stored the
+ * chosen key size at R_KEYSIZE; 0 is stored to indicate that the
+ * default size shall be used. */
+static gpg_error_t
+ask_card_rsa_keysize (unsigned int nbits, unsigned int *r_keysize)
+{
+ unsigned int min_nbits = 1024;
+ unsigned int max_nbits = 4096;
+ char*answer;
+ unsigned int req_nbits;
+
+ for (;;)
+ {
+ answer = tty_getf (_("What keysize do you want? (%u) "), nbits);
+ trim_spaces (answer);
+ tty_kill_prompt ();
+ if (*answer == CONTROL_D)
+ {
+ xfree (answer);
+ return gpg_error (GPG_ERR_CANCELED);
+ }
+ req_nbits = *answer? atoi (answer): nbits;
+ xfree (answer);
+
+ if (req_nbits != nbits && (req_nbits % 32) )
+ {
+ req_nbits = ((req_nbits + 31) / 32) * 32;
+ tty_printf (_("rounded up to %u bits\n"), req_nbits);
+ }
+
+ if (req_nbits == nbits)
+ {
+ /* Use default. */
+ *r_keysize = 0;
+ return 0;
+ }
+
+ if (req_nbits < min_nbits || req_nbits > max_nbits)
+ {
+ tty_printf (_("%s keysizes must be in the range %u-%u\n"),
+ "RSA", min_nbits, max_nbits);
+ }
+ else
+ {
+ *r_keysize = req_nbits;
+ return 0;
+ }
+ }
+}
+
+
+/* Ask for the key attribute of a card key. CURRENT is the current
+ * attribute configured for the card. KEYNO is the number of the key
+ * used to select the prompt. Stores NULL at result to use the
+ * default attribute or stores the selected attribute structure at
+ * RESULT. On error an error code is returned. */
+static gpg_error_t
+ask_card_keyattr (int keyno, const struct key_attr *current,
+ struct key_attr **result)
+{
+ gpg_error_t err;
+ struct key_attr *key_attr = NULL;
+ char *answer = NULL;
+ int selection;
+
+ *result = NULL;
+
+ key_attr = xcalloc (1, sizeof *key_attr);
+
+ tty_printf (_("Changing card key attribute for: "));
+ if (keyno == 0)
+ tty_printf (_("Signature key\n"));
+ else if (keyno == 1)
+ tty_printf (_("Encryption key\n"));
+ else
+ tty_printf (_("Authentication key\n"));
+
+ tty_printf (_("Please select what kind of key you want:\n"));
+ tty_printf (_(" (%d) RSA\n"), 1 );
+ tty_printf (_(" (%d) ECC\n"), 2 );
+
+ for (;;)
+ {
+ xfree (answer);
+ answer = tty_get (_("Your selection? "));
+ trim_spaces (answer);
+ tty_kill_prompt ();
+ if (!*answer || *answer == CONTROL_D)
+ {
+ err = gpg_error (GPG_ERR_CANCELED);
+ goto leave;
+ }
+ selection = *answer? atoi (answer) : 0;
+
+ if (selection == 1 || selection == 2)
+ break;
+ else
+ tty_printf (_("Invalid selection.\n"));
+ }
+
+
+ if (selection == 1)
+ {
+ unsigned int nbits, result_nbits;
+
+ if (current->algo == PUBKEY_ALGO_RSA)
+ nbits = current->nbits;
+ else
+ nbits = 2048;
+
+ err = ask_card_rsa_keysize (nbits, &result_nbits);
+ if (err)
+ goto leave;
+ if (result_nbits == 0)
+ {
+ if (current->algo == PUBKEY_ALGO_RSA)
+ {
+ xfree (key_attr);
+ key_attr = NULL;
+ }
+ else
+ result_nbits = nbits;
+ }
+
+ if (key_attr)
+ {
+ key_attr->algo = PUBKEY_ALGO_RSA;
+ key_attr->nbits = result_nbits;
+ }
+ }
+ else if (selection == 2)
+ {
+ const char *curve;
+ /* const char *oid_str; */
+ int algo;
+
+ if (current->algo == PUBKEY_ALGO_RSA)
+ {
+ if (keyno == 1) /* Encryption key */
+ algo = PUBKEY_ALGO_ECDH;
+ else /* Signature key or Authentication key */
+ algo = PUBKEY_ALGO_ECDSA;
+ curve = NULL;
+ }
+ else
+ {
+ algo = current->algo;
+ curve = current->curve;
+ }
+
+ (void)curve;
+ (void)algo;
+ err = GPG_ERR_NOT_IMPLEMENTED;
+ goto leave;
+ /* FIXME: We need to move 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 openpgp algo names but in the error
+ * message we use the gcrypt names. We should settle for a
+ * consistent solution. */
+ log_error (_("public key algorithm %d (%s) is not supported\n"),
+ key_attr->algo, gcry_pk_algo_name (key_attr->algo));
+ err = gpg_error (GPG_ERR_PUBKEY_ALGO);
+ goto leave;
+ }
+
+ err = scd_setattr ("KEY-ATTR", args, strlen (args));
+ if (err)
+ log_error (_("error changing key attribute for key %d: %s\n"),
+ keyno+1, gpg_strerror (err));
+ leave:
+ return err;
+}
+
+
+static gpg_error_t
+cmd_keyattr (card_info_t info, char *argstr)
+{
+ gpg_error_t err = 0;
+ int keyno;
+ struct key_attr *key_attr = NULL;
+
+ (void)argstr;
+
+ if (!info)
+ return print_help
+ ("KEY-ATTR\n\n"
+ "Menu to change the key attributes of an OpenPGP card.",
+ APP_TYPE_OPENPGP, 0);
+
+ if (info->apptype != APP_TYPE_OPENPGP)
+ {
+ log_info ("Note: This is an OpenPGP only command.\n");
+ return gpg_error (GPG_ERR_NOT_SUPPORTED);
+ }
+
+ if (!(info->is_v2 && info->extcap.aac))
+ {
+ log_error (_("This command is not supported by this card\n"));
+ err = gpg_error (GPG_ERR_NOT_SUPPORTED);
+ goto leave;
+ }
+
+ for (keyno = 0; keyno < DIM (info->key_attr); keyno++)
+ {
+ xfree (key_attr);
+ key_attr = NULL;
+ err = ask_card_keyattr (keyno, &info->key_attr[keyno], &key_attr);
+ if (err)
+ goto leave;
+
+ err = do_change_keyattr (keyno, key_attr);
+ if (err)
+ {
+ /* Error: Better read the default key attribute again. */
+ log_debug ("FIXME\n");
+ /* Ask again for this key. */
+ keyno--;
+ }
+ }
+
+ leave:
+ xfree (key_attr);
+ return err;
+}
+
+
+static gpg_error_t
+cmd_uif (card_info_t info, char *argstr)
+{
+ gpg_error_t err;
+ int keyno;
+
+ if (!info)
+ return print_help
+ ("UIF N [on|off|permanent]\n\n"
+ "Change the User Interaction Flag. N must in the range 1 to 3.",
+ APP_TYPE_OPENPGP, APP_TYPE_PIV, 0);
+
+ argstr = skip_options (argstr);
+
+ if (digitp (argstr))
+ {
+ keyno = atoi (argstr);
+ while (digitp (argstr))
+ argstr++;
+ while (spacep (argstr))
+ argstr++;
+ }
+ else
+ keyno = 0;
+
+ if (keyno < 1 || keyno > 3)
+ {
+ err = gpg_error (GPG_ERR_INV_ARG);
+ goto leave;
+ }
+
+
+ err = GPG_ERR_NOT_IMPLEMENTED;
+
+ leave:
+ return err;
+}
+
+
+static gpg_error_t
+cmd_yubikey (card_info_t info, char *argstr)
+{
+ gpg_error_t err, err2;
+ estream_t fp = opt.interactive? NULL : es_stdout;
+ char *words[20];
+ int nwords;
+
+ if (!info)
+ return print_help
+ ("YUBIKEY <cmd> args\n\n"
+ "Various commands pertaining to Yubikey tokens with <cmd> being:\n"
+ "\n"
+ " LIST \n"
+ "\n"
+ "List supported and enabled applications.\n"
+ "\n"
+ " ENABLE usb|nfc|all [otp|u2f|opgp|piv|oath|fido2|all]\n"
+ " DISABLE usb|nfc|all [otp|u2f|opgp|piv|oath|fido2|all]\n"
+ "\n"
+ "Enable or disable the specified or all applications on the\n"
+ "given interface.",
+ 0);
+
+ argstr = skip_options (argstr);
+
+ if (!info->cardtype || strcmp (info->cardtype, "yubikey"))
+ {
+ log_info ("This command can only be used with Yubikeys.\n");
+ err = gpg_error (GPG_ERR_NOT_SUPPORTED);
+ goto leave;
+ }
+
+ nwords = split_fields (argstr, words, DIM (words));
+ if (nwords < 1)
+ {
+ err = gpg_error (GPG_ERR_SYNTAX);
+ goto leave;
+ }
+
+
+ /* Note that we always do a learn to get a chance to the card back
+ * into a usable state. */
+ err = yubikey_commands (fp, nwords, words);
+ err2 = scd_learn (info);
+ if (err2)
+ log_error ("Error re-reading card: %s\n", gpg_strerror (err));
+
+ leave:
+ return err;
+}
+
+
+
+/* Data used by the command parser. This needs to be outside of the
+ * function scope to allow readline based command completion. */
+enum cmdids
+ {
+ cmdNOP = 0,
+ cmdQUIT, cmdHELP, cmdLIST, cmdRESET, cmdVERIFY,
+ cmdNAME, cmdURL, cmdFETCH, cmdLOGIN, cmdLANG, cmdSALUT, cmdCAFPR,
+ cmdFORCESIG, cmdGENERATE, cmdPASSWD, cmdPRIVATEDO, cmdWRITECERT,
+ cmdREADCERT, cmdWRITEKEY, cmdUNBLOCK, cmdFACTRST, cmdKDFSETUP,
+ cmdKEYATTR, cmdUIF, cmdAUTH, cmdYUBIKEY,
+ cmdINVCMD
+ };
+
+static struct
+{
+ const char *name;
+ enum cmdids id;
+ const char *desc;
+} cmds[] = {
+ { "quit" , cmdQUIT, N_("quit this menu")},
+ { "q" , cmdQUIT, NULL },
+ { "help" , cmdHELP, N_("show this help")},
+ { "?" , cmdHELP, NULL },
+ { "list" , cmdLIST, N_("list all available data")},
+ { "l" , cmdLIST, NULL },
+ { "name" , cmdNAME, N_("change card holder's name")},
+ { "url" , cmdURL, N_("change URL to retrieve key")},
+ { "fetch" , cmdFETCH, N_("fetch the key specified in the card URL")},
+ { "login" , cmdLOGIN, N_("change the login name")},
+ { "lang" , cmdLANG, N_("change the language preferences")},
+ { "salutation",cmdSALUT, N_("change card holder's salutation")},
+ { "salut" , cmdSALUT, NULL },
+ { "cafpr" , cmdCAFPR , N_("change a CA fingerprint")},
+ { "forcesig", cmdFORCESIG, N_("toggle the signature force PIN flag")},
+ { "generate", cmdGENERATE, N_("generate new keys")},
+ { "passwd" , cmdPASSWD, N_("menu to change or unblock the PIN")},
+ { "verify" , cmdVERIFY, N_("verify the PIN and list all data")},
+ { "unblock" , cmdUNBLOCK, N_("unblock the PIN using a Reset Code")},
+ { "authenticate",cmdAUTH, N_("authenticate to the card")},
+ { "auth" , cmdAUTH, NULL },
+ { "reset" , cmdRESET, N_("send a reset to the card daemon")},
+ { "factory-reset",cmdFACTRST, N_("destroy all keys and data")},
+ { "kdf-setup", cmdKDFSETUP, N_("setup KDF for PIN authentication")},
+ { "key-attr", cmdKEYATTR, N_("change the key attribute")},
+ { "uif", cmdUIF, N_("change the User Interaction Flag")},
+ { "privatedo", cmdPRIVATEDO, N_("change a private data object")},
+ { "readcert", cmdREADCERT, N_("read a certificate from a data object")},
+ { "writecert", cmdWRITECERT, N_("store a certificate to a data object")},
+ { "writekey", cmdWRITEKEY, N_("store a private key to a data object")},
+ { "yubikey", cmdYUBIKEY, N_("Yubikey management commands")},
+ { NULL, cmdINVCMD, NULL }
+};
+
+
+/* The command line command dispatcher. */
+static gpg_error_t
+dispatch_command (card_info_t info, const char *orig_command)
+{
+ gpg_error_t err = 0;
+ enum cmdids cmd; /* The command. */
+ char *command; /* A malloced copy of ORIG_COMMAND. */
+ char *argstr; /* The argument as a string. */
+ int i;
+ int ignore_error;
+
+ if ((ignore_error = *orig_command == '-'))
+ orig_command++;
+ command = xstrdup (orig_command);
+ argstr = NULL;
+ if ((argstr = strchr (command, ' ')))
+ {
+ *argstr++ = 0;
+ trim_spaces (command);
+ trim_spaces (argstr);
+ }
+
+ for (i=0; cmds[i].name; i++ )
+ if (!ascii_strcasecmp (command, cmds[i].name ))
+ break;
+ cmd = cmds[i].id; /* (If not found this will be cmdINVCMD). */
+
+ /* Make sure we have valid strings for the args. They are allowed
+ * to be modified and must thus point to a buffer. */
+ if (!argstr)
+ argstr = command + strlen (command);
+
+ /* For most commands we need to make sure that we have a card. */
+ if (!info)
+ ; /* Help mode */
+ else if (!(cmd == cmdNOP || cmd == cmdQUIT || cmd == cmdHELP
+ || cmd == cmdINVCMD)
+ && !info->initialized)
+ {
+ err = scd_learn (info);
+ if (err)
+ {
+ log_error ("Error reading card: %s\n", gpg_strerror (err));
+ goto leave;
+ }
+ }
+
+ switch (cmd)
+ {
+ case cmdNOP:
+ if (!info)
+ print_help ("NOP\n\n"
+ "Dummy command.", 0);
+ break;
+
+ case cmdQUIT:
+ if (!info)
+ print_help ("QUIT\n\n"
+ "Stop processing.", 0);
+ else
+ {
+ err = gpg_error (GPG_ERR_EOF);
+ goto leave;
+ }
+ break;
+
+ case cmdHELP:
+ if (!info)
+ print_help ("HELP [command]\n\n"
+ "Show all commands. With an argument show help\n"
+ "for that command.", 0);
+ else if (*argstr)
+ dispatch_command (NULL, argstr);
+ else
+ {
+ es_printf
+ ("List of commands (\"help <command>\" for details):\n");
+ for (i=0; cmds[i].name; i++ )
+ if(cmds[i].desc)
+ es_printf("%-14s %s\n", cmds[i].name, _(cmds[i].desc) );
+ es_printf ("Prefix a command with a dash to ignore its error.\n");
+ }
+ break;
+
+ case cmdLIST:
+ if (!info)
+ print_help ("LIST\n\n"
+ "Show content of the card.", 0);
+ else
+ {
+ err = scd_learn (info);
+ if (err)
+ log_error ("Error reading card: %s\n", gpg_strerror (err));
+ else
+ list_card (info);
+ }
+ break;
+
+ case cmdRESET:
+ if (!info)
+ print_help ("RESET\n\n"
+ "Send a RESET to the card daemon.", 0);
+ else
+ {
+ flush_keyblock_cache ();
+ err = scd_apdu (NULL, NULL, NULL, NULL);
+ }
+ break;
+
+ case cmdVERIFY: err = cmd_verify (info, argstr); break;
+ case cmdAUTH: err = cmd_authenticate (info, argstr); break;
+ case cmdNAME: err = cmd_name (info, argstr); break;
+ case cmdURL: err = cmd_url (info, argstr); break;
+ case cmdFETCH: err = cmd_fetch (info); break;
+ case cmdLOGIN: err = cmd_login (info, argstr); break;
+ case cmdLANG: err = cmd_lang (info, argstr); break;
+ case cmdSALUT: err = cmd_salut (info, argstr); break;
+ case cmdCAFPR: err = cmd_cafpr (info, argstr); break;
+ case cmdPRIVATEDO: err = cmd_privatedo (info, argstr); break;
+ case cmdWRITECERT: err = cmd_writecert (info, argstr); break;
+ case cmdREADCERT: err = cmd_readcert (info, argstr); break;
+ case cmdWRITEKEY: err = cmd_writekey (info, argstr); break;
+ case cmdFORCESIG: err = cmd_forcesig (info); break;
+ case cmdGENERATE: err = cmd_generate (info, argstr); break;
+ case cmdPASSWD: err = cmd_passwd (info, argstr); break;
+ case cmdUNBLOCK: err = cmd_unblock (info); break;
+ case cmdFACTRST: err = cmd_factoryreset (info); break;
+ case cmdKDFSETUP: err = cmd_kdfsetup (info, argstr); break;
+ case cmdKEYATTR: err = cmd_keyattr (info, argstr); break;
+ case cmdUIF: err = cmd_uif (info, argstr); break;
+ case cmdYUBIKEY: err = cmd_yubikey (info, argstr); break;
+
+ case cmdINVCMD:
+ default:
+ log_error (_("Invalid command (try \"help\")\n"));
+ break;
+ } /* End command switch. */
+
+
+ leave:
+ /* Return GPG_ERR_EOF only if its origin was "quit". */
+ es_fflush (es_stdout);
+ if (gpg_err_code (err) == GPG_ERR_EOF && cmd != cmdQUIT)
+ err = gpg_error (GPG_ERR_GENERAL);
+ if (err && gpg_err_code (err) != GPG_ERR_EOF)
+ {
+ if (ignore_error)
+ {
+ log_info ("Command '%s' failed: %s\n", command, gpg_strerror (err));
+ err = 0;
+ }
+ else
+ log_error ("Command '%s' failed: %s\n", command, gpg_strerror (err));
+ }
+ xfree (command);
+
+ return err;
+}
+
+
+/* The interactive main loop. */
+static void
+interactive_loop (void)
+{
+ gpg_error_t err;
+ char *answer = NULL; /* The input line. */
+ enum cmdids cmd = cmdNOP; /* The command. */
+ char *argstr; /* The argument as a string. */
+ int redisplay = 1; /* Whether to redisplay the main info. */
+ char *help_arg = NULL; /* Argument of the HELP command. */
+ struct card_info_s info_buffer = { 0 };
+ card_info_t info = &info_buffer;
+ char *p;
+ int i;
+
+ /* In the interactive mode we do not want to print the program prefix. */
+ log_set_prefix (NULL, 0);
+
+ for (;;)
+ {
+ if (help_arg)
+ {
+ /* Clear info to indicate helpmode */
+ info = NULL;
+ }
+ else if (!info)
+ {
+ /* Get out of help. */
+ info = &info_buffer;
+ help_arg = NULL;
+ redisplay = 0;
+ }
+ else if (redisplay)
+ {
+ err = scd_learn (info);
+ if (err)
+ {
+ log_error ("Error reading card: %s\n", gpg_strerror (err));
+ }
+ else
+ {
+ list_card (info);
+ tty_printf("\n");
+ redisplay = 0;
+ }
+ }
+
+ if (!info)
+ {
+ /* Copy the pending help arg into our answer. Noe that
+ * help_arg points into answer. */
+ p = xstrdup (help_arg);
+ help_arg = NULL;
+ xfree (answer);
+ answer = p;
+ }
+ else
+ {
+ do
+ {
+ xfree (answer);
+ tty_enable_completion (command_completion);
+ answer = tty_get (_("gpg/card> "));
+ tty_kill_prompt();
+ tty_disable_completion ();
+ trim_spaces(answer);
+ }
+ while ( *answer == '#' );
+ }
+
+ argstr = NULL;
+ 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;
+ }
+
+ /* Make sure we have valid strings for the args. They are
+ * allowed to be modified and must thus point to a buffer. */
+ if (!argstr)
+ argstr = answer + strlen (answer);
+
+ if (!(cmd == cmdNOP || cmd == cmdQUIT || cmd == cmdHELP
+ || cmd == cmdINVCMD))
+ {
+ /* If redisplay is set we know that there was an error reading
+ * the card. In this case we force a LIST command to retry. */
+ if (!info)
+ ; /* In help mode. */
+ else if (redisplay)
+ {
+ cmd = cmdLIST;
+ }
+ 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;
+ }
+ }
+
+ 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 <command>\" for details):\n");
+ for (i=0; cmds[i].name; i++ )
+ if(cmds[i].desc)
+ tty_printf("%-14s %s\n", cmds[i].name, _(cmds[i].desc) );
+ }
+ break;
+
+ case cmdLIST:
+ if (!info)
+ print_help ("LIST\n\n"
+ "Show content of the card.", 0);
+ else
+ {
+ /* Actual work is done by the redisplay code block. */
+ redisplay = 1;
+ }
+ break;
+
+ case cmdRESET:
+ if (!info)
+ print_help ("RESET\n\n"
+ "Send a RESET to the card daemon.", 0);
+ else
+ {
+ flush_keyblock_cache ();
+ err = scd_apdu (NULL, NULL, NULL, NULL);
+ }
+ break;
+
+ case cmdVERIFY:
+ err = cmd_verify (info, argstr);
+ if (!err)
+ redisplay = 1;
+ break;
+ case cmdAUTH: err = cmd_authenticate (info, argstr); break;
+ case cmdNAME: err = cmd_name (info, argstr); break;
+ case cmdURL: err = cmd_url (info, argstr); break;
+ case cmdFETCH: err = cmd_fetch (info); break;
+ case cmdLOGIN: err = cmd_login (info, argstr); break;
+ case cmdLANG: err = cmd_lang (info, argstr); break;
+ case cmdSALUT: err = cmd_salut (info, argstr); break;
+ case cmdCAFPR: err = cmd_cafpr (info, argstr); break;
+ case cmdPRIVATEDO: err = cmd_privatedo (info, argstr); break;
+ case cmdWRITECERT: err = cmd_writecert (info, argstr); break;
+ case cmdREADCERT: err = cmd_readcert (info, argstr); break;
+ case cmdWRITEKEY: err = cmd_writekey (info, argstr); break;
+ case cmdFORCESIG: err = cmd_forcesig (info); break;
+ case cmdGENERATE: err = cmd_generate (info, argstr); break;
+ case cmdPASSWD: err = cmd_passwd (info, argstr); break;
+ case cmdUNBLOCK: err = cmd_unblock (info); break;
+ case cmdFACTRST:
+ err = cmd_factoryreset (info);
+ if (!err)
+ redisplay = 1;
+ break;
+ case cmdKDFSETUP: err = cmd_kdfsetup (info, argstr); break;
+ case cmdKEYATTR: err = cmd_keyattr (info, argstr); break;
+ case cmdUIF: err = cmd_uif (info, argstr); break;
+ case cmdYUBIKEY: err = cmd_yubikey (info, argstr); break;
+
+ case cmdINVCMD:
+ default:
+ tty_printf ("\n");
+ tty_printf (_("Invalid command (try \"help\")\n"));
+ break;
+ } /* End command switch. */
+
+ if (gpg_err_code (err) == GPG_ERR_CANCELED)
+ tty_fprintf (NULL, "\n");
+ else if (err)
+ {
+ const char *s = "?";
+ for (i=0; cmds[i].name; i++ )
+ if (cmd == cmds[i].id)
+ {
+ s = cmds[i].name;
+ break;
+ }
+ log_error ("Command '%s' failed: %s\n", s, gpg_strerror (err));
+ }
+
+ } /* End of main menu loop. */
+
+ leave:
+ release_card_info (info);
+ xfree (answer);
+}
+
+#ifdef HAVE_LIBREADLINE
+/* Helper function for readline's command completion. */
+static char *
+command_generator (const char *text, int state)
+{
+ static int list_index, len;
+ const char *name;
+
+ /* If this is a new word to complete, initialize now. This includes
+ * saving the length of TEXT for efficiency, and initializing the
+ index variable to 0. */
+ if (!state)
+ {
+ list_index = 0;
+ len = strlen(text);
+ }
+
+ /* Return the next partial match */
+ while ((name = cmds[list_index].name))
+ {
+ /* Only complete commands that have help text. */
+ if (cmds[list_index++].desc && !strncmp (name, text, len))
+ return strdup(name);
+ }
+
+ return NULL;
+}
+
+/* Second helper function for readline's command completion. */
+static char **
+command_completion (const char *text, int start, int end)
+{
+ (void)end;
+
+ /* If we are at the start of a line, we try and command-complete.
+ * If not, just do nothing for now. The support for help completion
+ * needs to be more smarter. */
+ if (!start)
+ return rl_completion_matches (text, command_generator);
+ else if (start == 5 && !ascii_strncasecmp (rl_line_buffer, "help ", 5))
+ return rl_completion_matches (text, command_generator);
+
+ rl_attempted_completion_over = 1;
+
+ return NULL;
+}
+#endif /*HAVE_LIBREADLINE*/
diff --git a/tools/gpg-card.h b/tools/gpg-card.h
new file mode 100644
index 000000000..099ea5448
--- /dev/null
+++ b/tools/gpg-card.h
@@ -0,0 +1,230 @@
+/* gpg-card.h - Common definitions for the gpg-card-tool
+ * Copyright (C) 2019 g10 Code GmbH
+ *
+ * This file is part of GnuPG.
+ *
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://gnu.org/licenses/>.
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#ifndef GNUPG_GPG_CARD_H
+#define GNUPG_GPG_CARD_H
+
+#include "../common/session-env.h"
+
+
+/* We keep all global options in the structure OPT. */
+struct
+{
+ int interactive;
+ int verbose;
+ unsigned int debug;
+ int quiet;
+ int with_colons;
+ const char *gpg_program;
+ const char *gpgsm_program;
+ const char *agent_program;
+ int autostart;
+
+ /* Options passed to the gpg-agent: */
+ session_env_t session_env;
+ char *lc_ctype;
+ char *lc_messages;
+
+} opt;
+
+/* Debug values and macros. */
+#define DBG_IPC_VALUE 1024 /* Debug assuan communication. */
+#define DBG_EXTPROG_VALUE 16384 /* Debug external program calls */
+
+#define DBG_IPC (opt.debug & DBG_IPC_VALUE)
+#define DBG_EXTPROG (opt.debug & DBG_EXTPROG_VALUE)
+
+/* The maximum length of a binary fingerprint. */
+#define MAX_FINGERPRINT_LEN 32
+
+
+/*
+ * Data structures to store keyblocks (aka certificates).
+ */
+struct pubkey_s
+{
+ struct pubkey_s *next; /* The next key. */
+ unsigned char grip[KEYGRIP_LEN];
+ unsigned char fpr[MAX_FINGERPRINT_LEN];
+ unsigned char fprlen; /* The used length of a FPR. */
+ unsigned int grip_valid:1;/* The grip is valid. */
+ unsigned int requested: 1;/* This is the requested grip. */
+};
+typedef struct pubkey_s *pubkey_t;
+
+struct userid_s
+{
+ struct userid_s *next;
+ char *value; /* Malloced. */
+};
+typedef struct userid_s *userid_t;
+
+struct keyblock_s
+{
+ struct keyblock_s *next; /* Allow to link several keyblocks. */
+ int protocol; /* GPGME_PROTOCOL_OPENPGP or _CMS. */
+ pubkey_t keys; /* The key. For OpenPGP primary + list of subkeys. */
+ userid_t uids; /* The list of user ids. */
+};
+typedef struct keyblock_s *keyblock_t;
+
+
+
+/* Enumeration of the known card application types. */
+typedef enum
+ {
+ APP_TYPE_NONE, /* Not yet known or for direct APDU sending. */
+ APP_TYPE_OPENPGP,
+ APP_TYPE_NKS,
+ APP_TYPE_DINSIG,
+ APP_TYPE_P15,
+ APP_TYPE_GELDKARTE,
+ APP_TYPE_SC_HSM,
+ APP_TYPE_PIV,
+ APP_TYPE_UNKNOWN /* Unknown by this tool. */
+ } app_type_t;
+
+
+/* OpenPGP card key attributes. */
+struct key_attr
+{
+ int algo; /* Algorithm identifier. */
+ union {
+ unsigned int nbits; /* Supported keysize. */
+ const char *curve; /* Name of curve. */
+ };
+};
+
+/* An object to store information pertaining to a key pair as stored
+ * on a card. This is commonly used as a linked list with all keys
+ * known for the current card. */
+struct key_info_s
+{
+ struct key_info_s *next;
+
+ unsigned char grip[20];/* The keygrip. */
+
+ unsigned char xflag; /* Temporary flag to help processing a list. */
+
+ /* The three next items are mostly useful for OpenPGP cards. */
+ unsigned char fprlen; /* Use length of the next item. */
+ unsigned char fpr[32]; /* The binary fingerprint of length FPRLEN. */
+ u32 created; /* The time the key was created. */
+ unsigned int usage; /* Usage flags. (GCRY_PK_USAGE_*) */
+ char keyref[1]; /* String with the keyref (e.g. OPENPGP.1). */
+};
+typedef struct key_info_s *key_info_t;
+
+
+/*
+ * The object used to store information about a card.
+ */
+struct card_info_s
+{
+ int initialized; /* True if a learn command was successful. */
+ int error; /* private. */
+ char *reader; /* Reader information. */
+ char *cardtype; /* NULL or type of the card. */
+ unsigned int cardversion; /* Firmware version of the card. */
+ char *apptypestr; /* Malloced application type string. */
+ app_type_t apptype;/* Translated from APPTYPESTR. */
+ unsigned int appversion; /* Version of the application. */
+ char *serialno; /* malloced hex string. */
+ char *dispserialno;/* malloced string. */
+ char *disp_name; /* malloced. */
+ char *disp_lang; /* malloced. */
+ int disp_sex; /* 0 = unspecified, 1 = male, 2 = female */
+ char *pubkey_url; /* malloced. */
+ char *login_data; /* malloced. */
+ char *private_do[4]; /* malloced. */
+ char cafpr1len; /* Length of the CA-fingerprint or 0 if invalid. */
+ char cafpr2len;
+ char cafpr3len;
+ char cafpr1[20];
+ char cafpr2[20];
+ char cafpr3[20];
+ key_info_t kinfo; /* Linked list with all keypair related data. */
+ unsigned long sig_counter;
+ int chv1_cached; /* For openpgp this is true if a PIN is not
+ required for each signing. Note that the
+ gpg-agent might cache it anyway. */
+ int is_v2; /* True if this is a v2 openpgp card. */
+ int chvmaxlen[3]; /* Maximum allowed length of a CHV. */
+ int chvinfo[3]; /* Allowed retries for the CHV; 0 = blocked. */
+ unsigned char chvusage[2]; /* Data object 5F2F */
+ struct key_attr key_attr[3]; /* OpenPGP card key attributes. */
+ struct {
+ unsigned int ki:1; /* Key import available. */
+ unsigned int aac:1; /* Algorithm attributes are changeable. */
+ unsigned int kdf:1; /* KDF object to support PIN hashing available. */
+ unsigned int bt:1; /* Button for confirmation available. */
+ } extcap;
+ unsigned int status_indicator;
+ int kdf_do_enabled; /* True if card has a KDF object. */
+ int uif[3]; /* True if User Interaction Flag is on. */
+};
+typedef struct card_info_s *card_info_t;
+
+
+/*-- card-keys.c --*/
+void release_keyblock (keyblock_t keyblock);
+void flush_keyblock_cache (void);
+gpg_error_t get_matching_keys (const unsigned char *keygrip, int protocol,
+ keyblock_t *r_keyblock);
+gpg_error_t test_get_matching_keys (const char *hexgrip);
+
+
+/*-- card-misc.c --*/
+key_info_t find_kinfo (card_info_t info, const char *keyref);
+void *hex_to_buffer (const char *string, size_t *r_length);
+gpg_error_t send_apdu (const char *hexapdu, const char *desc,
+ unsigned int ignore,
+ unsigned char **r_data, size_t *r_datalen);
+
+/*-- card-call-scd.c --*/
+void release_card_info (card_info_t info);
+const char *app_type_string (app_type_t app_type);
+
+gpg_error_t scd_apdu (const char *hexapdu, unsigned int *r_sw,
+ unsigned char **r_data, size_t *r_datalen);
+gpg_error_t scd_learn (card_info_t info);
+gpg_error_t scd_getattr (const char *name, struct card_info_s *info);
+gpg_error_t scd_setattr (const char *name,
+ const unsigned char *value, size_t valuelen);
+gpg_error_t scd_writecert (const char *certidstr,
+ const unsigned char *certdata, size_t certdatalen);
+gpg_error_t scd_writekey (const char *keyref, int force, const char *keygrip);
+gpg_error_t scd_genkey (const char *keyref, int force, const char *algo,
+ u32 *createtime);
+gpg_error_t scd_serialno (char **r_serialno, const char *demand);
+gpg_error_t scd_readcert (const char *certidstr,
+ void **r_buf, size_t *r_buflen);
+gpg_error_t scd_readkey (const char *keyrefstr, gcry_sexp_t *r_result);
+gpg_error_t scd_cardlist (strlist_t *result);
+gpg_error_t scd_change_pin (const char *pinref, int reset_mode);
+gpg_error_t scd_checkpin (const char *serialno);
+
+unsigned long agent_get_s2k_count (void);
+
+/*-- card-yubikey.c --*/
+gpg_error_t yubikey_commands (estream_t fp, int argc, char *argv[]);
+
+
+#endif /*GNUPG_GPG_CARD_H*/
diff --git a/tools/gpg-check-pattern.c b/tools/gpg-check-pattern.c
index 4db8f3706..dee5d5d47 100644
--- a/tools/gpg-check-pattern.c
+++ b/tools/gpg-check-pattern.c
@@ -91,7 +91,7 @@ static struct
enum {
PAT_NULL, /* Indicates end of the array. */
PAT_STRING, /* The pattern is a simple string. */
- PAT_REGEX /* The pattern is an extended regualr expression. */
+ PAT_REGEX /* The pattern is an extended regular expression. */
};
diff --git a/tools/gpg-connect-agent-w32info.rc b/tools/gpg-connect-agent-w32info.rc
index 4e7b19d35..8c6735918 100644
--- a/tools/gpg-connect-agent-w32info.rc
+++ b/tools/gpg-connect-agent-w32info.rc
@@ -1,4 +1,4 @@
-/* scdaemon-w32info.rc -*- c -*-
+/* gpg-connect-agent-w32info.rc -*- c -*-
* Copyright (C) 2013 g10 Code GmbH
*
* This file is free software; as a special exception the author gives
diff --git a/tools/gpg-connect-agent.c b/tools/gpg-connect-agent.c
index 00482a32e..7eb7ffa3a 100644
--- a/tools/gpg-connect-agent.c
+++ b/tools/gpg-connect-agent.c
@@ -983,7 +983,7 @@ do_open (char *line)
name, mode, strerror (errno));
return;
}
- fd = fileno (fp);
+ fd = dup (fileno (fp));
if (fd >= 0 && fd < DIM (open_fd_table))
{
open_fd_table[fd].inuse = 1;
@@ -1030,8 +1030,10 @@ do_open (char *line)
else
{
log_error ("can't put fd %d into table\n", fd);
- close (fd);
+ if (fd != -1)
+ close (fd); /* Table was full. */
}
+ fclose (fp);
}
diff --git a/tools/gpg-pair-tool.c b/tools/gpg-pair-tool.c
new file mode 100644
index 000000000..347b29d24
--- /dev/null
+++ b/tools/gpg-pair-tool.c
@@ -0,0 +1,2020 @@
+/* gpg-pair-tool.c - The tool to run the pairing protocol.
+ * Copyright (C) 2018 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 Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 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 Lesser General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+/* Protocol:
+ *
+ * Initiator Responder
+ * | |
+ * | COMMIT |
+ * |-------------------->|
+ * | |
+ * | DHPART1 |
+ * |<--------------------|
+ * | |
+ * | DHPART2 |
+ * |-------------------->|
+ * | |
+ * | CONFIRM |
+ * |<--------------------|
+ * | |
+ *
+ * The initiator creates a keypar (PKi,SKi) and sends this COMMIT
+ * message to the responder:
+ *
+ * 7 byte Magic, value: "GPG-pa1"
+ * 1 byte MessageType, value 1 (COMMIT)
+ * 8 byte SessionId, value: 8 random bytes
+ * 1 byte Realm, value 1
+ * 2 byte reserved, value 0
+ * 5 byte ExpireTime, value: seconds since Epoch as an unsigned int.
+ * 32 byte Hash(PKi)
+ *
+ * The initiator also needs to locally store the sessionid, the realm,
+ * the expiration time, the keypair and a hash of the entire message
+ * sent.
+ *
+ * The responder checks that the received message has not expired and
+ * stores sessionid, realm, expiretime and the Hash(PKi). The
+ * Responder then creates and locally stores its own keypair (PKr,SKr)
+ * and sends the DHPART1 message back:
+ *
+ * 7 byte Magic, value: "GPG-pa1"
+ * 1 byte MessageType, value 2 (DHPART1)
+ * 8 byte SessionId from COMMIT message
+ * 32 byte PKr
+ * 32 byte Hash(Hash(COMMIT) || DHPART1[0..47])
+ *
+ * Note that Hash(COMMIT) is the hash over the entire received COMMIT
+ * message. DHPART1[0..47] are the first 48 bytes of the created
+ * DHPART1 message.
+ *
+ * The Initiator receives the DHPART1 message and checks that the hash
+ * matches. Although this hash is easily malleable it is later in the
+ * protocol used to assert the integrity of all messages. The
+ * Initiator then computes the shared master secret from its SKi and
+ * the received PKr. Using this master secret several keys are
+ * derived:
+ *
+ * - HMACi-key using the label "GPG-pa1-HMACi-key".
+ * - SYMx-key using the label "GPG-pa1-SYMx-key"
+ *
+ * For details on the KDF see the implementation of the function kdf.
+ * The master secret is stored securily in the local state. The
+ * DHPART2 message is then created and send to the Responder:
+ *
+ * 7 byte Magic, value: "GPG-pa1"
+ * 1 byte MessageType, value 3 (DHPART2)
+ * 8 byte SessionId from COMMIT message
+ * 32 byte PKi
+ * 32 byte MAC(HMACi-key, Hash(DHPART1) || DHPART2[0..47] || SYMx-key)
+ *
+ * The Responder receives the DHPART2 message and checks that the hash
+ * of the received PKi matches the Hash(PKi) value as received earlier
+ * with the COMMIT message. The Responder now also computes the
+ * shared master secret from its SKr and the recived PKi and derives
+ * the keys:
+ *
+ * - HMACi-key using the label "GPG-pa1-HMACi-key".
+ * - HMACr-key using the label "GPG-pa1-HMACr-key".
+ * - SYMx-key using the label "GPG-pa1-SYMx-key"
+ * - SAS using the label "GPG-pa1-SAS"
+ *
+ * With these keys the MAC from the received DHPART2 message is
+ * checked. On success a SAS is displayed to the user and a CONFIRM
+ * message send back:
+ *
+ * 7 byte Magic, value: "GPG-pa1"
+ * 1 byte MessageType, value 4 (CONFIRM)
+ * 8 byte SessionId from COMMIT message
+ * 32 byte MAC(HMACr-key, Hash(DHPART2) || CONFIRM[0..15] || SYMx-key)
+ *
+ * The Initiator receives this CONFIRM message, gets the master shared
+ * secrey from its local state and derives the keys. It checks the
+ * the MAC in the received CONFIRM message and ask the user to enter
+ * the SAS as displayed by the responder. Iff the SAS matches the
+ * master key is flagged as confirmed and the Initiator may now use a
+ * derived key to send encrypted data to the Responder.
+ *
+ * In case the Responder also needs to send encrypted data we need to
+ * introduce another final message to tell the responder that the
+ * Initiator validated the SAS.
+ *
+ * TODO: Encrypt the state files using a key stored in gpg-agent's cache.
+ *
+ */
+
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <stdarg.h>
+
+#include "../common/util.h"
+#include "../common/status.h"
+#include "../common/i18n.h"
+#include "../common/sysutils.h"
+#include "../common/init.h"
+#include "../common/name-value.h"
+
+
+/* Constants to identify the commands and options. */
+enum cmd_and_opt_values
+ {
+ aNull = 0,
+
+ oQuiet = 'q',
+ oVerbose = 'v',
+ oOutput = 'o',
+ oArmor = 'a',
+
+ aInitiate = 400,
+ aRespond = 401,
+ aGet = 402,
+ aCleanup = 403,
+
+ oDebug = 500,
+ oStatusFD,
+ oHomedir,
+ oSAS,
+
+ oDummy
+ };
+
+
+/* The list of commands and options. */
+static gpgrt_opt_t opts[] = {
+ ARGPARSE_group (300, ("@Commands:\n ")),
+
+ ARGPARSE_c (aInitiate, "initiate", N_("initiate a pairing request")),
+ ARGPARSE_c (aRespond, "respond", N_("respond to a pairing request")),
+ ARGPARSE_c (aGet, "get", N_("return the keys")),
+ ARGPARSE_c (aCleanup, "cleanup", N_("remove expired states etc.")),
+
+ ARGPARSE_group (301, ("@\nOptions:\n ")),
+
+ ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
+ ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")),
+ ARGPARSE_s_n (oArmor, "armor", N_("create ascii armored output")),
+ ARGPARSE_s_s (oSAS, "sas", N_("|SAS|the SAS as shown by the peer")),
+ ARGPARSE_s_s (oDebug, "debug", "@"),
+ ARGPARSE_s_s (oOutput, "output", N_("|FILE|write the request to FILE")),
+ ARGPARSE_s_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")),
+
+ ARGPARSE_s_s (oHomedir, "homedir", "@"),
+
+ ARGPARSE_end ()
+};
+
+
+/* We keep all global options in the structure OPT. */
+static struct
+{
+ int verbose;
+ unsigned int debug;
+ int quiet;
+ int armor;
+ const char *output;
+ estream_t statusfp;
+ unsigned int ttl;
+ const char *sas;
+} opt;
+
+
+/* Debug values and macros. */
+#define DBG_MESSAGE_VALUE 2 /* Debug the messages. */
+#define DBG_CRYPTO_VALUE 4 /* Debug low level crypto. */
+#define DBG_MEMORY_VALUE 32 /* Debug memory allocation stuff. */
+
+#define DBG_MESSAGE (opt.debug & DBG_MESSAGE_VALUE)
+#define DBG_CRYPTO (opt.debug & DBG_CRYPTO_VALUE)
+
+
+/* The list of supported debug flags. */
+static struct debug_flags_s debug_flags [] =
+ {
+ { DBG_MESSAGE_VALUE, "message" },
+ { DBG_CRYPTO_VALUE , "crypto" },
+ { DBG_MEMORY_VALUE , "memory" },
+ { 0, NULL }
+ };
+
+
+/* The directory name below the cache dir to store paring states. */
+#define PAIRING_STATE_DIR "state"
+
+/* Message types. */
+#define MSG_TYPE_COMMIT 1
+#define MSG_TYPE_DHPART1 2
+#define MSG_TYPE_DHPART2 3
+#define MSG_TYPE_CONFIRM 4
+
+
+/* Realm values. */
+#define REALM_STANDARD 1
+
+
+
+
+/* Local prototypes. */
+static void wrong_args (const char *text) GPGRT_ATTR_NORETURN;
+static void xnvc_set_printf (nvc_t nvc, const char *name, const char *format,
+ ...) GPGRT_ATTR_PRINTF(3,4);
+static void *hash_data (void *result, size_t resultsize,
+ ...) GPGRT_ATTR_SENTINEL(0);
+static void *hmac_data (void *result, size_t resultsize,
+ const unsigned char *key, size_t keylen,
+ ...) GPGRT_ATTR_SENTINEL(0);
+
+
+static gpg_error_t command_initiate (void);
+static gpg_error_t command_respond (void);
+static gpg_error_t command_cleanup (void);
+static gpg_error_t command_get (const char *sessionidstr);
+
+
+
+
+/* Print usage information and provide strings for help. */
+static const char *
+my_strusage( int level )
+{
+ const char *p;
+
+ switch (level)
+ {
+ case 9: p = "LGPL-2.1-or-later"; break;
+ case 11: p = "gpg-pair-tool"; break;
+ case 12: p = "@GNUPG@"; break;
+ case 13: p = VERSION; break;
+ case 14: p = GNUPG_DEF_COPYRIGHT_LINE; 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-pair-tool [command] [options] [args] (-h for help)");
+ break;
+ case 41:
+ p = ("Syntax: gpg-pair-tool [command] [options] [args]\n"
+ "Client to run the pairing protocol\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);
+}
+
+
+/* Set the status FD. */
+static void
+set_status_fd (int fd)
+{
+ static int last_fd = -1;
+
+ if (fd != -1 && last_fd == fd)
+ return;
+
+ if (opt.statusfp && opt.statusfp != es_stdout && opt.statusfp != es_stderr)
+ es_fclose (opt.statusfp);
+ opt.statusfp = NULL;
+ if (fd == -1)
+ return;
+
+ if (fd == 1)
+ opt.statusfp = es_stdout;
+ else if (fd == 2)
+ opt.statusfp = es_stderr;
+ else
+ opt.statusfp = es_fdopen (fd, "w");
+ if (!opt.statusfp)
+ {
+ log_fatal ("can't open fd %d for status output: %s\n",
+ fd, gpg_strerror (gpg_error_from_syserror ()));
+ }
+ last_fd = fd;
+}
+
+
+/* Write a status line with code NO followed by the outout of the
+ * printf style FORMAT. The caller needs to make sure that LFs and
+ * CRs are not printed. */
+static void
+write_status (int no, const char *format, ...)
+{
+ va_list arg_ptr;
+
+ if (!opt.statusfp)
+ return; /* Not enabled. */
+
+ es_fputs ("[GNUPG:] ", opt.statusfp);
+ es_fputs (get_status_string (no), opt.statusfp);
+ if (format)
+ {
+ es_putc (' ', opt.statusfp);
+ va_start (arg_ptr, format);
+ es_vfprintf (opt.statusfp, format, arg_ptr);
+ va_end (arg_ptr);
+ }
+ es_putc ('\n', opt.statusfp);
+}
+
+
+
+/* gpg-pair-tool main. */
+int
+main (int argc, char **argv)
+{
+ gpg_error_t err;
+ gpgrt_argparse_t pargs = { &argc, &argv };
+ enum cmd_and_opt_values cmd = 0;
+
+ opt.ttl = 8*3600; /* Default to 8 hours. */
+
+ gnupg_reopen_std ("gpg-pair-tool");
+ gpgrt_set_strusage (my_strusage);
+ log_set_prefix ("gpg-pair-tool", GPGRT_LOG_WITH_PREFIX);
+
+ /* Make sure that our subsystems are ready. */
+ i18n_init();
+ init_common_subsystems (&argc, &argv);
+
+ /* Parse the command line. */
+ while (gpgrt_argparse (NULL, &pargs, opts))
+ {
+ switch (pargs.r_opt)
+ {
+ case oQuiet: opt.quiet = 1; break;
+ case oVerbose: opt.verbose++; break;
+ case oArmor: opt.armor = 1; 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 oOutput:
+ opt.output = pargs.r.ret_str;
+ break;
+
+ case oStatusFD:
+ set_status_fd (translate_sys2libc_fd_int (pargs.r.ret_int, 1));
+ break;
+
+ case oHomedir:
+ gnupg_set_homedir (pargs.r.ret_str);
+ break;
+
+ case oSAS:
+ opt.sas = pargs.r.ret_str;
+ break;
+
+ case aInitiate:
+ case aRespond:
+ case aGet:
+ case aCleanup:
+ if (cmd && cmd != pargs.r_opt)
+ log_error (_("conflicting commands\n"));
+ else
+ cmd = pargs.r_opt;
+ break;
+
+ default: pargs.err = ARGPARSE_PRINT_WARNING; break;
+ }
+ }
+
+ /* 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]);
+ }
+ gpgrt_argparse (NULL, &pargs, NULL); /* Free internal memory. */
+
+ if (opt.sas)
+ {
+ if (strlen (opt.sas) != 11
+ || !digitp (opt.sas+0) || !digitp (opt.sas+1) || !digitp (opt.sas+2)
+ || opt.sas[3] != '-'
+ || !digitp (opt.sas+4) || !digitp (opt.sas+5) || !digitp (opt.sas+6)
+ || opt.sas[7] != '-'
+ || !digitp (opt.sas+8) || !digitp (opt.sas+9) || !digitp (opt.sas+10))
+ log_error ("invalid formatted SAS\n");
+ }
+
+ /* Stop if any error, inclduing ARGPARSE_PRINT_WARNING, occurred. */
+ if (log_get_errorcount (0))
+ exit (2);
+
+ if (DBG_CRYPTO)
+ gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1|2);
+
+
+ /* Now run the requested command. */
+ switch (cmd)
+ {
+ case aInitiate:
+ if (argc)
+ wrong_args ("--initiate");
+ err = command_initiate ();
+ break;
+
+ case aRespond:
+ if (argc)
+ wrong_args ("--respond");
+ err = command_respond ();
+ break;
+
+ case aGet:
+ if (argc > 1)
+ wrong_args ("--respond [sessionid]");
+ err = command_get (argc? *argv:NULL);
+ break;
+
+ case aCleanup:
+ if (argc)
+ wrong_args ("--cleanup");
+ err = command_cleanup ();
+ break;
+
+ default:
+ gpgrt_usage (1);
+ err = 0;
+ break;
+ }
+
+ if (err)
+ write_status (STATUS_FAILURE, "- %u", err);
+ else if (log_get_errorcount (0))
+ write_status (STATUS_FAILURE, "- %u", GPG_ERR_GENERAL);
+ else
+ write_status (STATUS_SUCCESS, NULL);
+ return log_get_errorcount (0)? 1:0;
+}
+
+
+
+/* Wrapper around nvc_new which terminates in the error case. */
+static nvc_t
+xnvc_new (void)
+{
+ nvc_t c = nvc_new ();
+ if (!c)
+ log_fatal ("error creating NVC object: %s\n",
+ gpg_strerror (gpg_error_from_syserror ()));
+ return c;
+}
+
+/* Wrapper around nvc_set which terminates in the error case. */
+static void
+xnvc_set (nvc_t nvc, const char *name, const char *value)
+{
+ gpg_error_t err = nvc_set (nvc, name, value);
+ if (err)
+ log_fatal ("error updating NVC object: %s\n", gpg_strerror (err));
+}
+
+/* Call vnc_set with (BUFFER, BUFLEN) converted to a hex string as
+ * value. Terminates in the error case. */
+static void
+xnvc_set_hex (nvc_t nvc, const char *name, const void *buffer, size_t buflen)
+{
+ char *hex;
+
+ hex = bin2hex (buffer, buflen, NULL);
+ if (!hex)
+ xoutofcore ();
+ strlwr (hex);
+ xnvc_set (nvc, name, hex);
+ xfree (hex);
+}
+
+/* Call nvc_set with a value created from the string generated using
+ * the printf style FORMAT. Terminates in the error case. */
+static void
+xnvc_set_printf (nvc_t nvc, const char *name, const char *format, ...)
+{
+ va_list arg_ptr;
+ char *buffer;
+
+ va_start (arg_ptr, format);
+ if (gpgrt_vasprintf (&buffer, format, arg_ptr) < 0)
+ log_fatal ("estream_asprintf failed: %s\n",
+ gpg_strerror (gpg_error_from_syserror ()));
+ va_end (arg_ptr);
+ xnvc_set (nvc, name, buffer);
+ xfree (buffer);
+}
+
+
+/* Return the string for the first entry in NVC with NAME. If NAME is
+ * missing, an empty string is returned. The returned string is a
+ * pointer into NVC. */
+static const char *
+xnvc_get_string (nvc_t nvc, const char *name)
+{
+ nve_t item;
+
+ if (!nvc)
+ return "";
+ item = nvc_lookup (nvc, name);
+ if (!item)
+ return "";
+ return nve_value (item);
+}
+
+
+
+/* Return a string for MSGTYPE. */
+const char *
+msgtypestr (int msgtype)
+{
+ switch (msgtype)
+ {
+ case MSG_TYPE_COMMIT: return "Commit";
+ case MSG_TYPE_DHPART1: return "DHPart1";
+ case MSG_TYPE_DHPART2: return "DHPart2";
+ case MSG_TYPE_CONFIRM: return "Confirm";
+ }
+ return "?";
+}
+
+
+/* Private to {get,set}_session_id(). */
+static struct {
+ int initialized;
+ unsigned char sessid[8];
+} session_id;
+
+
+/* Return the 8 octet session. */
+static unsigned char *
+get_session_id (void)
+{
+ if (!session_id.initialized)
+ {
+ session_id.initialized = 1;
+ gcry_create_nonce (session_id.sessid, sizeof session_id.sessid);
+ }
+
+ return session_id.sessid;
+}
+
+static void
+set_session_id (const void *sessid, size_t len)
+{
+ log_assert (!session_id.initialized);
+ if (len > sizeof session_id.sessid)
+ len = sizeof session_id.sessid;
+ memcpy (session_id.sessid, sessid, len);
+ if (len < sizeof session_id.sessid)
+ memset (session_id.sessid+len, 0, sizeof session_id.sessid - len);
+ session_id.initialized = 1;
+}
+
+/* Return a string with the hexified session id. */
+static const char *
+get_session_id_hex (void)
+{
+ static char hexstr[16+1];
+
+ bin2hex (get_session_id (), 8, hexstr);
+ strlwr (hexstr);
+ return hexstr;
+}
+
+
+/* Return a fixed string with the directory used to store the state of
+ * pairings. On error a diagnostic is printed but the file name is
+ * returned anyway. It is expected that the expected failure of the
+ * following open is responsible for error handling. */
+static const char *
+get_pairing_statedir (void)
+{
+ static char *fname;
+ gpg_error_t err = 0;
+ char *tmpstr;
+ struct stat statbuf;
+
+ if (fname)
+ return fname;
+
+ fname = make_filename (gnupg_homedir (), GNUPG_CACHE_DIR, NULL);
+ if (stat (fname, &statbuf) && errno == ENOENT)
+ {
+ if (gnupg_mkdir (fname, "-rwx"))
+ {
+ err = gpg_error_from_syserror ();
+ log_error (_("can't create directory '%s': %s\n"),
+ fname, gpg_strerror (err) );
+ }
+ else if (!opt.quiet)
+ log_info (_("directory '%s' created\n"), fname);
+ }
+
+ tmpstr = make_filename (fname, PAIRING_STATE_DIR, NULL);
+ xfree (fname);
+ fname = tmpstr;
+ if (stat (fname, &statbuf) && errno == ENOENT)
+ {
+ if (gnupg_mkdir (fname, "-rwx"))
+ {
+ if (!err)
+ {
+ err = gpg_error_from_syserror ();
+ log_error (_("can't create directory '%s': %s\n"),
+ fname, gpg_strerror (err) );
+ }
+ }
+ else if (!opt.quiet)
+ log_info (_("directory '%s' created\n"), fname);
+ }
+
+ return fname;
+}
+
+
+/* Open the pairing state file. SESSIONID is a 8 byte buffer with the
+ * session-id. If CREATE_FLAG is set the file is created and will
+ * always return a valid stream. If CREATE_FLAG is not set the file
+ * is opened for reading and writing. If the file does not exist NULL
+ * is return; in all other error cases the process is terminated. If
+ * R_FNAME is not NULL the name of the file is stored there and the
+ * caller needs to free it. */
+static estream_t
+open_pairing_state (const unsigned char *sessionid, int create_flag,
+ char **r_fname)
+{
+ gpg_error_t err;
+ char *fname, *tmpstr;
+ estream_t fp;
+
+ /* The filename is the session id with a "pa1" suffix. Note that
+ * the state dir may eventually be used for other purposes as well
+ * and thus the suffix identifies that the file belongs to this
+ * tool. We use lowercase file names for no real reason. */
+ tmpstr = bin2hex (sessionid, 8, NULL);
+ if (!tmpstr)
+ xoutofcore ();
+ strlwr (tmpstr);
+ fname = xstrconcat (tmpstr, ".pa1", NULL);
+ xfree (tmpstr);
+ tmpstr = make_filename (get_pairing_statedir (), fname, NULL);
+ xfree (fname);
+ fname = tmpstr;
+
+ fp = es_fopen (fname, create_flag? "wbx,mode=-rw": "rb+,mode=-rw");
+ if (!fp)
+ {
+ err = gpg_error_from_syserror ();
+ if (create_flag)
+ {
+ /* We should always be able to create a file. Also we use a
+ * 64 bit session id, it is theoretically possible that such
+ * a session already exists. However, that is rare enough
+ * and thus the fatal error message should still be okay. */
+ log_fatal ("can't create '%s': %s\n", fname, gpg_strerror (err));
+ }
+ else if (gpg_err_code (err) == GPG_ERR_ENOENT)
+ {
+ /* That is an expected error; return NULL. */
+ }
+ else
+ {
+ log_fatal ("can't open '%s': %s\n", fname, gpg_strerror (err));
+ }
+ }
+
+ if (r_fname)
+ *r_fname = fname;
+ else
+ xfree (fname);
+
+ return fp;
+}
+
+
+/* Write the state to a possible new state file. */
+static void
+write_state (nvc_t state, int create_flag)
+{
+ gpg_error_t err;
+ char *fname = NULL;
+ estream_t fp;
+
+ fp = open_pairing_state (get_session_id (), create_flag, &fname);
+ log_assert (fp);
+
+ err = nvc_write (state, fp);
+ if (err)
+ {
+ es_fclose (fp);
+ gnupg_remove (fname);
+ log_fatal ("error writing '%s': %s\n", fname, gpg_strerror (err));
+ }
+
+ /* If we did not create the file, we need to truncate the file. */
+ if (!create_flag && ftruncate (es_fileno (fp), es_ftello (fp)))
+ {
+ err = gpg_error_from_syserror ();
+ log_fatal ("error truncating '%s': %s\n", fname, gpg_strerror (err));
+ }
+ if (es_ferror (fp) || es_fclose (fp))
+ {
+ err = gpg_error_from_syserror ();
+ es_fclose (fp);
+ gnupg_remove (fname);
+ log_fatal ("error writing '%s': %s\n", fname, gpg_strerror (err));
+ }
+}
+
+
+/* Read the state into a newly allocated state object and store that
+ * at R_STATE. If no state is available GPG_ERR_NOT_FOUND is returned
+ * and as with all errors NULL is tored at R_STATE. SESSIONID is an
+ * input with the 8 session id. */
+static gpg_error_t
+read_state (nvc_t *r_state)
+{
+ gpg_error_t err;
+ char *fname = NULL;
+ estream_t fp;
+ nvc_t state = NULL;
+ nve_t item;
+ const char *value;
+ unsigned long expire;
+
+ *r_state = NULL;
+
+ fp = open_pairing_state (get_session_id (), 0, &fname);
+ if (!fp)
+ return gpg_error (GPG_ERR_NOT_FOUND);
+
+ err = nvc_parse (&state, NULL, fp);
+ if (err)
+ {
+ log_info ("failed to parse state file '%s': %s\n",
+ fname, gpg_strerror (err));
+ goto leave;
+ }
+
+ /* Check whether the state already expired. */
+ item = nvc_lookup (state, "Expires:");
+ if (!item)
+ {
+ log_info ("invalid state file '%s': %s\n",
+ fname, "field 'expire' not found");
+ goto leave;
+ }
+ value = nve_value (item);
+ if (!value || !(expire = strtoul (value, NULL, 10)))
+ {
+ log_info ("invalid state file '%s': %s\n",
+ fname, "field 'expire' has an invalid value");
+ goto leave;
+ }
+ if (expire <= gnupg_get_time ())
+ {
+ es_fclose (fp);
+ fp = NULL;
+ if (gnupg_remove (fname))
+ {
+ err = gpg_error_from_syserror ();
+ log_info ("failed to delete state file '%s': %s\n",
+ fname, gpg_strerror (err));
+ }
+ else if (opt.verbose)
+ log_info ("state file '%s' deleted\n", fname);
+ err = gpg_error (GPG_ERR_NOT_FOUND);
+ goto leave;
+ }
+
+ *r_state = state;
+ state = NULL;
+
+ leave:
+ nvc_release (state);
+ es_fclose (fp);
+ return err;
+}
+
+
+/* Send (MSG,MSGLEN) to the output device. */
+static void
+send_message (const unsigned char *msg, size_t msglen)
+{
+ gpg_error_t err;
+
+ if (opt.verbose)
+ log_info ("session %s: sending %s message\n",
+ get_session_id_hex (), msgtypestr (msg[7]));
+
+ if (DBG_MESSAGE)
+ log_printhex (msg, msglen, "send msg(%s):", msgtypestr (msg[7]));
+
+ /* FIXME: For now only stdout. */
+ if (opt.armor)
+ {
+ gpgrt_b64state_t state;
+
+ state = gpgrt_b64enc_start (es_stdout, "");
+ if (!state)
+ log_fatal ("error setting up base64 encoder: %s\n",
+ gpg_strerror (gpg_error_from_syserror ()));
+ err = gpgrt_b64enc_write (state, msg, msglen);
+ if (!err)
+ err = gpgrt_b64enc_finish (state);
+ if (err)
+ log_fatal ("error writing base64 to stdout: %s\n", gpg_strerror (err));
+ }
+ else
+ {
+ if (es_fwrite (msg, msglen, 1, es_stdout) != 1)
+ log_fatal ("error writing to stdout: %s\n",
+ gpg_strerror (gpg_error_from_syserror ()));
+ }
+ es_fputc ('\n', es_stdout);
+}
+
+
+/* Read a message from stdin and store it at the address (R_MSG,
+ * R_MSGLEN). This function detects armoring and removes it. On
+ * error NULL is stored at R_MSG, a diagnostic printed and an error
+ * code returned. The returned message has a proper message type and
+ * an appropriate length. The message type is stored at R_MSGTYPE and
+ * if a state is availabale it is stored at R_STATE. */
+static gpg_error_t
+read_message (unsigned char **r_msg, size_t *r_msglen, int *r_msgtype,
+ nvc_t *r_state)
+{
+ gpg_error_t err;
+ unsigned char msg[128]; /* max msg size is 80 but 107 with base64. */
+ size_t msglen;
+ size_t reqlen;
+
+ *r_msg = NULL;
+ *r_state = NULL;
+
+ es_setvbuf (es_stdin, NULL, _IONBF, 0);
+ es_set_binary (es_stdin);
+
+ if (es_read (es_stdin, msg, sizeof msg, &msglen))
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("error reading from message: %s\n", gpg_strerror (err));
+ return err;
+ }
+
+ if (msglen > 4 && !memcmp (msg, "R1BH", 4))
+ {
+ /* This is base64 of the first 3 bytes. */
+ gpgrt_b64state_t state = gpgrt_b64dec_start (NULL);
+ if (!state)
+ log_fatal ("error setting up base64 decoder: %s\n",
+ gpg_strerror (gpg_error_from_syserror ()));
+ err = gpgrt_b64dec_proc (state, msg, msglen, &msglen);
+ gpgrt_b64dec_finish (state);
+ if (err)
+ {
+ log_error ("error decoding message: %s\n", gpg_strerror (err));
+ return err;
+ }
+ }
+
+ if (msglen < 16 || memcmp (msg, "GPG-pa1", 7))
+ {
+ log_error ("error parsing message: %s\n",
+ msglen? "invalid header":"empty message");
+ return gpg_error (GPG_ERR_INV_RESPONSE);
+ }
+ switch (msg[7])
+ {
+ case MSG_TYPE_COMMIT: reqlen = 56; break;
+ case MSG_TYPE_DHPART1: reqlen = 80; break;
+ case MSG_TYPE_DHPART2: reqlen = 80; break;
+ case MSG_TYPE_CONFIRM: reqlen = 48; break;
+
+ default:
+ log_error ("error parsing message: %s\n", "invalid message type");
+ return gpg_error (GPG_ERR_INV_RESPONSE);
+ }
+ if (msglen < reqlen)
+ {
+ log_error ("error parsing message: %s\n", "message too short");
+ return gpg_error (GPG_ERR_INV_RESPONSE);
+ }
+
+ if (DBG_MESSAGE)
+ log_printhex (msg, msglen, "recv msg(%s):", msgtypestr (msg[7]));
+
+ /* Note that we ignore any garbage at the end of a message. */
+ msglen = reqlen;
+
+ set_session_id (msg+8, 8);
+
+ if (opt.verbose)
+ log_info ("session %s: received %s message\n",
+ get_session_id_hex (), msgtypestr (msg[7]));
+
+ /* Read the state. */
+ err = read_state (r_state);
+ if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND)
+ return err;
+
+ *r_msg = xmalloc (msglen);
+ memcpy (*r_msg, msg, msglen);
+ *r_msglen = msglen;
+ *r_msgtype = msg[7];
+ return err;
+}
+
+
+/* Display the Short Authentication String (SAS). If WAIT is true the
+ * function waits until the user has entered the SAS as seen at the
+ * peer.
+ *
+ * To construct the SAS we take the 4 most significant octets of HASH,
+ * interpret them as a 32 bit big endian unsigned integer, divide that
+ * integer by 10^9 and take the remainder. The remainder is displayed
+ * as 3 groups of 3 decimal digits delimited by a hyphens. This gives
+ * a search space of close to 2^30 and is still easy to compare.
+ */
+static gpg_error_t
+display_sas (const unsigned char *hash, size_t hashlen, int wait)
+{
+ gpg_error_t err;
+ unsigned long sas = 0;
+ char sasbuf[12];
+
+ log_assert (hashlen >= 4);
+
+ sas |= (unsigned long)hash[20] << 24;
+ sas |= (unsigned long)hash[21] << 16;
+ sas |= (unsigned long)hash[22] << 8;
+ sas |= (unsigned long)hash[23];
+ sas %= 1000000000ul;
+ snprintf (sasbuf, sizeof sasbuf, "%09lu", sas);
+ memmove (sasbuf+8, sasbuf+6, 3);
+ memmove (sasbuf+4, sasbuf+3, 3);
+ sasbuf[3] = sasbuf[7] = '-';
+ sasbuf[11] = 0;
+
+ if (wait)
+ log_info ("Please check the SAS:\n");
+ else
+ log_info ("Please note the SAS:\n");
+ log_info ("\n");
+ log_info (" %s\n", sasbuf);
+ log_info ("\n");
+
+ if (wait)
+ {
+ if (!opt.sas || strcmp (sasbuf, opt.sas))
+ err = gpg_error (GPG_ERR_NOT_CONFIRMED);
+ else
+ log_info ("SAS confirmed\n");
+ }
+
+ if (err)
+ log_info ("checking SAS failed: %s\n", gpg_strerror (err));
+ return err;
+}
+
+
+
+static gpg_error_t
+create_dh_keypair (unsigned char *dh_secret, size_t dh_secret_len,
+ unsigned char *dh_public, size_t dh_public_len)
+{
+ gpg_error_t err;
+ gcry_sexp_t sexp;
+ gcry_sexp_t s_keypair;
+ gcry_buffer_t secret;
+ gcry_buffer_t public;
+ unsigned char publicbuf[33];
+
+ /* We need a temporary buffer for the public key. Check the length
+ * for the later memcpy. */
+ if (dh_public_len < 32)
+ return gpg_error (GPG_ERR_BUFFER_TOO_SHORT);
+
+ secret.size = dh_secret_len;
+ secret.data = dh_secret;
+ secret.off = 0;
+ public.size = sizeof publicbuf;
+ public.data = publicbuf;
+ public.off = 0;
+
+ err = gcry_sexp_build (&sexp, NULL,
+ "(genkey(ecc(curve Curve25519)(flags djb-tweak)))");
+ if (err)
+ return err;
+ err = gcry_pk_genkey (&s_keypair, sexp);
+ gcry_sexp_release (sexp);
+ if (err)
+ return err;
+ err = gcry_sexp_extract_param (s_keypair, "key-data!private-key",
+ "&dq", &secret, &public, NULL);
+ gcry_sexp_release (s_keypair);
+ if (err)
+ return err;
+
+ /* Gcrypt prepends a 0x40 indicator - remove that. */
+ if (public.len == 33)
+ {
+ public.len = 32;
+ memmove (public.data, publicbuf+1, 32);
+ }
+ memcpy (dh_public, public.data, public.len);
+
+ if (DBG_CRYPTO)
+ {
+ log_printhex (secret.data, secret.len, "DH secret:");
+ log_printhex (public.data, public.len, "DH public:");
+ }
+
+ return 0;
+}
+
+
+/* SHA256 the data given as varargs tuples of (const void*, size_t)
+ * and store the result in RESULT. The end of the list is indicated
+ * by a NULL element in a tuple. RESULTLEN gives the length of the
+ * RESULT buffer which must be at least 32. Note that the second item
+ * of the tuple is the length and it is a size_t. */
+static void *
+hash_data (void *result, size_t resultsize, ...)
+{
+ va_list arg_ptr;
+ gpg_error_t err;
+ gcry_md_hd_t hd;
+ const void *data;
+ size_t datalen;
+
+ log_assert (resultsize >= 32);
+
+ err = gcry_md_open (&hd, GCRY_MD_SHA256, 0);
+ if (err)
+ log_fatal ("error creating a Hash handle: %s\n", gpg_strerror (err));
+ /* log_printhex ("", 0, "Hash-256:"); */
+
+ va_start (arg_ptr, resultsize);
+ while ((data = va_arg (arg_ptr, const void *)))
+ {
+ datalen = va_arg (arg_ptr, size_t);
+ /* log_printhex (data, datalen, " data:"); */
+ gcry_md_write (hd, data, datalen);
+ }
+ va_end (arg_ptr);
+
+ memcpy (result, gcry_md_read (hd, 0), 32);
+ /* log_printhex (result, 32, " result:"); */
+
+ gcry_md_close (hd);
+
+ return result;
+}
+
+
+/* HMAC-SHA256 the data given as varargs tuples of (const void*,
+ * size_t) using (KEYLEN,KEY) and store the result in RESULT. The end
+ * of the list is indicated by a NULL element in a tuple. RESULTLEN
+ * gives the length of the RESULT buffer which must be at least 32.
+ * Note that the second item of the tuple is the length and it is a
+ * size_t. */
+static void *
+hmac_data (void *result, size_t resultsize,
+ const unsigned char *key, size_t keylen, ...)
+{
+ va_list arg_ptr;
+ gpg_error_t err;
+ gcry_mac_hd_t hd;
+ const void *data;
+ size_t datalen;
+
+ log_assert (resultsize >= 32);
+
+ err = gcry_mac_open (&hd, GCRY_MAC_HMAC_SHA256, 0, NULL);
+ if (err)
+ log_fatal ("error creating a MAC handle: %s\n", gpg_strerror (err));
+ err = gcry_mac_setkey (hd, key, keylen);
+ if (err)
+ log_fatal ("error setting the MAC key: %s\n", gpg_strerror (err));
+ /* log_printhex (key, keylen, "HMAC-key:"); */
+
+ va_start (arg_ptr, keylen);
+ while ((data = va_arg (arg_ptr, const void *)))
+ {
+ datalen = va_arg (arg_ptr, size_t);
+ /* log_printhex (data, datalen, " data:"); */
+ err = gcry_mac_write (hd, data, datalen);
+ if (err)
+ log_fatal ("error writing to the MAC handle: %s\n", gpg_strerror (err));
+ }
+ va_end (arg_ptr);
+
+ err = gcry_mac_read (hd, result, &resultsize);
+ if (err || resultsize != 32)
+ log_fatal ("error reading MAC value: %s\n", gpg_strerror (err));
+ /* log_printhex (result, resultsize, " result:"); */
+
+ gcry_mac_close (hd);
+
+ return result;
+}
+
+
+/* Key derivation function:
+ *
+ * FIXME(doc)
+ */
+static void
+kdf (unsigned char *result, size_t resultlen,
+ const unsigned char *master, size_t masterlen,
+ const unsigned char *sessionid, size_t sessionidlen,
+ const unsigned char *expire, size_t expirelen,
+ const char *label)
+{
+ log_assert (masterlen == 32 && sessionidlen == 8 && expirelen == 5);
+ log_assert (*label);
+ log_assert (resultlen == 32);
+
+ hmac_data (result, resultlen, master, masterlen,
+ "\x00\x00\x00\x01", (size_t)4, /* Counter=1*/
+ label, strlen (label) + 1, /* Label, 0x00 */
+ sessionid, sessionidlen, /* Context */
+ expire, expirelen, /* Context */
+ "\x00\x00\x01\x00", (size_t)4, /* L=256 */
+ NULL);
+}
+
+
+static gpg_error_t
+compute_master_secret (unsigned char *master, size_t masterlen,
+ const unsigned char *sk_a, size_t sk_a_len,
+ const unsigned char *pk_b, size_t pk_b_len)
+{
+ gpg_error_t err;
+ gcry_sexp_t s_sk_a = NULL;
+ gcry_sexp_t s_pk_b = NULL;
+ gcry_sexp_t s_shared = NULL;
+ gcry_sexp_t s_tmp;
+ const char *s;
+ size_t n;
+
+ log_assert (masterlen == 32);
+
+ err = gcry_sexp_build (&s_sk_a, NULL, "%b", (int)sk_a_len, sk_a);
+ if (!err)
+ err = gcry_sexp_build (&s_pk_b, NULL,
+ "(public-key(ecdh(curve Curve25519)"
+ " (flags djb-tweak)(q%b)))",
+ (int)pk_b_len, pk_b);
+ if (err)
+ {
+ log_error ("error building S-expression: %s\n", gpg_strerror (err));
+ goto leave;
+ }
+
+ err = gcry_pk_encrypt (&s_shared, s_sk_a, s_pk_b);
+ if (err)
+ {
+ log_error ("error computing DH: %s\n", gpg_strerror (err));
+ goto leave;
+ }
+ /* gcry_log_debugsxp ("sk_a", s_sk_a); */
+ /* gcry_log_debugsxp ("pk_b", s_pk_b); */
+ /* gcry_log_debugsxp ("shared", s_shared); */
+
+ s_tmp = gcry_sexp_find_token (s_shared, "s", 0);
+ if (!s_tmp || !(s = gcry_sexp_nth_data (s_tmp, 1, &n))
+ || n != 33 || s[0] != 0x40)
+ {
+ err = gpg_error (GPG_ERR_INTERNAL);
+ log_error ("error computing DH: %s\n", gpg_strerror (err));
+ goto leave;
+ }
+ memcpy (master, s+1, 32);
+
+
+ leave:
+ gcry_sexp_release (s_sk_a);
+ gcry_sexp_release (s_pk_b);
+ gcry_sexp_release (s_shared);
+ return err;
+}
+
+
+/* We are the Initiator: Create the commit message. This function
+ * sends the COMMIT message and writes STATE. */
+static gpg_error_t
+make_msg_commit (nvc_t state)
+{
+ gpg_error_t err;
+ uint64_t now, expire;
+ unsigned char secret[32];
+ unsigned char public[32];
+ unsigned char *newmsg;
+ size_t newmsglen;
+ unsigned char tmphash[32];
+
+ err = create_dh_keypair (secret, sizeof secret, public, sizeof public );
+ if (err)
+ log_error ("creating DH keypair failed: %s\n", gpg_strerror (err));
+
+ now = gnupg_get_time ();
+ expire = now + opt.ttl;
+
+ newmsglen = 7+1+8+1+2+5+32;
+ newmsg = xmalloc (newmsglen);
+ memcpy (newmsg+0, "GPG-pa1", 7);
+ newmsg[7] = MSG_TYPE_COMMIT;
+ memcpy (newmsg+8, get_session_id (), 8);
+ newmsg[16] = REALM_STANDARD;
+ newmsg[17] = 0;
+ newmsg[18] = 0;
+ newmsg[19] = expire >> 32;
+ newmsg[20] = expire >> 24;
+ newmsg[21] = expire >> 16;
+ newmsg[22] = expire >> 8;
+ newmsg[23] = expire;
+ gcry_md_hash_buffer (GCRY_MD_SHA256, newmsg+24, public, 32);
+
+ /* Create the state file. */
+ xnvc_set (state, "State:", "Commit-sent");
+ xnvc_set_printf (state, "Created:", "%llu", (unsigned long long)now);
+ xnvc_set_printf (state, "Expires:", "%llu", (unsigned long long)expire);
+ xnvc_set_hex (state, "DH-PKi:", public, 32);
+ xnvc_set_hex (state, "DH-SKi:", secret, 32);
+ gcry_md_hash_buffer (GCRY_MD_SHA256, tmphash, newmsg, newmsglen);
+ xnvc_set_hex (state, "Hash-Commit:", tmphash, 32);
+
+ /* Write the state. Note that we need to create it. The state
+ * updating should in theory be done atomically with send_message.
+ * However, we can't assure that the message will actually be
+ * delivered and thus it doesn't matter whether we have an already
+ * update state when we later fail in send_message. */
+ write_state (state, 1);
+
+ /* Write the message. */
+ send_message (newmsg, newmsglen);
+
+ xfree (newmsg);
+ return err;
+}
+
+
+/* We are the Responder: Process a commit message in (MSG,MSGLEN)
+ * which has already been validated to have a correct header and
+ * message type. Sends the DHPart1 message and writes STATE. */
+static gpg_error_t
+proc_msg_commit (nvc_t state, const unsigned char *msg, size_t msglen)
+{
+ gpg_error_t err;
+ uint64_t now, expire;
+ unsigned char tmphash[32];
+ unsigned char secret[32];
+ unsigned char public[32];
+ unsigned char *newmsg = NULL;
+ size_t newmsglen;
+
+ log_assert (msglen >= 56);
+ now = gnupg_get_time ();
+
+ /* Check that the message has not expired. */
+ expire = (uint64_t)msg[19] << 32;
+ expire |= (uint64_t)msg[20] << 24;
+ expire |= (uint64_t)msg[21] << 16;
+ expire |= (uint64_t)msg[22] << 8;
+ expire |= (uint64_t)msg[23];
+ if (expire < now)
+ {
+ log_error ("received %s message is too old\n",
+ msgtypestr (MSG_TYPE_COMMIT));
+ err = gpg_error (GPG_ERR_TOO_OLD);
+ goto leave;
+ }
+
+ /* Create the response. */
+ err = create_dh_keypair (secret, sizeof secret, public, sizeof public );
+ if (err)
+ {
+ log_error ("creating DH keypair failed: %s\n", gpg_strerror (err));
+ goto leave;
+ }
+
+ newmsglen = 7+1+8+32+32;
+ newmsg = xmalloc (newmsglen);
+ memcpy (newmsg+0, "GPG-pa1", 7);
+ newmsg[7] = MSG_TYPE_DHPART1;
+ memcpy (newmsg+8, msg + 8, 8); /* SessionID. */
+ memcpy (newmsg+16, public, 32); /* PKr */
+ /* Hash(Hash(Commit) || DHPart1[0..47]) */
+ gcry_md_hash_buffer (GCRY_MD_SHA256, tmphash, msg, msglen);
+ hash_data (newmsg+48, 32,
+ tmphash, sizeof tmphash,
+ newmsg, (size_t)48,
+ NULL);
+
+ /* Update the state. */
+ xnvc_set (state, "State:", "DHPart1-sent");
+ xnvc_set_printf (state, "Created:", "%llu", (unsigned long long)now);
+ xnvc_set_printf (state, "Expires:", "%llu", (unsigned long long)expire);
+ xnvc_set_hex (state, "Hash-PKi:", msg+24, 32);
+ xnvc_set_hex (state, "DH-PKr:", public, 32);
+ xnvc_set_hex (state, "DH-SKr:", secret, 32);
+ gcry_md_hash_buffer (GCRY_MD_SHA256, tmphash, newmsg, newmsglen);
+ xnvc_set_hex (state, "Hash-DHPart1:", tmphash, 32);
+
+ /* Write the state. Note that we need to create it. */
+ write_state (state, 1);
+
+ /* Write the message. */
+ send_message (newmsg, newmsglen);
+
+ leave:
+ xfree (newmsg);
+ return err;
+}
+
+
+/* We are the Initiator: Process a DHPART1 message in (MSG,MSGLEN)
+ * which has already been validated to have a correct header and
+ * message type. Sends the DHPart2 message and writes STATE. */
+static gpg_error_t
+proc_msg_dhpart1 (nvc_t state, const unsigned char *msg, size_t msglen)
+{
+ gpg_error_t err;
+ unsigned char hash[32];
+ unsigned char tmphash[32];
+ unsigned char pki[32];
+ unsigned char pkr[32];
+ unsigned char ski[32];
+ unsigned char master[32];
+ uint64_t expire;
+ unsigned char expirebuf[5];
+ unsigned char hmacikey[32];
+ unsigned char symxkey[32];
+ unsigned char *newmsg = NULL;
+ size_t newmsglen;
+
+ log_assert (msglen >= 80);
+
+ /* Check that the message includes the Hash(Commit). */
+ if (hex2bin (xnvc_get_string (state, "Hash-Commit:"), hash, sizeof hash) < 0)
+ {
+ err = gpg_error (GPG_ERR_INV_VALUE);
+ log_error ("no or garbled 'Hash-Commit' in our state file\n");
+ goto leave;
+ }
+ hash_data (tmphash, 32,
+ hash, sizeof hash,
+ msg, (size_t)48,
+ NULL);
+ if (memcmp (msg+48, tmphash, 32))
+ {
+ err = gpg_error (GPG_ERR_BAD_DATA);
+ log_error ("manipulation of received %s message detected: %s\n",
+ msgtypestr (MSG_TYPE_DHPART1), "Bad Hash");
+ goto leave;
+ }
+ /* Check that the received PKr is different from our PKi and copy
+ * PKr into PKR. */
+ if (hex2bin (xnvc_get_string (state, "DH-PKi:"), pki, sizeof pki) < 0)
+ {
+ err = gpg_error (GPG_ERR_INV_VALUE);
+ log_error ("no or garbled 'DH-PKi' in our state file\n");
+ goto leave;
+ }
+ if (!memcmp (msg+16, pki, 32))
+ {
+ /* This can only happen if the state file leaked to the
+ * responder. */
+ err = gpg_error (GPG_ERR_BAD_DATA);
+ log_error ("received our own public key PKi instead of PKr\n");
+ goto leave;
+ }
+ memcpy (pkr, msg+16, 32);
+
+ /* Put the expire value into a buffer. */
+ expire = string_to_u64 (xnvc_get_string (state, "Expires:"));
+ if (!expire)
+ {
+ err = gpg_error (GPG_ERR_INV_VALUE);
+ log_error ("no 'Expire' in our state file\n");
+ goto leave;
+ }
+ expirebuf[0] = expire >> 32;
+ expirebuf[1] = expire >> 24;
+ expirebuf[2] = expire >> 16;
+ expirebuf[3] = expire >> 8;
+ expirebuf[4] = expire;
+
+ /* Get our secret from the state. */
+ if (hex2bin (xnvc_get_string (state, "DH-SKi:"), ski, sizeof ski) < 0)
+ {
+ err = gpg_error (GPG_ERR_INV_VALUE);
+ log_error ("no or garbled 'DH-SKi' in our state file\n");
+ goto leave;
+ }
+
+ /* Compute the shared secrets. */
+ err = compute_master_secret (master, sizeof master,
+ ski, sizeof ski, pkr, sizeof pkr);
+ if (err)
+ {
+ log_error ("creating DH keypair failed: %s\n", gpg_strerror (err));
+ goto leave;
+ }
+
+ kdf (hmacikey, sizeof hmacikey,
+ master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf,
+ "GPG-pa1-HMACi-key");
+ kdf (symxkey, sizeof symxkey,
+ master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf,
+ "GPG-pa1-SYMx-key");
+
+
+ /* Create the response. */
+ newmsglen = 7+1+8+32+32;
+ newmsg = xmalloc (newmsglen);
+ memcpy (newmsg+0, "GPG-pa1", 7);
+ newmsg[7] = MSG_TYPE_DHPART2;
+ memcpy (newmsg+8, msg + 8, 8); /* SessionID. */
+ memcpy (newmsg+16, pki, 32); /* PKi */
+ /* MAC(HMACi-key, Hash(DHPART1) || DHPART2[0..47] || SYMx-key) */
+ gcry_md_hash_buffer (GCRY_MD_SHA256, tmphash, msg, msglen);
+ hmac_data (newmsg+48, 32, hmacikey, sizeof hmacikey,
+ tmphash, sizeof tmphash,
+ newmsg, (size_t)48,
+ symxkey, sizeof symxkey,
+ NULL);
+
+ /* Update the state. */
+ xnvc_set (state, "State:", "DHPart2-sent");
+ xnvc_set_hex (state, "DH-Master:", master, sizeof master);
+ gcry_md_hash_buffer (GCRY_MD_SHA256, tmphash, newmsg, newmsglen);
+ xnvc_set_hex (state, "Hash-DHPart2:", tmphash, 32);
+
+ /* Write the state. */
+ write_state (state, 0);
+
+ /* Write the message. */
+ send_message (newmsg, newmsglen);
+
+ leave:
+ xfree (newmsg);
+ return err;
+}
+
+
+/* We are the Responder: Process a DHPART2 message in (MSG,MSGLEN)
+ * which has already been validated to have a correct header and
+ * message type. Sends the CONFIRM message and writes STATE. */
+static gpg_error_t
+proc_msg_dhpart2 (nvc_t state, const unsigned char *msg, size_t msglen)
+{
+ gpg_error_t err;
+ unsigned char hash[32];
+ unsigned char tmphash[32];
+ uint64_t expire;
+ unsigned char expirebuf[5];
+ unsigned char pki[32];
+ unsigned char pkr[32];
+ unsigned char skr[32];
+ unsigned char master[32];
+ unsigned char hmacikey[32];
+ unsigned char hmacrkey[32];
+ unsigned char symxkey[32];
+ unsigned char sas[32];
+ unsigned char *newmsg = NULL;
+ size_t newmsglen;
+
+ log_assert (msglen >= 80);
+
+ /* Check that the PKi in the message matches the Hash(Pki) received
+ * with the Commit message. */
+ memcpy (pki, msg + 16, 32);
+ gcry_md_hash_buffer (GCRY_MD_SHA256, hash, pki, 32);
+ if (hex2bin (xnvc_get_string (state, "Hash-PKi:"),
+ tmphash, sizeof tmphash) < 0)
+ {
+ err = gpg_error (GPG_ERR_INV_VALUE);
+ log_error ("no or garbled 'Hash-PKi' in our state file\n");
+ goto leave;
+ }
+ if (memcmp (hash, tmphash, 32))
+ {
+ err = gpg_error (GPG_ERR_BAD_DATA);
+ log_error ("Initiator sent a different key in %s than announced in %s\n",
+ msgtypestr (MSG_TYPE_DHPART2),
+ msgtypestr (MSG_TYPE_COMMIT));
+ goto leave;
+ }
+ /* Check that the received PKi is different from our PKr. */
+ if (hex2bin (xnvc_get_string (state, "DH-PKr:"), pkr, sizeof pkr) < 0)
+ {
+ err = gpg_error (GPG_ERR_INV_VALUE);
+ log_error ("no or garbled 'DH-PKr' in our state file\n");
+ goto leave;
+ }
+ if (!memcmp (pkr, pki, 32))
+ {
+ err = gpg_error (GPG_ERR_BAD_DATA);
+ log_error ("Initiator sent our own PKr back\n");
+ goto leave;
+ }
+
+ /* Put the expire value into a buffer. */
+ expire = string_to_u64 (xnvc_get_string (state, "Expires:"));
+ if (!expire)
+ {
+ err = gpg_error (GPG_ERR_INV_VALUE);
+ log_error ("no 'Expire' in our state file\n");
+ goto leave;
+ }
+ expirebuf[0] = expire >> 32;
+ expirebuf[1] = expire >> 24;
+ expirebuf[2] = expire >> 16;
+ expirebuf[3] = expire >> 8;
+ expirebuf[4] = expire;
+
+ /* Get our secret from the state. */
+ if (hex2bin (xnvc_get_string (state, "DH-SKr:"), skr, sizeof skr) < 0)
+ {
+ err = gpg_error (GPG_ERR_INV_VALUE);
+ log_error ("no or garbled 'DH-SKr' in our state file\n");
+ goto leave;
+ }
+
+ /* Compute the shared secrets. */
+ err = compute_master_secret (master, sizeof master,
+ skr, sizeof skr, pki, sizeof pki);
+ if (err)
+ {
+ log_error ("creating DH keypair failed: %s\n", gpg_strerror (err));
+ goto leave;
+ }
+
+ kdf (hmacikey, sizeof hmacikey,
+ master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf,
+ "GPG-pa1-HMACi-key");
+ kdf (hmacrkey, sizeof hmacrkey,
+ master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf,
+ "GPG-pa1-HMACr-key");
+ kdf (symxkey, sizeof symxkey,
+ master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf,
+ "GPG-pa1-SYMx-key");
+ kdf (sas, sizeof sas,
+ master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf,
+ "GPG-pa1-SAS");
+
+ /* Check the MAC from the message which is
+ * MAC(HMACi-key, Hash(DHPART1) || DHPART2[0..47] || SYMx-key).
+ * For that we need to fetch the stored hash from the state. */
+ if (hex2bin (xnvc_get_string (state, "Hash-DHPart1:"),
+ tmphash, sizeof tmphash) < 0)
+ {
+ err = gpg_error (GPG_ERR_INV_VALUE);
+ log_error ("no or garbled 'Hash-DHPart1' in our state file\n");
+ goto leave;
+ }
+ hmac_data (hash, 32, hmacikey, sizeof hmacikey,
+ tmphash, sizeof tmphash,
+ msg, 48,
+ symxkey, sizeof symxkey,
+ NULL);
+ if (memcmp (msg+48, hash, 32))
+ {
+ err = gpg_error (GPG_ERR_BAD_DATA);
+ log_error ("manipulation of received %s message detected: %s\n",
+ msgtypestr (MSG_TYPE_DHPART2), "Bad MAC");
+ goto leave;
+ }
+
+ /* Create the response. */
+ newmsglen = 7+1+8+32;
+ newmsg = xmalloc (newmsglen);
+ memcpy (newmsg+0, "GPG-pa1", 7);
+ newmsg[7] = MSG_TYPE_CONFIRM;
+ memcpy (newmsg+8, msg + 8, 8); /* SessionID. */
+ /* MAC(HMACr-key, Hash(DHPART2) || CONFIRM[0..15] || SYMx-key) */
+ gcry_md_hash_buffer (GCRY_MD_SHA256, tmphash, msg, msglen);
+ hmac_data (newmsg+16, 32, hmacrkey, sizeof hmacrkey,
+ tmphash, sizeof tmphash,
+ newmsg, (size_t)16,
+ symxkey, sizeof symxkey,
+ NULL);
+
+ /* Update the state. */
+ xnvc_set (state, "State:", "Confirm-sent");
+ xnvc_set_hex (state, "DH-Master:", master, sizeof master);
+
+ /* Write the state. */
+ write_state (state, 0);
+
+ /* Write the message. */
+ send_message (newmsg, newmsglen);
+
+ display_sas (sas, sizeof sas, 0);
+
+
+ leave:
+ xfree (newmsg);
+ return err;
+}
+
+
+/* We are the Initiator: Process a CONFIRM message in (MSG,MSGLEN)
+ * which has already been validated to have a correct header and
+ * message type. Does not send anything back. */
+static gpg_error_t
+proc_msg_confirm (nvc_t state, const unsigned char *msg, size_t msglen)
+{
+ gpg_error_t err;
+ unsigned char hash[32];
+ unsigned char tmphash[32];
+ unsigned char master[32];
+ uint64_t expire;
+ unsigned char expirebuf[5];
+ unsigned char hmacrkey[32];
+ unsigned char symxkey[32];
+ unsigned char sas[32];
+
+ log_assert (msglen >= 48);
+
+ /* Put the expire value into a buffer. */
+ expire = string_to_u64 (xnvc_get_string (state, "Expires:"));
+ if (!expire)
+ {
+ err = gpg_error (GPG_ERR_INV_VALUE);
+ log_error ("no 'Expire' in our state file\n");
+ goto leave;
+ }
+ expirebuf[0] = expire >> 32;
+ expirebuf[1] = expire >> 24;
+ expirebuf[2] = expire >> 16;
+ expirebuf[3] = expire >> 8;
+ expirebuf[4] = expire;
+
+ /* Get the master secret. */
+ if (hex2bin (xnvc_get_string (state, "DH-Master:"),master,sizeof master) < 0)
+ {
+ err = gpg_error (GPG_ERR_INV_VALUE);
+ log_error ("no or garbled 'DH-Master' in our state file\n");
+ goto leave;
+ }
+
+ kdf (hmacrkey, sizeof hmacrkey,
+ master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf,
+ "GPG-pa1-HMACr-key");
+ kdf (symxkey, sizeof symxkey,
+ master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf,
+ "GPG-pa1-SYMx-key");
+ kdf (sas, sizeof sas,
+ master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf,
+ "GPG-pa1-SAS");
+
+ /* Check the MAC from the message which is */
+ /* MAC(HMACr-key, Hash(DHPART2) || CONFIRM[0..15] || SYMx-key). */
+ if (hex2bin (xnvc_get_string (state, "Hash-DHPart2:"),
+ tmphash, sizeof tmphash) < 0)
+ {
+ err = gpg_error (GPG_ERR_INV_VALUE);
+ log_error ("no or garbled 'Hash-DHPart2' in our state file\n");
+ goto leave;
+ }
+ hmac_data (hash, 32, hmacrkey, sizeof hmacrkey,
+ tmphash, sizeof tmphash,
+ msg, (size_t)16,
+ symxkey, sizeof symxkey,
+ NULL);
+ if (!memcmp (msg+48, hash, 32))
+ {
+ err = gpg_error (GPG_ERR_BAD_DATA);
+ log_error ("manipulation of received %s message detected: %s\n",
+ msgtypestr (MSG_TYPE_CONFIRM), "Bad MAC");
+ goto leave;
+ }
+
+
+ err = display_sas (sas, sizeof sas, 1);
+ if (err)
+ goto leave;
+
+ /* Update the state. */
+ xnvc_set (state, "State:", "Confirmed");
+
+ /* Write the state. */
+ write_state (state, 0);
+
+ leave:
+ return err;
+}
+
+
+
+/* Expire old state files. This loops over all state files and remove
+ * those which are expired. */
+static void
+expire_old_states (void)
+{
+ gpg_error_t err = 0;
+ const char *dirname;
+ DIR *dir = NULL;
+ struct dirent *dir_entry;
+ char *fname = NULL;
+ estream_t fp = NULL;
+ nvc_t nvc = NULL;
+ nve_t item;
+ const char *value;
+ unsigned long expire;
+ unsigned long now = gnupg_get_time ();
+
+ dirname = get_pairing_statedir ();
+ dir = opendir (dirname);
+ if (!dir)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+
+ while ((dir_entry = readdir (dir)))
+ {
+ if (strlen (dir_entry->d_name) != 16+4
+ || strcmp (dir_entry->d_name + 16, ".pa1"))
+ continue;
+
+ xfree (fname);
+ fname = make_filename (dirname, dir_entry->d_name, NULL);
+ es_fclose (fp);
+ fp = es_fopen (fname, "rb");
+ if (!fp)
+ {
+ err = gpg_error_from_syserror ();
+ if (gpg_err_code (err) != GPG_ERR_ENOENT)
+ log_info ("failed to open state file '%s': %s\n",
+ fname, gpg_strerror (err));
+ continue;
+ }
+ nvc_release (nvc);
+
+ /* NB.: The following is similar to code in read_state. */
+ err = nvc_parse (&nvc, NULL, fp);
+ if (err)
+ {
+ log_info ("failed to parse state file '%s': %s\n",
+ fname, gpg_strerror (err));
+ continue; /* Skip */
+ }
+ item = nvc_lookup (nvc, "Expires:");
+ if (!item)
+ {
+ log_info ("invalid state file '%s': %s\n",
+ fname, "field 'expire' not found");
+ continue; /* Skip */
+ }
+ value = nve_value (item);
+ if (!value || !(expire = strtoul (value, NULL, 10)))
+ {
+ log_info ("invalid state file '%s': %s\n",
+ fname, "field 'expire' has an invalid value");
+ continue; /* Skip */
+ }
+
+ if (expire <= now)
+ {
+ es_fclose (fp);
+ fp = NULL;
+ if (gnupg_remove (fname))
+ {
+ err = gpg_error_from_syserror ();
+ log_info ("failed to delete state file '%s': %s\n",
+ fname, gpg_strerror (err));
+ }
+ else if (opt.verbose)
+ log_info ("state file '%s' deleted\n", fname);
+ }
+ }
+
+ leave:
+ if (err)
+ log_error ("expiring old states in '%s' failed: %s\n",
+ dirname, gpg_strerror (err));
+ if (dir)
+ closedir (dir);
+ es_fclose (fp);
+ xfree (fname);
+}
+
+
+
+/* Initiate a pairing. The output needs to be conveyed to the
+ * peer */
+static gpg_error_t
+command_initiate (void)
+{
+ gpg_error_t err;
+ nvc_t state;
+
+ state = xnvc_new ();
+ xnvc_set (state, "Version:", "GPG-pa1");
+ xnvc_set_hex (state, "Session:", get_session_id (), 8);
+ xnvc_set (state, "Role:", "Initiator");
+
+ err = make_msg_commit (state);
+
+ nvc_release (state);
+ return err;
+}
+
+
+
+/* Helper for command_respond(). */
+static gpg_error_t
+expect_state (int msgtype, const char *statestr, const char *expected)
+{
+ if (strcmp (statestr, expected))
+ {
+ log_error ("received %s message in %s state (should be %s)\n",
+ msgtypestr (msgtype), statestr, expected);
+ return gpg_error (GPG_ERR_INV_RESPONSE);
+ }
+ return 0;
+}
+
+/* Respond to a pairing intiation. This is used by the peer and later
+ * by the original responder. Depending on the state the output needs
+ * to be conveyed to the peer. */
+static gpg_error_t
+command_respond (void)
+{
+ gpg_error_t err;
+ unsigned char *msg;
+ size_t msglen = 0; /* In case that read_message returns an error. */
+ int msgtype = 0; /* ditto. */
+ nvc_t state;
+ const char *rolestr;
+ const char *statestr;
+
+ err = read_message (&msg, &msglen, &msgtype, &state);
+ if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND)
+ goto leave;
+ rolestr = xnvc_get_string (state, "Role:");
+ statestr = xnvc_get_string (state, "State:");
+ if (DBG_MESSAGE)
+ {
+ if (!state)
+ log_debug ("no state available\n");
+ else
+ log_debug ("we are %s, our current state is %s\n", rolestr, statestr);
+ log_debug ("got message of type %s (%d)\n",
+ msgtypestr (msgtype), msgtype);
+ }
+
+ if (!state)
+ {
+ if (msgtype == MSG_TYPE_COMMIT)
+ {
+ state = xnvc_new ();
+ xnvc_set (state, "Version:", "GPG-pa1");
+ xnvc_set_hex (state, "Session:", get_session_id (), 8);
+ xnvc_set (state, "Role:", "Responder");
+ err = proc_msg_commit (state, msg, msglen);
+ }
+ else
+ {
+ log_error ("%s message expected but got %s\n",
+ msgtypestr (MSG_TYPE_COMMIT), msgtypestr (msgtype));
+ if (msgtype == MSG_TYPE_DHPART1)
+ log_info ("the pairing probably took too long and timed out\n");
+ err = gpg_error (GPG_ERR_INV_RESPONSE);
+ goto leave;
+ }
+ }
+ else if (!strcmp (rolestr, "Initiator"))
+ {
+ if (msgtype == MSG_TYPE_DHPART1)
+ {
+ if (!(err = expect_state (msgtype, statestr, "Commit-sent")))
+ err = proc_msg_dhpart1 (state, msg, msglen);
+ }
+ else if (msgtype == MSG_TYPE_CONFIRM)
+ {
+ if (!(err = expect_state (msgtype, statestr, "DHPart2-sent")))
+ err = proc_msg_confirm (state, msg, msglen);
+ }
+ else
+ {
+ log_error ("%s message not expected by Initiator\n",
+ msgtypestr (msgtype));
+ err = gpg_error (GPG_ERR_INV_RESPONSE);
+ goto leave;
+ }
+ }
+ else if (!strcmp (rolestr, "Responder"))
+ {
+ if (msgtype == MSG_TYPE_DHPART2)
+ {
+ if (!(err = expect_state (msgtype, statestr, "DHPart1-sent")))
+ err = proc_msg_dhpart2 (state, msg, msglen);
+ }
+ else
+ {
+ log_error ("%s message not expected by Responder\n",
+ msgtypestr (msgtype));
+ err = gpg_error (GPG_ERR_INV_RESPONSE);
+ goto leave;
+ }
+ }
+ else
+ log_fatal ("invalid role '%s' in state file\n", rolestr);
+
+
+ leave:
+ xfree (msg);
+ nvc_release (state);
+ return err;
+}
+
+
+
+/* Return the keys for SESSIONIDSTR or the last one if it is NULL.
+ * Two keys are returned: The first is the one for sending encrypted
+ * data and the second one for decrypting received data. The keys are
+ * always returned hex encoded and both are terminated by a LF. */
+static gpg_error_t
+command_get (const char *sessionidstr)
+{
+ gpg_error_t err;
+ unsigned char sessid[8];
+ nvc_t state;
+
+ if (!sessionidstr)
+ {
+ log_error ("calling without session-id is not yet implemented\n");
+ err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+ goto leave;
+ }
+ if (hex2bin (sessionidstr, sessid, sizeof sessid) < 0)
+ {
+ err = gpg_error (GPG_ERR_INV_VALUE);
+ log_error ("invalid session id given\n");
+ goto leave;
+ }
+ set_session_id (sessid, sizeof sessid);
+ err = read_state (&state);
+ if (err)
+ {
+ log_error ("reading state of session %s failed: %s\n",
+ sessionidstr, gpg_strerror (err));
+ goto leave;
+ }
+
+ leave:
+ return err;
+}
+
+
+
+/* Cleanup command. */
+static gpg_error_t
+command_cleanup (void)
+{
+ expire_old_states ();
+ return 0;
+}
diff --git a/tools/gpg-wks-client.c b/tools/gpg-wks-client.c
index 73945ff30..0f08737c4 100644
--- a/tools/gpg-wks-client.c
+++ b/tools/gpg-wks-client.c
@@ -22,6 +22,8 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
#include "../common/util.h"
#include "../common/status.h"
@@ -48,6 +50,7 @@ enum cmd_and_opt_values
oQuiet = 'q',
oVerbose = 'v',
oOutput = 'o',
+ oDirectory = 'C',
oDebug = 500,
@@ -56,11 +59,14 @@ enum cmd_and_opt_values
aCreate,
aReceive,
aRead,
+ aInstallKey,
+ aRemoveKey,
oGpgProgram,
oSend,
oFakeSubmissionAddr,
oStatusFD,
+ oWithColons,
oDummy
};
@@ -80,6 +86,10 @@ static ARGPARSE_OPTS opts[] = {
("receive a MIME confirmation request")),
ARGPARSE_c (aRead, "read",
("receive a plain text confirmation request")),
+ ARGPARSE_c (aInstallKey, "install-key",
+ "install a key into a directory"),
+ ARGPARSE_c (aRemoveKey, "remove-key",
+ "remove a key from a directory"),
ARGPARSE_group (301, ("@\nOptions:\n ")),
@@ -90,6 +100,8 @@ static ARGPARSE_OPTS opts[] = {
ARGPARSE_s_n (oSend, "send", "send the mail using sendmail"),
ARGPARSE_s_s (oOutput, "output", "|FILE|write the mail to FILE"),
ARGPARSE_s_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")),
+ ARGPARSE_s_n (oWithColons, "with-colons", "@"),
+ ARGPARSE_s_s (oDirectory, "directory", "@"),
ARGPARSE_s_s (oFakeSubmissionAddr, "fake-submission-addr", "@"),
@@ -192,6 +204,9 @@ parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts)
case oGpgProgram:
opt.gpg_program = pargs->r.ret_str;
break;
+ case oDirectory:
+ opt.directory = pargs->r.ret_str;
+ break;
case oSend:
opt.use_sendmail = 1;
break;
@@ -204,12 +219,17 @@ parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts)
case oStatusFD:
wks_set_status_fd (translate_sys2libc_fd_int (pargs->r.ret_int, 1));
break;
+ case oWithColons:
+ opt.with_colons = 1;
+ break;
case aSupported:
case aCreate:
case aReceive:
case aRead:
case aCheck:
+ case aInstallKey:
+ case aRemoveKey:
cmd = pargs->r_opt;
break;
@@ -264,18 +284,52 @@ main (int argc, char **argv)
if (!opt.gpg_program)
opt.gpg_program = gnupg_module_name (GNUPG_MODULE_NAME_GPG);
+ if (!opt.directory)
+ opt.directory = "openpgpkey";
+
/* Tell call-dirmngr what options we want. */
set_dirmngr_options (opt.verbose, (opt.debug & DBG_IPC_VALUE), 1);
+
+ /* Check that the top directory exists. */
+ if (cmd == aInstallKey || cmd == aRemoveKey)
+ {
+ struct stat sb;
+
+ if (stat (opt.directory, &sb))
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("error accessing directory '%s': %s\n",
+ opt.directory, gpg_strerror (err));
+ goto leave;
+ }
+ if (!S_ISDIR(sb.st_mode))
+ {
+ log_error ("error accessing directory '%s': %s\n",
+ opt.directory, "not a directory");
+ err = gpg_error (GPG_ERR_ENOENT);
+ goto leave;
+ }
+ }
+
/* Run the selected command. */
switch (cmd)
{
case aSupported:
- if (argc != 1)
- wrong_args ("--supported USER-ID");
- err = command_supported (argv[0]);
- if (err && gpg_err_code (err) != GPG_ERR_FALSE)
- log_error ("checking support failed: %s\n", gpg_strerror (err));
+ if (opt.with_colons)
+ {
+ for (; argc; argc--, argv++)
+ command_supported (*argv);
+ err = 0;
+ }
+ else
+ {
+ if (argc != 1)
+ wrong_args ("--supported DOMAIN");
+ err = command_supported (argv[0]);
+ if (err && gpg_err_code (err) != GPG_ERR_FALSE)
+ log_error ("checking support failed: %s\n", gpg_strerror (err));
+ }
break;
case aCreate:
@@ -308,12 +362,28 @@ main (int argc, char **argv)
err = command_check (argv[0]);
break;
+ case aInstallKey:
+ if (!argc)
+ err = wks_cmd_install_key (NULL, NULL);
+ else if (argc == 2)
+ err = wks_cmd_install_key (*argv, argv[1]);
+ else
+ wrong_args ("--install-key [FILE|FINGERPRINT USER-ID]");
+ break;
+
+ case aRemoveKey:
+ if (argc != 1)
+ wrong_args ("--remove-key USER-ID");
+ err = wks_cmd_remove_key (*argv);
+ break;
+
default:
usage (1);
err = 0;
break;
}
+ leave:
if (err)
wks_write_status (STATUS_FAILURE, "- %u", err);
else if (log_get_errorcount (0))
@@ -471,6 +541,134 @@ decrypt_stream (estream_t *r_output, struct decrypt_stream_parm_s *decinfo,
}
+/* Return the submission address for the address or just the domain in
+ * ADDRSPEC. The submission address is stored as a malloced string at
+ * R_SUBMISSION_ADDRESS. At R_POLICY the policy flags of the domain
+ * are stored. The caller needs to free them with wks_free_policy.
+ * The function returns an error code on failure to find a submission
+ * address or policy file. Note: The function may store NULL at
+ * R_SUBMISSION_ADDRESS but return success to indicate that the web
+ * key directory is supported but not the web key service. As per WKD
+ * specs a policy file is always required and will thus be return on
+ * success. */
+static gpg_error_t
+get_policy_and_sa (const char *addrspec, int silent,
+ policy_flags_t *r_policy, char **r_submission_address)
+{
+ gpg_error_t err;
+ estream_t mbuf = NULL;
+ const char *domain;
+ const char *s;
+ policy_flags_t policy = NULL;
+ char *submission_to = NULL;
+
+ *r_submission_address = NULL;
+ *r_policy = NULL;
+
+ domain = strchr (addrspec, '@');
+ if (domain)
+ domain++;
+
+ if (opt.with_colons)
+ {
+ s = domain? domain : addrspec;
+ es_write_sanitized (es_stdout, s, strlen (s), ":", NULL);
+ es_putc (':', es_stdout);
+ }
+
+ /* We first try to get the submission address from the policy file
+ * (this is the new method). If both are available we check that
+ * they match and print a warning if not. In the latter case we
+ * keep on using the one from the submission-address file. */
+ err = wkd_get_policy_flags (addrspec, &mbuf);
+ if (err && gpg_err_code (err) != GPG_ERR_NO_DATA
+ && gpg_err_code (err) != GPG_ERR_NO_NAME)
+ {
+ if (!opt.with_colons)
+ log_error ("error reading policy flags for '%s': %s\n",
+ domain, gpg_strerror (err));
+ goto leave;
+ }
+ if (!mbuf)
+ {
+ if (!opt.with_colons)
+ log_error ("provider for '%s' does NOT support the Web Key Directory\n",
+ addrspec);
+ err = gpg_error (GPG_ERR_FALSE);
+ goto leave;
+ }
+
+ policy = xtrycalloc (1, sizeof *policy);
+ if (!policy)
+ err = gpg_error_from_syserror ();
+ else
+ err = wks_parse_policy (policy, mbuf, 1);
+ es_fclose (mbuf);
+ mbuf = NULL;
+ if (err)
+ goto leave;
+
+ err = wkd_get_submission_address (addrspec, &submission_to);
+ if (err && !policy->submission_address)
+ {
+ if (!silent && !opt.with_colons)
+ log_error (_("error looking up submission address for domain '%s'"
+ ": %s\n"), domain, gpg_strerror (err));
+ if (!silent && gpg_err_code (err) == GPG_ERR_NO_DATA && !opt.with_colons)
+ log_error (_("this domain probably doesn't support WKS.\n"));
+ goto leave;
+ }
+
+ if (submission_to && policy->submission_address
+ && ascii_strcasecmp (submission_to, policy->submission_address))
+ log_info ("Warning: different submission addresses (sa=%s, po=%s)\n",
+ submission_to, policy->submission_address);
+
+ if (!submission_to && policy->submission_address)
+ {
+ submission_to = xtrystrdup (policy->submission_address);
+ if (!submission_to)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ }
+
+ leave:
+ *r_submission_address = submission_to;
+ submission_to = NULL;
+ *r_policy = policy;
+ policy = NULL;
+
+ if (opt.with_colons)
+ {
+ if (*r_policy && !*r_submission_address)
+ es_fprintf (es_stdout, "1:0::");
+ else if (*r_policy && *r_submission_address)
+ es_fprintf (es_stdout, "1:1::");
+ else if (err && !(gpg_err_code (err) == GPG_ERR_FALSE
+ || gpg_err_code (err) == GPG_ERR_NO_DATA
+ || gpg_err_code (err) == GPG_ERR_UNKNOWN_HOST))
+ es_fprintf (es_stdout, "0:0:%d:", err);
+ else
+ es_fprintf (es_stdout, "0:0::");
+ if (*r_policy)
+ {
+ es_fprintf (es_stdout, "%u:%u:%u:",
+ (*r_policy)->protocol_version,
+ (*r_policy)->auth_submit,
+ (*r_policy)->mailbox_only);
+ }
+ es_putc ('\n', es_stdout);
+ }
+
+ xfree (submission_to);
+ wks_free_policy (policy);
+ xfree (policy);
+ es_fclose (mbuf);
+ return err;
+}
+
/* Check whether the provider supports the WKS protocol. */
@@ -480,15 +678,16 @@ command_supported (char *userid)
gpg_error_t err;
char *addrspec = NULL;
char *submission_to = NULL;
+ policy_flags_t policy = NULL;
if (!strchr (userid, '@'))
{
char *tmp = xstrconcat ("foo@", userid, NULL);
- addrspec = mailbox_from_userid (tmp);
+ addrspec = mailbox_from_userid (tmp, 0);
xfree (tmp);
}
else
- addrspec = mailbox_from_userid (userid);
+ addrspec = mailbox_from_userid (userid, 0);
if (!addrspec)
{
log_error (_("\"%s\" is not a proper mail address\n"), userid);
@@ -497,24 +696,41 @@ command_supported (char *userid)
}
/* Get the submission address. */
- err = wkd_get_submission_address (addrspec, &submission_to);
- if (err)
+ err = get_policy_and_sa (addrspec, 1, &policy, &submission_to);
+ if (err || !submission_to)
{
- if (gpg_err_code (err) == GPG_ERR_NO_DATA
- || gpg_err_code (err) == GPG_ERR_UNKNOWN_HOST)
+ if (!submission_to
+ || gpg_err_code (err) == GPG_ERR_FALSE
+ || gpg_err_code (err) == GPG_ERR_NO_DATA
+ || gpg_err_code (err) == GPG_ERR_UNKNOWN_HOST
+ )
{
- if (opt.verbose)
- log_info ("provider for '%s' does NOT support WKS (%s)\n",
- addrspec, gpg_strerror (err));
+ /* FALSE is returned if we already figured out that even the
+ * Web Key Directory is not supported and thus printed an
+ * error message. */
+ if (opt.verbose && gpg_err_code (err) != GPG_ERR_FALSE
+ && !opt.with_colons)
+ {
+ if (gpg_err_code (err) == GPG_ERR_NO_DATA)
+ log_info ("provider for '%s' does NOT support WKS\n",
+ addrspec);
+ else
+ log_info ("provider for '%s' does NOT support WKS (%s)\n",
+ addrspec, gpg_strerror (err));
+ }
err = gpg_error (GPG_ERR_FALSE);
- log_inc_errorcount ();
+ if (!opt.with_colons)
+ log_inc_errorcount ();
}
goto leave;
}
- if (opt.verbose)
+
+ if (opt.verbose && !opt.with_colons)
log_info ("provider for '%s' supports WKS\n", addrspec);
leave:
+ wks_free_policy (policy);
+ xfree (policy);
xfree (submission_to);
xfree (addrspec);
return err;
@@ -534,7 +750,7 @@ command_check (char *userid)
uidinfo_list_t sl;
int found = 0;
- addrspec = mailbox_from_userid (userid);
+ addrspec = mailbox_from_userid (userid, 0);
if (!addrspec)
{
log_error (_("\"%s\" is not a proper mail address\n"), userid);
@@ -628,7 +844,7 @@ command_send (const char *fingerprint, const char *userid)
estream_t keyenc = NULL;
char *submission_to = NULL;
mime_maker_t mime = NULL;
- struct policy_flags_s policy;
+ policy_flags_t policy = NULL;
int no_encrypt = 0;
int posteo_hack = 0;
const char *domain;
@@ -636,18 +852,15 @@ command_send (const char *fingerprint, const char *userid)
uidinfo_list_t uid, thisuid;
time_t thistime;
- memset (&policy, 0, sizeof policy);
-
if (classify_user_id (fingerprint, &desc, 1)
- || !(desc.mode == KEYDB_SEARCH_MODE_FPR
- || desc.mode == KEYDB_SEARCH_MODE_FPR20))
+ || desc.mode != KEYDB_SEARCH_MODE_FPR)
{
log_error (_("\"%s\" is not a fingerprint\n"), fingerprint);
err = gpg_error (GPG_ERR_INV_NAME);
goto leave;
}
- addrspec = mailbox_from_userid (userid);
+ addrspec = mailbox_from_userid (userid, 0);
if (!addrspec)
{
log_error (_("\"%s\" is not a proper mail address\n"), userid);
@@ -665,62 +878,26 @@ command_send (const char *fingerprint, const char *userid)
/* Get the submission address. */
if (fake_submission_addr)
{
+ policy = xcalloc (1, sizeof *policy);
submission_to = xstrdup (fake_submission_addr);
err = 0;
}
else
{
- /* We first try to get the submission address from the policy
- * file (this is the new method). If both are available we
- * check that they match and print a warning if not. In the
- * latter case we keep on using the one from the
- * submission-address file. */
- estream_t mbuf;
-
- err = wkd_get_policy_flags (addrspec, &mbuf);
- if (err && gpg_err_code (err) != GPG_ERR_NO_DATA)
- {
- log_error ("error reading policy flags for '%s': %s\n",
- domain, gpg_strerror (err));
- goto leave;
- }
- if (mbuf)
- {
- err = wks_parse_policy (&policy, mbuf, 1);
- es_fclose (mbuf);
- if (err)
- goto leave;
- }
-
- err = wkd_get_submission_address (addrspec, &submission_to);
- if (err && !policy.submission_address)
- {
- log_error (_("error looking up submission address for domain '%s'"
- ": %s\n"), domain, gpg_strerror (err));
- if (gpg_err_code (err) == GPG_ERR_NO_DATA)
- log_error (_("this domain probably doesn't support WKS.\n"));
- goto leave;
- }
-
- if (submission_to && policy.submission_address
- && ascii_strcasecmp (submission_to, policy.submission_address))
- log_info ("Warning: different submission addresses (sa=%s, po=%s)\n",
- submission_to, policy.submission_address);
-
+ err = get_policy_and_sa (addrspec, 0, &policy, &submission_to);
+ if (err)
+ goto leave;
if (!submission_to)
{
- submission_to = xtrystrdup (policy.submission_address);
- if (!submission_to)
- {
- err = gpg_error_from_syserror ();
- goto leave;
- }
+ log_error (_("this domain probably doesn't support WKS.\n"));
+ err = gpg_error (GPG_ERR_NO_DATA);
+ goto leave;
}
}
log_info ("submitting request to '%s'\n", submission_to);
- if (policy.auth_submit)
+ if (policy->auth_submit)
log_info ("no confirmation required for '%s'\n", addrspec);
/* In case the key has several uids with the same addr-spec we will
@@ -738,8 +915,7 @@ command_send (const char *fingerprint, const char *userid)
{
if (!uid->mbox)
continue; /* Should not happen anyway. */
- if (policy.mailbox_only
- && ascii_strcasecmp (uid->uid, uid->mbox))
+ if (policy->mailbox_only && ascii_strcasecmp (uid->uid, uid->mbox))
continue; /* UID has more than just the mailbox. */
if (uid->created > thistime)
{
@@ -770,7 +946,7 @@ command_send (const char *fingerprint, const char *userid)
key = newkey;
}
- if (policy.mailbox_only
+ if (policy->mailbox_only
&& (!thisuid->mbox || ascii_strcasecmp (thisuid->uid, thisuid->mbox)))
{
log_info ("Warning: policy requires 'mailbox-only'"
@@ -791,7 +967,7 @@ command_send (const char *fingerprint, const char *userid)
/* Hack to support posteo but let them disable this by setting the
* new policy-version flag. */
- if (policy.protocol_version < 3
+ if (policy->protocol_version < 3
&& !ascii_strcasecmp (domain, "posteo.de"))
{
log_info ("Warning: Using draft-1 method for domain '%s'\n", domain);
@@ -908,7 +1084,8 @@ command_send (const char *fingerprint, const char *userid)
free_uidinfo_list (uidlist);
es_fclose (keyenc);
es_fclose (key);
- wks_free_policy (&policy);
+ wks_free_policy (policy);
+ xfree (policy);
xfree (addrspec);
return err;
}
@@ -973,6 +1150,7 @@ encrypt_response (estream_t *r_output, estream_t input, const char *addrspec,
ccparray_put (&ccp, "--status-fd=2");
ccparray_put (&ccp, "--always-trust");
ccparray_put (&ccp, "--armor");
+ ccparray_put (&ccp, "-z0"); /* No compression for improved robustness. */
if (fake_submission_addr)
ccparray_put (&ccp, "--auto-key-locate=clear,local");
else
diff --git a/tools/gpg-wks-server.c b/tools/gpg-wks-server.c
index a5881557f..f83ef6528 100644
--- a/tools/gpg-wks-server.c
+++ b/tools/gpg-wks-server.c
@@ -58,6 +58,7 @@ enum cmd_and_opt_values
oQuiet = 'q',
oVerbose = 'v',
oOutput = 'o',
+ oDirectory = 'C',
oDebug = 500,
@@ -108,6 +109,7 @@ static ARGPARSE_OPTS opts[] = {
ARGPARSE_s_s (oGpgProgram, "gpg", "@"),
ARGPARSE_s_n (oSend, "send", "send the mail using sendmail"),
ARGPARSE_s_s (oOutput, "output", "|FILE|write the mail to FILE"),
+ ARGPARSE_s_s (oDirectory, "directory", "|DIR|use DIR as top directory"),
ARGPARSE_s_s (oFrom, "from", "|ADDR|use ADDR as the default sender"),
ARGPARSE_s_s (oHeader, "header" ,
"|NAME=VALUE|add \"NAME: VALUE\" as header to all mails"),
@@ -155,8 +157,6 @@ static gpg_error_t command_receive_cb (void *opaque,
const char *mediatype, estream_t fp,
unsigned int flags);
static gpg_error_t command_list_domains (void);
-static gpg_error_t command_install_key (const char *fname, const char *userid);
-static gpg_error_t command_remove_key (const char *mailaddr);
static gpg_error_t command_revoke_key (const char *mailaddr);
static gpg_error_t command_check_key (const char *mailaddr);
static gpg_error_t command_cron (void);
@@ -225,6 +225,9 @@ parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts)
case oGpgProgram:
opt.gpg_program = pargs->r.ret_str;
break;
+ case oDirectory:
+ opt.directory = pargs->r.ret_str;
+ break;
case oFrom:
opt.default_from = pargs->r.ret_str;
break;
@@ -350,6 +353,7 @@ main (int argc, char **argv)
{
log_error ("directory '%s' has too relaxed permissions\n",
opt.directory);
+ log_info ("Fix by running: chmod o-rw '%s'\n", opt.directory);
exit (2);
}
}
@@ -377,15 +381,18 @@ main (int argc, char **argv)
break;
case aInstallKey:
- if (argc != 2)
- wrong_args ("--install-key FILE USER-ID");
- err = command_install_key (*argv, argv[1]);
+ if (!argc)
+ err = wks_cmd_install_key (NULL, NULL);
+ else if (argc == 2)
+ err = wks_cmd_install_key (*argv, argv[1]);
+ else
+ wrong_args ("--install-key [FILE|FINGERPRINT USER-ID]");
break;
case aRemoveKey:
if (argc != 1)
wrong_args ("--remove-key USER-ID");
- err = command_remove_key (*argv);
+ err = wks_cmd_remove_key (*argv);
break;
case aRevokeKey:
@@ -579,6 +586,7 @@ encrypt_stream (estream_t *r_output, estream_t input, const char *keyfile)
ccparray_put (&ccp, "--always-trust");
ccparray_put (&ccp, "--no-keyring");
ccparray_put (&ccp, "--armor");
+ ccparray_put (&ccp, "-z0"); /* No compression for improved robustness. */
ccparray_put (&ccp, "--recipient-file");
ccparray_put (&ccp, keyfile);
ccparray_put (&ccp, "--encrypt");
@@ -1340,81 +1348,6 @@ send_congratulation_message (const char *mbox, const char *keyfile)
}
-/* Write the content of SRC to the new file FNAME. */
-static gpg_error_t
-write_to_file (estream_t src, const char *fname)
-{
- gpg_error_t err;
- estream_t dst;
- char buffer[4096];
- size_t nread, written;
-
- dst = es_fopen (fname, "wb");
- if (!dst)
- return gpg_error_from_syserror ();
-
- do
- {
- nread = es_fread (buffer, 1, sizeof buffer, src);
- if (!nread)
- break;
- written = es_fwrite (buffer, 1, nread, dst);
- if (written != nread)
- break;
- }
- while (!es_feof (src) && !es_ferror (src) && !es_ferror (dst));
- if (!es_feof (src) || es_ferror (src) || es_ferror (dst))
- {
- err = gpg_error_from_syserror ();
- es_fclose (dst);
- gnupg_remove (fname);
- return err;
- }
-
- if (es_fclose (dst))
- {
- err = gpg_error_from_syserror ();
- log_error ("error closing '%s': %s\n", fname, gpg_strerror (err));
- return err;
- }
-
- return 0;
-}
-
-
-/* Compute the the full file name for the key with ADDRSPEC and return
- * it at R_FNAME. */
-static gpg_error_t
-compute_hu_fname (char **r_fname, const char *addrspec)
-{
- gpg_error_t err;
- char *hash;
- const char *domain;
- char sha1buf[20];
-
- *r_fname = NULL;
-
- domain = strchr (addrspec, '@');
- if (!domain || !domain[1] || domain == addrspec)
- return gpg_error (GPG_ERR_INV_ARG);
- domain++;
-
- gcry_md_hash_buffer (GCRY_MD_SHA1, sha1buf, addrspec, domain - addrspec - 1);
- hash = zb32_encode (sha1buf, 8*20);
- if (!hash)
- return gpg_error_from_syserror ();
-
- *r_fname = make_filename_try (opt.directory, domain, "hu", hash, NULL);
- if (!*r_fname)
- err = gpg_error_from_syserror ();
- else
- err = 0;
-
- xfree (hash);
- return err;
-}
-
-
/* Check that we have send a request with NONCE and publish the key. */
static gpg_error_t
check_and_publish (server_ctx_t ctx, const char *address, const char *nonce)
@@ -1489,7 +1422,7 @@ check_and_publish (server_ctx_t ctx, const char *address, const char *nonce)
}
/* Hash user ID and create filename. */
- err = compute_hu_fname (&fnewname, address);
+ err = wks_compute_hu_fname (&fnewname, address);
if (err)
goto leave;
@@ -1667,7 +1600,7 @@ command_receive_cb (void *opaque, const char *mediatype,
-/* Return a list of all configured domains. ECh list element is the
+/* Return a list of all configured domains. Each list element is the
* top directory for the domain. To figure out the actual domain
* name strrchr(name, '/') can be used. */
static gpg_error_t
@@ -1946,7 +1879,17 @@ command_list_domains (void)
if (!fp)
{
err = gpg_error_from_syserror ();
- if (gpg_err_code (err) != GPG_ERR_ENOENT)
+ if (gpg_err_code (err) == GPG_ERR_ENOENT)
+ {
+ fp = es_fopen (fname, "w");
+ if (!fp)
+ log_error ("domain %s: can't create policy file: %s\n",
+ domain, gpg_strerror (err));
+ else
+ es_fclose (fp);
+ fp = NULL;
+ }
+ else
log_error ("domain %s: error in policy file: %s\n",
domain, gpg_strerror (err));
}
@@ -1955,17 +1898,8 @@ command_list_domains (void)
struct policy_flags_s policy;
err = wks_parse_policy (&policy, fp, 0);
es_fclose (fp);
- if (!err)
- {
- struct policy_flags_s empty_policy;
- memset (&empty_policy, 0, sizeof empty_policy);
- if (!memcmp (&empty_policy, &policy, sizeof policy))
- log_error ("domain %s: empty policy file\n", domain);
- }
wks_free_policy (&policy);
}
-
-
}
err = 0;
@@ -1997,195 +1931,6 @@ command_cron (void)
}
-/* Install a single key into the WKD by reading FNAME and extracting
- * USERID. */
-static gpg_error_t
-command_install_key (const char *fname, const char *userid)
-{
- gpg_error_t err;
- KEYDB_SEARCH_DESC desc;
- estream_t fp = NULL;
- char *addrspec = NULL;
- char *fpr = NULL;
- uidinfo_list_t uidlist = NULL;
- uidinfo_list_t uid, thisuid;
- time_t thistime;
- char *huname = NULL;
- int any;
-
- addrspec = mailbox_from_userid (userid);
- if (!addrspec)
- {
- log_error ("\"%s\" is not a proper mail address\n", userid);
- err = gpg_error (GPG_ERR_INV_USER_ID);
- goto leave;
- }
-
- if (!classify_user_id (fname, &desc, 1)
- && (desc.mode == KEYDB_SEARCH_MODE_FPR
- || desc.mode == KEYDB_SEARCH_MODE_FPR20))
- {
- /* FNAME looks like a fingerprint. Get the key from the
- * standard keyring. */
- err = wks_get_key (&fp, fname, addrspec, 0);
- if (err)
- {
- log_error ("error getting key '%s' (uid='%s'): %s\n",
- fname, addrspec, gpg_strerror (err));
- goto leave;
- }
- }
- else /* Take it from the file */
- {
- fp = es_fopen (fname, "rb");
- if (!fp)
- {
- err = gpg_error_from_syserror ();
- log_error ("error reading '%s': %s\n", fname, gpg_strerror (err));
- goto leave;
- }
- }
-
- /* List the key so that we can figure out the newest UID with the
- * requested addrspec. */
- err = wks_list_key (fp, &fpr, &uidlist);
- if (err)
- {
- log_error ("error parsing key: %s\n", gpg_strerror (err));
- err = gpg_error (GPG_ERR_NO_PUBKEY);
- goto leave;
- }
- thistime = 0;
- thisuid = NULL;
- any = 0;
- for (uid = uidlist; uid; uid = uid->next)
- {
- if (!uid->mbox)
- continue; /* Should not happen anyway. */
- if (ascii_strcasecmp (uid->mbox, addrspec))
- continue; /* Not the requested addrspec. */
- any = 1;
- if (uid->created > thistime)
- {
- thistime = uid->created;
- thisuid = uid;
- }
- }
- if (!thisuid)
- thisuid = uidlist; /* This is the case for a missing timestamp. */
- if (!any)
- {
- log_error ("public key in '%s' has no mail address '%s'\n",
- fname, addrspec);
- err = gpg_error (GPG_ERR_INV_USER_ID);
- goto leave;
- }
-
- if (opt.verbose)
- log_info ("using key with user id '%s'\n", thisuid->uid);
-
- {
- estream_t fp2;
-
- es_rewind (fp);
- err = wks_filter_uid (&fp2, fp, thisuid->uid, 1);
- if (err)
- {
- log_error ("error filtering key: %s\n", gpg_strerror (err));
- err = gpg_error (GPG_ERR_NO_PUBKEY);
- goto leave;
- }
- es_fclose (fp);
- fp = fp2;
- }
-
- /* Hash user ID and create filename. */
- err = compute_hu_fname (&huname, addrspec);
- if (err)
- goto leave;
-
- /* Publish. */
- err = write_to_file (fp, huname);
- if (err)
- {
- log_error ("copying key to '%s' failed: %s\n", huname,gpg_strerror (err));
- goto leave;
- }
-
- /* Make sure it is world readable. */
- if (gnupg_chmod (huname, "-rwxr--r--"))
- log_error ("can't set permissions of '%s': %s\n",
- huname, gpg_strerror (gpg_err_code_from_syserror()));
-
- if (!opt.quiet)
- log_info ("key %s published for '%s'\n", fpr, addrspec);
-
- leave:
- xfree (huname);
- free_uidinfo_list (uidlist);
- xfree (fpr);
- xfree (addrspec);
- es_fclose (fp);
- return err;
-}
-
-
-/* Return the filename and optionally the addrspec for USERID at
- * R_FNAME and R_ADDRSPEC. R_ADDRSPEC might also be set on error. */
-static gpg_error_t
-fname_from_userid (const char *userid, char **r_fname, char **r_addrspec)
-{
- gpg_error_t err;
- char *addrspec = NULL;
- const char *domain;
- char *hash = NULL;
- const char *s;
- char shaxbuf[32]; /* Used for SHA-1 and SHA-256 */
-
- *r_fname = NULL;
- if (r_addrspec)
- *r_addrspec = NULL;
-
- addrspec = mailbox_from_userid (userid);
- if (!addrspec)
- {
- if (opt.verbose)
- log_info ("\"%s\" is not a proper mail address\n", userid);
- err = gpg_error (GPG_ERR_INV_USER_ID);
- goto leave;
- }
-
- domain = strchr (addrspec, '@');
- log_assert (domain);
- domain++;
-
- /* Hash user ID and create filename. */
- s = strchr (addrspec, '@');
- log_assert (s);
- gcry_md_hash_buffer (GCRY_MD_SHA1, shaxbuf, addrspec, s - addrspec);
- hash = zb32_encode (shaxbuf, 8*20);
- if (!hash)
- {
- err = gpg_error_from_syserror ();
- goto leave;
- }
-
- *r_fname = make_filename_try (opt.directory, domain, "hu", hash, NULL);
- if (!*r_fname)
- err = gpg_error_from_syserror ();
- else
- err = 0;
-
- leave:
- if (r_addrspec && addrspec)
- *r_addrspec = addrspec;
- else
- xfree (addrspec);
- xfree (hash);
- return err;
-}
-
-
/* Check whether the key with USER_ID is installed. */
static gpg_error_t
command_check_key (const char *userid)
@@ -2194,7 +1939,7 @@ command_check_key (const char *userid)
char *addrspec = NULL;
char *fname = NULL;
- err = fname_from_userid (userid, &fname, &addrspec);
+ err = wks_fname_from_userid (userid, &fname, &addrspec);
if (err)
goto leave;
@@ -2229,49 +1974,11 @@ command_check_key (const char *userid)
}
-/* Remove the key with mail address in USERID. */
-static gpg_error_t
-command_remove_key (const char *userid)
-{
- gpg_error_t err;
- char *addrspec = NULL;
- char *fname = NULL;
-
- err = fname_from_userid (userid, &fname, &addrspec);
- if (err)
- goto leave;
-
- if (gnupg_remove (fname))
- {
- err = gpg_error_from_syserror ();
- if (gpg_err_code (err) == GPG_ERR_ENOENT)
- {
- if (!opt.quiet)
- log_info ("key for '%s' is not installed\n", addrspec);
- log_inc_errorcount ();
- err = 0;
- }
- else
- log_error ("error removing '%s': %s\n", fname, gpg_strerror (err));
- goto leave;
- }
-
- if (opt.verbose)
- log_info ("key for '%s' removed\n", addrspec);
- err = 0;
-
- leave:
- xfree (fname);
- xfree (addrspec);
- return err;
-}
-
-
/* Revoke the key with mail address MAILADDR. */
static gpg_error_t
command_revoke_key (const char *mailaddr)
{
/* Remove should be different from removing but we have not yet
* defined a suitable way to do this. */
- return command_remove_key (mailaddr);
+ return wks_cmd_remove_key (mailaddr);
}
diff --git a/tools/gpg-wks.h b/tools/gpg-wks.h
index 1b91b6504..e36943090 100644
--- a/tools/gpg-wks.h
+++ b/tools/gpg-wks.h
@@ -36,6 +36,7 @@ struct
unsigned int debug;
int quiet;
int use_sendmail;
+ int with_colons;
const char *output;
const char *gpg_program;
const char *directory;
@@ -97,6 +98,13 @@ gpg_error_t wks_parse_policy (policy_flags_t flags, estream_t stream,
int ignore_unknown);
void wks_free_policy (policy_flags_t policy);
+gpg_error_t wks_fname_from_userid (const char *userid,
+ char **r_fname, char **r_addrspec);
+gpg_error_t wks_compute_hu_fname (char **r_fname, const char *addrspec);
+gpg_error_t wks_cmd_install_key (const char *fname, const char *userid);
+gpg_error_t wks_cmd_remove_key (const char *userid);
+
+
/*-- wks-receive.c --*/
/* Flag values for the receive callback. */
diff --git a/tools/gpg-zip.in b/tools/gpg-zip.in
deleted file mode 100644
index 48c4766b1..000000000
--- a/tools/gpg-zip.in
+++ /dev/null
@@ -1,148 +0,0 @@
-#!/bin/sh
-
-# gpg-archive - gpg-ized tar using the same format as PGP's PGP Zip.
-# Copyright (C) 2005 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 <http://www.gnu.org/licenses/>.
-# Despite the name, PGP Zip format is actually an OpenPGP-wrapped tar
-# file. To be compatible with PGP itself, this must be a USTAR format
-# tar file. Unclear on whether there is a distinction here between
-# the GNU or POSIX variant of USTAR.
-
-VERSION=@VERSION@
-TAR=@TAR@
-GPG=gpg
-
-usage="\
-Usage: gpg-zip [--help] [--version] [--encrypt] [--decrypt] [--symmetric]
- [--list-archive] [--output FILE] [--gpg GPG] [--gpg-args ARGS]
- [--tar TAR] [--tar-args ARGS] filename1 [filename2, ...]
- directory1 [directory2, ...]
-
-Encrypt or sign files into an archive."
-
-tar_verbose_opt="v"
-
-while test $# -gt 0 ; do
- case $1 in
- -h | --help | --h*)
- echo "$usage"
- exit 0
- ;;
- --list-archive)
- list=yes
- create=no
- unpack=no
- shift
- ;;
- --encrypt | -e)
- gpg_args="$gpg_args --encrypt"
- list=no
- create=yes
- unpack=no
- shift
- ;;
- --decrypt | -d)
- gpg_args="$gpg_args --decrypt"
- list=no
- create=no
- unpack=yes
- shift
- ;;
- --symmetric | -c)
- gpg_args="$gpg_args --symmetric"
- list=no
- create=yes
- unpack=no
- shift
- ;;
- --sign | -s)
- gpg_args="$gpg_args --sign"
- list=no
- create=yes
- unpack=no
- shift
- ;;
- --recipient | -r)
- gpg_args="$gpg_args --recipient $2"
- shift
- shift
- ;;
- --local-user | -u)
- gpg_args="$gpg_args --local-user $2"
- shift
- shift
- ;;
- --output | -o)
- gpg_args="$gpg_args --output $2"
- shift
- shift
- ;;
- --version)
- echo "gpg-zip (GnuPG) $VERSION"
- exit 0
- ;;
- --gpg)
- GPG=$2
- shift
- shift
- ;;
- --gpg-args)
- gpg_args="$gpg_args $2"
- shift
- shift
- ;;
- --tar)
- TAR=$2
- shift
- shift
- ;;
- --tar-args)
- tar_args="$tar_args $2"
- shift
- shift
- ;;
- --quiet)
- tar_verbose_opt=""
- shift
- ;;
- --)
- shift
- break
- ;;
- -*)
- echo "$usage" 1>&2
- exit 1
- ;;
- *)
- break
- ;;
- esac
-done
-
-if test x$create = xyes ; then
-# echo "$TAR $tar_args -cf - "$@" | $GPG --set-filename x.tar $gpg_args" 1>&2
- $TAR $tar_args -cf - "$@" | $GPG --set-filename x.tar $gpg_args
-elif test x$list = xyes ; then
-# echo "cat \"$1\" | $GPG $gpg_args | $TAR $tar_args -tf -" 1>&2
- cat "$1" | $GPG $gpg_args | $TAR $tar_args -tf -
-elif test x$unpack = xyes ; then
-# echo "cat \"$1\" | $GPG $gpg_args | $TAR $tar_args -xvf -" 1>&2
- cat "$1" | $GPG $gpg_args | $TAR $tar_args -x${tar_verbose_opt}f -
-else
- echo "$usage" 1>&2
- exit 1
-fi
diff --git a/tools/gpgconf-comp.c b/tools/gpgconf-comp.c
index 924f90785..2ae79d91d 100644
--- a/tools/gpgconf-comp.c
+++ b/tools/gpgconf-comp.c
@@ -1066,34 +1066,6 @@ static gc_option_t gc_options_pinentry[] =
-/* Component system. Each component is a set of options that can be
- configured at the same time. If you change this, don't forget to
- update GC_COMPONENT below. */
-typedef enum
- {
- /* The classic GPG for OpenPGP. */
- GC_COMPONENT_GPG,
-
- /* The GPG Agent. */
- GC_COMPONENT_GPG_AGENT,
-
- /* The Smardcard Daemon. */
- GC_COMPONENT_SCDAEMON,
-
- /* GPG for S/MIME. */
- GC_COMPONENT_GPGSM,
-
- /* The LDAP Directory Manager for CRLs. */
- GC_COMPONENT_DIRMNGR,
-
- /* The external Pinentry. */
- GC_COMPONENT_PINENTRY,
-
- /* The number of components. */
- GC_COMPONENT_NR
- } gc_component_t;
-
-
/* The information associated with each component. */
static const struct
{
diff --git a/tools/gpgconf.c b/tools/gpgconf.c
index 59085d8b5..b67125b89 100644
--- a/tools/gpgconf.c
+++ b/tools/gpgconf.c
@@ -47,6 +47,7 @@ enum cmd_and_opt_values
oHomedir,
oBuilddir,
oStatusFD,
+ oShowSocket,
aListComponents,
aCheckPrograms,
@@ -108,6 +109,7 @@ static ARGPARSE_OPTS opts[] =
{ oBuilddir, "build-prefix", 2, "@" },
{ oNull, "null", 0, "@" },
{ oNoVerbose, "no-verbose", 0, "@"},
+ ARGPARSE_s_n (oShowSocket, "show-socket", "@"),
ARGPARSE_end(),
};
@@ -525,6 +527,7 @@ main (int argc, char **argv)
int no_more_options = 0;
enum cmd_and_opt_values cmd = 0;
estream_t outfp = NULL;
+ int show_socket = 0;
early_system_init ();
gnupg_reopen_std (GPGCONF_NAME);
@@ -558,6 +561,7 @@ main (int argc, char **argv)
case oStatusFD:
set_status_fd (translate_sys2libc_fd_int (pargs.r.ret_int, 1));
break;
+ case oShowSocket: show_socket = 1; break;
case aListDirs:
case aListComponents:
@@ -682,7 +686,22 @@ main (int argc, char **argv)
}
else if (cmd == aLaunch)
{
- if (gc_component_launch (idx))
+ err = gc_component_launch (idx);
+ if (show_socket)
+ {
+ char *names[2];
+
+ if (idx == GC_COMPONENT_GPG_AGENT)
+ names[0] = "agent-socket";
+ else if (idx == GC_COMPONENT_DIRMNGR)
+ names[0] = "dirmngr-socket";
+ else
+ names[0] = NULL;
+ names[1] = NULL;
+ get_outfp (&outfp);
+ list_dirs (outfp, names);
+ }
+ if (err)
gpgconf_failure (0);
}
else
@@ -823,7 +842,7 @@ main (int argc, char **argv)
;
else if (rmdir (socketdir))
{
- /* If the director is not empty we first try to delet
+ /* If the director is not empty we first try to delete
* socket files. */
err = gpg_error_from_syserror ();
if (gpg_err_code (err) == GPG_ERR_ENOTEMPTY
diff --git a/tools/gpgconf.h b/tools/gpgconf.h
index 8a061ef68..192259789 100644
--- a/tools/gpgconf.h
+++ b/tools/gpgconf.h
@@ -43,6 +43,34 @@ void gpgconf_failure (gpg_error_t err) GPGRT_ATTR_NORETURN;
/*-- gpgconf-comp.c --*/
+/* Component system. Each component is a set of options that can be
+ * configured at the same time. If you change this, don't forget to
+ * update GC_COMPONENT in gpgconf-comp.c. */
+typedef enum
+ {
+ /* The classic GPG for OpenPGP. */
+ GC_COMPONENT_GPG,
+
+ /* The GPG Agent. */
+ GC_COMPONENT_GPG_AGENT,
+
+ /* The Smardcard Daemon. */
+ GC_COMPONENT_SCDAEMON,
+
+ /* GPG for S/MIME. */
+ GC_COMPONENT_GPGSM,
+
+ /* The LDAP Directory Manager for CRLs. */
+ GC_COMPONENT_DIRMNGR,
+
+ /* The external Pinentry. */
+ GC_COMPONENT_PINENTRY,
+
+ /* The number of components. */
+ GC_COMPONENT_NR
+ } gc_component_t;
+
+
/* Initialize the components. */
void gc_components_init (void);
diff --git a/tools/gpgtar-create.c b/tools/gpgtar-create.c
index c622a6672..a08601634 100644
--- a/tools/gpgtar-create.c
+++ b/tools/gpgtar-create.c
@@ -762,6 +762,14 @@ gpgtar_create (char **inpattern, int encrypt, int sign)
memset (scanctrl, 0, sizeof *scanctrl);
scanctrl->flist_tail = &scanctrl->flist;
+ if (opt.directory && gnupg_chdir (opt.directory))
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("chdir to '%s' failed: %s\n",
+ opt.directory, gpg_strerror (err));
+ return err;
+ }
+
while (!eof_seen)
{
char *pat, *p;
diff --git a/tools/gpgtar-extract.c b/tools/gpgtar-extract.c
index 8613d193f..3da100c07 100644
--- a/tools/gpgtar-extract.c
+++ b/tools/gpgtar-extract.c
@@ -36,7 +36,7 @@
static gpg_error_t
extract_regular (estream_t stream, const char *dirname,
- tar_header_t hdr)
+ tarinfo_t info, tar_header_t hdr)
{
gpg_error_t err;
char record[RECORDSIZE];
@@ -70,6 +70,7 @@ extract_regular (estream_t stream, const char *dirname,
err = read_record (stream, record);
if (err)
goto leave;
+ info->nblocks++;
n++;
if (n < hdr->nrecords || (hdr->size && !(hdr->size % RECORDSIZE)))
nbytes = RECORDSIZE;
@@ -163,7 +164,8 @@ extract_directory (const char *dirname, tar_header_t hdr)
static gpg_error_t
-extract (estream_t stream, const char *dirname, tar_header_t hdr)
+extract (estream_t stream, const char *dirname, tarinfo_t info,
+ tar_header_t hdr)
{
gpg_error_t err;
size_t n;
@@ -190,7 +192,7 @@ extract (estream_t stream, const char *dirname, tar_header_t hdr)
}
if (hdr->typeflag == TF_REGULAR || hdr->typeflag == TF_UNKNOWN)
- err = extract_regular (stream, dirname, hdr);
+ err = extract_regular (stream, dirname, info, hdr);
else if (hdr->typeflag == TF_DIRECTORY)
err = extract_directory (dirname, hdr);
else
@@ -200,7 +202,11 @@ extract (estream_t stream, const char *dirname, tar_header_t hdr)
log_info ("unsupported file type %d for '%s' - skipped\n",
(int)hdr->typeflag, hdr->name);
for (err = 0, n=0; !err && n < hdr->nrecords; n++)
- err = read_record (stream, record);
+ {
+ err = read_record (stream, record);
+ if (!err)
+ info->nblocks++;
+ }
}
return err;
}
@@ -282,6 +288,10 @@ gpgtar_extract (const char *filename, int decrypt)
tar_header_t header = NULL;
const char *dirprefix = NULL;
char *dirname = NULL;
+ struct tarinfo_s tarinfo_buffer;
+ tarinfo_t tarinfo = &tarinfo_buffer;
+
+ memset (&tarinfo_buffer, 0, sizeof tarinfo_buffer);
if (filename)
{
@@ -378,11 +388,11 @@ gpgtar_extract (const char *filename, int decrypt)
for (;;)
{
- err = gpgtar_read_header (stream, &header);
+ err = gpgtar_read_header (stream, tarinfo, &header);
if (err || header == NULL)
goto leave;
- err = extract (stream, dirname, header);
+ err = extract (stream, dirname, tarinfo, header);
if (err)
goto leave;
xfree (header);
diff --git a/tools/gpgtar-list.c b/tools/gpgtar-list.c
index 0e10be8a0..396e837f4 100644
--- a/tools/gpgtar-list.c
+++ b/tools/gpgtar-list.c
@@ -77,12 +77,15 @@ parse_xoctal (const void *data, size_t length, const char *filename)
static tar_header_t
-parse_header (const void *record, const char *filename)
+parse_header (const void *record, const char *filename, tarinfo_t info)
{
const struct ustar_raw_header *raw = record;
size_t n, namelen, prefixlen;
tar_header_t header;
int use_prefix;
+ int anyerror = 0;
+
+ info->headerblock = info->nblocks - 1;
use_prefix = (!memcmp (raw->magic, "ustar", 5)
&& (raw->magic[5] == ' ' || !raw->magic[5]));
@@ -91,27 +94,31 @@ parse_header (const void *record, const char *filename)
for (namelen=0; namelen < sizeof raw->name && raw->name[namelen]; namelen++)
;
if (namelen == sizeof raw->name)
- log_info ("%s: warning: name not terminated by a nul byte\n", filename);
+ {
+ log_info ("%s: warning: name not terminated by a nul\n", filename);
+ anyerror = 1;
+ }
for (n=namelen+1; n < sizeof raw->name; n++)
if (raw->name[n])
{
log_info ("%s: warning: garbage after name\n", filename);
+ anyerror = 1;
break;
}
-
if (use_prefix && raw->prefix[0])
{
for (prefixlen=0; (prefixlen < sizeof raw->prefix
&& raw->prefix[prefixlen]); prefixlen++)
;
if (prefixlen == sizeof raw->prefix)
- log_info ("%s: warning: prefix not terminated by a nul byte\n",
- filename);
+ log_info ("%s: warning: prefix not terminated by a nul (block %llu)\n",
+ filename, info->headerblock);
for (n=prefixlen+1; n < sizeof raw->prefix; n++)
if (raw->prefix[n])
{
log_info ("%s: warning: garbage after prefix\n", filename);
+ anyerror = 1;
break;
}
}
@@ -156,25 +163,32 @@ parse_header (const void *record, const char *filename)
default: header->typeflag = TF_UNKNOWN; break;
}
-
/* Compute the number of data records following this header. */
if (header->typeflag == TF_REGULAR || header->typeflag == TF_UNKNOWN)
header->nrecords = (header->size + RECORDSIZE-1)/RECORDSIZE;
else
header->nrecords = 0;
+ if (anyerror)
+ {
+ log_info ("%s: header block %llu is corrupt"
+ " (size=%llu type=%d nrec=%llu)\n",
+ filename, info->headerblock,
+ header->size, header->typeflag, header->nrecords);
+ /* log_printhex (record, RECORDSIZE, " "); */
+ }
return header;
}
-/* Read the next block, assming it is a tar header. Returns a header
+/* Read the next block, assuming it is a tar header. Returns a header
object on success in R_HEADER, or an error. If the stream is
consumed, R_HEADER is set to NULL. In case of an error an error
message has been printed. */
static gpg_error_t
-read_header (estream_t stream, tar_header_t *r_header)
+read_header (estream_t stream, tarinfo_t info, tar_header_t *r_header)
{
gpg_error_t err;
char record[RECORDSIZE];
@@ -183,6 +197,7 @@ read_header (estream_t stream, tar_header_t *r_header)
err = read_record (stream, record);
if (err)
return err;
+ info->nblocks++;
for (i=0; i < RECORDSIZE && !record[i]; i++)
;
@@ -193,6 +208,7 @@ read_header (estream_t stream, tar_header_t *r_header)
err = read_record (stream, record);
if (err)
return err;
+ info->nblocks++;
for (i=0; i < RECORDSIZE && !record[i]; i++)
;
@@ -207,7 +223,7 @@ read_header (estream_t stream, tar_header_t *r_header)
}
}
- *r_header = parse_header (record, es_fname_get (stream));
+ *r_header = parse_header (record, es_fname_get (stream), info);
return *r_header ? 0 : gpg_error_from_syserror ();
}
@@ -215,7 +231,7 @@ read_header (estream_t stream, tar_header_t *r_header)
/* Skip the data records according to HEADER. Prints an error message
on error and return -1. */
static int
-skip_data (estream_t stream, tar_header_t header)
+skip_data (estream_t stream, tarinfo_t info, tar_header_t header)
{
char record[RECORDSIZE];
unsigned long long n;
@@ -224,6 +240,7 @@ skip_data (estream_t stream, tar_header_t header)
{
if (read_record (stream, record))
return -1;
+ info->nblocks++;
}
return 0;
@@ -278,6 +295,10 @@ gpgtar_list (const char *filename, int decrypt)
estream_t stream;
estream_t cipher_stream = NULL;
tar_header_t header = NULL;
+ struct tarinfo_s tarinfo_buffer;
+ tarinfo_t tarinfo = &tarinfo_buffer;
+
+ memset (&tarinfo_buffer, 0, sizeof tarinfo_buffer);
if (filename)
{
@@ -339,13 +360,13 @@ gpgtar_list (const char *filename, int decrypt)
for (;;)
{
- err = read_header (stream, &header);
+ err = read_header (stream, tarinfo, &header);
if (err || header == NULL)
goto leave;
print_header (header, es_stdout);
- if (skip_data (stream, header))
+ if (skip_data (stream, tarinfo, header))
goto leave;
xfree (header);
header = NULL;
@@ -362,9 +383,9 @@ gpgtar_list (const char *filename, int decrypt)
}
gpg_error_t
-gpgtar_read_header (estream_t stream, tar_header_t *r_header)
+gpgtar_read_header (estream_t stream, tarinfo_t info, tar_header_t *r_header)
{
- return read_header (stream, r_header);
+ return read_header (stream, info, r_header);
}
void
diff --git a/tools/gpgtar.c b/tools/gpgtar.c
index 2757ab011..b33aa6d0f 100644
--- a/tools/gpgtar.c
+++ b/tools/gpgtar.c
@@ -112,7 +112,7 @@ static ARGPARSE_OPTS opts[] = {
ARGPARSE_group (302, N_("@\nTar options:\n ")),
ARGPARSE_s_s (oDirectory, "directory",
- N_("|DIRECTORY|extract files into DIRECTORY")),
+ N_("|DIRECTORY|change to DIRECTORY first")),
ARGPARSE_s_s (oFilesFrom, "files-from",
N_("|FILE|get names to create from FILE")),
ARGPARSE_s_n (oNull, "null", N_("-T reads null-terminated names")),
@@ -136,6 +136,14 @@ static ARGPARSE_OPTS tar_opts[] = {
};
+/* Global flags. */
+enum cmd_and_opt_values cmd = 0;
+int skip_crypto = 0;
+const char *files_from = NULL;
+int null_names = 0;
+
+
+
/* Print usage information and provide strings for help. */
static const char *
@@ -169,23 +177,25 @@ my_strusage( int level )
static void
set_cmd (enum cmd_and_opt_values *ret_cmd, enum cmd_and_opt_values new_cmd)
{
- enum cmd_and_opt_values cmd = *ret_cmd;
-
- if (!cmd || cmd == new_cmd)
- cmd = new_cmd;
- else if (cmd == aSign && new_cmd == aEncrypt)
- cmd = aSignEncrypt;
- else if (cmd == aEncrypt && new_cmd == aSign)
- cmd = aSignEncrypt;
+ enum cmd_and_opt_values c = *ret_cmd;
+
+ if (!c || c == new_cmd)
+ c = new_cmd;
+ else if (c == aSign && new_cmd == aEncrypt)
+ c = aSignEncrypt;
+ else if (c == aEncrypt && new_cmd == aSign)
+ c = aSignEncrypt;
else
{
log_error (_("conflicting commands\n"));
exit (2);
}
- *ret_cmd = cmd;
+ *ret_cmd = c;
}
+
+
/* Shell-like argument splitting.
For compatibility with gpg-zip we accept arguments for GnuPG and
@@ -287,14 +297,9 @@ shell_parse_argv (const char *s, int *r_argc, char ***r_argv)
gpgrt_annotate_leaked_object (*r_argv);
return 0;
}
-
-/* Global flags. */
-enum cmd_and_opt_values cmd = 0;
-int skip_crypto = 0;
-const char *files_from = NULL;
-int null_names = 0;
+
/* Command line parsing. */
static void
parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts)
diff --git a/tools/gpgtar.h b/tools/gpgtar.h
index 8cbe80bbb..1a1b913d7 100644
--- a/tools/gpgtar.h
+++ b/tools/gpgtar.h
@@ -41,12 +41,21 @@ struct
} opt;
+/* An info structure to avoid global variables. */
+struct tarinfo_s
+{
+ unsigned long long nblocks; /* Count of processed blocks. */
+ unsigned long long headerblock; /* Number of current header block. */
+};
+typedef struct tarinfo_s *tarinfo_t;
+
+
/* The size of a tar record. All IO is done in chunks of this size.
Note that we don't care about blocking because this version of tar
is not expected to be used directly on a tape drive in fact it is
used in a pipeline with GPG and thus any blocking would be
useless. */
-#define RECORDSIZE 512
+#define RECORDSIZE 512
/* Description of the USTAR header format. */
@@ -64,16 +73,16 @@ struct ustar_raw_header
char magic[6];
char version[2];
char uname[32];
- char gname[32];
- char devmajor[8];
+ char gname[32];
+ char devmajor[8];
char devminor[8];
- char prefix[155];
+ char prefix[155];
char pad[12];
};
/* Filetypes as defined by USTAR. */
-typedef enum
+typedef enum
{
TF_REGULAR,
TF_HARDLINK,
@@ -88,12 +97,12 @@ typedef enum
} typeflag_t;
-/* The internal represenation of a TAR header. */
+/* The internal representation of a TAR header. */
struct tar_header_s;
typedef struct tar_header_s *tar_header_t;
struct tar_header_s
{
- tar_header_t next; /* Used to build a linked list iof entries. */
+ tar_header_t next; /* Used to build a linked list of entries. */
unsigned long mode; /* The file mode. */
unsigned long nlink; /* Number of hard links. */
@@ -106,7 +115,7 @@ struct tar_header_s
that 32 bit and thus allows tracking
times beyond 2106. */
typeflag_t typeflag; /* The type of the file. */
-
+
unsigned long long nrecords; /* Number of data records. */
@@ -126,7 +135,8 @@ gpg_error_t gpgtar_extract (const char *filename, int decrypt);
/*-- gpgtar-list.c --*/
gpg_error_t gpgtar_list (const char *filename, int decrypt);
-gpg_error_t gpgtar_read_header (estream_t stream, tar_header_t *r_header);
+gpg_error_t gpgtar_read_header (estream_t stream, tarinfo_t info,
+ tar_header_t *r_header);
void gpgtar_print_header (tar_header_t header, estream_t out);
diff --git a/tools/mime-maker.c b/tools/mime-maker.c
index 0edc14d78..91eab8258 100644
--- a/tools/mime-maker.c
+++ b/tools/mime-maker.c
@@ -25,14 +25,10 @@
#include "../common/util.h"
#include "../common/zb32.h"
+#include "rfc822parse.h"
#include "mime-maker.h"
-/* All valid characters in a header name. */
-#define HEADER_NAME_CHARS ("abcdefghijklmnopqrstuvwxyz" \
- "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
- "-01234567890")
-
/* An object to store an header. Also used for a list of headers. */
struct header_s
{
@@ -269,38 +265,6 @@ ensure_part (mime_maker_t ctx, part_t *r_parent)
}
-/* Transform a header name into a standard capitalized format.
- * "Content-Type". Conversion stops at the colon. */
-static void
-capitalize_header_name (char *name)
-{
- unsigned char *p = name;
- int first = 1;
-
- /* Special cases first. */
- if (!ascii_strcasecmp (name, "MIME-Version"))
- {
- strcpy (name, "MIME-Version");
- return;
- }
-
- /* Regular cases. */
- for (; *p && *p != ':'; p++)
- {
- if (*p == '-')
- first = 1;
- else if (first)
- {
- if (*p >= 'a' && *p <= 'z')
- *p = *p - 'a' + 'A';
- first = 0;
- }
- else if (*p >= 'A' && *p <= 'Z')
- *p = *p - 'A' + 'a';
- }
-}
-
-
/* Check whether a header with NAME has already been set into PART.
* NAME must be in canonical capitalized format. Return true or
* false. */
@@ -344,17 +308,14 @@ add_header (part_t part, const char *name, const char *value)
memcpy (hdr->name, name, namelen);
hdr->name[namelen] = 0;
- /* Check that the header name is valid. We allow all lower and
- * uppercase letters and, except for the first character, digits and
- * the dash. */
- if (strspn (hdr->name, HEADER_NAME_CHARS) != namelen
- || strchr ("-0123456789", *hdr->name))
+ /* Check that the header name is valid. */
+ if (!rfc822_valid_header_name_p (hdr->name))
{
xfree (hdr);
return gpg_error (GPG_ERR_INV_NAME);
}
- capitalize_header_name (hdr->name);
+ rfc822_capitalize_header_name (hdr->name);
hdr->value = xtrystrdup (value);
if (!hdr->value)
{
diff --git a/tools/mime-parser.c b/tools/mime-parser.c
index a151dc65e..98f27105c 100644
--- a/tools/mime-parser.c
+++ b/tools/mime-parser.c
@@ -50,7 +50,7 @@ struct mime_parser_context_s
{
void *cookie; /* Cookie passed to all callbacks. */
- /* The callback to announce the transation from header to body. */
+ /* The callback to announce the transition from header to body. */
gpg_error_t (*t2body) (void *cookie, int level);
/* The callback to announce a new part. */
diff --git a/tools/no-libgcrypt.c b/tools/no-libgcrypt.c
index 873996889..3b577567a 100644
--- a/tools/no-libgcrypt.c
+++ b/tools/no-libgcrypt.c
@@ -114,7 +114,7 @@ gcry_free (void *a)
/* We need this dummy because exechelp.c uses gcry_control to
- terminate the secure memeory. */
+ terminate the secure memory. */
gcry_error_t
gcry_control (enum gcry_ctl_cmds cmd, ...)
{
diff --git a/tools/rfc822parse.c b/tools/rfc822parse.c
index e8cdb0215..f1e95bd34 100644
--- a/tools/rfc822parse.c
+++ b/tools/rfc822parse.c
@@ -41,6 +41,12 @@
#include "rfc822parse.h"
+/* All valid characters in a header name. */
+#define HEADER_NAME_CHARS ("abcdefghijklmnopqrstuvwxyz" \
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
+ "-01234567890")
+
+
enum token_type
{
tSPACE,
@@ -90,7 +96,7 @@ struct rfc822parse_context
void *callback_value;
int callback_error;
int in_body;
- int in_preamble; /* Wether we are before the first boundary. */
+ int in_preamble; /* Whether we are before the first boundary. */
part_t parts; /* The tree of parts. */
part_t current_part; /* Whom we are processing (points into parts). */
const char *boundary; /* Current boundary. */
@@ -131,28 +137,31 @@ lowercase_string (unsigned char *string)
*string = *string - 'A' + 'a';
}
-/* Transform a header name into a standard capitalized format; i.e
- "Content-Type". Conversion stops at the colon. As usual we don't
- use the localized versions of ctype.h.
- */
-static void
-capitalize_header_name (unsigned char *name)
+
+static int
+my_toupper (int c)
{
- int first = 1;
+ if (c >= 'a' && c <= 'z')
+ c &= ~0x20;
+ return c;
+}
+
+/* This is the same as ascii_strcasecmp. */
+static int
+my_strcasecmp (const char *a, const char *b)
+{
+ if (a == b)
+ return 0;
- for (; *name && *name != ':'; name++)
- if (*name == '-')
- first = 1;
- else if (first)
- {
- if (*name >= 'a' && *name <= 'z')
- *name = *name - 'a' + 'A';
- first = 0;
- }
- else if (*name >= 'A' && *name <= 'Z')
- *name = *name - 'A' + 'a';
+ for (; *a && *b; a++, b++)
+ {
+ if (*a != *b && my_toupper(*a) != my_toupper(*b))
+ break;
+ }
+ return *a == *b? 0 : (my_toupper (*a) - my_toupper (*b));
}
+
#ifndef HAVE_STPCPY
static char *
my_stpcpy (char *a,const char *b)
@@ -167,7 +176,7 @@ my_stpcpy (char *a,const char *b)
#endif
-/* If a callback has been registerd, call it for the event of type
+/* If a callback has been registered, call it for the event of type
EVENT. */
static int
do_callback (rfc822parse_t msg, rfc822parse_event_t event)
@@ -228,6 +237,62 @@ release_handle_data (rfc822parse_t msg)
}
+/* Check that the header name is valid. We allow all lower and
+ * uppercase letters and, except for the first character, digits and
+ * the dash. The check stops at the first colon or at string end.
+ * Returns true if the name is valid. */
+int
+rfc822_valid_header_name_p (const char *name)
+{
+ const char *s;
+ size_t namelen;
+
+ if ((s=strchr (name, ':')))
+ namelen = s - name;
+ else
+ namelen = strlen (name);
+
+ if (!namelen
+ || strspn (name, HEADER_NAME_CHARS) != namelen
+ || strchr ("-0123456789", *name))
+ return 0;
+ return 1;
+}
+
+
+/* Transform a header NAME into a standard capitalized format.
+ * Conversion stops at the colon. */
+void
+rfc822_capitalize_header_name (char *name)
+{
+ unsigned char *p = name;
+ int first = 1;
+
+ /* Special cases first. */
+ if (!my_strcasecmp (name, "MIME-Version"))
+ {
+ strcpy (name, "MIME-Version");
+ return;
+ }
+
+ /* Regular cases. */
+ for (; *p && *p != ':'; p++)
+ {
+ if (*p == '-')
+ first = 1;
+ else if (first)
+ {
+ if (*p >= 'a' && *p <= 'z')
+ *p = *p - 'a' + 'A';
+ first = 0;
+ }
+ else if (*p >= 'A' && *p <= 'Z')
+ *p = *p - 'A' + 'a';
+ }
+}
+
+
+
/* Create a new parsing context for an entire rfc822 message and
return it. CB and CB_VALUE may be given to callback for certain
events. NULL is returned on error with errno set appropriately. */
@@ -432,7 +497,7 @@ insert_header (rfc822parse_t msg, const unsigned char *line, size_t length)
/* Transform a field name into canonical format. */
if (!hdr->cont && strchr (line, ':'))
- capitalize_header_name (hdr->line);
+ rfc822_capitalize_header_name (hdr->line);
*msg->current_part->hdr_lines_tail = hdr;
msg->current_part->hdr_lines_tail = &hdr->next;
@@ -578,7 +643,7 @@ rfc822parse_get_field (rfc822parse_t msg, const char *name, int which,
/****************
* Enumerate all header. Caller has to provide the address of a pointer
- * which has to be initialzed to NULL, the caller should then never change this
+ * which has to be initialized to NULL, the caller should then never change this
* pointer until he has closed the enumeration by passing again the address
* of the pointer but with msg set to NULL.
* The function returns pointers to all the header lines or NULL when
@@ -616,7 +681,7 @@ rfc822parse_enum_header_lines (rfc822parse_t msg, void **context)
* >0 : Retrieve the n-th field
* RPREV may be used to return the predecessor of the returned field;
- * which may be NULL for the very first one. It has to be initialzed
+ * which may be NULL for the very first one. It has to be initialized
* to either NULL in which case the search start at the first header line,
* or it may point to a headerline, where the search should start
*/
@@ -1013,7 +1078,7 @@ is_parameter (TOKEN t)
parse context is valid; NULL is returned in case that attr is not
defined in the header, a missing value is reppresented by an empty string.
- With LOWER_VALUE set to true, a matching field valuebe be
+ With LOWER_VALUE set to true, a matching field value will be
lowercased.
Note, that ATTR should be lowercase.
diff --git a/tools/rfc822parse.h b/tools/rfc822parse.h
index 177d8271a..e2f2bedac 100644
--- a/tools/rfc822parse.h
+++ b/tools/rfc822parse.h
@@ -48,6 +48,8 @@ typedef int (*rfc822parse_cb_t) (void *opaque,
rfc822parse_event_t event,
rfc822parse_t msg);
+int rfc822_valid_header_name_p (const char *name);
+void rfc822_capitalize_header_name (char *name);
rfc822parse_t rfc822parse_open (rfc822parse_cb_t cb, void *opaque_value);
diff --git a/tools/wks-util.c b/tools/wks-util.c
index 3fd824c1a..1459045ef 100644
--- a/tools/wks-util.c
+++ b/tools/wks-util.c
@@ -19,12 +19,17 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
#include "../common/util.h"
#include "../common/status.h"
#include "../common/ccparray.h"
#include "../common/exectool.h"
+#include "../common/zb32.h"
+#include "../common/userids.h"
#include "../common/mbox-util.h"
+#include "../common/sysutils.h"
#include "mime-maker.h"
#include "send-mail.h"
#include "gpg-wks.h"
@@ -65,7 +70,7 @@ wks_set_status_fd (int fd)
}
-/* Write a status line with code NO followed by the outout of the
+/* Write a status line with code NO followed by the output of the
* printf style FORMAT. The caller needs to make sure that LFs and
* CRs are not printed. */
void
@@ -104,7 +109,7 @@ append_to_uidinfo_list (uidinfo_list_t *list, const char *uid, time_t created)
strcpy (sl->uid, uid);
sl->created = created;
- sl->mbox = mailbox_from_userid (uid);
+ sl->mbox = mailbox_from_userid (uid, 0);
sl->next = NULL;
if (!*list)
*list = sl;
@@ -187,7 +192,7 @@ wks_get_key (estream_t *r_key, const char *fingerprint, const char *addrspec,
es_fputs ("Content-Type: application/pgp-keys\n"
"\n", key);
- filterexp = es_bsprintf ("keep-uid=%s=%s", exact? "uid":"mbox", addrspec);
+ filterexp = es_bsprintf ("keep-uid=%s= %s", exact? "uid":"mbox", addrspec);
if (!filterexp)
{
err = gpg_error_from_syserror ();
@@ -461,7 +466,7 @@ wks_filter_uid (estream_t *r_newkey, estream_t key, const char *uid,
es_fputs ("Content-Type: application/pgp-keys\n"
"\n", newkey);
- filterexp = es_bsprintf ("keep-uid=uid=%s", uid);
+ filterexp = es_bsprintf ("keep-uid=uid= %s", uid);
if (!filterexp)
{
err = gpg_error_from_syserror ();
@@ -556,7 +561,7 @@ wks_send_mime (mime_maker_t mime)
/* Parse the policy flags by reading them from STREAM and storing them
- * into FLAGS. If IGNORE_UNKNOWN is iset unknown keywords are
+ * into FLAGS. If IGNORE_UNKNOWN is set unknown keywords are
* ignored. */
gpg_error_t
wks_parse_policy (policy_flags_t flags, estream_t stream, int ignore_unknown)
@@ -699,3 +704,388 @@ wks_free_policy (policy_flags_t policy)
memset (policy, 0, sizeof *policy);
}
}
+
+
+/* Write the content of SRC to the new file FNAME. */
+static gpg_error_t
+write_to_file (estream_t src, const char *fname)
+{
+ gpg_error_t err;
+ estream_t dst;
+ char buffer[4096];
+ size_t nread, written;
+
+ dst = es_fopen (fname, "wb");
+ if (!dst)
+ return gpg_error_from_syserror ();
+
+ do
+ {
+ nread = es_fread (buffer, 1, sizeof buffer, src);
+ if (!nread)
+ break;
+ written = es_fwrite (buffer, 1, nread, dst);
+ if (written != nread)
+ break;
+ }
+ while (!es_feof (src) && !es_ferror (src) && !es_ferror (dst));
+ if (!es_feof (src) || es_ferror (src) || es_ferror (dst))
+ {
+ err = gpg_error_from_syserror ();
+ es_fclose (dst);
+ gnupg_remove (fname);
+ return err;
+ }
+
+ if (es_fclose (dst))
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("error closing '%s': %s\n", fname, gpg_strerror (err));
+ return err;
+ }
+
+ return 0;
+}
+
+
+/* Return the filename and optionally the addrspec for USERID at
+ * R_FNAME and R_ADDRSPEC. R_ADDRSPEC might also be set on error. */
+gpg_error_t
+wks_fname_from_userid (const char *userid, char **r_fname, char **r_addrspec)
+{
+ gpg_error_t err;
+ char *addrspec = NULL;
+ const char *domain;
+ char *hash = NULL;
+ const char *s;
+ char shaxbuf[32]; /* Used for SHA-1 and SHA-256 */
+
+ *r_fname = NULL;
+ if (r_addrspec)
+ *r_addrspec = NULL;
+
+ addrspec = mailbox_from_userid (userid, 0);
+ if (!addrspec)
+ {
+ if (opt.verbose)
+ log_info ("\"%s\" is not a proper mail address\n", userid);
+ err = gpg_error (GPG_ERR_INV_USER_ID);
+ goto leave;
+ }
+
+ domain = strchr (addrspec, '@');
+ log_assert (domain);
+ domain++;
+
+ /* Hash user ID and create filename. */
+ s = strchr (addrspec, '@');
+ log_assert (s);
+ gcry_md_hash_buffer (GCRY_MD_SHA1, shaxbuf, addrspec, s - addrspec);
+ hash = zb32_encode (shaxbuf, 8*20);
+ if (!hash)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+
+ *r_fname = make_filename_try (opt.directory, domain, "hu", hash, NULL);
+ if (!*r_fname)
+ err = gpg_error_from_syserror ();
+ else
+ err = 0;
+
+ leave:
+ if (r_addrspec && addrspec)
+ *r_addrspec = addrspec;
+ else
+ xfree (addrspec);
+ xfree (hash);
+ return err;
+}
+
+
+/* Compute the the full file name for the key with ADDRSPEC and return
+ * it at R_FNAME. */
+gpg_error_t
+wks_compute_hu_fname (char **r_fname, const char *addrspec)
+{
+ gpg_error_t err;
+ char *hash;
+ const char *domain;
+ char sha1buf[20];
+ char *fname;
+ struct stat sb;
+
+ *r_fname = NULL;
+
+ domain = strchr (addrspec, '@');
+ if (!domain || !domain[1] || domain == addrspec)
+ return gpg_error (GPG_ERR_INV_ARG);
+ domain++;
+
+ gcry_md_hash_buffer (GCRY_MD_SHA1, sha1buf, addrspec, domain - addrspec - 1);
+ hash = zb32_encode (sha1buf, 8*20);
+ if (!hash)
+ return gpg_error_from_syserror ();
+
+ /* Try to create missing directories below opt.directory. */
+ fname = make_filename_try (opt.directory, domain, NULL);
+ if (fname && stat (fname, &sb)
+ && gpg_err_code_from_syserror () == GPG_ERR_ENOENT)
+ if (!gnupg_mkdir (fname, "-rwxr--r--") && opt.verbose)
+ log_info ("directory '%s' created\n", fname);
+ xfree (fname);
+ fname = make_filename_try (opt.directory, domain, "hu", NULL);
+ if (fname && stat (fname, &sb)
+ && gpg_err_code_from_syserror () == GPG_ERR_ENOENT)
+ if (!gnupg_mkdir (fname, "-rwxr--r--") && opt.verbose)
+ log_info ("directory '%s' created\n", fname);
+ xfree (fname);
+
+ /* Create the filename. */
+ fname = make_filename_try (opt.directory, domain, "hu", hash, NULL);
+ err = fname? 0 : gpg_error_from_syserror ();
+
+ if (err)
+ xfree (fname);
+ else
+ *r_fname = fname; /* Okay. */
+ xfree (hash);
+ return err;
+}
+
+
+
+/* Helper form wks_cmd_install_key. */
+static gpg_error_t
+install_key_from_spec_file (const char *fname)
+{
+ gpg_error_t err;
+ estream_t fp;
+ char *line = NULL;
+ size_t linelen = 0;
+ size_t maxlen = 2048;
+ char *fields[2];
+ unsigned int lnr = 0;
+
+ if (!fname || !strcmp (fname, ""))
+ fp = es_stdin;
+ else
+ fp = es_fopen (fname, "rb");
+ if (!fp)
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("error reading '%s': %s\n", fname, gpg_strerror (err));
+ goto leave;
+ }
+
+ while (es_read_line (fp, &line, &linelen, &maxlen) > 0)
+ {
+ if (!maxlen)
+ {
+ err = gpg_error (GPG_ERR_LINE_TOO_LONG);
+ log_error ("error reading '%s': %s\n", fname, gpg_strerror (err));
+ goto leave;
+ }
+ lnr++;
+ trim_spaces (line);
+ if (!*line || *line == '#')
+ continue;
+ if (split_fields (line, fields, DIM(fields)) < 2)
+ {
+ log_error ("error reading '%s': syntax error at line %u\n",
+ fname, lnr);
+ continue;
+ }
+ err = wks_cmd_install_key (fields[0], fields[1]);
+ if (err)
+ goto leave;
+ }
+ if (es_ferror (fp))
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("error reading '%s': %s\n", fname, gpg_strerror (err));
+ goto leave;
+ }
+
+ leave:
+ if (fp != es_stdin)
+ es_fclose (fp);
+ es_free (line);
+ return err;
+}
+
+
+/* Install a single key into the WKD by reading FNAME and extracting
+ * USERID. If USERID is NULL FNAME is expected to be a list of fpr
+ * mbox lines and for each line the respective key will be
+ * installed. */
+gpg_error_t
+wks_cmd_install_key (const char *fname, const char *userid)
+{
+ gpg_error_t err;
+ KEYDB_SEARCH_DESC desc;
+ estream_t fp = NULL;
+ char *addrspec = NULL;
+ char *fpr = NULL;
+ uidinfo_list_t uidlist = NULL;
+ uidinfo_list_t uid, thisuid;
+ time_t thistime;
+ char *huname = NULL;
+ int any;
+
+ if (!userid)
+ return install_key_from_spec_file (fname);
+
+ addrspec = mailbox_from_userid (userid, 0);
+ if (!addrspec)
+ {
+ log_error ("\"%s\" is not a proper mail address\n", userid);
+ err = gpg_error (GPG_ERR_INV_USER_ID);
+ goto leave;
+ }
+
+ if (!classify_user_id (fname, &desc, 1)
+ && desc.mode == KEYDB_SEARCH_MODE_FPR)
+ {
+ /* FNAME looks like a fingerprint. Get the key from the
+ * standard keyring. */
+ err = wks_get_key (&fp, fname, addrspec, 0);
+ if (err)
+ {
+ log_error ("error getting key '%s' (uid='%s'): %s\n",
+ fname, addrspec, gpg_strerror (err));
+ goto leave;
+ }
+ }
+ else /* Take it from the file */
+ {
+ fp = es_fopen (fname, "rb");
+ if (!fp)
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("error reading '%s': %s\n", fname, gpg_strerror (err));
+ goto leave;
+ }
+ }
+
+ /* List the key so that we can figure out the newest UID with the
+ * requested addrspec. */
+ err = wks_list_key (fp, &fpr, &uidlist);
+ if (err)
+ {
+ log_error ("error parsing key: %s\n", gpg_strerror (err));
+ err = gpg_error (GPG_ERR_NO_PUBKEY);
+ goto leave;
+ }
+ thistime = 0;
+ thisuid = NULL;
+ any = 0;
+ for (uid = uidlist; uid; uid = uid->next)
+ {
+ if (!uid->mbox)
+ continue; /* Should not happen anyway. */
+ if (ascii_strcasecmp (uid->mbox, addrspec))
+ continue; /* Not the requested addrspec. */
+ any = 1;
+ if (uid->created > thistime)
+ {
+ thistime = uid->created;
+ thisuid = uid;
+ }
+ }
+ if (!thisuid)
+ thisuid = uidlist; /* This is the case for a missing timestamp. */
+ if (!any)
+ {
+ log_error ("public key in '%s' has no mail address '%s'\n",
+ fname, addrspec);
+ err = gpg_error (GPG_ERR_INV_USER_ID);
+ goto leave;
+ }
+
+ if (opt.verbose)
+ log_info ("using key with user id '%s'\n", thisuid->uid);
+
+ {
+ estream_t fp2;
+
+ es_rewind (fp);
+ err = wks_filter_uid (&fp2, fp, thisuid->uid, 1);
+ if (err)
+ {
+ log_error ("error filtering key: %s\n", gpg_strerror (err));
+ err = gpg_error (GPG_ERR_NO_PUBKEY);
+ goto leave;
+ }
+ es_fclose (fp);
+ fp = fp2;
+ }
+
+ /* Hash user ID and create filename. */
+ err = wks_compute_hu_fname (&huname, addrspec);
+ if (err)
+ goto leave;
+
+ /* Publish. */
+ err = write_to_file (fp, huname);
+ if (err)
+ {
+ log_error ("copying key to '%s' failed: %s\n", huname,gpg_strerror (err));
+ goto leave;
+ }
+
+ /* Make sure it is world readable. */
+ if (gnupg_chmod (huname, "-rwxr--r--"))
+ log_error ("can't set permissions of '%s': %s\n",
+ huname, gpg_strerror (gpg_err_code_from_syserror()));
+
+ if (!opt.quiet)
+ log_info ("key %s published for '%s'\n", fpr, addrspec);
+
+ leave:
+ xfree (huname);
+ free_uidinfo_list (uidlist);
+ xfree (fpr);
+ xfree (addrspec);
+ es_fclose (fp);
+ return err;
+}
+
+
+/* Remove the key with mail address in USERID. */
+gpg_error_t
+wks_cmd_remove_key (const char *userid)
+{
+ gpg_error_t err;
+ char *addrspec = NULL;
+ char *fname = NULL;
+
+ err = wks_fname_from_userid (userid, &fname, &addrspec);
+ if (err)
+ goto leave;
+
+ if (gnupg_remove (fname))
+ {
+ err = gpg_error_from_syserror ();
+ if (gpg_err_code (err) == GPG_ERR_ENOENT)
+ {
+ if (!opt.quiet)
+ log_info ("key for '%s' is not installed\n", addrspec);
+ log_inc_errorcount ();
+ err = 0;
+ }
+ else
+ log_error ("error removing '%s': %s\n", fname, gpg_strerror (err));
+ goto leave;
+ }
+
+ if (opt.verbose)
+ log_info ("key for '%s' removed\n", addrspec);
+ err = 0;
+
+ leave:
+ xfree (fname);
+ xfree (addrspec);
+ return err;
+}