-
Notifications
You must be signed in to change notification settings - Fork 711
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Change-Id: I9ccb5f71d719422607b421bf698319150401e30e
- Loading branch information
1 parent
2e87e4c
commit ccfc805
Showing
7 changed files
with
610 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,379 @@ | ||
/* | ||
* Driver for EstEID card issued from December 2018. | ||
* | ||
* Copyright (C) 2019, Martin Paljak <[email protected]> | ||
* | ||
* 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 <ctype.h> | ||
#include <stdlib.h> | ||
#include <string.h> | ||
|
||
#include "asn1.h" | ||
#include "gp.h" | ||
#include "internal.h" | ||
|
||
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 struct sc_card_operations esteid_ops; | ||
|
||
static struct sc_card_driver esteid2018_driver = {"EstEID 2018", "esteid2018", &esteid_ops, NULL, 0, NULL}; | ||
|
||
static const struct sc_card_operations *iso_ops = NULL; | ||
|
||
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; | ||
} | ||
|
||
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_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); | ||
} | ||
|
||
static int esteid_read_binary(struct sc_card *card, unsigned int idx, u8 *buf, size_t count, unsigned long flags) { | ||
struct sc_apdu apdu; | ||
int r; | ||
LOG_FUNC_CALLED(card->ctx); | ||
|
||
if (idx > 0x7fff) { | ||
sc_log(card->ctx, "invalid EF offset: 0x%X > 0x7FFF", idx); | ||
LOG_FUNC_RETURN(card->ctx, SC_ERROR_OFFSET_TOO_LARGE); | ||
} | ||
|
||
sc_format_apdu(card, &apdu, SC_APDU_CASE_2, 0xB0, (idx >> 8) & 0x7F, idx & 0xFF); | ||
apdu.le = count; | ||
apdu.resplen = count; | ||
apdu.resp = buf; | ||
|
||
r = sc_transmit_apdu(card, &apdu); | ||
LOG_TEST_RET(card->ctx, r, "APDU transmit failed"); | ||
if (apdu.resplen == 0) { | ||
if (apdu.sw1 == 0x6B) { | ||
// This means "end of file reached" for this card. Return success and zero | ||
// bytes | ||
LOG_FUNC_RETURN(card->ctx, 0); | ||
} | ||
LOG_FUNC_RETURN(card->ctx, sc_check_sw(card, apdu.sw1, apdu.sw2)); | ||
} | ||
|
||
r = sc_check_sw(card, apdu.sw1, apdu.sw2); | ||
// This is the normal READ BINARY end of file reached | ||
if (r == SC_ERROR_FILE_END_REACHED) | ||
LOG_FUNC_RETURN(card->ctx, apdu.resplen); | ||
LOG_TEST_RET(card->ctx, r, "READ BINARY failed"); | ||
|
||
if (apdu.resplen < count) { | ||
r = esteid_read_binary(card, idx + apdu.resplen, buf + apdu.resplen, count - apdu.resplen, flags); | ||
LOG_TEST_RET(card->ctx, r, "READ BINARY failed"); | ||
if (r > 0) | ||
apdu.resplen += r; | ||
} | ||
|
||
LOG_FUNC_RETURN(card->ctx, apdu.resplen); | ||
} | ||
|
||
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)) | ||
return 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[256] = {0}; | ||
|
||
if (data == NULL || out == NULL) | ||
return SC_ERROR_INVALID_ARGUMENTS; | ||
env = &priv->sec_env; | ||
|
||
LOG_FUNC_CALLED(card->ctx); | ||
if (env->operation != SC_SEC_OPERATION_SIGN) | ||
return SC_ERROR_INVALID_ARGUMENTS; | ||
if (datalen > 255) | ||
LOG_FUNC_RETURN(card->ctx, SC_ERROR_INVALID_ARGUMENTS); | ||
|
||
// left-pad if necessary | ||
memcpy(&sbuf[0x30 - datalen], data, MIN(datalen, 0x30)); | ||
datalen = 0x30; | ||
|
||
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); | ||
} | ||
|
||
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"); | ||
|
||
return apdu_resp[13]; // FIXME: tag 0x9B | ||
} | ||
|
||
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; | ||
return SC_SUCCESS; | ||
} else if (data->cmd == SC_PIN_CMD_UNBLOCK) { | ||
// Verify PUK, then issue "CHANGE" | ||
// 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) | ||
return SC_ERROR_OUT_OF_MEMORY; | ||
card->drv_data = priv; | ||
card->cla = 0x00; | ||
card->caps = SC_CARD_CAP_ISO7816_PIN_INFO; | ||
card->max_recv_size = 233; // FIXME: empirical | ||
|
||
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); | ||
|
||
return SC_SUCCESS; | ||
} | ||
|
||
static int esteid_finish(sc_card_t *card) { | ||
if (card != NULL) | ||
free(DRVDATA(card)); | ||
return 0; | ||
} | ||
|
||
static struct sc_card_driver *sc_get_drv(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; | ||
} | ||
|
||
struct sc_card_driver *sc_get_esteid2018_driver(void) { | ||
return sc_get_drv(); | ||
} |
Oops, something went wrong.