aboutsummaryrefslogtreecommitdiffstats
path: root/g10/ccid-driver.c
diff options
context:
space:
mode:
Diffstat (limited to 'g10/ccid-driver.c')
-rw-r--r--g10/ccid-driver.c1105
1 files changed, 923 insertions, 182 deletions
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 <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
+#include <time.h>
+#ifdef HAVE_PTH
+# include <pth.h>
+#endif /*HAVE_PTH*/
#include <usb.h>
@@ -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))
+ {
+ /* 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 ("3 failed retries\n");
+ 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"))