From b3d4a0d69a8f56c417e6ee141b57d358f7f58163 Mon Sep 17 00:00:00 2001 From: Martin Paljak Date: Thu, 7 Mar 2019 20:47:50 +0200 Subject: [PATCH] EstEID 2018+ driver This adds support for a minimalistic, small and fast card profile based on IAS-ECC. Based on information from https://installer.id.ee/media/id2019/TD-ID1-Chip-App.pdf and proprietary driver snoops. Thanks to @metsma and @frankmorgner. Change-Id: I2e4b4914d8a3b991d9a639728695abf4a2362ca0 --- src/libopensc/Makefile.am | 4 +- src/libopensc/card-esteid2018.c | 347 ++++++++++++++++++++++++++++++ src/libopensc/cards.h | 5 + src/libopensc/ctx.c | 1 + src/libopensc/pkcs15-esteid2018.c | 223 +++++++++++++++++++ src/libopensc/pkcs15-syn.c | 2 + src/libopensc/pkcs15-syn.h | 1 + 7 files changed, 581 insertions(+), 2 deletions(-) create mode 100644 src/libopensc/card-esteid2018.c create mode 100644 src/libopensc/pkcs15-esteid2018.c diff --git a/src/libopensc/Makefile.am b/src/libopensc/Makefile.am index a6d7650393..7d33fcfa3e 100644 --- a/src/libopensc/Makefile.am +++ b/src/libopensc/Makefile.am @@ -48,7 +48,7 @@ libopensc_la_SOURCES_BASE = \ card-iasecc.c iasecc-sdo.c iasecc-sm.c card-sc-hsm.c \ card-dnie.c cwa14890.c cwa-dnie.c \ card-isoApplet.c card-masktech.c card-gids.c card-jpki.c \ - card-npa.c \ + card-npa.c card-esteid2018.c \ \ pkcs15-openpgp.c pkcs15-starcert.c \ pkcs15-tcos.c pkcs15-esteid.c pkcs15-gemsafeGPK.c \ @@ -56,7 +56,7 @@ libopensc_la_SOURCES_BASE = \ pkcs15-cac.c pkcs15-esinit.c pkcs15-westcos.c pkcs15-pteid.c \ pkcs15-oberthur.c pkcs15-itacns.c pkcs15-gemsafeV1.c pkcs15-sc-hsm.c \ pkcs15-coolkey.c pkcs15-din-66291.c \ - pkcs15-dnie.c pkcs15-gids.c pkcs15-iasecc.c pkcs15-jpki.c \ + pkcs15-dnie.c pkcs15-gids.c pkcs15-iasecc.c pkcs15-jpki.c pkcs15-esteid2018.c \ compression.c p15card-helper.c sm.c \ aux-data.c diff --git a/src/libopensc/card-esteid2018.c b/src/libopensc/card-esteid2018.c new file mode 100644 index 0000000000..ccb2d892e4 --- /dev/null +++ b/src/libopensc/card-esteid2018.c @@ -0,0 +1,347 @@ +/* + * Driver for EstEID card issued from December 2018. + * + * Copyright (C) 2019, Martin Paljak + * + * 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 + */ + +#if HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include "asn1.h" +#include "gp.h" +#include "internal.h" + +/* Helping defines */ +#define SIGNATURE_PAYLOAD_SIZE 0x30 + +static const struct sc_atr_table esteid_atrs[] = { + {"3b:db:96:00:80:b1:fe:45:1f:83:00:12:23:3f:53:65:49:44:0f:90:00:f1", NULL, "EstEID 2018", SC_CARD_TYPE_ESTEID_2018, 0, NULL}, + {NULL, NULL, NULL, 0, 0, NULL}}; + +static const struct sc_aid IASECC_AID = {{0xA0, 0x00, 0x00, 0x00, 0x77, 0x01, 0x08, 0x00, 0x07, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x01, 0x00}, + 16}; + +static const struct sc_path adf2 = {{0x3f, 0x00, 0xAD, 0xF2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 4, 0, 0, SC_PATH_TYPE_PATH, {{0}, 0}}; + +static const struct sc_card_operations *iso_ops = NULL; +static struct sc_card_operations esteid_ops; + +static struct sc_card_driver esteid2018_driver = {"EstEID 2018", "esteid2018", &esteid_ops, NULL, 0, NULL}; + +struct esteid_priv_data { + sc_security_env_t sec_env; /* current security environment */ +}; + +#define DRVDATA(card) ((struct esteid_priv_data *)((card)->drv_data)) + +static int esteid_match_card(sc_card_t *card) { + int i = 0; + int r = 0; + + i = _sc_match_atr(card, esteid_atrs, &card->type); + if (i >= 0) { + r = gp_select_aid(card, &IASECC_AID); + if (r == SC_SUCCESS) { + card->name = esteid_atrs[i].name; + return 1; + } + } + return 0; +} + +static int esteid_check_sw(sc_card_t *card, unsigned int sw1, unsigned int sw2) { + if (sw1 == 0x6B && sw2 == 0x00) + LOG_FUNC_RETURN(card->ctx, SC_ERROR_FILE_END_REACHED); + return iso_ops->check_sw(card, sw1, sw2); +} + +static int esteid_select(struct sc_card *card, unsigned char p1, unsigned char id1, unsigned char id2) { + struct sc_apdu apdu; + unsigned char sbuf[2]; + + LOG_FUNC_CALLED(card->ctx); + + // Select EF/DF + sbuf[0] = id1; + sbuf[1] = id2; + + sc_format_apdu(card, &apdu, SC_APDU_CASE_1, 0xA4, p1, 0x0C); + if (id1 != 0x3F && id2 != 0x00) { + apdu.cse = SC_APDU_CASE_3_SHORT; + apdu.lc = 2; + apdu.data = sbuf; + apdu.datalen = 2; + } + apdu.le = 0x00; + apdu.resplen = 0; + + 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), "SELECT failed"); + + LOG_FUNC_RETURN(card->ctx, SC_SUCCESS); +} + +static int esteid_select_file(struct sc_card *card, const struct sc_path *in_path, struct sc_file **file_out) { + unsigned char pathbuf[SC_MAX_PATH_SIZE], *path = pathbuf; + int pathlen; + struct sc_file *file = NULL; + + LOG_FUNC_CALLED(card->ctx); + + // Only support full paths + if (in_path->type != SC_PATH_TYPE_PATH) { + LOG_FUNC_RETURN(card->ctx, SC_ERROR_INVALID_ARGUMENTS); + } + + memcpy(path, in_path->value, in_path->len); + pathlen = in_path->len; + + while (pathlen >= 2) { + if (memcmp(path, "\x3F\x00", 2) == 0) { + LOG_TEST_RET(card->ctx, esteid_select(card, 0x00, 0x3F, 0x00), "MF select failed"); + } else if (pathlen >= 2 && path[0] == 0xAD) { + LOG_TEST_RET(card->ctx, esteid_select(card, 0x01, path[0], path[1]), "DF select failed"); + } else if (pathlen == 2) { + LOG_TEST_RET(card->ctx, esteid_select(card, 0x02, path[0], path[1]), "EF select failed"); + + if (file_out != NULL) // Just make a dummy file + { + file = sc_file_new(); + if (file == NULL) + LOG_FUNC_RETURN(card->ctx, SC_ERROR_OUT_OF_MEMORY); + file->path = *in_path; + + *file_out = file; + } + } + path += 2; + pathlen -= 2; + } + LOG_FUNC_RETURN(card->ctx, SC_SUCCESS); +} + +// temporary hack, overload 6B00 SW processing +static int esteid_read_binary(struct sc_card *card, unsigned int idx, u8 *buf, size_t count, unsigned long flags) { + int r; + LOG_FUNC_CALLED(card->ctx); + void *saved = card->ops->check_sw; + card->ops->check_sw = esteid_check_sw; + r = iso_ops->read_binary(card, idx, buf, count, flags); + card->ops->check_sw = saved; + LOG_FUNC_RETURN(card->ctx, r); +} + +static int esteid_set_security_env(sc_card_t *card, const sc_security_env_t *env, int se_num) { + struct esteid_priv_data *priv; + struct sc_apdu apdu; + + const unsigned char cse_crt_aut[] = {0x80, 0x04, 0xFF, 0x20, 0x08, 0x00, 0x84, 0x01, 0x81}; + const unsigned char cse_crt_sig[] = {0x80, 0x04, 0xFF, 0x15, 0x08, 0x00, 0x84, 0x01, 0x9F}; + const unsigned char cse_crt_dec[] = {0x80, 0x04, 0xFF, 0x30, 0x04, 0x00, 0x84, 0x01, 0x81}; + + LOG_FUNC_CALLED(card->ctx); + + if (card == NULL || env == NULL || env->key_ref_len != 1) + LOG_FUNC_RETURN(card->ctx, SC_ERROR_INTERNAL); + + sc_log(card->ctx, "algo: %d operation: %d keyref: %d", env->algorithm, env->operation, env->key_ref[0]); + + if (env->algorithm == SC_ALGORITHM_EC && env->operation == SC_SEC_OPERATION_SIGN && env->key_ref[0] == 1) { + sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, 0x22, 0x41, 0xA4); + apdu.data = cse_crt_aut; + apdu.datalen = sizeof(cse_crt_aut); + apdu.lc = sizeof(cse_crt_aut); + } else if (env->algorithm == SC_ALGORITHM_EC && env->operation == SC_SEC_OPERATION_SIGN && env->key_ref[0] == 2) { + sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, 0x22, 0x41, 0xB6); + apdu.data = cse_crt_sig; + apdu.datalen = sizeof(cse_crt_sig); + apdu.lc = sizeof(cse_crt_sig); + } else if (env->algorithm == SC_ALGORITHM_EC && env->operation == SC_SEC_OPERATION_DERIVE && env->key_ref[0] == 1) { + sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, 0x22, 0x41, 0xB8); + apdu.data = cse_crt_dec; + apdu.datalen = sizeof(cse_crt_dec); + apdu.lc = sizeof(cse_crt_dec); + } else { + LOG_FUNC_RETURN(card->ctx, SC_ERROR_NOT_SUPPORTED); + } + + 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), "SET SECURITY ENV failed"); + + priv = DRVDATA(card); + priv->sec_env = *env; + LOG_FUNC_RETURN(card->ctx, SC_SUCCESS); +} + +static int esteid_compute_signature(sc_card_t *card, const u8 *data, size_t datalen, u8 *out, size_t outlen) { + struct esteid_priv_data *priv = DRVDATA(card); + struct sc_security_env *env = NULL; + struct sc_apdu apdu; + u8 sbuf[SIGNATURE_PAYLOAD_SIZE] = {0}; + + LOG_FUNC_CALLED(card->ctx); + if (data == NULL || out == NULL || datalen > SIGNATURE_PAYLOAD_SIZE) + LOG_FUNC_RETURN(card->ctx, SC_ERROR_INVALID_ARGUMENTS); + + env = &priv->sec_env; + // left-pad if necessary + memcpy(&sbuf[SIGNATURE_PAYLOAD_SIZE - datalen], data, MIN(datalen, SIGNATURE_PAYLOAD_SIZE)); + memset(sbuf, 0x00, SIGNATURE_PAYLOAD_SIZE - datalen); + datalen = SIGNATURE_PAYLOAD_SIZE; + + switch (env->key_ref[0]) { + case 1: /* authentication key */ + sc_format_apdu(card, &apdu, SC_APDU_CASE_4_SHORT, 0x88, 0, 0); + break; + default: + sc_format_apdu(card, &apdu, SC_APDU_CASE_4_SHORT, 0x2A, 0x9E, 0x9A); + } + apdu.lc = datalen; + apdu.data = sbuf; + apdu.datalen = datalen; + apdu.le = MIN(256, outlen); + apdu.resp = out; + apdu.resplen = outlen; + + 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), "PSO CDS/INTERNAL AUTHENTICATE failed"); + + LOG_FUNC_RETURN(card->ctx, apdu.resplen); +} + +static int esteid_get_pin_remaining_tries(sc_card_t *card, int pin_reference) { + unsigned char get_pin_info[] = {0x4D, 0x08, 0x70, 0x06, 0xBF, 0x81, 0xFF, 0x02, 0xA0, 0x80}; + + struct sc_apdu apdu; + unsigned char apdu_resp[SC_MAX_APDU_BUFFER_SIZE]; + LOG_FUNC_CALLED(card->ctx); + + // We don't get the file information here, so we need to be ugly + if (pin_reference == 1 || pin_reference == 2) { + LOG_TEST_RET(card->ctx, esteid_select(card, 0x00, 0x3F, 0x00), "Cannot select MF"); + } else if (pin_reference == 0x85) { + LOG_TEST_RET(card->ctx, esteid_select_file(card, &adf2, NULL), "Cannot select QSCD AID"); + } else { + LOG_FUNC_RETURN(card->ctx, SC_ERROR_INTERNAL); + } + + sc_format_apdu(card, &apdu, SC_APDU_CASE_4_SHORT, 0xCB, 0x3F, 0xFF); + get_pin_info[6] = pin_reference & 0x0F; // mask out local/global + apdu.lc = sizeof(get_pin_info); + apdu.data = get_pin_info; + apdu.datalen = sizeof(get_pin_info); + apdu.resplen = sizeof(apdu_resp); + apdu.resp = apdu_resp; + apdu.le = 256; + + 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), "GET DATA(pin info) failed"); + + // XXX: sc_asn1_find_tag with the following payload (to get to tag 0x9B): + // https://lapo.it/asn1js/#cB6_gQEaoBiaAQObAQOhEIwG8wAAc0MAnAbzAABzQwA + return apdu_resp[13]; +} + +static int esteid_pin_cmd(sc_card_t *card, struct sc_pin_cmd_data *data, int *tries_left) { + int r; + struct sc_pin_cmd_data tmp; + LOG_FUNC_CALLED(card->ctx); + sc_log(card->ctx, "PIN CMD is %d", data->cmd); + if (data->cmd == SC_PIN_CMD_GET_INFO) { + sc_log(card->ctx, "SC_PIN_CMD_GET_INFO for %d", data->pin_reference); + r = esteid_get_pin_remaining_tries(card, data->pin_reference); + LOG_TEST_RET(card->ctx, r, "GET DATA(pin info) failed"); + + data->pin1.tries_left = r; + data->pin1.max_tries = -1; // "no support, which means the one set in PKCS#15 emulation sticks + data->pin1.logged_in = SC_PIN_STATE_UNKNOWN; + LOG_FUNC_RETURN(card->ctx, SC_SUCCESS); + } else if (data->cmd == SC_PIN_CMD_UNBLOCK) { + // Verify PUK, then issue UNBLOCK + // VERIFY + memcpy(&tmp, data, sizeof(struct sc_pin_cmd_data)); + tmp.cmd = SC_PIN_CMD_VERIFY; + tmp.pin_reference = 0x02; // hardcoded, ugly + tmp.pin2.len = 0; + r = iso_ops->pin_cmd(card, &tmp, tries_left); + sc_mem_clear(&tmp, sizeof(tmp)); + LOG_TEST_RET(card->ctx, r, "VERIFY during unblock failed"); + + if (data->pin_reference == 0x85) { + LOG_TEST_RET(card->ctx, esteid_select_file(card, &adf2, NULL), "Cannot select QSCD AID"); + } + // UNBLOCK + memcpy(&tmp, data, sizeof(struct sc_pin_cmd_data)); + tmp.cmd = SC_PIN_CMD_UNBLOCK; + tmp.pin1.len = 0; + r = iso_ops->pin_cmd(card, &tmp, tries_left); + sc_mem_clear(&tmp, sizeof(tmp)); + LOG_FUNC_RETURN(card->ctx, r); + } + + LOG_FUNC_RETURN(card->ctx, iso_ops->pin_cmd(card, data, tries_left)); +} + +static int esteid_init(sc_card_t *card) { + unsigned long flags, ext_flags; + struct esteid_priv_data *priv; + + priv = calloc(1, sizeof *priv); + if (!priv) + LOG_FUNC_RETURN(card->ctx, SC_ERROR_OUT_OF_MEMORY); + card->drv_data = priv; + card->max_recv_size = 233; // XXX: empirical, not documented + + flags = SC_ALGORITHM_ECDSA_RAW | SC_ALGORITHM_ECDH_CDH_RAW | SC_ALGORITHM_ECDSA_HASH_NONE; + ext_flags = SC_ALGORITHM_EXT_EC_NAMEDCURVE | SC_ALGORITHM_EXT_EC_UNCOMPRESES; + + _sc_card_add_ec_alg(card, 384, flags, ext_flags, NULL); + + LOG_FUNC_RETURN(card->ctx, SC_SUCCESS); +} + +static int esteid_finish(sc_card_t *card) { + if (card != NULL) + free(DRVDATA(card)); + return 0; +} + +struct sc_card_driver *sc_get_esteid2018_driver(void) { + struct sc_card_driver *iso_drv = sc_get_iso7816_driver(); + + if (iso_ops == NULL) + iso_ops = iso_drv->ops; + + esteid_ops = *iso_drv->ops; + esteid_ops.match_card = esteid_match_card; + esteid_ops.init = esteid_init; + esteid_ops.finish = esteid_finish; + + esteid_ops.select_file = esteid_select_file; + esteid_ops.read_binary = esteid_read_binary; + + esteid_ops.set_security_env = esteid_set_security_env; + esteid_ops.compute_signature = esteid_compute_signature; + esteid_ops.pin_cmd = esteid_pin_cmd; + + return &esteid2018_driver; +} \ No newline at end of file diff --git a/src/libopensc/cards.h b/src/libopensc/cards.h index dd78e7e298..5b4ee05fa8 100644 --- a/src/libopensc/cards.h +++ b/src/libopensc/cards.h @@ -247,6 +247,10 @@ enum { SC_CARD_TYPE_NPA = 34000, SC_CARD_TYPE_NPA_TEST, SC_CARD_TYPE_NPA_ONLINE, + + /* EstEID cards */ + SC_CARD_TYPE_ESTEID = 35000, + SC_CARD_TYPE_ESTEID_2018, }; extern sc_card_driver_t *sc_get_default_driver(void); @@ -289,6 +293,7 @@ extern sc_card_driver_t *sc_get_coolkey_driver(void); extern sc_card_driver_t *sc_get_cac_driver(void); 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); #ifdef __cplusplus } diff --git a/src/libopensc/ctx.c b/src/libopensc/ctx.c index 84df68ff7c..e5d8b9e691 100644 --- a/src/libopensc/ctx.c +++ b/src/libopensc/ctx.c @@ -126,6 +126,7 @@ static const struct _sc_driver_entry internal_card_drivers[] = { { "masktech", (void *(*)(void)) sc_get_masktech_driver }, { "atrust-acos",(void *(*)(void)) sc_get_atrust_acos_driver }, { "westcos", (void *(*)(void)) sc_get_westcos_driver }, + { "esteid2018", (void *(*)(void)) sc_get_esteid2018_driver }, /* Here should be placed drivers that need some APDU transactions in the * driver's `match_card()` function. */ diff --git a/src/libopensc/pkcs15-esteid2018.c b/src/libopensc/pkcs15-esteid2018.c new file mode 100644 index 0000000000..243e231817 --- /dev/null +++ b/src/libopensc/pkcs15-esteid2018.c @@ -0,0 +1,223 @@ +/* + * PKCS15 emulation layer for EstEID card issued from December 2018. + * + * Copyright (C) 2019, Martin Paljak + * + * 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 + */ + +#if HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#include "common/compat_strlcpy.h" + +#include "internal.h" +#include "opensc.h" +#include "pkcs15.h" + +static void set_string(char **strp, const char *value) { + if (*strp) + free(*strp); + *strp = value ? strdup(value) : NULL; +} + +static int sc_pkcs15emu_esteid2018_init(sc_pkcs15_card_t *p15card) { + sc_card_t *card = p15card->card; + u8 buff[128]; + int r, i; + size_t field_length = 0, taglen, j; + sc_path_t tmppath; + + set_string(&p15card->tokeninfo->label, "ID-kaart"); + set_string(&p15card->tokeninfo->manufacturer_id, "IDEMIA"); + + /* Read docnr */ + sc_format_path("3F00D003", &tmppath); + LOG_TEST_RET(card->ctx, sc_select_file(card, &tmppath, NULL), "SELECT docnr"); + r = sc_read_binary(card, 0, buff, 11, 0); + LOG_TEST_RET(card->ctx, r, "read docnr failed"); + const unsigned char *tag = sc_asn1_find_tag(card->ctx, buff, r, 0x04, &taglen); + if (tag == NULL) + LOG_FUNC_RETURN(card->ctx, SC_ERROR_INTERNAL); + + for (j = 0; j < taglen; j++) + if (!isalnum(tag[j])) + LOG_FUNC_RETURN(card->ctx, SC_ERROR_INTERNAL); + p15card->tokeninfo->serial_number = malloc(taglen + 1); + if (!p15card->tokeninfo->serial_number) + LOG_FUNC_RETURN(card->ctx, SC_ERROR_OUT_OF_MEMORY); + p15card->tokeninfo->serial_number = memcpy(p15card->tokeninfo->serial_number, tag, taglen); + p15card->tokeninfo->serial_number[taglen] = '\0'; + p15card->tokeninfo->flags = SC_PKCS15_TOKEN_READONLY; + + /* add certificates */ + for (i = 0; i < 2; i++) { + const char *esteid_cert_names[2] = {"Isikutuvastus", "Allkirjastamine"}; + const char *esteid_cert_paths[2] = {"3f00:adf1:3401", "3f00:adf2:341f"}; + const int esteid_cert_ids[2] = {1, 2}; + + struct sc_pkcs15_cert_info cert_info; + struct sc_pkcs15_object cert_obj; + + memset(&cert_info, 0, sizeof(cert_info)); + memset(&cert_obj, 0, sizeof(cert_obj)); + + strlcpy(cert_obj.label, esteid_cert_names[i], sizeof(cert_obj.label)); + sc_format_path(esteid_cert_paths[i], &cert_info.path); + cert_info.id.value[0] = esteid_cert_ids[i]; + cert_info.id.len = 1; + r = sc_pkcs15emu_add_x509_cert(p15card, &cert_obj, &cert_info); + if (r < 0) + LOG_FUNC_RETURN(card->ctx, SC_ERROR_INTERNAL); + + // Read data from first cert + if (i != 0) + continue; + + sc_pkcs15_cert_t *cert = NULL; + r = sc_pkcs15_read_certificate(p15card, &cert_info, &cert); + LOG_TEST_RET(card->ctx, r, "Could not read authentication certificate"); + + if (cert->key->algorithm == SC_ALGORITHM_EC) + field_length = cert->key->u.ec.params.field_length; + + static const struct sc_object_id cn_oid = {{2, 5, 4, 3, -1}}; + u8 *cn_name = NULL; + size_t cn_len = 0; + sc_pkcs15_get_name_from_dn(card->ctx, cert->subject, cert->subject_len, &cn_oid, &cn_name, &cn_len); + if (cn_len > 0) { + char *token_name = malloc(cn_len + 1); + if (token_name) { + memcpy(token_name, cn_name, cn_len); + token_name[cn_len] = '\0'; + set_string(&p15card->tokeninfo->label, (const char *)token_name); + free(token_name); + } + } + free(cn_name); + sc_pkcs15_free_certificate(cert); + } + + /* add pins */ + for (i = 0; i < 3; i++) { + static const char *esteid_pin_names[3] = {"PIN1", "PIN2", "PUK"}; + static const int esteid_pin_min[3] = {4, 5, 8}; + static const int esteid_pin_ref[3] = {0x01, 0x85, 0x02}; + static const int esteid_pin_authid[3] = {1, 2, 3}; + static const char *esteid_pin_path[3] = {"3F00", "3F00ADF2", "3F00"}; + + static const int esteid_pin_flags[3] = { + SC_PKCS15_PIN_FLAG_NEEDS_PADDING | SC_PKCS15_PIN_FLAG_INITIALIZED, + SC_PKCS15_PIN_FLAG_NEEDS_PADDING | SC_PKCS15_PIN_FLAG_INITIALIZED | SC_PKCS15_PIN_FLAG_LOCAL, + SC_PKCS15_PIN_FLAG_NEEDS_PADDING | SC_PKCS15_PIN_FLAG_INITIALIZED | SC_PKCS15_PIN_FLAG_UNBLOCKING_PIN}; + + struct sc_pkcs15_auth_info pin_info; + struct sc_pkcs15_object pin_obj; + + memset(&pin_info, 0, sizeof(pin_info)); + memset(&pin_obj, 0, sizeof(pin_obj)); + + sc_format_path(esteid_pin_path[i], &pin_info.path); + pin_info.auth_id.len = 1; + pin_info.auth_id.value[0] = esteid_pin_authid[i]; + pin_info.auth_type = SC_PKCS15_PIN_AUTH_TYPE_PIN; + pin_info.attrs.pin.reference = esteid_pin_ref[i]; + pin_info.attrs.pin.flags = esteid_pin_flags[i]; + pin_info.attrs.pin.type = SC_PKCS15_PIN_TYPE_ASCII_NUMERIC; + pin_info.attrs.pin.min_length = esteid_pin_min[i]; + pin_info.attrs.pin.stored_length = 12; + pin_info.attrs.pin.max_length = 12; + pin_info.attrs.pin.pad_char = 0xFF; + pin_info.tries_left = 3; + pin_info.max_tries = 3; + + strlcpy(pin_obj.label, esteid_pin_names[i], sizeof(pin_obj.label)); + pin_obj.flags = esteid_pin_flags[i]; + + /* Link normal PINs with PUK */ + if (i < 2) { + pin_obj.auth_id.len = 1; + pin_obj.auth_id.value[0] = 3; + } + + r = sc_pkcs15emu_add_pin_obj(p15card, &pin_obj, &pin_info); + if (r < 0) + LOG_FUNC_RETURN(card->ctx, SC_ERROR_INTERNAL); + } + + struct sc_pkcs15_object *objs[3]; + r = sc_pkcs15_get_objects(p15card, SC_PKCS15_TYPE_AUTH, objs, 3); + + if (r != 3) { + + LOG_FUNC_RETURN(card->ctx, SC_ERROR_INTERNAL); + } + + // trigger PIN counter refresh via pin_cmd + for (i = 0; i < r; i++) { + r = sc_pkcs15_get_pin_info(p15card, objs[i]); + if (r < 0) + LOG_FUNC_RETURN(card->ctx, SC_ERROR_INTERNAL); + } + + /* add private keys */ + for (i = 0; i < 2; i++) { + int prkey_pin[2] = {1, 2}; + + const char *prkey_name[2] = {"Isikutuvastus", "Allkirjastamine"}; + const char *prkey_path[2] = {"3F00:ADF1", "3F00:ADF2"}; + const int prkey_usage[2] = {SC_PKCS15_PRKEY_USAGE_SIGN | SC_PKCS15_PRKEY_USAGE_DERIVE, + SC_PKCS15_PRKEY_USAGE_NONREPUDIATION}; + const int prkey_consent[2] = {0, 1}; + + struct sc_pkcs15_prkey_info prkey_info; + struct sc_pkcs15_object prkey_obj; + + memset(&prkey_info, 0, sizeof(prkey_info)); + memset(&prkey_obj, 0, sizeof(prkey_obj)); + + sc_format_path(prkey_path[i], &prkey_info.path); + prkey_info.id.len = 1; + prkey_info.id.value[0] = prkey_pin[i]; + prkey_info.native = 1; + prkey_info.key_reference = i + 1; + prkey_info.field_length = field_length; + prkey_info.usage = prkey_usage[i]; + + strlcpy(prkey_obj.label, prkey_name[i], sizeof(prkey_obj.label)); + prkey_obj.auth_id.len = 1; + prkey_obj.auth_id.value[0] = prkey_pin[i]; + prkey_obj.user_consent = prkey_consent[i]; + prkey_obj.flags = SC_PKCS15_CO_FLAG_PRIVATE; + + r = sc_pkcs15emu_add_ec_prkey(p15card, &prkey_obj, &prkey_info); + if (r < 0) + LOG_FUNC_RETURN(card->ctx, SC_ERROR_INTERNAL); + } + + return SC_SUCCESS; +} + +int sc_pkcs15emu_esteid2018_init_ex(sc_pkcs15_card_t *p15card, struct sc_aid *aid) { + if (p15card->card->type == SC_CARD_TYPE_ESTEID_2018) + return sc_pkcs15emu_esteid2018_init(p15card); + return SC_ERROR_WRONG_CARD; +} diff --git a/src/libopensc/pkcs15-syn.c b/src/libopensc/pkcs15-syn.c index 8ecc345664..82e120d929 100644 --- a/src/libopensc/pkcs15-syn.c +++ b/src/libopensc/pkcs15-syn.c @@ -58,6 +58,8 @@ struct sc_pkcs15_emulator_handler builtin_emulators[] = { { "jpki", sc_pkcs15emu_jpki_init_ex }, { "coolkey", sc_pkcs15emu_coolkey_init_ex }, { "din66291", sc_pkcs15emu_din_66291_init_ex }, + { "esteid2018", sc_pkcs15emu_esteid2018_init_ex }, + { NULL, NULL } }; diff --git a/src/libopensc/pkcs15-syn.h b/src/libopensc/pkcs15-syn.h index a141f79fe3..6811b3dab1 100644 --- a/src/libopensc/pkcs15-syn.h +++ b/src/libopensc/pkcs15-syn.h @@ -34,6 +34,7 @@ int sc_pkcs15emu_openpgp_init_ex(sc_pkcs15_card_t *, struct sc_aid *); int sc_pkcs15emu_starcert_init_ex(sc_pkcs15_card_t *, struct sc_aid *); int sc_pkcs15emu_tcos_init_ex(sc_pkcs15_card_t *, struct sc_aid *); int sc_pkcs15emu_esteid_init_ex(sc_pkcs15_card_t *, struct sc_aid *); +int sc_pkcs15emu_esteid2018_init_ex(sc_pkcs15_card_t *, struct sc_aid *); int sc_pkcs15emu_piv_init_ex(sc_pkcs15_card_t *p15card, struct sc_aid *); int sc_pkcs15emu_cac_init_ex(sc_pkcs15_card_t *p15card, struct sc_aid *); int sc_pkcs15emu_gemsafeGPK_init_ex(sc_pkcs15_card_t *p15card, struct sc_aid *);