From d81491f4ea3355eba8d0cdcbea684662c984de83 Mon Sep 17 00:00:00 2001 From: Frank Morgner Date: Fri, 28 Feb 2014 23:16:24 +0100 Subject: [PATCH] better PIN handling - avoid overloading of the unblocking command, instead perform resuming the pin automatically. - implemented cache for npa card driver - added support for eSign PIN verification - addes support for get pin info cmd --- npa/src/card-npa.c | 375 +++++++++++++++++++++++++++++++++++++-------- npa/src/card-npa.h | 58 +++++++ npa/src/npa.c | 21 +++ npa/src/npa/npa.h | 17 ++ 4 files changed, 409 insertions(+), 62 deletions(-) create mode 100644 npa/src/card-npa.h diff --git a/npa/src/card-npa.c b/npa/src/card-npa.c index 112eba8..5ef5c8e 100644 --- a/npa/src/card-npa.c +++ b/npa/src/card-npa.c @@ -21,6 +21,7 @@ #include "iso-sm-internal.h" #include "libopensc/internal.h" #include "libopensc/pace.h" +#include "card-npa.h" #include #include #include @@ -29,10 +30,11 @@ #include "libopensc/card.c" #endif -enum { - SC_CARD_TYPE_NPA = 42000, - SC_CARD_TYPE_NPA_TEST, - SC_CARD_TYPE_NPA_ONLINE, +struct npa_drv_data { + unsigned char *can; + size_t can_length; + unsigned char *ef_cardaccess; + size_t ef_cardaccess_length; }; static struct sc_atr_table npa_atrs[] = { @@ -59,24 +61,209 @@ static int npa_match_card(sc_card_t * card) static int npa_init(sc_card_t * card) { - card->drv_data = NULL; - card->caps |= SC_CARD_CAP_APDU_EXT | SC_CARD_CAP_RNG; + struct npa_drv_data *drv_data; + if (card) { + drv_data = calloc(1, sizeof *drv_data); + card->drv_data = drv_data; + if (drv_data) { + drv_data->can = NULL; + drv_data->can_length = 0; + drv_data->ef_cardaccess = NULL; + drv_data->ef_cardaccess_length = 0; + } + + card->caps |= SC_CARD_CAP_APDU_EXT | SC_CARD_CAP_RNG; + memset(&card->sm_ctx, 0, sizeof card->sm_ctx); #ifdef DISABLE_GLOBAL_BOXING_INITIALIZATION - sc_detect_boxing_cmds(card->reader); + sc_detect_boxing_cmds(card->reader); #endif + } return SC_SUCCESS; } -static int npa_pin_cmd(struct sc_card *card, struct sc_pin_cmd_data *data, int *tries_left) +static int npa_finish(sc_card_t * card) { + struct npa_drv_data *drv_data; + + if (card) { + drv_data = card->drv_data; + if (drv_data) { + free(drv_data->ef_cardaccess); + free(drv_data->can); + free(drv_data); + } + card->drv_data = NULL; + } + + return SC_SUCCESS; +} + +static int npa_pin_cmd_get_info(struct sc_card *card, + struct sc_pin_cmd_data *data, int *tries_left) +{ + int r; + u8 pin_reference; + + if (!data || data->pin_type != SC_AC_CHV || !tries_left) { + r = SC_ERROR_INVALID_ARGUMENTS; + goto err; + } + + pin_reference = data->pin_reference; + switch (data->pin_reference) { + case PACE_PIN_ID_CAN: + case PACE_PIN_ID_MRZ: + case PACE_PIN_ID_PUK: + /* usually unlimited number of retries */ + *tries_left = -1; + data->pin1.max_tries = -1; + data->pin1.tries_left = -1; + r = SC_SUCCESS; + break; + + case PACE_PIN_ID_PIN: + /* usually 3 tries */ + *tries_left = 3; + data->pin1.max_tries = 3; + r = npa_pace_get_tries_left(card, + pin_reference, tries_left); + data->pin1.tries_left = *tries_left; + break; + + default: + r = SC_ERROR_OBJECT_NOT_FOUND; + goto err; + } + +err: + return r; +} + +static int npa_pace_verify(struct sc_card *card, + unsigned char pin_reference, struct sc_pin_cmd_pin *pin, + const unsigned char *chat, size_t chat_length, int *tries_left) +{ + int r; struct establish_pace_channel_input pace_input; struct establish_pace_channel_output pace_output; - int r; memset(&pace_input, 0, sizeof pace_input); memset(&pace_output, 0, sizeof pace_output); + if (chat) { + pace_input.chat = chat; + pace_input.chat_length = chat_length; + } + pace_input.pin_id = pin_reference; + if (pin) { + pace_input.pin = pin->data; + pace_input.pin_length = pin->len; + } + npa_get_cache(card, pace_input.pin_id, &pace_input.pin, + &pace_input.pin_length, &pace_output.ef_cardaccess, + &pace_output.ef_cardaccess_length); + + r = perform_pace(card, pace_input, &pace_output, EAC_TR_VERSION_2_02); + + if (tries_left) { + if (pace_output.mse_set_at_sw1 == 0x63 + && (pace_output.mse_set_at_sw2 & 0xc0) == 0xc0) { + *tries_left = pace_output.mse_set_at_sw2 & 0x0f; + } else { + *tries_left = -1; + } + } + + /* resume the PIN if needed */ + if (pin_reference == PACE_PIN_ID_PIN + && r != SC_SUCCESS + && pace_output.mse_set_at_sw1 == 0x63 + && (pace_output.mse_set_at_sw2 & 0xc0) == 0xc0 + && (pace_output.mse_set_at_sw2 & 0x0f) <= UC_PIN_SUSPENDED) { + /* TODO ask for user consent when automatically resuming the PIN */ + sc_log(card->ctx, "%s is suspended. Will try to resume it with %s.\n", + npa_secret_name(pin_reference), npa_secret_name(PACE_PIN_ID_CAN)); + + pace_input.pin_id = PACE_PIN_ID_CAN; + pace_input.pin = NULL; + pace_input.pin_length = 0; + npa_get_cache(card, pace_input.pin_id, &pace_input.pin, + &pace_input.pin_length, NULL, NULL); + + r = perform_pace(card, pace_input, &pace_output, EAC_TR_VERSION_2_02); + + if (r == SC_SUCCESS) { + pace_input.pin_id = pin_reference; + if (pin) { + pace_input.pin = pin->data; + pace_input.pin_length = pin->len; + } + + r = perform_pace(card, pace_input, &pace_output, EAC_TR_VERSION_2_02); + + if (r == SC_SUCCESS) { + sc_log(card->ctx, "%s resumed.\n"); + if (tries_left) { + *tries_left = MAX_PIN_TRIES; + } + } else { + if (tries_left) { + if (pace_output.mse_set_at_sw1 == 0x63 + && (pace_output.mse_set_at_sw2 & 0xc0) == 0xc0) { + *tries_left = pace_output.mse_set_at_sw2 & 0x0f; + } else { + *tries_left = -1; + } + } + } + } + } + + if (pin_reference == PACE_PIN_ID_PIN && tries_left) { + if (*tries_left == 0) { + sc_log(card->ctx, "%s is suspended and must be resumed.\n", + npa_secret_name(pin_reference)); + } else if (*tries_left == 1) { + sc_log(card->ctx, "%s is blocked and must be unblocked.\n", + npa_secret_name(pin_reference)); + } + } + + npa_set_cache(card, pace_input.pin_id, pace_input.pin, + pace_input.pin_length, pace_output.ef_cardaccess, + pace_output.ef_cardaccess_length); + + free(pace_output.ef_cardaccess); + free(pace_output.recent_car); + free(pace_output.previous_car); + free(pace_output.id_icc); + free(pace_output.id_pcd); + + return r; +} + +static int npa_standard_pin_cmd(struct sc_card *card, + struct sc_pin_cmd_data *data, int *tries_left) +{ + int r; + struct sc_card_driver *iso_drv; + + iso_drv = sc_get_iso7816_driver(); + + if (!iso_drv || !iso_drv->ops || !iso_drv->ops->pin_cmd) { + r = SC_ERROR_INTERNAL; + } else { + r = iso_drv->ops->pin_cmd(card, data, tries_left); + } + + return r; +} + +static int npa_pin_cmd(struct sc_card *card, + struct sc_pin_cmd_data *data, int *tries_left) +{ + int r; if (!data) { r = SC_ERROR_INVALID_ARGUMENTS; @@ -88,68 +275,83 @@ static int npa_pin_cmd(struct sc_card *card, struct sc_pin_cmd_data *data, int * goto err; } - if (tries_left) - *tries_left = -1; - - if (data->cmd != SC_PIN_CMD_UNBLOCK - && data->cmd != SC_PIN_CMD_VERIFY - && data->cmd != SC_PIN_CMD_CHANGE) { - /* TODO support SC_PIN_CMD_GET_INFO */ - r = SC_ERROR_NOT_SUPPORTED; - goto err; - } - - pace_input.pin_id = data->pin_reference; - pace_input.pin = data->pin1.data; - pace_input.pin_length = data->pin1.len; - - if (data->cmd == SC_PIN_CMD_UNBLOCK) { - /* FIXME SC_PIN_CMD_UNBLOCK is currently overloaded with the resume - * command. Iff the first secret has the length of a CAN (6) we are - * resuming the PIN with the CAN. Otherwise we will unblock the PIN - * with PUK. */ - if (pace_input.pin_length == CAN_LEN) { - /* Try to resume the PIN with the CAN. */ - pace_input.pin_id = PACE_PIN_ID_CAN; - r = perform_pace(card, pace_input, &pace_output, 2); + switch (data->cmd) { + case SC_PIN_CMD_GET_INFO: + r = npa_pin_cmd_get_info(card, data, tries_left); if (r != SC_SUCCESS) goto err; + break; - /* Initialize pace_input for usage with the intended PIN */ - pace_input.pin_id = data->pin_reference; - pace_input.pin = data->pin2.data; - pace_input.pin_length = data->pin2.len; - } else { - /* Initialize pace_input for usage with the PUK */ - pace_input.pin_id = PACE_PIN_ID_PUK; - } - } - - r = perform_pace(card, pace_input, &pace_output, 2); - if (r != SC_SUCCESS) - goto err; - - if (pace_output.mse_set_at_sw1 == 0x63) { - if ((pace_output.mse_set_at_sw2 & 0xF0) == 0xC0 && tries_left != NULL) - *tries_left = pace_output.mse_set_at_sw2 & 0x0F; - if (*tries_left > 0 && r != SC_SUCCESS) - *tries_left = *tries_left - 1; - } - - switch (data->cmd) { case SC_PIN_CMD_UNBLOCK: - if (pace_input.pin_length != CAN_LEN) { - r = npa_unblock_pin(card); + /* opensc-explorer unblocks the PIN by only sending + * SC_PIN_CMD_UNBLOCK whereas the PKCS#15 framework first verifies + * the PUK with SC_PIN_CMD_VERIFY and then calls with + * SC_PIN_CMD_UNBLOCK. + * + * Here we determine whether the PUK has been verified or not by + * checking if an SM channel has been established. */ + if (card->sm_ctx.sm_mode != SM_MODE_TRANSMIT) { + /* PUK has not yet been verified */ + r = npa_pace_verify(card, PACE_PIN_ID_PUK, &(data->pin1), NULL, + 0, NULL); + if (r != SC_SUCCESS) + goto err; + } + r = npa_reset_retry_counter(card, data->pin_reference, 0, + NULL, 0); + if (r != SC_SUCCESS) + goto err; + break; + + case SC_PIN_CMD_CHANGE: + case SC_PIN_CMD_VERIFY: + switch (data->pin_reference) { + case PACE_PIN_ID_CAN: + case PACE_PIN_ID_PUK: + case PACE_PIN_ID_MRZ: + case PACE_PIN_ID_PIN: + r = npa_pace_verify(card, data->pin_reference, + &(data->pin1), NULL, 0, tries_left); + if (r != SC_SUCCESS) + goto err; + break; + + case NPA_PIN_ID_ESIGN_PIN: + if (card->reader->capabilities & SC_READER_CAP_PACE_ESIGN) { + sc_log(card->ctx, "Found a comfort reader (CAT-K).\n"); + sc_log(card->ctx, "Will verify cached CAN first.\n"); + r = npa_pace_verify(card, PACE_PIN_ID_CAN, NULL, + esign_chat, sizeof esign_chat, tries_left); + if (r != SC_SUCCESS) + goto err; + } else { + sc_log(card->ctx, "No comfort reader (CAT-K) found.\n"); + sc_log(card->ctx, "I hope you have already performed EAC as ST...\n"); + } + r = npa_standard_pin_cmd(card, data, tries_left); + if (r != SC_SUCCESS) + goto err; + break; + + default: + r = SC_ERROR_OBJECT_NOT_FOUND; + goto err; + break; + } + + if (data->cmd == SC_PIN_CMD_CHANGE) { + r = npa_reset_retry_counter(card, data->pin_reference, 1, + (const char *) data->pin2.data, data->pin2.len); if (r != SC_SUCCESS) goto err; } break; - case SC_PIN_CMD_CHANGE: - r = npa_reset_retry_counter(card, data->pin_reference, 1, - (const char *) data->pin2.data, data->pin2.len); - if (r != SC_SUCCESS) - goto err; + + default: + r = SC_ERROR_INTERNAL; + goto err; break; + } err: @@ -163,6 +365,7 @@ static struct sc_card_driver *npa_get_driver(void) npa_ops = *iso_drv->ops; npa_ops.match_card = npa_match_card; npa_ops.init = npa_init; + npa_ops.finish = npa_finish; npa_ops.pin_cmd = npa_pin_cmd; return &npa_drv; @@ -185,3 +388,51 @@ const char *sc_driver_version(void) * tell OpenSC that everything is fine, here. */ return sc_get_version(); } + +void npa_get_cache(struct sc_card *card, + unsigned char pin_id, const unsigned char **pin, size_t *pin_length, + unsigned char **ef_cardaccess, size_t *ef_cardaccess_length) +{ + struct npa_drv_data *drv_data; + if (card && card->drv_data) { + drv_data = card->drv_data; + if (pin_id == PACE_PIN_ID_CAN && pin && pin_length && !*pin) { + /* set CAN if *pin is NULL */ + *pin = drv_data->can; + *pin_length = drv_data->can_length; + sc_log(card->ctx, "Using the cached CAN\n"); + } + if (ef_cardaccess && ef_cardaccess_length && !*ef_cardaccess) { + /* set EF.CardAccess if *ef_cardaccess is NULL */ + *ef_cardaccess = drv_data->ef_cardaccess; + *ef_cardaccess_length = drv_data->ef_cardaccess_length; + } + } +} + +void npa_set_cache(struct sc_card *card, + unsigned char pin_id, const unsigned char *pin, size_t pin_length, + const unsigned char *ef_cardaccess, size_t ef_cardaccess_length) +{ + unsigned char *p; + struct npa_drv_data *drv_data; + if (card && card->drv_data) { + drv_data = card->drv_data; + if (pin_id == PACE_PIN_ID_CAN && pin) { + p = realloc(drv_data->can, pin_length); + if (p) { + memcpy(p, pin, pin_length); + drv_data->can = p; + drv_data->can_length = pin_length; + } + } + if (ef_cardaccess) { + p = realloc(drv_data->ef_cardaccess, ef_cardaccess_length); + if (p) { + memcpy(p, ef_cardaccess, ef_cardaccess_length); + drv_data->ef_cardaccess = p; + drv_data->ef_cardaccess_length = ef_cardaccess_length; + } + } + } +} diff --git a/npa/src/card-npa.h b/npa/src/card-npa.h new file mode 100644 index 0000000..94df2c3 --- /dev/null +++ b/npa/src/card-npa.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2014 Frank Morgner + * + * This file is part of npa. + * + * npa is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * npa is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * npa. If not, see . + */ + +#ifndef _CARD_NPA_H +#define _CARD_NPA_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "libopensc/opensc.h" + +enum { + SC_CARD_TYPE_NPA = 42000, + SC_CARD_TYPE_NPA_TEST, + SC_CARD_TYPE_NPA_ONLINE, +}; + +#define NPA_PIN_ID_ESIGN_PIN 0x83 + +const unsigned char esign_chat[] = { + 0x7F, 0x4C, 0x0E, + 0x06, 0x09, 0x04, 0x00, 0x7F, 0x00, 0x07, 0x03, 0x01, 0x02, 0x03, + 0x53, 0x01, 0x03, +}; + +static const unsigned char df_esign_aid[] = { 0xa0, 0x00, 0x00, 0x01, 0x67, 0x45, 0x53, 0x49, 0x47, 0x4e}; +static const unsigned char df_esign_path[] = { 0x3f, 0x00, 0x50, 0x15, 0x1f, 0xff}; +static const unsigned char ef_cardaccess_path[] = { 0x3f, 0x00, 0x01, 0x1c}; + +void npa_get_cache(struct sc_card *card, + unsigned char pin_id, const unsigned char **pin, size_t *pin_length, + unsigned char **ef_cardaccess, size_t *ef_cardaccess_length); + +void npa_set_cache(struct sc_card *card, + unsigned char pin_id, const unsigned char *pin, size_t pin_length, + const unsigned char *ef_cardaccess, size_t ef_cardaccess_length); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/npa/src/npa.c b/npa/src/npa.c index 46a3c92..4536333 100644 --- a/npa/src/npa.c +++ b/npa/src/npa.c @@ -602,6 +602,27 @@ static int npa_mse_set_at_pace(sc_card_t *card, int protocol, return r; } +int npa_pace_get_tries_left(sc_card_t *card, + enum s_type pin_id, int *tries_left) +{ + int r; + u8 sw1, sw2; + + if (tries_left) { + r = npa_mse_set_at_pace(card, 0, pin_id, 0, &sw1, &sw2); + + if (r > 0 && (sw1 == 0x63) && ((sw2 & 0xc0) == 0xc0)) { + *tries_left = sw2 & 0x0f; + } else { + *tries_left = -1; + } + } else { + r = SC_ERROR_INVALID_ARGUMENTS; + } + + return r; +} + #define ISO_GENERAL_AUTHENTICATE 0x86 #define ISO_COMMAND_CHAINING 0x10 diff --git a/npa/src/npa/npa.h b/npa/src/npa/npa.h index a2eba16..cdac603 100644 --- a/npa/src/npa/npa.h +++ b/npa/src/npa/npa.h @@ -98,6 +98,11 @@ extern "C" { #define CAN_LEN 6 /** @brief Minimum length of MRZ */ #define MAX_MRZ_LEN 128 +/** @brief Number of retries for PIN */ +#define MAX_PIN_TRIES 3 +/** @brief Usage counter of PIN in suspended state */ +#define UC_PIN_SUSPENDED 1 + /** * @brief Names the type of the PACE secret @@ -199,6 +204,18 @@ int perform_chip_authentication(sc_card_t *card); int npa_reset_retry_counter(sc_card_t *card, enum s_type pin_id, int ask_for_secret, const char *new, size_t new_len); + +/** + * @brief Sends an MSE:Set AT to determine the number of remaining tries + * + * @param[in] card + * @param[in] pin_id Type of secret (usually PIN or CAN). You may use enum s_type from \c . + * @param[in,out] tries_left Tries left or -1 if no specific number has been returned by the card (e.g. when there is no limit in retries). + * + * @return \c SC_SUCCESS or error code if an error occurred + */ +int npa_pace_get_tries_left(sc_card_t *card, + enum s_type pin_id, int *tries_left); /** * @brief Send APDU to unblock the PIN *