From f0c793c5a74aec13e73e6b585f3f84b8c130fc80 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 6 Oct 2004 13:13:51 +0000 Subject: [PATCH] (ccid_open_reader): Store the vendor ID. (ccid_transceive_secure): New. (parse_ccid_descriptor): Workaround for an SCM reader problem. --- scd/ChangeLog | 10 ++ scd/ccid-driver.c | 380 ++++++++++++++++++++++++++++++++++++++++++---- scd/ccid-driver.h | 5 + 3 files changed, 369 insertions(+), 26 deletions(-) diff --git a/scd/ChangeLog b/scd/ChangeLog index 493af3fd6..60311e705 100644 --- a/scd/ChangeLog +++ b/scd/ChangeLog @@ -1,3 +1,13 @@ +2004-10-06 Werner Koch + + * ccid-driver.c (ccid_open_reader): Store the vendor ID. + (ccid_transceive_secure): New. + (parse_ccid_descriptor): Workaround for an SCM reader problem. + +2004-10-04 Werner Koch + + * ccid-driver.c (send_escape_cmd): New. + 2004-09-30 Werner Koch * Makefile.am: Adjusted for gettext 0.14. diff --git a/scd/ccid-driver.c b/scd/ccid-driver.c index 77fea944b..287a8d87d 100644 --- a/scd/ccid-driver.c +++ b/scd/ccid-driver.c @@ -154,8 +154,6 @@ - - enum { RDR_to_PC_NotifySlotChange= 0x50, RDR_to_PC_HardwareError = 0x51, @@ -183,12 +181,21 @@ enum { }; +/* We need to know the vendor to do some hacks. */ +enum { + VENDOR_SCM = 0x04e6 +}; + + /* Store information on the driver's state. A pointer to such a structure is used as handle for most functions. */ struct ccid_driver_s { usb_dev_handle *idev; char *rid; + unsigned short id_vendor; + unsigned short id_product; + unsigned short bcd_device; int seqno; unsigned char t1_ns; unsigned char t1_nr; @@ -251,6 +258,8 @@ parse_ccid_descriptor (ccid_driver_t handle, handle->max_ifsd = 32; handle->ifsd = 0; handle->has_pinpad = 0; + DEBUGOUT_3 ("idVendor: %04X idProduct: %04X bcdDevice: %04X\n", + handle->id_vendor, handle->id_product, handle->bcd_device); if (buflen < 54 || buf[0] < 54) { DEBUGOUT ("CCID device descriptor is too short\n"); @@ -417,8 +426,25 @@ parse_ccid_descriptor (ccid_driver_t handle, "this is not available\n"); return -1; } - else - return 0; + + + /* SCM drivers get stuck in their internal USB stack if they try to + send a frame of n*wMaxPacketSize back to us. Given that + wMaxPacketSize is 64 for these readers we set the IFSD to a value + lower than that: + 64 - 10 CCID header - 4 T1frame - 2 reserved = 48 */ + if (handle->id_vendor == VENDOR_SCM + /* FIXME: check whether it is the same + firmware version for all drivers. */ + && handle->bcd_device < 0x0513 + && handle->max_ifsd > 48) + { + DEBUGOUT ("enabling workaround for buggy SCM readers\n"); + handle->max_ifsd = 48; + } + + + return 0; } @@ -436,7 +462,7 @@ get_escaped_usb_string (usb_dev_handle *idev, int idx, if (!idx) return NULL; - /* Fixme: The next line for the current Valgrid without support + /* Fixme: The next line is for the current Valgrid without support for USB IOCTLs. */ memset (buf, 0, sizeof buf); @@ -825,6 +851,9 @@ ccid_open_reader (ccid_driver_t *handle, const char *readerid) } (*handle)->idev = idev; (*handle)->rid = rid; + (*handle)->id_vendor = dev->descriptor.idVendor; + (*handle)->id_product = dev->descriptor.idProduct; + (*handle)->bcd_device = dev->descriptor.bcdDevice; DEBUGOUT_2 ("using CCID reader %d (ID=%s)\n", readerno, rid ); @@ -1082,6 +1111,43 @@ bulk_in (ccid_driver_t handle, unsigned char *buffer, size_t length, } +/* Note that this fucntion won't return the error codes NO_CARD or + CARD_INACTIVE */ +static int +send_escape_cmd (ccid_driver_t handle, + const unsigned char *data, size_t datalen) +{ + int i, rc; + unsigned char msg[100]; + size_t msglen; + unsigned char seqno; + + if (datalen > sizeof msg - 10) + return CCID_DRIVER_ERR_INV_VALUE; /* Escape data too large. */ + + 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 */ + memcpy (msg+10, data, datalen); + 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); + if (rc) + return rc; + rc = bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_Escape, seqno); + + return rc; +} + + /* experimental */ int ccid_poll (ccid_driver_t handle) @@ -1617,10 +1683,232 @@ ccid_transceive (ccid_driver_t handle, } +/* Send the CCID Secure command to the reader. APDU_BUF should contain the APDU template. PIN_MODE defines now the pin gets formatted: + + 1 := The PIN is ASCII encoded and of variable length. The + length of the PIN entered will be put into Lc by the reader. + The APDU should me made up of 4 bytes without Lc. + + PINLEN_MIN and PINLEN_MAX define the limits for the pin length. 0 + may be used t enable usbale defaults. PIN_PADLEN should be 0 + + When called with RESP and NRESP set to NULL, the function will + merely check whether the reader supports the secure command for the + given APDU and PIN_MODE. */ +int +ccid_transceive_secure (ccid_driver_t handle, + const unsigned char *apdu_buf, size_t apdu_buflen, + int pin_mode, int pinlen_min, int pinlen_max, + int pin_padlen, + unsigned char *resp, size_t maxresplen, size_t *nresp) +{ + int rc; + unsigned char send_buffer[10+259], recv_buffer[10+259]; + unsigned char *msg, *tpdu, *p; + size_t msglen, tpdulen, n; + unsigned char seqno; + int i; + size_t dummy_nresp; + int testmode; + + testmode = !resp && !nresp; + + if (!nresp) + nresp = &dummy_nresp; + *nresp = 0; + + if (apdu_buflen >= 4 && apdu_buf[1] == 0x20 && (handle->has_pinpad & 1)) + ; + else if (apdu_buflen >= 4 && apdu_buf[1] == 0x24 && (handle->has_pinpad & 2)) + return CCID_DRIVER_ERR_NOT_SUPPORTED; /* Not yet by our code. */ + else + return CCID_DRIVER_ERR_NOT_SUPPORTED; + + if (pin_mode != 1) + return CCID_DRIVER_ERR_NOT_SUPPORTED; + + if (pin_padlen != 0) + return CCID_DRIVER_ERR_NOT_SUPPORTED; + + if (!pinlen_min) + pinlen_min = 1; + if (!pinlen_max) + pinlen_max = 25; + + /* Note that the 25 is the maximum value the SPR532 allows. */ + if (pinlen_min < 1 || pinlen_min > 25 + || pinlen_max < 1 || pinlen_max > 25 + || 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; + + if (testmode) + return 0; /* Success */ + + msg = send_buffer; + if (handle->id_vendor == VENDOR_SCM) + { + DEBUGOUT ("sending escape sequence to switch to a case 1 APDU\n"); + rc = send_escape_cmd (handle, "\x80\x02\x00", 3); + if (rc) + return rc; + } + + msg[0] = PC_to_RDR_Secure; + msg[5] = 0; /* slot */ + msg[6] = seqno = handle->seqno++; + msg[7] = 4; /* bBWI */ + msg[8] = 0; /* RFU */ + msg[9] = 0; /* RFU */ + msg[10] = 0; /* Perform PIN verification. */ + msg[11] = 0; /* Timeout in seconds. */ + msg[12] = 0x82; /* bmFormatString: Byte, pos=0, left, ASCII. */ + 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 to Martin Paljak for this + hint. */ + msg[13] = msg[14] = 0; + } + else + { + msg[13] = 0x00; /* bmPINBlockString: + 0 bits of pin length to insert. + 0 bytes of PIN block size. */ + msg[14] = 0x00; /* bmPINLengthFormat: + Units are bytes, position is 0. */ + } + msg[15] = pinlen_min; /* wPINMaxExtraDigit-Minimum. */ + msg[16] = pinlen_max; /* wPINMaxExtraDigit-Maximum. */ + msg[17] = 0x02; /* bEntryValidationCondition: + Validation key pressed */ + if (pinlen_min && pinlen_max && pinlen_min == pinlen_max) + msg[17] |= 0x01; /* Max size reached. */ + msg[18] = 0xff; /* bNumberMessage: Default. */ + msg[19] = 0x04; /* wLangId-High. */ + msg[20] = 0x09; /* wLangId-Low: English FIXME: use the first entry. */ + msg[21] = 0; /* bMsgIndex. */ + /* bTeoProlog follows: */ + msg[22] = handle->nonnull_nad? ((1 << 4) | 0): 0; + msg[23] = ((handle->t1_ns & 1) << 6); /* I-block */ + msg[24] = 4; /* apdulen. */ + /* 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; + 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); + if (rc) + return rc; + + msg = recv_buffer; + rc = bulk_in (handle, msg, sizeof recv_buffer, &msglen, + RDR_to_PC_DataBlock, seqno); + if (rc) + return rc; + + tpdu = msg + 10; + tpdulen = msglen - 10; + + if (tpdulen < 4) + { + usb_clear_halt (handle->idev, 0x82); + return CCID_DRIVER_ERR_ABORTED; + } +#ifdef DEBUG_T1 + fprintf (stderr, "T1: got %c-block seq=%d err=%d\n", + ((msg[11] & 0xc0) == 0x80)? 'R' : + (msg[11] & 0x80)? 'S' : 'I', + ((msg[11] & 0x80)? !!(msg[11]& 0x10) : !!(msg[11] & 0x40)), + ((msg[11] & 0xc0) == 0x80)? (msg[11] & 0x0f) : 0 + ); +#endif + + if (!(tpdu[1] & 0x80)) + { /* This is an I-block. */ + /* Last block sent was successful. */ + handle->t1_ns ^= 1; + + if (!!(tpdu[1] & 0x40) != handle->t1_nr) + { /* Reponse does not match our sequence number. */ + DEBUGOUT ("I-block with wrong seqno received\n"); + return CCID_DRIVER_ERR_CARD_IO_ERROR; + } + + handle->t1_nr ^= 1; + + p = tpdu + 3; /* Skip the prologue field. */ + n = tpdulen - 3 - 1; /* Strip the epilogue field. */ + /* fixme: verify the checksum. */ + if (resp) + { + if (n > maxresplen) + { + DEBUGOUT_2 ("provided buffer too short for received data " + "(%u/%u)\n", + (unsigned int)n, (unsigned int)maxresplen); + return CCID_DRIVER_ERR_INV_VALUE; + } + + memcpy (resp, p, n); + resp += n; + *nresp += n; + maxresplen -= n; + } + + if (!(tpdu[1] & 0x20)) + return 0; /* No chaining requested - ready. */ + + DEBUGOUT ("chaining requested but not supported for Secure operation\n"); + return CCID_DRIVER_ERR_CARD_IO_ERROR; + } + else if ((tpdu[1] & 0xc0) == 0x80) + { /* This is a R-block. */ + if ( (tpdu[1] & 0x0f)) + { /* Error: repeat last block */ + DEBUGOUT ("No retries supported for Secure operation\n"); + return CCID_DRIVER_ERR_CARD_IO_ERROR; + } + else if (!!(tpdu[1] & 0x40) == handle->t1_ns) + { /* Reponse does not match our sequence number. */ + DEBUGOUT ("R-block with wrong seqno received on more bit\n"); + return CCID_DRIVER_ERR_CARD_IO_ERROR; + } + else + { /* Send next chunk. */ + DEBUGOUT ("chaining not supported on Secure operation\n"); + return CCID_DRIVER_ERR_CARD_IO_ERROR; + } + } + else + { /* This is a S-block. */ + DEBUGOUT_2 ("T1 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; + } + + return 0; +} + + #ifdef TEST + static void print_error (int err) { @@ -1682,6 +1970,9 @@ main (int argc, char **argv) unsigned int slotstat; unsigned char result[512]; size_t resultlen; + int no_pinpad = 0; + int verify_123456 = 0; + int did_verify = 0; if (argc) { @@ -1706,6 +1997,16 @@ main (int argc, char **argv) ccid_set_debug_level (1); argc--; argv++; } + else if ( !strcmp (*argv, "--no-pinpad")) + { + no_pinpad = 1; + argc--; argv++; + } + else if ( !strcmp (*argv, "--verify-123456")) + { + verify_123456 = 1; + argc--; argv++; + } else break; } @@ -1755,28 +2056,55 @@ main (int argc, char **argv) print_result (rc, result, resultlen); } - ccid_poll (ccid); + if (!no_pinpad) + { + } -/* if (!ccid->has_pinpad) */ -/* { */ -/* fputs ("verifying that CHV1 is 123456....\n", stderr); */ -/* { */ -/* static unsigned char apdu[] = {0, 0x20, 0, 0x81, */ -/* 6, '1','2','3','4','5','6'}; */ -/* rc = ccid_transceive (ccid, apdu, sizeof apdu, */ -/* result, sizeof result, &resultlen); */ -/* print_result (rc, result, resultlen); */ -/* } */ -/* } */ -/* else */ -/* { */ -/* fputs ("verifying CHV1 using the PINPad ....\n", stderr); */ -/* { */ -/* rc = ccid_secure_transceive (ccid, */ -/* result, sizeof result, &resultlen); */ -/* print_result (rc, result, resultlen); */ -/* } */ -/* } */ + if (!no_pinpad) + { + static unsigned char apdu[] = { 0, 0x20, 0, 0x81 }; + + + if (ccid_transceive_secure (ccid, + apdu, sizeof apdu, + 1, 0, 0, 0, + NULL, 0, NULL)) + fputs ("can't verify using a PIN-Pad reader\n", stderr); + else + { + fputs ("verifying CHV1 using the PINPad ....\n", stderr); + + rc = ccid_transceive_secure (ccid, + apdu, sizeof apdu, + 1, 0, 0, 0, + result, sizeof result, &resultlen); + print_result (rc, result, resultlen); + did_verify = 1; + } + } + + if (verify_123456 && !did_verify) + { + fputs ("verifying that CHV1 is 123456....\n", stderr); + { + static unsigned char apdu[] = {0, 0x20, 0, 0x81, + 6, '1','2','3','4','5','6'}; + rc = ccid_transceive (ccid, apdu, sizeof apdu, + result, sizeof result, &resultlen); + print_result (rc, result, resultlen); + } + } + + if (!rc) + { + fputs ("getting OpenPGP DO 0x5E ....\n", stderr); + { + static unsigned char apdu[] = { 0, 0xCA, 0, 0x5E, 254 }; + rc = ccid_transceive (ccid, apdu, sizeof apdu, + result, sizeof result, &resultlen); + print_result (rc, result, resultlen); + } + } ccid_close_reader (ccid); diff --git a/scd/ccid-driver.h b/scd/ccid-driver.h index cbadb40c1..9cb23253b 100644 --- a/scd/ccid-driver.h +++ b/scd/ccid-driver.h @@ -86,6 +86,11 @@ int ccid_slot_status (ccid_driver_t handle, int *statusbits); int ccid_transceive (ccid_driver_t handle, const unsigned char *apdu, size_t apdulen, unsigned char *resp, size_t maxresplen, size_t *nresp); +int ccid_transceive_secure (ccid_driver_t handle, + const unsigned char *apdu, size_t apdulen, + int pin_mode, + int pinlen_min, int pinlen_max, int pin_padlen, + unsigned char *resp, size_t maxresplen, size_t *nresp);