From 7931ded481170704ca047ae5442e5a6b2a299926 Mon Sep 17 00:00:00 2001 From: aj Date: Wed, 24 Jun 2009 15:26:37 +0000 Subject: [PATCH] Add new rutoken_ecp driver by Aktiv Co. / Aleksey Samsonov git-svn-id: https://www.opensc-project.org/svnp/opensc/trunk@3696 c6295689-39f2-0310-b995-f0e70906c6a9 --- src/libopensc/Makefile.am | 3 +- src/libopensc/Makefile.mak | 1 + src/libopensc/card-rtecp.c | 850 +++++++++++++++++++++++++++++ src/libopensc/cardctl.h | 19 + src/libopensc/cards.h | 3 +- src/libopensc/ctx.c | 3 +- src/pkcs15init/Makefile.am | 5 +- src/pkcs15init/Makefile.mak | 4 +- src/pkcs15init/pkcs15-init.h | 1 + src/pkcs15init/pkcs15-lib.c | 1 + src/pkcs15init/pkcs15-rtecp.c | 502 +++++++++++++++++ src/pkcs15init/pkcs15init.exports | 1 + src/pkcs15init/rutoken_ecp.profile | 204 +++++++ 13 files changed, 1590 insertions(+), 7 deletions(-) create mode 100644 src/libopensc/card-rtecp.c create mode 100644 src/pkcs15init/pkcs15-rtecp.c create mode 100644 src/pkcs15init/rutoken_ecp.profile diff --git a/src/libopensc/Makefile.am b/src/libopensc/Makefile.am index c487bb28af..16b4f67f8f 100644 --- a/src/libopensc/Makefile.am +++ b/src/libopensc/Makefile.am @@ -39,7 +39,8 @@ 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-entersafe.c \ card-incrypto34.c card-piv.c card-muscle.c card-acos5.c \ - card-asepcos.c card-akis.c card-gemsafeV1.c card-rutoken.c\ + card-asepcos.c card-akis.c card-gemsafeV1.c card-rutoken.c \ + card-rtecp.c \ \ pkcs15-openpgp.c pkcs15-infocamere.c pkcs15-starcert.c \ pkcs15-tcos.c pkcs15-esteid.c pkcs15-postecert.c pkcs15-gemsafeGPK.c \ diff --git a/src/libopensc/Makefile.mak b/src/libopensc/Makefile.mak index 425ac12946..5acccce66a 100644 --- a/src/libopensc/Makefile.mak +++ b/src/libopensc/Makefile.mak @@ -28,6 +28,7 @@ OBJECTS = \ card-oberthur.obj card-belpic.obj card-atrust-acos.obj card-entersafe.obj \ card-incrypto34.obj card-piv.obj card-muscle.obj card-acos5.obj \ card-asepcos.obj card-akis.obj card-gemsafeV1.obj card-rutoken.obj \ + card-rtecp.obj \ \ pkcs15-openpgp.obj pkcs15-infocamere.obj pkcs15-starcert.obj \ pkcs15-tcos.obj pkcs15-esteid.obj pkcs15-postecert.obj pkcs15-gemsafeGPK.obj \ diff --git a/src/libopensc/card-rtecp.c b/src/libopensc/card-rtecp.c new file mode 100644 index 0000000000..7e9d32d00e --- /dev/null +++ b/src/libopensc/card-rtecp.c @@ -0,0 +1,850 @@ +/* + * card-rtecp.c: Support for Rutoken ECP cards + * + * Copyright (C) 2009 Aleksey Samsonov + * + * 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 +#include +#include "cardctl.h" +#include "asn1.h" + +static const struct sc_card_operations *iso_ops = NULL; +static struct sc_card_operations rtecp_ops; + +static struct sc_card_driver rtecp_drv = { + "Rutoken ECP driver", + "rutoken_ecp", + &rtecp_ops, + NULL, 0, NULL +}; + +static struct sc_atr_table rtecp_atrs[] = { + /* Rutoken ECP */ + { "3B:8B:01:52:75:74:6F:6B:65:6E:20:45:43:50:A0", + NULL, NULL, SC_CARD_TYPE_GENERIC_BASE, 0, NULL }, + { NULL, NULL, NULL, 0, 0, NULL } +}; + +static int rtecp_match_card(sc_card_t *card) +{ + assert(card && card->ctx); + if (_sc_match_atr(card, rtecp_atrs, &card->type) >= 0) + SC_FUNC_RETURN(card->ctx, 1, 1); + SC_FUNC_RETURN(card->ctx, 1, 0); +} + +static int rtecp_init(sc_card_t *card) +{ + unsigned long flags; + + assert(card && card->ctx); + card->name = "Rutoken ECP card"; + card->caps |= SC_CARD_CAP_RSA_2048 | SC_CARD_CAP_NO_FCI | SC_CARD_CAP_RNG; + card->cla = 0; + + flags = SC_ALGORITHM_RSA_RAW | SC_ALGORITHM_ONBOARD_KEY_GEN + | SC_ALGORITHM_RSA_PAD_NONE | SC_ALGORITHM_RSA_HASH_NONE; + + _sc_card_add_rsa_alg(card, 256, flags, 0); + _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, 1280, 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); + + SC_FUNC_RETURN(card->ctx, 2, 0); +} + +static void reverse(unsigned char *buf, size_t len) +{ + unsigned char tmp; + size_t i; + + assert(buf || len == 0); + for (i = 0; i < len / 2; ++i) + { + tmp = buf[i]; + buf[i] = buf[len - 1 - i]; + buf[len - 1 - i] = tmp; + } +} + +static unsigned int sec_attr_to_method(unsigned int attr) +{ + if (attr == 0xFF) + return SC_AC_NEVER; + else if (attr == 0) + return SC_AC_NONE; + else if (attr & 0x03) + return SC_AC_CHV; + else + return SC_AC_UNKNOWN; +} + +static unsigned long sec_attr_to_key_ref(unsigned int attr) +{ + if (attr == 1 || attr == 2) + return attr; + return 0; +} + +static unsigned int to_sec_attr(unsigned int method, unsigned int key_ref) +{ + if (method == SC_AC_NEVER || method == SC_AC_NONE) + return method; + if (method == SC_AC_CHV && (key_ref == 1 || key_ref == 2)) + return key_ref; + return 0; +} + +static void set_acl_from_sec_attr(sc_card_t *card, sc_file_t *file) +{ + unsigned int method; + unsigned long key_ref; + + assert(card && card->ctx && file); + assert(file->sec_attr && file->sec_attr_len == SEC_ATTR_SIZE); + assert(1 + 6 < SEC_ATTR_SIZE); + + sc_file_add_acl_entry(file, SC_AC_OP_SELECT, SC_AC_NONE, SC_AC_KEY_REF_NONE); + if (file->sec_attr[0] & 0x40) /* if AccessMode.6 */ + { + method = sec_attr_to_method(file->sec_attr[1 + 6]); + key_ref = sec_attr_to_key_ref(file->sec_attr[1 + 6]); + if (card->ctx->debug >= 3) + sc_debug(card->ctx, "SC_AC_OP_DELETE %i %lu\n", + (int)method, key_ref); + sc_file_add_acl_entry(file, SC_AC_OP_DELETE, method, key_ref); + } + if (file->sec_attr[0] & 0x01) /* if AccessMode.0 */ + { + method = sec_attr_to_method(file->sec_attr[1 + 0]); + key_ref = sec_attr_to_key_ref(file->sec_attr[1 + 0]); + if (card->ctx->debug >= 3) + sc_debug(card->ctx, (file->type == SC_FILE_TYPE_DF) ? + "SC_AC_OP_CREATE %i %lu\n" + : "SC_AC_OP_READ %i %lu\n", + (int)method, key_ref); + sc_file_add_acl_entry(file, (file->type == SC_FILE_TYPE_DF) ? + SC_AC_OP_CREATE : SC_AC_OP_READ, method, key_ref); + } + if (file->type == SC_FILE_TYPE_DF) + { + sc_file_add_acl_entry(file, SC_AC_OP_LIST_FILES, + SC_AC_NONE, SC_AC_KEY_REF_NONE); + } + else + if (file->sec_attr[0] & 0x02) /* if AccessMode.1 */ + { + method = sec_attr_to_method(file->sec_attr[1 + 1]); + key_ref = sec_attr_to_key_ref(file->sec_attr[1 + 1]); + if (card->ctx->debug >= 3) + sc_debug(card->ctx, "SC_AC_OP_UPDATE %i %lu\n", + (int)method, key_ref); + sc_file_add_acl_entry(file, SC_AC_OP_UPDATE, method, key_ref); + if (card->ctx->debug >= 3) + sc_debug(card->ctx, "SC_AC_OP_WRITE %i %lu\n", + (int)method, key_ref); + sc_file_add_acl_entry(file, SC_AC_OP_WRITE, method, key_ref); + } +} + +static int set_sec_attr_from_acl(sc_card_t *card, sc_file_t *file) +{ + const sc_acl_entry_t *entry; + u8 sec_attr[SEC_ATTR_SIZE] = { 0 }; + int r; + + assert(card && card->ctx && file); + assert(!file->sec_attr && file->sec_attr_len == 0); + assert(1 + 6 < sizeof(sec_attr)); + + entry = sc_file_get_acl_entry(file, SC_AC_OP_DELETE); + if (entry) + { + sec_attr[0] |= 0x40; + sec_attr[1 + 6] = to_sec_attr(entry->method, entry->key_ref); + } + if (file->type == SC_FILE_TYPE_DF) + { + entry = sc_file_get_acl_entry(file, SC_AC_OP_CREATE); + if (entry) + { + /* ATTR: Create DF/EF file */ + sec_attr[0] |= 0x01; + sec_attr[1 + 0] = to_sec_attr(entry->method, entry->key_ref); + /* ATTR: Create Internal EF (RSF) file */ + sec_attr[0] |= 0x02; + sec_attr[1 + 1] = to_sec_attr(entry->method, entry->key_ref); + } + } + else + { + entry = sc_file_get_acl_entry(file, SC_AC_OP_READ); + if (entry) + { + sec_attr[0] |= 0x01; + sec_attr[1 + 0] = to_sec_attr(entry->method, entry->key_ref); + } + entry = sc_file_get_acl_entry(file, SC_AC_OP_WRITE); + if (entry) + { + sec_attr[0] |= 0x02; + sec_attr[1 + 1] = to_sec_attr(entry->method, entry->key_ref); + } + entry = sc_file_get_acl_entry(file, SC_AC_OP_UPDATE); + if (entry) + { + /* rewrite if sec_attr[1 + 1] already set */ + sec_attr[0] |= 0x02; + sec_attr[1 + 1] = to_sec_attr(entry->method, entry->key_ref); + } + } + /* FIXME: Find the best solution */ + if (file->path.len == 2 && !memcmp(file->path.value, "\x3F\x00", 2)) + { + /* ATTR: Put data */ + sec_attr[0] |= 0x04; + sec_attr[1 + 2] = 1; /* so-pin reference */ + } + r = sc_file_set_sec_attr(file, sec_attr, sizeof(sec_attr)); + SC_FUNC_RETURN(card->ctx, 3, r); +} + +static int rtecp_select_file(sc_card_t *card, + const sc_path_t *in_path, sc_file_t **file_out) +{ + sc_apdu_t apdu; + u8 buf[SC_MAX_APDU_BUFFER_SIZE], pathbuf[SC_MAX_PATH_SIZE], *path = pathbuf; + sc_file_t *file = NULL; + size_t pathlen; + int r; + + assert(card && card->ctx && in_path); + assert(sizeof(pathbuf) >= in_path->len); + memcpy(path, in_path->value, in_path->len); + pathlen = in_path->len; + + /* p2 = 0; first record, return FCI */ + sc_format_apdu(card, &apdu, SC_APDU_CASE_4_SHORT, 0xA4, 0, 0); + switch (in_path->type) + { + case SC_PATH_TYPE_FILE_ID: + apdu.p1 = 0; + if (pathlen != 2) + SC_FUNC_RETURN(card->ctx, 1, SC_ERROR_INVALID_ARGUMENTS); + break; + case SC_PATH_TYPE_PATH: + apdu.p1 = 0x08; + if (pathlen >= 2 && memcmp(path, "\x3F\x00", 2) == 0) + { + if (pathlen == 2) + { + /* only 3F00 supplied */ + apdu.p1 = 0; + break; + } + path += 2; + pathlen -= 2; + } + break; + case SC_PATH_TYPE_DF_NAME: + case SC_PATH_TYPE_FROM_CURRENT: + case SC_PATH_TYPE_PARENT: + SC_FUNC_RETURN(card->ctx, 1, SC_ERROR_NOT_SUPPORTED); + default: + SC_FUNC_RETURN(card->ctx, 1, SC_ERROR_INVALID_ARGUMENTS); + } + apdu.lc = pathlen; + apdu.data = path; + apdu.datalen = pathlen; + + if (file_out != NULL) + { + apdu.resp = buf; + apdu.resplen = sizeof(buf); + apdu.le = sizeof(buf) - 2; + } + else + { + apdu.resplen = 0; + apdu.le = 0; + apdu.cse = SC_APDU_CASE_3_SHORT; + } + r = sc_transmit_apdu(card, &apdu); + SC_TEST_RET(card->ctx, r, "APDU transmit failed"); + if (file_out == NULL) + { + if (apdu.sw1 == 0x61) + SC_FUNC_RETURN(card->ctx, 2, 0); + SC_FUNC_RETURN(card->ctx, 2, sc_check_sw(card, apdu.sw1, apdu.sw2)); + } + r = sc_check_sw(card, apdu.sw1, apdu.sw2); + SC_TEST_RET(card->ctx, r, ""); + + if (apdu.resplen > 0 && apdu.resp[0] != 0x6F) /* Tag 0x6F - FCI */ + SC_FUNC_RETURN(card->ctx, 1, SC_ERROR_UNKNOWN_DATA_RECEIVED); + + file = sc_file_new(); + if (file == NULL) + SC_FUNC_RETURN(card->ctx, 0, SC_ERROR_OUT_OF_MEMORY); + file->path = *in_path; + if (card->ops->process_fci == NULL) + { + sc_file_free(file); + SC_FUNC_RETURN(card->ctx, 1, SC_ERROR_NOT_SUPPORTED); + } + if (apdu.resplen > 1 && apdu.resplen >= (size_t)apdu.resp[1] + 2) + r = card->ops->process_fci(card, file, apdu.resp+2, apdu.resp[1]); + if (file->sec_attr && file->sec_attr_len == SEC_ATTR_SIZE) + set_acl_from_sec_attr(card, file); + else + r = SC_ERROR_UNKNOWN_DATA_RECEIVED; + if (r) + sc_file_free(file); + else + { + assert(file_out); + *file_out = file; + } + SC_FUNC_RETURN(card->ctx, 2, r); +} + +static int rtecp_verify(sc_card_t *card, unsigned int type, int ref_qualifier, + const u8 *data, size_t data_len, int *tries_left) +{ + sc_apdu_t apdu; + int r, send_logout = 0; + + (void)type; /* no warning */ + assert(card && card->ctx && data); + for (;;) + { + sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, + 0x20, 0, ref_qualifier); + apdu.lc = data_len; + apdu.data = data; + apdu.datalen = data_len; + apdu.sensitive = 1; + r = sc_transmit_apdu(card, &apdu); + SC_TEST_RET(card->ctx, r, "APDU transmit failed"); + if (send_logout++ == 0 && apdu.sw1 == 0x6F && apdu.sw2 == 0x86) + { + r = sc_logout(card); + SC_TEST_RET(card->ctx, r, "Logout failed"); + } + else + break; + } + if (apdu.sw1 == 0x63 && apdu.sw2 == 0) + { + /* Verification failed */ + sc_format_apdu(card, &apdu, SC_APDU_CASE_1, 0x20, 0, ref_qualifier); + r = sc_transmit_apdu(card, &apdu); + SC_TEST_RET(card->ctx, r, "APDU transmit failed"); + } + r = sc_check_sw(card, apdu.sw1, apdu.sw2); + if (r == SC_ERROR_PIN_CODE_INCORRECT && tries_left) + *tries_left = (int)(apdu.sw2 & 0x0F); + SC_FUNC_RETURN(card->ctx, 2, r); +} + +static int rtecp_logout(sc_card_t *card) +{ + sc_apdu_t apdu; + int r; + + assert(card && card->ctx); + sc_format_apdu(card, &apdu, SC_APDU_CASE_1, 0x40, 0, 0); + apdu.cla = 0x80; + r = sc_transmit_apdu(card, &apdu); + SC_TEST_RET(card->ctx, r, "APDU transmit failed"); + r = sc_check_sw(card, apdu.sw1, apdu.sw2); + SC_FUNC_RETURN(card->ctx, 2, r); +} + +static int rtecp_set_security_env(sc_card_t *card, const sc_security_env_t *env, + int se_num) +{ + sc_apdu_t apdu; + u8 buf[8], tmp, *p = buf; + int r; + + (void)se_num; /* no warning */ + assert(card && card->ctx && env); + sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, 0x22, 0x41, 0); + switch (env->operation) + { + case SC_SEC_OPERATION_DECIPHER: + apdu.p2 = 0xB8; + break; + case SC_SEC_OPERATION_SIGN: + apdu.p2 = 0xB6; + break; + default: + SC_FUNC_RETURN(card->ctx, 1, SC_ERROR_INVALID_ARGUMENTS); + } + if (env->flags & SC_SEC_ENV_ALG_REF_PRESENT) + { + tmp = env->algorithm_ref & 0xFF; + sc_asn1_put_tag(0x80, &tmp, sizeof(tmp), p, sizeof(buf) - (p - buf), &p); + } + if (env->flags & SC_SEC_ENV_FILE_REF_PRESENT && card->ctx->debug >= 4) + sc_debug(card->ctx, "%s\n", "SC_SEC_ENV_FILE_REF_PRESENT not supported"); + if (env->flags & SC_SEC_ENV_KEY_REF_PRESENT) + sc_asn1_put_tag(env->flags & SC_SEC_ENV_KEY_REF_ASYMMETRIC ? 0x83 : 0x84, + env->key_ref, env->key_ref_len, + p, sizeof(buf) - (p - buf), &p); + + apdu.lc = p - buf; + apdu.data = buf; + apdu.datalen = p - buf; + + r = sc_transmit_apdu(card, &apdu); + SC_TEST_RET(card->ctx, r, "APDU transmit failed"); + r = sc_check_sw(card, apdu.sw1, apdu.sw2); + SC_FUNC_RETURN(card->ctx, 2, r); +} + +static int rtecp_rsa_cipher(sc_card_t *card, const u8 *data, size_t data_len, + u8 *out, size_t out_len, int sign) +{ + sc_apdu_t apdu; + u8 *buf, *buf_out; + size_t i; + int r; + + assert(card && card->ctx && data && out); + buf_out = malloc(data_len + 2); + buf = malloc(data_len); + if (!buf || !buf_out) + { + free(buf); + free(buf_out); + SC_FUNC_RETURN(card->ctx, 0, SC_ERROR_OUT_OF_MEMORY); + } + + for (i = 0; i < data_len; ++i) + buf[i] = data[data_len - 1 - i]; + + if (sign) + sc_format_apdu(card, &apdu, SC_APDU_CASE_4_SHORT, 0x2A, 0x9E, 0x9A); + else + sc_format_apdu(card, &apdu, SC_APDU_CASE_4_SHORT, 0x2A, 0x80, 0x86); + apdu.lc = data_len; + apdu.data = buf; + apdu.datalen = data_len; + apdu.sensitive = 1; + apdu.resp = buf_out; + apdu.resplen = data_len + 2; + apdu.le = data_len; + if (apdu.lc > 255) + apdu.flags |= SC_APDU_FLAGS_CHAINING; + r = sc_transmit_apdu(card, &apdu); + if (!sign) + { + assert(buf); + sc_mem_clear(buf, data_len); + } + assert(buf); + free(buf); + if (r) + sc_error(card->ctx, "APDU transmit failed: %s\n", sc_strerror(r)); + else + { + if (apdu.sw1 == 0x90 && apdu.sw2 == 0x00) + { + assert(buf_out); + for (i = 0; i < out_len && i < data_len && i < apdu.resplen; ++i) + out[i] = buf_out[data_len - 1 - i]; + r = (i > 0) ? (int)i : SC_ERROR_INTERNAL; + } + else + r = sc_check_sw(card, apdu.sw1, apdu.sw2); + } + if (!sign) + { + assert(buf_out); + sc_mem_clear(buf_out, data_len + 2); + } + assert(buf_out); + free(buf_out); + SC_FUNC_RETURN(card->ctx, 2, r); + +} + +static int rtecp_decipher(sc_card_t *card, + const u8 *data, size_t data_len, u8 *out, size_t out_len) +{ + int r; + + assert(card && card->ctx && data && out); + /* decipher */ + r = rtecp_rsa_cipher(card, data, data_len, out, out_len, 0); + SC_FUNC_RETURN(card->ctx, 3, r); +} + +static int rtecp_compute_signature(sc_card_t *card, + const u8 *data, size_t data_len, u8 *out, size_t out_len) +{ + int r; + + assert(card && card->ctx && data && out); + /* compute digital signature */ + r = rtecp_rsa_cipher(card, data, data_len, out, out_len, 1); + SC_FUNC_RETURN(card->ctx, 2, r); +} + +static int rtecp_change_reference_data(sc_card_t *card, unsigned int type, + int ref_qualifier, const u8 *old, size_t oldlen, + const u8 *newref, size_t newlen, int *tries_left) +{ + sc_apdu_t apdu; + u8 tmp[2], buf[0x1000], *p = buf; + size_t taglen; + int r; + + assert(card && card->ctx && newref); + if (card->ctx->debug >= 3) + sc_debug(card->ctx, "newlen = %u\n", newlen); + if (newlen > sizeof(buf) - 2 - sizeof(tmp) - 2 * (sizeof(buf) / 0xFF + 1)) + SC_FUNC_RETURN(card->ctx, 1, SC_ERROR_INVALID_ARGUMENTS); + + if (type == SC_AC_CHV && old && oldlen != 0) + { + r = sc_verify(card, type, ref_qualifier, old, oldlen, tries_left); + SC_TEST_RET(card->ctx, r, "Verify old pin failed"); + } + sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, 0x24, 0x01, ref_qualifier); + tmp[0] = (newlen >> 8) & 0xFF; + tmp[1] = newlen & 0xFF; + sc_asn1_put_tag(0x80, tmp, sizeof(tmp), p, sizeof(buf) - (p - buf), &p); + r = sc_asn1_put_tag(0xA5, newref, newlen, p, sizeof(buf) - (p - buf), &p); + if (r == SC_ERROR_INVALID_ARGUMENTS) + { + while (newlen) + { + assert(sizeof(buf) - (p - buf) >= newlen + 2); + assert((p - buf) % 0xFF < 0x80); + if ((p - buf) % 0xFF % 0x80 + newlen + 2 > 0xFF) + taglen = 0xFF - 2 - (p - buf) % 0xFF % 0x80; + else + taglen = newlen; + *p++ = 0xA5; + *p++ = (u8)taglen; + assert(taglen <= newlen); + memcpy(p, newref, taglen); + p += taglen; + newref += taglen; + newlen -= taglen; + if (newlen) + apdu.flags |= SC_APDU_FLAGS_CHAINING; + } + } + apdu.lc = p - buf; + apdu.data = buf; + apdu.datalen = p - buf; + apdu.sensitive = 1; + + r = sc_transmit_apdu(card, &apdu); + sc_mem_clear(buf, sizeof(buf)); + SC_TEST_RET(card->ctx, r, "APDU transmit failed"); + r = sc_check_sw(card, apdu.sw1, apdu.sw2); + SC_FUNC_RETURN(card->ctx, 2, r); +} + +static int rtecp_reset_retry_counter(sc_card_t *card, unsigned int type, + int ref_qualifier, const u8 *puk, size_t puklen, + const u8 *newref, size_t newlen) +{ + sc_apdu_t apdu; + int r; + + (void)type, (void)puk, (void)puklen, (void)newref, (void)newlen; /* no warning */ + assert(card && card->ctx); + sc_format_apdu(card, &apdu, SC_APDU_CASE_1, 0x2C, 0x03, ref_qualifier); + r = sc_transmit_apdu(card, &apdu); + SC_TEST_RET(card->ctx, r, "APDU transmit failed"); + r = sc_check_sw(card, apdu.sw1, apdu.sw2); + SC_FUNC_RETURN(card->ctx, 2, r); +} + +static int rtecp_create_file(sc_card_t *card, sc_file_t *file) +{ + int r; + + assert(card && card->ctx && file); + if (file->sec_attr_len == 0) + { + r = set_sec_attr_from_acl(card, file); + SC_TEST_RET(card->ctx, r, "Set sec_attr from ACL failed"); + } + assert(iso_ops && iso_ops->create_file); + r = iso_ops->create_file(card, file); + SC_FUNC_RETURN(card->ctx, 2, r); +} + +static int rtecp_list_files(sc_card_t *card, u8 *buf, size_t buflen) +{ + sc_apdu_t apdu; + u8 rbuf[SC_MAX_APDU_BUFFER_SIZE], previd[2]; + const u8 *tag; + size_t taglen, len = 0; + int r; + + assert(card && card->ctx && buf); + sc_format_apdu(card, &apdu, SC_APDU_CASE_2_SHORT, 0xA4, 0, 0); + for (;;) + { + apdu.resp = rbuf; + apdu.resplen = sizeof(rbuf); + apdu.le = sizeof(rbuf) - 2; + r = sc_transmit_apdu(card, &apdu); + SC_TEST_RET(card->ctx, r, "APDU transmit failed"); + if (apdu.sw1 == 0x6A && apdu.sw2 == 0x82) + break; /* Next file not found */ + + r = sc_check_sw(card, apdu.sw1, apdu.sw2); + SC_TEST_RET(card->ctx, r, ""); + + if (apdu.resplen <= 2) + SC_FUNC_RETURN(card->ctx, 1, SC_ERROR_WRONG_LENGTH); + + /* save first file(dir) ID */ + tag = sc_asn1_find_tag(card->ctx, apdu.resp + 2, apdu.resplen - 2, + 0x83, &taglen); + if (!tag || taglen != sizeof(previd)) + SC_FUNC_RETURN(card->ctx, 1, SC_ERROR_UNKNOWN_DATA_RECEIVED); + memcpy(previd, tag, sizeof(previd)); + + if (len + sizeof(previd) <= buflen) + { + memcpy(&buf[len], previd, sizeof(previd)); + len += sizeof(previd); + } + + tag = sc_asn1_find_tag(card->ctx, apdu.resp + 2, apdu.resplen - 2, + 0x82, &taglen); + if (!tag || taglen != 2) + SC_FUNC_RETURN(card->ctx, 1, SC_ERROR_UNKNOWN_DATA_RECEIVED); + if (tag[0] == 0x38) + { + /* Select parent DF of the current DF */ + sc_format_apdu(card, &apdu, SC_APDU_CASE_1, 0xA4, 0x03, 0); + r = sc_transmit_apdu(card, &apdu); + SC_TEST_RET(card->ctx, r, "APDU transmit failed"); + r = sc_check_sw(card, apdu.sw1, apdu.sw2); + SC_TEST_RET(card->ctx, r, ""); + } + sc_format_apdu(card, &apdu, SC_APDU_CASE_4_SHORT, 0xA4, 0, 0x02); + apdu.lc = sizeof(previd); + apdu.data = previd; + apdu.datalen = sizeof(previd); + } + SC_FUNC_RETURN(card->ctx, 2, len); +} + +static int rtecp_card_ctl(sc_card_t *card, unsigned long request, void *data) +{ + sc_apdu_t apdu; + u8 buf[SC_MAX_APDU_BUFFER_SIZE]; + sc_rtecp_genkey_data_t *genkey_data = data; + sc_serial_number_t *serial = data; + int r; + + assert(card && card->ctx); + switch (request) + { + case SC_CARDCTL_RTECP_INIT: + sc_format_apdu(card, &apdu, SC_APDU_CASE_1, 0x8A, 0, 0); + apdu.cla = 0x80; + break; + case SC_CARDCTL_RTECP_INIT_END: + sc_format_apdu(card, &apdu, SC_APDU_CASE_1, 0x84, 0x4E, 0x19); + apdu.cla = 0x80; + break; + case SC_CARDCTL_GET_SERIALNR: + if (!serial) + SC_FUNC_RETURN(card->ctx, 1, SC_ERROR_INVALID_ARGUMENTS); + sc_format_apdu(card, &apdu, SC_APDU_CASE_2_SHORT, 0xCA, 0x01, 0x81); + apdu.resp = buf; + apdu.resplen = sizeof(buf); + apdu.le = sizeof(buf) - 2; + break; + case SC_CARDCTL_RTECP_GENERATE_KEY: + if (!genkey_data) + SC_FUNC_RETURN(card->ctx, 1, SC_ERROR_INVALID_ARGUMENTS); + sc_format_apdu(card, &apdu, SC_APDU_CASE_2_SHORT, 0x46, 0x80, + genkey_data->key_id); + apdu.resp = buf; + apdu.resplen = sizeof(buf); + apdu.le = sizeof(buf) - 2; + break; + case SC_CARDCTL_LIFECYCLE_SET: + if (card->ctx->debug >= 4) + sc_debug(card->ctx, "%s\n", + "SC_CARDCTL_LIFECYCLE_SET not supported"); + /* no call sc_error (SC_FUNC_RETURN) */ + return SC_ERROR_NOT_SUPPORTED; + default: + if (card->ctx->debug >= 3) + sc_debug(card->ctx, "request = 0x%lx\n", request); + SC_FUNC_RETURN(card->ctx, 1, SC_ERROR_NOT_SUPPORTED); + } + r = sc_transmit_apdu(card, &apdu); + SC_TEST_RET(card->ctx, r, "APDU transmit failed"); + r = sc_check_sw(card, apdu.sw1, apdu.sw2); + if (!r && request == SC_CARDCTL_RTECP_GENERATE_KEY) + { + if (genkey_data->modulus_len >= apdu.resplen && + genkey_data->exponent_len >= 3) + { + memcpy(genkey_data->modulus, apdu.resp, apdu.resplen); + genkey_data->modulus_len = apdu.resplen; + reverse(genkey_data->modulus, genkey_data->modulus_len); + memcpy(genkey_data->exponent, "\x01\x00\x01", 3); + genkey_data->exponent_len = 3; + } + else + r = SC_ERROR_BUFFER_TOO_SMALL; + } + else if (!r && request == SC_CARDCTL_GET_SERIALNR) + { + if (serial->len >= apdu.resplen) + { + memcpy(serial->value, apdu.resp, apdu.resplen); + serial->len = apdu.resplen; + } + else + r = SC_ERROR_BUFFER_TOO_SMALL; + } + SC_FUNC_RETURN(card->ctx, 2, r); +} + +static int rtecp_construct_fci(sc_card_t *card, const sc_file_t *file, + u8 *out, size_t *outlen) +{ + u8 buf[64], *p = out; + + assert(card && card->ctx && file && out && outlen); + assert(*outlen >= (size_t)(p - out) + 2); + *p++ = 0x6F; /* FCI template */ + p++; /* for length */ + + /* 0x80 - Number of data bytes in the file, excluding structural information */ + buf[0] = (file->size >> 8) & 0xFF; + buf[1] = file->size & 0xFF; + sc_asn1_put_tag(0x80, buf, 2, p, *outlen - (p - out), &p); + + /* 0x82 - File descriptor byte */ + if (file->type_attr_len) + { + assert(sizeof(buf) >= file->type_attr_len); + memcpy(buf, file->type_attr, file->type_attr_len); + sc_asn1_put_tag(0x82, buf, file->type_attr_len, + p, *outlen - (p - out), &p); + } + else + { + switch (file->type) + { + case SC_FILE_TYPE_WORKING_EF: + buf[0] = 0x01; + break; + case SC_FILE_TYPE_DF: + buf[0] = 0x38; + break; + case SC_FILE_TYPE_INTERNAL_EF: + default: + SC_FUNC_RETURN(card->ctx, 1, SC_ERROR_NOT_SUPPORTED); + } + buf[1] = 0; + sc_asn1_put_tag(0x82, buf, 2, p, *outlen - (p - out), &p); + } + /* 0x83 - File identifier */ + buf[0] = (file->id >> 8) & 0xFF; + buf[1] = file->id & 0xFF; + sc_asn1_put_tag(0x83, buf, 2, p, *outlen - (p - out), &p); + + if (file->prop_attr_len) + { + assert(sizeof(buf) >= file->prop_attr_len); + memcpy(buf, file->prop_attr, file->prop_attr_len); + sc_asn1_put_tag(0x85, buf, file->prop_attr_len, + p, *outlen - (p - out), &p); + } + if (file->sec_attr_len) + { + assert(sizeof(buf) >= file->sec_attr_len); + memcpy(buf, file->sec_attr, file->sec_attr_len); + sc_asn1_put_tag(0x86, buf, file->sec_attr_len, + p, *outlen - (p - out), &p); + } + out[1] = p - out - 2; /* length */ + *outlen = p - out; + SC_FUNC_RETURN(card->ctx, 2, 0); +} + +struct sc_card_driver * sc_get_rtecp_driver(void) +{ + if (iso_ops == NULL) + iso_ops = sc_get_iso7816_driver()->ops; + rtecp_ops = *iso_ops; + + rtecp_ops.match_card = rtecp_match_card; + rtecp_ops.init = rtecp_init; + rtecp_ops.finish = NULL; + /* read_binary */ + rtecp_ops.write_binary = NULL; + /* update_binary */ + rtecp_ops.erase_binary = NULL; + rtecp_ops.read_record = NULL; + rtecp_ops.write_record = NULL; + rtecp_ops.append_record = NULL; + rtecp_ops.update_record = NULL; + rtecp_ops.select_file = rtecp_select_file; + rtecp_ops.get_response = NULL; + /* get_challenge */ + rtecp_ops.verify = rtecp_verify; + rtecp_ops.logout = rtecp_logout; + /* restore_security_env */ + rtecp_ops.set_security_env = rtecp_set_security_env; + rtecp_ops.decipher = rtecp_decipher; + rtecp_ops.compute_signature = rtecp_compute_signature; + rtecp_ops.change_reference_data = rtecp_change_reference_data; + rtecp_ops.reset_retry_counter = rtecp_reset_retry_counter; + rtecp_ops.create_file = rtecp_create_file; + /* delete_file */ + rtecp_ops.list_files = rtecp_list_files; + /* check_sw */ + rtecp_ops.card_ctl = rtecp_card_ctl; + /* process_fci */ + rtecp_ops.construct_fci = rtecp_construct_fci; + rtecp_ops.pin_cmd = NULL; + rtecp_ops.get_data = NULL; + rtecp_ops.put_data = NULL; + rtecp_ops.delete_record = NULL; + + return &rtecp_drv; +} + diff --git a/src/libopensc/cardctl.h b/src/libopensc/cardctl.h index 5de1cb24ee..9483f773eb 100644 --- a/src/libopensc/cardctl.h +++ b/src/libopensc/cardctl.h @@ -167,6 +167,14 @@ enum { SC_CARDCTL_ENTERSAFE_WRITE_KEY, SC_CARDCTL_ENTERSAFE_GENERATE_KEY, SC_CARDCTL_ENTERSAFE_PREINSTALL_KEYS, + + /* + * Rutoken ECP specific calls + */ + SC_CARDCTL_RTECP_BASE = _CTL_PREFIX('R', 'T', 'E'), + SC_CARDCTL_RTECP_INIT, + SC_CARDCTL_RTECP_INIT_END, + SC_CARDCTL_RTECP_GENERATE_KEY, }; enum { @@ -642,6 +650,17 @@ typedef struct sc_entersafe_gen_key_data_st { #pragma pack(pop) +/* + * Rutoken ECP stuff + */ +typedef struct sc_rtecp_genkey_data { + unsigned int key_id; + unsigned char *exponent; + size_t exponent_len; + unsigned char *modulus; + size_t modulus_len; +} sc_rtecp_genkey_data_t; + #ifdef __cplusplus } #endif diff --git a/src/libopensc/cards.h b/src/libopensc/cards.h index 7af7bd77f2..120cfee82c 100644 --- a/src/libopensc/cards.h +++ b/src/libopensc/cards.h @@ -147,7 +147,6 @@ enum { SC_CARD_TYPE_ENTERSAFE_FTCOS_PK_01C, }; -extern sc_card_driver_t *sc_get_rutoken_driver(void); extern sc_card_driver_t *sc_get_default_driver(void); extern sc_card_driver_t *sc_get_emv_driver(void); extern sc_card_driver_t *sc_get_cardos_driver(void); @@ -172,6 +171,8 @@ extern sc_card_driver_t *sc_get_acos5_driver(void); extern sc_card_driver_t *sc_get_asepcos_driver(void); extern sc_card_driver_t *sc_get_akis_driver(void); extern sc_card_driver_t *sc_get_entersafe_driver(void); +extern sc_card_driver_t *sc_get_rutoken_driver(void); +extern sc_card_driver_t *sc_get_rtecp_driver(void); #ifdef __cplusplus } diff --git a/src/libopensc/ctx.c b/src/libopensc/ctx.c index b545acf184..2c5202bf8f 100644 --- a/src/libopensc/ctx.c +++ b/src/libopensc/ctx.c @@ -51,7 +51,6 @@ struct _sc_driver_entry { static const struct _sc_driver_entry internal_card_drivers[] = { /* legacy, the old name was "etoken", so we keep that for a while */ - { "rutoken", (void *(*)(void)) sc_get_rutoken_driver }, { "cardos", (void *(*)(void)) sc_get_cardos_driver }, { "etoken", (void *(*)(void)) sc_get_cardos_driver }, { "flex", (void *(*)(void)) sc_get_cryptoflex_driver }, @@ -84,6 +83,8 @@ static const struct _sc_driver_entry internal_card_drivers[] = { #ifdef ENABLE_OPENSSL { "entersafe",(void *(*)(void)) sc_get_entersafe_driver }, #endif + { "rutoken", (void *(*)(void)) sc_get_rutoken_driver }, + { "rutoken_ecp",(void *(*)(void)) sc_get_rtecp_driver }, /* The default driver should be last, as it handles all the * unrecognized cards. */ { "default", (void *(*)(void)) sc_get_default_driver }, diff --git a/src/pkcs15init/Makefile.am b/src/pkcs15init/Makefile.am index a01d8129c1..5ffdbba6ac 100644 --- a/src/pkcs15init/Makefile.am +++ b/src/pkcs15init/Makefile.am @@ -23,7 +23,8 @@ dist_pkgdata_DATA = \ muscle.profile \ rutoken.profile \ asepcos.profile \ - entersafe.profile + entersafe.profile \ + rutoken_ecp.profile AM_CPPFLAGS = -DSC_PKCS15_PROFILE_DIRECTORY=\"$(pkgdatadir)\" AM_CFLAGS = $(OPTIONAL_OPENSSL_CFLAGS) $(LTLIB_CFLAGS) @@ -35,7 +36,7 @@ libpkcs15init_la_SOURCES = \ pkcs15-cardos.c pkcs15-jcop.c pkcs15-starcos.c \ pkcs15-oberthur.c pkcs15-setcos.c pkcs15-incrypto34.c \ pkcs15-muscle.c pkcs15-asepcos.c pkcs15-rutoken.c \ - pkcs15-entersafe.c \ + pkcs15-entersafe.c pkcs15-rtecp.c \ pkcs15init.exports if WIN32 libpkcs15init_la_SOURCES += versioninfo.rc diff --git a/src/pkcs15init/Makefile.mak b/src/pkcs15init/Makefile.mak index 2b93d9ed82..1626668e15 100644 --- a/src/pkcs15init/Makefile.mak +++ b/src/pkcs15init/Makefile.mak @@ -10,8 +10,8 @@ OBJECTS = pkcs15-lib.obj profile.obj keycache.obj \ pkcs15-cardos.obj pkcs15-jcop.obj pkcs15-starcos.obj \ pkcs15-oberthur.obj pkcs15-setcos.obj pkcs15-incrypto34.obj \ pkcs15-muscle.obj pkcs15-asepcos.obj pkcs15-rutoken.obj \ - pkcs15-entersafe.obj \ - versioninfo.res + pkcs15-entersafe.obj pkcs15-rtecp.obj \ + versioninfo.res all: install-headers $(TARGET) diff --git a/src/pkcs15init/pkcs15-init.h b/src/pkcs15init/pkcs15-init.h index b9ff5d3bda..80dd83b6a8 100644 --- a/src/pkcs15init/pkcs15-init.h +++ b/src/pkcs15init/pkcs15-init.h @@ -403,6 +403,7 @@ extern struct sc_pkcs15init_operations *sc_pkcs15init_get_muscle_ops(void); extern struct sc_pkcs15init_operations *sc_pkcs15init_get_asepcos_ops(void); extern struct sc_pkcs15init_operations *sc_pkcs15init_get_rutoken_ops(void); extern struct sc_pkcs15init_operations *sc_pkcs15init_get_entersafe_ops(void); +extern struct sc_pkcs15init_operations *sc_pkcs15init_get_rtecp_ops(void); #ifdef __cplusplus } diff --git a/src/pkcs15init/pkcs15-lib.c b/src/pkcs15init/pkcs15-lib.c index c7289a219d..03dbeaa91a 100644 --- a/src/pkcs15init/pkcs15-lib.c +++ b/src/pkcs15init/pkcs15-lib.c @@ -164,6 +164,7 @@ static struct profile_operations { { "muscle", (void*) sc_pkcs15init_get_muscle_ops }, { "asepcos", (void*) sc_pkcs15init_get_asepcos_ops }, { "entersafe",(void*) sc_pkcs15init_get_entersafe_ops }, + { "rutoken_ecp", (void *) sc_pkcs15init_get_rtecp_ops }, { NULL, NULL }, }; diff --git a/src/pkcs15init/pkcs15-rtecp.c b/src/pkcs15init/pkcs15-rtecp.c new file mode 100644 index 0000000000..54a06a5018 --- /dev/null +++ b/src/pkcs15init/pkcs15-rtecp.c @@ -0,0 +1,502 @@ +/* + * pkcs15-rtecp.c: Rutoken ECP specific operation for PKCS15 initialization + * + * Copyright (C) 2009 Aleksey Samsonov + * + * 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 +#include "pkcs15-init.h" +#include "profile.h" + +#define RTECP_SO_PIN_REF 1 +#define RTECP_USER_PIN_REF 2 + +/* + * Erase everything that's on the card + */ +static int rtecp_erase(sc_profile_t *profile, sc_card_t *card) +{ + if (!profile || !card) + return SC_ERROR_INVALID_ARGUMENTS; + return sc_card_ctl(card, SC_CARDCTL_RTECP_INIT, NULL); +} + +static int create_sysdf(sc_profile_t *profile, sc_card_t *card, const char *name) +{ + sc_file_t *file; + sc_path_t path; + int r; + + assert(profile && card && card->ctx && name); + r = sc_profile_get_file(profile, name, &file); + if (r == SC_SUCCESS) + { + assert(file); + path = file->path; + assert(path.len > 2); + if (path.len > 2) + path.len -= 2; + r = sc_select_file(card, &path, NULL); + if (r == SC_SUCCESS) + r = sc_file_add_acl_entry(file, SC_AC_OP_CREATE, + SC_AC_CHV, RTECP_USER_PIN_REF); + if (r == SC_SUCCESS) + r = sc_file_add_acl_entry(file, SC_AC_OP_DELETE, + SC_AC_NEVER, SC_AC_KEY_REF_NONE); + if (r == SC_SUCCESS) + r = sc_create_file(card, file); + assert(file); + sc_file_free(file); + } + if (r && card->ctx->debug >= 2) + sc_debug(card->ctx, "Create %s failed: %s\n", name, sc_strerror(r)); + return r; +} + +/* + * Card-specific initialization of PKCS15 meta-information + */ +static int rtecp_init(sc_profile_t *profile, sc_card_t *card) +{ + sc_file_t *file; + int r; + + if (!profile || !card || !card->ctx) + return SC_ERROR_INVALID_ARGUMENTS; + + r = sc_profile_get_file(profile, "MF", &file); + SC_TEST_RET(card->ctx, r, "Get MF info failed"); + assert(file); + r = sc_create_file(card, file); + assert(file); + sc_file_free(file); + SC_TEST_RET(card->ctx, r, "Create MF failed"); + + r = sc_profile_get_file(profile, "DIR", &file); + SC_TEST_RET(card->ctx, r, "Get DIR file info failed"); + assert(file); + r = sc_create_file(card, file); + assert(file); + sc_file_free(file); + SC_TEST_RET(card->ctx, r, "Create DIR file failed"); + + create_sysdf(profile, card, "Sys-DF"); + create_sysdf(profile, card, "SysKey-DF"); + create_sysdf(profile, card, "PuKey-DF"); + create_sysdf(profile, card, "PrKey-DF"); + create_sysdf(profile, card, "SKey-DF"); + create_sysdf(profile, card, "Cer-DF"); + create_sysdf(profile, card, "LCHV-DF"); + + return sc_select_file(card, sc_get_mf_path(), NULL); +} + +/* + * Create a DF + */ +static int rtecp_create_dir(sc_profile_t *profile, sc_card_t *card, sc_file_t *df) +{ + if (!profile || !card || !df) + return SC_ERROR_INVALID_ARGUMENTS; + return sc_create_file(card, df); +} + +/* + * Select a PIN reference + */ +static int rtecp_select_pin_reference(sc_profile_t *profile, sc_card_t *card, + sc_pkcs15_pin_info_t *pin_info) +{ + if (!profile || !card || !card->ctx || !pin_info) + return SC_ERROR_INVALID_ARGUMENTS; + + if (pin_info->reference > 2) + SC_FUNC_RETURN(card->ctx, 1, SC_ERROR_NOT_SUPPORTED); + if (pin_info->flags & SC_PKCS15_PIN_FLAG_SO_PIN) + pin_info->reference = RTECP_SO_PIN_REF; + else + pin_info->reference = RTECP_USER_PIN_REF; + return SC_SUCCESS; +} + +/* + * Create a PIN object within the given DF + */ +static int rtecp_create_pin(sc_profile_t *profile, sc_card_t *card, + sc_file_t *df, sc_pkcs15_object_t *pin_obj, + const unsigned char *pin, size_t pin_len, + const unsigned char *puk, size_t puk_len) +{ + sc_pkcs15_pin_info_t *pin_info; + sc_file_t *file; + /* GCHV min-length Flags Attempts Reserve */ + unsigned char prop[] = { 0x01, '?', 0x01, 0xFF, 0, 0 }; + /* AccessMode Unblock Change Delete */ + unsigned char sec[15] = { 0x43, RTECP_SO_PIN_REF, '?', 0, 0, 0, 0, 0xFF }; + int r; + + (void)puk; /* no warning */ + if (!profile || !card || !card->ctx || !df || !pin_obj || !pin_obj->data + || !pin || !pin_len) + return SC_ERROR_INVALID_ARGUMENTS; + + SC_FUNC_CALLED(card->ctx, 1); + if (puk_len != 0) + { + sc_error(card->ctx, "Do not enter User unblocking PIN (PUK): %s\n", + sc_strerror(SC_ERROR_NOT_SUPPORTED)); + return SC_ERROR_NOT_SUPPORTED; + } + pin_info = (sc_pkcs15_pin_info_t *)pin_obj->data; + if (pin_info->reference != RTECP_SO_PIN_REF + && pin_info->reference != RTECP_USER_PIN_REF) + { + sc_debug(card->ctx, "PIN reference %i not found in standard" + " (Rutoken ECP) PINs\n", pin_info->reference); + return SC_ERROR_NOT_SUPPORTED; + } + file = sc_file_new(); + if (!file) + SC_FUNC_RETURN(card->ctx, 0, SC_ERROR_OUT_OF_MEMORY); + file->id = pin_info->reference; + file->size = pin_len; + assert(sizeof(sec)/sizeof(sec[0]) > 2); + sec[2] = (unsigned char)pin_info->reference; + r = sc_file_set_sec_attr(file, sec, sizeof(sec)); + if (r == SC_SUCCESS) + { + assert(sizeof(prop)/sizeof(prop[0]) > 1); + prop[1] = (unsigned char)pin_info->min_length; + r = sc_file_set_prop_attr(file, prop, sizeof(prop)); + } + if (r == SC_SUCCESS) + r = sc_file_set_type_attr(file, (const u8*)"\x10\x00", 2); + if (r == SC_SUCCESS) + r = sc_create_file(card, file); + sc_file_free(file); + + if (r == SC_SUCCESS) + r = sc_change_reference_data(card, pin_info->type, pin_info->reference, + NULL, 0, pin, pin_len, NULL); + SC_FUNC_RETURN(card->ctx, 1, r); +} + +/* + * Select a reference for a private key object + */ +static int rtecp_select_key_reference(sc_profile_t *profile, + sc_card_t *card, sc_pkcs15_prkey_info_t *key_info) +{ + sc_file_t *df; + int r; + + if (!profile || !card || !card->ctx || !key_info) + return SC_ERROR_INVALID_ARGUMENTS; + + if (key_info->key_reference <= 0) + key_info->key_reference = 1; + else if (key_info->key_reference > 0xFF) + return SC_ERROR_TOO_MANY_OBJECTS; + + r = sc_profile_get_file(profile, "PrKey-DF", &df); + SC_TEST_RET(card->ctx, r, "Get PrKey-DF info failed"); + assert(df); + key_info->path = df->path; + sc_file_free(df); + r = sc_append_file_id(&key_info->path, key_info->key_reference); + return r; +} + +/* + * Create an empty key object + */ +static int rtecp_create_key(sc_profile_t *profile, sc_card_t *card, + sc_pkcs15_object_t *obj) +{ + /* RSA_PRkey/ for Miller- + * RSA_PUBkey Rabin test Attempts Reserve */ + const unsigned char prkey_prop[] = { 0x23, 0x1F, 0, 0xFF, 0, 0 }; + const unsigned char pbkey_prop[] = { 0x33, 0x1F, 0, 0xFF, 0, 0 }; + /* AccessMode - Update Use - - - Delete */ + unsigned char prkey_sec[15] = { 0x46, 0, '?', '?', 0, 0, 0, '?' }; + unsigned char pbkey_sec[15] = { 0x46, 0, '?', 0, 0, 0, 0, '?' }; + unsigned char auth_id; + sc_pkcs15_prkey_info_t *key_info; + sc_file_t *file; + int r; + + if (!profile || !card || !card->ctx || !obj || !obj->data) + return SC_ERROR_INVALID_ARGUMENTS; + + SC_FUNC_CALLED(card->ctx, 1); + if (obj->type != SC_PKCS15_TYPE_PRKEY_RSA) + return SC_ERROR_NOT_SUPPORTED; + if (obj->auth_id.len != 1) + return SC_ERROR_INVALID_ARGUMENTS; + auth_id = obj->auth_id.value[0]; + + key_info = (sc_pkcs15_prkey_info_t *)obj->data; + assert(key_info); + if (key_info->modulus_length % 128 != 0) + { + sc_error(card->ctx, "Unsupported key size %u\n", + key_info->modulus_length); + return SC_ERROR_INVALID_ARGUMENTS; + } + + r = sc_profile_get_file(profile, "PKCS15-AppDF", &file); + SC_TEST_RET(card->ctx, r, "Get PKCS15-AppDF info failed"); + r = sc_file_add_acl_entry(file, SC_AC_OP_CREATE, SC_AC_CHV, auth_id); + if (r == SC_SUCCESS) + r = sc_pkcs15init_authenticate(profile, card, file, SC_AC_OP_CREATE); + assert(file); + sc_file_free(file); + SC_TEST_RET(card->ctx, r, "Authenticate failed"); + + file = sc_file_new(); + if (!file) + SC_FUNC_RETURN(card->ctx, 0, SC_ERROR_OUT_OF_MEMORY); + file->id = key_info->key_reference; + r = sc_file_set_type_attr(file, (const u8*)"\x10\x00", 2); + /* private key file */ + file->size = key_info->modulus_length / 8 / 2 * 5 + 8; + if (r == SC_SUCCESS) + { + assert(sizeof(prkey_sec)/sizeof(prkey_sec[0]) > 7); + prkey_sec[2] = auth_id; + prkey_sec[3] = auth_id; + prkey_sec[7] = auth_id; + r = sc_file_set_sec_attr(file, prkey_sec, sizeof(prkey_sec)); + } + if (r == SC_SUCCESS) + r = sc_file_set_prop_attr(file, prkey_prop, sizeof(prkey_prop)); + if (r == SC_SUCCESS) + r = sc_create_file(card, file); + /* public key file */ + file->size = key_info->modulus_length / 8 / 2 * 3; + if (r == SC_SUCCESS) + { + assert(sizeof(pbkey_sec)/sizeof(pbkey_sec[0]) > 7); + pbkey_sec[2] = auth_id; + pbkey_sec[7] = auth_id; + r = sc_file_set_sec_attr(file, pbkey_sec, sizeof(pbkey_sec)); + } + if (r == SC_SUCCESS) + r = sc_file_set_prop_attr(file, pbkey_prop, sizeof(pbkey_prop)); + if (r == SC_SUCCESS) + r = sc_create_file(card, file); + assert(file); + sc_file_free(file); + SC_FUNC_RETURN(card->ctx, 1, r); +} + +/* + * Store a key on the card + */ +static int rtecp_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 *key_info; + sc_file_t *pukey_df; + sc_path_t path; + unsigned char *buf; + size_t len, i; + int r; + + if (!profile || !card || !card->ctx || !obj || !obj->data || !key) + return SC_ERROR_INVALID_ARGUMENTS; + + SC_FUNC_CALLED(card->ctx, 1); + if (obj->type != SC_PKCS15_TYPE_PRKEY_RSA || key->algorithm != SC_ALGORITHM_RSA) + return SC_ERROR_NOT_SUPPORTED; + + key_info = (sc_pkcs15_prkey_info_t *)obj->data; + assert(key_info); + + assert(key_info->modulus_length % 128 == 0); + len = key_info->modulus_length / 8 / 2; + if (!key->u.rsa.p.data || !key->u.rsa.q.data || !key->u.rsa.iqmp.data + || !key->u.rsa.dmp1.data || !key->u.rsa.dmq1.data + || !key->u.rsa.modulus.data || !key->u.rsa.exponent.data + || key->u.rsa.p.len != len || key->u.rsa.q.len != len + || key->u.rsa.iqmp.len != len || key->u.rsa.dmp1.len != len + || key->u.rsa.dmq1.len != len || key->u.rsa.modulus.len != 2*len + || key->u.rsa.exponent.len > len || key->u.rsa.exponent.len == 0) + return SC_ERROR_INVALID_ARGUMENTS; + + buf = calloc(1, len * 5 + 8); + if (!buf) + SC_FUNC_RETURN(card->ctx, 0, SC_ERROR_OUT_OF_MEMORY); + assert(key->u.rsa.p.data && key->u.rsa.q.data && key->u.rsa.iqmp.data + && key->u.rsa.dmp1.data && key->u.rsa.dmq1.data + && key->u.rsa.p.len == len && key->u.rsa.q.len == len + && key->u.rsa.iqmp.len == len + && key->u.rsa.dmp1.len == len + && key->u.rsa.dmq1.len == len); + /* p */ + for (i = 0; i < len; ++i) + buf[i] = key->u.rsa.p.data[len - 1 - i]; + /* q */ + for (i = 0; i < len; ++i) + buf[len + 4 + i] = key->u.rsa.q.data[len - 1 - i]; + /* iqmp */ + for (i = 0; i < len; ++i) + buf[len + 4 + len + 4 + i] = key->u.rsa.iqmp.data[len - 1 - i]; + /* dmp1 */ + for (i = 0; i < len; ++i) + buf[len + 4 + len + 4 + len + i] = key->u.rsa.dmp1.data[len - 1 - i]; + /* dmq1 */ + for (i = 0; i < len; ++i) + buf[len * 4 + 8 + i] = key->u.rsa.dmq1.data[len - 1 - i]; + + path = key_info->path; + r = sc_select_file(card, &path, NULL); + if (r == SC_SUCCESS) + r = sc_change_reference_data(card, 0, 0, NULL, 0, buf, len*5 + 8, NULL); + assert(buf); + sc_mem_clear(buf, len * 5 + 8); + /* store public key */ + assert(key->u.rsa.modulus.data && key->u.rsa.exponent.data + && key->u.rsa.modulus.len == 2*len + && key->u.rsa.exponent.len <= len + && key->u.rsa.exponent.len > 0); + /* modulus */ + for (i = 0; i < 2*len; ++i) + buf[i] = key->u.rsa.modulus.data[2*len - 1 - i]; + /* exponent */ + for (i = 0; i < key->u.rsa.exponent.len && i < len; ++i) + buf[2*len+i] = key->u.rsa.exponent.data[key->u.rsa.exponent.len - 1 - i]; + if (r == SC_SUCCESS) + { + r = sc_profile_get_file(profile, "PuKey-DF", &pukey_df); + if (r == SC_SUCCESS) + { + assert(pukey_df); + path = pukey_df->path; + r = sc_append_file_id(&path, key_info->key_reference); + sc_file_free(pukey_df); + } + else if (card->ctx->debug >= 2) + sc_debug(card->ctx, "%s\n", "Get PuKey-DF info failed"); + } + if (r == SC_SUCCESS) + { + r = sc_select_file(card, &path, NULL); + if (r == SC_SUCCESS) + r = sc_change_reference_data(card, 0, 0, NULL, 0, + buf, len*3, NULL); + if (r && card->ctx->debug >= 2) + sc_debug(card->ctx, "%s\n", "Store public key failed"); + } + assert(buf); + free(buf); + SC_FUNC_RETURN(card->ctx, 1, r); +} + +/* + * Generate key + */ +static int rtecp_generate_key(sc_profile_t *profile, sc_card_t *card, + sc_pkcs15_object_t *obj, sc_pkcs15_pubkey_t *pubkey) +{ + sc_pkcs15_prkey_info_t *key_info; + sc_rtecp_genkey_data_t data; + int r; + + if (!profile || !card || !card->ctx || !obj || !obj->data || !pubkey) + return SC_ERROR_INVALID_ARGUMENTS; + + SC_FUNC_CALLED(card->ctx, 1); + if (obj->type != SC_PKCS15_TYPE_PRKEY_RSA) + return SC_ERROR_NOT_SUPPORTED; + + key_info = (sc_pkcs15_prkey_info_t *)obj->data; + assert(key_info); + data.key_id = key_info->key_reference; + assert(data.key_id != 0); + assert(key_info->modulus_length % 128 == 0); + data.modulus_len = key_info->modulus_length / 8; + data.modulus = calloc(1, data.modulus_len); + data.exponent_len = key_info->modulus_length / 8 / 2; + data.exponent = calloc(1, data.exponent_len); + if (!data.modulus || !data.exponent) + { + free(data.modulus); + free(data.exponent); + SC_FUNC_RETURN(card->ctx, 0, SC_ERROR_OUT_OF_MEMORY); + } + r = sc_card_ctl(card, SC_CARDCTL_RTECP_GENERATE_KEY, &data); + if (r == SC_SUCCESS) + { + assert(pubkey); + pubkey->algorithm = SC_ALGORITHM_RSA; + pubkey->u.rsa.modulus.data = data.modulus; + pubkey->u.rsa.modulus.len = data.modulus_len; + pubkey->u.rsa.exponent.data = data.exponent; + pubkey->u.rsa.exponent.len = data.exponent_len; + } + SC_FUNC_RETURN(card->ctx, 1, r); +} + +/* + * Finalize card + * Ends the initialization phase of the smart card/token + */ +static int rtecp_finalize(sc_card_t *card) +{ + if (!card) + return SC_ERROR_INVALID_ARGUMENTS; + return sc_card_ctl(card, SC_CARDCTL_RTECP_INIT_END, NULL); +} + +static struct sc_pkcs15init_operations sc_pkcs15init_rtecp_operations = { + rtecp_erase, /* erase_card */ + rtecp_init, /* init_card */ + rtecp_create_dir, /* create_dir */ + NULL, /* create_domain */ + rtecp_select_pin_reference, /* select_pin_reference */ + rtecp_create_pin, /* create_pin */ + rtecp_select_key_reference, /* select_key_reference */ + rtecp_create_key, /* create_key */ + rtecp_store_key, /* store_key */ + rtecp_generate_key, /* generate_key */ + NULL, /* encode_private_key */ + NULL, /* encode_public_key */ + rtecp_finalize, /* finalize_card */ + /* Old-style API */ + NULL, /* init_app */ + NULL, /* new_pin */ + NULL, /* new_key */ + NULL, /* new_file */ + NULL, /* old_generate_key */ + NULL /* delete_object */ +}; + +struct sc_pkcs15init_operations * sc_pkcs15init_get_rtecp_ops(void) +{ + return &sc_pkcs15init_rtecp_operations; +} + diff --git a/src/pkcs15init/pkcs15init.exports b/src/pkcs15init/pkcs15init.exports index f46051d1b0..e43215e818 100644 --- a/src/pkcs15init/pkcs15init.exports +++ b/src/pkcs15init/pkcs15init.exports @@ -33,6 +33,7 @@ sc_pkcs15init_get_muscle_ops sc_pkcs15init_get_oberthur_ops sc_pkcs15init_get_pin_info sc_pkcs15init_get_rutoken_ops +sc_pkcs15init_get_rtecp_ops sc_pkcs15init_get_serial sc_pkcs15init_get_setcos_ops sc_pkcs15init_get_starcos_ops diff --git a/src/pkcs15init/rutoken_ecp.profile b/src/pkcs15init/rutoken_ecp.profile new file mode 100644 index 0000000000..2285ecff46 --- /dev/null +++ b/src/pkcs15init/rutoken_ecp.profile @@ -0,0 +1,204 @@ +# +# PKCS15 profile, generic information. +# This profile is loaded before any card specific profile. +# + +cardinfo { + label = "Rutoken ECP"; + manufacturer = "Aktiv Co."; + + max-pin-length = 32; + min-pin-length = 1; + pin-encoding = ascii-numeric; +} + +# +# The following controls some aspects of the PKCS15 we put onto +# the card. +# +pkcs15 { + # Put certificates into the CDF itself? + direct-certificates = no; + # Put the DF length into the ODF file? + encode-df-length = no; + # Have a lastUpdate field in the EF(TokenInfo)? + do-last-update = yes; +} + +# Default settings. +# This option block will always be processed. +option default { + macros { + ti-size = 128; + odf-size = 128; + aodf-size = 256; + dodf-size = 2048; + cdf-size = 2048; + prkdf-size = 2048; + pukdf-size = 2048; + } +} + +# Define reasonable limits for PINs and PUK +# Note that we do not set a file path or reference +# for the user pin; that is done dynamically. +PIN user-pin { + auth-id = 2; + reference = 2; + attempts = 2; + min-length = 8; + max-length = 32; + flags = case-sensitive, local, initialized; +} +PIN user-puk { + min-length = 0; + max-length = 0; +} + +PIN so-pin { + auth-id = 1; + reference = 1; + attempts = 2; + min-length = 8; + max-length = 32; + flags = case-sensitive, local, initialized, soPin; +} +PIN so-puk { + min-length = 0; + max-length = 0; +} + +filesystem { + + DF MF { + path = 3F00; + type = DF; + acl = *=NEVER, SELECT=NONE, DELETE=NEVER, CREATE=CHV2, READ=NONE; + + DF Sys-DF { + file-id = 1000; + + DF SysKey-DF { + file-id = 1000; + + DF PuKey-DF { + file-id = 6001; + } + + DF PrKey-DF { + file-id = 6002; + } + + DF SKey-DF { + file-id = 6003; + } + + DF Cer-DF { + file-id = 6004; + } + + DF LCHV-DF { + file-id = 6005; + } + } + } + + EF DIR { + type = EF; + file-id = 2F00; + size = 128; + acl = *=NEVER, READ=NONE, UPDATE=CHV1, WRITE=CHV1, DELETE=CHV1; + } + + # Here comes the application DF + DF PKCS15-AppDF { + type = DF; + file-id = 5000; + acl = *=NONE, DELETE=CHV2; +# acl = *=NEVER, SELECT=NONE, DELETE=CHV2, CREATE=CHV2, READ=NONE; + + EF PKCS15-ODF { + file-id = 5031; + size = $odf-size; + acl = *=NONE, DELETE=$SOPIN; + } + + EF PKCS15-TokenInfo { + file-id = 5032; + size = $ti-size; + acl = *=NONE, DELETE=CHV2; + } + + EF PKCS15-AODF { + file-id = 6005; + size = $aodf-size; + acl = *=NEVER, READ=NONE, UPDATE=$SOPIN, WRITE=$SOPIN, DELETE=$SOPIN; + } + + EF PKCS15-PrKDF { + file-id = 6002; + size = $prkdf-size; + acl = *=NEVER, READ=NONE, UPDATE=$PIN, WRITE=$PIN, DELETE=$PIN; + } + + EF PKCS15-PuKDF { + file-id = 6001; + size = $pukdf-size; + acl = *=NEVER, READ=NONE, UPDATE=$PIN, WRITE=$PIN, DELETE=$PIN; + } + + EF PKCS15-CDF { + file-id = 6004; + size = $cdf-size; + acl = *=NEVER, READ=NONE, UPDATE=$PIN, WRITE=$PIN, DELETE=$PIN; + } + + EF PKCS15-DODF { + file-id = 6006; + size = $dodf-size; + acl = *=NEVER, READ=NONE, UPDATE=$PIN, WRITE=$PIN, DELETE=$PIN; + } + + # 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 { + EF private-key { + file-id = 0100; + structure = transparent; + acl = *=NEVER, READ=$PIN, UPDATE=$PIN, WRITE=$PIN, DELETE=$PIN; + } + + EF public-key { + file-id = 0200; + structure = transparent; + acl = *=NEVER, READ=NONE, UPDATE=$PIN, WRITE=$PIN, DELETE=$PIN; + } + + # Certificate template + EF certificate { + file-id = 0300; + structure = transparent; + acl = *=NEVER, READ=NONE, UPDATE=$PIN, WRITE=$PIN, DELETE=$PIN; + } + + # data objects are stored in transparent EFs. + EF data { + file-id = 0400; + structure = transparent; + acl = *=NEVER, READ=NONE, UPDATE=$PIN, WRITE=$PIN, DELETE=$PIN; + } + + # private data objects are stored in transparent EFs. + EF privdata { + file-id = 0500; + structure = transparent; + acl = *=NEVER, READ=$PIN, UPDATE=$PIN, WRITE=$PIN, DELETE=$PIN; + } + } + } + } +} +