From 5a11d0e2fd9c7734dcc11352483b6d4f7f9291b5 Mon Sep 17 00:00:00 2001 From: Nicholas Wilson Date: Fri, 21 Aug 2015 18:51:30 +0100 Subject: [PATCH 1/2] Add support for C_GetTokenInfo pin status flags for ISO7816 cards This is already supported for a couple of the card drivers, but since it's a general feature of ISO7816 it should go in iso7816.c, rather than the current situation where identical code for this is copy and pasted in each driver. However, some cards apparently don't support this feature and count it as a failed PIN attempt, so I've added a flag for now to indicate whether the card supports this feature. It future, it could be moved to blacklist cards rather than whitelist them, subject to more testing. --- src/libopensc/card-myeid.c | 43 +------------------------------------ src/libopensc/card-sc-hsm.c | 36 +------------------------------ src/libopensc/iso7816.c | 41 +++++++++++++++++++++++++++++------ src/libopensc/opensc.h | 3 +++ 4 files changed, 39 insertions(+), 84 deletions(-) diff --git a/src/libopensc/card-myeid.c b/src/libopensc/card-myeid.c index 9d6417f1..ead6ba9a 100644 --- a/src/libopensc/card-myeid.c +++ b/src/libopensc/card-myeid.c @@ -170,7 +170,7 @@ static int myeid_init(struct sc_card *card) #endif /* State that we have an RNG */ - card->caps |= SC_CARD_CAP_RNG; + card->caps |= SC_CARD_CAP_RNG | SC_CARD_CAP_ISO7816_PIN_INFO; card->max_recv_size = 255; card->max_send_size = 255; @@ -524,42 +524,6 @@ static int myeid_delete_file(struct sc_card *card, const struct sc_path *path) LOG_FUNC_RETURN(card->ctx, sc_check_sw(card, apdu.sw1, apdu.sw2)); } -static int myeid_pin_info(sc_card_t *card, struct sc_pin_cmd_data *data, - int *tries_left) -{ - sc_apdu_t apdu; - int r; - - sc_format_apdu(card, &apdu, SC_APDU_CASE_1, 0x20, 0x00, data->pin_reference); - - r = sc_transmit_apdu(card, &apdu); - LOG_TEST_RET(card->ctx, r, "APDU transmit failed"); - - r = sc_check_sw(card, apdu.sw1, apdu.sw2); - - if (r == SC_ERROR_PIN_CODE_INCORRECT) { - data->pin1.tries_left = apdu.sw2 & 0xF; - r = SC_SUCCESS; - } else if (r == SC_ERROR_AUTH_METHOD_BLOCKED) { - data->pin1.tries_left = 0; - r = SC_SUCCESS; - } - LOG_TEST_RET(card->ctx, r, "Check SW error"); - - if (r == SC_SUCCESS) - { - data->pin1.pad_length = data->pin2.pad_length = 8; - data->pin1.pad_char = data->pin2.pad_char = 0xFF; - } - - - if (tries_left != NULL) { - *tries_left = data->pin1.tries_left; - } - - LOG_FUNC_RETURN(card->ctx, SC_SUCCESS); -} - static int myeid_pin_cmd(sc_card_t *card, struct sc_pin_cmd_data *data, int *tries_left) { @@ -567,11 +531,6 @@ static int myeid_pin_cmd(sc_card_t *card, struct sc_pin_cmd_data *data, LOG_FUNC_CALLED(card->ctx); - if (data->cmd == SC_PIN_CMD_GET_INFO) - { - return myeid_pin_info(card, data, tries_left); - } - sc_log(card->ctx, "ref (%d), pin1 len(%d), pin2 len (%d)\n", data->pin_reference, data->pin1.len, data->pin2.len); diff --git a/src/libopensc/card-sc-hsm.c b/src/libopensc/card-sc-hsm.c index 87d563b2..b02c84e0 100644 --- a/src/libopensc/card-sc-hsm.c +++ b/src/libopensc/card-sc-hsm.c @@ -137,37 +137,6 @@ static int sc_hsm_match_card(struct sc_card *card) -static int sc_hsm_pin_info(sc_card_t *card, struct sc_pin_cmd_data *data, - int *tries_left) -{ - sc_apdu_t apdu; - int r; - - sc_format_apdu(card, &apdu, SC_APDU_CASE_1, 0x20, 0x00, data->pin_reference); - - r = sc_transmit_apdu(card, &apdu); - LOG_TEST_RET(card->ctx, r, "APDU transmit failed"); - - r = sc_check_sw(card, apdu.sw1, apdu.sw2); - - if (r == SC_ERROR_PIN_CODE_INCORRECT) { - data->pin1.tries_left = apdu.sw2 & 0xF; - r = SC_SUCCESS; - } else if (r == SC_ERROR_AUTH_METHOD_BLOCKED) { - data->pin1.tries_left = 0; - r = SC_SUCCESS; - } - LOG_TEST_RET(card->ctx, r, "Check SW error"); - - if (tries_left != NULL) { - *tries_left = data->pin1.tries_left; - } - - LOG_FUNC_RETURN(card->ctx, SC_SUCCESS); -} - - - /* * Encode 16 hexadecimals of SO-PIN into binary form * Caller must check length of sopin and provide an 8 byte buffer @@ -206,9 +175,6 @@ static int sc_hsm_pin_cmd(sc_card_t *card, struct sc_pin_cmd_data *data, sc_hsm_private_data_t *priv = (sc_hsm_private_data_t *) card->drv_data; int r; - if (data->cmd == SC_PIN_CMD_GET_INFO) { - return sc_hsm_pin_info(card, data, tries_left); - } if ((data->cmd == SC_PIN_CMD_VERIFY) && (data->pin_reference == 0x88)) { if (data->pin1.len != 16) return SC_ERROR_INVALID_PIN_LENGTH; @@ -1058,7 +1024,7 @@ static int sc_hsm_init(struct sc_card *card) _sc_card_add_ec_alg(card, 256, flags, ext_flags, NULL); _sc_card_add_ec_alg(card, 320, flags, ext_flags, NULL); - card->caps |= SC_CARD_CAP_RNG|SC_CARD_CAP_APDU_EXT; + card->caps |= SC_CARD_CAP_RNG|SC_CARD_CAP_APDU_EXT|SC_CARD_CAP_ISO7816_PIN_INFO; card->max_send_size = 1431; // 1439 buffer size - 8 byte TLV because of odd ins in UPDATE BINARY card->max_recv_size = 0; // Card supports sending with extended length APDU and without limit diff --git a/src/libopensc/iso7816.c b/src/libopensc/iso7816.c index baa15814..d0bf8b4f 100644 --- a/src/libopensc/iso7816.c +++ b/src/libopensc/iso7816.c @@ -987,6 +987,7 @@ iso7816_build_pin_apdu(struct sc_card *card, struct sc_apdu *apdu, struct sc_pin_cmd_data *data, u8 *buf, size_t buf_len) { int r, len = 0, pad = 0, use_pin_pad = 0, ins, p1 = 0; + int cse = SC_APDU_CASE_3_SHORT; switch (data->pin_type) { case SC_AC_CHV: @@ -1052,11 +1053,16 @@ iso7816_build_pin_apdu(struct sc_card *card, struct sc_apdu *apdu, p1 |= 0x01; } break; + case SC_PIN_CMD_GET_INFO: + ins = 0x20; + /* No data to send or to receive */ + cse = SC_APDU_CASE_1; + break; default: return SC_ERROR_NOT_SUPPORTED; } - sc_format_apdu(card, apdu, SC_APDU_CASE_3_SHORT, ins, p1, data->pin_reference); + sc_format_apdu(card, apdu, cse, ins, p1, data->pin_reference); apdu->lc = len; apdu->datalen = len; apdu->data = buf; @@ -1076,6 +1082,16 @@ iso7816_pin_cmd(struct sc_card *card, struct sc_pin_cmd_data *data, int *tries_l if (tries_left) *tries_left = -1; + /* Many cards do support PIN status queries, but some cards don't and + * mistakenly count the command as a failed PIN attempt, so for now we + * whitelist cards with this flag. In future this may be reduced to a + * blacklist, subject to testing more cards. */ + if (data->cmd == SC_PIN_CMD_GET_INFO && + !(card->caps & SC_CARD_CAP_ISO7816_PIN_INFO)) { + sc_log(card->ctx, "Card does not support PIN status queries"); + return SC_ERROR_NOT_SUPPORTED; + } + /* See if we've been called from another card driver, which is * passing an APDU to us (this allows to write card drivers * whose PIN functions behave "mostly like ISO" except in some @@ -1089,7 +1105,7 @@ iso7816_pin_cmd(struct sc_card *card, struct sc_pin_cmd_data *data, int *tries_l } apdu = data->apdu; - if (!(data->flags & SC_PIN_CMD_USE_PINPAD)) { + if (!(data->flags & SC_PIN_CMD_USE_PINPAD) || data->cmd == SC_PIN_CMD_GET_INFO) { /* Transmit the APDU to the card */ r = sc_transmit_apdu(card, apdu); @@ -1119,12 +1135,23 @@ iso7816_pin_cmd(struct sc_card *card, struct sc_pin_cmd_data *data, int *tries_l data->apdu = NULL; LOG_TEST_RET(card->ctx, r, "APDU transmit failed"); - if (apdu->sw1 == 0x63) { - if ((apdu->sw2 & 0xF0) == 0xC0 && tries_left != NULL) - *tries_left = apdu->sw2 & 0x0F; - return SC_ERROR_PIN_CODE_INCORRECT; + r = sc_check_sw(card, apdu->sw1, apdu->sw2); + + if (data->cmd == SC_PIN_CMD_GET_INFO) { + if (r == SC_ERROR_PIN_CODE_INCORRECT) { + data->pin1.tries_left = apdu->sw2 & 0xF; + r = SC_SUCCESS; + } else if (r == SC_ERROR_AUTH_METHOD_BLOCKED) { + data->pin1.tries_left = 0; + r = SC_SUCCESS; + } + + if (tries_left != NULL) { + *tries_left = data->pin1.tries_left; + } } - return sc_check_sw(card, apdu->sw1, apdu->sw2); + + return r; } diff --git a/src/libopensc/opensc.h b/src/libopensc/opensc.h index e9a4d19a..69fdd34a 100644 --- a/src/libopensc/opensc.h +++ b/src/libopensc/opensc.h @@ -439,6 +439,9 @@ struct sc_reader_operations { /* Card has on-board random number source. */ #define SC_CARD_CAP_RNG 0x00000004 +/* Card supports ISO7816 PIN status queries using an empty VERIFY */ +#define SC_CARD_CAP_ISO7816_PIN_INFO 0x00000008 + /* Use the card's ACs in sc_pkcs15init_authenticate(), * instead of relying on the ACL info in the profile files. */ #define SC_CARD_CAP_USE_FCI_AC 0x00000010 From 2897e6fb5cbb78696a887c68a675c696957bb455 Mon Sep 17 00:00:00 2001 From: Nicholas Wilson Date: Sun, 23 Aug 2015 22:33:50 +0100 Subject: [PATCH 2/2] Leniently interpret the ISO7816 return codes in card-piv.c This adds support for the Yubikey NEO. I'm not sure whether it breaks the specification, or follows some other version of the spec, but in my testing it returns SW1=0x63, SW2=0x0N for N PIN tries remaining. Ignoring the top nibble seems a harmless change to the behaviour to support this device. --- src/libopensc/card-piv.c | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/libopensc/card-piv.c b/src/libopensc/card-piv.c index c12c9bd4..218d74d3 100644 --- a/src/libopensc/card-piv.c +++ b/src/libopensc/card-piv.c @@ -2895,7 +2895,7 @@ static int piv_init(sc_card_t *card) _sc_card_add_ec_alg(card, 256, flags, ext_flags, NULL); _sc_card_add_ec_alg(card, 384, flags, ext_flags, NULL); - card->caps |= SC_CARD_CAP_RNG; + card->caps |= SC_CARD_CAP_RNG | SC_CARD_CAP_ISO7816_PIN_INFO; /* * 800-73-3 cards may have a history object and/or a discovery object @@ -2912,6 +2912,27 @@ static int piv_init(sc_card_t *card) } +static int piv_check_sw(struct sc_card *card, unsigned int sw1, unsigned int sw2) +{ + struct sc_card_driver *iso_drv = sc_get_iso7816_driver(); + + const u8 *yubikey_neo_atr = + (const u8*)"\x3B\xFC\x13\x00\x00\x81\x31\xFE\x15\x59\x75\x62\x69\x6B\x65\x79\x4E\x45\x4F\x72\x33\xE1"; + if (card->atr.len != 22 || memcmp(card->atr.value, yubikey_neo_atr, 22) != 0) + return iso_drv->ops->check_sw(card, sw1, sw2); + + /* Handle here the Yubikey NEO, which returns 0x0X rather than 0xCX to + * indicate the number of remaining PIN retries. Perhaps they misread the + * spec and thought 0xCX meant "clear" or "don't care", not a literal 0xC! */ + if (sw1 == 0x63U && (sw2 & ~0x0fU) == 0x00U && sw2 != 0) { + sc_log(card->ctx, "Verification failed (remaining tries: %d)", (sw2 & 0x0f)); + return SC_ERROR_PIN_CODE_INCORRECT; + } + + return iso_drv->ops->check_sw(card, sw1, sw2); +} + + static int piv_pin_cmd(sc_card_t *card, struct sc_pin_cmd_data *data, int *tries_left) { @@ -2952,6 +2973,7 @@ static struct sc_card_driver * sc_get_driver(void) piv_ops.restore_security_env = piv_restore_security_env; piv_ops.compute_signature = piv_compute_signature; piv_ops.decipher = piv_decipher; + piv_ops.check_sw = piv_check_sw; piv_ops.card_ctl = piv_card_ctl; piv_ops.pin_cmd = piv_pin_cmd;