/* * card-jcop.c * * Copyright (C) 2003 Chaskiel Grundman * * 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 "internal.h" #include "cardctl.h" static const struct sc_atr_table jcop_atrs[] = { { "3B:E6:00:FF:81:31:FE:45:4A:43:4F:50:33:31:06", NULL, NULL, SC_CARD_TYPE_JCOP_GENERIC, 0, NULL }, { NULL, NULL, NULL, 0, 0, NULL } }; static struct sc_card_operations jcop_ops; static struct sc_card_driver jcop_drv = { "JCOP cards with BlueZ PKCS#15 applet", "jcop", &jcop_ops, NULL, 0, NULL }; #define SELECT_MF 0 #define SELECT_EFDIR 1 #define SELECT_APPDF 2 #define SELECT_EF 3 #define SELECT_UNKNOWN 4 #define SELECTING_TARGET 0xf #define SELECTING_ABS 0x80 #define SELECTING_VIA_APPDF 0x100 struct jcop_private_data { sc_file_t *virtmf; sc_file_t *virtdir; sc_path_t aid; int selected; int invalid_senv; int nfiles; u8 *filelist; }; #define DRVDATA(card) ((struct jcop_private_data *) ((card)->drv_data)) static int jcop_finish(sc_card_t *card) { struct jcop_private_data *drvdata=DRVDATA(card); if (drvdata) { sc_file_free(drvdata->virtmf); sc_file_free(drvdata->virtdir); free(drvdata); card->drv_data=NULL; } return 0; } static int jcop_match_card(sc_card_t *card) { int i; i = _sc_match_atr(card, jcop_atrs, &card->type); if (i < 0) return 0; return 1; } static unsigned char ef_dir_contents[128] = { 0x61, 0x21, 0x4f, 0xc, 0xA0, 0x0, 0x0, 0x0, 0x63, 'P', 'K', 'C', 'S', '-', '1', '5', 0x50, 0xb, 'O', 'p', 'e', 'n', 'S', 'C', ' ', 'C', 'a', 'r', 'd', 0x51, 0x04, 0x3f, 0x00, 0x50, 0x15 }; static int jcop_init(sc_card_t *card) { struct jcop_private_data *drvdata; sc_file_t *f; int flags; drvdata=malloc(sizeof(struct jcop_private_data)); if (!drvdata) return SC_ERROR_OUT_OF_MEMORY; memset(drvdata, 0, sizeof(struct jcop_private_data)); sc_format_path("A000:0000:6350:4B43:532D:3135", &drvdata->aid); drvdata->aid.type = SC_PATH_TYPE_DF_NAME; drvdata->selected=SELECT_MF; drvdata->invalid_senv=1; drvdata->nfiles=-1; drvdata->filelist=NULL; f=sc_file_new(); if (!f){ free(drvdata); return SC_ERROR_OUT_OF_MEMORY; } sc_format_path("3f00", &f->path); f->type=SC_FILE_TYPE_DF; f->shareable=0; f->ef_structure=SC_FILE_EF_UNKNOWN; f->size=0; f->id=0x3f00; f->status=SC_FILE_STATUS_ACTIVATED; sc_file_add_acl_entry(f, SC_AC_OP_SELECT, SC_AC_NONE, 0); sc_file_add_acl_entry(f, SC_AC_OP_LIST_FILES, SC_AC_NONE, 0); sc_file_add_acl_entry(f, SC_AC_OP_LOCK, SC_AC_NEVER, 0); sc_file_add_acl_entry(f, SC_AC_OP_DELETE, SC_AC_NEVER, 0); sc_file_add_acl_entry(f, SC_AC_OP_CREATE, SC_AC_NEVER, 0); drvdata->virtmf=f; f=sc_file_new(); if (!f){ sc_file_free(drvdata->virtmf); free(drvdata); return SC_ERROR_OUT_OF_MEMORY; } sc_format_path("3f002f00", &f->path); f->type=SC_FILE_TYPE_WORKING_EF; f->shareable=0; f->ef_structure=SC_FILE_EF_TRANSPARENT; f->size=128; f->id=0x2f00; f->status=SC_FILE_STATUS_ACTIVATED; sc_file_add_acl_entry(f, SC_AC_OP_READ, SC_AC_NONE, 0); sc_file_add_acl_entry(f, SC_AC_OP_LOCK, SC_AC_NEVER, 0); sc_file_add_acl_entry(f, SC_AC_OP_ERASE, SC_AC_NEVER, 0); sc_file_add_acl_entry(f, SC_AC_OP_UPDATE, SC_AC_NEVER, 0); sc_file_add_acl_entry(f, SC_AC_OP_WRITE, SC_AC_NEVER, 0); sc_file_add_acl_entry(f, SC_AC_OP_CRYPTO, SC_AC_NEVER, 0); drvdata->virtdir=f; card->drv_data = drvdata; card->cla = 0x00; /* card supports host-side padding, but not raw rsa */ flags = SC_ALGORITHM_RSA_PAD_PKCS1; flags |= SC_ALGORITHM_RSA_HASH_NONE; flags |= SC_ALGORITHM_RSA_HASH_SHA1; flags |= SC_ALGORITHM_RSA_HASH_MD5; /* only supports keygen with 3 and F-4 exponents */ flags |= SC_ALGORITHM_ONBOARD_KEY_GEN; _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, 2048, flags, 0); /* State that we have an RNG */ card->caps |= SC_CARD_CAP_RNG; return 0; } static int jcop_get_default_key(sc_card_t *card, struct sc_cardctl_default_key *data) { const char *key; if (data->method != SC_AC_PRO || data->key_ref > 2) return SC_ERROR_NO_DEFAULT_KEY; key = "40:41:42:43:44:45:46:47:48:49:4A:4B:4C:4D:4E:4F"; return sc_hex_to_bin(key, data->key_data, &data->len); } /* since the card is actually a javacard, we're expected to use ISO 7816-4 direct application selection instead of reading the DIR ourselves and selecting the AppDF by path. Since opensc doesn' do that, I fake an MF containing the AppDF and a fixed DIR pointing at the fake AppDF. This has the added advantage of allowing opensc-explorer to be used with this driver */ static int jcop_select_file(sc_card_t *card, const sc_path_t *path, sc_file_t **file) { struct jcop_private_data *drvdata=DRVDATA(card); int r,selecting; struct sc_card_driver *iso_drv = sc_get_iso7816_driver(); const struct sc_card_operations *iso_ops = iso_drv->ops; sc_path_t shortpath; sc_file_t *tfile, **fileptr; if (!drvdata) return SC_ERROR_FILE_NOT_FOUND; /* Something about the card does not like Case 4 APDU's to be sent as Case 3. you must send a length and accept a response. */ if (file) { fileptr=file; } else { fileptr=&tfile; } /* Selecting the MF. return a copy of the constructed MF */ if (path->len == 2 && memcmp(path->value, "\x3F\x00", 2) == 0) { drvdata->selected=SELECT_MF; if (file) { sc_file_dup(file, drvdata->virtmf); if (*file == NULL) return SC_ERROR_OUT_OF_MEMORY; } return 0; } /* Selecting the EF(DIR). return a copy of the constructed EF(DIR) */ if ((path->len == 4 && memcmp(path->value, "\x3F\x00\x2F\x00", 4) == 0) || (drvdata->selected == SELECT_MF && path->len == 2 && memcmp(path->value, "\x2F\x00", 2) == 0)) { drvdata->selected=SELECT_EFDIR; if (file) { sc_file_dup(file, drvdata->virtdir); if (*file == NULL) return SC_ERROR_OUT_OF_MEMORY; } return 0; } /* selecting the PKCS15 AppDF or a file in it. Select the applet, then pass through any remaining path components to the applet's select command */ selecting=SELECT_UNKNOWN; if (path->len >= 4 && memcmp(path->value, "\x3F\x00\x50\x15", 4) == 0) { if (path->len == 4) selecting = SELECTING_ABS | SELECT_APPDF; else selecting = SELECTING_ABS | SELECT_EF; } if (drvdata->selected==SELECT_MF && memcmp(path->value, "\x50\x15", 2) == 0) { if (path->len == 2) selecting = SELECTING_VIA_APPDF | SELECT_APPDF; else selecting = SELECTING_VIA_APPDF | SELECT_EF; } if (selecting & (SELECTING_ABS|SELECTING_VIA_APPDF)) { if (file == NULL && (selecting & SELECTING_TARGET) == SELECT_APPDF && drvdata->selected == SELECT_APPDF) { return 0; } if ((r = iso_ops->select_file(card, &drvdata->aid, fileptr)) < 0) return r; if (fileptr && (selecting & SELECTING_TARGET) == SELECT_APPDF) { (*fileptr)->type = SC_FILE_TYPE_DF; drvdata->selected=SELECT_APPDF; goto select_ok; } sc_file_free(*fileptr); *fileptr=NULL; memset(&shortpath, 0, sizeof(sc_path_t)); if (selecting & SELECTING_ABS) { memcpy(&shortpath.value, &path->value[4], path->len-4); shortpath.len=path->len-4; } else { memcpy(&shortpath.value, &path->value[2], path->len-2); shortpath.len=path->len-2; } shortpath.type = shortpath.len == 2 ? SC_PATH_TYPE_FILE_ID : path->type; shortpath.index=path->index; shortpath.count=path->count; path=&shortpath; } else { /* There seems to be better debugging output if I call sc_check_sw * with appropriate input than if I just return the appropriate * SC_ERROR_*, so that's what I do for all errors returned by code * related to the MF/DIR emulation */ if (drvdata->selected == SELECT_MF || drvdata->selected == SELECT_EFDIR) return sc_check_sw(card, 0x6A, 0x82); } r = iso_ops->select_file(card, path, fileptr); if (r) return r; drvdata->selected=SELECT_EF; select_ok: if (!file) { sc_file_free(*fileptr); } return 0; } static int jcop_read_binary(sc_card_t *card, unsigned int idx, u8 * buf, size_t count, unsigned long flags) { struct jcop_private_data *drvdata=DRVDATA(card); struct sc_card_driver *iso_drv = sc_get_iso7816_driver(); const struct sc_card_operations *iso_ops = iso_drv->ops; int r; if (drvdata->selected == SELECT_MF) { return sc_check_sw(card, 0x69, 0x86); } if (drvdata->selected == SELECT_EFDIR) { if (idx > 127) { return sc_check_sw(card, 0x6A, 0x86); } if (idx + count > 128) { count=128-idx; } r = iso_ops->select_file(card, &drvdata->aid, NULL); if (r < 0) { /* no pkcs15 app, so return empty DIR. */ memset(buf, 0, count); } else { memcpy(buf, (u8 *)(ef_dir_contents + idx), count); } return count; } return iso_ops->read_binary(card, idx, buf, count, flags); } static int jcop_list_files(sc_card_t *card, u8 *buf, size_t buflen) { struct jcop_private_data *drvdata=DRVDATA(card); struct sc_card_driver *iso_drv = sc_get_iso7816_driver(); const struct sc_card_operations *iso_ops = iso_drv->ops; int r; if (drvdata->selected == SELECT_MF) { if (buflen < 2) return 0; memcpy(buf, "\x2f\x00", 2); if (buflen < 4) return 2; /* AppDF only exists if applet is selectable */ r = iso_ops->select_file(card, &drvdata->aid, NULL); if (r < 0) { return 2; } else { memcpy(buf+2, "\x50\x15", 2); return 4; } } if (drvdata->nfiles == -1) return SC_ERROR_NOT_ALLOWED; if (drvdata->nfiles == 0) return 0; if (buflen > 2 * (size_t)drvdata->nfiles) buflen=2*drvdata->nfiles; memcpy(buf, drvdata->filelist, buflen); return buflen; } static int sa_to_acl(sc_file_t *file, unsigned int operation, int nibble) { switch (nibble & 0x7) { case 0: sc_file_add_acl_entry(file, operation, SC_AC_NONE, SC_AC_KEY_REF_NONE); break; case 1: sc_file_add_acl_entry(file, operation, SC_AC_NEVER, SC_AC_KEY_REF_NONE); break; case 2: sc_file_add_acl_entry(file, operation, SC_AC_CHV, 1); break; case 3: sc_file_add_acl_entry(file, operation, SC_AC_CHV, 2); break; case 4: sc_file_add_acl_entry(file, operation, SC_AC_CHV, 3); break; case 5: sc_file_add_acl_entry(file, operation, SC_AC_AUT, SC_AC_KEY_REF_NONE); break; case 6: sc_file_add_acl_entry(file, operation, SC_AC_PRO, SC_AC_KEY_REF_NONE); break; default: sc_file_add_acl_entry(file, operation, SC_AC_UNKNOWN, SC_AC_KEY_REF_NONE); } return 0; } static int jcop_process_fci(sc_card_t *card, sc_file_t *file, const u8 *buf, size_t buflen) { struct jcop_private_data *drvdata=DRVDATA(card); struct sc_card_driver *iso_drv = sc_get_iso7816_driver(); const struct sc_card_operations *iso_ops = iso_drv->ops; u8 *sa; int r; /* the FCI for EF's includes a bogus length for the overall structure! */ if (buflen == 19) buflen=24; r=iso_ops->process_fci(card, file, buf, buflen); if (r < 0) return r; if (file->type != SC_FILE_TYPE_DF) { if (drvdata->nfiles) { drvdata->nfiles=-1; free(drvdata->filelist); drvdata->filelist=NULL; } if(file->sec_attr_len >=3) { /* The security attribute bytes are divided into nibbles and are as follows: READ | MODIFY || SIGN | ENCIPHER || DECIPHER | DELETE */ sa=file->sec_attr; sa_to_acl(file, SC_AC_OP_READ, sa[0] >> 4); sa_to_acl(file, SC_AC_OP_UPDATE, sa[0] & 0xf); /* Files may be locked by anyone who can MODIFY. */ /* opensc seems to think LOCK ACs are only on DFs */ /* sa_to_acl(file, SC_AC_OP_LOCK, sa[0] & 0xf); */ /* there are separate SIGN, ENCIPHER, and DECIPHER ACs. I use SIGN for SC_AC_OP_CRYPTO unless it is NEVER, in which case I use DECIPHER */ if ((sa[1] & 0xf0) == 0x10) sa_to_acl(file, SC_AC_OP_CRYPTO, sa[1] >> 4); else sa_to_acl(file, SC_AC_OP_CRYPTO, sa[2] >> 4); sa_to_acl(file, SC_AC_OP_ERASE, sa[2] & 0xf); } } else { /* No AC information is reported for the AppDF */ sc_file_add_acl_entry(file, SC_AC_OP_SELECT, SC_AC_NONE, 0); sc_file_add_acl_entry(file, SC_AC_OP_CREATE, SC_AC_CHV, 3); sc_file_add_acl_entry(file, SC_AC_OP_DELETE, SC_AC_NONE, 0); sc_file_add_acl_entry(file, SC_AC_OP_LIST_FILES, SC_AC_NONE, 0); if (drvdata->nfiles) { drvdata->nfiles=0; free(drvdata->filelist); drvdata->filelist=NULL; } /* the format of the poprietary attributes is: 4 bytes unique id 1 byte # files in DF 2 bytes 1st File ID 2 bytes 2nd File ID ... */ if (file->prop_attr_len > 4) { int nfiles; u8 *filelist; nfiles=file->prop_attr[4]; if (nfiles) { filelist=malloc(2*nfiles); if (!filelist) return SC_ERROR_OUT_OF_MEMORY; memcpy(filelist, &file->prop_attr[5], 2*nfiles); drvdata->nfiles=nfiles; drvdata->filelist=filelist; } } } return r; } static int acl_to_ac_nibble(const sc_acl_entry_t *e) { if (e == NULL) return -1; if (e->next != NULL) /* FIXME */ return -1; switch (e->method) { case SC_AC_NONE: return 0x00; case SC_AC_NEVER: return 0x01; case SC_AC_CHV: switch (e->key_ref) { case 1: return 0x02; case 2: return 0x03; case 3: return 0x04; } return -1; case SC_AC_AUT: return 0x05; case SC_AC_PRO: return 0x06; } return -1; } static int jcop_create_file(sc_card_t *card, sc_file_t *file) { struct jcop_private_data *drvdata=DRVDATA(card); unsigned char sec_attr_data[3]; int ops[6]; int i, r; struct sc_card_driver *iso_drv = sc_get_iso7816_driver(); const struct sc_card_operations *iso_ops = iso_drv->ops; if (drvdata->selected == SELECT_MF || drvdata->selected == SELECT_EFDIR ) return sc_check_sw(card, 0x69, 0x82); /* Can't create DFs */ if (file->type != SC_FILE_TYPE_WORKING_EF) return sc_check_sw(card, 0x6A, 0x80); ops[0] = SC_AC_OP_READ; /* read */ ops[1] = SC_AC_OP_UPDATE; /* modify */ ops[2] = SC_AC_OP_CRYPTO; /* sign */ ops[3] = -1; /* encipher */ ops[4] = SC_AC_OP_CRYPTO; /* decipher */ ops[5] = SC_AC_OP_ERASE; /* delete */ memset(sec_attr_data, 0, 3); for (i = 0; i < 6; i++) { const sc_acl_entry_t *entry; if (ops[i] == -1) { sec_attr_data[i/2] |= 1 << ((i % 2) ? 0 : 4); continue; } entry = sc_file_get_acl_entry(file, ops[i]); r = acl_to_ac_nibble(entry); if (r >= 0) { sec_attr_data[i/2] |= r << ((i % 2) ? 0 : 4); } } sc_file_set_sec_attr(file, sec_attr_data, 3); r=iso_ops->create_file(card, file); if (r > 0) drvdata->selected=SELECT_EF; return r; } /* We need to trap these functions so that proper errors can be returned when one of the virtual files is selected */ static int jcop_write_binary(sc_card_t *card, unsigned int idx, const u8 *buf, size_t count, unsigned long flags) { struct jcop_private_data *drvdata=DRVDATA(card); struct sc_card_driver *iso_drv = sc_get_iso7816_driver(); const struct sc_card_operations *iso_ops = iso_drv->ops; if (drvdata->selected == SELECT_MF) return sc_check_sw(card, 0x6A, 0x86); if (drvdata->selected == SELECT_EFDIR) return sc_check_sw(card, 0x69, 0x82); return iso_ops->write_binary(card, idx, buf, count, flags); } static int jcop_update_binary(sc_card_t *card, unsigned int idx, const u8 *buf, size_t count, unsigned long flags) { struct jcop_private_data *drvdata=DRVDATA(card); struct sc_card_driver *iso_drv = sc_get_iso7816_driver(); const struct sc_card_operations *iso_ops = iso_drv->ops; if (drvdata->selected == SELECT_MF) return sc_check_sw(card, 0x69, 0x86); if (drvdata->selected == SELECT_EFDIR) return sc_check_sw(card, 0x69, 0x82); return iso_ops->update_binary(card, idx, buf, count, flags); } static int jcop_delete_file(sc_card_t *card, const sc_path_t *path) { struct jcop_private_data *drvdata=DRVDATA(card); struct sc_card_driver *iso_drv = sc_get_iso7816_driver(); const struct sc_card_operations *iso_ops = iso_drv->ops; if (drvdata->selected == SELECT_MF || drvdata->selected == SELECT_EFDIR ) return sc_check_sw(card, 0x69, 0x82); return iso_ops->delete_file(card, path); } /* BlueZ doesn't support stored security environments. you have to construct one with SET every time */ static int jcop_set_security_env(sc_card_t *card, const sc_security_env_t *env, int se_num) { sc_apdu_t apdu; u8 sbuf[SC_MAX_APDU_BUFFER_SIZE]; u8 *p; int r; struct jcop_private_data *drvdata=DRVDATA(card); assert(card != NULL && env != NULL); if (se_num) LOG_FUNC_RETURN(card->ctx, SC_ERROR_INVALID_ARGUMENTS); if (drvdata->selected == SELECT_MF || drvdata->selected == SELECT_EFDIR) { drvdata->invalid_senv=1; return 0; } if (env->flags & SC_SEC_ENV_ALG_PRESENT) { sc_security_env_t tmp; tmp = *env; tmp.flags &= ~SC_SEC_ENV_ALG_PRESENT; tmp.flags |= SC_SEC_ENV_ALG_REF_PRESENT; if (tmp.algorithm != SC_ALGORITHM_RSA) { sc_log(card->ctx, "Only RSA algorithm supported.\n"); return SC_ERROR_NOT_SUPPORTED; } if (!(env->algorithm_flags & SC_ALGORITHM_RSA_PAD_PKCS1)){ sc_log(card->ctx, "Card requires RSA padding\n"); return SC_ERROR_NOT_SUPPORTED; } tmp.algorithm_ref = 0x02; /* potential FIXME: return an error, if an unsupported * pad or hash was requested, although this shouldn't happen. */ if (tmp.algorithm_flags & SC_ALGORITHM_RSA_HASH_SHA1) tmp.algorithm_ref |= 0x10; if (tmp.algorithm_flags & SC_ALGORITHM_RSA_HASH_MD5) tmp.algorithm_ref |= 0x20; memcpy((sc_security_env_t *) env, &tmp, sizeof(struct sc_security_env)); } sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, 0x22, 0xC1, 0); switch (env->operation) { case SC_SEC_OPERATION_DECIPHER: apdu.p2 = 0xB8; break; case SC_SEC_OPERATION_SIGN: apdu.p2 = 0xB6; break; default: return SC_ERROR_INVALID_ARGUMENTS; } apdu.le = 0; if (!(env->flags & SC_SEC_ENV_ALG_REF_PRESENT)) return SC_ERROR_INVALID_ARGUMENTS; if (!(env->flags & SC_SEC_ENV_FILE_REF_PRESENT)) return SC_ERROR_INVALID_ARGUMENTS; if (env->flags & SC_SEC_ENV_KEY_REF_PRESENT) { if (env->key_ref_len > 1 || env->key_ref[0] != 0) return SC_ERROR_INVALID_ARGUMENTS; } p = sbuf; *p++ = 0x80; /* algorithm reference */ *p++ = 0x01; *p++ = env->algorithm_ref & 0xFF; *p++ = 0x81; *p++ = env->file_ref.len; memcpy(p, env->file_ref.value, env->file_ref.len); p += env->file_ref.len; r = p - sbuf; apdu.lc = r; apdu.datalen = r; apdu.data = sbuf; apdu.resplen = 0; r = sc_transmit_apdu(card, &apdu); if (r) { sc_log(card->ctx, "%s: APDU transmit failed", sc_strerror(r)); return r; } r = sc_check_sw(card, apdu.sw1, apdu.sw2); if (r) { sc_log(card->ctx, "%s: Card returned error", sc_strerror(r)); return r; } drvdata->invalid_senv=0; return 0; } static int jcop_compute_signature(sc_card_t *card, const u8 * data, size_t datalen, u8 * out, size_t outlen) { int r; sc_apdu_t apdu; u8 rbuf[SC_MAX_APDU_BUFFER_SIZE]; u8 sbuf[SC_MAX_APDU_BUFFER_SIZE]; struct jcop_private_data *drvdata=DRVDATA(card); assert(card != NULL && data != NULL && out != NULL); if (datalen > 256) SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, SC_ERROR_INVALID_ARGUMENTS); if (drvdata->invalid_senv) return sc_check_sw(card, 0x69, 0x88); /* 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_4_SHORT, 0x2A, 0x9E, 0x9A); apdu.resp = rbuf; apdu.resplen = sizeof(rbuf); /* FIXME */ apdu.le = 256; if (datalen == 256) { apdu.p2 = data[0]; memcpy(sbuf, data+1, datalen-1); apdu.lc = datalen - 1; apdu.datalen = datalen - 1; } else { memcpy(sbuf, data, datalen); apdu.lc = datalen; apdu.datalen = datalen; } apdu.data = sbuf; r = sc_transmit_apdu(card, &apdu); LOG_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, SC_LOG_DEBUG_VERBOSE, len); } SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, sc_check_sw(card, apdu.sw1, apdu.sw2)); } static int jcop_decipher(sc_card_t *card, const u8 * crgram, size_t crgram_len, u8 * out, size_t outlen) { int r; sc_apdu_t apdu; u8 rbuf[SC_MAX_APDU_BUFFER_SIZE]; u8 sbuf[SC_MAX_APDU_BUFFER_SIZE]; struct jcop_private_data *drvdata=DRVDATA(card); assert(card != NULL && crgram != NULL && out != NULL); LOG_FUNC_CALLED(card->ctx); if (crgram_len > 256) SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, SC_ERROR_INVALID_ARGUMENTS); if (drvdata->invalid_senv) return sc_check_sw(card, 0x69, 0x88); /* INS: 0x2A PERFORM SECURITY OPERATION * P1: 0x80 Resp: Plain value * P2: 0x86 Cmd: Padding indicator byte followed by cryptogram */ sc_format_apdu(card, &apdu, SC_APDU_CASE_4_SHORT, 0x2A, 0x80, 0x86); apdu.resp = rbuf; apdu.resplen = sizeof(rbuf); /* FIXME */ apdu.le = crgram_len; if (crgram_len == 256) { apdu.p2 = crgram[0]; memcpy(sbuf, crgram+1, crgram_len-1); apdu.lc = crgram_len - 1; apdu.datalen = crgram_len -1; } else { sbuf[0] = 0; /* padding indicator byte, 0x00 = No further indication */ memcpy(sbuf + 1, crgram, crgram_len); apdu.lc = crgram_len + 1; apdu.datalen = crgram_len + 1; } apdu.data = sbuf; r = sc_transmit_apdu(card, &apdu); LOG_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, SC_LOG_DEBUG_VERBOSE, len); } SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, sc_check_sw(card, apdu.sw1, apdu.sw2)); } static int jcop_generate_key(sc_card_t *card, struct sc_cardctl_jcop_genkey *a) { int r; sc_apdu_t apdu; u8 rbuf[SC_MAX_APDU_BUFFER_SIZE]; u8 sbuf[SC_MAX_APDU_BUFFER_SIZE]; u8 *p; int is_f4; struct jcop_private_data *drvdata=DRVDATA(card); if (drvdata->selected == SELECT_MF || drvdata->selected == SELECT_EFDIR ) return sc_check_sw(card, 0x6A, 0x82); is_f4=0; if (a->exponent == 0x10001) { is_f4=1; } else if (a->exponent != 3) { sc_log(card->ctx, "%s: Invalid exponent", sc_strerror(SC_ERROR_NOT_SUPPORTED)); return SC_ERROR_NOT_SUPPORTED; } sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, 0x22, 0xC1, 0xB6); p = sbuf; *p++ = 0x80; /* algorithm reference */ *p++ = 0x01; *p++ = is_f4 ? 0x6E : 0x6D; *p++ = 0x81; *p++ = a->pub_file_ref.len; memcpy(p, a->pub_file_ref.value, a->pub_file_ref.len); p += a->pub_file_ref.len; *p++ = 0x81; *p++ = a->pri_file_ref.len; memcpy(p, a->pri_file_ref.value, a->pri_file_ref.len); p += a->pri_file_ref.len; r = p - sbuf; apdu.lc = r; apdu.datalen = r; apdu.data = sbuf; apdu.resplen = 0; r = sc_transmit_apdu(card, &apdu); if (r) { sc_log(card->ctx, "%s: APDU transmit failed", sc_strerror(r)); return r; } r = sc_check_sw(card, apdu.sw1, apdu.sw2); if (r) { sc_log(card->ctx, "%s: Card returned error", sc_strerror(r)); return r; } sc_format_apdu(card, &apdu, SC_APDU_CASE_2_SHORT, 0x46, 0, 0); apdu.le = 256; apdu.resp=rbuf; apdu.resplen = sizeof(rbuf); r = sc_transmit_apdu(card, &apdu); if (r) { sc_log(card->ctx, "%s: APDU transmit failed", sc_strerror(r)); return r; } r = sc_check_sw(card, apdu.sw1, apdu.sw2); if (r) { sc_log(card->ctx, "%s: Card returned error", sc_strerror(r)); return r; } if (rbuf[0] != 0x4) { return SC_ERROR_INVALID_DATA; } if (a->pubkey_len < rbuf[1]) return SC_ERROR_BUFFER_TOO_SMALL; a->pubkey_len=rbuf[1] * 4; memcpy(a->pubkey, &rbuf[2], a->pubkey_len); return 0; } static int jcop_card_ctl(sc_card_t *card, unsigned long cmd, void *ptr) { switch (cmd) { case SC_CARDCTL_GET_DEFAULT_KEY: return jcop_get_default_key(card, (struct sc_cardctl_default_key *) ptr); case SC_CARDCTL_JCOP_GENERATE_KEY: return jcop_generate_key(card, (struct sc_cardctl_jcop_genkey *) ptr); } return SC_ERROR_NOT_SUPPORTED; } static struct sc_card_driver * sc_get_driver(void) { struct sc_card_driver *iso_drv = sc_get_iso7816_driver(); jcop_ops = *iso_drv->ops; jcop_ops.match_card = jcop_match_card; jcop_ops.init = jcop_init; jcop_ops.finish = jcop_finish; /* no record oriented file services */ jcop_ops.read_record = NULL; jcop_ops.write_record = NULL; jcop_ops.append_record = NULL; jcop_ops.update_record = NULL; jcop_ops.read_binary = jcop_read_binary; jcop_ops.write_binary = jcop_write_binary; jcop_ops.update_binary = jcop_update_binary; jcop_ops.select_file = jcop_select_file; jcop_ops.create_file = jcop_create_file; jcop_ops.delete_file = jcop_delete_file; jcop_ops.list_files = jcop_list_files; jcop_ops.set_security_env = jcop_set_security_env; jcop_ops.compute_signature = jcop_compute_signature; jcop_ops.decipher = jcop_decipher; jcop_ops.process_fci = jcop_process_fci; jcop_ops.card_ctl = jcop_card_ctl; return &jcop_drv; } struct sc_card_driver * sc_get_jcop_driver(void) { return sc_get_driver(); }