933 lines
28 KiB
C
933 lines
28 KiB
C
/*
|
|
* card-jcop.c
|
|
*
|
|
* Copyright (C) 2003 Chaskiel Grundman <cg2v@andrew.cmu.edu>
|
|
*
|
|
* 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 <string.h>
|
|
#include <stdlib.h>
|
|
|
|
#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);
|
|
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();
|
|
}
|
|
|