diff --git a/src/libopensc/Makefile.am b/src/libopensc/Makefile.am index 339fd506a0..577240622b 100644 --- a/src/libopensc/Makefile.am +++ b/src/libopensc/Makefile.am @@ -30,6 +30,7 @@ libopensc_la_SOURCES = \ card-mcrd.c card-starcos.c card-openpgp.c card-jcop.c \ card-oberthur.c card-belpic.c card-atrust-acos.c \ card-incrypto34.c card-piv.c card-muscle.c card-acos5.c \ + card-asepcos.c \ \ pkcs15-openpgp.c pkcs15-infocamere.c pkcs15-starcert.c \ pkcs15-tcos.c pkcs15-esteid.c pkcs15-postecert.c pkcs15-gemsafe.c \ diff --git a/src/libopensc/Makefile.mak b/src/libopensc/Makefile.mak index eaae9dbb0f..4bc06030ad 100644 --- a/src/libopensc/Makefile.mak +++ b/src/libopensc/Makefile.mak @@ -27,7 +27,7 @@ OBJECTS = \ card-cardos.obj card-tcos.obj card-emv.obj card-default.obj \ card-mcrd.obj card-starcos.obj card-openpgp.obj card-jcop.obj \ card-oberthur.obj card-belpic.obj card-atrust-acos.obj \ - card-incrypto34.obj card-piv.obj card-acos5.obj \ + card-incrypto34.obj card-piv.obj card-acos5.obj card-asepcos.obj \ muscle.obj card-muscle.obj muscle-filesystem.obj \ compression.obj p15card-helper.obj \ \ diff --git a/src/libopensc/card-asepcos.c b/src/libopensc/card-asepcos.c new file mode 100644 index 0000000000..0fd9614ad2 --- /dev/null +++ b/src/libopensc/card-asepcos.c @@ -0,0 +1,1112 @@ +/* + * Copyright (c) 2007 Athena Smartcard Solutions Inc. + * + * 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 + */ + +#include "internal.h" +#include +#include + +#include "asn1.h" +#include "cardctl.h" + +static const struct sc_card_operations *iso_ops = NULL; + +struct sc_card_operations asepcos_ops; +static struct sc_card_driver asepcos_drv = { + "Athena ASEPCOS", + "asepcos", + &asepcos_ops, + NULL, 0, NULL +}; + +static struct sc_atr_table asepcos_atrs[] = { + { "3b:d6:18:00:81:b1:80:7d:1f:03:80:51:00:61:10:30:8f", NULL, NULL, SC_CARD_TYPE_ASEPCOS_GENERIC, 0, NULL}, + { "3b:d6:18:00:81:b1:fe:7d:1f:03:41:53:45:37:35:35:01", NULL, NULL, SC_CARD_TYPE_ASEPCOS_JAVA, 0, NULL}, + { NULL, NULL, NULL, 0, 0, NULL } +}; + +static int asepcos_finish(sc_card_t *card) +{ + return SC_SUCCESS; +} + +static int asepcos_match_card(sc_card_t *card) +{ + int i = _sc_match_atr(card, asepcos_atrs, &card->type); + if (i < 0) + return 0; + return 1; +} + +static int asepcos_select_asepcos_applet(sc_card_t *card) +{ + static const u8 asepcos_aid[] = {0xA0,0x00,0x00,0x01,0x64,0x41,0x53,0x45,0x50,0x43,0x4F,0x53,0x00}; + sc_path_t tpath; + int r; + + tpath.type = SC_PATH_TYPE_DF_NAME; + tpath.len = sizeof(asepcos_aid); + memcpy(tpath.value, asepcos_aid, sizeof(asepcos_aid)); + + r = sc_select_file(card, &tpath, NULL); + if (r != SC_SUCCESS) { + sc_error(card->ctx, "unable to select ASEPCOS applet"); + return r; + } + + return SC_SUCCESS; +} + +static int asepcos_init(sc_card_t *card) +{ + unsigned long flags; + + card->name = "Athena ASEPCOS"; + card->cla = 0x00; + + /* in case of a Java card try to select the ASEPCOS applet */ + if (card->type == SC_CARD_TYPE_ASEPCOS_JAVA) { + int r = asepcos_select_asepcos_applet(card); + if (r != SC_SUCCESS) + return r; + } + + /* Set up algorithm info. */ + flags = SC_ALGORITHM_RSA_RAW + | SC_ALGORITHM_RSA_HASH_NONE + | SC_ALGORITHM_ONBOARD_KEY_GEN + ; + _sc_card_add_rsa_alg(card, 512, flags, 0); + _sc_card_add_rsa_alg(card, 768, flags, 0); + _sc_card_add_rsa_alg(card, 1024, flags, 0); + _sc_card_add_rsa_alg(card, 1536, flags, 0); + _sc_card_add_rsa_alg(card, 1792, flags, 0); + _sc_card_add_rsa_alg(card, 2048, flags, 0); + + card->caps |= SC_CARD_CAP_RSA_2048 + | SC_CARD_CAP_APDU_EXT + | SC_CARD_CAP_USE_FCI_AC; + + return SC_SUCCESS; +} + +/* tables to map the asepcos access mode bytes to the OpenSC + * access mode flags */ + +typedef struct { + unsigned int am; + unsigned int sc; +} amode_entry_t; + +static const amode_entry_t df_amode_table[] = { + { 0x40, SC_AC_OP_DELETE_SELF }, /* DELETE self */ + { 0x01, SC_AC_OP_DELETE }, /* DELETE child */ + { 0x10, SC_AC_OP_INVALIDATE }, /* DEACTIVATE FILE */ + { 0x08, SC_AC_OP_REHABILITATE },/* ACTIVATE FILE */ + { 0x04, SC_AC_OP_CREATE }, /* CREATE DF */ + { 0x02, SC_AC_OP_CREATE }, /* CREATE EF */ + { 0, 0 } +}; + +static const amode_entry_t wef_amode_table[] = { + { 0x04, SC_AC_OP_WRITE }, + { 0x02, SC_AC_OP_UPDATE }, + { 0x01, SC_AC_OP_READ }, + { 0, 0 }, +}; + +static const amode_entry_t ief_amode_table[] = { + { 0x90, SC_AC_OP_REHABILITATE }, + /* UPDATE is also used when a new key is generated */ + { 0x82, SC_AC_OP_UPDATE }, + { 0, 0 }, +}; + +static int set_sec_attr(sc_file_t *file, unsigned int am, unsigned int ac, + unsigned int meth) +{ + const amode_entry_t *table; + if (file->type == SC_FILE_TYPE_DF) + table = df_amode_table; + else if (file->type == SC_FILE_TYPE_WORKING_EF) + table = wef_amode_table; + else if (file->type == SC_FILE_TYPE_INTERNAL_EF) + table = ief_amode_table; + else + return SC_ERROR_INVALID_ARGUMENTS; + for (; table->am != 0; table++) { + if (table->am & am) + sc_file_add_acl_entry(file, table->sc, meth, ac); + } + return SC_SUCCESS; +} + +/* Convert asepcos security attributes to opensc access conditions. + */ +static int asepcos_parse_sec_attr(sc_card_t *card, sc_file_t *file, const u8 *buf, + size_t len) +{ + const u8 *p = buf; + + while (len != 0) { + unsigned int amode, tlen = 3; + if (len < 5 && p[0] != 0x80 && p[1] != 0x01) { + sc_error(card->ctx, "invalid access mode encoding"); + return SC_ERROR_INTERNAL; + } + amode = p[2]; + if (p[3] == 0x90 && p[4] == 0x00) { + int r = set_sec_attr(file, amode, 0, SC_AC_NONE); + if (r != SC_SUCCESS) + return r; + tlen += 2; + } else if (p[3] == 0x97 && p[4] == 0x00) { + int r = set_sec_attr(file, amode, 0, SC_AC_NEVER); + if (r != SC_SUCCESS) + return r; + tlen += 2; + } else if (p[3] == 0xA0 && len >= 4U + p[4]) { + /* TODO: support OR expressions */ + int r = set_sec_attr(file, amode, p[5], SC_AC_CHV); + if (r != SC_SUCCESS) + return r; + tlen += 2 + p[4]; // FIXME + } else if (p[3] == 0xAF && len >= 4U + p[4]) { + /* TODO: support AND expressions */ + int r = set_sec_attr(file, amode, p[5], SC_AC_CHV); + if (r != SC_SUCCESS) + return r; + tlen += 2 + p[4]; // FIXME + } else { + sc_error(card->ctx, "invalid security condition"); + return SC_ERROR_INTERNAL; + } + p += tlen; + len -= tlen; + } + + return SC_SUCCESS; +} + +/* sets a TLV encoded path as returned from GET DATA in a sc_path_t object + */ +static int asepcos_tlvpath_to_scpath(sc_path_t *out, const u8 *in, size_t in_len) +{ + int r; + size_t len = in_len; + + memset(out, 0, sizeof(sc_path_t)); + + while (len != 0) { + if (len < 4 || in[0] != 0x8b || in[1] != 0x02) + return SC_ERROR_INTERNAL; + /* append file id to the path */ + r = sc_append_path_id(out, &in[2], 2); + if (r != SC_SUCCESS) + return r; + len -= 4; + in += 4; + } + out->type = SC_PATH_TYPE_PATH; + + return SC_SUCCESS; +} + +/* returns the currently selected DF (if a EF is currently selected + * it returns the path from the MF to the DF in which the EF is + * located. + * @param card sc_card_t object to use + * @param path OUT path from the MF to the current DF + * @return SC_SUCCESS on success and an error value otherwise + */ +static int asepcos_get_current_df_path(sc_card_t *card, sc_path_t *path) +{ + int r; + sc_apdu_t apdu; + u8 rbuf[SC_MAX_APDU_BUFFER_SIZE]; + + sc_format_apdu(card, &apdu, SC_APDU_CASE_2_SHORT, 0xca, 0x01, 0x83); + apdu.resp = rbuf; + apdu.resplen = sizeof(rbuf); + apdu.le = 256; + + r = sc_transmit_apdu(card, &apdu); + SC_TEST_RET(card->ctx, r, "APDU transmit failed"); + if (apdu.sw1 != 0x90 || apdu.sw2 != 0x00) + return sc_check_sw(card, apdu.sw1, apdu.sw2); + return asepcos_tlvpath_to_scpath(path, apdu.resp, apdu.resplen); +} + +/* SELECT FILE: call the ISO SELECT FILE implementation and parse + * asepcos specific security attributes. + */ +static int asepcos_select_file(sc_card_t *card, const sc_path_t *in_path, + sc_file_t **file) +{ + int r; + sc_path_t npath = *in_path; + + SC_FUNC_CALLED(card->ctx, 2); + + if (in_path->type == SC_PATH_TYPE_PATH) { + /* check the current DF to avoid unnecessary re-selection of + * the MF (as this might invalidate a security status) */ + sc_path_t tpath; + + r = asepcos_get_current_df_path(card, &tpath); + if (r != SC_SUCCESS) + return r; + if (sc_compare_path_prefix(&tpath, &npath) != 0) { + /* remove the currently selected DF from the path */ + if (tpath.len == npath.len) { + /* we are already in the requested DF */ + if (file == NULL) + /* no file information requested => + * nothing to do */ + return SC_SUCCESS; + } else { + /* shorten path */ + r = sc_path_set(&npath, 0, &in_path->value[tpath.len], + npath.len - tpath.len, 0, 0); + if (r != SC_SUCCESS) + return r; + if (npath.len == 2) + npath.type = SC_PATH_TYPE_FILE_ID; + else + npath.type = SC_PATH_TYPE_PATH; + } + } + } + + r = iso_ops->select_file(card, &npath, file); + /* XXX: this doesn't look right */ + if (file != NULL && *file != NULL) + if ((*file)->ef_structure == SC_FILE_EF_UNKNOWN) + (*file)->ef_structure = SC_FILE_EF_TRANSPARENT; + if (r == SC_SUCCESS && file != NULL) { + r = asepcos_parse_sec_attr(card, *file, (*file)->sec_attr, (*file)->sec_attr_len); + if (r != SC_SUCCESS) + sc_error(card->ctx, "error parsing security attributes"); + } + SC_FUNC_RETURN(card->ctx, 1, r); +} + +static int asepcos_set_security_env(sc_card_t *card, + const sc_security_env_t *env, int se_num) +{ +#if 0 + /* this function doesn't seem to be necessary if RSA ENCRYPT DECRYPT + * is used. */ + + sc_apdu_t apdu; + u8 sbuf[SC_MAX_APDU_BUFFER_SIZE], *p = sbuf; + int r, locked = 0; + + SC_FUNC_CALLED(card->ctx, 1); + sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, 0x22, 0, 0); + switch (env->operation) { + case SC_SEC_OPERATION_DECIPHER: + apdu.p1 = 0x41; + apdu.p2 = 0xB8; + break; + case SC_SEC_OPERATION_SIGN: + apdu.p1 = 0x41; + apdu.p2 = 0xB6; + break; + default: + return SC_ERROR_INVALID_ARGUMENTS; + } + if (env->flags & SC_SEC_ENV_ALG_REF_PRESENT) { + *p++ = 0x80; /* algorithm reference */ + *p++ = 0x01; + *p++ = env->algorithm_ref & 0xFF; + } + if (env->flags & SC_SEC_ENV_FILE_REF_PRESENT) { + *p++ = 0x84; + *p++ = env->file_ref.len; + memcpy(p, env->file_ref.value, env->file_ref.len); + p += env->file_ref.len; + } + + apdu.lc = p - sbuf; + apdu.datalen = p - sbuf; + apdu.data = sbuf; + if (se_num > 0) { + r = sc_lock(card); + SC_TEST_RET(card->ctx, r, "sc_lock() failed"); + locked = 1; + } + if (apdu.datalen != 0) { + r = sc_transmit_apdu(card, &apdu); + if (r) { + sc_perror(card->ctx, r, "APDU transmit failed"); + goto err; + } + r = sc_check_sw(card, apdu.sw1, apdu.sw2); + if (r) { + sc_perror(card->ctx, r, "Card returned error"); + goto err; + } + } + if (se_num <= 0) + return 0; + sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, 0x22, 0xF2, se_num); + r = sc_transmit_apdu(card, &apdu); + sc_unlock(card); + SC_TEST_RET(card->ctx, r, "APDU transmit failed"); + return sc_check_sw(card, apdu.sw1, apdu.sw2); +err: + if (locked) + sc_unlock(card); + return r; +#else + return SC_SUCCESS; +#endif +} + + +static int asepcos_akn_to_fileid(sc_card_t *card, sc_cardctl_asepcos_akn2fileid_t *p) +{ + int r; + u8 sbuf[32], rbuf[SC_MAX_APDU_BUFFER_SIZE]; + sc_apdu_t apdu; + + sbuf[0] = p->akn & 0xff; + sc_format_apdu(card, &apdu, SC_APDU_CASE_4_SHORT, 0x28, 0x02, 0x01); + apdu.cla |= 0x80; + apdu.resp = rbuf; + apdu.resplen = sizeof(rbuf); + apdu.le = 256; + apdu.lc = 1; + apdu.datalen = 1; + apdu.data = sbuf; + + r = sc_transmit_apdu(card, &apdu); + SC_TEST_RET(card->ctx, r, "APDU transmit failed"); + + if (apdu.resplen != 4) + return SC_ERROR_INTERNAL; + + p->fileid = (apdu.resp[1] << 16) | (apdu.resp[2] << 8) | apdu.resp[3]; + + return SC_SUCCESS; +} + +/* sets the security attribute of a EF/DF + */ +static int asepcos_set_sec_attributes(sc_card_t *card, const u8 *data, size_t len, + int is_ef) +{ + int r, type = is_ef != 0 ? 0x02 : 0x04; + sc_apdu_t apdu; + + sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, 0x8a, type, 0xab); + apdu.cla |= 0x80; + apdu.lc = len; + apdu.datalen = len; + apdu.data = data; + r = sc_transmit_apdu(card, &apdu); + SC_TEST_RET(card->ctx, r, "APDU transmit failed"); + return sc_check_sw(card, apdu.sw1, apdu.sw2); +} + +/* encodes the opensc file attributes into the card specific format + */ +static int asepcos_set_security_attributes(sc_card_t *card, sc_file_t *file) +{ + size_t i; + const amode_entry_t *table; + u8 buf[64], *p = buf; + int r = SC_SUCCESS; + + /* first check wether the security attributes in encoded form + * are already set. If present use these */ + if (file->sec_attr != NULL && file->sec_attr_len != 0) + return asepcos_set_sec_attributes(card, file->sec_attr, + file->sec_attr_len, file->type == SC_FILE_TYPE_DF ? 0:1); + /* otherwise construct the ACL from the opensc ACLs */ + if (file->type == SC_FILE_TYPE_DF) + table = df_amode_table; + else if (file->type == SC_FILE_TYPE_WORKING_EF) + table = wef_amode_table; + else if (file->type == SC_FILE_TYPE_INTERNAL_EF) + table = ief_amode_table; + else + return SC_ERROR_INVALID_ARGUMENTS; + + p = buf; + for (i = 0; table[i].am != 0; i++) { + const struct sc_acl_entry *ent = sc_file_get_acl_entry(file, table[i].sc); + if (ent == NULL) + continue; + *p++ = 0x80; + *p++ = 0x01; + *p++ = table[i].am & 0xff; + if (ent->method == SC_AC_NONE) { + *p++ = 0x90; + *p++ = 0x00; + } else if (ent->method == SC_AC_NEVER) { + *p++ = 0x97; + *p++ = 0x00; + } else if (ent->method == SC_AC_CHV) { + sc_cardctl_asepcos_akn2fileid_t st; + st.akn = ent->key_ref; + r = asepcos_akn_to_fileid(card, &st); + if (r != SC_SUCCESS) + return r; + *p++ = 0xa0; + *p++ = 0x05; + *p++ = 0x89; + *p++ = 0x03; + *p++ = (st.fileid >> 16) & 0xff; + *p++ = (st.fileid >> 8 ) & 0xff; + *p++ = st.fileid & 0xff; + } else { + sc_error(card->ctx, "unknow auth method: '%d'", ent->method); + return SC_ERROR_INTERNAL; + } + } + + if (p != buf) + r = asepcos_set_sec_attributes(card, buf, p-buf, file->type == SC_FILE_TYPE_DF ? 0:1); + return r; +} + +static int asepcos_decipher(sc_card_t *card, const u8 * crgram, size_t crgram_len, + u8 * out, size_t outlen) +{ + int r; + sc_apdu_t apdu; + + SC_FUNC_CALLED(card->ctx, 2); + + /* call RSA ENCRYPT DECRYPT for the decipher operation */ + sc_format_apdu(card, &apdu, SC_APDU_CASE_4, 0x14, 0x01, 0x00); + apdu.cla |= 0x80; + apdu.resp = out; + apdu.resplen = outlen; + /* if less than 256 bytes are expected than set Le to 0x00 + * to tell the card the we want everything available (note: we + * always have Le <= crgram_len) */ + apdu.le = (outlen >= 256 && crgram_len < 256) ? 256 : outlen; + apdu.sensitive = 1; + + apdu.data = crgram; + apdu.lc = crgram_len; + apdu.datalen = crgram_len; + r = sc_transmit_apdu(card, &apdu); + SC_TEST_RET(card->ctx, r, "APDU transmit failed"); + if (apdu.sw1 != 0x90 || apdu.sw2 != 0x00) + SC_FUNC_RETURN(card->ctx, 2, sc_check_sw(card, apdu.sw1, apdu.sw2)); + return apdu.resplen; +} + +/* compute the signature. Currently the RSA ENCRYPT DECRYPT command + * is used here (TODO: use the key attributes to determine method + * to use for signature generation). + */ +static int asepcos_compute_signature(sc_card_t *card, const u8 *data, size_t datalen, + u8 *out, size_t outlen) +{ + int r = SC_SUCCESS, atype; + u8 rbuf[SC_MAX_APDU_BUFFER_SIZE]; + sc_apdu_t apdu; + + SC_FUNC_CALLED(card->ctx, 2); + + if (datalen >= 256) + atype = SC_APDU_CASE_4_EXT; + else + atype = SC_APDU_CASE_4_SHORT; + sc_format_apdu(card, &apdu, atype, 0x14, 0x01, 0x00); + apdu.cla |= 0x80; + apdu.lc = datalen; + apdu.datalen = datalen; + apdu.data = data; + apdu.resp = rbuf; + apdu.resplen = sizeof(rbuf); + apdu.le = 256; + + r = sc_transmit_apdu(card, &apdu); + SC_TEST_RET(card->ctx, r, "APDU transmit failed"); + if (apdu.sw1 != 0x90 || apdu.sw2 != 0x00) { + sc_error(card->ctx, "error creating signature"); + return sc_check_sw(card, apdu.sw1, apdu.sw2); + } + + if (apdu.resplen > outlen) + return SC_ERROR_BUFFER_TOO_SMALL; + memcpy(out, apdu.resp, apdu.resplen); + + return apdu.resplen; +} + +/* activates the EF/DF specified in the file id. + */ +static int asepcos_activate_file(sc_card_t *card, int fileid, int is_ef) +{ + int r, type = is_ef != 0 ? 2 : 1; + sc_apdu_t apdu; + u8 sbuf[2]; + + sbuf[0] = (fileid >> 8) & 0xff; + sbuf[1] = fileid & 0xff; + sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, 0x44, type, 0x00); + apdu.lc = 2; + apdu.datalen = 2; + apdu.data = sbuf; + r = sc_transmit_apdu(card, &apdu); + SC_TEST_RET(card->ctx, r, "APDU transmit failed"); + return sc_check_sw(card, apdu.sw1, apdu.sw2); +} + +/* CREATE FILE: creates wEF, iEF and DFs. Note: although the ISO + * command is used for wEF and iEF so format of the data send to + * the card is asepcos specific. + * @param card the sc_card_t object to use + * @param file sc_file_t object describing the file to create + * @return SC_SUCCESS on success and an error code otherwise. + */ +static int asepcos_create_file(sc_card_t *card, sc_file_t *file) +{ + if (file->type == SC_FILE_TYPE_DF) { + int r, type; + sc_apdu_t apdu; + u8 sbuf[SC_MAX_APDU_BUFFER_SIZE], *p = &sbuf[0]; + + *p++ = (file->id >> 8) & 0xff; + *p++ = file->id & 0xff; + if (file->size > 0xffff) { + *p++ = (file->size >> 24) & 0xff; + *p++ = (file->size >> 16) & 0xff; + *p++ = (file->size >> 8 ) & 0xff; + *p++ = file->size & 0xff; + type = 1; + } else { + *p++ = (file->size >> 8) & 0xff; + *p++ = file->size & 0xff; + type = 0; + } + if (file->namelen != 0 && file->namelen <= 16) { + memcpy(p, file->name, file->namelen); + p += file->namelen; + } + sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, 0xe0, 0x38, type); + apdu.cla |= 0x80; + apdu.lc = p - sbuf; + apdu.datalen = p - sbuf; + apdu.data = sbuf; + + r = sc_transmit_apdu(card, &apdu); + SC_TEST_RET(card->ctx, r, "APDU transmit failed"); + if (apdu.sw1 != 0x90 || apdu.sw2 != 0x00) + return sc_check_sw(card, apdu.sw1, apdu.sw2); + /* set security attributes */ + r = asepcos_set_security_attributes(card, file); + if (r != SC_SUCCESS) { + sc_error(card->ctx, "unable to set security attributes"); + return r; + } + return SC_SUCCESS; + } else if (file->type == SC_FILE_TYPE_WORKING_EF) { + int r; + sc_apdu_t apdu; + u8 descr_byte = file->ef_structure & 7; + u8 sbuf[SC_MAX_APDU_BUFFER_SIZE], *p = &sbuf[0]; + + *p++ = 0x85; + p++; + /* file id */ + *p++ = (file->id >> 8) & 0xff; + *p++ = file->id & 0xff; + /* record size */ + if (file->ef_structure == SC_FILE_EF_TRANSPARENT) { + *p++ = 0x00; + *p++ = 0x00; + } else { + *p++ = (file->record_length >> 8) & 0xff; + *p++ = file->record_length & 0xff; + } + /* number of records or file size */ + if (file->ef_structure == SC_FILE_EF_TRANSPARENT) { + *p++ = (file->size >> 8) & 0xff; + *p++ = file->size & 0xff; + } else { + *p++ = (file->record_count >> 8) & 0xff; + *p++ = file->record_count & 0xff; + } + /* set the length of the inner TLV object */ + sbuf[1] = p - sbuf - 2; /* FIXME */ + + sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, 0xe0, descr_byte, 0x00); + apdu.lc = p - sbuf; + apdu.datalen = p - sbuf; + apdu.data = sbuf; + r = sc_transmit_apdu(card, &apdu); + SC_TEST_RET(card->ctx, r, "APDU transmit failed"); + if (apdu.sw1 != 0x90 || apdu.sw2 != 0x00) + return sc_check_sw(card, apdu.sw1, apdu.sw2); + + /* set security attributes */ + r = asepcos_set_security_attributes(card, file); + if (r != SC_SUCCESS) { + sc_error(card->ctx, "unable to set security attributes"); + return r; + } + return asepcos_activate_file(card, file->id, 1); + } else if (file->type == SC_FILE_TYPE_INTERNAL_EF) { + /* for internal EF we 'misuse' the prop_attr field of the + * sc_file_t object to store the data send to the card in + * the CREATE EF call. + */ + int r, atype = SC_APDU_CASE_3_SHORT; + sc_apdu_t apdu; + + if (file->prop_attr_len > 255) + atype = SC_APDU_CASE_3_EXT; + + sc_format_apdu(card, &apdu, atype, 0xe0, 0x08, 0x00); + apdu.lc = file->prop_attr_len; + apdu.datalen = file->prop_attr_len; + apdu.data = file->prop_attr; + + r = sc_transmit_apdu(card, &apdu); + SC_TEST_RET(card->ctx, r, "APDU transmit failed"); + if (apdu.sw1 != 0x90 || apdu.sw2 != 0x00) + return sc_check_sw(card, apdu.sw1, apdu.sw2); + /* set security attributes */ + r = asepcos_set_security_attributes(card, file); + if (r != SC_SUCCESS) { + sc_error(card->ctx, "unable to set security attributes"); + return r; + } + return asepcos_activate_file(card, file->id, 1); + } else + return SC_ERROR_INVALID_ARGUMENTS; +} + +/* list files: the function first calls GET DATA to get the current + * working DF. It then re-selects the DF to get proprietary FCI which + * contain the FID of the first child DF EF. + * The FID of the other EFs/DFs within the selected DF are then + * obtained by selecting the know FIDs to get next child EF/DF. + * @param card the sc_card_t object to use + * @param buff the output buffer for the list of FIDs + * @param blen the length of the buffer + * @return the number of FIDs read on success and an error value otherwise. + */ +static int asepcos_list_files(sc_card_t *card, u8 *buf, size_t blen) +{ + int r, rv = 0, dfFID, efFID; + sc_path_t bpath, tpath; + sc_file_t *tfile = NULL; + + /* 1. get currently selected DF */ + r = asepcos_get_current_df_path(card, &bpath); + if (rv != SC_SUCCESS) + return r; + /* 2. re-select DF to get the FID of the child EFs/DFs */ + r = sc_select_file(card, &bpath, &tfile); + if (r != SC_SUCCESS) + return r; + if (tfile->prop_attr_len != 6 || tfile->prop_attr == NULL) { + sc_file_free(tfile); + sc_error(card->ctx, "unable to parse proprietary FCI attributes"); + return SC_ERROR_INTERNAL; + } + dfFID = (tfile->prop_attr[2] << 8) | tfile->prop_attr[3]; + efFID = (tfile->prop_attr[4] << 8) | tfile->prop_attr[5]; + sc_file_free(tfile); + /* 3. select every child DF to get the FID of the next child DF */ + while (dfFID != 0) { + /* put DF FID on the list */ + if (blen < 2) + return SC_ERROR_BUFFER_TOO_SMALL; + *buf++ = (dfFID >> 8) & 0xff; + *buf++ = dfFID & 0xff; + rv += 2; + blen -= 2; + /* select DF to get next DF FID */ + tpath = bpath; + r = sc_append_file_id(&tpath, dfFID); + if (r != SC_SUCCESS) + return r; + r = sc_select_file(card, &tpath, &tfile); + if (r != SC_SUCCESS) + return r; + if (tfile->prop_attr_len != 6 || tfile->prop_attr == NULL) + return SC_ERROR_INTERNAL; + dfFID = (tfile->prop_attr[0] << 8) | tfile->prop_attr[1]; + sc_file_free(tfile); + } + /* 4. select every child EF ... */ + while (efFID != 0) { + /* put DF FID on the list */ + if (blen < 2) + return SC_ERROR_BUFFER_TOO_SMALL; + *buf++ = (efFID >> 8) & 0xff; + *buf++ = efFID & 0xff; + rv += 2; + blen -= 2; + /* select EF to get next EF FID */ + tpath = bpath; + r = sc_append_file_id(&tpath, efFID); + if (r != SC_SUCCESS) + return r; + r = sc_select_file(card, &tpath, &tfile); + if (r != SC_SUCCESS) + return r; + if (tfile->prop_attr_len < 2 || tfile->prop_attr == NULL) + return SC_ERROR_INTERNAL; + efFID = (tfile->prop_attr[0] << 8) | tfile->prop_attr[1]; + sc_file_free(tfile); + } + + return rv; +} + +static int asepcos_delete_file(sc_card_t *card, const sc_path_t *path) +{ + int r, ftype, atype; + sc_apdu_t apdu; + u8 buf[SC_MAX_APDU_BUFFER_SIZE]; + +#if 0 + /* select the file (note: if the file is already selected we do + * not re-select it as we might otherwise lose the necessary + * credential */ + r = sc_select_file(card, path, NULL); + if (r != SC_SUCCESS) + return r; +#endif + /* use GET DATA to determine whether it is a DF or EF */ + sc_format_apdu(card, &apdu, SC_APDU_CASE_2_SHORT, 0xca, 0x01, 0x84); + apdu.le = 256; + apdu.resplen = sizeof(buf); + apdu.resp = buf; + r = sc_transmit_apdu(card, &apdu); + SC_TEST_RET(card->ctx, r, "APDU transmit failed"); + if (apdu.sw1 == 0x90 && apdu.sw2 == 0x00) { + /* looks like a EF */ + atype = SC_APDU_CASE_3_SHORT; + ftype = 0x02; + buf[0] = path->value[path->len-2]; + buf[1] = path->value[path->len-1]; + } else { + /* presumedly a DF */ + atype = SC_APDU_CASE_1; + ftype = 0x00; + } + + sc_format_apdu(card, &apdu, atype, 0xe4, ftype, 0x00); + if (atype == SC_APDU_CASE_3_SHORT) { + apdu.lc = 2; + apdu.datalen = 2; + apdu.data = buf; + } + + r = sc_transmit_apdu(card, &apdu); + SC_TEST_RET(card->ctx, r, "APDU transmit failed"); + return sc_check_sw(card, apdu.sw1, apdu.sw2); +} + +/* returns the default transport key (note: this should be put in the + * pkcs15 profile file). + */ +static int asepcos_get_default_key(sc_card_t *card, + struct sc_cardctl_default_key *data) +{ + static const u8 asepcos_def_key[] = {0x41,0x53,0x45,0x43,0x41,0x52,0x44,0x2b}; + if (data->method != SC_AC_CHV && data->method != SC_AC_AUT) + return SC_ERROR_NO_DEFAULT_KEY; + if (data->key_data == NULL || data->len < sizeof(asepcos_def_key)) + return SC_ERROR_BUFFER_TOO_SMALL; + memcpy(data->key_data, asepcos_def_key, sizeof(asepcos_def_key)); + data->len = sizeof(asepcos_def_key); + return SC_SUCCESS; +} + +static int asepcos_get_serialnr(sc_card_t *card, sc_serial_number_t *serial) +{ + int r; + sc_apdu_t apdu; + u8 rbuf[SC_MAX_APDU_BUFFER_SIZE]; + + sc_format_apdu(card, &apdu, SC_APDU_CASE_2_SHORT, 0xca, 0x01, 0x14); + apdu.resp = rbuf; + apdu.resplen = sizeof(rbuf); + apdu.le = 256; + r = sc_transmit_apdu(card, &apdu); + SC_TEST_RET(card->ctx, r, "APDU transmit failed"); + if (apdu.sw1 != 0x90 || apdu.sw2 != 0x00) + return SC_ERROR_INTERNAL; + if (apdu.resplen != 8) { + sc_debug(card->ctx, "unexpected response to GET DATA serial number\n"); + return SC_ERROR_INTERNAL; + } + /* cache serial number */ + memcpy(card->serialnr.value, rbuf, 8); + card->serialnr.len = 8; + /* copy and return serial number */ + memcpy(serial, &card->serialnr, sizeof(*serial)); + return SC_SUCCESS; +} + +static int asepcos_change_key(sc_card_t *card, sc_cardctl_asepcos_change_key_t *p) +{ + int r, atype; + sc_apdu_t apdu; + + if (p->datalen > 255) + atype = SC_APDU_CASE_3_EXT; + else + atype = SC_APDU_CASE_3_SHORT; + + sc_format_apdu(card, &apdu, atype, 0x24, 0x01, 0x80); + apdu.lc = p->datalen; + apdu.datalen = p->datalen; + apdu.data = p->data; + + r = sc_transmit_apdu(card, &apdu); + SC_TEST_RET(card->ctx, r, "APDU transmit failed"); + return sc_check_sw(card, apdu.sw1, apdu.sw2); +} + +static int asepcos_card_ctl(sc_card_t *card, unsigned long cmd, void *ptr) +{ + switch (cmd) { + case SC_CARDCTL_GET_DEFAULT_KEY: + return asepcos_get_default_key(card, (struct sc_cardctl_default_key *) ptr); + case SC_CARDCTL_GET_SERIALNR: + return asepcos_get_serialnr(card, (sc_serial_number_t *)ptr); + case SC_CARDCTL_ASEPCOS_CHANGE_KEY: + return asepcos_change_key(card, (sc_cardctl_asepcos_change_key_t*)ptr); + case SC_CARDCTL_ASEPCOS_AKN2FILEID: + return asepcos_akn_to_fileid(card, (sc_cardctl_asepcos_akn2fileid_t*)ptr); + case SC_CARDCTL_ASEPCOS_SET_SATTR: + return asepcos_set_security_attributes(card, (sc_file_t*)ptr); + case SC_CARDCTL_ASEPCOS_ACTIVATE_FILE: + return asepcos_activate_file(card, ((sc_cardctl_asepcos_activate_file_t*)ptr)->fileid, + ((sc_cardctl_asepcos_activate_file_t *)ptr)->is_ef); + } + return SC_ERROR_NOT_SUPPORTED; +} + +/* build the different APDUs for the PIN handling commands + */ +static int asepcos_build_pin_apdu(sc_card_t *card, sc_apdu_t *apdu, + struct sc_pin_cmd_data *data, u8 *buf, size_t buf_len, + unsigned int cmd, int is_puk) +{ + int r, fileid; + u8 *p = buf; + sc_cardctl_asepcos_akn2fileid_t st; + + switch (cmd) { + case SC_PIN_CMD_VERIFY: + st.akn = data->pin_reference; + r = asepcos_akn_to_fileid(card, &st); + if (r != SC_SUCCESS) + return r; + fileid = st.fileid; + /* the fileid of the puk is the fileid of the pin + 1 */ + if (is_puk != 0) + fileid++; + sc_format_apdu(card, apdu, SC_APDU_CASE_3_SHORT, 0x20, 0x02, 0x80); + *p++ = (fileid >> 24) & 0xff; + *p++ = (fileid >> 16) & 0xff; + *p++ = (fileid >> 8 ) & 0xff; + *p++ = fileid & 0xff; + if (is_puk == 0) { + memcpy(p, data->pin1.data, data->pin1.len); + p += data->pin1.len; + } else { + memcpy(p, data->pin1.data, data->pin1.len); + p += data->pin1.len; + } + apdu->lc = p - buf; + apdu->datalen = p - buf; + apdu->data = buf; + break; + case SC_PIN_CMD_CHANGE: + /* build the CHANGE KEY apdu. Note: the PIN file is implicitly + * selected by its SFID */ + *p++ = 0x81; + *p++ = data->pin2.len & 0xff; + memcpy(p, data->pin2.data, data->pin2.len); + p += data->pin2.len; + st.akn = data->pin_reference; + r = asepcos_akn_to_fileid(card, &st); + if (r != SC_SUCCESS) + return r; + fileid = 0x80 | (st.fileid & 0x1f); + sc_format_apdu(card, apdu, SC_APDU_CASE_3_SHORT, 0x24, 0x01, fileid); + apdu->lc = p - buf; + apdu->datalen = p - buf; + apdu->data = buf; + break; + case SC_PIN_CMD_UNBLOCK: + /* build the UNBLOCK KEY apdu. The PIN file is implicitly + * selected by its SFID. The new PIN is provided in the + * data field of the UNBLOCK KEY command. */ + *p++ = 0x81; + *p++ = data->pin2.len & 0xff; + memcpy(p, data->pin2.data, data->pin2.len); + p += data->pin2.len; + st.akn = data->pin_reference; + r = asepcos_akn_to_fileid(card, &st); + if (r != SC_SUCCESS) + return r; + fileid = 0x80 | (st.fileid & 0x1f); + sc_format_apdu(card, apdu, SC_APDU_CASE_3_SHORT, 0x2C, 0x02, fileid); + apdu->lc = p - buf; + apdu->datalen = p - buf; + apdu->data = buf; + break; + default: + return SC_ERROR_NOT_SUPPORTED; + } + /* all PIN related APDUs are sensitive */ + apdu->sensitive = 1; + + return SC_SUCCESS; +} + +/* generic function to handle the different PIN operations, i.e verify + * change and unblock. + */ +static int asepcos_pin_cmd(sc_card_t *card, struct sc_pin_cmd_data *pdata, + int *tries_left) +{ + sc_apdu_t apdu; + int r = SC_SUCCESS; + u8 sbuf[SC_MAX_APDU_BUFFER_SIZE]; + + if (tries_left) + *tries_left = -1; + /* only PIN verification is supported at the moment */ + if (pdata->pin_type != SC_AC_CHV && pdata->pin_type != SC_AC_AUT) + return SC_ERROR_INVALID_ARGUMENTS; + /* check PIN length */ + if (pdata->pin1.len < 4 || pdata->pin1.len > 16) { + sc_error(card->ctx, "invalid PIN1 length"); + return SC_ERROR_INVALID_PIN_LENGTH; + } + + switch (pdata->cmd) { + case SC_PIN_CMD_VERIFY: + /* build verify APDU and send it to the card */ + r = asepcos_build_pin_apdu(card, &apdu, pdata, sbuf, sizeof(sbuf), SC_PIN_CMD_VERIFY, 0); + if (r != SC_SUCCESS) + break; + r = sc_transmit_apdu(card, &apdu); + if (r != SC_SUCCESS) + sc_error(card->ctx, "APDU transmit failed"); + break; + case SC_PIN_CMD_CHANGE: + if (pdata->pin2.len < 4 || pdata->pin2.len > 16) { + sc_error(card->ctx, "invalid PIN2 length"); + return SC_ERROR_INVALID_PIN_LENGTH; + } + /* 1. step: verify the old pin */ + r = asepcos_build_pin_apdu(card, &apdu, pdata, sbuf, sizeof(sbuf), SC_PIN_CMD_VERIFY, 0); + if (r != SC_SUCCESS) + break; + r = sc_transmit_apdu(card, &apdu); + if (r != SC_SUCCESS) { + sc_error(card->ctx, "APDU transmit failed"); + break; + } + if (apdu.sw1 != 0x90 || apdu.sw2 != 0x00) { + /* unable to verify the old PIN */ + r = sc_check_sw(card, apdu.sw1, apdu.sw2); + break; + } + /* 2, step: use CHANGE KEY to update the PIN */ + r = asepcos_build_pin_apdu(card, &apdu, pdata, sbuf, sizeof(sbuf), SC_PIN_CMD_CHANGE, 0); + if (r != SC_SUCCESS) + break; + r = sc_transmit_apdu(card, &apdu); + if (r != SC_SUCCESS) + sc_error(card->ctx, "APDU transmit failed"); + r = sc_check_sw(card, apdu.sw1, apdu.sw2); + break; + case SC_PIN_CMD_UNBLOCK: + if (pdata->pin2.len < 4 || pdata->pin2.len > 16) { + sc_error(card->ctx, "invalid PIN2 length"); + return SC_ERROR_INVALID_PIN_LENGTH; + } + /* 1. step: verify the puk */ + r = asepcos_build_pin_apdu(card, &apdu, pdata, sbuf, sizeof(sbuf), SC_PIN_CMD_VERIFY, 1); + if (r != SC_SUCCESS) + break; + r = sc_transmit_apdu(card, &apdu); + if (r != SC_SUCCESS) { + sc_error(card->ctx, "APDU transmit failed"); + break; + } + /* 2, step: unblock and change the pin */ + r = asepcos_build_pin_apdu(card, &apdu, pdata, sbuf, sizeof(sbuf), SC_PIN_CMD_UNBLOCK, 0); + if (r != SC_SUCCESS) + break; + r = sc_transmit_apdu(card, &apdu); + if (r != SC_SUCCESS) { + sc_error(card->ctx, "APDU transmit failed"); + break; + } + r = sc_check_sw(card, apdu.sw1, apdu.sw2); + break; + default: + sc_error(card->ctx, "error: unknow cmd type"); + return SC_ERROR_INTERNAL; + } + /* Clear the buffer - it may contain pins */ + sc_mem_clear(sbuf, sizeof(sbuf)); + /* check for remaining tries if verification failed */ + if (apdu.sw1 == 0x63) { + if ((apdu.sw2 & 0xF0) == 0xC0 && tries_left != NULL) + *tries_left = apdu.sw2 & 0x0F; + return SC_ERROR_PIN_CODE_INCORRECT; + } + return r; +} + +static struct sc_card_driver * sc_get_driver(void) +{ + if (iso_ops == NULL) + iso_ops = sc_get_iso7816_driver()->ops; + asepcos_ops = *iso_ops; + asepcos_ops.match_card = asepcos_match_card; + asepcos_ops.init = asepcos_init; + asepcos_ops.finish = asepcos_finish; + asepcos_ops.select_file = asepcos_select_file; + asepcos_ops.set_security_env = asepcos_set_security_env; + asepcos_ops.decipher = asepcos_decipher; + asepcos_ops.compute_signature = asepcos_compute_signature; + asepcos_ops.create_file = asepcos_create_file; + asepcos_ops.delete_file = asepcos_delete_file; + asepcos_ops.list_files = asepcos_list_files; + asepcos_ops.card_ctl = asepcos_card_ctl; + asepcos_ops.pin_cmd = asepcos_pin_cmd; + + return &asepcos_drv; +} + +struct sc_card_driver * sc_get_asepcos_driver(void) +{ + return sc_get_driver(); +} diff --git a/src/libopensc/cardctl.h b/src/libopensc/cardctl.h index bca9baddde..2b73605a1e 100644 --- a/src/libopensc/cardctl.h +++ b/src/libopensc/cardctl.h @@ -130,7 +130,16 @@ enum { SC_CARDCTL_MUSCLE_GENERATE_KEY, SC_CARDCTL_MUSCLE_EXTRACT_KEY, SC_CARDCTL_MUSCLE_IMPORT_KEY, - SC_CARDCTL_MUSCLE_VERIFIED_PINS + SC_CARDCTL_MUSCLE_VERIFIED_PINS, + + /* + * ASEPCOS specific calls + */ + SC_CARDCTL_ASEPCOS_BASE = _CTL_PREFIX('A','S','E'), + SC_CARDCTL_ASEPCOS_CHANGE_KEY, + SC_CARDCTL_ASEPCOS_AKN2FILEID, + SC_CARDCTL_ASEPCOS_SET_SATTR, + SC_CARDCTL_ASEPCOS_ACTIVATE_FILE }; enum { @@ -400,6 +409,23 @@ typedef struct sc_cardctl_muscle_key_info { typedef struct sc_cardctl_muscle_verified_pins_info { unsigned verifiedPins; } sc_cardctl_muscle_verified_pins_info_t; + +/* ASEPCOS ctl specific structures */ +typedef struct sc_cardctl_asepcos_change_key { + const u8 *data; + size_t datalen; +} sc_cardctl_asepcos_change_key_t; + +typedef struct sc_cardctl_asepcos_akn2fileid { + int akn; + int fileid; +} sc_cardctl_asepcos_akn2fileid_t; + +typedef struct sc_cardctl_asepcos_activate_file { + int fileid; + int is_ef; +} sc_cardctl_asepcos_activate_file_t; + #ifdef __cplusplus } #endif diff --git a/src/libopensc/cards.h b/src/libopensc/cards.h index 2e6b039691..6820efb841 100644 --- a/src/libopensc/cards.h +++ b/src/libopensc/cards.h @@ -125,7 +125,12 @@ enum { /* ACOS5 driver */ SC_CARD_TYPE_ACOS5_BASE = 16000, - SC_CARD_TYPE_ACOS5_GENERIC + SC_CARD_TYPE_ACOS5_GENERIC, + + /* Athena APCOS cards */ + SC_CARD_TYPE_ASEPCOS_BASE = 17000, + SC_CARD_TYPE_ASEPCOS_GENERIC, + SC_CARD_TYPE_ASEPCOS_JAVA }; #ifdef __cplusplus diff --git a/src/libopensc/ctx.c b/src/libopensc/ctx.c index e7a0b66ca2..07c74e03bd 100644 --- a/src/libopensc/ctx.c +++ b/src/libopensc/ctx.c @@ -60,6 +60,7 @@ static const struct _sc_driver_entry internal_card_drivers[] = { #endif { "miocos", (void *(*)(void)) sc_get_miocos_driver }, { "mcrd", (void *(*)(void)) sc_get_mcrd_driver }, + { "asepcos", (void *(*)(void)) sc_get_asepcos_driver }, { "setcos", (void *(*)(void)) sc_get_setcos_driver }, { "starcos", (void *(*)(void)) sc_get_starcos_driver }, { "tcos", (void *(*)(void)) sc_get_tcos_driver }, @@ -69,7 +70,7 @@ static const struct _sc_driver_entry internal_card_drivers[] = { { "oberthur", (void *(*)(void)) sc_get_oberthur_driver }, #endif { "belpic", (void *(*)(void)) sc_get_belpic_driver }, - { "atrust-acos",(void *(*)(void))sc_get_atrust_acos_driver }, + { "atrust-acos",(void *(*)(void)) sc_get_atrust_acos_driver }, { "muscle", (void *(*)(void)) sc_get_muscle_driver }, // Above EMV because the detection gets caught there first { "emv", (void *(*)(void)) sc_get_emv_driver }, { "incrypto34", (void *(*)(void)) sc_get_incrypto34_driver }, diff --git a/src/libopensc/opensc.h b/src/libopensc/opensc.h index 41942bf89e..ff26bc3b57 100644 --- a/src/libopensc/opensc.h +++ b/src/libopensc/opensc.h @@ -1175,6 +1175,7 @@ extern sc_card_driver_t *sc_get_incrypto34_driver(void); extern sc_card_driver_t *sc_get_piv_driver(void); extern sc_card_driver_t *sc_get_muscle_driver(void); extern sc_card_driver_t *sc_get_acos5_driver(void); +extern sc_card_driver_t *sc_get_asepcos_driver(void); #ifdef __cplusplus } diff --git a/src/pkcs15init/Makefile.am b/src/pkcs15init/Makefile.am index e81c174367..e890932503 100644 --- a/src/pkcs15init/Makefile.am +++ b/src/pkcs15init/Makefile.am @@ -19,7 +19,8 @@ PROFILES = \ starcos.profile \ setcos.profile \ pkcs15.profile \ - muscle.profile + muscle.profile \ + asepcos.profile EXTRA_DIST = $(PROFILES) Makefile.mak @@ -31,7 +32,7 @@ libpkcs15init_la_SOURCES = \ pkcs15-gpk.c pkcs15-miocos.c pkcs15-cflex.c \ pkcs15-cardos.c pkcs15-jcop.c pkcs15-starcos.c \ pkcs15-oberthur.c pkcs15-setcos.c pkcs15-incrypto34.c \ - pkcs15-muscle.c + pkcs15-muscle.c pkcs15-asepcos.c libpkcs15init_la_LDFLAGS = -version-info @OPENSC_LT_CURRENT@:@OPENSC_LT_REVISION@:@OPENSC_LT_AGE@ diff --git a/src/pkcs15init/Makefile.mak b/src/pkcs15init/Makefile.mak index c826521948..c885140ed1 100644 --- a/src/pkcs15init/Makefile.mak +++ b/src/pkcs15init/Makefile.mak @@ -9,7 +9,7 @@ OBJECTS = profile.obj pkcs15-lib.obj keycache.obj \ pkcs15-miocos.obj pkcs15-gpk.obj pkcs15-cflex.obj \ pkcs15-cardos.obj pkcs15-jcop.obj pkcs15-starcos.obj \ pkcs15-oberthur.obj pkcs15-setcos.obj pkcs15-incrypto34.obj \ - pkcs15-muscle.obj + pkcs15-muscle.obj pkcs15-asepcos.obj all: install-headers $(TARGET) diff --git a/src/pkcs15init/asepcos.profile b/src/pkcs15init/asepcos.profile new file mode 100644 index 0000000000..2752b5d774 --- /dev/null +++ b/src/pkcs15init/asepcos.profile @@ -0,0 +1,110 @@ +# +# PKCS15 r/w profile for Athena APCOS cards +# +cardinfo { + max-pin-length = 16; + pin-encoding = ascii-numeric; + pin-pad-char = 0x00; +} + +# Default settings. +# This option block will always be processed. +option default { + macros { + so-pin-flags = local, initialized, soPin; + df_acl = *=$SOPIN; + } +} + +# This option sets up the card so that a single +# user PIN protects all files +option onepin { + macros { + so-pin-flags = local, initialized; + df_acl = *=$PIN; + } +} + + +# Define reasonable limits for PINs and PUK +PIN so-pin { + reference = 1; + flags = $so-pin-flags; +} +PIN so-puk { + reference = 2; +} +PIN user-pin { + attempts = 3; + flags = local, initialized; +} +PIN user-puk { + attempts = 10; + flags = local, initialized; +} + +# Additional filesystem info. +# This is added to the file system info specified in the +# main profile. +filesystem { + DF MF { + ACL = *=AUT0; + + DF PKCS15-AppDF { + size = 8192; + + ACL = $df_acl; + + EF PKCS15-PrKDF { + size = 384; + } + + EF PKCS15-PuKDF { + size = 384; + } + + # This template defines files for keys, certificates etc. + # + # When instantiating the template, each file id will be + # combined with the last octet of the object's pkcs15 id + # to form a unique file ID. + template key-domain { + # This is a dummy entry - pkcs15-init insists that + # this is present + EF private-key { + file-id = 0100; + ACL = *=NEVER, CRYPTO=$PIN, UPDATE=$PIN; + } + # public keys + EF public-key { + file-id = 3003; + structure = transparent; + ACL = *=NEVER, + READ=NONE, + UPDATE=$PIN, + ERASE=$PIN; + } + # Certificate template + EF certificate { + file-id = 3104; + structure = transparent; + ACL = *=NEVER, + READ=NONE, + UPDATE=$PIN, + ERASE=$PIN; + } + # data objects are stored in transparent EFs. + EF data { + file-id = 3302; + structure = transparent; + ACL = *=NEVER, + READ=NONE, + UPDATE=$PIN, + ERASE=$PIN; + } + + } + + } + } +} diff --git a/src/pkcs15init/pkcs15-asepcos.c b/src/pkcs15init/pkcs15-asepcos.c new file mode 100644 index 0000000000..adf3b92688 --- /dev/null +++ b/src/pkcs15init/pkcs15-asepcos.c @@ -0,0 +1,846 @@ +/* + * Copyright (c) 2007 Athena Smartcard Solutions Inc. + * + * 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 +#endif +#include +#include +#include +#include +#include +#include +#include "pkcs15-init.h" +#include "profile.h" + +/* delete a EF/DF if present. This function does not return an + * error if the requested file is not present. + */ +static int asepcos_cond_delete(sc_profile_t *pro, sc_card_t *card, + const sc_path_t *path) +{ + int r; + sc_file_t *tfile = NULL; + + sc_ctx_suppress_errors_on(card->ctx); + r = sc_select_file(card, path, &tfile); + sc_ctx_suppress_errors_off(card->ctx); + if (r == SC_SUCCESS) { + r = sc_pkcs15init_authenticate(pro, card, tfile, SC_AC_OP_DELETE_SELF); + sc_file_free(tfile); + if (r != SC_SUCCESS) + return r; + r = sc_delete_file(card, path); + } else if (r == SC_ERROR_FILE_NOT_FOUND) + r = SC_SUCCESS; + return r; +} + +/* checks whether the file with the transport key exists. If existent + * the transport key is verified and stored in the keycache (as a + * normal user PIN with the same reference). + * @param profile profile information for this card + * @param card sc_card_t object to use + * @return SC_SUCCESS on success and an error code otherwise + */ +static int asepcos_check_verify_tpin(sc_profile_t *profile, sc_card_t *card) +{ + int r; + sc_path_t path; + /* check whether the file with the transport PIN exists */ + sc_format_path("3f000001", &path); + sc_ctx_suppress_errors_on(card->ctx); + r = sc_select_file(card, &path, NULL); + sc_ctx_suppress_errors_off(card->ctx); + if (r == SC_SUCCESS) { + /* try to verify the transport key */ + u8 pbuf[64]; + size_t psize = sizeof(pbuf); + sc_file_t *tfile = NULL; + sc_format_path("3f00", &path); + r = sc_profile_get_file_by_path(profile, sc_get_mf_path(), &tfile); + if (r != SC_SUCCESS) + return r; + /* we need to temporarily disable the SC_CARD_CAP_USE_FCI_AC + * flag to trick sc_pkcs15init_authenticate() to use access + * information form the profile file */ + card->caps &= ~SC_CARD_CAP_USE_FCI_AC; + r = sc_pkcs15init_authenticate(profile, card, tfile, SC_AC_OP_CRYPTO); + card->caps |= SC_CARD_CAP_USE_FCI_AC; + sc_file_free(tfile); + if (r != SC_SUCCESS) { + sc_error(card->ctx, "unable to authenticate"); + return r; + } + /* store the transport key as a PIN */ + r = sc_keycache_get_key(&path, SC_AC_AUT, 0, pbuf, psize); + if (r < 0) { + sc_error(card->ctx, "unable to get transport key"); + return r; + } + r = sc_keycache_put_key(&path, SC_AC_CHV, 0, pbuf, (size_t)r); + if (r != SC_SUCCESS) { + sc_error(card->ctx, "unable to store transport key"); + return r; + } + } + return SC_SUCCESS; +} + +/* erase card: erase all EFs/DFs created by OpenSC + * @param profile the sc_profile_t object with the configurable profile + * information + * @param card the card from which the opensc application should be + * erased. + * @return SC_SUCCESS on success and an error code otherwise + */ +static int asepcos_erase(struct sc_profile *profile, sc_card_t *card) +{ + int r; + sc_path_t path; + + /* TODO: - only remove the OpenSC entry in EF(DIR) + * - use EF(DIR) to get the DF of the OpenSC + * pkcs15 application. + */ + /* EF(DIR) */ + sc_format_path("3f002f00", &path); + r = asepcos_cond_delete(profile, card, &path); + if (r != SC_SUCCESS) + return r; + /* DF(PKCS15) */ + sc_format_path("3f005015", &path); + r = asepcos_cond_delete(profile, card, &path); + if (r != SC_SUCCESS) + return r; + + return SC_SUCCESS; +} + +/* create application DF + * @param profile sc_profile_t object with the configurable profile + * information + * @param cardd sc_card_t object to be used + * @param df sc_file_t with the application DF to create + * @return SC_SUCCESS on success and an error value otherwise + */ +static int asepcos_create_dir(sc_profile_t *profile, sc_card_t *card, + sc_file_t *df) +{ + int r; + static const u8 pa_acl[] = {0x80,0x01,0x5f,0x90,0x00}; + sc_file_t *tfile; + + /* Check wether a transport exists and verify it if present */ + r = asepcos_check_verify_tpin(profile, card); + if (r != SC_SUCCESS) + return r; + /* As we don't know whether or not a SO-PIN is used to protect the AC + * in the application DF we set the preliminary security attributes + * of the DF(PKCS15) to allow everything. Once a SO-PIN is set + * we tighten security attributes to values specified in the profile. + */ + sc_file_dup(&tfile, df); + /* we use a separate copy of the sc_file_t object so we don't + * override the permissions specified in the profile */ + if (tfile == NULL) + return SC_ERROR_OUT_OF_MEMORY; + r = sc_file_set_sec_attr(tfile, pa_acl, sizeof(pa_acl)); + if (r != SC_SUCCESS) { + sc_file_free(tfile); + return r; + } + /* create application DF */ + r = sc_pkcs15init_create_file(profile, card, tfile); + sc_file_free(tfile); + return r; +} + + +/* select PIN reference: do nothing special, the real PIN reference if + * determined when the PIN is created. This is just helper function to + * determine the next best file id of the PIN file. + */ +static int asepcos_select_pin_reference(sc_profile_t *profile, sc_card_t *card, + sc_pkcs15_pin_info_t *pinfo) +{ + if (pinfo->flags & SC_PKCS15_PIN_FLAG_SO_PIN) + return SC_SUCCESS; + if (pinfo->reference <= 0) + pinfo->reference = 1; + /* as we want to use + 1 for the PUK we need to + * ensure that all references are odd => if the reference is + * even add one */ + if ((pinfo->reference & 1) == 0) + pinfo->reference++; + return SC_SUCCESS; +} + +/* asepcos_pinid_to_akn: returns the AKN of a PIN EF + * This functions calls SELECT FILE and extracts the AKN from the + * proprietary FCP attributes. + * @param card sc_card_t object to use + * @param fileid IN file id of the PIN file + * @param akn OUT the AKN of the PIN + * @return SC_SUCCESS on success and an error code otherwise + */ +static int asepcos_pinid_to_akn(sc_card_t *card, int fileid, int *akn) +{ + int r; + u8 fid[2]; + sc_path_t path; + sc_file_t *nfile = NULL; + + fid[0] = (fileid >> 8) & 0xff; + fid[1] = fileid & 0xff; + r = sc_path_set(&path, SC_PATH_TYPE_FILE_ID, fid, 2, 0, 0); + if (r != SC_SUCCESS) + return r; + r = sc_select_file(card, &path, &nfile); + if (r != SC_SUCCESS) + return r; + if (nfile->prop_attr == NULL || nfile->prop_attr_len != 11) { + sc_error(card->ctx, "unable to determine AKN"); + sc_file_free(nfile); + return SC_ERROR_INTERNAL; + } + *akn = nfile->prop_attr[10]; + sc_file_free(nfile); + return SC_SUCCESS; +} + +static int asepcos_do_store_pin(sc_profile_t *profile, sc_card_t *card, + sc_pkcs15_pin_info_t *pinfo, const u8* pin, size_t pinlen, + int puk, int pinid) +{ + sc_file_t *nfile = NULL; + u8 buf[64], sbuf[64], *p = buf, *q = sbuf; + int r, akn; + + /* outter tag */ + *p++ = 0x85; + p++; + /* as a file id for pin with use 0x00: */ + *p++ = (pinid >> 8) & 0xff; + *p++ = pinid & 0xff; + /* pin length */ + if (pinlen < 4 || pinlen > 16) { + sc_error(card->ctx, "invalid PIN length"); + return SC_ERROR_INVALID_ARGUMENTS; + } + *p++ = 0x00; + *p++ = pinlen & 0xff; + /* max tries */ + *p++ = pinfo->tries_left & 0xff; + /* algorithm id and key key usage and padding bytes */ + *p++ = 0x00; + *p++ = 0x00; + /* key attributes (SO PIN) */ +#if 0 + *p++ = (pinfo->flags & SC_PKCS15_PIN_FLAG_SO_PIN) ? 0x08 : 0x00; +#else + *p++ = 0x00; +#endif + /* the PIN */ + *p++ = 0x81; + *p++ = pinlen & 0xff; + memcpy(p, pin, pinlen); + p += pinlen; + /* set outer length */ + buf[1] = p - buf - 2; + + nfile = sc_file_new(); + if (nfile == NULL) + return SC_ERROR_OUT_OF_MEMORY; + nfile->type = SC_FILE_TYPE_INTERNAL_EF; + nfile->id = pinid & 0xffff; + r = sc_file_set_prop_attr(nfile, buf, p - buf); + if (r != SC_SUCCESS) { + sc_file_free(nfile); + return r; + } + + /* set security attributes */ + *q++ = 0x80; + *q++ = 0x01; + *q++ = 0x92; + *q++ = 0xa0; + q++; + *q++ = 0x89; + *q++ = 0x03; + *q++ = (pinid >> 16) & 0xff; + *q++ = (pinid >> 8 ) & 0xff; + *q++ = pinid & 0xff; + if (puk != 0) { + *q++ = 0x89; + *q++ = 0x03; + *q++ = (puk >> 16) & 0xff; + *q++ = (puk >> 8 ) & 0xff; + *q++ = puk & 0xff; + } + sbuf[4] = q - sbuf - 5; + /* we need to set the security attributes separately as PIN itself + * is used to protect the UPDATE access permission. + */ + r = sc_file_set_sec_attr(nfile, sbuf, q - sbuf); + if (r != SC_SUCCESS) { + sc_file_free(nfile); + return r; + } + + r = sc_create_file(card, nfile); + sc_file_free(nfile); + if (r != SC_SUCCESS) { + sc_error(card->ctx, "unable to create PIN file"); + return r; + } + /* get AKN of the newly created PIN */ + r = asepcos_pinid_to_akn(card, pinid, &akn); + if (r != SC_SUCCESS) + return r; + /* use the AKN as reference */ + pinfo->reference = akn; + /* set the correct PIN length */ + pinfo->min_length = 4; + pinfo->stored_length = pinlen; + pinfo->max_length = 16; + + return r; +} + +/* simple function to detect whether or not the "onepin" profile is used + * (copied from pkcs15-starcos.c). + */ +static int have_onepin(sc_profile_t *profile) +{ + sc_pkcs15_pin_info_t sopin; + sc_profile_get_pin_info(profile, SC_PKCS15INIT_SO_PIN, &sopin); + if (!(sopin.flags & SC_PKCS15_PIN_FLAG_SO_PIN)) + return 1; + else + return 0; +} + +static void asepcos_fix_pin_reference(sc_pkcs15_pin_info_t *pinfo) +{ + if (pinfo->flags & SC_PKCS15_PIN_FLAG_SO_PIN) + sc_keycache_set_pin_name(&pinfo->path, pinfo->reference, SC_PKCS15INIT_SO_PIN); + else + sc_keycache_set_pin_name(&pinfo->path, pinfo->reference, SC_PKCS15INIT_USER_PIN); +} + +/* create PIN and, if specified, PUK files + * @param profile profile information for this card + * @param card sc_card_t object to use + * @param pin_obj sc_pkcs15_object_t for the PIN + * @param pin PIN value + * @param len_len PIN length + * @param puk PUK value (optional) + * @param puk_len PUK length (optional) + * @return SC_SUCCESS on success and an error code otherwise + */ +static int asepcos_create_pin(sc_profile_t *profile, sc_card_t *card, + sc_file_t *df, sc_pkcs15_object_t *pin_obj, + const u8 *pin, size_t pin_len, const u8 *puk, size_t puk_len) +{ + sc_pkcs15_pin_info_t *pinfo = (sc_pkcs15_pin_info_t *) pin_obj->data; + int r, pid; + sc_path_t tpath = df->path; + sc_file_t *tfile = NULL; + + if (!pin || !pin_len) + return SC_ERROR_INVALID_ARGUMENTS; + + pid = (pinfo->reference & 0xff) | (((tpath.len >> 1) - 1) << 16); + + /* get the ACL of the application DF */ + r = sc_select_file(card, &df->path, &tfile); + if (r != SC_SUCCESS) + return r; + /* verify the PIN protecting the CREATE acl (if necessary) */ + r = sc_pkcs15init_authenticate(profile, card, tfile, SC_AC_OP_CREATE); + sc_file_free(tfile); + if (r != SC_SUCCESS) { + sc_error(card->ctx, "unable to create PIN file, insufficent rights"); + return r; + } + + do { + sc_path_t pin_path; + memset(&pin_path, 0, sizeof(sc_path_t)); + pin_path.type = SC_PATH_TYPE_FILE_ID; + /* XXX: check the pkcs15 structure whether this file id + * is already used */ + r = sc_append_file_id(&pin_path, pid & 0xff); + if (r != SC_SUCCESS) + return r; + sc_ctx_suppress_errors_on(card->ctx); + r = sc_select_file(card, &pin_path, NULL); + sc_ctx_suppress_errors_off(card->ctx); + if (r == SC_SUCCESS) + pid += 2; + else if (r != SC_ERROR_FILE_NOT_FOUND) { + sc_error(card->ctx, "error selecting PIN file"); + return r; + } + } while (r != SC_ERROR_FILE_NOT_FOUND); + + if (puk != NULL && puk_len != 0) { + /* Create PUK (if specified). Note: we need to create the PUK + * the PIN as the PUK fileid is used in the PIN acl. + */ + struct sc_pkcs15_pin_info puk_info; + + if (pinfo->flags & SC_PKCS15_PIN_FLAG_SO_PIN) + sc_profile_get_pin_info(profile, SC_PKCS15INIT_SO_PUK, &puk_info); + else + sc_profile_get_pin_info(profile, SC_PKCS15INIT_USER_PUK, &puk_info); + /* If a PUK we use "file id of the PIN" + 1 as the file id + * of the PUK. + */ + r = asepcos_do_store_pin(profile, card, &puk_info, puk, puk_len, 0, pid+1); + if (r != SC_SUCCESS) + return r; + } + + r = asepcos_do_store_pin(profile, card, pinfo, pin, pin_len, pid+1, pid); + if (r != SC_SUCCESS) + return r; + +#if 1 + if (pinfo->flags & SC_PKCS15_PIN_FLAG_SO_PIN || + (have_onepin(profile) && pid == 0x010001)) { + sc_cardctl_asepcos_activate_file_t st; + /* Once the SO PIN or ,in case of the "onepin" profile", the + * first USER PIN has been set we can tighten the ACLs of + * the application DF. + */ + sc_debug(card->ctx, "finalizing application DF"); + + /* first we need to fix the reference to pin in the key + * keycache as sc_pkcs15init_fixup_file() will otherwise + * mess up the ACLs */ + asepcos_fix_pin_reference(pinfo); + + r = sc_select_file(card, &df->path, NULL); + if (r != SC_SUCCESS) + return r; + /* remove symbolic references from the ACLs */ + r = sc_pkcs15init_fixup_file(profile, df); + if (r != SC_SUCCESS) + return r; + r = sc_card_ctl(card, SC_CARDCTL_ASEPCOS_SET_SATTR, df); + if (r != SC_SUCCESS) { + sc_error(card->ctx, "unable to change the security attributes"); + return r; + } + /* finally activate the application DF (fix ACLs) */ + /* 1. select MF */ + r = sc_select_file(card, sc_get_mf_path(), NULL); + if (r != SC_SUCCESS) + return r; + /* 2. activate the application DF */ + st.fileid = df->id; + st.is_ef = 0; + r = sc_card_ctl(card, SC_CARDCTL_ASEPCOS_ACTIVATE_FILE, &st); + if (r != SC_SUCCESS) { + sc_error(card->ctx, "unable to activate DF"); + return r; + } + } +#endif + +#ifdef asepcos_USE_PIN_PATH + /* using the real path to the PIN file would be nice but unfortunately + * it currently causes some problems with the keycache code + */ + r = sc_append_file_id(&tpath, pid & 0xff); + if (r != SC_SUCCESS) + return r; + pinfo->path = tpath; +#endif + return r; +} + +/* internal wrapper for sc_pkcs15init_authenticate() + * @param profile information for this card + * @param card sc_card_t object to use + * @param path path to the EF/DF for which the credential is required + * @param op the required access method + * @return SC_SUCCESS on success and an error code otherwise + */ +static int asepcos_do_authenticate(sc_profile_t *profile, sc_card_t *card, + const sc_path_t *path, int op) +{ + int r; + sc_file_t *prkey = NULL; + r = sc_profile_get_file_by_path(profile, path, &prkey); + if (r != SC_SUCCESS) { + sc_error(card->ctx, "unable to find file in profile"); + return r; + } + + r = sc_pkcs15init_authenticate(profile, card, prkey, op); + sc_file_free(prkey); + if (r != SC_SUCCESS) { + sc_error(card->ctx, "unable to authenticate"); + return r; + } + return SC_SUCCESS; +} + + +#define SET_TLV_LENGTH(p,l) do { \ + if ((l) < 128) \ + *(p)++ = (l) & 0x7f; \ + else if ((l) < 256) { \ + *(p)++ = 0x81; \ + *(p)++ = (l) & 0xff; \ + } else { \ + *(p)++ = 0x82; \ + *(p)++ = ((l) >> 8 ) & 0xff; \ + *(p)++ = (l) & 0xff; \ + } \ + } while(0) + +static int asepcos_do_create_key(sc_card_t *card, size_t ksize, int fileid, + const u8 *keydata, size_t kdlen) +{ + int r; + size_t len; + sc_file_t *nfile = NULL; + u8 buf[512], *p = buf; + + if (sizeof(buf) < kdlen + 11) + return SC_ERROR_BUFFER_TOO_SMALL; + + *p++ = 0x85; + *p++ = 0x82; + p += 2; + /* file id */ + *p++ = (fileid >> 8) && 0xff; + *p++ = fileid & 0xff; + /* key size */ + *p++ = (ksize >> 8) & 0xff; + *p++ = ksize & 0xff; + /* max attempts */ + *p++ = 0x03; + /* key attributes */ + *p++ = 0xc0; + *p++ = 0x80; + *p++ = 0x00; + /* key parts */ + memcpy(p, keydata, kdlen); + p += kdlen; + /* set outer TLV length */ + len = p - buf - 4; + buf[2] = (len >> 8) & 0xff; + buf[3] = len & 0xff; + + nfile = sc_file_new(); + if (nfile == NULL) + return SC_ERROR_OUT_OF_MEMORY; + nfile->type = SC_FILE_TYPE_INTERNAL_EF; + nfile->id = fileid & 0xffff; + r = sc_file_set_prop_attr(nfile, buf, p - buf); + if (r != SC_SUCCESS) { + sc_error(card->ctx, "unable to set key prop. attributes"); + sc_file_free(nfile); + return r; + } + + r = sc_create_file(card, nfile); + sc_file_free(nfile); + if (r != SC_SUCCESS) { + sc_error(card->ctx, "unable to create key file"); + return r; + } + return r; +} + +/* creates a key file + */ +static int asepcos_create_key(sc_profile_t *profile, sc_card_t *card, + sc_pkcs15_object_t *obj) +{ + sc_pkcs15_prkey_info_t *kinfo = (sc_pkcs15_prkey_info_t *) obj->data; + int r, len; + u8 buf[512], *p = buf; + size_t blen = kinfo->modulus_length / 8; + int afileid = -1, + fileid = (kinfo->path.value[kinfo->path.len-2]) << 8 | + kinfo->path.value[kinfo->path.len-1]; + + if (obj->auth_id.len != 0) { + /* the key is proctected by a PIN */ + /* XXX use the pkcs15 structures for this */ + sc_cardctl_asepcos_akn2fileid_t st; + st.akn = sc_keycache_find_named_pin(NULL, SC_PKCS15INIT_USER_PIN); + r = sc_card_ctl(card, SC_CARDCTL_ASEPCOS_AKN2FILEID, &st); + if (r != SC_SUCCESS) { + sc_error(card->ctx, "unable to determine file id of the PIN"); + return r; + } + afileid = st.fileid; + } +#if 0 + /* select the DF */ + r = sc_select_file(card, &profile->df_info->file->path, NULL); + if (r != SC_SUCCESS) + return r; +#endif + + /* authenticate if necessary */ + r = asepcos_do_authenticate(profile, card, &profile->df_info->file->path, SC_AC_OP_CREATE); + if (r != SC_SUCCESS) + return r; + + /* first: create private key (file id = 0x0100 | ) */ + /* key parts */ + *p++ = 0xc1; + *p++ = 0x82; + p += 2; +#if 0 + /* private exponent */ + *p++ = 0x92; + SET_TLV_LENGTH(p, blen); + memset(p, 0xff, blen); + p += blen; +#else + /* public exponent */ + *p++ = 0x90; + SET_TLV_LENGTH(p, 3); + memset(p, 0xff, 3); + p += 3; +#endif + /* primes p, q */ + *p++ = 0x93; + SET_TLV_LENGTH(p, blen); + memset(p, 0xff, blen); + p += blen; + + /* key TLV length */ + len = p - buf - 4; + buf[2] = (len >> 8) & 0xff; + buf[3] = len & 0xff; + + /* security attributes */ + *p++ = 0x80; + *p++ = 0x01; + *p++ = 0xa2; /* compute signature and generate key pair */ + if (afileid > 0) { + *p++ = 0xa0; + *p++ = 0x05; + *p++ = 0x89; + *p++ = 0x03; + *p++ = (afileid >> 16) & 0xff; + *p++ = (afileid >> 8 ) & 0xff; + *p++ = afileid & 0xff; + } else { + *p++ = 0x90; + *p++ = 0x00; + } + + r = asepcos_do_create_key(card, kinfo->modulus_length, fileid, buf, p - buf); + if (r != SC_SUCCESS) { + sc_error(card->ctx, "unable to create private key file"); + return r; + } + return r; +} + +/* stores a rsa private key in a internal EF + */ +static int asepcos_do_store_rsa_key(sc_card_t *card, sc_profile_t *profile, + sc_pkcs15_object_t *obj, sc_pkcs15_prkey_info_t *kinfo, + struct sc_pkcs15_prkey_rsa *key) +{ + int r, klen; + u8 buf[512], *p = buf; + sc_path_t tpath; + sc_cardctl_asepcos_change_key_t ckdata; + + /* authenticate if necessary */ + if (obj->auth_id.len != 0) { + r = asepcos_do_authenticate(profile, card, &kinfo->path, SC_AC_OP_UPDATE); + if (r != SC_SUCCESS) + return r; + } + + /* select the rsa private key */ + tpath.type = SC_PATH_TYPE_FILE_ID; + tpath.len = 2; + tpath.value[0] = kinfo->path.value[kinfo->path.len-2]; + tpath.value[1] = kinfo->path.value[kinfo->path.len-1]; + r = sc_select_file(card, &tpath, NULL); + if (r != SC_SUCCESS) { + sc_error(card->ctx, "unable to select rsa key file"); + return r; + } + + /* store key parts in buffer */ + *p++ = 0xc1; + *p++ = 0x82; + p += 2; +#if 0 + /* private exponent */ + *p++ = 0x92; + SET_TLV_LENGTH(p, key->d.len); + memcpy(p, key->d.data, key->d.len); + p += key->d.len; +#else + /* public exponent */ + *p++ = 0x90; + SET_TLV_LENGTH(p, key->exponent.len); + memcpy(p, key->exponent.data, key->exponent.len); + p += key->exponent.len; +#endif + /* primes p, q */ + *p++ = 0x93; + SET_TLV_LENGTH(p, (key->p.len + key->q.len)); + memcpy(p, key->p.data, key->p.len); + p += key->p.len; + memcpy(p, key->q.data, key->q.len); + p += key->q.len; + + /* key TLV length */ + klen = p - buf - 4; + buf[2] = (klen >> 8) & 0xff; + buf[3] = klen & 0xff; + + ckdata.data = buf; + ckdata.datalen = p - buf; + + r = sc_card_ctl(card, SC_CARDCTL_ASEPCOS_CHANGE_KEY, &ckdata); + if (r != SC_SUCCESS) { + sc_error(card->ctx, "unable to change key data"); + return r; + } + + return SC_SUCCESS; +} + +/* Stores an external (RSA) on the card. + * @param profile profile information for this card + * @param card sc_card_t object to use + * @param obj sc_pkcs15_object_t object with pkcs15 information + * @param key the private key + * @return SC_SUCCESS on success and an error code otherwise + */ +static int asepcos_store_key(sc_profile_t *profile, sc_card_t *card, + sc_pkcs15_object_t *obj, sc_pkcs15_prkey_t *key) +{ + sc_pkcs15_prkey_info_t *kinfo = (sc_pkcs15_prkey_info_t *) obj->data; + + if (obj->type != SC_PKCS15_TYPE_PRKEY_RSA) { + sc_error(card->ctx, "only RSA is currently supported"); + return SC_ERROR_NOT_SUPPORTED; + } + + return asepcos_do_store_rsa_key(card, profile, obj, kinfo, &key->u.rsa); +} + +/* Generates a new (RSA) key pair using an existing key file. + * @param profile IN profile information for this card + * @param card IN sc_card_t object to use + * @param obj IN sc_pkcs15_object_t object with pkcs15 information + * @param pukkey OUT the newly created public key + * @return SC_SUCCESS on success and an error code otherwise + */ +static int asepcos_generate_key(sc_profile_t *profile, sc_card_t *card, + sc_pkcs15_object_t *obj, sc_pkcs15_pubkey_t *pubkey) +{ + int r; + sc_pkcs15_prkey_info_t *kinfo = (sc_pkcs15_prkey_info_t *) obj->data; + sc_apdu_t apdu; + sc_path_t tpath; + u8 rbuf[SC_MAX_APDU_BUFFER_SIZE], + sbuf[SC_MAX_APDU_BUFFER_SIZE]; + + /* authenticate if necessary */ + r = asepcos_do_authenticate(profile, card, &kinfo->path, SC_AC_OP_UPDATE); + if (r != SC_SUCCESS) + return r; + + /* select the rsa private key */ + tpath.type = SC_PATH_TYPE_FILE_ID; + tpath.len = 2; + tpath.value[0] = kinfo->path.value[kinfo->path.len-2]; + tpath.value[1] = kinfo->path.value[kinfo->path.len-1]; + r = sc_select_file(card, &tpath, NULL); + if (r != SC_SUCCESS) { + sc_error(card->ctx, "unable to select rsa key file"); + return r; + } + + sbuf[0] = 0x01; + sbuf[1] = 0x00; + sbuf[2] = 0x01; + + sc_format_apdu(card, &apdu, SC_APDU_CASE_4_SHORT, 0x46, 0x00, 0x00); + apdu.lc = 3; + apdu.datalen = 3; + apdu.data = sbuf; + apdu.le = 256; + apdu.resplen = sizeof(rbuf); + apdu.resp = rbuf; + + r = sc_transmit_apdu(card, &apdu); + SC_TEST_RET(card->ctx, r, "APDU transmit failed"); + if (apdu.sw1 != 0x90 || apdu.sw2 != 0x00) { + sc_error(card->ctx, "error creating key"); + return SC_ERROR_INTERNAL; + } + + pubkey->u.rsa.modulus.len = apdu.resplen; + pubkey->u.rsa.modulus.data = malloc(apdu.resplen); + if (pubkey->u.rsa.modulus.data == NULL) + return SC_ERROR_OUT_OF_MEMORY; + memcpy(pubkey->u.rsa.modulus.data, apdu.resp, apdu.resplen); + + pubkey->u.rsa.exponent.len = 3; + pubkey->u.rsa.exponent.data = malloc(3); + if (pubkey->u.rsa.exponent.data == NULL) + return SC_ERROR_OUT_OF_MEMORY; + memcpy(pubkey->u.rsa.exponent.data, sbuf, 3); + + return SC_SUCCESS; +} + + +static struct sc_pkcs15init_operations sc_pkcs15init_asepcos_operations = { + asepcos_erase, + NULL, /* init_card */ + asepcos_create_dir, + NULL, /* create_domain */ + asepcos_select_pin_reference, + asepcos_create_pin, + NULL, /* select key reference */ + asepcos_create_key, + asepcos_store_key, + asepcos_generate_key, + NULL, NULL, /* encode private/public key */ + NULL, /* finalize_card */ + NULL, NULL, NULL, NULL, NULL, /* old style api */ + NULL /* delete_object */ +}; + +struct sc_pkcs15init_operations * sc_pkcs15init_get_asepcos_ops(void) +{ + return &sc_pkcs15init_asepcos_operations; +} diff --git a/src/pkcs15init/pkcs15-init.h b/src/pkcs15init/pkcs15-init.h index fafbfb1687..7f278b18e3 100644 --- a/src/pkcs15init/pkcs15-init.h +++ b/src/pkcs15init/pkcs15-init.h @@ -400,7 +400,7 @@ extern struct sc_pkcs15init_operations *sc_pkcs15init_get_oberthur_ops(void); extern struct sc_pkcs15init_operations *sc_pkcs15init_get_setcos_ops(void); extern struct sc_pkcs15init_operations *sc_pkcs15init_get_incrypto34_ops(void); extern struct sc_pkcs15init_operations *sc_pkcs15init_get_muscle_ops(void); -extern struct sc_pkcs15init_operations *sc_pkcs15init_get_apcos_ops(void); +extern struct sc_pkcs15init_operations *sc_pkcs15init_get_asepcos_ops(void); #ifdef __cplusplus } diff --git a/src/pkcs15init/pkcs15-lib.c b/src/pkcs15init/pkcs15-lib.c index 38818a60e3..abb3319dbb 100644 --- a/src/pkcs15init/pkcs15-lib.c +++ b/src/pkcs15init/pkcs15-lib.c @@ -161,6 +161,7 @@ static struct profile_operations { { "setcos", (void *) sc_pkcs15init_get_setcos_ops }, { "incrypto34", (void *) sc_pkcs15init_get_incrypto34_ops }, { "muscle", (void*) sc_pkcs15init_get_muscle_ops }, + { "asepcos", (void*) sc_pkcs15init_get_asepcos_ops }, { NULL, NULL }, };