From a776f660363d20b6cc023609c4547e0aa8825d97 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Tue, 8 Jan 2013 15:22:31 +0900 Subject: [PATCH] SCD: Support not-so-smart card readers. * scd/ccid-driver.c (struct ccid_driver_s): Add auto_voltage, auto_param, and auto_pps. (parse_ccid_descriptor): Set auto_voltage, auto_param, and auto_pps. Support non-autoconf readers. (update_param_by_atr): New. (ccid_get_atr): Use 5V for PowerOn when auto_voltage is not supported. Use 0x10 when nonnull_nad for SetParameters. Call update_param_by_atr for parsing ATR, and use param for SetParameters. Send PPS if reader requires it and card is negotiable. When bNadValue in the return values of SetParameters == 0, clear handle->nonnull_nad flag. -- This change is to support more card readers by the internal driver. Tested with 08e6:3478 Gemplus PinPad Smart Card Reader. --- scd/ccid-driver.c | 290 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 253 insertions(+), 37 deletions(-) diff --git a/scd/ccid-driver.c b/scd/ccid-driver.c index 3fe932ca6..5b3bcaf9a 100644 --- a/scd/ccid-driver.c +++ b/scd/ccid-driver.c @@ -264,6 +264,9 @@ struct ccid_driver_s 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_voltage:1; + unsigned int auto_param:1; + unsigned int auto_pps:1; unsigned int auto_ifsd:1; unsigned int powered_off:1; unsigned int has_pinpad:2; @@ -758,7 +761,7 @@ parse_ccid_descriptor (ccid_driver_t handle, { unsigned int i; unsigned int us; - int have_t1 = 0, have_tpdu=0, have_auto_conf = 0; + int have_t1 = 0, have_tpdu=0; handle->nonnull_nad = 0; @@ -767,6 +770,9 @@ parse_ccid_descriptor (ccid_driver_t handle, handle->ifsd = 0; handle->has_pinpad = 0; handle->apdu_level = 0; + handle->auto_voltage = 0; + handle->auto_param = 0; + handle->auto_pps = 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) @@ -842,22 +848,31 @@ parse_ccid_descriptor (ccid_driver_t handle, DEBUGOUT_1 (" dwFeatures %08X\n", us); if ((us & 0x0002)) { - DEBUGOUT (" Auto configuration based on ATR\n"); - have_auto_conf = 1; + DEBUGOUT (" Auto configuration based on ATR (assumes auto voltage)\n"); + handle->auto_voltage = 1; } if ((us & 0x0004)) DEBUGOUT (" Auto activation on insert\n"); if ((us & 0x0008)) - DEBUGOUT (" Auto voltage selection\n"); + { + DEBUGOUT (" Auto voltage selection\n"); + handle->auto_voltage = 1; + } if ((us & 0x0010)) DEBUGOUT (" Auto clock change\n"); if ((us & 0x0020)) DEBUGOUT (" Auto baud rate change\n"); if ((us & 0x0040)) - DEBUGOUT (" Auto parameter negotiation made by CCID\n"); + { + DEBUGOUT (" Auto parameter negotiation made by CCID\n"); + handle->auto_param = 1; + } else if ((us & 0x0080)) - DEBUGOUT (" Auto PPS made by CCID\n"); - else if ((us & (0x0040 | 0x0080))) + { + DEBUGOUT (" Auto PPS made by CCID\n"); + handle->auto_pps = 1; + } + if ((us & (0x0040 | 0x0080)) == (0x0040 | 0x0080)) DEBUGOUT (" WARNING: conflicting negotiation features\n"); if ((us & 0x0100)) @@ -935,11 +950,10 @@ parse_ccid_descriptor (ccid_driver_t handle, DEBUGOUT_LF (); } - if (!have_t1 || !(have_tpdu || handle->apdu_level) || !have_auto_conf) + if (!have_t1 || !(have_tpdu || handle->apdu_level)) { DEBUGOUT ("this drivers requires that the reader supports T=1, " - "TPDU or APDU level exchange and auto configuration - " - "this is not available\n"); + "TPDU or APDU level exchange - this is not available\n"); return -1; } @@ -2338,6 +2352,151 @@ ccid_slot_status (ccid_driver_t handle, int *statusbits) } +/* Parse ATR string (of ATRLEN) and update parameters at PARAM. + Calling this routine, it should prepare default values at PARAM + beforehand. This routine assumes that card is accessed by T=1 + protocol. It doesn't analyze historical bytes at all. + + Returns < 0 value on error: + -1 for parse error or integrity check error + -2 for card doesn't support T=1 protocol + -3 for parameters are nod explicitly defined by ATR + -4 for this driver doesn't support CRC + + Returns >= 0 on success: + 0 for card is negotiable mode + 1 for card is specific mode (and not negotiable) + */ +static int +update_param_by_atr (unsigned char *param, unsigned char *atr, size_t atrlen) +{ + int i = -1; + int t, y, chk; + int historical_bytes_num, negotiable = 1; + +#define NEXTBYTE() do { i++; if (atrlen <= i) return -1; } while (0) + + NEXTBYTE (); + + if (atr[i] == 0x3F) + param[1] |= 0x02; /* Convention is inverse. */ + NEXTBYTE (); + + y = (atr[i] >> 4); + historical_bytes_num = atr[i] & 0x0f; + NEXTBYTE (); + + if ((y & 1)) + { + param[0] = atr[i]; /* TA1 - Fi & Di */ + NEXTBYTE (); + } + + if ((y & 2)) + NEXTBYTE (); /* TB1 - ignore */ + + if ((y & 4)) + { + param[2] = atr[i]; /* TC1 - Guard Time */ + NEXTBYTE (); + } + + if ((y & 8)) + { + y = (atr[i] >> 4); /* TD1 */ + t = atr[i] & 0x0f; + NEXTBYTE (); + + if ((y & 1)) + { /* TA2 - PPS mode */ + if ((atr[i] & 0x0f) != 1) + return -2; /* Wrong card protocol (!= 1). */ + + if ((atr[i] & 0x10) != 0x10) + return -3; /* Transmission parameters are implicitly defined. */ + + negotiable = 0; /* TA2 means specific mode. */ + NEXTBYTE (); + } + + if ((y & 2)) + NEXTBYTE (); /* TB2 - ignore */ + + if ((y & 4)) + NEXTBYTE (); /* TC2 - ignore */ + + if ((y & 8)) + { + y = (atr[i] >> 4); /* TD2 */ + t = atr[i] & 0x0f; + NEXTBYTE (); + } + else + y = 0; + + while (y) + { + if ((y & 1)) + { /* TAx */ + if (t == 1) + param[5] = atr[i]; /* IFSC */ + else if (t == 15) + /* XXX: check voltage? */ + param[4] = (atr[i] >> 6); /* ClockStop */ + + NEXTBYTE (); + } + + if ((y & 2)) + { + if (t == 1) + param[3] = atr[i]; /* TBx - BWI & CWI */ + NEXTBYTE (); + } + + if ((y & 4)) + { + if (t == 1) + param[1] |= (atr[i] & 0x01); /* TCx - LRC/CRC */ + NEXTBYTE (); + + if (param[1] & 0x01) + return -4; /* CRC not supported yet. */ + } + + if ((y & 8)) + { + y = (atr[i] >> 4); /* TDx */ + t = atr[i] & 0x0f; + NEXTBYTE (); + } + else + y = 0; + } + } + + i += historical_bytes_num - 1; + NEXTBYTE (); + if (atrlen != i+1) + return -1; + +#undef NEXTBYTE + + chk = 0; + do + { + chk ^= atr[i]; + i--; + } + while (i > 0); + + if (chk != 0) + return -1; + + return negotiable; +} + + /* Return the ATR of the card. This is not a cached value and thus an actual reset is done. */ int @@ -2354,6 +2513,15 @@ ccid_get_atr (ccid_driver_t handle, unsigned int edc; int tried_iso = 0; int got_param; + unsigned char param[7] = { /* For Protocol T=1 */ + 0x11, /* bmFindexDindex */ + 0x10, /* bmTCCKST1 */ + 0x00, /* bGuardTimeT1 */ + 0x4d, /* bmWaitingIntegersT1 */ + 0x00, /* bClockStop */ + 0x20, /* bIFSC */ + 0x00 /* bNadValue */ + }; /* First check whether a card is available. */ rc = ccid_slot_status (handle, &statusbits); @@ -2368,7 +2536,8 @@ ccid_get_atr (ccid_driver_t handle, msg[0] = PC_to_RDR_IccPowerOn; msg[5] = 0; /* slot */ msg[6] = seqno = handle->seqno++; - msg[7] = 0; /* power select (0=auto, 1=5V, 2=3V, 3=1.8V) */ + /* power select (0=auto, 1=5V, 2=3V, 3=1.8V) */ + msg[7] = handle->auto_voltage ? 0 : 1; msg[8] = 0; /* RFU */ msg[9] = 0; /* RFU */ set_msg_len (msg, 0); @@ -2410,23 +2579,73 @@ ccid_get_atr (ccid_driver_t handle, *atrlen = n; } + param[6] = handle->nonnull_nad? ((1 << 4) | 0): 0; + rc = update_param_by_atr (param, msg+10, msglen - 10); + if (rc < 0) + { + DEBUGOUT_1 ("update_param_by_atr failed: %d\n", rc); + return CCID_DRIVER_ERR_CARD_IO_ERROR; + } + got_param = 0; - msg[0] = PC_to_RDR_GetParameters; - msg[5] = 0; /* slot */ - msg[6] = seqno = handle->seqno++; - msg[7] = 0; /* RFU */ - msg[8] = 0; /* RFU */ - msg[9] = 0; /* RFU */ - set_msg_len (msg, 0); - msglen = 10; - 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 if (msglen == 17 && msg[9] == 1) - got_param = 1; + + if (handle->auto_param) + { + msg[0] = PC_to_RDR_GetParameters; + msg[5] = 0; /* slot */ + msg[6] = seqno = handle->seqno++; + msg[7] = 0; /* RFU */ + msg[8] = 0; /* RFU */ + msg[9] = 0; /* RFU */ + set_msg_len (msg, 0); + msglen = 10; + 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 if (msglen == 17 && msg[9] == 1) + got_param = 1; + } + else if (handle->auto_pps) + ; + else if (rc == 1) /* It's negotiable, send PPS. */ + { + msg[0] = PC_to_RDR_XfrBlock; + msg[5] = 0; /* slot */ + msg[6] = seqno = handle->seqno++; + msg[7] = 0; + msg[8] = 0; + msg[9] = 0; + msg[10] = 0xff; /* PPSS */ + msg[11] = 0x11; /* PPS0: PPS1, Protocol T=1 */ + msg[12] = param[0]; /* PPS1: Fi / Di */ + msg[13] = 0xff ^ 0x11 ^ param[0]; /* PCK */ + set_msg_len (msg, 4); + msglen = 10 + 4; + + rc = bulk_out (handle, msg, msglen, 0); + if (rc) + return rc; + + rc = bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_DataBlock, + seqno, 5000, 0); + if (rc) + return rc; + + if (msglen != 10 + 4) + { + DEBUGOUT_1 ("Setting PPS failed: %d\n", msglen); + return CCID_DRIVER_ERR_CARD_IO_ERROR; + } + + if (msg[10] != 0xff || msg[11] != 0x11 || msg[12] != param[0]) + { + DEBUGOUT_1 ("Setting PPS failed: 0x%02x\n", param[0]); + return CCID_DRIVER_ERR_CARD_IO_ERROR; + } + } /* Setup parameters to select T=1. */ msg[0] = PC_to_RDR_SetParameters; @@ -2437,16 +2656,7 @@ ccid_get_atr (ccid_driver_t handle, msg[9] = 0; /* RFU */ if (!got_param) - { - /* FIXME: Get those values from the ATR. */ - msg[10]= 0x01; /* Fi/Di */ - msg[11]= 0x10; /* LRC, direct convention. */ - msg[12]= 0; /* Extra guardtime. */ - msg[13]= 0x41; /* BWI/CWI */ - msg[14]= 0; /* No clock stoppping. */ - msg[15]= 254; /* IFSC */ - msg[16]= 0; /* Does not support non default NAD values. */ - } + memcpy (&msg[10], param, 7); set_msg_len (msg, 7); msglen = 10 + 7; @@ -2463,6 +2673,12 @@ ccid_get_atr (ccid_driver_t handle, else handle->ifsc = 128; /* Something went wrong, assume 128 bytes. */ + if (handle->nonnull_nad && msglen > 16 && msg[16] == 0) + { + DEBUGOUT ("Use Null-NAD, clearing handle->nonnull_nad.\n"); + handle->nonnull_nad = 0; + } + handle->t1_ns = 0; handle->t1_nr = 0;