diff --git a/AUTHORS b/AUTHORS index 31466ed5d..742608e75 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,6 +1,6 @@ Program: GnuPG Maintainer: Werner Koch -Bug reports: +Bug reports: http://bugs.gnupg.org Security related bug reports: License: GPLv3+ diff --git a/ChangeLog b/ChangeLog index 6db7d83f7..ba5d0ed55 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2009-07-21 Werner Koch + + * configure.ac (AH_BOTTOM): Add macros for estream-printf. + (estream_PRINTF_INIT): Add it. + 2009-06-05 David Shaw * configure.ac: Remove Camellia restriction. diff --git a/NEWS b/NEWS index d66a33aff..6441452c4 100644 --- a/NEWS +++ b/NEWS @@ -8,6 +8,10 @@ Noteworthy changes in version 1.4.10 (unreleased) * Fixed a memory leak which made imports of many keys very slow. + * Support v2 OpenPGP cards. + + * FIXME: Anything else? + Noteworthy changes in version 1.4.9 (2008-03-26) ------------------------------------------------ diff --git a/configure.ac b/configure.ac index b017040fb..91836753f 100644 --- a/configure.ac +++ b/configure.ac @@ -499,6 +499,11 @@ is intended for making fat binary builds on OS X. */ #define SAFE_VERSION_DOT '.' #define SAFE_VERSION_DASH '-' +/* We want to use our memory allocator for estream-printf. */ +#define _ESTREAM_PRINTF_MALLOC xtrymalloc +#define _ESTREAM_PRINTF_FREE xfree +#define _ESTREAM_PRINTF_EXTRA_INCLUDE "memory.h" + #endif /*GNUPG_CONFIG_H_INCLUDED*/ ]) @@ -1049,6 +1054,12 @@ fi GNUPG_CHECK_MLOCK GNUPG_FUNC_MKDIR_TAKES_ONE_ARG +# +# Prepare building of estream-printf +# +estream_PRINTF_INIT + + dnl dnl Check whether we can use Linux capabilities as requested dnl diff --git a/g10/ChangeLog b/g10/ChangeLog index 68cf2d9ba..b5ed29a6e 100644 --- a/g10/ChangeLog +++ b/g10/ChangeLog @@ -1,3 +1,31 @@ +2009-07-21 Werner Koch + + * app-common.h, app-openpgp.c, iso7816.c, iso7816.h, apdu.c, + * apdu.h, ccid-driver.c, ccid-driver.h, card-util.c: Update from + GnuPG 2.0 SVN revision 5084. + + * cardglue.h (GCRY_MD_SHA256): Add more GCRY_MD constants. + (gcry_handler_progress_t): Add definition. + (struct agent_card_info_s): Add fields apptype, is_v2, key_attr. + * cardglue.c (learn_status_cb): Set them. + (agent_release_card_info): Release APPTYPE. + (unescape_status_string, send_status_direct): New. + (gcry_mpi_release, gcry_mpi_set_opaque): New. + (gcry_md_algo_name): New. + (open_card): s/initialized/ref_count/. + (agent_learn): Pass new new flag arg to learn_status. + (agent_scd_genkey): Add new arg createtime. + * keygen.c (gen_card_key, gen_card_key_with_backup): Add new arg + TIMESTAMP. + (write_direct_sig, write_selfsigs, write_keybinding) + (make_backsig): Ditto. + (do_generate_keypair): Pass timestamp to all signing functions. + (generate_card_subkeypair): Ditto. + * keyedit.c (menu_backsign): Pass a new timestamp to all backsisg. + + * gpg.c (main): Disable keypad support. + * options.h (struct): Add field disable_keypad. + 2009-07-17 Werner Koch * keyring.c (keyring_rebuild_cache): Replace the assert by a diff --git a/g10/apdu.c b/g10/apdu.c index d29676b41..d114b6eca 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, 2004 Free Software Foundation, Inc. + * Copyright (C) 2003, 2004, 2008, 2009 Free Software Foundation, Inc. * * This file is part of GnuPG. * @@ -31,9 +31,9 @@ #include #include #ifdef USE_GNU_PTH -# include # include # include +# include #endif @@ -54,13 +54,14 @@ #include "memory.h" #include "util.h" #include "i18n.h" +#include "dynload.h" #include "cardglue.h" #else /* GNUPG_MAJOR_VERSION != 1 */ #include "scdaemon.h" +#include "exechelp.h" #endif /* GNUPG_MAJOR_VERSION != 1 */ #include "apdu.h" -#include "dynload.h" #include "ccid-driver.h" @@ -82,13 +83,8 @@ #define DLSTDCALL #endif -#ifdef _POSIX_OPEN_MAX -#define MAX_OPEN_FDS _POSIX_OPEN_MAX -#else -#define MAX_OPEN_FDS 20 -#endif -/* Helper to pass patrameters related to keypad based operations. */ +/* Helper to pass parameters related to keypad based operations. */ struct pininfo_s { int mode; @@ -104,6 +100,8 @@ struct reader_table_s { unsigned short port; /* Port number: 0 = unused, 1 - dev/tty */ /* Function pointers intialized to the various backends. */ + int (*connect_card)(int); + int (*disconnect_card)(int); int (*close_reader)(int); int (*shutdown_reader)(int); int (*reset_reader)(int); @@ -112,6 +110,7 @@ struct reader_table_s { unsigned char *, size_t *, struct pininfo_s *); int (*check_keypad)(int, int, int, int, int, int); void (*dump_status_reader)(int); + int (*set_progress_cb)(int, gcry_handler_progress_t, void*); struct { ccid_driver_t handle; @@ -132,6 +131,7 @@ struct reader_table_s { } rapdu; #endif /*USE_G10CODE_RAPDU*/ char *rdrname; /* Name of the connected reader or NULL if unknown. */ + int any_status; /* True if we have seen any status. */ int last_status; int status; int is_t0; /* True if we know that we are running T=0. */ @@ -220,6 +220,11 @@ static char (* DLSTDCALL CT_close) (unsigned short ctn); #define PCSC_E_READER_UNAVAILABLE 0x80100017 #define PCSC_W_REMOVED_CARD 0x80100069 +/* The PC/SC error is defined as a long as per specs. Due to left + shifts bit 31 will get sign extended. We use this mask to fix + it. */ +#define PCSC_ERR_MASK(a) ((a) & 0xffffffff) + struct pcsc_io_request_s { @@ -272,7 +277,8 @@ long (* DLSTDCALL pcsc_status) (unsigned long card, unsigned long *r_protocol, unsigned char *atr, unsigned long *atrlen); long (* DLSTDCALL pcsc_begin_transaction) (unsigned long card); -long (* DLSTDCALL pcsc_end_transaction) (unsigned long card); +long (* DLSTDCALL pcsc_end_transaction) (unsigned long card, + unsigned long disposition); long (* DLSTDCALL pcsc_transmit) (unsigned long card, const pcsc_io_request_t send_pci, const unsigned char *send_buffer, @@ -286,6 +292,10 @@ long (* DLSTDCALL pcsc_set_timeout) (unsigned long context, /* Prototypes. */ static int pcsc_get_status (int slot, unsigned int *status); +static int reset_pcsc_reader (int slot); +static int apdu_get_status_internal (int slot, int hang, int no_atr_reset, + unsigned int *status, + unsigned int *changed); @@ -322,6 +332,8 @@ new_reader_slot (void) reader_table[reader].lock_initialized = 1; } #endif /*USE_GNU_PTH*/ + reader_table[reader].connect_card = NULL; + reader_table[reader].disconnect_card = NULL; reader_table[reader].close_reader = NULL; reader_table[reader].shutdown_reader = NULL; reader_table[reader].reset_reader = NULL; @@ -329,8 +341,10 @@ new_reader_slot (void) reader_table[reader].send_apdu_reader = NULL; reader_table[reader].check_keypad = NULL; reader_table[reader].dump_status_reader = NULL; + reader_table[reader].set_progress_cb = NULL; - reader_table[reader].used = 1; + reader_table[reader].used = 1; + reader_table[reader].any_status = 0; reader_table[reader].last_status = 0; reader_table[reader].is_t0 = 1; #ifdef NEED_PCSC_WRAPPER @@ -381,6 +395,7 @@ host_sw_string (long err) case SW_HOST_NO_READER: return "no reader"; case SW_HOST_ABORTED: return "aborted"; case SW_HOST_NO_KEYPAD: return "no keypad"; + case SW_HOST_ALREADY_CONNECTED: return "already connected"; default: return "unknown host status error"; } } @@ -402,6 +417,7 @@ apdu_strerror (int rc) case SW_FILE_NOT_FOUND : return "file not found"; case SW_RECORD_NOT_FOUND:return "record not found"; case SW_REF_NOT_FOUND : return "reference not found"; + case SW_BAD_LC : return "bad Lc"; case SW_BAD_P0_P1 : return "bad P0 or P1"; case SW_INS_NOT_SUP : return "instruction not supported"; case SW_CLA_NOT_SUP : return "class not supported"; @@ -531,10 +547,11 @@ reset_ct_reader (int slot) static int ct_get_status (int slot, unsigned int *status) { - *status = 1|2|4; /* FIXME */ + (void)slot; + /* The status we returned is wrong but we don't care becuase ctAPI + is not anymore required. */ + *status = APDU_CARD_USABLE|APDU_CARD_PRESENT|APDU_CARD_ACTIVE; return 0; - - return SW_HOST_NOT_SUPPORTED; } /* Actually send the APDU of length APDULEN to SLOT and return a @@ -548,6 +565,8 @@ ct_send_apdu (int slot, unsigned char *apdu, size_t apdulen, unsigned char dad[1], sad[1]; unsigned short ctbuflen; + (void)pininfo; + /* If we don't have an ATR, we need to reset the reader first. */ if (!reader_table[slot].atrlen && (rc = reset_ct_reader (slot))) @@ -655,6 +674,9 @@ readn (int fd, void *buf, size_t buflen, size_t *nread) while (nleft > 0) { #ifdef USE_GNU_PTH +# ifdef HAVE_W32_SYSTEM +# error Cannot use pth_read here because it expects a system HANDLE. +# endif n = pth_read (fd, buf, nleft); #else n = read (fd, buf, nleft); @@ -736,7 +758,7 @@ pcsc_error_to_sw (long ec) { int rc; - switch (ec) + switch ( PCSC_ERR_MASK (ec) ) { case 0: rc = 0; break; @@ -762,23 +784,575 @@ pcsc_error_to_sw (long ec) static void dump_pcsc_reader_status (int slot) { - log_info ("reader slot %d: active protocol:", slot); - if ((reader_table[slot].pcsc.protocol & PCSC_PROTOCOL_T0)) - log_printf (" T0"); - else if ((reader_table[slot].pcsc.protocol & PCSC_PROTOCOL_T1)) - log_printf (" T1"); - else if ((reader_table[slot].pcsc.protocol & PCSC_PROTOCOL_RAW)) - log_printf (" raw"); - log_printf ("\n"); + if (reader_table[slot].pcsc.card) + { + log_info ("reader slot %d: active protocol:", slot); + if ((reader_table[slot].pcsc.protocol & PCSC_PROTOCOL_T0)) + log_printf (" T0"); + else if ((reader_table[slot].pcsc.protocol & PCSC_PROTOCOL_T1)) + log_printf (" T1"); + else if ((reader_table[slot].pcsc.protocol & PCSC_PROTOCOL_RAW)) + log_printf (" raw"); + log_printf ("\n"); + } + else + log_info ("reader slot %d: not connected\n", slot); } -/* Send an PC/SC reset command and return a status word on error or 0 - on success. */ +#ifndef NEED_PCSC_WRAPPER static int -reset_pcsc_reader (int slot) +pcsc_get_status_direct (int slot, unsigned int *status) +{ + long err; + struct pcsc_readerstate_s rdrstates[1]; + + memset (rdrstates, 0, sizeof *rdrstates); + rdrstates[0].reader = reader_table[slot].rdrname; + rdrstates[0].current_state = PCSC_STATE_UNAWARE; + err = pcsc_get_status_change (reader_table[slot].pcsc.context, + 0, + rdrstates, 1); + if (err == PCSC_E_TIMEOUT) + err = 0; /* Timeout is no error error here. */ + if (err) + { + log_error ("pcsc_get_status_change failed: %s (0x%lx)\n", + pcsc_error_string (err), err); + return pcsc_error_to_sw (err); + } + + /* log_debug */ + /* ("pcsc_get_status_change: %s%s%s%s%s%s%s%s%s%s\n", */ + /* (rdrstates[0].event_state & PCSC_STATE_IGNORE)? " ignore":"", */ + /* (rdrstates[0].event_state & PCSC_STATE_CHANGED)? " changed":"", */ + /* (rdrstates[0].event_state & PCSC_STATE_UNKNOWN)? " unknown":"", */ + /* (rdrstates[0].event_state & PCSC_STATE_UNAVAILABLE)?" unavail":"", */ + /* (rdrstates[0].event_state & PCSC_STATE_EMPTY)? " empty":"", */ + /* (rdrstates[0].event_state & PCSC_STATE_PRESENT)? " present":"", */ + /* (rdrstates[0].event_state & PCSC_STATE_ATRMATCH)? " atr":"", */ + /* (rdrstates[0].event_state & PCSC_STATE_EXCLUSIVE)? " excl":"", */ + /* (rdrstates[0].event_state & PCSC_STATE_INUSE)? " unuse":"", */ + /* (rdrstates[0].event_state & PCSC_STATE_MUTE)? " mute":"" ); */ + + *status = 0; + if ( (rdrstates[0].event_state & PCSC_STATE_PRESENT) ) + *status |= APDU_CARD_PRESENT; + if ( !(rdrstates[0].event_state & PCSC_STATE_MUTE) ) + *status |= APDU_CARD_ACTIVE; +#ifndef HAVE_W32_SYSTEM + /* We indicate a useful card if it is not in use by another + application. This is because we only use exclusive access + mode. */ + if ( (*status & (APDU_CARD_PRESENT|APDU_CARD_ACTIVE)) + == (APDU_CARD_PRESENT|APDU_CARD_ACTIVE) + && !(rdrstates[0].event_state & PCSC_STATE_INUSE) ) + *status |= APDU_CARD_USABLE; +#else + /* Some winscard drivers may set EXCLUSIVE and INUSE at the same + time when we are the only user (SCM SCR335) under Windows. */ + if ((*status & (APDU_CARD_PRESENT|APDU_CARD_ACTIVE)) + == (APDU_CARD_PRESENT|APDU_CARD_ACTIVE)) + *status |= APDU_CARD_USABLE; +#endif + + return 0; +} +#endif /*!NEED_PCSC_WRAPPER*/ + + +#ifdef NEED_PCSC_WRAPPER +static int +pcsc_get_status_wrapped (int slot, unsigned int *status) +{ + long err; + reader_table_t slotp; + size_t len, full_len; + int i, n; + unsigned char msgbuf[9]; + unsigned char buffer[16]; + int sw = SW_HOST_CARD_IO_ERROR; + + slotp = reader_table + slot; + + if (slotp->pcsc.req_fd == -1 + || slotp->pcsc.rsp_fd == -1 + || slotp->pcsc.pid == (pid_t)(-1) ) + { + log_error ("pcsc_get_status: pcsc-wrapper not running\n"); + return sw; + } + + msgbuf[0] = 0x04; /* STATUS 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 STATUS 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 STATUS 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 = PCSC_ERR_MASK ((msgbuf[5] << 24) | (msgbuf[6] << 16) + | (msgbuf[7] << 8 ) | msgbuf[8]); + if (err) + { + log_error ("pcsc_status failed: %s (0x%lx)\n", + pcsc_error_string (err), err); + /* This is a proper error code, so return immediately. */ + return pcsc_error_to_sw (err); + } + + full_len = len; + + /* The current version returns 3 words but we allow also for old + versions returning only 2 words. */ + n = 12 < len ? 12 : len; + if ((i=readn (slotp->pcsc.rsp_fd, buffer, n, &len)) + || (len != 8 && len != 12)) + { + log_error ("error receiving PC/SC STATUS response: %s\n", + i? strerror (errno) : "premature EOF"); + goto command_failed; + } + + slotp->is_t0 = (len == 12 && !!(buffer[11] & PCSC_PROTOCOL_T0)); + + + full_len -= len; + /* Newer versions of the wrapper might send more status bytes. + Read them. */ + 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; + } + + /* We are lucky: The wrapper already returns the data in the + required format. */ + *status = buffer[3]; + return 0; + + 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 sw; +} +#endif /*NEED_PCSC_WRAPPER*/ + + +static int +pcsc_get_status (int slot, unsigned int *status) { #ifdef NEED_PCSC_WRAPPER + return pcsc_get_status_wrapped (slot, status); +#else + return pcsc_get_status_direct (slot, status); +#endif +} + + +#ifndef NEED_PCSC_WRAPPER +static int +pcsc_send_apdu_direct (int slot, unsigned char *apdu, size_t apdulen, + unsigned char *buffer, size_t *buflen, + struct pininfo_s *pininfo) +{ + long err; + struct pcsc_io_request_s send_pci; + unsigned long recv_len; + + if (!reader_table[slot].atrlen + && (err = reset_pcsc_reader (slot))) + return err; + + if (DBG_CARD_IO) + log_printhex (" PCSC_data:", apdu, apdulen); + + if ((reader_table[slot].pcsc.protocol & PCSC_PROTOCOL_T1)) + send_pci.protocol = PCSC_PROTOCOL_T1; + else + send_pci.protocol = PCSC_PROTOCOL_T0; + send_pci.pci_len = sizeof send_pci; + recv_len = *buflen; + err = pcsc_transmit (reader_table[slot].pcsc.card, + &send_pci, apdu, apdulen, + NULL, buffer, &recv_len); + *buflen = recv_len; + if (err) + log_error ("pcsc_transmit failed: %s (0x%lx)\n", + pcsc_error_string (err), err); + + return pcsc_error_to_sw (err); +} +#endif /*!NEED_PCSC_WRAPPER*/ + + +#ifdef NEED_PCSC_WRAPPER +static int +pcsc_send_apdu_wrapped (int slot, unsigned char *apdu, size_t apdulen, + unsigned char *buffer, size_t *buflen, + struct pininfo_s *pininfo) +{ + long err; + reader_table_t slotp; + size_t len, full_len; + int i, n; + unsigned char msgbuf[9]; + int sw = SW_HOST_CARD_IO_ERROR; + + (void)pininfo; + + if (!reader_table[slot].atrlen + && (err = reset_pcsc_reader (slot))) + return err; + + 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 sw; + } + + 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 = PCSC_ERR_MASK ((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 pcsc_error_to_sw (err); + } + + 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 = SW_HOST_INV_VALUE; + } + /* We need to read any rest of the response, to keep the + protocol running. */ + 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 sw; +} +#endif /*NEED_PCSC_WRAPPER*/ + + +/* Send the APDU of length APDULEN to SLOT and return a maximum of + *BUFLEN data in BUFFER, the actual returned size will be stored at + BUFLEN. Returns: A status word. */ +static int +pcsc_send_apdu (int slot, unsigned char *apdu, size_t apdulen, + unsigned char *buffer, size_t *buflen, + struct pininfo_s *pininfo) +{ +#ifdef NEED_PCSC_WRAPPER + return pcsc_send_apdu_wrapped (slot, apdu, apdulen, buffer, buflen, pininfo); +#else + return pcsc_send_apdu_direct (slot, apdu, apdulen, buffer, buflen, pininfo); +#endif +} + + +#ifndef NEED_PCSC_WRAPPER +static int +close_pcsc_reader_direct (int slot) +{ + pcsc_release_context (reader_table[slot].pcsc.context); + xfree (reader_table[slot].rdrname); + reader_table[slot].rdrname = NULL; + reader_table[slot].used = 0; + return 0; +} +#endif /*!NEED_PCSC_WRAPPER*/ + + +#ifdef NEED_PCSC_WRAPPER +static int +close_pcsc_reader_wrapped (int slot) +{ + 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 = PCSC_ERR_MASK ((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 close 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; +} +#endif /*NEED_PCSC_WRAPPER*/ + + +static int +close_pcsc_reader (int slot) +{ +#ifdef NEED_PCSC_WRAPPER + return close_pcsc_reader_wrapped (slot); +#else + return close_pcsc_reader_direct (slot); +#endif +} + + +/* Connect a PC/SC card. */ +#ifndef NEED_PCSC_WRAPPER +static int +connect_pcsc_card (int slot) +{ + long err; + + assert (slot >= 0 && slot < MAX_READER); + + if (reader_table[slot].pcsc.card) + return SW_HOST_ALREADY_CONNECTED; + + reader_table[slot].atrlen = 0; + reader_table[slot].last_status = 0; + reader_table[slot].is_t0 = 0; + + err = pcsc_connect (reader_table[slot].pcsc.context, + reader_table[slot].rdrname, + PCSC_SHARE_EXCLUSIVE, + PCSC_PROTOCOL_T0|PCSC_PROTOCOL_T1, + &reader_table[slot].pcsc.card, + &reader_table[slot].pcsc.protocol); + if (err) + { + reader_table[slot].pcsc.card = 0; + if (err != PCSC_E_NO_SMARTCARD) + log_error ("pcsc_connect failed: %s (0x%lx)\n", + pcsc_error_string (err), err); + } + else + { + char reader[250]; + unsigned long readerlen, atrlen; + unsigned long card_state, card_protocol; + + atrlen = DIM (reader_table[0].atr); + readerlen = sizeof reader -1 ; + err = pcsc_status (reader_table[slot].pcsc.card, + reader, &readerlen, + &card_state, &card_protocol, + reader_table[slot].atr, &atrlen); + if (err) + log_error ("pcsc_status failed: %s (0x%lx) %lu\n", + pcsc_error_string (err), err, readerlen); + else + { + if (atrlen > DIM (reader_table[0].atr)) + log_bug ("ATR returned by pcsc_status is too large\n"); + reader_table[slot].atrlen = atrlen; + /* If we got to here we know that a card is present + and usable. Remember this. */ + reader_table[slot].last_status = ( APDU_CARD_USABLE + | APDU_CARD_PRESENT + | APDU_CARD_ACTIVE); + reader_table[slot].is_t0 = !!(card_protocol & PCSC_PROTOCOL_T0); + } + } + + dump_reader_status (slot); + return pcsc_error_to_sw (err); +} +#endif /*!NEED_PCSC_WRAPPER*/ + + +/* Disconnect a PC/SC card. Note that this succeeds even if the card + is not connected. */ +#ifndef NEED_PCSC_WRAPPER +static int +disconnect_pcsc_card (int slot) +{ + long err; + + assert (slot >= 0 && slot < MAX_READER); + + if (!reader_table[slot].pcsc.card) + return 0; + + err = pcsc_disconnect (reader_table[slot].pcsc.card, PCSC_LEAVE_CARD); + if (err) + { + log_error ("pcsc_disconnect failed: %s (0x%lx)\n", + pcsc_error_string (err), err); + return SW_HOST_CARD_IO_ERROR; + } + reader_table[slot].pcsc.card = 0; + return 0; +} +#endif /*!NEED_PCSC_WRAPPER*/ + + +#ifndef NEED_PCSC_WRAPPER +static int +reset_pcsc_reader_direct (int slot) +{ + int sw; + + sw = disconnect_pcsc_card (slot); + if (!sw) + sw = connect_pcsc_card (slot); + + return sw; +} +#endif /*NEED_PCSC_WRAPPER*/ + + +#ifdef NEED_PCSC_WRAPPER +static int +reset_pcsc_reader_wrapped (int slot) +{ long err; reader_table_t slotp; size_t len; @@ -831,7 +1405,8 @@ reset_pcsc_reader (int slot) sw = SW_HOST_GENERAL_ERROR; goto command_failed; } - err = (msgbuf[5] << 24) | (msgbuf[6] << 16) | (msgbuf[7] << 8 ) | msgbuf[8]; + err = PCSC_ERR_MASK ((msgbuf[5] << 24) | (msgbuf[6] << 16) + | (msgbuf[7] << 8 ) | msgbuf[8]); if (err) { log_error ("PC/SC RESET failed: %s (0x%lx)\n", @@ -872,450 +1447,130 @@ reset_pcsc_reader (int slot) slotp->pcsc.pid = (pid_t)(-1); slotp->used = 0; return sw; - -#else /* !NEED_PCSC_WRAPPER */ - long err; - char reader[250]; - unsigned long nreader, atrlen; - unsigned long card_state, card_protocol; - - if (reader_table[slot].pcsc.card) - { - err = pcsc_disconnect (reader_table[slot].pcsc.card, PCSC_LEAVE_CARD); - if (err) - { - log_error ("pcsc_disconnect failed: %s (0x%lx)\n", - pcsc_error_string (err), err); - return SW_HOST_CARD_IO_ERROR; - } - reader_table[slot].pcsc.card = 0; - } - - err = pcsc_connect (reader_table[slot].pcsc.context, - reader_table[slot].rdrname, - PCSC_SHARE_EXCLUSIVE, - PCSC_PROTOCOL_T0|PCSC_PROTOCOL_T1, - &reader_table[slot].pcsc.card, - &reader_table[slot].pcsc.protocol); - if (err) - { - log_error ("pcsc_connect failed: %s (0x%lx)\n", - pcsc_error_string (err), err); - reader_table[slot].pcsc.card = 0; - return pcsc_error_to_sw (err); - } - - - atrlen = 33; - nreader = sizeof reader - 1; - err = pcsc_status (reader_table[slot].pcsc.card, - reader, &nreader, - &card_state, &card_protocol, - reader_table[slot].atr, &atrlen); - if (err) - { - log_error ("pcsc_status failed: %s (0x%lx)\n", - pcsc_error_string (err), err); - reader_table[slot].atrlen = 0; - return pcsc_error_to_sw (err); - } - if (atrlen >= DIM (reader_table[0].atr)) - log_bug ("ATR returned by pcsc_status is too large\n"); - reader_table[slot].atrlen = atrlen; - reader_table[slot].is_t0 = !!(card_protocol & PCSC_PROTOCOL_T0); - - return 0; +} #endif /* !NEED_PCSC_WRAPPER */ + + +/* Send an PC/SC reset command and return a status word on error or 0 + on success. */ +static int +reset_pcsc_reader (int slot) +{ +#ifdef NEED_PCSC_WRAPPER + return reset_pcsc_reader_wrapped (slot); +#else + return reset_pcsc_reader_direct (slot); +#endif } +/* Open the PC/SC reader without using the wrapper. Returns -1 on + error or a slot number for the reader. */ +#ifndef NEED_PCSC_WRAPPER static int -pcsc_get_status (int slot, unsigned int *status) +open_pcsc_reader_direct (const char *portstr) { -#ifdef NEED_PCSC_WRAPPER long err; - reader_table_t slotp; - size_t len, full_len; - int i, n; - unsigned char msgbuf[9]; - unsigned char buffer[16]; - int sw = SW_HOST_CARD_IO_ERROR; + int slot; + char *list = NULL; + unsigned long nreader, listlen; + char *p; - slotp = reader_table + slot; + slot = new_reader_slot (); + if (slot == -1) + return -1; - if (slotp->pcsc.req_fd == -1 - || slotp->pcsc.rsp_fd == -1 - || slotp->pcsc.pid == (pid_t)(-1) ) - { - log_error ("pcsc_get_status: pcsc-wrapper not running\n"); - return sw; - } - - msgbuf[0] = 0x04; /* STATUS 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 STATUS 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 STATUS 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]; + /* Fixme: Allocating a context for each slot is not required. One + global context should be sufficient. */ + err = pcsc_establish_context (PCSC_SCOPE_SYSTEM, NULL, NULL, + &reader_table[slot].pcsc.context); if (err) { - log_error ("pcsc_status failed: %s (0x%lx)\n", + log_error ("pcsc_establish_context failed: %s (0x%lx)\n", pcsc_error_string (err), err); - /* This is a proper error code, so return immediately. */ - return pcsc_error_to_sw (err); + reader_table[slot].used = 0; + return -1; } - full_len = len; - - /* The current version returns 3 words but we allow also for old - versions returning only 2 words. */ - n = 12 < len ? 12 : len; - if ((i=readn (slotp->pcsc.rsp_fd, buffer, n, &len)) - || (len != 8 && len != 12)) + err = pcsc_list_readers (reader_table[slot].pcsc.context, + NULL, NULL, &nreader); + if (!err) { - log_error ("error receiving PC/SC STATUS response: %s\n", - i? strerror (errno) : "premature EOF"); - goto command_failed; - } - - slotp->is_t0 = (len == 12 && !!(buffer[11] & PCSC_PROTOCOL_T0)); - - - full_len -= len; - /* Newer versions of the wrapper might send more status bytes. - Read them. */ - 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) + list = xtrymalloc (nreader+1); /* Better add 1 for safety reasons. */ + if (!list) { - log_error ("error receiving PC/SC TRANSMIT response: %s\n", - i? strerror (errno) : "premature EOF"); - goto command_failed; + log_error ("error allocating memory for reader list\n"); + pcsc_release_context (reader_table[slot].pcsc.context); + reader_table[slot].used = 0; + return -1 /*SW_HOST_OUT_OF_CORE*/; } - full_len -= n; + err = pcsc_list_readers (reader_table[slot].pcsc.context, + NULL, list, &nreader); } - - /* We are lucky: The wrapper already returns the data in the - required format. */ - *status = buffer[3]; - - return 0; - - 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 sw; - -#else /*!NEED_PCSC_WRAPPER*/ - - long err; - struct pcsc_readerstate_s rdrstates[1]; - - memset (rdrstates, 0, sizeof *rdrstates); - rdrstates[0].reader = reader_table[slot].rdrname; - rdrstates[0].current_state = PCSC_STATE_UNAWARE; - err = pcsc_get_status_change (reader_table[slot].pcsc.context, - 0, - rdrstates, 1); - if (err == PCSC_E_TIMEOUT) - err = 0; /* Timeout is no error error here. */ if (err) { - log_error ("pcsc_get_status_change failed: %s (0x%lx)\n", + log_error ("pcsc_list_readers failed: %s (0x%lx)\n", pcsc_error_string (err), err); - return pcsc_error_to_sw (err); + pcsc_release_context (reader_table[slot].pcsc.context); + reader_table[slot].used = 0; + xfree (list); + return -1; } + listlen = nreader; + p = list; + while (nreader) + { + if (!*p && !p[1]) + break; + if (*p) + log_info ("detected reader `%s'\n", p); + if (nreader < (strlen (p)+1)) + { + log_error ("invalid response from pcsc_list_readers\n"); + break; + } + nreader -= strlen (p)+1; + p += strlen (p) + 1; + } - /* log_debug */ - /* ("pcsc_get_status_change: %s%s%s%s%s%s%s%s%s%s\n", */ - /* (rdrstates[0].event_state & PCSC_STATE_IGNORE)? " ignore":"", */ - /* (rdrstates[0].event_state & PCSC_STATE_CHANGED)? " changed":"", */ - /* (rdrstates[0].event_state & PCSC_STATE_UNKNOWN)? " unknown":"", */ - /* (rdrstates[0].event_state & PCSC_STATE_UNAVAILABLE)?" unavail":"", */ - /* (rdrstates[0].event_state & PCSC_STATE_EMPTY)? " empty":"", */ - /* (rdrstates[0].event_state & PCSC_STATE_PRESENT)? " present":"", */ - /* (rdrstates[0].event_state & PCSC_STATE_ATRMATCH)? " atr":"", */ - /* (rdrstates[0].event_state & PCSC_STATE_EXCLUSIVE)? " excl":"", */ - /* (rdrstates[0].event_state & PCSC_STATE_INUSE)? " unuse":"", */ - /* (rdrstates[0].event_state & PCSC_STATE_MUTE)? " mute":"" ); */ + reader_table[slot].rdrname = xtrymalloc (strlen (portstr? portstr : list)+1); + if (!reader_table[slot].rdrname) + { + log_error ("error allocating memory for reader name\n"); + pcsc_release_context (reader_table[slot].pcsc.context); + reader_table[slot].used = 0; + return -1; + } + strcpy (reader_table[slot].rdrname, portstr? portstr : list); + xfree (list); + list = NULL; - *status = 0; - if ( (rdrstates[0].event_state & PCSC_STATE_PRESENT) ) - *status |= 2; - if ( !(rdrstates[0].event_state & PCSC_STATE_MUTE) ) - *status |= 4; - /* We indicate a useful card if it is not in use by another - application. This is because we only use exclusive access - mode. */ - if ( (*status & 6) == 6 - && !(rdrstates[0].event_state & PCSC_STATE_INUSE) ) - *status |= 1; + reader_table[slot].pcsc.card = 0; + reader_table[slot].atrlen = 0; + reader_table[slot].last_status = 0; - return 0; -#endif /*!NEED_PCSC_WRAPPER*/ + reader_table[slot].connect_card = connect_pcsc_card; + reader_table[slot].disconnect_card = disconnect_pcsc_card; + reader_table[slot].close_reader = close_pcsc_reader; + reader_table[slot].reset_reader = reset_pcsc_reader; + reader_table[slot].get_status_reader = pcsc_get_status; + reader_table[slot].send_apdu_reader = pcsc_send_apdu; + reader_table[slot].dump_status_reader = dump_pcsc_reader_status; + + dump_reader_status (slot); + return slot; } +#endif /*!NEED_PCSC_WRAPPER */ -/* Actually send the APDU of length APDULEN to SLOT and return a - maximum of *BUFLEN data in BUFFER, the actual returned size will be - set to BUFLEN. Returns: CT API error code. */ -static int -pcsc_send_apdu (int slot, unsigned char *apdu, size_t apdulen, - unsigned char *buffer, size_t *buflen, - struct pininfo_s *pininfo) -{ -#ifdef NEED_PCSC_WRAPPER - long err; - reader_table_t slotp; - size_t len, full_len; - int i, n; - unsigned char msgbuf[9]; - int sw = SW_HOST_CARD_IO_ERROR; - - if (!reader_table[slot].atrlen - && (err = reset_pcsc_reader (slot))) - return err; - - 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 sw; - } - - 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 pcsc_error_to_sw (err); - } - - 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 = SW_HOST_INV_VALUE; - } - /* 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 sw; - -#else /*!NEED_PCSC_WRAPPER*/ - - long err; - struct pcsc_io_request_s send_pci; - unsigned long recv_len; - - if (!reader_table[slot].atrlen - && (err = reset_pcsc_reader (slot))) - return err; - - if (DBG_CARD_IO) - log_printhex (" PCSC_data:", apdu, apdulen); - - if ((reader_table[slot].pcsc.protocol & PCSC_PROTOCOL_T1)) - send_pci.protocol = PCSC_PROTOCOL_T1; - else - send_pci.protocol = PCSC_PROTOCOL_T0; - send_pci.pci_len = sizeof send_pci; - recv_len = *buflen; - err = pcsc_transmit (reader_table[slot].pcsc.card, - &send_pci, apdu, apdulen, - NULL, buffer, &recv_len); - *buflen = recv_len; - if (err) - log_error ("pcsc_transmit failed: %s (0x%lx)\n", - pcsc_error_string (err), err); - - return pcsc_error_to_sw (err); -#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 close 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); - xfree (reader_table[slot].rdrname); - reader_table[slot].rdrname = NULL; - reader_table[slot].used = 0; - return 0; -#endif /*!NEED_PCSC_WRAPPER*/ -} - -/* Note: It is a pitty that we can't return proper error codes. */ -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. */ +#ifdef NEED_PCSC_WRAPPER +static int +open_pcsc_reader_wrapped (const char *portstr) +{ int slot; reader_table_t slotp; int fd, rp[2], wp[2]; @@ -1326,14 +1581,24 @@ open_pcsc_reader (const char *portstr) int err; unsigned int dummy_status; int sw = SW_HOST_CARD_IO_ERROR; + /* Note that we use the constant and not the fucntion because this + code won't be be used under Windows. */ + const char *wrapperpgm = GNUPG_LIBEXECDIR "/gnupg-pcsc-wrapper"; + + if (access (wrapperpgm, X_OK)) + { + log_error ("can't run PC/SC access module `%s': %s\n", + wrapperpgm, strerror (errno)); + return -1; + } 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 + /* Fire up the PC/SCc wrapper. We don't use any fork/exec code from + the common directy but implement it directly so that this file may still be source copied. */ if (pipe (rp) == -1) @@ -1391,14 +1656,9 @@ open_pcsc_reader (const char *portstr) 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; + close_all_fds (3, NULL); - execl (GNUPG_LIBDIR "/pcsc-wrapper", + execl (wrapperpgm, "pcsc-wrapper", "--", "1", /* API version */ @@ -1423,7 +1683,7 @@ open_pcsc_reader (const char *portstr) #endif while ( (i=WAIT (pid, NULL, 0)) == -1 && errno == EINTR) ; -#undef X +#undef WAIT /* Now send the open request. */ msgbuf[0] = 0x01; /* OPEN command. */ @@ -1459,7 +1719,8 @@ open_pcsc_reader (const char *portstr) (unsigned long)len); goto command_failed; } - err = (msgbuf[5] << 24) | (msgbuf[6] << 16) | (msgbuf[7] << 8 ) | msgbuf[8]; + err = PCSC_ERR_MASK ((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)); @@ -1482,7 +1743,9 @@ open_pcsc_reader (const char *portstr) } /* If we got to here we know that a card is present and usable. Thus remember this. */ - slotp->last_status = (1|2|4| 0x8000); + slotp->last_status = ( APDU_CARD_USABLE + | APDU_CARD_PRESENT + | APDU_CARD_ACTIVE); } slotp->atrlen = len; @@ -1490,7 +1753,6 @@ open_pcsc_reader (const char *portstr) reader_table[slot].reset_reader = reset_pcsc_reader; reader_table[slot].get_status_reader = pcsc_get_status; reader_table[slot].send_apdu_reader = pcsc_send_apdu; - reader_table[slot].check_keypad = NULL; reader_table[slot].dump_status_reader = dump_pcsc_reader_status; /* Read the status so that IS_T0 will be set. */ @@ -1510,145 +1772,20 @@ open_pcsc_reader (const char *portstr) /* There is no way to return SW. */ return -1; -#else /*!NEED_PCSC_WRAPPER */ - long err; - int slot; - char *list = NULL; - unsigned long nreader, listlen, atrlen; - char *p; - unsigned long card_state, card_protocol; - - slot = new_reader_slot (); - if (slot == -1) - return -1; - - err = pcsc_establish_context (PCSC_SCOPE_SYSTEM, NULL, NULL, - &reader_table[slot].pcsc.context); - if (err) - { - log_error ("pcsc_establish_context failed: %s (0x%lx)\n", - pcsc_error_string (err), err); - reader_table[slot].used = 0; - return -1; - } - - err = pcsc_list_readers (reader_table[slot].pcsc.context, - NULL, NULL, &nreader); - if (!err) - { - list = xtrymalloc (nreader+1); /* Better add 1 for safety reasons. */ - if (!list) - { - log_error ("error allocating memory for reader list\n"); - pcsc_release_context (reader_table[slot].pcsc.context); - reader_table[slot].used = 0; - return -1 /*SW_HOST_OUT_OF_CORE*/; - } - err = pcsc_list_readers (reader_table[slot].pcsc.context, - NULL, list, &nreader); - } - if (err) - { - log_error ("pcsc_list_readers failed: %s (0x%lx)\n", - pcsc_error_string (err), err); - pcsc_release_context (reader_table[slot].pcsc.context); - reader_table[slot].used = 0; - xfree (list); - return -1 /*pcsc_error_to_sw (err)*/; - } - - listlen = nreader; - p = list; - while (nreader) - { - if (!*p && !p[1]) - break; - if (*p) - log_info ("detected reader `%s'\n", p); - if (nreader < (strlen (p)+1)) - { - log_error ("invalid response from pcsc_list_readers\n"); - break; - } - nreader -= strlen (p)+1; - p += strlen (p) + 1; - } - - reader_table[slot].rdrname = xtrymalloc (strlen (portstr? portstr : list)+1); - if (!reader_table[slot].rdrname) - { - log_error ("error allocating memory for reader name\n"); - pcsc_release_context (reader_table[slot].pcsc.context); - reader_table[slot].used = 0; - return -1 /*SW_HOST_OUT_OF_CORE*/; - } - strcpy (reader_table[slot].rdrname, portstr? portstr : list); - xfree (list); - list = NULL; - - err = pcsc_connect (reader_table[slot].pcsc.context, - reader_table[slot].rdrname, - PCSC_SHARE_EXCLUSIVE, - PCSC_PROTOCOL_T0|PCSC_PROTOCOL_T1, - &reader_table[slot].pcsc.card, - &reader_table[slot].pcsc.protocol); - if (err == PCSC_E_NO_SMARTCARD) - reader_table[slot].pcsc.card = 0; - else if (err) - { - log_error ("pcsc_connect failed: %s (0x%lx)\n", - pcsc_error_string (err), err); - pcsc_release_context (reader_table[slot].pcsc.context); - xfree (reader_table[slot].rdrname); - reader_table[slot].rdrname = NULL; - reader_table[slot].used = 0; - return -1 /*pcsc_error_to_sw (err)*/; - } - - reader_table[slot].atrlen = 0; - reader_table[slot].last_status = 0; - if (!err) - { - char reader[250]; - unsigned long readerlen; - - atrlen = 32; - readerlen = sizeof reader -1 ; - err = pcsc_status (reader_table[slot].pcsc.card, - reader, &readerlen, - &card_state, &card_protocol, - reader_table[slot].atr, &atrlen); - if (err) - log_error ("pcsc_status failed: %s (0x%lx) %lu\n", - pcsc_error_string (err), err, readerlen); - else - { - if (atrlen >= DIM (reader_table[0].atr)) - log_bug ("ATR returned by pcsc_status is too large\n"); - reader_table[slot].atrlen = atrlen; - /* If we got to here we know that a card is present - and usable. Thus remember this. */ - reader_table[slot].last_status = (1|2|4| 0x8000); - reader_table[slot].is_t0 = !!(card_protocol & PCSC_PROTOCOL_T0); - } - } - - reader_table[slot].close_reader = close_pcsc_reader; - reader_table[slot].reset_reader = reset_pcsc_reader; - reader_table[slot].get_status_reader = pcsc_get_status; - reader_table[slot].send_apdu_reader = pcsc_send_apdu; - reader_table[slot].check_keypad = NULL; - reader_table[slot].dump_status_reader = dump_pcsc_reader_status; - -/* log_debug ("state from pcsc_status: 0x%lx\n", card_state); */ -/* log_debug ("protocol from pcsc_status: 0x%lx\n", card_protocol); */ - - dump_reader_status (slot); - return slot; -#endif /*!NEED_PCSC_WRAPPER */ } +#endif /*NEED_PCSC_WRAPPER*/ +static int +open_pcsc_reader (const char *portstr) +{ +#ifdef NEED_PCSC_WRAPPER + return open_pcsc_reader_wrapped (portstr); +#else + return open_pcsc_reader_direct (portstr); +#endif +} + #ifdef HAVE_LIBUSB @@ -1700,6 +1837,15 @@ reset_ccid_reader (int slot) } +static int +set_progress_cb_ccid_reader (int slot, gcry_handler_progress_t cb, void *cb_arg) +{ + reader_table_t slotp = reader_table + slot; + + return ccid_set_progress_cb (slotp->ccid.handle, cb, cb_arg); +} + + static int get_status_ccid (int slot, unsigned int *status) { @@ -1708,12 +1854,12 @@ get_status_ccid (int slot, unsigned int *status) rc = ccid_slot_status (reader_table[slot].ccid.handle, &bits); if (rc) - return -1; + return rc; if (bits == 0) - *status = 1|2|4; + *status = (APDU_CARD_USABLE|APDU_CARD_PRESENT|APDU_CARD_ACTIVE); else if (bits == 1) - *status = 2; + *status = APDU_CARD_PRESENT; else *status = 0; @@ -1738,7 +1884,7 @@ send_apdu_ccid (int slot, unsigned char *apdu, size_t apdulen, return err; if (DBG_CARD_IO) - log_printhex (" APDU_data:", apdu, apdulen); + log_printhex (" raw apdu:", apdu, apdulen); maxbuflen = *buflen; if (pininfo) @@ -1809,7 +1955,9 @@ open_ccid_reader (const char *portstr) { /* If we got to here we know that a card is present and usable. Thus remember this. */ - reader_table[slot].last_status = (1|2|4| 0x8000); + reader_table[slot].last_status = (APDU_CARD_USABLE + | APDU_CARD_PRESENT + | APDU_CARD_ACTIVE); } reader_table[slot].close_reader = close_ccid_reader; @@ -1819,6 +1967,10 @@ open_ccid_reader (const char *portstr) reader_table[slot].send_apdu_reader = send_apdu_ccid; reader_table[slot].check_keypad = check_ccid_keypad; reader_table[slot].dump_status_reader = dump_ccid_reader_status; + reader_table[slot].set_progress_cb = set_progress_cb_ccid_reader; + /* Our CCID reader code does not support T=0 at all, thus reset the + flag. */ + reader_table[slot].is_t0 = 0; dump_reader_status (slot); return slot; @@ -1913,7 +2065,7 @@ reset_rapdu_reader (int slot) rapdu_msg_release (msg); return sw; } - if (msg->datalen >= DIM (slotp->atr)) + if (msg->datalen > DIM (slotp->atr)) { log_error ("ATR returned by the RAPDU layer is too large\n"); rapdu_msg_release (msg); @@ -2094,7 +2246,7 @@ open_rapdu_reader (int portno, rapdu_strerror (msg->cmd)); goto failure; } - if (msg->datalen >= DIM (slotp->atr)) + if (msg->datalen > DIM (slotp->atr)) { log_error ("ATR returned by the RAPDU layer is too large\n"); goto failure; @@ -2338,6 +2490,15 @@ apdu_open_remote_reader (const char *portstr, writefnc, writefnc_value, closefnc, closefnc_value); #else + (void)portstr; + (void)cookie; + (void)length; + (void)readfnc; + (void)readfnc_value; + (void)writefnc; + (void)writefnc_value; + (void)closefnc; + (void)closefnc_value; #ifdef _WIN32 errno = ENOENT; #else @@ -2351,21 +2512,58 @@ apdu_open_remote_reader (const char *portstr, int apdu_close_reader (int slot) { + int sw; + if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) return SW_HOST_NO_DRIVER; + sw = apdu_disconnect (slot); + if (sw) + return sw; if (reader_table[slot].close_reader) return reader_table[slot].close_reader (slot); return SW_HOST_NOT_SUPPORTED; } + +/* Function suitable for a cleanup function to close all reader. It + should not be used if the reader will be opened again. The reason + for implementing this to properly close USB devices so that they + will startup the next time without error. */ +void +apdu_prepare_exit (void) +{ + static int sentinel; + int slot; + + if (!sentinel) + { + sentinel = 1; + for (slot = 0; slot < MAX_READER; slot++) + if (reader_table[slot].used) + { + apdu_disconnect (slot); + if (reader_table[slot].close_reader) + reader_table[slot].close_reader (slot); + reader_table[slot].used = 0; + } + sentinel = 0; + } +} + + /* Shutdown a reader; that is basically the same as a close but keeps - the handle ready for later use. A apdu_reset_header should be used - to get it active again. */ + the handle ready for later use. A apdu_reset_reader or apdu_connect + should be used to get it active again. */ int apdu_shutdown_reader (int slot) { + int sw; + if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) return SW_HOST_NO_DRIVER; + sw = apdu_disconnect (slot); + if (sw) + return sw; if (reader_table[slot].shutdown_reader) return reader_table[slot].shutdown_reader (slot); return SW_HOST_NOT_SUPPORTED; @@ -2383,6 +2581,91 @@ apdu_enum_reader (int slot, int *used) return 0; } + +/* Connect a card. This is used to power up the card and make sure + that an ATR is available. */ +int +apdu_connect (int slot) +{ + int sw; + + if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) + return SW_HOST_NO_DRIVER; + + /* Only if the access method provides a connect function we use it. + If not, we expect that the card has been implicitly connected by + apdu_open_reader. */ + if (reader_table[slot].connect_card) + { + sw = lock_slot (slot); + if (!sw) + { + sw = reader_table[slot].connect_card (slot); + unlock_slot (slot); + } + } + else + sw = 0; + + /* We need to call apdu_get_status_internal, so that the last-status + machinery gets setup properly even if a card is inserted while + scdaemon is fired up and apdu_get_status has not yet been called. + Without that we would force a reset of the card with the next + call to apdu_get_status. */ + apdu_get_status_internal (slot, 1, 1, NULL, NULL); + + return sw; +} + + +int +apdu_disconnect (int slot) +{ + int sw; + + if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) + return SW_HOST_NO_DRIVER; + + if (reader_table[slot].disconnect_card) + { + sw = lock_slot (slot); + if (!sw) + { + sw = reader_table[slot].disconnect_card (slot); + unlock_slot (slot); + } + } + else + sw = 0; + return sw; +} + + +/* Set the progress callback of SLOT to CB and its args to CB_ARG. If + CB is NULL the progress callback is removed. */ +int +apdu_set_progress_cb (int slot, gcry_handler_progress_t cb, void *cb_arg) +{ + int sw; + + if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) + return SW_HOST_NO_DRIVER; + + if (reader_table[slot].set_progress_cb) + { + sw = lock_slot (slot); + if (!sw) + { + sw = reader_table[slot].set_progress_cb (slot, cb, cb_arg); + unlock_slot (slot); + } + } + else + sw = 0; + return sw; +} + + /* Do a reset for the card in reader at SLOT. */ int apdu_reset (int slot) @@ -2403,7 +2686,9 @@ apdu_reset (int slot) { /* If we got to here we know that a card is present and usable. Thus remember this. */ - reader_table[slot].last_status = (1|2|4| 0x8000); + reader_table[slot].last_status = (APDU_CARD_USABLE + | APDU_CARD_PRESENT + | APDU_CARD_ACTIVE); } unlock_slot (slot); @@ -2447,7 +2732,9 @@ apdu_activate (int slot) { /* If we got to here we know that a card is present and usable. Thus remember this. */ - reader_table[slot].last_status = (1|2|4| 0x8000); + reader_table[slot].last_status = (APDU_CARD_USABLE + | APDU_CARD_PRESENT + | APDU_CARD_ACTIVE); } } } @@ -2458,7 +2745,6 @@ apdu_activate (int slot) } - unsigned char * apdu_get_atr (int slot, size_t *atrlen) { @@ -2466,7 +2752,8 @@ apdu_get_atr (int slot, size_t *atrlen) if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) return NULL; - + if (!reader_table[slot].atrlen) + return NULL; buf = xtrymalloc (reader_table[slot].atrlen); if (!buf) return NULL; @@ -2481,20 +2768,20 @@ apdu_get_atr (int slot, size_t *atrlen) 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] + APDU_CARD_USABLE (bit 0) = card present and usable + APDU_CARD_PRESENT (bit 1) = card present + APDU_CARD_ACTIVE (bit 2) = card active + (bit 3) = card access locked [not yet implemented] - For must application, testing bit 0 is sufficient. + For must applications, testing 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) +static int +apdu_get_status_internal (int slot, int hang, int no_atr_reset, + unsigned int *status, unsigned int *changed) { int sw; unsigned int s; @@ -2516,17 +2803,18 @@ apdu_get_status (int slot, int hang, return sw; } - /* Keep track of changes. We use one extra bit to test whether we - have checked the status at least once. */ - if ( s != (reader_table[slot].last_status & 0x07ff) - || !reader_table[slot].last_status ) + /* Keep track of changes. */ + if (s != reader_table[slot].last_status + || !reader_table[slot].any_status ) { reader_table[slot].change_counter++; - /* Make sure that the ATR is invalid so that a reset will be by - activate. */ - reader_table[slot].atrlen = 0; + /* Make sure that the ATR is invalid so that a reset will be + triggered by apdu_activate. */ + if (!no_atr_reset) + reader_table[slot].atrlen = 0; } - reader_table[slot].last_status = (s | 0x8000); + reader_table[slot].any_status = 1; + reader_table[slot].last_status = s; if (status) *status = s; @@ -2536,6 +2824,15 @@ apdu_get_status (int slot, int hang, } +/* See above for a description. */ +int +apdu_get_status (int slot, int hang, + unsigned int *status, unsigned int *changed) +{ + return apdu_get_status_internal (slot, hang, 0, status, changed); +} + + /* Check whether the reader supports the ISO command code COMMAND on the keypad. Return 0 on success. For a description of the pin parameters, see ccid-driver.c */ @@ -2567,88 +2864,245 @@ send_apdu (int slot, unsigned char *apdu, size_t apdulen, if (reader_table[slot].send_apdu_reader) return reader_table[slot].send_apdu_reader (slot, apdu, apdulen, - buffer, buflen, pininfo); + buffer, buflen, + pininfo); else return SW_HOST_NOT_SUPPORTED; } -/* Core APDU trabceiver function. Parameters are described at +/* Core APDU tranceiver function. Parameters are described at apdu_send_le with the exception of PININFO which indicates keypad - related operations if not NULL. */ + related operations if not NULL. If EXTENDED_MODE is not 0 + command chaining or extended length will be used according to these + values: + n < 0 := Use command chaining with the data part limited to -n + in each chunk. If -1 is used a default value is used. + n == 0 := No extended mode or command chaining. + n == 1 := Use extended length for input and output without a + length limit. + n > 1 := Use extended length with up to N bytes. + +*/ static int 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, - struct pininfo_s *pininfo) + struct pininfo_s *pininfo, int extended_mode) { -#define RESULTLEN 256 - unsigned char result[RESULTLEN+10]; /* 10 extra in case of bugs in - the driver. */ +#define SHORT_RESULT_BUFFER_SIZE 258 + /* We allocate 8 extra bytes as a safety margin towards a driver bug. */ + unsigned char short_result_buffer[SHORT_RESULT_BUFFER_SIZE+10]; + unsigned char *result_buffer = NULL; + size_t result_buffer_size; + unsigned char *result; size_t resultlen; - unsigned char apdu[5+256+1]; + unsigned char short_apdu_buffer[5+256+1]; + unsigned char *apdu_buffer = NULL; + size_t apdu_buffer_size; + unsigned char *apdu; size_t apdulen; int sw; - long rc; /* we need a long here due to PC/SC. */ + long rc; /* We need a long here due to PC/SC. */ + int did_exact_length_hack = 0; + int use_chaining = 0; + int use_extended_length = 0; + int lc_chunk; 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); + log_debug ("send apdu: c=%02X i=%02X p1=%02X p2=%02X lc=%d le=%d em=%d\n", + class, ins, p0, p1, lc, le, extended_mode); if (lc != -1 && (lc > 255 || lc < 0)) - return SW_WRONG_LENGTH; - if (le != -1 && (le > 256 || le < 0)) - return SW_WRONG_LENGTH; + { + /* Data does not fit into an APDU. What we do now depends on + the EXTENDED_MODE parameter. */ + if (!extended_mode) + return SW_WRONG_LENGTH; /* No way to send such an APDU. */ + else if (extended_mode > 0) + use_extended_length = 1; + else if (extended_mode < 0) + { + /* Send APDU using chaining mode. */ + if (lc > 16384) + return SW_WRONG_LENGTH; /* Sanity check. */ + if ((class&0xf0) != 0) + return SW_HOST_INV_VALUE; /* Upper 4 bits need to be 0. */ + use_chaining = extended_mode == -1? 255 : -extended_mode; + use_chaining &= 0xff; + } + else + return SW_HOST_INV_VALUE; + } + else if (lc == -1 && extended_mode > 0) + use_extended_length = 1; + + if (le != -1 && (le > (extended_mode > 0? 255:256) || le < 0)) + { + /* Expected Data does not fit into an APDU. What we do now + depends on the EXTENDED_MODE parameter. Note that a check + for command chaining does not make sense because we are + looking at Le. */ + if (!extended_mode) + return SW_WRONG_LENGTH; /* No way to send such an APDU. */ + else if (use_extended_length) + ; /* We are already using extended length. */ + else if (extended_mode > 0) + use_extended_length = 1; + else + return SW_HOST_INV_VALUE; + } + 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; - apdu[apdulen++] = p0; - apdu[apdulen++] = p1; - if (lc != -1) + if (use_extended_length) { - apdu[apdulen++] = lc; - memcpy (apdu+apdulen, data, lc); - apdulen += lc; - /* T=0 does not allow the use of Lc together with Le; thus - disable Le in this case. */ if (reader_table[slot].is_t0) - le = -1; + return SW_HOST_NOT_SUPPORTED; + + /* Space for: cls/ins/p1/p2+Z+2_byte_Lc+Lc+2_byte_Le. */ + apdu_buffer_size = 4 + 1 + (lc >= 0? (2+lc):0) + 2; + apdu_buffer = xtrymalloc (apdu_buffer_size + 10); + if (!apdu_buffer) + return SW_HOST_OUT_OF_CORE; + apdu = apdu_buffer; } - if (le != -1) - apdu[apdulen++] = le; /* Truncation is okay because 0 means 256. */ - assert (sizeof (apdu) >= apdulen); - /* As safeguard don't pass any garbage from the stack to the driver. */ - memset (apdu+apdulen, 0, sizeof (apdu) - apdulen); - resultlen = RESULTLEN; - rc = send_apdu (slot, apdu, apdulen, result, &resultlen, pininfo); - if (rc || resultlen < 2) + else { - /* We use log_info here so that in case of a transient error, and - if this module is used by gpg standalone, the error counter - isn't incremented. */ - log_info ("apdu_send_simple(%d) failed: %s\n", - slot, apdu_strerror (rc)); - unlock_slot (slot); - return rc? rc : SW_HOST_INCOMPLETE_CARD_RESPONSE; + apdu_buffer_size = sizeof short_apdu_buffer; + apdu = short_apdu_buffer; } - sw = (result[resultlen-2] << 8) | result[resultlen-1]; - /* store away the returned data but strip the statusword. */ + + if (use_extended_length && (le > 256 || le < 0)) + { + result_buffer_size = le < 0? 4096 : le; + result_buffer = xtrymalloc (result_buffer_size + 10); + if (!result_buffer) + { + xfree (apdu_buffer); + return SW_HOST_OUT_OF_CORE; + } + result = result_buffer; + } + else + { + result_buffer_size = SHORT_RESULT_BUFFER_SIZE; + result = short_result_buffer; + } +#undef SHORT_RESULT_BUFFER_SIZE + + if ((sw = lock_slot (slot))) + { + xfree (apdu_buffer); + xfree (result_buffer); + return sw; + } + + do + { + if (use_extended_length) + { + use_chaining = 0; + apdulen = 0; + apdu[apdulen++] = class; + apdu[apdulen++] = ins; + apdu[apdulen++] = p0; + apdu[apdulen++] = p1; + apdu[apdulen++] = 0; /* Z byte: Extended length marker. */ + if (lc >= 0) + { + apdu[apdulen++] = ((lc >> 8) & 0xff); + apdu[apdulen++] = (lc & 0xff); + memcpy (apdu+apdulen, data, lc); + data += lc; + apdulen += lc; + } + if (le != -1) + { + apdu[apdulen++] = ((le >> 8) & 0xff); + apdu[apdulen++] = (le & 0xff); + } + } + else + { + apdulen = 0; + apdu[apdulen] = class; + if (use_chaining && lc > 255) + { + apdu[apdulen] |= 0x10; + assert (use_chaining < 256); + lc_chunk = use_chaining; + lc -= use_chaining; + } + else + { + use_chaining = 0; + lc_chunk = lc; + } + apdulen++; + apdu[apdulen++] = ins; + apdu[apdulen++] = p0; + apdu[apdulen++] = p1; + if (lc_chunk != -1) + { + apdu[apdulen++] = lc_chunk; + memcpy (apdu+apdulen, data, lc_chunk); + data += lc_chunk; + apdulen += lc_chunk; + /* T=0 does not allow the use of Lc together with Le; + thus disable Le in this case. */ + if (reader_table[slot].is_t0) + le = -1; + } + if (le != -1 && !use_chaining) + apdu[apdulen++] = le; /* Truncation is okay (0 means 256). */ + } + + exact_length_hack: + /* As a safeguard don't pass any garbage to the driver. */ + assert (apdulen <= apdu_buffer_size); + memset (apdu+apdulen, 0, apdu_buffer_size - apdulen); + resultlen = result_buffer_size; + rc = send_apdu (slot, apdu, apdulen, result, &resultlen, pininfo); + if (rc || resultlen < 2) + { + log_info ("apdu_send_simple(%d) failed: %s\n", + slot, apdu_strerror (rc)); + unlock_slot (slot); + xfree (apdu_buffer); + xfree (result_buffer); + return rc? rc : SW_HOST_INCOMPLETE_CARD_RESPONSE; + } + sw = (result[resultlen-2] << 8) | result[resultlen-1]; + if (!use_extended_length + && !did_exact_length_hack && SW_EXACT_LENGTH_P (sw)) + { + apdu[apdulen-1] = (sw & 0x00ff); + did_exact_length_hack = 1; + goto exact_length_hack; + } + } + while (use_chaining && sw == SW_SUCCESS); + + if (apdu_buffer) + { + xfree (apdu_buffer); + apdu_buffer = NULL; + apdu_buffer_size = 0; + } + + /* Store away the returned data but strip the statusword. */ resultlen -= 2; if (DBG_CARD_IO) { log_debug (" response: sw=%04X datalen=%d\n", sw, (unsigned int)resultlen); if ( !retbuf && (sw == SW_SUCCESS || (sw & 0xff00) == SW_MORE_DATA)) - log_printhex (" dump: ", result, resultlen); + log_printhex (" dump: ", result, resultlen); } if (sw == SW_SUCCESS || sw == SW_EOF_REACHED) @@ -2659,6 +3113,7 @@ send_le (int slot, int class, int ins, int p0, int p1, if (!*retbuf) { unlock_slot (slot); + xfree (result_buffer); return SW_HOST_OUT_OF_CORE; } *retbuflen = resultlen; @@ -2678,6 +3133,7 @@ send_le (int slot, int class, int ins, int p0, int p1, if (!*retbuf) { unlock_slot (slot); + xfree (result_buffer); return SW_HOST_OUT_OF_CORE; } assert (resultlen < bufsize); @@ -2692,20 +3148,24 @@ send_le (int slot, int class, int ins, int p0, int p1, if (DBG_CARD_IO) log_debug ("apdu_send_simple(%d): %d more bytes available\n", slot, len); + apdu_buffer_size = sizeof short_apdu_buffer; + apdu = short_apdu_buffer; apdulen = 0; apdu[apdulen++] = class; apdu[apdulen++] = 0xC0; apdu[apdulen++] = 0; apdu[apdulen++] = 0; apdu[apdulen++] = len; - memset (apdu+apdulen, 0, sizeof (apdu) - apdulen); - resultlen = RESULTLEN; + assert (apdulen <= apdu_buffer_size); + memset (apdu+apdulen, 0, apdu_buffer_size - apdulen); + resultlen = result_buffer_size; rc = send_apdu (slot, apdu, apdulen, result, &resultlen, NULL); if (rc || resultlen < 2) { log_error ("apdu_send_simple(%d) for get response failed: %s\n", slot, apdu_strerror (rc)); unlock_slot (slot); + xfree (result_buffer); return rc? rc : SW_HOST_INCOMPLETE_CARD_RESPONSE; } sw = (result[resultlen-2] << 8) | result[resultlen-1]; @@ -2731,6 +3191,7 @@ send_le (int slot, int class, int ins, int p0, int p1, if (!tmp) { unlock_slot (slot); + xfree (result_buffer); return SW_HOST_OUT_OF_CORE; } p = tmp + (p - *retbuf); @@ -2757,63 +3218,70 @@ send_le (int slot, int class, int ins, int p0, int p1, } unlock_slot (slot); + xfree (result_buffer); 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 given parameters: CLASS, INS, P0, P1, LC, DATA, LE. A value of -1 for LC won't sent this field and the data field; in this case DATA - must also be passed as NULL. The return value is the status word - or -1 for an invalid SLOT or other non card related error. If - RETBUF is not NULL, it will receive an allocated buffer with the - returned data. The length of that data will be put into - *RETBUFLEN. The caller is reponsible for releasing the buffer even - in case of errors. */ + must also be passed as NULL. If EXTENDED_MODE is not 0 command + chaining or extended length will be used; see send_le for details. + The return value is the status word or -1 for an invalid SLOT or + other non card related error. If RETBUF is not NULL, it will + receive an allocated buffer with the returned data. The length of + that data will be put into *RETBUFLEN. The caller is reponsible + for releasing the buffer even in case of errors. */ int -apdu_send_le(int slot, int class, int ins, int p0, int p1, +apdu_send_le(int slot, int extended_mode, + int class, int ins, int p0, int p1, int lc, const char *data, int le, unsigned char **retbuf, size_t *retbuflen) { return send_le (slot, class, ins, p0, p1, lc, data, le, retbuf, retbuflen, - NULL); + NULL, extended_mode); } /* Send an APDU to the card in SLOT. The APDU is created from all given parameters: CLASS, INS, P0, P1, LC, DATA. A value of -1 for LC won't sent this field and the data field; in this case DATA must - also be passed as NULL. The return value is the status word or -1 - for an invalid SLOT or other non card related error. If RETBUF is - not NULL, it will receive an allocated buffer with the returned - data. The length of that data will be put into *RETBUFLEN. The - caller is reponsible for releasing the buffer even in case of - errors. */ + also be passed as NULL. If EXTENDED_MODE is not 0 command chaining + or extended length will be used; see send_le for details. The + return value is the status word or -1 for an invalid SLOT or other + non card related error. If RETBUF is not NULL, it will receive an + allocated buffer with the returned data. The length of that data + will be put into *RETBUFLEN. The caller is reponsible for + releasing the buffer even in case of errors. */ int -apdu_send (int slot, int class, int ins, int p0, int p1, +apdu_send (int slot, int extended_mode, + int class, int ins, int p0, int p1, int lc, const char *data, unsigned char **retbuf, size_t *retbuflen) { return send_le (slot, class, ins, p0, p1, lc, data, 256, - retbuf, retbuflen, NULL); + retbuf, retbuflen, NULL, extended_mode); } /* Send an APDU to the card in SLOT. The APDU is created from all given parameters: CLASS, INS, P0, P1, LC, DATA. A value of -1 for LC won't sent this field and the data field; in this case DATA must - also be passed as NULL. The return value is the status word or -1 - for an invalid SLOT or other non card related error. No data will be - returned. */ + also be passed as NULL. If EXTENDED_MODE is not 0 command chaining + or extended length will be used; see send_le for details. The + return value is the status word or -1 for an invalid SLOT or other + non card related error. No data will be returned. */ int -apdu_send_simple (int slot, int class, int ins, int p0, int p1, +apdu_send_simple (int slot, int extended_mode, + int class, int ins, int p0, int p1, int lc, const char *data) { - return send_le (slot, class, ins, p0, p1, lc, data, -1, NULL, NULL, NULL); + return send_le (slot, class, ins, p0, p1, lc, data, -1, NULL, NULL, NULL, + extended_mode); } @@ -2831,29 +3299,36 @@ apdu_send_simple_kp (int slot, int class, int ins, int p0, int p1, pininfo.maxlen = pinlen_max; pininfo.padlen = pin_padlen; return send_le (slot, class, ins, p0, p1, lc, data, -1, - NULL, NULL, &pininfo); + NULL, NULL, &pininfo, 0); } /* This is a more generic version of the apdu sending routine. It takes an already formatted APDU in APDUDATA or length APDUDATALEN - and returns the with the APDU including the status word. With + and returns with an APDU including the status word. With HANDLE_MORE set to true this function will handle the MORE DATA status and return all APDUs concatenated with one status word at - the end. The function does not return a regular status word but 0 - on success. If the slot is locked, the fucntion returns - immediately.*/ + the end. If EXTENDED_LENGTH is != 0 extended lengths are allowed + with a max. result data length of EXTENDED_LENGTH bytes. The + function does not return a regular status word but 0 on success. + If the slot is locked, the function returns immediately with an + error. */ int -apdu_send_direct (int slot, const unsigned char *apdudata, size_t apdudatalen, +apdu_send_direct (int slot, size_t extended_length, + const unsigned char *apdudata, size_t apdudatalen, int handle_more, unsigned char **retbuf, size_t *retbuflen) { -#define RESULTLEN 256 - unsigned char apdu[5+256+1]; - size_t apdulen; - unsigned char result[RESULTLEN+10]; /* 10 extra in case of bugs in - the driver. */ +#define SHORT_RESULT_BUFFER_SIZE 258 + unsigned char short_result_buffer[SHORT_RESULT_BUFFER_SIZE+10]; + unsigned char *result_buffer = NULL; + size_t result_buffer_size; + unsigned char *result; size_t resultlen; + unsigned char short_apdu_buffer[5+256+10]; + unsigned char *apdu_buffer = NULL; + unsigned char *apdu; + size_t apdulen; int sw; long rc; /* we need a long here due to PC/SC. */ int class; @@ -2861,23 +3336,59 @@ apdu_send_direct (int slot, const unsigned char *apdudata, size_t apdudatalen, if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) return SW_HOST_NO_DRIVER; - if ((sw = trylock_slot (slot))) - return sw; + if (apdudatalen > 65535) + return SW_HOST_INV_VALUE; - /* We simply trunctate a too long APDU. */ - if (apdudatalen > sizeof apdu) - apdudatalen = sizeof apdu; + if (apdudatalen > sizeof short_apdu_buffer - 5) + { + apdu_buffer = xtrymalloc (apdudatalen + 5); + if (!apdu_buffer) + return SW_HOST_OUT_OF_CORE; + apdu = apdu_buffer; + } + else + { + apdu = short_apdu_buffer; + } apdulen = apdudatalen; memcpy (apdu, apdudata, apdudatalen); class = apdulen? *apdu : 0; - resultlen = RESULTLEN; + if (extended_length >= 256 && extended_length <= 65536) + { + result_buffer_size = extended_length; + result_buffer = xtrymalloc (result_buffer_size + 10); + if (!result_buffer) + { + xfree (apdu_buffer); + return SW_HOST_OUT_OF_CORE; + } + result = result_buffer; + } + else + { + result_buffer_size = SHORT_RESULT_BUFFER_SIZE; + result = short_result_buffer; + } +#undef SHORT_RESULT_BUFFER_SIZE + + if ((sw = trylock_slot (slot))) + { + xfree (apdu_buffer); + xfree (result_buffer); + return sw; + } + + resultlen = result_buffer_size; rc = send_apdu (slot, apdu, apdulen, result, &resultlen, NULL); + xfree (apdu_buffer); + apdu_buffer = NULL; if (rc || resultlen < 2) { log_error ("apdu_send_direct(%d) failed: %s\n", slot, apdu_strerror (rc)); unlock_slot (slot); + xfree (result_buffer); return rc? rc : SW_HOST_INCOMPLETE_CARD_RESPONSE; } sw = (result[resultlen-2] << 8) | result[resultlen-1]; @@ -2904,6 +3415,7 @@ apdu_send_direct (int slot, const unsigned char *apdudata, size_t apdudatalen, if (!*retbuf) { unlock_slot (slot); + xfree (result_buffer); return SW_HOST_OUT_OF_CORE; } assert (resultlen < bufsize); @@ -2918,20 +3430,22 @@ apdu_send_direct (int slot, const unsigned char *apdudata, size_t apdudatalen, if (DBG_CARD_IO) log_debug ("apdu_send_direct(%d): %d more bytes available\n", slot, len); + apdu = short_apdu_buffer; apdulen = 0; apdu[apdulen++] = class; apdu[apdulen++] = 0xC0; apdu[apdulen++] = 0; apdu[apdulen++] = 0; apdu[apdulen++] = len; - memset (apdu+apdulen, 0, sizeof (apdu) - apdulen); - resultlen = RESULTLEN; + memset (apdu+apdulen, 0, sizeof (short_apdu_buffer) - apdulen); + resultlen = result_buffer_size; rc = send_apdu (slot, apdu, apdulen, result, &resultlen, NULL); if (rc || resultlen < 2) { log_error ("apdu_send_direct(%d) for get response failed: %s\n", slot, apdu_strerror (rc)); unlock_slot (slot); + xfree (result_buffer); return rc ? rc : SW_HOST_INCOMPLETE_CARD_RESPONSE; } sw = (result[resultlen-2] << 8) | result[resultlen-1]; @@ -2957,6 +3471,7 @@ apdu_send_direct (int slot, const unsigned char *apdudata, size_t apdudatalen, if (!tmp) { unlock_slot (slot); + xfree (result_buffer); return SW_HOST_OUT_OF_CORE; } p = tmp + (p - *retbuf); @@ -2967,7 +3482,7 @@ apdu_send_direct (int slot, const unsigned char *apdudata, size_t apdudatalen, } } else - log_info ("apdu_send_sdirect(%d) " + log_info ("apdu_send_direct(%d) " "got unexpected status %04X from get response\n", slot, sw); } @@ -2989,6 +3504,7 @@ apdu_send_direct (int slot, const unsigned char *apdudata, size_t apdudatalen, if (!*retbuf) { unlock_slot (slot); + xfree (result_buffer); return SW_HOST_OUT_OF_CORE; } *retbuflen = resultlen; @@ -2997,9 +3513,10 @@ apdu_send_direct (int slot, const unsigned char *apdudata, size_t apdudatalen, } unlock_slot (slot); + xfree (result_buffer); - /* Append the status word - we reseved the two extra bytes while - allocating the buffer. */ + /* Append the status word. Note that we reserved the two extra + bytes while allocating the buffer. */ if (retbuf) { (*retbuf)[(*retbuflen)++] = (sw >> 8); @@ -3010,5 +3527,4 @@ apdu_send_direct (int slot, const unsigned char *apdudata, size_t apdudatalen, log_printhex (" dump: ", *retbuf, *retbuflen); return 0; -#undef RESULTLEN } diff --git a/g10/apdu.h b/g10/apdu.h index 92d073897..c47dea882 100644 --- a/g10/apdu.h +++ b/g10/apdu.h @@ -1,5 +1,5 @@ /* apdu.h - ISO 7816 APDU functions and low level I/O - * Copyright (C) 2003 Free Software Foundation, Inc. + * Copyright (C) 2003, 2008 Free Software Foundation, Inc. * * This file is part of GnuPG. * @@ -29,8 +29,11 @@ enum { SW_MORE_DATA = 0x6100, /* Note: that the low byte must be masked of.*/ SW_EOF_REACHED = 0x6282, + SW_TERM_STATE = 0x6285, /* Selected file is in termination state. */ SW_EEPROM_FAILURE = 0x6581, SW_WRONG_LENGTH = 0x6700, + SW_SM_NOT_SUP = 0x6882, /* Secure Messaging is not supported. */ + SW_CC_NOT_SUP = 0x6884, /* Command Chaining is not supported. */ SW_CHV_WRONG = 0x6982, SW_CHV_BLOCKED = 0x6983, SW_USE_CONDITIONS = 0x6985, @@ -38,6 +41,7 @@ enum { SW_NOT_SUPPORTED = 0x6a81, SW_FILE_NOT_FOUND = 0x6a82, SW_RECORD_NOT_FOUND = 0x6a83, + SW_BAD_LC = 0x6a87, /* Lc does not match command or p1/p2. */ SW_REF_NOT_FOUND = 0x6a88, SW_BAD_P0_P1 = 0x6b00, SW_EXACT_LENGTH = 0x6c00, @@ -62,13 +66,20 @@ enum { SW_HOST_GENERAL_ERROR = 0x1000b, SW_HOST_NO_READER = 0x1000c, SW_HOST_ABORTED = 0x1000d, - SW_HOST_NO_KEYPAD = 0x1000e + SW_HOST_NO_KEYPAD = 0x1000e, + SW_HOST_ALREADY_CONNECTED = 0x1000f }; #define SW_EXACT_LENGTH_P(a) (((a)&~0xff) == SW_EXACT_LENGTH) +/* Bit flags for the card status. */ +#define APDU_CARD_USABLE (1) /* Card is present and ready for use. */ +#define APDU_CARD_PRESENT (2) /* Card is just present. */ +#define APDU_CARD_ACTIVE (4) /* Card is active. */ + + /* Note , that apdu_open_reader returns no status word but -1 on error. */ int apdu_open_reader (const char *portstr); int apdu_open_remote_reader (const char *portstr, @@ -83,13 +94,19 @@ int apdu_open_remote_reader (const char *portstr, void *closefnc_value); int apdu_shutdown_reader (int slot); int apdu_close_reader (int slot); +void apdu_prepare_exit (void); int apdu_enum_reader (int slot, int *used); unsigned char *apdu_get_atr (int slot, size_t *atrlen); const char *apdu_strerror (int rc); -/* These apdu functions do return status words. */ +/* These APDU functions return status words. */ + +int apdu_connect (int slot); +int apdu_disconnect (int slot); + +int apdu_set_progress_cb (int slot, gcry_handler_progress_t cb, void *cb_arg); int apdu_activate (int slot); int apdu_reset (int slot); @@ -97,19 +114,21 @@ int apdu_get_status (int slot, int hang, unsigned int *status, unsigned int *changed); int apdu_check_keypad (int slot, int command, int pin_mode, int pinlen_min, int pinlen_max, int pin_padlen); -int apdu_send_simple (int slot, int class, int ins, int p0, int p1, +int apdu_send_simple (int slot, int extended_mode, + int class, int ins, int p0, int p1, int lc, const char *data); int apdu_send_simple_kp (int slot, int class, int ins, int p0, int p1, int lc, const char *data, int pin_mode, int pinlen_min, int pinlen_max, int pin_padlen); -int apdu_send (int slot, int class, int ins, int p0, int p1, - int lc, const char *data, +int apdu_send (int slot, int extended_mode, + int class, int ins, int p0, int p1, int lc, const char *data, unsigned char **retbuf, size_t *retbuflen); -int apdu_send_le (int slot, int class, int ins, int p0, int p1, +int apdu_send_le (int slot, int extended_mode, + int class, int ins, int p0, int p1, int lc, const char *data, int le, unsigned char **retbuf, size_t *retbuflen); -int apdu_send_direct (int slot, +int apdu_send_direct (int slot, size_t extended_length, const unsigned char *apdudata, size_t apdudatalen, int handle_more, unsigned char **retbuf, size_t *retbuflen); diff --git a/g10/app-common.h b/g10/app-common.h index 0e28487eb..4b2e13e3a 100644 --- a/g10/app-common.h +++ b/g10/app-common.h @@ -1,5 +1,5 @@ /* app-common.h - Common declarations for all card applications - * Copyright (C) 2003, 2005 Free Software Foundation, Inc. + * Copyright (C) 2003, 2005, 2008 Free Software Foundation, Inc. * * This file is part of GnuPG. * @@ -31,14 +31,26 @@ #endif +#define APP_CHANGE_FLAG_RESET 1 +#define APP_CHANGE_FLAG_NULLPIN 2 + + 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 - unsupported operations the particular - function pointer is set to NULL */ - int slot; /* Used reader. */ + /* Number of connections currently using this application context. + If this is not 0 the application has been initialized and the + function pointers may be used. Note that for unsupported + operations the particular function pointer is set to NULL */ + unsigned int ref_count; + + /* Flag indicating that a reset has been done for that application + and that this context is merely lingering and just should not be + reused. */ + int no_reuse; + + /* Used reader slot. */ + int slot; /* If this is used by GnuPG 1.4 we need to know the assuan context in case we need to divert the operation to an already running @@ -59,7 +71,7 @@ struct app_ctx_s { struct app_local_s *app_local; /* Local to the application. */ struct { void (*deinit) (app_t app); - gpg_error_t (*learn_status) (app_t app, ctrl_t ctrl); + gpg_error_t (*learn_status) (app_t app, ctrl_t ctrl, unsigned int flags); gpg_error_t (*readcert) (app_t app, const char *certid, unsigned char **cert, size_t *certlen); gpg_error_t (*readkey) (app_t app, const char *certid, @@ -85,17 +97,23 @@ struct app_ctx_s { void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen); + gpg_error_t (*writecert) (app_t app, ctrl_t ctrl, + const char *certid, + gpg_error_t (*pincb)(void*,const char *,char **), + void *pincb_arg, + const unsigned char *data, size_t datalen); gpg_error_t (*writekey) (app_t app, ctrl_t ctrl, - const char *certid, unsigned int flags, + const char *keyid, unsigned int flags, gpg_error_t (*pincb)(void*,const char *,char **), void *pincb_arg, const unsigned char *pk, size_t pklen); gpg_error_t (*genkey) (app_t app, ctrl_t ctrl, - const char *keynostr, unsigned int flags, - gpg_error_t (*pincb)(void*, const char *, char **), - void *pincb_arg); + const char *keynostr, unsigned int flags, + time_t createtime, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg); gpg_error_t (*change_pin) (app_t app, ctrl_t ctrl, - const char *chvnostr, int reset_mode, + const char *chvnostr, unsigned int flags, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg); gpg_error_t (*check_pin) (app_t app, const char *keyidstr, @@ -117,17 +135,23 @@ gpg_error_t app_openpgp_storekey (app_t app, int keyno, void *pincb_arg); #else /*-- app-help.c --*/ +unsigned int app_help_count_bits (const unsigned char *a, size_t len); 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_dump_state (void); +void application_notify_card_reset (int slot); +gpg_error_t check_application_conflict (ctrl_t ctrl, const char *name); gpg_error_t select_application (ctrl_t ctrl, int slot, const char *name, app_t *r_app); +char *get_supported_applications (void); void release_application (app_t app); gpg_error_t app_munge_serialno (app_t app); gpg_error_t app_get_serial_and_stamp (app_t app, char **serial, time_t *stamp); -gpg_error_t app_write_learn_status (app_t app, ctrl_t ctrl); +gpg_error_t app_write_learn_status (app_t app, ctrl_t ctrl, + unsigned int flags); gpg_error_t app_readcert (app_t app, const char *certid, unsigned char **cert, size_t *certlen); gpg_error_t app_readkey (app_t app, const char *keyid, @@ -152,15 +176,21 @@ gpg_error_t app_decipher (app_t app, const char *keyidstr, void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen ); +gpg_error_t app_writecert (app_t app, ctrl_t ctrl, + const char *certidstr, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const unsigned char *keydata, size_t keydatalen); gpg_error_t app_writekey (app_t app, ctrl_t ctrl, const char *keyidstr, unsigned int flags, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const unsigned char *keydata, size_t keydatalen); gpg_error_t app_genkey (app_t app, ctrl_t ctrl, - const char *keynostr, unsigned int flags, - gpg_error_t (*pincb)(void*, const char *, char **), - void *pincb_arg); + const char *keynostr, unsigned int flags, + time_t createtime, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg); gpg_error_t app_get_challenge (app_t app, size_t nbytes, unsigned char *buffer); gpg_error_t app_change_pin (app_t app, ctrl_t ctrl, @@ -184,6 +214,9 @@ gpg_error_t app_select_dinsig (app_t app); /*-- app-p15.c --*/ gpg_error_t app_select_p15 (app_t app); +/*-- app-geldkarte.c --*/ +gpg_error_t app_select_geldkarte (app_t app); + #endif diff --git a/g10/app-openpgp.c b/g10/app-openpgp.c index 9c949f323..d2b2bdd3f 100644 --- a/g10/app-openpgp.c +++ b/g10/app-openpgp.c @@ -1,5 +1,6 @@ /* app-openpgp.c - The OpenPGP card application. - * Copyright (C) 2003, 2004, 2005, 2007 Free Software Foundation, Inc. + * Copyright (C) 2003, 2004, 2005, 2007, 2008, + * 2009 Free Software Foundation, Inc. * * This file is part of GnuPG. * @@ -19,6 +20,29 @@ * $Id$ */ +/* Some notes: + + CHV means Card Holder Verification and is nothing else than a PIN + or password. That term seems to have been used originally with GSM + cards. Version v2 of the specs changes the term to the clearer + term PW for password. We use the terms here interchangeable + because we do not want to change existing strings i18n wise. + + Version 2 of the specs also drops the separate PW2 which was + required in v1 due to ISO requirements. It is now possible to have + one physical PW but two reference to it so that they can be + individually be verified (e.g. to implement a forced verification + for one key). Thus you will noticed the use of PW2 with the verify + command but not with change_reference_data because the latter + operates directly on the physical PW. + + The Reset Code (RC) as implemented by v2 cards uses the same error + counter as the PW2 of v1 cards. By default no RC is set and thus + that error counter is set to 0. After setting the RC the error + counter will be initialized to 3. + + */ + #include #include #include @@ -46,49 +70,66 @@ #include "tlv.h" +/* A table describing the DOs of the card. */ static struct { int tag; int constructed; int get_from; /* Constructed DO with this DO or 0 for direct access. */ - int binary; - int dont_cache; - int flush_on_error; - int get_immediate_in_v11; /* Enable a hack to bypass the cache of - this data object if it is used in 1.1 - and later versions of the card. This - does not work with composite DO and is - currently only useful for the CHV - status bytes. */ + int binary:1; + int dont_cache:1; + int flush_on_error:1; + int get_immediate_in_v11:1; /* Enable a hack to bypass the cache of + this data object if it is used in 1.1 + and later versions of the card. This + does not work with composite DO and + is currently only useful for the CHV + status bytes. */ + int try_extlen:1; /* Large object; try to use an extended + length APDU. */ char *desc; } data_objects[] = { - { 0x005E, 0, 0, 1, 0, 0, 0, "Login Data" }, - { 0x5F50, 0, 0, 0, 0, 0, 0, "URL" }, - { 0x0065, 1, 0, 1, 0, 0, 0, "Cardholder Related Data"}, - { 0x005B, 0, 0x65, 0, 0, 0, 0, "Name" }, - { 0x5F2D, 0, 0x65, 0, 0, 0, 0, "Language preferences" }, - { 0x5F35, 0, 0x65, 0, 0, 0, 0, "Sex" }, - { 0x006E, 1, 0, 1, 0, 0, 0, "Application Related Data" }, - { 0x004F, 0, 0x6E, 1, 0, 0, 0, "AID" }, - { 0x0073, 1, 0, 1, 0, 0, 0, "Discretionary Data Objects" }, - { 0x0047, 0, 0x6E, 1, 1, 0, 0, "Card Capabilities" }, - { 0x00C0, 0, 0x6E, 1, 1, 0, 0, "Extended Card Capabilities" }, - { 0x00C1, 0, 0x6E, 1, 1, 0, 0, "Algorithm Attributes Signature" }, - { 0x00C2, 0, 0x6E, 1, 1, 0, 0, "Algorithm Attributes Decryption" }, - { 0x00C3, 0, 0x6E, 1, 1, 0, 0, "Algorithm Attributes Authentication" }, - { 0x00C4, 0, 0x6E, 1, 0, 1, 1, "CHV Status Bytes" }, - { 0x00C5, 0, 0x6E, 1, 0, 0, 0, "Fingerprints" }, - { 0x00C6, 0, 0x6E, 1, 0, 0, 0, "CA Fingerprints" }, - { 0x00CD, 0, 0x6E, 1, 0, 0, 0, "Generation time" }, - { 0x007A, 1, 0, 1, 0, 0, 0, "Security Support Template" }, - { 0x0093, 0, 0x7A, 1, 1, 0, 0, "Digital Signature Counter" }, - { 0x0101, 0, 0, 0, 0, 0, 0, "Private DO 1"}, - { 0x0102, 0, 0, 0, 0, 0, 0, "Private DO 2"}, - { 0x0103, 0, 0, 0, 0, 0, 0, "Private DO 3"}, - { 0x0104, 0, 0, 0, 0, 0, 0, "Private DO 4"}, + { 0x005E, 0, 0, 1, 0, 0, 0, 0, "Login Data" }, + { 0x5F50, 0, 0, 0, 0, 0, 0, 0, "URL" }, + { 0x5F52, 0, 0, 1, 0, 0, 0, 0, "Historical Bytes" }, + { 0x0065, 1, 0, 1, 0, 0, 0, 0, "Cardholder Related Data"}, + { 0x005B, 0, 0x65, 0, 0, 0, 0, 0, "Name" }, + { 0x5F2D, 0, 0x65, 0, 0, 0, 0, 0, "Language preferences" }, + { 0x5F35, 0, 0x65, 0, 0, 0, 0, 0, "Sex" }, + { 0x006E, 1, 0, 1, 0, 0, 0, 0, "Application Related Data" }, + { 0x004F, 0, 0x6E, 1, 0, 0, 0, 0, "AID" }, + { 0x0073, 1, 0, 1, 0, 0, 0, 0, "Discretionary Data Objects" }, + { 0x0047, 0, 0x6E, 1, 1, 0, 0, 0, "Card Capabilities" }, + { 0x00C0, 0, 0x6E, 1, 1, 0, 0, 0, "Extended Card Capabilities" }, + { 0x00C1, 0, 0x6E, 1, 1, 0, 0, 0, "Algorithm Attributes Signature" }, + { 0x00C2, 0, 0x6E, 1, 1, 0, 0, 0, "Algorithm Attributes Decryption" }, + { 0x00C3, 0, 0x6E, 1, 1, 0, 0, 0, "Algorithm Attributes Authentication" }, + { 0x00C4, 0, 0x6E, 1, 0, 1, 1, 0, "CHV Status Bytes" }, + { 0x00C5, 0, 0x6E, 1, 0, 0, 0, 0, "Fingerprints" }, + { 0x00C6, 0, 0x6E, 1, 0, 0, 0, 0, "CA Fingerprints" }, + { 0x00CD, 0, 0x6E, 1, 0, 0, 0, 0, "Generation time" }, + { 0x007A, 1, 0, 1, 0, 0, 0, 0, "Security Support Template" }, + { 0x0093, 0, 0x7A, 1, 1, 0, 0, 0, "Digital Signature Counter" }, + { 0x0101, 0, 0, 0, 0, 0, 0, 0, "Private DO 1"}, + { 0x0102, 0, 0, 0, 0, 0, 0, 0, "Private DO 2"}, + { 0x0103, 0, 0, 0, 0, 0, 0, 0, "Private DO 3"}, + { 0x0104, 0, 0, 0, 0, 0, 0, 0, "Private DO 4"}, + { 0x7F21, 1, 0, 1, 0, 0, 0, 1, "Cardholder certificate"}, { 0 } }; +/* The format of RSA private keys. */ +typedef enum + { + RSA_UNKNOWN_FMT, + RSA_STD, + RSA_STD_N, + RSA_CRT, + RSA_CRT_N + } +rsa_key_format_t; + + /* One cache item for DOs. */ struct cache_s { struct cache_s *next; @@ -111,19 +152,36 @@ struct app_local_s { encoded S-expression encoding a public key. Might be NULL if key is not available. */ - size_t keylen; /* The length of the above S-expression. Thsi - is usullay only required for corss checks + size_t keylen; /* The length of the above S-expression. This + is usually only required for cross checks because the length of an S-expression is implicitly available. */ } pk[3]; - /* Keep track of card capabilities. */ + unsigned char status_indicator; /* The card status indicator. */ + + /* Keep track of the ISO card capabilities. */ + struct + { + unsigned int cmd_chaining:1; /* Command chaining is supported. */ + unsigned int ext_lc_le:1; /* Extended Lc and Le are supported. */ + } cardcap; + + /* Keep track of extended card capabilities. */ struct { + unsigned int is_v2:1; /* This is a v2.0 compatible card. */ unsigned int get_challenge:1; unsigned int key_import:1; unsigned int change_force_chv:1; unsigned int private_dos:1; + unsigned int algo_attr_change:1; /* Algorithm attributes changeable. */ + unsigned int sm_supported:1; /* Secure Messaging is supported. */ + unsigned int sm_aes128:1; /* Use AES-128 for SM. */ + unsigned int max_certlen_3:16; + unsigned int max_get_challenge:16; /* Maximum size for get_challenge. */ + unsigned int max_cmd_data:16; /* Maximum data size for a command. */ + unsigned int max_rsp_data:16; /* Maximum size of a response. */ } extcap; /* Flags used to control the application. */ @@ -132,6 +190,16 @@ struct app_local_s { unsigned int no_sync:1; /* Do not sync CHV1 and CHV2 */ unsigned int def_chv2:1; /* Use 123456 for CHV2. */ } flags; + + struct + { + unsigned int n_bits; /* Size of the modulus in bits. The rest + of this strucuire is only valid if + this is not 0. */ + unsigned int e_bits; /* Size of the public exponent in bits. */ + rsa_key_format_t format; + } keyattr[3]; + }; @@ -140,6 +208,12 @@ struct app_local_s { static unsigned long convert_sig_counter_value (const unsigned char *value, size_t valuelen); static unsigned long get_sig_counter (app_t app); +static gpg_error_t do_auth (app_t app, const char *keyidstr, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen); +static void parse_algorithm_attribute (app_t app, int keyno); @@ -173,17 +247,19 @@ do_deinit (app_t app) /* Wrapper around iso7816_get_data which first tries to get the data from the cache. With GET_IMMEDIATE passed as true, the cache is - bypassed. */ + bypassed. With TRY_EXTLEN extended lengths APDUs are use if + supported by the card. */ static gpg_error_t get_cached_data (app_t app, int tag, unsigned char **result, size_t *resultlen, - int get_immediate) + int get_immediate, int try_extlen) { gpg_error_t err; int i; unsigned char *p; size_t len; struct cache_s *c; + int exmode; *result = NULL; *resultlen = 0; @@ -208,7 +284,12 @@ get_cached_data (app_t app, int tag, } } - err = iso7816_get_data (app->slot, tag, &p, &len); + if (try_extlen && app->app_local->cardcap.ext_lc_le) + exmode = app->app_local->extcap.max_rsp_data; + else + exmode = 0; + + err = iso7816_get_data (app->slot, exmode, tag, &p, &len); if (err) return err; *result = p; @@ -321,6 +402,7 @@ get_one_do (app_t app, int tag, unsigned char **result, size_t *nbytes, unsigned char *value; size_t valuelen; int dummyrc; + int exmode; if (!r_rc) r_rc = &dummyrc; @@ -333,7 +415,11 @@ get_one_do (app_t app, int tag, unsigned char **result, size_t *nbytes, if (app->card_version > 0x0100 && data_objects[i].get_immediate_in_v11) { - rc = iso7816_get_data (app->slot, tag, &buffer, &buflen); + if (data_objects[i].try_extlen && app->app_local->cardcap.ext_lc_le) + exmode = app->app_local->extcap.max_rsp_data; + else + exmode = 0; + rc = iso7816_get_data (app->slot, exmode, tag, &buffer, &buflen); if (rc) { *r_rc = rc; @@ -351,7 +437,8 @@ get_one_do (app_t app, int tag, unsigned char **result, size_t *nbytes, rc = get_cached_data (app, data_objects[i].get_from, &buffer, &buflen, (data_objects[i].dont_cache - || data_objects[i].get_immediate_in_v11)); + || data_objects[i].get_immediate_in_v11), + data_objects[i].try_extlen); if (!rc) { const unsigned char *s; @@ -374,7 +461,8 @@ get_one_do (app_t app, int tag, unsigned char **result, size_t *nbytes, { rc = get_cached_data (app, tag, &buffer, &buflen, (data_objects[i].dont_cache - || data_objects[i].get_immediate_in_v11)); + || data_objects[i].get_immediate_in_v11), + data_objects[i].try_extlen); if (!rc) { value = buffer; @@ -405,7 +493,9 @@ dump_all_do (int slot) if (data_objects[i].get_from) continue; - rc = iso7816_get_data (slot, data_objects[i].tag, &buffer, &buflen); + /* We don't try extended length APDU because such large DO would + be pretty useless in a log file. */ + rc = iso7816_get_data (slot, 0, data_objects[i].tag, &buffer, &buflen); if (gpg_err_code (rc) == GPG_ERR_NO_OBJ) ; else if (rc) @@ -443,7 +533,10 @@ dump_all_do (int slot) if (data_objects[j].binary) { log_info ("DO `%s': ", data_objects[j].desc); - log_printhex ("", value, valuelen); + if (valuelen > 200) + log_info ("[%u]\n", (unsigned int)valuelen); + else + log_printhex ("", value, valuelen); } else log_info ("DO `%s': `%.*s'\n", @@ -476,14 +569,14 @@ count_bits (const unsigned char *a, size_t len) return n; } -/* GnuPG makes special use of the login-data DO, this fucntion parses +/* GnuPG makes special use of the login-data DO, this function parses the login data to store the flags for later use. It may be called at any time and should be called after changing the login-data DO. Everything up to a LF is considered a mailbox or account name. If the first LF is followed by DC4 (0x14) control sequence are expected up to the next LF. Control sequences are separated by FS - (0x28) and consist of key=value pairs. There is one key defined: + (0x18) and consist of key=value pairs. There is one key defined: F= @@ -547,13 +640,14 @@ parse_login_data (app_t app) /* Note, that FPR must be at least 20 bytes. */ static gpg_error_t -store_fpr (int slot, int keynumber, u32 timestamp, +store_fpr (app_t app, int keynumber, u32 timestamp, const unsigned char *m, size_t mlen, const unsigned char *e, size_t elen, unsigned char *fpr, unsigned int card_version) { unsigned int n, nbits; unsigned char *buffer, *p; + int tag, tag2; int rc; for (; mlen && !*m; mlen--, m++) /* strip leading zeroes */ @@ -564,7 +658,7 @@ store_fpr (int slot, int keynumber, u32 timestamp, n = 6 + 2 + mlen + 2 + elen; p = buffer = xtrymalloc (3 + n); if (!buffer) - return gpg_error_from_errno (errno); + return gpg_error_from_syserror (); *p++ = 0x99; /* ctb */ *p++ = n >> 8; /* 2 byte length header */ @@ -588,8 +682,12 @@ store_fpr (int slot, int keynumber, u32 timestamp, xfree (buffer); - rc = iso7816_put_data (slot, (card_version > 0x0007? 0xC7 : 0xC6) - + keynumber, fpr, 20); + tag = (card_version > 0x0007? 0xC7 : 0xC6) + keynumber; + flush_cache_item (app, tag); + tag2 = 0xCE + keynumber; + flush_cache_item (app, tag2); + + rc = iso7816_put_data (app->slot, 0, tag, fpr, 20); if (rc) log_error (_("failed to store the fingerprint: %s\n"),gpg_strerror (rc)); @@ -602,7 +700,7 @@ store_fpr (int slot, int keynumber, u32 timestamp, buf[2] = timestamp >> 8; buf[3] = timestamp; - rc = iso7816_put_data (slot, 0xCE + keynumber, buf, 4); + rc = iso7816_put_data (app->slot, 0, tag2, buf, 4); if (rc) log_error (_("failed to store the creation date: %s\n"), gpg_strerror (rc)); @@ -624,8 +722,7 @@ send_fpr_if_not_null (ctrl_t ctrl, const char *keyword, ; if (i==20) return; /* All zero. */ - for (i=0; i< 20; i++) - sprintf (buf+2*i, "%02X", fpr[i]); + bin2hex (fpr, 20, buf); if (number == -1) *numbuf = 0; /* Don't print the key number */ else @@ -656,10 +753,14 @@ static void send_key_data (ctrl_t ctrl, const char *name, const unsigned char *a, size_t alen) { - char *p, *buf = xmalloc (alen*2+1); + char *buf; - for (p=buf; alen; a++, alen--, p += 2) - sprintf (p, "%02X", *a); + buf = bin2hex (a, alen, NULL); + if (!buf) + { + log_error ("memory allocation error in send_key_data\n"); + return; + } send_status_info (ctrl, "KEY-DATA", name, (size_t)strlen(name), @@ -668,6 +769,24 @@ send_key_data (ctrl_t ctrl, const char *name, xfree (buf); } + +static void +send_key_attr (ctrl_t ctrl, app_t app, const char *keyword, int number) +{ + char buffer[200]; + + assert (number >=0 && number < DIM(app->app_local->keyattr)); + + /* We only support RSA thus the algo identifier is fixed to 1. */ + snprintf (buffer, sizeof buffer, "%d 1 %u %u %d", + number+1, + app->app_local->keyattr[number].n_bits, + app->app_local->keyattr[number].e_bits, + app->app_local->keyattr[number].format); + send_status_direct (ctrl, keyword, buffer); +} + + /* Implement the GETATTR command. This is similar to the LEARN command but returns just one value via the status interface. */ static gpg_error_t @@ -685,6 +804,7 @@ do_getattr (app_t app, ctrl_t ctrl, const char *name) { "PUBKEY-URL", 0x5F50 }, { "KEY-FPR", 0x00C5, 3 }, { "KEY-TIME", 0x00CD, 4 }, + { "KEY-ATTR", 0x0000, -5 }, { "CA-FPR", 0x00C6, 3 }, { "CHV-STATUS", 0x00C4, 1 }, { "SIG-COUNTER", 0x0093, 2 }, @@ -695,6 +815,8 @@ do_getattr (app_t app, ctrl_t ctrl, const char *name) { "PRIVATE-DO-2", 0x0102 }, { "PRIVATE-DO-3", 0x0103 }, { "PRIVATE-DO-4", 0x0104 }, + { "$AUTHKEYID", 0x0000, -3 }, + { "$DISPSERIALNO",0x0000, -4 }, { NULL, 0 } }; int idx, i, rc; @@ -731,16 +853,51 @@ do_getattr (app_t app, ctrl_t ctrl, const char *name) } if (table[idx].special == -2) { - char tmp[50]; + char tmp[100]; - sprintf (tmp, "gc=%d ki=%d fc=%d pd=%d", - app->app_local->extcap.get_challenge, - app->app_local->extcap.key_import, - app->app_local->extcap.change_force_chv, - app->app_local->extcap.private_dos); + snprintf (tmp, sizeof tmp, + "gc=%d ki=%d fc=%d pd=%d mcl3=%u aac=%d sm=%d", + app->app_local->extcap.get_challenge, + app->app_local->extcap.key_import, + app->app_local->extcap.change_force_chv, + app->app_local->extcap.private_dos, + app->app_local->extcap.max_certlen_3, + app->app_local->extcap.algo_attr_change, + (app->app_local->extcap.sm_supported + ? (app->app_local->extcap.sm_aes128? 7 : 2) + : 0)); send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0); return 0; } + if (table[idx].special == -3) + { + char const tmp[] = "OPENPGP.3"; + send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0); + return 0; + } + if (table[idx].special == -4) + { + char *serial; + time_t stamp; + + if (!app_get_serial_and_stamp (app, &serial, &stamp)) + { + if (strlen (serial) > 16+12) + { + send_status_info (ctrl, table[idx].name, serial+16, 12, NULL, 0); + xfree (serial); + return 0; + } + xfree (serial); + } + return gpg_error (GPG_ERR_INV_NAME); + } + if (table[idx].special == -5) + { + for (i=0; i < 3; i++) + send_key_attr (ctrl, app, table[idx].name, i); + return 0; + } relptr = get_one_do (app, table[idx].tag, &value, &valuelen, &rc); if (relptr) @@ -794,16 +951,12 @@ retrieve_fpr_from_card (app_t app, int keyno, char *fpr) void *relptr; unsigned char *value; size_t valuelen; - int i; assert (keyno >=0 && keyno <= 2); relptr = get_one_do (app, 0x00C5, &value, &valuelen, NULL); if (relptr && valuelen >= 60) - { - for (i = 0; i < 20; i++) - sprintf (fpr + (i * 2), "%02X", value[(keyno*20)+i]); - } + bin2hex (value+keyno*20, 20, fpr); else err = gpg_error (GPG_ERR_NOT_FOUND); xfree (relptr); @@ -834,7 +987,7 @@ retrieve_key_material (FILE *fp, const char *hexkeyid, size_t e_new_n = 0; /* Loop over all records until we have found the subkey - corresponsing to the fingerprint. Inm general the first record + corresponding to the fingerprint. Inm general the first record should be the pub record, but we don't rely on that. Given that we only need to look at one key, it is sufficient to compare the keyid so that we don't need to look at "fpr" records. */ @@ -853,7 +1006,7 @@ retrieve_key_material (FILE *fp, const char *hexkeyid, break; /* EOF. */ if (i < 0) { - err = gpg_error_from_errno (errno); + err = gpg_error_from_syserror (); goto leave; /* Error. */ } if (!max_length) @@ -947,8 +1100,8 @@ get_public_key (app_t app, int keyno) size_t buflen, keydatalen, mlen, elen; unsigned char *mbuf = NULL; unsigned char *ebuf = NULL; - unsigned char *keybuf = NULL; - unsigned char *keybuf_p; + char *keybuf = NULL; + char *keybuf_p; if (keyno < 1 || keyno > 3) return gpg_error (GPG_ERR_INV_ID); @@ -962,14 +1115,30 @@ get_public_key (app_t app, int keyno) app->app_local->pk[keyno].key = NULL; app->app_local->pk[keyno].keylen = 0; + m = e = NULL; /* (avoid cc warning) */ + if (app->card_version > 0x0100) { + int exmode, le_value; + /* We may simply read the public key out of these cards. */ - err = iso7816_read_public_key (app->slot, - keyno == 0? "\xB6" : - keyno == 1? "\xB8" : "\xA4", - 2, - &buffer, &buflen); + if (app->app_local->cardcap.ext_lc_le) + { + exmode = 1; /* Use extended length. */ + le_value = app->app_local->extcap.max_rsp_data; + } + else + { + exmode = 0; + le_value = 256; /* Use legacy value. */ + } + + err = iso7816_read_public_key + (app->slot, exmode, + (const unsigned char*)(keyno == 0? "\xB6" : + keyno == 1? "\xB8" : "\xA4"), 2, + le_value, + &buffer, &buflen); if (err) { log_error (_("reading public key failed: %s\n"), gpg_strerror (err)); @@ -1007,7 +1176,7 @@ get_public_key (app_t app, int keyno) mbuf = xtrymalloc ( mlen + 1); if (!mbuf) { - err = gpg_error_from_errno (errno); + err = gpg_error_from_syserror (); goto leave; } *mbuf = 0; @@ -1020,7 +1189,7 @@ get_public_key (app_t app, int keyno) ebuf = xtrymalloc ( elen + 1); if (!ebuf) { - err = gpg_error_from_errno (errno); + err = gpg_error_from_syserror (); goto leave; } *ebuf = 0; @@ -1057,20 +1226,20 @@ get_public_key (app_t app, int keyno) } hexkeyid = fpr + 24; - ret = asprintf (&command, - "gpg --list-keys --with-colons --with-key-data '%s'", - fpr); + ret = estream_asprintf (&command, + "gpg --list-keys --with-colons --with-key-data '%s'", + fpr); if (ret < 0) { - err = gpg_error_from_errno (errno); + err = gpg_error_from_syserror (); goto leave; } fp = popen (command, "r"); - free (command); + xfree (command); if (!fp) { - err = gpg_error_from_errno (errno); + err = gpg_error_from_syserror (); log_error ("running gpg failed: %s\n", gpg_strerror (err)); goto leave; } @@ -1091,7 +1260,7 @@ get_public_key (app_t app, int keyno) keybuf = xtrymalloc (50 + 2*35 + mlen + elen + 1); if (!keybuf) { - err = gpg_error_from_errno (errno); + err = gpg_error_from_syserror (); goto leave; } @@ -1106,7 +1275,7 @@ get_public_key (app_t app, int keyno) strcpy (keybuf_p, ")))"); keybuf_p += strlen (keybuf_p); - app->app_local->pk[keyno].key = keybuf; + app->app_local->pk[keyno].key = (unsigned char*)keybuf; app->app_local->pk[keyno].keylen = (keybuf_p - keybuf); leave: @@ -1134,7 +1303,6 @@ send_keypair_info (app_t app, ctrl_t ctrl, int keyno) unsigned char grip[20]; char gripstr[41]; char idbuf[50]; - int i; err = get_public_key (app, keyno); if (err) @@ -1150,8 +1318,7 @@ send_keypair_info (app_t app, ctrl_t ctrl, int keyno) if (err) goto leave; - for (i=0; i < 20; i++) - sprintf (gripstr+i*2, "%02X", grip[i]); + bin2hex (grip, 20, gripstr); sprintf (idbuf, "OPENPGP.%d", keyno); send_status_info (ctrl, "KEYPAIRINFO", @@ -1168,8 +1335,10 @@ send_keypair_info (app_t app, ctrl_t ctrl, int keyno) /* Handle the LEARN command for OpenPGP. */ static gpg_error_t -do_learn_status (app_t app, ctrl_t ctrl) +do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags) { + (void)flags; + do_getattr (app, ctrl, "EXTCAP"); do_getattr (app, ctrl, "DISP-NAME"); do_getattr (app, ctrl, "DISP-LANG"); @@ -1194,6 +1363,8 @@ do_learn_status (app_t app, ctrl_t ctrl) send_keypair_info (app, ctrl, 1); send_keypair_info (app, ctrl, 2); send_keypair_info (app, ctrl, 3); + /* Note: We do not send the Cardholder Certificate, because that is + relativly long and for OpenPGP applications not really needed. */ return 0; } @@ -1227,14 +1398,189 @@ do_readkey (app_t app, const char *keyid, unsigned char **pk, size_t *pklen) buf = app->app_local->pk[keyno-1].key; if (!buf) return gpg_error (GPG_ERR_NO_PUBKEY); - *pk = buf; *pklen = app->app_local->pk[keyno-1].keylen;; + *pk = xtrymalloc (*pklen); + if (!*pk) + { + err = gpg_error_from_syserror (); + *pklen = 0; + return err; + } + memcpy (*pk, buf, *pklen); return 0; #else return gpg_error (GPG_ERR_NOT_IMPLEMENTED); #endif } +/* Read the standard certificate of an OpenPGP v2 card. It is + returned in a freshly allocated buffer with that address stored at + CERT and the length of the certificate stored at CERTLEN. CERTID + needs to be set to "OPENPGP.3". */ +static gpg_error_t +do_readcert (app_t app, const char *certid, + unsigned char **cert, size_t *certlen) +{ +#if GNUPG_MAJOR_VERSION > 1 + gpg_error_t err; + unsigned char *buffer; + size_t buflen; + void *relptr; + + *cert = NULL; + *certlen = 0; + if (strcmp (certid, "OPENPGP.3")) + return gpg_error (GPG_ERR_INV_ID); + if (!app->app_local->extcap.is_v2) + return gpg_error (GPG_ERR_NOT_FOUND); + + relptr = get_one_do (app, 0x7F21, &buffer, &buflen, NULL); + if (!relptr) + return gpg_error (GPG_ERR_NOT_FOUND); + + if (!buflen) + err = gpg_error (GPG_ERR_NOT_FOUND); + else if (!(*cert = xtrymalloc (buflen))) + err = gpg_error_from_syserror (); + else + { + memcpy (*cert, buffer, buflen); + *certlen = buflen; + err = 0; + } + xfree (relptr); + return err; +#else + return gpg_error (GPG_ERR_NOT_IMPLEMENTED); +#endif +} + + +/* Verify a CHV either using using the pinentry or if possibile by + using a keypad. PINCB and PINCB_ARG describe the usual callback + for the pinentry. CHVNO must be either 1 or 2. SIGCOUNT is only + used with CHV1. PINVALUE is the address of a pointer which will + receive a newly allocated block with the actual PIN (this is useful + in case that PIN shall be used for another verify operation). The + caller needs to free this value. If the function returns with + success and NULL is stored at PINVALUE, the caller should take this + as an indication that the keypad has been used. + */ +static gpg_error_t +verify_a_chv (app_t app, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + int chvno, unsigned long sigcount, char **pinvalue) +{ + int rc = 0; + char *prompt_buffer = NULL; + const char *prompt; + iso7816_pininfo_t pininfo; + int minlen = 6; + + assert (chvno == 1 || chvno == 2); + + *pinvalue = NULL; + + if (chvno == 2 && app->app_local->flags.def_chv2) + { + /* Special case for def_chv2 mechanism. */ + if (opt.verbose) + log_info (_("using default PIN as %s\n"), "CHV2"); + rc = iso7816_verify (app->slot, 0x82, "123456", 6); + if (rc) + { + /* Verification of CHV2 with the default PIN failed, + although the card pretends to have the default PIN set as + CHV2. We better disable the def_chv2 flag now. */ + log_info (_("failed to use default PIN as %s: %s" + " - disabling further default use\n"), + "CHV2", gpg_strerror (rc)); + app->app_local->flags.def_chv2 = 0; + } + return rc; + } + + memset (&pininfo, 0, sizeof pininfo); + pininfo.mode = 1; + pininfo.minlen = minlen; + + + if (chvno == 1) + { +#define PROMPTSTRING _("||Please enter the PIN%%0A[sigs done: %lu]") + size_t promptsize = strlen (PROMPTSTRING) + 50; + + prompt_buffer = xtrymalloc (promptsize); + if (!prompt_buffer) + return gpg_error_from_syserror (); + snprintf (prompt_buffer, promptsize-1, PROMPTSTRING, sigcount); + prompt = prompt_buffer; +#undef PROMPTSTRING + } + else + prompt = _("||Please enter the PIN"); + + + if (!opt.disable_keypad + && !iso7816_check_keypad (app->slot, ISO7816_VERIFY, &pininfo) ) + { + /* The reader supports the verify command through the keypad. + Note that the pincb appends a text to the prompt telling the + user to use the keypad. */ + rc = pincb (pincb_arg, prompt, NULL); + prompt = NULL; + xfree (prompt_buffer); + prompt_buffer = NULL; + if (rc) + { + log_info (_("PIN callback returned error: %s\n"), + gpg_strerror (rc)); + return rc; + } + rc = iso7816_verify_kp (app->slot, 0x80+chvno, "", 0, &pininfo); + /* Dismiss the prompt. */ + pincb (pincb_arg, NULL, NULL); + + assert (!*pinvalue); + } + else + { + /* The reader has no keypad or we don't want to use it. */ + rc = pincb (pincb_arg, prompt, pinvalue); + prompt = NULL; + xfree (prompt_buffer); + prompt_buffer = NULL; + if (rc) + { + log_info (_("PIN callback returned error: %s\n"), + gpg_strerror (rc)); + return rc; + } + + if (strlen (*pinvalue) < minlen) + { + log_error (_("PIN for CHV%d is too short;" + " minimum length is %d\n"), chvno, minlen); + xfree (*pinvalue); + *pinvalue = NULL; + return gpg_error (GPG_ERR_BAD_PIN); + } + + rc = iso7816_verify (app->slot, 0x80+chvno, + *pinvalue, strlen (*pinvalue)); + } + + if (rc) + { + log_error (_("verify CHV%d failed: %s\n"), chvno, gpg_strerror (rc)); + xfree (*pinvalue); + *pinvalue = NULL; + flush_cache_after_error (app); + } + + return rc; +} /* Verify CHV2 if required. Depending on the configuration of the @@ -1244,56 +1590,91 @@ verify_chv2 (app_t app, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { - int rc = 0; + int rc; + char *pinvalue; - if (!app->did_chv2) + if (app->did_chv2) + return 0; /* We already verified CHV2. */ + + rc = verify_a_chv (app, pincb, pincb_arg, 2, 0, &pinvalue); + if (rc) + return rc; + app->did_chv2 = 1; + + if (!app->did_chv1 && !app->force_chv1 && pinvalue) { - char *pinvalue; - - rc = pincb (pincb_arg, "PIN", &pinvalue); + /* For convenience we verify CHV1 here too. We do this only if + the card is not configured to require a verification before + each CHV1 controlled operation (force_chv1) and if we are not + using the keypad (PINVALUE == NULL). */ + rc = iso7816_verify (app->slot, 0x81, pinvalue, strlen (pinvalue)); + if (gpg_err_code (rc) == GPG_ERR_BAD_PIN) + rc = gpg_error (GPG_ERR_PIN_NOT_SYNCED); if (rc) { - log_info (_("PIN callback returned error: %s\n"), gpg_strerror (rc)); - return rc; - } - - if (strlen (pinvalue) < 6) - { - log_error (_("PIN for CHV%d is too short;" - " minimum length is %d\n"), 2, 6); - xfree (pinvalue); - return gpg_error (GPG_ERR_BAD_PIN); - } - - rc = iso7816_verify (app->slot, 0x82, pinvalue, strlen (pinvalue)); - if (rc) - { - log_error (_("verify CHV%d failed: %s\n"), 2, gpg_strerror (rc)); - xfree (pinvalue); + log_error (_("verify CHV%d failed: %s\n"), 1, gpg_strerror (rc)); flush_cache_after_error (app); - return rc; } - app->did_chv2 = 1; - - if (!app->did_chv1 && !app->force_chv1) - { - rc = iso7816_verify (app->slot, 0x81, pinvalue, strlen (pinvalue)); - if (gpg_err_code (rc) == GPG_ERR_BAD_PIN) - rc = gpg_error (GPG_ERR_PIN_NOT_SYNCED); - if (rc) - { - log_error (_("verify CHV%d failed: %s\n"), 1, gpg_strerror (rc)); - xfree (pinvalue); - flush_cache_after_error (app); - return rc; - } - app->did_chv1 = 1; - } - xfree (pinvalue); + else + app->did_chv1 = 1; } + + xfree (pinvalue); + return rc; } + +/* Build the prompt to enter the Admin PIN. The prompt depends on the + current sdtate of the card. */ +static gpg_error_t +build_enter_admin_pin_prompt (app_t app, char **r_prompt) +{ + void *relptr; + unsigned char *value; + size_t valuelen; + int remaining; + char *prompt; + + *r_prompt = NULL; + + relptr = get_one_do (app, 0x00C4, &value, &valuelen, NULL); + if (!relptr || valuelen < 7) + { + log_error (_("error retrieving CHV status from card\n")); + xfree (relptr); + return gpg_error (GPG_ERR_CARD); + } + if (value[6] == 0) + { + log_info (_("card is permanently locked!\n")); + xfree (relptr); + return gpg_error (GPG_ERR_BAD_PIN); + } + remaining = value[6]; + xfree (relptr); + + log_info(_("%d Admin PIN attempts remaining before card" + " is permanently locked\n"), remaining); + + if (remaining < 3) + { + /* TRANSLATORS: Do not translate the "|A|" prefix but keep it at + the start of the string. Use %%0A to force a linefeed. */ + prompt = xtryasprintf (_("|A|Please enter the Admin PIN%%0A" + "[remaining attempts: %d]"), remaining); + } + else + prompt = xtrystrdup (_("|A|Please enter the Admin PIN")); + + if (!prompt) + return gpg_error_from_syserror (); + + *r_prompt = prompt; + return 0; +} + + /* Verify CHV3 if required. */ static gpg_error_t verify_chv3 (app_t app, @@ -1312,49 +1693,61 @@ verify_chv3 (app_t app, if (!app->did_chv3) { - char *pinvalue; - void *relptr; - unsigned char *value; - size_t valuelen; + iso7816_pininfo_t pininfo; + int minlen = 8; + char *prompt; - relptr = get_one_do (app, 0x00C4, &value, &valuelen, NULL); - if (!relptr || valuelen < 7) - { - log_error (_("error retrieving CHV status from card\n")); - xfree (relptr); - return gpg_error (GPG_ERR_CARD); - } - if (value[6] == 0) - { - log_info (_("card is permanently locked!\n")); - xfree (relptr); - return gpg_error (GPG_ERR_BAD_PIN); - } + memset (&pininfo, 0, sizeof pininfo); + pininfo.mode = 1; + pininfo.minlen = minlen; - log_info(_("%d Admin PIN attempts remaining before card" - " is permanently locked\n"), value[6]); - xfree (relptr); - - /* TRANSLATORS: Do not translate the "|A|" prefix but - keep it at the start of the string. We need this elsewhere - to get some infos on the string. */ - rc = pincb (pincb_arg, _("|A|Admin PIN"), &pinvalue); + rc = build_enter_admin_pin_prompt (app, &prompt); if (rc) - { - log_info (_("PIN callback returned error: %s\n"), gpg_strerror (rc)); - return rc; - } + return rc; - if (strlen (pinvalue) < 8) + if (!opt.disable_keypad + && !iso7816_check_keypad (app->slot, ISO7816_VERIFY, &pininfo) ) { - log_error (_("PIN for CHV%d is too short;" - " minimum length is %d\n"), 3, 8); + /* The reader supports the verify command through the keypad. */ + rc = pincb (pincb_arg, prompt, NULL); + xfree (prompt); + prompt = NULL; + if (rc) + { + log_info (_("PIN callback returned error: %s\n"), + gpg_strerror (rc)); + return rc; + } + rc = iso7816_verify_kp (app->slot, 0x83, "", 0, &pininfo); + /* Dismiss the prompt. */ + pincb (pincb_arg, NULL, NULL); + } + else + { + char *pinvalue; + + rc = pincb (pincb_arg, prompt, &pinvalue); + xfree (prompt); + prompt = NULL; + if (rc) + { + log_info (_("PIN callback returned error: %s\n"), + gpg_strerror (rc)); + return rc; + } + + if (strlen (pinvalue) < minlen) + { + log_error (_("PIN for CHV%d is too short;" + " minimum length is %d\n"), 3, minlen); + xfree (pinvalue); + return gpg_error (GPG_ERR_BAD_PIN); + } + + rc = iso7816_verify (app->slot, 0x83, pinvalue, strlen (pinvalue)); 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 CHV%d failed: %s\n"), 3, gpg_strerror (rc)); @@ -1382,6 +1775,7 @@ do_setattr (app_t app, const char *name, int tag; int need_chv; int special; + unsigned int need_v2:1; } table[] = { { "DISP-NAME", 0x005B, 3 }, { "LOGIN-DATA", 0x005E, 3, 2 }, @@ -1396,14 +1790,19 @@ do_setattr (app_t app, const char *name, { "PRIVATE-DO-2", 0x0102, 3 }, { "PRIVATE-DO-3", 0x0103, 2 }, { "PRIVATE-DO-4", 0x0104, 3 }, + { "CERT-3", 0x7F21, 3, 0, 1 }, + { "SM-KEY-ENC", 0x00D1, 3, 0, 1 }, + { "SM-KEY-MAC", 0x00D2, 3, 0, 1 }, { NULL, 0 } }; - + int exmode; for (idx=0; table[idx].name && strcmp (table[idx].name, name); idx++) ; if (!table[idx].name) return gpg_error (GPG_ERR_INV_NAME); + if (table[idx].need_v2 && !app->app_local->extcap.is_v2) + return gpg_error (GPG_ERR_NOT_SUPPORTED); /* Not yet supported. */ switch (table[idx].need_chv) { @@ -1423,7 +1822,14 @@ do_setattr (app_t app, const char *name, 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 (app->app_local->cardcap.ext_lc_le && valuelen > 254) + exmode = 1; /* Use extended length w/o a limit. */ + else if (app->app_local->cardcap.cmd_chaining && valuelen > 254) + exmode = -254; /* Command chaining with max. 254 bytes. */ + else + exmode = 0; + rc = iso7816_put_data (app->slot, exmode, table[idx].tag, value, valuelen); if (rc) log_error ("failed to set `%s': %s\n", table[idx].name, gpg_strerror (rc)); @@ -1436,47 +1842,201 @@ do_setattr (app_t app, const char *name, } -/* Handle the PASSWD command. */ +/* Handle the WRITECERT command for OpenPGP. This rites the standard + certifciate to the card; CERTID needs to be set to "OPENPGP.3". + PINCB and PINCB_ARG are the usual arguments for the pinentry + callback. */ +static gpg_error_t +do_writecert (app_t app, ctrl_t ctrl, + const char *certidstr, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const unsigned char *certdata, size_t certdatalen) +{ + (void)ctrl; +#if GNUPG_MAJOR_VERSION > 1 + if (strcmp (certidstr, "OPENPGP.3")) + return gpg_error (GPG_ERR_INV_ID); + if (!certdata || !certdatalen) + return gpg_error (GPG_ERR_INV_ARG); + if (!app->app_local->extcap.is_v2) + return gpg_error (GPG_ERR_NOT_SUPPORTED); + if (certdatalen > app->app_local->extcap.max_certlen_3) + return gpg_error (GPG_ERR_TOO_LARGE); + return do_setattr (app, "CERT-3", pincb, pincb_arg, certdata, certdatalen); +#else + return gpg_error (GPG_ERR_NOT_IMPLEMENTED); +#endif +} + + + +/* Handle the PASSWD command. The following combinations are + possible: + + Flags CHVNO Vers. Description + RESET 1 1 Verify CHV3 and set a new CHV1 and CHV2 + RESET 1 2 Verify PW3 and set a new PW1. + RESET 2 1 Verify CHV3 and set a new CHV1 and CHV2. + RESET 2 2 Verify PW3 and set a new Reset Code. + RESET 3 any Returns GPG_ERR_INV_ID. + - 1 1 Verify CHV2 and set a new CHV1 and CHV2. + - 1 2 Verify PW1 and set a new PW1. + - 2 1 Verify CHV2 and set a new CHV1 and CHV2. + - 2 2 Verify Reset Code and set a new PW1. + - 3 any Verify CHV3/PW3 and set a new CHV3/PW3. + */ static gpg_error_t -do_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, int reset_mode, +do_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, + unsigned int flags, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { int rc = 0; int chvno = atoi (chvnostr); + char *resetcode = NULL; + char *oldpinvalue = NULL; char *pinvalue; + int reset_mode = !!(flags & APP_CHANGE_FLAG_RESET); + int set_resetcode = 0; + + (void)ctrl; if (reset_mode && chvno == 3) { rc = gpg_error (GPG_ERR_INV_ID); goto leave; } - else if (reset_mode || chvno == 3) - { - /* we always require that the PIN is entered. */ - app->did_chv3 = 0; - rc = verify_chv3 (app, pincb, pincb_arg); - if (rc) - goto leave; - } - else if (chvno == 1 || chvno == 2) - { - /* CHV1 and CVH2 should always have the same value, thus we - enforce it here. */ - int save_force = app->force_chv1; - app->force_chv1 = 0; - app->did_chv1 = 0; - app->did_chv2 = 0; - rc = verify_chv2 (app, pincb, pincb_arg); - app->force_chv1 = save_force; - if (rc) - goto leave; + if (!app->app_local->extcap.is_v2) + { + /* Version 1 cards. */ + + if (reset_mode || chvno == 3) + { + /* We always require that the PIN is entered. */ + app->did_chv3 = 0; + rc = verify_chv3 (app, pincb, pincb_arg); + if (rc) + goto leave; + } + else if (chvno == 1 || chvno == 2) + { + /* On a v1.x card CHV1 and CVH2 should always have the same + value, thus we enforce it here. */ + int save_force = app->force_chv1; + + app->force_chv1 = 0; + app->did_chv1 = 0; + app->did_chv2 = 0; + rc = verify_chv2 (app, pincb, pincb_arg); + app->force_chv1 = save_force; + if (rc) + goto leave; + } + else + { + rc = gpg_error (GPG_ERR_INV_ID); + goto leave; + } } else { - rc = gpg_error (GPG_ERR_INV_ID); - goto leave; + /* Version 2 cards. */ + + if (reset_mode) + { + /* To reset a PIN the Admin PIN is required. */ + app->did_chv3 = 0; + rc = verify_chv3 (app, pincb, pincb_arg); + if (rc) + goto leave; + + if (chvno == 2) + set_resetcode = 1; + } + else if (chvno == 1 || chvno == 3) + { + int minlen = (chvno ==3)? 8 : 6; + char *promptbuf = NULL; + const char *prompt; + + if (chvno == 3) + { + rc = build_enter_admin_pin_prompt (app, &promptbuf); + if (rc) + goto leave; + prompt = promptbuf; + } + else + prompt = _("||Please enter the PIN"); + rc = pincb (pincb_arg, prompt, &oldpinvalue); + xfree (promptbuf); + promptbuf = NULL; + if (rc) + { + log_info (_("PIN callback returned error: %s\n"), + gpg_strerror (rc)); + goto leave; + } + + if (strlen (oldpinvalue) < minlen) + { + log_info (_("PIN for CHV%d is too short;" + " minimum length is %d\n"), chvno, minlen); + rc = gpg_error (GPG_ERR_BAD_PIN); + goto leave; + } + } + else if (chvno == 2) + { + /* There is no PW2 for v2 cards. We use this condition to + allow a PW reset using the Reset Code. */ + void *relptr; + unsigned char *value; + size_t valuelen; + int remaining; + int minlen = 8; + + relptr = get_one_do (app, 0x00C4, &value, &valuelen, NULL); + if (!relptr || valuelen < 7) + { + log_error (_("error retrieving CHV status from card\n")); + xfree (relptr); + rc = gpg_error (GPG_ERR_CARD); + goto leave; + } + remaining = value[5]; + xfree (relptr); + if (!remaining) + { + log_error (_("Reset Code not or not anymore available\n")); + rc = gpg_error (GPG_ERR_BAD_PIN); + goto leave; + } + + rc = pincb (pincb_arg, + _("||Please enter the Reset Code for the card"), + &resetcode); + if (rc) + { + log_info (_("PIN callback returned error: %s\n"), + gpg_strerror (rc)); + goto leave; + } + if (strlen (resetcode) < minlen) + { + log_info (_("Reset Code is too short; minimum length is %d\n"), + minlen); + rc = gpg_error (GPG_ERR_BAD_PIN); + goto leave; + } + } + else + { + rc = gpg_error (GPG_ERR_INV_ID); + goto leave; + } } if (chvno == 3) @@ -1487,7 +2047,9 @@ do_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, int reset_mode, /* TRANSLATORS: Do not translate the "|*|" prefixes but keep it at the start of the string. We need this elsewhere to get some infos on the string. */ - rc = pincb (pincb_arg, chvno == 3? _("|AN|New Admin PIN") : _("|N|New PIN"), + rc = pincb (pincb_arg, + set_resetcode? _("|RN|New Reset Code") : + chvno == 3? _("|AN|New Admin PIN") : _("|N|New PIN"), &pinvalue); if (rc) { @@ -1495,16 +2057,45 @@ do_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, int reset_mode, goto leave; } - if (reset_mode) + + if (resetcode) + { + char *buffer; + + buffer = xtrymalloc (strlen (resetcode) + strlen (pinvalue) + 1); + if (!buffer) + rc = gpg_error_from_syserror (); + else + { + strcpy (stpcpy (buffer, resetcode), pinvalue); + rc = iso7816_reset_retry_counter_with_rc (app->slot, 0x81, + buffer, strlen (buffer)); + wipememory (buffer, strlen (buffer)); + xfree (buffer); + } + } + else if (set_resetcode) + { + if (strlen (pinvalue) < 8) + { + log_error (_("Reset Code is too short; minimum length is %d\n"), 8); + rc = gpg_error (GPG_ERR_BAD_PIN); + } + else + rc = iso7816_put_data (app->slot, 0, 0xD3, + pinvalue, strlen (pinvalue)); + } + else if (reset_mode) { rc = iso7816_reset_retry_counter (app->slot, 0x81, pinvalue, strlen (pinvalue)); - if (!rc) + if (!rc && !app->app_local->extcap.is_v2) rc = iso7816_reset_retry_counter (app->slot, 0x82, pinvalue, strlen (pinvalue)); } - else + else if (!app->app_local->extcap.is_v2) { + /* Version 1 cards. */ if (chvno == 1 || chvno == 2) { rc = iso7816_change_reference_data (app->slot, 0x81, NULL, 0, @@ -1513,24 +2104,51 @@ do_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, int reset_mode, rc = iso7816_change_reference_data (app->slot, 0x82, NULL, 0, pinvalue, strlen (pinvalue)); } - else - rc = iso7816_change_reference_data (app->slot, 0x80 + chvno, NULL, 0, - pinvalue, strlen (pinvalue)); + else /* CHVNO == 3 */ + { + rc = iso7816_change_reference_data (app->slot, 0x80 + chvno, NULL, 0, + pinvalue, strlen (pinvalue)); + } + } + else + { + /* Version 2 cards. */ + assert (chvno == 1 || chvno == 3); + + rc = iso7816_change_reference_data (app->slot, 0x80 + chvno, + oldpinvalue, strlen (oldpinvalue), + pinvalue, strlen (pinvalue)); + } + + if (pinvalue) + { + wipememory (pinvalue, strlen (pinvalue)); + xfree (pinvalue); } - xfree (pinvalue); if (rc) flush_cache_after_error (app); leave: + if (resetcode) + { + wipememory (resetcode, strlen (resetcode)); + xfree (resetcode); + } + if (oldpinvalue) + { + wipememory (oldpinvalue, strlen (oldpinvalue)); + xfree (oldpinvalue); + } return rc; } /* Check whether a key already exists. KEYIDX is the index of the key (0..2). If FORCE is TRUE a diagnositic will be printed but no - error returned if the key already exists. */ + error returned if the key already exists. The flag GENERATING is + only used to print correct messages. */ static gpg_error_t -does_key_exist (app_t app, int keyidx, int force) +does_key_exist (app_t app, int keyidx, int generating, int force) { const unsigned char *fpr; unsigned char *buffer; @@ -1539,7 +2157,7 @@ does_key_exist (app_t app, int keyidx, int force) assert (keyidx >=0 && keyidx <= 2); - if (iso7816_get_data (app->slot, 0x006E, &buffer, &buflen)) + if (iso7816_get_data (app->slot, 0, 0x006E, &buffer, &buflen)) { log_error (_("error reading application data\n")); return gpg_error (GPG_ERR_GENERAL); @@ -1562,12 +2180,230 @@ does_key_exist (app_t app, int keyidx, int force) } else if (i!=20) log_info (_("existing key will be replaced\n")); - else + else if (generating) log_info (_("generating new key\n")); + else + log_info (_("writing new key\n")); return 0; } +/* Create a TLV tag and value and store it at BUFFER. Return the length + of tag and length. A LENGTH greater than 65535 is truncated. */ +static size_t +add_tlv (unsigned char *buffer, unsigned int tag, size_t length) +{ + unsigned char *p = buffer; + + assert (tag <= 0xffff); + if ( tag > 0xff ) + *p++ = tag >> 8; + *p++ = tag; + if (length < 128) + *p++ = length; + else if (length < 256) + { + *p++ = 0x81; + *p++ = length; + } + else + { + if (length > 0xffff) + length = 0xffff; + *p++ = 0x82; + *p++ = length >> 8; + *p++ = length; + } + + return p - buffer; +} + + +/* Build the private key template as specified in the OpenPGP specs + v2.0 section 4.3.3.7. */ +static gpg_error_t +build_privkey_template (app_t app, int keyno, + const unsigned char *rsa_n, size_t rsa_n_len, + const unsigned char *rsa_e, size_t rsa_e_len, + const unsigned char *rsa_p, size_t rsa_p_len, + const unsigned char *rsa_q, size_t rsa_q_len, + unsigned char **result, size_t *resultlen) +{ + size_t rsa_e_reqlen; + unsigned char privkey[7*(1+3)]; + size_t privkey_len; + unsigned char exthdr[2+2+3]; + size_t exthdr_len; + unsigned char suffix[2+3]; + size_t suffix_len; + unsigned char *tp; + size_t datalen; + unsigned char *template; + size_t template_size; + + *result = NULL; + *resultlen = 0; + + switch (app->app_local->keyattr[keyno].format) + { + case RSA_STD: + case RSA_STD_N: + break; + case RSA_CRT: + case RSA_CRT_N: + return gpg_error (GPG_ERR_NOT_SUPPORTED); + + default: + return gpg_error (GPG_ERR_INV_VALUE); + } + + /* Get the required length for E. */ + rsa_e_reqlen = app->app_local->keyattr[keyno].e_bits/8; + assert (rsa_e_len <= rsa_e_reqlen); + + /* Build the 7f48 cardholder private key template. */ + datalen = 0; + tp = privkey; + + tp += add_tlv (tp, 0x91, rsa_e_reqlen); + datalen += rsa_e_reqlen; + + tp += add_tlv (tp, 0x92, rsa_p_len); + datalen += rsa_p_len; + + tp += add_tlv (tp, 0x93, rsa_q_len); + datalen += rsa_q_len; + + if (app->app_local->keyattr[keyno].format == RSA_STD_N + || app->app_local->keyattr[keyno].format == RSA_CRT_N) + { + tp += add_tlv (tp, 0x97, rsa_n_len); + datalen += rsa_n_len; + } + privkey_len = tp - privkey; + + /* Build the extended header list without the private key template. */ + tp = exthdr; + *tp++ = keyno ==0 ? 0xb6 : keyno == 1? 0xb8 : 0xa4; + *tp++ = 0; + tp += add_tlv (tp, 0x7f48, privkey_len); + exthdr_len = tp - exthdr; + + /* Build the 5f48 suffix of the data. */ + tp = suffix; + tp += add_tlv (tp, 0x5f48, datalen); + suffix_len = tp - suffix; + + /* Now concatenate everything. */ + template_size = (1 + 3 /* 0x4d and len. */ + + exthdr_len + + privkey_len + + suffix_len + + datalen); + tp = template = xtrymalloc_secure (template_size); + if (!template) + return gpg_error_from_syserror (); + + tp += add_tlv (tp, 0x4d, exthdr_len + privkey_len + suffix_len + datalen); + memcpy (tp, exthdr, exthdr_len); + tp += exthdr_len; + memcpy (tp, privkey, privkey_len); + tp += privkey_len; + memcpy (tp, suffix, suffix_len); + tp += suffix_len; + + memcpy (tp, rsa_e, rsa_e_len); + if (rsa_e_len < rsa_e_reqlen) + { + /* Right justify E. */ + memmove (tp + rsa_e_reqlen - rsa_e_len, tp, rsa_e_len); + memset (tp, 0, rsa_e_reqlen - rsa_e_len); + } + tp += rsa_e_reqlen; + + memcpy (tp, rsa_p, rsa_p_len); + tp += rsa_p_len; + + memcpy (tp, rsa_q, rsa_q_len); + tp += rsa_q_len; + + if (app->app_local->keyattr[keyno].format == RSA_STD_N + || app->app_local->keyattr[keyno].format == RSA_CRT_N) + { + memcpy (tp, rsa_n, rsa_n_len); + tp += rsa_n_len; + } + + /* Sanity check. We don't know the exact length because we + allocated 3 bytes for the first length header. */ + assert (tp - template <= template_size); + + *result = template; + *resultlen = tp - template; + return 0; +} + + +/* Helper for do_writekley to change the size of a key. Not ethat + this deletes the entire key without asking. */ +static gpg_error_t +change_keyattr (app_t app, int keyno, unsigned int nbits, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg) +{ + gpg_error_t err; + unsigned char *buffer; + size_t buflen; + void *relptr; + + assert (keyno >=0 && keyno <= 2); + + if (nbits > 3072) + return gpg_error (GPG_ERR_TOO_LARGE); + + /* Read the current attributes into a buffer. */ + relptr = get_one_do (app, 0xC1+keyno, &buffer, &buflen, NULL); + if (!relptr) + return gpg_error (GPG_ERR_CARD); + if (buflen < 6 || buffer[0] != 1) + { + /* Attriutes too short or not an RSA key. */ + xfree (relptr); + return gpg_error (GPG_ERR_CARD); + } + + /* We only change n_bits and don't touch anything else. Before we + do so, we round up NBITS to a sensible way in the same way as + gpg's key generation does it. This may help to sort out problems + with a few bits too short keys. */ + nbits = ((nbits + 31) / 32) * 32; + buffer[1] = (nbits >> 8); + buffer[2] = nbits; + + /* Prepare for storing the key. */ + err = verify_chv3 (app, pincb, pincb_arg); + if (err) + { + xfree (relptr); + return err; + } + + /* Change the attribute. */ + err = iso7816_put_data (app->slot, 0, 0xC1+keyno, buffer, buflen); + xfree (relptr); + if (err) + log_error ("error changing size of key %d to %u bits\n", keyno+1, nbits); + else + log_info ("size of key %d changed to %u bits\n", keyno+1, nbits); + flush_cache (app); + parse_algorithm_attribute (app, keyno); + app->did_chv1 = 0; + app->did_chv2 = 0; + app->did_chv3 = 0; + return err; +} + + /* Handle the WRITEKEY command for OpenPGP. This function expects a canonical encoded S-expression with the secret key in KEYDATA and @@ -1595,12 +2431,15 @@ do_writekey (app_t app, ctrl_t ctrl, const unsigned char *rsa_q = NULL; size_t rsa_n_len, rsa_e_len, rsa_p_len, rsa_q_len; unsigned int nbits; + unsigned int maxbits; unsigned char *template = NULL; unsigned char *tp; size_t template_len; unsigned char fprbuf[20]; u32 created_at = 0; + (void)ctrl; + if (!strcmp (keyid, "OPENPGP.1")) keyno = 0; else if (!strcmp (keyid, "OPENPGP.2")) @@ -1610,7 +2449,7 @@ do_writekey (app_t app, ctrl_t ctrl, else return gpg_error (GPG_ERR_INV_ID); - err = does_key_exist (app, keyno, force); + err = does_key_exist (app, keyno, 0, force); if (err) return err; @@ -1734,99 +2573,153 @@ do_writekey (app_t app, ctrl_t ctrl, err = gpg_error (GPG_ERR_INV_VALUE); goto leave; } + + maxbits = app->app_local->keyattr[keyno].n_bits; nbits = rsa_n? count_bits (rsa_n, rsa_n_len) : 0; - if (nbits != 1024) + if (opt.verbose) + log_info ("RSA modulus size is %u bits (%u bytes)\n", + nbits, (unsigned int)rsa_n_len); + if (nbits && nbits != maxbits + && app->app_local->extcap.algo_attr_change) { - log_error (_("RSA modulus missing or not of size %d bits\n"), 1024); + /* Try to switch the key to a new length. */ + err = change_keyattr (app, keyno, nbits, pincb, pincb_arg); + if (!err) + maxbits = app->app_local->keyattr[keyno].n_bits; + } + if (nbits != maxbits) + { + log_error (_("RSA modulus missing or not of size %d bits\n"), + (int)maxbits); err = gpg_error (GPG_ERR_BAD_SECKEY); goto leave; } + + maxbits = app->app_local->keyattr[keyno].e_bits; + if (maxbits > 32 && !app->app_local->extcap.is_v2) + maxbits = 32; /* Our code for v1 does only support 32 bits. */ nbits = rsa_e? count_bits (rsa_e, rsa_e_len) : 0; - if (nbits < 2 || nbits > 32) + if (nbits < 2 || nbits > maxbits) { log_error (_("RSA public exponent missing or larger than %d bits\n"), - 32); + (int)maxbits); err = gpg_error (GPG_ERR_BAD_SECKEY); goto leave; } + + maxbits = app->app_local->keyattr[keyno].n_bits/2; nbits = rsa_p? count_bits (rsa_p, rsa_p_len) : 0; - if (nbits != 512) + if (nbits != maxbits) { - log_error (_("RSA prime %s missing or not of size %d bits\n"), "P", 512); + log_error (_("RSA prime %s missing or not of size %d bits\n"), + "P", (int)maxbits); err = gpg_error (GPG_ERR_BAD_SECKEY); goto leave; } nbits = rsa_q? count_bits (rsa_q, rsa_q_len) : 0; - if (nbits != 512) + if (nbits != maxbits) { - log_error (_("RSA prime %s missing or not of size %d bits\n"), "Q", 512); + log_error (_("RSA prime %s missing or not of size %d bits\n"), + "Q", (int)maxbits); err = gpg_error (GPG_ERR_BAD_SECKEY); goto leave; } - - /* Build the private key template as described in section 4.3.3.6 of - the OpenPGP card specs: - 0xC0 public exponent - 0xC1 prime p - 0xC2 prime q - */ - assert (rsa_e_len <= 4); - template_len = (1 + 1 + 4 - + 1 + 1 + rsa_p_len - + 1 + 1 + rsa_q_len); - template = tp = xtrymalloc_secure (template_len); - if (!template) - { - err = gpg_error_from_errno (errno); - goto leave; - } - *tp++ = 0xC0; - *tp++ = 4; - memcpy (tp, rsa_e, rsa_e_len); - if (rsa_e_len < 4) - { - /* Right justify E. */ - memmove (tp+4-rsa_e_len, tp, rsa_e_len); - memset (tp, 0, 4-rsa_e_len); - } - tp += 4; - - *tp++ = 0xC1; - *tp++ = rsa_p_len; - memcpy (tp, rsa_p, rsa_p_len); - tp += rsa_p_len; - - *tp++ = 0xC2; - *tp++ = rsa_q_len; - memcpy (tp, rsa_q, rsa_q_len); - tp += rsa_q_len; - - assert (tp - template == template_len); - - - /* Obviously we need to remove the cached public key. */ + /* We need to remove the cached public key. */ xfree (app->app_local->pk[keyno].key); app->app_local->pk[keyno].key = NULL; app->app_local->pk[keyno].keylen = 0; app->app_local->pk[keyno].read_done = 0; - /* Prepare for storing the key. */ - err = verify_chv3 (app, pincb, pincb_arg); - if (err) - goto leave; - /* Store the key. */ - err = iso7816_put_data (app->slot, - (app->card_version > 0x0007? 0xE0 : 0xE9) + keyno, - template, template_len); + if (app->app_local->extcap.is_v2) + { + /* Build the private key template as described in section 4.3.3.7 of + the OpenPGP card specs version 2.0. */ + int exmode; + + err = build_privkey_template (app, keyno, + rsa_n, rsa_n_len, + rsa_e, rsa_e_len, + rsa_p, rsa_p_len, + rsa_q, rsa_q_len, + &template, &template_len); + if (err) + goto leave; + + /* Prepare for storing the key. */ + err = verify_chv3 (app, pincb, pincb_arg); + if (err) + goto leave; + + /* Store the key. */ + if (app->app_local->cardcap.ext_lc_le && template_len > 254) + exmode = 1; /* Use extended length w/o a limit. */ + else if (app->app_local->cardcap.cmd_chaining && template_len > 254) + exmode = -254; + else + exmode = 0; + err = iso7816_put_data_odd (app->slot, exmode, 0x3fff, + template, template_len); + } + else + { + /* Build the private key template as described in section 4.3.3.6 of + the OpenPGP card specs version 1.1: + 0xC0 public exponent + 0xC1 prime p + 0xC2 prime q + */ + assert (rsa_e_len <= 4); + template_len = (1 + 1 + 4 + + 1 + 1 + rsa_p_len + + 1 + 1 + rsa_q_len); + template = tp = xtrymalloc_secure (template_len); + if (!template) + { + err = gpg_error_from_syserror (); + goto leave; + } + *tp++ = 0xC0; + *tp++ = 4; + memcpy (tp, rsa_e, rsa_e_len); + if (rsa_e_len < 4) + { + /* Right justify E. */ + memmove (tp+4-rsa_e_len, tp, rsa_e_len); + memset (tp, 0, 4-rsa_e_len); + } + tp += 4; + + *tp++ = 0xC1; + *tp++ = rsa_p_len; + memcpy (tp, rsa_p, rsa_p_len); + tp += rsa_p_len; + + *tp++ = 0xC2; + *tp++ = rsa_q_len; + memcpy (tp, rsa_q, rsa_q_len); + tp += rsa_q_len; + + assert (tp - template == template_len); + + /* Prepare for storing the key. */ + err = verify_chv3 (app, pincb, pincb_arg); + if (err) + goto leave; + + /* Store the key. */ + err = iso7816_put_data (app->slot, 0, + (app->card_version > 0x0007? 0xE0:0xE9)+keyno, + template, template_len); + } if (err) { log_error (_("failed to store the key: %s\n"), gpg_strerror (err)); goto leave; } - err = store_fpr (app->slot, keyno, created_at, + err = store_fpr (app, keyno, created_at, rsa_n, rsa_n_len, rsa_e, rsa_e_len, fprbuf, app->card_version); if (err) @@ -1842,8 +2735,9 @@ do_writekey (app_t app, ctrl_t ctrl, /* Handle the GENKEY command. */ static gpg_error_t do_genkey (app_t app, ctrl_t ctrl, const char *keynostr, unsigned int flags, - gpg_error_t (*pincb)(void*, const char *, char **), - void *pincb_arg) + time_t createtime, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg) { int rc; char numbuf[30]; @@ -1855,6 +2749,9 @@ do_genkey (app_t app, ctrl_t ctrl, const char *keynostr, unsigned int flags, int keyno = atoi (keynostr); int force = (flags & 1); time_t start_at; + int exmode; + int le_value; + unsigned int keybits; if (keyno < 1 || keyno > 3) return gpg_error (GPG_ERR_INV_ID); @@ -1871,28 +2768,49 @@ do_genkey (app_t app, ctrl_t ctrl, const char *keynostr, unsigned int flags, app->app_local->pk[keyno].read_done = 0; /* Check whether a key already exists. */ - rc = does_key_exist (app, keyno, force); + rc = does_key_exist (app, keyno, 1, force); if (rc) return rc; - /* Prepare for key generation by verifying the ADmin PIN. */ + /* Because we send the key parameter back via status lines we need + to put a limit on the max. allowed keysize. 2048 bit will + already lead to a 527 byte long status line and thus a 4096 bit + key would exceed the Assuan line length limit. */ + keybits = app->app_local->keyattr[keyno].n_bits; + if (keybits > 3072) + return gpg_error (GPG_ERR_TOO_LARGE); + + /* Prepare for key generation by verifying the Admin PIN. */ rc = verify_chv3 (app, pincb, pincb_arg); if (rc) goto leave; - -#if 1 + + /* Test whether we will need extended length mode. (1900 is an + arbitrary length which for sure fits into a short apdu.) */ + if (app->app_local->cardcap.ext_lc_le && keybits > 1900) + { + exmode = 1; /* Use extended length w/o a limit. */ + le_value = app->app_local->extcap.max_rsp_data; + /* No need to check le_value because it comes from a 16 bit + value and thus can't create an overflow on a 32 bit + system. */ + } + else + { + exmode = 0; + le_value = 256; /* Use legacy value. */ + } + log_info (_("please wait while key is being generated ...\n")); start_at = time (NULL); rc = iso7816_generate_keypair -#else -#warning key generation temporary replaced by reading an existing key. - rc = iso7816_read_public_key -#endif - (app->slot, - keyno == 0? "\xB6" : - keyno == 1? "\xB8" : "\xA4", - 2, - &buffer, &buflen); +/* # warning key generation temporary replaced by reading an existing key. */ +/* rc = iso7816_read_public_key */ + (app->slot, exmode, + (const unsigned char*)(keyno == 0? "\xB6" : + keyno == 1? "\xB8" : "\xA4"), 2, + le_value, + &buffer, &buflen); if (rc) { rc = gpg_error (GPG_ERR_CARD); @@ -1901,6 +2819,7 @@ do_genkey (app_t app, ctrl_t 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); if (!keydata) { @@ -1916,7 +2835,7 @@ do_genkey (app_t app, ctrl_t ctrl, const char *keynostr, unsigned int flags, log_error (_("response does not contain the RSA modulus\n")); goto leave; } -/* log_printhex ("RSA n:", m, mlen); */ + /* log_printhex ("RSA n:", m, mlen); */ send_key_data (ctrl, "n", m, mlen); e = find_tlv (keydata, keydatalen, 0x0082, &elen); @@ -1926,15 +2845,15 @@ do_genkey (app_t app, ctrl_t ctrl, const char *keynostr, unsigned int flags, log_error (_("response does not contain the RSA public exponent\n")); goto leave; } -/* log_printhex ("RSA e:", e, elen); */ + /* log_printhex ("RSA e:", e, elen); */ send_key_data (ctrl, "e", e, elen); - created_at = gnupg_get_time (); + created_at = createtime? createtime : gnupg_get_time (); sprintf (numbuf, "%lu", (unsigned long)created_at); send_status_info (ctrl, "KEY-CREATED-AT", numbuf, (size_t)strlen(numbuf), NULL, 0); - rc = store_fpr (app->slot, keyno, (u32)created_at, + rc = store_fpr (app, keyno, (u32)created_at, m, mlen, e, elen, fprbuf, app->card_version); if (rc) goto leave; @@ -1988,7 +2907,7 @@ compare_fingerprint (app_t app, int keyno, unsigned char *sha1fpr) assert (keyno >= 1 && keyno <= 3); - rc = get_cached_data (app, 0x006E, &buffer, &buflen, 0); + rc = get_cached_data (app, 0x006E, &buffer, &buflen, 0, 0); if (rc) { log_error (_("error reading application data\n")); @@ -2006,6 +2925,7 @@ compare_fingerprint (app_t app, int keyno, unsigned char *sha1fpr) if (sha1fpr[i] != fpr[i]) { xfree (buffer); + log_info (_("fingerprint on card does not match requested one\n")); return gpg_error (GPG_ERR_WRONG_SECKEY); } xfree (buffer); @@ -2013,11 +2933,11 @@ compare_fingerprint (app_t app, int keyno, unsigned char *sha1fpr) } - /* If a fingerprint has been specified check it against the one on - the card. This is allows for a meaningful error message in case - the key on the card has been replaced but the shadow information - known to gpg was not updated. If there is no fingerprint we - assume that this is okay. */ +/* If a fingerprint has been specified check it against the one on the + card. This allows for a meaningful error message in case the key + on the card has been replaced but the shadow information known to + gpg has not been updated. If there is no fingerprint we assume + that this is okay. */ static gpg_error_t check_against_given_fingerprint (app_t app, const char *fpr, int keyno) { @@ -2045,10 +2965,14 @@ check_against_given_fingerprint (app_t app, const char *fpr, int keyno) raw message digest. For this application the KEYIDSTR consists of the serialnumber and the fingerprint delimited by a slash. - Note that this fucntion may return the error code + Note that this function may return the error code GPG_ERR_WRONG_CARD to indicate that the card currently present does not match the one required for the requested action (e.g. the - serial number does not match). */ + serial number does not match). + + As a special feature a KEYIDSTR of "OPENPGP.3" redirects the + operation to the auth command. +*/ static gpg_error_t do_sign (app_t app, const char *keyidstr, int hashalgo, gpg_error_t (*pincb)(void*, const char *, char **), @@ -2056,59 +2980,102 @@ do_sign (app_t app, const char *keyidstr, int hashalgo, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen ) { - static unsigned char sha1_prefix[15] = /* Object ID is 1.3.14.3.2.26 */ - { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, - 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14 }; static unsigned char rmd160_prefix[15] = /* Object ID is 1.3.36.3.2.1 */ - { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x24, 0x03, - 0x02, 0x01, 0x05, 0x00, 0x04, 0x14 }; + { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x24, 0x03, + 0x02, 0x01, 0x05, 0x00, 0x04, 0x14 }; + static unsigned char sha1_prefix[15] = /* (1.3.14.3.2.26) */ + { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, + 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14 }; + static unsigned char sha224_prefix[19] = /* (2.16.840.1.101.3.4.2.4) */ + { 0x30, 0x2D, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, + 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05, 0x00, 0x04, + 0x1C }; + static unsigned char sha256_prefix[19] = /* (2.16.840.1.101.3.4.2.1) */ + { 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, + 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, + 0x00, 0x04, 0x20 }; + static unsigned char sha384_prefix[19] = /* (2.16.840.1.101.3.4.2.2) */ + { 0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, + 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, + 0x00, 0x04, 0x30 }; + static unsigned char sha512_prefix[19] = /* (2.16.840.1.101.3.4.2.3) */ + { 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, + 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, + 0x00, 0x04, 0x40 }; int rc; - unsigned char data[35]; - unsigned char tmp_sn[20]; /* actually 16 but we use it also for the fpr. */ + unsigned char data[19+64]; + size_t datalen; + unsigned char tmp_sn[20]; /* Actually 16 bytes but also for the fpr. */ const char *s; int n; const char *fpr = NULL; unsigned long sigcount; + int use_auth = 0; + int exmode, le_value; if (!keyidstr || !*keyidstr) return gpg_error (GPG_ERR_INV_VALUE); + + /* Strip off known prefixes. */ +#define X(a,b,c,d) \ + if (hashalgo == GCRY_MD_ ## a \ + && (d) \ + && indatalen == sizeof b ## _prefix + (c) \ + && !memcmp (indata, b ## _prefix, sizeof b ## _prefix)) \ + { \ + indata = (const char*)indata + sizeof b ## _prefix; \ + indatalen -= sizeof b ## _prefix; \ + } + if (indatalen == 20) - ; - else if (indatalen == (15 + 20) && hashalgo == GCRY_MD_SHA1 - && !memcmp (indata, sha1_prefix, 15)) - ; - else if (indatalen == (15 + 20) && hashalgo == GCRY_MD_RMD160 - && !memcmp (indata, rmd160_prefix, 15)) - ; + ; /* Assume a plain SHA-1 or RMD160 digest has been given. */ + else X(SHA1, sha1, 20, 1) + else X(RMD160, rmd160, 20, 1) + else X(SHA224, sha224, 28, app->app_local->extcap.is_v2) + else X(SHA256, sha256, 32, app->app_local->extcap.is_v2) + else X(SHA384, sha384, 48, app->app_local->extcap.is_v2) + else X(SHA512, sha512, 64, app->app_local->extcap.is_v2) + else if ((indatalen == 28 || indatalen == 32 + || indatalen == 48 || indatalen ==64) + && app->app_local->extcap.is_v2) + ; /* Assume a plain SHA-3 digest has been given. */ else { - log_error(_("card does not support digest algorithm %s\n"), - digest_algo_to_string(hashalgo)); + log_error (_("card does not support digest algorithm %s\n"), + gcry_md_algo_name (hashalgo)); + /* Or the supplied digest length does not match an algorithm. */ return gpg_error (GPG_ERR_INV_VALUE); } +#undef X /* Check whether an OpenPGP card of any version has been requested. */ - if (strlen (keyidstr) < 32 || strncmp (keyidstr, "D27600012401", 12)) - return gpg_error (GPG_ERR_INV_ID); - - for (s=keyidstr, n=0; hexdigitp (s); s++, n++) + if (!strcmp (keyidstr, "OPENPGP.1")) ; - if (n != 32) + else if (!strcmp (keyidstr, "OPENPGP.3")) + use_auth = 1; + else if (strlen (keyidstr) < 32 || strncmp (keyidstr, "D27600012401", 12)) return gpg_error (GPG_ERR_INV_ID); - else if (!*s) - ; /* no fingerprint given: we allow this for now. */ - else if (*s == '/') - fpr = s + 1; else - return gpg_error (GPG_ERR_INV_ID); + { + for (s=keyidstr, n=0; hexdigitp (s); s++, n++) + ; + if (n != 32) + return gpg_error (GPG_ERR_INV_ID); + else if (!*s) + ; /* no fingerprint given: we allow this for now. */ + else if (*s == '/') + fpr = s + 1; + else + return gpg_error (GPG_ERR_INV_ID); - for (s=keyidstr, n=0; n < 16; s += 2, n++) - tmp_sn[n] = xtoi_2 (s); + for (s=keyidstr, n=0; n < 16; s += 2, n++) + tmp_sn[n] = xtoi_2 (s); - if (app->serialnolen != 16) - return gpg_error (GPG_ERR_INV_CARD); - if (memcmp (app->serialno, tmp_sn, 16)) - return gpg_error (GPG_ERR_WRONG_CARD); + if (app->serialnolen != 16) + return gpg_error (GPG_ERR_INV_CARD); + if (memcmp (app->serialno, tmp_sn, 16)) + return gpg_error (GPG_ERR_WRONG_CARD); + } /* If a fingerprint has been specified check it against the one on the card. This is allows for a meaningful error message in case @@ -2120,59 +3087,56 @@ do_sign (app_t app, const char *keyidstr, int hashalgo, if (rc) return rc; - if (hashalgo == GCRY_MD_SHA1) - memcpy (data, sha1_prefix, 15); - else if (hashalgo == GCRY_MD_RMD160) - memcpy (data, rmd160_prefix, 15); + /* Concatenate prefix and digest. */ +#define X(a,b,d) \ + if (hashalgo == GCRY_MD_ ## a && (d) ) \ + { \ + datalen = sizeof b ## _prefix + indatalen; \ + assert (datalen <= sizeof data); \ + memcpy (data, b ## _prefix, sizeof b ## _prefix); \ + memcpy (data + sizeof b ## _prefix, indata, indatalen); \ + } + + X(SHA1, sha1, 1) + else X(RMD160, rmd160, 1) + else X(SHA224, sha224, app->app_local->extcap.is_v2) + else X(SHA256, sha256, app->app_local->extcap.is_v2) + else X(SHA384, sha384, app->app_local->extcap.is_v2) + else X(SHA512, sha512, app->app_local->extcap.is_v2) else return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); - memcpy (data+15, indata, indatalen); +#undef X + /* Redirect to the AUTH command if asked to. */ + if (use_auth) + { + return do_auth (app, "OPENPGP.3", pincb, pincb_arg, + data, datalen, + outdata, outdatalen); + } + + /* Show the number of signature done using this key. */ sigcount = get_sig_counter (app); log_info (_("signatures created so far: %lu\n"), sigcount); + /* Check CHV if needed. */ if (!app->did_chv1 || app->force_chv1 ) { char *pinvalue; - { - char *prompt; -#define PROMPTSTRING _("||Please enter the PIN%%0A[sigs done: %lu]") - - prompt = malloc (strlen (PROMPTSTRING) + 50); - if (!prompt) - return gpg_error_from_errno (errno); - sprintf (prompt, PROMPTSTRING, sigcount); - rc = pincb (pincb_arg, prompt, &pinvalue); - free (prompt); -#undef PROMPTSTRING - } + rc = verify_a_chv (app, pincb, pincb_arg, 1, sigcount, &pinvalue); if (rc) - { - log_info (_("PIN callback returned error: %s\n"), gpg_strerror (rc)); - return rc; - } + return rc; - if (strlen (pinvalue) < 6) - { - log_error (_("PIN for CHV%d is too short;" - " minimum length is %d\n"), 1, 6); - xfree (pinvalue); - return gpg_error (GPG_ERR_BAD_PIN); - } - - rc = iso7816_verify (app->slot, 0x81, pinvalue, strlen (pinvalue)); - if (rc) - { - log_error (_("verify CHV%d failed: %s\n"), 1, gpg_strerror (rc)); - xfree (pinvalue); - flush_cache_after_error (app); - return rc; - } app->did_chv1 = 1; - if (!app->did_chv2) + + /* For cards with versions < 2 we want to keep CHV1 and CHV2 in + sync, thus we verify CHV2 here using the given PIN. Cards + with version2 to not have the need for a separate CHV2 and + internally use just one. Obviously we can't do that if the + keypad has been used. */ + if (!app->did_chv2 && pinvalue && !app->app_local->extcap.is_v2) { - /* We should also verify CHV2. */ rc = iso7816_verify (app->slot, 0x82, pinvalue, strlen (pinvalue)); if (gpg_err_code (rc) == GPG_ERR_BAD_PIN) rc = gpg_error (GPG_ERR_PIN_NOT_SYNCED); @@ -2188,7 +3152,19 @@ do_sign (app_t app, const char *keyidstr, int hashalgo, xfree (pinvalue); } - rc = iso7816_compute_ds (app->slot, data, 35, outdata, outdatalen); + + if (app->app_local->cardcap.ext_lc_le) + { + exmode = 1; /* Use extended length. */ + le_value = app->app_local->extcap.max_rsp_data; + } + else + { + exmode = 0; + le_value = 0; + } + rc = iso7816_compute_ds (app->slot, exmode, data, datalen, le_value, + outdata, outdatalen); return rc; } @@ -2198,7 +3174,7 @@ do_sign (app_t app, const char *keyidstr, int hashalgo, fingerprint delimited by a slash. Optionally the id OPENPGP.3 may be given. - Note that this fucntion may return the error code + Note that this function may return the error code GPG_ERR_WRONG_CARD to indicate that the card currently present does not match the one required for the requested action (e.g. the serial number does not match). */ @@ -2210,14 +3186,14 @@ do_auth (app_t app, const char *keyidstr, unsigned char **outdata, size_t *outdatalen ) { int rc; - unsigned char tmp_sn[20]; /* actually 16 but we use it also for the fpr. */ + unsigned char tmp_sn[20]; /* Actually 16 but we use it also for the fpr. */ const char *s; int n; const char *fpr = NULL; if (!keyidstr || !*keyidstr) return gpg_error (GPG_ERR_INV_VALUE); - if (indatalen > 50) /* For a 1024 bit key. */ + if (indatalen > 101) /* For a 2048 bit key. */ return gpg_error (GPG_ERR_INV_VALUE); /* Check whether an OpenPGP card of any version has been requested. */ @@ -2259,8 +3235,23 @@ do_auth (app_t app, const char *keyidstr, rc = verify_chv2 (app, pincb, pincb_arg); if (!rc) - rc = iso7816_internal_authenticate (app->slot, indata, indatalen, - outdata, outdatalen); + { + int exmode, le_value; + + if (app->app_local->cardcap.ext_lc_le) + { + exmode = 1; /* Use extended length. */ + le_value = app->app_local->extcap.max_rsp_data; + } + else + { + exmode = 0; + le_value = 0; + } + rc = iso7816_internal_authenticate (app->slot, exmode, + indata, indatalen, le_value, + outdata, outdatalen); + } return rc; } @@ -2277,38 +3268,43 @@ do_decipher (app_t app, const char *keyidstr, const char *s; int n; const char *fpr = NULL; + int exmode; if (!keyidstr || !*keyidstr || !indatalen) return gpg_error (GPG_ERR_INV_VALUE); /* Check whether an OpenPGP card of any version has been requested. */ - if (strlen (keyidstr) < 32 || strncmp (keyidstr, "D27600012401", 12)) - return gpg_error (GPG_ERR_INV_ID); - - for (s=keyidstr, n=0; hexdigitp (s); s++, n++) + if (!strcmp (keyidstr, "OPENPGP.2")) ; - if (n != 32) + else if (strlen (keyidstr) < 32 || strncmp (keyidstr, "D27600012401", 12)) return gpg_error (GPG_ERR_INV_ID); - else if (!*s) - ; /* no fingerprint given: we allow this for now. */ - else if (*s == '/') - fpr = s + 1; else - return gpg_error (GPG_ERR_INV_ID); - - for (s=keyidstr, n=0; n < 16; s += 2, n++) - tmp_sn[n] = xtoi_2 (s); - - if (app->serialnolen != 16) - return gpg_error (GPG_ERR_INV_CARD); - if (memcmp (app->serialno, tmp_sn, 16)) - return gpg_error (GPG_ERR_WRONG_CARD); + { + for (s=keyidstr, n=0; hexdigitp (s); s++, n++) + ; + if (n != 32) + return gpg_error (GPG_ERR_INV_ID); + else if (!*s) + ; /* no fingerprint given: we allow this for now. */ + else if (*s == '/') + fpr = s + 1; + else + return gpg_error (GPG_ERR_INV_ID); + + for (s=keyidstr, n=0; n < 16; s += 2, n++) + tmp_sn[n] = xtoi_2 (s); + + if (app->serialnolen != 16) + return gpg_error (GPG_ERR_INV_CARD); + if (memcmp (app->serialno, tmp_sn, 16)) + return gpg_error (GPG_ERR_WRONG_CARD); + } /* If a fingerprint has been specified check it against the one on the card. This is allows for a meaningful error message in case the key on the card has been replaced but the shadow information known to gpg was not updated. If there is no fingerprint, the - decryption will won't produce the right plaintext anyway. */ + decryption won't produce the right plaintext anyway. */ rc = fpr? check_against_given_fingerprint (app, fpr, 2) : 0; if (rc) return rc; @@ -2317,6 +3313,8 @@ do_decipher (app_t app, const char *keyidstr, if (!rc) { size_t fixuplen; + unsigned char *fixbuf = NULL; + int padind = 0; /* We might encounter a couple of leading zeroes in the cryptogram. Due to internal use of MPIs thease leading @@ -2327,35 +3325,44 @@ do_decipher (app_t app, const char *keyidstr, probability anyway broken. */ if (indatalen >= (128-16) && indatalen < 128) /* 1024 bit key. */ fixuplen = 128 - indatalen; - else if (indatalen >= (256-16) && indatalen < 256) /* 2048 bit key. */ - fixuplen = 256 - indatalen; else if (indatalen >= (192-16) && indatalen < 192) /* 1536 bit key. */ fixuplen = 192 - indatalen; + else if (indatalen >= (256-16) && indatalen < 256) /* 2048 bit key. */ + fixuplen = 256 - indatalen; + else if (indatalen >= (384-16) && indatalen < 384) /* 3072 bit key. */ + fixuplen = 384 - indatalen; else fixuplen = 0; + if (fixuplen) { - unsigned char *fixbuf; - /* While we have to prepend stuff anyway, we can also include the padding byte here so that iso1816_decipher - does not need to do yet another data mangling. */ + does not need to do another data mangling. */ fixuplen++; + fixbuf = xtrymalloc (fixuplen + indatalen); if (!fixbuf) - rc = gpg_error_from_syserror (); - else - { - memset (fixbuf, 0, fixuplen); - memcpy (fixbuf+fixuplen, indata, indatalen); - rc = iso7816_decipher (app->slot, fixbuf, fixuplen+indatalen, -1, - outdata, outdatalen); - xfree (fixbuf); - } + return gpg_error_from_syserror (); + + memset (fixbuf, 0, fixuplen); + memcpy (fixbuf+fixuplen, indata, indatalen); + indata = fixbuf; + indatalen = fixuplen + indatalen; + padind = -1; /* Already padded. */ } + + if (app->app_local->cardcap.ext_lc_le && indatalen > 254 ) + exmode = 1; /* Extended length w/o a limit. */ + else if (app->app_local->cardcap.cmd_chaining && indatalen > 254) + exmode = -254; /* Command chaining with max. 254 bytes. */ else - rc = iso7816_decipher (app->slot, indata, indatalen, 0, - outdata, outdatalen); + exmode = 0; + + rc = iso7816_decipher (app->slot, exmode, + indata, indatalen, padind, + outdata, outdatalen); + xfree (fixbuf); } return rc; @@ -2452,7 +3459,141 @@ do_check_pin (app_t app, const char *keyidstr, } +/* Show information about card capabilities. */ +static void +show_caps (struct app_local_s *s) +{ + log_info ("Version-2 ......: %s\n", s->extcap.is_v2? "yes":"no"); + log_info ("Get-Challenge ..: %s", s->extcap.get_challenge? "yes":"no"); + if (s->extcap.get_challenge) + log_printf (" (%u bytes max)", s->extcap.max_get_challenge); + log_info ("Key-Import .....: %s\n", s->extcap.key_import? "yes":"no"); + log_info ("Change-Force-PW1: %s\n", s->extcap.change_force_chv? "yes":"no"); + log_info ("Private-DOs ....: %s\n", s->extcap.private_dos? "yes":"no"); + log_info ("Algo-Attr-Change: %s\n", s->extcap.algo_attr_change? "yes":"no"); + log_info ("SM-Support .....: %s", s->extcap.sm_supported? "yes":"no"); + if (s->extcap.sm_supported) + log_printf (" (%s)", s->extcap.sm_aes128? "AES-128":"3DES"); + log_info ("Max-Cert3-Len ..: %u\n", s->extcap.max_certlen_3); + log_info ("Max-Cmd-Data ...: %u\n", s->extcap.max_cmd_data); + log_info ("Max-Rsp-Data ...: %u\n", s->extcap.max_rsp_data); + log_info ("Cmd-Chaining ...: %s\n", s->cardcap.cmd_chaining?"yes":"no"); + log_info ("Ext-Lc-Le ......: %s\n", s->cardcap.ext_lc_le?"yes":"no"); + log_info ("Status Indicator: %02X\n", s->status_indicator); + log_info ("GnuPG-No-Sync ..: %s\n", s->flags.no_sync? "yes":"no"); + log_info ("GnuPG-Def-PW2 ..: %s\n", s->flags.def_chv2? "yes":"no"); +} + + +/* Parse the historical bytes in BUFFER of BUFLEN and store them in + APPLOC. */ +static void +parse_historical (struct app_local_s *apploc, + const unsigned char * buffer, size_t buflen) +{ + /* Example buffer: 00 31 C5 73 C0 01 80 00 90 00 */ + if (buflen < 4) + { + log_error ("warning: historical bytes are too short\n"); + return; /* Too short. */ + } + if (*buffer) + { + log_error ("warning: bad category indicator in historical bytes\n"); + return; + } + + /* Skip category indicator. */ + buffer++; + buflen--; + + /* Get the status indicator. */ + apploc->status_indicator = buffer[buflen-3]; + buflen -= 3; + + /* Parse the compact TLV. */ + while (buflen) + { + unsigned int tag = (*buffer & 0xf0) >> 4; + unsigned int len = (*buffer & 0x0f); + if (len+1 > buflen) + { + log_error ("warning: bad Compact-TLV in historical bytes\n"); + return; /* Error. */ + } + buffer++; + buflen--; + if (tag == 7 && len == 3) + { + /* Card capabilities. */ + apploc->cardcap.cmd_chaining = !!(buffer[2] & 0x80); + apploc->cardcap.ext_lc_le = !!(buffer[2] & 0x40); + } + buffer += len; + buflen -= len; + } +} + + +/* Parse and optionally show the algorithm attributes for KEYNO. + KEYNO must be in the range 0..2. */ +static void +parse_algorithm_attribute (app_t app, int keyno) +{ + unsigned char *buffer; + size_t buflen; + void *relptr; + const char const desc[3][5] = {"sign", "encr", "auth"}; + + assert (keyno >=0 && keyno <= 2); + + app->app_local->keyattr[keyno].n_bits = 0; + + relptr = get_one_do (app, 0xC1+keyno, &buffer, &buflen, NULL); + if (!relptr) + { + log_error ("error reading DO 0x%02X\n", 0xc1+keyno); + return; + } + if (buflen < 1) + { + log_error ("error reading DO 0x%02X\n", 0xc1+keyno); + xfree (relptr); + return; + } + + if (opt.verbose) + log_info ("Key-Attr-%s ..: ", desc[keyno]); + if (*buffer == 1 && (buflen == 5 || buflen == 6)) + { + app->app_local->keyattr[keyno].n_bits = (buffer[1]<<8 | buffer[2]); + app->app_local->keyattr[keyno].e_bits = (buffer[3]<<8 | buffer[4]); + app->app_local->keyattr[keyno].format = 0; + if (buflen < 6) + app->app_local->keyattr[keyno].format = RSA_STD; + else + app->app_local->keyattr[keyno].format = (buffer[5] == 0? RSA_STD : + buffer[5] == 1? RSA_STD_N : + buffer[5] == 2? RSA_CRT : + buffer[5] == 3? RSA_CRT_N : + RSA_UNKNOWN_FMT); + + if (opt.verbose) + log_printf + ("RSA, n=%u, e=%u, fmt=%s\n", + app->app_local->keyattr[keyno].n_bits, + app->app_local->keyattr[keyno].e_bits, + app->app_local->keyattr[keyno].format == RSA_STD? "std" : + app->app_local->keyattr[keyno].format == RSA_STD_N?"std+n": + app->app_local->keyattr[keyno].format == RSA_CRT? "crt" : + app->app_local->keyattr[keyno].format == RSA_CRT_N?"crt+n":"?"); + } + else if (opt.verbose) + log_printhex ("", buffer, buflen); + + xfree (relptr); +} /* Select the OpenPGP application on the card in SLOT. This function must be used before any other OpenPGP application functions. */ @@ -2466,7 +3607,9 @@ app_select_openpgp (app_t app) size_t buflen; void *relptr; - rc = iso7816_select_application (slot, aid, sizeof aid); + /* Note that the card can't cope with P2=0xCO, thus we need to pass a + special flag value. */ + rc = iso7816_select_application (slot, aid, sizeof aid, 0x0001); if (!rc) { unsigned int manufacturer; @@ -2483,7 +3626,7 @@ app_select_openpgp (app_t app) 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); + rc = iso7816_get_data (slot, 0, 0x004F, &buffer, &buflen); if (rc) goto leave; if (opt.verbose) @@ -2507,6 +3650,24 @@ app_select_openpgp (app_t app) goto leave; } + if (app->card_version >= 0x0200) + app->app_local->extcap.is_v2 = 1; + + + /* Read the historical bytes. */ + relptr = get_one_do (app, 0x5f52, &buffer, &buflen, NULL); + if (relptr) + { + if (opt.verbose) + { + log_info ("Historical Bytes: "); + log_printhex ("", buffer, buflen); + } + parse_historical (app->app_local, buffer, buflen); + xfree (relptr); + } + + /* Read the force-chv1 flag. */ relptr = get_one_do (app, 0x00C4, &buffer, &buflen, NULL); if (!relptr) { @@ -2517,6 +3678,7 @@ app_select_openpgp (app_t app) app->force_chv1 = (buflen && *buffer == 0); xfree (relptr); + /* Read the extended capabilities. */ relptr = get_one_do (app, 0x00C0, &buffer, &buflen, NULL); if (!relptr) { @@ -2526,13 +3688,25 @@ app_select_openpgp (app_t app) } if (buflen) { + app->app_local->extcap.sm_supported = !!(*buffer & 0x80); app->app_local->extcap.get_challenge = !!(*buffer & 0x40); app->app_local->extcap.key_import = !!(*buffer & 0x20); app->app_local->extcap.change_force_chv = !!(*buffer & 0x10); app->app_local->extcap.private_dos = !!(*buffer & 0x08); + app->app_local->extcap.algo_attr_change = !!(*buffer & 0x04); + } + if (buflen >= 10) + { + /* Available with v2 cards. */ + app->app_local->extcap.sm_aes128 = (buffer[1] == 1); + app->app_local->extcap.max_get_challenge + = (buffer[2] << 8 | buffer[3]); + app->app_local->extcap.max_certlen_3 = (buffer[4] << 8 | buffer[5]); + app->app_local->extcap.max_cmd_data = (buffer[6] << 8 | buffer[7]); + app->app_local->extcap.max_rsp_data = (buffer[8] << 8 | buffer[9]); } xfree (relptr); - + /* Some of the first cards accidently don't set the CHANGE_FORCE_CHV bit but allow it anyway. */ if (app->card_version <= 0x0100 && manufacturer == 1) @@ -2540,14 +3714,23 @@ app_select_openpgp (app_t app) parse_login_data (app); + if (opt.verbose) + show_caps (app->app_local); + + parse_algorithm_attribute (app, 0); + parse_algorithm_attribute (app, 1); + parse_algorithm_attribute (app, 2); + if (opt.verbose > 1) dump_all_do (slot); app->fnc.deinit = do_deinit; app->fnc.learn_status = do_learn_status; + app->fnc.readcert = do_readcert; app->fnc.readkey = do_readkey; app->fnc.getattr = do_getattr; app->fnc.setattr = do_setattr; + app->fnc.writecert = do_writecert; app->fnc.writekey = do_writekey; app->fnc.genkey = do_genkey; app->fnc.sign = do_sign; diff --git a/g10/card-util.c b/g10/card-util.c index 07d8ddad4..8c402a00f 100644 --- a/g10/card-util.c +++ b/g10/card-util.c @@ -1,5 +1,5 @@ /* card-util.c - Utility functions for the OpenPGP card. - * Copyright (C) 2003, 2004, 2005 Free Software Foundation, Inc. + * Copyright (C) 2003, 2004, 2005, 2009 Free Software Foundation, Inc. * * This file is part of GnuPG. * @@ -25,7 +25,7 @@ #include #if GNUPG_MAJOR_VERSION != 1 -#include "gpg.h" +# include "gpg.h" #endif /*GNUPG_MAJOR_VERSION != 1*/ #include "util.h" #include "i18n.h" @@ -35,22 +35,46 @@ #include "main.h" #include "keyserver-internal.h" #if GNUPG_MAJOR_VERSION == 1 -#ifdef HAVE_LIBREADLINE -#include -#include -#endif /*HAVE_LIBREADLINE*/ -#include "cardglue.h" +# ifdef HAVE_LIBREADLINE +# define GNUPG_LIBREADLINE_H_INCLUDED +# include +# include +# endif /*HAVE_LIBREADLINE*/ +# include "cardglue.h" #else /*GNUPG_MAJOR_VERSION!=1*/ -#include "call-agent.h" +# include "call-agent.h" #endif /*GNUPG_MAJOR_VERSION!=1*/ #define CONTROL_D ('D' - 'A' + 1) +static void +write_sc_op_status (gpg_error_t err) +{ + switch (gpg_err_code (err)) + { + case 0: + write_status (STATUS_SC_OP_SUCCESS); + break; +#if GNUPG_MAJOR_VERSION != 1 + case GPG_ERR_CANCELED: + write_status_text (STATUS_SC_OP_FAILURE, "1"); + break; + case GPG_ERR_BAD_PIN: + write_status_text (STATUS_SC_OP_FAILURE, "2"); + break; + default: + write_status (STATUS_SC_OP_FAILURE); + break; +#endif /* GNUPG_MAJOR_VERSION != 1 */ + } +} + + /* Change the PIN of a an OpenPGP card. This is an interactive function. */ void -change_pin (int chvno, int allow_admin) +change_pin (int unblock_v2, int allow_admin) { struct agent_card_info_s info; int rc; @@ -75,16 +99,31 @@ change_pin (int chvno, int allow_admin) return; } - if(!allow_admin) + + if (unblock_v2) + { + if (!info.is_v2) + log_error (_("This command is only available for version 2 cards\n")); + else if (!info.chvretry[1]) + log_error (_("Reset Code not or not anymore available\n")); + else + { + rc = agent_scd_change_pin (2, info.serialno); + write_sc_op_status (rc); + if (rc) + tty_printf ("Error changing the PIN: %s\n", gpg_strerror (rc)); + else + tty_printf ("PIN changed.\n"); + } + } + else if (!allow_admin) { rc = agent_scd_change_pin (1, info.serialno); + write_sc_op_status (rc); if (rc) tty_printf ("Error changing the PIN: %s\n", gpg_strerror (rc)); else - { - write_status (STATUS_SC_OP_SUCCESS); - tty_printf ("PIN changed.\n"); - } + tty_printf ("PIN changed.\n"); } else for (;;) @@ -95,6 +134,7 @@ change_pin (int chvno, int allow_admin) tty_printf ("1 - change PIN\n" "2 - unblock PIN\n" "3 - change Admin PIN\n" + "4 - set the Reset Code\n" "Q - quit\n"); tty_printf ("\n"); @@ -106,36 +146,44 @@ change_pin (int chvno, int allow_admin) rc = 0; if (*answer == '1') { + /* Change PIN. */ rc = agent_scd_change_pin (1, info.serialno); + write_sc_op_status (rc); if (rc) tty_printf ("Error changing the PIN: %s\n", gpg_strerror (rc)); else - { - write_status (STATUS_SC_OP_SUCCESS); - tty_printf ("PIN changed.\n"); - } + tty_printf ("PIN changed.\n"); } else if (*answer == '2') { + /* Unblock PIN. */ rc = agent_scd_change_pin (101, info.serialno); + write_sc_op_status (rc); if (rc) tty_printf ("Error unblocking the PIN: %s\n", gpg_strerror (rc)); else - { - write_status (STATUS_SC_OP_SUCCESS); - tty_printf ("PIN unblocked and new PIN set.\n"); - } + tty_printf ("PIN unblocked and new PIN set.\n"); } else if (*answer == '3') { + /* Change Admin PIN. */ rc = agent_scd_change_pin (3, info.serialno); + write_sc_op_status (rc); if (rc) tty_printf ("Error changing the PIN: %s\n", gpg_strerror (rc)); else - { - write_status (STATUS_SC_OP_SUCCESS); - tty_printf ("PIN changed.\n"); - } + tty_printf ("PIN changed.\n"); + } + else if (*answer == '4') + { + /* Set a new Reset Code. */ + rc = agent_scd_change_pin (102, info.serialno); + write_sc_op_status (rc); + if (rc) + tty_printf ("Error setting the Reset Code: %s\n", + gpg_strerror (rc)); + else + tty_printf ("Reset Code set.\n"); } else if (*answer == 'q' || *answer == 'Q') { @@ -156,11 +204,13 @@ get_manufacturer (unsigned int no) case 0x0002: return "Prism"; case 0x0003: return "OpenFortress"; case 0x0004: return "Wewid AB"; + case 0x0005: return "ZeitControl"; + case 0x002A: return "Magrathea"; /* 0x00000 and 0xFFFF are defined as test cards per spec, 0xFFF00 to 0xFFFE are assigned for use with randomly created serial numbers. */ - case 0: + case 0x0000: case 0xffff: return "test card"; default: return (no & 0xff00) == 0xff00? "unmanaged S/N range":"unknown"; } @@ -287,6 +337,18 @@ fpr_is_zero (const char *fpr) } +/* Return true if the SHA1 fingerprint FPR consists only of 0xFF. */ +static int +fpr_is_ff (const char *fpr) +{ + int i; + + for (i=0; i < 20 && fpr[i] == '\xff'; i++) + ; + return (i == 20); +} + + /* Print all available information about the current card. */ void card_status (FILE *fp, char *serialno, size_t serialnobuflen) @@ -320,8 +382,35 @@ card_status (FILE *fp, char *serialno, size_t serialnobuflen) if (!info.serialno || strncmp (info.serialno, "D27600012401", 12) || strlen (info.serialno) != 32 ) { - if (opt.with_colons) - fputs ("unknown:\n", fp); + if (info.apptype && !strcmp (info.apptype, "NKS")) + { + if (opt.with_colons) + fputs ("netkey-card:\n", fp); + log_info ("this is a NetKey card\n"); + } + else if (info.apptype && !strcmp (info.apptype, "DINSIG")) + { + if (opt.with_colons) + fputs ("dinsig-card:\n", fp); + log_info ("this is a DINSIG compliant card\n"); + } + else if (info.apptype && !strcmp (info.apptype, "P15")) + { + if (opt.with_colons) + fputs ("pkcs15-card:\n", fp); + log_info ("this is a PKCS#15 compliant card\n"); + } + else if (info.apptype && !strcmp (info.apptype, "GELDKARTE")) + { + if (opt.with_colons) + fputs ("geldkarte-card:\n", fp); + log_info ("this is a Geldkarte compliant card\n"); + } + else + { + if (opt.with_colons) + fputs ("unknown:\n", fp); + } log_info ("not an OpenPGP card\n"); agent_release_card_info (&info); xfree (pk); @@ -367,6 +456,10 @@ card_status (FILE *fp, char *serialno, size_t serialnobuflen) fputs (":\n", fp); fprintf (fp, "forcepin:%d:::\n", !info.chv1_cached); + for (i=0; i < DIM (info.key_attr); i++) + if (info.key_attr[0].algo) + fprintf (fp, "keyattr:%d:%d:%u:\n", i+1, + info.key_attr[i].algo, info.key_attr[i].nbits); fprintf (fp, "maxpinlen:%d:%d:%d:\n", info.chvmaxlen[0], info.chvmaxlen[1], info.chvmaxlen[2]); fprintf (fp, "pinretry:%d:%d:%d:\n", @@ -442,6 +535,16 @@ card_status (FILE *fp, char *serialno, size_t serialnobuflen) } tty_fprintf (fp, "Signature PIN ....: %s\n", info.chv1_cached? _("not forced"): _("forced")); + if (info.key_attr[0].algo) + { + tty_fprintf (fp, "Key attributes ...:"); + for (i=0; i < DIM (info.key_attr); i++) + tty_fprintf (fp, " %u%c", + info.key_attr[i].nbits, + info.key_attr[i].algo == 1? 'R': + info.key_attr[i].algo == 17? 'D': '?'); + tty_fprintf (fp, "\n"); + } tty_fprintf (fp, "Max. PIN lengths .: %d %d %d\n", info.chvmaxlen[0], info.chvmaxlen[1], info.chvmaxlen[2]); tty_fprintf (fp, "PIN retry counter : %d %d %d\n", @@ -466,7 +569,10 @@ card_status (FILE *fp, char *serialno, size_t serialnobuflen) thefpr = (info.fpr1valid? info.fpr1 : info.fpr2valid? info.fpr2 : info.fpr3valid? info.fpr3 : NULL); - if ( thefpr && !get_pubkey_byfprint (pk, thefpr, 20)) + /* If the fingerprint is all 0xff, the key has no asssociated + OpenPGP certificate. */ + if ( thefpr && !fpr_is_ff (thefpr) + && !get_pubkey_byfprint (pk, thefpr, 20)) { KBNODE keyblock = NULL; @@ -599,6 +705,7 @@ change_url (void) if (rc) log_error ("error setting URL: %s\n", gpg_strerror (rc)); xfree (url); + write_sc_op_status (rc); return rc; } @@ -608,7 +715,6 @@ change_url (void) static int fetch_url(void) { -#if GNUPG_MAJOR_VERSION == 1 int rc; struct agent_card_info_s info; @@ -648,9 +754,91 @@ fetch_url(void) } return rc; -#else - return 0; +} + + +/* Read data from file FNAME up to MAXLEN characters. On error return + -1 and store NULL at R_BUFFER; on success return the number of + bytes read and store the address of a newly allocated buffer at + R_BUFFER. */ +static int +get_data_from_file (const char *fname, size_t maxlen, char **r_buffer) +{ + FILE *fp; + char *data; + int n; + + *r_buffer = NULL; + + fp = fopen (fname, "rb"); +#if GNUPG_MAJOR_VERSION == 1 + if (fp && is_secured_file (fileno (fp))) + { + fclose (fp); + fp = NULL; + errno = EPERM; + } #endif + if (!fp) + { + tty_printf (_("can't open `%s': %s\n"), fname, strerror (errno)); + return -1; + } + + data = xtrymalloc (maxlen? maxlen:1); + if (!data) + { + tty_printf (_("error allocating enough memory: %s\n"), strerror (errno)); + fclose (fp); + return -1; + } + + if (maxlen) + n = fread (data, 1, maxlen, fp); + else + n = 0; + fclose (fp); + if (n < 0) + { + tty_printf (_("error reading `%s': %s\n"), fname, strerror (errno)); + xfree (data); + return -1; + } + *r_buffer = data; + return n; +} + + +/* Write LENGTH bytes from BUFFER to file FNAME. Return 0 on + success. */ +static int +put_data_to_file (const char *fname, const void *buffer, size_t length) +{ + FILE *fp; + + fp = fopen (fname, "wb"); +#if GNUPG_MAJOR_VERSION == 1 + if (fp && is_secured_file (fileno (fp))) + { + fclose (fp); + fp = NULL; + errno = EPERM; + } +#endif + if (!fp) + { + tty_printf (_("can't create `%s': %s\n"), fname, strerror (errno)); + return -1; + } + + if (length && fwrite (buffer, length, 1, fp) != 1) + { + tty_printf (_("error writing `%s': %s\n"), fname, strerror (errno)); + fclose (fp); + return -1; + } + fclose (fp); + return 0; } @@ -663,34 +851,11 @@ change_login (const char *args) if (args && *args == '<') /* Read it from a file */ { - FILE *fp; - for (args++; spacep (args); args++) ; - fp = fopen (args, "rb"); -#if GNUPG_MAJOR_VERSION == 1 - if (fp && is_secured_file (fileno (fp))) - { - fclose (fp); - fp = NULL; - errno = EPERM; - } -#endif - if (!fp) - { - tty_printf (_("can't open `%s': %s\n"), args, strerror (errno)); - return -1; - } - - data = xmalloc (254); - n = fread (data, 1, 254, fp); - fclose (fp); + n = get_data_from_file (args, 254, &data); if (n < 0) - { - tty_printf (_("error reading `%s': %s\n"), args, strerror (errno)); - xfree (data); - return -1; - } + return -1; } else { @@ -715,6 +880,7 @@ change_login (const char *args) if (rc) log_error ("error setting login data: %s\n", gpg_strerror (rc)); xfree (data); + write_sc_op_status (rc); return rc; } @@ -731,35 +897,11 @@ change_private_do (const char *args, int nr) if (args && (args = strchr (args, '<'))) /* Read it from a file */ { - FILE *fp; - - /* Fixme: Factor this duplicated code out. */ for (args++; spacep (args); args++) ; - fp = fopen (args, "rb"); -#if GNUPG_MAJOR_VERSION == 1 - if (fp && is_secured_file (fileno (fp))) - { - fclose (fp); - fp = NULL; - errno = EPERM; - } -#endif - if (!fp) - { - tty_printf (_("can't open `%s': %s\n"), args, strerror (errno)); - return -1; - } - - data = xmalloc (254); - n = fread (data, 1, 254, fp); - fclose (fp); + n = get_data_from_file (args, 254, &data); if (n < 0) - { - tty_printf (_("error reading `%s': %s\n"), args, strerror (errno)); - xfree (data); - return -1; - } + return -1; } else { @@ -784,9 +926,74 @@ change_private_do (const char *args, int nr) if (rc) log_error ("error setting private DO: %s\n", gpg_strerror (rc)); xfree (data); + write_sc_op_status (rc); return rc; } + +static int +change_cert (const char *args) +{ + char *data; + int n; + int rc; + + if (args && *args == '<') /* Read it from a file */ + { + for (args++; spacep (args); args++) + ; + n = get_data_from_file (args, 16384, &data); + if (n < 0) + return -1; + } + else + { + tty_printf ("usage error: redirectrion to file required\n"); + return -1; + } + +#warning need to implement this fucntion + rc = -1; /*agent_scd_writecert ("OPENPGP.3", data, n);*/ + if (rc) + log_error ("error writing certificate to card: %s\n", gpg_strerror (rc)); + xfree (data); + write_sc_op_status (rc); + return rc; +} + + +static int +read_cert (const char *args) +{ + const char *fname; + void *buffer; + size_t length; + int rc; + + if (args && *args == '>') /* Write it to a file */ + { + for (args++; spacep (args); args++) + ; + fname = args; + } + else + { + tty_printf ("usage error: redirectrion to file required\n"); + return -1; + } + +#warning need to implement this fucntion + rc = -1; /*agent_scd_readcert ("OPENPGP.3", &buffer, &length);*/ + if (rc) + log_error ("error reading certificate from card: %s\n", gpg_strerror (rc)); + else + rc = put_data_to_file (fname, buffer, length); + xfree (buffer); + write_sc_op_status (rc); + return rc; +} + + static int change_lang (void) { @@ -820,6 +1027,7 @@ change_lang (void) if (rc) log_error ("error setting lang: %s\n", gpg_strerror (rc)); xfree (data); + write_sc_op_status (rc); return rc; } @@ -855,6 +1063,7 @@ change_sex (void) if (rc) log_error ("error setting sex: %s\n", gpg_strerror (rc)); xfree (data); + write_sc_op_status (rc); return rc; } @@ -899,6 +1108,7 @@ change_cafpr (int fprno) fprno==3?"CA-FPR-3":"x", fpr, 20, NULL ); if (rc) log_error ("error setting cafpr: %s\n", gpg_strerror (rc)); + write_sc_op_status (rc); return rc; } @@ -924,6 +1134,7 @@ toggle_forcesig (void) rc = agent_scd_setattr ("CHV-STATUS-1", newstate? "\x01":"", 1, NULL); if (rc) log_error ("error toggling signature PIN flag: %s\n", gpg_strerror (rc)); + write_sc_op_status (rc); } @@ -963,7 +1174,7 @@ check_pin_for_key_operation (struct agent_card_info_s *info, int *forced_chv1) *forced_chv1 = !info->chv1_cached; if (*forced_chv1) - { /* Switch of the forced mode so that during key generation we + { /* Switch off the forced mode so that during key generation we don't get bothered with PIN queries for each self-signature. */ rc = agent_scd_setattr ("CHV-STATUS-1", "\x01", 1, info->serialno); @@ -981,8 +1192,11 @@ check_pin_for_key_operation (struct agent_card_info_s *info, int *forced_chv1) binding signature. */ rc = agent_scd_checkpin (info->serialno); if (rc) - log_error ("error checking the PIN: %s\n", gpg_strerror (rc)); - } + { + log_error ("error checking the PIN: %s\n", gpg_strerror (rc)); + write_sc_op_status (rc); + } + } return rc; } @@ -1003,7 +1217,7 @@ restore_forced_chv1 (int *forced_chv1) } } -#if GNUPG_MAJOR_VERSION == 1 + /* Helper for the key generation/edit functions. */ static void show_card_key_info (struct agent_card_info_s *info) @@ -1016,9 +1230,8 @@ show_card_key_info (struct agent_card_info_s *info) print_sha1_fpr (NULL, info->fpr3valid? info->fpr3:NULL); tty_printf ("\n"); } -#endif -#if GNUPG_MAJOR_VERSION == 1 + /* Helper for the key generation/edit functions. */ static int replace_existing_key_p (struct agent_card_info_s *info, int keyno) @@ -1038,11 +1251,10 @@ replace_existing_key_p (struct agent_card_info_s *info, int keyno) } return 0; } -#endif static void -generate_card_keys (const char *serialno) +generate_card_keys (void) { struct agent_card_info_s info; int forced_chv1; @@ -1094,12 +1306,8 @@ generate_card_keys (const char *serialno) if (check_pin_for_key_operation (&info, &forced_chv1)) goto leave; -#if GNUPG_MAJOR_VERSION == 1 generate_keypair (NULL, info.serialno, want_backup? opt.homedir:NULL); -#else - generate_keypair (NULL, info.serialno); -#endif leave: agent_release_card_info (&info); @@ -1112,7 +1320,6 @@ generate_card_keys (const char *serialno) int card_generate_subkey (KBNODE pub_keyblock, KBNODE sec_keyblock) { -#if GNUPG_MAJOR_VERSION == 1 struct agent_card_info_s info; int okay = 0; int forced_chv1 = 0; @@ -1159,9 +1366,6 @@ card_generate_subkey (KBNODE pub_keyblock, KBNODE sec_keyblock) agent_release_card_info (&info); restore_forced_chv1 (&forced_chv1); return okay; -#else - return 0; -#endif } @@ -1172,7 +1376,6 @@ card_generate_subkey (KBNODE pub_keyblock, KBNODE sec_keyblock) int card_store_subkey (KBNODE node, int use) { -#if GNUPG_MAJOR_VERSION == 1 struct agent_card_info_s info; int okay = 0; int rc; @@ -1192,7 +1395,8 @@ card_store_subkey (KBNODE node, int use) show_card_key_info (&info); - if (!is_RSA (sk->pubkey_algo) || nbits_from_sk (sk) != 1024 ) + if (!is_RSA (sk->pubkey_algo) + || (!info.is_v2 && nbits_from_sk (sk) != 1024) ) { tty_printf ("You may only store a 1024 bit RSA key on the card\n"); tty_printf ("\n"); @@ -1260,7 +1464,10 @@ card_store_subkey (KBNODE node, int use) rc = save_unprotected_key_to_card (sk, keyno); if (rc) - goto leave; + { + log_error (_("error writing key to card: %s\n"), gpg_strerror (rc)); + goto leave; + } /* Get back to the maybe protected original secret key. */ if (copied_sk) @@ -1274,11 +1481,11 @@ card_store_subkey (KBNODE node, int use) n = pubkey_get_nskey (sk->pubkey_algo); for (i=pubkey_get_npkey (sk->pubkey_algo); i < n; i++) { - mpi_free (sk->skey[i]); + gcry_mpi_release (sk->skey[i]); sk->skey[i] = NULL; } i = pubkey_get_npkey (sk->pubkey_algo); - sk->skey[i] = mpi_set_opaque (NULL, xstrdup ("dummydata"), 10); + sk->skey[i] = gcry_mpi_set_opaque (NULL, xstrdup ("dummydata"), 10*8); sk->is_protected = 1; sk->protect.s2k.mode = 1002; s = info.serialno; @@ -1293,9 +1500,6 @@ card_store_subkey (KBNODE node, int use) free_secret_key (copied_sk); agent_release_card_info (&info); return okay; -#else - return 0; -#endif } @@ -1307,7 +1511,8 @@ enum cmdids cmdNOP = 0, cmdQUIT, cmdADMIN, cmdHELP, cmdLIST, cmdDEBUG, cmdVERIFY, cmdNAME, cmdURL, cmdFETCH, cmdLOGIN, cmdLANG, cmdSEX, cmdCAFPR, - cmdFORCESIG, cmdGENERATE, cmdPASSWD, cmdPRIVATEDO, + cmdFORCESIG, cmdGENERATE, cmdPASSWD, cmdPRIVATEDO, cmdWRITECERT, + cmdREADCERT, cmdUNBLOCK, cmdINVCMD }; @@ -1338,8 +1543,11 @@ static struct { "generate", cmdGENERATE, 1, N_("generate new keys")}, { "passwd" , cmdPASSWD, 0, N_("menu to change or unblock the PIN")}, { "verify" , cmdVERIFY, 0, N_("verify the PIN and list all data")}, - /* Note, that we do not announce this command yet. */ + { "unblock" , cmdUNBLOCK,0, N_("unblock the PIN using a Reset Code") }, + /* Note, that we do not announce these command yet. */ { "privatedo", cmdPRIVATEDO, 0, NULL }, + { "readcert", cmdREADCERT, 0, NULL }, + { "writecert", cmdWRITECERT, 1, NULL }, { NULL, cmdINVCMD, 0, NULL } }; @@ -1392,13 +1600,13 @@ card_edit_completion(const char *text, int start, int end) /* Menu to edit all user changeable values on an OpenPGP card. Only Key creation is not handled here. */ void -card_edit (STRLIST commands) +card_edit (strlist_t commands) { enum cmdids cmd = cmdNOP; int have_commands = !!commands; int redisplay = 1; char *answer = NULL; - int did_checkpin = 0, allow_admin=0; + int allow_admin=0; char serialnobuf[50]; @@ -1414,6 +1622,7 @@ card_edit (STRLIST commands) { int arg_number; const char *arg_string = ""; + const char *arg_rest = ""; char *p; int i; int cmd_admin_only; @@ -1482,6 +1691,11 @@ card_edit (STRLIST commands) trim_spaces (p); arg_number = atoi(p); arg_string = p; + arg_rest = p; + while (digitp (arg_rest)) + arg_rest++; + while (spacep (arg_rest)) + arg_rest++; } for (i=0; cmds[i].name; i++ ) @@ -1580,17 +1794,34 @@ card_edit (STRLIST commands) change_private_do (arg_string, arg_number); break; + case cmdWRITECERT: + if ( arg_number != 3 ) + tty_printf ("usage: writecert 3 < FILE\n"); + else + change_cert (arg_rest); + break; + + case cmdREADCERT: + if ( arg_number != 3 ) + tty_printf ("usage: readcert 3 > FILE\n"); + else + read_cert (arg_rest); + break; + case cmdFORCESIG: toggle_forcesig (); break; case cmdGENERATE: - generate_card_keys (serialnobuf); + generate_card_keys (); break; case cmdPASSWD: change_pin (0, allow_admin); - did_checkpin = 0; /* Need to reset it of course. */ + break; + + case cmdUNBLOCK: + change_pin (1, allow_admin); break; case cmdQUIT: diff --git a/g10/cardglue.c b/g10/cardglue.c index b09aec3fe..0833d6050 100644 --- a/g10/cardglue.c +++ b/g10/cardglue.c @@ -190,6 +190,37 @@ send_status_info (ctrl_t ctrl, const char *keyword, ...) va_end (arg_ptr); } +/* Send a ready formatted status line via assuan. */ +void +send_status_direct (ctrl_t ctrl, const char *keyword, const char *args) +{ + char buf[950]; + + if (strchr (args, '\n')) + log_error ("error: LF detected in status line - not sending\n"); + else + { + snprintf (buf, sizeof buf, "%s%s%s", + keyword, args? " ":"", args? args:""); + if (ctrl && ctrl->status_cb) + ctrl->status_cb (ctrl->status_cb_arg, buf); + } +} + + + +void +gcry_mpi_release (MPI a) +{ + mpi_free (a); +} + +MPI +gcry_mpi_set_opaque (MPI a, void *p, unsigned int len) +{ + return mpi_set_opaque (a, p, len); +} + /* Replacement function of the Libgcrypt onewhich is used in gnupg 1.9. Thus function computes the digest of ALGO from the data in @@ -208,6 +239,17 @@ gcry_md_hash_buffer (int algo, void *digest, } +/* This function simply returns the name of the algorithm or some + constant string when there is no algo. It will never return + NULL. */ +const char * +gcry_md_algo_name (int algorithm) +{ + const char *s = digest_algo_to_string (algorithm); + return s ? s : "?"; +} + + /* This is a limited version of the one in 1.9 but it should be sufficient here. */ void @@ -297,6 +339,7 @@ agent_release_card_info (struct agent_card_info_s *info) return; xfree (info->serialno); info->serialno = NULL; + xfree (info->apptype); info->apptype = NULL; xfree (info->disp_name); info->disp_name = NULL; xfree (info->disp_lang); info->disp_lang = NULL; xfree (info->pubkey_url); info->pubkey_url = NULL; @@ -448,7 +491,7 @@ open_card (void) } ready: - app->initialized = 1; + app->ref_count = 1; current_app = app; if (is_status_enabled () ) { @@ -629,6 +672,15 @@ store_serialno (const char *line) return p; } +/* Return a new malloced string by unescaping the string S. Escaping + is percent escaping and '+'/space mapping. A binary nul will + silently be replaced by a 0xFF. Function returns NULL to indicate + an out of memory status. */ +static char * +unescape_status_string (const unsigned char *s) +{ + return unescape_percent_string (s); +} static assuan_error_t @@ -649,6 +701,13 @@ learn_status_cb (void *opaque, const char *line) { xfree (parm->serialno); parm->serialno = store_serialno (line); + parm->is_v2 = (strlen (parm->serialno) >= 16 + && xtoi_2 (parm->serialno+12) >= 2 ); + } + else if (keywordlen == 7 && !memcmp (keyword, "APPTYPE", keywordlen)) + { + xfree (parm->apptype); + parm->apptype = unescape_status_string (line); } else if (keywordlen == 9 && !memcmp (keyword, "DISP-NAME", keywordlen)) { @@ -761,6 +820,18 @@ learn_status_cb (void *opaque, const char *line) xfree (parm->private_do[no]); parm->private_do[no] = unescape_percent_string (line); } + else if (keywordlen == 8 && !memcmp (keyword, "KEY-ATTR", keywordlen)) + { + int keyno, algo, nbits; + + sscanf (line, "%d %d %d", &keyno, &algo, &nbits); + keyno--; + if (keyno >= 0 && keyno < DIM (parm->key_attr)) + { + parm->key_attr[keyno].algo = algo; + parm->key_attr[keyno].nbits = nbits; + } + } return 0; } @@ -801,7 +872,7 @@ agent_learn (struct agent_card_info_s *info) send_status_info (&ctrl, "SERIALNO", serial, strlen(serial), NULL, 0); xfree (serial); - rc = app->fnc.learn_status (app, &ctrl); + rc = app->fnc.learn_status (app, &ctrl, 0); } } @@ -1132,7 +1203,7 @@ genkey_status_cb (void *opaque, const char *line) /* Send a GENKEY command to the SCdaemon. */ int agent_scd_genkey (struct agent_card_genkey_s *info, int keyno, int force, - const char *serialno) + const char *serialno, u32 *createtime) { app_t app; char line[ASSUAN_LINELENGTH]; @@ -1166,6 +1237,7 @@ agent_scd_genkey (struct agent_card_genkey_s *info, int keyno, int force, ctrl.status_cb_arg = info; rc = app->fnc.genkey (app, &ctrl, line, force? 1:0, + *createtime, pin_cb, &parm); } diff --git a/g10/cardglue.h b/g10/cardglue.h index e6a11c3f6..f1b51c88b 100644 --- a/g10/cardglue.h +++ b/g10/cardglue.h @@ -21,15 +21,21 @@ #ifdef ENABLE_CARD_SUPPORT /* - Note, that most card related code has been taken from 1.9.x branch + Note, that most card related code has been taken from 2.x branch and is maintained over there if at all possible. Thus, if you make changes here, please check that a similar change has been commited - to the 1.9.x branch. + to the 2.x branch. */ +/* We don't use libgcrypt but the shared codes uses a function type + from libgcrypt. Thus we have to provide this type here. */ +typedef void (*gcry_handler_progress_t) (void *, const char *, int, int, int); + +/* Object to hold all info about the card. */ struct agent_card_info_s { int error; /* private. */ + char *apptype; /* Malloced application type string. */ char *serialno; /* malloced hex string. */ char *disp_name; /* malloced. */ char *disp_lang; /* malloced. */ @@ -56,8 +62,13 @@ struct agent_card_info_s { int chv1_cached; /* True if a PIN is not required for each signing. Note that the gpg-agent might cache it anyway. */ + int is_v2; /* True if this is a v2 card. */ int chvmaxlen[3]; /* Maximum allowed length of a CHV. */ int chvretry[3]; /* Allowed retries for the CHV; 0 = blocked. */ + struct { /* Array with key attributes. */ + int algo; /* Algorithm identifier. */ + unsigned int nbits; /* Supported keysize. */ + } key_attr[3]; }; struct agent_card_genkey_s { @@ -147,14 +158,24 @@ void card_set_reader_port (const char *portstr); char *serialno_and_fpr_from_sk (const unsigned char *sn, size_t snlen, PKT_secret_key *sk); void send_status_info (ctrl_t ctrl, const char *keyword, ...); +void send_status_direct (ctrl_t ctrl, const char *keyword, const char *args); void gcry_md_hash_buffer (int algo, void *digest, const void *buffer, size_t length); +const char *gcry_md_algo_name (int algorithm); void log_printf (const char *fmt, ...); void log_printhex (const char *text, const void *buffer, size_t length); #define GCRY_MD_SHA1 DIGEST_ALGO_SHA1 #define GCRY_MD_RMD160 DIGEST_ALGO_RMD160 +#define GCRY_MD_SHA256 DIGEST_ALGO_SHA256 +#define GCRY_MD_SHA384 DIGEST_ALGO_SHA384 +#define GCRY_MD_SHA512 DIGEST_ALGO_SHA512 +#define GCRY_MD_SHA224 DIGEST_ALGO_SHA224 + +void gcry_mpi_release (MPI a); +MPI gcry_mpi_set_opaque (MPI a, void *p, unsigned int len); + void card_close (void); @@ -183,7 +204,7 @@ int agent_scd_writekey (int keyno, const char *serialno, /* Send a GENKEY command to the SCdaemon. */ int agent_scd_genkey (struct agent_card_genkey_s *info, int keyno, int force, - const char *serialno); + const char *serialno, u32 *createtime); /* Send a PKSIGN command to the SCdaemon. */ int agent_scd_pksign (const char *keyid, int hashalgo, diff --git a/g10/ccid-driver.c b/g10/ccid-driver.c index 97e7bf9c6..b71c43c29 100644 --- a/g10/ccid-driver.c +++ b/g10/ccid-driver.c @@ -1,6 +1,7 @@ /* ccid-driver.c - USB ChipCardInterfaceDevices driver - * Copyright (C) 2003, 2004, 2005, 2006 Free Software Foundation, Inc. - * Written by Werner Koch. + * Copyright (C) 2003, 2004, 2005, 2006, 2007 + * 2008, 2009 Free Software Foundation, Inc. + * Written by Werner Koch. * * This file is part of GnuPG. * @@ -83,6 +84,10 @@ #include #include #include +#include +#ifdef HAVE_PTH +# include +#endif /*HAVE_PTH*/ #include @@ -158,6 +163,11 @@ #endif /* This source not used by scdaemon. */ +#ifndef EAGAIN +#define EAGAIN EWOULDBLOCK +#endif + + enum { RDR_to_PC_NotifySlotChange= 0x50, @@ -198,7 +208,8 @@ enum { VENDOR_CHERRY = 0x046a, VENDOR_SCM = 0x04e6, VENDOR_OMNIKEY= 0x076b, - VENDOR_GEMPC = 0x08e6 + VENDOR_GEMPC = 0x08e6, + VENDOR_KAAN = 0x0d46 }; /* A list and a table with special transport descriptions. */ @@ -237,13 +248,24 @@ struct ccid_driver_s int seqno; unsigned char t1_ns; unsigned char t1_nr; - int nonnull_nad; - int auto_ifsd; + unsigned char nonnull_nad; int max_ifsd; int ifsd; - int powered_off; - int has_pinpad; - int apdu_level; /* Reader supports short APDU level exchange. */ + int ifsc; + unsigned char apdu_level:2; /* Reader supports short APDU level + exchange. With a value of 2 short + and extended level is supported.*/ + unsigned int auto_ifsd:1; + unsigned int powered_off:1; + unsigned int has_pinpad:2; + unsigned int enodev_seen:1; + + time_t last_progress; /* Last time we sent progress line. */ + + /* The progress callback and its first arg as supplied to + ccid_set_progress_cb. */ + void (*progress_cb)(void *, const char *, int, int, int); + void *progress_cb_arg; }; @@ -251,16 +273,19 @@ static int initialized_usb; /* Tracks whether USB has been initialized. */ static int debug_level; /* Flag to control the debug output. 0 = No debugging 1 = USB I/O info - 2 = T=1 protocol tracing + 2 = Level 1 + T=1 protocol tracing + 3 = Level 2 + USB/I/O tracing of SlotStatus. */ 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_out (ccid_driver_t handle, unsigned char *msg, size_t msglen, + int no_debug); static int bulk_in (ccid_driver_t handle, unsigned char *buffer, size_t length, size_t *nread, int expected_type, int seqno, int timeout, int no_debug); +static int abort_cmd (ccid_driver_t handle, int seqno); /* Convert a little endian stored 4 byte value into an unsigned integer. */ @@ -270,6 +295,15 @@ convert_le_u32 (const unsigned char *buf) return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24); } + +/* Convert a little endian stored 2 byte value into an unsigned + integer. */ +static unsigned int +convert_le_u16 (const unsigned char *buf) +{ + return buf[0] | (buf[1] << 8); +} + static void set_msg_len (unsigned char *msg, unsigned int length) { @@ -280,8 +314,49 @@ set_msg_len (unsigned char *msg, unsigned int length) } +static void +my_sleep (int seconds) +{ +#ifdef HAVE_PTH + /* With Pth we also call the standard sleep(0) so that the process + may give up its timeslot. */ + if (!seconds) + { +# ifdef HAVE_W32_SYSTEM + Sleep (0); +# else + sleep (0); +# endif + } + pth_sleep (seconds); +#else +# ifdef HAVE_W32_SYSTEM + Sleep (seconds*1000); +# else + sleep (seconds); +# endif +#endif +} + +static void +print_progress (ccid_driver_t handle) +{ + time_t ct = time (NULL); + + /* We don't want to print progress lines too often. */ + if (ct == handle->last_progress) + return; + + if (handle->progress_cb) + handle->progress_cb (handle->progress_cb_arg, "card_busy", 'w', 0, 0); + + handle->last_progress = ct; +} + + + /* Pint an error message for a failed CCID command including a textual - error code. MSG is shall be the CCID message of at least 10 bytes. */ + error code. MSG shall be the CCID message at a minimum of 10 bytes. */ static void print_command_failed (const unsigned char *msg) { @@ -325,8 +400,318 @@ print_command_failed (const unsigned char *msg) } DEBUGOUT_1 ("CCID command failed: %s\n", t); } + + +static void +print_pr_data (const unsigned char *data, size_t datalen, size_t off) +{ + int any = 0; + + for (; off < datalen; off++) + { + if (!any || !(off % 16)) + { + if (any) + DEBUGOUT_LF (); + DEBUGOUT_1 (" [%04d] ", off); + } + DEBUGOUT_CONT_1 (" %02X", data[off]); + any = 1; + } + if (any && (off % 16)) + DEBUGOUT_LF (); +} + + +static void +print_p2r_header (const char *name, const unsigned char *msg, size_t msglen) +{ + DEBUGOUT_1 ("%s:\n", name); + if (msglen < 7) + return; + DEBUGOUT_1 (" dwLength ..........: %u\n", convert_le_u32 (msg+1)); + DEBUGOUT_1 (" bSlot .............: %u\n", msg[5]); + DEBUGOUT_1 (" bSeq ..............: %u\n", msg[6]); +} + + +static void +print_p2r_iccpoweron (const unsigned char *msg, size_t msglen) +{ + print_p2r_header ("PC_to_RDR_IccPowerOn", msg, msglen); + if (msglen < 10) + return; + DEBUGOUT_2 (" bPowerSelect ......: 0x%02x (%s)\n", msg[7], + msg[7] == 0? "auto": + msg[7] == 1? "5.0 V": + msg[7] == 2? "3.0 V": + msg[7] == 3? "1.8 V":""); + print_pr_data (msg, msglen, 8); +} + + +static void +print_p2r_iccpoweroff (const unsigned char *msg, size_t msglen) +{ + print_p2r_header ("PC_to_RDR_IccPowerOff", msg, msglen); + print_pr_data (msg, msglen, 7); +} + + +static void +print_p2r_getslotstatus (const unsigned char *msg, size_t msglen) +{ + print_p2r_header ("PC_to_RDR_GetSlotStatus", msg, msglen); + print_pr_data (msg, msglen, 7); +} + + +static void +print_p2r_xfrblock (const unsigned char *msg, size_t msglen) +{ + unsigned int val; + + print_p2r_header ("PC_to_RDR_XfrBlock", msg, msglen); + if (msglen < 10) + return; + DEBUGOUT_1 (" bBWI ..............: 0x%02x\n", msg[7]); + val = convert_le_u16 (msg+8); + DEBUGOUT_2 (" wLevelParameter ...: 0x%04x%s\n", val, + val == 1? " (continued)": + val == 2? " (continues+ends)": + val == 3? " (continues+continued)": + val == 16? " (DataBlock-expected)":""); + print_pr_data (msg, msglen, 10); +} + + +static void +print_p2r_getparameters (const unsigned char *msg, size_t msglen) +{ + print_p2r_header ("PC_to_RDR_GetParameters", msg, msglen); + print_pr_data (msg, msglen, 7); +} + + +static void +print_p2r_resetparameters (const unsigned char *msg, size_t msglen) +{ + print_p2r_header ("PC_to_RDR_ResetParameters", msg, msglen); + print_pr_data (msg, msglen, 7); +} + + +static void +print_p2r_setparameters (const unsigned char *msg, size_t msglen) +{ + print_p2r_header ("PC_to_RDR_SetParameters", msg, msglen); + if (msglen < 10) + return; + DEBUGOUT_1 (" bProtocolNum ......: 0x%02x\n", msg[7]); + print_pr_data (msg, msglen, 8); +} + + +static void +print_p2r_escape (const unsigned char *msg, size_t msglen) +{ + print_p2r_header ("PC_to_RDR_Escape", msg, msglen); + print_pr_data (msg, msglen, 7); +} + + +static void +print_p2r_iccclock (const unsigned char *msg, size_t msglen) +{ + print_p2r_header ("PC_to_RDR_IccClock", msg, msglen); + if (msglen < 10) + return; + DEBUGOUT_1 (" bClockCommand .....: 0x%02x\n", msg[7]); + print_pr_data (msg, msglen, 8); +} + + +static void +print_p2r_to0apdu (const unsigned char *msg, size_t msglen) +{ + print_p2r_header ("PC_to_RDR_T0APDU", msg, msglen); + if (msglen < 10) + return; + DEBUGOUT_1 (" bmChanges .........: 0x%02x\n", msg[7]); + DEBUGOUT_1 (" bClassGetResponse .: 0x%02x\n", msg[8]); + DEBUGOUT_1 (" bClassEnvelope ....: 0x%02x\n", msg[9]); + print_pr_data (msg, msglen, 10); +} + + +static void +print_p2r_secure (const unsigned char *msg, size_t msglen) +{ + unsigned int val; + + print_p2r_header ("PC_to_RDR_Secure", msg, msglen); + if (msglen < 10) + return; + DEBUGOUT_1 (" bBMI ..............: 0x%02x\n", msg[7]); + val = convert_le_u16 (msg+8); + DEBUGOUT_2 (" wLevelParameter ...: 0x%04x%s\n", val, + val == 1? " (continued)": + val == 2? " (continues+ends)": + val == 3? " (continues+continued)": + val == 16? " (DataBlock-expected)":""); + print_pr_data (msg, msglen, 10); +} + + +static void +print_p2r_mechanical (const unsigned char *msg, size_t msglen) +{ + print_p2r_header ("PC_to_RDR_Mechanical", msg, msglen); + if (msglen < 10) + return; + DEBUGOUT_1 (" bFunction .........: 0x%02x\n", msg[7]); + print_pr_data (msg, msglen, 8); +} + + +static void +print_p2r_abort (const unsigned char *msg, size_t msglen) +{ + print_p2r_header ("PC_to_RDR_Abort", msg, msglen); + print_pr_data (msg, msglen, 7); +} + + +static void +print_p2r_setdatarate (const unsigned char *msg, size_t msglen) +{ + print_p2r_header ("PC_to_RDR_SetDataRate", msg, msglen); + if (msglen < 10) + return; + print_pr_data (msg, msglen, 7); +} + + +static void +print_p2r_unknown (const unsigned char *msg, size_t msglen) +{ + print_p2r_header ("Unknown PC_to_RDR command", msg, msglen); + if (msglen < 10) + return; + print_pr_data (msg, msglen, 0); +} + + +static void +print_r2p_header (const char *name, const unsigned char *msg, size_t msglen) +{ + DEBUGOUT_1 ("%s:\n", name); + if (msglen < 9) + return; + DEBUGOUT_1 (" dwLength ..........: %u\n", convert_le_u32 (msg+1)); + DEBUGOUT_1 (" bSlot .............: %u\n", msg[5]); + DEBUGOUT_1 (" bSeq ..............: %u\n", msg[6]); + DEBUGOUT_1 (" bStatus ...........: %u\n", msg[7]); + if (msg[8]) + DEBUGOUT_1 (" bError ............: %u\n", msg[8]); +} + + +static void +print_r2p_datablock (const unsigned char *msg, size_t msglen) +{ + print_r2p_header ("RDR_to_PC_DataBlock", msg, msglen); + if (msglen < 10) + return; + if (msg[9]) + DEBUGOUT_2 (" bChainParameter ...: 0x%02x%s\n", msg[9], + msg[9] == 1? " (continued)": + msg[9] == 2? " (continues+ends)": + msg[9] == 3? " (continues+continued)": + msg[9] == 16? " (XferBlock-expected)":""); + print_pr_data (msg, msglen, 10); +} + + +static void +print_r2p_slotstatus (const unsigned char *msg, size_t msglen) +{ + print_r2p_header ("RDR_to_PC_SlotStatus", msg, msglen); + if (msglen < 10) + return; + DEBUGOUT_2 (" bClockStatus ......: 0x%02x%s\n", msg[9], + msg[9] == 0? " (running)": + msg[9] == 1? " (stopped-L)": + msg[9] == 2? " (stopped-H)": + msg[9] == 3? " (stopped)":""); + print_pr_data (msg, msglen, 10); +} +static void +print_r2p_parameters (const unsigned char *msg, size_t msglen) +{ + print_r2p_header ("RDR_to_PC_Parameters", msg, msglen); + if (msglen < 10) + return; + + DEBUGOUT_1 (" protocol ..........: T=%d\n", msg[9]); + if (msglen == 17 && msg[9] == 1) + { + /* Protocol T=1. */ + DEBUGOUT_1 (" bmFindexDindex ....: %02X\n", msg[10]); + DEBUGOUT_1 (" bmTCCKST1 .........: %02X\n", msg[11]); + DEBUGOUT_1 (" bGuardTimeT1 ......: %02X\n", msg[12]); + DEBUGOUT_1 (" bmWaitingIntegersT1: %02X\n", msg[13]); + DEBUGOUT_1 (" bClockStop ........: %02X\n", msg[14]); + DEBUGOUT_1 (" bIFSC .............: %d\n", msg[15]); + DEBUGOUT_1 (" bNadValue .........: %d\n", msg[16]); + } + else + print_pr_data (msg, msglen, 10); +} + + +static void +print_r2p_escape (const unsigned char *msg, size_t msglen) +{ + print_r2p_header ("RDR_to_PC_Escape", msg, msglen); + if (msglen < 10) + return; + DEBUGOUT_1 (" buffer[9] .........: %02X\n", msg[9]); + print_pr_data (msg, msglen, 10); +} + + +static void +print_r2p_datarate (const unsigned char *msg, size_t msglen) +{ + print_r2p_header ("RDR_to_PC_DataRate", msg, msglen); + if (msglen < 10) + return; + if (msglen >= 18) + { + DEBUGOUT_1 (" dwClockFrequency ..: %u\n", convert_le_u32 (msg+10)); + DEBUGOUT_1 (" dwDataRate ..... ..: %u\n", convert_le_u32 (msg+14)); + print_pr_data (msg, msglen, 18); + } + else + print_pr_data (msg, msglen, 10); +} + + +static void +print_r2p_unknown (const unsigned char *msg, size_t msglen) +{ + print_r2p_header ("Unknown RDR_to_PC command", msg, msglen); + if (msglen < 10) + return; + DEBUGOUT_1 (" bMessageType ......: %02X\n", msg[0]); + DEBUGOUT_1 (" buffer[9] .........: %02X\n", msg[9]); + print_pr_data (msg, msglen, 10); +} + + /* Given a handle used for special transport prepare it for use. In particular setup all information in way that resembles what parse_cccid_descriptor does. */ @@ -492,7 +877,7 @@ parse_ccid_descriptor (ccid_driver_t handle, else if ((us & 0x00040000)) { DEBUGOUT (" Short and extended APDU level exchange\n"); - handle->apdu_level = 1; + handle->apdu_level = 2; } else if ((us & 0x00070000)) DEBUGOUT (" WARNING: conflicting exchange levels\n"); @@ -986,13 +1371,24 @@ scan_or_find_devices (int readerno, const char *readerid, char *rid, *p; fd = open (transports[i].name, O_RDWR); - if (fd == -1) - continue; + if (fd == -1 && scan_mode && errno == EBUSY) + { + /* Ignore this error in scan mode because it indicates that + the device exists but is already open (most likely by us) + and thus in general suitable as a reader. */ + } + else if (fd == -1) + { + DEBUGOUT_2 ("failed to open `%s': %s\n", + transports[i].name, strerror (errno)); + continue; + } rid = malloc (strlen (transports[i].name) + 30 + 10); if (!rid) { - close (fd); + if (fd != -1) + close (fd); free (rid_list); return -1; /* Error. */ } @@ -1003,7 +1399,8 @@ scan_or_find_devices (int readerno, const char *readerid, p = malloc ((rid_list? strlen (rid_list):0) + 1 + strlen (rid) + 1); if (!p) { - close (fd); + if (fd != -1) + close (fd); free (rid_list); free (rid); return -1; /* Error. */ @@ -1039,7 +1436,8 @@ scan_or_find_devices (int readerno, const char *readerid, --readerno; } free (rid); - close (fd); + if (fd != -1) + close (fd); } if (scan_mode) @@ -1055,7 +1453,11 @@ scan_or_find_devices (int readerno, const char *readerid, /* Set the level of debugging to LEVEL and return the old level. -1 just returns the old level. A level of 0 disables debugging, 1 enables debugging, 2 enables additional tracing of the T=1 - protocol, other values are not yet defined. */ + protocol, 3 additionally enables debugging for GetSlotStatus, other + values are not yet defined. + + Note that libusb may provide its own debugging feature which is + enabled by setting the envvar USB_DEBUG. */ int ccid_set_debug_level (int level) { @@ -1228,7 +1630,7 @@ do_close_reader (ccid_driver_t handle) set_msg_len (msg, 0); msglen = 10; - rc = bulk_out (handle, msg, msglen); + rc = bulk_out (handle, msg, msglen, 0); if (!rc) bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_SlotStatus, seqno, 2000, 0); @@ -1321,6 +1723,20 @@ ccid_shutdown_reader (ccid_driver_t handle) } +int +ccid_set_progress_cb (ccid_driver_t handle, + void (*cb)(void *, const char *, int, int, int), + void *cb_arg) +{ + if (!handle || !handle->rid) + return CCID_DRIVER_ERR_INV_VALUE; + + handle->progress_cb = cb; + handle->progress_cb_arg = cb_arg; + return 0; +} + + /* Close the reader HANDLE. */ int ccid_close_reader (ccid_driver_t handle) @@ -1339,7 +1755,7 @@ ccid_close_reader (ccid_driver_t handle) int ccid_check_card_presence (ccid_driver_t handle) { - + (void)handle; /* Not yet implemented. */ return -1; } @@ -1372,20 +1788,97 @@ writen (int fd, const void *buf, size_t nbytes) /* Write a MSG of length MSGLEN to the designated bulk out endpoint. Returns 0 on success. */ static int -bulk_out (ccid_driver_t handle, unsigned char *msg, size_t msglen) +bulk_out (ccid_driver_t handle, unsigned char *msg, size_t msglen, + int no_debug) { int rc; + /* No need to continue and clutter the log with USB write error + messages after we got the first ENODEV. */ + if (handle->enodev_seen) + return CCID_DRIVER_ERR_NO_READER; + + if (debug_level && (!no_debug || debug_level >= 3)) + { + switch (msglen? msg[0]:0) + { + case PC_to_RDR_IccPowerOn: + print_p2r_iccpoweron (msg, msglen); + break; + case PC_to_RDR_IccPowerOff: + print_p2r_iccpoweroff (msg, msglen); + break; + case PC_to_RDR_GetSlotStatus: + print_p2r_getslotstatus (msg, msglen); + break; + case PC_to_RDR_XfrBlock: + print_p2r_xfrblock (msg, msglen); + break; + case PC_to_RDR_GetParameters: + print_p2r_getparameters (msg, msglen); + break; + case PC_to_RDR_ResetParameters: + print_p2r_resetparameters (msg, msglen); + break; + case PC_to_RDR_SetParameters: + print_p2r_setparameters (msg, msglen); + break; + case PC_to_RDR_Escape: + print_p2r_escape (msg, msglen); + break; + case PC_to_RDR_IccClock: + print_p2r_iccclock (msg, msglen); + break; + case PC_to_RDR_T0APDU: + print_p2r_to0apdu (msg, msglen); + break; + case PC_to_RDR_Secure: + print_p2r_secure (msg, msglen); + break; + case PC_to_RDR_Mechanical: + print_p2r_mechanical (msg, msglen); + break; + case PC_to_RDR_Abort: + print_p2r_abort (msg, msglen); + break; + case PC_to_RDR_SetDataRate: + print_p2r_setdatarate (msg, msglen); + break; + default: + print_p2r_unknown (msg, msglen); + break; + } + } + if (handle->idev) { rc = usb_bulk_write (handle->idev, handle->ep_bulk_out, (char*)msg, msglen, - 1000 /* ms timeout */); + 5000 /* ms timeout */); if (rc == msglen) return 0; +#ifdef ENODEV + if (rc == -(ENODEV)) + { + /* The Linux libusb returns a negative error value. Catch + the most important one. */ + errno = ENODEV; + rc = -1; + } +#endif /*ENODEV*/ + if (rc == -1) - DEBUGOUT_1 ("usb_bulk_write error: %s\n", strerror (errno)); + { + DEBUGOUT_1 ("usb_bulk_write error: %s\n", strerror (errno)); +#ifdef ENODEV + if (errno == ENODEV) + { + handle->enodev_seen = 1; + return CCID_DRIVER_ERR_NO_READER; + } +#endif /*ENODEV*/ + } else DEBUGOUT_1 ("usb_bulk_write failed: %d\n", rc); } @@ -1407,14 +1900,16 @@ bulk_out (ccid_driver_t handle, unsigned char *msg, size_t msglen) is the sequence number used to send the request and EXPECTED_TYPE the type of message we expect. Does checks on the ccid header. TIMEOUT is the timeout value in ms. NO_DEBUG may be set to - avoid debug messages in case of no error. Returns 0 on success. */ + avoid debug messages in case of no error; this can be overriden + with a glibal debug level of at least 3. Returns 0 on success. */ static int bulk_in (ccid_driver_t handle, unsigned char *buffer, size_t length, size_t *nread, int expected_type, int seqno, int timeout, int no_debug) { - int i, rc; + int rc; size_t msglen; + int eagain_retries = 0; /* Fixme: The next line for the current Valgrind without support for USB IOCTLs. */ @@ -1428,7 +1923,13 @@ bulk_in (ccid_driver_t handle, unsigned char *buffer, size_t length, timeout); if (rc < 0) { - DEBUGOUT_1 ("usb_bulk_read error: %s\n", strerror (errno)); + rc = errno; + DEBUGOUT_1 ("usb_bulk_read error: %s\n", strerror (rc)); + if (rc == EAGAIN && eagain_retries++ < 3) + { + my_sleep (1); + goto retry; + } return CCID_DRIVER_ERR_CARD_IO_ERROR; } *nread = msglen = rc; @@ -1438,22 +1939,24 @@ bulk_in (ccid_driver_t handle, unsigned char *buffer, size_t length, rc = read (handle->dev_fd, buffer, length); if (rc < 0) { + rc = errno; DEBUGOUT_2 ("read from %d failed: %s\n", - handle->dev_fd, strerror (errno)); + handle->dev_fd, strerror (rc)); + if (rc == EAGAIN && eagain_retries++ < 5) + { + my_sleep (1); + goto retry; + } return CCID_DRIVER_ERR_CARD_IO_ERROR; } *nread = msglen = rc; } - + eagain_retries = 0; if (msglen < 10) { DEBUGOUT_1 ("bulk-in msg too short (%u)\n", (unsigned int)msglen); - return CCID_DRIVER_ERR_INV_VALUE; - } - if (buffer[0] != expected_type) - { - DEBUGOUT_1 ("unexpected bulk-in msg type (%02x)\n", buffer[0]); + abort_cmd (handle, seqno); return CCID_DRIVER_ERR_INV_VALUE; } if (buffer[5] != 0) @@ -1465,9 +1968,14 @@ bulk_in (ccid_driver_t handle, unsigned char *buffer, size_t length, { DEBUGOUT_2 ("bulk-in seqno does not match (%d/%d)\n", seqno, buffer[6]); - return CCID_DRIVER_ERR_INV_VALUE; + /* Retry until we are synced again. */ + goto retry; } + /* We need to handle the time extension request before we check that + we got the expected message type. This is in particular required + for the Cherry keyboard which sends a time extension request for + each key hit. */ if ( !(buffer[7] & 0x03) && (buffer[7] & 0xC0) == 0x80) { /* Card present and active, time extension requested. */ @@ -1476,13 +1984,36 @@ bulk_in (ccid_driver_t handle, unsigned char *buffer, size_t length, goto retry; } - if (!no_debug) + if (buffer[0] != expected_type) { - DEBUGOUT_3 ("status: %02X error: %02X octet[9]: %02X\n" - " data:", buffer[7], buffer[8], buffer[9] ); - for (i=10; i < msglen; i++) - DEBUGOUT_CONT_1 (" %02X", buffer[i]); - DEBUGOUT_LF (); + DEBUGOUT_1 ("unexpected bulk-in msg type (%02x)\n", buffer[0]); + abort_cmd (handle, seqno); + return CCID_DRIVER_ERR_INV_VALUE; + } + + if (debug_level && (!no_debug || debug_level >= 3)) + { + switch (buffer[0]) + { + case RDR_to_PC_DataBlock: + print_r2p_datablock (buffer, msglen); + break; + case RDR_to_PC_SlotStatus: + print_r2p_slotstatus (buffer, msglen); + break; + case RDR_to_PC_Parameters: + print_r2p_parameters (buffer, msglen); + break; + case RDR_to_PC_Escape: + print_r2p_escape (buffer, msglen); + break; + case RDR_to_PC_DataRate: + print_r2p_datarate (buffer, msglen); + break; + default: + print_r2p_unknown (buffer, msglen); + break; + } } if (CCID_COMMAND_FAILED (buffer)) print_command_failed (buffer); @@ -1501,6 +2032,110 @@ bulk_in (ccid_driver_t handle, unsigned char *buffer, size_t length, } + +/* Send an abort sequence and wait until everything settled. */ +static int +abort_cmd (ccid_driver_t handle, int seqno) +{ + int rc; + char dummybuf[8]; + unsigned char msg[100]; + size_t msglen; + + if (!handle->idev) + { + /* I don't know how to send an abort to non-USB devices. */ + rc = CCID_DRIVER_ERR_NOT_SUPPORTED; + } + + seqno &= 0xff; + DEBUGOUT_1 ("sending abort sequence for seqno %d\n", seqno); + /* Send the abort command to the control pipe. Note that we don't + need to keep track of sent abort commands because there should + never be another thread using the same slot concurrently. */ + rc = usb_control_msg (handle->idev, + 0x21,/* bmRequestType: host-to-device, + class specific, to interface. */ + 1, /* ABORT */ + (seqno << 8 | 0 /* slot */), + handle->ifc_no, + dummybuf, 0, + 1000 /* ms timeout */); + if (rc < 0) + { + DEBUGOUT_1 ("usb_control_msg error: %s\n", strerror (errno)); + return CCID_DRIVER_ERR_CARD_IO_ERROR; + } + + /* Now send the abort command to the bulk out pipe using the same + SEQNO and SLOT. Do this in a loop to so that all seqno are + tried. */ + seqno--; /* Adjust for next increment. */ + do + { + seqno++; + msg[0] = PC_to_RDR_Abort; + msg[5] = 0; /* slot */ + msg[6] = seqno; + msg[7] = 0; /* RFU */ + msg[8] = 0; /* RFU */ + msg[9] = 0; /* RFU */ + msglen = 10; + set_msg_len (msg, 0); + + rc = usb_bulk_write (handle->idev, + handle->ep_bulk_out, + (char*)msg, msglen, + 5000 /* ms timeout */); + if (rc == msglen) + rc = 0; + else if (rc == -1) + DEBUGOUT_1 ("usb_bulk_write error in abort_cmd: %s\n", + strerror (errno)); + else + DEBUGOUT_1 ("usb_bulk_write failed in abort_cmd: %d\n", rc); + + if (rc) + return rc; + + rc = usb_bulk_read (handle->idev, + handle->ep_bulk_in, + (char*)msg, sizeof msg, + 5000 /*ms timeout*/); + if (rc < 0) + { + DEBUGOUT_1 ("usb_bulk_read error in abort_cmd: %s\n", + strerror (errno)); + return CCID_DRIVER_ERR_CARD_IO_ERROR; + } + msglen = rc; + + if (msglen < 10) + { + DEBUGOUT_1 ("bulk-in msg in abort_cmd too short (%u)\n", + (unsigned int)msglen); + return CCID_DRIVER_ERR_INV_VALUE; + } + if (msg[5] != 0) + { + DEBUGOUT_1 ("unexpected bulk-in slot (%d) in abort_cmd\n", msg[5]); + return CCID_DRIVER_ERR_INV_VALUE; + } + + DEBUGOUT_3 ("status: %02X error: %02X octet[9]: %02X\n", + msg[7], msg[8], msg[9]); + if (CCID_COMMAND_FAILED (msg)) + print_command_failed (msg); + } + while (msg[0] != RDR_to_PC_SlotStatus && msg[5] != 0 && msg[6] != seqno); + + handle->seqno = ((seqno + 1) & 0xff); + DEBUGOUT ("sending abort sequence succeeded\n"); + + return 0; +} + + /* Note that this function won't return the error codes NO_CARD or CARD_INACTIVE. IF RESULT is not NULL, the result from the operation will get returned in RESULT and its length in RESULTLEN. @@ -1511,7 +2146,7 @@ send_escape_cmd (ccid_driver_t handle, const unsigned char *data, size_t datalen, unsigned char *result, size_t resultmax, size_t *resultlen) { - int i, rc; + int rc; unsigned char msg[100]; size_t msglen; unsigned char seqno; @@ -1532,11 +2167,7 @@ send_escape_cmd (ccid_driver_t handle, msglen = 10 + datalen; set_msg_len (msg, datalen); - DEBUGOUT ("sending"); - for (i=0; i < msglen; i++) - DEBUGOUT_CONT_1 (" %02X", msg[i]); - DEBUGOUT_LF (); - rc = bulk_out (handle, msg, msglen); + rc = bulk_out (handle, msg, msglen, 0); if (rc) return rc; rc = bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_Escape, @@ -1637,7 +2268,7 @@ ccid_poll (ccid_driver_t handle) } -/* Note that this fucntion won't return the error codes NO_CARD or +/* Note that this function won't return the error codes NO_CARD or CARD_INACTIVE */ int ccid_slot_status (ccid_driver_t handle, int *statusbits) @@ -1657,7 +2288,7 @@ ccid_slot_status (ccid_driver_t handle, int *statusbits) msg[9] = 0; /* RFU */ set_msg_len (msg, 0); - rc = bulk_out (handle, msg, 10); + rc = bulk_out (handle, msg, 10, 1); if (rc) return rc; /* Note that we set the NO_DEBUG flag here, so that the logs won't @@ -1687,6 +2318,8 @@ ccid_slot_status (ccid_driver_t handle, int *statusbits) } +/* Return the ATR of the card. This is not a cached value and thus an + actual reset is done. */ int ccid_get_atr (ccid_driver_t handle, unsigned char *atr, size_t maxatrlen, size_t *atrlen) @@ -1699,7 +2332,6 @@ ccid_get_atr (ccid_driver_t handle, unsigned char seqno; int use_crc = 0; unsigned int edc; - int i; int tried_iso = 0; int got_param; @@ -1710,7 +2342,6 @@ ccid_get_atr (ccid_driver_t handle, if (statusbits == 2) return CCID_DRIVER_ERR_NO_CARD; - /* For an inactive and also for an active card, issue the PowerOn command to get the ATR. */ again: @@ -1723,7 +2354,7 @@ ccid_get_atr (ccid_driver_t handle, set_msg_len (msg, 0); msglen = 10; - rc = bulk_out (handle, msg, msglen); + rc = bulk_out (handle, msg, msglen, 0); if (rc) return rc; rc = bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_DataBlock, @@ -1768,34 +2399,14 @@ ccid_get_atr (ccid_driver_t handle, msg[9] = 0; /* RFU */ set_msg_len (msg, 0); msglen = 10; - rc = bulk_out (handle, msg, msglen); + rc = bulk_out (handle, msg, msglen, 0); if (!rc) rc = bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_Parameters, seqno, 2000, 0); if (rc) DEBUGOUT ("GetParameters failed\n"); - else - { - DEBUGOUT ("GetParametes returned"); - for (i=0; i < msglen; i++) - DEBUGOUT_CONT_1 (" %02X", msg[i]); - DEBUGOUT_LF (); - if (msglen >= 10) - { - DEBUGOUT_1 (" protocol ..........: T=%d\n", msg[9]); - if (msglen == 17 && msg[9] == 1) - { - DEBUGOUT_1 (" bmFindexDindex ....: %02X\n", msg[10]); - DEBUGOUT_1 (" bmTCCKST1 .........: %02X\n", msg[11]); - DEBUGOUT_1 (" bGuardTimeT1 ......: %02X\n", msg[12]); - DEBUGOUT_1 (" bmWaitingIntegersT1: %02X\n", msg[13]); - DEBUGOUT_1 (" bClockStop ........: %02X\n", msg[14]); - DEBUGOUT_1 (" bIFSC .............: %d\n", msg[15]); - DEBUGOUT_1 (" bNadValue .........: %d\n", msg[16]); - got_param = 1; - } - } - } + else if (msglen == 17 && msg[9] == 1) + got_param = 1; /* Setup parameters to select T=1. */ msg[0] = PC_to_RDR_SetParameters; @@ -1819,12 +2430,7 @@ ccid_get_atr (ccid_driver_t handle, 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); + rc = bulk_out (handle, msg, msglen, 0); if (rc) return rc; rc = bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_Parameters, @@ -1832,6 +2438,11 @@ ccid_get_atr (ccid_driver_t handle, if (rc) DEBUGOUT ("SetParameters failed (ignored)\n"); + if (!rc && msglen > 15 && msg[15] >= 16 && msg[15] <= 254 ) + handle->ifsc = msg[15]; + else + handle->ifsc = 128; /* Something went wrong, assume 128 bytes. */ + handle->t1_ns = 0; handle->t1_nr = 0; @@ -1859,11 +2470,6 @@ ccid_get_atr (ccid_driver_t handle, set_msg_len (msg, tpdulen); msglen = 10 + tpdulen; - DEBUGOUT ("sending"); - for (i=0; i < msglen; i++) - DEBUGOUT_CONT_1 (" %02X", msg[i]); - DEBUGOUT_LF (); - if (debug_level > 1) DEBUGOUT_3 ("T=1: put %c-block seq=%d%s\n", ((msg[11] & 0xc0) == 0x80)? 'R' : @@ -1872,7 +2478,7 @@ ccid_get_atr (ccid_driver_t handle, : !!(msg[11] & 0x40)), (!(msg[11] & 0x80) && (msg[11] & 0x20)? " [more]":"")); - rc = bulk_out (handle, msg, msglen); + rc = bulk_out (handle, msg, msglen, 0); if (rc) return rc; @@ -1929,6 +2535,16 @@ compute_edc (const unsigned char *data, size_t datalen, int use_crc) } +/* Return true if APDU is an extended length one. */ +static int +is_exlen_apdu (const unsigned char *apdu, size_t apdulen) +{ + if (apdulen < 7 || apdu[4]) + return 0; /* Too short or no Z byte. */ + return 1; +} + + /* Helper for ccid_transceive used for APDU level exchanges. */ static int ccid_transceive_apdu_level (ccid_driver_t handle, @@ -1937,13 +2553,13 @@ ccid_transceive_apdu_level (ccid_driver_t handle, size_t *nresp) { int rc; - unsigned char send_buffer[10+259], recv_buffer[10+259]; + unsigned char send_buffer[10+261+300], recv_buffer[10+261+300]; const unsigned char *apdu; size_t apdulen; unsigned char *msg; size_t msglen; unsigned char seqno; - int i; + int bwi = 4; msg = send_buffer; @@ -1951,25 +2567,23 @@ ccid_transceive_apdu_level (ccid_driver_t handle, apdulen = apdu_buflen; assert (apdulen); - if (apdulen > 254) + /* The maximum length for a short APDU T=1 block is 261. For an + extended APDU T=1 block the maximum length 65544; however + extended APDU exchange level is not yet supported. */ + if (apdulen > 261) return CCID_DRIVER_ERR_INV_VALUE; /* Invalid length. */ - + msg[0] = PC_to_RDR_XfrBlock; msg[5] = 0; /* slot */ msg[6] = seqno = handle->seqno++; - msg[7] = 4; /* bBWI */ + msg[7] = bwi; /* bBWI */ msg[8] = 0; /* RFU */ msg[9] = 0; /* RFU */ memcpy (msg+10, apdu, apdulen); set_msg_len (msg, apdulen); msglen = 10 + apdulen; - DEBUGOUT ("sending"); - for (i=0; i < msglen; i++) - DEBUGOUT_CONT_1 (" %02X", msg[i]); - DEBUGOUT_LF (); - - rc = bulk_out (handle, msg, msglen); + rc = bulk_out (handle, msg, msglen, 0); if (rc) return rc; @@ -2059,19 +2673,24 @@ ccid_transceive (ccid_driver_t handle, unsigned char *resp, size_t maxresplen, size_t *nresp) { int rc; - unsigned char send_buffer[10+259], recv_buffer[10+259]; + /* The size of the buffer used to be 10+259. For the via_escape + hack we need one extra byte, thus 11+259. */ + unsigned char send_buffer[11+259], recv_buffer[11+259]; const unsigned char *apdu; size_t apdulen; unsigned char *msg, *tpdu, *p; size_t msglen, tpdulen, last_tpdulen, n; unsigned char seqno; - int i; unsigned int edc; int use_crc = 0; + int hdrlen, pcboff; size_t dummy_nresp; + int via_escape = 0; int next_chunk = 1; int sending = 1; int retries = 0; + int resyncing = 0; + int nad_byte; if (!nresp) nresp = &dummy_nresp; @@ -2079,13 +2698,37 @@ ccid_transceive (ccid_driver_t handle, /* Smarter readers allow to send APDUs directly; divert here. */ if (handle->apdu_level) - return ccid_transceive_apdu_level (handle, apdu_buf, apdu_buflen, - resp, maxresplen, nresp); + { + /* We employ a hack for Omnikey readers which are able to send + TPDUs using an escape sequence. There is no documentation + but the Windows driver does it this way. Tested using a + CM6121. This method works also for the Cherry XX44 + keyboards; however there are problems with the + ccid_tranceive_secure which leads to a loss of sync on the + CCID level. If Cherry wants to make their keyboard work + again, they should hand over some docs. */ + if ((handle->id_vendor == VENDOR_OMNIKEY + || (!handle->idev && handle->id_product == TRANSPORT_CM4040)) + && handle->apdu_level < 2 + && is_exlen_apdu (apdu_buf, apdu_buflen)) + via_escape = 1; + else + return ccid_transceive_apdu_level (handle, apdu_buf, apdu_buflen, + resp, maxresplen, nresp); + } /* The other readers we support require sending TPDUs. */ tpdulen = 0; /* Avoid compiler warning about no initialization. */ msg = send_buffer; + hdrlen = via_escape? 11 : 10; + + /* NAD: DAD=1, SAD=0 */ + nad_byte = handle->nonnull_nad? ((1 << 4) | 0): 0; + if (via_escape) + nad_byte = 0; + + last_tpdulen = 0; /* Avoid gcc warning (controlled by RESYNCING). */ for (;;) { if (next_chunk) @@ -2097,18 +2740,14 @@ ccid_transceive (ccid_driver_t handle, assert (apdulen); /* Construct an I-Block. */ - if (apdulen > 254) - return CCID_DRIVER_ERR_INV_VALUE; /* Invalid length. */ - - tpdu = msg+10; - /* NAD: DAD=1, SAD=0 */ - tpdu[0] = handle->nonnull_nad? ((1 << 4) | 0): 0; + tpdu = msg + hdrlen; + tpdu[0] = nad_byte; tpdu[1] = ((handle->t1_ns & 1) << 6); /* I-block */ - if (apdulen > 128 /* fixme: replace by ifsc */) + if (apdulen > handle->ifsc ) { - apdulen = 128; - apdu_buf += 128; - apdu_buflen -= 128; + apdulen = handle->ifsc; + apdu_buf += handle->ifsc; + apdu_buflen -= handle->ifsc; tpdu[1] |= (1 << 5); /* Set more bit. */ } tpdu[2] = apdulen; @@ -2120,42 +2759,56 @@ ccid_transceive (ccid_driver_t handle, tpdu[tpdulen++] = edc; } - msg[0] = PC_to_RDR_XfrBlock; - msg[5] = 0; /* slot */ - msg[6] = seqno = handle->seqno++; - msg[7] = 4; /* bBWI */ - msg[8] = 0; /* RFU */ - msg[9] = 0; /* RFU */ - set_msg_len (msg, tpdulen); - msglen = 10 + tpdulen; - last_tpdulen = tpdulen; - - DEBUGOUT ("sending"); - for (i=0; i < msglen; i++) - DEBUGOUT_CONT_1 (" %02X", msg[i]); - DEBUGOUT_LF (); + if (via_escape) + { + msg[0] = PC_to_RDR_Escape; + msg[5] = 0; /* slot */ + msg[6] = seqno = handle->seqno++; + msg[7] = 0; /* RFU */ + msg[8] = 0; /* RFU */ + msg[9] = 0; /* RFU */ + msg[10] = 0x1a; /* Omnikey command to send a TPDU. */ + set_msg_len (msg, 1 + tpdulen); + } + else + { + msg[0] = PC_to_RDR_XfrBlock; + msg[5] = 0; /* slot */ + msg[6] = seqno = handle->seqno++; + msg[7] = 4; /* bBWI */ + msg[8] = 0; /* RFU */ + msg[9] = 0; /* RFU */ + set_msg_len (msg, tpdulen); + } + msglen = hdrlen + tpdulen; + if (!resyncing) + last_tpdulen = tpdulen; + pcboff = hdrlen+1; if (debug_level > 1) - DEBUGOUT_3 ("T=1: put %c-block seq=%d%s\n", - ((msg[11] & 0xc0) == 0x80)? 'R' : - (msg[11] & 0x80)? 'S' : 'I', - ((msg[11] & 0x80)? !!(msg[11]& 0x10) - : !!(msg[11] & 0x40)), - (!(msg[11] & 0x80) && (msg[11] & 0x20)? " [more]":"")); - - rc = bulk_out (handle, msg, msglen); + DEBUGOUT_3 ("T=1: put %c-block seq=%d%s\n", + ((msg[pcboff] & 0xc0) == 0x80)? 'R' : + (msg[pcboff] & 0x80)? 'S' : 'I', + ((msg[pcboff] & 0x80)? !!(msg[pcboff]& 0x10) + : !!(msg[pcboff] & 0x40)), + (!(msg[pcboff] & 0x80) && (msg[pcboff] & 0x20)? + " [more]":"")); + + rc = bulk_out (handle, msg, msglen, 0); if (rc) return rc; msg = recv_buffer; rc = bulk_in (handle, msg, sizeof recv_buffer, &msglen, - RDR_to_PC_DataBlock, seqno, 5000, 0); + via_escape? RDR_to_PC_Escape : RDR_to_PC_DataBlock, + seqno, 5000, 0); if (rc) return rc; - - tpdu = msg + 10; - tpdulen = msglen - 10; - + + tpdu = msg + hdrlen; + tpdulen = msglen - hdrlen; + resyncing = 0; + if (tpdulen < 4) { usb_clear_halt (handle->idev, handle->ep_bulk_in); @@ -2164,11 +2817,13 @@ ccid_transceive (ccid_driver_t handle, if (debug_level > 1) DEBUGOUT_4 ("T=1: got %c-block seq=%d err=%d%s\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, - (!(msg[11] & 0x80) && (msg[11] & 0x20)? " [more]":"")); + ((msg[pcboff] & 0xc0) == 0x80)? 'R' : + (msg[pcboff] & 0x80)? 'S' : 'I', + ((msg[pcboff] & 0x80)? !!(msg[pcboff]& 0x10) + : !!(msg[pcboff] & 0x40)), + ((msg[pcboff] & 0xc0) == 0x80)? (msg[pcboff] & 0x0f) : 0, + (!(msg[pcboff] & 0x80) && (msg[pcboff] & 0x20)? + " [more]":"")); if (!(tpdu[1] & 0x80)) { /* This is an I-block. */ @@ -2182,9 +2837,8 @@ ccid_transceive (ccid_driver_t handle, if (!!(tpdu[1] & 0x40) != handle->t1_nr) { /* Reponse does not match our sequence number. */ msg = send_buffer; - tpdu = msg+10; - /* NAD: DAD=1, SAD=0 */ - tpdu[0] = handle->nonnull_nad? ((1 << 4) | 0): 0; + tpdu = msg + hdrlen; + tpdu[0] = nad_byte; tpdu[1] = (0x80 | (handle->t1_nr & 1) << 4 | 2); /* R-block */ tpdu[2] = 0; tpdulen = 3; @@ -2221,9 +2875,8 @@ ccid_transceive (ccid_driver_t handle, return 0; /* No chaining requested - ready. */ msg = send_buffer; - tpdu = msg+10; - /* NAD: DAD=1, SAD=0 */ - tpdu[0] = handle->nonnull_nad? ((1 << 4) | 0): 0; + tpdu = msg + hdrlen; + tpdu[0] = nad_byte; tpdu[1] = (0x80 | (handle->t1_nr & 1) << 4); /* R-block */ tpdu[2] = 0; tpdulen = 3; @@ -2235,14 +2888,37 @@ ccid_transceive (ccid_driver_t handle, else if ((tpdu[1] & 0xc0) == 0x80) { /* This is a R-block. */ if ( (tpdu[1] & 0x0f)) - { /* Error: repeat last block */ - if (++retries > 3) + { + retries++; + if (via_escape && retries == 1 && (msg[pcboff] & 0x0f)) { - DEBUGOUT ("3 failed retries\n"); + /* Error probably due to switching to TPDU. Send a + resync request. We use the recv_buffer so that + we don't corrupt the send_buffer. */ + msg = recv_buffer; + tpdu = msg + hdrlen; + tpdu[0] = nad_byte; + tpdu[1] = 0xc0; /* S-block resync request. */ + tpdu[2] = 0; + tpdulen = 3; + edc = compute_edc (tpdu, tpdulen, use_crc); + if (use_crc) + tpdu[tpdulen++] = (edc >> 8); + tpdu[tpdulen++] = edc; + resyncing = 1; + DEBUGOUT ("T=1: requesting resync\n"); + } + else if (retries > 3) + { + DEBUGOUT ("T=1: 3 failed retries\n"); return CCID_DRIVER_ERR_CARD_IO_ERROR; } - msg = send_buffer; - tpdulen = last_tpdulen; + else + { + /* Error: repeat last block */ + msg = send_buffer; + tpdulen = last_tpdulen; + } } else if (sending && !!(tpdu[1] & 0x10) == handle->t1_ns) { /* Response does not match our sequence number. */ @@ -2265,16 +2941,37 @@ ccid_transceive (ccid_driver_t handle, else { /* This is a S-block. */ retries = 0; - DEBUGOUT_2 ("T=1 S-block %s received cmd=%d\n", + DEBUGOUT_2 ("T=1: S-block %s received cmd=%d\n", (tpdu[1] & 0x20)? "response": "request", (tpdu[1] & 0x1f)); - if ( !(tpdu[1] & 0x20) && (tpdu[1] & 0x1f) == 3 && tpdu[2]) - { /* Wait time extension request. */ + if ( !(tpdu[1] & 0x20) && (tpdu[1] & 0x1f) == 1 && tpdu[2] == 1) + { + /* Information field size request. */ + unsigned char ifsc = tpdu[3]; + + if (ifsc < 16 || ifsc > 254) + return CCID_DRIVER_ERR_CARD_IO_ERROR; + + msg = send_buffer; + tpdu = msg + hdrlen; + tpdu[0] = nad_byte; + tpdu[1] = (0xc0 | 0x20 | 1); /* S-block response */ + tpdu[2] = 1; + tpdu[3] = ifsc; + tpdulen = 4; + edc = compute_edc (tpdu, tpdulen, use_crc); + if (use_crc) + tpdu[tpdulen++] = (edc >> 8); + tpdu[tpdulen++] = edc; + DEBUGOUT_1 ("T=1: requesting an ifsc=%d\n", ifsc); + } + else if ( !(tpdu[1] & 0x20) && (tpdu[1] & 0x1f) == 3 && tpdu[2]) + { + /* Wait time extension request. */ unsigned char bwi = tpdu[3]; msg = send_buffer; - tpdu = msg+10; - /* NAD: DAD=1, SAD=0 */ - tpdu[0] = handle->nonnull_nad? ((1 << 4) | 0): 0; + tpdu = msg + hdrlen; + tpdu[0] = nad_byte; tpdu[1] = (0xc0 | 0x20 | 3); /* S-block response */ tpdu[2] = 1; tpdu[3] = bwi; @@ -2283,7 +2980,15 @@ ccid_transceive (ccid_driver_t handle, if (use_crc) tpdu[tpdulen++] = (edc >> 8); tpdu[tpdulen++] = edc; - DEBUGOUT_1 ("T=1 waittime extension of bwi=%d\n", bwi); + DEBUGOUT_1 ("T=1: waittime extension of bwi=%d\n", bwi); + print_progress (handle); + } + else if ( (tpdu[1] & 0x20) && (tpdu[1] & 0x1f) == 0 && !tpdu[2]) + { + DEBUGOUT ("T=1: resync ack from reader\n"); + /* Repeat previous block. */ + msg = send_buffer; + tpdulen = last_tpdulen; } else return CCID_DRIVER_ERR_CARD_IO_ERROR; @@ -2320,9 +3025,9 @@ ccid_transceive_secure (ccid_driver_t handle, unsigned char *msg, *tpdu, *p; size_t msglen, tpdulen, n; unsigned char seqno; - int i; size_t dummy_nresp; int testmode; + int cherry_mode = 0; testmode = !resp && !nresp; @@ -2354,10 +3059,26 @@ ccid_transceive_secure (ccid_driver_t handle, || pinlen_min > pinlen_max) return CCID_DRIVER_ERR_INV_VALUE; - /* We have only tested this with an SCM reader so better don't risk - anything and do not allow the use with other readers. */ - if (handle->id_vendor != VENDOR_SCM) - return CCID_DRIVER_ERR_NOT_SUPPORTED; + /* We have only tested a few readers so better don't risk anything + and do not allow the use with other readers. */ + switch (handle->id_vendor) + { + case VENDOR_SCM: /* Tested with SPR 532. */ + case VENDOR_KAAN: /* Tested with KAAN Advanced (1.02). */ + break; + case VENDOR_CHERRY: + /* The CHERRY XX44 keyboard echos an asterisk for each entered + character on the keyboard channel. We use a special variant + of PC_to_RDR_Secure which directs these characters to the + smart card's bulk-in channel. We also need to append a zero + Lc byte to the APDU. It seems that it will be replaced with + the actual length instead of being appended before the APDU + is send to the card. */ + cherry_mode = 1; + break; + default: + return CCID_DRIVER_ERR_NOT_SUPPORTED; + } if (testmode) return 0; /* Success */ @@ -2372,10 +3093,10 @@ ccid_transceive_secure (ccid_driver_t handle, return rc; } - msg[0] = PC_to_RDR_Secure; + msg[0] = cherry_mode? 0x89 : PC_to_RDR_Secure; msg[5] = 0; /* slot */ msg[6] = seqno = handle->seqno++; - msg[7] = 4; /* bBWI */ + msg[7] = 0; /* bBWI */ msg[8] = 0; /* RFU */ msg[9] = 0; /* RFU */ msg[10] = 0; /* Perform PIN verification. */ @@ -2384,7 +3105,7 @@ ccid_transceive_secure (ccid_driver_t handle, if (handle->id_vendor == VENDOR_SCM) { /* For the SPR532 the next 2 bytes need to be zero. We do this - for all SCM product. Kudos to Martin Paljak for this + for all SCM products. Kudos to Martin Paljak for this hint. */ msg[13] = msg[14] = 0; } @@ -2396,8 +3117,11 @@ ccid_transceive_secure (ccid_driver_t handle, msg[14] = 0x00; /* bmPINLengthFormat: Units are bytes, position is 0. */ } - msg[15] = pinlen_min; /* wPINMaxExtraDigit-Minimum. */ - msg[16] = pinlen_max; /* wPINMaxExtraDigit-Maximum. */ + + /* The following is a little endian word. */ + msg[15] = pinlen_max; /* wPINMaxExtraDigit-Maximum. */ + msg[16] = pinlen_min; /* wPINMaxExtraDigit-Minimum. */ + msg[17] = 0x02; /* bEntryValidationCondition: Validation key pressed */ if (pinlen_min && pinlen_max && pinlen_min == pinlen_max) @@ -2409,32 +3133,48 @@ ccid_transceive_secure (ccid_driver_t handle, /* bTeoProlog follows: */ msg[22] = handle->nonnull_nad? ((1 << 4) | 0): 0; msg[23] = ((handle->t1_ns & 1) << 6); /* I-block */ - msg[24] = 4; /* apdulen. */ + msg[24] = 0; /* The apdulen will be filled in by the reader. */ /* APDU follows: */ msg[25] = apdu_buf[0]; /* CLA */ msg[26] = apdu_buf[1]; /* INS */ msg[27] = apdu_buf[2]; /* P1 */ msg[28] = apdu_buf[3]; /* P2 */ msglen = 29; + if (cherry_mode) + msg[msglen++] = 0; + /* An EDC is not required. */ set_msg_len (msg, msglen - 10); - DEBUGOUT ("sending"); - for (i=0; i < msglen; i++) - DEBUGOUT_CONT_1 (" %02X", msg[i]); - DEBUGOUT_LF (); - - rc = bulk_out (handle, msg, msglen); + rc = bulk_out (handle, msg, msglen, 0); if (rc) return rc; msg = recv_buffer; rc = bulk_in (handle, msg, sizeof recv_buffer, &msglen, - RDR_to_PC_DataBlock, seqno, 5000, 0); + RDR_to_PC_DataBlock, seqno, 30000, 0); if (rc) return rc; tpdu = msg + 10; tpdulen = msglen - 10; + + if (handle->apdu_level) + { + if (resp) + { + if (tpdulen > maxresplen) + { + DEBUGOUT_2 ("provided buffer too short for received data " + "(%u/%u)\n", + (unsigned int)tpdulen, (unsigned int)maxresplen); + return CCID_DRIVER_ERR_INV_VALUE; + } + + memcpy (resp, tpdu, tpdulen); + *nresp = tpdulen; + } + return 0; + } if (tpdulen < 4) { @@ -2507,7 +3247,7 @@ ccid_transceive_secure (ccid_driver_t handle, } else { /* This is a S-block. */ - DEBUGOUT_2 ("T=1 S-block %s received cmd=%d for Secure operation\n", + DEBUGOUT_2 ("T=1: S-block %s received cmd=%d for Secure operation\n", (tpdu[1] & 0x20)? "response": "request", (tpdu[1] & 0x1f)); return CCID_DRIVER_ERR_CARD_IO_ERROR; @@ -2548,6 +3288,7 @@ print_error (int err) fprintf (stderr, "operation failed: %s\n", p); } + static void print_data (const unsigned char *data, size_t length) { @@ -2580,7 +3321,7 @@ main (int argc, char **argv) { int rc; ccid_driver_t ccid; - unsigned int slotstat; + int slotstat; unsigned char result[512]; size_t resultlen; int no_pinpad = 0; @@ -2608,7 +3349,7 @@ main (int argc, char **argv) } else if ( !strcmp (*argv, "--debug")) { - ccid_set_debug_level (1); + ccid_set_debug_level (ccid_set_debug_level (-1)+1); argc--; argv++; } else if ( !strcmp (*argv, "--no-poll")) diff --git a/g10/ccid-driver.h b/g10/ccid-driver.h index 341876b22..6bb1913e1 100644 --- a/g10/ccid-driver.h +++ b/g10/ccid-driver.h @@ -80,6 +80,9 @@ typedef struct ccid_driver_s *ccid_driver_t; int ccid_set_debug_level (int level); char *ccid_get_reader_list (void); int ccid_open_reader (ccid_driver_t *handle, const char *readerid); +int ccid_set_progress_cb (ccid_driver_t handle, + void (*cb)(void *, const char *, int, int, int), + void *cb_arg); int ccid_shutdown_reader (ccid_driver_t handle); int ccid_close_reader (ccid_driver_t handle); int ccid_get_atr (ccid_driver_t handle, diff --git a/g10/gpg.c b/g10/gpg.c index 302b0681a..0d9122ff2 100644 --- a/g10/gpg.c +++ b/g10/gpg.c @@ -1932,6 +1932,7 @@ main (int argc, char **argv ) #else opt.pcsc_driver = "libpcsclite.so"; #endif + opt.disable_keypad = 1; /* No keypad support; use gpg2 instead. */ #endif /*ENABLE_CARD_SUPPORT*/ /* check whether we have a config file on the commandline */ diff --git a/g10/iso7816.c b/g10/iso7816.c index 8c6af044f..f1ee0daef 100644 --- a/g10/iso7816.c +++ b/g10/iso7816.c @@ -1,5 +1,5 @@ /* iso7816.c - ISO 7816 commands - * Copyright (C) 2003, 2004 Free Software Foundation, Inc. + * Copyright (C) 2003, 2004, 2008, 2009 Free Software Foundation, Inc. * * This file is part of GnuPG. * @@ -45,9 +45,9 @@ #define CMD_SELECT_FILE 0xA4 -#define CMD_VERIFY 0x20 -#define CMD_CHANGE_REFERENCE_DATA 0x24 -#define CMD_RESET_RETRY_COUNTER 0x2C +#define CMD_VERIFY ISO7816_VERIFY +#define CMD_CHANGE_REFERENCE_DATA ISO7816_CHANGE_REFERENCE_DATA +#define CMD_RESET_RETRY_COUNTER ISO7816_RESET_RETRY_COUNTER #define CMD_GET_DATA 0xCA #define CMD_PUT_DATA 0xDA #define CMD_MSE 0x22 @@ -66,7 +66,10 @@ map_sw (int sw) switch (sw) { case SW_EEPROM_FAILURE: ec = GPG_ERR_HARDWARE; break; + case SW_TERM_STATE: ec = GPG_ERR_CARD; break; case SW_WRONG_LENGTH: ec = GPG_ERR_INV_VALUE; break; + case SW_SM_NOT_SUP: ec = GPG_ERR_NOT_SUPPORTED; break; + case SW_CC_NOT_SUP: ec = GPG_ERR_NOT_SUPPORTED; break; case SW_CHV_WRONG: ec = GPG_ERR_BAD_PIN; break; case SW_CHV_BLOCKED: ec = GPG_ERR_PIN_BLOCKED; break; case SW_USE_CONDITIONS: ec = GPG_ERR_USE_CONDITIONS; break; @@ -93,6 +96,7 @@ map_sw (int sw) case SW_HOST_GENERAL_ERROR: ec = GPG_ERR_GENERAL; break; case SW_HOST_NO_READER: ec = GPG_ERR_ENODEV; break; case SW_HOST_ABORTED: ec = GPG_ERR_CANCELED; break; + case SW_HOST_NO_KEYPAD: ec = GPG_ERR_NOT_SUPPORTED; break; default: if ((sw & 0x010000)) @@ -122,12 +126,15 @@ iso7816_map_sw (int sw) 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 a GPG error code. Note that ISO error codes are - internally mapped. */ + internally mapped. Bit 0 of FLAGS should be set if the card does + not understand P2=0xC0. */ gpg_error_t -iso7816_select_application (int slot, const char *aid, size_t aidlen) +iso7816_select_application (int slot, const char *aid, size_t aidlen, + unsigned int flags) { int sw; - sw = apdu_send_simple (slot, 0x00, CMD_SELECT_FILE, 4, 0, aidlen, aid); + sw = apdu_send_simple (slot, 0, 0x00, CMD_SELECT_FILE, 4, + (flags&1)? 0 :0x0c, aidlen, aid); return map_sw (sw); } @@ -152,7 +159,7 @@ iso7816_select_file (int slot, int tag, int is_dir, { p0 = (tag == 0x3F00)? 0: is_dir? 1:2; p1 = 0x0c; /* No FC return. */ - sw = apdu_send_simple (slot, 0x00, CMD_SELECT_FILE, + sw = apdu_send_simple (slot, 0, 0x00, CMD_SELECT_FILE, p0, p1, 2, (char*)tagbuf ); return map_sw (sw); } @@ -188,7 +195,7 @@ iso7816_select_path (int slot, const unsigned short *path, size_t pathlen, p0 = 0x08; p1 = 0x0c; /* No FC return. */ - sw = apdu_send_simple (slot, 0x00, CMD_SELECT_FILE, + sw = apdu_send_simple (slot, 0, 0x00, CMD_SELECT_FILE, p0, p1, buflen, (char*)buffer ); return map_sw (sw); } @@ -206,7 +213,7 @@ iso7816_list_directory (int slot, int list_dirs, *result = NULL; *resultlen = 0; - sw = apdu_send (slot, 0x80, 0xAA, list_dirs? 1:2, 0, -1, NULL, + sw = apdu_send (slot, 0, 0x80, 0xAA, list_dirs? 1:2, 0, -1, NULL, result, resultlen); if (sw != SW_SUCCESS) { @@ -219,27 +226,101 @@ iso7816_list_directory (int slot, int list_dirs, } +/* This funcion sends an already formatted APDU to the card. With + HANDLE_MORE set to true a MORE DATA status will be handled + internally. The return value is a gpg error code (i.e. a mapped + status word). This is basically the same as apdu_send_direct but + it maps the status word and does not return it in the result + buffer. */ +gpg_error_t +iso7816_apdu_direct (int slot, const void *apdudata, size_t apdudatalen, + int handle_more, + 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_direct (slot, 0, apdudata, apdudatalen, handle_more, + result, resultlen); + if (!sw) + { + if (*resultlen < 2) + sw = SW_HOST_GENERAL_ERROR; + else + { + sw = ((*result)[*resultlen-2] << 8) | (*result)[*resultlen-1]; + (*resultlen)--; + (*resultlen)--; + } + } + if (sw != SW_SUCCESS) + { + /* Make sure that pending buffers are released. */ + xfree (*result); + *result = NULL; + *resultlen = 0; + } + return map_sw (sw); +} + + +/* Check whether the reader supports the ISO command code COMMAND on + the keypad. Returns 0 on success. */ +gpg_error_t +iso7816_check_keypad (int slot, int command, iso7816_pininfo_t *pininfo) +{ + int sw; + + sw = apdu_check_keypad (slot, command, + pininfo->mode, pininfo->minlen, pininfo->maxlen, + pininfo->padlen); + return iso7816_map_sw (sw); +} + + +/* Perform a VERIFY command on SLOT using the card holder verification + vector CHVNO with a CHV of lenght CHVLEN. With PININFO non-NULL + the keypad of the reader will be used. Returns 0 on success. */ +gpg_error_t +iso7816_verify_kp (int slot, int chvno, const char *chv, size_t chvlen, + iso7816_pininfo_t *pininfo) +{ + int sw; + + if (pininfo && pininfo->mode) + sw = apdu_send_simple_kp (slot, 0x00, CMD_VERIFY, 0, chvno, chvlen, chv, + pininfo->mode, + pininfo->minlen, + pininfo->maxlen, + pininfo->padlen); + else + sw = apdu_send_simple (slot, 0, 0x00, CMD_VERIFY, 0, chvno, chvlen, chv); + return map_sw (sw); +} /* Perform a VERIFY command on SLOT using the card holder verification vector CHVNO with a CHV of lenght CHVLEN. Returns 0 on success. */ gpg_error_t iso7816_verify (int slot, int chvno, const char *chv, size_t chvlen) { - int sw; - - sw = apdu_send_simple (slot, 0x00, CMD_VERIFY, 0, chvno, chvlen, chv); - return map_sw (sw); + return iso7816_verify_kp (slot, chvno, chv, chvlen, NULL); } /* Perform a CHANGE_REFERENCE_DATA command on SLOT for the card holder verification vector CHVNO. If the OLDCHV is NULL (and OLDCHVLEN 0), a "change reference data" is done, otherwise an "exchange reference data". The new reference data is expected in NEWCHV of - length NEWCHVLEN. */ + length NEWCHVLEN. With PININFO non-NULL the keypad of the reader + will be used. */ gpg_error_t -iso7816_change_reference_data (int slot, int chvno, - const char *oldchv, size_t oldchvlen, - const char *newchv, size_t newchvlen) +iso7816_change_reference_data_kp (int slot, int chvno, + const char *oldchv, size_t oldchvlen, + const char *newchv, size_t newchvlen, + iso7816_pininfo_t *pininfo) { int sw; char *buf; @@ -256,45 +337,110 @@ iso7816_change_reference_data (int slot, int chvno, memcpy (buf, oldchv, oldchvlen); memcpy (buf+oldchvlen, newchv, newchvlen); - sw = apdu_send_simple (slot, 0x00, CMD_CHANGE_REFERENCE_DATA, - oldchvlen? 0 : 1, chvno, oldchvlen+newchvlen, buf); + if (pininfo && pininfo->mode) + sw = apdu_send_simple_kp (slot, 0x00, CMD_CHANGE_REFERENCE_DATA, + oldchvlen? 0 : 1, chvno, oldchvlen+newchvlen, buf, + pininfo->mode, + pininfo->minlen, + pininfo->maxlen, + pininfo->padlen); + else + sw = apdu_send_simple (slot, 0, 0x00, CMD_CHANGE_REFERENCE_DATA, + oldchvlen? 0 : 1, chvno, oldchvlen+newchvlen, buf); xfree (buf); return map_sw (sw); } +/* Perform a CHANGE_REFERENCE_DATA command on SLOT for the card holder + verification vector CHVNO. If the OLDCHV is NULL (and OLDCHVLEN + 0), a "change reference data" is done, otherwise an "exchange + reference data". The new reference data is expected in NEWCHV of + length NEWCHVLEN. */ gpg_error_t -iso7816_reset_retry_counter (int slot, int chvno, - const char *newchv, size_t newchvlen) +iso7816_change_reference_data (int slot, int chvno, + const char *oldchv, size_t oldchvlen, + const char *newchv, size_t newchvlen) +{ + return iso7816_change_reference_data_kp (slot, chvno, oldchv, oldchvlen, + newchv, newchvlen, NULL); +} + + +gpg_error_t +iso7816_reset_retry_counter_kp (int slot, int chvno, + const char *newchv, size_t newchvlen, + iso7816_pininfo_t *pininfo) { int sw; if (!newchv || !newchvlen ) return gpg_error (GPG_ERR_INV_VALUE); - sw = apdu_send_simple (slot, 0x00, CMD_RESET_RETRY_COUNTER, - 2, chvno, newchvlen, newchv); + /* FIXME: The keypad mode has not yet been tested. */ + if (pininfo && pininfo->mode) + sw = apdu_send_simple_kp (slot, 0x00, CMD_RESET_RETRY_COUNTER, + 2, chvno, newchvlen, newchv, + pininfo->mode, + pininfo->minlen, + pininfo->maxlen, + pininfo->padlen); + else + sw = apdu_send_simple (slot, 0, 0x00, CMD_RESET_RETRY_COUNTER, + 2, chvno, newchvlen, newchv); return map_sw (sw); } +gpg_error_t +iso7816_reset_retry_counter_with_rc (int slot, int chvno, + const char *data, size_t datalen) +{ + int sw; + + if (!data || !datalen ) + return gpg_error (GPG_ERR_INV_VALUE); + + sw = apdu_send_simple (slot, 0, 0x00, CMD_RESET_RETRY_COUNTER, + 0, chvno, datalen, data); + return map_sw (sw); +} + + +gpg_error_t +iso7816_reset_retry_counter (int slot, int chvno, + const char *newchv, size_t newchvlen) +{ + return iso7816_reset_retry_counter_kp (slot, chvno, newchv, newchvlen, NULL); +} + + + /* Perform a GET DATA command requesting TAG and storing the result in a newly allocated buffer at the address passed by RESULT. Return the length of this data at the address of RESULTLEN. */ gpg_error_t -iso7816_get_data (int slot, int tag, +iso7816_get_data (int slot, int extended_mode, int tag, unsigned char **result, size_t *resultlen) { int sw; + int le; if (!result || !resultlen) return gpg_error (GPG_ERR_INV_VALUE); *result = NULL; *resultlen = 0; - sw = apdu_send (slot, 0x00, CMD_GET_DATA, - ((tag >> 8) & 0xff), (tag & 0xff), -1, NULL, - result, resultlen); + if (extended_mode > 0 && extended_mode < 256) + le = 65534; /* Not 65535 in case it is used as some special flag. */ + else if (extended_mode > 0) + le = extended_mode; + else + le = 256; + + sw = apdu_send_le (slot, extended_mode, 0x00, CMD_GET_DATA, + ((tag >> 8) & 0xff), (tag & 0xff), -1, NULL, le, + result, resultlen); if (sw != SW_SUCCESS) { /* Make sure that pending buffers are released. */ @@ -309,14 +455,29 @@ iso7816_get_data (int slot, int tag, /* Perform a PUT DATA command on card in SLOT. Write DATA of length - DATALEN to TAG. */ + DATALEN to TAG. EXTENDED_MODE controls whether extended length + headers or command chaining is used instead of single length + bytes. */ gpg_error_t -iso7816_put_data (int slot, int tag, +iso7816_put_data (int slot, int extended_mode, int tag, const unsigned char *data, size_t datalen) { int sw; - sw = apdu_send_simple (slot, 0x00, CMD_PUT_DATA, + sw = apdu_send_simple (slot, extended_mode, 0x00, CMD_PUT_DATA, + ((tag >> 8) & 0xff), (tag & 0xff), + datalen, (const char*)data); + return map_sw (sw); +} + +/* Same as iso7816_put_data but uses an odd instruction byte. */ +gpg_error_t +iso7816_put_data_odd (int slot, int extended_mode, int tag, + const unsigned char *data, size_t datalen) +{ + int sw; + + sw = apdu_send_simple (slot, extended_mode, 0x00, CMD_PUT_DATA+1, ((tag >> 8) & 0xff), (tag & 0xff), datalen, (const char*)data); return map_sw (sw); @@ -335,7 +496,7 @@ iso7816_manage_security_env (int slot, int p1, int p2, if (p1 < 0 || p1 > 255 || p2 < 0 || p2 > 255 ) return gpg_error (GPG_ERR_INV_VALUE); - sw = apdu_send_simple (slot, 0x00, CMD_MSE, p1, p2, + sw = apdu_send_simple (slot, 0, 0x00, CMD_MSE, p1, p2, data? datalen : -1, (const char*)data); return map_sw (sw); } @@ -344,9 +505,10 @@ iso7816_manage_security_env (int slot, int p1, int p2, /* Perform the security operation COMPUTE DIGITAL SIGANTURE. On success 0 is returned and the data is availavle in a newly allocated buffer stored at RESULT with its length stored at - RESULTLEN. */ + RESULTLEN. For LE see do_generate_keypair. */ gpg_error_t -iso7816_compute_ds (int slot, const unsigned char *data, size_t datalen, +iso7816_compute_ds (int slot, int extended_mode, + const unsigned char *data, size_t datalen, int le, unsigned char **result, size_t *resultlen) { int sw; @@ -356,8 +518,16 @@ iso7816_compute_ds (int slot, const unsigned char *data, size_t datalen, *result = NULL; *resultlen = 0; - sw = apdu_send (slot, 0x00, CMD_PSO, 0x9E, 0x9A, datalen, (const char*)data, - result, resultlen); + if (!extended_mode) + le = 256; /* Ignore provided Le and use what apdu_send uses. */ + else if (le >= 0 && le < 256) + le = 256; + + sw = apdu_send_le (slot, extended_mode, + 0x00, CMD_PSO, 0x9E, 0x9A, + datalen, (const char*)data, + le, + result, resultlen); if (sw != SW_SUCCESS) { /* Make sure that pending buffers are released. */ @@ -377,7 +547,8 @@ iso7816_compute_ds (int slot, const unsigned char *data, size_t datalen, 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, +iso7816_decipher (int slot, int extended_mode, + const unsigned char *data, size_t datalen, int padind, unsigned char **result, size_t *resultlen) { int sw; @@ -394,17 +565,19 @@ iso7816_decipher (int slot, const unsigned char *data, size_t datalen, 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, + sw = apdu_send (slot, extended_mode, + 0x00, CMD_PSO, 0x80, 0x86, datalen+1, (char*)buf, result, resultlen); xfree (buf); } else { - sw = apdu_send (slot, 0x00, CMD_PSO, 0x80, 0x86, + sw = apdu_send (slot, extended_mode, + 0x00, CMD_PSO, 0x80, 0x86, datalen, (const char *)data, result, resultlen); } @@ -421,9 +594,11 @@ iso7816_decipher (int slot, const unsigned char *data, size_t datalen, } +/* For LE see do_generate_keypair. */ gpg_error_t -iso7816_internal_authenticate (int slot, +iso7816_internal_authenticate (int slot, int extended_mode, const unsigned char *data, size_t datalen, + int le, unsigned char **result, size_t *resultlen) { int sw; @@ -433,8 +608,16 @@ iso7816_internal_authenticate (int slot, *result = NULL; *resultlen = 0; - sw = apdu_send (slot, 0x00, CMD_INTERNAL_AUTHENTICATE, 0, 0, - datalen, (const char*)data, result, resultlen); + if (!extended_mode) + le = 256; /* Ignore provided Le and use what apdu_send uses. */ + else if (le >= 0 && le < 256) + le = 256; + + sw = apdu_send_le (slot, extended_mode, + 0x00, CMD_INTERNAL_AUTHENTICATE, 0, 0, + datalen, (const char*)data, + le, + result, resultlen); if (sw != SW_SUCCESS) { /* Make sure that pending buffers are released. */ @@ -448,10 +631,15 @@ iso7816_internal_authenticate (int slot, } +/* LE is the expected return length. This is usually 0 except if + extended length mode is used and more than 256 byte will be + returned. In that case a value of -1 uses a large default + (e.g. 4096 bytes), a value larger 256 used that value. */ static gpg_error_t -do_generate_keypair (int slot, int readonly, - const unsigned char *data, size_t datalen, - unsigned char **result, size_t *resultlen) +do_generate_keypair (int slot, int extended_mode, int readonly, + const unsigned char *data, size_t datalen, + int le, + unsigned char **result, size_t *resultlen) { int sw; @@ -460,8 +648,11 @@ do_generate_keypair (int slot, int readonly, *result = NULL; *resultlen = 0; - sw = apdu_send (slot, 0x00, CMD_GENERATE_KEYPAIR, readonly? 0x81:0x80, 0, - datalen, (const char*)data, result, resultlen); + sw = apdu_send_le (slot, extended_mode, + 0x00, CMD_GENERATE_KEYPAIR, readonly? 0x81:0x80, 0, + datalen, (const char*)data, + le >= 0 && le < 256? 256:le, + result, resultlen); if (sw != SW_SUCCESS) { /* Make sure that pending buffers are released. */ @@ -476,20 +667,24 @@ do_generate_keypair (int slot, int readonly, gpg_error_t -iso7816_generate_keypair (int slot, +iso7816_generate_keypair (int slot, int extended_mode, const unsigned char *data, size_t datalen, + int le, unsigned char **result, size_t *resultlen) { - return do_generate_keypair (slot, 0, data, datalen, result, resultlen); + return do_generate_keypair (slot, extended_mode, 0, + data, datalen, le, result, resultlen); } gpg_error_t -iso7816_read_public_key (int slot, - const unsigned char *data, size_t datalen, - unsigned char **result, size_t *resultlen) +iso7816_read_public_key (int slot, int extended_mode, + const unsigned char *data, size_t datalen, + int le, + unsigned char **result, size_t *resultlen) { - return do_generate_keypair (slot, 1, data, datalen, result, resultlen); + return do_generate_keypair (slot, extended_mode, 1, + data, datalen, le, result, resultlen); } @@ -508,8 +703,8 @@ iso7816_get_challenge (int slot, int length, unsigned char *buffer) { result = NULL; n = length > 254? 254 : length; - sw = apdu_send_le (slot, 0x00, CMD_GET_CHALLENGE, 0, 0, -1, NULL, - n, + sw = apdu_send_le (slot, 0, + 0x00, CMD_GET_CHALLENGE, 0, 0, -1, NULL, n, &result, &resultlen); if (sw != SW_SUCCESS) { @@ -557,21 +752,14 @@ iso7816_read_binary (int slot, size_t offset, size_t nmax, { buffer = NULL; bufferlen = 0; - /* Note, that we to set N to 254 due to problems either with the - ccid driver or some TCOS cards. It actually should be 0 - which is the official ISO value to read a variable length - object. */ - if (read_all || nmax > 254) - n = 254; - else - n = nmax; - sw = apdu_send_le (slot, 0x00, CMD_READ_BINARY, + n = read_all? 0 : nmax; + sw = apdu_send_le (slot, 0, 0x00, CMD_READ_BINARY, ((offset>>8) & 0xff), (offset & 0xff) , -1, NULL, n, &buffer, &bufferlen); if ( SW_EXACT_LENGTH_P(sw) ) { n = (sw & 0x00ff); - sw = apdu_send_le (slot, 0x00, CMD_READ_BINARY, + sw = apdu_send_le (slot, 0, 0x00, CMD_READ_BINARY, ((offset>>8) & 0xff), (offset & 0xff) , -1, NULL, n, &buffer, &bufferlen); } @@ -598,7 +786,7 @@ iso7816_read_binary (int slot, size_t offset, size_t nmax, unsigned char *p = xtryrealloc (*result, *resultlen + bufferlen); if (!p) { - gpg_error_t err = gpg_error_from_errno (errno); + gpg_error_t err = gpg_error_from_syserror (); xfree (buffer); xfree (*result); *result = NULL; @@ -658,13 +846,11 @@ iso7816_read_record (int slot, int recno, int reccount, int short_ef, buffer = NULL; bufferlen = 0; - /* Fixme: Either the ccid driver or the TCOS cards have problems - with an Le of 0. */ - sw = apdu_send_le (slot, 0x00, CMD_READ_RECORD, + sw = apdu_send_le (slot, 0, 0x00, CMD_READ_RECORD, recno, short_ef? short_ef : 0x04, -1, NULL, - 254, &buffer, &bufferlen); + 0, &buffer, &bufferlen); if (sw != SW_SUCCESS && sw != SW_EOF_REACHED) { diff --git a/g10/iso7816.h b/g10/iso7816.h index a9dd9ee44..4e7a344ab 100644 --- a/g10/iso7816.h +++ b/g10/iso7816.h @@ -26,10 +26,30 @@ #include "cardglue.h" #endif +/* Command codes used by iso7816_check_keypad. */ +#define ISO7816_VERIFY 0x20 +#define ISO7816_CHANGE_REFERENCE_DATA 0x24 +#define ISO7816_RESET_RETRY_COUNTER 0x2C + + +/* Information to be passed to keypad equipped readers. See + ccid-driver.c for details. */ +struct iso7816_pininfo_s +{ + int mode; /* A mode of 0 means: Do not use the keypad. */ + int minlen; + int maxlen; + int padlen; + int padchar; +}; +typedef struct iso7816_pininfo_s iso7816_pininfo_t; + + gpg_error_t iso7816_map_sw (int sw); gpg_error_t iso7816_select_application (int slot, - const char *aid, size_t aidlen); + const char *aid, size_t aidlen, + unsigned int flags); gpg_error_t iso7816_select_file (int slot, int tag, int is_dir, unsigned char **result, size_t *resultlen); gpg_error_t iso7816_select_path (int slot, @@ -37,36 +57,62 @@ gpg_error_t iso7816_select_path (int slot, 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_apdu_direct (int slot, + const void *apdudata, size_t apdudatalen, + int handle_more, + unsigned char **result, size_t *resultlen); +gpg_error_t iso7816_check_keypad (int slot, int command, + iso7816_pininfo_t *pininfo); gpg_error_t iso7816_verify (int slot, int chvno, const char *chv, size_t chvlen); +gpg_error_t iso7816_verify_kp (int slot, + int chvno, const char *chv, size_t chvlen, + iso7816_pininfo_t *pininfo); gpg_error_t iso7816_change_reference_data (int slot, int chvno, const char *oldchv, size_t oldchvlen, const char *newchv, size_t newchvlen); +gpg_error_t iso7816_change_reference_data_kp (int slot, int chvno, + const char *oldchv, size_t oldchvlen, + const char *newchv, size_t newchvlen, + iso7816_pininfo_t *pininfo); gpg_error_t iso7816_reset_retry_counter (int slot, int chvno, const char *newchv, size_t newchvlen); -gpg_error_t iso7816_get_data (int slot, int tag, +gpg_error_t iso7816_reset_retry_counter_kp (int slot, int chvno, + const char *newchv, + size_t newchvlen, + iso7816_pininfo_t *pininfo); +gpg_error_t iso7816_reset_retry_counter_with_rc (int slot, int chvno, + const char *data, + size_t datalen); +gpg_error_t iso7816_get_data (int slot, int extended_mode, int tag, unsigned char **result, size_t *resultlen); -gpg_error_t iso7816_put_data (int slot, int tag, +gpg_error_t iso7816_put_data (int slot, int extended_mode, int tag, const unsigned char *data, size_t datalen); +gpg_error_t iso7816_put_data_odd (int slot, int extended_mode, 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, +gpg_error_t iso7816_compute_ds (int slot, int extended_mode, const unsigned char *data, size_t datalen, + int le, unsigned char **result, size_t *resultlen); -gpg_error_t iso7816_decipher (int slot, +gpg_error_t iso7816_decipher (int slot, int extended_mode, 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, - unsigned char **result, size_t *resultlen); -gpg_error_t iso7816_generate_keypair (int slot, - const unsigned char *data, size_t datalen, - unsigned char **result, size_t *resultlen); -gpg_error_t iso7816_read_public_key (int slot, +gpg_error_t iso7816_internal_authenticate (int slot, int extended_mode, const unsigned char *data, size_t datalen, + int le, unsigned char **result, size_t *resultlen); +gpg_error_t iso7816_generate_keypair (int slot, int extended_mode, + const unsigned char *data, size_t datalen, + int le, + unsigned char **result, size_t *resultlen); +gpg_error_t iso7816_read_public_key (int slot, int extended_mode, + const unsigned char *data, size_t datalen, + int le, + unsigned char **result, size_t *resultlen); gpg_error_t iso7816_get_challenge (int slot, int length, unsigned char *buffer); diff --git a/g10/keyedit.c b/g10/keyedit.c index ccb506361..674bafc93 100644 --- a/g10/keyedit.c +++ b/g10/keyedit.c @@ -3652,6 +3652,7 @@ menu_backsign(KBNODE pub_keyblock,KBNODE sec_keyblock) PKT_public_key *main_pk; PKT_secret_key *main_sk,*sub_sk=NULL; KBNODE node; + u32 timestamp; assert(pub_keyblock->pkt->pkttype==PKT_PUBLIC_KEY); assert(sec_keyblock->pkt->pkttype==PKT_SECRET_KEY); @@ -3661,6 +3662,10 @@ menu_backsign(KBNODE pub_keyblock,KBNODE sec_keyblock) main_sk=copy_secret_key(NULL,sec_keyblock->pkt->pkt.secret_key); keyid_from_pk(main_pk,NULL); + /* We use the same timestamp for all backsigs so that we don't + reveal information about the used machine. */ + timestamp = make_timestamp (); + for(node=pub_keyblock;node;node=node->next) { PKT_public_key *sub_pk=NULL; @@ -3748,7 +3753,8 @@ menu_backsign(KBNODE pub_keyblock,KBNODE sec_keyblock) set_next_passphrase(passphrase); xfree(passphrase); - rc=make_backsig(sig_pk->pkt->pkt.signature,main_pk,sub_pk,sub_sk); + rc = make_backsig (sig_pk->pkt->pkt.signature, main_pk, sub_pk, sub_sk, + timestamp); if(rc==0) { PKT_signature *newsig; diff --git a/g10/keygen.c b/g10/keygen.c index d5a80dd59..e789fd8c7 100644 --- a/g10/keygen.c +++ b/g10/keygen.c @@ -1,6 +1,6 @@ /* keygen.c - generate a key pair * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, - * 2007 Free Software Foundation, Inc. + * 2007, 2009 Free Software Foundation, Inc. * * This file is part of GnuPG. * @@ -127,9 +127,11 @@ static int write_keyblock( IOBUF out, KBNODE node ); static int gen_card_key (int algo, int keyno, int is_primary, KBNODE pub_root, KBNODE sec_root, PKT_secret_key **ret_sk, + u32 *timestamp, u32 expireval, struct para_data_s *para); static int gen_card_key_with_backup (int algo, int keyno, int is_primary, KBNODE pub_root, KBNODE sec_root, + u32 timestamp, u32 expireval, struct para_data_s *para, const char *backup_dir); @@ -770,17 +772,20 @@ keygen_add_revkey(PKT_signature *sig, void *opaque) return 0; } +/* Create a back-signature. If TIMESTAMP is not NULL, use it for the + signature creation time. */ int -make_backsig(PKT_signature *sig,PKT_public_key *pk, - PKT_public_key *sub_pk,PKT_secret_key *sub_sk) +make_backsig (PKT_signature *sig, PKT_public_key *pk, + PKT_public_key *sub_pk, PKT_secret_key *sub_sk, + u32 timestamp) { PKT_signature *backsig; int rc; cache_public_key(sub_pk); - rc=make_keysig_packet(&backsig,pk,NULL,sub_pk,sub_sk,0x19,0,0, - sub_pk->timestamp,0,NULL,NULL); + rc= make_keysig_packet (&backsig, pk, NULL, sub_pk, sub_sk, 0x19, + 0, 0, timestamp, 0, NULL, NULL); if(rc) log_error("make_keysig_packet failed for backsig: %s\n",g10_errstr(rc)); else @@ -863,7 +868,7 @@ make_backsig(PKT_signature *sig,PKT_public_key *pk, static int write_direct_sig( KBNODE root, KBNODE pub_root, PKT_secret_key *sk, - struct revocation_key *revkey ) + struct revocation_key *revkey, u32 timestamp) { PACKET *pkt; PKT_signature *sig; @@ -885,8 +890,9 @@ write_direct_sig( KBNODE root, KBNODE pub_root, PKT_secret_key *sk, cache_public_key (pk); /* and make the signature */ - rc = make_keysig_packet(&sig,pk,NULL,NULL,sk,0x1F,0,0,pk->timestamp,0, - keygen_add_revkey,revkey); + rc = make_keysig_packet (&sig, pk, NULL, NULL, sk, 0x1F, + 0, 0, timestamp, 0, + keygen_add_revkey, revkey); if( rc ) { log_error("make_keysig_packet failed: %s\n", g10_errstr(rc) ); return rc; @@ -900,8 +906,8 @@ write_direct_sig( KBNODE root, KBNODE pub_root, PKT_secret_key *sk, } static int -write_selfsigs( KBNODE sec_root, KBNODE pub_root, PKT_secret_key *sk, - unsigned int use ) +write_selfsigs (KBNODE sec_root, KBNODE pub_root, PKT_secret_key *sk, + unsigned int use, u32 timestamp) { PACKET *pkt; PKT_signature *sig; @@ -929,8 +935,9 @@ write_selfsigs( KBNODE sec_root, KBNODE pub_root, PKT_secret_key *sk, cache_public_key (pk); /* and make the signature */ - rc = make_keysig_packet( &sig, pk, uid, NULL, sk, 0x13, 0, 0, - pk->timestamp, 0, keygen_add_std_prefs, pk ); + rc = make_keysig_packet (&sig, pk, uid, NULL, sk, 0x13, + 0, 0, timestamp, 0, + keygen_add_std_prefs, pk); if( rc ) { log_error("make_keysig_packet failed: %s\n", g10_errstr(rc) ); return rc; @@ -949,9 +956,9 @@ write_selfsigs( KBNODE sec_root, KBNODE pub_root, PKT_secret_key *sk, } static int -write_keybinding( KBNODE root, KBNODE pub_root, +write_keybinding (KBNODE root, KBNODE pub_root, PKT_secret_key *pri_sk, PKT_secret_key *sub_sk, - unsigned int use ) + unsigned int use, u32 timestamp) { PACKET *pkt; PKT_signature *sig; @@ -984,8 +991,8 @@ write_keybinding( KBNODE root, KBNODE pub_root, /* and make the signature */ oduap.usage = use; oduap.pk = sub_pk; - rc=make_keysig_packet(&sig, pri_pk, NULL, sub_pk, pri_sk, 0x18, 0, 0, - sub_pk->timestamp, 0, + rc=make_keysig_packet(&sig, pri_pk, NULL, sub_pk, pri_sk, 0x18, + 0, 0, timestamp, 0, keygen_add_key_flags_and_expire, &oduap ); if( rc ) { log_error("make_keysig_packet failed: %s\n", g10_errstr(rc) ); @@ -995,7 +1002,7 @@ write_keybinding( KBNODE root, KBNODE pub_root, /* make a backsig */ if(use&PUBKEY_USAGE_SIG) { - rc=make_backsig(sig,pri_pk,sub_pk,sub_sk); + rc = make_backsig (sig, pri_pk, sub_pk, sub_sk, timestamp); if(rc) return rc; } @@ -3041,6 +3048,15 @@ do_generate_keypair (struct para_data_s *para,struct output_control_s *outctrl, timestamp = get_parameter_u32 (para, pKEYCREATIONDATE); + /* Note that, depending on the backend (i.e. the used scdaemon + version or the internal code), the card key generation may + update TIMESTAMP for each key. Thus we need to pass TIMESTAMP + to all signing function to make sure that the binding signature + is done using the timestamp of the corresponding (sub)key and + not that of the primary key. An alternative implementation + could tell the signing function the node of the subkey but that + is more work than just to pass the current timestamp. */ + if (!card) { rc = do_create( get_parameter_algo( para, pKEYTYPE ), @@ -3055,6 +3071,7 @@ do_generate_keypair (struct para_data_s *para,struct output_control_s *outctrl, else { rc = gen_card_key (PUBKEY_ALGO_RSA, 1, 1, pub_root, sec_root, NULL, + ×tamp, get_parameter_u32 (para, pKEYEXPIRE), para); if (!rc) { @@ -3065,9 +3082,9 @@ do_generate_keypair (struct para_data_s *para,struct output_control_s *outctrl, if(!rc && (revkey=get_parameter_revkey(para,pREVOKER))) { - rc=write_direct_sig(pub_root,pub_root,pri_sk,revkey); - if(!rc) - write_direct_sig(sec_root,pub_root,pri_sk,revkey); + rc = write_direct_sig (pub_root, pub_root, pri_sk, revkey, timestamp); + if (!rc) + write_direct_sig (sec_root, pub_root, pri_sk, revkey, timestamp); } if( !rc && (s=get_parameter_value(para, pUSERID)) ) @@ -3076,9 +3093,10 @@ do_generate_keypair (struct para_data_s *para,struct output_control_s *outctrl, if( !rc ) write_uid(sec_root, s ); - if( !rc ) - rc = write_selfsigs(sec_root, pub_root, pri_sk, - get_parameter_uint (para, pKEYUSAGE)); + if (!rc) + rc = write_selfsigs (sec_root, pub_root, pri_sk, + get_parameter_uint (para, pKEYUSAGE), + timestamp); } /* Write the auth key to the card before the encryption key. This @@ -3091,12 +3109,15 @@ do_generate_keypair (struct para_data_s *para,struct output_control_s *outctrl, if (!rc && card && get_parameter (para, pAUTHKEYTYPE)) { rc = gen_card_key (PUBKEY_ALGO_RSA, 3, 0, pub_root, sec_root, NULL, + ×tamp, get_parameter_u32 (para, pKEYEXPIRE), para); if (!rc) - rc = write_keybinding (pub_root, pub_root, pri_sk, sub_sk, PUBKEY_USAGE_AUTH); + rc = write_keybinding (pub_root, pub_root, pri_sk, sub_sk, + PUBKEY_USAGE_AUTH, timestamp); if (!rc) - rc = write_keybinding (sec_root, pub_root, pri_sk, sub_sk, PUBKEY_USAGE_AUTH); + rc = write_keybinding (sec_root, pub_root, pri_sk, sub_sk, + PUBKEY_USAGE_AUTH, timestamp); } if( !rc && get_parameter( para, pSUBKEYTYPE ) ) @@ -3121,6 +3142,7 @@ do_generate_keypair (struct para_data_s *para,struct output_control_s *outctrl, the card. Write a backup file. */ rc = gen_card_key_with_backup (PUBKEY_ALGO_RSA, 2, 0, pub_root, sec_root, + timestamp, get_parameter_u32 (para, pKEYEXPIRE), para, s); @@ -3128,15 +3150,18 @@ do_generate_keypair (struct para_data_s *para,struct output_control_s *outctrl, else rc = gen_card_key (PUBKEY_ALGO_RSA, 2, 0, pub_root, sec_root, NULL, + ×tamp, get_parameter_u32 (para, pKEYEXPIRE), para); } if( !rc ) - rc = write_keybinding(pub_root, pub_root, pri_sk, sub_sk, - get_parameter_uint (para, pSUBKEYUSAGE)); + rc = write_keybinding (pub_root, pub_root, pri_sk, sub_sk, + get_parameter_uint (para, pSUBKEYUSAGE), + timestamp ); if( !rc ) - rc = write_keybinding(sec_root, pub_root, pri_sk, sub_sk, - get_parameter_uint (para, pSUBKEYUSAGE)); + rc = write_keybinding (sec_root, pub_root, pri_sk, sub_sk, + get_parameter_uint (para, pSUBKEYUSAGE), + timestamp); did_sub = 1; } @@ -3351,16 +3376,19 @@ generate_subkeypair( KBNODE pub_keyblock, KBNODE sec_keyblock ) NULL, NULL ); } - rc = do_create( algo, nbits, pub_keyblock, sec_keyblock, + rc = do_create (algo, nbits, pub_keyblock, sec_keyblock, dek, s2k, &sub_sk, timestamp, expire, 1 ); - if( !rc ) - rc = write_keybinding(pub_keyblock, pub_keyblock, pri_sk, sub_sk, use); - if( !rc ) - rc = write_keybinding(sec_keyblock, pub_keyblock, pri_sk, sub_sk, use); - if( !rc ) { + if (!rc) + rc = write_keybinding (pub_keyblock, pub_keyblock, pri_sk, sub_sk, + use, timestamp); + if (!rc) + rc = write_keybinding (sec_keyblock, pub_keyblock, pri_sk, sub_sk, + use, timestamp); + if (!rc) + { okay = 1; write_status_text (STATUS_KEY_CREATED, "S"); - } + } leave: if( rc ) @@ -3466,11 +3494,13 @@ generate_card_subkeypair (KBNODE pub_keyblock, KBNODE sec_keyblock, if (passphrase) set_next_passphrase (passphrase); rc = gen_card_key (algo, keyno, 0, pub_keyblock, sec_keyblock, - &sub_sk, expire, para); + &sub_sk, ×tamp, expire, para); if (!rc) - rc = write_keybinding (pub_keyblock, pub_keyblock, pri_sk, sub_sk, use); + rc = write_keybinding (pub_keyblock, pub_keyblock, pri_sk, sub_sk, use, + timestamp); if (!rc) - rc = write_keybinding (sec_keyblock, pub_keyblock, pri_sk, sub_sk, use); + rc = write_keybinding (sec_keyblock, pub_keyblock, pri_sk, sub_sk, use, + timestamp); if (!rc) { okay = 1; @@ -3515,10 +3545,11 @@ write_keyblock( IOBUF out, KBNODE node ) } +/* Note that TIMESTAMP is an in/out arg. */ static int gen_card_key (int algo, int keyno, int is_primary, KBNODE pub_root, KBNODE sec_root, PKT_secret_key **ret_sk, - u32 expireval, struct para_data_s *para) + u32 *timestamp, u32 expireval, struct para_data_s *para) { #ifdef ENABLE_CARD_SUPPORT int rc; @@ -3531,7 +3562,7 @@ gen_card_key (int algo, int keyno, int is_primary, assert (algo == PUBKEY_ALGO_RSA); /* Fixme: We don't have the serialnumber available, thus passing NULL. */ - rc = agent_scd_genkey (&info, keyno, 1, NULL); + rc = agent_scd_genkey (&info, keyno, 1, NULL, timestamp); /* if (gpg_err_code (rc) == GPG_ERR_EEXIST) */ /* { */ /* tty_printf ("\n"); */ @@ -3555,6 +3586,9 @@ gen_card_key (int algo, int keyno, int is_primary, return gpg_error (GPG_ERR_GENERAL); } + if (*timestamp != info.created_at) + log_info ("Note that the key does not use the suggested creation date\n"); + *timestamp = info.created_at; pk = xcalloc (1, sizeof *pk ); sk = xcalloc (1, sizeof *sk ); @@ -3602,6 +3636,7 @@ gen_card_key (int algo, int keyno, int is_primary, static int gen_card_key_with_backup (int algo, int keyno, int is_primary, KBNODE pub_root, KBNODE sec_root, + u32 timestamp, u32 expireval, struct para_data_s *para, const char *backup_dir) { @@ -3616,7 +3651,7 @@ gen_card_key_with_backup (int algo, int keyno, int is_primary, sk_unprotected = NULL; sk_protected = NULL; - rc = generate_raw_key (algo, 1024, make_timestamp (), + rc = generate_raw_key (algo, 1024, timestamp, &sk_unprotected, &sk_protected); if (rc) return rc; diff --git a/g10/main.h b/g10/main.h index 9f53d0820..657a481ba 100644 --- a/g10/main.h +++ b/g10/main.h @@ -183,7 +183,8 @@ int keygen_add_keyserver_url(PKT_signature *sig, void *opaque); int keygen_add_notations(PKT_signature *sig,void *opaque); int keygen_add_revkey(PKT_signature *sig, void *opaque); int make_backsig(PKT_signature *sig,PKT_public_key *pk, - PKT_public_key *sub_pk,PKT_secret_key *sub_sk); + PKT_public_key *sub_pk,PKT_secret_key *sub_sk, + u32 timestamp); int generate_subkeypair( KBNODE pub_keyblock, KBNODE sec_keyblock ); #ifdef ENABLE_CARD_SUPPORT int generate_card_subkeypair (KBNODE pub_keyblock, KBNODE sec_keyblock, diff --git a/g10/options.h b/g10/options.h index 756a8edc1..cac1c4c2c 100644 --- a/g10/options.h +++ b/g10/options.h @@ -214,6 +214,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 disable_keypad; /* Do not allow the use of a keypad. */ #endif /*ENABLE_CARD_SUPPORT*/ struct diff --git a/include/ChangeLog b/include/ChangeLog index 581904cc7..572830ccb 100644 --- a/include/ChangeLog +++ b/include/ChangeLog @@ -1,3 +1,15 @@ +2009-07-21 Werner Koch + + * estream-printf.h: New. Taken from libestream.x + +2009-07-20 Werner Koch + + * types.h (strlist_t): Add new alias for STRLIST. + + * memory.h (xtrymalloc,xtrystrdup): New. + + * util.h: Add prototypes for util/convert.c. + 2009-05-26 David Shaw * http.h: Pass in a STRLIST for additional headers on http_open diff --git a/include/distfiles b/include/distfiles index 4415193fb..6b1f560f3 100644 --- a/include/distfiles +++ b/include/distfiles @@ -15,5 +15,6 @@ dynload.h assuan.h compat.h srv.h +estream-printf.h ChangeLog diff --git a/include/estream-printf.h b/include/estream-printf.h new file mode 100644 index 000000000..3987b33f2 --- /dev/null +++ b/include/estream-printf.h @@ -0,0 +1,110 @@ +/* estream-printf.h - Versatile C-99 compliant printf formatting. + * Copyright (C) 2007 g10 Code GmbH + * + * This file is part of Libestream. + * + * Libestream 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. + * + * Libestream 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 Libestream; if not, see . + */ + +#ifndef ESTREAM_PRINTF_H +#define ESTREAM_PRINTF_H + +#include +#include + +/* To use this file with libraries the following macro is useful: + + #define _ESTREAM_EXT_SYM_PREFIX _foo_ + + This prefixes all external symbols with "_foo_". + + For the implementation of the code (estream-printf.c) the following + macros may be used to tune the implementation for certain systems: + + #define _ESTREAM_PRINTF_MALLOC foo_malloc + #define _ESTREAM_PRINTF_FREE foo_free + + Make estream_asprintf and estream_vasprintf use foo_malloc and + foo_free instead of the standard malloc and free functions to + allocate the memory returned to the caller. + + #define _ESTREAM_PRINTF_EXTRA_INCLUDE "foo.h" + + This includes the file "foo.h" which may provide prototypes for + the custom memory allocation functions. + */ + + +#ifdef _ESTREAM_EXT_SYM_PREFIX +#ifndef _ESTREAM_PREFIX +#define _ESTREAM_PREFIX1(x,y) x ## y +#define _ESTREAM_PREFIX2(x,y) _ESTREAM_PREFIX1(x,y) +#define _ESTREAM_PREFIX(x) _ESTREAM_PREFIX2(_ESTREAM_EXT_SYM_PREFIX,x) +#endif /*_ESTREAM_PREFIX*/ +#define estream_printf_out_t _ESTREAM_PREFIX(estream_printf_out_t) +#define estream_format _ESTREAM_PREFIX(estream_format) +#define estream_printf _ESTREAM_PREFIX(estream_printf) +#define estream_fprintf _ESTREAM_PREFIX(estream_fprintf) +#define estream_vfprintf _ESTREAM_PREFIX(estream_vfprintf) +#define estream_snprintf _ESTREAM_PREFIX(estream_snprintf) +#define estream_vsnprintf _ESTREAM_PREFIX(estream_vsnprintf) +#define estream_asprintf _ESTREAM_PREFIX(estream_asprintf) +#define estream_vasprintf _ESTREAM_PREFIX(estream_vasprintf) +#endif /*_ESTREAM_EXT_SYM_PREFIX*/ + +#ifndef _ESTREAM_GCC_A_PRINTF +#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 5 ) +# define _ESTREAM_GCC_A_PRINTF( f, a ) __attribute__ ((format (printf,f,a))) +#else +# define _ESTREAM_GCC_A_PRINTF( f, a ) +#endif +#endif /*_ESTREAM_GCC_A_PRINTF*/ + + +#ifdef __cplusplus +extern "C" +{ +#if 0 +} +#endif +#endif + + +typedef int (*estream_printf_out_t) + (void *outfncarg, const char *buf, size_t buflen); + +int estream_format (estream_printf_out_t outfnc, void *outfncarg, + const char *format, va_list vaargs) + _ESTREAM_GCC_A_PRINTF(3,0); +int estream_printf (const char *format, ...) + _ESTREAM_GCC_A_PRINTF(1,2); +int estream_fprintf (FILE *fp, const char *format, ... ) + _ESTREAM_GCC_A_PRINTF(2,3); +int estream_vfprintf (FILE *fp, const char *format, va_list arg_ptr) + _ESTREAM_GCC_A_PRINTF(2,0); +int estream_snprintf (char *buf, size_t bufsize, const char *format, ...) + _ESTREAM_GCC_A_PRINTF(3,4); +int estream_vsnprintf (char *buf,size_t bufsize, + const char *format, va_list arg_ptr) + _ESTREAM_GCC_A_PRINTF(3,0); +int estream_asprintf (char **bufp, const char *format, ...) + _ESTREAM_GCC_A_PRINTF(2,3); +int estream_vasprintf (char **bufp, const char *format, va_list arg_ptr) + _ESTREAM_GCC_A_PRINTF(2,0); + + +#ifdef __cplusplus +} +#endif +#endif /*ESTREAM_PRINTF_H*/ diff --git a/include/memory.h b/include/memory.h index 84e92b473..d414a9b2e 100644 --- a/include/memory.h +++ b/include/memory.h @@ -30,6 +30,7 @@ #define M_DBGINFO(a) "["__FILE__ ":" STR(a) "]" #endif /* __riscos__ */ #define xmalloc(n) m_debug_alloc((n), M_DBGINFO( __LINE__ ) ) +#define xtrymalloc(n) m_debug_trymalloc ((n), M_DBGINFO( __LINE__ )) #define xmalloc_clear(n) m_debug_alloc_clear((n), M_DBGINFO(__LINE__) ) #define xmalloc_secure(n) m_debug_alloc_secure(n), M_DBGINFO(__LINE__) ) #define xmalloc_secure_clear(n) m_debug_alloc_secure_clear((n), M_DBGINFO(__LINE__) ) @@ -38,8 +39,10 @@ #define m_check(n) m_debug_check((n), M_DBGINFO(__LINE__) ) /*#define m_copy(a) m_debug_copy((a), M_DBGINFO(__LINE__) )*/ #define xstrdup(a) m_debug_strdup((a), M_DBGINFO(__LINE__) ) +#define xtrystrdup(a) m_debug_trystrdup((a), M_DBGINFO(__LINE__) ) void *m_debug_alloc( size_t n, const char *info ); +void *m_debug_trymalloc (size_t n, const char *info); void *m_debug_alloc_clear( size_t n, const char *info ); void *m_debug_alloc_secure( size_t n, const char *info ); void *m_debug_alloc_secure_clear( size_t n, const char *info ); @@ -48,9 +51,11 @@ void m_debug_free( void *p, const char *info ); void m_debug_check( const void *a, const char *info ); /*void *m_debug_copy( const void *a, const char *info );*/ char *m_debug_strdup( const char *a, const char *info ); +char *m_debug_trystrdup (const char *a, const char *info); #else void *xmalloc( size_t n ); +void *xtrymalloc (size_t n); void *xmalloc_clear( size_t n ); void *xmalloc_secure( size_t n ); void *xmalloc_secure_clear( size_t n ); @@ -59,6 +64,7 @@ void xfree( void *p ); void m_check( const void *a ); /*void *m_copy( const void *a );*/ char *xstrdup( const char * a); +char *xtrystrdup (const char *a); #endif size_t m_size( const void *a ); diff --git a/include/types.h b/include/types.h index f57f33ab6..d6cd0735a 100644 --- a/include/types.h +++ b/include/types.h @@ -131,10 +131,12 @@ typedef union { double g; } PROPERLY_ALIGNED_TYPE; -typedef struct string_list { +struct string_list { struct string_list *next; unsigned int flags; char d[1]; -} *STRLIST; +}; +typedef struct string_list *STRLIST; +typedef struct string_list *strlist_t; #endif /*G10_TYPES_H*/ diff --git a/include/util.h b/include/util.h index ffae3d211..b15181eed 100644 --- a/include/util.h +++ b/include/util.h @@ -240,11 +240,13 @@ char *read_w32_registry_string( const char *root, int write_w32_registry_string(const char *root, const char *dir, const char *name, const char *value); -/*-- strgutil.c --*/ -int vasprintf (char **result, const char *format, va_list args); -int asprintf (char **buf, const char *fmt, ...); #endif /*_WIN32*/ +/*-- strgutil.c --*/ +char *xasprintf (const char *fmt, ...); +char *xtryasprintf (const char *fmt, ...); + + /*-- pka.c --*/ char *get_pka_info (const char *address, unsigned char *fpr); @@ -252,6 +254,16 @@ char *get_pka_info (const char *address, unsigned char *fpr); int get_cert(const char *name,size_t max_size,IOBUF *iobuf, unsigned char **fpr,size_t *fpr_len,char **url); +/*-- convert.c --*/ +int hex2bin (const char *string, void *buffer, size_t length); +int hexcolon2bin (const char *string, void *buffer, size_t length); +char *bin2hex (const void *buffer, size_t length, char *stringbuf); +char *bin2hexcolon (const void *buffer, size_t length, char *stringbuf); +const char *hex2str (const char *hexstring, + char *buffer, size_t bufsize, size_t *buflen); +char *hex2str_alloc (const char *hexstring, size_t *r_count); + + /**** other missing stuff ****/ #ifndef HAVE_ATEXIT /* For SunOS */ #define atexit(a) (on_exit((a),0)) diff --git a/m4/ChangeLog b/m4/ChangeLog index 680322064..5cbbb6321 100644 --- a/m4/ChangeLog +++ b/m4/ChangeLog @@ -1,3 +1,7 @@ +2009-07-21 Werner Koch + + * estream.m4: New. Taken from libestream. + 2007-12-17 Werner Koch * ldap.m4: Test for ldap_start_tls_sA. diff --git a/m4/Makefile.am b/m4/Makefile.am index a7eb1f527..3d43c044d 100644 --- a/m4/Makefile.am +++ b/m4/Makefile.am @@ -6,6 +6,7 @@ EXTRA_DIST = glibc2.m4 intl.m4 intldir.m4 lock.m4 visibility.m4 intmax.m4 longdo po.m4 progtest.m4 stdint_h.m4 uintmax_t.m4 ulonglong.m4 \ readline.m4 libcurl.m4 libusb.m4 tar-ustar.m4 \ ldap.m4 \ - noexecstack.m4 autobuild.m4 + noexecstack.m4 autobuild.m4 estream.m4 + diff --git a/m4/estream.m4 b/m4/estream.m4 new file mode 100644 index 000000000..4b6c745aa --- /dev/null +++ b/m4/estream.m4 @@ -0,0 +1,48 @@ +dnl Autoconf macros for libestream +dnl Copyright (C) 2007 g10 Code GmbH +dnl +dnl This file is free software; as a special exception the author gives +dnl unlimited permission to copy and/or distribute it, with or without +dnl modifications, as long as this notice is preserved. +dnl +dnl This file is distributed in the hope that it will be useful, but +dnl WITHOUT ANY WARRANTY, to the extent permitted by law; without even the +dnl implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + +dnl estream_PRINTF_INIT +dnl Prepare build of source included estream-printf.c +dnl +AC_DEFUN([estream_PRINTF_INIT], +[ + AC_MSG_NOTICE([checking system features for estream-printf]) + AC_TYPE_LONG_LONG_INT + AC_TYPE_LONG_DOUBLE + AC_TYPE_INTMAX_T + AC_TYPE_UINTMAX_T + AC_CHECK_TYPES([ptrdiff_t]) + AC_CHECK_SIZEOF([unsigned long]) + AC_CHECK_SIZEOF([void *]) + AC_CACHE_CHECK([for nl_langinfo and THOUSANDS_SEP], + estream_cv_langinfo_thousands_sep, + [AC_TRY_LINK([#include ], + [char* cs = nl_langinfo(THOUSANDS_SEP); return !cs;], + estream_cv_langinfo_thousands_sep=yes, + estream_cv_langinfo_thousands_sep=no) + ]) + if test $estream_cv_langinfo_thousands_sep = yes; then + AC_DEFINE(HAVE_LANGINFO_THOUSANDS_SEP, 1, + [Define if you have and nl_langinfo(THOUSANDS_SEP).]) + fi +]) + + +dnl estream_INIT +dnl Prepare build of source included estream.c +dnl +AC_DEFUN([estream_INIT], +[ + AC_REQUIRE([estream_PRINTF_INIT]) + AC_MSG_NOTICE([checking system features for estream]) + +]) diff --git a/util/ChangeLog b/util/ChangeLog index 4df28cc0e..73a8adb0c 100644 --- a/util/ChangeLog +++ b/util/ChangeLog @@ -1,3 +1,20 @@ +2009-07-21 Werner Koch + + * ttyio.c (tty_printf): Replace vasprintf by xtryasprintf. + (tty_fprintf): Ditto. + + * strgutil.c: Include estream-printf.h. + (xasprintf, xtryasprintf): New. + (vasprintf, asprintf): Remove. + + * estream-printf.c: New. Taken from libestream. + * Makefile.am (libutil_a_SOURCES): Add it. + + * memory.c (trymalloc,trystrdup): New. + + * convert.c: New. Taken from GnuPG 2.0 SVN. + * Makefile.am (libutil_a_SOURCES): Add it. + 2009-05-26 David Shaw * http.c (send_request): Pass in a STRLIST for additional headers. diff --git a/util/Makefile.am b/util/Makefile.am index 329a06214..c75c9bdd2 100644 --- a/util/Makefile.am +++ b/util/Makefile.am @@ -23,7 +23,8 @@ noinst_LIBRARIES = libutil.a libcompat.a libutil_a_SOURCES = logger.c fileutil.c miscutil.c strgutil.c \ ttyio.c argparse.c memory.c secmem.c errors.c iobuf.c \ - dotlock.c http.c pka.c membuf.c cert.c \ + dotlock.c http.c pka.c membuf.c cert.c convert.c \ + estream-printf.c \ $(libcompat_a_SOURCES) if USE_SIMPLE_GETTEXT diff --git a/util/convert.c b/util/convert.c new file mode 100644 index 000000000..d3e8b642e --- /dev/null +++ b/util/convert.c @@ -0,0 +1,249 @@ +/* convert.c - Hex conversion functions. + * Copyright (C) 2006, 2008 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include + +#include "util.h" + + +#define tohex(n) ((n) < 10 ? ((n) + '0') : (((n) - 10) + 'A')) + + +/* Convert STRING consisting of hex characters into its binary + representation and store that at BUFFER. BUFFER needs to be of + LENGTH bytes. The function checks that the STRING will convert + exactly to LENGTH bytes. The string is delimited by either end of + string or a white space character. The function returns -1 on + error or the length of the parsed string. */ +int +hex2bin (const char *string, void *buffer, size_t length) +{ + int i; + const char *s = string; + + for (i=0; i < length; ) + { + if (!hexdigitp (s) || !hexdigitp (s+1)) + return -1; /* Invalid hex digits. */ + ((unsigned char*)buffer)[i++] = xtoi_2 (s); + s += 2; + } + if (*s && (!isascii (*s) || !isspace (*s)) ) + return -1; /* Not followed by Nul or white space. */ + if (i != length) + return -1; /* Not of expected length. */ + if (*s) + s++; /* Skip the delimiter. */ + return s - string; +} + + +/* Convert STRING consisting of hex characters into its binary representation + and store that at BUFFER. BUFFER needs to be of LENGTH bytes. The + function check that the STRING will convert exactly to LENGTH + bytes. Colons inbetween the hex digits are allowed, if one colon + has been given a colon is expected very 2 characters. The string + is delimited by either end of string or a white space character. + The function returns -1 on error or the length of the parsed + string. */ +int +hexcolon2bin (const char *string, void *buffer, size_t length) +{ + int i; + const char *s = string; + int need_colon = 0; + + for (i=0; i < length; ) + { + if (i==1 && *s == ':') /* Skip colons between hex digits. */ + { + need_colon = 1; + s++; + } + else if (need_colon && *s == ':') + s++; + else if (need_colon) + return -1; /* Colon expected. */ + if (!hexdigitp (s) || !hexdigitp (s+1)) + return -1; /* Invalid hex digits. */ + ((unsigned char*)buffer)[i++] = xtoi_2 (s); + s += 2; + } + if (*s == ':') + return -1; /* Trailing colons are not allowed. */ + if (*s && (!isascii (*s) || !isspace (*s)) ) + return -1; /* Not followed by Nul or white space. */ + if (i != length) + return -1; /* Not of expected length. */ + if (*s) + s++; /* Skip the delimiter. */ + return s - string; +} + + + +static char * +do_bin2hex (const void *buffer, size_t length, char *stringbuf, int with_colon) +{ + const unsigned char *s; + char *p; + + if (!stringbuf) + { + /* Not really correct for with_colon but we don't care about the + one wasted byte. */ + size_t n = with_colon? 3:2; + size_t nbytes = n * length + 1; + if (length && (nbytes-1) / n != length) + { + errno = ENOMEM; + return NULL; + } + stringbuf = xtrymalloc (nbytes); + if (!stringbuf) + return NULL; + } + + for (s = buffer, p = stringbuf; length; length--, s++) + { + if (with_colon && s != buffer) + *p++ = ':'; + *p++ = tohex ((*s>>4)&15); + *p++ = tohex (*s&15); + } + *p = 0; + + return stringbuf; +} + + +/* Convert LENGTH bytes of data in BUFFER into hex encoding and store + that at the provided STRINGBUF. STRINGBUF must be allocated of at + least (2*LENGTH+1) bytes or be NULL so that the function mallocs an + appropriate buffer. Returns STRINGBUF or NULL on error (which may + only occur if STRINGBUF has been NULL and the internal malloc + failed). */ +char * +bin2hex (const void *buffer, size_t length, char *stringbuf) +{ + return do_bin2hex (buffer, length, stringbuf, 0); +} + +/* Convert LENGTH bytes of data in BUFFER into hex encoding and store + that at the provided STRINGBUF. STRINGBUF must be allocated of at + least (3*LENGTH+1) bytes or be NULL so that the function mallocs an + appropriate buffer. Returns STRINGBUF or NULL on error (which may + only occur if STRINGBUF has been NULL and the internal malloc + failed). */ +char * +bin2hexcolon (const void *buffer, size_t length, char *stringbuf) +{ + return do_bin2hex (buffer, length, stringbuf, 1); +} + + + +/* Convert HEXSTRING consisting of hex characters into string and + store that at BUFFER. HEXSTRING is either delimited by end of + string or a white space character. The function makes sure that + the resulting string in BUFFER is terminated by a Nul character. + BUFSIZE is the availabe length of BUFFER; if the converted result + plus a possible required Nul character does not fit into this + buffer, the function returns NULL and won't change the existing + conent of buffer. In-place conversion is possible as long as + BUFFER points to HEXSTRING. + + If BUFFER is NULL and bufsize is 0 the function scans HEXSTRING but + does not store anything. This may be used to find the end of + hexstring. + + On sucess the function returns a pointer to the next character + after HEXSTRING (which is either end-of-string or a the next white + space). If BUFLEN is not NULL the strlen of buffer is stored + there; this will even be done if BUFFER has been passed as NULL. */ +const char * +hex2str (const char *hexstring, char *buffer, size_t bufsize, size_t *buflen) +{ + const char *s = hexstring; + int idx, count; + int need_nul = 0; + + if (buflen) + *buflen = 0; + + for (s=hexstring, count=0; hexdigitp (s) && hexdigitp (s+1); s += 2, count++) + ; + if (*s && (!isascii (*s) || !isspace (*s)) ) + return NULL; /* Not followed by Nul or white space. */ + /* We need to append a nul character. However we don't want that if + the hexstring already ends with "00". */ + need_nul = ((s == hexstring) || !(s[-2] == '0' && s[-1] == '0')); + if (need_nul) + count++; + + if (buffer) + { + if (count > bufsize) + return NULL; /* Too long. */ + + for (s=hexstring, idx=0; hexdigitp (s) && hexdigitp (s+1); s += 2) + ((unsigned char*)buffer)[idx++] = xtoi_2 (s); + if (need_nul) + buffer[idx] = 0; + } + + if (buflen) + *buflen = count - 1; + return s; +} + + +/* Same as hex2str but this function allocated a new string. Returns + NULL on error. If R_COUNT is not NULL, the number of scanned bytes + will be stored there. ERRNO is set on error. */ +char * +hex2str_alloc (const char *hexstring, size_t *r_count) +{ + const char *tail; + size_t nbytes; + char *result; + + tail = hex2str (hexstring, NULL, 0, &nbytes); + if (!tail) + { + if (r_count) + *r_count = 0; + errno = EINVAL; + return NULL; + } + if (r_count) + *r_count = tail - hexstring; + result = xtrymalloc (nbytes+1); + if (!result) + return NULL; + if (!hex2str (hexstring, result, nbytes+1, NULL)) + BUG (); + return result; +} + + + diff --git a/util/estream-printf.c b/util/estream-printf.c new file mode 100644 index 000000000..8ea602e69 --- /dev/null +++ b/util/estream-printf.c @@ -0,0 +1,2110 @@ +/* estream-printf.c - Versatile C-99 compliant printf formatting + * Copyright (C) 2007, 2008 g10 Code GmbH + * + * This file is part of Libestream. + * + * Libestream 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. + * + * Libestream 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 Libestream; if not, see . + */ + +/* Required autoconf tests: + + AC_TYPE_LONG_LONG_INT defines HAVE_LONG_LONG_INT + AC_TYPE_LONG_DOUBLE defines HAVE_LONG_DOUBLE + AC_TYPE_INTMAX_T defines HAVE_INTMAX_T + AC_TYPE_UINTMAX_T defines HAVE_UINTMAX_T + AC_CHECK_TYPES([ptrdiff_t]) defines HAVE_PTRDIFF_T + AC_CHECK_SIZEOF([unsigned long]) defines SIZEOF_UNSIGNED_LONG + AC_CHECK_SIZEOF([void *]) defines SIZEOF_VOID_P + HAVE_LANGINFO_THOUSANDS_SEP + + Note that the file estream.m4 provides the autoconf macro + ESTREAM_PRINTF_INIT which runs all required checks. + See estream-printf.h for ways to tune this code. + + Missing stuff: wchar and wint_t + thousands_sep in pr_float. +*/ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(HAVE_INTMAX_T) || defined(HAVE_UINTMAX_T) +# include +#endif +#ifdef HAVE_LANGINFO_THOUSANDS_SEP +#include +#endif +#ifdef TEST +# include +#else +# ifdef _ESTREAM_PRINTF_EXTRA_INCLUDE +# include _ESTREAM_PRINTF_EXTRA_INCLUDE +# endif +#endif +#include "estream-printf.h" + +/* Allow redefinition of asprintf used malloc functions. */ +#if defined(_ESTREAM_PRINTF_MALLOC) && !defined(TEST) +#define my_printf_malloc(a) _ESTREAM_PRINTF_MALLOC((a)) +#else +#define my_printf_malloc(a) malloc((a)) +#endif +#if defined(_ESTREAM_PRINTF_FREE) && !defined(TEST) +#define my_printf_free(a) _ESTREAM_PRINTF_FREE((a)) +#else +#define my_printf_free(a) free((a)) +#endif + + +/* Calculate array dimension. */ +#ifndef DIM +#define DIM(array) (sizeof (array) / sizeof (*array)) +#endif + + +/* We allow for that many args without requiring malloced memory. */ +#define DEFAULT_MAX_ARGSPECS 5 + +/* We allow for that many values without requiring malloced memory. */ +#define DEFAULT_MAX_VALUES 8 + +/* We allocate this many new array argspec elements each time. */ +#define ARGSPECS_BUMP_VALUE 10 + +/* Special values for the field width and the precision. */ +#define NO_FIELD_VALUE (-1) +#define STAR_FIELD_VALUE (-2) + +/* Bit valuues used for the conversion flags. */ +#define FLAG_GROUPING 1 +#define FLAG_LEFT_JUST 2 +#define FLAG_PLUS_SIGN 4 +#define FLAG_SPACE_PLUS 8 +#define FLAG_ALT_CONV 16 +#define FLAG_ZERO_PAD 32 + +/* Constants used the length modifiers. */ +typedef enum + { + LENMOD_NONE = 0, + LENMOD_CHAR, /* "hh" */ + LENMOD_SHORT, /* "h" */ + LENMOD_LONG, /* "l" */ + LENMOD_LONGLONG, /* "ll" */ + LENMOD_INTMAX, /* "j" */ + LENMOD_SIZET, /* "z" */ + LENMOD_PTRDIFF, /* "t" */ + LENMOD_LONGDBL /* "L" */ + } lenmod_t; + +/* All the conversion specifiers. */ +typedef enum + { + CONSPEC_UNKNOWN = 0, + CONSPEC_DECIMAL, + CONSPEC_OCTAL, + CONSPEC_UNSIGNED, + CONSPEC_HEX, + CONSPEC_HEX_UP, + CONSPEC_FLOAT, + CONSPEC_FLOAT_UP, + CONSPEC_EXP, + CONSPEC_EXP_UP, + CONSPEC_F_OR_G, + CONSPEC_F_OR_G_UP, + CONSPEC_HEX_EXP, + CONSPEC_HEX_EXP_UP, + CONSPEC_CHAR, + CONSPEC_STRING, + CONSPEC_POINTER, + CONSPEC_STRERROR, + CONSPEC_BYTES_SO_FAR + } conspec_t; + + +/* Constants describing all the suppoorted types. Note that we list + all the types we know about even if certain types are not available + on this system. */ +typedef enum + { + VALTYPE_UNSUPPORTED = 0, /* Artificial type for error detection. */ + VALTYPE_CHAR, + VALTYPE_SCHAR, + VALTYPE_UCHAR, + VALTYPE_SHORT, + VALTYPE_USHORT, + VALTYPE_INT, + VALTYPE_UINT, + VALTYPE_LONG, + VALTYPE_ULONG, + VALTYPE_LONGLONG, + VALTYPE_ULONGLONG, + VALTYPE_DOUBLE, + VALTYPE_LONGDOUBLE, + VALTYPE_STRING, + VALTYPE_INTMAX, + VALTYPE_UINTMAX, + VALTYPE_SIZE, + VALTYPE_PTRDIFF, + VALTYPE_POINTER, + VALTYPE_CHAR_PTR, + VALTYPE_SCHAR_PTR, + VALTYPE_SHORT_PTR, + VALTYPE_INT_PTR, + VALTYPE_LONG_PTR, + VALTYPE_LONGLONG_PTR, + VALTYPE_INTMAX_PTR, + VALTYPE_SIZE_PTR, + VALTYPE_PTRDIFF_PTR + } valtype_t; + + +/* A union used to store the actual values. */ +typedef union +{ + char a_char; + signed char a_schar; + unsigned char a_uchar; + short a_short; + unsigned short a_ushort; + int a_int; + unsigned int a_uint; + long int a_long; + unsigned long int a_ulong; +#ifdef HAVE_LONG_LONG_INT + long long int a_longlong; + unsigned long long int a_ulonglong; +#endif + double a_double; +#ifdef HAVE_LONG_DOUBLE + long double a_longdouble; +#endif + const char *a_string; +#ifdef HAVE_INTMAX_T + intmax_t a_intmax; +#endif +#ifdef HAVE_UINTMAX_T + intmax_t a_uintmax; +#endif + size_t a_size; +#ifdef HAVE_PTRDIFF_T + ptrdiff_t a_ptrdiff; +#endif + void *a_void_ptr; + char *a_char_ptr; + signed char *a_schar_ptr; + short *a_short_ptr; + int *a_int_ptr; + long *a_long_ptr; +#ifdef HAVE_LONG_LONG_INT + long long int *a_longlong_ptr; +#endif +#ifdef HAVE_INTMAX_T + intmax_t *a_intmax_ptr; +#endif + size_t *a_size_ptr; +#ifdef HAVE_PTRDIFF_T + ptrdiff_t *a_ptrdiff_ptr; +#endif +} value_t; + +/* An object used to keep track of a format option and arguments. */ +struct argspec_s +{ + size_t length; /* The length of these args including the percent. */ + unsigned int flags; /* The conversion flags (bits defined by FLAG_foo). */ + int width; /* The field width. */ + int precision; /* The precision. */ + lenmod_t lenmod; /* The length modifier. */ + conspec_t conspec; /* The conversion specifier. */ + int arg_pos; /* The position of the argument. This one may + be -1 to indicate that no value is expected + (e.g. for "%m"). */ + int width_pos; /* The position of the argument for a field + width star's value. 0 for not used. */ + int precision_pos; /* The position of the argument for the a + precision star's value. 0 for not used. */ + valtype_t vt; /* The type of the corresponding argument. */ +}; +typedef struct argspec_s *argspec_t; + +/* An object to build up a table of values and their types. */ +struct valueitem_s +{ + valtype_t vt; /* The type of the value. */ + value_t value; /* The value. */ +}; +typedef struct valueitem_s *valueitem_t; + + +#ifdef TEST +static int verbose; + +static void +dump_argspecs (argspec_t arg, size_t argcount) +{ + int idx; + + for (idx=0; argcount; argcount--, arg++, idx++) + fprintf (stderr, + "%2d: len=%u flags=%u width=%d prec=%d mod=%d " + "con=%d vt=%d pos=%d-%d-%d\n", + idx, + (unsigned int)arg->length, + arg->flags, + arg->width, + arg->precision, + arg->lenmod, + arg->conspec, + arg->vt, + arg->arg_pos, + arg->width_pos, + arg->precision_pos); +} +#endif /*TEST*/ + + +/* Set the vt field for ARG. */ +static void +compute_type (argspec_t arg) +{ + switch (arg->conspec) + { + case CONSPEC_UNKNOWN: + arg->vt = VALTYPE_UNSUPPORTED; + break; + + case CONSPEC_DECIMAL: + switch (arg->lenmod) + { + case LENMOD_CHAR: arg->vt = VALTYPE_SCHAR; break; + case LENMOD_SHORT: arg->vt = VALTYPE_SHORT; break; + case LENMOD_LONG: arg->vt = VALTYPE_LONG; break; + case LENMOD_LONGLONG: arg->vt = VALTYPE_LONGLONG; break; + case LENMOD_INTMAX: arg->vt = VALTYPE_INTMAX; break; + case LENMOD_SIZET: arg->vt = VALTYPE_SIZE; break; + case LENMOD_PTRDIFF: arg->vt = VALTYPE_PTRDIFF; break; + default: arg->vt = VALTYPE_INT; break; + } + break; + + case CONSPEC_OCTAL: + case CONSPEC_UNSIGNED: + case CONSPEC_HEX: + case CONSPEC_HEX_UP: + switch (arg->lenmod) + { + case LENMOD_CHAR: arg->vt = VALTYPE_UCHAR; break; + case LENMOD_SHORT: arg->vt = VALTYPE_USHORT; break; + case LENMOD_LONG: arg->vt = VALTYPE_ULONG; break; + case LENMOD_LONGLONG: arg->vt = VALTYPE_ULONGLONG; break; + case LENMOD_INTMAX: arg->vt = VALTYPE_UINTMAX; break; + case LENMOD_SIZET: arg->vt = VALTYPE_SIZE; break; + case LENMOD_PTRDIFF: arg->vt = VALTYPE_PTRDIFF; break; + default: arg->vt = VALTYPE_UINT; break; + } + break; + + case CONSPEC_FLOAT: + case CONSPEC_FLOAT_UP: + case CONSPEC_EXP: + case CONSPEC_EXP_UP: + case CONSPEC_F_OR_G: + case CONSPEC_F_OR_G_UP: + case CONSPEC_HEX_EXP: + case CONSPEC_HEX_EXP_UP: + switch (arg->lenmod) + { + case LENMOD_LONGDBL: arg->vt = VALTYPE_LONGDOUBLE; break; + case LENMOD_LONG: arg->vt = VALTYPE_DOUBLE; break; + default: arg->vt = VALTYPE_DOUBLE; break; + } + break; + + case CONSPEC_CHAR: + arg->vt = VALTYPE_INT; + break; + + case CONSPEC_STRING: + arg->vt = VALTYPE_STRING; + break; + + case CONSPEC_POINTER: + arg->vt = VALTYPE_POINTER; + break; + + case CONSPEC_STRERROR: + arg->vt = VALTYPE_STRING; + break; + + case CONSPEC_BYTES_SO_FAR: + switch (arg->lenmod) + { + case LENMOD_CHAR: arg->vt = VALTYPE_SCHAR_PTR; break; + case LENMOD_SHORT: arg->vt = VALTYPE_SHORT_PTR; break; + case LENMOD_LONG: arg->vt = VALTYPE_LONG_PTR; break; + case LENMOD_LONGLONG: arg->vt = VALTYPE_LONGLONG_PTR; break; + case LENMOD_INTMAX: arg->vt = VALTYPE_INTMAX_PTR; break; + case LENMOD_SIZET: arg->vt = VALTYPE_SIZE_PTR; break; + case LENMOD_PTRDIFF: arg->vt = VALTYPE_PTRDIFF_PTR; break; + default: arg->vt = VALTYPE_INT_PTR; break; + } + break; + + } +} + + + +/* Parse the FORMAT string and populate the specification array stored + at the address ARGSPECS_ADDR. The caller has provided enough space + to store up to MAX_ARGSPECS in that buffer. The function may + however ignore the provided buffer and malloc a larger one. On + success the addrrss of that larger buffer will be stored at + ARGSPECS_ADDR. The actual number of specifications will be + returned at R_ARGSPECS_COUNT. */ +static int +parse_format (const char *format, + argspec_t *argspecs_addr, size_t max_argspecs, + size_t *r_argspecs_count) +{ + const char *s; + argspec_t argspecs = *argspecs_addr; + argspec_t arg; + size_t argcount = 0; + + if (!format) + goto leave_einval; + + for (; *format; format++) + { + unsigned int flags; + int width, precision; + lenmod_t lenmod; + conspec_t conspec; + int arg_pos, width_pos, precision_pos; + + if (*format != '%') + continue; + s = ++format; + if (!*s) + goto leave_einval; + if (*s == '%') + continue; /* Just a quoted percent. */ + + /* First check whether there is a positional argument. */ + arg_pos = 0; /* No positional argument given. */ + if (*s >= '1' && *s <= '9') + { + const char *save_s = s; + + arg_pos = (*s++ - '0'); + for (; *s >= '0' && *s <= '9'; s++) + arg_pos = 10*arg_pos + (*s - '0'); + if (arg_pos < 0) + goto leave_einval; /* Overflow during conversion. */ + if (*s == '$') + s++; + else + { + arg_pos = 0; + s = save_s; + } + } + + /* Parse the flags. */ + flags = 0; + for ( ; *s; s++) + { + switch (*s) + { + case '\'': flags |= FLAG_GROUPING; break; + case '-': flags |= FLAG_LEFT_JUST; break; + case '+': flags |= FLAG_PLUS_SIGN; break; + case ' ': flags |= FLAG_SPACE_PLUS; break; + case '#': flags |= FLAG_ALT_CONV; break; + case '0': flags |= FLAG_ZERO_PAD; break; + default: + goto flags_parsed; + } + } + flags_parsed: + + /* Parse the field width. */ + width_pos = 0; + if (*s == '*') + { + width = STAR_FIELD_VALUE; + s++; + /* If we have a positional argument, another one might also + be used to give the position of the star's value. */ + if (arg_pos && *s >= '1' && *s <= '9') + { + width_pos = (*s++ - '0'); + for (; *s >= '0' && *s <= '9'; s++) + width_pos = 10*width_pos + (*s - '0'); + if (width_pos < 1) + goto leave_einval; /* Overflow during conversion. */ + if (*s != '$') + goto leave_einval; /* Not followed by $. */ + s++; + } + } + else if ( *s >= '0' && *s <= '9') + { + width = (*s++ - '0'); + for (; *s >= '0' && *s <= '9'; s++) + { + if (!width && *s == '0') + goto leave_einval; /* Leading zeroes are not allowed. + Fixme: check what other + implementations do. */ + width = 10*width + (*s - '0'); + } + if (width < 0) + goto leave_einval; /* Overflow during conversion. */ + } + else + width = NO_FIELD_VALUE; + + /* Parse the precision. */ + precision_pos = 0; + precision = NO_FIELD_VALUE; + if (*s == '.') + { + int ignore_value = (s[1] == '-'); + + s++; + if (*s == '*') + { + precision = STAR_FIELD_VALUE; + s++; + /* If we have a positional argument, another one might also + be used to give the position of the star's value. */ + if (arg_pos && *s >= '1' && *s <= '9') + { + precision_pos = (*s++ - '0'); + for (; *s >= '0' && *s <= '9'; s++) + precision_pos = 10*precision_pos + (*s - '0'); + if (precision_pos < 1) + goto leave_einval; /* Overflow during conversion. */ + if (*s != '$') + goto leave_einval; /* Not followed by $. */ + s++; + } + } + else if ( *s >= '0' && *s <= '9') + { + precision = (*s++ - '0'); + for (; *s >= '0' && *s <= '9'; s++) + { + if (!precision && *s == '0') + goto leave_einval; /* Leading zeroes are not allowed. + Fixme: check what other + implementations do. */ + precision = 10*precision + (*s - '0'); + } + if (precision < 0) + goto leave_einval; /* Overflow during conversion. */ + } + else + precision = 0; + if (ignore_value) + precision = NO_FIELD_VALUE; + } + + /* Parse the length modifiers. */ + switch (*s) + { + case 'h': + if (s[1] == 'h') + { + lenmod = LENMOD_CHAR; + s++; + } + else + lenmod = LENMOD_SHORT; + s++; + break; + case 'l': + if (s[1] == 'l') + { + lenmod = LENMOD_LONGLONG; + s++; + } + else + lenmod = LENMOD_LONG; + s++; + break; + case 'j': lenmod = LENMOD_INTMAX; s++; break; + case 'z': lenmod = LENMOD_SIZET; s++; break; + case 't': lenmod = LENMOD_PTRDIFF; s++; break; + case 'L': lenmod = LENMOD_LONGDBL; s++; break; + default: lenmod = LENMOD_NONE; break; + } + + /* Parse the conversion specifier. */ + switch (*s) + { + case 'd': + case 'i': conspec = CONSPEC_DECIMAL; break; + case 'o': conspec = CONSPEC_OCTAL; break; + case 'u': conspec = CONSPEC_UNSIGNED; break; + case 'x': conspec = CONSPEC_HEX; break; + case 'X': conspec = CONSPEC_HEX_UP; break; + case 'f': conspec = CONSPEC_FLOAT; break; + case 'F': conspec = CONSPEC_FLOAT_UP; break; + case 'e': conspec = CONSPEC_EXP; break; + case 'E': conspec = CONSPEC_EXP_UP; break; + case 'g': conspec = CONSPEC_F_OR_G; break; + case 'G': conspec = CONSPEC_F_OR_G_UP; break; + case 'a': conspec = CONSPEC_HEX_EXP; break; + case 'A': conspec = CONSPEC_HEX_EXP_UP; break; + case 'c': conspec = CONSPEC_CHAR; break; + case 's': conspec = CONSPEC_STRING; break; + case 'p': conspec = CONSPEC_POINTER; break; + case 'n': conspec = CONSPEC_BYTES_SO_FAR; break; + case 'C': conspec = CONSPEC_CHAR; lenmod = LENMOD_LONG; break; + case 'S': conspec = CONSPEC_STRING; lenmod = LENMOD_LONG; break; + case 'm': conspec = CONSPEC_STRERROR; arg_pos = -1; break; + default: conspec = CONSPEC_UNKNOWN; + } + + /* Save the args. */ + if (argcount >= max_argspecs) + { + /* We either need to allocate a new array instead of the + caller provided one or realloc the array. Instead of + using realloc we allocate a new one and release the + original one then. */ + size_t n, newmax; + argspec_t newarg; + + newmax = max_argspecs + ARGSPECS_BUMP_VALUE; + if (newmax <= max_argspecs) + goto leave_einval; /* Too many arguments. */ + newarg = calloc (newmax, sizeof *newarg); + if (!newarg) + goto leave; + for (n=0; n < argcount; n++) + newarg[n] = argspecs[n]; + if (argspecs != *argspecs_addr) + free (argspecs); + argspecs = newarg; + max_argspecs = newmax; + } + + arg = argspecs + argcount; + arg->length = s - format + 2; + arg->flags = flags; + arg->width = width; + arg->precision = precision; + arg->lenmod = lenmod; + arg->conspec = conspec; + arg->arg_pos = arg_pos; + arg->width_pos = width_pos; + arg->precision_pos = precision_pos; + compute_type (arg); + argcount++; + format = s; + } + + *argspecs_addr = argspecs; + *r_argspecs_count = argcount; + return 0; /* Success. */ + + leave_einval: + errno = EINVAL; + leave: + if (argspecs != *argspecs_addr) + free (argspecs); + *argspecs_addr = NULL; + return -1; +} + + +/* This function reads all the values as specified by VALUETABLE into + VALUETABLE. The values are expected in VAARGS. The function + returns -1 if a specified type is not supported. */ +static int +read_values (valueitem_t valuetable, size_t valuetable_len, va_list vaargs) +{ + int validx; + + for (validx=0; validx < valuetable_len; validx++) + { + value_t *value = &valuetable[validx].value; + valtype_t vt = valuetable[validx].vt; + + switch (vt) + { + case VALTYPE_CHAR: value->a_char = va_arg (vaargs, int); break; + case VALTYPE_CHAR_PTR: + value->a_char_ptr = va_arg (vaargs, char *); + break; + case VALTYPE_SCHAR: value->a_schar = va_arg (vaargs, int); break; + case VALTYPE_SCHAR_PTR: + value->a_schar_ptr = va_arg (vaargs, signed char *); + break; + case VALTYPE_UCHAR: value->a_uchar = va_arg (vaargs, int); break; + case VALTYPE_SHORT: value->a_short = va_arg (vaargs, int); break; + case VALTYPE_USHORT: value->a_ushort = va_arg (vaargs, int); break; + case VALTYPE_SHORT_PTR: + value->a_short_ptr = va_arg (vaargs, short *); + break; + case VALTYPE_INT: + value->a_int = va_arg (vaargs, int); + break; + case VALTYPE_INT_PTR: + value->a_int_ptr = va_arg (vaargs, int *); + break; + case VALTYPE_UINT: + value->a_uint = va_arg (vaargs, unsigned int); + break; + case VALTYPE_LONG: + value->a_long = va_arg (vaargs, long); + break; + case VALTYPE_ULONG: + value->a_ulong = va_arg (vaargs, unsigned long); + break; + case VALTYPE_LONG_PTR: + value->a_long_ptr = va_arg (vaargs, long *); + break; +#ifdef HAVE_LONG_LONG_INT + case VALTYPE_LONGLONG: + value->a_longlong = va_arg (vaargs, long long int); + break; + case VALTYPE_ULONGLONG: + value->a_ulonglong = va_arg (vaargs, unsigned long long int); + break; + case VALTYPE_LONGLONG_PTR: + value->a_longlong_ptr = va_arg (vaargs, long long *); + break; +#endif + case VALTYPE_DOUBLE: + value->a_double = va_arg (vaargs, double); + break; +#ifdef HAVE_LONG_DOUBLE + case VALTYPE_LONGDOUBLE: + value->a_longdouble = va_arg (vaargs, long double); + break; +#endif + case VALTYPE_STRING: + value->a_string = va_arg (vaargs, const char *); + break; + case VALTYPE_POINTER: + value->a_void_ptr = va_arg (vaargs, void *); + break; +#ifdef HAVE_INTMAX_T + case VALTYPE_INTMAX: + value->a_intmax = va_arg (vaargs, intmax_t); + break; + case VALTYPE_INTMAX_PTR: + value->a_intmax_ptr = va_arg (vaargs, intmax_t *); + break; +#endif +#ifdef HAVE_UINTMAX_T + case VALTYPE_UINTMAX: + value->a_uintmax = va_arg (vaargs, uintmax_t); + break; +#endif + case VALTYPE_SIZE: + value->a_size = va_arg (vaargs, size_t); + break; + case VALTYPE_SIZE_PTR: + value->a_size_ptr = va_arg (vaargs, size_t *); + break; +#ifdef HAVE_PTRDIFF_T + case VALTYPE_PTRDIFF: + value->a_ptrdiff = va_arg (vaargs, ptrdiff_t); + break; + case VALTYPE_PTRDIFF_PTR: + value->a_ptrdiff_ptr = va_arg (vaargs, ptrdiff_t *); + break; +#endif + default: /* Unsupported type. */ + return -1; + } + } + return 0; +} + + + +/* Output COUNT padding characters PADCHAR and update NBYTES by the + number of bytes actually written. */ +static int +pad_out (estream_printf_out_t outfnc, void *outfncarg, + int padchar, int count, size_t *nbytes) +{ + char buf[32]; + size_t n; + int rc; + + while (count > 0) + { + n = (count <= sizeof buf)? count : sizeof buf; + memset (buf, padchar, n); + rc = outfnc (outfncarg, buf, n); + if (rc) + return rc; + *nbytes += n; + count -= n; + } + + return 0; +} + + +/* "d,i,o,u,x,X" formatting. OUTFNC and OUTFNCARG describes the + output routine, ARG gives the argument description and VALUE the + actual value (its type is available through arg->vt). */ +static int +pr_integer (estream_printf_out_t outfnc, void *outfncarg, + argspec_t arg, value_t value, size_t *nbytes) +{ + int rc; +#ifdef HAVE_LONG_LONG_INT + unsigned long long aulong; +#else + unsigned long aulong; +#endif + char numbuf[100]; + char *p, *pend; + size_t n; + char signchar = 0; + int n_prec; /* Number of extra precision digits required. */ + int n_extra; /* Extra number of prefix or sign characters. */ + + if (arg->conspec == CONSPEC_DECIMAL) + { +#ifdef HAVE_LONG_LONG_INT + long long along; +#else + long along; +#endif + + switch (arg->vt) + { + case VALTYPE_SHORT: along = value.a_short; break; + case VALTYPE_INT: along = value.a_int; break; + case VALTYPE_LONG: along = value.a_long; break; +#ifdef HAVE_LONG_LONG_INT + case VALTYPE_LONGLONG: along = value.a_longlong; break; + case VALTYPE_SIZE: along = value.a_size; break; +# ifdef HAVE_INTMAX_T + case VALTYPE_INTMAX: along = value.a_intmax; break; +# endif +# ifdef HAVE_PTRDIFF_T + case VALTYPE_PTRDIFF: along = value.a_ptrdiff; break; +# endif +#endif /*HAVE_LONG_LONG_INT*/ + default: + return -1; + } + if (along < 0) + { + aulong = -along; + signchar = '-'; + } + else + aulong = along; + } + else + { + switch (arg->vt) + { + case VALTYPE_USHORT: aulong = value.a_ushort; break; + case VALTYPE_UINT: aulong = value.a_uint; break; + case VALTYPE_ULONG: aulong = value.a_ulong; break; +#ifdef HAVE_LONG_LONG_INT + case VALTYPE_ULONGLONG: aulong = value.a_ulonglong; break; + case VALTYPE_SIZE: aulong = value.a_size; break; +# ifdef HAVE_UINTMAX_T + case VALTYPE_UINTMAX: aulong = value.a_uintmax; break; +# endif +# ifdef HAVE_PTRDIFF_T + case VALTYPE_PTRDIFF: aulong = value.a_ptrdiff; break; +# endif +#endif /*HAVE_LONG_LONG_INT*/ + default: + return -1; + } + } + + if (signchar == '-') + ; + else if ((arg->flags & FLAG_PLUS_SIGN)) + signchar = '+'; + else if ((arg->flags & FLAG_SPACE_PLUS)) + signchar = ' '; + + n_extra = !!signchar; + + /* We build the string up backwards. */ + p = pend = numbuf + DIM(numbuf); + if ((!aulong && !arg->precision)) + ; + else if (arg->conspec == CONSPEC_DECIMAL + || arg->conspec == CONSPEC_UNSIGNED) + { + int grouping = -1; + const char * grouping_string = +#ifdef HAVE_LANGINFO_THOUSANDS_SEP + nl_langinfo(THOUSANDS_SEP); +#else + "'"; +#endif + + do + { + if ((arg->flags & FLAG_GROUPING) + && (++grouping == 3) && *grouping_string) + { + *--p = *grouping_string; + grouping = 0; + } + *--p = '0' + (aulong % 10); + aulong /= 10; + } + while (aulong); + } + else if (arg->conspec == CONSPEC_OCTAL) + { + do + { + *--p = '0' + (aulong % 8); + aulong /= 8; + } + while (aulong); + if ((arg->flags & FLAG_ALT_CONV) && *p != '0') + *--p = '0'; + } + else /* HEX or HEXUP */ + { + const char *digits = ((arg->conspec == CONSPEC_HEX) + ? "0123456789abcdef" : "0123456789ABCDEF"); + do + { + *--p = digits[(aulong % 16)]; + aulong /= 16; + } + while (aulong); + if ((arg->flags & FLAG_ALT_CONV)) + n_extra += 2; + } + + n = pend - p; + + if ((arg->flags & FLAG_ZERO_PAD) + && arg->precision == NO_FIELD_VALUE && !(arg->flags & FLAG_LEFT_JUST) + && n && arg->width - n_extra > n ) + n_prec = arg->width - n_extra - n; + else if (arg->precision > 0 && arg->precision > n) + n_prec = arg->precision - n; + else + n_prec = 0; + + if (!(arg->flags & FLAG_LEFT_JUST) + && arg->width >= 0 && arg->width - n_extra > n + && arg->width - n_extra - n >= n_prec ) + { + rc = pad_out (outfnc, outfncarg, ' ', + arg->width - n_extra - n - n_prec, nbytes); + if (rc) + return rc; + } + + if (signchar) + { + rc = outfnc (outfncarg, &signchar, 1); + if (rc) + return rc; + *nbytes += 1; + } + + if ((arg->flags & FLAG_ALT_CONV) + && (arg->conspec == CONSPEC_HEX || arg->conspec == CONSPEC_HEX_UP)) + { + rc = outfnc (outfncarg, arg->conspec == CONSPEC_HEX? "0x": "0X", 2); + if (rc) + return rc; + *nbytes += 2; + } + + if (n_prec) + { + rc = pad_out (outfnc, outfncarg, '0', n_prec, nbytes); + if (rc) + return rc; + } + + rc = outfnc (outfncarg, p, pend - p); + if (rc) + return rc; + *nbytes += pend - p; + + if ((arg->flags & FLAG_LEFT_JUST) + && arg->width >= 0 && arg->width - n_extra - n_prec > n) + { + rc = pad_out (outfnc, outfncarg, ' ', + arg->width - n_extra - n_prec - n, nbytes); + if (rc) + return rc; + } + + return 0; +} + + +/* "e,E,f,F,g,G,a,A" formatting. OUTFNC and OUTFNCARG describes the + output routine, ARG gives the argument description and VALUE the + actual value (its type is available through arg->vt). For + portability reasons sprintf is used for the actual formatting. + This is useful because sprint is the only standard function to + convert a floating number into its ascii representation. To avoid + using malloc we just pass the precision to sprintf and do the final + formatting with our own code. */ +static int +pr_float (estream_printf_out_t outfnc, void *outfncarg, + argspec_t arg, value_t value, size_t *nbytes) +{ + int rc; +#ifdef HAVE_LONG_DOUBLE + long double adblfloat = 0; /* Just to please gcc. */ + int use_dbl = 0; +#endif + double afloat; + char numbuf[350]; + char formatstr[20]; + char *p, *pend; + size_t n; + char signchar = 0; + int n_extra; /* Extra number of prefix or sign characters. */ + + switch (arg->vt) + { + case VALTYPE_DOUBLE: afloat = value.a_double; break; +#ifdef HAVE_LONG_DOUBLE + case VALTYPE_LONGDOUBLE: + afloat = 0; /* Just to please gcc. */ + adblfloat = value.a_longdouble; + use_dbl=1; break; +#endif + default: + return -1; + } + + /* We build the string using sprint. */ + p = formatstr + sizeof formatstr; + *--p = 0; + switch (arg->conspec) + { + case CONSPEC_FLOAT: *--p = 'f'; break; + case CONSPEC_FLOAT_UP: *--p = 'F'; break; + case CONSPEC_EXP: *--p = 'e'; break; + case CONSPEC_EXP_UP: *--p = 'E'; break; + case CONSPEC_F_OR_G: *--p = 'g'; break; + case CONSPEC_F_OR_G_UP: *--p = 'G'; break; + case CONSPEC_HEX_EXP: *--p = 'a'; break; + case CONSPEC_HEX_EXP_UP: *--p = 'A'; break; + default: + return -1; /* Actually a bug. */ + } +#ifdef HAVE_LONG_DOUBLE + if (use_dbl) + *--p = 'L'; +#endif + if (arg->precision != NO_FIELD_VALUE) + { + /* Limit it to a meaningful value so that even a stupid sprintf + won't overflow our buffer. */ + n = arg->precision <= 100? arg->precision : 100; + do + { + *--p = '0' + (n % 10); + n /= 10; + } + while (n); + *--p = '.'; + } + if ((arg->flags & FLAG_ALT_CONV)) + *--p = '#'; + *--p = '%'; +#ifdef HAVE_LONG_DOUBLE + if (use_dbl) + sprintf (numbuf, p, adblfloat); + else +#endif /*HAVE_LONG_DOUBLE*/ + sprintf (numbuf, p, afloat); + p = numbuf; + n = strlen (numbuf); + pend = p + n; + + if (*p =='-') + { + signchar = '-'; + p++; + n--; + } + else if ((arg->flags & FLAG_PLUS_SIGN)) + signchar = '+'; + else if ((arg->flags & FLAG_SPACE_PLUS)) + signchar = ' '; + + n_extra = !!signchar; + + if (!(arg->flags & FLAG_LEFT_JUST) + && arg->width >= 0 && arg->width - n_extra > n) + { + rc = pad_out (outfnc, outfncarg, ' ', arg->width - n_extra - n, nbytes); + if (rc) + return rc; + } + + if (signchar) + { + rc = outfnc (outfncarg, &signchar, 1); + if (rc) + return rc; + *nbytes += 1; + } + + rc = outfnc (outfncarg, p, pend - p); + if (rc) + return rc; + *nbytes += pend - p; + + if ((arg->flags & FLAG_LEFT_JUST) + && arg->width >= 0 && arg->width - n_extra > n) + { + rc = pad_out (outfnc, outfncarg, ' ', arg->width - n_extra - n, nbytes); + if (rc) + return rc; + } + + return 0; +} + + +/* "c" formatting. */ +static int +pr_char (estream_printf_out_t outfnc, void *outfncarg, + argspec_t arg, value_t value, size_t *nbytes) +{ + int rc; + char buf[1]; + + if (arg->vt != VALTYPE_INT) + return -1; + buf[0] = (unsigned int)value.a_int; + rc = outfnc (outfncarg, buf, 1); + if(rc) + return rc; + *nbytes += 1; + + return 0; +} + + +/* "s" formatting. */ +static int +pr_string (estream_printf_out_t outfnc, void *outfncarg, + argspec_t arg, value_t value, size_t *nbytes) +{ + int rc; + size_t n; + const char *string, *s; + + if (arg->vt != VALTYPE_STRING) + return -1; + string = value.a_string; + if (!string) + string = "(null)"; + if (arg->precision >= 0) + { + for (n=0,s=string; *s && n < arg->precision; s++) + n++; + } + else + n = strlen (string); + + if (!(arg->flags & FLAG_LEFT_JUST) + && arg->width >= 0 && arg->width > n ) + { + rc = pad_out (outfnc, outfncarg, ' ', arg->width - n, nbytes); + if (rc) + return rc; + } + + rc = outfnc (outfncarg, string, n); + if (rc) + return rc; + *nbytes += n; + + if ((arg->flags & FLAG_LEFT_JUST) + && arg->width >= 0 && arg->width > n) + { + rc = pad_out (outfnc, outfncarg, ' ', arg->width - n, nbytes); + if (rc) + return rc; + } + + return 0; +} + + +/* "p" formatting. */ +static int +pr_pointer (estream_printf_out_t outfnc, void *outfncarg, + argspec_t arg, value_t value, size_t *nbytes) +{ + int rc; +#ifdef HAVE_LONG_LONG_INT + unsigned long long aulong; +#else + unsigned long aulong; +#endif + char numbuf[100]; + char *p, *pend; + + if (arg->vt != VALTYPE_POINTER) + return -1; + /* We assume that a pointer can be converted to an unsigned long. + That is not correct for a 64 bit Windows, but then we assume that + long long is supported and usable for storing a pointer. */ +#if defined(HAVE_LONG_LONG_INT) && (SIZEOF_UNSIGNED_LONG < SIZEOF_VOID_P) + aulong = (unsigned long long)value.a_void_ptr; +#else + aulong = (unsigned long)value.a_void_ptr; +#endif + + p = pend = numbuf + DIM(numbuf); + do + { + *--p = "0123456789abcdefx"[(aulong % 16)]; + aulong /= 16; + } + while (aulong); + while ((pend-p) < 2*sizeof (aulong)) + *--p = '0'; + *--p = 'x'; + *--p = '0'; + + rc = outfnc (outfncarg, p, pend - p); + if (rc) + return rc; + *nbytes += pend - p; + + return 0; +} + +/* "n" pesudo format operation. */ +static int +pr_bytes_so_far (estream_printf_out_t outfnc, void *outfncarg, + argspec_t arg, value_t value, size_t *nbytes) +{ + (void)outfnc; + (void)outfncarg; + + switch (arg->vt) + { + case VALTYPE_SCHAR_PTR: + *value.a_schar_ptr = (signed char)(unsigned int)(*nbytes); + break; + case VALTYPE_SHORT_PTR: + *value.a_short_ptr = (short)(unsigned int)(*nbytes); + break; + case VALTYPE_LONG_PTR: + *value.a_long_ptr = (long)(*nbytes); + break; +#ifdef HAVE_LONG_LONG_INT + case VALTYPE_LONGLONG_PTR: + *value.a_longlong_ptr = (long long)(*nbytes); + break; +#endif +#ifdef HAVE_INTMAX_T + case VALTYPE_INTMAX_PTR: + *value.a_intmax_ptr = (intmax_t)(*nbytes); + break; +#endif + case VALTYPE_SIZE_PTR: + *value.a_size_ptr = (*nbytes); + break; +#ifdef HAVE_PTRDIFF_T + case VALTYPE_PTRDIFF_PTR: + *value.a_ptrdiff_ptr = (ptrdiff_t)(*nbytes); + break; +#endif + case VALTYPE_INT_PTR: + *value.a_int_ptr = (int)(*nbytes); + break; + default: + return -1; /* An unsupported type has been used. */ + } + + return 0; +} + + + +/* Run the actual formatting. OUTFNC and OUTFNCARG are the output + functions. FORMAT is format string ARGSPECS is the parsed format + string, ARGSPECS_LEN the number of items in ARGSPECS. VALUETABLE + holds the values and may be directly addressed using the position + arguments given by ARGSPECS. MYERRNO is used for the "%m" + conversion. NBYTES well be updated to reflect the number of bytes + send to the output function. */ +static int +do_format (estream_printf_out_t outfnc, void *outfncarg, + const char *format, argspec_t argspecs, size_t argspecs_len, + valueitem_t valuetable, int myerrno, size_t *nbytes) +{ + int rc = 0; + const char *s; + argspec_t arg = argspecs; + int argidx = 0; /* Only used for assertion. */ + size_t n; + value_t value; + + s = format; + while ( *s ) + { + if (*s != '%') + { + s++; + continue; + } + if (s != format) + { + rc = outfnc (outfncarg, format, (n=s-format)); + if (rc) + return rc; + *nbytes += n; + } + if (s[1] == '%') + { + /* Note that this code ignores one trailing percent escape - + this is however okay as the args parser must have + detected this already. */ + rc = outfnc (outfncarg, s, 1); + if (rc) + return rc; + *nbytes += 1; + s += 2; + format = s; + continue; + } + + /* Save the next start. */ + s += arg->length; + format = s; + + assert (argidx < argspecs_len); + argidx++; + + /* Apply indirect field width and precision values. */ + if (arg->width == STAR_FIELD_VALUE) + { + assert (valuetable[arg->width_pos-1].vt == VALTYPE_INT); + arg->width = valuetable[arg->width_pos-1].value.a_int; + if (arg->width < 0) + { + arg->width = -arg->width; + arg->flags |= FLAG_LEFT_JUST; + } + } + if (arg->precision == STAR_FIELD_VALUE) + { + assert (valuetable[arg->precision_pos-1].vt == VALTYPE_INT); + arg->precision = valuetable[arg->precision_pos-1].value.a_int; + if (arg->precision < 0) + arg->precision = NO_FIELD_VALUE; + } + + if (arg->arg_pos == -1 && arg->conspec == CONSPEC_STRERROR) + value.a_string = strerror (myerrno); + else + { + assert (arg->vt == valuetable[arg->arg_pos-1].vt); + value = valuetable[arg->arg_pos-1].value; + } + + switch (arg->conspec) + { + case CONSPEC_UNKNOWN: assert (!"bug"); break; + + case CONSPEC_DECIMAL: + case CONSPEC_UNSIGNED: + case CONSPEC_OCTAL: + case CONSPEC_HEX: + case CONSPEC_HEX_UP: + rc = pr_integer (outfnc, outfncarg, arg, value, nbytes); + break; + case CONSPEC_FLOAT: + case CONSPEC_FLOAT_UP: + case CONSPEC_EXP: + case CONSPEC_EXP_UP: + case CONSPEC_F_OR_G: + case CONSPEC_F_OR_G_UP: + case CONSPEC_HEX_EXP: + case CONSPEC_HEX_EXP_UP: + rc = pr_float (outfnc, outfncarg, arg, value, nbytes); + break; + case CONSPEC_CHAR: + rc = pr_char (outfnc, outfncarg, arg, value, nbytes); + break; + case CONSPEC_STRING: + case CONSPEC_STRERROR: + rc = pr_string (outfnc, outfncarg, arg, value, nbytes); + break; + case CONSPEC_POINTER: + rc = pr_pointer (outfnc, outfncarg, arg, value, nbytes); + break; + case CONSPEC_BYTES_SO_FAR: + rc = pr_bytes_so_far (outfnc, outfncarg, arg, value, nbytes); + break; + } + if (rc) + return rc; + arg++; + } + + /* Print out any trailing stuff. */ + n = s - format; + rc = n? outfnc (outfncarg, format, n) : 0; + if (!rc) + *nbytes += n; + + return rc; +} + + + + +/* The versatile printf formatting routine. It expects a callback + function OUTFNC and an opaque argument OUTFNCARG used for actual + output of the formatted stuff. FORMAT is the format specification + and VAARGS a variable argumemt list matching the arguments of + FORMAT. */ +int +estream_format (estream_printf_out_t outfnc, + void *outfncarg, + const char *format, va_list vaargs) +{ + /* Buffer to hold the argspecs and a pointer to it.*/ + struct argspec_s argspecs_buffer[DEFAULT_MAX_ARGSPECS]; + argspec_t argspecs = argspecs_buffer; + size_t argspecs_len; /* Number of specifications in ARGSPECS. */ + + /* Buffer to hold the description for the values. */ + struct valueitem_s valuetable_buffer[DEFAULT_MAX_VALUES]; + valueitem_t valuetable = valuetable_buffer; + + int rc; /* Return code. */ + size_t argidx; /* Used to index the argspecs array. */ + size_t validx; /* Used to index the valuetable. */ + int max_pos;/* Highest argument position. */ + + size_t nbytes = 0; /* Keep track of the number of bytes passed to + the output function. */ + + int myerrno = errno; /* Save the errno for use with "%m". */ + + + /* Parse the arguments to come up with descriptive list. We can't + do this on the fly because we need to support positional + arguments. */ + rc = parse_format (format, &argspecs, DIM(argspecs_buffer), &argspecs_len); + if (rc) + goto leave; + + /* Check that all ARG_POS fields are set. */ + for (argidx=0,max_pos=0; argidx < argspecs_len; argidx++) + { + if (argspecs[argidx].arg_pos != -1 + && argspecs[argidx].arg_pos > max_pos) + max_pos = argspecs[argidx].arg_pos; + if (argspecs[argidx].width_pos > max_pos) + max_pos = argspecs[argidx].width_pos; + if (argspecs[argidx].precision_pos > max_pos) + max_pos = argspecs[argidx].precision_pos; + } + if (!max_pos) + { + /* Fill in all the positions. */ + for (argidx=0; argidx < argspecs_len; argidx++) + { + if (argspecs[argidx].width == STAR_FIELD_VALUE) + argspecs[argidx].width_pos = ++max_pos; + if (argspecs[argidx].precision == STAR_FIELD_VALUE) + argspecs[argidx].precision_pos = ++max_pos; + if (argspecs[argidx].arg_pos != -1 ) + argspecs[argidx].arg_pos = ++max_pos; + } + } + else + { + /* Check that they are all filled. More test are done later. */ + for (argidx=0; argidx < argspecs_len; argidx++) + { + if (!argspecs[argidx].arg_pos + || (argspecs[argidx].width == STAR_FIELD_VALUE + && !argspecs[argidx].width_pos) + || (argspecs[argidx].precision == STAR_FIELD_VALUE + && !argspecs[argidx].precision_pos)) + goto leave_einval; + } + } + /* Check that there is no overflow in max_pos and that it has a + reasonable length. There may never be more elements than the + number of characters in FORMAT. */ + if (max_pos < 0 || max_pos >= strlen (format)) + goto leave_einval; + +#ifdef TEST + if (verbose > 1) + dump_argspecs (argspecs, argspecs_len); +#endif + + /* Allocate a table to hold the values. If it is small enough we + use a stack allocated buffer. */ + if (max_pos > DIM(valuetable_buffer)) + { + valuetable = calloc (max_pos, sizeof *valuetable); + if (!valuetable) + goto leave_error; + } + else + { + for (validx=0; validx < DIM(valuetable_buffer); validx++) + valuetable[validx].vt = VALTYPE_UNSUPPORTED; + } + for (argidx=0; argidx < argspecs_len; argidx++) + { + if (argspecs[argidx].arg_pos != - 1) + { + validx = argspecs[argidx].arg_pos - 1; + if (valuetable[validx].vt) + goto leave_einval; /* Already defined. */ + valuetable[validx].vt = argspecs[argidx].vt; + } + if (argspecs[argidx].width == STAR_FIELD_VALUE) + { + validx = argspecs[argidx].width_pos - 1; + if (valuetable[validx].vt) + goto leave_einval; /* Already defined. */ + valuetable[validx].vt = VALTYPE_INT; + } + if (argspecs[argidx].precision == STAR_FIELD_VALUE) + { + validx = argspecs[argidx].precision_pos - 1; + if (valuetable[validx].vt) + goto leave_einval; /* Already defined. */ + valuetable[validx].vt = VALTYPE_INT; + } + } + + /* Read all the arguments. This will error out for unsupported + types and for not given positional arguments. */ + rc = read_values (valuetable, max_pos, vaargs); + if (rc) + goto leave_einval; + +/* for (validx=0; validx < max_pos; validx++) */ +/* fprintf (stderr, "%2d: vt=%d\n", validx, valuetable[validx].vt); */ + + /* Everything has been collected, go ahead with the formatting. */ + rc = do_format (outfnc, outfncarg, format, + argspecs, argspecs_len, valuetable, myerrno, &nbytes); + + goto leave; + + leave_einval: + errno = EINVAL; + leave_error: + rc = -1; + leave: + if (valuetable != valuetable_buffer) + free (valuetable); + if (argspecs != argspecs_buffer) + free (argspecs); + return rc; +} + + + + +/* A simple output handler utilizing stdio. */ +static int +plain_stdio_out (void *outfncarg, const char *buf, size_t buflen) +{ + FILE *fp = (FILE*)outfncarg; + + if ( fwrite (buf, buflen, 1, fp) != 1 ) + return -1; + return 0; +} + + +/* A replacement for printf. */ +int +estream_printf (const char *format, ...) +{ + int rc; + va_list arg_ptr; + + va_start (arg_ptr, format); + rc = estream_format (plain_stdio_out, stderr, format, arg_ptr); + va_end (arg_ptr); + + return rc; +} + +/* A replacement for fprintf. */ +int +estream_fprintf (FILE *fp, const char *format, ...) +{ + int rc; + va_list arg_ptr; + + va_start (arg_ptr, format); + rc = estream_format (plain_stdio_out, fp, format, arg_ptr); + va_end (arg_ptr); + + return rc; +} + +/* A replacement for vfprintf. */ +int +estream_vfprintf (FILE *fp, const char *format, va_list arg_ptr) +{ + return estream_format (plain_stdio_out, fp, format, arg_ptr); +} + + + +/* Communication object used between estream_snprintf and + fixed_buffer_out. */ +struct fixed_buffer_parm_s +{ + size_t size; /* Size of the buffer. */ + size_t count; /* Number of bytes requested for output. */ + size_t used; /* Used size of the buffer. */ + char *buffer; /* Provided buffer. */ +}; + +/* A simple malloced buffer output handler. */ +static int +fixed_buffer_out (void *outfncarg, const char *buf, size_t buflen) +{ + struct fixed_buffer_parm_s *parm = outfncarg; + + parm->count += buflen; + + if (!parm->buffer) + ; + else if (parm->used + buflen < parm->size) + { + /* Handle the common case that everything fits into the buffer + separately. */ + memcpy (parm->buffer + parm->used, buf, buflen); + parm->used += buflen; + } + else + { + /* The slow version of above. */ + for ( ;buflen && parm->used < parm->size; buflen--) + parm->buffer[parm->used++] = *buf++; + } + + return 0; +} + + +/* A replacement for vsnprintf. */ +int +estream_vsnprintf (char *buf, size_t bufsize, + const char *format, va_list arg_ptr) +{ + struct fixed_buffer_parm_s parm; + int rc; + + parm.size = bufsize; + parm.count = 0; + parm.used = 0; + parm.buffer = bufsize?buf:NULL; + rc = estream_format (fixed_buffer_out, &parm, format, arg_ptr); + if (!rc) + rc = fixed_buffer_out (&parm, "", 1); /* Print terminating Nul. */ + if (rc == -1) + return -1; + if (bufsize && buf && parm.size && parm.count >= parm.size) + buf[parm.size-1] = 0; + + parm.count--; /* Do not count the trailing nul. */ + return (int)parm.count; /* Return number of bytes which would have + been written. */ +} + +/* A replacement for snprintf. */ +int +estream_snprintf (char *buf, size_t bufsize, const char *format, ...) +{ + int rc; + va_list arg_ptr; + + va_start (arg_ptr, format); + rc = estream_vsnprintf (buf, bufsize, format, arg_ptr); + va_end (arg_ptr); + + return rc; +} + + + +/* Communication object used between estream_asprintf and + dynamic_buffer_out. */ +struct dynamic_buffer_parm_s +{ + int error_flag; /* Internal helper. */ + size_t alloced; /* Allocated size of the buffer. */ + size_t used; /* Used size of the buffer. */ + char *buffer; /* Malloced buffer. */ +}; + +/* A simple malloced buffer output handler. */ +static int +dynamic_buffer_out (void *outfncarg, const char *buf, size_t buflen) +{ + struct dynamic_buffer_parm_s *parm = outfncarg; + + if (parm->error_flag) + { + /* Just in case some formatting routine did not checked for an + error. */ + errno = parm->error_flag; + return -1; + } + + if (parm->used + buflen >= parm->alloced) + { + char *p; + + parm->alloced += buflen + 512; + p = realloc (parm->buffer, parm->alloced); + if (!p) + { + parm->error_flag = errno ? errno : ENOMEM; + /* Wipe out what we already accumulated. This is useful in + case sensitive data is formated. */ + memset (parm->buffer, 0, parm->used); + return -1; + } + parm->buffer = p; + } + memcpy (parm->buffer + parm->used, buf, buflen); + parm->used += buflen; + + return 0; +} + + +/* A replacement for vasprintf. As with the BSD of vasprintf version -1 + will be returned on error and NULL stored at BUFP. On success the + number of bytes printed will be returned. */ +int +estream_vasprintf (char **bufp, const char *format, va_list arg_ptr) +{ + struct dynamic_buffer_parm_s parm; + int rc; + + parm.error_flag = 0; + parm.alloced = 512; + parm.used = 0; + parm.buffer = my_printf_malloc (parm.alloced); + if (!parm.buffer) + { + *bufp = NULL; + return -1; + } + + rc = estream_format (dynamic_buffer_out, &parm, format, arg_ptr); + if (!rc) + rc = dynamic_buffer_out (&parm, "", 1); /* Print terminating Nul. */ + /* Fixme: Should we shrink the resulting buffer? */ + if (rc != -1 && parm.error_flag) + { + rc = -1; + errno = parm.error_flag; + } + if (rc == -1) + { + memset (parm.buffer, 0, parm.used); + my_printf_free (parm.buffer); + *bufp = NULL; + return -1; + } + assert (parm.used); /* We have at least the terminating Nul. */ + *bufp = parm.buffer; + return parm.used - 1; /* Do not include that Nul. */ +} + +/* A replacement for asprintf. As with the BSD of asprintf version -1 + will be returned on error and NULL stored at BUFP. On success the + number of bytes printed will be returned. */ +int +estream_asprintf (char **bufp, const char *format, ...) +{ + int rc; + va_list arg_ptr; + + va_start (arg_ptr, format); + rc = estream_vasprintf (bufp, format, arg_ptr); + va_end (arg_ptr); + + return rc; +} + + +#ifdef TEST + +static int +one_test (const char *format, ...) +{ +#ifdef _WIN32 + { + static int show; + + if (!show) + { + /* We do not have a system vasprintf. */ + printf ("one-test: disabled under W32\n"); + show = 1; + } + } +#else + int rc1, rc2; + va_list arg_ptr; + char *buf1, *buf2; + + if (verbose) + printf ("format: ->%s<-\n", format); + + va_start (arg_ptr, format); + rc1 = vasprintf (&buf1, format, arg_ptr); + va_end (arg_ptr); + if (rc1 == -1) + { + printf (" sys: errno=%d (%s)\n", errno, strerror (errno)); + buf1 = NULL; + } + else if (verbose) + printf (" sys: ->%s<-\n", buf1); + + va_start (arg_ptr, format); + rc2 = estream_vasprintf (&buf2, format, arg_ptr); + va_end (arg_ptr); + if (rc2 == -1) + printf (" our: errno=%d (%s)\n", errno, strerror (errno)); + else if (verbose) + printf (" our: ->%s<-\n", buf2); + + if (rc1 != -1 && rc2 != -1 && strcmp (buf1, buf2)) + printf ("error: output does not match\n" + "format: ->%s<-\n sys: ->%s<-\n our: ->%s<-\n", + format, buf1, buf2); + else if ( rc1 != rc2 ) + printf ("error: return codes are different: sys_rc=%d our_rc=%d\n", + rc1, rc2); + + free (buf2); + free (buf1); +#endif + return 0; +} + + +static void +run_tests (void) +{ + /*one_test ("%d %% %'d", 17, 19681977);*/ + + one_test ("%d %% %d", 17, 768114563); + one_test ("%d %% %d", 17, -768114563); + + one_test ("%d", 17); + one_test ("%4d", 17); + one_test ("%40d", 17); + one_test ("%-d", 17); + one_test ("%-4d", 17); + one_test ("%-140d", 17); + one_test ("%d", -17); + one_test ("%4d", -17); + one_test ("%40d", -17); + one_test ("%-d", -17); + one_test ("%-4d", -17); + one_test ("%-40d", -17); + + one_test ("%+4d", 17); + one_test ("%+4d", -17); + one_test ("%-+4d", 17); + one_test ("%-+4d", -17); + one_test ("% 4d", 17); + one_test ("% 4d", -17); + one_test ("%- +4d", 17); + one_test ("%- +4d", -17); + + one_test ("%.4d", 17); + one_test ("%.0d", 17); + one_test ("%.0d", 0); + one_test ("%.4d", -17); + one_test ("%.0d", -17); + one_test ("%6.4d", 17); + one_test ("%6.4d", -17); + one_test ("%6.0d", 0); + one_test ("%4.6d", 17); + one_test ("%4.6d", -17); + + one_test ("% 4.6d", 17); + one_test ("% 6.0d", 0); + + one_test ("%.4d", 17); + one_test ("%04d", 17); + one_test ("%.4d", -17); + one_test ("%04d", -17); + one_test ("%0.d", 0); + + one_test ("%*d", 7, 42); + one_test ("%*d", -7, 42); + one_test ("%.*d", 7, 42); + one_test ("%.*d", -7, 42); + one_test ("%*.*d", 10, 7, 42); + one_test ("%*.*d", 10, -7, 42); + one_test ("%*.*d", -10, 7, 42); + one_test ("%*.*d", -10, -7, 42); + + one_test ("%*x", 7, 42); + one_test ("%*x", -7, 42); + one_test ("%.*x", 7, 42); + one_test ("%.*x", -7, 42); + one_test ("%*.*x", 10, 7, 42); + one_test ("%*.*x", 10, -7, 42); + one_test ("%*.*x", -10, 7, 42); + one_test ("%*.*x", -10, -7, 42); + one_test ("%#*x", 7, 42); + one_test ("%#*x", -7, 42); + one_test ("%#.*x", 7, 42); + one_test ("%#.*x", -7, 42); + one_test ("%#*.*x", 10, 7, 42); + one_test ("%#*.*x", 10, -7, 42); + one_test ("%#*.*x", -10, 7, 42); + one_test ("%#*.*x", -10, -7, 42); + + one_test ("%*X", 7, 42); + one_test ("%*X", -7, 42); + one_test ("%.*X", 7, 42); + one_test ("%.*X", -7, 42); + one_test ("%*.*X", 10, 7, 42); + one_test ("%*.*X", 10, -7, 42); + one_test ("%*.*X", -10, 7, 42); + one_test ("%*.*X", -10, -7, 42); + one_test ("%#*X", 7, 42); + one_test ("%#*X", -7, 42); + one_test ("%#.*X", 7, 42); + one_test ("%#.*X", -7, 42); + one_test ("%#*.*X", 10, 7, 42); + one_test ("%#*.*X", 10, -7, 42); + one_test ("%#*.*X", -10, 7, 42); + one_test ("%#*.*X", -10, -7, 42); + + one_test ("%*o", 7, 42); + one_test ("%*o", -7, 42); + one_test ("%.*o", 7, 42); + one_test ("%.*o", -7, 42); + one_test ("%*.*o", 10, 7, 42); + one_test ("%*.*o", 10, -7, 42); + one_test ("%*.*o", -10, 7, 42); + one_test ("%*.*o", -10, -7, 42); + one_test ("%#*o", 7, 42); + one_test ("%#*o", -7, 42); + one_test ("%#.*o", 7, 42); + one_test ("%#.*o", -7, 42); + one_test ("%#*.*o", 10, 7, 42); + one_test ("%#*.*o", 10, -7, 42); + one_test ("%#*.*o", -10, 7, 42); + one_test ("%#*.*o", -10, -7, 42); + + one_test ("%s", "the quick brown fox jumps over the lazy dogs back"); + one_test ("%.0s", "the quick brown fox jumps over the lazy dogs back"); + one_test ("%.10s", "the quick brown fox jumps over the lazy dogs back"); + one_test ("%.48s", "the quick brown fox jumps over the lazy dogs back"); + one_test ("%.49s", "the quick brown fox jumps over the lazy dogs back"); + one_test ("%.50s", "the quick brown fox jumps over the lazy dogs back"); + one_test ("%.51s", "the quick brown fox jumps over the lazy dogs back"); + one_test ("%48s", "the quick brown fox jumps over the lazy dogs back"); + one_test ("%49s", "the quick brown fox jumps over the lazy dogs back"); + one_test ("%50s", "the quick brown fox jumps over the lazy dogs back"); + one_test ("%51s", "the quick brown fox jumps over the lazy dogs back"); + one_test ("%-51s", "the quick brown fox jumps over the lazy dogs back"); + + one_test ("/%s=", "CN"); + + one_test ("%f", 3.1415926535); + one_test ("%f", -3.1415926535); + one_test ("%.10f", 3.1415926535); + one_test ("%.2f", 3.1415926535); + one_test ("%.1f", 3.1415926535); + one_test ("%.0f", 3.1415926535); + one_test ("%.20f", 3.1415926535); + one_test ("%10.10f", 3.1415926535); + one_test ("%10.2f", 3.1415926535); + one_test ("%10.1f", 3.1415926535); + one_test ("%10.0f", 3.1415926535); + one_test ("%30.20f", 3.1415926535); + one_test ("%10.10f", -3.1415926535); + one_test ("%10.2f", -3.1415926535); + one_test ("%10.1f", -3.1415926535); + one_test ("%10.0f", -3.1415926535); + one_test ("%30.20f", -3.1415926535); + + one_test ("%-10f", 3.1415926535); + one_test ("%-10.10f", 3.1415926535); + one_test ("%-10.2f", 3.1415926535); + one_test ("%-10.1f", 3.1415926535); + one_test ("%-10.0f", 3.1415926535); + one_test ("%-30.20f", 3.1415926535); + one_test ("%-10f", -3.1415926535); + one_test ("%-10.10f", -3.1415926535); + one_test ("%-10.2f", -3.1415926535); + one_test ("%-10.1f", -3.1415926535); + one_test ("%-10.0f", -3.1415926535); + one_test ("%-30.20f", -3.1415926535); + + one_test ("%#.0f", 3.1415926535); + one_test ("%#10.0f", 3.1415926535); + one_test ("%#10.0f", -3.1415926535); + one_test ("%-#10.0f", 3.1415926535); + one_test ("%-#10.0f", -3.1415926535); + + one_test ("%e", 3.1415926535); + one_test ("%g", 3.1415926535); + + one_test ("%a", 1); + one_test ("%a", -1); + one_test ("%a", 3.1415926535); + +#ifdef HAVE_LONG_DOUBLE + one_test ("%La", 1); + one_test ("%La", -1); + one_test ("%La", 3.1415926535); +#endif + +#ifdef __GLIBC__ + /* "%m" is a glibc extension so this _test_ will only work on such a + system. */ + errno = ENOENT; + one_test ("%m"); + errno = ENOENT; + one_test ("%d=%m", 17); + errno = ENOENT; + one_test ("%2$d:%m:%1$d", 42, 17); +#endif /*__GLIBC__*/ + +} + +static void +check_snprintf (void) +{ + char buffer[20]; + int rc, rc2; + size_t tmplen, blen, blen2; + + rc = estream_snprintf (buffer, 0, "%*s", 18, ""); + if (rc != 18) + printf ("rc=%d\n", rc ); + rc = estream_snprintf (buffer, sizeof buffer, "%*s", 18, ""); + if (rc != 18) + printf ("rc=%d, strlen(buffer)=%d\n", rc, (int)strlen (buffer)); + rc = estream_snprintf (buffer, sizeof buffer, "%*s", 19, ""); + if (rc != 19) + printf ("rc=%d, strlen(buffer)=%d\n", rc, (int)strlen (buffer)); + rc = estream_snprintf (buffer, sizeof buffer, "%*s", 20, ""); + if (rc != 20) + printf ("rc=%d, strlen(buffer)=%d\n", rc, (int)strlen (buffer)); + rc = estream_snprintf (buffer, sizeof buffer, "%*s", 21, ""); + if (rc != 21) + printf ("rc=%d, strlen(buffer)=%d\n", rc, (int)strlen (buffer)); + + for (tmplen = 0; tmplen <= sizeof buffer; tmplen++) + { + rc = estream_snprintf (buffer, tmplen, "%04d%02d%02dT%02d%02d%02d", + 1998, 9, 7, 16, 56, 05); + blen = strlen (buffer); + rc2 = snprintf (buffer, tmplen, "%04d%02d%02dT%02d%02d%02d", + 1998, 9, 7, 16, 56, 05); + blen2 = strlen (buffer); + if (rc != rc2 || blen != blen2) + printf ("snprintf test with len %u gives %d instead of %d (%d,%d)\n", + (unsigned int)tmplen, rc, rc2, blen, blen2); + } +} + + + +int +main (int argc, char **argv) +{ + int rc; + + if (argc) {argc--; argv++; } + + setlocale (LC_NUMERIC, ""); + + while (argc && !strcmp (*argv, "--verbose")) + { + verbose++; + argc--; + argv++; + } + + if (!argc) + { + run_tests (); + check_snprintf () ; + } + else + { + rc = estream_vfprintf (stdout, argv[0], NULL); + fflush (stdout); + fprintf (stderr, "[estream_vfprintf returns: %d]\n", rc); + } + + return 0; +} +#endif /*TEST*/ +/* +Local Variables: +compile-command: "cc -Wall -O3 -g -I.. -DHAVE_CONFIG_H -DTEST -o estream-printf estream-printf.c" +End: +*/ diff --git a/util/memory.c b/util/memory.c index 55ac49e35..79436545b 100644 --- a/util/memory.c +++ b/util/memory.c @@ -62,6 +62,7 @@ #define M_GUARD 1 #endif #undef xmalloc +#undef xtrymalloc #undef xmalloc_clear #undef xmalloc_secure #undef xmalloc_secure_clear @@ -69,6 +70,7 @@ #undef xfree #undef m_check #undef xstrdup +#undef xtrystrdup #define FNAME(a) m_debug_ ##a #define FNAMEX(a) m_debug_ ##a #define FNAMEXM(a) m_debug_ ##a @@ -444,6 +446,30 @@ FNAMEXM(alloc)( size_t n FNAMEPRT ) #endif } +/* Allocate memory of size n. This function returns NULL if we do not + have enough memory. */ +void * +FNAMEX(trymalloc)(size_t n FNAMEPRT) +{ +#ifdef M_GUARD + char *p; + + if (!n) + n = 1; + p = malloc (n + EXTRA_ALIGN+5); + if (!p) + return NULL; + store_len(p,n,0); + used_memory += n; + p[4+EXTRA_ALIGN+n] = MAGIC_END_BYTE; + return p+EXTRA_ALIGN+4; +#else + /* Mallocing zero bytes is undefined by ISO-C, so we better make + sure that it won't happen. */ + return malloc (n? n: 1); +#endif +} + /**************** * Allocate memory of size n from the secure memory pool. * This function gives up if we do not have enough memory @@ -616,6 +642,16 @@ FNAMEX(strdup)( const char *a FNAMEPRT ) return p; } +char * +FNAMEX(trystrdup)(const char *a FNAMEPRT) +{ + size_t n = strlen (a); + char *p = FNAMEX(trymalloc)(n+1 FNAMEARG); + if (p) + strcpy (p, a); + return p; +} + /* Wrapper around xmalloc_clear to take the usual 2 arguments of a calloc style function. */ diff --git a/util/strgutil.c b/util/strgutil.c index f19bab590..0791dbbfa 100644 --- a/util/strgutil.c +++ b/util/strgutil.c @@ -1,6 +1,6 @@ /* strgutil.c - string utilities * Copyright (C) 1994, 1998, 1999, 2000, 2001, - * 2003, 2004, 2005 Free Software Foundation, Inc. + * 2003, 2004, 2005, 2009 Free Software Foundation, Inc. * * This file is part of GnuPG. * @@ -47,6 +47,14 @@ #include "memory.h" #include "i18n.h" #include "dynload.h" +#include "estream-printf.h" + +/* Our xasprintf replacements are expected to work with our memory + allocator. Let's test for this here. */ +#if !defined(_ESTREAM_PRINTF_MALLOC) || !defined(_ESTREAM_PRINTF_FREE) +#error Please define _ESTREAM_PRINTF_MALLOC and _FREE +#endif + #ifndef USE_GNUPG_ICONV @@ -1040,6 +1048,40 @@ utf8_to_native( const char *string, size_t length, int delim ) } } + +/* Same as asprintf but return an allocated buffer suitable to be + freed using xfree. This function simply dies on memory failure, + thus no extra check is required. */ +char * +xasprintf (const char *fmt, ...) +{ + va_list ap; + char *buf; + + va_start (ap, fmt); + if (estream_vasprintf (&buf, fmt, ap) < 0) + log_fatal ("estream_asprintf failed: %s\n", strerror (errno)); + va_end (ap); + return buf; +} + +/* Same as above but return NULL on memory failure. */ +char * +xtryasprintf (const char *fmt, ...) +{ + int rc; + va_list ap; + char *buf; + + va_start (ap, fmt); + rc = estream_vasprintf (&buf, fmt, ap); + va_end (ap); + if (rc < 0) + return NULL; + return buf; +} + + /**************************************************** ******** locale insensitive ctype functions ******** ****************************************************/ @@ -1127,111 +1169,6 @@ strncasecmp( const char *a, const char *b, size_t n ) #ifdef _WIN32 -/* - * Like vsprintf but provides a pointer to malloc'd storage, which - * must be freed by the caller (xfree). Taken from libiberty as - * found in gcc-2.95.2 and a little bit modernized. - * FIXME: Write a new CRT for W32. - */ -int -vasprintf (char **result, const char *format, va_list args) -{ - const char *p = format; - /* Add one to make sure that it is never zero, which might cause malloc - to return NULL. */ - int total_width = strlen (format) + 1; - va_list ap; - - /* this is not really portable but works under Windows */ - memcpy ( &ap, &args, sizeof (va_list)); - - while (*p != '\0') - { - if (*p++ == '%') - { - while (strchr ("-+ #0", *p)) - ++p; - if (*p == '*') - { - ++p; - total_width += abs (va_arg (ap, int)); - } - else - { - char *endp; - total_width += strtoul (p, &endp, 10); - p = endp; - } - if (*p == '.') - { - ++p; - if (*p == '*') - { - ++p; - total_width += abs (va_arg (ap, int)); - } - else - { - char *endp; - total_width += strtoul (p, &endp, 10); - p = endp; - } - } - while (strchr ("hlL", *p)) - ++p; - /* Should be big enough for any format specifier except %s - and floats. */ - total_width += 30; - switch (*p) - { - case 'd': - case 'i': - case 'o': - case 'u': - case 'x': - case 'X': - case 'c': - (void) va_arg (ap, int); - break; - case 'f': - case 'e': - case 'E': - case 'g': - case 'G': - (void) va_arg (ap, double); - /* Since an ieee double can have an exponent of 307, we'll - make the buffer wide enough to cover the gross case. */ - total_width += 307; - - case 's': - total_width += strlen (va_arg (ap, char *)); - break; - case 'p': - case 'n': - (void) va_arg (ap, char *); - break; - } - } - } - *result = xmalloc (total_width); - if (*result != NULL) - return vsprintf (*result, format, args); - else - return 0; -} - -int -asprintf (char **buf, const char *fmt, ...) -{ - int status; - va_list ap; - - va_start (ap, fmt); - status = vasprintf (buf, fmt, ap); - va_end (ap); - return status; -} - const char * w32_strerror (int w32_errno) { diff --git a/util/ttyio.c b/util/ttyio.c index 2a0c91be2..d02a884ee 100644 --- a/util/ttyio.c +++ b/util/ttyio.c @@ -239,13 +239,14 @@ tty_printf( const char *fmt, ... ) va_start( arg_ptr, fmt ) ; #ifdef _WIN32 { - char *buf = NULL; + char *buf; int n; DWORD nwritten; - n = vasprintf(&buf, fmt, arg_ptr); - if( !buf ) - log_bug("vasprintf() failed\n"); + buf = xtryasprintf(fmt, arg_ptr); + if (!buf) + log_bug("xtryasprintf() failed\n"); + n = strlen (buf); if (!WriteConsoleA (con.out, buf, n, &nwritten, NULL)) log_fatal ("WriteConsole failed: %s", w32_strerror (0)); @@ -286,13 +287,14 @@ tty_fprintf (FILE *fp, const char *fmt, ... ) va_start( arg_ptr, fmt ) ; #ifdef _WIN32 { - char *buf = NULL; + char *buf; int n; DWORD nwritten; - n = vasprintf(&buf, fmt, arg_ptr); - if( !buf ) - log_bug("vasprintf() failed\n"); + buf = xtryasprintf (fmt, arg_ptr); + if (!buf) + log_bug ("xtryasprintf() failed\n"); + n = strlen (buf); if (!WriteConsoleA (con.out, buf, n, &nwritten, NULL)) log_fatal ("WriteConsole failed: %s", w32_strerror (0));