diff --git a/src/libopensc/ctbcs.c b/src/libopensc/ctbcs.c index 74e0d55b..da9d93a0 100644 --- a/src/libopensc/ctbcs.c +++ b/src/libopensc/ctbcs.c @@ -77,7 +77,7 @@ ctbcs_build_output_apdu(sc_apdu_t *apdu, const char *message) #endif static int -ctbcs_build_perform_verification_apdu(sc_apdu_t *apdu, struct sc_pin_cmd_data *data) +ctbcs_build_perform_verification_apdu(sc_apdu_t *apdu, struct sc_pin_cmd_data *data, sc_slot_info_t *slot) { const char *prompt; size_t buflen, count = 0, j = 0, len; @@ -87,7 +87,7 @@ ctbcs_build_perform_verification_apdu(sc_apdu_t *apdu, struct sc_pin_cmd_data *d ctbcs_init_apdu(apdu, SC_APDU_CASE_3_SHORT, CTBCS_INS_PERFORM_VERIFICATION, - CTBCS_P1_KEYPAD, + CTBCS_P1_INTERFACE1 + (slot ? slot->id : 0), 0); buflen = sizeof(buf); @@ -121,7 +121,7 @@ ctbcs_build_perform_verification_apdu(sc_apdu_t *apdu, struct sc_pin_cmd_data *d if (data->pin1.min_length == data->pin1.max_length) control |= data->pin1.min_length << CTBCS_PIN_CONTROL_LEN_SHIFT; buf[j++] = control; - buf[j++] = data->pin1.offset; + buf[j++] = data->pin1.offset+1; /* Looks like offset is 1-based in CTBCS */ buf[j++] = data->apdu->cla; buf[j++] = data->apdu->ins; buf[j++] = data->apdu->p1; @@ -145,7 +145,7 @@ ctbcs_build_perform_verification_apdu(sc_apdu_t *apdu, struct sc_pin_cmd_data *d } static int -ctbcs_build_modify_verification_apdu(sc_apdu_t *apdu, struct sc_pin_cmd_data *data) +ctbcs_build_modify_verification_apdu(sc_apdu_t *apdu, struct sc_pin_cmd_data *data, sc_slot_info_t *slot) { /* to be implemented */ return SC_ERROR_NOT_SUPPORTED; @@ -162,11 +162,11 @@ ctbcs_pin_cmd(struct sc_reader *reader, sc_slot_info_t *slot, switch (data->cmd) { case SC_PIN_CMD_VERIFY: - r = ctbcs_build_perform_verification_apdu(&apdu, data); + r = ctbcs_build_perform_verification_apdu(&apdu, data, slot); break; case SC_PIN_CMD_CHANGE: case SC_PIN_CMD_UNBLOCK: - r = ctbcs_build_modify_verification_apdu(&apdu, data); + r = ctbcs_build_modify_verification_apdu(&apdu, data, slot); break; default: sc_error(reader->ctx, "Unknown PIN command %d", data->cmd); @@ -209,5 +209,11 @@ ctbcs_pin_cmd(struct sc_reader *reader, sc_slot_info_t *slot, } SC_TEST_RET(card->ctx, r, "PIN command failed"); + /* Calling Function may expect SW1/SW2 in data-apdu set... */ + if (data->apdu) { + data->apdu->sw1 = apdu.sw1; + data->apdu->sw2 = apdu.sw2; + } + return 0; } diff --git a/src/libopensc/ctbcs.h b/src/libopensc/ctbcs.h index 2c27ff51..1004b512 100644 --- a/src/libopensc/ctbcs.h +++ b/src/libopensc/ctbcs.h @@ -71,6 +71,13 @@ #define CTBCS_P1_INTERFACE14 0x0E #define CTBCS_P1_DISPLAY 0x40 #define CTBCS_P1_KEYPAD 0x50 +#define CTBCS_P1_PRINTER 0x60 /* New CT-BCS 1.0 */ +#define CTBCS_P1_FINGERPRINT 0x70 /* New CT-BCS 1.0 */ +#define CTBCS_P1_VOICEPRINT 0x71 /* New CT-BCS 1.0 */ +#define CTBCS_P1_DSV 0x72 /* "Dynamic Signature Verification" New CT-BCS 1.0 */ +#define CTBCS_P1_FACE_RECOGNITION 0x73 /* New CT-BCS 1.0 */ +#define CTBCS_P1_IRISSCAN 0x74 /* New CT-BCS 1.0 */ +/* Other biometric units may use values up to 0x7F */ /* * P2 parameter for Reset CT: data to be returned @@ -91,6 +98,7 @@ */ #define CTBCS_P2_STATUS_MANUFACTURER 0x46 /* Return manufacturer DO */ #define CTBCS_P2_STATUS_ICC 0x80 /* Return ICC DO */ +#define CTBCS_P2_STATUS_TFU 0x81 /* Return Functional Units, new in Version 1.0 */ /* * P2 parameter for Input @@ -153,20 +161,20 @@ #define CTBCS_SW2_REQUEST_NO_CARD 0x00 #define CTBCS_SW1_REQUEST_CARD_PRESENT 0x62 /* Card already present */ #define CTBCS_SW2_REQUEST_CARD_PRESENT 0x01 -#define CTBCS_SW1_REQUEST_ERROR 0x64 /* Reset not successful */ -#define CTBCS_SW2_REQUEST_ERROR 0x00 +#define CTBCS_SW1_REQUEST_ERROR 0x64 /* Reset not successful */ +#define CTBCS_SW2_REQUEST_ERROR 0x00 #define CTBCS_SW1_REQUEST_TIMER_ERROR 0x69 /* Timer not supported */ #define CTBCS_SW2_REQUEST_TIMER_ERROR 0x00 /* * Return codes for Eject ICC */ -#define CTBCS_SW1_EJECT_OK 0x90 /* Command succesful, */ -#define CTBCS_SW2_EJECT_OK 0x00 -#define CTBCS_SW1_EJECT_REMOVED 0x90 /* Command succesful, */ -#define CTBCS_SW2_EJECT_REMOVED 0x01 /* Card removed */ -#define CTBCS_SW1_EJECT_NOT_REMOVED 0x62 /* Card not removed */ -#define CTBCS_SW2_EJECT_NOT_REMOVED 0x00 +#define CTBCS_SW1_EJECT_OK 0x90 /* Command succesful, */ +#define CTBCS_SW2_EJECT_OK 0x00 +#define CTBCS_SW1_EJECT_REMOVED 0x90 /* Command succesful, */ +#define CTBCS_SW2_EJECT_REMOVED 0x01 /* Card removed */ +#define CTBCS_SW1_EJECT_NOT_REMOVED 0x62 /* Card not removed */ +#define CTBCS_SW2_EJECT_NOT_REMOVED 0x00 /* * Data returned on Get Status command @@ -180,6 +188,4 @@ */ int ctbcs_pin_cmd(struct sc_reader *, sc_slot_info_t *, struct sc_pin_cmd_data *); - #endif /* _CTBCS_ */ - diff --git a/src/libopensc/ctx.c b/src/libopensc/ctx.c index 6b5bc8b3..ee0d9e69 100644 --- a/src/libopensc/ctx.c +++ b/src/libopensc/ctx.c @@ -84,8 +84,8 @@ static const struct _sc_driver_entry internal_reader_drivers[] = { #if defined(HAVE_PCSC) { "pcsc", (void *) sc_get_pcsc_driver, NULL }, #endif -#ifndef _WIN32 { "ctapi", (void *) sc_get_ctapi_driver, NULL }, +#ifndef _WIN32 #ifdef HAVE_OPENCT { "openct", (void *) sc_get_openct_driver, NULL }, #endif diff --git a/src/libopensc/reader-ctapi.c b/src/libopensc/reader-ctapi.c index 6218d8ac..e1773909 100644 --- a/src/libopensc/reader-ctapi.c +++ b/src/libopensc/reader-ctapi.c @@ -63,15 +63,180 @@ struct ctapi_functions { }; /* Reader specific private data */ +#define CTAPI_FU_KEYBOARD 0x1 +#define CTAPI_FU_DISPLAY 0x2 +#define CTAPI_FU_BIOMETRIC 0x4 +#define CTAPI_FU_PRINTER 0x8 + struct ctapi_private_data { struct ctapi_functions funcs; unsigned short ctn; + int ctapi_functional_units; }; struct ctapi_slot_data { void *filler; }; +/* Reset slot or reader */ +static int ctapi_reset(struct sc_reader *reader, struct sc_slot_info *slot) +{ + struct ctapi_private_data *priv = GET_PRIV_DATA(reader); + char rv; + u8 cmd[5], rbuf[256], sad, dad; + unsigned short lr; + + cmd[0] = CTBCS_CLA; + cmd[1] = CTBCS_INS_RESET; + cmd[2] = slot ? CTBCS_P1_INTERFACE1 + slot->id : CTBCS_P1_CT_KERNEL; + cmd[3] = 0x00; /* No response. We might also use 0x01 (return ATR) or 0x02 (return historical bytes) here */ + cmd[4] = 0x00; + dad = 1; + sad = 2; + lr = 256; + + rv = priv->funcs.CT_data(priv->ctn, &dad, &sad, 5, cmd, &lr, rbuf); + if (rv || (lr < 2)) { + sc_error(reader->ctx, "Error getting status of terminal: %d, using defaults\n", rv); + return SC_ERROR_TRANSMIT_FAILED; + } + if (rbuf[lr-2] != 0x90) { + sc_error(reader->ctx, "SW1/SW2: 0x%x/0x%x\n", rbuf[lr-2], rbuf[lr-1]); + return SC_ERROR_TRANSMIT_FAILED; + } + return 0; +} + +static void set_default_fu(struct sc_reader *reader) +{ + if (!reader) return; + + reader->slot_count = 1; + reader->slot[0].id = 0; + reader->slot[0].capabilities = 0; + reader->slot[0].atr_len = 0; + reader->slot[0].drv_data = NULL; +} + +/* Detect functional units of the reader according to CT-BCS spec version 1.0 + (14.04.2004, http://www.teletrust.de/down/mct1-0_t4.zip) */ +static void detect_functional_units(struct sc_reader *reader) +{ + struct ctapi_private_data *priv = GET_PRIV_DATA(reader); + char rv; + u8 cmd[5], rbuf[256], sad, dad; + unsigned short lr; + int NumUnits; + int i; + + priv->ctapi_functional_units = 0; + + cmd[0] = CTBCS_CLA; + cmd[1] = CTBCS_INS_STATUS; + cmd[2] = CTBCS_P1_CT_KERNEL; + cmd[3] = CTBCS_P2_STATUS_TFU; + cmd[4] = 0x00; + dad = 1; + sad = 2; + lr = 256; + + rv = priv->funcs.CT_data(priv->ctn, &dad, &sad, 5, cmd, &lr, rbuf); + if (rv || (lr < 4) || (rbuf[lr-2] != 0x90)) { + sc_error(reader->ctx, "Error getting status of terminal: %d, using defaults\n", rv); + set_default_fu(reader); + return; + } + if (rbuf[0] != CTBCS_P2_STATUS_TFU) { + /* Number of slots might also detected by using CTBCS_P2_STATUS_ICC. + If you think that's important please do it... ;) */ + set_default_fu(reader); + sc_error(reader->ctx, "Invalid data object returnd on CTBCS_P2_STATUS_TFU: 0x%x\n", rbuf[0]); + return; + } + NumUnits = rbuf[1]; + if (NumUnits + 4 > lr) { + set_default_fu(reader); + sc_error(reader->ctx, "Invalid data returnd: %d functional units, size %d\n", NumUnits, rv); + set_default_fu(reader); + return; + } + reader->slot_count = 0; + for(i = 0; i < NumUnits; i++) { + switch(rbuf[i+2]) + { + case CTBCS_P1_INTERFACE1: + case CTBCS_P1_INTERFACE2: + case CTBCS_P1_INTERFACE3: + case CTBCS_P1_INTERFACE4: + case CTBCS_P1_INTERFACE5: + case CTBCS_P1_INTERFACE6: + case CTBCS_P1_INTERFACE7: + case CTBCS_P1_INTERFACE8: + case CTBCS_P1_INTERFACE9: + case CTBCS_P1_INTERFACE10: + case CTBCS_P1_INTERFACE11: + case CTBCS_P1_INTERFACE12: + case CTBCS_P1_INTERFACE13: + case CTBCS_P1_INTERFACE14: + /* Maybe a weak point here if multiple interfaces are present and not returned + in the "canonical" order. This is not forbidden by the specs, but why should + anyone want to do that? */ + if (reader->slot_count >= SC_MAX_SLOTS) { + sc_debug(reader->ctx, "Ignoring slot id 0x%x, can only handle %d slots\n", rbuf[i+2], SC_MAX_SLOTS); + } else { + reader->slot[reader->slot_count].id = reader->slot_count; + reader->slot[reader->slot_count].capabilities = 0; /* Just to start with */ + reader->slot[reader->slot_count].atr_len = 0; + reader->slot[reader->slot_count].drv_data = NULL; + reader->slot_count++; + } + break; + + case CTBCS_P1_DISPLAY: + priv->ctapi_functional_units |= CTAPI_FU_DISPLAY; + sc_debug(reader->ctx, "Display detected\n"); + break; + + case CTBCS_P1_KEYPAD: + priv->ctapi_functional_units |= CTAPI_FU_KEYBOARD; + sc_debug(reader->ctx, "Keypad detected\n"); + break; + + case CTBCS_P1_PRINTER: + priv->ctapi_functional_units |= CTAPI_FU_PRINTER; + sc_debug(reader->ctx, "Printer detected\n"); + break; + + case CTBCS_P1_FINGERPRINT: + case CTBCS_P1_VOICEPRINT: + case CTBCS_P1_DSV: + case CTBCS_P1_FACE_RECOGNITION: + case CTBCS_P1_IRISSCAN: + priv->ctapi_functional_units |= CTAPI_FU_BIOMETRIC; + sc_debug(reader->ctx, "Biometric sensor detected\n"); + break; + + default: + sc_debug(reader->ctx, "Unknown functional unit 0x%x\n", rbuf[i+2]); + } + + } + if (reader->slot_count == 0) { + sc_debug(reader->ctx, "No slots returned, assuning one default slot\n"); + set_default_fu(reader); + } + /* CT-BCS does not define Keyboard/Display for each slot, so I assume + those additional units can be used for each slot */ + if (priv->ctapi_functional_units) { + for(i = 0; i < reader->slot_count; i++) { + if (priv->ctapi_functional_units & CTAPI_FU_KEYBOARD) + reader->slot[i].capabilities |= SC_SLOT_CAP_PIN_PAD; + if (priv->ctapi_functional_units & CTAPI_FU_DISPLAY) + reader->slot[i].capabilities |= SC_SLOT_CAP_DISPLAY; + } + } +} + static int refresh_slot_attributes(struct sc_reader *reader, struct sc_slot_info *slot) { @@ -92,12 +257,31 @@ static int refresh_slot_attributes(struct sc_reader *reader, slot->flags = 0; rv = priv->funcs.CT_data(priv->ctn, &dad, &sad, 5, cmd, &lr, rbuf); - if (rv || rbuf[lr-2] != 0x90) { - sc_error(reader->ctx, "Error getting status of terminal: %d\n", rv); + if (rv || (lr < 3) || (rbuf[lr-2] != 0x90)) { + sc_error(reader->ctx, "Error getting status of terminal: %d/%d/0x%x\n", rv, lr, rbuf[lr-2]); return SC_ERROR_TRANSMIT_FAILED; } - if (rbuf[0] == CTBCS_DATA_STATUS_CARD_CONNECT) + if (lr < 4) { + /* Looks like older readers do not return data tag and length field, so assume one slot only */ + if (slot->id > 0) { + sc_error(reader->ctx, "Status for slot id %d not returned, have only 1\n", slot->id); + return SC_ERROR_SLOT_NOT_FOUND; + } + if (rbuf[0] & CTBCS_DATA_STATUS_CARD) + slot->flags = SC_SLOT_CARD_PRESENT; + } else { + if (rbuf[0] != CTBCS_P2_STATUS_ICC) { + /* Should we be more tolerant here? I do not think so... */ + sc_error(reader->ctx, "Invalid data object returnd on CTBCS_P2_STATUS_ICC: 0x%x\n", rbuf[0]); + return SC_ERROR_TRANSMIT_FAILED; + } + if (rbuf[1] <= slot->id) { + sc_error(reader->ctx, "Status for slot id %d not returned, only %d\n", slot->id, rbuf[1]); + return SC_ERROR_SLOT_NOT_FOUND; + } + if (rbuf[2+slot->id] & CTBCS_DATA_STATUS_CARD) slot->flags = SC_SLOT_CARD_PRESENT; + } return 0; } @@ -112,11 +296,16 @@ static int ctapi_transmit(struct sc_reader *reader, struct sc_slot_info *slot, unsigned short lr; char rv; - dad = control? 1 : 0; + if (control) + dad = 1; + else if (!slot || slot->id == 0) + dad = 0; + else + dad = slot->id + 1; /* Adressing of multiple slots, according to CT API 1.0 */ sad = 2; lr = *recvsize; - rv = priv->funcs.CT_data(priv->ctn, &dad, &sad, sendsize, (u8 *) sendbuf, &lr, recvbuf); + rv = priv->funcs.CT_data(priv->ctn, &dad, &sad, (unsigned short)sendsize, (u8 *) sendbuf, &lr, recvbuf); if (rv != 0) { sc_error(reader->ctx, "Error transmitting APDU: %d\n", rv); return SC_ERROR_TRANSMIT_FAILED; @@ -146,7 +335,7 @@ static int ctapi_connect(struct sc_reader *reader, struct sc_slot_info *slot) cmd[0] = CTBCS_CLA; cmd[1] = CTBCS_INS_REQUEST; - cmd[2] = CTBCS_P1_INTERFACE1; + cmd[2] = CTBCS_P1_INTERFACE1+slot->id; cmd[3] = CTBCS_P2_REQUEST_GET_ATR; cmd[4] = 0x00; dad = 1; @@ -195,7 +384,7 @@ static int ctapi_disconnect(struct sc_reader *reader, struct sc_slot_info *slot, { return 0; } - + static int ctapi_lock(struct sc_reader *reader, struct sc_slot_info *slot) { return 0; @@ -248,7 +437,7 @@ static int ctapi_load_module(struct sc_context *ctx, struct ctapi_module *mod; const scconf_list *list; void *dlh; - int r; + int r, i; list = scconf_find_list(conf, "ports"); if (list == NULL) { @@ -263,13 +452,13 @@ static int ctapi_load_module(struct sc_context *ctx, return -1; } - funcs.CT_init = (CT_INIT_TYPE *) scdl_get_address(dlh, "CT_init"); + funcs.CT_init = (CT_INIT_TYPE *) scdl_get_address(dlh, "CT_init"); if (!funcs.CT_init) goto symerr; funcs.CT_close = (CT_CLOSE_TYPE *) scdl_get_address(dlh, "CT_close"); if (!funcs.CT_close) goto symerr; - funcs.CT_data = (CT_DATA_TYPE *) scdl_get_address(dlh, "CT_data"); + funcs.CT_data = (CT_DATA_TYPE *) scdl_get_address(dlh, "CT_data"); if (!funcs.CT_data) goto symerr; @@ -280,13 +469,12 @@ static int ctapi_load_module(struct sc_context *ctx, char rv; struct sc_reader *reader; struct ctapi_private_data *priv; - struct sc_slot_info *slot; if (sscanf(list->data, "%d", &port) != 1) { sc_error(ctx, "Port '%s' is not a number.\n", list->data); continue; } - rv = funcs.CT_init(mod->ctn_count, port); + rv = funcs.CT_init((unsigned short)mod->ctn_count, (unsigned short)port); if (rv) { sc_error(ctx, "CT_init() failed with %d\n", rv); continue; @@ -297,26 +485,25 @@ static int ctapi_load_module(struct sc_context *ctx, reader->drv_data = priv; reader->ops = &ctapi_ops; reader->driver = &ctapi_drv; - reader->slot_count = 1; snprintf(namebuf, sizeof(namebuf), "CT-API %s, port %d", mod->name, port); reader->name = strdup(namebuf); priv->funcs = funcs; priv->ctn = mod->ctn_count; r = _sc_add_reader(ctx, reader); if (r) { - funcs.CT_close(mod->ctn_count); + funcs.CT_close((unsigned short)mod->ctn_count); free(priv); free(reader->name); free(reader); break; } - slot = &reader->slot[0]; - slot->id = 0; - slot->capabilities = 0; - slot->atr_len = 0; - slot->drv_data = NULL; + /* slot count and properties are set in detect_functional_units */ + detect_functional_units(reader); - refresh_slot_attributes(reader, slot); + ctapi_reset(reader, NULL); + for(i = 0; i < reader->slot_count; i++) { + refresh_slot_attributes(reader, &(reader->slot[i])); + } mod->ctn_count++; }