From 577d9c2342f99ebf163d35519ab8adc573f26765 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 27 Apr 2004 08:23:45 +0000 Subject: [PATCH] A bunch of changes for the openpgp card. --- g10/ChangeLog | 65 +++++ g10/Makefile.am | 2 +- g10/apdu.c | 717 ++++++++++++++++++++++++++++++++++++++++++++-- g10/apdu.h | 16 +- g10/app-common.h | 84 ++++-- g10/app-openpgp.c | 400 +++++++++++++++++--------- g10/card-util.c | 29 +- g10/cardglue.c | 2 +- g10/cardglue.h | 17 +- g10/ccid-driver.c | 297 +++++++++++++++---- g10/ccid-driver.h | 1 + g10/g10.c | 8 + g10/iso7816.c | 272 ++++++++++++++++-- g10/iso7816.h | 13 + g10/options.h | 1 + g10/status.c | 1 + g10/status.h | 1 + g10/tlv.c | 209 ++++++++++++++ g10/tlv.h | 86 ++++++ 19 files changed, 1958 insertions(+), 263 deletions(-) create mode 100644 g10/tlv.c create mode 100644 g10/tlv.h diff --git a/g10/ChangeLog b/g10/ChangeLog index 518983b8d..07c57d8d0 100644 --- a/g10/ChangeLog +++ b/g10/ChangeLog @@ -1,3 +1,63 @@ +2004-04-27 Werner Koch + + * g10.c: New commands --allow-admin and --deny-admin. + * options.h (opt): Add member ALLOW_ADMIN. + + * tlv.h, tlv.c: New. Copied from gnupg-1.9. + * cardglue.c (open_card): The serialno is now set internally by + app_select_openpgp; changed invocation. + * cardglue.h (app_t, ctrl_t): New. + (GPG_ERR_EBUSY, GPG_ERR_ENOENT, GPG_ERR_NOT_FOUND, GPG_ERR_BUG) + (GPG_ERR_NOT_IMPLEMENTED, GPG_ERR_EACCESS): New. + (gpg_err_code_from_errno): New. + + * app-common.h, app-openpgp.c, iso7816.c, iso7816.h + * apdu.c, apdu.h, ccid-driver.c, ccid-driver.h + * card-util.c: Updated from current gnupg-1.9. + + Changes are: + + * app-common.h: New members FNC.DEINIT and APP_LOCAL. + * app-openpgp.c (do_deinit): New. + (get_cached_data, flush_cache_item, flush_cache_after_error) + (flush_cache): New. + (get_one_do): Replaced arg SLOT by APP. Make used of cached data. + (verify_chv2, verify_chv3): Flush some cache item after error. + (do_change_pin): Ditto. + (do_sign): Ditto. + (do_setattr): Flush cache item. + (do_genkey): Flush the entire cache. + (compare_fingerprint): Use cached data. + + * apdu.c (apdu_send_le): Reinitialize RESULTLEN. Handle + SW_EOF_REACHED like SW_SUCCESS. + + * ccid-driver.c (parse_ccid_descriptor): Store some of the reader + features away. New arg HANDLE + (read_device_info): New arg HANDLE. Changed caller. + (bulk_in): Handle time extension requests. + (ccid_get_atr): Setup parameters and the IFSD. + (compute_edc): New. Factored out code. + (ccid_transceive): Use default NADs when required. + + * apdu.h: New pseudo stati SW_HOST_NOT_SUPPORTED, + SW_HOST_LOCKING_FAILED and SW_HOST_BUSY. + * iso7816.c (map_sw): Map it. + + * ccid-driver.c (ccid_slot_status): Add arg STATUSBITS. + * apdu.c (apdu_get_status): New. + (ct_get_status, pcsc_get_status, ocsc_get_status): New stubs. + (get_status_ccid): New. + (apdu_reset): New. + (reset_ct_reader, reset_pcsc_reader, reset_osc_reader): New stubs. + (reset_ccid_reader): New. + (apdu_enum_reader): New. + + * apdu.c (lock_slot, trylock_slot, unlock_slot): New helpers. + (new_reader_slot) [USE_GNU_PTH]: Init mutex. + (apdu_reset, apdu_get_status, apdu_send_le): Run functions + in locked mode. + 2004-04-25 David Shaw * getkey.c (get_seckey_byname2): Significantly simplify this @@ -131,6 +191,11 @@ * options.h: Encapsulate keyserver details. Change all callers. +2004-04-05 Werner Koch + + * status.h (STATUS_NEWSIG): New. + * status.c (get_status_string): Add it. + 2004-03-27 David Shaw * keyedit.c (keyedit_menu): Request a trustdb update when adding a diff --git a/g10/Makefile.am b/g10/Makefile.am index 3f08d6204..2c5d82020 100644 --- a/g10/Makefile.am +++ b/g10/Makefile.am @@ -78,7 +78,7 @@ card_support_source_scd = \ iso7816.c iso7816.h \ apdu.c apdu.h \ ccid-driver.c ccid-driver.h -card_support_source_local = cardglue.c cardglue.h +card_support_source_local = cardglue.c cardglue.h tlv.c tlv.h else card_support_source_g10 = card_support_source_scd = diff --git a/g10/apdu.c b/g10/apdu.c index 27304c8b5..42b337238 100644 --- a/g10/apdu.c +++ b/g10/apdu.c @@ -1,5 +1,5 @@ /* apdu.c - ISO 7816 APDU functions and low level I/O - * Copyright (C) 2003 Free Software Foundation, Inc. + * Copyright (C) 2003, 2004 Free Software Foundation, Inc. * * This file is part of GnuPG. * @@ -24,11 +24,18 @@ #include #include #include +#ifdef USE_GNU_PTH +# include +# include +# include +#endif #ifdef HAVE_OPENSC # include #endif -#if GNUPG_MAJOR_VERSION == 1 +#if defined(GNUPG_SCD_MAIN_HEADER) +#include GNUPG_SCD_MAIN_HEADER +#elif GNUPG_MAJOR_VERSION == 1 /* This is used with GnuPG version < 1.9. The code has been source copied from the current GnuPG >= 1.9 and is maintained over there. */ @@ -46,6 +53,11 @@ #include "dynload.h" #include "ccid-driver.h" +#ifdef USE_GNU_PTH +#define NEED_PCSC_WRAPPER 1 +#endif + + #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). */ @@ -57,6 +69,12 @@ #define DLSTDCALL #endif +#ifdef _POSIX_OPEN_MAX +#define MAX_OPEN_FDS _POSIX_OPEN_MAX +#else +#define MAX_OPEN_FDS 20 +#endif + /* A structure to collect information pertaining to one reader slot. */ @@ -72,6 +90,11 @@ struct reader_table_s { unsigned long context; unsigned long card; unsigned long protocol; +#ifdef NEED_PCSC_WRAPPER + int req_fd; + int rsp_fd; + pid_t pid; +#endif /*NEED_PCSC_WRAPPER*/ } pcsc; #ifdef HAVE_OPENSC int is_osc; /* We are using the OpenSC driver layer. */ @@ -83,6 +106,11 @@ struct reader_table_s { int status; unsigned char atr[33]; size_t atrlen; + unsigned int change_counter; +#ifdef USE_GNU_PTH + int lock_initialized; + pth_mutex_t lock; +#endif }; typedef struct reader_table_s *reader_table_t; @@ -183,11 +211,27 @@ new_reader_slot (void) log_error ("new_reader_slot: out of slots\n"); return -1; } +#ifdef USE_GNU_PTH + if (!reader_table[reader].lock_initialized) + { + if (!pth_mutex_init (&reader_table[reader].lock)) + { + log_error ("error initializing mutex: %s\n", strerror (errno)); + return -1; + } + reader_table[reader].lock_initialized = 1; + } +#endif /*USE_GNU_PTH*/ 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 +#ifdef NEED_PCSC_WRAPPER + reader_table[reader].pcsc.req_fd = -1; + reader_table[reader].pcsc.rsp_fd = -1; + reader_table[reader].pcsc.pid = (pid_t)(-1); #endif return reader; } @@ -368,6 +412,18 @@ close_ct_reader (int slot) return 0; } +static int +reset_ct_reader (int slot) +{ + return SW_HOST_NOT_SUPPORTED; +} + + +static int +ct_get_status (int slot, unsigned int *status) +{ + return SW_HOST_NOT_SUPPORTED; +} /* Actually send the APDU of length APDULEN to SLOT and return a maximum of *BUFLEN data in BUFFER, the actual retruned size will be @@ -395,6 +451,66 @@ ct_send_apdu (int slot, unsigned char *apdu, size_t apdulen, +#ifdef NEED_PCSC_WRAPPER +static int +writen (int fd, const void *buf, size_t nbytes) +{ + size_t nleft = nbytes; + int nwritten; + +/* log_printhex (" writen:", buf, nbytes); */ + + while (nleft > 0) + { +#ifdef USE_GNU_PTH + nwritten = pth_write (fd, buf, nleft); +#else + nwritten = write (fd, buf, nleft); +#endif + if (nwritten < 0 && errno == EINTR) + continue; + if (nwritten < 0) + return -1; + nleft -= nwritten; + buf = (const char*)buf + nwritten; + } + return 0; +} + +/* Read up to BUFLEN bytes from FD and return the number of bytes + actually read in NREAD. Returns -1 on error or 0 on success. */ +static int +readn (int fd, void *buf, size_t buflen, size_t *nread) +{ + size_t nleft = buflen; + int n; +/* void *orig_buf = buf; */ + + while (nleft > 0) + { +#ifdef USE_GNU_PTH + n = pth_read (fd, buf, nleft); +#else + n = read (fd, buf, nleft); +#endif + if (n < 0 && errno == EINTR) + continue; + if (n < 0) + return -1; /* read error. */ + if (!n) + break; /* EOF */ + nleft -= n; + buf = (char*)buf + n; + } + if (nread) + *nread = buflen - nleft; + +/* log_printhex (" readn:", orig_buf, *nread); */ + + return 0; +} +#endif /*NEED_PCSC_WRAPPER*/ + static const char * pcsc_error_string (long err) { @@ -455,6 +571,172 @@ pcsc_error_string (long err) static int open_pcsc_reader (const char *portstr) { +#ifdef NEED_PCSC_WRAPPER +/* Open the PC/SC reader using the pcsc_wrapper program. This is + needed to cope with different thread models and other peculiarities + of libpcsclite. */ + int slot; + reader_table_t slotp; + int fd, rp[2], wp[2]; + int n, i; + pid_t pid; + size_t len; + unsigned char msgbuf[9]; + int err; + + slot = new_reader_slot (); + if (slot == -1) + return -1; + slotp = reader_table + slot; + + /* Fire up the pcsc wrapper. We don't use any fork/exec code from + the common directy but implement it direclty so that this file + may still be source copied. */ + + if (pipe (rp) == -1) + { + log_error ("error creating a pipe: %s\n", strerror (errno)); + slotp->used = 0; + return -1; + } + if (pipe (wp) == -1) + { + log_error ("error creating a pipe: %s\n", strerror (errno)); + close (rp[0]); + close (rp[1]); + slotp->used = 0; + return -1; + } + + pid = fork (); + if (pid == -1) + { + log_error ("error forking process: %s\n", strerror (errno)); + close (rp[0]); + close (rp[1]); + close (wp[0]); + close (wp[1]); + slotp->used = 0; + return -1; + } + slotp->pcsc.pid = pid; + + if (!pid) + { /* + === Child === + */ + + /* Double fork. */ + pid = fork (); + if (pid == -1) + _exit (31); + if (pid) + _exit (0); /* Immediate exit this parent, so that the child + gets cleaned up by the init process. */ + + /* Connect our pipes. */ + if (wp[0] != 0 && dup2 (wp[0], 0) == -1) + log_fatal ("dup2 stdin failed: %s\n", strerror (errno)); + if (rp[1] != 1 && dup2 (rp[1], 1) == -1) + log_fatal ("dup2 stdout failed: %s\n", strerror (errno)); + + /* Send stderr to the bit bucket. */ + fd = open ("/dev/null", O_WRONLY); + if (fd == -1) + log_fatal ("can't open `/dev/null': %s", strerror (errno)); + if (fd != 2 && dup2 (fd, 2) == -1) + log_fatal ("dup2 stderr failed: %s\n", strerror (errno)); + + /* Close all other files. */ + n = sysconf (_SC_OPEN_MAX); + if (n < 0) + n = MAX_OPEN_FDS; + for (i=3; i < n; i++) + close(i); + errno = 0; + + execl (GNUPG_LIBDIR "/pcsc-wrapper", + "pcsc-wrapper", + "--", + "1", /* API version */ + opt.pcsc_driver, /* Name of the PC/SC library. */ + NULL); + _exit (31); + } + + /* + === Parent === + */ + close (wp[0]); + close (rp[1]); + slotp->pcsc.req_fd = wp[1]; + slotp->pcsc.rsp_fd = rp[0]; + + /* Wait for the intermediate child to terminate. */ + while ( (i=pth_waitpid (pid, NULL, 0)) == -1 && errno == EINTR) + ; + + /* Now send the open request. */ + msgbuf[0] = 0x01; /* OPEN command. */ + len = portstr? strlen (portstr):0; + msgbuf[1] = (len >> 24); + msgbuf[2] = (len >> 16); + msgbuf[3] = (len >> 8); + msgbuf[4] = (len ); + if ( writen (slotp->pcsc.req_fd, msgbuf, 5) + || (portstr && writen (slotp->pcsc.req_fd, portstr, len))) + { + log_error ("error sending PC/SC OPEN request: %s\n", + strerror (errno)); + goto command_failed; + } + /* Read the response. */ + if ((i=readn (slotp->pcsc.rsp_fd, msgbuf, 9, &len)) || len != 9) + { + log_error ("error receiving PC/SC OPEN response: %s\n", + i? strerror (errno) : "premature EOF"); + goto command_failed; + } + len = (msgbuf[1] << 24) | (msgbuf[2] << 16) | (msgbuf[3] << 8 ) | msgbuf[4]; + if (msgbuf[0] != 0x81 || len < 4) + { + log_error ("invalid response header from PC/SC received\n"); + goto command_failed; + } + len -= 4; /* Already read the error code. */ + if (len > DIM (slotp->atr)) + { + log_error ("PC/SC returned a too large ATR (len=%x)\n", len); + goto command_failed; + } + err = (msgbuf[5] << 24) | (msgbuf[6] << 16) | (msgbuf[7] << 8 ) | msgbuf[8]; + if (err) + { + log_error ("PC/SC OPEN failed: %s\n", pcsc_error_string (err)); + goto command_failed; + } + n = len; + if ((i=readn (slotp->pcsc.rsp_fd, slotp->atr, n, &len)) || len != n) + { + log_error ("error receiving PC/SC OPEN response: %s\n", + i? strerror (errno) : "premature EOF"); + goto command_failed; + } + slotp->atrlen = len; + + dump_reader_status (slot); + return slot; + + command_failed: + close (slotp->pcsc.req_fd); + close (slotp->pcsc.rsp_fd); + slotp->pcsc.req_fd = -1; + slotp->pcsc.rsp_fd = -1; + kill (slotp->pcsc.pid, SIGTERM); + slotp->pcsc.pid = (pid_t)(-1); + slotp->used = 0; + return -1; +#else /*!NEED_PCSC_WRAPPER */ long err; int slot; char *list = NULL; @@ -557,9 +839,16 @@ open_pcsc_reader (const char *portstr) dump_reader_status (slot); return slot; +#endif /*!NEED_PCSC_WRAPPER */ } +static int +pcsc_get_status (int slot, unsigned int *status) +{ + return SW_HOST_NOT_SUPPORTED; +} + /* 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. */ @@ -567,6 +856,109 @@ static int pcsc_send_apdu (int slot, unsigned char *apdu, size_t apdulen, unsigned char *buffer, size_t *buflen) { +#ifdef NEED_PCSC_WRAPPER + long err; + reader_table_t slotp; + size_t len, full_len; + int i, n; + unsigned char msgbuf[9]; + + if (DBG_CARD_IO) + log_printhex (" PCSC_data:", apdu, apdulen); + + slotp = reader_table + slot; + + if (slotp->pcsc.req_fd == -1 + || slotp->pcsc.rsp_fd == -1 + || slotp->pcsc.pid == (pid_t)(-1) ) + { + log_error ("pcsc_send_apdu: pcsc-wrapper not running\n"); + return -1; + } + + msgbuf[0] = 0x03; /* TRANSMIT command. */ + len = apdulen; + msgbuf[1] = (len >> 24); + msgbuf[2] = (len >> 16); + msgbuf[3] = (len >> 8); + msgbuf[4] = (len ); + if ( writen (slotp->pcsc.req_fd, msgbuf, 5) + || writen (slotp->pcsc.req_fd, apdu, len)) + { + log_error ("error sending PC/SC TRANSMIT request: %s\n", + strerror (errno)); + goto command_failed; + } + + /* Read the response. */ + if ((i=readn (slotp->pcsc.rsp_fd, msgbuf, 9, &len)) || len != 9) + { + log_error ("error receiving PC/SC TRANSMIT response: %s\n", + i? strerror (errno) : "premature EOF"); + goto command_failed; + } + len = (msgbuf[1] << 24) | (msgbuf[2] << 16) | (msgbuf[3] << 8 ) | msgbuf[4]; + if (msgbuf[0] != 0x81 || len < 4) + { + log_error ("invalid response header from PC/SC received\n"); + goto command_failed; + } + len -= 4; /* Already read the error code. */ + err = (msgbuf[5] << 24) | (msgbuf[6] << 16) | (msgbuf[7] << 8 ) | msgbuf[8]; + if (err) + { + log_error ("pcsc_transmit failed: %s (0x%lx)\n", + pcsc_error_string (err), err); + return -1; + } + + full_len = len; + + n = *buflen < len ? *buflen : len; + if ((i=readn (slotp->pcsc.rsp_fd, buffer, n, &len)) || len != n) + { + log_error ("error receiving PC/SC TRANSMIT response: %s\n", + i? strerror (errno) : "premature EOF"); + goto command_failed; + } + *buflen = n; + + full_len -= len; + if (full_len) + { + log_error ("pcsc_send_apdu: provided buffer too short - truncated\n"); + err = -1; + } + /* We need to read any rest of the response, to keep the + protocol runnng. */ + while (full_len) + { + unsigned char dummybuf[128]; + + n = full_len < DIM (dummybuf) ? full_len : DIM (dummybuf); + if ((i=readn (slotp->pcsc.rsp_fd, dummybuf, n, &len)) || len != n) + { + log_error ("error receiving PC/SC TRANSMIT response: %s\n", + i? strerror (errno) : "premature EOF"); + goto command_failed; + } + full_len -= n; + } + + return err; + + command_failed: + close (slotp->pcsc.req_fd); + close (slotp->pcsc.rsp_fd); + slotp->pcsc.req_fd = -1; + slotp->pcsc.rsp_fd = -1; + kill (slotp->pcsc.pid, SIGTERM); + slotp->pcsc.pid = (pid_t)(-1); + slotp->used = 0; + return -1; + +#else /*!NEED_PCSC_WRAPPER*/ + long err; struct pcsc_io_request_s send_pci; unsigned long recv_len; @@ -589,14 +981,87 @@ pcsc_send_apdu (int slot, unsigned char *apdu, size_t apdulen, pcsc_error_string (err), err); return err? -1:0; /* FIXME: Return appropriate error code. */ +#endif /*!NEED_PCSC_WRAPPER*/ } + static int close_pcsc_reader (int slot) { +#ifdef NEED_PCSC_WRAPPER + long err; + reader_table_t slotp; + size_t len; + int i; + unsigned char msgbuf[9]; + + slotp = reader_table + slot; + + if (slotp->pcsc.req_fd == -1 + || slotp->pcsc.rsp_fd == -1 + || slotp->pcsc.pid == (pid_t)(-1) ) + { + log_error ("close_pcsc_reader: pcsc-wrapper not running\n"); + return 0; + } + + msgbuf[0] = 0x02; /* CLOSE command. */ + len = 0; + msgbuf[1] = (len >> 24); + msgbuf[2] = (len >> 16); + msgbuf[3] = (len >> 8); + msgbuf[4] = (len ); + if ( writen (slotp->pcsc.req_fd, msgbuf, 5) ) + { + log_error ("error sending PC/SC CLOSE request: %s\n", + strerror (errno)); + goto command_failed; + } + + /* Read the response. */ + if ((i=readn (slotp->pcsc.rsp_fd, msgbuf, 9, &len)) || len != 9) + { + log_error ("error receiving PC/SC CLOSE response: %s\n", + i? strerror (errno) : "premature EOF"); + goto command_failed; + } + len = (msgbuf[1] << 24) | (msgbuf[2] << 16) | (msgbuf[3] << 8 ) | msgbuf[4]; + if (msgbuf[0] != 0x81 || len < 4) + { + log_error ("invalid response header from PC/SC received\n"); + goto command_failed; + } + len -= 4; /* Already read the error code. */ + err = (msgbuf[5] << 24) | (msgbuf[6] << 16) | (msgbuf[7] << 8 ) | msgbuf[8]; + if (err) + log_error ("pcsc_close failed: %s (0x%lx)\n", + pcsc_error_string (err), err); + + /* We will the wrapper in any case - errors are merely + informational. */ + + command_failed: + close (slotp->pcsc.req_fd); + close (slotp->pcsc.rsp_fd); + slotp->pcsc.req_fd = -1; + slotp->pcsc.rsp_fd = -1; + kill (slotp->pcsc.pid, SIGTERM); + slotp->pcsc.pid = (pid_t)(-1); + slotp->used = 0; + return 0; + +#else /*!NEED_PCSC_WRAPPER*/ + pcsc_release_context (reader_table[slot].pcsc.context); reader_table[slot].used = 0; return 0; +#endif /*!NEED_PCSC_WRAPPER*/ +} + +static int +reset_pcsc_reader (int slot) +{ + return SW_HOST_NOT_SUPPORTED; } @@ -659,6 +1124,46 @@ close_ccid_reader (int slot) } +static int +reset_ccid_reader (int slot) +{ + int err; + reader_table_t slotp = reader_table + slot; + unsigned char atr[33]; + size_t atrlen; + + err = ccid_get_atr (slotp->ccid.handle, atr, sizeof atr, &atrlen); + if (err) + return -1; + /* If the reset was successful, update the ATR. */ + assert (sizeof slotp->atr >= sizeof atr); + slotp->atrlen = atrlen; + memcpy (slotp->atr, atr, atrlen); + dump_reader_status (slot); + return 0; +} + + +static int +get_status_ccid (int slot, unsigned int *status) +{ + int rc; + int bits; + + rc = ccid_slot_status (reader_table[slot].ccid.handle, &bits); + if (rc) + return -1; + + if (bits == 0) + *status = 1|2|4; + else if (bits == 1) + *status = 2; + else + *status = 0; + + return 0; +} + /* Actually send the APDU of length APDULEN to SLOT and return a maximum of *BUFLEN data in BUFFER, the actual returned size will be @@ -796,6 +1301,18 @@ close_osc_reader (int slot) return 0; } +static int +reset_osc_reader (int slot) +{ + return SW_HOST_NOT_SUPPORTED; +} + + +static int +ocsc_get_status (int slot, unsigned int *status) +{ + return SW_HOST_NOT_SUPPORTED; +} /* Actually send the APDU of length APDULEN to SLOT and return a @@ -894,6 +1411,45 @@ osc_send_apdu (int slot, unsigned char *apdu, size_t apdulen, Driver Access */ + +static int +lock_slot (int slot) +{ +#ifdef USE_GNU_PTH + if (!pth_mutex_acquire (&reader_table[slot].lock, 0, NULL)) + { + log_error ("failed to acquire apdu lock: %s\n", strerror (errno)); + return SW_HOST_LOCKING_FAILED; + } +#endif /*USE_GNU_PTH*/ + return 0; +} + +static int +trylock_slot (int slot) +{ +#ifdef USE_GNU_PTH + if (!pth_mutex_acquire (&reader_table[slot].lock, TRUE, NULL)) + { + if (errno == EBUSY) + return SW_HOST_BUSY; + log_error ("failed to acquire apdu lock: %s\n", strerror (errno)); + return SW_HOST_LOCKING_FAILED; + } +#endif /*USE_GNU_PTH*/ + return 0; +} + +static void +unlock_slot (int slot) +{ +#ifdef USE_GNU_PTH + if (!pth_mutex_release (&reader_table[slot].lock)) + log_error ("failed to release apdu lock: %s\n", strerror (errno)); +#endif /*USE_GNU_PTH*/ +} + + /* 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 @@ -935,7 +1491,7 @@ apdu_open_reader (const char *portstr) handle = dlopen (opt.ctapi_driver, RTLD_LAZY); if (!handle) { - log_error ("apdu_open_reader: failed to open driver: %s", + log_error ("apdu_open_reader: failed to open driver: %s\n", dlerror ()); return -1; } @@ -957,12 +1513,13 @@ apdu_open_reader (const char *portstr) /* No ctAPI configured, so lets try the PC/SC API */ if (!pcsc_api_loaded) { +#ifndef NEED_PCSC_WRAPPER void *handle; handle = dlopen (opt.pcsc_driver, RTLD_LAZY); if (!handle) { - log_error ("apdu_open_reader: failed to open driver `%s': %s", + log_error ("apdu_open_reader: failed to open driver `%s': %s\n", opt.pcsc_driver, dlerror ()); return -1; } @@ -1018,9 +1575,10 @@ apdu_open_reader (const char *portstr) dlclose (handle); return -1; } +#endif /*!NEED_PCSC_WRAPPER*/ pcsc_api_loaded = 1; } - + return open_pcsc_reader (portstr); } @@ -1044,6 +1602,47 @@ apdu_close_reader (int slot) return close_pcsc_reader (slot); } +/* Enumerate all readers and return information on whether this reader + is in use. The caller should start with SLOT set to 0 and + increment it with each call until an error is returned. */ +int +apdu_enum_reader (int slot, int *used) +{ + if (slot < 0 || slot >= MAX_READER) + return SW_HOST_NO_DRIVER; + *used = reader_table[slot].used; + return 0; +} + +/* Do a reset for the card in reader at SLOT. */ +int +apdu_reset (int slot) +{ + int sw; + + if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) + return SW_HOST_NO_DRIVER; + + if ((sw = lock_slot (slot))) + return sw; + + if (reader_table[slot].is_ctapi) + sw = reset_ct_reader (slot); +#ifdef HAVE_LIBUSB + else if (reader_table[slot].is_ccid) + sw = reset_ccid_reader (slot); +#endif +#ifdef HAVE_OPENSC + else if (reader_table[slot].is_osc) + sw = reset_osc_reader (slot); +#endif + else + sw = reset_pcsc_reader (slot); + + unlock_slot (slot); + return sw; +} + unsigned char * apdu_get_atr (int slot, size_t *atrlen) @@ -1060,6 +1659,7 @@ apdu_get_atr (int slot, size_t *atrlen) *atrlen = reader_table[slot].atrlen; return buf; } + static const char * @@ -1082,7 +1682,62 @@ error_string (int slot, long rc) } -/* Dispatcher for the actual send_apdu fucntion. */ +/* Retrieve the status for SLOT. The function does obnly wait fot the + card to become available if HANG is set to true. On success the + bits in STATUS will be set to + + bit 0 = card present and usable + bit 1 = card present + bit 2 = card active + bit 3 = card access locked [not yet implemented] + + For must application, tetsing bit 0 is sufficient. + + CHANGED will receive the value of the counter tracking the number + of card insertions. This value may be used to detect a card + change. +*/ +int +apdu_get_status (int slot, int hang, + unsigned int *status, unsigned int *changed) +{ + int sw; + unsigned int s; + + if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) + return SW_HOST_NO_DRIVER; + + if ((sw = hang? lock_slot (slot) : trylock_slot (slot))) + return sw; + + if (reader_table[slot].is_ctapi) + sw = ct_get_status (slot, &s); +#ifdef HAVE_LIBUSB + else if (reader_table[slot].is_ccid) + sw = get_status_ccid (slot, &s); +#endif +#ifdef HAVE_OPENSC + else if (reader_table[slot].is_osc) + sw = osc_get_status (slot, &s); +#endif + else + sw = pcsc_get_status (slot, &s); + + unlock_slot (slot); + + if (sw) + return sw; + + if (status) + *status = s; + if (changed) + *changed = reader_table[slot].change_counter; + return 0; +} + + +/* Dispatcher for the actual send_apdu function. Note, that this + function should be called in locked state. */ static int send_apdu (int slot, unsigned char *apdu, size_t apdulen, unsigned char *buffer, size_t *buflen) @@ -1117,13 +1772,18 @@ 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; +#define RESULTLEN 256 + unsigned char result[RESULTLEN+10]; /* 10 extra in case of bugs in + the driver. */ + size_t resultlen; unsigned char apdu[5+256+1]; size_t apdulen; int sw; long rc; /* we need a long here due to PC/SC. */ + if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) + return SW_HOST_NO_DRIVER; + 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); @@ -1135,6 +1795,9 @@ apdu_send_le(int slot, int class, int ins, int p0, int p1, if ((!data && lc != -1) || (data && lc == -1)) return SW_HOST_INV_VALUE; + if ((sw = lock_slot (slot))) + return sw; + apdulen = 0; apdu[apdulen++] = class; apdu[apdulen++] = ins; @@ -1151,11 +1814,13 @@ apdu_send_le(int slot, int class, int ins, int p0, int p1, assert (sizeof (apdu) >= apdulen); /* As safeguard don't pass any garbage from the stack to the driver. */ memset (apdu+apdulen, 0, sizeof (apdu) - apdulen); + resultlen = RESULTLEN; 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)); + unlock_slot (slot); return SW_HOST_INCOMPLETE_CARD_RESPONSE; } sw = (result[resultlen-2] << 8) | result[resultlen-1]; @@ -1168,13 +1833,16 @@ apdu_send_le(int slot, int class, int ins, int p0, int p1, log_printhex (" dump: ", result, resultlen); } - if (sw == SW_SUCCESS) + if (sw == SW_SUCCESS || sw == SW_EOF_REACHED) { if (retbuf) { *retbuf = xtrymalloc (resultlen? resultlen : 1); if (!*retbuf) - return SW_HOST_OUT_OF_CORE; + { + unlock_slot (slot); + return SW_HOST_OUT_OF_CORE; + } *retbuflen = resultlen; memcpy (*retbuf, result, resultlen); } @@ -1190,7 +1858,10 @@ apdu_send_le(int slot, int class, int ins, int p0, int p1, { *retbuf = p = xtrymalloc (bufsize); if (!*retbuf) - return SW_HOST_OUT_OF_CORE; + { + unlock_slot (slot); + return SW_HOST_OUT_OF_CORE; + } assert (resultlen < bufsize); memcpy (p, result, resultlen); p += resultlen; @@ -1200,20 +1871,23 @@ apdu_send_le(int slot, int class, int ins, int p0, int p1, { int len = (sw & 0x00ff); - log_debug ("apdu_send_simple(%d): %d more bytes available\n", - slot, len); + if (DBG_CARD_IO) + 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 */ + apdu[apdulen++] = len; memset (apdu+apdulen, 0, sizeof (apdu) - apdulen); + resultlen = RESULTLEN; 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)); + unlock_slot (slot); return SW_HOST_INCOMPLETE_CARD_RESPONSE; } sw = (result[resultlen-2] << 8) | result[resultlen-1]; @@ -1225,16 +1899,21 @@ apdu_send_le(int slot, int class, int ins, int p0, int p1, log_printhex (" dump: ", result, resultlen); } - if ((sw & 0xff00) == SW_MORE_DATA || sw == SW_SUCCESS) + if ((sw & 0xff00) == SW_MORE_DATA + || sw == SW_SUCCESS + || sw == SW_EOF_REACHED ) { - if (retbuf) + if (retbuf && resultlen) { if (p - *retbuf + resultlen > bufsize) { bufsize += resultlen > 4096? resultlen: 4096; tmp = xtryrealloc (*retbuf, bufsize); if (!tmp) - return SW_HOST_OUT_OF_CORE; + { + unlock_slot (slot); + return SW_HOST_OUT_OF_CORE; + } p = tmp + (p - *retbuf); *retbuf = tmp; } @@ -1257,10 +1936,14 @@ apdu_send_le(int slot, int class, int ins, int p0, int p1, *retbuf = tmp; } } + + unlock_slot (slot); + if (DBG_CARD_IO && retbuf && sw == SW_SUCCESS) log_printhex (" dump: ", *retbuf, *retbuflen); return sw; +#undef RESULTLEN } /* Send an APDU to the card in SLOT. The APDU is created from all diff --git a/g10/apdu.h b/g10/apdu.h index 21e2b9840..f74bab7fe 100644 --- a/g10/apdu.h +++ b/g10/apdu.h @@ -27,13 +27,16 @@ enum { SW_MORE_DATA = 0x6100, /* Note: that the low byte must be masked of.*/ + SW_EOF_REACHED = 0x6282, 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_NOT_SUPPORTED = 0x6a81, + SW_FILE_NOT_FOUND = 0x6a82, + SW_RECORD_NOT_FOUND = 0x6a83, SW_REF_NOT_FOUND = 0x6a88, SW_BAD_P0_P1 = 0x6b00, SW_INS_NOT_SUP = 0x6d00, @@ -45,9 +48,12 @@ enum { 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_INV_VALUE = 0x10002, SW_HOST_INCOMPLETE_CARD_RESPONSE = 0x10003, - SW_HOST_NO_DRIVER = 0x10004 + SW_HOST_NO_DRIVER = 0x10004, + SW_HOST_NOT_SUPPORTED = 0x10005, + SW_HOST_LOCKING_FAILED= 0x10006, + SW_HOST_BUSY = 0x10007 }; @@ -55,10 +61,14 @@ enum { /* Note , that apdu_open_reader returns no status word but -1 on error. */ int apdu_open_reader (const char *portstr); int apdu_close_reader (int slot); +int apdu_enum_reader (int slot, int *used); unsigned char *apdu_get_atr (int slot, size_t *atrlen); /* The apdu send functions do return status words. */ +int apdu_reset (int slot); +int apdu_get_status (int slot, int hang, + unsigned int *status, unsigned int *changed); 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, diff --git a/g10/app-common.h b/g10/app-common.h index de1e02cac..c61bcca60 100644 --- a/g10/app-common.h +++ b/g10/app-common.h @@ -21,6 +21,12 @@ #ifndef GNUPG_SCD_APP_COMMON_H #define GNUPG_SCD_APP_COMMON_H +#if GNUPG_MAJOR_VERSION != 1 +#include +#endif + +struct app_local_s; /* Defined by all app-*.c. */ + struct app_ctx_s { int initialized; /* The application has been initialied and the function pointers may be used. Note that for @@ -29,111 +35,131 @@ struct app_ctx_s { int slot; /* Used reader. */ unsigned char *serialno; /* Serialnumber in raw form, allocated. */ size_t serialnolen; /* Length in octets of serialnumber. */ + const char *apptype; unsigned int card_version; int did_chv1; int force_chv1; /* True if the card does not cache CHV1. */ int did_chv2; int did_chv3; + struct app_local_s *app_local; /* Local to the application. */ struct { - int (*learn_status) (APP app, CTRL ctrl); - int (*getattr) (APP app, CTRL ctrl, const char *name); - int (*setattr) (APP app, const char *name, + void (*deinit) (app_t app); + int (*learn_status) (app_t app, ctrl_t ctrl); + int (*readcert) (app_t app, const char *certid, + unsigned char **cert, size_t *certlen); + int (*getattr) (app_t app, ctrl_t ctrl, const char *name); + int (*setattr) (app_t app, const char *name, int (*pincb)(void*, const char *, char **), void *pincb_arg, const unsigned char *value, size_t valuelen); - int (*sign) (APP app, + int (*sign) (app_t 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 (*auth) (app_t 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 (*decipher) (app_t 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, + int (*genkey) (app_t app, ctrl_t ctrl, const char *keynostr, unsigned int flags, int (*pincb)(void*, const char *, char **), void *pincb_arg); - int (*change_pin) (APP app, CTRL ctrl, + int (*change_pin) (app_t app, ctrl_t ctrl, const char *chvnostr, int reset_mode, int (*pincb)(void*, const char *, char **), void *pincb_arg); - int (*check_pin) (APP app, const char *keyidstr, + int (*check_pin) (app_t app, const char *keyidstr, int (pincb)(void*, const char *, char **), void *pincb_arg); } fnc; - }; #if GNUPG_MAJOR_VERSION == 1 -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); +int app_select_openpgp (app_t app); +int app_get_serial_and_stamp (app_t app, char **serial, time_t *stamp); #else +/*-- app-help.c --*/ +gpg_error_t app_help_get_keygrip_string (ksba_cert_t cert, char *hexkeygrip); +size_t app_help_read_length_of_cert (int slot, int fid, size_t *r_certoff); + + /*-- app.c --*/ -void app_set_default_reader_port (const char *portstr); -APP select_application (void); -int app_get_serial_and_stamp (APP app, char **serial, time_t *stamp); -int app_write_learn_status (APP app, CTRL ctrl); -int app_getattr (APP app, CTRL ctrl, const char *name); -int app_setattr (APP app, const char *name, +app_t select_application (ctrl_t ctrl, int slot, const char *name); +void release_application (app_t app); +int app_get_serial_and_stamp (app_t app, char **serial, time_t *stamp); +int app_write_learn_status (app_t app, ctrl_t ctrl); +int app_readcert (app_t app, const char *certid, + unsigned char **cert, size_t *certlen); +int app_getattr (app_t app, ctrl_t ctrl, const char *name); +int app_setattr (app_t app, const char *name, int (*pincb)(void*, const char *, char **), void *pincb_arg, const unsigned char *value, size_t valuelen); -int app_sign (APP app, const char *keyidstr, int hashalgo, +int app_sign (app_t 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 app_auth (APP app, const char *keyidstr, +int app_auth (app_t 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 app_decipher (APP app, const char *keyidstr, +int app_decipher (app_t 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 app_genkey (APP app, CTRL ctrl, const char *keynostr, unsigned int flags, +int app_genkey (app_t app, ctrl_t ctrl, + const char *keynostr, unsigned int flags, int (*pincb)(void*, const char *, char **), void *pincb_arg); -int app_get_challenge (APP app, size_t nbytes, unsigned char *buffer); -int app_change_pin (APP app, CTRL ctrl, const char *chvnostr, int reset_mode, +int app_get_challenge (app_t app, size_t nbytes, unsigned char *buffer); +int app_change_pin (app_t app, ctrl_t ctrl, + const char *chvnostr, int reset_mode, int (*pincb)(void*, const char *, char **), void *pincb_arg); -int app_check_pin (APP app, const char *keyidstr, +int app_check_pin (app_t app, const char *keyidstr, int (*pincb)(void*, const char *, char **), void *pincb_arg); /*-- app-openpgp.c --*/ -int app_select_openpgp (APP app, unsigned char **sn, size_t *snlen); +int app_select_openpgp (app_t app); -int app_openpgp_cardinfo (APP app, +int app_openpgp_cardinfo (app_t app, char **serialno, char **disp_name, char **pubkey_url, unsigned char **fpr1, unsigned char **fpr2, unsigned char **fpr3); -int app_openpgp_storekey (APP app, int keyno, +int app_openpgp_storekey (app_t 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 app_openpgp_readkey (APP app, int keyno, +int app_openpgp_readkey (app_t app, int keyno, unsigned char **m, size_t *mlen, unsigned char **e, size_t *elen); +/*-- app-nks.c --*/ +int app_select_nks (app_t app); + +/*-- app-dinsig.c --*/ +int app_select_dinsig (app_t app); + + #endif diff --git a/g10/app-openpgp.c b/g10/app-openpgp.c index 07abf9bfb..07420c697 100644 --- a/g10/app-openpgp.c +++ b/g10/app-openpgp.c @@ -1,5 +1,5 @@ /* app-openpgp.c - The OpenPGP card application. - * Copyright (C) 2003 Free Software Foundation, Inc. + * Copyright (C) 2003, 2004 Free Software Foundation, Inc. * * This file is part of GnuPG. * @@ -42,7 +42,7 @@ #include "iso7816.h" #include "app-common.h" - +#include "tlv.h" static struct { @@ -50,121 +50,196 @@ static struct { int constructed; int get_from; /* Constructed DO with this DO or 0 for direct access. */ int binary; + int dont_cache; + int flush_on_error; 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" }, + { 0x005E, 0, 0, 1, 0, 0, "Login Data" }, + { 0x5F50, 0, 0, 0, 0, 0, "URL" }, + { 0x0065, 1, 0, 1, 0, 0, "Cardholder Related Data"}, + { 0x005B, 0, 0x65, 0, 0, 0, "Name" }, + { 0x5F2D, 0, 0x65, 0, 0, 0, "Language preferences" }, + { 0x5F35, 0, 0x65, 0, 0, 0, "Sex" }, + { 0x006E, 1, 0, 1, 0, 0, "Application Related Data" }, + { 0x004F, 0, 0x6E, 1, 0, 0, "AID" }, + { 0x0073, 1, 0, 1, 0, 0, "Discretionary Data Objects" }, + { 0x0047, 0, 0x6E, 1, 0, 0, "Card Capabilities" }, + { 0x00C0, 0, 0x6E, 1, 0, 0, "Extended Card Capabilities" }, + { 0x00C1, 0, 0x6E, 1, 0, 0, "Algorithm Attributes Signature" }, + { 0x00C2, 0, 0x6E, 1, 0, 0, "Algorithm Attributes Decryption" }, + { 0x00C3, 0, 0x6E, 1, 0, 0, "Algorithm Attributes Authentication" }, + { 0x00C4, 0, 0x6E, 1, 0, 1, "CHV Status Bytes" }, + { 0x00C5, 0, 0x6E, 1, 0, 0, "Fingerprints" }, + { 0x00C6, 0, 0x6E, 1, 0, 0, "CA Fingerprints" }, + { 0x007A, 1, 0, 1, 0, 0, "Security Support Template" }, + { 0x0093, 0, 0x7A, 1, 1, 0, "Digital Signature Counter" }, { 0 } }; +struct cache_s { + struct cache_s *next; + int tag; + size_t length; + unsigned char data[1]; +}; + +struct app_local_s { + struct cache_s *cache; +}; + + static unsigned long convert_sig_counter_value (const unsigned char *value, size_t valuelen); 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) +/* Deconstructor. */ +static void +do_deinit (app_t app) { - const unsigned char *s = buffer; - size_t n = length; - size_t len; - int this_tag; - int composite; - - for (;;) + if (app && app->app_local) { - 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. */ + struct cache_s *c, *c2; - 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) + for (c = app->app_local->cache; c; c = c2) { - *nbytes = len; - return s; + c2 = c->next; + xfree (c); } - if (len > n) - return NULL; /* buffer too short to skip to the next tag. */ - s += len; n -= len; + xfree (app->app_local); + app->app_local = NULL; + } +} + + +/* Wrapper around iso7816_get_data which first tries to get the data + from the cache. */ +static gpg_error_t +get_cached_data (app_t app, int tag, + unsigned char **result, size_t *resultlen) +{ + gpg_error_t err; + int i; + unsigned char *p; + size_t len; + struct cache_s *c; + + + *result = NULL; + *resultlen = 0; + + if (app->app_local) + { + for (c=app->app_local->cache; c; c = c->next) + if (c->tag == tag) + { + p = xtrymalloc (c->length); + if (!p) + return gpg_error (gpg_err_code_from_errno (errno)); + memcpy (p, c->data, c->length); + *resultlen = c->length; + *result = p; + return 0; + } + } + + err = iso7816_get_data (app->slot, tag, &p, &len); + if (err) + return err; + *result = p; + *resultlen = len; + + /* Check whether we should cache this object. */ + for (i=0; data_objects[i].tag; i++) + if (data_objects[i].tag == tag) + { + if (data_objects[i].dont_cache) + return 0; + break; + } + + /* No, cache it. */ + if (!app->app_local) + app->app_local = xtrycalloc (1, sizeof *app->app_local); + + /* Note that we can safely ignore out of core errors. */ + if (app->app_local) + { + for (c=app->app_local->cache; c; c = c->next) + assert (c->tag != tag); + + c = xtrymalloc (sizeof *c + len); + if (c) + { + memcpy (c->data, p, len); + c->length = len; + c->tag = tag; + c->next = app->app_local->cache; + app->app_local->cache = c; + } + } + + return 0; +} + +/* Remove DO at TAG from the cache. */ +static void +flush_cache_item (app_t app, int tag) +{ + struct cache_s *c, *cprev; + int i; + + if (!app->app_local) + return; + + for (c=app->app_local->cache, cprev=NULL; c ; cprev=c, c = c->next) + if (c->tag == tag) + { + if (cprev) + cprev->next = c->next; + else + app->app_local->cache = c->next; + xfree (c); + + for (c=app->app_local->cache; c ; c = c->next) + assert (c->tag != tag); /* Oops: duplicated entry. */ + return; + } + + /* Try again if we have an outer tag. */ + for (i=0; data_objects[i].tag; i++) + if (data_objects[i].tag == tag && data_objects[i].get_from + && data_objects[i].get_from != tag) + flush_cache_item (app, data_objects[i].get_from); +} + +/* Flush all entries from the cache which might be out of sync after + an error. */ +static void +flush_cache_after_error (app_t app) +{ + int i; + + for (i=0; data_objects[i].tag; i++) + if (data_objects[i].flush_on_error) + flush_cache_item (app, data_objects[i].tag); +} + + +/* Flush the entire cache. */ +static void +flush_cache (app_t app) +{ + if (app && app->app_local) + { + struct cache_s *c, *c2; + + for (c = app->app_local->cache; c; c = c2) + { + c2 = c->next; + xfree (c); + } + app->app_local->cache = NULL; } } @@ -174,7 +249,7 @@ find_tlv (const unsigned char *buffer, size_t length, 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) +get_one_do (app_t app, int tag, unsigned char **result, size_t *nbytes) { int rc, i; unsigned char *buffer; @@ -191,13 +266,13 @@ get_one_do (int slot, int tag, unsigned char **result, size_t *nbytes) rc = -1; if (data_objects[i].tag && data_objects[i].get_from) { - rc = iso7816_get_data (slot, data_objects[i].get_from, - &buffer, &buflen); + rc = get_cached_data (app, data_objects[i].get_from, + &buffer, &buflen); if (!rc) { const unsigned char *s; - s = find_tlv (buffer, buflen, tag, &valuelen, 0); + s = find_tlv (buffer, buflen, tag, &valuelen); if (!s) value = NULL; /* not found */ else if (valuelen > buflen - (s - buffer)) @@ -213,7 +288,7 @@ get_one_do (int slot, int tag, unsigned char **result, size_t *nbytes) if (!value) /* Not in a constructed DO, try simple. */ { - rc = iso7816_get_data (slot, tag, &buffer, &buflen); + rc = get_cached_data (app, tag, &buffer, &buflen); if (!rc) { value = buffer; @@ -271,7 +346,7 @@ dump_all_do (int slot) if (j==i || data_objects[i].tag != data_objects[j].get_from) continue; value = find_tlv (buffer, buflen, - data_objects[j].tag, &valuelen, 0); + data_objects[j].tag, &valuelen); if (!value) ; /* not found */ else if (valuelen > buflen - (value - buffer)) @@ -333,7 +408,7 @@ store_fpr (int slot, int keynumber, u32 timestamp, n = 6 + 2 + mlen + 2 + elen; p = buffer = xtrymalloc (3 + n); if (!buffer) - return out_of_core (); + return gpg_error (gpg_err_code_from_errno (errno)); *p++ = 0x99; /* ctb */ *p++ = n >> 8; /* 2 byte length header */ @@ -443,7 +518,7 @@ do_getattr (APP app, CTRL ctrl, const char *name) { /* The serial number is very special. We could have used the AID DO to retrieve it, but we have it already in the app - context and the stanmp argument is required anyway which we + context and the stamp argument is required anyway which we can't by other means. The AID DO is available anyway but not hex formatted. */ char *serial; @@ -462,7 +537,7 @@ do_getattr (APP app, CTRL ctrl, const char *name) return 0; } - relptr = get_one_do (app->slot, table[idx].tag, &value, &valuelen); + relptr = get_one_do (app, table[idx].tag, &value, &valuelen); if (relptr) { if (table[idx].special == 1) @@ -517,7 +592,7 @@ do_learn_status (APP app, CTRL ctrl) /* Verify CHV2 if required. Depending on the configuration of the card CHV1 will also be verified. */ static int -verify_chv2 (APP app, +verify_chv2 (app_t app, int (*pincb)(void*, const char *, char **), void *pincb_arg) { @@ -534,11 +609,19 @@ verify_chv2 (APP app, return rc; } + if (strlen (pinvalue) < 6) + { + log_error ("prassphrase (CHV2) is too short; minimum length is 6\n"); + xfree (pinvalue); + return gpg_error (GPG_ERR_BAD_PIN); + } + rc = iso7816_verify (app->slot, 0x82, pinvalue, strlen (pinvalue)); if (rc) { log_error ("verify CHV2 failed: %s\n", gpg_strerror (rc)); xfree (pinvalue); + flush_cache_after_error (app); return rc; } app->did_chv2 = 1; @@ -552,6 +635,7 @@ verify_chv2 (APP app, { log_error ("verify CHV1 failed: %s\n", gpg_strerror (rc)); xfree (pinvalue); + flush_cache_after_error (app); return rc; } app->did_chv1 = 1; @@ -569,6 +653,12 @@ verify_chv3 (APP app, { int rc = 0; + if (!opt.allow_admin) + { + log_info ("access to admin commands is not configured\n"); + return gpg_error (GPG_ERR_EACCES); + } + if (!app->did_chv3) { char *pinvalue; @@ -580,11 +670,19 @@ verify_chv3 (APP app, return rc; } + if (strlen (pinvalue) < 6) + { + log_error ("prassphrase (CHV3) is too short; minimum length is 6\n"); + xfree (pinvalue); + return gpg_error (GPG_ERR_BAD_PIN); + } + rc = iso7816_verify (app->slot, 0x83, pinvalue, strlen (pinvalue)); xfree (pinvalue); if (rc) { log_error ("verify CHV3 failed: %s\n", gpg_strerror (rc)); + flush_cache_after_error (app); return rc; } app->did_chv3 = 1; @@ -629,6 +727,10 @@ do_setattr (APP app, const char *name, if (rc) return rc; + /* Flush the cache before writing it, so that the next get operation + will reread the data from the card and thus get synced in case of + errors (e.g. data truncated by the card). */ + flush_cache_item (app, table[idx].tag); 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)); @@ -715,7 +817,8 @@ do_change_pin (APP app, CTRL ctrl, const char *chvnostr, int reset_mode, pinvalue, strlen (pinvalue)); } xfree (pinvalue); - + if (rc) + flush_cache_after_error (app); leave: return rc; @@ -746,13 +849,17 @@ do_genkey (APP app, CTRL ctrl, const char *keynostr, unsigned int flags, return gpg_error (GPG_ERR_INV_ID); keyno--; + /* We flush the cache to increase the traffic before a key + generation. This _might_ help a card to gather more entropy. */ + flush_cache (app); + 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); + fpr = find_tlv (buffer, buflen, 0x00C5, &n); if (!fpr || n != 60) { rc = gpg_error (GPG_ERR_GENERAL); @@ -779,6 +886,7 @@ do_genkey (APP app, CTRL ctrl, const char *keynostr, unsigned int flags, goto leave; xfree (buffer); buffer = NULL; + #if 1 log_info ("please wait while key is being generated ...\n"); start_at = time (NULL); @@ -800,7 +908,7 @@ do_genkey (APP app, CTRL ctrl, const char *keynostr, unsigned int flags, } log_info ("key generation completed (%d seconds)\n", (int)(time (NULL) - start_at)); - keydata = find_tlv (buffer, buflen, 0x7F49, &keydatalen, 0); + keydata = find_tlv (buffer, buflen, 0x7F49, &keydatalen); if (!keydata) { rc = gpg_error (GPG_ERR_CARD); @@ -808,7 +916,7 @@ do_genkey (APP app, CTRL ctrl, const char *keynostr, unsigned int flags, goto leave; } - m = find_tlv (keydata, keydatalen, 0x0081, &mlen, 0); + m = find_tlv (keydata, keydatalen, 0x0081, &mlen); if (!m) { rc = gpg_error (GPG_ERR_CARD); @@ -818,7 +926,7 @@ do_genkey (APP app, CTRL ctrl, const char *keynostr, unsigned int flags, /* log_printhex ("RSA n:", m, mlen); */ send_key_data (ctrl, "n", m, mlen); - e = find_tlv (keydata, keydatalen, 0x0082, &elen, 0); + e = find_tlv (keydata, keydatalen, 0x0082, &elen); if (!e) { rc = gpg_error (GPG_ERR_CARD); @@ -869,7 +977,7 @@ get_sig_counter (APP app) size_t valuelen; unsigned long ul; - relptr = get_one_do (app->slot, 0x0093, &value, &valuelen); + relptr = get_one_do (app, 0x0093, &value, &valuelen); if (!relptr) return 0; ul = convert_sig_counter_value (value, valuelen); @@ -887,13 +995,13 @@ compare_fingerprint (APP app, int keyno, unsigned char *sha1fpr) assert (keyno >= 1 && keyno <= 3); - rc = iso7816_get_data (app->slot, 0x006E, &buffer, &buflen); + rc = get_cached_data (app, 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); + fpr = find_tlv (buffer, buflen, 0x00C5, &n); if (!fpr || n != 60) { xfree (buffer); @@ -1035,11 +1143,19 @@ do_sign (APP app, const char *keyidstr, int hashalgo, return rc; } + if (strlen (pinvalue) < 6) + { + log_error ("prassphrase (CHV1) is too short; minimum length is 6\n"); + xfree (pinvalue); + return gpg_error (GPG_ERR_BAD_PIN); + } + rc = iso7816_verify (app->slot, 0x81, pinvalue, strlen (pinvalue)); if (rc) { log_error ("verify CHV1 failed\n"); xfree (pinvalue); + flush_cache_after_error (app); return rc; } app->did_chv1 = 1; @@ -1053,6 +1169,7 @@ do_sign (APP app, const char *keyidstr, int hashalgo, { log_error ("verify CHV2 failed\n"); xfree (pinvalue); + flush_cache_after_error (app); return rc; } app->did_chv2 = 1; @@ -1182,7 +1299,8 @@ do_decipher (APP app, const char *keyidstr, rc = verify_chv2 (app, pincb, pincb_arg); if (!rc) - rc = iso7816_decipher (app->slot, indata, indatalen, outdata, outdatalen); + rc = iso7816_decipher (app->slot, indata, indatalen, 0, + outdata, outdatalen); return rc; } @@ -1241,7 +1359,7 @@ do_check_pin (APP app, const char *keyidstr, /* 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) +app_select_openpgp (APP app) { static char const aid[] = { 0xD2, 0x76, 0x00, 0x01, 0x24, 0x01 }; int slot = app->slot; @@ -1253,10 +1371,17 @@ app_select_openpgp (APP app, unsigned char **sn, size_t *snlen) rc = iso7816_select_application (slot, aid, sizeof aid); if (!rc) { + app->apptype = "OPENPGP"; + app->did_chv1 = 0; app->did_chv2 = 0; app->did_chv3 = 0; + /* The OpenPGP card returns the serial number as part of the + AID; because we prefer to use OpenPGP serial numbers, we + replace a possibly already set one from a EF.GDO with this + one. Note, that for current OpenPGP cards, no EF.GDO exists + and thus it won't matter at all. */ rc = iso7816_get_data (slot, 0x004F, &buffer, &buflen); if (rc) goto leave; @@ -1266,17 +1391,14 @@ app_select_openpgp (APP app, unsigned char **sn, size_t *snlen) log_printhex ("", buffer, buflen); } - if (sn) - { - *sn = buffer; - *snlen = buflen; - app->card_version = buffer[6] << 8; - app->card_version |= buffer[7]; - } - else - xfree (buffer); + app->card_version = buffer[6] << 8; + app->card_version |= buffer[7]; + xfree (app->serialno); + app->serialno = buffer; + app->serialnolen = buflen; + buffer = NULL; - relptr = get_one_do (app->slot, 0x00C4, &buffer, &buflen); + relptr = get_one_do (app, 0x00C4, &buffer, &buflen); if (!relptr) { log_error ("can't access CHV Status Bytes - invalid OpenPGP card?\n"); @@ -1288,7 +1410,9 @@ app_select_openpgp (APP app, unsigned char **sn, size_t *snlen) if (opt.verbose > 1) dump_all_do (slot); + app->fnc.deinit = do_deinit; app->fnc.learn_status = do_learn_status; + app->fnc.readcert = NULL; app->fnc.getattr = do_getattr; app->fnc.setattr = do_setattr; app->fnc.genkey = do_genkey; @@ -1340,7 +1464,7 @@ app_openpgp_cardinfo (APP app, if (disp_name) { *disp_name = NULL; - relptr = get_one_do (app->slot, 0x005B, &value, &valuelen); + relptr = get_one_do (app, 0x005B, &value, &valuelen); if (relptr) { *disp_name = make_printable_string (value, valuelen, 0); @@ -1351,7 +1475,7 @@ app_openpgp_cardinfo (APP app, if (pubkey_url) { *pubkey_url = NULL; - relptr = get_one_do (app->slot, 0x5F50, &value, &valuelen); + relptr = get_one_do (app, 0x5F50, &value, &valuelen); if (relptr) { *pubkey_url = make_printable_string (value, valuelen, 0); @@ -1365,7 +1489,7 @@ app_openpgp_cardinfo (APP app, *fpr2 = NULL; if (fpr3) *fpr3 = NULL; - relptr = get_one_do (app->slot, 0x00C5, &value, &valuelen); + relptr = get_one_do (app, 0x00C5, &value, &valuelen); if (relptr && valuelen >= 60) { if (fpr1) @@ -1471,7 +1595,7 @@ app_openpgp_readkey (APP app, int keyno, unsigned char **m, size_t *mlen, goto leave; } - keydata = find_tlv (buffer, buflen, 0x7F49, &keydatalen, 0); + keydata = find_tlv (buffer, buflen, 0x7F49, &keydatalen); if (!keydata) { log_error ("response does not contain the public key data\n"); @@ -1479,7 +1603,7 @@ app_openpgp_readkey (APP app, int keyno, unsigned char **m, size_t *mlen, goto leave; } - a = find_tlv (keydata, keydatalen, 0x0081, &alen, 0); + a = find_tlv (keydata, keydatalen, 0x0081, &alen); if (!a) { log_error ("response does not contain the RSA modulus\n"); @@ -1490,7 +1614,7 @@ app_openpgp_readkey (APP app, int keyno, unsigned char **m, size_t *mlen, *m = xmalloc (alen); memcpy (*m, a, alen); - a = find_tlv (keydata, keydatalen, 0x0082, &alen, 0); + a = find_tlv (keydata, keydatalen, 0x0082, &alen); if (!e) { log_error ("response does not contain the RSA public exponent\n"); diff --git a/g10/card-util.c b/g10/card-util.c index de445b796..c56ab24aa 100644 --- a/g10/card-util.c +++ b/g10/card-util.c @@ -169,8 +169,7 @@ print_sha1_fpr_colon (FILE *fp, const unsigned char *fpr) static void print_name (FILE *fp, const char *text, const char *name) { - tty_fprintf (fp, text); - + tty_fprintf (fp, "%s", text); /* FIXME: tty_printf_utf8_string2 eats everything after and including an @ - e.g. when printing an url. */ @@ -192,7 +191,7 @@ 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); + tty_fprintf (fp, "%s", text); if (name && *name) { @@ -446,6 +445,14 @@ change_name (void) if (*p == ' ') *p = '<'; + if (strlen (isoname) > 39 ) + { + tty_printf (_("Error: Combined name too long " + "(limit is %d characters).\n"), 39); + xfree (isoname); + return -1; + } + log_debug ("setting Name to `%s'\n", isoname); rc = agent_scd_setattr ("DISP-NAME", isoname, strlen (isoname) ); if (rc) @@ -468,6 +475,14 @@ change_url (void) trim_spaces (url); cpr_kill_prompt (); + if (strlen (url) > 254 ) + { + tty_printf (_("Error: URL too long " + "(limit is %d characters).\n"), 254); + xfree (url); + return -1; + } + rc = agent_scd_setattr ("PUBKEY-URL", url, strlen (url) ); if (rc) log_error ("error setting URL: %s\n", gpg_strerror (rc)); @@ -488,6 +503,14 @@ change_login (void) trim_spaces (data); cpr_kill_prompt (); + if (strlen (data) > 254 ) + { + tty_printf (_("Error: Login data too long " + "(limit is %d characters).\n"), 254); + xfree (data); + return -1; + } + rc = agent_scd_setattr ("LOGIN-DATA", data, strlen (data) ); if (rc) log_error ("error setting login data: %s\n", gpg_strerror (rc)); diff --git a/g10/cardglue.c b/g10/cardglue.c index 55729bf7d..a19246211 100644 --- a/g10/cardglue.c +++ b/g10/cardglue.c @@ -262,7 +262,7 @@ open_card (void) app = xcalloc (1, sizeof *app); app->slot = slot; - rc = app_select_openpgp (app, &app->serialno, &app->serialnolen); + rc = app_select_openpgp (app); if (rc && !opt.batch) { write_status_text (STATUS_CARDCTRL, "1"); diff --git a/g10/cardglue.h b/g10/cardglue.h index b2f020589..454f8bf93 100644 --- a/g10/cardglue.h +++ b/g10/cardglue.h @@ -63,8 +63,10 @@ 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; +typedef struct app_ctx_s *APP; /* deprecated. */ +typedef struct app_ctx_s *app_t; +typedef struct ctrl_ctx_s *CTRL; /* deprecated. */ +typedef struct ctrl_ctx_s *ctrl_t; #define GPG_ERR_GENERAL G10ERR_GENERAL @@ -86,6 +88,15 @@ typedef struct ctrl_ctx_s *CTRL; #define GPG_ERR_WRONG_CARD G10ERR_GENERAL #define GPG_ERR_WRONG_SECKEY G10ERR_WRONG_SECKEY #define GPG_ERR_PIN_NOT_SYNCED G10ERR_GENERAL +#define GPG_ERR_NOT_FOUND G10ERR_GENERAL +#define GPG_ERR_BUG G10ERR_GENERAL +#define GPG_ERR_NOT_IMPLEMENTED G10ERR_GENERAL +#define GPG_ERR_BAD_BER G10ERR_GENERAL +#define GPG_ERR_EOF (-1) + +#define GPG_ERR_EBUSY G10ERR_GENERAL +#define GPG_ERR_ENOENT G10ERR_OPEN_FILE +#define GPG_ERR_EACCES G10ERR_UNSUPPORTED typedef int gpg_error_t; typedef int gpg_err_code_t; @@ -94,7 +105,7 @@ typedef int gpg_err_code_t; #define gpg_err_code(n) (n) #define gpg_strerror(n) g10_errstr ((n)) #define gpg_error_from_errno(n) (G10ERR_GENERAL) /*FIXME*/ - +#define gpg_err_code_from_errno(n) (G10ERR_GENERAL) /* We are not using it in a library, so we even let xtrymalloc abort. Because we won't never return from these malloc functions, diff --git a/g10/ccid-driver.c b/g10/ccid-driver.c index 099dae240..cd0bee6ef 100644 --- a/g10/ccid-driver.c +++ b/g10/ccid-driver.c @@ -1,5 +1,5 @@ /* ccid-driver.c - USB ChipCardInterfaceDevices driver - * Copyright (C) 2003 Free Software Foundation, Inc. + * Copyright (C) 2003, 2004 Free Software Foundation, Inc. * Written by Werner Koch. * * This file is part of GnuPG. @@ -87,16 +87,32 @@ #define DRVNAME "ccid-driver: " -#ifdef GNUPG_MAJOR_VERSION /* This source is used within GnuPG. */ +/* Depending on how this source is used we either define our error + output to go to stderr or to the jnlib based logging functions. We + use the latter when GNUPG_MAJOR_VERSION is defines or when both, + GNUPG_SCD_MAIN_HEADER and HAVE_JNLIB_LOGGING are defined. +*/ +#if defined(GNUPG_MAJOR_VERSION) \ + || (defined(GNUPG_SCD_MAIN_HEADER) && defined(HAVE_JNLIB_LOGGING)) -# if GNUPG_MAJOR_VERSION == 1 /* GnuPG Version is < 1.9. */ +#if defined(GNUPG_SCD_MAIN_HEADER) +# include GNUPG_SCD_MAIN_HEADER +#elif GNUPG_MAJOR_VERSION == 1 /* GnuPG Version is < 1.9. */ # include "options.h" # include "util.h" # include "memory.h" # include "cardglue.h" # else /* This is the modularized GnuPG 1.9 or later. */ # include "scdaemon.h" -# endif +#endif + +/* Disable all debugging output for now. */ +#undef DBG_CARD_IO +#define DBG_CARD_IO 0 + +/* Define to print information pertaining the T=1 protocol. */ +#undef DEBUG_T1 + # define DEBUGOUT(t) do { if (DBG_CARD_IO) \ log_debug (DRVNAME t); } while (0) @@ -132,8 +148,6 @@ #endif /* This source not used by scdaemon. */ -/* Define to print information pertaining the T=1 protocol. */ -#undef DEBUG_T1 1 @@ -171,9 +185,15 @@ struct ccid_driver_s { int seqno; unsigned char t1_ns; unsigned char t1_nr; + int nonnull_nad; + int auto_ifsd; + int max_ifsd; + int ifsd; }; +static unsigned int compute_edc (const unsigned char *data, size_t datalen, + int use_crc); static int bulk_out (ccid_driver_t handle, unsigned char *msg, size_t msglen); static int bulk_in (ccid_driver_t handle, unsigned char *buffer, size_t length, size_t *nread, int expected_type, int seqno); @@ -207,13 +227,18 @@ set_msg_len (unsigned char *msg, unsigned int length) 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) +parse_ccid_descriptor (ccid_driver_t handle, + const unsigned char *buf, size_t buflen) { unsigned int i; unsigned int us; int have_t1 = 0, have_tpdu=0, have_auto_conf = 0; + handle->nonnull_nad = 0; + handle->auto_ifsd = 0; + handle->max_ifsd = 32; + handle->ifsd = 0; if (buflen < 54 || buf[0] < 54) { DEBUGOUT ("CCID device descriptor is too short\n"); @@ -259,6 +284,7 @@ parse_ccid_descriptor (const unsigned char *buf, size_t buflen) us = convert_le_u32(buf+28); DEBUGOUT_1 (" dwMaxIFSD %5u\n", us); + handle->max_ifsd = us; us = convert_le_u32(buf+32); DEBUGOUT_1 (" dwSyncProtocols %08X ", us); @@ -307,9 +333,15 @@ parse_ccid_descriptor (const unsigned char *buf, size_t buflen) if ((us & 0x0100)) DEBUGOUT (" CCID can set ICC in clock stop mode\n"); if ((us & 0x0200)) - DEBUGOUT (" NAD value other than 0x00 accpeted\n"); + { + DEBUGOUT (" NAD value other than 0x00 accepted\n"); + handle->nonnull_nad = 1; + } if ((us & 0x0400)) - DEBUGOUT (" Auto IFSD exchange\n"); + { + DEBUGOUT (" Auto IFSD exchange\n"); + handle->auto_ifsd = 1; + } if ((us & 0x00010000)) { @@ -376,7 +408,7 @@ parse_ccid_descriptor (const unsigned char *buf, size_t buflen) that the device is usable for us. Returns 0 on success or an error code. */ static int -read_device_info (struct usb_device *dev) +read_device_info (ccid_driver_t handle, struct usb_device *dev) { int cfg_no; @@ -401,8 +433,9 @@ read_device_info (struct usb_device *dev) { if (ifcdesc->extra) { - if (!parse_ccid_descriptor (ifcdesc->extra, - ifcdesc->extralen)) + if (!parse_ccid_descriptor (handle, + ifcdesc->extra, + ifcdesc->extralen)) return 0; /* okay. we can use it. */ } } @@ -445,10 +478,22 @@ ccid_open_reader (ccid_driver_t *handle, int readerno) dev->descriptor->idVendor, dev->descriptor->idProduct); if (!readerno) { - rc = read_device_info (dev); + *handle = calloc (1, sizeof **handle); + if (!*handle) + { + DEBUGOUT ("out of memory\n"); + rc = -1; + free (*handle); + *handle = NULL; + goto leave; + } + + rc = read_device_info (*handle, dev); if (rc) { DEBUGOUT ("device not supported\n"); + free (*handle); + *handle = NULL; goto leave; } @@ -456,6 +501,8 @@ ccid_open_reader (ccid_driver_t *handle, int readerno) if (rc) { DEBUGOUT_1 ("usb_open failed: %d\n", rc); + free (*handle); + *handle = NULL; goto leave; } @@ -466,16 +513,11 @@ ccid_open_reader (ccid_driver_t *handle, int readerno) if (rc) { DEBUGOUT_1 ("usb_claim_interface failed: %d\n", rc); + free (*handle); + *handle = NULL; 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 @@ -495,7 +537,7 @@ ccid_open_reader (ccid_driver_t *handle, int readerno) usb_free_match (match); if (!rc && !*handle) - rc = -1; /* In case we didn't enter the while lool at all. */ + rc = -1; /* In case we didn't enter the while loop at all. */ return rc; } @@ -579,6 +621,7 @@ bulk_in (ccid_driver_t handle, unsigned char *buffer, size_t length, int i, rc; size_t msglen; + retry: rc = usb_bulk_read (handle->idev, 0x82, buffer, length, @@ -615,6 +658,14 @@ bulk_in (ccid_driver_t handle, unsigned char *buffer, size_t length, return -1; } + if ( !(buffer[7] & 0x03) && (buffer[7] & 0xC0) == 0x80) + { + /* Card present and active, time extension requested. */ + DEBUGOUT_2 ("time extension requested (%02X,%02X)\n", + buffer[7], buffer[8]); + goto retry; + } + DEBUGOUT_3 ("status: %02X error: %02X octet[9]: %02X\n" " data:", buffer[7], buffer[8], buffer[9] ); for (i=10; i < msglen; i++) @@ -682,7 +733,7 @@ ccid_poll (ccid_driver_t handle) int -ccid_slot_status (ccid_driver_t handle) +ccid_slot_status (ccid_driver_t handle, int *statusbits) { int rc; unsigned char msg[100]; @@ -703,6 +754,7 @@ ccid_slot_status (ccid_driver_t handle) rc = bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_SlotStatus, seqno); if (rc) return rc; + *statusbits = (msg[7] & 3); return 0; } @@ -714,8 +766,12 @@ ccid_get_atr (ccid_driver_t handle, { int rc; unsigned char msg[100]; - size_t msglen; + unsigned char *tpdu; + size_t msglen, tpdulen; unsigned char seqno; + int use_crc = 0; + unsigned int edc; + int i; msg[0] = PC_to_RDR_IccPowerOn; msg[5] = 0; /* slot */ @@ -743,11 +799,135 @@ ccid_get_atr (ccid_driver_t handle, *atrlen = n; } + /* Setup parameters to select T=1. */ + msg[0] = PC_to_RDR_SetParameters; + msg[5] = 0; /* slot */ + msg[6] = seqno = handle->seqno++; + msg[7] = 1; /* Select T=1. */ + msg[8] = 0; /* RFU */ + msg[9] = 0; /* RFU */ + + /* FIXME: Get those values from the ATR. */ + msg[10]= 0x01; /* Fi/Di */ + msg[11]= 0x10; /* LRC, direct convention. */ + msg[12]= 0; /* Extra guardtime. */ + msg[13]= 0x41; /* BWI/CWI */ + msg[14]= 0; /* No clock stoppping. */ + msg[15]= 254; /* IFSC */ + msg[16]= 0; /* Does not support non default NAD values. */ + set_msg_len (msg, 7); + msglen = 10 + 7; + + DEBUGOUT ("sending"); + for (i=0; i < msglen; i++) + DEBUGOUT_CONT_1 (" %02X", msg[i]); + DEBUGOUT_LF (); + + rc = bulk_out (handle, msg, msglen); + if (rc) + return rc; + /* Note that we ignore the error code on purpose. */ + bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_Parameters, seqno); + + + /* Send an S-Block with our maximun IFSD to the CCID. */ + if (!handle->auto_ifsd) + { + tpdu = msg+10; + /* NAD: DAD=1, SAD=0 */ + tpdu[0] = handle->nonnull_nad? ((1 << 4) | 0): 0; + tpdu[1] = (0xc0 | 0 | 1); /* S-block request: change IFSD */ + tpdu[2] = 1; + tpdu[3] = handle->max_ifsd? handle->max_ifsd : 32; + tpdulen = 4; + edc = compute_edc (tpdu, tpdulen, use_crc); + if (use_crc) + tpdu[tpdulen++] = (edc >> 8); + tpdu[tpdulen++] = edc; + + msg[0] = PC_to_RDR_XfrBlock; + msg[5] = 0; /* slot */ + msg[6] = seqno = handle->seqno++; + msg[7] = 0; + 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 (); + +#ifdef DEBUG_T1 + 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))); +#endif + + rc = bulk_out (handle, msg, msglen); + if (rc) + return rc; + + /* Fixme: The next line for the current Valgrid without support + for USB IOCTLs. */ + memset (msg, 0, sizeof msg); + + rc = bulk_in (handle, msg, sizeof msg, &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; + } + +#ifdef DEBUG_T1 + 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 + ); +#endif + if ((tpdu[1] & 0xe0) != 0xe0 || tpdu[2] != 1) + { + DEBUGOUT ("invalid response for S-block (Change-IFSD)\n"); + return -1; + } + DEBUGOUT_1 ("IFSD has been set to %d\n", tpdu[3]); + } + return 0; } + +static unsigned int +compute_edc (const unsigned char *data, size_t datalen, int use_crc) +{ + if (use_crc) + { + return 0x42; /* Not yet implemented. */ + } + else + { + unsigned char crc = 0; + + for (; datalen; datalen--) + crc ^= *data++; + return crc; + } +} + + /* Protocol T=1 overview @@ -806,17 +986,19 @@ ccid_transceive (ccid_driver_t handle, unsigned char *resp, size_t maxresplen, size_t *nresp) { int rc; - unsigned char send_buffer[10+258], recv_buffer[10+258]; + unsigned char send_buffer[10+259], recv_buffer[10+259]; const unsigned char *apdu; size_t apdulen; unsigned char *msg, *tpdu, *p; - size_t msglen, tpdulen, n; + size_t msglen, tpdulen, last_tpdulen, n; unsigned char seqno; int i; - unsigned char crc; + unsigned int edc; + int use_crc = 0; size_t dummy_nresp; int next_chunk = 1; int sending = 1; + int retries = 0; if (!nresp) nresp = &dummy_nresp; @@ -839,7 +1021,8 @@ ccid_transceive (ccid_driver_t handle, return -1; /* Invalid length. */ tpdu = msg+10; - tpdu[0] = ((1 << 4) | 0); /* NAD: DAD=1, SAD=0 */ + /* NAD: DAD=1, SAD=0 */ + tpdu[0] = handle->nonnull_nad? ((1 << 4) | 0): 0; tpdu[1] = ((handle->t1_ns & 1) << 6); /* I-block */ if (apdulen > 128 /* fixme: replace by ifsc */) { @@ -850,12 +1033,11 @@ ccid_transceive (ccid_driver_t handle, } 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; + tpdulen = 3 + apdulen; + edc = compute_edc (tpdu, tpdulen, use_crc); + if (use_crc) + tpdu[tpdulen++] = (edc >> 8); + tpdu[tpdulen++] = edc; } msg[0] = PC_to_RDR_XfrBlock; @@ -866,6 +1048,7 @@ ccid_transceive (ccid_driver_t handle, msg[9] = 0; /* RFU */ set_msg_len (msg, tpdulen); msglen = 10 + tpdulen; + last_tpdulen = tpdulen; DEBUGOUT ("sending"); for (i=0; i < msglen; i++) @@ -913,7 +1096,7 @@ ccid_transceive (ccid_driver_t handle, if (!(tpdu[1] & 0x80)) { /* This is an I-block. */ - + retries = 0; if (sending) { /* last block sent was successful. */ handle->t1_ns ^= 1; @@ -924,13 +1107,15 @@ ccid_transceive (ccid_driver_t handle, { /* Reponse does not match our sequence number. */ msg = send_buffer; tpdu = msg+10; - tpdu[0] = ((1 << 4) | 0); /* NAD: DAD=1, SAD=0 */ + /* NAD: DAD=1, SAD=0 */ + tpdu[0] = handle->nonnull_nad? ((1 << 4) | 0): 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; + edc = compute_edc (tpdu, tpdulen, use_crc); + if (use_crc) + tpdu[tpdulen++] = (edc >> 8); + tpdu[tpdulen++] = edc; continue; } @@ -944,7 +1129,9 @@ ccid_transceive (ccid_driver_t handle, { if (n > maxresplen) { - DEBUGOUT ("provided buffer too short for received data\n"); + DEBUGOUT_2 ("provided buffer too short for received data " + "(%u/%u)\n", + (unsigned int)n, (unsigned int)maxresplen); return -1; } @@ -959,20 +1146,27 @@ ccid_transceive (ccid_driver_t handle, msg = send_buffer; tpdu = msg+10; - tpdu[0] = ((1 << 4) | 0); /* NAD: DAD=1, SAD=0 */ + /* NAD: DAD=1, SAD=0 */ + tpdu[0] = handle->nonnull_nad? ((1 << 4) | 0): 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; - + edc = compute_edc (tpdu, tpdulen, use_crc); + if (use_crc) + tpdu[tpdulen++] = (edc >> 8); + tpdu[tpdulen++] = edc; } else if ((tpdu[1] & 0xc0) == 0x80) { /* This is a R-block. */ if ( (tpdu[1] & 0x0f)) { /* Error: repeat last block */ + if (++retries > 3) + { + DEBUGOUT ("3 failed retries\n"); + return -1; + } msg = send_buffer; + tpdulen = last_tpdulen; } else if (sending && !!(tpdu[1] & 0x40) == handle->t1_ns) { /* Reponse does not match our sequence number. */ @@ -981,6 +1175,7 @@ ccid_transceive (ccid_driver_t handle, } else if (sending) { /* Send next chunk. */ + retries = 0; msg = send_buffer; next_chunk = 1; handle->t1_ns ^= 1; @@ -993,6 +1188,7 @@ ccid_transceive (ccid_driver_t handle, } else { /* This is a S-block. */ + retries = 0; DEBUGOUT_2 ("T1 S-block %s received cmd=%d\n", (tpdu[1] & 0x20)? "response": "request", (tpdu[1] & 0x1f)); @@ -1001,14 +1197,16 @@ ccid_transceive (ccid_driver_t handle, unsigned char bwi = tpdu[3]; msg = send_buffer; tpdu = msg+10; - tpdu[0] = ((1 << 4) | 0); /* NAD: DAD=1, SAD=0 */ + /* NAD: DAD=1, SAD=0 */ + tpdu[0] = handle->nonnull_nad? ((1 << 4) | 0): 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; + edc = compute_edc (tpdu, tpdulen, use_crc); + if (use_crc) + tpdu[tpdulen++] = (edc >> 8); + tpdu[tpdulen++] = edc; DEBUGOUT_1 ("T1 waittime extension of bwi=%d\n", bwi); } else @@ -1028,6 +1226,7 @@ main (int argc, char **argv) { int rc; ccid_driver_t ccid; + unsigned int slotstat; rc = ccid_open_reader (&ccid, 0); if (rc) @@ -1041,7 +1240,7 @@ main (int argc, char **argv) ccid_poll (ccid); fputs ("getting slot status ...\n", stderr); - rc = ccid_slot_status (ccid); + rc = ccid_slot_status (ccid, &slotstat); if (rc) return 1; diff --git a/g10/ccid-driver.h b/g10/ccid-driver.h index e33be55d9..8b86eb1a5 100644 --- a/g10/ccid-driver.h +++ b/g10/ccid-driver.h @@ -63,6 +63,7 @@ int ccid_open_reader (ccid_driver_t *handle, int readerno); int ccid_close_reader (ccid_driver_t handle); int ccid_get_atr (ccid_driver_t handle, unsigned char *atr, size_t maxatrlen, size_t *atrlen); +int ccid_slot_status (ccid_driver_t handle, int *statusbits); int ccid_transceive (ccid_driver_t handle, const unsigned char *apdu, size_t apdulen, unsigned char *resp, size_t maxresplen, size_t *nresp); diff --git a/g10/g10.c b/g10/g10.c index 861d4625d..4281357ca 100644 --- a/g10/g10.c +++ b/g10/g10.c @@ -341,6 +341,8 @@ enum cmd_and_opt_values octapiDriver, opcscDriver, oDisableCCID, + oAllowAdmin, + oDenyAdmin, aTest }; @@ -521,6 +523,10 @@ static ARGPARSE_OPTS opts[] = { { oSetNotation, "notation-data", 2, "@" }, /* Alias */ { oSigNotation, "sig-notation", 2, "@" }, { oCertNotation, "cert-notation", 2, "@" }, +#ifdef ENABLE_CARD_SUPPORT + { oAllowAdmin, "allow-admin",0,N_("allow the use of admin card commands")}, + { oDenyAdmin, "deny-admin",0,"@"}, +#endif { 302, NULL, 0, N_( "@\n(See the man page for a complete listing of all commands and options)\n" @@ -1699,6 +1705,8 @@ main( int argc, char **argv ) 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; + case oAllowAdmin: opt.allow_admin = 1; break; + case oDenyAdmin: opt.allow_admin = 0; break; #endif /* ENABLE_CARD_SUPPORT*/ case oArmor: opt.armor = 1; opt.no_armor=0; break; diff --git a/g10/iso7816.c b/g10/iso7816.c index f4aa18c6f..fd3f0485c 100644 --- a/g10/iso7816.c +++ b/g10/iso7816.c @@ -1,5 +1,5 @@ /* iso7816.c - ISO 7816 commands - * Copyright (C) 2003 Free Software Foundation, Inc. + * Copyright (C) 2003, 2004 Free Software Foundation, Inc. * * This file is part of GnuPG. * @@ -24,7 +24,9 @@ #include #include -#if GNUPG_MAJOR_VERSION == 1 +#if defined(GNUPG_SCD_MAIN_HEADER) +#include GNUPG_SCD_MAIN_HEADER +#elif GNUPG_MAJOR_VERSION == 1 /* This is used with GnuPG version < 1.9. The code has been source copied from the current GnuPG >= 1.9 and is maintained over there. */ @@ -47,10 +49,13 @@ #define CMD_RESET_RETRY_COUNTER 0x2C #define CMD_GET_DATA 0xCA #define CMD_PUT_DATA 0xDA +#define CMD_MSE 0x22 #define CMD_PSO 0x2A #define CMD_INTERNAL_AUTHENTICATE 0x88 #define CMD_GENERATE_KEYPAIR 0x47 #define CMD_GET_CHALLENGE 0x84 +#define CMD_READ_BINARY 0xB0 +#define CMD_READ_RECORD 0xB2 static gpg_error_t map_sw (int sw) @@ -66,6 +71,8 @@ map_sw (int sw) 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_FILE_NOT_FOUND: ec = GPG_ERR_ENOENT; break; + case SW_RECORD_NOT_FOUND:ec= GPG_ERR_NOT_FOUND; 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; @@ -75,6 +82,9 @@ map_sw (int sw) 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; + case SW_HOST_NOT_SUPPORTED: ec = GPG_ERR_NOT_SUPPORTED; break; + case SW_HOST_LOCKING_FAILED: ec = GPG_ERR_BUG; break; + case SW_HOST_BUSY: ec = GPG_ERR_EBUSY; break; default: if ((sw & 0x010000)) ec = GPG_ERR_GENERAL; /* Should not happen. */ @@ -91,18 +101,79 @@ map_sw (int sw) 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 + for okay or a GPG error code. Note that ISO error codes are internally mapped. */ gpg_error_t iso7816_select_application (int slot, const char *aid, size_t aidlen) { + static char const openpgp_aid[] = { 0xD2, 0x76, 0x00, 0x01, 0x24, 0x01 }; int sw; + int p1 = 0x0C; /* No FCI to be returned. */ + + if (aidlen == sizeof openpgp_aid + && !memcmp (aid, openpgp_aid, sizeof openpgp_aid)) + p1 = 0; /* The current openpgp cards don't allow 0x0c. */ - sw = apdu_send_simple (slot, 0x00, CMD_SELECT_FILE, 4, 0, aidlen, aid); + sw = apdu_send_simple (slot, 0x00, CMD_SELECT_FILE, 4, p1, aidlen, aid); return map_sw (sw); } +gpg_error_t +iso7816_select_file (int slot, int tag, int is_dir, + unsigned char **result, size_t *resultlen) +{ + int sw, p0, p1; + unsigned char tagbuf[2]; + + tagbuf[0] = (tag >> 8) & 0xff; + tagbuf[1] = tag & 0xff; + + if (result || resultlen) + { + *result = NULL; + *resultlen = 0; + return gpg_error (GPG_ERR_NOT_IMPLEMENTED); + } + else + { + p0 = (tag == 0x3F00)? 0: is_dir? 1:2; + p1 = 0x0c; /* No FC return. */ + sw = apdu_send_simple (slot, 0x00, CMD_SELECT_FILE, + p0, p1, 2, tagbuf ); + return map_sw (sw); + } + + return 0; +} + + +/* This is a private command currently only working for TCOS cards. */ +gpg_error_t +iso7816_list_directory (int slot, int list_dirs, + 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, 0x80, 0xAA, list_dirs? 1:2, 0, -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); +} + + + /* 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 @@ -134,7 +205,7 @@ iso7816_change_reference_data (int slot, int chvno, buf = xtrymalloc (oldchvlen + newchvlen); if (!buf) - return out_of_core (); + return gpg_error (gpg_err_code_from_errno (errno)); if (oldchvlen) memcpy (buf, oldchv, oldchvlen); memcpy (buf+oldchvlen, newchv, newchvlen); @@ -205,6 +276,23 @@ iso7816_put_data (int slot, int tag, return map_sw (sw); } +/* Manage Security Environment. This is a weird operation and there + is no easy abstraction for it. Furthermore, some card seem to have + a different interpreation of 7816-8 and thus we resort to let the + caller decide what to do. */ +gpg_error_t +iso7816_manage_security_env (int slot, int p1, int p2, + const unsigned char *data, size_t datalen) +{ + int sw; + + if (p1 < 0 || p1 > 255 || p2 < 0 || p2 > 255 || !data || !datalen) + return gpg_error (GPG_ERR_INV_VALUE); + + sw = apdu_send_simple (slot, 0x00, CMD_MSE, p1, p2, 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 @@ -236,13 +324,14 @@ iso7816_compute_ds (int slot, const unsigned char *data, size_t datalen, } -/* 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. */ +/* Perform the security operation DECIPHER. PADIND is the padding + indicator to be used. It should be 0 if no padding is required, a + value of -1 suppresses the padding byte. 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 padind, unsigned char **result, size_t *resultlen) { int sw; unsigned char *buf; @@ -252,15 +341,24 @@ iso7816_decipher (int slot, const unsigned char *data, size_t datalen, *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 (padind >= 0) + { + /* We need to prepend the padding indicator. */ + buf = xtrymalloc (datalen + 1); + if (!buf) + return gpg_error (gpg_err_code_from_errno (errno)); + + *buf = padind; /* Padding indicator. */ + memcpy (buf+1, data, datalen); + sw = apdu_send (slot, 0x00, CMD_PSO, 0x80, 0x86, datalen+1, buf, + result, resultlen); + xfree (buf); + } + else + { + sw = apdu_send (slot, 0x00, CMD_PSO, 0x80, 0x86, datalen, data, + result, resultlen); + } if (sw != SW_SUCCESS) { /* Make sure that pending buffers are released. */ @@ -381,3 +479,139 @@ iso7816_get_challenge (int slot, int length, unsigned char *buffer) return 0; } + +/* Perform a READ BINARY command requesting a maximum of NMAX bytes + from OFFSET. With NMAX = 0 the entire file is read. The result is + stored in a newly allocated buffer at the address passed by RESULT. + Returns the length of this data at the address of RESULTLEN. */ +gpg_error_t +iso7816_read_binary (int slot, size_t offset, size_t nmax, + unsigned char **result, size_t *resultlen) +{ + int sw; + unsigned char *buffer; + size_t bufferlen; + int read_all = !nmax; + size_t n; + + if (!result || !resultlen) + return gpg_error (GPG_ERR_INV_VALUE); + *result = NULL; + *resultlen = 0; + + /* We can only encode 15 bits in p0,p1 to indicate an offset. Thus + we check for this limit. */ + if (offset > 32767) + return gpg_error (GPG_ERR_INV_VALUE); + + do + { + buffer = NULL; + bufferlen = 0; + /* Fixme: Either the ccid driver or the TCOS cards have problems + with an Le of 0. */ + if (read_all || nmax > 254) + n = 254; + else + n = nmax; + sw = apdu_send_le (slot, 0x00, CMD_READ_BINARY, + ((offset>>8) & 0xff), (offset & 0xff) , -1, NULL, + n, &buffer, &bufferlen); + + if (sw != SW_SUCCESS && sw != SW_EOF_REACHED) + { + /* Make sure that pending buffers are released. */ + xfree (buffer); + xfree (*result); + *result = NULL; + *resultlen = 0; + return map_sw (sw); + } + if (*result) /* Need to extend the buffer. */ + { + unsigned char *p = xtryrealloc (*result, *resultlen + bufferlen); + if (!p) + { + gpg_error_t err = gpg_error_from_errno (errno); + xfree (buffer); + xfree (*result); + *result = NULL; + *resultlen = 0; + return err; + } + *result = p; + memcpy (*result + *resultlen, buffer, bufferlen); + *resultlen += bufferlen; + xfree (buffer); + buffer = NULL; + } + else /* Transfer the buffer into our result. */ + { + *result = buffer; + *resultlen = bufferlen; + } + offset += bufferlen; + if (offset > 32767) + break; /* We simply truncate the result for too large + files. */ + if (nmax > bufferlen) + nmax -= bufferlen; + else + nmax = 0; + } + while ((read_all && sw != SW_EOF_REACHED) || (!read_all && nmax)); + + return 0; +} + +/* Perform a READ RECORD command. RECNO gives the record number to + read with 0 indicating the current record. RECCOUNT must be 1 (not + all cards support reading of more than one record). SHORT_EF + should be 0 to read the current EF or contain a short EF. The + result is stored in a newly allocated buffer at the address passed + by RESULT. Returns the length of this data at the address of + RESULTLEN. */ +gpg_error_t +iso7816_read_record (int slot, int recno, int reccount, int short_ef, + unsigned char **result, size_t *resultlen) +{ + int sw; + unsigned char *buffer; + size_t bufferlen; + + if (!result || !resultlen) + return gpg_error (GPG_ERR_INV_VALUE); + *result = NULL; + *resultlen = 0; + + /* We can only encode 15 bits in p0,p1 to indicate an offset. Thus + we check for this limit. */ + if (recno < 0 || recno > 255 || reccount != 1 + || short_ef < 0 || short_ef > 254 ) + return gpg_error (GPG_ERR_INV_VALUE); + + buffer = NULL; + bufferlen = 0; + /* Fixme: Either the ccid driver of the TCOS cards have problems + with an Le of 0. */ + sw = apdu_send_le (slot, 0x00, CMD_READ_RECORD, + recno, + short_ef? short_ef : 0x04, + -1, NULL, + 254, &buffer, &bufferlen); + + if (sw != SW_SUCCESS && sw != SW_EOF_REACHED) + { + /* Make sure that pending buffers are released. */ + xfree (buffer); + xfree (*result); + *result = NULL; + *resultlen = 0; + return map_sw (sw); + } + *result = buffer; + *resultlen = bufferlen; + + return 0; +} + diff --git a/g10/iso7816.h b/g10/iso7816.h index 26b8d6aba..8f2b150e6 100644 --- a/g10/iso7816.h +++ b/g10/iso7816.h @@ -27,6 +27,10 @@ gpg_error_t iso7816_select_application (int slot, const char *aid, size_t aidlen); +gpg_error_t iso7816_select_file (int slot, int tag, int is_dir, + unsigned char **result, size_t *resultlen); +gpg_error_t iso7816_list_directory (int slot, int list_dirs, + unsigned char **result, size_t *resultlen); 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, @@ -38,11 +42,15 @@ 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_manage_security_env (int slot, int p1, int p2, + 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, + int padind, unsigned char **result, size_t *resultlen); gpg_error_t iso7816_internal_authenticate (int slot, const unsigned char *data, size_t datalen, @@ -56,5 +64,10 @@ gpg_error_t iso7816_read_public_key (int slot, gpg_error_t iso7816_get_challenge (int slot, int length, unsigned char *buffer); +gpg_error_t iso7816_read_binary (int slot, size_t offset, size_t nmax, + unsigned char **result, size_t *resultlen); +gpg_error_t iso7816_read_record (int slot, int recno, int reccount, + int short_ef, + unsigned char **result, size_t *resultlen); #endif /*ISO7816_H*/ diff --git a/g10/options.h b/g10/options.h index 8a93a8014..5da785aaf 100644 --- a/g10/options.h +++ b/g10/options.h @@ -195,6 +195,7 @@ struct 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. */ + int allow_admin; /* Allow the use of Admin commands. */ #endif /*ENABLE_CARD_SUPPORT*/ } opt; diff --git a/g10/status.c b/g10/status.c index d39bc683b..5506e25a4 100644 --- a/g10/status.c +++ b/g10/status.c @@ -83,6 +83,7 @@ get_status_string ( int no ) case STATUS_ENTER : s = "ENTER"; break; case STATUS_LEAVE : s = "LEAVE"; break; case STATUS_ABORT : s = "ABORT"; break; + case STATUS_NEWSIG : s = "NEWSIG"; break; case STATUS_GOODSIG: s = "GOODSIG"; break; case STATUS_KEYEXPIRED: s = "KEYEXPIRED"; break; case STATUS_KEYREVOKED: s = "KEYREVOKED"; break; diff --git a/g10/status.h b/g10/status.h index 73cc3f1a9..63f01b9a4 100644 --- a/g10/status.h +++ b/g10/status.h @@ -101,6 +101,7 @@ #define STATUS_IMPORT_CHECK 69 #define STATUS_REVKEYSIG 70 #define STATUS_CARDCTRL 71 +#define STATUS_NEWSIG 72 /*-- status.c --*/ void set_status_fd ( int fd ); diff --git a/g10/tlv.c b/g10/tlv.c new file mode 100644 index 000000000..b7c8819e6 --- /dev/null +++ b/g10/tlv.c @@ -0,0 +1,209 @@ +/* tlv.c - Tag-Length-Value Utilities + * Copyright (C) 2003, 2004 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 + +#include +#include +#include +#include + +#include "errors.h" +#include "util.h" +#include "packet.h" +#include "tlv.h" + +static const unsigned char * +do_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 a too deep + nesting. */ + const unsigned char *tmp_s; + size_t tmp_len; + + tmp_s = do_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; + } +} + + +/* 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. */ +const unsigned char * +find_tlv (const unsigned char *buffer, size_t length, + int tag, size_t *nbytes) +{ + return do_find_tlv (buffer, length, tag, nbytes, 0); +} + + + + +/* ASN.1 BER parser: Parse BUFFER of length SIZE and return the tag + and the length part from the TLV triplet. Update BUFFER and SIZE + on success. */ +gpg_error_t +parse_ber_header (unsigned char const **buffer, size_t *size, + int *r_class, int *r_tag, + int *r_constructed, int *r_ndef, + size_t *r_length, size_t *r_nhdr) +{ + int c; + unsigned long tag; + const unsigned char *buf = *buffer; + size_t length = *size; + + *r_ndef = 0; + *r_length = 0; + *r_nhdr = 0; + + /* Get the tag. */ + if (!length) + return gpg_error (GPG_ERR_EOF); + c = *buf++; length--; ++*r_nhdr; + + *r_class = (c & 0xc0) >> 6; + *r_constructed = !!(c & 0x20); + tag = c & 0x1f; + + if (tag == 0x1f) + { + tag = 0; + do + { + tag <<= 7; + if (!length) + return gpg_error (GPG_ERR_EOF); + c = *buf++; length--; ++*r_nhdr; + tag |= c & 0x7f; + + } + while (c & 0x80); + } + *r_tag = tag; + + /* Get the length. */ + if (!length) + return gpg_error (GPG_ERR_EOF); + c = *buf++; length--; ++*r_nhdr; + + if ( !(c & 0x80) ) + *r_length = c; + else if (c == 0x80) + *r_ndef = 1; + else if (c == 0xff) + return gpg_error (GPG_ERR_BAD_BER); + else + { + unsigned long len = 0; + int count = c & 0x7f; + + if (count > sizeof (len) || count > sizeof (size_t)) + return gpg_error (GPG_ERR_BAD_BER); + + for (; count; count--) + { + len <<= 8; + if (!length) + return gpg_error (GPG_ERR_EOF); + c = *buf++; length--; ++*r_nhdr; + len |= c & 0xff; + } + *r_length = len; + } + + /* Without this kludge some example certs can't be parsed. */ + if (*r_class == CLASS_UNIVERSAL && !*r_tag) + *r_length = 0; + + *buffer = buf; + *size = length; + return 0; +} diff --git a/g10/tlv.h b/g10/tlv.h new file mode 100644 index 000000000..f1ef13415 --- /dev/null +++ b/g10/tlv.h @@ -0,0 +1,86 @@ +/* tlv.h - Tag-Length-Value Utilities + * Copyright (C) 2004 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 SCD_TLV_H +#define SCD_TLV_H 1 + +#include "types.h" +#include "cardglue.h" + +enum tlv_tag_class { + CLASS_UNIVERSAL = 0, + CLASS_APPLICATION = 1, + CLASS_CONTEXT = 2, + CLASS_PRIVATE =3 +}; + +enum tlv_tag_type { + TAG_NONE = 0, + TAG_BOOLEAN = 1, + TAG_INTEGER = 2, + TAG_BIT_STRING = 3, + TAG_OCTET_STRING = 4, + TAG_NULL = 5, + TAG_OBJECT_ID = 6, + TAG_OBJECT_DESCRIPTOR = 7, + TAG_EXTERNAL = 8, + TAG_REAL = 9, + TAG_ENUMERATED = 10, + TAG_EMBEDDED_PDV = 11, + TAG_UTF8_STRING = 12, + TAG_REALTIVE_OID = 13, + TAG_SEQUENCE = 16, + TAG_SET = 17, + TAG_NUMERIC_STRING = 18, + TAG_PRINTABLE_STRING = 19, + TAG_TELETEX_STRING = 20, + TAG_VIDEOTEX_STRING = 21, + TAG_IA5_STRING = 22, + TAG_UTC_TIME = 23, + TAG_GENERALIZED_TIME = 24, + TAG_GRAPHIC_STRING = 25, + TAG_VISIBLE_STRING = 26, + TAG_GENERAL_STRING = 27, + TAG_UNIVERSAL_STRING = 28, + TAG_CHARACTER_STRING = 29, + TAG_BMP_STRING = 30 +}; + + + +/* 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.*/ +const unsigned char *find_tlv (const unsigned char *buffer, size_t length, + int tag, size_t *nbytes); + + +/* ASN.1 BER parser: Parse BUFFER of length SIZE and return the tag + and the length part from the TLV triplet. Update BUFFER and SIZE + on success. */ +gpg_error_t parse_ber_header (unsigned char const **buffer, size_t *size, + int *r_class, int *r_tag, + int *r_constructed, + int *r_ndef, size_t *r_length, size_t *r_nhdr); + + + +#endif /* SCD_TLV_H */