diff --git a/src/libopensc/card-mcrd.c b/src/libopensc/card-mcrd.c new file mode 100644 index 0000000000..ae69398b0d --- /dev/null +++ b/src/libopensc/card-mcrd.c @@ -0,0 +1,1188 @@ +/* + * card-mcrd.c: Support for MICARDO cards + * + * Copyright (C) 2001 Juha Yrjölä + * Copyright (C) 2002 g10 Code GmbH + * + * 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 "log.h" +#include "asn1.h" +#include "cardctl.h" +#include +#include +#include + +static const char *mcrd_atrs[] = { + "3B:FF:94:00:FF:80:B1:FE:45:1F:03:00:68:D2:76:00:00:28:FF" + ":05:1E:31:80:00:90:00:23", /* German BMI card */ + "3B:FE:94:00:FF:80:B1:FA:45:1F:03:45:73:74:45:49:44:20:76" + ":65:72:20:31:2E:30:43", /* EstEID (Estonian Big Brother card) */ + NULL +}; + + +const static struct sc_card_error mcrd_errors[] = { + { 0x6600, SC_ERROR_INCORRECT_PARAMETERS, "Error setting the security env"}, + { 0x66F0, SC_ERROR_INCORRECT_PARAMETERS, "No space left for padding"}, + { 0x69F0, SC_ERROR_NOT_ALLOWED, "Command not allowed"}, + { 0x6A89, SC_ERROR_FILE_ALREADY_EXISTS, "Files exists"}, + { 0x6A8A, SC_ERROR_FILE_ALREADY_EXISTS, "Application exists"}, +}; + +static int mcrd_check_sw(struct sc_card *card, int sw1, int sw2) +{ + const int err_count = sizeof(mcrd_errors)/sizeof(mcrd_errors[0]); + int i; + + for (i = 0; i < err_count; i++) + if (mcrd_errors[i].SWs == ((sw1 << 8) | sw2)) { + error(card->ctx, "%s\n", mcrd_errors[i].errorstr); + return mcrd_errors[i].errorno; + } + /* Fall back to ISO 7816 error codes */ + return iso7816_check_sw(card, sw1, sw2); +} + +static struct sc_card_operations mcrd_ops; +static const struct sc_card_driver mcrd_drv = { + "MICARDO 2 cards", + "mcrd", + &mcrd_ops +}; + +static const struct sc_card_operations *iso_ops = NULL; + +enum { + MCRD_SEL_MF = 0x00, + MCRD_SEL_DF = 0x01, + MCRD_SEL_EF = 0x02, + MCRD_SEL_AID = 0x04 +}; + +#define MFID 0x3F00 +#define EF_KeyD 0x0013 /* File with extra key information. */ +#define EF_Rule 0x0030 /* Default ACL file. */ + +#define MAX_CURPATH 10 + + +struct rule_record_s { + struct rule_record_s *next; + int recno; + size_t datalen; + u8 data[1]; +}; + +struct keyd_record_s { + struct keyd_record_s *next; + int recno; + size_t datalen; + u8 data[1]; +}; + + +struct df_info_s { + struct df_info_s *next; + unsigned short path[MAX_CURPATH]; + int pathlen; + struct rule_record_s *rule_file; /* keeps records of EF_Rule. */ + struct keyd_record_s *keyd_file; /* keeps records of EF_KeyD. */ +}; + +struct mcrd_priv_data { + unsigned short curpath[MAX_CURPATH]; /* The currently selected path. */ + int curpathlen; /* Length of this path or 0 if unknown. */ + int is_ef; /* True if the path points to an EF. */ + struct df_info_s *df_infos; +}; + +#define DRVDATA(card) ((struct mcrd_priv_data *) ((card)->drv_data)) + + +static int load_special_files(struct sc_card *card); +static int select_part (struct sc_card *card, u8 kind, unsigned short int fid, + struct sc_file **file); + +/* Return the DF_info for the current path. If does not yet exist, + create it. Returns NULL on error. */ +static struct df_info_s *get_df_info (struct sc_card *card) +{ + struct sc_context *ctx = card->ctx; + struct mcrd_priv_data *priv = DRVDATA (card); + struct df_info_s *dfi; + + assert (!priv->is_ef); + + if (!priv->curpathlen) { + debug(ctx, "no current path to find the df_info\n"); + return NULL; + } + + for (dfi = priv->df_infos; dfi; dfi = dfi->next) { + if (dfi->pathlen == priv->curpathlen + && !memcmp (dfi->path, priv->curpath, + dfi->pathlen *sizeof *dfi->path)) + return dfi; + } + /* Not found, create it. */ + dfi = calloc (1, sizeof *dfi); + if (!dfi) { + debug(ctx, "out of core while allocating df_info\n"); + return NULL; + } + dfi->pathlen = priv->curpathlen; + memcpy (dfi->path, priv->curpath, dfi->pathlen * sizeof *dfi->path); + dfi->next = priv->df_infos; + priv->df_infos = dfi; + return dfi; +} + + +static void clear_special_files (struct df_info_s *dfi) +{ + if (dfi) { + while (dfi->rule_file) { + struct rule_record_s *tmp = dfi->rule_file->next; + free (dfi->rule_file); + dfi->rule_file = tmp; + } + while (dfi->keyd_file) { + struct keyd_record_s *tmp = dfi->keyd_file->next; + free (dfi->keyd_file); + dfi->keyd_file = tmp; + } + } +} + + + +static int mcrd_match_card(struct sc_card *card) +{ + int i, match = -1; + + for (i = 0; mcrd_atrs[i] != NULL; i++) { + u8 defatr[SC_MAX_ATR_SIZE]; + size_t len = sizeof(defatr); + const char *atrp = mcrd_atrs[i]; + + if (sc_hex_to_bin(atrp, defatr, &len)) + continue; + if (len != card->atr_len) + continue; + if (memcmp(card->atr, defatr, len) != 0) + continue; + match = i; + break; + } + if (match == -1) + return 0; + + return 1; +} + +static int mcrd_init(struct sc_card *card) +{ + unsigned long flags; + struct mcrd_priv_data *priv; + + priv = calloc (1, sizeof *priv); + if (!priv) + return SC_ERROR_OUT_OF_MEMORY; + card->drv_data = priv; + card->cla = 0x00; + + flags = SC_ALGORITHM_RSA_RAW; + flags |= SC_ALGORITHM_RSA_PAD_PKCS1; + flags |= SC_ALGORITHM_RSA_HASH_NONE; + + _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); + + priv->curpath[0] = MFID; + priv->curpathlen = 1; + load_special_files (card); + + return 0; +} + +static int mcrd_finish(struct sc_card *card) +{ + struct mcrd_priv_data *priv; + + if (card) + return 0; + priv = DRVDATA (card); + while (priv->df_infos) { + struct df_info_s *tmp = priv->df_infos->next; + clear_special_files (priv->df_infos); + priv->df_infos = tmp; + } + free(priv); + return 0; +} + + + + + + +/* Load the rule and keyd file into our private data. + Return 0 on success */ +static int load_special_files (struct sc_card *card) +{ + struct sc_context *ctx = card->ctx; + struct mcrd_priv_data *priv = DRVDATA (card); + int r, recno; + struct df_info_s *dfi; + struct rule_record_s *rule; + struct keyd_record_s *keyd; + + assert (!priv->is_ef); + + /* First check whether we already cached it. */ + dfi = get_df_info (card); + if (dfi && dfi->rule_file) + return 0; /* yes. */ + clear_special_files (dfi); + + /* Read rule file. Note that we bypass our cache here. */ + r = select_part (card, 2, EF_Rule, NULL); + SC_TEST_RET(ctx, r, "selecting EF_Rule failed"); + + for (recno=1;; recno++) { + struct sc_apdu apdu; + u8 recvbuf[200]; + + sc_format_apdu(card, &apdu, SC_APDU_CASE_2_SHORT, + 0xB2, recno, 0x04); + apdu.le = sizeof recvbuf; + apdu.resplen = sizeof recvbuf; + apdu.resp = recvbuf; + r = sc_transmit_apdu(card, &apdu); + SC_TEST_RET(card->ctx, r, "APDU transmit failed"); + if (apdu.sw1 == 0x6a && apdu.sw2 == 0x83) + break; /* No more records. */ + if (!((apdu.sw1 == 0x90 && apdu.sw2 == 0x00) + ||(apdu.sw1 == 0x62 && apdu.sw2 == 0x82))) + SC_FUNC_RETURN(ctx, 2, + sc_check_sw(card, apdu.sw1, apdu.sw2)); + rule = malloc (sizeof*rule + apdu.resplen); + if (!rule) + SC_FUNC_RETURN(ctx, 0, SC_ERROR_OUT_OF_MEMORY); + rule->recno = recno; + rule->datalen = apdu.resplen; + memcpy (rule->data, apdu.resp, apdu.resplen); + rule->next = dfi->rule_file; + dfi->rule_file = rule; + } + + debug(ctx, "new EF_Rule file loaded (%d records)\n", recno-1); + + /* Read the KeyD file. Note that we bypass our cache here. */ + r = select_part (card, 2, EF_KeyD, NULL); + if (r == SC_ERROR_FILE_NOT_FOUND) { + debug(ctx, "no EF_KeyD file available\n"); + return 0; /* That is okay. */ + } + SC_TEST_RET(ctx, r, "selecting EF_KeyD failed"); + + for (recno=1;; recno++) { + struct sc_apdu apdu; + u8 recvbuf[200]; + + sc_format_apdu(card, &apdu, SC_APDU_CASE_2_SHORT, + 0xB2, recno, 0x04); + apdu.le = sizeof recvbuf; + apdu.resplen = sizeof recvbuf; + apdu.resp = recvbuf; + r = sc_transmit_apdu(card, &apdu); + SC_TEST_RET(card->ctx, r, "APDU transmit failed"); + if (apdu.sw1 == 0x6a && apdu.sw2 == 0x83) + break; /* No more records. */ + if (!((apdu.sw1 == 0x90 && apdu.sw2 == 0x00) + ||(apdu.sw1 == 0x62 && apdu.sw2 == 0x82))) + SC_FUNC_RETURN(ctx, 2, + sc_check_sw(card, apdu.sw1, apdu.sw2)); + keyd = malloc (sizeof *keyd + apdu.resplen); + if (!keyd) + SC_FUNC_RETURN(ctx, 0, SC_ERROR_OUT_OF_MEMORY); + keyd->recno = recno; + keyd->datalen = apdu.resplen; + memcpy (keyd->data, apdu.resp, apdu.resplen); + keyd->next = dfi->keyd_file; + dfi->keyd_file = keyd; + } + + debug(ctx, "new EF_KeyD file loaded (%d records)\n", recno-1); + /* fixme: Do we need to restore the current DF? I guess it is + not required, but we could try to do so by selecting 3fff? */ + return 0; +} + + + +/* Return the SE number from the keyD for the FID. If ref_data is not + NULL the reference data is returned; this shoudl be an array of at + least 2 bytes. Returns -1 on error. */ +static int get_se_num_from_keyd (struct sc_card *card, unsigned short fid, + u8 *ref_data) +{ + struct sc_context *ctx = card->ctx; + struct df_info_s *dfi; + struct keyd_record_s *keyd; + size_t len, taglen; + const u8 *p, *tag; + char dbgbuf[2048]; + u8 fidbuf[2]; + + fidbuf[0] = fid >> 8; + fidbuf[1] = fid; + + dfi = get_df_info (card); + if (!dfi || !dfi->keyd_file) { + debug (ctx, "EF_keyD not loaded\n"); + return -1; + } + + for (keyd=dfi->keyd_file; keyd; keyd = keyd->next) { + p = keyd->data; + len = keyd->datalen; + + sc_hex_dump (ctx, p,len, dbgbuf, sizeof dbgbuf); + debug (ctx, "keyd no %d:\n%s", keyd->recno, dbgbuf); + + tag = sc_asn1_find_tag(ctx, p, len, 0x83, &taglen); + if (!tag || taglen != 4 || + !(tag[2] == fidbuf[0] && tag[3] == fidbuf[1])) + continue; + /* Found a matching record. */ + if (ref_data) { + ref_data[0] = tag[0]; + ref_data[1] = tag[1]; + } + /* Look for the SE-DO */ + tag = sc_asn1_find_tag(ctx, p, len, 0x7B, &taglen); + if (!tag || !taglen) + continue; + p = tag; + len = taglen; + /* And now look for the referenced SE. */ + tag = sc_asn1_find_tag(ctx, p, len, 0x80, &taglen); + if (!tag || taglen != 1) + continue; + return *tag; /* found. */ + } + debug (ctx, "EF_keyD for %04hx not found\n", fid); + return -1; +} + +/* Process an ARR (7816-9/8.5.4) and setup the ACL. */ +static void process_arr(struct sc_card *card, struct sc_file *file, + const u8 *buf, size_t buflen) +{ + struct sc_context *ctx = card->ctx; + struct df_info_s *dfi; + struct rule_record_s *rule; + size_t left, taglen; + unsigned int cla, tag; + const u8 *p; + int skip; + char dbgbuf[2048]; + + /* Currently we support only the short for. */ + if (buflen != 1) { + debug (ctx, "can't handle long ARRs\n"); + return; + } + + dfi = get_df_info (card); + for (rule = dfi? dfi->rule_file:NULL; rule && rule->recno != *buf; + rule = rule->next) + ; + if (!rule) { + debug (ctx, "referenced EF_rule record %d not found\n", *buf); + return; + } + + if (ctx->debug) { + sc_hex_dump (ctx, rule->data, rule->datalen, dbgbuf, sizeof dbgbuf); + debug (ctx, "rule for record %d:\n%s", *buf, dbgbuf); + } + + p = rule->data; + left = rule->datalen; + skip = 1; /* Skip over initial unknown SC DOs. */ + for (;;) { + buf = p; + if (sc_asn1_read_tag(&p, left, &cla, &tag, &taglen) != 1) + break; + left -= (p - buf); + tag |= cla; + + if (tag == 0x80 && taglen != 1) { + skip = 1; + } + else if (tag == 0x80) { /* AM byte. */ + debug (ctx," AM_DO: %02x\n", *p); + skip = 0; + } + else if (tag >= 0x81 && tag <= 0x8f) {/* Cmd description */ + sc_hex_dump (ctx, p, taglen, dbgbuf, sizeof dbgbuf); + debug (ctx, " AM_DO: cmd[%s%s%s%s] %s", + (tag & 8)? "C":"", + (tag & 4)? "I":"", + (tag & 2)? "1":"", + (tag & 1)? "2":"", + dbgbuf); + skip = 0; + } + else if (tag == 0x9C) {/* Proprietary state machine descrip.*/ + skip = 1; + } + else if (!skip) { + sc_hex_dump (ctx, p, taglen, dbgbuf, sizeof dbgbuf); + switch (tag) { + case 0x90: /* Always */ + debug (ctx," SC: always\n"); + break; + case 0x97: /* Never */ + debug (ctx," SC: never\n"); + break; + case 0xA4: /* Authentication, value is a CRT. */ + debug (ctx," SC: auth %s", dbgbuf); + break; + + case 0xB4: + case 0xB6: + case 0xB8: /* Cmd or resp with SM, value is a CRT. */ + debug (ctx," SC: cmd/resp %s", dbgbuf); + break; + + case 0x9E: /* Security Condition byte. */ + debug (ctx," SC: condition %s", dbgbuf); + break; + + case 0xA0: /* OR template. */ + debug (ctx," SC: OR\n"); + break; + case 0xAF: /* AND template. */ + debug (ctx," SC: AND\n"); + break; + } + } + left -= taglen; + p += taglen; + } + +} + + +static void process_fcp(struct sc_card *card, struct sc_file *file, + const u8 *buf, size_t buflen) +{ + struct sc_context *ctx = card->ctx; + size_t taglen, len = buflen; + const u8 *tag = NULL, *p = buf; + int bad_fde = 0; + + if (ctx->debug >= 3) + debug(ctx, "processing FCI bytes\n"); + /* File identifier. */ + tag = sc_asn1_find_tag(ctx, p, len, 0x83, &taglen); + if (tag != NULL && taglen == 2) { + file->id = (tag[0] << 8) | tag[1]; + if (ctx->debug >= 3) + debug(ctx, " file identifier: 0x%02X%02X\n", tag[0], + tag[1]); + } + /* Number of data bytes in the file including structural information.*/ + tag = sc_asn1_find_tag(ctx, p, len, 0x81, &taglen); + if (!tag) { + /* My card does not encode the filelength in 0x81 but + in 0x85 which is the file descriptor extension in TCOS. + Assume that this is the case when the regular file + size tag is not encoded. */ + tag = sc_asn1_find_tag(ctx, p, len, 0x85, &taglen); + bad_fde = !!tag; + } + if (tag != NULL && taglen >= 2) { + int bytes = (tag[0] << 8) + tag[1]; + if (ctx->debug >= 3) + debug(ctx, " bytes in file: %d\n", bytes); + file->size = bytes; + } + if (tag == NULL) { + tag = sc_asn1_find_tag(ctx, p, len, 0x80, &taglen); + if (tag != NULL && taglen >= 2) { + int bytes = (tag[0] << 8) + tag[1]; + if (ctx->debug >= 3) + debug(ctx, " bytes in file: %d\n", bytes); + file->size = bytes; + } + } + + /* File descriptor byte(s). */ + tag = sc_asn1_find_tag(ctx, p, len, 0x82, &taglen); + if (tag != NULL) { + /* Fixme, this might actual be up to 6 bytes. */ + if (taglen > 0) { + unsigned char byte = tag[0]; + const char *type; + + file->shareable = byte & 0x40 ? 1 : 0; + if (ctx->debug >= 3) + debug(ctx, " shareable: %s\n", + (byte & 0x40) ? "yes" : "no"); + file->ef_structure = byte & 0x07; + switch ((byte >> 3) & 7) { + case 0: + type = "working EF"; + file->type = SC_FILE_TYPE_WORKING_EF; + break; + case 1: + type = "internal EF"; + file->type = SC_FILE_TYPE_INTERNAL_EF; + break; + case 7: + type = "DF"; + file->type = SC_FILE_TYPE_DF; + break; + default: + type = "unknown"; + break; + } + if (ctx->debug >= 3) { + debug(ctx, " type: %s\n", type); + debug(ctx, " EF structure: %d\n", + byte & 0x07); + } + } + } + + /* DF name. */ + tag = sc_asn1_find_tag(ctx, p, len, 0x84, &taglen); + if (tag != NULL && taglen > 0 && taglen <= 16) { + char name[17]; + int i; + + memcpy(file->name, tag, taglen); + file->namelen = taglen; + + for (i = 0; i < taglen; i++) { + if (isalnum(tag[i]) || ispunct(tag[i]) + || isspace(tag[i])) + name[i] = tag[i]; + else + name[i] = '?'; + } + name[taglen] = 0; + if (ctx->debug >= 3) + debug(ctx, " file name: %s\n", name); + } + + /* Proprietary information. */ + tag = bad_fde? NULL : sc_asn1_find_tag(ctx, p, len, 0x85, &taglen); + if (tag != NULL && taglen) { + sc_file_set_prop_attr(file, tag, taglen); + } else + file->prop_attr_len = 0; + + /* Proprietary information, constructed. */ + tag = sc_asn1_find_tag(ctx, p, len, 0xA5, &taglen); + if (tag != NULL && taglen) { + sc_file_set_prop_attr(file, tag, taglen); + } + + /* Security attributes, proprietary format. */ + tag = sc_asn1_find_tag(ctx, p, len, 0x86, &taglen); + if (tag != NULL && taglen) { + sc_file_set_sec_attr(file, tag, taglen); + } + + /* Security attributes, reference to expanded format. */ + tag = sc_asn1_find_tag(ctx, p, len, 0x8B, &taglen); + if (tag && taglen) { + process_arr (card, file, tag, taglen); + } + else if ((tag = sc_asn1_find_tag(ctx, p, len, 0xA1, &taglen)) + && taglen) { + /* Not found, but there is a Security Attribute + Template for interface mode. */ + tag = sc_asn1_find_tag(ctx, tag, taglen, 0x8B, &taglen); + if (tag && taglen) + process_arr (card, file, tag, taglen); + } + + file->magic = SC_FILE_MAGIC; +} + + +/* Send a select command and parse the response. */ +static int +do_select(struct sc_card *card, u8 kind, + const u8 *buf, size_t buflen, + struct sc_file **file) +{ + struct sc_apdu apdu; + u8 resbuf[SC_MAX_APDU_BUFFER_SIZE]; + int r; + + /* do the apdu thing */ + memset(&apdu, 0, sizeof(apdu)); + apdu.cla = 0x00; + apdu.cse = SC_APDU_CASE_3_SHORT; + apdu.ins = 0xA4; + apdu.p1 = kind; + apdu.p2 = 0; + apdu.data = buf; + apdu.datalen = buflen; + apdu.lc = apdu.datalen; + apdu.resp = resbuf; + apdu.resplen = file? sizeof(resbuf) : 0; + + r = sc_transmit_apdu(card, &apdu); + SC_TEST_RET(card->ctx, r, "APDU transmit failed"); + if (!file) { + if (apdu.sw1 == 0x61) + SC_FUNC_RETURN(card->ctx, 2, 0); + r = sc_check_sw(card, apdu.sw1, apdu.sw2); + if (!r && kind == MCRD_SEL_AID) + card->cache.current_path.len = 0; + SC_FUNC_RETURN(card->ctx, 2, r); + } + r = sc_check_sw(card, apdu.sw1, apdu.sw2); + if (r) + SC_FUNC_RETURN(card->ctx, 2, r); + + switch (apdu.resp[0]) { + case 0x6F: + *file = sc_file_new(); + if (!*file) + SC_FUNC_RETURN(card->ctx, 0, SC_ERROR_OUT_OF_MEMORY); + if (apdu.resp[1] <= apdu.resplen) + process_fcp (card, *file, + apdu.resp+2, apdu.resp[1]); + 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 0; +} + +/* Wrapper around do_select to be used when multiple selects are + required. */ +static int +select_part (struct sc_card *card, u8 kind, unsigned short int fid, + struct sc_file **file) +{ + u8 fbuf[2]; + int r, log_errs; + + if (card->ctx->debug >=3) + debug(card->ctx, "select_part (0x%04X, kind=%u)\n", + fid, kind); + + if (fid == MFID) + kind = MCRD_SEL_MF; /* force this kind. */ + + fbuf[0] = fid >> 8; + fbuf[1] = fid & 0xff; + log_errs = card->ctx->log_errors; + card->ctx->log_errors = 0; + r = do_select (card, kind, fbuf, 2, file); + card->ctx->log_errors = log_errs; + + return r; +} + + +/* Select a file by iterating over the FID in the PATHPTR array while + updating the curpath kept in the private data cache. With DF_ONLY + passed as true only DF are selected, otherwise the function tries + to figure out whether the last path item is a DF or EF. */ +static int +select_down (struct sc_card *card, + unsigned short *pathptr, size_t pathlen, + int df_only, + struct sc_file **file) +{ + struct mcrd_priv_data *priv = DRVDATA (card); + int r; + int found_ef = 0; + + if (!pathlen) + return SC_ERROR_INVALID_ARGUMENTS; + + for (; pathlen; pathlen--, pathptr++) { + if (priv->curpathlen == MAX_CURPATH) + SC_TEST_RET (card->ctx, SC_ERROR_INTERNAL, + "path too long for cache"); + r = -1; /* force DF select. */ + if (pathlen == 1 && !df_only) { + /* first try to select an EF and retry an DF + on error. */ + r = select_part (card, MCRD_SEL_EF,*pathptr, file); + if (!r) + found_ef = 1; + } + if (r) + r = select_part (card, MCRD_SEL_DF, *pathptr, + pathlen == 1? file : NULL); + SC_TEST_RET(card->ctx, r, "unable to select DF"); + priv->curpath[priv->curpathlen] = *pathptr; + priv->curpathlen++; + } + priv->is_ef = found_ef; + if (!found_ef) + load_special_files (card); + + return 0; +} + +/* Handle the selection case when a PATH is requested. Our card does + not support this addressing so we have to emulate it. To keep the + security status we should not unnecessary change the directory; + this is accomplished be keeping track of the currently selected + file. Note that PATH is an array of PATHLEN file ids and not the + usual sc_path structure. */ +static int +select_file_by_path (struct sc_card *card, unsigned short *pathptr, + size_t pathlen, + struct sc_file **file) +{ + struct mcrd_priv_data *priv = DRVDATA (card); + int r; + int i; + + assert (!priv->curpathlen || priv->curpath[0] == MFID); + + if (pathlen && *pathptr == 0x3FFF) { + pathlen--; + pathptr++; + } + + + if (!pathlen || pathlen >= MAX_CURPATH) + r = SC_ERROR_INVALID_ARGUMENTS; + else if (pathlen == 1 && pathptr[0] == MFID) { + /* MF requested: clear the cache and select it. */ + priv->curpathlen = 0; + r = select_part (card, MCRD_SEL_MF, pathptr[0], file); + SC_TEST_RET(card->ctx, r, "unable to select MF"); + priv->curpath[0] = pathptr[0]; + priv->curpathlen = 1; + priv->is_ef = 0; + } + else if (pathlen > 1 && pathptr[0] == MFID) { + /* Absolute addressing, check cache to avoid + unnecessary selects. */ + for (i=0; (i < pathlen && i < priv->curpathlen + && pathptr[i] == priv->curpath[i]); i++) + ; + if (!priv->curpathlen) { + /* Need to do all selects starting at the root. */ + priv->curpathlen = 0; + priv->is_ef = 0; + r = select_down (card, pathptr, pathlen, 0, file); + } + else if ( i==pathlen && i < priv->curpathlen) { + /* Go upwards; we do it the easy way and start + at the root. However we know that the + target is a DF. */ + priv->curpathlen = 0; + priv->is_ef = 0; + r = select_down (card, pathptr, pathlen, 1, file); + } + else if (i == pathlen && i == priv->curpathlen) { + /* Already selected. */ + if (!file) + r=0; /* The caller did not request the fci. */ + else { + /* This EF or DF was already selected, but + we need to get the FCI, so we have + to select again. */ + assert (priv->curpathlen > 1); + priv->curpathlen--; + priv->is_ef = 0; + r = select_down (card, + pathptr+pathlen-1, 1, + 0, file); + } + } + else { + /* We have to append something. For now we + simply start at the root. (fixme) */ + priv->curpathlen = 0; + priv->is_ef = 0; + r = select_down (card, pathptr, pathlen, 0, file); + } + } + else { + /* Relative addressing. */ + if (!priv->curpathlen) { + /* Relative addressing without a current path. So we + select the MF first. */ + r = select_part (card, MCRD_SEL_MF, + pathptr[0], file); + SC_TEST_RET(card->ctx, r, "unable to select MF"); + priv->curpath[0] = pathptr[0]; + priv->curpathlen = 1; + priv->is_ef = 0; + } + if (priv->is_ef) { + assert (priv->curpathlen > 1); + priv->curpathlen--; + priv->is_ef = 0; + } + r = select_down (card, pathptr, pathlen, 0, file); + } + + return r; +} + +static int +select_file_by_fid (struct sc_card *card, unsigned short *pathptr, + size_t pathlen, struct sc_file **file) +{ + struct mcrd_priv_data *priv = DRVDATA (card); + int r; + + assert (!priv->curpathlen || priv->curpath[0] == MFID); + + if (pathlen > 1) + return SC_ERROR_INVALID_ARGUMENTS; + + + if (pathlen && *pathptr == 0x3FFF) + return 0; + + if (!pathlen) { + /* re-select the current one if needed. */ + if (!file) + r=0; /* The caller did not request the fci. */ + else if (!priv->curpathlen) { + /* There is no current file. */ + r = SC_ERROR_INTERNAL; + } + else { + assert (priv->curpathlen > 1); + priv->curpathlen--; + priv->is_ef = 0; + r = select_down (card, pathptr, 1, 0, file); + } + } + else if (pathptr[0] == MFID) { + /* MF requested: clear the cache and select it. */ + priv->curpathlen = 0; + r = select_part (card, MCRD_SEL_MF, MFID, file); + SC_TEST_RET(card->ctx, r, "unable to select MF"); + priv->curpath[0] = MFID; + priv->curpathlen = 1; + priv->is_ef = 0; + } + else { + /* Relative addressing. */ + if (!priv->curpathlen) { + /* Relative addressing without a current path. So we + select the MF first. */ + r = select_part (card, MCRD_SEL_MF, + pathptr[0], file); + SC_TEST_RET(card->ctx, r, "unable to select MF"); + priv->curpath[0] = pathptr[0]; + priv->curpathlen = 1; + priv->is_ef = 0; + } + if (priv->is_ef) { + assert (priv->curpathlen > 1); + priv->curpathlen--; + priv->is_ef = 0; + } + r = select_down (card, pathptr, 1, 0, file); + } + + return r; +} + + +/* This drivers select command handler. */ +static int +mcrd_select_file(struct sc_card *card, const struct sc_path *path, + struct sc_file **file) +{ + struct mcrd_priv_data *priv = DRVDATA (card); + int r = 0; + + SC_FUNC_CALLED(card->ctx, 1); + if (card->ctx->debug >= 3) { + char line[256], *linep = line; + int i; + + linep += sprintf(linep, + "requesting type %d, path ", + path->type); + for (i = 0; i < path->len; i++) { + sprintf(linep, "%02X", path->value[i]); + linep += 2; + } + strcpy(linep, "\n"); + debug(card->ctx, line); + + linep = line; + linep += sprintf(linep, " ef=%d, curpath=", + priv->is_ef); + for (i=0; i < priv->curpathlen; i++) { + sprintf(linep, "%04X", priv->curpath[i]); + linep += 4; + } + strcpy(linep, "\n"); + debug(card->ctx, line); + } + + if (path->type == SC_PATH_TYPE_DF_NAME) { + if (path->len > 16) + return SC_ERROR_INVALID_ARGUMENTS; + r = do_select(card, MCRD_SEL_AID, + path->value, path->len, file); + priv->curpathlen = 0; + } + else { + unsigned short int pathtmp[SC_MAX_PATH_SIZE/2]; + unsigned short int *pathptr; + size_t pathlen, n; + + if ((path->len & 1) || path->len > sizeof(pathtmp)) + return SC_ERROR_INVALID_ARGUMENTS; + pathptr = pathtmp; + for (n = 0; n < path->len; n += 2) + pathptr[n>>1] = (path->value[n] << 8)|path->value[n+1]; + pathlen = path->len >> 1; + + if (path->type == SC_PATH_TYPE_PATH) + r = select_file_by_path (card, pathptr, pathlen, file); + else { /* SC_PATH_TYPE_FILEID */ + r = select_file_by_fid (card, pathptr, pathlen, file); + } + } + + if (card->ctx->debug >= 3) { + char line[256], *linep = line; + int i; + + linep += sprintf(linep, " result=%d, ef=%d, curpath=", + r, priv->is_ef); + for (i=0; i < priv->curpathlen; i++) { + sprintf(linep, "%04X", priv->curpath[i]); + linep += 4; + } + strcpy(linep, "\n"); + debug(card->ctx, line); + } + + + return r; +} + + +/* Crypto operations */ + +static int restore_se (struct sc_card *card, int se_num) +{ + struct sc_apdu apdu; + int r; + + sc_format_apdu(card, &apdu, SC_APDU_CASE_1, 0x22, 0xF3, se_num); + r = sc_transmit_apdu(card, &apdu); + SC_TEST_RET(card->ctx, r, "APDU transmit failed"); + return sc_check_sw(card, apdu.sw1, apdu.sw2); +} + + + +/* It seems that MICARDO does not fully comply with ISO, so I use + values gathered from peeking actual signing opeations using a + different system. */ +static int mcrd_set_security_env(struct sc_card *card, + const struct sc_security_env *env, + int se_num) +{ + struct sc_apdu apdu; + u8 sbuf[SC_MAX_APDU_BUFFER_SIZE]; + u8 *p; + int r, locked = 0; + + assert(card != NULL && env != NULL); + if (se_num) + SC_FUNC_RETURN(card->ctx, 1, SC_ERROR_INVALID_ARGUMENTS); + + sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, 0x22, 0, 0); + apdu.le = 0; + p = sbuf; + 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; + } + *p++ = 0x83; + *p++ = 0x03; + *p++ = 0x80; + if ((env->flags & SC_SEC_ENV_FILE_REF_PRESENT) + && env->file_ref.len > 1) { + unsigned short fid; + int num; + + fid = env->file_ref.value[env->file_ref.len-2] << 8; + fid |= env->file_ref.value[env->file_ref.len-1]; + num = get_se_num_from_keyd (card, fid, p); + if (num != -1) { + /* Need to restore the security environmnet. */ + if (num) { + r = restore_se (card, num); + SC_TEST_RET(card->ctx, r, "restore_se failed"); + } + p += 2; + } + } + else { + return SC_ERROR_INVALID_ARGUMENTS; + } + + r = p - sbuf; + apdu.lc = r; + apdu.datalen = r; + apdu.data = sbuf; + apdu.resplen = 0; + 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_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; +} + +static int mcrd_compute_signature(struct sc_card *card, + const u8 * data, size_t datalen, + u8 * out, size_t outlen) +{ + int r; + struct sc_apdu apdu; + u8 rbuf[SC_MAX_APDU_BUFFER_SIZE]; + u8 sbuf[SC_MAX_APDU_BUFFER_SIZE]; + + assert(card != NULL && data != NULL && out != NULL); + if (datalen > 255) + SC_FUNC_RETURN(card->ctx, 4, SC_ERROR_INVALID_ARGUMENTS); + + /* INS: 0x2A PERFORM SECURITY OPERATION + * P1: 0x9E Resp: Digital Signature + * P2: 0x9A Cmd: Input for Digital Signature */ + sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, 0x2A, 0x9E, + 0x9A); + apdu.resp = rbuf; + apdu.resplen = sizeof(rbuf); /* FIXME */ + + memcpy(sbuf, data, datalen); + apdu.data = sbuf; + apdu.lc = datalen; + apdu.datalen = datalen; + apdu.sensitive = 1; + r = sc_transmit_apdu(card, &apdu); + SC_TEST_RET(card->ctx, r, "APDU transmit failed"); + if (apdu.sw1 == 0x90 && apdu.sw2 == 0x00) { + int len = apdu.resplen > outlen ? outlen : apdu.resplen; + + memcpy(out, apdu.resp, len); + SC_FUNC_RETURN(card->ctx, 4, len); + } + else if (apdu.sw1 == 0x60 && apdu.sw2 == 0x61) { + /* This might be a problem with by Cardman driver. + Status codes 60xx should never been seen at this + layer, so I assume 0x6180 here. FIXME! */ + int len; + + sc_format_apdu(card, &apdu, SC_APDU_CASE_2_SHORT, + 0xC0, 0, 0); + apdu.le = 0x80; + apdu.resp = rbuf; + apdu.resplen = 0x80; + r = sc_transmit_apdu(card, &apdu); + SC_TEST_RET(card->ctx, r, "APDU transmit failed"); + len = apdu.resplen > outlen ? outlen : apdu.resplen; + memcpy(out, apdu.resp, len); + SC_FUNC_RETURN(card->ctx, 4, len); + } + + SC_FUNC_RETURN(card->ctx, 4, sc_check_sw(card, apdu.sw1, apdu.sw2)); +} + + + + +/* Driver binding stuff */ +static const struct sc_card_driver * sc_get_driver(void) +{ + const struct sc_card_driver *iso_drv = sc_get_iso7816_driver(); + + mcrd_ops = *iso_drv->ops; + mcrd_ops.match_card = mcrd_match_card; + mcrd_ops.init = mcrd_init; + mcrd_ops.finish = mcrd_finish; + if (iso_ops == NULL) + iso_ops = iso_drv->ops; + mcrd_ops.select_file = mcrd_select_file; + mcrd_ops.set_security_env = mcrd_set_security_env; + mcrd_ops.compute_signature = mcrd_compute_signature; + + return &mcrd_drv; +} + +const struct sc_card_driver * sc_get_mcrd_driver(void) +{ + return sc_get_driver(); +} diff --git a/src/libopensc/ctx.c b/src/libopensc/ctx.c index c645c7c470..efe73f5126 100644 --- a/src/libopensc/ctx.c +++ b/src/libopensc/ctx.c @@ -53,6 +53,7 @@ static const struct _sc_driver_entry internal_card_drivers[] = { { "gpk", (void *) sc_get_gpk_driver, NULL }, #endif { "etoken", (void *) sc_get_etoken_driver, NULL }, + { "mcrd", (void *) sc_get_mcrd_driver, NULL }, /* The default driver should be last, as it handles all the * unrecognized cards. */ { "default", (void *) sc_get_default_driver, NULL }, diff --git a/src/libopensc/opensc.h b/src/libopensc/opensc.h index 99b8a5d0cd..da7d7646bd 100644 --- a/src/libopensc/opensc.h +++ b/src/libopensc/opensc.h @@ -654,7 +654,7 @@ int sc_delete_file(struct sc_card *card, const struct sc_path *path); int sc_card_ctl(struct sc_card *card, unsigned long command, void *arg); inline int sc_file_valid(const struct sc_file *file); -struct sc_file * sc_file_new(); +struct sc_file * sc_file_new(void); void sc_file_free(struct sc_file *file); void sc_file_dup(struct sc_file **dest, const struct sc_file *src); @@ -702,6 +702,7 @@ extern const struct sc_card_driver *sc_get_gpk_driver(void); extern const struct sc_card_driver *sc_get_tcos_driver(void); extern const struct sc_card_driver *sc_get_default_driver(void); extern const struct sc_card_driver *sc_get_etoken_driver(void); +extern const struct sc_card_driver *sc_get_mcrd_driver(void); #ifdef __cplusplus }