diff --git a/src/libopensc/Makefile.am b/src/libopensc/Makefile.am index 8c02156178..fcb80c8788 100644 --- a/src/libopensc/Makefile.am +++ b/src/libopensc/Makefile.am @@ -26,7 +26,7 @@ libopensc_la_SOURCES = \ \ card-setcos.c card-miocos.c card-flex.c card-gpk.c \ card-etoken.c card-tcos.c card-emv.c card-default.c \ - card-mcrd.c card-starcos.c card-openpgp.c \ + card-mcrd.c card-starcos.c card-openpgp.c card-jcop.c\ \ pkcs15-openpgp.c libopensc_la_LDFLAGS = -version-info @OPENSC_LT_CURRENT@:@OPENSC_LT_REVISION@:@OPENSC_LT_AGE@ diff --git a/src/libopensc/card-jcop.c b/src/libopensc/card-jcop.c new file mode 100644 index 0000000000..c0ffb0f705 --- /dev/null +++ b/src/libopensc/card-jcop.c @@ -0,0 +1,975 @@ +/* + * 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 + */ + +#include "internal.h" +#include "cardctl.h" +#include +#include + +static struct sc_card_operations jcop_ops; +static struct sc_card_driver jcop_drv = { + "JCOP cards with BlueZ PKCS#15 applet", + "jcop", + &jcop_ops +}; +#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 +{ + struct sc_file *virtmf; + struct sc_file *virtdir; + struct sc_path aid; + int selected; + int invalid_senv; + int nfiles; + u8 *filelist; +}; +#define DRVDATA(card) ((struct jcop_private_data *) ((card)->drv_data)) + +static int jcop_finish(struct sc_card *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 const char *jcop_atrs[] = { + "3B:E6:00:FF:81:31:FE:45:4A:43:4F:50:33:31:06", + /* Requires secure messaging */ + /*"3B:E6:00:FF:81:31:FE:45:4A:43:4F:50:32:31:06",*/ + NULL +}; + +static int jcop_match_card(struct sc_card *card) +{ + int i, match = -1; + + for (i = 0; jcop_atrs[i] != NULL; i++) { + u8 defatr[SC_MAX_ATR_SIZE]; + size_t len = sizeof(defatr); + const char *atrp = jcop_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 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(struct sc_card *card) +{ + struct jcop_private_data *drvdata; + struct sc_file *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=0; + 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(struct sc_card *card, + struct sc_cardctl_default_key *data) +{ + const char *key; + + if (data->method != SC_AC_AUT || data->key_ref > 2) + return SC_ERROR_NO_DEFAULT_KEY; + + key = "40:41:42:43:44:45:46:47:47: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(struct sc_card *card, const struct sc_path *path, + struct sc_file **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; + struct sc_file *tmpfile, **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=&tmpfile; + } + + /* 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); + } + 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); + } + 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 ((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(struct sc_card *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; + struct sc_file *tmpfile; + 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; + } + card->ctx->suppress_errors++; + r = iso_ops->select_file(card, &drvdata->aid, &tmpfile); + card->ctx->suppress_errors--; + if (r < 0) { /* no pkcs15 app, so return empty DIR. */ + memset(buf, 0, count); + } else { + sc_file_free(tmpfile); + 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(struct sc_card *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; + struct sc_file *tmpfile; + 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 */ + card->ctx->suppress_errors++; + r = iso_ops->select_file(card, &drvdata->aid, &tmpfile); + card->ctx->suppress_errors--; + if (r < 0) { + return 2; + } else { + sc_file_free(tmpfile); + 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 * drvdata->nfiles) + buflen=2*drvdata->nfiles; + memcpy(buf, drvdata->filelist, buflen); + return buflen; +} + +static int sa_to_acl(struct sc_file *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(struct sc_card *card, struct sc_file *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=0; + } + 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 seperate 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=0; + } + /* 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 struct sc_acl_entry *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(struct sc_card *card, struct sc_file *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 struct sc_acl_entry *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); + 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; +} + +/* no record oriented file services */ +static int jcop_read_record_unsupp(struct sc_card *card, + unsigned int rec_nr, u8 *buf, + size_t count, unsigned long flags) { + return SC_ERROR_NOT_SUPPORTED; +} + +static int jcop_wrupd_record_unsupp(struct sc_card *card, + unsigned int rec_nr, const u8 *buf, + size_t count, unsigned long flags) { + return SC_ERROR_NOT_SUPPORTED; +} + +static int jcop_append_record_unsupp(struct sc_card *card, + const u8 *buf, size_t count, + unsigned long flags) { + return SC_ERROR_NOT_SUPPORTED; +} + + +/* 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(struct sc_card *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(struct sc_card *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(struct sc_card *card, const struct sc_path *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(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; + struct jcop_private_data *drvdata=DRVDATA(card); + + assert(card != NULL && env != NULL); + if (se_num) + SC_FUNC_RETURN(card->ctx, 1, 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) { + struct sc_security_env 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_error(card->ctx, "Only RSA algorithm supported.\n"); + return SC_ERROR_NOT_SUPPORTED; + } + if (!(env->algorithm_flags & SC_ALGORITHM_RSA_PAD_PKCS1)){ + sc_error(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; + env=&tmp; + } + + 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_perror(card->ctx, r, "APDU transmit failed"); + return r; + } + r = sc_check_sw(card, apdu.sw1, apdu.sw2); + if (r) { + sc_perror(card->ctx, r, "Card returned error"); + return r; + } + drvdata->invalid_senv=0; + return 0; +} +static int jcop_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]; + struct jcop_private_data *drvdata=DRVDATA(card); + + assert(card != NULL && data != NULL && out != NULL); + if (datalen > 256) + SC_FUNC_RETURN(card->ctx, 4, 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; + 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); + } + SC_FUNC_RETURN(card->ctx, 4, sc_check_sw(card, apdu.sw1, apdu.sw2)); +} + + + +static int jcop_decipher(struct sc_card *card, + const u8 * crgram, size_t crgram_len, + 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]; + struct jcop_private_data *drvdata=DRVDATA(card); + + assert(card != NULL && crgram != NULL && out != NULL); + SC_FUNC_CALLED(card->ctx, 2); + if (crgram_len > 256) + SC_FUNC_RETURN(card->ctx, 2, 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; + apdu.sensitive = 1; + + 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); + 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, 2, len); + } + SC_FUNC_RETURN(card->ctx, 2, sc_check_sw(card, apdu.sw1, apdu.sw2)); +} + +static int jcop_generate_key(struct sc_card *card, struct sc_cardctl_jcop_genkey *a) { + int modlen; + int r; + struct sc_apdu 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_perror(card->ctx, SC_ERROR_NOT_SUPPORTED, "Invalid exponent"); + 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_perror(card->ctx, r, "APDU transmit failed"); + return r; + } + r = sc_check_sw(card, apdu.sw1, apdu.sw2); + if (r) { + sc_perror(card->ctx, r, "Card returned error"); + 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_perror(card->ctx, r, "APDU transmit failed"); + return r; + } + r = sc_check_sw(card, apdu.sw1, apdu.sw2); + if (r) { + sc_perror(card->ctx, r, "Card returned error"); + return r; + } + + if (rbuf[0] != 0x4) { + return SC_ERROR_INVALID_DATA; + } + modlen=rbuf[1] * 32; + 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(struct sc_card *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_LOCK: + /* XXX implement me */ + return SC_ERROR_NOT_SUPPORTED; + case SC_CARDCTL_JCOP_GENERATE_KEY: + return jcop_generate_key(card, + (struct sc_cardctl_jcop_genkey *) ptr); + } + + return SC_ERROR_NOT_SUPPORTED; +} + + +/* "The PINs are "global" in a PKCS#15 sense, meaning that they remain valid + * until card reset! Selecting another applet doesn't invalidate the PINs, + * you need to reset the card." - javacard@zurich.ibm.com, when asked about + * how to invalidate logged in pins. +*/ +static int jcop_logout(struct sc_card *card) +{ + return 0; /* Can't */ +} + +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; + jcop_ops.read_binary = jcop_read_binary; + jcop_ops.read_record = jcop_read_record_unsupp; + jcop_ops.write_record = jcop_wrupd_record_unsupp; + jcop_ops.append_record = jcop_append_record_unsupp; + jcop_ops.update_record = jcop_wrupd_record_unsupp; + 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.logout = jcop_logout; + jcop_ops.process_fci = jcop_process_fci; + jcop_ops.card_ctl = jcop_card_ctl; + + return &jcop_drv; +} + +#if 1 +struct sc_card_driver * sc_get_jcop_driver(void) +{ + return sc_get_driver(); +} +#endif + diff --git a/src/libopensc/cardctl.h b/src/libopensc/cardctl.h index beb6cb6319..c808b1a39d 100644 --- a/src/libopensc/cardctl.h +++ b/src/libopensc/cardctl.h @@ -80,6 +80,10 @@ enum { SC_CARDCTL_STARCOS_FREE_EX_DATA, SC_CARDCTL_STARCOS_FREE_ALL_EX_DATA, + SC_CARDCTL_JCOP_BASE = _CTL_PREFIX('J', 'C', 'P'), + SC_CARDCTL_JCOP_LOCK, + SC_CARDCTL_JCOP_GENERATE_KEY, + }; enum { @@ -207,6 +211,16 @@ struct sc_cardctl_starcos_pin_attr_st { int verify_once; }; + +struct sc_cardctl_jcop_genkey { + unsigned long exponent; + sc_path_t pub_file_ref; + sc_path_t pri_file_ref; + unsigned char * pubkey; + unsigned int pubkey_len; +}; + + #ifdef __cplusplus } #endif diff --git a/src/libopensc/ctx.c b/src/libopensc/ctx.c index 8ba033bfa7..90f1c2a6c3 100644 --- a/src/libopensc/ctx.c +++ b/src/libopensc/ctx.c @@ -68,6 +68,7 @@ static const struct _sc_driver_entry internal_card_drivers[] = { { "starcos", (void *) sc_get_starcos_driver, NULL }, { "tcos", (void *) sc_get_tcos_driver, NULL }, { "opengpg", (void *) sc_get_openpgp_driver, NULL }, + { "jcop", (void *) sc_get_jcop_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 bc7418448e..1ac05cbded 100644 --- a/src/libopensc/opensc.h +++ b/src/libopensc/opensc.h @@ -867,6 +867,7 @@ extern struct sc_card_driver *sc_get_setcos_driver(void); extern struct sc_card_driver *sc_get_starcos_driver(void); extern struct sc_card_driver *sc_get_tcos_driver(void); extern struct sc_card_driver *sc_get_openpgp_driver(void); +extern struct sc_card_driver *sc_get_jcop_driver(void); #ifdef __cplusplus } diff --git a/src/pkcs15init/Makefile.am b/src/pkcs15init/Makefile.am index d8ba646c7a..bcdb1c6110 100644 --- a/src/pkcs15init/Makefile.am +++ b/src/pkcs15init/Makefile.am @@ -13,6 +13,7 @@ PROFILES = \ gpk.profile \ miocos.profile \ etoken.profile \ + jcop.profile \ pkcs15.profile EXTRA_DIST = $(PROFILES) Makefile.mak @@ -22,7 +23,7 @@ lib_LTLIBRARIES = libpkcs15init.la libpkcs15init_la_SOURCES = \ pkcs15-lib.c profile.c keycache.c \ pkcs15-gpk.c pkcs15-miocos.c pkcs15-cflex.c \ - pkcs15-etoken.c + pkcs15-etoken.c pkcs15-jcop.c libpkcs15init_la_LDFLAGS = -version-info @OPENSC_LT_CURRENT@:@OPENSC_LT_REVISION@:@OPENSC_LT_AGE@ include_HEADERS = pkcs15-init.h diff --git a/src/pkcs15init/jcop.profile b/src/pkcs15init/jcop.profile new file mode 100644 index 0000000000..7fa57c0d33 --- /dev/null +++ b/src/pkcs15init/jcop.profile @@ -0,0 +1,64 @@ +# +# PKCS15 r/w profile for JCOP cards +# +cardinfo { + max-pin-length = 16; + pin-encoding = ascii-numeric; + pin-pad-char = 0x00; +} + +filesystem { + DF MF { + DF PKCS15-AppDF { + acl = *=NONE, CREATE=CHV3; + EF PKCS15-AODF { + file-id = 502E; + } + EF PKCS15-PrKDF { + file-id = 502C; + } + EF PKCS15-PuKDF { + file-id = 502B; + } + EF PKCS15-CDF { + file-id = 502D; + } + EF PKCS15-DODF { + file-id = 502F; + } + template key-domain { + EF private-key { + file-id = 3000; + acl = *=NEVER, UPDATE=$PIN, CRYPTO=$PIN, + ERASE=$SOPIN; + } + EF extractable-key { + file-id = 3100; + acl = *=NEVER, READ=$PIN, UPDATE=$PIN, + ERASE=$SOPIN; + } + EF data { + file-id = 3200; + acl = *=NEVER, UPDATE=$PIN, READ=NONE, + ERASE=$SOPIN; + } + EF public-key { + file-id = 3300; + acl = *=NEVER, UPDATE=$PIN, READ=NONE, + ERASE=$SOPIN; + } + EF certificate { + file-id = 3400; + acl = *=NEVER, UPDATE=$PIN, READ=NONE, + ERASE=$SOPIN; + } + } + EF temp-pubkey { + file-id = 0000; + acl = *=NEVER, UPDATE=$PIN, READ=NONE, + ERASE=$SOPIN; + } + } + } +} + diff --git a/src/pkcs15init/pkcs15-init.h b/src/pkcs15init/pkcs15-init.h index 4ee85b829c..50bcea9d69 100644 --- a/src/pkcs15init/pkcs15-init.h +++ b/src/pkcs15init/pkcs15-init.h @@ -352,6 +352,7 @@ extern struct sc_pkcs15init_operations *sc_pkcs15init_get_miocos_ops(void); extern struct sc_pkcs15init_operations *sc_pkcs15init_get_cryptoflex_ops(void); extern struct sc_pkcs15init_operations *sc_pkcs15init_get_cyberflex_ops(void); extern struct sc_pkcs15init_operations *sc_pkcs15init_get_etoken_ops(void); +extern struct sc_pkcs15init_operations *sc_pkcs15init_get_jcop_ops(void); #ifdef __cplusplus } diff --git a/src/pkcs15init/pkcs15-jcop.c b/src/pkcs15init/pkcs15-jcop.c new file mode 100644 index 0000000000..0b39889bce --- /dev/null +++ b/src/pkcs15init/pkcs15-jcop.c @@ -0,0 +1,361 @@ +/* + * JCOP specific operation for PKCS15 initialization + * + * Copyright 2003 Chaskiel Grundman + * Copyright (C) 2002 Olaf Kirch + * + * 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 + */ + +#ifdef HAVE_CONFIG_H +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include "pkcs15-init.h" +#include "profile.h" + + +#define JCOP_MAX_PINS 3 + +/* + * Erase the card + */ +static int +jcop_erase_card(struct sc_profile *pro, struct sc_card *card) { + /* later */ + return SC_ERROR_NOT_SUPPORTED; +} + +/* + * Create a new DF + * This will usually be the application DF + * for JCOP, it must be the application DF. no other DF's may exist. + */ +static int +jcop_init_app(sc_profile_t *profile, sc_card_t *card, + struct sc_pkcs15_pin_info *pin_info, + const u8 *pin, size_t pin_len, const u8 *puk, size_t puk_len) { + return SC_ERROR_NOT_SUPPORTED; +} + +/* + * Select a PIN reference + */ +static int +jcop_select_pin_reference(sc_profile_t *profile, sc_card_t *card, + sc_pkcs15_pin_info_t *pin_info) { + int preferred, current; + + if ((current = pin_info->reference) < 0) + current = 0; + + if (pin_info->flags & SC_PKCS15_PIN_FLAG_SO_PIN) { + preferred = 3; + } else { + preferred = current; + if (preferred < 1) + preferred=1; + if (preferred > 2) + return SC_ERROR_TOO_MANY_OBJECTS; + } + if (current > preferred) + return SC_ERROR_TOO_MANY_OBJECTS; + pin_info->reference = preferred; + return 0; +} + +/* + * Store a PIN + */ +static int +jcop_create_pin(sc_profile_t *profile, sc_card_t *card, sc_file_t *df, + sc_pkcs15_object_t *pin_obj, + const unsigned char *pin, size_t pin_len, + const unsigned char *puk, size_t puk_len) +{ + sc_pkcs15_pin_info_t *pin_info = (sc_pkcs15_pin_info_t *) pin_obj->data; + unsigned char nulpin[16]; + unsigned char padpin[16]; + int r, type; + + if (pin_info->flags & SC_PKCS15_PIN_FLAG_SO_PIN) { + type = SC_PKCS15INIT_SO_PIN; + + /* SO PIN reference must be 0 */ + if (pin_info->reference != 3) + return SC_ERROR_INVALID_ARGUMENTS; + } else { + type = SC_PKCS15INIT_USER_PIN; + if (pin_info->reference >= 3) + return SC_ERROR_TOO_MANY_OBJECTS; + } + if (puk != NULL && puk_len > 0) { + return SC_ERROR_NOT_SUPPORTED; + } + r = sc_select_file(card, &df->path, NULL); + if (r < 0) + return r; + + /* Current PIN is 00:00:00:00:00:00:00:00... */ + memset(nulpin, 0, sizeof(nulpin)); + memset(padpin, 0, sizeof(padpin)); + memcpy(padpin, pin, pin_len); + r = sc_change_reference_data(card, SC_AC_CHV, + pin_info->reference, + nulpin, sizeof(nulpin), + padpin, sizeof(padpin), NULL); + if (r < 0) + return r; + + + + sc_keycache_set_pin_name(&df->path, + pin_info->reference, + type); + pin_info->flags &= ~SC_PKCS15_PIN_FLAG_LOCAL; + return r; +} + +/* + * Create a new key file + */ +static int +jcop_create_key(sc_profile_t *profile, sc_card_t *card, sc_pkcs15_object_t *obj +) +{ + sc_pkcs15_prkey_info_t *key_info = (sc_pkcs15_prkey_info_t *) obj->data; + struct sc_file *keyfile = NULL; + size_t bytes, mod_len, exp_len, prv_len, pub_len; + int r; + + if (obj->type != SC_PKCS15_TYPE_PRKEY_RSA) { + sc_error(card->ctx, "JCOP supports only RSA keys."); + return SC_ERROR_NOT_SUPPORTED; + } + /* The caller is supposed to have chosen a key file path for us */ + if (key_info->path.len == 0 || key_info->modulus_length == 0) + return SC_ERROR_INVALID_ARGUMENTS; + + /* Get the file we're supposed to create */ + r = sc_profile_get_file_by_path(profile, &key_info->path, &keyfile); + if (r < 0) + return r; + + mod_len = key_info->modulus_length / 8; + exp_len = 4; + bytes = mod_len / 2; + pub_len = 2 + mod_len + exp_len; + prv_len = 2 + 5 * bytes; + keyfile->size = prv_len; + + /* Fix up PIN references in file ACL */ + r = sc_pkcs15init_fixup_file(profile, keyfile); + + if (r >= 0) + r = sc_pkcs15init_create_file(profile, card, keyfile); + + if (keyfile) + sc_file_free(keyfile); + return r; +} + +static void +jcop_bn2bin(unsigned char *dest, sc_pkcs15_bignum_t *bn, unsigned int size) +{ + u8 *src; + unsigned int n; + + assert(bn->len <= size); + memset(dest, 0, size); + for (n = size-bn->len, src = bn->data; n < size; n++,src++) + dest[n] = *src; +} + +/* + * Store a private key + * Private key file formats: (transparent file) + * Non-CRT: + * byte 0 0x05 + * byte 1 Modulus length (in byte/4) + * byte 2 Modulus (n) + * byte 2+x private exponent (d) + * + * CRT: + * byte 0 0x06 + * byte 1 component length (in byte/2; component length is half + * of modulus length + * byte 2 Prime (p) + * byte 2+x Prime (q) + * byte 2+2*x Exponent 1 (d mod (p-1)) + * byte 2+3*x Exponent 2 (d mod (q-1)) + * byte 2+4*x Coefficient ((p ^ -1) mod q + * + * We use the CRT format, since that's what key generation does. + * + * Numbers are stored big endian. + */ +static int +jcop_store_key(sc_profile_t *profile, sc_card_t *card, + sc_pkcs15_object_t *obj, + sc_pkcs15_prkey_t *key) +{ + sc_pkcs15_prkey_info_t *key_info = (sc_pkcs15_prkey_info_t *) obj->data; + sc_file_t *keyfile; + unsigned char keybuf[1024]; + size_t size,base; + int r; + + if (obj->type != SC_PKCS15_TYPE_PRKEY_RSA) { + sc_error(card->ctx, "JCOP supports only RSA keys."); + return SC_ERROR_NOT_SUPPORTED; + } + r = sc_profile_get_file_by_path(profile, &key_info->path, &keyfile); + if (r < 0) + return r; + base=key_info->modulus_length / 16; + size=2+5*base; + keybuf[0]=6; + keybuf[1]=base/4; + jcop_bn2bin(&keybuf[2 + 0 * base], &key->u.rsa.p, base); + jcop_bn2bin(&keybuf[2 + 1 * base], &key->u.rsa.q, base); + jcop_bn2bin(&keybuf[2 + 2 * base], &key->u.rsa.dmp1, base); + jcop_bn2bin(&keybuf[2 + 3 * base], &key->u.rsa.dmq1, base); + jcop_bn2bin(&keybuf[2 + 4 * base], &key->u.rsa.iqmp, base); + r = sc_pkcs15init_update_file(profile, card, keyfile, keybuf, size); + + sc_file_free(keyfile); + return r; +} + +/* + * Generate a keypair + */ +static int +jcop_generate_key(sc_profile_t *profile, sc_card_t *card, + sc_pkcs15_object_t *obj, + sc_pkcs15_pubkey_t *pubkey) { + sc_pkcs15_prkey_info_t *key_info = (sc_pkcs15_prkey_info_t *) obj->data; + struct sc_cardctl_jcop_genkey args; + sc_file_t *temppubfile=NULL, *keyfile=NULL; + unsigned char *keybuf=NULL; + size_t bytes, mod_len, exp_len, pub_len, keybits; + int r,delete_ok=0; + + if (obj->type != SC_PKCS15_TYPE_PRKEY_RSA) { + sc_error(card->ctx, "JCOP supports only RSA keys."); + return SC_ERROR_NOT_SUPPORTED; + } + + r=sc_profile_get_file(profile, "temp-pubkey", &temppubfile); + if (r < 0) + goto out; + + r = sc_select_file(card, &key_info->path, &keyfile); + if (r < 0) + goto out; + + mod_len = key_info->modulus_length / 8; + exp_len = 4; + bytes = mod_len / 2; + pub_len = 2 + mod_len + exp_len; + temppubfile->size = pub_len; + + r = sc_pkcs15init_fixup_file(profile, temppubfile); + if (r < 0) + goto out; + + r = sc_pkcs15init_create_file(profile, card, temppubfile); + if (r < 0) + goto out; + + delete_ok=1; + r = sc_pkcs15init_authenticate(profile, card, temppubfile, SC_AC_OP_UPDATE); + if (r < 0) + goto out; + r = sc_pkcs15init_authenticate(profile, card, keyfile, SC_AC_OP_UPDATE); + if (r < 0) + goto out; + + keybits = key_info->modulus_length; + + /* generate key */ + /* keysize is _not_ passed to the card at any point. it appears to + infer it from the file size */ + memset(&args, 0, sizeof(args)); + args.exponent = 0x10001; + sc_append_file_id(&args.pub_file_ref, temppubfile->id); + sc_append_file_id(&args.pri_file_ref, keyfile->id); + keybuf=malloc(keybits / 8); + if (!keybuf) { + r=SC_ERROR_OUT_OF_MEMORY; + goto out; + } + args.pubkey = keybuf; + args.pubkey_len = keybits / 8; + + r = sc_card_ctl(card, SC_CARDCTL_JCOP_GENERATE_KEY, (void *)&args); + if (r < 0) + goto out; + + /* extract public key */ + pubkey->algorithm = SC_ALGORITHM_RSA; + pubkey->u.rsa.modulus.len = keybits / 8; + pubkey->u.rsa.modulus.data = keybuf; + pubkey->u.rsa.exponent.len = 3; + pubkey->u.rsa.exponent.data = (u8 *) malloc(3); + if (!pubkey->u.rsa.exponent.data) { + pubkey->u.rsa.modulus.data = NULL; + r=SC_ERROR_OUT_OF_MEMORY; + goto out; + } + memcpy(pubkey->u.rsa.exponent.data, "\x01\x00\x01", 3); + + out: + if (r < 0 && keybuf) + free(keybuf); + if (delete_ok) + sc_pkcs15init_rmdir(card, profile, temppubfile); + if (keyfile) + sc_file_free(keyfile); + if (temppubfile) + sc_file_free(temppubfile); + return r; +} + + + +static struct sc_pkcs15init_operations sc_pkcs15init_jcop_operations; + +struct sc_pkcs15init_operations *sc_pkcs15init_get_jcop_ops(void) +{ + sc_pkcs15init_jcop_operations.erase_card = jcop_erase_card; + sc_pkcs15init_jcop_operations.init_app = jcop_init_app; + sc_pkcs15init_jcop_operations.select_pin_reference = jcop_select_pin_reference; + sc_pkcs15init_jcop_operations.create_pin = jcop_create_pin; + sc_pkcs15init_jcop_operations.create_key = jcop_create_key; + sc_pkcs15init_jcop_operations.store_key = jcop_store_key; + sc_pkcs15init_jcop_operations.generate_key = jcop_generate_key; + + return &sc_pkcs15init_jcop_operations; +} + + diff --git a/src/pkcs15init/pkcs15-lib.c b/src/pkcs15init/pkcs15-lib.c index 039ba6743d..23653730d8 100644 --- a/src/pkcs15init/pkcs15-lib.c +++ b/src/pkcs15init/pkcs15-lib.c @@ -134,6 +134,7 @@ static struct profile_operations { { "flex", (void *) sc_pkcs15init_get_cryptoflex_ops }, { "cyberflex", (void *) sc_pkcs15init_get_cyberflex_ops }, { "etoken", (void *) sc_pkcs15init_get_etoken_ops }, + { "jcop", (void *) sc_pkcs15init_get_jcop_ops }, { NULL, NULL }, };