diff --git a/doc/files/opensc.conf.5.xml.in b/doc/files/opensc.conf.5.xml.in index 4493fb87..4b95fbae 100644 --- a/doc/files/opensc.conf.5.xml.in +++ b/doc/files/opensc.conf.5.xml.in @@ -293,6 +293,9 @@ app application { dnie: See + + edo: See + Any other value: Configuration block for an externally loaded card driver @@ -715,6 +718,26 @@ app application { + + Configuration Options for Polish eID Card + + + + + + + CAN (Card Access Number – 6 digit number + printed on the right bottom corner of the + front side of the document) is required + to establish connection with the card. + It might be overwritten by EDO_CAN + environment variable. Currently, it is not + possible to set it in any other way. + + + + + Configuration based on ATR diff --git a/etc/opensc.conf.example.in b/etc/opensc.conf.example.in index bcb7ddb4..5e9ba53d 100644 --- a/etc/opensc.conf.example.in +++ b/etc/opensc.conf.example.in @@ -202,6 +202,15 @@ app default { # user_consent_app = "/usr/bin/pinentry"; } + card_driver edo { + # CAN is required to establish connection + # with the card. It might be overridden by + # EDO_CAN environment variable. Currently, + # it is not possible to set it in any other way. + # + #can = 123456; + } + # In addition to the built-in list of known cards in the # card driver, you can configure a new card for the driver # using the card_atr block. The goal is to centralize diff --git a/src/libopensc/Makefile.am b/src/libopensc/Makefile.am index 44e74db6..1b47d6eb 100644 --- a/src/libopensc/Makefile.am +++ b/src/libopensc/Makefile.am @@ -50,6 +50,7 @@ libopensc_la_SOURCES_BASE = \ card-dnie.c cwa14890.c cwa-dnie.c \ card-isoApplet.c card-masktech.c card-gids.c card-jpki.c \ card-npa.c card-esteid2018.c card-idprime.c \ + card-edo.c \ \ pkcs15-openpgp.c pkcs15-starcert.c pkcs15-cardos.c \ pkcs15-tcos.c pkcs15-esteid.c pkcs15-gemsafeGPK.c \ @@ -132,6 +133,7 @@ TIDY_FILES = \ cwa14890.c cwa-dnie.c \ card-isoApplet.c card-masktech.c card-jpki.c \ card-npa.c card-esteid2018.c card-idprime.c \ + card-edo.c \ \ pkcs15-openpgp.c pkcs15-cardos.c \ pkcs15-tcos.c pkcs15-esteid.c \ diff --git a/src/libopensc/Makefile.mak b/src/libopensc/Makefile.mak index 1b0ff45c..b41ed23d 100644 --- a/src/libopensc/Makefile.mak +++ b/src/libopensc/Makefile.mak @@ -28,6 +28,7 @@ OBJECTS = \ card-sc-hsm.obj card-dnie.obj card-isoApplet.obj pkcs15-coolkey.obj \ card-masktech.obj card-gids.obj card-jpki.obj \ card-npa.obj card-esteid2018.obj card-idprime.obj \ + card-edo.obj \ \ pkcs15-openpgp.obj pkcs15-starcert.obj pkcs15-cardos.obj \ pkcs15-tcos.obj pkcs15-esteid.obj pkcs15-gemsafeGPK.obj \ diff --git a/src/libopensc/card-edo.c b/src/libopensc/card-edo.c new file mode 100644 index 00000000..e00d4e88 --- /dev/null +++ b/src/libopensc/card-edo.c @@ -0,0 +1,336 @@ +/* + * Copyright (C) 2020 Piotr Majkrzak + * + * This file is part of OpenSC. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#if defined(ENABLE_SM) && defined(ENABLE_OPENPACE) + +#include "libopensc/internal.h" +#include "libopensc/opensc.h" +#include "libopensc/pace.h" +#include "libopensc/sm.h" +#include "libopensc/asn1.h" +#include "sm/sm-eac.h" +#include +#include + + +static struct sc_card_operations edo_ops; + + +static struct sc_card_driver edo_drv = { + "Polish eID card (e-dowód, eDO)", + "edo", + &edo_ops, + NULL, 0, NULL +}; + + +static const struct sc_atr_table edo_atrs[] = { + { "3b:84:80:01:47:43:50:43:12", NULL, NULL, SC_CARD_TYPE_EDO, 0, NULL }, + { NULL, NULL, NULL, 0, 0, NULL } +}; + + +static struct { + int len; + struct sc_object_id oid; +} edo_curves[] = { + // secp384r1 + {384, {{1, 3, 132, 0, 34, -1}}} +}; + + +static void edo_eac_init() { + extern void EAC_init(void); + static int initialized = 0; + if (!initialized) { + EAC_init(); + initialized = 1; + } +} + + +static int edo_match_card(sc_card_t* card) { + SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE); + if (_sc_match_atr(card, edo_atrs, &card->type) >= 0) { + sc_log(card->ctx, "ATR recognized as Polish eID card."); + LOG_FUNC_RETURN(card->ctx, 1); + } + LOG_FUNC_RETURN(card->ctx, 0); +} + + +static int edo_get_can(sc_card_t* card, struct establish_pace_channel_input* pace_input) { + SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE); + const char* can; + + can = getenv("EDO_CAN"); + + if (!can || can[0] != '\0') { + for (size_t i = 0; card->ctx->conf_blocks[i]; ++i) { + scconf_block** blocks = scconf_find_blocks(card->ctx->conf, card->ctx->conf_blocks[i], "card_driver", "edo"); + if (!blocks) + continue; + for (size_t j = 0; blocks[j]; ++j) + if ((can = scconf_get_str(blocks[j], "can", NULL))) + break; + free(blocks); + } + } + + if (!can || 6 != strlen(can)) { + sc_log(card->ctx, "Missing or invalid CAN. 6 digits required."); + LOG_FUNC_RETURN(card->ctx, SC_ERROR_UNKNOWN); + } + + pace_input->pin_id = PACE_PIN_ID_CAN; + pace_input->pin = (const unsigned char*)can; + pace_input->pin_length = 6; + + LOG_FUNC_RETURN(card->ctx, SC_SUCCESS); +} + + +static int edo_unlock(sc_card_t* card) { + SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE); + + struct establish_pace_channel_input pace_input; + struct establish_pace_channel_output pace_output; + + memset(&pace_input, 0, sizeof pace_input); + memset(&pace_output, 0, sizeof pace_output); + + if (SC_SUCCESS != edo_get_can(card, &pace_input)) { + sc_log(card->ctx, "Error reading CAN."); + LOG_FUNC_RETURN(card->ctx, SC_ERROR_UNKNOWN); + } + + if (SC_SUCCESS != perform_pace(card, pace_input, &pace_output, EAC_TR_VERSION_2_02)) { + sc_log(card->ctx, "Error verifying CAN."); + LOG_FUNC_RETURN(card->ctx, SC_ERROR_UNKNOWN); + } + + LOG_FUNC_RETURN(card->ctx, SC_SUCCESS); +} + + +struct edo_buff { + u8 val[SC_MAX_APDU_RESP_SIZE]; + size_t len; +}; + + +static int edo_select_mf(struct sc_card* card, struct edo_buff* buff) { + LOG_FUNC_CALLED(card->ctx); + struct sc_apdu apdu; + sc_format_apdu_ex(&apdu, 00, 0xA4, 0x00, 0x00, NULL, 0, buff->val, sizeof buff->val); + LOG_TEST_RET(card->ctx, sc_transmit_apdu(card, &apdu), "APDU transmit failed"); + LOG_TEST_RET(card->ctx, sc_check_sw(card, apdu.sw1, apdu.sw2), "SW check failed"); + buff->len = apdu.resplen; + LOG_FUNC_RETURN(card->ctx, SC_SUCCESS); +} + + +static int edo_select_df(struct sc_card* card, const u8 path[2], struct edo_buff* buff) { + LOG_FUNC_CALLED(card->ctx); + struct sc_apdu apdu; + sc_format_apdu_ex(&apdu, 00, 0xA4, 0x01, 0x04, path, 2, buff->val, sizeof buff->val); + LOG_TEST_RET(card->ctx, sc_transmit_apdu(card, &apdu), "APDU transmit failed"); + LOG_TEST_RET(card->ctx, sc_check_sw(card, apdu.sw1, apdu.sw2), "SW check failed"); + buff->len = apdu.resplen; + LOG_FUNC_RETURN(card->ctx, SC_SUCCESS); +} + + +static int edo_select_ef(struct sc_card* card, const u8 path[2], struct edo_buff* buff) { + LOG_FUNC_CALLED(card->ctx); + struct sc_apdu apdu; + sc_format_apdu_ex(&apdu, 00, 0xA4, 0x02, 0x04, path, 2, buff->val, sizeof buff->val); + LOG_TEST_RET(card->ctx, sc_transmit_apdu(card, &apdu), "APDU transmit failed"); + LOG_TEST_RET(card->ctx, sc_check_sw(card, apdu.sw1, apdu.sw2), "SW check failed"); + buff->len = apdu.resplen; + LOG_FUNC_RETURN(card->ctx, SC_SUCCESS); +} + + +static int edo_select_name(struct sc_card* card, const u8* name, size_t namelen, struct edo_buff* buff) { + LOG_FUNC_CALLED(card->ctx); + struct sc_apdu apdu; + sc_format_apdu_ex(&apdu, 00, 0xA4, 0x04, 0x00, name, namelen, buff->val, sizeof buff->val); + LOG_TEST_RET(card->ctx, sc_transmit_apdu(card, &apdu), "APDU transmit failed"); + LOG_TEST_RET(card->ctx, sc_check_sw(card, apdu.sw1, apdu.sw2), "SW check failed"); + buff->len = apdu.resplen; + LOG_FUNC_RETURN(card->ctx, SC_SUCCESS); +} + + +static int edo_select_path(struct sc_card* card, const u8* path, size_t pathlen, struct edo_buff* buff) { + LOG_FUNC_CALLED(card->ctx); + while (pathlen >= 2) { + if (path[0] == 0x3F && path[1] == 0x00) + LOG_TEST_RET(card->ctx, edo_select_mf(card, buff), "MF select failed"); + else if (path[0] == 0xDF) + LOG_TEST_RET(card->ctx, edo_select_df(card, path, buff), "DF select failed"); + else if (pathlen == 2) + LOG_TEST_RET(card->ctx, edo_select_ef(card, path, buff), "EF select failed"); + else + LOG_FUNC_RETURN(card->ctx, SC_ERROR_INVALID_ARGUMENTS); + + path += 2; + pathlen -= 2; + } + if (pathlen) + LOG_FUNC_RETURN(card->ctx, SC_ERROR_INCORRECT_PARAMETERS); + LOG_FUNC_RETURN(card->ctx, SC_SUCCESS); +} + + +/*! Selects file specified by given path. + * + * Card does not support selecting file at once, that's why it have to be done in following way: + * 1. Select AID if provided, + * 2. Select MF if provided, + * 3. Select DF until provided, + * 4. Select EF if provided. + */ +static int edo_select_file(struct sc_card* card, const struct sc_path* in_path, struct sc_file** file_out) { + LOG_FUNC_CALLED(card->ctx); + struct edo_buff buff; + + switch (in_path->type) { + case SC_PATH_TYPE_PATH: + case SC_PATH_TYPE_FILE_ID: + if (in_path->aid.len) + LOG_TEST_RET(card->ctx, edo_select_name(card, in_path->aid.value, in_path->aid.len, &buff), "Select AID failed"); + if (in_path->len) + LOG_TEST_RET(card->ctx, edo_select_path(card, in_path->value, in_path->len, &buff), "Select path failed"); + break; + case SC_PATH_TYPE_DF_NAME: + LOG_TEST_RET(card->ctx, edo_select_name(card, in_path->value, in_path->len, &buff), "Select AID failed"); + break; + default: + LOG_FUNC_RETURN(card->ctx, SC_ERROR_NOT_SUPPORTED); + } + + if (file_out) { + if (buff.len < 2) + LOG_FUNC_RETURN(card->ctx, SC_ERROR_UNKNOWN_DATA_RECEIVED); + if (!(*file_out = sc_file_new())) + LOG_FUNC_RETURN(card->ctx, SC_ERROR_OUT_OF_MEMORY); + (*file_out)->path = *in_path; + LOG_TEST_RET(card->ctx, card->ops->process_fci(card, *file_out, buff.val, buff.len), "Process FCI failed"); + } + + LOG_FUNC_RETURN(card->ctx, SC_SUCCESS); +} + + +/*! Computes ECDSA signature. + * + * If ECDSA was used, the ASN.1 sequence of integers R,S returned by the + * card needs to be converted to the raw concatenation of R,S for PKCS#11. + */ +static int edo_compute_signature(struct sc_card* card, const u8* data, size_t datalen, u8* out, size_t outlen) { + LOG_FUNC_CALLED(card->ctx); + u8 sig[SC_MAX_APDU_RESP_SIZE]; + LOG_TEST_RET(card->ctx, sc_get_iso7816_driver()->ops->compute_signature(card, data, datalen, sig, sizeof sig), "Internal signature failed"); + LOG_TEST_RET(card->ctx, sc_asn1_sig_value_sequence_to_rs(card->ctx, sig, sizeof sig, out, outlen), "ASN.1 conversion failed"); + LOG_FUNC_RETURN(card->ctx, SC_SUCCESS); +} + + +/*! Sets security environment + * + * Card expects key file to be selected first, followed by the + * set security env packet with: 0x80, 0x01, 0xcc, 0x84, 0x01, 0x80|x, + * where x is the key reference byte. + */ +static int edo_set_security_env(struct sc_card* card, const struct sc_security_env* env, int se_num) { + LOG_FUNC_CALLED(card->ctx); + struct sc_apdu apdu; + + if (env->algorithm == SC_ALGORITHM_EC && env->operation == SC_SEC_OPERATION_SIGN && env->flags & SC_SEC_ENV_KEY_REF_PRESENT) { + u8 payload[] = {0x80, 0x01, 0xcc, 0x84, 0x01, 0x80 | env->key_ref[0]}; + sc_format_apdu_ex(&apdu, 0x00, 0x22, 0x41, 0xB6, payload, sizeof payload, NULL, 0); + } else + LOG_FUNC_RETURN(card->ctx, SC_ERROR_NOT_SUPPORTED); + + LOG_TEST_RET(card->ctx, sc_select_file(card, &env->file_ref, NULL), "SELECT file failed"); + LOG_TEST_RET(card->ctx, sc_transmit_apdu(card, &apdu), "APDU transmit failed"); + LOG_TEST_RET(card->ctx, sc_check_sw(card, apdu.sw1, apdu.sw2), "SW check failed"); + + LOG_FUNC_RETURN(card->ctx, SC_SUCCESS); +} + + +/*! Initializes card driver. + * + * Card is known to support only short APDU-s. + * Preinitialized keys are on secp384r1 curve. + * PACE channel have to be established. + */ +static int edo_init(sc_card_t* card) { + SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE); + + edo_eac_init(); + + memset(&card->sm_ctx, 0, sizeof card->sm_ctx); + + card->max_send_size = SC_MAX_APDU_RESP_SIZE; + card->max_recv_size = SC_MAX_APDU_RESP_SIZE; + + for (size_t i = 0; i < sizeof edo_curves / sizeof * edo_curves; ++i) { + LOG_TEST_RET(card->ctx, _sc_card_add_ec_alg( + card, edo_curves[i].len, + SC_ALGORITHM_ECDSA_RAW | SC_ALGORITHM_ECDSA_HASH_NONE, + 0, &edo_curves[i].oid + ), "Add EC alg failed"); + } + + LOG_TEST_RET(card->ctx, sc_enum_apps(card), "Enumerate apps failed"); + + LOG_TEST_RET(card->ctx, edo_unlock(card), "Unlock card failed"); + + LOG_FUNC_RETURN(card->ctx, SC_SUCCESS); +} + + +struct sc_card_driver* sc_get_edo_driver(void) { + edo_ops = *sc_get_iso7816_driver()->ops; + edo_ops.match_card = edo_match_card; + edo_ops.init = edo_init; + edo_ops.select_file = edo_select_file; + edo_ops.set_security_env = edo_set_security_env; + edo_ops.compute_signature = edo_compute_signature; + + return &edo_drv; +} + +#else + +#include "libopensc/opensc.h" + +struct sc_card_driver* sc_get_edo_driver(void) { + return NULL; +} + +#endif diff --git a/src/libopensc/cards.h b/src/libopensc/cards.h index 8d58fb93..0ec25a46 100644 --- a/src/libopensc/cards.h +++ b/src/libopensc/cards.h @@ -266,6 +266,9 @@ enum { SC_CARD_TYPE_IDPRIME_V1, SC_CARD_TYPE_IDPRIME_V2, SC_CARD_TYPE_IDPRIME_GENERIC, + + /* eDO cards */ + SC_CARD_TYPE_EDO = 38000 }; extern sc_card_driver_t *sc_get_default_driver(void); @@ -310,6 +313,7 @@ extern sc_card_driver_t *sc_get_cac1_driver(void); extern sc_card_driver_t *sc_get_npa_driver(void); extern sc_card_driver_t *sc_get_esteid2018_driver(void); extern sc_card_driver_t *sc_get_idprime_driver(void); +extern sc_card_driver_t *sc_get_edo_driver(void); #ifdef __cplusplus } diff --git a/src/libopensc/ctx.c b/src/libopensc/ctx.c index 4c5adc6e..6b57170f 100644 --- a/src/libopensc/ctx.c +++ b/src/libopensc/ctx.c @@ -129,6 +129,9 @@ static const struct _sc_driver_entry internal_card_drivers[] = { { "westcos", (void *(*)(void)) sc_get_westcos_driver }, { "esteid2018", (void *(*)(void)) sc_get_esteid2018_driver }, { "idprime", (void *(*)(void)) sc_get_idprime_driver }, +#if defined(ENABLE_SM) && defined(ENABLE_OPENPACE) + { "edo", (void *(*)(void)) sc_get_edo_driver }, +#endif /* Here should be placed drivers that need some APDU transactions in the * driver's `match_card()` function. */ diff --git a/src/sm/sm-eac.c b/src/sm/sm-eac.c index b1d660cc..8c12119a 100644 --- a/src/sm/sm-eac.c +++ b/src/sm/sm-eac.c @@ -612,7 +612,7 @@ static int eac_gen_auth_1_encrypted_nonce(sc_card_t *card, EAC_GEN_AUTH_PACE_R *r_data = NULL; unsigned char *d = NULL, *p; int r, l; - unsigned char resp[SC_MAX_EXT_APDU_RESP_SIZE]; + unsigned char resp[SC_MAX_APDU_RESP_SIZE]; c_data = EAC_GEN_AUTH_PACE_C_new(); if (!c_data) { @@ -691,7 +691,7 @@ static int eac_gen_auth_2_map_nonce(sc_card_t *card, EAC_GEN_AUTH_PACE_R *r_data = NULL; unsigned char *d = NULL, *p; int r, l; - unsigned char resp[SC_MAX_EXT_APDU_RESP_SIZE]; + unsigned char resp[SC_MAX_APDU_RESP_SIZE]; c_data = EAC_GEN_AUTH_PACE_C_new(); if (!c_data) { @@ -777,7 +777,7 @@ static int eac_gen_auth_3_perform_key_agreement(sc_card_t *card, EAC_GEN_AUTH_PACE_R *r_data = NULL; unsigned char *d = NULL, *p; int r, l; - unsigned char resp[SC_MAX_EXT_APDU_RESP_SIZE]; + unsigned char resp[SC_MAX_APDU_RESP_SIZE]; c_data = EAC_GEN_AUTH_PACE_C_new(); if (!c_data) { @@ -865,7 +865,7 @@ static int eac_gen_auth_4_mutual_authentication(sc_card_t *card, EAC_GEN_AUTH_PACE_R *r_data = NULL; unsigned char *d = NULL, *p; int r, l; - unsigned char resp[SC_MAX_EXT_APDU_RESP_SIZE]; + unsigned char resp[SC_MAX_APDU_RESP_SIZE]; c_data = EAC_GEN_AUTH_PACE_C_new(); if (!c_data) { @@ -1136,12 +1136,6 @@ int perform_pace(sc_card_t *card, sc_debug_hex(card->ctx, SC_LOG_DEBUG_SM, "EF.CardAccess", pace_output->ef_cardaccess, pace_output->ef_cardaccess_length); - /* XXX Card capabilities should be determined by the OpenSC card driver. We - * set it here to be able to use the nPA without patching OpenSC. By - * now we have read the EF.CardAccess so the assumption to have an nPA - * seems valid. */ - card->caps |= SC_CARD_CAP_APDU_EXT; - eac_ctx = EAC_CTX_new(); if (!eac_ctx || !EAC_CTX_init_ef_cardaccess(pace_output->ef_cardaccess, @@ -1667,7 +1661,7 @@ static int eac_gen_auth_ca(sc_card_t *card, const BUF_MEM *eph_pub_key, EAC_GEN_AUTH_CA_R *r_data = NULL; unsigned char *d = NULL; int r; - unsigned char resp[SC_MAX_EXT_APDU_RESP_SIZE]; + unsigned char resp[SC_MAX_APDU_RESP_SIZE]; c_data = EAC_GEN_AUTH_CA_C_new(); if (!c_data) {