/* * card-rtecp.c: Support for Rutoken ECP and Rutoken Lite 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 */ #if HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include "internal.h" #include "asn1.h" #include "cardctl.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 and Lite driver", "rutoken_ecp", &rtecp_ops, NULL, 0, NULL }; static const struct sc_atr_table rtecp_atrs[] = { /* Rutoken ECP */ { "3B:8B:01:52:75:74:6F:6B:65:6E:20:45:43:50:A0", NULL, "Rutoken ECP", SC_CARD_TYPE_RUTOKEN_ECP, 0, NULL }, /* Rutoken ECP (DS) */ { "3B:8B:01:52:75:74:6F:6B:65:6E:20:44:53:20:C1", NULL, "Rutoken ECP (DS)", SC_CARD_TYPE_RUTOKEN_ECP, 0, NULL }, /* Rutoken ECP SC T0 */ { "3B:9C:96:00:52:75:74:6F:6B:65:6E:45:43:50:73:63", "00:00:00:00:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF", "Rutoken ECP SC", SC_CARD_TYPE_RUTOKEN_ECP_SC, 0, NULL }, /* Rutoken ECP SC T1 */ { "3B:9C:94:80:11:40:52:75:74:6F:6B:65:6E:45:43:50:73:63:C3", "00:00:00:00:00:00:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:00", "Rutoken ECP SC", SC_CARD_TYPE_RUTOKEN_ECP_SC, 0, NULL }, /* Rutoken ECP SC NFC */ { "3B:88:80:01:52:74:53:43:77:81:83:20:6A", "00:00:00:00:FF:FF:FF:FF:00:00:00:00:00", "Rutoken ECP SC NFC", SC_CARD_TYPE_RUTOKEN_ECP_SC, 0, NULL }, /* Rutoken Lite */ { "3B:8B:01:52:75:74:6F:6B:65:6E:6C:69:74:65:C2", NULL, "Rutoken Lite", SC_CARD_TYPE_RUTOKEN_LITE, 0, NULL }, /* Rutoken Lite SC*/ { "3B:9E:96:00:52:75:74:6F:6B:65:6E:4C:69:74:65:53:43:32", "00:00:00:00:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF", "Rutoken Lite SC", SC_CARD_TYPE_RUTOKEN_LITE_SC, 0, NULL }, { NULL, NULL, NULL, 0, 0, NULL } }; static int rtecp_match_card(sc_card_t *card) { int i = -1; i = _sc_match_atr(card, rtecp_atrs, &card->type); if (i >= 0) { card->name = rtecp_atrs[i].name; LOG_FUNC_RETURN(card->ctx, 1); } LOG_FUNC_RETURN(card->ctx, 0); } static int rtecp_init(sc_card_t *card) { sc_algorithm_info_t info; unsigned long flags; assert(card && card->ctx); card->cla = 0; if (card->type == SC_CARD_TYPE_RUTOKEN_LITE || card->type == SC_CARD_TYPE_RUTOKEN_LITE_SC) SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, 0); card->caps |= SC_CARD_CAP_RNG; 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); memset(&info, 0, sizeof(info)); info.algorithm = SC_ALGORITHM_GOSTR3410; info.key_length = 256; info.flags = SC_ALGORITHM_GOSTR3410_RAW | SC_ALGORITHM_ONBOARD_KEY_GEN | SC_ALGORITHM_GOSTR3410_HASH_NONE; _sc_card_add_algorithm(card, &info); SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, 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 == SC_RTECP_SEC_ATTR_SIZE); assert(1 + 6 < SC_RTECP_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]); sc_log(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]); sc_log(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]); sc_log(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); sc_log(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[SC_RTECP_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, SC_LOG_DEBUG_VERBOSE, r); } static int rtecp_select_file(sc_card_t *card, const sc_path_t *in_path, sc_file_t **file_out) { sc_file_t *file = NULL; int r = SC_SUCCESS; if (!card || !card->ctx || !in_path) return SC_ERROR_INVALID_ARGUMENTS; SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE); switch (in_path->type) { case SC_PATH_TYPE_DF_NAME: case SC_PATH_TYPE_FROM_CURRENT: case SC_PATH_TYPE_PARENT: SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, SC_ERROR_NOT_SUPPORTED); } // Card Rutoken ECP SC T0 doesn't support SELECT FILE without return a file info. // So here we request a file and then assign/free it depending on file_out. r = iso_ops->select_file(card, in_path, &file); if (r != SC_SUCCESS) SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, r); if (file->sec_attr && file->sec_attr_len == SC_RTECP_SEC_ATTR_SIZE) set_acl_from_sec_attr(card, file); else { sc_file_free(file); SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, SC_ERROR_UNKNOWN_DATA_RECEIVED); } if (file_out) *file_out = file; else sc_file_free(file); SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, 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; r = sc_transmit_apdu(card, &apdu); LOG_TEST_RET(card->ctx, r, "APDU transmit failed"); if (send_logout++ == 0 && apdu.sw1 == 0x6F && apdu.sw2 == 0x86) { r = sc_logout(card); LOG_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); LOG_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, SC_LOG_DEBUG_VERBOSE, 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); LOG_TEST_RET(card->ctx, r, "APDU transmit failed"); r = sc_check_sw(card, apdu.sw1, apdu.sw2); SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, r); } static int rtecp_set_security_env( struct sc_card *card, const struct sc_security_env *env, int se_num) { struct sc_security_env se_env; if(!env) return SC_ERROR_INVALID_ARGUMENTS; se_env= *env; se_env.flags &= ~SC_SEC_ENV_FILE_REF_PRESENT; return iso_ops->set_security_env(card, &se_env, se_num); } static int rtecp_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(out_len + 2); buf = malloc(data_len); if (!buf || !buf_out) { free(buf); free(buf_out); LOG_FUNC_RETURN(card->ctx, 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.resp = buf_out; apdu.resplen = out_len + 2; apdu.le = out_len > 256 ? 256 : out_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_log(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 < apdu.resplen; ++i) out[i] = buf_out[apdu.resplen - 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, out_len + 2); } assert(buf_out); free(buf_out); SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, 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); if (card->type == SC_CARD_TYPE_RUTOKEN_LITE || card->type == SC_CARD_TYPE_RUTOKEN_LITE_SC) SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, SC_ERROR_NOT_SUPPORTED); /* decipher */ r = rtecp_cipher(card, data, data_len, out, out_len, 0); SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, 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); if (card->type == SC_CARD_TYPE_RUTOKEN_LITE || card->type == SC_CARD_TYPE_RUTOKEN_LITE_SC) SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, SC_ERROR_NOT_SUPPORTED); /* compute digital signature */ r = rtecp_cipher(card, data, data_len, out, out_len, 1); SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, 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 rsf_length[2], *buf, *buf_end, *p; size_t val_length, buf_length, max_transmit_length; int transmits_num, r; assert(card && card->ctx && newref); sc_log(card->ctx, "newlen = %"SC_FORMAT_LEN_SIZE_T"u\n", newlen); if (newlen > 0xFFFF) LOG_FUNC_RETURN(card->ctx, SC_ERROR_INVALID_ARGUMENTS); if (type == SC_AC_CHV && old && oldlen != 0) { r = sc_verify(card, type, ref_qualifier, old, oldlen, tries_left); LOG_TEST_RET(card->ctx, r, "Verify old pin failed"); } max_transmit_length = sc_get_max_send_size(card); assert(max_transmit_length > 2); /* * (2 + sizeof(rsf_length) + newlen) - total length of data we need to transfer, * (max_transmit_length - 2) - amount of useful data we can transfer in one transmit (2 bytes for 0xA5 tag) */ transmits_num = (2 + sizeof(rsf_length) + newlen) / (max_transmit_length - 2) + 1; /* buffer length = size of 0x80 TLV + size of RSF-file + (size of Tag and Length)*(number of APDUs) */ buf_length = (2 + sizeof(rsf_length)) + newlen + 2*(transmits_num); p = buf = (u8 *)malloc(buf_length); if (buf == NULL) LOG_FUNC_RETURN(card->ctx, SC_ERROR_OUT_OF_MEMORY); buf_end = buf + buf_length; sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, 0x24, 0x01, ref_qualifier); /* put 0x80 TLV */ rsf_length[0] = (newlen >> 8) & 0xFF; rsf_length[1] = newlen & 0xFF; assert(buf_end - p >= (int)(2 + sizeof(rsf_length))); sc_asn1_put_tag(0x80, rsf_length, sizeof(rsf_length), p, buf_end - p, &p); /* put 0xA5 TLVs (one or more); each transmit must begin with 0xA5 TLV */ while (newlen) { assert(buf_end - p >= (int)(newlen + 2)); if ((p - buf) % max_transmit_length + newlen + 2 > max_transmit_length) val_length = max_transmit_length - (p - buf) % max_transmit_length - 2; else val_length = newlen; /* not using sc_asn1_put_tag(...) because rtecp do not support asn1 properly (when val_length > 127) */ *p++ = 0xA5; *p++ = (u8)val_length; assert(val_length <= newlen); memcpy(p, newref, val_length); p += val_length; newref += val_length; newlen -= val_length; if (newlen) apdu.flags |= SC_APDU_FLAGS_CHAINING; } apdu.lc = p - buf; apdu.data = buf; apdu.datalen = p - buf; r = sc_transmit_apdu(card, &apdu); sc_mem_clear(buf, buf_length); free(buf); LOG_TEST_RET(card->ctx, r, "APDU transmit failed"); r = sc_check_sw(card, apdu.sw1, apdu.sw2); SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, 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; /* 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); LOG_TEST_RET(card->ctx, r, "APDU transmit failed"); r = sc_check_sw(card, apdu.sw1, apdu.sw2); LOG_TEST_RET(card->ctx, r, "Unblock card failed"); if (newref && newlen) { u8 tmp[2], buf[SC_MAX_APDU_BUFFER_SIZE]; u8 *p = buf; 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); LOG_TEST_RET(card->ctx, r, "Invalid new PIN length"); sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, 0x24, 0x01, ref_qualifier); apdu.lc = p - buf; apdu.data = buf; apdu.datalen = p - buf; r = sc_transmit_apdu(card, &apdu); LOG_TEST_RET(card->ctx, r, "APDU transmit failed"); r = sc_check_sw(card, apdu.sw1, apdu.sw2); LOG_TEST_RET(card->ctx, r, "Set PIN failed"); } SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, 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); LOG_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, SC_LOG_DEBUG_VERBOSE, r); } static int rtecp_list_files(sc_card_t *card, u8 *buf, size_t buflen) { sc_apdu_t apdu; u8 rbuf[SC_MAX_APDU_RESP_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); r = sc_transmit_apdu(card, &apdu); LOG_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); LOG_TEST_RET(card->ctx, r, ""); if (apdu.resplen <= 2) LOG_FUNC_RETURN(card->ctx, 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)) LOG_FUNC_RETURN(card->ctx, 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) LOG_FUNC_RETURN(card->ctx, SC_ERROR_UNKNOWN_DATA_RECEIVED); if (tag[0] == 0x38) { /* Select parent DF of the current DF */ sc_format_apdu(card, &apdu, SC_APDU_CASE_2_SHORT, 0xA4, 0x03, 0); /* We should set le and resp buf to actually call Get Response for card on T0. */ apdu.resp = rbuf; apdu.resplen = sizeof(rbuf); apdu.le = sizeof(rbuf); r = sc_transmit_apdu(card, &apdu); LOG_TEST_RET(card->ctx, r, "APDU transmit failed"); r = sc_check_sw(card, apdu.sw1, apdu.sw2); LOG_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, SC_LOG_DEBUG_VERBOSE, 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) LOG_FUNC_RETURN(card->ctx, 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 = 256; serial->len = sizeof(serial->value); break; case SC_CARDCTL_RTECP_GENERATE_KEY: if (!genkey_data) LOG_FUNC_RETURN(card->ctx, 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 = 256; break; case SC_CARDCTL_LIFECYCLE_SET: sc_log(card->ctx, "%s\n", "SC_CARDCTL_LIFECYCLE_SET not supported"); /* no call sc_debug (SC_FUNC_RETURN) */ return SC_ERROR_NOT_SUPPORTED; default: sc_log(card->ctx, "request = 0x%lx\n", request); LOG_FUNC_RETURN(card->ctx, SC_ERROR_NOT_SUPPORTED); } r = sc_transmit_apdu(card, &apdu); LOG_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->type == SC_ALGORITHM_RSA && genkey_data->u.rsa.modulus_len >= apdu.resplen && genkey_data->u.rsa.exponent_len >= 3) { memcpy(genkey_data->u.rsa.modulus, apdu.resp, apdu.resplen); genkey_data->u.rsa.modulus_len = apdu.resplen; reverse(genkey_data->u.rsa.modulus, genkey_data->u.rsa.modulus_len); memcpy(genkey_data->u.rsa.exponent, "\x01\x00\x01", 3); genkey_data->u.rsa.exponent_len = 3; } else if (genkey_data->type == SC_ALGORITHM_GOSTR3410 && genkey_data->u.gostr3410.xy_len >= apdu.resplen) { memcpy(genkey_data->u.gostr3410.xy, apdu.resp, apdu.resplen); genkey_data->u.gostr3410.xy_len = apdu.resplen; } 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, SC_LOG_DEBUG_VERBOSE, 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: LOG_FUNC_RETURN(card->ctx, 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, SC_LOG_DEBUG_VERBOSE, 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; /* read_binary */ rtecp_ops.write_binary = NULL; /* update_binary */ 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; /* get_response */ /* 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; return &rtecp_drv; }