aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWerner Koch <[email protected]>2003-09-28 13:41:58 +0000
committerWerner Koch <[email protected]>2003-09-28 13:41:58 +0000
commite369270a655fdab6b78053231c03bc2d82b672a9 (patch)
tree52d106255c5a1ce9dbfdffd09cb9b3f08197eb77
parentFirst bits of a card support backport from 1.9. It is not enabled by (diff)
downloadgnupg-e369270a655fdab6b78053231c03bc2d82b672a9.tar.gz
gnupg-e369270a655fdab6b78053231c03bc2d82b672a9.zip
* g10.c (main): New commands --card-edit, --card-status and
--change-pin. New options --ctapi-driver, --pcsc-driver and --disable-ccid * options.h (DBG_CARD_IO): New. * cardglue.c, cardclue.h: Enhanced. * card-util.c: New. Taken from current the gnupg 1.9 branch. * app-common.h, app-openpgp.c, iso7816.c, iso7816.h, apdu.c * apdu.h, ccid-driver.c, ccid-driver.h: New. Takem from the current gnupg 1.9 branch withy minor changes to include directives. * Makefile.am: Added these files.
-rw-r--r--g10/ChangeLog14
-rw-r--r--g10/Makefile.am8
-rw-r--r--g10/apdu.c1193
-rw-r--r--g10/apdu.h74
-rw-r--r--g10/app-common.h76
-rw-r--r--g10/app-openpgp.c1430
-rw-r--r--g10/card-util.c720
-rw-r--r--g10/cardglue.c433
-rw-r--r--g10/cardglue.h51
-rw-r--r--g10/ccid-driver.c990
-rw-r--r--g10/ccid-driver.h74
-rw-r--r--g10/g10.c63
-rw-r--r--g10/iso7816.c385
-rw-r--r--g10/iso7816.h58
-rw-r--r--g10/options.h9
15 files changed, 5576 insertions, 2 deletions
diff --git a/g10/ChangeLog b/g10/ChangeLog
index 4c71b2e21..a0905eda4 100644
--- a/g10/ChangeLog
+++ b/g10/ChangeLog
@@ -1,10 +1,22 @@
+2003-09-28 Werner Koch <[email protected]>
+
+ * g10.c (main): New commands --card-edit, --card-status and
+ --change-pin. New options --ctapi-driver, --pcsc-driver and
+ --disable-ccid
+ * options.h (DBG_CARD_IO): New.
+ * cardglue.c, cardclue.h: Enhanced.
+ * card-util.c: New. Taken from current the gnupg 1.9 branch.
+ * app-common.h, app-openpgp.c, iso7816.c, iso7816.h, apdu.c
+ * apdu.h, ccid-driver.c, ccid-driver.h: New. Takem from the current
+ gnupg 1.9 branch withy minor changes to include directives.
+ * Makefile.am: Added these files.
+
2003-09-27 Werner Koch <[email protected]>
* sign.c (do_sign) [ENABLE_CARD_SUPPORT]: Divert to card.
* cardglue.c, cardglue.h: New.
* Makefile.am (gpg_LDADD): Added.
(card_support_sources): New.
-
2003-09-25 David Shaw <[email protected]>
diff --git a/g10/Makefile.am b/g10/Makefile.am
index 4105a7e6c..2be9e35e7 100644
--- a/g10/Makefile.am
+++ b/g10/Makefile.am
@@ -64,7 +64,13 @@ common_source = \
signal.c
card_support_source = \
- cardglue.c cardclue.h
+ cardglue.c cardclue.h \
+ card-util.c \
+ app-common.h \
+ app-openpgp.c \
+ iso7816.c iso7816.h \
+ apdu.c apdu.h \
+ ccid-driver.c ccid-driver.h
gpg_SOURCES = g10.c \
diff --git a/g10/apdu.c b/g10/apdu.c
new file mode 100644
index 000000000..068949810
--- /dev/null
+++ b/g10/apdu.c
@@ -0,0 +1,1193 @@
+/* apdu.c - ISO 7816 APDU functions and low level I/O
+ * Copyright (C) 2003 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#ifdef ENABLE_CARD_SUPPORT
+/*
+ Note, that most of this code has been taken from 1.9.x branch
+ and is maintained over there if at all possible. Thus, if you make
+ changes here, please check that a similar change has been commited
+ to the 1.9.x branch.
+*/
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#ifdef HAVE_OPENSC
+# include <opensc/opensc.h>
+#endif
+
+#include "options.h"
+#include "errors.h"
+#include "memory.h"
+#include "util.h"
+#include "i18n.h"
+#include "cardglue.h"
+
+#include "apdu.h"
+#include "dynload.h"
+#include "ccid-driver.h"
+
+#define MAX_READER 4 /* Number of readers we support concurrently. */
+#define CARD_CONNECT_TIMEOUT 1 /* Number of seconds to wait for
+ insertion of the card (1 = don't wait). */
+
+
+/* A structure to collect information pertaining to one reader
+ slot. */
+struct reader_table_s {
+ int used; /* True if slot is used. */
+ unsigned short port; /* Port number: 0 = unused, 1 - dev/tty */
+ int is_ccid; /* Uses the internal CCID driver. */
+ struct {
+ ccid_driver_t handle;
+ } ccid;
+ int is_ctapi; /* This is a ctAPI driver. */
+ struct {
+ unsigned long context;
+ unsigned long card;
+ unsigned long protocol;
+ } pcsc;
+#ifdef HAVE_OPENSC
+ int is_osc; /* We are using the OpenSC driver layer. */
+ struct {
+ struct sc_context *ctx;
+ struct sc_card *scard;
+ } osc;
+#endif /*HAVE_OPENSC*/
+ int status;
+ unsigned char atr[33];
+ size_t atrlen;
+};
+typedef struct reader_table_s *reader_table_t;
+
+/* A global table to keep track of active readers. */
+static struct reader_table_s reader_table[MAX_READER];
+
+
+/* ct API function pointer. */
+static char (*CT_init) (unsigned short ctn, unsigned short Pn);
+static char (*CT_data) (unsigned short ctn, unsigned char *dad,
+ unsigned char *sad, unsigned short lc,
+ unsigned char *cmd, unsigned short *lr,
+ unsigned char *rsp);
+static char (*CT_close) (unsigned short ctn);
+
+/* PC/SC constants and function pointer. */
+#define PCSC_SCOPE_USER 0
+#define PCSC_SCOPE_TERMINAL 1
+#define PCSC_SCOPE_SYSTEM 2
+#define PCSC_SCOPE_GLOBAL 3
+
+#define PCSC_PROTOCOL_T0 1
+#define PCSC_PROTOCOL_T1 2
+#define PCSC_PROTOCOL_RAW 4
+
+#define PCSC_SHARE_EXCLUSIVE 1
+#define PCSC_SHARE_SHARED 2
+#define PCSC_SHARE_DIRECT 3
+
+#define PCSC_LEAVE_CARD 0
+#define PCSC_RESET_CARD 1
+#define PCSC_UNPOWER_CARD 2
+#define PCSC_EJECT_CARD 3
+
+struct pcsc_io_request_s {
+ unsigned long protocol;
+ unsigned long pci_len;
+};
+
+typedef struct pcsc_io_request_s *pcsc_io_request_t;
+
+long (*pcsc_establish_context) (unsigned long scope,
+ const void *reserved1,
+ const void *reserved2,
+ unsigned long *r_context);
+long (*pcsc_release_context) (unsigned long context);
+long (*pcsc_list_readers) (unsigned long context, const char *groups,
+ char *readers, unsigned long *readerslen);
+long (*pcsc_connect) (unsigned long context,
+ const char *reader,
+ unsigned long share_mode,
+ unsigned long preferred_protocols,
+ unsigned long *r_card,
+ unsigned long *r_active_protocol);
+long (*pcsc_disconnect) (unsigned long card, unsigned long disposition);
+long (*pcsc_status) (unsigned long card,
+ char *reader, unsigned long *readerlen,
+ unsigned long *r_state, unsigned long *r_protocol,
+ unsigned char *atr, unsigned long *atrlen);
+long (*pcsc_begin_transaction) (unsigned long card);
+long (*pcsc_end_transaction) (unsigned long card);
+long (*pcsc_transmit) (unsigned long card,
+ const pcsc_io_request_t send_pci,
+ const unsigned char *send_buffer,
+ unsigned long send_len,
+ pcsc_io_request_t recv_pci,
+ unsigned char *recv_buffer,
+ unsigned long *recv_len);
+long (*pcsc_set_timeout) (unsigned long context, unsigned long timeout);
+
+
+
+
+
+/*
+ Helper
+ */
+
+
+/* Find an unused reader slot for PORTSTR and put it into the reader
+ table. Return -1 on error or the index into the reader table. */
+static int
+new_reader_slot (void)
+{
+ int i, reader = -1;
+
+ for (i=0; i < MAX_READER; i++)
+ {
+ if (!reader_table[i].used && reader == -1)
+ reader = i;
+ }
+ if (reader == -1)
+ {
+ log_error ("new_reader_slot: out of slots\n");
+ return -1;
+ }
+ reader_table[reader].used = 1;
+ reader_table[reader].is_ccid = 0;
+ reader_table[reader].is_ctapi = 0;
+#ifdef HAVE_OPENSC
+ reader_table[reader].is_osc = 0;
+#endif
+ return reader;
+}
+
+
+static void
+dump_reader_status (int reader)
+{
+ if (reader_table[reader].is_ccid)
+ log_info ("reader slot %d: using ccid driver\n", reader);
+ else if (reader_table[reader].is_ctapi)
+ {
+ log_info ("reader slot %d: %s\n", reader,
+ reader_table[reader].status == 1? "Processor ICC present" :
+ reader_table[reader].status == 0? "Memory ICC present" :
+ "ICC not present" );
+ }
+ else
+ {
+ log_info ("reader slot %d: active protocol:", reader);
+ if ((reader_table[reader].pcsc.protocol & PCSC_PROTOCOL_T0))
+ log_printf (" T0");
+ else if ((reader_table[reader].pcsc.protocol & PCSC_PROTOCOL_T1))
+ log_printf (" T1");
+ else if ((reader_table[reader].pcsc.protocol & PCSC_PROTOCOL_RAW))
+ log_printf (" raw");
+ log_printf ("\n");
+ }
+
+ if (reader_table[reader].status != -1)
+ {
+ log_info ("reader %d: ATR=", reader);
+ log_printhex ("", reader_table[reader].atr,
+ reader_table[reader].atrlen);
+ }
+}
+
+
+
+/*
+ ct API Interface
+ */
+
+static const char *
+ct_error_string (long err)
+{
+ switch (err)
+ {
+ case 0: return "okay";
+ case -1: return "invalid data";
+ case -8: return "ct error";
+ case -10: return "transmission error";
+ case -11: return "memory allocation error";
+ case -128: return "HTSI error";
+ default: return "unknown CT-API error";
+ }
+}
+
+/* Wait for the card in READER and activate it. Return -1 on error or
+ 0 on success. */
+static int
+ct_activate_card (int reader)
+{
+ int rc, count;
+
+ for (count = 0; count < CARD_CONNECT_TIMEOUT; count++)
+ {
+ unsigned char dad[1], sad[1], cmd[11], buf[256];
+ unsigned short buflen;
+
+ if (count)
+ ; /* FIXME: we should use a more reliable timer than sleep. */
+
+ /* Check whether card has been inserted. */
+ dad[0] = 1; /* Destination address: CT. */
+ sad[0] = 2; /* Source address: Host. */
+
+ cmd[0] = 0x20; /* Class byte. */
+ cmd[1] = 0x13; /* Request status. */
+ cmd[2] = 0x00; /* From kernel. */
+ cmd[3] = 0x80; /* Return card's DO. */
+ cmd[4] = 0x00;
+
+ buflen = DIM(buf);
+
+ rc = CT_data (reader, dad, sad, 5, cmd, &buflen, buf);
+ if (rc || buflen < 2 || buf[buflen-2] != 0x90)
+ {
+ log_error ("ct_activate_card: can't get status of reader %d: %s\n",
+ reader, ct_error_string (rc));
+ return -1;
+ }
+
+ /* Connected, now activate the card. */
+ dad[0] = 1; /* Destination address: CT. */
+ sad[0] = 2; /* Source address: Host. */
+
+ cmd[0] = 0x20; /* Class byte. */
+ cmd[1] = 0x12; /* Request ICC. */
+ cmd[2] = 0x01; /* From first interface. */
+ cmd[3] = 0x01; /* Return card's ATR. */
+ cmd[4] = 0x00;
+
+ buflen = DIM(buf);
+
+ rc = CT_data (reader, dad, sad, 5, cmd, &buflen, buf);
+ if (rc || buflen < 2 || buf[buflen-2] != 0x90)
+ {
+ log_error ("ct_activate_card(%d): activation failed: %s\n",
+ reader, ct_error_string (rc));
+ return -1;
+ }
+
+ /* Store the type and the ATR. */
+ if (buflen - 2 > DIM (reader_table[0].atr))
+ {
+ log_error ("ct_activate_card(%d): ATR too long\n", reader);
+ return -1;
+ }
+
+ reader_table[reader].status = buf[buflen - 1];
+ memcpy (reader_table[reader].atr, buf, buflen - 2);
+ reader_table[reader].atrlen = buflen - 2;
+ return 0;
+ }
+
+ log_info ("ct_activate_card(%d): timeout waiting for card\n", reader);
+ return -1;
+}
+
+
+/* Open a reader and return an internal handle for it. PORT is a
+ non-negative value with the port number of the reader. USB readers
+ do have port numbers starting at 32769. */
+static int
+open_ct_reader (int port)
+{
+ int rc, reader;
+
+ if (port < 0 || port > 0xffff)
+ {
+ log_error ("open_ct_reader: invalid port %d requested\n", port);
+ return -1;
+ }
+ reader = new_reader_slot ();
+ if (reader == -1)
+ return reader;
+ reader_table[reader].port = port;
+
+ rc = CT_init (reader, (unsigned short)port);
+ if (rc)
+ {
+ log_error ("apdu_open_ct_reader failed on port %d: %s\n",
+ port, ct_error_string (rc));
+ reader_table[reader].used = 0;
+ return -1;
+ }
+
+ rc = ct_activate_card (reader);
+ if (rc)
+ {
+ reader_table[reader].used = 0;
+ return -1;
+ }
+
+ reader_table[reader].is_ctapi = 1;
+ dump_reader_status (reader);
+ return reader;
+}
+
+
+/* Actually send the APDU of length APDULEN to SLOT and return a
+ maximum of *BUFLEN data in BUFFER, the actual retruned size will be
+ set to BUFLEN. Returns: CT API error code. */
+static int
+ct_send_apdu (int slot, unsigned char *apdu, size_t apdulen,
+ unsigned char *buffer, size_t *buflen)
+{
+ int rc;
+ unsigned char dad[1], sad[1];
+ unsigned short ctbuflen;
+
+ dad[0] = 0; /* Destination address: Card. */
+ sad[0] = 2; /* Source address: Host. */
+ ctbuflen = *buflen;
+ if (DBG_CARD_IO)
+ log_printhex (" CT_data:", apdu, apdulen);
+ rc = CT_data (slot, dad, sad, apdulen, apdu, &ctbuflen, buffer);
+ *buflen = ctbuflen;
+
+ /* FIXME: map the errorcodes to GNUPG ones, so that they can be
+ shared between CTAPI and PCSC. */
+ return rc;
+}
+
+
+
+static const char *
+pcsc_error_string (long err)
+{
+ const char *s;
+
+ if (!err)
+ return "okay";
+ if ((err & 0x80100000) != 0x80100000)
+ return "invalid PC/SC error code";
+ err &= 0xffff;
+ switch (err)
+ {
+ case 0x0002: s = "cancelled"; break;
+ case 0x000e: s = "can't dispose"; break;
+ case 0x0008: s = "insufficient buffer"; break;
+ case 0x0015: s = "invalid ATR"; break;
+ case 0x0003: s = "invalid handle"; break;
+ case 0x0004: s = "invalid parameter"; break;
+ case 0x0005: s = "invalid target"; break;
+ case 0x0011: s = "invalid value"; break;
+ case 0x0006: s = "no memory"; break;
+ case 0x0013: s = "comm error"; break;
+ case 0x0001: s = "internal error"; break;
+ case 0x0014: s = "unknown error"; break;
+ case 0x0007: s = "waited too long"; break;
+ case 0x0009: s = "unknown reader"; break;
+ case 0x000a: s = "timeout"; break;
+ case 0x000b: s = "sharing violation"; break;
+ case 0x000c: s = "no smartcard"; break;
+ case 0x000d: s = "unknown card"; break;
+ case 0x000f: s = "proto mismatch"; break;
+ case 0x0010: s = "not ready"; break;
+ case 0x0012: s = "system cancelled"; break;
+ case 0x0016: s = "not transacted"; break;
+ case 0x0017: s = "reader unavailable"; break;
+ case 0x0065: s = "unsupported card"; break;
+ case 0x0066: s = "unresponsive card"; break;
+ case 0x0067: s = "unpowered card"; break;
+ case 0x0068: s = "reset card"; break;
+ case 0x0069: s = "removed card"; break;
+ case 0x006a: s = "inserted card"; break;
+ case 0x001f: s = "unsupported feature"; break;
+ case 0x0019: s = "PCI too small"; break;
+ case 0x001a: s = "reader unsupported"; break;
+ case 0x001b: s = "duplicate reader"; break;
+ case 0x001c: s = "card unsupported"; break;
+ case 0x001d: s = "no service"; break;
+ case 0x001e: s = "service stopped"; break;
+ default: s = "unknown PC/SC error code"; break;
+ }
+ return s;
+}
+
+/*
+ PC/SC Interface
+ */
+
+static int
+open_pcsc_reader (const char *portstr)
+{
+ long err;
+ int slot;
+ char *list = NULL;
+ unsigned long nreader, listlen, atrlen;
+ char *p;
+ unsigned long card_state, card_protocol;
+
+ slot = new_reader_slot ();
+ if (slot == -1)
+ return -1;
+
+ err = pcsc_establish_context (PCSC_SCOPE_SYSTEM, NULL, NULL,
+ &reader_table[slot].pcsc.context);
+ if (err)
+ {
+ log_error ("pcsc_establish_context failed: %s (0x%lx)\n",
+ pcsc_error_string (err), err);
+ reader_table[slot].used = 0;
+ return -1;
+ }
+
+ err = pcsc_list_readers (reader_table[slot].pcsc.context,
+ NULL, NULL, &nreader);
+ if (!err)
+ {
+ list = xtrymalloc (nreader+1); /* Better add 1 for safety reasons. */
+ if (!list)
+ {
+ log_error ("error allocating memory for reader list\n");
+ pcsc_release_context (reader_table[slot].pcsc.context);
+ reader_table[slot].used = 0;
+ return -1;
+ }
+ err = pcsc_list_readers (reader_table[slot].pcsc.context,
+ NULL, list, &nreader);
+ }
+ if (err)
+ {
+ log_error ("pcsc_list_readers failed: %s (0x%lx)\n",
+ pcsc_error_string (err), err);
+ pcsc_release_context (reader_table[slot].pcsc.context);
+ reader_table[slot].used = 0;
+ xfree (list);
+ return -1;
+ }
+
+ listlen = nreader;
+ p = list;
+ while (nreader)
+ {
+ if (!*p && !p[1])
+ break;
+ log_info ("detected reader `%s'\n", p);
+ if (nreader < (strlen (p)+1))
+ {
+ log_error ("invalid response from pcsc_list_readers\n");
+ break;
+ }
+ nreader -= strlen (p)+1;
+ p += strlen (p) + 1;
+ }
+
+ err = pcsc_connect (reader_table[slot].pcsc.context,
+ portstr? portstr : list,
+ PCSC_SHARE_EXCLUSIVE,
+ PCSC_PROTOCOL_T0|PCSC_PROTOCOL_T1,
+ &reader_table[slot].pcsc.card,
+ &reader_table[slot].pcsc.protocol);
+ if (err)
+ {
+ log_error ("pcsc_connect failed: %s (0x%lx)\n",
+ pcsc_error_string (err), err);
+ pcsc_release_context (reader_table[slot].pcsc.context);
+ reader_table[slot].used = 0;
+ xfree (list);
+ return -1;
+ }
+
+ atrlen = 32;
+ /* (We need to pass a dummy buffer. We use LIST because it ought to
+ be large enough.) */
+ err = pcsc_status (reader_table[slot].pcsc.card,
+ list, &listlen,
+ &card_state, &card_protocol,
+ reader_table[slot].atr, &atrlen);
+ xfree (list);
+ if (err)
+ {
+ log_error ("pcsc_status failed: %s (0x%lx)\n",
+ pcsc_error_string (err), err);
+ pcsc_release_context (reader_table[slot].pcsc.context);
+ reader_table[slot].used = 0;
+ return -1;
+ }
+ if (atrlen >= DIM (reader_table[0].atr))
+ log_bug ("ATR returned by pcsc_status is too large\n");
+ reader_table[slot].atrlen = atrlen;
+/* log_debug ("state from pcsc_status: 0x%lx\n", card_state); */
+/* log_debug ("protocol from pcsc_status: 0x%lx\n", card_protocol); */
+
+ dump_reader_status (slot);
+ return slot;
+}
+
+
+/* Actually send the APDU of length APDULEN to SLOT and return a
+ maximum of *BUFLEN data in BUFFER, the actual returned size will be
+ set to BUFLEN. Returns: CT API error code. */
+static int
+pcsc_send_apdu (int slot, unsigned char *apdu, size_t apdulen,
+ unsigned char *buffer, size_t *buflen)
+{
+ long err;
+ struct pcsc_io_request_s send_pci;
+ unsigned long recv_len;
+
+ if (DBG_CARD_IO)
+ log_printhex (" PCSC_data:", apdu, apdulen);
+
+ if ((reader_table[slot].pcsc.protocol & PCSC_PROTOCOL_T1))
+ send_pci.protocol = PCSC_PROTOCOL_T1;
+ else
+ send_pci.protocol = PCSC_PROTOCOL_T0;
+ send_pci.pci_len = sizeof send_pci;
+ recv_len = *buflen;
+ err = pcsc_transmit (reader_table[slot].pcsc.card,
+ &send_pci, apdu, apdulen,
+ NULL, buffer, &recv_len);
+ *buflen = recv_len;
+ if (err)
+ log_error ("pcsc_transmit failed: %s (0x%lx)\n",
+ pcsc_error_string (err), err);
+
+ return err? -1:0; /* FIXME: Return appropriate error code. */
+}
+
+
+#ifdef HAVE_LIBUSB
+/*
+ Internal CCID driver interface.
+ */
+
+static int
+open_ccid_reader (void)
+{
+ int err;
+ int slot;
+ reader_table_t slotp;
+
+ slot = new_reader_slot ();
+ if (slot == -1)
+ return -1;
+ slotp = reader_table + slot;
+
+ err = ccid_open_reader (&slotp->ccid.handle, 0);
+ if (err)
+ {
+ slotp->used = 0;
+ return -1;
+ }
+
+ err = ccid_get_atr (slotp->ccid.handle,
+ slotp->atr, sizeof slotp->atr, &slotp->atrlen);
+ if (err)
+ {
+ slotp->used = 0;
+ return -1;
+ }
+
+ slotp->is_ccid = 1;
+
+ dump_reader_status (slot);
+ return slot;
+}
+
+
+/* Actually send the APDU of length APDULEN to SLOT and return a
+ maximum of *BUFLEN data in BUFFER, the actual returned size will be
+ set to BUFLEN. Returns: Internal CCID driver error code. */
+static int
+send_apdu_ccid (int slot, unsigned char *apdu, size_t apdulen,
+ unsigned char *buffer, size_t *buflen)
+{
+ long err;
+ size_t maxbuflen;
+
+ if (DBG_CARD_IO)
+ log_printhex (" APDU_data:", apdu, apdulen);
+
+ maxbuflen = *buflen;
+ err = ccid_transceive (reader_table[slot].ccid.handle,
+ apdu, apdulen,
+ buffer, maxbuflen, buflen);
+ if (err)
+ log_error ("ccid_transceive failed: (0x%lx)\n",
+ err);
+
+ return err? -1:0; /* FIXME: Return appropriate error code. */
+}
+
+#endif /* HAVE_LIBUSB */
+
+
+
+#ifdef HAVE_OPENSC
+/*
+ OpenSC Interface.
+
+ This uses the OpenSC primitives to send APDUs. We need this
+ because we can't mix OpenSC and native (i.e. ctAPI or PC/SC)
+ access to a card for resource conflict reasons.
+ */
+
+static int
+open_osc_reader (int portno)
+{
+ int err;
+ int slot;
+ reader_table_t slotp;
+
+ slot = new_reader_slot ();
+ if (slot == -1)
+ return -1;
+ slotp = reader_table + slot;
+
+ err = sc_establish_context (&slotp->osc.ctx, "scdaemon");
+ if (err)
+ {
+ log_error ("failed to establish SC context: %s\n", sc_strerror (err));
+ slotp->used = 0;
+ return -1;
+ }
+ if (portno < 0 || portno >= slotp->osc.ctx->reader_count)
+ {
+ log_error ("no card reader available\n");
+ sc_release_context (slotp->osc.ctx);
+ slotp->used = 0;
+ return -1;
+ }
+
+ /* Redirect to our logging facility. */
+ slotp->osc.ctx->error_file = log_get_stream ();
+ slotp->osc.ctx->debug = opt.debug_sc;
+ slotp->osc.ctx->debug_file = log_get_stream ();
+
+ if (sc_detect_card_presence (slotp->osc.ctx->reader[portno], 0) != 1)
+ {
+ log_error ("no card present\n");
+ sc_release_context (slotp->osc.ctx);
+ slotp->used = 0;
+ return -1;
+ }
+
+ /* We want the standard ISO driver. */
+ /*FIXME: OpenSC does not like "iso7816", so we use EMV for now. */
+ err = sc_set_card_driver(slotp->osc.ctx, "emv");
+ if (err)
+ {
+ log_error ("failed to select the iso7816 driver: %s\n",
+ sc_strerror (err));
+ sc_release_context (slotp->osc.ctx);
+ slotp->used = 0;
+ return -1;
+ }
+
+ /* Now connect the card and hope that OpenSC won't try to be too
+ smart. */
+ err = sc_connect_card (slotp->osc.ctx->reader[portno], 0,
+ &slotp->osc.scard);
+ if (err)
+ {
+ log_error ("failed to connect card in reader %d: %s\n",
+ portno, sc_strerror (err));
+ sc_release_context (slotp->osc.ctx);
+ slotp->used = 0;
+ return -1;
+ }
+ if (opt.verbose)
+ log_info ("connected to card in opensc reader %d using driver `%s'\n",
+ portno, slotp->osc.scard->driver->name);
+
+ err = sc_lock (slotp->osc.scard);
+ if (err)
+ {
+ log_error ("can't lock card in reader %d: %s\n",
+ portno, sc_strerror (err));
+ sc_disconnect_card (slotp->osc.scard, 0);
+ sc_release_context (slotp->osc.ctx);
+ slotp->used = 0;
+ return -1;
+ }
+
+ if (slotp->osc.scard->atr_len >= DIM (slotp->atr))
+ log_bug ("ATR returned by opensc is too large\n");
+ slotp->atrlen = slotp->osc.scard->atr_len;
+ memcpy (slotp->atr, slotp->osc.scard->atr, slotp->atrlen);
+
+ slotp->is_osc = 1;
+
+ dump_reader_status (slot);
+ return slot;
+}
+
+
+/* Actually send the APDU of length APDULEN to SLOT and return a
+ maximum of *BUFLEN data in BUFFER, the actual returned size will be
+ set to BUFLEN. Returns: OpenSC error code. */
+static int
+osc_send_apdu (int slot, unsigned char *apdu, size_t apdulen,
+ unsigned char *buffer, size_t *buflen)
+{
+ long err;
+ struct sc_apdu a;
+ unsigned char data[SC_MAX_APDU_BUFFER_SIZE];
+ unsigned char result[SC_MAX_APDU_BUFFER_SIZE];
+
+ if (DBG_CARD_IO)
+ log_printhex (" APDU_data:", apdu, apdulen);
+
+ if (apdulen < 4)
+ {
+ log_error ("osc_send_apdu: APDU is too short\n");
+ return SC_ERROR_CMD_TOO_SHORT;
+ }
+
+ memset(&a, 0, sizeof a);
+ a.cla = *apdu++;
+ a.ins = *apdu++;
+ a.p1 = *apdu++;
+ a.p2 = *apdu++;
+ apdulen -= 4;
+
+ if (!apdulen)
+ a.cse = SC_APDU_CASE_1;
+ else if (apdulen == 1)
+ {
+ a.le = *apdu? *apdu : 256;
+ apdu++; apdulen--;
+ a.cse = SC_APDU_CASE_2_SHORT;
+ }
+ else
+ {
+ a.lc = *apdu++; apdulen--;
+ if (apdulen < a.lc)
+ {
+ log_error ("osc_send_apdu: APDU shorter than specified in Lc\n");
+ return SC_ERROR_CMD_TOO_SHORT;
+
+ }
+ memcpy(data, apdu, a.lc);
+ apdu += a.lc; apdulen -= a.lc;
+
+ a.data = data;
+ a.datalen = a.lc;
+
+ if (!apdulen)
+ a.cse = SC_APDU_CASE_3_SHORT;
+ else
+ {
+ a.le = *apdu? *apdu : 256;
+ apdu++; apdulen--;
+ if (apdulen)
+ {
+ log_error ("osc_send_apdu: APDU larger than specified\n");
+ return SC_ERROR_CMD_TOO_LONG;
+ }
+ a.cse = SC_APDU_CASE_4_SHORT;
+ }
+ }
+
+ a.resp = result;
+ a.resplen = DIM(result);
+
+ err = sc_transmit_apdu (reader_table[slot].osc.scard, &a);
+ if (err)
+ {
+ log_error ("sc_apdu_transmit failed: %s\n", sc_strerror (err));
+ return err;
+ }
+
+ if (*buflen < 2 || a.resplen > *buflen - 2)
+ {
+ log_error ("osc_send_apdu: provided buffer too short to store result\n");
+ return SC_ERROR_BUFFER_TOO_SMALL;
+ }
+ memcpy (buffer, a.resp, a.resplen);
+ buffer[a.resplen] = a.sw1;
+ buffer[a.resplen+1] = a.sw2;
+ *buflen = a.resplen + 2;
+ return 0;
+}
+
+#endif /* HAVE_OPENSC */
+
+
+
+/*
+ Driver Access
+ */
+
+/* Open the reader and return an internal slot number or -1 on
+ error. If PORTSTR is NULL we default to a suitable port (for ctAPI:
+ the first USB reader. For PC/SC the first listed reader). If
+ OpenSC support is compiled in, we first try to use OpenSC. */
+int
+apdu_open_reader (const char *portstr)
+{
+ static int pcsc_api_loaded, ct_api_loaded;
+
+#ifdef HAVE_LIBUSB
+ if (!opt.disable_ccid)
+ {
+ int slot;
+
+ slot = open_ccid_reader ();
+ if (slot != -1)
+ return slot; /* got one */
+ }
+#endif /* HAVE_LIBUSB */
+
+#ifdef HAVE_OPENSC
+ if (!opt.disable_opensc)
+ {
+ int port = portstr? atoi (portstr) : 0;
+
+ return open_osc_reader (port);
+ }
+#endif /* HAVE_OPENSC */
+
+
+ if (opt.ctapi_driver && *opt.ctapi_driver)
+ {
+ int port = portstr? atoi (portstr) : 32768;
+
+ if (!ct_api_loaded)
+ {
+ void *handle;
+
+ handle = dlopen (opt.ctapi_driver, RTLD_LAZY);
+ if (!handle)
+ {
+ log_error ("apdu_open_reader: failed to open driver: %s",
+ dlerror ());
+ return -1;
+ }
+ CT_init = dlsym (handle, "CT_init");
+ CT_data = dlsym (handle, "CT_data");
+ CT_close = dlsym (handle, "CT_close");
+ if (!CT_init || !CT_data || !CT_close)
+ {
+ log_error ("apdu_open_reader: invalid ctAPI driver\n");
+ dlclose (handle);
+ return -1;
+ }
+ ct_api_loaded = 1;
+ }
+ return open_ct_reader (port);
+ }
+
+
+ /* No ctAPI configured, so lets try the PC/SC API */
+ if (!pcsc_api_loaded)
+ {
+ void *handle;
+
+ handle = dlopen (opt.pcsc_driver, RTLD_LAZY);
+ if (!handle)
+ {
+ log_error ("apdu_open_reader: failed to open driver `%s': %s",
+ opt.pcsc_driver, dlerror ());
+ return -1;
+ }
+
+ pcsc_establish_context = dlsym (handle, "SCardEstablishContext");
+ pcsc_release_context = dlsym (handle, "SCardReleaseContext");
+ pcsc_list_readers = dlsym (handle, "SCardListReaders");
+ pcsc_connect = dlsym (handle, "SCardConnect");
+ pcsc_disconnect = dlsym (handle, "SCardDisconnect");
+ pcsc_status = dlsym (handle, "SCardStatus");
+ pcsc_begin_transaction = dlsym (handle, "SCardBeginTransaction");
+ pcsc_end_transaction = dlsym (handle, "SCardEndTransaction");
+ pcsc_transmit = dlsym (handle, "SCardTransmit");
+ pcsc_set_timeout = dlsym (handle, "SCardSetTimeout");
+
+ if (!pcsc_establish_context
+ || !pcsc_release_context
+ || !pcsc_list_readers
+ || !pcsc_connect
+ || !pcsc_disconnect
+ || !pcsc_status
+ || !pcsc_begin_transaction
+ || !pcsc_end_transaction
+ || !pcsc_transmit
+ || !pcsc_set_timeout)
+ {
+ log_error ("apdu_open_reader: invalid PC/SC driver\n");
+ dlclose (handle);
+ return -1;
+ }
+ pcsc_api_loaded = 1;
+ }
+
+ return open_pcsc_reader (portstr);
+}
+
+
+unsigned char *
+apdu_get_atr (int slot, size_t *atrlen)
+{
+ char *buf;
+
+ if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
+ return NULL;
+
+ buf = xtrymalloc (reader_table[slot].atrlen);
+ if (!buf)
+ return NULL;
+ memcpy (buf, reader_table[slot].atr, reader_table[slot].atrlen);
+ *atrlen = reader_table[slot].atrlen;
+ return buf;
+}
+
+
+static const char *
+error_string (int slot, long rc)
+{
+ if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
+ return "[invalid slot]";
+ if (reader_table[slot].is_ctapi)
+ return ct_error_string (rc);
+#ifdef HAVE_LIBUSB
+ else if (reader_table[slot].is_ccid)
+ return "no CCID driver error strings yet";
+#endif
+#ifdef HAVE_OPENSC
+ else if (reader_table[slot].is_osc)
+ return sc_strerror (rc);
+#endif
+ else
+ return pcsc_error_string (rc);
+}
+
+
+/* Dispatcher for the actual send_apdu fucntion. */
+static int
+send_apdu (int slot, unsigned char *apdu, size_t apdulen,
+ unsigned char *buffer, size_t *buflen)
+{
+ if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
+ return SW_HOST_NO_DRIVER;
+ if (reader_table[slot].is_ctapi)
+ return ct_send_apdu (slot, apdu, apdulen, buffer, buflen);
+#ifdef HAVE_LIBUSB
+ else if (reader_table[slot].is_ccid)
+ return send_apdu_ccid (slot, apdu, apdulen, buffer, buflen);
+#endif
+#ifdef HAVE_OPENSC
+ else if (reader_table[slot].is_osc)
+ return osc_send_apdu (slot, apdu, apdulen, buffer, buflen);
+#endif
+ else
+ return pcsc_send_apdu (slot, apdu, apdulen, buffer, buflen);
+}
+
+/* Send an APDU to the card in SLOT. The APDU is created from all
+ given parameters: CLASS, INS, P0, P1, LC, DATA, LE. A value of -1
+ for LC won't sent this field and the data field; in this case DATA
+ must also be passed as NULL. The return value is the status word
+ or -1 for an invalid SLOT or other non card related error. If
+ RETBUF is not NULL, it will receive an allocated buffer with the
+ returned data. The length of that data will be put into
+ *RETBUFLEN. The caller is reponsible for releasing the buffer even
+ in case of errors. */
+int
+apdu_send_le(int slot, int class, int ins, int p0, int p1,
+ int lc, const char *data, int le,
+ unsigned char **retbuf, size_t *retbuflen)
+{
+ unsigned char result[256+10]; /* 10 extra in case of bugs in the driver. */
+ size_t resultlen = 256;
+ unsigned char apdu[5+256+1];
+ size_t apdulen;
+ int sw;
+ long rc; /* we need a long here due to PC/SC. */
+
+ if (DBG_CARD_IO)
+ log_debug ("send apdu: c=%02X i=%02X p0=%02X p1=%02X lc=%d le=%d\n",
+ class, ins, p0, p1, lc, le);
+
+ if (lc != -1 && (lc > 255 || lc < 0))
+ return SW_WRONG_LENGTH;
+ if (le != -1 && (le > 256 || le < 1))
+ return SW_WRONG_LENGTH;
+ if ((!data && lc != -1) || (data && lc == -1))
+ return SW_HOST_INV_VALUE;
+
+ apdulen = 0;
+ apdu[apdulen++] = class;
+ apdu[apdulen++] = ins;
+ apdu[apdulen++] = p0;
+ apdu[apdulen++] = p1;
+ if (lc != -1)
+ {
+ apdu[apdulen++] = lc;
+ memcpy (apdu+apdulen, data, lc);
+ apdulen += lc;
+ }
+ if (le != -1)
+ apdu[apdulen++] = le; /* Truncation is okay becuase 0 means 256. */
+ assert (sizeof (apdu) >= apdulen);
+ /* As safeguard don't pass any garbage from the stack to the driver. */
+ memset (apdu+apdulen, 0, sizeof (apdu) - apdulen);
+ rc = send_apdu (slot, apdu, apdulen, result, &resultlen);
+ if (rc || resultlen < 2)
+ {
+ log_error ("apdu_send_simple(%d) failed: %s\n",
+ slot, error_string (slot, rc));
+ return SW_HOST_INCOMPLETE_CARD_RESPONSE;
+ }
+ sw = (result[resultlen-2] << 8) | result[resultlen-1];
+ /* store away the returned data but strip the statusword. */
+ resultlen -= 2;
+ if (DBG_CARD_IO)
+ {
+ log_debug (" response: sw=%04X datalen=%d\n", sw, resultlen);
+ if ( !retbuf && (sw == SW_SUCCESS || (sw & 0xff00) == SW_MORE_DATA))
+ log_printhex (" dump: ", result, resultlen);
+ }
+
+ if (sw == SW_SUCCESS)
+ {
+ if (retbuf)
+ {
+ *retbuf = xtrymalloc (resultlen? resultlen : 1);
+ if (!*retbuf)
+ return SW_HOST_OUT_OF_CORE;
+ *retbuflen = resultlen;
+ memcpy (*retbuf, result, resultlen);
+ }
+ }
+ else if ((sw & 0xff00) == SW_MORE_DATA)
+ {
+ unsigned char *p = NULL, *tmp;
+ size_t bufsize = 4096;
+
+ /* It is likely that we need to return much more data, so we
+ start off with a large buffer. */
+ if (retbuf)
+ {
+ *retbuf = p = xtrymalloc (bufsize);
+ if (!*retbuf)
+ return SW_HOST_OUT_OF_CORE;
+ assert (resultlen < bufsize);
+ memcpy (p, result, resultlen);
+ p += resultlen;
+ }
+
+ do
+ {
+ int len = (sw & 0x00ff);
+
+ log_debug ("apdu_send_simple(%d): %d more bytes available\n",
+ slot, len);
+ apdulen = 0;
+ apdu[apdulen++] = class;
+ apdu[apdulen++] = 0xC0;
+ apdu[apdulen++] = 0;
+ apdu[apdulen++] = 0;
+ apdu[apdulen++] = 64; /* that is 256 bytes for Le */
+ memset (apdu+apdulen, 0, sizeof (apdu) - apdulen);
+ rc = send_apdu (slot, apdu, apdulen, result, &resultlen);
+ if (rc || resultlen < 2)
+ {
+ log_error ("apdu_send_simple(%d) for get response failed: %s\n",
+ slot, error_string (slot, rc));
+ return SW_HOST_INCOMPLETE_CARD_RESPONSE;
+ }
+ sw = (result[resultlen-2] << 8) | result[resultlen-1];
+ resultlen -= 2;
+ if (DBG_CARD_IO)
+ {
+ log_debug (" more: sw=%04X datalen=%d\n", sw, resultlen);
+ if (!retbuf && (sw==SW_SUCCESS || (sw&0xff00)==SW_MORE_DATA))
+ log_printhex (" dump: ", result, resultlen);
+ }
+
+ if ((sw & 0xff00) == SW_MORE_DATA || sw == SW_SUCCESS)
+ {
+ if (retbuf)
+ {
+ if (p - *retbuf + resultlen > bufsize)
+ {
+ bufsize += resultlen > 4096? resultlen: 4096;
+ tmp = xtryrealloc (*retbuf, bufsize);
+ if (!tmp)
+ return SW_HOST_OUT_OF_CORE;
+ p = tmp + (p - *retbuf);
+ *retbuf = tmp;
+ }
+ memcpy (p, result, resultlen);
+ p += resultlen;
+ }
+ }
+ else
+ log_info ("apdu_send_simple(%d) "
+ "got unexpected status %04X from get response\n",
+ slot, sw);
+ }
+ while ((sw & 0xff00) == SW_MORE_DATA);
+
+ if (retbuf)
+ {
+ *retbuflen = p - *retbuf;
+ tmp = xtryrealloc (*retbuf, *retbuflen);
+ if (tmp)
+ *retbuf = tmp;
+ }
+ }
+ if (DBG_CARD_IO && retbuf && sw == SW_SUCCESS)
+ log_printhex (" dump: ", *retbuf, *retbuflen);
+
+ return sw;
+}
+
+/* Send an APDU to the card in SLOT. The APDU is created from all
+ given parameters: CLASS, INS, P0, P1, LC, DATA. A value of -1 for
+ LC won't sent this field and the data field; in this case DATA must
+ also be passed as NULL. The return value is the status word or -1
+ for an invalid SLOT or other non card related error. If RETBUF is
+ not NULL, it will receive an allocated buffer with the returned
+ data. The length of that data will be put into *RETBUFLEN. The
+ caller is reponsible for releasing the buffer even in case of
+ errors. */
+int
+apdu_send (int slot, int class, int ins, int p0, int p1,
+ int lc, const char *data, unsigned char **retbuf, size_t *retbuflen)
+{
+ return apdu_send_le (slot, class, ins, p0, p1, lc, data, 256,
+ retbuf, retbuflen);
+}
+
+/* Send an APDU to the card in SLOT. The APDU is created from all
+ given parameters: CLASS, INS, P0, P1, LC, DATA. A value of -1 for
+ LC won't sent this field and the data field; in this case DATA must
+ also be passed as NULL. The return value is the status word or -1
+ for an invalid SLOT or other non card related error. No data will be
+ returned. */
+int
+apdu_send_simple (int slot, int class, int ins, int p0, int p1,
+ int lc, const char *data)
+{
+ return apdu_send_le (slot, class, ins, p0, p1, lc, data, -1, NULL, NULL);
+}
+
+
+
+#endif /*ENABLE_CARD_SUPPORT*/
diff --git a/g10/apdu.h b/g10/apdu.h
new file mode 100644
index 000000000..6e4244ba0
--- /dev/null
+++ b/g10/apdu.h
@@ -0,0 +1,74 @@
+/* apdu.h - ISO 7816 APDU functions and low level I/O
+ * Copyright (C) 2003 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#ifndef APDU_H
+#define APDU_H
+
+/* ISO 7816 values for the statusword are defined here because they
+ should not be visible to the users of the actual ISO command
+ API. */
+enum {
+ SW_MORE_DATA = 0x6100, /* Note: that the low byte must be
+ masked of.*/
+ SW_EEPROM_FAILURE = 0x6581,
+ SW_WRONG_LENGTH = 0x6700,
+ SW_CHV_WRONG = 0x6982,
+ SW_CHV_BLOCKED = 0x6983,
+ SW_USE_CONDITIONS = 0x6985,
+ SW_NOT_SUPPORTED = 0x6a81,
+ SW_BAD_PARAMETER = 0x6a80, /* (in the data field) */
+ SW_REF_NOT_FOUND = 0x6a88,
+ SW_BAD_P0_P1 = 0x6b00,
+ SW_INS_NOT_SUP = 0x6d00,
+ SW_CLA_NOT_SUP = 0x6e00,
+ SW_SUCCESS = 0x9000,
+
+ /* The follwoing statuswords are no real ones but used to map host
+ OS errors into status words. A status word is 16 bit so that
+ those values can't be issued by a card. */
+ SW_HOST_OUT_OF_CORE = 0x10001, /* No way yet to differentiate
+ between errnos on a failed malloc. */
+ SW_HOST_INV_VALUE = 0x10002,
+ SW_HOST_INCOMPLETE_CARD_RESPONSE = 0x10003,
+ SW_HOST_NO_DRIVER = 0x10004
+};
+
+
+
+/* Note , that apdu_open_reader returns no status word but -1 on error. */
+int apdu_open_reader (const char *portstr);
+unsigned char *apdu_get_atr (int slot, size_t *atrlen);
+
+
+/* The apdu send functions do return status words. */
+int apdu_send_simple (int slot, int class, int ins, int p0, int p1,
+ int lc, const char *data);
+int apdu_send (int slot, int class, int ins, int p0, int p1,
+ int lc, const char *data,
+ unsigned char **retbuf, size_t *retbuflen);
+int apdu_send_le (int slot, int class, int ins, int p0, int p1,
+ int lc, const char *data, int le,
+ unsigned char **retbuf, size_t *retbuflen);
+
+
+#endif /*APDU_H*/
+
+
+
diff --git a/g10/app-common.h b/g10/app-common.h
new file mode 100644
index 000000000..26666e7a7
--- /dev/null
+++ b/g10/app-common.h
@@ -0,0 +1,76 @@
+/* app-common.h
+ * Copyright (C) 2003 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#ifndef APP_COMMON_H
+#define APP_COMMON_H
+
+struct app_ctx_s {
+ int initialized; /* The application has been initialied and the
+ function pointers may be used. Note that for
+ unsupported operations the particular
+ function pointer is set to NULL */
+ int slot; /* Used reader. */
+ unsigned char *serialno; /* Serialnumber in raw form, allocated. */
+ size_t serialnolen; /* Length in octets of serialnumber. */
+ unsigned int card_version;
+ int did_chv1;
+ int did_chv2;
+ int did_chv3;
+ struct {
+ int (*learn_status) (APP app, CTRL ctrl);
+ int (*setattr) (APP app, const char *name,
+ int (*pincb)(void*, const char *, char **),
+ void *pincb_arg,
+ const unsigned char *value, size_t valuelen);
+ int (*sign) (APP app,
+ const char *keyidstr, int hashalgo,
+ int (pincb)(void*, const char *, char **),
+ void *pincb_arg,
+ const void *indata, size_t indatalen,
+ unsigned char **outdata, size_t *outdatalen );
+ int (*auth) (APP app, const char *keyidstr,
+ int (*pincb)(void*, const char *, char **),
+ void *pincb_arg,
+ const void *indata, size_t indatalen,
+ unsigned char **outdata, size_t *outdatalen);
+ int (*decipher) (APP app, const char *keyidstr,
+ int (pincb)(void*, const char *, char **),
+ void *pincb_arg,
+ const void *indata, size_t indatalen,
+ unsigned char **outdata, size_t *outdatalen);
+ int (*genkey) (APP app, CTRL ctrl,
+ const char *keynostr, unsigned int flags,
+ int (*pincb)(void*, const char *, char **),
+ void *pincb_arg);
+ int (*change_pin) (APP app, CTRL ctrl,
+ const char *chvnostr, int reset_mode,
+ int (*pincb)(void*, const char *, char **),
+ void *pincb_arg);
+ } fnc;
+};
+
+
+int app_select_openpgp (APP app, unsigned char **sn, size_t *snlen);
+int app_get_serial_and_stamp (APP app, char **serial, time_t *stamp);
+
+#endif /*APP_COMMON_H*/
+
+
+
diff --git a/g10/app-openpgp.c b/g10/app-openpgp.c
new file mode 100644
index 000000000..5469a0955
--- /dev/null
+++ b/g10/app-openpgp.c
@@ -0,0 +1,1430 @@
+/* app-openpgp.c - The OpenPGP card application.
+ * Copyright (C) 2003 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#ifdef ENABLE_CARD_SUPPORT
+/*
+ Note, that most of this code has been taken from 1.9.x branch
+ and is maintained over there if at all possible. Thus, if you make
+ changes here, please check that a similar change has been commited
+ to the 1.9.x branch.
+*/
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <time.h>
+
+#include "options.h"
+#include "errors.h"
+#include "memory.h"
+#include "util.h"
+#include "i18n.h"
+
+#include "iso7816.h"
+#include "cardglue.h"
+#include "app-common.h"
+
+static struct {
+ int tag;
+ int constructed;
+ int get_from; /* Constructed DO with this DO or 0 for direct access. */
+ int binary;
+ char *desc;
+} data_objects[] = {
+ { 0x005E, 0, 0, 1, "Login Data" },
+ { 0x5F50, 0, 0, 0, "URL" },
+ { 0x0065, 1, 0, 1, "Cardholder Related Data"},
+ { 0x005B, 0, 0x65, 0, "Name" },
+ { 0x5F2D, 0, 0x65, 0, "Language preferences" },
+ { 0x5F35, 0, 0x65, 0, "Sex" },
+ { 0x006E, 1, 0, 1, "Application Related Data" },
+ { 0x004F, 0, 0x6E, 1, "AID" },
+ { 0x0073, 1, 0, 1, "Discretionary Data Objects" },
+ { 0x0047, 0, 0x6E, 1, "Card Capabilities" },
+ { 0x00C0, 0, 0x6E, 1, "Extended Card Capabilities" },
+ { 0x00C1, 0, 0x6E, 1, "Algorithm Attributes Signature" },
+ { 0x00C2, 0, 0x6E, 1, "Algorithm Attributes Decryption" },
+ { 0x00C3, 0, 0x6E, 1, "Algorithm Attributes Authentication" },
+ { 0x00C4, 0, 0x6E, 1, "CHV Status Bytes" },
+ { 0x00C5, 0, 0x6E, 1, "Fingerprints" },
+ { 0x00C6, 0, 0x6E, 1, "CA Fingerprints" },
+ { 0x007A, 1, 0, 1, "Security Support Template" },
+ { 0x0093, 0, 0x7A, 1, "Digital Signature Counter" },
+ { 0 }
+};
+
+
+static unsigned long get_sig_counter (APP app);
+
+
+
+/* Locate a TLV encoded data object in BUFFER of LENGTH and
+ return a pointer to value as well as its length in NBYTES. Return
+ NULL if it was not found. Note, that the function does not check
+ whether the value fits into the provided buffer.
+
+ FIXME: Move this to an extra file, it is mostly duplicated from card.c.
+*/
+static const unsigned char *
+find_tlv (const unsigned char *buffer, size_t length,
+ int tag, size_t *nbytes, int nestlevel)
+{
+ const unsigned char *s = buffer;
+ size_t n = length;
+ size_t len;
+ int this_tag;
+ int composite;
+
+ for (;;)
+ {
+ buffer = s;
+ if (n < 2)
+ return NULL; /* buffer definitely too short for tag and length. */
+ if (!*s || *s == 0xff)
+ { /* Skip optional filler between TLV objects. */
+ s++;
+ n--;
+ continue;
+ }
+ composite = !!(*s & 0x20);
+ if ((*s & 0x1f) == 0x1f)
+ { /* more tag bytes to follow */
+ s++;
+ n--;
+ if (n < 2)
+ return NULL; /* buffer definitely too short for tag and length. */
+ if ((*s & 0x1f) == 0x1f)
+ return NULL; /* We support only up to 2 bytes. */
+ this_tag = (s[-1] << 8) | (s[0] & 0x7f);
+ }
+ else
+ this_tag = s[0];
+ len = s[1];
+ s += 2; n -= 2;
+ if (len < 0x80)
+ ;
+ else if (len == 0x81)
+ { /* One byte length follows. */
+ if (!n)
+ return NULL; /* we expected 1 more bytes with the length. */
+ len = s[0];
+ s++; n--;
+ }
+ else if (len == 0x82)
+ { /* Two byte length follows. */
+ if (n < 2)
+ return NULL; /* we expected 2 more bytes with the length. */
+ len = (s[0] << 8) | s[1];
+ s += 2; n -= 2;
+ }
+ else
+ return NULL; /* APDU limit is 65535, thus it does not make
+ sense to assume longer length fields. */
+
+ if (composite && nestlevel < 100)
+ { /* Dive into this composite DO after checking for too deep
+ nesting. */
+ const unsigned char *tmp_s;
+ size_t tmp_len;
+
+ tmp_s = find_tlv (s, len, tag, &tmp_len, nestlevel+1);
+ if (tmp_s)
+ {
+ *nbytes = tmp_len;
+ return tmp_s;
+ }
+ }
+
+ if (this_tag == tag)
+ {
+ *nbytes = len;
+ return s;
+ }
+ if (len > n)
+ return NULL; /* buffer too short to skip to the next tag. */
+ s += len; n -= len;
+ }
+}
+
+
+/* Get the DO identified by TAG from the card in SLOT and return a
+ buffer with its content in RESULT and NBYTES. The return value is
+ NULL if not found or a pointer which must be used to release the
+ buffer holding value. */
+static void *
+get_one_do (int slot, int tag, unsigned char **result, size_t *nbytes)
+{
+ int rc, i;
+ unsigned char *buffer;
+ size_t buflen;
+ unsigned char *value;
+ size_t valuelen;
+
+ *result = NULL;
+ *nbytes = 0;
+ for (i=0; data_objects[i].tag && data_objects[i].tag != tag; i++)
+ ;
+
+ value = NULL;
+ rc = -1;
+ if (data_objects[i].tag && data_objects[i].get_from)
+ {
+ rc = iso7816_get_data (slot, data_objects[i].get_from,
+ &buffer, &buflen);
+ if (!rc)
+ {
+ const unsigned char *s;
+
+ s = find_tlv (buffer, buflen, tag, &valuelen, 0);
+ if (!s)
+ value = NULL; /* not found */
+ else if (valuelen > buflen - (s - buffer))
+ {
+ log_error ("warning: constructed DO too short\n");
+ value = NULL;
+ xfree (buffer); buffer = NULL;
+ }
+ else
+ value = buffer + (s - buffer);
+ }
+ }
+
+ if (!value) /* Not in a constructed DO, try simple. */
+ {
+ rc = iso7816_get_data (slot, tag, &buffer, &buflen);
+ if (!rc)
+ {
+ value = buffer;
+ valuelen = buflen;
+ }
+ }
+
+ if (!rc)
+ {
+ *nbytes = valuelen;
+ *result = value;
+ return buffer;
+ }
+ return NULL;
+}
+
+
+static void
+dump_all_do (int slot)
+{
+ int rc, i, j;
+ unsigned char *buffer;
+ size_t buflen;
+
+ for (i=0; data_objects[i].tag; i++)
+ {
+ if (data_objects[i].get_from)
+ continue;
+
+ rc = iso7816_get_data (slot, data_objects[i].tag, &buffer, &buflen);
+ if (gpg_err_code (rc) == GPG_ERR_NO_OBJ)
+ ;
+ else if (rc)
+ log_info ("DO `%s' not available: %s\n",
+ data_objects[i].desc, gpg_strerror (rc));
+ else
+ {
+ if (data_objects[i].binary)
+ {
+ log_info ("DO `%s': ", data_objects[i].desc);
+ log_printhex ("", buffer, buflen);
+ }
+ else
+ log_info ("DO `%s': `%.*s'\n",
+ data_objects[i].desc,
+ (int)buflen, buffer); /* FIXME: sanitize */
+
+ if (data_objects[i].constructed)
+ {
+ for (j=0; data_objects[j].tag; j++)
+ {
+ const unsigned char *value;
+ size_t valuelen;
+
+ if (j==i || data_objects[i].tag != data_objects[j].get_from)
+ continue;
+ value = find_tlv (buffer, buflen,
+ data_objects[j].tag, &valuelen, 0);
+ if (!value)
+ ; /* not found */
+ else if (valuelen > buflen - (value - buffer))
+ log_error ("warning: constructed DO too short\n");
+ else
+ {
+ if (data_objects[j].binary)
+ {
+ log_info ("DO `%s': ", data_objects[j].desc);
+ log_printhex ("", value, valuelen);
+ }
+ else
+ log_info ("DO `%s': `%.*s'\n",
+ data_objects[j].desc,
+ (int)valuelen, value); /* FIXME: sanitize */
+ }
+ }
+ }
+ }
+ xfree (buffer); buffer = NULL;
+ }
+}
+
+
+/* Count the number of bits, assuming the A represents an unsigned big
+ integer of length LEN bytes. */
+static unsigned int
+count_bits (const unsigned char *a, size_t len)
+{
+ unsigned int n = len * 8;
+ int i;
+
+ for (; len && !*a; len--, a++, n -=8)
+ ;
+ if (len)
+ {
+ for (i=7; i && !(*a & (1<<i)); i--)
+ n--;
+ }
+ return n;
+}
+
+/* Note, that FPR must be at least 20 bytes. */
+static int
+store_fpr (int slot, int keynumber, u32 timestamp,
+ const unsigned char *m, size_t mlen,
+ const unsigned char *e, size_t elen,
+ unsigned char *fpr, unsigned int card_version)
+{
+ unsigned int n, nbits;
+ unsigned char *buffer, *p;
+ int rc;
+
+ for (; mlen && !*m; mlen--, m++) /* strip leading zeroes */
+ ;
+ for (; elen && !*e; elen--, e++) /* strip leading zeroes */
+ ;
+
+ n = 6 + 2 + mlen + 2 + elen;
+ p = buffer = xtrymalloc (3 + n);
+ if (!buffer)
+ return out_of_core ();
+
+ *p++ = 0x99; /* ctb */
+ *p++ = n >> 8; /* 2 byte length header */
+ *p++ = n;
+ *p++ = 4; /* key packet version */
+ *p++ = timestamp >> 24;
+ *p++ = timestamp >> 16;
+ *p++ = timestamp >> 8;
+ *p++ = timestamp;
+ *p++ = 1; /* RSA */
+ nbits = count_bits (m, mlen);
+ *p++ = nbits >> 8;
+ *p++ = nbits;
+ memcpy (p, m, mlen); p += mlen;
+ nbits = count_bits (e, elen);
+ *p++ = nbits >> 8;
+ *p++ = nbits;
+ memcpy (p, e, elen); p += elen;
+
+ log_printhex ("fprbuf:", buffer, n+3);
+ gcry_md_hash_buffer (GCRY_MD_SHA1, fpr, buffer, n+3);
+
+ xfree (buffer);
+
+ rc = iso7816_put_data (slot, (card_version > 0x0007? 0xC7 : 0xC6)
+ + keynumber, fpr, 20);
+ if (rc)
+ log_error ("failed to store the fingerprint: %s\n",gpg_strerror (rc));
+
+ return rc;
+}
+
+
+static void
+send_fpr_if_not_null (CTRL ctrl, const char *keyword,
+ int number, const unsigned char *fpr)
+{
+ int i;
+ char buf[41];
+ char numbuf[25];
+
+ for (i=0; i < 20 && !fpr[i]; i++)
+ ;
+ if (i==20)
+ return; /* All zero. */
+ for (i=0; i< 20; i++)
+ sprintf (buf+2*i, "%02X", fpr[i]);
+ if (number == -1)
+ *numbuf = 0; /* Don't print the key number */
+ else
+ sprintf (numbuf, "%d", number);
+ send_status_info (ctrl, keyword,
+ numbuf, (size_t)strlen(numbuf),
+ buf, (size_t)strlen (buf), NULL, 0);
+}
+
+static void
+send_key_data (CTRL ctrl, const char *name,
+ const unsigned char *a, size_t alen)
+{
+ char *p, *buf = xmalloc (alen*2+1);
+
+ for (p=buf; alen; a++, alen--, p += 2)
+ sprintf (p, "%02X", *a);
+
+ send_status_info (ctrl, "KEY-DATA",
+ name, (size_t)strlen(name),
+ buf, (size_t)strlen (buf),
+ NULL, 0);
+ xfree (buf);
+}
+
+
+
+static int
+do_learn_status (APP app, CTRL ctrl)
+{
+ void *relptr;
+ unsigned char *value;
+ size_t valuelen;
+ int i;
+
+ relptr = get_one_do (app->slot, 0x005B, &value, &valuelen);
+ if (relptr)
+ {
+ send_status_info (ctrl, "DISP-NAME", value, valuelen, NULL, 0);
+ xfree (relptr);
+ }
+ relptr = get_one_do (app->slot, 0x5F2D, &value, &valuelen);
+ if (relptr)
+ {
+ send_status_info (ctrl, "DISP-LANG", value, valuelen, NULL, 0);
+ xfree (relptr);
+ }
+ relptr = get_one_do (app->slot, 0x5F35, &value, &valuelen);
+ if (relptr)
+ {
+ send_status_info (ctrl, "DISP-SEX", value, valuelen, NULL, 0);
+ xfree (relptr);
+ }
+ relptr = get_one_do (app->slot, 0x5F50, &value, &valuelen);
+ if (relptr)
+ {
+ send_status_info (ctrl, "PUBKEY-URL", value, valuelen, NULL, 0);
+ xfree (relptr);
+ }
+ relptr = get_one_do (app->slot, 0x005E, &value, &valuelen);
+ if (relptr)
+ {
+ send_status_info (ctrl, "LOGIN-DATA", value, valuelen, NULL, 0);
+ xfree (relptr);
+ }
+
+ relptr = get_one_do (app->slot, 0x00C5, &value, &valuelen);
+ if (relptr && valuelen >= 60)
+ {
+ for (i=0; i < 3; i++)
+ send_fpr_if_not_null (ctrl, "KEY-FPR", i+1, value+i*20);
+ }
+ xfree (relptr);
+ relptr = get_one_do (app->slot, 0x00C6, &value, &valuelen);
+ if (relptr && valuelen >= 60)
+ {
+ for (i=0; i < 3; i++)
+ send_fpr_if_not_null (ctrl, "CA-FPR", i+1, value+i*20);
+ }
+ xfree (relptr);
+ relptr = get_one_do (app->slot, 0x00C4, &value, &valuelen);
+ if (relptr)
+ {
+ char numbuf[7*23];
+
+ for (i=0,*numbuf=0; i < valuelen && i < 7; i++)
+ sprintf (numbuf+strlen (numbuf), " %d", value[i]);
+ send_status_info (ctrl, "CHV-STATUS", numbuf, strlen (numbuf), NULL, 0);
+ xfree (relptr);
+ }
+
+ {
+ unsigned long ul = get_sig_counter (app);
+ char numbuf[23];
+
+ sprintf (numbuf, "%lu", ul);
+ send_status_info (ctrl, "SIG-COUNTER", numbuf, strlen (numbuf), NULL, 0);
+ }
+ return 0;
+}
+
+
+/* Handle the SETATTR operation. All arguments are already basically
+ checked. */
+static int
+do_setattr (APP app, const char *name,
+ int (*pincb)(void*, const char *, char **),
+ void *pincb_arg,
+ const unsigned char *value, size_t valuelen)
+{
+ gpg_error_t rc;
+ int idx;
+ static struct {
+ const char *name;
+ int tag;
+ } table[] = {
+ { "DISP-NAME", 0x005B },
+ { "LOGIN-DATA", 0x005E },
+ { "DISP-LANG", 0x5F2D },
+ { "DISP-SEX", 0x5F35 },
+ { "PUBKEY-URL", 0x5F50 },
+ { "CHV-STATUS-1", 0x00C4 },
+ { "CA-FPR-1", 0x00CA },
+ { "CA-FPR-2", 0x00CB },
+ { "CA-FPR-3", 0x00CC },
+ { NULL, 0 }
+ };
+
+
+ for (idx=0; table[idx].name && strcmp (table[idx].name, name); idx++)
+ ;
+ if (!table[idx].name)
+ return gpg_error (GPG_ERR_INV_NAME);
+
+ if (!app->did_chv3)
+ {
+ char *pinvalue;
+
+ rc = pincb (pincb_arg, "Admin PIN (CHV3)",
+ &pinvalue);
+/* pinvalue = xstrdup ("12345678"); */
+/* rc = 0; */
+ if (rc)
+ {
+ log_info ("PIN callback returned error: %s\n", gpg_strerror (rc));
+ return rc;
+ }
+
+ rc = iso7816_verify (app->slot, 0x83, pinvalue, strlen (pinvalue));
+ xfree (pinvalue);
+ if (rc)
+ {
+ log_error ("verify CHV3 failed: %s\n", gpg_strerror (rc));
+ rc = gpg_error (GPG_ERR_GENERAL);
+ return rc;
+ }
+ app->did_chv3 = 1;
+ }
+
+ rc = iso7816_put_data (app->slot, table[idx].tag, value, valuelen);
+ if (rc)
+ log_error ("failed to set `%s': %s\n", table[idx].name, gpg_strerror (rc));
+ /* FIXME: If this fails we should *once* try again after
+ doing a verify command, so that in case of a problem with
+ tracking the verify operation we have a fallback. */
+
+ return rc;
+}
+
+/* Handle the PASSWD command. */
+static int
+do_change_pin (APP app, CTRL ctrl, const char *chvnostr, int reset_mode,
+ int (*pincb)(void*, const char *, char **),
+ void *pincb_arg)
+{
+ int rc = 0;
+ int chvno = atoi (chvnostr);
+ char *pinvalue;
+
+ if (reset_mode && chvno == 3)
+ {
+ rc = gpg_error (GPG_ERR_INV_ID);
+ goto leave;
+ }
+ else if (reset_mode || chvno == 3)
+ {
+ rc = pincb (pincb_arg, "Admin PIN", &pinvalue);
+ if (rc)
+ {
+ log_error ("error getting PIN: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+ rc = iso7816_verify (app->slot, 0x83, pinvalue, strlen (pinvalue));
+ xfree (pinvalue);
+ if (rc)
+ {
+ log_error ("verify CHV3 failed: rc=%s\n", gpg_strerror (rc));
+ goto leave;
+ }
+ }
+ else if (chvno == 1)
+ {
+ rc = pincb (pincb_arg, "Signature PIN", &pinvalue);
+ if (rc)
+ {
+ log_error ("error getting PIN: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+ rc = iso7816_verify (app->slot, 0x81, pinvalue, strlen (pinvalue));
+ xfree (pinvalue);
+ if (rc)
+ {
+ log_error ("verify CHV1 failed: rc=%s\n", gpg_strerror (rc));
+ goto leave;
+ }
+ }
+ else if (chvno == 2)
+ {
+ rc = pincb (pincb_arg, "Decryption PIN", &pinvalue);
+ if (rc)
+ {
+ log_error ("error getting PIN: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+ rc = iso7816_verify (app->slot, 0x82, pinvalue, strlen (pinvalue));
+ xfree (pinvalue);
+ if (rc)
+ {
+ log_error ("verify CHV2 failed: rc=%s\n", gpg_strerror (rc));
+ goto leave;
+ }
+ }
+ else
+ {
+ rc = gpg_error (GPG_ERR_INV_ID);
+ goto leave;
+ }
+
+
+ rc = pincb (pincb_arg, chvno == 1? "New Signature PIN" :
+ chvno == 2? "New Decryption PIN" :
+ chvno == 3? "New Admin PIN" : "?", &pinvalue);
+ if (rc)
+ {
+ log_error ("error getting new PIN: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+
+ if (reset_mode)
+ rc = iso7816_reset_retry_counter (app->slot, 0x80 + chvno,
+ pinvalue, strlen (pinvalue));
+ else
+ rc = iso7816_change_reference_data (app->slot, 0x80 + chvno,
+ NULL, 0,
+ pinvalue, strlen (pinvalue));
+ xfree (pinvalue);
+
+
+ leave:
+ return rc;
+}
+
+
+
+/* Handle the GENKEY command. */
+static int
+do_genkey (APP app, CTRL ctrl, const char *keynostr, unsigned int flags,
+ int (*pincb)(void*, const char *, char **),
+ void *pincb_arg)
+{
+ int rc;
+ int i;
+ char numbuf[30];
+ unsigned char fprbuf[20];
+ const unsigned char *fpr;
+ const unsigned char *keydata, *m, *e;
+ unsigned char *buffer;
+ size_t buflen, keydatalen, n, mlen, elen;
+ time_t created_at;
+ int keyno = atoi (keynostr);
+ int force = (flags & 1);
+ time_t start_at;
+
+ if (keyno < 1 || keyno > 3)
+ return gpg_error (GPG_ERR_INV_ID);
+ keyno--;
+
+ rc = iso7816_get_data (app->slot, 0x006E, &buffer, &buflen);
+ if (rc)
+ {
+ log_error ("error reading application data\n");
+ return gpg_error (GPG_ERR_GENERAL);
+ }
+ fpr = find_tlv (buffer, buflen, 0x00C5, &n, 0);
+ if (!fpr || n != 60)
+ {
+ rc = gpg_error (GPG_ERR_GENERAL);
+ log_error ("error reading fingerprint DO\n");
+ goto leave;
+ }
+ fpr += 20*keyno;
+ for (i=0; i < 20 && !fpr[i]; i++)
+ ;
+ if (i!=20 && !force)
+ {
+ rc = gpg_error (GPG_ERR_EEXIST);
+ log_error ("key already exists\n");
+ goto leave;
+ }
+ else if (i!=20)
+ log_info ("existing key will be replaced\n");
+ else
+ log_info ("generating new key\n");
+
+ {
+ char *pinvalue;
+ rc = pincb (pincb_arg, "Admin PIN", &pinvalue);
+ if (rc)
+ {
+ log_error ("error getting PIN: %s\n", gpg_strerror (rc));
+ return rc;
+ }
+ rc = iso7816_verify (app->slot, 0x83, pinvalue, strlen (pinvalue));
+ xfree (pinvalue);
+ }
+ if (rc)
+ {
+ log_error ("verify CHV3 failed: rc=%s\n", gpg_strerror (rc));
+ goto leave;
+ }
+
+ xfree (buffer); buffer = NULL;
+#if 1
+ log_info ("please wait while key is being generated ...\n");
+ start_at = time (NULL);
+ rc = iso7816_generate_keypair
+#else
+#warning key generation temporary replaced by reading an existing key.
+ rc = iso7816_read_public_key
+#endif
+ (app->slot,
+ keyno == 0? "\xB6" :
+ keyno == 1? "\xB8" : "\xA4",
+ 2,
+ &buffer, &buflen);
+ if (rc)
+ {
+ rc = gpg_error (GPG_ERR_CARD);
+ log_error ("generating key failed\n");
+ goto leave;
+ }
+ log_info ("key generation completed (%d seconds)\n",
+ (int)(time (NULL) - start_at));
+ keydata = find_tlv (buffer, buflen, 0x7F49, &keydatalen, 0);
+ if (!keydata)
+ {
+ rc = gpg_error (GPG_ERR_CARD);
+ log_error ("response does not contain the public key data\n");
+ goto leave;
+ }
+
+ m = find_tlv (keydata, keydatalen, 0x0081, &mlen, 0);
+ if (!m)
+ {
+ rc = gpg_error (GPG_ERR_CARD);
+ log_error ("response does not contain the RSA modulus\n");
+ goto leave;
+ }
+/* log_printhex ("RSA n:", m, mlen); */
+ send_key_data (ctrl, "n", m, mlen);
+
+ e = find_tlv (keydata, keydatalen, 0x0082, &elen, 0);
+ if (!e)
+ {
+ rc = gpg_error (GPG_ERR_CARD);
+ log_error ("response does not contain the RSA public exponent\n");
+ goto leave;
+ }
+/* log_printhex ("RSA e:", e, elen); */
+ send_key_data (ctrl, "e", e, elen);
+
+ created_at = gnupg_get_time ();
+ sprintf (numbuf, "%lu", (unsigned long)created_at);
+ send_status_info (ctrl, "KEY-CREATED-AT",
+ numbuf, (size_t)strlen(numbuf), NULL, 0);
+
+ rc = store_fpr (app->slot, keyno, (u32)created_at,
+ m, mlen, e, elen, fprbuf, app->card_version);
+ if (rc)
+ goto leave;
+ send_fpr_if_not_null (ctrl, "KEY-FPR", -1, fprbuf);
+
+
+ leave:
+ xfree (buffer);
+ return rc;
+}
+
+
+static unsigned long
+get_sig_counter (APP app)
+{
+ void *relptr;
+ unsigned char *value;
+ size_t valuelen;
+ unsigned long ul;
+
+ relptr = get_one_do (app->slot, 0x0093, &value, &valuelen);
+ if (!relptr)
+ return 0;
+ if (valuelen == 3 )
+ ul = (value[0] << 16) | (value[1] << 8) | value[2];
+ else
+ {
+ log_error ("invalid structure of OpenPGP card (DO 0x93)\n");
+ ul = 0;
+ }
+ xfree (relptr);
+ return ul;
+}
+
+static int
+compare_fingerprint (APP app, int keyno, unsigned char *sha1fpr)
+{
+ const unsigned char *fpr;
+ unsigned char *buffer;
+ size_t buflen, n;
+ int rc, i;
+
+ assert (keyno >= 1 && keyno <= 3);
+
+ rc = iso7816_get_data (app->slot, 0x006E, &buffer, &buflen);
+ if (rc)
+ {
+ log_error ("error reading application data\n");
+ return gpg_error (GPG_ERR_GENERAL);
+ }
+ fpr = find_tlv (buffer, buflen, 0x00C5, &n, 0);
+ if (!fpr || n != 60)
+ {
+ xfree (buffer);
+ log_error ("error reading fingerprint DO\n");
+ return gpg_error (GPG_ERR_GENERAL);
+ }
+ fpr += (keyno-1)*20;
+ for (i=0; i < 20; i++)
+ if (sha1fpr[i] != fpr[i])
+ {
+ xfree (buffer);
+ return gpg_error (GPG_ERR_WRONG_SECKEY);
+ }
+ xfree (buffer);
+ return 0;
+}
+
+
+
+/* Compute a digital signature on INDATA which is expected to be the
+ raw message digest. For this application the KEYIDSTR consists of
+ the serialnumber and the fingerprint delimited by a slash.
+
+ Note that this fucntion may return the error code
+ GPG_ERR_WRONG_CARD to indicate that the card currently present does
+ not match the one required for the requested action (e.g. the
+ serial number does not match). */
+static int
+do_sign (APP app, const char *keyidstr, int hashalgo,
+ int (*pincb)(void*, const char *, char **),
+ void *pincb_arg,
+ const void *indata, size_t indatalen,
+ unsigned char **outdata, size_t *outdatalen )
+{
+ static unsigned char sha1_prefix[15] = /* Object ID is 1.3.14.3.2.26 */
+ { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03,
+ 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14 };
+ static unsigned char rmd160_prefix[15] = /* Object ID is 1.3.36.3.2.1 */
+ { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x24, 0x03,
+ 0x02, 0x01, 0x05, 0x00, 0x04, 0x14 };
+ int rc;
+ unsigned char data[35];
+ unsigned char tmp_sn[20]; /* actually 16 but we use it also for the fpr. */
+ const char *s;
+ int n;
+ const char *fpr = NULL;
+ unsigned long sigcount;
+
+ if (!keyidstr || !*keyidstr)
+ return gpg_error (GPG_ERR_INV_VALUE);
+ if (indatalen != 20)
+ return gpg_error (GPG_ERR_INV_VALUE);
+
+ /* Check whether an OpenPGP card of any version has been requested. */
+ if (strlen (keyidstr) < 32 || strncmp (keyidstr, "D27600012401", 12))
+ return gpg_error (GPG_ERR_INV_ID);
+
+ for (s=keyidstr, n=0; hexdigitp (s); s++, n++)
+ ;
+ if (n != 32)
+ return gpg_error (GPG_ERR_INV_ID);
+ else if (!*s)
+ ; /* no fingerprint given: we allow this for now. */
+ else if (*s == '/')
+ fpr = s + 1;
+ else
+ return gpg_error (GPG_ERR_INV_ID);
+
+ for (s=keyidstr, n=0; n < 16; s += 2, n++)
+ tmp_sn[n] = xtoi_2 (s);
+
+ if (app->serialnolen != 16)
+ return gpg_error (GPG_ERR_INV_CARD);
+ if (memcmp (app->serialno, tmp_sn, 16))
+ return gpg_error (GPG_ERR_WRONG_CARD);
+
+ /* If a fingerprint has been specified check it against the one on
+ the card. This is allows for a meaningful error message in case
+ the key on the card has been replaced but the shadow information
+ known to gpg was not updated. If there is no fingerprint, gpg
+ will detect a bogus signature anyway due to the
+ verify-after-signing feature. */
+ if (fpr)
+ {
+ for (s=fpr, n=0; hexdigitp (s); s++, n++)
+ ;
+ if (n != 40)
+ return gpg_error (GPG_ERR_INV_ID);
+ else if (!*s)
+ ; /* okay */
+ else
+ return gpg_error (GPG_ERR_INV_ID);
+
+ for (s=fpr, n=0; n < 20; s += 2, n++)
+ tmp_sn[n] = xtoi_2 (s);
+ rc = compare_fingerprint (app, 1, tmp_sn);
+ if (rc)
+ return rc;
+ }
+
+ if (hashalgo == GCRY_MD_SHA1)
+ memcpy (data, sha1_prefix, 15);
+ else if (hashalgo == GCRY_MD_RMD160)
+ memcpy (data, rmd160_prefix, 15);
+ else
+ return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
+ memcpy (data+15, indata, indatalen);
+
+ sigcount = get_sig_counter (app);
+ log_info ("signatures created so far: %lu\n", sigcount);
+
+ /* FIXME: Check whether we are really required to enter the PIN for
+ each signature. There is a DO for this. */
+ if (!app->did_chv1 || 1)
+ {
+ char *pinvalue;
+
+ {
+ char *prompt;
+ if (asprintf (&prompt, "Signature PIN [sigs done: %lu]", sigcount) < 0)
+ return gpg_error_from_errno (errno);
+ rc = pincb (pincb_arg, prompt, &pinvalue);
+ free (prompt);
+ }
+/* pinvalue = xstrdup ("123456"); */
+/* rc = 0; */
+ if (rc)
+ {
+ log_info ("PIN callback returned error: %s\n", gpg_strerror (rc));
+ return rc;
+ }
+
+ rc = iso7816_verify (app->slot, 0x81, pinvalue, strlen (pinvalue));
+ xfree (pinvalue);
+ if (rc)
+ {
+ log_error ("verify CHV1 failed\n");
+ rc = gpg_error (GPG_ERR_GENERAL);
+ return rc;
+ }
+ app->did_chv1 = 1;
+ }
+
+ rc = iso7816_compute_ds (app->slot, data, 35, outdata, outdatalen);
+ return rc;
+}
+
+/* Compute a digital signature using the INTERNAL AUTHENTICATE command
+ on INDATA which is expected to be the raw message digest. For this
+ application the KEYIDSTR consists of the serialnumber and the
+ fingerprint delimited by a slash.
+
+ Note that this fucntion may return the error code
+ GPG_ERR_WRONG_CARD to indicate that the card currently present does
+ not match the one required for the requested action (e.g. the
+ serial number does not match). */
+static int
+do_auth (APP app, const char *keyidstr,
+ int (*pincb)(void*, const char *, char **),
+ void *pincb_arg,
+ const void *indata, size_t indatalen,
+ unsigned char **outdata, size_t *outdatalen )
+{
+ int rc;
+ unsigned char tmp_sn[20]; /* actually 16 but we use it also for the fpr. */
+ const char *s;
+ int n;
+ const char *fpr = NULL;
+
+ if (!keyidstr || !*keyidstr)
+ return gpg_error (GPG_ERR_INV_VALUE);
+ if (indatalen > 50) /* For a 1024 bit key. */
+ return gpg_error (GPG_ERR_INV_VALUE);
+
+ /* Check whether an OpenPGP card of any version has been requested. */
+ if (strlen (keyidstr) < 32 || strncmp (keyidstr, "D27600012401", 12))
+ return gpg_error (GPG_ERR_INV_ID);
+
+ for (s=keyidstr, n=0; hexdigitp (s); s++, n++)
+ ;
+ if (n != 32)
+ return gpg_error (GPG_ERR_INV_ID);
+ else if (!*s)
+ ; /* no fingerprint given: we allow this for now. */
+ else if (*s == '/')
+ fpr = s + 1;
+ else
+ return gpg_error (GPG_ERR_INV_ID);
+
+ for (s=keyidstr, n=0; n < 16; s += 2, n++)
+ tmp_sn[n] = xtoi_2 (s);
+
+ if (app->serialnolen != 16)
+ return gpg_error (GPG_ERR_INV_CARD);
+ if (memcmp (app->serialno, tmp_sn, 16))
+ return gpg_error (GPG_ERR_WRONG_CARD);
+
+ /* If a fingerprint has been specified check it against the one on
+ the card. This is allows for a meaningful error message in case
+ the key on the card has been replaced but the shadow information
+ known to gpg was not updated. If there is no fingerprint, gpg
+ will detect a bogus signature anyway due to the
+ verify-after-signing feature. */
+ if (fpr)
+ {
+ for (s=fpr, n=0; hexdigitp (s); s++, n++)
+ ;
+ if (n != 40)
+ return gpg_error (GPG_ERR_INV_ID);
+ else if (!*s)
+ ; /* okay */
+ else
+ return gpg_error (GPG_ERR_INV_ID);
+
+ for (s=fpr, n=0; n < 20; s += 2, n++)
+ tmp_sn[n] = xtoi_2 (s);
+ rc = compare_fingerprint (app, 3, tmp_sn);
+ if (rc)
+ return rc;
+ }
+
+ if (!app->did_chv2)
+ {
+ char *pinvalue;
+
+ rc = pincb (pincb_arg, "Authentication/Decryption PIN", &pinvalue);
+ if (rc)
+ {
+ log_info ("PIN callback returned error: %s\n", gpg_strerror (rc));
+ return rc;
+ }
+
+ rc = iso7816_verify (app->slot, 0x82, pinvalue, strlen (pinvalue));
+ xfree (pinvalue);
+ if (rc)
+ {
+ log_error ("verify CHV2 failed\n");
+ rc = gpg_error (GPG_ERR_GENERAL);
+ return rc;
+ }
+ app->did_chv2 = 1;
+ }
+
+ rc = iso7816_internal_authenticate (app->slot, indata, indatalen,
+ outdata, outdatalen);
+ return rc;
+}
+
+
+static int
+do_decipher (APP app, const char *keyidstr,
+ int (pincb)(void*, const char *, char **),
+ void *pincb_arg,
+ const void *indata, size_t indatalen,
+ unsigned char **outdata, size_t *outdatalen )
+{
+ int rc;
+ unsigned char tmp_sn[20]; /* actually 16 but we use it also for the fpr. */
+ const char *s;
+ int n;
+ const char *fpr = NULL;
+
+ if (!keyidstr || !*keyidstr || !indatalen)
+ return gpg_error (GPG_ERR_INV_VALUE);
+
+ /* Check whether an OpenPGP card of any version has been requested. */
+ if (strlen (keyidstr) < 32 || strncmp (keyidstr, "D27600012401", 12))
+ return gpg_error (GPG_ERR_INV_ID);
+
+ for (s=keyidstr, n=0; hexdigitp (s); s++, n++)
+ ;
+ if (n != 32)
+ return gpg_error (GPG_ERR_INV_ID);
+ else if (!*s)
+ ; /* no fingerprint given: we allow this for now. */
+ else if (*s == '/')
+ fpr = s + 1;
+ else
+ return gpg_error (GPG_ERR_INV_ID);
+
+ for (s=keyidstr, n=0; n < 16; s += 2, n++)
+ tmp_sn[n] = xtoi_2 (s);
+
+ if (app->serialnolen != 16)
+ return gpg_error (GPG_ERR_INV_CARD);
+ if (memcmp (app->serialno, tmp_sn, 16))
+ return gpg_error (GPG_ERR_WRONG_CARD);
+
+ /* If a fingerprint has been specified check it against the one on
+ the card. This is allows for a meaningful error message in case
+ the key on the card has been replaced but the shadow information
+ known to gpg was not updated. If there is no fingerprint, the
+ decryption will won't produce the right plaintext anyway. */
+ if (fpr)
+ {
+ for (s=fpr, n=0; hexdigitp (s); s++, n++)
+ ;
+ if (n != 40)
+ return gpg_error (GPG_ERR_INV_ID);
+ else if (!*s)
+ ; /* okay */
+ else
+ return gpg_error (GPG_ERR_INV_ID);
+
+ for (s=fpr, n=0; n < 20; s += 2, n++)
+ tmp_sn[n] = xtoi_2 (s);
+ rc = compare_fingerprint (app, 2, tmp_sn);
+ if (rc)
+ return rc;
+ }
+
+ if (!app->did_chv2)
+ {
+ char *pinvalue;
+
+ rc = pincb (pincb_arg, "Decryption PIN", &pinvalue);
+/* pinvalue = xstrdup ("123456"); */
+/* rc = 0; */
+ if (rc)
+ {
+ log_info ("PIN callback returned error: %s\n", gpg_strerror (rc));
+ return rc;
+ }
+
+ rc = iso7816_verify (app->slot, 0x82, pinvalue, strlen (pinvalue));
+ xfree (pinvalue);
+ if (rc)
+ {
+ log_error ("verify CHV2 failed\n");
+ rc = gpg_error (GPG_ERR_GENERAL);
+ return rc;
+ }
+ app->did_chv2 = 1;
+ }
+
+ rc = iso7816_decipher (app->slot, indata, indatalen, outdata, outdatalen);
+ return rc;
+}
+
+
+
+
+/* Select the OpenPGP application on the card in SLOT. This function
+ must be used before any other OpenPGP application functions. */
+int
+app_select_openpgp (APP app, unsigned char **sn, size_t *snlen)
+{
+ static char const aid[] = { 0xD2, 0x76, 0x00, 0x01, 0x24, 0x01 };
+ int slot = app->slot;
+ int rc;
+ unsigned char *buffer;
+ size_t buflen;
+
+ rc = iso7816_select_application (slot, aid, sizeof aid);
+ if (!rc)
+ {
+ rc = iso7816_get_data (slot, 0x004F, &buffer, &buflen);
+ if (rc)
+ goto leave;
+ if (opt.verbose)
+ {
+ log_info ("got AID: ");
+ log_printhex ("", buffer, buflen);
+ }
+
+ if (sn)
+ {
+ *sn = buffer;
+ *snlen = buflen;
+ app->card_version = buffer[6] << 8;
+ app->card_version |= buffer[7];
+ }
+ else
+ xfree (buffer);
+
+ if (opt.verbose > 1)
+ dump_all_do (slot);
+
+ app->fnc.learn_status = do_learn_status;
+ app->fnc.setattr = do_setattr;
+ app->fnc.genkey = do_genkey;
+ app->fnc.sign = do_sign;
+ app->fnc.auth = do_auth;
+ app->fnc.decipher = do_decipher;
+ app->fnc.change_pin = do_change_pin;
+ }
+
+leave:
+ return rc;
+}
+
+
+
+/* This function is a hack to retrieve essential information about the
+ card to be displayed by simple tools. It mostly resembles what the
+ LEARN command returns. All parameters return allocated strings or
+ buffers or NULL if the data object is not available. All returned
+ values are sanitized. */
+int
+app_openpgp_cardinfo (APP app,
+ char **serialno,
+ char **disp_name,
+ char **pubkey_url,
+ unsigned char **fpr1,
+ unsigned char **fpr2,
+ unsigned char **fpr3)
+{
+ int rc;
+ void *relptr;
+ unsigned char *value;
+ size_t valuelen;
+
+ if (serialno)
+ {
+ time_t dummy;
+
+ *serialno = NULL;
+ rc = app_get_serial_and_stamp (app, serialno, &dummy);
+ if (rc)
+ {
+ log_error ("error getting serial number: %s\n", gpg_strerror (rc));
+ return rc;
+ }
+ }
+
+ if (disp_name)
+ {
+ *disp_name = NULL;
+ relptr = get_one_do (app->slot, 0x005B, &value, &valuelen);
+ if (relptr)
+ {
+ *disp_name = make_printable_string (value, valuelen, 0);
+ xfree (relptr);
+ }
+ }
+
+ if (pubkey_url)
+ {
+ *pubkey_url = NULL;
+ relptr = get_one_do (app->slot, 0x5F50, &value, &valuelen);
+ if (relptr)
+ {
+ *pubkey_url = make_printable_string (value, valuelen, 0);
+ xfree (relptr);
+ }
+ }
+
+ if (fpr1)
+ *fpr1 = NULL;
+ if (fpr2)
+ *fpr2 = NULL;
+ if (fpr3)
+ *fpr3 = NULL;
+ relptr = get_one_do (app->slot, 0x00C5, &value, &valuelen);
+ if (relptr && valuelen >= 60)
+ {
+ if (fpr1)
+ {
+ *fpr1 = xmalloc (20);
+ memcpy (*fpr1, value + 0, 20);
+ }
+ if (fpr2)
+ {
+ *fpr2 = xmalloc (20);
+ memcpy (*fpr2, value + 20, 20);
+ }
+ if (fpr3)
+ {
+ *fpr3 = xmalloc (20);
+ memcpy (*fpr3, value + 40, 20);
+ }
+ }
+ xfree (relptr);
+
+ return 0;
+}
+
+
+
+/* This function is currently only used by the sc-copykeys program to
+ store a key on the smartcard. APP ist the application handle,
+ KEYNO is the number of the key and PINCB, PINCB_ARG are used to ask
+ for the SO PIN. TEMPLATE and TEMPLATE_LEN describe a buffer with
+ the key template to store. CREATED_AT is the timestamp used to
+ create the fingerprint. M, MLEN is the RSA modulus and E, ELEN the
+ RSA public exponent. This function silently overwrites an existing
+ key.*/
+int
+app_openpgp_storekey (APP app, int keyno,
+ unsigned char *template, size_t template_len,
+ time_t created_at,
+ const unsigned char *m, size_t mlen,
+ const unsigned char *e, size_t elen,
+ int (*pincb)(void*, const char *, char **),
+ void *pincb_arg)
+{
+ int rc;
+ unsigned char fprbuf[20];
+
+ if (keyno < 1 || keyno > 3)
+ return gpg_error (GPG_ERR_INV_ID);
+ keyno--;
+
+ {
+ char *pinvalue;
+ rc = pincb (pincb_arg, "Admin PIN", &pinvalue);
+ if (rc)
+ {
+ log_error ("error getting PIN: %s\n", gpg_strerror (rc));
+ return rc;
+ }
+ rc = iso7816_verify (app->slot, 0x83, pinvalue, strlen (pinvalue));
+ xfree (pinvalue);
+ }
+ if (rc)
+ {
+ log_error ("verify CHV3 failed: rc=%s\n", gpg_strerror (rc));
+ goto leave;
+ }
+
+ rc = iso7816_put_data (app->slot,
+ (app->card_version > 0x0007? 0xE0 : 0xE9) + keyno,
+ template, template_len);
+ if (rc)
+ {
+ log_error ("failed to store the key: rc=%s\n", gpg_strerror (rc));
+ rc = gpg_error (GPG_ERR_CARD);
+ goto leave;
+ }
+
+/* log_printhex ("RSA n:", m, mlen); */
+/* log_printhex ("RSA e:", e, elen); */
+
+ rc = store_fpr (app->slot, keyno, (u32)created_at,
+ m, mlen, e, elen, fprbuf, app->card_version);
+
+ leave:
+ return rc;
+}
+
+
+/* Utility function for external tools: Read the public RSA key at
+ KEYNO and return modulus and exponent in (M,MLEN) and (E,ELEN). */
+int
+app_openpgp_readkey (APP app, int keyno, unsigned char **m, size_t *mlen,
+ unsigned char **e, size_t *elen)
+{
+ int rc;
+ const unsigned char *keydata, *a;
+ unsigned char *buffer;
+ size_t buflen, keydatalen, alen;
+
+ *m = NULL;
+ *e = NULL;
+
+ if (keyno < 1 || keyno > 3)
+ return gpg_error (GPG_ERR_INV_ID);
+ keyno--;
+
+ rc = iso7816_read_public_key(app->slot,
+ keyno == 0? "\xB6" :
+ keyno == 1? "\xB8" : "\xA4",
+ 2,
+ &buffer, &buflen);
+ if (rc)
+ {
+ rc = gpg_error (GPG_ERR_CARD);
+ log_error ("reading key failed\n");
+ goto leave;
+ }
+
+ keydata = find_tlv (buffer, buflen, 0x7F49, &keydatalen, 0);
+ if (!keydata)
+ {
+ log_error ("response does not contain the public key data\n");
+ rc = gpg_error (GPG_ERR_CARD);
+ goto leave;
+ }
+
+ a = find_tlv (keydata, keydatalen, 0x0081, &alen, 0);
+ if (!a)
+ {
+ log_error ("response does not contain the RSA modulus\n");
+ rc = gpg_error (GPG_ERR_CARD);
+ goto leave;
+ }
+ *mlen = alen;
+ *m = xmalloc (alen);
+ memcpy (*m, a, alen);
+
+ a = find_tlv (keydata, keydatalen, 0x0082, &alen, 0);
+ if (!e)
+ {
+ log_error ("response does not contain the RSA public exponent\n");
+ rc = gpg_error (GPG_ERR_CARD);
+ goto leave;
+ }
+ *elen = alen;
+ *e = xmalloc (alen);
+ memcpy (*e, a, alen);
+
+ leave:
+ xfree (buffer);
+ if (rc)
+ {
+ xfree (*m); *m = NULL;
+ xfree (*e); *e = NULL;
+ }
+ return rc;
+}
+
+#endif /*ENABLE_CARD_SUPPORT*/
diff --git a/g10/card-util.c b/g10/card-util.c
new file mode 100644
index 000000000..901ce922c
--- /dev/null
+++ b/g10/card-util.c
@@ -0,0 +1,720 @@
+/* card-util.c - Utility functions for the OpenPGP card.
+ * Copyright (C) 2003 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#ifdef ENABLE_CARD_SUPPORT
+/*
+ Note, that most card related code has been taken from 1.9.x branch
+ and is maintained over there if at all possible. Thus, if you make
+ changes here, please check that a similar change has been commited
+ to the 1.9.x branch.
+*/
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+
+#include "util.h"
+#include "i18n.h"
+#include "ttyio.h"
+#include "status.h"
+#include "options.h"
+#include "main.h"
+
+#include "cardglue.h"
+
+#define CONTROL_D ('D' - 'A' + 1)
+
+
+/* Change the PIN of a an OpenPGP card. This is an interactive
+ function. */
+void
+change_pin (int chvno)
+{
+ struct agent_card_info_s info;
+ int rc;
+ int reset_mode = 0;
+
+ rc = agent_learn (&info);
+ if (rc)
+ {
+ log_error (_("OpenPGP card not available: %s\n"),
+ gpg_strerror (rc));
+ return;
+ }
+
+ log_info (_("OpenPGP card no. %s detected\n"),
+ info.serialno? info.serialno : "[none]");
+
+ agent_release_card_info (&info);
+
+ if (opt.batch)
+ {
+ log_error (_("sorry, can't do this in batch mode\n"));
+ return;
+ }
+
+ for (;;)
+ {
+ char *answer;
+
+ tty_printf ("\n");
+ tty_printf ("1 - change signature PIN\n"
+ "2 - change decryption and authentication PIN\n"
+ "3 - change Admin's PIN\n"
+ "R - toggle reset retry counter mode\n"
+ "Q - quit\n");
+ tty_printf ("\n");
+ if (reset_mode)
+ {
+ tty_printf ("Reset Retry Counter mode active\n");
+ tty_printf ("\n");
+ }
+
+ answer = cpr_get("cardutil.change_pin.menu",_("Your selection? "));
+ cpr_kill_prompt();
+ if (strlen (answer) != 1)
+ continue;
+
+ rc = 0;
+ if (reset_mode && *answer == '3')
+ {
+ tty_printf ("Sorry, reset of the Admin PIN's retry counter "
+ "is not possible.\n");
+ }
+ else if (*answer == '1' || *answer == '2' || *answer == '3')
+ {
+ rc = agent_scd_change_pin (*answer - '0' + (reset_mode?100:0));
+ if (rc)
+ tty_printf ("Error changing/resetting the PIN: %s\n",
+ gpg_strerror (rc));
+ else
+ tty_printf ("New PIN successfully set.\n");
+ }
+ else if (*answer == 'r' || *answer == 'R')
+ {
+ reset_mode = !reset_mode;
+ }
+ else if (*answer == 'q' || *answer == 'Q')
+ {
+ break;
+ }
+ }
+
+}
+
+static const char *
+get_manufacturer (unsigned int no)
+{
+ /* Note: Make sure that there is no colon or linefeed in the string. */
+ switch (no)
+ {
+ case 0:
+ case 0xffff: return "test card";
+ case 0x0001: return "PPC Card Systems";
+ default: return "unknown";
+ }
+}
+
+
+static void
+print_sha1_fpr (FILE *fp, const unsigned char *fpr)
+{
+ int i;
+
+ if (fpr)
+ {
+ for (i=0; i < 20 ; 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");
+}
+
+
+static void
+print_sha1_fpr_colon (FILE *fp, const unsigned char *fpr)
+{
+ int i;
+
+ if (fpr)
+ {
+ for (i=0; i < 20 ; i++, fpr++)
+ fprintf (fp, "%02X", *fpr);
+ }
+ putc (':', fp);
+}
+
+
+static void
+print_name (FILE *fp, const char *text, const char *name)
+{
+ tty_fprintf (fp, 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_string2 (fp, name, strlen (name), '\n');
+ else
+ tty_print_utf8_string2 (name, strlen (name), '\n');
+ }
+ else
+ tty_fprintf (fp, _("[not set]"));
+ tty_fprintf (fp, "\n");
+}
+
+static void
+print_isoname (FILE *fp, const char *text, const char *tag, const char *name)
+{
+ if (opt.with_colons)
+ fprintf (fp, "%s:", tag);
+ else
+ tty_fprintf (fp, text);
+
+ if (name && *name)
+ {
+ char *p, *given, *buf = xstrdup (name);
+
+ given = strstr (buf, "<<");
+ for (p=buf; *p; p++)
+ if (*p == '<')
+ *p = ' ';
+ if (given && given[2])
+ {
+ *given = 0;
+ given += 2;
+ if (opt.with_colons)
+ print_string (fp, given, strlen (given), ':');
+ else if (fp)
+ print_utf8_string2 (fp, given, strlen (given), '\n');
+ else
+ tty_print_utf8_string2 (given, strlen (given), '\n');
+
+ if (opt.with_colons)
+ putc (':', fp);
+ else if (*buf)
+ tty_fprintf (fp, " ");
+ }
+
+ if (opt.with_colons)
+ print_string (fp, buf, strlen (buf), ':');
+ else if (fp)
+ print_utf8_string2 (fp, buf, strlen (buf), '\n');
+ else
+ tty_print_utf8_string2 (buf, strlen (buf), '\n');
+ xfree (buf);
+ }
+ else
+ {
+ if (opt.with_colons)
+ putc (':', fp);
+ else
+ tty_fprintf (fp, _("[not set]"));
+ }
+
+ if (opt.with_colons)
+ fputs (":\n", fp);
+ else
+ tty_fprintf (fp, "\n");
+}
+
+
+/* Print all available information about the current card. */
+void
+card_status (FILE *fp)
+{
+ struct agent_card_info_s info;
+ PKT_public_key *pk = xcalloc (1, sizeof *pk);
+ int rc;
+ unsigned int uval;
+
+ rc = agent_learn (&info);
+ if (rc)
+ {
+ if (opt.with_colons)
+ fputs ("AID:::\n", fp);
+ log_error (_("OpenPGP card not available: %s\n"),
+ gpg_strerror (rc));
+ xfree (pk);
+ return;
+ }
+
+ if (opt.with_colons)
+ fprintf (fp, "AID:%s:", info.serialno? info.serialno : "");
+ else
+ tty_fprintf (fp, "Application ID ...: %s\n",
+ info.serialno? info.serialno : "[none]");
+ if (!info.serialno || strncmp (info.serialno, "D27600012401", 12)
+ || strlen (info.serialno) != 32 )
+ {
+ if (opt.with_colons)
+ fputs ("unknown:\n", fp);
+ log_info ("not an OpenPGP card\n");
+ agent_release_card_info (&info);
+ xfree (pk);
+ return;
+ }
+
+ if (opt.with_colons)
+ fputs ("openpgp-card:\n", fp);
+
+
+ if (opt.with_colons)
+ {
+ fprintf (fp, "version:%.4s:\n", info.serialno+12);
+ uval = xtoi_2(info.serialno+16)*256 + xtoi_2 (info.serialno+18);
+ fprintf (fp, "vendor:%04x:%s:\n", uval, get_manufacturer (uval));
+ fprintf (fp, "serial:%.8s:\n", info.serialno+20);
+
+ print_isoname (fp, "Name of cardholder: ", "name", info.disp_name);
+
+ fputs ("lang:", fp);
+ if (info.disp_lang)
+ print_string (fp, info.disp_lang, strlen (info.disp_lang), ':');
+ fputs (":\n", fp);
+
+ fprintf (fp, "sex:%c:\n", (info.disp_sex == 1? 'm':
+ info.disp_sex == 2? 'f' : 'u'));
+
+ fputs ("url:", fp);
+ if (info.pubkey_url)
+ print_string (fp, info.pubkey_url, strlen (info.pubkey_url), ':');
+ fputs (":\n", fp);
+
+ fputs ("login:", fp);
+ if (info.login_data)
+ print_string (fp, info.login_data, strlen (info.login_data), ':');
+ fputs (":\n", fp);
+
+ fprintf (fp, "forcepin:%d:::\n", !info.chv1_cached);
+ fprintf (fp, "maxpinlen:%d:%d:%d:\n",
+ info.chvmaxlen[0], info.chvmaxlen[1], info.chvmaxlen[2]);
+ fprintf (fp, "pinretry:%d:%d:%d:\n",
+ info.chvretry[0], info.chvretry[1], info.chvretry[2]);
+ fprintf (fp, "sigcount:%lu:::\n", info.sig_counter);
+
+ fputs ("fpr:", fp);
+ print_sha1_fpr_colon (fp, info.fpr1valid? info.fpr1:NULL);
+ print_sha1_fpr_colon (fp, info.fpr2valid? info.fpr2:NULL);
+ print_sha1_fpr_colon (fp, info.fpr3valid? info.fpr3:NULL);
+ putc ('\n', fp);
+
+ }
+ else
+ {
+ tty_fprintf (fp, "Version ..........: %.1s%c.%.1s%c\n",
+ info.serialno[12] == '0'?"":info.serialno+12,
+ info.serialno[13],
+ info.serialno[14] == '0'?"":info.serialno+14,
+ info.serialno[15]);
+ tty_fprintf (fp, "Manufacturer .....: %s\n",
+ get_manufacturer (xtoi_2(info.serialno+16)*256
+ + xtoi_2 (info.serialno+18)));
+ tty_fprintf (fp, "Serial number ....: %.8s\n", info.serialno+20);
+
+ print_isoname (fp, "Name of cardholder: ", "name", info.disp_name);
+ print_name (fp, "Language prefs ...: ", info.disp_lang);
+ tty_fprintf (fp, "Sex ..............: %s\n",
+ info.disp_sex == 1? _("male"):
+ info.disp_sex == 2? _("female") : _("unspecified"));
+ print_name (fp, "URL of public key : ", info.pubkey_url);
+ print_name (fp, "Login data .......: ", info.login_data);
+ tty_fprintf (fp, "Signature PIN ....: %s\n",
+ info.chv1_cached? _("cached"): _("not cached"));
+ tty_fprintf (fp, "Max. PIN lengths .: %d %d %d\n",
+ info.chvmaxlen[0], info.chvmaxlen[1], info.chvmaxlen[2]);
+ tty_fprintf (fp, "PIN retry counter : %d %d %d\n",
+ info.chvretry[0], info.chvretry[1], info.chvretry[2]);
+ tty_fprintf (fp, "Signature counter : %lu\n", info.sig_counter);
+ tty_fprintf (fp, "Signature key ....:");
+ print_sha1_fpr (fp, info.fpr1valid? info.fpr1:NULL);
+ tty_fprintf (fp, "Encryption key....:");
+ print_sha1_fpr (fp, info.fpr2valid? info.fpr2:NULL);
+ tty_fprintf (fp, "Authentication key:");
+ print_sha1_fpr (fp, info.fpr3valid? info.fpr3:NULL);
+/* tty_fprintf (fp, "General key info..: "); */
+/* if (info.fpr1valid && !get_pubkey_byfprint (pk, info.fpr1, 20)) */
+/* print_pubkey_info (fp, pk); */
+/* else */
+/* tty_fprintf (fp, "[none]\n"); */
+ }
+
+ free_public_key (pk);
+ agent_release_card_info (&info);
+}
+
+
+static char *
+get_one_name (const char *prompt1, const char *prompt2)
+{
+ char *name;
+ int i;
+
+ for (;;)
+ {
+ name = cpr_get (prompt1, prompt2);
+ if (!name)
+ return NULL;
+ trim_spaces (name);
+ cpr_kill_prompt ();
+ 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);
+ }
+}
+
+
+
+static int
+change_name (void)
+{
+ char *surname = NULL, *givenname = NULL;
+ char *isoname, *p;
+ int rc;
+
+ surname = get_one_name ("keygen.smartcard.surname",
+ _("Cardholder's surname: "));
+ givenname = get_one_name ("keygen.smartcard.givenname",
+ _("Cardholder's given name: "));
+ if (!surname || !givenname || (!*surname && !*givenname))
+ {
+ xfree (surname);
+ xfree (givenname);
+ return -1; /*canceled*/
+ }
+
+ isoname = xmalloc ( strlen (surname) + 2 + strlen (givenname) + 1);
+ strcpy (stpcpy (stpcpy (isoname, surname), "<<"), givenname);
+ xfree (surname);
+ xfree (givenname);
+ for (p=isoname; *p; p++)
+ if (*p == ' ')
+ *p = '<';
+
+ log_debug ("setting Name to `%s'\n", isoname);
+ rc = agent_scd_setattr ("DISP-NAME", isoname, strlen (isoname) );
+ if (rc)
+ log_error ("error setting Name: %s\n", gpg_strerror (rc));
+
+ xfree (isoname);
+ return rc;
+}
+
+
+static int
+change_url (void)
+{
+ char *url;
+ int rc;
+
+ url = cpr_get ("cardedit.change_url", _("URL to retrieve public key: "));
+ if (!url)
+ return -1;
+ trim_spaces (url);
+ cpr_kill_prompt ();
+
+ rc = agent_scd_setattr ("PUBKEY-URL", url, strlen (url) );
+ if (rc)
+ log_error ("error setting URL: %s\n", gpg_strerror (rc));
+ xfree (url);
+ return rc;
+}
+
+static int
+change_login (void)
+{
+ char *data;
+ int rc;
+
+ data = cpr_get ("cardedit.change_login",
+ _("Login data (account name): "));
+ if (!data)
+ return -1;
+ trim_spaces (data);
+ cpr_kill_prompt ();
+
+ rc = agent_scd_setattr ("LOGIN-DATA", data, strlen (data) );
+ if (rc)
+ log_error ("error setting login data: %s\n", gpg_strerror (rc));
+ xfree (data);
+ return rc;
+}
+
+static int
+change_lang (void)
+{
+ char *data, *p;
+ int rc;
+
+ data = cpr_get ("cardedit.change_lang",
+ _("Language preferences: "));
+ if (!data)
+ return -1;
+ trim_spaces (data);
+ cpr_kill_prompt ();
+
+ if (strlen (data) > 8 || (strlen (data) & 1))
+ {
+ tty_printf (_("Error: invalid length of preference string.\n"));
+ xfree (data);
+ return -1;
+ }
+
+ for (p=data; *p && *p >= 'a' && *p <= 'z'; p++)
+ ;
+ if (*p)
+ {
+ tty_printf (_("Error: invalid characters in preference string.\n"));
+ xfree (data);
+ return -1;
+ }
+
+ rc = agent_scd_setattr ("DISP-LANG", data, strlen (data) );
+ if (rc)
+ log_error ("error setting lang: %s\n", gpg_strerror (rc));
+ xfree (data);
+ return rc;
+}
+
+
+static int
+change_sex (void)
+{
+ char *data;
+ const char *str;
+ int rc;
+
+ data = cpr_get ("cardedit.change_sex",
+ _("Sex ((M)ale, (F)emale or space): "));
+ if (!data)
+ return -1;
+ trim_spaces (data);
+ cpr_kill_prompt ();
+
+ 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);
+ return -1;
+ }
+
+ rc = agent_scd_setattr ("DISP-SEX", str, 1 );
+ if (rc)
+ log_error ("error setting sex: %s\n", gpg_strerror (rc));
+ xfree (data);
+ return rc;
+}
+
+
+/* Menu to edit all user changeable values on an OpenPGP card. Only
+ Key creation is not handled here. */
+void
+card_edit (STRLIST commands)
+{
+ enum cmdids {
+ cmdNOP = 0,
+ cmdQUIT, cmdHELP, cmdLIST, cmdDEBUG,
+ cmdNAME, cmdURL, cmdLOGIN, cmdLANG, cmdSEX,
+
+ cmdINVCMD
+ };
+
+ static struct {
+ const char *name;
+ enum cmdids id;
+ const char *desc;
+ } cmds[] = {
+ { N_("quit") , cmdQUIT , N_("quit this menu") },
+ { N_("q") , cmdQUIT , NULL },
+ { N_("help") , cmdHELP , N_("show this help") },
+ { "?" , cmdHELP , NULL },
+ { N_("list") , cmdLIST , N_("list all available data") },
+ { N_("l") , cmdLIST , NULL },
+ { N_("debug") , cmdDEBUG , NULL },
+ { N_("name") , cmdNAME , N_("change card holder's name") },
+ { N_("url") , cmdURL , N_("change URL to retrieve key") },
+ { N_("login") , cmdLOGIN , N_("change the login name") },
+ { N_("lang") , cmdLANG , N_("change the language preferences") },
+ { N_("sex") , cmdSEX , N_("change card holder's sex") },
+ { NULL, cmdINVCMD }
+ };
+
+ enum cmdids cmd = cmdNOP;
+ int have_commands = !!commands;
+ int redisplay = 1;
+ char *answer = NULL;
+
+ if (opt.command_fd != -1)
+ ;
+ else if (opt.batch && !have_commands)
+ {
+ log_error(_("can't do that in batchmode\n"));
+ goto leave;
+ }
+
+ for (;;)
+ {
+ int arg_number;
+ const char *arg_string = "";
+ char *p;
+ int i;
+
+ tty_printf("\n");
+ if (redisplay )
+ {
+ if (opt.with_colons)
+ {
+ card_status (stdout);
+ fflush (stdout);
+ }
+ else
+ {
+ card_status (NULL);
+ tty_printf("\n");
+ }
+ redisplay = 0;
+ }
+
+ do
+ {
+ xfree (answer);
+ if (have_commands)
+ {
+ if (commands)
+ {
+ answer = xstrdup (commands->d);
+ commands = commands->next;
+ }
+ else if (opt.batch)
+ {
+ answer = xstrdup ("quit");
+ }
+ else
+ have_commands = 0;
+ }
+
+ if (!have_commands)
+ {
+ answer = cpr_get_no_help("cardedit.prompt", _("Command> "));
+ cpr_kill_prompt();
+ }
+ trim_spaces(answer);
+ }
+ while( *answer == '#' );
+
+ arg_number = 0; /* Yes, here is the init which egcc complains about */
+ if (!*answer)
+ cmd = cmdLIST; /* Default to the list command */
+ else if (*answer == CONTROL_D)
+ cmd = cmdQUIT;
+ else {
+ if ((p=strchr (answer,' ')))
+ {
+ *p++ = 0;
+ trim_spaces (answer);
+ trim_spaces (p);
+ arg_number = atoi(p);
+ arg_string = p;
+ }
+
+ for (i=0; cmds[i].name; i++ )
+ if (!ascii_strcasecmp (answer, cmds[i].name ))
+ break;
+
+ cmd = cmds[i].id;
+ }
+
+ switch (cmd)
+ {
+ case cmdHELP:
+ for (i=0; cmds[i].name; i++ )
+ if (cmds[i].desc)
+ tty_printf("%-10s %s\n", cmds[i].name, _(cmds[i].desc) );
+ break;
+
+ case cmdLIST:
+ redisplay = 1;
+ break;
+
+ case cmdNAME:
+ change_name ();
+ break;
+
+ case cmdURL:
+ change_url ();
+ break;
+
+ case cmdLOGIN:
+ change_login ();
+ break;
+
+ case cmdLANG:
+ change_lang ();
+ break;
+
+ case cmdSEX:
+ change_sex ();
+ break;
+
+ case cmdQUIT:
+ goto leave;
+
+ case cmdNOP:
+ break;
+
+ case cmdINVCMD:
+ default:
+ tty_printf ("\n");
+ tty_printf (_("Invalid command (try \"help\")\n"));
+ break;
+ } /* End command switch. */
+ } /* End of main menu loop. */
+
+ leave:
+ xfree (answer);
+}
+
+#endif /*ENABLE_CARD_SUPPORT*/
diff --git a/g10/cardglue.c b/g10/cardglue.c
index ca47cefd5..8924889dd 100644
--- a/g10/cardglue.c
+++ b/g10/cardglue.c
@@ -30,6 +30,7 @@
#include <stdlib.h>
#include <string.h>
#include <errno.h>
+#include <stdarg.h>
#include <assert.h>
#include "options.h"
@@ -42,6 +43,17 @@
#include "i18n.h"
#include "cardglue.h"
+#include "apdu.h"
+#include "app-common.h"
+
+struct ctrl_ctx_s {
+ int (*status_cb)(void *opaque, const char *line);
+ void *status_cb_arg;
+};
+
+
+static char *default_reader_port;
+
@@ -70,11 +82,432 @@ serialno_and_fpr_from_sk (const unsigned char *sn, size_t snlen,
}
+/* 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 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;
+ ctrl->status_cb (ctrl->status_cb_arg, buf);
+
+ va_end (arg_ptr);
+}
+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;
+}
+
+
+/* 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 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)
+{
+ 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;
+}
+
+
+/* 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
+open_card (void)
+{
+ int slot;
+ int rc;
+ APP app;
+
+ slot = apdu_open_reader (default_reader_port);
+ if (slot == -1)
+ {
+ log_error ("card reader not available\n");
+ return NULL;
+ }
+
+ app = xcalloc (1, sizeof *app);
+ app->slot = slot;
+ rc = app_select_openpgp (app, &app->serialno, &app->serialnolen);
+ if (rc)
+ {
+/* apdu_close_reader (slot); */
+ log_info ("selecting openpgp failed: %s\n", gpg_strerror (rc));
+ xfree (app);
+ return NULL;
+ }
+
+ app->initialized = 1;
+ return app;
+}
+/* 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)
+{
+ char *buffer, *d;
+
+ buffer = d = xmalloc (strlen (s)+1);
+ while (*s)
+ {
+ if (*s == '%' && s[1] && s[2])
+ {
+ s++;
+ *d = xtoi_2 (s);
+ if (!*d)
+ *d = '\xff';
+ d++;
+ s += 2;
+ }
+ else if (*s == '+')
+ {
+ *d++ = ' ';
+ s++;
+ }
+ else
+ *d++ = *s++;
+ }
+ *d = 0;
+ return buffer;
+}
+
+/* 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 int
+learn_status_cb (void *opaque, const char *line)
+{
+ struct agent_card_info_s *parm = opaque;
+ const char *keyword = line;
+ int keywordlen;
+ int i;
+
+ for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
+ ;
+ while (spacep (line))
+ line++;
+
+ if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen))
+ {
+ parm->serialno = store_serialno (line);
+ }
+ else if (keywordlen == 9 && !memcmp (keyword, "DISP-NAME", keywordlen))
+ {
+ parm->disp_name = unescape_status_string (line);
+ }
+ else if (keywordlen == 9 && !memcmp (keyword, "DISP-LANG", keywordlen))
+ {
+ parm->disp_lang = unescape_status_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))
+ {
+ parm->pubkey_url = unescape_status_string (line);
+ }
+ else if (keywordlen == 10 && !memcmp (keyword, "LOGIN-DATA", keywordlen))
+ {
+ parm->login_data = unescape_status_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_status_string (line);
+ if (buf)
+ {
+ while (spacep (p))
+ p++;
+ parm->chv1_cached = atoi (p);
+ while (!spacep (p))
+ p++;
+ while (spacep (p))
+ p++;
+ for (i=0; *p && i < 3; i++)
+ {
+ parm->chvmaxlen[i] = atoi (p);
+ while (!spacep (p))
+ p++;
+ while (spacep (p))
+ p++;
+ }
+ for (i=0; *p && i < 3; i++)
+ {
+ parm->chvretry[i] = atoi (p);
+ while (!spacep (p))
+ p++;
+ while (spacep (p))
+ p++;
+ }
+ xfree (buf);
+ }
+ }
+ else if (keywordlen == 7 && !memcmp (keyword, "KEY-FPR", keywordlen))
+ {
+ int no = atoi (line);
+ while (!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);
+ }
+
+ return 0;
+}
+
+
+/* Return card info. */
+int
+agent_learn (struct agent_card_info_s *info)
+{
+ APP app;
+ int rc;
+ struct ctrl_ctx_s ctrl;
+ time_t stamp;
+ char *serial;
+
+ app = open_card ();
+ if (!app)
+ return gpg_error (GPG_ERR_CARD);
+
+ 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;
+}
+
+/* Send a SETATTR command to the SCdaemon. */
+int
+agent_scd_setattr (const char *name,
+ const unsigned char *value, size_t valuelen)
+{
+
+ return gpg_error (GPG_ERR_CARD);
+}
+
+/* Send a GENKEY command to the SCdaemon. */
+int
+agent_scd_genkey (struct agent_card_genkey_s *info, int keyno, int force)
+{
+
+ return gpg_error (GPG_ERR_CARD);
+}
+
+/* Send a PKSIGN command to the SCdaemon. */
+int
+agent_scd_pksign (const char *keyid, int hashalgo,
+ const unsigned char *indata, size_t indatalen,
+ char **r_buf, size_t *r_buflen)
+{
+
+ return gpg_error (GPG_ERR_CARD);
+}
+
+
+/* Send a PKDECRYPT command to the SCdaemon. */
+int
+agent_scd_pkdecrypt (const char *serialno,
+ const unsigned char *indata, size_t indatalen,
+ char **r_buf, size_t *r_buflen)
+{
+
+ return gpg_error (GPG_ERR_CARD);
+}
+
+/* Change the PIN of an OpenPGP card or reset the retry counter. */
+int
+agent_scd_change_pin (int chvno)
+{
+
+ return gpg_error (GPG_ERR_CARD);
+}
+
diff --git a/g10/cardglue.h b/g10/cardglue.h
index 03b40a077..076849866 100644
--- a/g10/cardglue.h
+++ b/g10/cardglue.h
@@ -60,13 +60,64 @@ struct agent_card_genkey_s {
};
+struct app_ctx_s;
+struct ctrl_ctx_s;
+typedef struct app_ctx_s *APP;
+typedef struct ctrl_ctx_s *CTRL;
+
+
+#define GPG_ERR_BAD_PIN G10ERR_BAD_PASS
+#define GPG_ERR_CARD G10ERR_GENERAL
+#define GPG_ERR_EEXIST G10ERR_FILE_EXISTS
+#define GPG_ERR_ENOMEM G10ERR_RESOURCE_LIMIT
+#define GPG_ERR_GENERAL G10ERR_GENERAL
+#define GPG_ERR_HARDWARE G10ERR_GENERAL
+#define GPG_ERR_INV_CARD G10ERR_GENERAL
+#define GPG_ERR_INV_ID G10ERR_GENERAL
+#define GPG_ERR_INV_NAME G10ERR_GENERAL
+#define GPG_ERR_INV_VALUE G10ERR_INV_ARG
+#define GPG_ERR_NOT_SUPPORTED G10ERR_UNSUPPORTED
+#define GPG_ERR_NO_OBJ G10ERR_GENERAL
+#define GPG_ERR_PIN_BLOCKED G10ERR_PASSPHRASE
+#define GPG_ERR_UNSUPPORTED_ALGORITHM G10ERR_PUBKEY_ALGO
+#define GPG_ERR_USE_CONDITIONS G10ERR_GENERAL
+#define GPG_ERR_WRONG_CARD G10ERR_GENERAL
+#define GPG_ERR_WRONG_SECKEY G10ERR_WRONG_SECKEY
+
+
+typedef int gpg_error_t;
+typedef int gpg_err_code_t;
+
+#define gpg_error(n) (n)
+#define gpg_err_code(n) (n)
+#define gpg_strerror(n) g10_errstr ((n))
+#define gpg_error_from_errno(n) (G10ERR_GENERAL) /*FIXME*/
+
+
+/* We are not using it in a library, so we even let xtrymalloc
+ abort. Because we won't never return from these malloc functions,
+ we also don't need the out_of_core function, we simply define it to
+ return -1 */
+#define xtrymalloc(n) xmalloc((n))
+#define xtrycalloc(n,m) xcalloc((n),(m))
+#define xtryrealloc(n,m) xrealloc((n),(m))
+#define out_of_core() (-1)
+
+#define gnupg_get_time() make_timestamp ()
char *serialno_and_fpr_from_sk (const unsigned char *sn, size_t snlen,
PKT_secret_key *sk);
+void send_status_info (CTRL ctrl, const char *keyword, ...);
+void gcry_md_hash_buffer (int algo, void *digest,
+ const void *buffer, size_t length);
+void log_printf (const char *fmt, ...);
+void log_printhex (const char *text, const void *buffer, size_t length);
+#define GCRY_MD_SHA1 DIGEST_ALGO_SHA1
+#define GCRY_MD_RMD160 DIGEST_ALGO_RMD160
/* Release the card info structure. */
diff --git a/g10/ccid-driver.c b/g10/ccid-driver.c
new file mode 100644
index 000000000..de8f870a5
--- /dev/null
+++ b/g10/ccid-driver.c
@@ -0,0 +1,990 @@
+/* ccid-driver.c - USB ChipCardInterfaceDevices driver
+ * Copyright (C) 2003 Free Software Foundation, Inc.
+ * Written by 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 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ *
+ * ALTERNATIVELY, this file may be distributed under the terms of the
+ * following license, in which case the provisions of this license are
+ * required INSTEAD OF the GNU General Public License. If you wish to
+ * allow use of your version of this file only under the terms of the
+ * GNU General Public License, and not to allow others to use your
+ * version of this file under the terms of the following license,
+ * indicate your decision by deleting this paragraph and the license
+ * below.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, and the entire permission notice in its entirety,
+ * including the disclaimer of warranties.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+/* CCID (ChipCardInterfaceDevices) is a specification for accessing
+ smartcard via a reader connected to the USB.
+
+ This is a limited driver allowing to use some CCID drivers directly
+ without any other specila drivers. This is a fallback driver to be
+ used when nothing else works or the system should be kept minimal
+ for security reasons. It makes use of the libusb library to gain
+ portable access to USB.
+
+ This driver has been tested with the SCM SCR335 smartcard reader
+ and requires that reader implements the TPDU level exchange and
+ does fully automatic initialization.
+*/
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#if defined(HAVE_LIBUSB) || defined(TEST)
+
+#define GNUPG_DEFAULT_SCDAEMON 1 /* Hack for 1.3 */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include <usb.h>
+
+#include "ccid-driver.h"
+
+#define DRVNAME "ccid-driver: "
+
+
+#ifdef GNUPG_DEFAULT_SCDAEMON /* This source is used within the
+ gnupg>=1.9 source tree. */
+# include "options.h"
+# include "util.h"
+# include "memory.h"
+
+
+# define DEBUGOUT(t) do { if (DBG_CARD_IO) \
+ log_debug (DRVNAME t); } while (0)
+# define DEBUGOUT_1(t,a) do { if (DBG_CARD_IO) \
+ log_debug (DRVNAME t,(a)); } while (0)
+# define DEBUGOUT_2(t,a,b) do { if (DBG_CARD_IO) \
+ log_debug (DRVNAME t,(a),(b)); } while (0)
+# define DEBUGOUT_3(t,a,b,c) do { if (DBG_CARD_IO) \
+ log_debug (DRVNAME t,(a),(b),(c));} while (0)
+# define DEBUGOUT_CONT(t) do { if (DBG_CARD_IO) \
+ log_printf (t); } while (0)
+# define DEBUGOUT_CONT_1(t,a) do { if (DBG_CARD_IO) \
+ log_printf (t,(a)); } while (0)
+# define DEBUGOUT_CONT_2(t,a,b) do { if (DBG_CARD_IO) \
+ log_printf (t,(a),(b)); } while (0)
+# define DEBUGOUT_CONT_3(t,a,b,c) do { if (DBG_CARD_IO) \
+ log_printf (t,(a),(b),(c)); } while (0)
+# define DEBUGOUT_LF() do { if (DBG_CARD_IO) \
+ log_printf ("\n"); } while (0)
+
+#else /* Other usage of this source - don't use gnupg specifics. */
+
+# define DEBUGOUT(t) fprintf (stderr, DRVNAME t)
+# define DEBUGOUT_1(t,a) fprintf (stderr, DRVNAME t, (a))
+# define DEBUGOUT_2(t,a,b) fprintf (stderr, DRVNAME t, (a), (b))
+# define DEBUGOUT_3(t,a,b,c) fprintf (stderr, DRVNAME t, (a), (b), (c))
+# define DEBUGOUT_CONT(t) fprintf (stderr, t)
+# define DEBUGOUT_CONT_1(t,a) fprintf (stderr, t, (a))
+# define DEBUGOUT_CONT_2(t,a,b) fprintf (stderr, t, (a), (b))
+# define DEBUGOUT_CONT_3(t,a,b,c) fprintf (stderr, t, (a), (b), (c))
+# define DEBUGOUT_LF() putc ('\n', stderr)
+
+#endif /* This source not used by scdaemon. */
+
+
+enum {
+ RDR_to_PC_NotifySlotChange= 0x50,
+ RDR_to_PC_HardwareError = 0x51,
+
+ PC_to_RDR_SetParameters = 0x61,
+ PC_to_RDR_IccPowerOn = 0x62,
+ PC_to_RDR_IccPowerOff = 0x63,
+ PC_to_RDR_GetSlotStatus = 0x65,
+ PC_to_RDR_Secure = 0x69,
+ PC_to_RDR_T0APDU = 0x6a,
+ PC_to_RDR_Escape = 0x6b,
+ PC_to_RDR_GetParameters = 0x6c,
+ PC_to_RDR_ResetParameters = 0x6d,
+ PC_to_RDR_IccClock = 0x6e,
+ PC_to_RDR_XfrBlock = 0x6f,
+ PC_to_RDR_Mechanical = 0x71,
+ PC_to_RDR_Abort = 0x72,
+ PC_to_RDR_SetDataRate = 0x73,
+
+ RDR_to_PC_DataBlock = 0x80,
+ RDR_to_PC_SlotStatus = 0x81,
+ RDR_to_PC_Parameters = 0x82,
+ RDR_to_PC_Escape = 0x83,
+ RDR_to_PC_DataRate = 0x84
+};
+
+
+/* Store information on the driver's state. A pointer to such a
+ structure is used as handle for most functions. */
+struct ccid_driver_s {
+ usb_dev_handle *idev;
+ int seqno;
+ unsigned char t1_ns;
+ unsigned char t1_nr;
+};
+
+
+/* Convert a little endian stored 4 byte value into an unsigned
+ integer. */
+static unsigned int
+convert_le_u32 (const unsigned char *buf)
+{
+ return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
+}
+
+
+
+/* Parse a CCID descriptor, optionally print all available features
+ and test whether this reader is usable by this driver. Returns 0
+ if it is usable.
+
+ Note, that this code is based on the one in lsusb.c of the
+ usb-utils package, I wrote on 2003-09-01. -wk. */
+static int
+parse_ccid_descriptor (const unsigned char *buf, size_t buflen)
+{
+ unsigned int i;
+ unsigned int us;
+ int have_t1 = 0, have_tpdu=0, have_auto_conf = 0;
+
+
+ if (buflen < 54 || buf[0] < 54)
+ {
+ DEBUGOUT ("CCID device descriptor is too short\n");
+ return -1;
+ }
+
+ DEBUGOUT ("ChipCard Interface Descriptor:\n");
+ DEBUGOUT_1 (" bLength %5u\n", buf[0]);
+ DEBUGOUT_1 (" bDescriptorType %5u\n", buf[1]);
+ DEBUGOUT_2 (" bcdCCID %2x.%02x", buf[3], buf[2]);
+ if (buf[3] != 1 || buf[2] != 0)
+ DEBUGOUT_CONT(" (Warning: Only accurate for version 1.0)");
+ DEBUGOUT_LF ();
+
+ DEBUGOUT_1 (" nMaxSlotIndex %5u\n", buf[4]);
+ DEBUGOUT_2 (" bVoltageSupport %5u %s\n",
+ buf[5], (buf[5] == 1? "5.0V" : buf[5] == 2? "3.0V"
+ : buf[5] == 3? "1.8V":"?"));
+
+ us = convert_le_u32 (buf+6);
+ DEBUGOUT_1 (" dwProtocols %5u ", us);
+ if ((us & 1))
+ DEBUGOUT_CONT (" T=0");
+ if ((us & 2))
+ {
+ DEBUGOUT_CONT (" T=1");
+ have_t1 = 1;
+ }
+ if ((us & ~3))
+ DEBUGOUT_CONT (" (Invalid values detected)");
+ DEBUGOUT_LF ();
+
+ us = convert_le_u32(buf+10);
+ DEBUGOUT_1 (" dwDefaultClock %5u\n", us);
+ us = convert_le_u32(buf+14);
+ DEBUGOUT_1 (" dwMaxiumumClock %5u\n", us);
+ DEBUGOUT_1 (" bNumClockSupported %5u\n", buf[18]);
+ us = convert_le_u32(buf+19);
+ DEBUGOUT_1 (" dwDataRate %7u bps\n", us);
+ us = convert_le_u32(buf+23);
+ DEBUGOUT_1 (" dwMaxDataRate %7u bps\n", us);
+ DEBUGOUT_1 (" bNumDataRatesSupp. %5u\n", buf[27]);
+
+ us = convert_le_u32(buf+28);
+ DEBUGOUT_1 (" dwMaxIFSD %5u\n", us);
+
+ us = convert_le_u32(buf+32);
+ DEBUGOUT_1 (" dwSyncProtocols %08X ", us);
+ if ((us&1))
+ DEBUGOUT_CONT ( " 2-wire");
+ if ((us&2))
+ DEBUGOUT_CONT ( " 3-wire");
+ if ((us&4))
+ DEBUGOUT_CONT ( " I2C");
+ DEBUGOUT_LF ();
+
+ us = convert_le_u32(buf+36);
+ DEBUGOUT_1 (" dwMechanical %08X ", us);
+ if ((us & 1))
+ DEBUGOUT_CONT (" accept");
+ if ((us & 2))
+ DEBUGOUT_CONT (" eject");
+ if ((us & 4))
+ DEBUGOUT_CONT (" capture");
+ if ((us & 8))
+ DEBUGOUT_CONT (" lock");
+ DEBUGOUT_LF ();
+
+ us = convert_le_u32(buf+40);
+ DEBUGOUT_1 (" dwFeatures %08X\n", us);
+ if ((us & 0x0002))
+ {
+ DEBUGOUT (" Auto configuration based on ATR\n");
+ have_auto_conf = 1;
+ }
+ if ((us & 0x0004))
+ DEBUGOUT (" Auto activation on insert\n");
+ if ((us & 0x0008))
+ DEBUGOUT (" Auto voltage selection\n");
+ if ((us & 0x0010))
+ DEBUGOUT (" Auto clock change\n");
+ if ((us & 0x0020))
+ DEBUGOUT (" Auto baud rate change\n");
+ if ((us & 0x0040))
+ DEBUGOUT (" Auto parameter negotation made by CCID\n");
+ else if ((us & 0x0080))
+ DEBUGOUT (" Auto PPS made by CCID\n");
+ else if ((us & (0x0040 | 0x0080)))
+ DEBUGOUT (" WARNING: conflicting negotation features\n");
+
+ if ((us & 0x0100))
+ DEBUGOUT (" CCID can set ICC in clock stop mode\n");
+ if ((us & 0x0200))
+ DEBUGOUT (" NAD value other than 0x00 accpeted\n");
+ if ((us & 0x0400))
+ DEBUGOUT (" Auto IFSD exchange\n");
+
+ if ((us & 0x00010000))
+ {
+ DEBUGOUT (" TPDU level exchange\n");
+ have_tpdu = 1;
+ }
+ else if ((us & 0x00020000))
+ DEBUGOUT (" Short APDU level exchange\n");
+ else if ((us & 0x00040000))
+ DEBUGOUT (" Short and extended APDU level exchange\n");
+ else if ((us & 0x00070000))
+ DEBUGOUT (" WARNING: conflicting exchange levels\n");
+
+ us = convert_le_u32(buf+44);
+ DEBUGOUT_1 (" dwMaxCCIDMsgLen %5u\n", us);
+
+ DEBUGOUT ( " bClassGetResponse ");
+ if (buf[48] == 0xff)
+ DEBUGOUT_CONT ("echo\n");
+ else
+ DEBUGOUT_CONT_1 (" %02X\n", buf[48]);
+
+ DEBUGOUT ( " bClassEnvelope ");
+ if (buf[49] == 0xff)
+ DEBUGOUT_CONT ("echo\n");
+ else
+ DEBUGOUT_1 (" %02X\n", buf[48]);
+
+ DEBUGOUT ( " wlcdLayout ");
+ if (!buf[50] && !buf[51])
+ DEBUGOUT_CONT ("none\n");
+ else
+ DEBUGOUT_CONT_2 ("%u cols %u lines\n", buf[50], buf[51]);
+
+ DEBUGOUT_1 (" bPINSupport %5u ", buf[52]);
+ if ((buf[52] & 1))
+ DEBUGOUT_CONT ( " verification");
+ if ((buf[52] & 2))
+ DEBUGOUT_CONT ( " modification");
+ DEBUGOUT_LF ();
+
+ DEBUGOUT_1 (" bMaxCCIDBusySlots %5u\n", buf[53]);
+
+ if (buf[0] > 54) {
+ DEBUGOUT (" junk ");
+ for (i=54; i < buf[0]-54; i++)
+ DEBUGOUT_CONT_1 (" %02X", buf[i]);
+ DEBUGOUT_LF ();
+ }
+
+ if (!have_t1 || !have_tpdu || !have_auto_conf)
+ {
+ DEBUGOUT ("this drivers requires that the reader supports T=1, "
+ "TPDU level exchange and auto configuration - "
+ "this is not available\n");
+ return -1;
+ }
+ else
+ return 0;
+}
+
+
+/* Read the device information, return all required data and check
+ that the device is usable for us. Returns 0 on success or an error
+ code. */
+static int
+read_device_info (struct usb_device *dev)
+{
+ int cfg_no;
+
+ for (cfg_no=0; cfg_no < dev->descriptor->bNumConfigurations; cfg_no++)
+ {
+ struct usb_config_descriptor *config = dev->config + cfg_no;
+ int ifc_no;
+
+ for (ifc_no=0; ifc_no < config->bNumInterfaces; ifc_no++)
+ {
+ struct usb_interface *interface = config->interface + ifc_no;
+ int set_no;
+
+ for (set_no=0; set_no < interface->num_altsetting; set_no++)
+ {
+ struct usb_interface_descriptor *ifcdesc
+ = interface->altsetting + set_no;
+
+ if (ifcdesc->bInterfaceClass == 11
+ && ifcdesc->bInterfaceSubClass == 0
+ && ifcdesc->bInterfaceProtocol == 0)
+ {
+ if (ifcdesc->extra)
+ {
+ if (!parse_ccid_descriptor (ifcdesc->extra,
+ ifcdesc->extralen))
+ return 0; /* okay. we can use it. */
+ }
+ }
+ }
+ }
+ }
+ return -1; /* No suitable device found. */
+}
+
+
+/* Open the reader with the internal number READERNO and return a a
+ pointer to be used as handle in HANDLE. Returns 0 on success. */
+int
+ccid_open_reader (ccid_driver_t *handle, int readerno)
+{
+ static int initialized;
+
+ int rc;
+ usb_match_handle *match = NULL;
+ struct usb_device *dev = NULL;
+ usb_dev_handle *idev = NULL;
+
+ *handle = NULL;
+ if (!initialized)
+ {
+ usb_init ();
+ initialized = 1;
+ }
+
+ rc = usb_create_match (&match, -1, -1, 11, -1, -1);
+ if (rc)
+ {
+ DEBUGOUT_1 ("usb_create_match failed: %d\n", rc);
+ return -1;
+ }
+
+ while (usb_find_device(match, dev, &dev) >= 0)
+ {
+ DEBUGOUT_3 ("%-40s %04X/%04X\n", dev->filename,
+ dev->descriptor->idVendor, dev->descriptor->idProduct);
+ if (!readerno)
+ {
+ rc = read_device_info (dev);
+ if (rc)
+ {
+ DEBUGOUT ("device not supported\n");
+ goto leave;
+ }
+
+ rc = usb_open (dev, &idev);
+ if (rc)
+ {
+ DEBUGOUT_1 ("usb_open failed: %d\n", rc);
+ goto leave;
+ }
+
+
+ /* fixme: Do we need to claim and set the interface as
+ determined by read_device_info ()? */
+ rc = usb_claim_interface (idev, 0);
+ if (rc)
+ {
+ DEBUGOUT_1 ("usb_claim_interface failed: %d\n", rc);
+ goto leave;
+ }
+
+ *handle = calloc (1, sizeof **handle);
+ if (!*handle)
+ {
+ DEBUGOUT ("out of memory\n");
+ rc = -1;
+ goto leave;
+ }
+ (*handle)->idev = idev;
+ idev = NULL;
+ /* FIXME: Do we need to get the endpoint addresses from the
+ structure and store them with the handle? */
+
+ break;
+ }
+ readerno--;
+ }
+
+
+ leave:
+ if (idev)
+ usb_close (idev);
+ /* fixme: Do we need to release dev or is it supposed to be a
+ shallow copy of the list created internally by usb_init ? */
+ usb_free_match (match);
+
+ return rc;
+}
+
+
+/* Return False if a card is present and powered. */
+int
+ccid_check_card_presence (ccid_driver_t handle)
+{
+
+ return -1;
+}
+
+
+static void
+set_msg_len (unsigned char *msg, unsigned int length)
+{
+ msg[1] = length;
+ msg[2] = length >> 8;
+ msg[3] = length >> 16;
+ msg[4] = length >> 24;
+}
+
+
+/* Write a MSG of length MSGLEN to the designated bulk out endpoint.
+ Returns 0 on success. */
+static int
+bulk_out (ccid_driver_t handle, unsigned char *msg, size_t msglen)
+{
+ int rc;
+
+ rc = usb_bulk_write (handle->idev,
+ 1, /*endpoint */
+ msg, msglen,
+ 1000 /* ms timeout */);
+ if (rc == msglen)
+ return 0;
+
+ if (rc == -1)
+ DEBUGOUT_1 ("usb_bulk_write error: %s\n", strerror (errno));
+ else
+ DEBUGOUT_1 ("usb_bulk_write failed: %d\n", rc);
+ return -1;
+}
+
+
+/* Read a maximum of LENGTH bytes from the bulk in endpoint into
+ BUFFER and return the actual read number if bytes in NREAD. SEQNO
+ is the sequence number used to send the request and EXPECTED_TYPE
+ the type of message we expect. Does checks on the ccid
+ header. Returns 0 on success. */
+static int
+bulk_in (ccid_driver_t handle, unsigned char *buffer, size_t length,
+ size_t *nread, int expected_type, int seqno)
+{
+ int i, rc;
+ size_t msglen;
+
+ rc = usb_bulk_read (handle->idev,
+ 0x82,
+ buffer, length,
+ 10000 /* ms timeout */ );
+ /* Fixme: instead of using a 10 second timeout we should better
+ handle the timeout here and retry if appropriate. */
+ if (rc < 0)
+ {
+ DEBUGOUT_1 ("usb_bulk_read error: %s\n", strerror (errno));
+ return -1;
+ }
+
+ *nread = msglen = rc;
+
+ if (msglen < 10)
+ {
+ DEBUGOUT_1 ("bulk-in msg too short (%u)\n", (unsigned int)msglen);
+ return -1;
+ }
+ if (buffer[0] != expected_type)
+ {
+ DEBUGOUT_1 ("unexpected bulk-in msg type (%02x)\n", buffer[0]);
+ return -1;
+ }
+ if (buffer[5] != 0)
+ {
+ DEBUGOUT_1 ("unexpected bulk-in slot (%d)\n", buffer[5]);
+ return -1;
+ }
+ if (buffer[6] != seqno)
+ {
+ DEBUGOUT_2 ("bulk-in seqno does not match (%d/%d)\n",
+ seqno, buffer[6]);
+ return -1;
+ }
+
+ DEBUGOUT_3 ("status: %02X error: %02X clock-status: %02X\n"
+ " data:", buffer[7], buffer[8], buffer[9] );
+ for (i=10; i < msglen; i++)
+ DEBUGOUT_CONT_1 (" %02X", buffer[i]);
+ DEBUGOUT_LF ();
+
+ return 0;
+}
+
+
+/* experimental */
+int
+ccid_poll (ccid_driver_t handle)
+{
+ int rc;
+ unsigned char msg[10];
+ size_t msglen;
+ int i, j;
+
+ rc = usb_bulk_read (handle->idev,
+ 0x83,
+ msg, sizeof msg,
+ 0 /* ms timeout */ );
+ if (rc < 0 && errno == ETIMEDOUT)
+ return 0;
+
+ if (rc < 0)
+ {
+ DEBUGOUT_1 ("usb_intr_read error: %s\n", strerror (errno));
+ return -1;
+ }
+
+ msglen = rc;
+ rc = 0;
+
+ if (msglen < 1)
+ {
+ DEBUGOUT ("intr-in msg too short\n");
+ return -1;
+ }
+
+ if (msg[0] == RDR_to_PC_NotifySlotChange)
+ {
+ DEBUGOUT ("notify slot change:");
+ for (i=1; i < msglen; i++)
+ for (j=0; j < 4; j++)
+ DEBUGOUT_CONT_3 (" %d:%c%c",
+ (i-1)*4+j,
+ (msg[i] & (1<<(j*2)))? 'p':'-',
+ (msg[i] & (2<<(j*2)))? '*':' ');
+ DEBUGOUT_LF ();
+ }
+ else if (msg[0] == RDR_to_PC_HardwareError)
+ {
+ DEBUGOUT ("hardware error occured\n");
+ }
+ else
+ {
+ DEBUGOUT_1 ("unknown intr-in msg of type %02X\n", msg[0]);
+ }
+
+ return 0;
+}
+
+
+
+int
+ccid_slot_status (ccid_driver_t handle)
+{
+ int rc;
+ unsigned char msg[100];
+ size_t msglen;
+ unsigned char seqno;
+
+ msg[0] = PC_to_RDR_GetSlotStatus;
+ msg[5] = 0; /* slot */
+ msg[6] = seqno = handle->seqno++;
+ msg[7] = 0; /* RFU */
+ msg[8] = 0; /* RFU */
+ msg[9] = 0; /* RFU */
+ set_msg_len (msg, 0);
+
+ rc = bulk_out (handle, msg, 10);
+ if (rc)
+ return rc;
+ rc = bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_SlotStatus, seqno);
+ if (rc)
+ return rc;
+
+ return 0;
+}
+
+
+int
+ccid_get_atr (ccid_driver_t handle,
+ unsigned char *atr, size_t maxatrlen, size_t *atrlen)
+{
+ int rc;
+ unsigned char msg[100];
+ size_t msglen;
+ unsigned char seqno;
+
+ msg[0] = PC_to_RDR_IccPowerOn;
+ msg[5] = 0; /* slot */
+ msg[6] = seqno = handle->seqno++;
+ msg[7] = 0; /* power select (0=auto, 1=5V, 2=3V, 3=1.8V) */
+ msg[8] = 0; /* RFU */
+ msg[9] = 0; /* RFU */
+ set_msg_len (msg, 0);
+ msglen = 10;
+
+ rc = bulk_out (handle, msg, msglen);
+ if (rc)
+ return rc;
+ rc = bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_DataBlock, seqno);
+ if (rc)
+ return rc;
+
+ if (atr)
+ {
+ size_t n = msglen - 10;
+
+ if (n > maxatrlen)
+ n = maxatrlen;
+ memcpy (atr, msg+10, n);
+ *atrlen = n;
+ }
+
+ return 0;
+}
+
+
+
+/*
+ Protocol T=1 overview
+
+ Block Structure:
+ Prologue Field:
+ 1 byte Node Address (NAD)
+ 1 byte Protocol Control Byte (PCB)
+ 1 byte Length (LEN)
+ Information Field:
+ 0-254 byte APDU or Control Information (INF)
+ Epilogue Field:
+ 1 byte Error Detection Code (EDC)
+
+ NAD:
+ bit 7 unused
+ bit 4..6 Destination Node Address (DAD)
+ bit 3 unused
+ bit 2..0 Source Node Address (SAD)
+
+ If node adresses are not used, SAD and DAD should be set to 0 on
+ the first block sent to the card. If they are used they should
+ have different values (0 for one is okay); that first block sets up
+ the addresses of the nodes.
+
+ PCB:
+ Information Block (I-Block):
+ bit 7 0
+ bit 6 Sequence number (yep, that is modulo 2)
+ bit 5 Chaining flag
+ bit 4..0 reserved
+ Received-Ready Block (R-Block):
+ bit 7 1
+ bit 6 0
+ bit 5 0
+ bit 4 Sequence number
+ bit 3..0 0 = no error
+ 1 = EDC or parity error
+ 2 = other error
+ other values are reserved
+ Supervisory Block (S-Block):
+ bit 7 1
+ bit 6 1
+ bit 5 clear=request,set=response
+ bit 4..0 0 = resyncronisation request
+ 1 = information field size request
+ 2 = abort request
+ 3 = extension of BWT request
+ 4 = VPP error
+ other values are reserved
+
+*/
+
+int
+ccid_transceive (ccid_driver_t handle,
+ const unsigned char *apdu, size_t apdulen,
+ unsigned char *resp, size_t maxresplen, size_t *nresp)
+{
+ int rc;
+ unsigned char send_buffer[10+258], recv_buffer[10+258];
+ unsigned char *msg, *tpdu, *p;
+ size_t msglen, tpdulen, n;
+ unsigned char seqno;
+ int i;
+ unsigned char crc;
+ size_t dummy_nresp;
+ int sending = 1;
+
+ if (!nresp)
+ nresp = &dummy_nresp;
+
+ *nresp = 0;
+
+ /* Construct an I-Block. */
+ if (apdulen > 254)
+ return -1; /* Invalid length. */
+
+ msg = send_buffer;
+
+ tpdu = msg+10;
+ tpdu[0] = ((1 << 4) | 0); /* NAD: DAD=1, SAD=0 */
+ tpdu[1] = ((handle->t1_ns & 1) << 6); /* I-block */
+ tpdu[2] = apdulen;
+ memcpy (tpdu+3, apdu, apdulen);
+ crc = 0;
+ for (i=0,p=tpdu; i < apdulen+3; i++)
+ crc ^= *p++;
+ tpdu[3+apdulen] = crc;
+
+ tpdulen = apdulen + 4;
+
+ for (;;)
+ {
+ msg[0] = PC_to_RDR_XfrBlock;
+ msg[5] = 0; /* slot */
+ msg[6] = seqno = handle->seqno++;
+ msg[7] = 4; /* bBWI */
+ msg[8] = 0; /* RFU */
+ msg[9] = 0; /* RFU */
+ set_msg_len (msg, tpdulen);
+ msglen = 10 + tpdulen;
+
+ DEBUGOUT ("sending");
+ for (i=0; i < msglen; i++)
+ DEBUGOUT_CONT_1 (" %02X", msg[i]);
+ DEBUGOUT_LF ();
+
+/* fprintf (stderr, "T1: put %c-block seq=%d\n", */
+/* ((msg[11] & 0xc0) == 0x80)? 'R' : */
+/* (msg[11] & 0x80)? 'S' : 'I', */
+/* ((msg[11] & 0x80)? !!(msg[11]& 0x10) : !!(msg[11] & 0x40))); */
+
+ rc = bulk_out (handle, msg, msglen);
+ if (rc)
+ return rc;
+
+ msg = recv_buffer;
+ rc = bulk_in (handle, msg, sizeof recv_buffer, &msglen,
+ RDR_to_PC_DataBlock, seqno);
+ if (rc)
+ return rc;
+
+ tpdu = msg + 10;
+ tpdulen = msglen - 10;
+
+ if (tpdulen < 4)
+ {
+ DEBUGOUT ("cannot yet handle short blocks!\n");
+ return -1;
+ }
+
+/* fprintf (stderr, "T1: got %c-block seq=%d err=%d\n", */
+/* ((msg[11] & 0xc0) == 0x80)? 'R' : */
+/* (msg[11] & 0x80)? 'S' : 'I', */
+/* ((msg[11] & 0x80)? !!(msg[11]& 0x10) : !!(msg[11] & 0x40)), */
+/* ((msg[11] & 0xc0) == 0x80)? (msg[11] & 0x0f) : 0 */
+/* ); */
+
+ if (!(tpdu[1] & 0x80))
+ { /* This is an I-block. */
+
+ if (sending)
+ { /* last block sent was successful. */
+ handle->t1_ns ^= 1;
+ sending = 0;
+ }
+
+ if (!!(tpdu[1] & 0x40) != handle->t1_nr)
+ { /* Reponse does not match our sequence number. */
+ msg = send_buffer;
+ tpdu = msg+10;
+ tpdu[0] = ((1 << 4) | 0); /* NAD: DAD=1, SAD=0 */
+ tpdu[1] = (0x80 | (handle->t1_nr & 1) << 4 | 2); /* R-block */
+ tpdu[2] = 0;
+ tpdulen = 3;
+ for (crc=0,i=0,p=tpdu; i < tpdulen; i++)
+ crc ^= *p++;
+ tpdu[tpdulen++] = crc;
+
+ continue;
+ }
+
+ handle->t1_nr ^= 1;
+
+ p = tpdu + 3; /* Skip the prologue field. */
+ n = tpdulen - 3 - 1; /* Strip the epilogue field. */
+ /* fixme: verify the checksum. */
+ if (resp)
+ {
+ if (n > maxresplen)
+ {
+ DEBUGOUT ("provided buffer too short for received data\n");
+ return -1;
+ }
+
+ memcpy (resp, p, n);
+ resp += n;
+ *nresp += n;
+ maxresplen -= n;
+ }
+
+ if (!(tpdu[1] & 0x20))
+ return 0; /* No chaining requested - ready. */
+
+ msg = send_buffer;
+ tpdu = msg+10;
+ tpdu[0] = ((1 << 4) | 0); /* NAD: DAD=1, SAD=0 */
+ tpdu[1] = (0x80 | (handle->t1_nr & 1) << 4); /* R-block */
+ tpdu[2] = 0;
+ tpdulen = 3;
+ for (crc=0,i=0,p=tpdu; i < tpdulen; i++)
+ crc ^= *p++;
+ tpdu[tpdulen++] = crc;
+
+ }
+ else if ((tpdu[1] & 0xc0) == 0x80)
+ { /* This is a R-block. */
+ if ( (tpdu[1] & 0x0f))
+ { /* Error: repeat last block */
+ msg = send_buffer;
+ }
+ else
+ {
+ DEBUGOUT ("unxpectec ACK R-block received\n");
+ return -1;
+ }
+ }
+ else
+ { /* This is a S-block. */
+ DEBUGOUT_2 ("T1 S-block %s received cmd=%d\n",
+ (tpdu[1] & 0x20)? "response": "request",
+ (tpdu[1] & 0x1f));
+ if ( !(tpdu[1] & 0x20) && (tpdu[1] & 0x1f) == 3 && tpdu[2])
+ { /* Wait time extension request. */
+ unsigned char bwi = tpdu[3];
+ msg = send_buffer;
+ tpdu = msg+10;
+ tpdu[0] = ((1 << 4) | 0); /* NAD: DAD=1, SAD=0 */
+ tpdu[1] = (0xc0 | 0x20 | 3); /* S-block response */
+ tpdu[2] = 1;
+ tpdu[3] = bwi;
+ tpdulen = 4;
+ for (crc=0,i=0,p=tpdu; i < tpdulen; i++)
+ crc ^= *p++;
+ tpdu[tpdulen++] = crc;
+ DEBUGOUT_1 ("T1 waittime extension of bwi=%d\n", bwi);
+ }
+ else
+ return -1;
+ }
+ } /* end T=1 protocol loop. */
+
+ return 0;
+}
+
+
+
+
+#ifdef TEST
+int
+main (int argc, char **argv)
+{
+ int rc;
+ ccid_driver_t ccid;
+
+ rc = ccid_open_reader (&ccid, 0);
+ if (rc)
+ return 1;
+
+ ccid_poll (ccid);
+ fputs ("getting ATR ...\n", stderr);
+ rc = ccid_get_atr (ccid, NULL, 0, NULL);
+ if (rc)
+ return 1;
+
+ ccid_poll (ccid);
+ fputs ("getting slot status ...\n", stderr);
+ rc = ccid_slot_status (ccid);
+ if (rc)
+ return 1;
+
+ ccid_poll (ccid);
+
+ {
+ static unsigned char apdu[] = {
+ 0, 0xA4, 4, 0, 6, 0xD2, 0x76, 0x00, 0x01, 0x24, 0x01};
+ rc = ccid_transceive (ccid,
+ apdu, sizeof apdu,
+ NULL, 0, NULL);
+ }
+ ccid_poll (ccid);
+
+ {
+ static unsigned char apdu[] = {
+ 0, 0xCA, 0, 0x65, 254 };
+ rc = ccid_transceive (ccid,
+ apdu, sizeof apdu,
+ NULL, 0, NULL);
+ }
+ ccid_poll (ccid);
+
+
+ return 0;
+}
+
+/*
+ * Local Variables:
+ * compile-command: "gcc -DTEST -Wall -I/usr/local/include -lusb -g ccid-driver.c"
+ * End:
+ */
+#endif /*TEST*/
+#endif /*HAVE_LIBUSB*/
diff --git a/g10/ccid-driver.h b/g10/ccid-driver.h
new file mode 100644
index 000000000..4d12d88c2
--- /dev/null
+++ b/g10/ccid-driver.h
@@ -0,0 +1,74 @@
+/* ccid-driver.c - USB ChipCardInterfaceDevices driver
+ * Copyright (C) 2003 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ *
+ * ALTERNATIVELY, this file may be distributed under the terms of the
+ * following license, in which case the provisions of this license are
+ * required INSTEAD OF the GNU General Public License. If you wish to
+ * allow use of your version of this file only under the terms of the
+ * GNU General Public License, and not to allow others to use your
+ * version of this file under the terms of the following license,
+ * indicate your decision by deleting this paragraph and the license
+ * below.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, and the entire permission notice in its entirety,
+ * including the disclaimer of warranties.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef CCID_DRIVER_H
+#define CCID_DRIVER_H
+
+
+struct ccid_driver_s;
+typedef struct ccid_driver_s *ccid_driver_t;
+
+int ccid_open_reader (ccid_driver_t *handle, int readerno);
+int ccid_get_atr (ccid_driver_t handle,
+ unsigned char *atr, size_t maxatrlen, size_t *atrlen);
+int ccid_transceive (ccid_driver_t handle,
+ const unsigned char *apdu, size_t apdulen,
+ unsigned char *resp, size_t maxresplen, size_t *nresp);
+
+
+
+#endif /*CCID_DRIVER_H*/
+
+
+
diff --git a/g10/g10.c b/g10/g10.c
index a28077960..f50bbe88b 100644
--- a/g10/g10.c
+++ b/g10/g10.c
@@ -128,6 +128,9 @@ enum cmd_and_opt_values { aNull = 0,
aPipeMode,
aRebuildKeydbCaches,
aRefreshKeys,
+ aCardStatus,
+ aCardEdit,
+ aChangePIN,
oTextmode,
oNoTextmode,
@@ -318,6 +321,12 @@ enum cmd_and_opt_values { aNull = 0,
oNoMangleDosFilenames,
oEnableProgressFilter,
oMultifile,
+
+ oReaderPort,
+ octapiDriver,
+ opcscDriver,
+ oDisableCCID,
+
aTest };
@@ -365,6 +374,11 @@ static ARGPARSE_OPTS opts[] = {
{ aExportSecretSub, "export-secret-subkeys" , 256, "@" },
{ aImport, "import", 256 , N_("import/merge keys")},
{ aFastImport, "fast-import", 256 , "@"},
+#ifdef ENABLE_CARD_SUPPORT
+ { aCardStatus, "card-status", 256, N_("print the card status")},
+ { aCardEdit, "card-edit", 256, N_("change data on a card")},
+ { aChangePIN, "change-pin", 256, N_("change a card's PIN")},
+#endif
{ aListPackets, "list-packets",256, "@"},
{ aExportOwnerTrust, "export-ownertrust", 256, "@"},
{ aImportOwnerTrust, "import-ownertrust", 256, "@"},
@@ -622,6 +636,13 @@ static ARGPARSE_OPTS opts[] = {
{ oNoMangleDosFilenames, "no-mangle-dos-filenames", 0, "@" },
{ oEnableProgressFilter, "enable-progress-filter", 0, "@" },
{ oMultifile, "multifile", 0, "@" },
+
+ { oReaderPort, "reader-port", 2, "@"},
+ { octapiDriver, "ctapi-driver", 2, "@"},
+ { opcscDriver, "pcsc-driver", 2, "@"},
+ { oDisableCCID, "disable-ccidc", 0, "@"},
+
+
{0} };
@@ -1392,6 +1413,18 @@ main( int argc, char **argv )
case aPipeMode: set_cmd( &cmd, aPipeMode); break;
case aRebuildKeydbCaches: set_cmd( &cmd, aRebuildKeydbCaches); break;
+#ifdef ENABLE_CARD_SUPPORT
+ case aCardStatus: set_cmd (&cmd, aCardStatus); break;
+ case aCardEdit: set_cmd (&cmd, aCardEdit); break;
+ case aChangePIN: set_cmd (&cmd, aChangePIN); break;
+ case oReaderPort:
+ app_set_default_reader_port (pargs.r.ret_str);
+ break;
+ case octapiDriver: opt.ctapi_driver = pargs.r.ret_str; break;
+ case opcscDriver: opt.pcsc_driver = pargs.r.ret_str; break;
+ case oDisableCCID: opt.disable_ccid = 1; break;
+#endif /* ENABLE_CARD_SUPPORT*/
+
case oArmor: opt.armor = 1; opt.no_armor=0; break;
case oOutput: opt.outfile = pargs.r.ret_str; break;
case oQuiet: opt.quiet = 1; break;
@@ -2828,6 +2861,36 @@ main( int argc, char **argv )
keydb_rebuild_caches ();
break;
+#ifdef ENABLE_CARD_SUPPORT
+ case aCardStatus:
+ if (argc)
+ wrong_args ("--card-status");
+ card_status (stdout);
+ break;
+
+ case aCardEdit:
+ if (argc) {
+ sl = NULL;
+ for (argc--, argv++ ; argc; argc--, argv++)
+ append_to_strlist (&sl, *argv);
+ card_edit (sl);
+ free_strlist (sl);
+ }
+ else
+ card_edit (NULL);
+ break;
+
+ case aChangePIN:
+ if (!argc)
+ change_pin (0);
+ else if (argc == 1)
+ change_pin ( atoi (*argv));
+ else
+ wrong_args ("--change-pin [no]");
+ break;
+#endif /* ENABLE_CARD_SUPPORT*/
+
+
case aListPackets:
opt.list_packets=2;
default:
diff --git a/g10/iso7816.c b/g10/iso7816.c
new file mode 100644
index 000000000..f4308f5b4
--- /dev/null
+++ b/g10/iso7816.c
@@ -0,0 +1,385 @@
+/* iso7816.c - ISO 7816 commands
+ * Copyright (C) 2003 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#ifdef ENABLE_CARD_SUPPORT
+/*
+ Note, that most of this code has been taken from 1.9.x branch
+ and is maintained over there if at all possible. Thus, if you make
+ changes here, please check that a similar change has been commited
+ to the 1.9.x branch.
+*/
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "options.h"
+#include "errors.h"
+#include "memory.h"
+#include "util.h"
+#include "i18n.h"
+
+#include "iso7816.h"
+#include "apdu.h"
+
+
+#define CMD_SELECT_FILE 0xA4
+#define CMD_VERIFY 0x20
+#define CMD_CHANGE_REFERENCE_DATA 0x24
+#define CMD_RESET_RETRY_COUNTER 0x2C
+#define CMD_GET_DATA 0xCA
+#define CMD_PUT_DATA 0xDA
+#define CMD_PSO 0x2A
+#define CMD_INTERNAL_AUTHENTICATE 0x88
+#define CMD_GENERATE_KEYPAIR 0x47
+#define CMD_GET_CHALLENGE 0x84
+
+static gpg_error_t
+map_sw (int sw)
+{
+ gpg_err_code_t ec;
+
+ switch (sw)
+ {
+ case SW_EEPROM_FAILURE: ec = GPG_ERR_HARDWARE; break;
+ case SW_WRONG_LENGTH: ec = GPG_ERR_INV_VALUE; break;
+ case SW_CHV_WRONG: ec = GPG_ERR_BAD_PIN; break;
+ case SW_CHV_BLOCKED: ec = GPG_ERR_PIN_BLOCKED; break;
+ case SW_USE_CONDITIONS: ec = GPG_ERR_USE_CONDITIONS; break;
+ case SW_NOT_SUPPORTED: ec = GPG_ERR_NOT_SUPPORTED; break;
+ case SW_BAD_PARAMETER: ec = GPG_ERR_INV_VALUE; break;
+ case SW_REF_NOT_FOUND: ec = GPG_ERR_NO_OBJ; break;
+ case SW_BAD_P0_P1: ec = GPG_ERR_INV_VALUE; break;
+ case SW_INS_NOT_SUP: ec = GPG_ERR_CARD; break;
+ case SW_CLA_NOT_SUP: ec = GPG_ERR_CARD; break;
+ case SW_SUCCESS: ec = 0; break;
+
+ case SW_HOST_OUT_OF_CORE: ec = GPG_ERR_ENOMEM; break;
+ case SW_HOST_INV_VALUE: ec = GPG_ERR_INV_VALUE; break;
+ case SW_HOST_INCOMPLETE_CARD_RESPONSE: ec = GPG_ERR_CARD; break;
+ default:
+ if ((sw & 0x010000))
+ ec = GPG_ERR_GENERAL; /* Should not happen. */
+ else if ((sw & 0xff00) == SW_MORE_DATA)
+ ec = 0; /* This should actually never been seen here. */
+ else
+ ec = GPG_ERR_CARD;
+ }
+ return gpg_error (ec);
+}
+
+/* This function is specialized version of the SELECT FILE command.
+ SLOT is the card and reader as created for example by
+ apdu_open_reader (), AID is a buffer of size AIDLEN holding the
+ requested application ID. The function can't be used to enumerate
+ AIDs and won't return the AID on success. The return value is 0
+ for okay or GNUPG error code. Note that ISO error codes are
+ internally mapped. */
+gpg_error_t
+iso7816_select_application (int slot, const char *aid, size_t aidlen)
+{
+ int sw;
+
+ sw = apdu_send_simple (slot, 0x00, CMD_SELECT_FILE, 4, 0, aidlen, aid);
+ return map_sw (sw);
+}
+
+
+/* Perform a VERIFY command on SLOT using the card holder verification
+ vector CHVNO with a CHV of lenght CHVLEN. Returns 0 on success. */
+gpg_error_t
+iso7816_verify (int slot, int chvno, const char *chv, size_t chvlen)
+{
+ int sw;
+
+ sw = apdu_send_simple (slot, 0x00, CMD_VERIFY, 0, chvno, chvlen, chv);
+ return map_sw (sw);
+}
+
+/* Perform a CHANGE_REFERENCE_DATA command on SLOT for the card holder
+ verification vector CHVNO. If the OLDCHV is NULL (and OLDCHVLEN
+ 0), a "change reference data" is done, otherwise an "exchange
+ reference data". The new reference data is expected in NEWCHV of
+ length NEWCHVLEN. */
+gpg_error_t
+iso7816_change_reference_data (int slot, int chvno,
+ const char *oldchv, size_t oldchvlen,
+ const char *newchv, size_t newchvlen)
+{
+ int sw;
+ char *buf;
+
+ if ((!oldchv && oldchvlen)
+ || (oldchv && !oldchvlen)
+ || !newchv || !newchvlen )
+ return gpg_error (GPG_ERR_INV_VALUE);
+
+ buf = xtrymalloc (oldchvlen + newchvlen);
+ if (!buf)
+ return out_of_core ();
+ if (oldchvlen)
+ memcpy (buf, oldchv, oldchvlen);
+ memcpy (buf+oldchvlen, newchv, newchvlen);
+
+ sw = apdu_send_simple (slot, 0x00, CMD_CHANGE_REFERENCE_DATA,
+ oldchvlen? 0 : 1, chvno, oldchvlen+newchvlen, buf);
+ xfree (buf);
+ return map_sw (sw);
+
+}
+
+gpg_error_t
+iso7816_reset_retry_counter (int slot, int chvno,
+ const char *newchv, size_t newchvlen)
+{
+ int sw;
+
+ if (!newchv || !newchvlen )
+ return gpg_error (GPG_ERR_INV_VALUE);
+
+ sw = apdu_send_simple (slot, 0x00, CMD_RESET_RETRY_COUNTER,
+ 2, chvno, newchvlen, newchv);
+ return map_sw (sw);
+}
+
+
+/* Perform a GET DATA command requesting TAG and storing the result in
+ a newly allocated buffer at the address passed by RESULT. Return
+ the length of this data at the address of RESULTLEN. */
+gpg_error_t
+iso7816_get_data (int slot, int tag,
+ unsigned char **result, size_t *resultlen)
+{
+ int sw;
+
+ if (!result || !resultlen)
+ return gpg_error (GPG_ERR_INV_VALUE);
+ *result = NULL;
+ *resultlen = 0;
+
+ sw = apdu_send (slot, 0x00, CMD_GET_DATA,
+ ((tag >> 8) & 0xff), (tag & 0xff), -1, NULL,
+ result, resultlen);
+ if (sw != SW_SUCCESS)
+ {
+ /* Make sure that pending buffers are released. */
+ xfree (*result);
+ *result = NULL;
+ *resultlen = 0;
+ return map_sw (sw);
+ }
+
+ return 0;
+}
+
+
+/* Perform a PUT DATA command on card in SLOT. Write DATA of length
+ DATALEN to TAG. */
+gpg_error_t
+iso7816_put_data (int slot, int tag,
+ const unsigned char *data, size_t datalen)
+{
+ int sw;
+
+ sw = apdu_send_simple (slot, 0x00, CMD_PUT_DATA,
+ ((tag >> 8) & 0xff), (tag & 0xff),
+ datalen, data);
+ return map_sw (sw);
+}
+
+
+/* Perform the security operation COMPUTE DIGITAL SIGANTURE. On
+ success 0 is returned and the data is availavle in a newly
+ allocated buffer stored at RESULT with its length stored at
+ RESULTLEN. */
+gpg_error_t
+iso7816_compute_ds (int slot, const unsigned char *data, size_t datalen,
+ unsigned char **result, size_t *resultlen)
+{
+ int sw;
+
+ if (!data || !datalen || !result || !resultlen)
+ return gpg_error (GPG_ERR_INV_VALUE);
+ *result = NULL;
+ *resultlen = 0;
+
+ sw = apdu_send (slot, 0x00, CMD_PSO, 0x9E, 0x9A, datalen, data,
+ result, resultlen);
+ if (sw != SW_SUCCESS)
+ {
+ /* Make sure that pending buffers are released. */
+ xfree (*result);
+ *result = NULL;
+ *resultlen = 0;
+ return map_sw (sw);
+ }
+
+ return 0;
+}
+
+
+/* Perform the security operation DECIPHER. On
+ success 0 is returned and the plaintext is available in a newly
+ allocated buffer stored at RESULT with its length stored at
+ RESULTLEN. */
+gpg_error_t
+iso7816_decipher (int slot, const unsigned char *data, size_t datalen,
+ unsigned char **result, size_t *resultlen)
+{
+ int sw;
+ unsigned char *buf;
+
+ if (!data || !datalen || !result || !resultlen)
+ return gpg_error (GPG_ERR_INV_VALUE);
+ *result = NULL;
+ *resultlen = 0;
+
+ /* We need to prepend the padding indicator. */
+ buf = xtrymalloc (datalen + 1);
+ if (!buf)
+ return out_of_core ();
+ *buf = 0; /* Padding indicator. */
+ memcpy (buf+1, data, datalen);
+ sw = apdu_send (slot, 0x00, CMD_PSO, 0x80, 0x86, datalen+1, buf,
+ result, resultlen);
+ xfree (buf);
+ if (sw != SW_SUCCESS)
+ {
+ /* Make sure that pending buffers are released. */
+ xfree (*result);
+ *result = NULL;
+ *resultlen = 0;
+ return map_sw (sw);
+ }
+
+ return 0;
+}
+
+
+gpg_error_t
+iso7816_internal_authenticate (int slot,
+ const unsigned char *data, size_t datalen,
+ unsigned char **result, size_t *resultlen)
+{
+ int sw;
+
+ if (!data || !datalen || !result || !resultlen)
+ return gpg_error (GPG_ERR_INV_VALUE);
+ *result = NULL;
+ *resultlen = 0;
+
+ sw = apdu_send (slot, 0x00, CMD_INTERNAL_AUTHENTICATE, 0, 0,
+ datalen, data, result, resultlen);
+ if (sw != SW_SUCCESS)
+ {
+ /* Make sure that pending buffers are released. */
+ xfree (*result);
+ *result = NULL;
+ *resultlen = 0;
+ return map_sw (sw);
+ }
+
+ return 0;
+}
+
+
+static gpg_error_t
+do_generate_keypair (int slot, int readonly,
+ const unsigned char *data, size_t datalen,
+ unsigned char **result, size_t *resultlen)
+{
+ int sw;
+
+ if (!data || !datalen || !result || !resultlen)
+ return gpg_error (GPG_ERR_INV_VALUE);
+ *result = NULL;
+ *resultlen = 0;
+
+ sw = apdu_send (slot, 0x00, CMD_GENERATE_KEYPAIR, readonly? 0x81:0x80, 0,
+ datalen, data, result, resultlen);
+ if (sw != SW_SUCCESS)
+ {
+ /* Make sure that pending buffers are released. */
+ xfree (*result);
+ *result = NULL;
+ *resultlen = 0;
+ return map_sw (sw);
+ }
+
+ return 0;
+}
+
+
+gpg_error_t
+iso7816_generate_keypair (int slot,
+ const unsigned char *data, size_t datalen,
+ unsigned char **result, size_t *resultlen)
+{
+ return do_generate_keypair (slot, 0, data, datalen, result, resultlen);
+}
+
+
+gpg_error_t
+iso7816_read_public_key (int slot,
+ const unsigned char *data, size_t datalen,
+ unsigned char **result, size_t *resultlen)
+{
+ return do_generate_keypair (slot, 1, data, datalen, result, resultlen);
+}
+
+
+
+gpg_error_t
+iso7816_get_challenge (int slot, int length, unsigned char *buffer)
+{
+ int sw;
+ unsigned char *result;
+ size_t resultlen, n;
+
+ if (!buffer || length < 1)
+ return gpg_error (GPG_ERR_INV_VALUE);
+
+ do
+ {
+ result = NULL;
+ n = length > 254? 254 : length;
+ sw = apdu_send_le (slot, 0x00, CMD_GET_CHALLENGE, 0, 0, -1, NULL,
+ n,
+ &result, &resultlen);
+ if (sw != SW_SUCCESS)
+ {
+ /* Make sure that pending buffers are released. */
+ xfree (result);
+ return map_sw (sw);
+ }
+ if (resultlen > n)
+ resultlen = n;
+ memcpy (buffer, result, resultlen);
+ buffer += resultlen;
+ length -= resultlen;
+ xfree (result);
+ }
+ while (length > 0);
+
+ return 0;
+}
+
+#endif /*ENABLE_CARD_SUPPORT*/
diff --git a/g10/iso7816.h b/g10/iso7816.h
new file mode 100644
index 000000000..99c4a8ed8
--- /dev/null
+++ b/g10/iso7816.h
@@ -0,0 +1,58 @@
+/* iso7816.h - ISO 7816 commands
+ * Copyright (C) 2003 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#ifndef ISO7816_H
+#define ISO7816_H
+
+#include "cardglue.h"
+
+gpg_error_t iso7816_select_application (int slot,
+ const char *aid, size_t aidlen);
+gpg_error_t iso7816_verify (int slot,
+ int chvno, const char *chv, size_t chvlen);
+gpg_error_t iso7816_change_reference_data (int slot, int chvno,
+ const char *oldchv, size_t oldchvlen,
+ const char *newchv, size_t newchvlen);
+gpg_error_t iso7816_reset_retry_counter (int slot, int chvno,
+ const char *newchv, size_t newchvlen);
+gpg_error_t iso7816_get_data (int slot, int tag,
+ unsigned char **result, size_t *resultlen);
+gpg_error_t iso7816_put_data (int slot, int tag,
+ const unsigned char *data, size_t datalen);
+gpg_error_t iso7816_compute_ds (int slot,
+ const unsigned char *data, size_t datalen,
+ unsigned char **result, size_t *resultlen);
+gpg_error_t iso7816_decipher (int slot,
+ const unsigned char *data, size_t datalen,
+ unsigned char **result, size_t *resultlen);
+gpg_error_t iso7816_internal_authenticate (int slot,
+ const unsigned char *data, size_t datalen,
+ unsigned char **result, size_t *resultlen);
+gpg_error_t iso7816_generate_keypair (int slot,
+ const unsigned char *data, size_t datalen,
+ unsigned char **result, size_t *resultlen);
+gpg_error_t iso7816_read_public_key (int slot,
+ const unsigned char *data, size_t datalen,
+ unsigned char **result, size_t *resultlen);
+gpg_error_t iso7816_get_challenge (int slot,
+ int length, unsigned char *buffer);
+
+
+#endif /*ISO7816_H*/
diff --git a/g10/options.h b/g10/options.h
index 2ac5680d6..c4fd89d52 100644
--- a/g10/options.h
+++ b/g10/options.h
@@ -182,6 +182,13 @@ struct {
int strict;
int mangle_dos_filenames;
int enable_progress_filter;
+
+#ifdef ENABLE_CARD_SUPPORT
+ const char *ctapi_driver; /* Library to access the ctAPI. */
+ const char *pcsc_driver; /* Library to access the PC/SC system. */
+ int disable_ccid; /* Disable the use of the internal CCID driver. */
+#endif /*ENABLE_CARD_SUPPORT*/
+
} opt;
@@ -199,6 +206,7 @@ struct {
#define DBG_TRUST_VALUE 256 /* debug the trustdb */
#define DBG_HASHING_VALUE 512 /* debug hashing operations */
#define DBG_EXTPROG_VALUE 1024 /* debug external program calls */
+#define DBG_CARD_IO_VALUE 2048
#define DBG_PACKET (opt.debug & DBG_PACKET_VALUE)
@@ -207,6 +215,7 @@ struct {
#define DBG_TRUST (opt.debug & DBG_TRUST_VALUE)
#define DBG_HASHING (opt.debug & DBG_HASHING_VALUE)
#define DBG_EXTPROG (opt.debug & DBG_EXTPROG_VALUE)
+#define DBG_CARD_IO (opt.debug & DBG_CARD_IO_VALUE)
#define GNUPG (opt.compliance==CO_GNUPG)
#define RFC1991 (opt.compliance==CO_RFC1991 || opt.compliance==CO_PGP2)