From e369270a655fdab6b78053231c03bc2d82b672a9 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Sun, 28 Sep 2003 13:41:58 +0000 Subject: [PATCH] * 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. --- g10/ChangeLog | 14 +- g10/Makefile.am | 8 +- g10/apdu.c | 1193 +++++++++++++++++++++++++++++++++++++ g10/apdu.h | 74 +++ g10/app-common.h | 76 +++ g10/app-openpgp.c | 1430 +++++++++++++++++++++++++++++++++++++++++++++ g10/card-util.c | 720 +++++++++++++++++++++++ g10/cardglue.c | 433 ++++++++++++++ g10/cardglue.h | 51 ++ g10/ccid-driver.c | 990 +++++++++++++++++++++++++++++++ g10/ccid-driver.h | 74 +++ g10/g10.c | 63 ++ g10/iso7816.c | 385 ++++++++++++ g10/iso7816.h | 58 ++ g10/options.h | 9 + 15 files changed, 5576 insertions(+), 2 deletions(-) create mode 100644 g10/apdu.c create mode 100644 g10/apdu.h create mode 100644 g10/app-common.h create mode 100644 g10/app-openpgp.c create mode 100644 g10/card-util.c create mode 100644 g10/ccid-driver.c create mode 100644 g10/ccid-driver.h create mode 100644 g10/iso7816.c create mode 100644 g10/iso7816.h 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 + + * 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 * 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 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 +#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 +#include +#include +#include +#include +#ifdef HAVE_OPENSC +# include +#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 +#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 +#include +#include +#include +#include +#include + +#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<> 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 +#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 +#include +#include +#include +#include + +#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 #include #include +#include #include #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,12 +82,433 @@ 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); +} + + #endif /*ENABLE_CARD_SUPPORT*/ 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 +#endif + +#if defined(HAVE_LIBUSB) || defined(TEST) + +#define GNUPG_DEFAULT_SCDAEMON 1 /* Hack for 1.3 */ + +#include +#include +#include +#include +#include + +#include + +#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 +#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 +#include +#include +#include + +#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)