summaryrefslogtreecommitdiff
path: root/g10/cardglue.c
diff options
context:
space:
mode:
Diffstat (limited to 'g10/cardglue.c')
-rw-r--r--g10/cardglue.c1432
1 files changed, 1432 insertions, 0 deletions
diff --git a/g10/cardglue.c b/g10/cardglue.c
new file mode 100644
index 0000000..1bfb9e4
--- /dev/null
+++ b/g10/cardglue.c
@@ -0,0 +1,1432 @@
+/* cardglue.c - mainly dispatcher for card related functions.
+ * Copyright (C) 2003, 2004, 2005, 2006 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ */
+
+#include <config.h>
+#ifndef ENABLE_CARD_SUPPORT
+#error not configured for card support.
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <assert.h>
+#include "options.h"
+#include "packet.h"
+#include "errors.h"
+#include "memory.h"
+#include "util.h"
+#include "main.h"
+#include "status.h"
+#include "ttyio.h"
+#include "i18n.h"
+
+#include "cardglue.h"
+#include "apdu.h"
+#include "app-common.h"
+
+
+
+struct ctrl_ctx_s
+{
+ assuan_error_t (*status_cb)(void *opaque, const char *line);
+ void *status_cb_arg;
+};
+
+
+struct pincb_parm_s
+{
+ const char *sn;
+};
+
+
+struct writekey_parm_s
+{
+ assuan_context_t ctx;
+ const unsigned char *keydata;
+ size_t keydatalen;
+};
+
+
+
+static char *default_reader_port;
+static app_t current_app;
+
+
+/* Local prototypes. */
+static assuan_error_t learn_status_cb (void *opaque, const char *line);
+
+
+/* To avoid cluttering the code with bunches of ifdefs we use a few
+ dummy functions instead and defines. */
+#ifndef ENABLE_AGENT_SUPPORT
+
+#define ASSUAN_LINELENGTH 100
+
+static assuan_context_t
+agent_open (int try, const char *orig_codeset)
+{
+ return NULL;
+}
+
+void
+agent_close (assuan_context_t ctx)
+{
+}
+
+const char *
+assuan_strerror (assuan_error_t err)
+{
+ return "no Assuan support";
+}
+
+assuan_error_t
+assuan_transact (assuan_context_t ctx,
+ const char *command,
+ assuan_error_t (*data_cb)(void *, const void *, size_t),
+ void *data_cb_arg,
+ assuan_error_t (*inquire_cb)(void*, const char *),
+ void *inquire_cb_arg,
+ assuan_error_t (*status_cb)(void*, const char *),
+ void *status_cb_arg)
+{
+ return 100; /* ASSUAN_NOT_IMPLEMENTED */
+}
+assuan_error_t
+assuan_send_data (assuan_context_t ctx, const void *buffer, size_t length)
+{
+ return 100; /* ASSUAN_NOT_IMPLEMENTED */
+}
+#endif /*!ENABLE_AGENT_SUPPORT*/
+
+
+/* Create a serialno/fpr string from the serial number and the secret
+ key. caller must free the returned string. There is no error
+ return. [Taken from 1.9's keyid.c]*/
+char *
+serialno_and_fpr_from_sk (const unsigned char *sn, size_t snlen,
+ PKT_secret_key *sk)
+{
+ unsigned char fpr[MAX_FINGERPRINT_LEN];
+ size_t fprlen;
+ char *buffer, *p;
+ int i;
+
+ fingerprint_from_sk (sk, fpr, &fprlen);
+ buffer = p = xmalloc (snlen*2 + 1 + fprlen*2 + 1);
+ for (i=0; i < snlen; i++, p+=2)
+ sprintf (p, "%02X", sn[i]);
+ *p++ = '/';
+ for (i=0; i < fprlen; i++, p+=2)
+ sprintf (p, "%02X", fpr[i]);
+ *p = 0;
+ return buffer;
+}
+
+
+/* Send a line with status information via assuan and escape all given
+ buffers. The variable elements are pairs of (char *, size_t),
+ terminated with a (NULL, 0). */
+void
+send_status_info (ctrl_t ctrl, const char *keyword, ...)
+{
+ va_list arg_ptr;
+ const unsigned char *value;
+ size_t valuelen;
+ char buf[950], *p;
+ size_t n;
+
+ va_start (arg_ptr, keyword);
+
+ p = buf;
+ n = 0;
+ valuelen = strlen (keyword);
+ for ( ; valuelen && n < DIM (buf)-2; n++, valuelen--, keyword++)
+ *p++ = *keyword;
+
+ while ( (value = va_arg (arg_ptr, const unsigned char *)) )
+ {
+ valuelen = va_arg (arg_ptr, size_t);
+ if (!valuelen)
+ continue; /* empty buffer */
+ if (n)
+ {
+ *p++ = ' ';
+ n++;
+ }
+ for ( ; valuelen && n < DIM (buf)-2; n++, valuelen--, value++)
+ {
+ if (*value < ' ' || *value == '+')
+ {
+ sprintf (p, "%%%02X", *value);
+ p += 3;
+ }
+ else if (*value == ' ')
+ *p++ = '+';
+ else
+ *p++ = *value;
+ }
+ }
+ *p = 0;
+ if (ctrl && ctrl->status_cb)
+ ctrl->status_cb (ctrl->status_cb_arg, buf);
+
+ va_end (arg_ptr);
+}
+
+
+/* Replacement function of the Libgcrypt onewhich is used in gnupg
+ 1.9. Thus function computes the digest of ALGO from the data in
+ BUFFER of LENGTH. ALGO must be supported. */
+void
+gcry_md_hash_buffer (int algo, void *digest,
+ const void *buffer, size_t length)
+{
+ MD_HANDLE h = md_open (algo, 0);
+ if (!h)
+ BUG();
+ md_write (h, (byte *) buffer, length);
+ md_final (h);
+ memcpy (digest, md_read (h, algo), md_digest_length (algo));
+ md_close (h);
+}
+
+
+/* This is a limited version of the one in 1.9 but it should be
+ sufficient here. */
+void
+log_printf (const char *fmt, ...)
+{
+ va_list arg_ptr;
+
+ va_start (arg_ptr, fmt);
+ vfprintf (log_stream (), fmt, arg_ptr);
+ va_end (arg_ptr);
+}
+
+
+
+/* Print a hexdump of BUFFER. With TEXT of NULL print just the raw
+ dump, with TEXT just an empty string, print a trailing linefeed,
+ otherwise print an entire debug line. */
+void
+log_printhex (const char *text, const void *buffer, size_t length)
+{
+ if (text && *text)
+ log_debug ("%s ", text);
+ if (length)
+ {
+ const unsigned char *p = buffer;
+ log_printf ("%02X", *p);
+ for (length--, p++; length--; p++)
+ log_printf (" %02X", *p);
+ }
+ if (text)
+ log_printf ("\n");
+}
+
+
+
+void
+app_set_default_reader_port (const char *portstr)
+{
+ xfree (default_reader_port);
+ default_reader_port = portstr? xstrdup (portstr): NULL;
+}
+
+
+void
+card_set_reader_port (const char *portstr)
+{
+ app_set_default_reader_port (portstr);
+}
+
+
+/* Retrieve the serial number and the time of the last update of the
+ card. The serial number is returned as a malloced string (hex
+ encoded) in SERIAL and the time of update is returned in STAMP. If
+ no update time is available the returned value is 0. Caller must
+ free SERIAL unless the function returns an error. */
+int
+app_get_serial_and_stamp (app_t app, char **serial, time_t *stamp)
+{
+ unsigned char *buf, *p;
+ int i;
+
+ if (!app || !serial || !stamp)
+ return gpg_error (GPG_ERR_INV_VALUE);
+
+ *serial = NULL;
+ *stamp = 0; /* not available */
+
+ buf = xtrymalloc (app->serialnolen * 2 + 1);
+ if (!buf)
+ return gpg_error_from_errno (errno);
+ for (p=buf, i=0; i < app->serialnolen; p +=2, i++)
+ sprintf (p, "%02X", app->serialno[i]);
+ *p = 0;
+ *serial = buf;
+ return 0;
+}
+
+
+
+/* Release the card info structure. */
+void
+agent_release_card_info (struct agent_card_info_s *info)
+{
+ int i;
+
+ if (!info)
+ return;
+
+ xfree (info->serialno); info->serialno = NULL;
+ 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->fpr1valid = info->fpr2valid = info->fpr3valid = 0;
+ info->cafpr1valid = info->cafpr2valid = info->cafpr3valid = 0;
+ for (i=0; i < 4; i++)
+ {
+ xfree (info->private_do[i]);
+ info->private_do[i] = NULL;
+ }
+}
+
+
+/* Print an error message for a failed assuan_transact and return a
+ gpg error code. No error is printed if RC is 0. */
+static gpg_error_t
+test_transact (int rc, const char *command)
+{
+ if (!rc)
+ return 0;
+ log_error ("sending command `%s' to agent failed: %s\n",
+ command, assuan_strerror (rc));
+ return gpg_error (GPG_ERR_CARD);
+}
+
+
+/* Try to open a card using an already running agent. Prepare a
+ proper application context and return it. */
+static app_t
+open_card_via_agent (int *scd_available)
+{
+ assuan_context_t ctx;
+ app_t app;
+ struct agent_card_info_s info;
+ int rc;
+
+ *scd_available = 0;
+ ctx = agent_open (1, NULL);
+ if (!ctx)
+ return NULL;
+
+ /* Request the serialbnumber of the card. If we get
+ NOT_SUPPORTED or NO_SCDAEMON back, the gpg-agent either has
+ disabled scdaemon or it can't be used. We close the connection
+ in this case and use our own code. This may happen if just the
+ gpg-agent has been installed for the sake of passphrase
+ caching. */
+ memset (&info, 0, sizeof info);
+ rc = assuan_transact (ctx, "SCD SERIALNO openpgp",
+ NULL, NULL, NULL, NULL,
+ learn_status_cb, &info);
+ if (rc)
+ {
+ if ((rc & 0xffff) == 60 || (rc & 0xffff) == 119)
+ ; /* No scdaemon available to gpg-agent. */
+ else
+ {
+ write_status_text (STATUS_CARDCTRL, "4");
+ log_info ("selecting openpgp failed: %s\n", assuan_strerror (rc));
+ *scd_available = 1;
+ }
+ agent_release_card_info (&info);
+ agent_close (ctx);
+ return NULL;
+ }
+
+ app = xcalloc (1, sizeof *app);
+ app->assuan_ctx = ctx;
+
+ return app;
+}
+
+
+
+/* Open the current card and select the openpgp application. Return
+ an APP context handle to be used for further procesing or NULL on
+ error or if no OpenPGP application exists.*/
+static app_t
+open_card (void)
+{
+ int slot = -1;
+ int rc;
+ app_t app;
+ int did_shutdown = 0;
+ int retry_count = 0;
+
+ /* First check whether we can contact a gpg-agent and divert all
+ operation to it. This is required because gpg as well as the
+ agent require exclusive access to the reader. */
+ if (opt.use_agent)
+ {
+ int scd_available;
+
+ app = open_card_via_agent (&scd_available);
+ if (app)
+ goto ready; /* Yes, there is a agent with a usable card, go that way. */
+ if (scd_available)
+ return NULL; /* agent avilabale but card problem. */
+ }
+
+
+ /* No agent or usable agent, thus we do it on our own. */
+ card_close ();
+
+ retry:
+ if (did_shutdown)
+ apdu_reset (slot);
+ else
+ {
+ slot = apdu_open_reader (default_reader_port);
+ if (slot == -1)
+ {
+ write_status_text (STATUS_CARDCTRL, "5");
+ log_error (_("card reader not available\n"));
+ return NULL;
+ }
+ }
+
+ app = xcalloc (1, sizeof *app);
+ app->slot = slot;
+ rc = app_select_openpgp (app);
+ if (opt.limit_card_insert_tries
+ && ++retry_count >= opt.limit_card_insert_tries)
+ ;
+ else if (rc && !opt.batch)
+ {
+ write_status_text (STATUS_CARDCTRL, "1");
+
+ did_shutdown = !!apdu_shutdown_reader (slot);
+
+ if ( cpr_get_answer_okay_cancel ("cardctrl.insert_card.okay",
+ _("Please insert the card and hit return or enter 'c' to cancel: "),
+ 1) )
+ {
+ if (!did_shutdown)
+ apdu_close_reader (slot);
+ xfree (app);
+ goto retry;
+ }
+ }
+ if (rc)
+ {
+ write_status_text (STATUS_CARDCTRL, "4");
+ log_info (_("selecting openpgp failed: %s\n"), gpg_strerror (rc));
+ apdu_close_reader (slot);
+ xfree (app);
+ return NULL;
+ }
+
+ ready:
+ app->initialized = 1;
+ current_app = app;
+ if (is_status_enabled () )
+ {
+ int i;
+ char *p, *buf;
+
+ buf = xmalloc (5 + app->serialnolen * 2 + 1);
+ p = stpcpy (buf, "3 ");
+ for (i=0; i < app->serialnolen; p +=2, i++)
+ sprintf (p, "%02X", app->serialno[i]);
+ write_status_text (STATUS_CARDCTRL, buf);
+ xfree (buf);
+ }
+
+ return app;
+}
+
+
+void
+card_close (void)
+{
+ if (current_app)
+ {
+ app_t app = current_app;
+ current_app = NULL;
+
+ if (app->assuan_ctx)
+ agent_close (app->assuan_ctx);
+ else
+ apdu_close_reader (app->slot);
+ xfree (app);
+ }
+}
+
+
+/* Format a cache ID from the serialnumber in SN and return it as an
+ allocated string. In case of an error NULL is returned. */
+static char *
+format_cacheid (const char *sn)
+{
+ const char *s;
+ size_t snlen;
+ char *cacheid = NULL;
+
+ /* The serialnumber we use for a card is "CARDSN:serialno". Where
+ serialno is the BCD string (i.e. hex string) with the full
+ number. The serial number expect here constsis of hexdigits
+ followed by other characters, we cut off these other
+ characters. */
+ if (sn)
+ {
+ for (s=sn,snlen=0; hexdigitp (s); s++, snlen++)
+ ;
+ if (snlen == 32)
+ {
+ /* Yes, this looks indeed like an OpenPGP card S/N. */
+ cacheid = xtrymalloc (7+snlen+1);
+ if (cacheid)
+ {
+ memcpy (cacheid, "CARDSN:", 7);
+ memcpy (cacheid+7, sn, snlen);
+ cacheid[7+snlen] = 0;
+ }
+ }
+ }
+ return cacheid;
+}
+
+
+/* If RC is not 0, write an appropriate status message. */
+static void
+status_sc_op_failure (int rc)
+{
+ if (rc == G10ERR_CANCELED)
+ write_status_text (STATUS_SC_OP_FAILURE, "1");
+ else if (rc == G10ERR_BAD_PASS)
+ write_status_text (STATUS_SC_OP_FAILURE, "2");
+ else if (rc)
+ write_status (STATUS_SC_OP_FAILURE);
+}
+
+
+/* Check that the serial number of the current card (as described by
+ APP) matches SERIALNO. If there is no match and we are not in
+ batch mode, present a prompt to insert the desired card. The
+ function returnd 0 if the present card is okay, -1 if the user
+ selected to insert a new card or an error value. Note that the
+ card context will be closed in all cases except for 0 as return
+ value and if it was possible to merely shutdown the reader. */
+static int
+check_card_serialno (app_t app, const char *serialno)
+{
+ const char *s;
+ int ask = 0;
+ int n;
+
+ for (s = serialno, n=0; *s != '/' && hexdigitp (s); s++, n++)
+ ;
+ if (n != 32)
+ {
+ log_error ("invalid serial number in keyring detected\n");
+ return gpg_error (GPG_ERR_INV_ID);
+ }
+ if (app->serialnolen != 16)
+ ask = 1;
+ for (s = serialno, n=0; !ask && n < 16; s += 2, n++)
+ if (app->serialno[n] != xtoi_2 (s))
+ ask = 1;
+ if (ask)
+ {
+ char buf[5+32+1];
+ int did_shutdown = 0;
+
+ if (current_app && !apdu_shutdown_reader (current_app->slot))
+ did_shutdown = 1;
+ else
+ card_close ();
+
+ if (!opt.batch)
+ tty_printf (_("Please remove the current card and "
+ "insert the one with serial number:\n"
+ " %.*s\n"), 32, serialno);
+
+ sprintf (buf, "1 %.32s", serialno);
+ write_status_text (STATUS_CARDCTRL, buf);
+
+ if ( !opt.batch
+ && cpr_get_answer_okay_cancel ("cardctrl.change_card.okay",
+ _("Hit return when ready "
+ "or enter 'c' to cancel: "),
+ 1) )
+ {
+ card_close ();
+ return -1;
+ }
+ if (did_shutdown)
+ apdu_reset (current_app->slot);
+ else
+ card_close ();
+ return gpg_error (GPG_ERR_INV_ID);
+ }
+ return 0;
+}
+
+
+/* Take a 20 byte hexencoded string and put it into the the provided
+ 20 byte buffer FPR in binary format. */
+static int
+unhexify_fpr (const char *hexstr, unsigned char *fpr)
+{
+ const char *s;
+ int n;
+
+ for (s=hexstr, n=0; hexdigitp (s); s++, n++)
+ ;
+ if (*s || (n != 40))
+ return 0; /* no fingerprint (invalid or wrong length). */
+ n /= 2;
+ for (s=hexstr, n=0; *s; s += 2, n++)
+ fpr[n] = xtoi_2 (s);
+ return 1; /* okay */
+}
+
+/* Take the serial number from LINE and return it verbatim in a newly
+ allocated string. We make sure that only hex characters are
+ returned. */
+static char *
+store_serialno (const char *line)
+{
+ const char *s;
+ char *p;
+
+ for (s=line; hexdigitp (s); s++)
+ ;
+ p = xmalloc (s + 1 - line);
+ memcpy (p, line, s-line);
+ p[s-line] = 0;
+ return p;
+}
+
+
+
+static assuan_error_t
+learn_status_cb (void *opaque, const char *line)
+{
+ struct agent_card_info_s *parm = opaque;
+ const char *keyword = line;
+ int keywordlen;
+ int i;
+
+/* log_debug ("got status line `%s'\n", line); */
+ for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
+ ;
+ while (spacep (line))
+ line++;
+
+ if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen))
+ {
+ xfree (parm->serialno);
+ parm->serialno = store_serialno (line);
+ }
+ else if (keywordlen == 9 && !memcmp (keyword, "DISP-NAME", keywordlen))
+ {
+ xfree (parm->disp_name);
+ parm->disp_name = unescape_percent_string (line);
+ }
+ else if (keywordlen == 9 && !memcmp (keyword, "DISP-LANG", keywordlen))
+ {
+ xfree (parm->disp_lang);
+ parm->disp_lang = unescape_percent_string (line);
+ }
+ else if (keywordlen == 8 && !memcmp (keyword, "DISP-SEX", keywordlen))
+ {
+ parm->disp_sex = *line == '1'? 1 : *line == '2' ? 2: 0;
+ }
+ else if (keywordlen == 10 && !memcmp (keyword, "PUBKEY-URL", keywordlen))
+ {
+ xfree (parm->pubkey_url);
+ parm->pubkey_url = unescape_percent_string (line);
+ }
+ else if (keywordlen == 10 && !memcmp (keyword, "LOGIN-DATA", keywordlen))
+ {
+ xfree (parm->login_data);
+ parm->login_data = unescape_percent_string (line);
+ }
+ else if (keywordlen == 11 && !memcmp (keyword, "SIG-COUNTER", keywordlen))
+ {
+ parm->sig_counter = strtoul (line, NULL, 0);
+ }
+ else if (keywordlen == 10 && !memcmp (keyword, "CHV-STATUS", keywordlen))
+ {
+ char *p, *buf;
+
+ buf = p = unescape_percent_string (line);
+ if (buf)
+ {
+ while (spacep (p))
+ p++;
+ parm->chv1_cached = atoi (p);
+ while (*p && !spacep (p))
+ p++;
+ while (spacep (p))
+ p++;
+ for (i=0; *p && i < 3; i++)
+ {
+ parm->chvmaxlen[i] = atoi (p);
+ while (*p && !spacep (p))
+ p++;
+ while (spacep (p))
+ p++;
+ }
+ for (i=0; *p && i < 3; i++)
+ {
+ parm->chvretry[i] = atoi (p);
+ while (*p && !spacep (p))
+ p++;
+ while (spacep (p))
+ p++;
+ }
+ xfree (buf);
+ }
+ }
+ else if (keywordlen == 7 && !memcmp (keyword, "KEY-FPR", keywordlen))
+ {
+ int no = atoi (line);
+ while (* line && !spacep (line))
+ line++;
+ while (spacep (line))
+ line++;
+ if (no == 1)
+ parm->fpr1valid = unhexify_fpr (line, parm->fpr1);
+ else if (no == 2)
+ parm->fpr2valid = unhexify_fpr (line, parm->fpr2);
+ else if (no == 3)
+ parm->fpr3valid = unhexify_fpr (line, parm->fpr3);
+ }
+ else if (keywordlen == 8 && !memcmp (keyword, "KEY-TIME", keywordlen))
+ {
+ int no = atoi (line);
+ while (* line && !spacep (line))
+ line++;
+ while (spacep (line))
+ line++;
+ if (no == 1)
+ parm->fpr1time = strtoul (line, NULL, 10);
+ else if (no == 2)
+ parm->fpr2time = strtoul (line, NULL, 10);
+ else if (no == 3)
+ parm->fpr3time = strtoul (line, NULL, 10);
+ }
+ else if (keywordlen == 6 && !memcmp (keyword, "CA-FPR", keywordlen))
+ {
+ int no = atoi (line);
+ while (*line && !spacep (line))
+ line++;
+ while (spacep (line))
+ line++;
+ if (no == 1)
+ parm->cafpr1valid = unhexify_fpr (line, parm->cafpr1);
+ else if (no == 2)
+ parm->cafpr2valid = unhexify_fpr (line, parm->cafpr2);
+ else if (no == 3)
+ parm->cafpr3valid = unhexify_fpr (line, parm->cafpr3);
+ }
+ else if (keywordlen == 12 && !memcmp (keyword, "PRIVATE-DO-", 11)
+ && strchr ("1234", keyword[11]))
+ {
+ int no = keyword[11] - '1';
+ assert (no >= 0 && no <= 3);
+ xfree (parm->private_do[no]);
+ parm->private_do[no] = unescape_percent_string (line);
+ }
+
+ return 0;
+}
+
+
+/* Return card info. */
+int
+agent_learn (struct agent_card_info_s *info)
+{
+ app_t app;
+ int rc;
+ struct ctrl_ctx_s ctrl;
+ time_t stamp;
+ char *serial;
+
+ app = current_app? current_app : open_card ();
+ if (!app)
+ return gpg_error (GPG_ERR_CARD);
+
+ memset (info, 0, sizeof *info);
+
+ if (app->assuan_ctx)
+ {
+ rc = assuan_transact (app->assuan_ctx, "SCD LEARN --force",
+ NULL, NULL, NULL, NULL,
+ learn_status_cb, info);
+ rc = test_transact (rc, "SCD LEARN");
+ }
+ else
+ {
+ memset (&ctrl, 0, sizeof ctrl);
+ ctrl.status_cb = learn_status_cb;
+ ctrl.status_cb_arg = info;
+
+ rc = app_get_serial_and_stamp (app, &serial, &stamp);
+ if (!rc)
+ {
+ send_status_info (&ctrl, "SERIALNO",
+ serial, strlen(serial), NULL, 0);
+ xfree (serial);
+ rc = app->fnc.learn_status (app, &ctrl);
+ }
+ }
+
+ return rc;
+}
+
+
+/* Get an attribute from the card. Make sure info is initialized. */
+int
+agent_scd_getattr (const char *name, struct agent_card_info_s *info)
+{
+ int rc;
+ app_t app;
+ struct ctrl_ctx_s ctrl;
+
+ app = current_app? current_app : open_card ();
+ if (!app)
+ return gpg_error (GPG_ERR_CARD);
+
+ if (app->assuan_ctx)
+ {
+ char line[ASSUAN_LINELENGTH];
+
+ /* We assume that NAME does not need escaping. */
+ if (12 + strlen (name) > DIM(line)-1)
+ return gpg_error (GPG_ERR_CARD);
+ stpcpy (stpcpy (line, "SCD GETATTR "), name);
+
+ rc = test_transact (assuan_transact (app->assuan_ctx, line,
+ NULL, NULL, NULL, NULL,
+ learn_status_cb, info),
+ "SCD GETATTR");
+ }
+ else
+ {
+ ctrl.status_cb = learn_status_cb;
+ ctrl.status_cb_arg = info;
+ rc = app->fnc.getattr (app, &ctrl, name);
+ }
+
+ return rc;
+}
+
+
+
+static int
+pin_cb (void *opaque, const char *info, char **retstr)
+{
+ struct pincb_parm_s *parm = opaque;
+ char *value;
+ int canceled;
+ int isadmin = 0;
+ int newpin = 0;
+ const char *again_text = NULL;
+ const char *ends, *s;
+ char *cacheid = NULL;
+
+ *retstr = NULL;
+ /* log_debug ("asking for PIN '%s'\n", info); */
+
+ /* We use a special prefix to check whether the Admin PIN has been
+ requested. */
+ if (info && *info =='|' && (ends=strchr (info+1, '|')))
+ {
+ for (s=info+1; s < ends; s++)
+ {
+ if (*s == 'A')
+ isadmin = 1;
+ else if (*s == 'N')
+ newpin = 1;
+ }
+ info = ends+1;
+ }
+ else if (info && *info == '|')
+ log_debug ("pin_cb called without proper PIN info hack\n");
+
+ /* If we are not requesting a new PIN and we are not requesting an
+ AdminPIN, compute a string to be used as the cacheID for
+ gpg-agent. */
+ if (!newpin && !isadmin && parm)
+ {
+ cacheid = format_cacheid (parm->sn);
+ }
+ else if (newpin && parm)
+ {
+ /* Make really sure that it is not cached anymore. */
+ agent_clear_pin_cache (parm->sn);
+ }
+
+
+ again:
+ if (is_status_enabled())
+ {
+ if (parm && parm->sn && *parm->sn)
+ {
+ char *buf = xmalloc ( 10 + strlen (parm->sn) + 1);
+ strcpy (stpcpy (buf, isadmin? "OPENPGP 3 ":"OPENPGP 1 "), parm->sn);
+ write_status_text (STATUS_NEED_PASSPHRASE_PIN, buf);
+ xfree (buf);
+ }
+ else
+ write_status_text (STATUS_NEED_PASSPHRASE_PIN,
+ isadmin? "OPENPGP 3" : "OPENPGP 1");
+ }
+
+ value = ask_passphrase (info, again_text,
+ newpin && isadmin? "passphrase.adminpin.new.ask" :
+ newpin? "passphrase.pin.new.ask" :
+ isadmin? "passphrase.adminpin.ask" :
+ "passphrase.pin.ask",
+ newpin && isadmin? _("Enter New Admin PIN: ") :
+ newpin? _("Enter New PIN: ") :
+ isadmin? _("Enter Admin PIN: ")
+ : _("Enter PIN: "),
+ cacheid,
+ &canceled);
+ xfree (cacheid);
+ cacheid = NULL;
+ again_text = NULL;
+ if (!value && canceled)
+ return G10ERR_CANCELED;
+ else if (!value)
+ return G10ERR_GENERAL;
+
+ if (newpin)
+ {
+ char *value2;
+
+ value2 = ask_passphrase (info, NULL,
+ "passphrase.pin.repeat",
+ _("Repeat this PIN: "),
+ NULL,
+ &canceled);
+ if (!value2 && canceled)
+ {
+ xfree (value);
+ return G10ERR_CANCELED;
+ }
+ else if (!value2)
+ {
+ xfree (value);
+ return G10ERR_GENERAL;
+ }
+ if (strcmp (value, value2))
+ {
+ again_text = N_("PIN not correctly repeated; try again");
+ xfree (value2);
+ xfree (value);
+ value = NULL;
+ goto again;
+ }
+ xfree (value2);
+ }
+
+ *retstr = value;
+ return 0;
+}
+
+
+
+/* Send a SETATTR command to the SCdaemon. */
+int
+agent_scd_setattr (const char *name,
+ const unsigned char *value, size_t valuelen,
+ const char *serialno)
+{
+ app_t app;
+ int rc;
+ struct pincb_parm_s parm;
+
+ memset (&parm, 0, sizeof parm);
+ parm.sn = serialno;
+
+ app = current_app? current_app : open_card ();
+ if (!app)
+ return gpg_error (GPG_ERR_CARD);
+
+ if (app->assuan_ctx)
+ {
+ char line[ASSUAN_LINELENGTH];
+ char *p;
+
+ /* We assume that NAME does not need escaping. */
+ if (12 + strlen (name) > DIM(line)-1)
+ return gpg_error (GPG_ERR_CARD);
+ p = stpcpy (stpcpy (line, "SCD SETATTR "), name);
+ *p++ = ' ';
+ for (; valuelen; value++, valuelen--)
+ {
+ if (p >= line + DIM(line)-5 )
+ return gpg_error (GPG_ERR_CARD);
+ if (*value < ' ' || *value == '+' || *value == '%')
+ {
+ sprintf (p, "%%%02X", *value);
+ p += 3;
+ }
+ else if (*value == ' ')
+ *p++ = '+';
+ else
+ *p++ = *value;
+ }
+ *p = 0;
+
+ rc = test_transact (assuan_transact (app->assuan_ctx, line,
+ NULL, NULL, NULL, NULL, NULL, NULL),
+ "SCD SETATTR");
+ }
+ else
+ {
+ rc = app->fnc.setattr (app, name, pin_cb, &parm, value, valuelen);
+ }
+
+ status_sc_op_failure (rc);
+ return rc;
+}
+
+
+/* Handle a KEYDATA inquiry. Note, we only send the data,
+ assuan_transact takes care of flushing and writing the end */
+static assuan_error_t
+inq_writekey_parms (void *opaque, const char *keyword)
+{
+ struct writekey_parm_s *parm = opaque;
+
+ return assuan_send_data (parm->ctx, parm->keydata, parm->keydatalen);
+}
+
+
+/* Send a WRITEKEY command to the SCdaemon. */
+int
+agent_scd_writekey (int keyno, const char *serialno,
+ const unsigned char *keydata, size_t keydatalen)
+{
+ app_t app;
+ int rc;
+ char line[ASSUAN_LINELENGTH];
+ struct pincb_parm_s parm;
+
+ memset (&parm, 0, sizeof parm);
+ parm.sn = serialno;
+
+ app = current_app? current_app : open_card ();
+ if (!app)
+ return gpg_error (GPG_ERR_CARD);
+
+ if (app->assuan_ctx)
+ {
+ struct writekey_parm_s parms;
+
+ snprintf (line, DIM(line)-1, "SCD WRITEKEY --force OPENPGP.%d", keyno);
+ line[DIM(line)-1] = 0;
+ parms.ctx = app->assuan_ctx;
+ parms.keydata = keydata;
+ parms.keydatalen = keydatalen;
+ rc = test_transact (assuan_transact (app->assuan_ctx, line,
+ NULL, NULL,
+ inq_writekey_parms, &parms,
+ NULL, NULL),
+ "SCD WRITEKEY");
+ }
+ else
+ {
+ snprintf (line, DIM(line)-1, "OPENPGP.%d", keyno);
+ line[DIM(line)-1] = 0;
+ rc = app->fnc.writekey (app, NULL, line, 0x0001,
+ pin_cb, &parm,
+ keydata, keydatalen);
+ }
+
+ status_sc_op_failure (rc);
+ return rc;
+}
+
+
+
+static assuan_error_t
+genkey_status_cb (void *opaque, const char *line)
+{
+ struct agent_card_genkey_s *parm = opaque;
+ const char *keyword = line;
+ int keywordlen;
+
+/* log_debug ("got status line `%s'\n", line); */
+ for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
+ ;
+ while (spacep (line))
+ line++;
+
+ if (keywordlen == 7 && !memcmp (keyword, "KEY-FPR", keywordlen))
+ {
+ parm->fprvalid = unhexify_fpr (line, parm->fpr);
+ }
+ if (keywordlen == 8 && !memcmp (keyword, "KEY-DATA", keywordlen))
+ {
+ MPI a;
+ const char *name = line;
+ char *buf;
+
+ while (*line && !spacep (line))
+ line++;
+ while (spacep (line))
+ line++;
+
+ buf = xmalloc ( 2 + strlen (line) + 1);
+ strcpy (stpcpy (buf, "0x"), line);
+ a = mpi_alloc (300);
+ if( mpi_fromstr (a, buf) )
+ log_error ("error parsing received key data\n");
+ else if (*name == 'n' && spacep (name+1))
+ parm->n = a;
+ else if (*name == 'e' && spacep (name+1))
+ parm->e = a;
+ else
+ {
+ log_info ("unknown parameter name in received key data\n");
+ mpi_free (a);
+ }
+ xfree (buf);
+ }
+ else if (keywordlen == 14 && !memcmp (keyword,"KEY-CREATED-AT", keywordlen))
+ {
+ parm->created_at = (u32)strtoul (line, NULL, 10);
+ }
+
+ return 0;
+}
+
+/* Send a GENKEY command to the SCdaemon. */
+int
+agent_scd_genkey (struct agent_card_genkey_s *info, int keyno, int force,
+ const char *serialno)
+{
+ app_t app;
+ char line[ASSUAN_LINELENGTH];
+ struct ctrl_ctx_s ctrl;
+ int rc;
+ struct pincb_parm_s parm;
+
+ memset (&parm, 0, sizeof parm);
+ parm.sn = serialno;
+
+ app = current_app? current_app : open_card ();
+ if (!app)
+ return gpg_error (GPG_ERR_CARD);
+
+ memset (info, 0, sizeof *info);
+
+ if (app->assuan_ctx)
+ {
+ snprintf (line, DIM(line)-1, "SCD GENKEY %s%d",
+ force? "--force ":"", keyno);
+ line[DIM(line)-1] = 0;
+ rc = test_transact (assuan_transact (app->assuan_ctx, line,
+ NULL, NULL, NULL, NULL,
+ genkey_status_cb, info),
+ "SCD GENKEY");
+ }
+ else
+ {
+ snprintf (line, DIM(line)-1, "%d", keyno);
+ ctrl.status_cb = genkey_status_cb;
+ ctrl.status_cb_arg = info;
+ rc = app->fnc.genkey (app, &ctrl, line,
+ force? 1:0,
+ pin_cb, &parm);
+ }
+
+ status_sc_op_failure (rc);
+ return rc;
+}
+
+
+static assuan_error_t
+membuf_data_cb (void *opaque, const void *buffer, size_t length)
+{
+ membuf_t *data = opaque;
+
+ if (buffer)
+ put_membuf (data, buffer, length);
+ return 0;
+}
+
+
+/* Send a PKSIGN command to the SCdaemon. */
+int
+agent_scd_pksign (const char *serialno, int hashalgo,
+ const unsigned char *indata, size_t indatalen,
+ unsigned char **r_buf, size_t *r_buflen)
+{
+ struct pincb_parm_s parm;
+ app_t app;
+ int rc;
+
+ *r_buf = NULL;
+ *r_buflen = 0;
+ memset (&parm, 0, sizeof parm);
+ parm.sn = serialno;
+ retry:
+ app = current_app? current_app : open_card ();
+ if (!app)
+ return gpg_error (GPG_ERR_CARD);
+
+ if (app->assuan_ctx)
+ {
+ char *p, line[ASSUAN_LINELENGTH];
+ membuf_t data;
+ size_t len;
+ int i;
+
+ if (indatalen*2 + 50 > DIM(line))
+ return gpg_error (GPG_ERR_GENERAL);
+
+ p = stpcpy (line, "SCD SETDATA ");
+ for (i=0; i < indatalen ; i++, p += 2 )
+ sprintf (p, "%02X", indata[i]);
+ rc = test_transact (assuan_transact (app->assuan_ctx, line,
+ NULL, NULL, NULL, NULL, NULL, NULL),
+ "SCD SETDATA");
+ if (!rc)
+ {
+ init_membuf (&data, 1024);
+ snprintf (line, DIM(line)-1, "SCD PKSIGN %s%s",
+ hashalgo == GCRY_MD_RMD160? "--hash=rmd160 ": "",
+ serialno);
+ line[DIM(line)-1] = 0;
+ rc = test_transact (assuan_transact (app->assuan_ctx, line,
+ membuf_data_cb, &data,
+ NULL, NULL, NULL, NULL),
+ "SCD PKSIGN");
+ if (rc)
+ xfree (get_membuf (&data, &len));
+ else
+ *r_buf = get_membuf (&data, r_buflen);
+ }
+ }
+ else
+ {
+ /* Check that the card's serialnumber is as required.*/
+ rc = check_card_serialno (app, serialno);
+ if (rc == -1)
+ goto retry;
+
+ if (!rc)
+ rc = app->fnc.sign (app, serialno, hashalgo,
+ pin_cb, &parm,
+ indata, indatalen,
+ r_buf, r_buflen);
+ }
+
+ if (rc)
+ {
+ status_sc_op_failure (rc);
+ if (!app->assuan_ctx)
+ agent_clear_pin_cache (serialno);
+ }
+ return rc;
+}
+
+
+/* Send a PKDECRYPT command to the SCdaemon. */
+int
+agent_scd_pkdecrypt (const char *serialno,
+ const unsigned char *indata, size_t indatalen,
+ unsigned char **r_buf, size_t *r_buflen)
+{
+ struct pincb_parm_s parm;
+ app_t app;
+ int rc;
+
+ *r_buf = NULL;
+ *r_buflen = 0;
+ memset (&parm, 0, sizeof parm);
+ parm.sn = serialno;
+ retry:
+ app = current_app? current_app : open_card ();
+ if (!app)
+ return gpg_error (GPG_ERR_CARD);
+
+ if (app->assuan_ctx)
+ {
+ char *p, line[ASSUAN_LINELENGTH];
+ membuf_t data;
+ size_t len;
+ int i;
+
+ if (indatalen*2 + 50 > DIM(line))
+ return gpg_error (GPG_ERR_GENERAL);
+
+ p = stpcpy (line, "SCD SETDATA ");
+ for (i=0; i < indatalen ; i++, p += 2 )
+ sprintf (p, "%02X", indata[i]);
+ rc = test_transact (assuan_transact (app->assuan_ctx, line,
+ NULL, NULL, NULL, NULL, NULL, NULL),
+ "SCD SETDATA");
+ if (!rc)
+ {
+ init_membuf (&data, 1024);
+ snprintf (line, DIM(line)-1, "SCD PKDECRYPT %s", serialno);
+ line[DIM(line)-1] = 0;
+ rc = test_transact (assuan_transact (app->assuan_ctx, line,
+ membuf_data_cb, &data,
+ NULL, NULL, NULL, NULL),
+ "SCD PKDECRYPT");
+ if (rc)
+ xfree (get_membuf (&data, &len));
+ else
+ *r_buf = get_membuf (&data, r_buflen);
+ }
+ }
+ else
+ {
+ /* Check that the card's serialnumber is as required.*/
+ rc = check_card_serialno (app, serialno);
+ if (rc == -1)
+ goto retry;
+
+ if (!rc)
+ rc = app->fnc.decipher (app, serialno,
+ pin_cb, &parm,
+ indata, indatalen,
+ r_buf, r_buflen);
+ }
+
+ if (rc)
+ {
+ status_sc_op_failure (rc);
+ if (!app->assuan_ctx)
+ agent_clear_pin_cache (serialno);
+ }
+ return rc;
+}
+
+/* Change the PIN of an OpenPGP card or reset the retry
+ counter. SERIALNO may be NULL or a hex string finally passed to the
+ passphrase callback. */
+int
+agent_scd_change_pin (int chvno, const char *serialno)
+{
+ app_t app;
+ int reset = 0;
+ int rc;
+ struct pincb_parm_s parm;
+
+ memset (&parm, 0, sizeof parm);
+ parm.sn = serialno;
+
+ reset = (chvno >= 100);
+ chvno %= 100;
+
+ app = current_app? current_app : open_card ();
+ if (!app)
+ return gpg_error (GPG_ERR_CARD);
+
+ if (app->assuan_ctx)
+ {
+ char line[ASSUAN_LINELENGTH];
+
+ snprintf (line, DIM(line)-1, "SCD PASSWD%s %d",
+ reset? " --reset":"", chvno);
+ line[DIM(line)-1] = 0;
+ rc = test_transact (assuan_transact (app->assuan_ctx, line,
+ NULL, NULL, NULL, NULL, NULL, NULL),
+ "SCD PASSWD");
+ }
+ else
+ {
+ char chvnostr[50];
+
+ sprintf (chvnostr, "%d", chvno);
+ rc = app->fnc.change_pin (app, NULL, chvnostr, reset,
+ pin_cb, &parm);
+ }
+
+ status_sc_op_failure (rc);
+ return rc;
+}
+
+/* Perform a CHECKPIN operation. SERIALNO should be the serial
+ number of the card - optionally followed by the fingerprint;
+ however the fingerprint is ignored here. */
+int
+agent_scd_checkpin (const char *serialnobuf)
+{
+ app_t app;
+ int rc;
+ struct pincb_parm_s parm;
+
+ memset (&parm, 0, sizeof parm);
+ parm.sn = serialnobuf;
+
+ app = current_app? current_app : open_card ();
+ if (!app)
+ return gpg_error (GPG_ERR_CARD);
+
+ if (app->assuan_ctx)
+ {
+ char line[ASSUAN_LINELENGTH];
+
+ if (15 + strlen (serialnobuf) > DIM(line)-1)
+ return gpg_error (GPG_ERR_CARD);
+ stpcpy (stpcpy (line, "SCD CHECKPIN "), serialnobuf);
+ rc = test_transact (assuan_transact (app->assuan_ctx, line,
+ NULL, NULL, NULL, NULL, NULL, NULL),
+ "SCD CHECKPIN");
+ }
+ else
+ {
+ rc = app->fnc.check_pin (app, serialnobuf, pin_cb, &parm);
+ }
+
+ status_sc_op_failure (rc);
+ return rc;
+}
+
+
+
+void
+agent_clear_pin_cache (const char *sn)
+{
+ char *cacheid = format_cacheid (sn);
+ if (cacheid)
+ {
+ passphrase_clear_cache (NULL, cacheid, 0);
+ xfree (cacheid);
+ }
+}