/* * Driver for IAS based cards, e.g. Portugal's eID card. * * Copyright (C) 2009, Joao Poupino * * 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 * * Partially based on the ISO7816 driver. * * Thanks to Andre Cruz, Jorge Ferreira and Paulo F. Andrade */ #include "config.h" #include #include #include "internal.h" #include "asn1.h" #include "cardctl.h" /* Portugal eID uses 1024 bit keys */ #define PTEID_RSA_KEYSIZE 128 #define DRVDATA(card) ((struct ias_priv_data *) ((card)->drv_data)) static struct sc_card_operations ias_ops; static struct sc_card_operations *iso_ops = NULL; static struct sc_card_driver ias_drv = { "IAS", "ias", &ias_ops, NULL, 0, NULL }; /* Known ATRs */ static struct sc_atr_table ias_atrs[] = { /* Portugal eID cards */ {"3B:65:00:00:D0:00:54:01:31", NULL, NULL, SC_CARD_TYPE_IAS_PTEID, 0, NULL}, {"3B:65:00:00:D0:00:54:01:32", NULL, NULL, SC_CARD_TYPE_IAS_PTEID, 0, NULL}, {"3B:95:95:40:FF:D0:00:54:01:31", NULL, NULL, SC_CARD_TYPE_IAS_PTEID, 0, NULL}, {"3B:95:95:40:FF:D0:00:54:01:32", NULL, NULL, SC_CARD_TYPE_IAS_PTEID, 0, NULL}, {NULL, NULL, NULL, 0, 0, NULL} }; /* Known AIDs */ static const u8 ias_aid_pteid[] = {0x60, 0x46, 0x32, 0xFF, 0x00, 0x01, 0x02}; static int ias_select_applet(sc_card_t *card, const u8 *aid, size_t aid_len) { int r; sc_path_t tpath; tpath.type = SC_PATH_TYPE_DF_NAME; tpath.len = aid_len; memcpy(tpath.value, aid, aid_len); r = iso_ops->select_file(card, &tpath, NULL); if (r != SC_SUCCESS) { sc_debug(card->ctx, "unable to select applet"); return r; } return SC_SUCCESS; } static int ias_init(sc_card_t *card) { unsigned long flags; assert(card != NULL); SC_FUNC_CALLED(card->ctx, 1); card->name = "IAS"; card->cla = 0x00; /* Card version detection */ if (card->type == SC_CARD_TYPE_IAS_PTEID) { int r = ias_select_applet(card, ias_aid_pteid, sizeof(ias_aid_pteid)); if (r != SC_SUCCESS) return r; /* Add other cards if necessary */ } else { return SC_ERROR_INTERNAL; } /* Set card capabilities */ card->caps |= SC_CARD_CAP_RNG; /* Set the supported algorithms */ flags = SC_ALGORITHM_RSA_PAD_PKCS1 | SC_ALGORITHM_RSA_HASH_NONE; /* Only 1024 bit key sizes were tested */ _sc_card_add_rsa_alg(card, 1024, flags, 0); return SC_SUCCESS; } static int ias_match_card(sc_card_t *card) { int i; i = _sc_match_atr(card, ias_atrs, &card->type); if (i < 0) return 0; return 1; } static int ias_build_pin_apdu(sc_card_t *card, sc_apdu_t *apdu, struct sc_pin_cmd_data *data) { static u8 sbuf[SC_MAX_APDU_BUFFER_SIZE]; int r, len, pad, use_pin_pad, ins, p1; r = len = pad = use_pin_pad = ins = p1 = 0; assert(card != NULL); switch (data->pin_type) { case SC_AC_CHV: break; default: return SC_ERROR_INVALID_ARGUMENTS; } if (data->flags & SC_PIN_CMD_USE_PINPAD) use_pin_pad = 1; /* "needs-padding" necessary for the PTEID card, * but not defined in the pin structure */ if ((data->flags & SC_PIN_CMD_NEED_PADDING) || card->type == SC_CARD_TYPE_IAS_PTEID) pad = 1; data->pin1.offset = 5; switch (data->cmd) { case SC_PIN_CMD_VERIFY: ins = 0x20; if ( (r = sc_build_pin(sbuf, sizeof(sbuf), &data->pin1, pad)) < 0) return r; len = r; break; case SC_PIN_CMD_CHANGE: ins = 0x24; if ((data->flags & SC_PIN_CMD_IMPLICIT_CHANGE) == 0 && (data->pin1.len != 0 || use_pin_pad)) { if ( (r = sc_build_pin(sbuf, sizeof(sbuf), &data->pin1, pad)) < 0) return r; len += r; } else { /* implicit test */ p1 = 1; } data->pin2.offset = data->pin1.offset + len; if ( (r = sc_build_pin(sbuf+len, sizeof(sbuf)-len, &data->pin2, pad)) < 0) return r; len += r; break; case SC_PIN_CMD_UNBLOCK: ins = 0x2C; if (data->pin1.len != 0 || use_pin_pad) { if ( (r = sc_build_pin(sbuf, sizeof(sbuf), &data->pin1, pad)) < 0) return r; len += r; } else { p1 |= 0x02; } if (data->pin2.len != 0 || use_pin_pad) { data->pin2.offset = data->pin1.offset + len; if ( (r = sc_build_pin(sbuf+len, sizeof(sbuf)-len, &data->pin2, pad)) < 0) return r; len += r; } else { p1 |= 0x01; } break; default: return SC_ERROR_NOT_SUPPORTED; } sc_format_apdu(card, apdu, SC_APDU_CASE_3_SHORT, ins, p1, data->pin_reference); apdu->lc = len; apdu->datalen = len; apdu->data = sbuf; apdu->resplen = 0; return SC_SUCCESS; } static int ias_pin_cmd(sc_card_t *card, struct sc_pin_cmd_data *data, int *tries_left) { int r; sc_apdu_t local_apdu; SC_FUNC_CALLED(card->ctx, 1); /* Check if a PIN change operation is being requested, * as it requires sending two separate APDUs */ if (data->cmd == SC_PIN_CMD_CHANGE) { /* Build a SC_PIN_CMD_VERIFY APDU */ data->cmd = SC_PIN_CMD_VERIFY; r = ias_build_pin_apdu(card, &local_apdu, data); if (r < 0) return r; data->apdu = &local_apdu; r = iso_ops->pin_cmd(card, data, tries_left); if (r < 0) return r; /* Continue processing */ data->cmd = SC_PIN_CMD_CHANGE; /* The IAS spec mandates an implicit change PIN operation */ data->flags |= SC_PIN_CMD_IMPLICIT_CHANGE; } r = ias_build_pin_apdu(card, &local_apdu, data); if (r < 0) return r; data->apdu = &local_apdu; return iso_ops->pin_cmd(card, data, tries_left); } static int ias_set_security_env(sc_card_t *card, const sc_security_env_t *env, int se_num) { int r; sc_apdu_t apdu; u8 sbuf[SC_MAX_APDU_BUFFER_SIZE]; sc_debug(card->ctx, "ias_set_security_env, keyRef = 0x%0x, algo = 0x%0x\n", *env->key_ref, env->algorithm_flags); assert(card != NULL && env != NULL); sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, 0x22, 0x41, 0); switch (env->operation) { case SC_SEC_OPERATION_DECIPHER: apdu.p2 = 0xB8; /* confidentiality template */ sbuf[0] = 0x95; /* tag for usage qualifier byte */ sbuf[1] = 0x01; /* tag length */ sbuf[2] = 0x40; /* data decryption */ sbuf[3] = 0x84; /* tag for private key reference */ sbuf[4] = 0x01; /* tag length */ sbuf[5] = *env->key_ref; /* key reference */ sbuf[6] = 0x80; /* tag for algorithm reference */ sbuf[7] = 0x01; /* tag length */ if (env->algorithm_flags & SC_ALGORITHM_RSA_PAD_PKCS1) sbuf[8] = 0x1A; /* RSA PKCS#1 with no data formatting */ else { sc_debug(card->ctx, "Set Sec Env: unsupported algo 0X%0X\n", env->algorithm_flags); return SC_ERROR_INVALID_ARGUMENTS; } apdu.lc = 9; apdu.datalen = 9; break; case SC_SEC_OPERATION_SIGN: apdu.p2 = 0xA4; /* authentication template */ sbuf[0] = 0x95; /* tag for usage qualifier byte */ sbuf[1] = 0x01; /* tag length */ sbuf[2] = 0x40; /* internal authentication */ sbuf[3] = 0x84; /* tag for private key reference */ sbuf[4] = 0x01; /* tag length */ sbuf[5] = *env->key_ref; /* key reference */ sbuf[6] = 0x80; /* tag for algorithm reference */ sbuf[7] = 0x01; /* tag length */ if (env->algorithm_flags & SC_ALGORITHM_RSA_PAD_PKCS1) sbuf[8] = 0x02; /* RSA PKCS#1 with no data formatting */ else { sc_debug(card->ctx, "Set Sec Env: unsupported algo 0X%0X\n", env->algorithm_flags); return SC_ERROR_INVALID_ARGUMENTS; } apdu.lc = 9; apdu.datalen = 9; break; default: return SC_ERROR_INVALID_ARGUMENTS; } apdu.le = 0; apdu.data = sbuf; apdu.resplen = 0; r = sc_transmit_apdu(card, &apdu); SC_TEST_RET(card->ctx, r, "Set Security Env APDU transmit failed"); r = sc_check_sw(card, apdu.sw1, apdu.sw2); SC_TEST_RET(card->ctx, r, "Card's Set Security Env command returned error"); return r; } static int ias_compute_signature(sc_card_t *card, const u8 * data, size_t data_len, u8 * out, size_t outlen) { int r; size_t len = 0; sc_apdu_t apdu; u8 sbuf[SC_MAX_APDU_BUFFER_SIZE]; sc_context_t *ctx = card->ctx; SC_FUNC_CALLED(ctx, 1); if (data_len > 64) { sc_debug(ctx, "error: input data too long: %lu bytes\n", data_len); return SC_ERROR_INVALID_ARGUMENTS; } /* Send the data */ sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, 0x88, 0x02, 0x00); memcpy(sbuf, data, data_len); apdu.data = sbuf; apdu.lc = data_len; apdu.datalen = data_len; r = sc_transmit_apdu(card, &apdu); SC_TEST_RET(card->ctx, r, "APDU transmit failed"); /* Get the result */ if (apdu.sw1 == 0x90 && apdu.sw2 == 0x00) { len = card->type == SC_CARD_TYPE_IAS_PTEID ? PTEID_RSA_KEYSIZE : outlen; r = iso_ops->get_response(card, &len, out); if (r == 0) SC_FUNC_RETURN(card->ctx, 2, len); else SC_FUNC_RETURN(card->ctx, 2, r); } SC_FUNC_RETURN(card->ctx, 2, sc_check_sw(card, apdu.sw1, apdu.sw2)); } static int ias_select_file(sc_card_t *card, const sc_path_t *in_path, sc_file_t **file_out) { int r, pathlen, stripped_len, offset; u8 buf[SC_MAX_APDU_BUFFER_SIZE]; u8 pathbuf[SC_MAX_PATH_SIZE], *path; sc_context_t *ctx; sc_apdu_t apdu; sc_file_t *file; r = pathlen = stripped_len = offset = 0; path = pathbuf; file = NULL; assert(card != NULL && in_path != NULL); ctx = card->ctx; if (in_path->len > SC_MAX_PATH_SIZE) return SC_ERROR_INVALID_ARGUMENTS; memcpy(path, in_path->value, in_path->len); pathlen = in_path->len; sc_format_apdu(card, &apdu, SC_APDU_CASE_4_SHORT, 0xA4, 0, 0); apdu.p2 = 0; /* First record, return FCI */ switch (in_path->type) { case SC_PATH_TYPE_FILE_ID: apdu.p1 = 2; if (pathlen != 2) return SC_ERROR_INVALID_ARGUMENTS; break; case SC_PATH_TYPE_DF_NAME: apdu.p1 = 4; break; case SC_PATH_TYPE_PATH: apdu.p1 = 9; /* Strip the MF */ if (pathlen >= 2 && memcmp(path, "\x3f\x00", 2) == 0) { if (pathlen == 2) { /* Only 3f00 provided */ apdu.p1 = 0; break; } path += 2; pathlen -= 2; } /* Optimization based on the normal Portuguese eID usage pattern: * paths with len >= 4 shall be stripped - this avoids unnecessary * "file not found" errors. Other cards may benefit from this also. * * This works perfectly for the Portuguese eID card, but if you * are adapting this driver to another card, "false positives" may * occur depending, of course, on the file structure of the card. * * Please have this in mind if adapting this driver to another card. */ if (pathlen >= 4) { stripped_len = pathlen - 2; path += stripped_len; pathlen = 2; } else if (pathlen == 2) { apdu.p1 = 0; } break; case SC_PATH_TYPE_FROM_CURRENT: apdu.p1 = 9; break; case SC_PATH_TYPE_PARENT: apdu.p1 = 3; apdu.p2 = 0x0C; pathlen = 0; apdu.cse = SC_APDU_CASE_2_SHORT; break; default: SC_FUNC_RETURN(card->ctx, 2, 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 = 256; } else { apdu.p2 = 0x0C; apdu.cse = (apdu.lc == 0) ? SC_APDU_CASE_1 : 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)); } /* A "file not found" error was received, this can mean two things: * 1) the file does not exist * 2) the current DF may be incorrect due to the optimization applied * earlier. If the path was previously stripped, select the first DF * and try to re-select the path with the full value. */ if (stripped_len > 0 && apdu.sw1 == 0x6A && apdu.sw2 == 0x82) { sc_path_t tpath; /* Restore original path value */ path -= stripped_len; pathlen += stripped_len; memset(&tpath, 0, sizeof(sc_path_t)); tpath.type = SC_PATH_TYPE_PATH; tpath.len = 2; tpath.value[0] = path[0]; tpath.value[1] = path[1]; /* Go up in the hierarchy to the correct DF */ r = ias_select_file(card, &tpath, NULL); SC_TEST_RET(card->ctx, r, "Error selecting parent."); /* We're now in the right place, reconstruct the APDU and retry */ path += 2; pathlen -= 2; apdu.lc = pathlen; apdu.data = path; apdu.datalen = pathlen; if (file_out != NULL) apdu.resplen = sizeof(buf); 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); if (r) SC_FUNC_RETURN(card->ctx, 2, r); if (apdu.resplen < 2) SC_FUNC_RETURN(card->ctx, 2, SC_ERROR_UNKNOWN_DATA_RECEIVED); switch (apdu.resp[0]) { case 0x6F: 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, 2, SC_ERROR_NOT_SUPPORTED); } if ((size_t)apdu.resp[1] + 2 <= apdu.resplen) card->ops->process_fci(card, file, apdu.resp+2, apdu.resp[1]); *file_out = file; break; case 0x00: /* proprietary coding */ SC_FUNC_RETURN(card->ctx, 2, SC_ERROR_UNKNOWN_DATA_RECEIVED); default: SC_FUNC_RETURN(card->ctx, 2, SC_ERROR_UNKNOWN_DATA_RECEIVED); } return SC_SUCCESS; } static struct sc_card_driver *sc_get_driver(void) { struct sc_card_driver *iso_drv = sc_get_iso7816_driver(); if (iso_ops == NULL) iso_ops = iso_drv->ops; /* Use the standard iso operations as default */ ias_ops = *iso_drv->ops; /* IAS specific functions */ ias_ops.select_file = ias_select_file; ias_ops.match_card = ias_match_card; ias_ops.init = ias_init; ias_ops.set_security_env = ias_set_security_env; ias_ops.compute_signature = ias_compute_signature; ias_ops.pin_cmd = ias_pin_cmd; return &ias_drv; } struct sc_card_driver *sc_get_ias_driver(void) { return sc_get_driver(); }