diff --git a/src/libopensc/card-tcos.c b/src/libopensc/card-tcos.c index 9c042c68..2849f5a9 100644 --- a/src/libopensc/card-tcos.c +++ b/src/libopensc/card-tcos.c @@ -1,7 +1,8 @@ /* * card-tcos.c: Support for TCOS 2.0 cards * - * Copyright (C) 2001, 2002 Juha Yrjölä + * 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 @@ -20,7 +21,10 @@ #include "internal.h" #include "log.h" +#include "asn1.h" +#include "cardctl.h" #include +#include static const char *tcos_atrs[] = { "3B:BA:13:00:81:31:86:5D:00:64:05:0A:02:01:31:80:90:00:8B", /* SLE44 */ @@ -36,6 +40,38 @@ static const struct sc_card_driver tcos_drv = { &tcos_ops }; +static const struct sc_card_operations *iso_ops = NULL; + +const static struct sc_card_error tcos_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 tcos_check_sw(struct sc_card *card, int sw1, int sw2) +{ + const int err_count = sizeof(tcos_errors)/sizeof(tcos_errors[0]); + int i; + + if (sw1 == 0x90) + return SC_NO_ERROR; + if (sw1 == 0x63 && (sw2 & ~0x0f) == 0xc0 ) { + error(card->ctx, "Verification failed (remaining tries: %d)\n", + (sw2 & 0x0f)); + return SC_ERROR_PIN_CODE_INCORRECT; + } + + for (i = 0; i < err_count; i++) + if (tcos_errors[i].SWs == ((sw1 << 8) | sw2)) { + error(card->ctx, "%s\n", tcos_errors[i].errorstr); + return tcos_errors[i].errorno; + } + return iso_ops->check_sw(card, sw1, sw2); +} + + static int tcos_finish(struct sc_card *card) { return 0; @@ -67,67 +103,464 @@ static int tcos_match_card(struct sc_card *card) static int tcos_init(struct sc_card *card) { + unsigned long flags; + card->drv_data = NULL; 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); + + return 0; +} + + +/* Hmmm, I don't know what to do. It seems that the ACL design of + OpenSC should be enhanced to allow for the command based security + attributes of TCOS. FIXME: This just allows to create a very basic + file. */ +static int tcos_construct_fci(const struct sc_file *file, + u8 *out, size_t *outlen) +{ + u8 *p = out; + u8 buf[64]; + int n; + + /* FIXME: possible buffer overflow */ + + *p++ = 0x6F; /* FCI */ + p++; + + /* File size */ + buf[0] = (file->size >> 8) & 0xFF; + buf[1] = file->size & 0xFF; + sc_asn1_put_tag(0x81, buf, 2, p, 16, &p); + + /* File descriptor */ + n = 0; + buf[n] = file->shareable ? 0x40 : 0; + switch (file->type) { + case SC_FILE_TYPE_WORKING_EF: + break; + case SC_FILE_TYPE_DF: + buf[0] |= 0x38; + break; + default: + return SC_ERROR_NOT_SUPPORTED; + } + buf[n++] |= file->ef_structure & 7; + if ( (file->ef_structure & 7) > 1) { + /* record structured file */ + buf[n++] = 0x41; /* indicate 3rd byte */ + buf[n++] = file->record_length; + } + sc_asn1_put_tag(0x82, buf, n, p, 8, &p); + + /* File identifier */ + buf[0] = (file->id >> 8) & 0xFF; + buf[1] = file->id & 0xFF; + sc_asn1_put_tag(0x83, buf, 2, p, 16, &p); + + /* Directory name */ + if (file->type == SC_FILE_TYPE_DF) { + if (file->namelen) { + if (file->namelen > 16 || !file->name) + return SC_ERROR_INVALID_ARGUMENTS; + sc_asn1_put_tag(0x84, file->name, file->namelen, + p, 16, &p); + } + else { + /* TCOS needs one, so we use a faked one */ + snprintf (buf, sizeof(buf)-1, "foo-%lu", + (unsigned long) time (NULL)); + sc_asn1_put_tag(0x84, buf, strlen (buf), p, 16, &p); + } + } + + /* File descriptor extension */ + if (file->prop_attr_len && file->prop_attr) { + n = file->prop_attr_len; + memcpy(buf, file->prop_attr, n); + } + else { + n = 0; + buf[n++] = 0x01; /* not invalidated, permanent */ + if (file->type == SC_FILE_TYPE_WORKING_EF) + buf[n++] = 0x00; /* generic data file */ + } + sc_asn1_put_tag(0x85, buf, n, p, 16, &p); + + /* Security attributes */ + if (file->sec_attr_len && file->sec_attr) { + memcpy(buf, file->sec_attr, file->sec_attr_len); + n = file->sec_attr_len; + } + else { + /* no attributes given - fall back to default one */ + memcpy (buf+ 0, "\xa4\x00\x00\x00\xff\xff", 6); /* select */ + memcpy (buf+ 6, "\xb0\x00\x00\x00\xff\xff", 6); /* read bin */ + memcpy (buf+12, "\xd6\x00\x00\x00\xff\xff", 6); /* upd bin */ + memcpy (buf+18, "\x60\x00\x00\x00\xff\xff", 6); /* admin grp*/ + n = 24; + } + sc_asn1_put_tag(0x86, buf, n, p, sizeof (buf), &p); + + + /* fixup length of FCI */ + out[1] = p - out - 2; + + *outlen = p - out; return 0; } -static const struct sc_card_operations *iso_ops = NULL; static int tcos_create_file(struct sc_card *card, struct sc_file *file) { - struct sc_file tmp; + int r; + size_t len; + u8 sbuf[SC_MAX_APDU_BUFFER_SIZE]; + struct sc_apdu apdu; + + len = SC_MAX_APDU_BUFFER_SIZE; + r = tcos_construct_fci(file, sbuf, &len); + SC_TEST_RET(card->ctx, r, "tcos_construct_fci() failed"); - tmp = *file; - memcpy(tmp.prop_attr, "\x03\x00\x00", 3); - tmp.prop_attr_len = 3; - return iso_ops->create_file(card, &tmp); + sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, 0xE0, 0x00, 0x00); + apdu.cla |= 0x80; /* this is an proprietary extension */ + apdu.lc = len; + apdu.datalen = len; + apdu.data = sbuf; + + r = sc_transmit_apdu(card, &apdu); + SC_TEST_RET(card->ctx, r, "APDU transmit failed"); + return sc_check_sw(card, apdu.sw1, apdu.sw2); } -static int tcos_set_security_env(struct sc_card *card, - const struct sc_security_env *env, - int se_num) + + +static int map_operations (int commandbyte ) { - if (env->flags & SC_SEC_ENV_ALG_PRESENT) { - struct sc_security_env tmp; + int op = -1; - tmp = *env; - tmp.flags &= ~SC_SEC_ENV_ALG_PRESENT; - tmp.flags |= SC_SEC_ENV_ALG_REF_PRESENT; - if (tmp.algorithm != SC_ALGORITHM_RSA) { - error(card->ctx, "Only RSA algorithm supported.\n"); - return SC_ERROR_NOT_SUPPORTED; - } - tmp.algorithm_ref = 0x00; - if (tmp.algorithm_flags & SC_ALGORITHM_RSA_PAD_PKCS1) - tmp.algorithm_ref = 0x02; -#if 0 - if (tmp.algorithm_flags & SC_ALGORITHM_RSA_HASH_SHA1) - tmp.algorithm_ref |= 0x10; -#endif - return iso_ops->set_security_env(card, &tmp, se_num); + switch ( (commandbyte & 0xfe) ) + { + case 0xe2: /* append record */ op = SC_AC_OP_UPDATE; break; + case 0x24: /* change password */ op = SC_AC_OP_UPDATE; break; + case 0xe0: /* create */ op = SC_AC_OP_CREATE; break; + case 0xe4: /* delete */ op = SC_AC_OP_DELETE; break; + case 0xe8: /* exclude sfi */ op = SC_AC_OP_WRITE; break; + case 0x82: /* external auth */ op = SC_AC_OP_READ; break; + case 0xe6: /* include sfi */ op = SC_AC_OP_WRITE; break; + case 0x88: /* internal auth */ op = SC_AC_OP_READ; break; + case 0x04: /* invalidate */ op = SC_AC_OP_INVALIDATE; break; + case 0x2a: /* perform sec. op */ op = SC_AC_OP_SELECT; break; + case 0xb0: /* read binary */ op = SC_AC_OP_READ; break; + case 0xb2: /* read record */ op = SC_AC_OP_READ; break; + case 0x44: /* rehabilitate */ op = SC_AC_OP_REHABILITATE; break; + case 0xa4: /* select */ op = SC_AC_OP_SELECT; break; + case 0xee: /* set permanent */ op = SC_AC_OP_CREATE; break; + case 0x2c: /* unblock password */op = SC_AC_OP_WRITE; break; + case 0xd6: /* update binary */ op = SC_AC_OP_WRITE; break; + case 0xdc: /* update record */ op = SC_AC_OP_WRITE; break; + case 0x20: /* verify password */ op = SC_AC_OP_SELECT; break; + case 0x60: /* admin group */ op = SC_AC_OP_CREATE; break; + } + return op; +} + + + +/* Hmmm, I don't know what to do. It seems that the ACL design of + OpenSC should be enhacned to allow for the command based security + attributes of TCOS. FIXME: This just allows to create a very basic + file. */ +static void parse_sec_attr(struct sc_card *card, + struct sc_file *file, const u8 *buf, size_t len) +{ + int op; + + /* list directory is not covered by ACLs - so always add an entry */ + sc_file_add_acl_entry (file, SC_AC_OP_LIST_FILES, + SC_AC_NONE, SC_AC_KEY_REF_NONE); + /* FIXME: check for what LOCK is used */ + sc_file_add_acl_entry (file, SC_AC_OP_LOCK, + SC_AC_NONE, SC_AC_KEY_REF_NONE); + for (; len >= 6; len -= 6, buf += 6) { + /* FIXME: temporary hacks */ + if (!memcmp(buf, "\xa4\x00\x00\x00\xff\xff", 6)) /* select */ + sc_file_add_acl_entry (file, SC_AC_OP_SELECT, + SC_AC_NONE, SC_AC_KEY_REF_NONE); + else if (!memcmp(buf, "\xb0\x00\x00\x00\xff\xff", 6)) /*read*/ + sc_file_add_acl_entry (file, SC_AC_OP_READ, + SC_AC_NONE, SC_AC_KEY_REF_NONE); + else if (!memcmp(buf, "\xd6\x00\x00\x00\xff\xff", 6)) /*upd*/ + sc_file_add_acl_entry (file, SC_AC_OP_UPDATE, + SC_AC_NONE, SC_AC_KEY_REF_NONE); + else if (!memcmp(buf, "\x60\x00\x00\x00\xff\xff", 6)) {/*adm */ + sc_file_add_acl_entry (file, SC_AC_OP_WRITE, + SC_AC_NONE, SC_AC_KEY_REF_NONE); + sc_file_add_acl_entry (file, SC_AC_OP_CREATE, + SC_AC_NONE, SC_AC_KEY_REF_NONE); + sc_file_add_acl_entry (file, SC_AC_OP_INVALIDATE, + SC_AC_NONE, SC_AC_KEY_REF_NONE); + sc_file_add_acl_entry (file, SC_AC_OP_REHABILITATE, + SC_AC_NONE, SC_AC_KEY_REF_NONE); + } + else { + /* the first byte tells use the command or the + command group. We have to mask bit 0 + because this one distinguish between AND/OR + combination of PINs*/ + op = map_operations (buf[0]); + if (op == -1) + { + debug (card->ctx, + "Unknown security command byte %02x\n", + buf[0]); + continue; + } + if (!buf[1]) + sc_file_add_acl_entry (file, op, + SC_AC_NONE, + SC_AC_KEY_REF_NONE); + else + sc_file_add_acl_entry (file, op, + SC_AC_CHV, buf[1]); + + if (!buf[2] && !buf[3]) + sc_file_add_acl_entry (file, op, + SC_AC_NONE, + SC_AC_KEY_REF_NONE); + else + sc_file_add_acl_entry (file, op, + SC_AC_TERM, + (buf[2]<<8)|buf[3]); + } + } +} + +/* Arghh. duplicated from iso7816.c */ +static void tcos_process_fci(struct sc_context *ctx, struct sc_file *file, + const u8 *buf, size_t buflen) +{ + size_t taglen, len = buflen; + const u8 *tag = NULL, *p = buf; + + if (ctx->debug >= 3) + debug(ctx, "processing FCI bytes\n"); + 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]); } - return iso_ops->set_security_env(card, env, se_num); + tag = sc_asn1_find_tag(ctx, p, len, 0x81, &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; + } + 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; + } + } + tag = sc_asn1_find_tag(ctx, p, len, 0x82, &taglen); + if (tag != NULL) { + 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); + } + } + } + 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); + } + tag = 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; + tag = sc_asn1_find_tag(ctx, p, len, 0xA5, &taglen); + if (tag != NULL && taglen) { + sc_file_set_prop_attr(file, tag, taglen); + } + tag = sc_asn1_find_tag(ctx, p, len, 0x86, &taglen); + if (tag != NULL && taglen) { + sc_file_set_sec_attr(file, tag, taglen); + } + file->magic = SC_FILE_MAGIC; } -static void parse_sec_attr(struct sc_file *file, const u8 *buf, size_t len) + +/* This is a special version of the standard select_file which is + needed to cope with some starngeness in APDU construction. It is + probably better to have this specfic for TCOS, so that support for + other cards does not break. */ +static int hacked_iso7816_select_file(struct sc_card *card, + const struct sc_path *in_path, + struct sc_file **file_out) { - return; + struct sc_context *ctx; + struct sc_apdu apdu; + u8 buf[SC_MAX_APDU_BUFFER_SIZE]; + u8 pathbuf[SC_MAX_PATH_SIZE], *path = pathbuf; + int r, pathlen; + struct sc_file *file = NULL; + + assert(card != NULL && in_path != NULL); + ctx = card->ctx; + 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); + + switch (in_path->type) { + case SC_PATH_TYPE_FILE_ID: + apdu.p1 = 0; + 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 = 8; + if (pathlen >= 2 && memcmp(path, "\x3F\x00", 2) == 0) { + if (pathlen == 2) { /* only 3F00 supplied */ + apdu.p1 = 0; + break; + } + path += 2; + pathlen -= 2; + } + break; + default: + SC_FUNC_RETURN(card->ctx, 2, SC_ERROR_INVALID_ARGUMENTS); + } + apdu.p2 = 0; /* first record, return FCI */ + apdu.lc = pathlen; + apdu.data = path; + apdu.datalen = pathlen; + + if (file_out != NULL) { + apdu.resp = buf; + apdu.resplen = sizeof(buf); + apdu.le = 255; /* 256 will be represented as 0 which + conflicts with the apdu sanity check */ + } else { + apdu.resp = buf; + apdu.resplen = sizeof(buf); + apdu.le = 255; + /* does not work apdu.cse = SC_APDU_CASE_3_SHORT;*/ + } + if (!apdu.lc) /* never send an empty lc */ + apdu.cse = SC_APDU_CASE_2_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); + if (r) + SC_FUNC_RETURN(card->ctx, 2, r); + + 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 (apdu.resp[1] <= apdu.resplen) + tcos_process_fci(card->ctx, 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 0; } + + static int tcos_select_file(struct sc_card *card, const struct sc_path *in_path, struct sc_file **file) { int r; - r = iso_ops->select_file(card, in_path, file); + /*r = iso_ops->select_file(card, in_path, file);*/ + r = hacked_iso7816_select_file(card, in_path, file); if (r) return r; - if (file != NULL) - parse_sec_attr((*file), (*file)->sec_attr, (*file)->sec_attr_len); + + if (file) { + parse_sec_attr(card, (*file), (*file)->sec_attr, + (*file)->sec_attr_len); + } + return 0; } @@ -160,6 +593,157 @@ static int tcos_list_files(struct sc_card *card, u8 *buf, size_t buflen) return count; } + + +static int tcos_delete_file(struct sc_card *card, const struct sc_path *path) +{ + int r; + u8 sbuf[2]; + struct sc_apdu apdu; + + SC_FUNC_CALLED(card->ctx, 1); + if (path->type != SC_PATH_TYPE_FILE_ID && path->len != 2) { + error(card->ctx, "File type has to be SC_PATH_TYPE_FILE_ID\n"); + SC_FUNC_RETURN(card->ctx, 1, SC_ERROR_INVALID_ARGUMENTS); + } + sbuf[0] = path->value[0]; + sbuf[1] = path->value[1]; + sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, 0xE4, 0x00, 0x00); + apdu.cla |= 0x80; + apdu.lc = 2; + apdu.datalen = 2; + apdu.data = sbuf; + + r = sc_transmit_apdu(card, &apdu); + SC_TEST_RET(card->ctx, r, "APDU transmit failed"); + return sc_check_sw(card, apdu.sw1, apdu.sw2); +} + +/* Crypto operations */ + + +/* Although the TCOS manual says that the manage security environment + is compatible with 7816-8.2 we use our own implementation here. + The problem is that I don't have the ISO specs and Juha's 7816 code + uses parameters which are not described in the TCOS specs. [wk] +*/ +static int tcos_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); + + if (env->operation == SC_SEC_OPERATION_SIGN) { + /* There is only a default security environment for + signature creation. */ + return 0; + } + + sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, 0x22, 0, 0); + switch (env->operation) { + case SC_SEC_OPERATION_DECIPHER: + apdu.p1 = 0xC1; + apdu.p2 = 0xB8; + break; + default: + return SC_ERROR_INVALID_ARGUMENTS; + } + apdu.le = 0; + p = sbuf; + if (env->flags & SC_SEC_ENV_ALG_REF_PRESENT) { + *p++ = 0x80; /* algorithm reference */ + *p++ = 0x01; + *p++ = env->algorithm_ref & 0xFF; + } + if (env->flags & SC_SEC_ENV_KEY_REF_PRESENT) { + if (env->flags & SC_SEC_ENV_KEY_REF_ASYMMETRIC) + *p++ = 0x83; + else + *p++ = 0x84; + *p++ = env->key_ref_len; + memcpy(p, env->key_ref, env->key_ref_len); + p += env->key_ref_len; + } + 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; +} + +/* See tcos_set_security_env() for comments. So we always return + success */ +static int tcos_restore_security_env(struct sc_card *card, int se_num) +{ + return 0; +} + + +/* Issue the SET PERMANENT command. With ENABLE_NULLPIN set the + NullPIN method will be activated, otherwise the permanent operation + will be done on the active file. */ +static int tcos_setperm(struct sc_card *card, int enable_nullpin) +{ + int r; + struct sc_apdu apdu; + + SC_FUNC_CALLED(card->ctx, 1); + sc_format_apdu(card, &apdu, SC_APDU_CASE_1, 0xEE, 0x00, 0x00); + apdu.cla |= 0x80; + apdu.lc = 0; + apdu.datalen = 0; + apdu.data = NULL; + + r = sc_transmit_apdu(card, &apdu); + SC_TEST_RET(card->ctx, r, "APDU transmit failed"); + return sc_check_sw(card, apdu.sw1, apdu.sw2); +} + +static int tcos_card_ctl(struct sc_card *card, unsigned long cmd, void *ptr) +{ + switch (cmd) { + case SC_CARDCTL_TCOS_SETPERM: + return tcos_setperm(card, !!ptr); + } + error(card->ctx, "card_ctl command %u not supported\n", cmd); + return SC_ERROR_NOT_SUPPORTED; +} + + +/* Driver binding stuff */ static const struct sc_card_driver * sc_get_driver(void) { const struct sc_card_driver *iso_drv = sc_get_iso7816_driver(); @@ -170,17 +754,20 @@ static const struct sc_card_driver * sc_get_driver(void) tcos_ops.finish = tcos_finish; if (iso_ops == NULL) iso_ops = iso_drv->ops; + tcos_ops.check_sw = tcos_check_sw; tcos_ops.create_file = tcos_create_file; tcos_ops.set_security_env = tcos_set_security_env; tcos_ops.select_file = tcos_select_file; - tcos_ops.list_files = tcos_list_files; + tcos_ops.list_files = tcos_list_files; + tcos_ops.delete_file = tcos_delete_file; + tcos_ops.set_security_env = tcos_set_security_env; + tcos_ops.restore_security_env = tcos_restore_security_env; + tcos_ops.card_ctl = tcos_card_ctl; return &tcos_drv; } -#if 1 const struct sc_card_driver * sc_get_tcos_driver(void) { return sc_get_driver(); } -#endif diff --git a/src/libopensc/cardctl.h b/src/libopensc/cardctl.h index fee0faa4..d75e5cd1 100644 --- a/src/libopensc/cardctl.h +++ b/src/libopensc/cardctl.h @@ -9,6 +9,7 @@ * 0x0000xxxx generic * 0x4C4658xx Cryptoflex * 0x47504Bxx GPK + * 0x544353xx TCOS */ #ifndef _OPENSC_CARDCTL_H @@ -45,6 +46,12 @@ enum { */ SC_CARDCTL_MIOCOS_BASE = _CTL_PREFIX('M', 'I', 'O'), SC_CARDCTL_MIOCOS_CREATE_AC, + + /* + * TCOS specific calls + */ + SC_CARDCTL_TCOS_BASE = _CTL_PREFIX('T','C','S'), + SC_CARDCTL_TCOS_SETPERM }; /* diff --git a/src/libopensc/pkcs15-pin.c b/src/libopensc/pkcs15-pin.c index dcca5f67..bca38e5f 100644 --- a/src/libopensc/pkcs15-pin.c +++ b/src/libopensc/pkcs15-pin.c @@ -77,6 +77,7 @@ int sc_pkcs15_decode_aodf_entry(struct sc_pkcs15_card *p15card, sc_format_asn1_entry(asn1_pin_attr + 1, &info.type, NULL, 0); sc_format_asn1_entry(asn1_pin_attr + 2, &info.min_length, NULL, 0); sc_format_asn1_entry(asn1_pin_attr + 3, &info.stored_length, NULL, 0); + /* fixme: we should also support max_length [wk] */ sc_format_asn1_entry(asn1_pin_attr + 5, &info.reference, NULL, 0); sc_format_asn1_entry(asn1_pin_attr + 6, &info.pad_char, &padchar_len, 0); sc_format_asn1_entry(asn1_pin_attr + 8, &info.path, NULL, 0); @@ -151,6 +152,7 @@ int sc_pkcs15_verify_pin(struct sc_pkcs15_card *p15card, int r; struct sc_card *card; u8 pinbuf[SC_MAX_PIN_SIZE]; + size_t len; assert(p15card != NULL); if (pin->magic != SC_PKCS15_PIN_MAGIC) @@ -167,8 +169,11 @@ int sc_pkcs15_verify_pin(struct sc_pkcs15_card *p15card, } memset(pinbuf, pin->pad_char, pin->stored_length); memcpy(pinbuf, pincode, pinlen); + len = pin->stored_length; + if ( !(pin->flags & SC_PKCS15_PIN_FLAG_NEEDS_PADDING)) + len = pinlen; r = sc_verify(card, SC_AC_CHV, pin->reference, - pinbuf, pin->stored_length, &pin->tries_left); + pinbuf, len, &pin->tries_left); memset(pinbuf, 0, pinlen); sc_unlock(card); if (r)