diff --git a/src/libopensc/Makefile.am b/src/libopensc/Makefile.am index 86d4a48f..c8017574 100644 --- a/src/libopensc/Makefile.am +++ b/src/libopensc/Makefile.am @@ -10,10 +10,15 @@ lib_LTLIBRARIES = libopensc.la libopensc_la_SOURCES = asn1.c base64.c sec.c log.c sc.c card.c iso7816.c \ pkcs15.c pkcs15-cert.c pkcs15-pin.c \ pkcs15-prkey.c pkcs15-sec.c pkcs15-cache.c \ - card-setec.c card-flex.c \ + card-setec.c card-flex.c card-gpk.c \ card-emv.c card-default.c libopensc_la_LDFLAGS = -version-info 0:5:0 + +if HAVE_SSL +libopensc_la_LIBADD = @LIBPCSC@ @LIBCRYPTO@ +else libopensc_la_LIBADD = @LIBPCSC@ +endif include_HEADERS = opensc.h opensc-pkcs15.h opensc-emv.h noinst_HEADERS = sc-asn1.h sc-log.h sc-internal.h diff --git a/src/libopensc/card-gpk.c b/src/libopensc/card-gpk.c new file mode 100644 index 00000000..fbee698d --- /dev/null +++ b/src/libopensc/card-gpk.c @@ -0,0 +1,823 @@ +/* + * GPK4000 driver for opensc + * + * Copyright (C) 2002 Olaf Kirch + */ + +#include "sc-internal.h" +#include "sc-log.h" +#include +#include +#include + +#ifdef HAVE_OPENSSL + +/* GPK4000 variants */ +enum { + GPK4000_su256, + GPK4000_s, + GPK4000_sp, + GPK4000_sdo, +}; + +#define GPK_SEL_MF 0x00 +#define GPK_SEL_DF 0x01 +#define GPK_SEL_EF 0x02 +#define GPK_SEL_AID 0x04 +#define GPK_FID_MF 0x3F00 + +/* + * GPK4000 private data + */ +struct gpk_private_data { + int variant; + + /* access control bits of file most recently selected */ + u_int16_t ac[3]; + + /* is non-zero if we should use secure messaging */ + u_int8_t key_set : 1; + u_int8_t key_local : 1, + key_sfi : 5; + u_int8_t key[16]; +}; +#define OPSDATA(card) ((struct gpk_private_data *) ((card)->ops_data)) + +/* + * ATRs of GPK4000 cards courtesy of libscez + */ +static struct atrinfo { + unsigned char atr[SC_MAX_ATR_SIZE]; + unsigned int atr_len; + int variant; +} atrlist[] = { + { "\x3B\x27\x00\x80\x65\xA2\x04\x01\x01\x37", 10, GPK4000_s }, + { "\x3B\x27\x00\x80\x65\xA2\x05\x01\x01\x37", 10, GPK4000_sp }, + { "\x3B\x27\x00\x80\x65\xA2\x0C\x01\x01\x37", 10, GPK4000_su256 }, + { "\x3B\xA7\x00\x40\x14\x80\x65\xA2\x14\x01\x01\x37", 12, GPK4000_sdo }, + + { "", 0, -1 } +}; + +/* + * Driver and card ops structures + */ +static struct sc_card_operations gpk_ops; +static const struct sc_card_driver gpk_drv = { + NULL, + "Gemplus GPK 4000 driver", + "gpk", + &gpk_ops +}; + + +/* + * Identify the card variant based on the ATR + */ +static struct atrinfo * +gpk_identify(struct sc_card *card) +{ + struct atrinfo *ai; + + for (ai = atrlist; ai->atr_len; ai++) { + if (card->atr_len >= ai->atr_len + && !memcmp(card->atr, ai->atr, ai->atr_len)) + return ai; + } + return NULL; +} + +/* + * return 1 iff this driver can handle the card + */ +static int +gpk_match(struct sc_card *card) +{ + return gpk_identify(card)? 1 : 0; +} + +/* + * Initialize the card struct + */ +static int +gpk_init(struct sc_card *card) +{ + struct gpk_private_data *priv; + struct atrinfo *ai; + + if (!(ai = gpk_identify(card))) + return SC_ERROR_INVALID_CARD; + card->ops_data = priv = malloc(sizeof(*priv)); + if (card->ops_data == NULL) + return SC_ERROR_OUT_OF_MEMORY; + memset(priv, 0, sizeof(*priv)); + priv->variant = ai->variant; + card->cla = 0; + + return 0; +} + +/* + * Card is being closed; discard any private data etc + */ +static int +gpk_finish(struct sc_card *card) +{ + if (card->ops_data) + free(card->ops_data); + card->ops_data = NULL; + return 0; +} + +/* + * Error code handling for the GPK4000. + * sc_sw_to_errorcode doesn't seem to handle all of the + * status words the GPK is capable of returning + */ +static int +gpk_sw_to_errorcode(struct sc_card *card, u_int8_t sw1, u_int8_t sw2) +{ + u_int16_t sw = (sw1 << 8) | sw2; + + if ((sw & 0xFFF0) == 0x63C0) { + error(card->ctx, "wrong PIN, %u tries left", sw&0xf); + return SC_ERROR_PIN_CODE_INCORRECT; + } + + switch (sw) { + case 0x6400: + error(card->ctx, "wrong crypto context"); + return SC_ERROR_OBJECT_NOT_VALID; /* XXX ??? */ + case 0x6581: + error(card->ctx, "out of space on card or file"); + return SC_ERROR_OUT_OF_MEMORY; + case 0x6981: + return SC_ERROR_FILE_NOT_FOUND; + case 0x6A80: + case 0x6b00: + return SC_ERROR_INVALID_ARGUMENTS; + } + + return sc_sw_to_errorcode(card, sw1, sw2); +} + +/* + * Select a DF/EF + */ +static int +match_path(struct sc_card *card, u_int16_t **pathptr, size_t *pathlen, + int need_info) +{ + u_int16_t *curptr, *ptr; + size_t curlen, len; + size_t i; + + curptr = (u_int16_t *) card->cache.current_path.value; + curlen = card->cache.current_path.len; + ptr = *pathptr; + len = *pathlen; + + if (curlen < 1 || len < 1) + return 0; + + /* Skip the MF if present */ + if (ptr[0] != GPK_FID_MF) { + curptr++; + curlen--; + } + + if (len < curlen) + return 0; + + for (i = 0; i < len; i++) { + if (ptr[i] != curptr[i]) + return 0; + } + + /* Exact match? */ + if (len == curlen && need_info) + return 0; + + *pathptr = ptr + i; + *pathlen = len - i; + return 1; +} + +static inline unsigned int +ac_to_acl(u_int16_t ac) +{ + unsigned int npins, pin; + unsigned int res = 0; + + npins = (ac >> 14) & 3; + if (npins == 3) + return SC_AC_NEVER; + pin = ac & 0xFF; + while (npins--) { + switch (pin & 7) { + case 0: res |= SC_AC_CHV1; break; + case 1: res |= SC_AC_CHV2; break; + default:return SC_AC_NEVER; + } + pin >>= 4; + } + + /* Check whether secure messaging key is specified */ + if (ac & 0x1F00) + res |= SC_AC_PRO; + + return res; +} + +/* + * Convert ACLs requested by the application to access condition + * bits supported by the GPK. Since these do not map 1:1 there's + * some fuzz involved. + */ +static inline void +acl_to_ac(unsigned int acl, u_int8_t *ac) +{ + ac[0] = ac[1] = 0; + + if (acl == SC_AC_NEVER) { + ac[0] = 0xC0; + return; + } + + /* XXX should we set the "local" flag for PINs or not? + * OpenSC does not provide for a "lock file" operation + * that lets us freeze the ac bits after setting up the file. + */ + if (acl & SC_AC_CHV2) { + ac[0] += 0x40; + ac[1] |= 1; + } + if (acl & SC_AC_CHV1) { + ac[0] += 0x40; + ac[1] <<= 4; + ac[1] |= 0; + } + + /* XXX should we set the "local" flag on key files or not? + * OpenSC does not provide for a "lock file" operation + * that lets us freeze the ac bits after setting up the file. + */ + if (acl & SC_AC_PRO) { + ac[0] |= 0x01; + } +} + +static int +gpk_parse_fileinfo(struct sc_card *card, + const u_int8_t *buf, size_t buflen, + struct sc_file *file) +{ + struct gpk_private_data *priv = OPSDATA(card); + const u_int8_t *sp, *end, *next; + int i; + + memset(file, 0, sizeof(*file)); + for (i = 0; i < SC_MAX_AC_OPS; i++) + file->acl[i] = SC_AC_UNKNOWN; + + end = buf + buflen; + for (sp = buf; sp + 2 < end; sp = next) { + next = sp + 2 + sp[1]; + if (next > end) + break; + if (sp[0] == 0x84) { + /* ignore if name is longer than what it should be */ + if (sp[1] > sizeof(file->name)) + continue; + memset(file->name, 0, sizeof(file->name)); + memcpy(file->name, sp+2, sp[1]); + } else + if (sp[0] == 0x85) { + unsigned int ac1, ac2, ac3; + + file->id = (sp[4] << 8) | sp[5]; + file->size = (sp[8] << 8) | sp[9]; + file->record_length = sp[7]; + + /* Map ACLs */ + priv->ac[0] = (sp[10] << 8) | sp[11]; + priv->ac[1] = (sp[12] << 8) | sp[13]; + priv->ac[2] = (sp[14] << 8) | sp[15]; /* EF only */ + ac1 = ac_to_acl(priv->ac[0]); + ac2 = ac_to_acl(priv->ac[1]); + ac3 = ac_to_acl(priv->ac[2]); + + /* Examine file type */ + switch (sp[6] & 7) { + case 0x01: case 0x02: case 0x03: case 0x04: + case 0x05: case 0x06: case 0x07: + file->type = SC_FILE_TYPE_WORKING_EF; + file->ef_structure = sp[6] & 7; + file->acl[SC_AC_OP_READ] = ac3; + file->acl[SC_AC_OP_WRITE] = ac3; + file->acl[SC_AC_OP_UPDATE] = ac1; + break; + case 0x00: /* 0x38 is DF */ + file->type = SC_FILE_TYPE_DF; + file->acl[SC_AC_OP_SELECT] = SC_AC_NONE; + file->acl[SC_AC_OP_LOCK] = ac1; + /* Icky: the GPK uses different ACLs + * for creating data files and + * 'sensitive' i.e. key files */ + file->acl[SC_AC_OP_CREATE] = ac2; + file->acl[SC_AC_OP_DELETE] = SC_AC_NEVER; + file->acl[SC_AC_OP_REHABILITATE] = SC_AC_NEVER; + file->acl[SC_AC_OP_INVALIDATE] = SC_AC_NEVER; + file->acl[SC_AC_OP_LIST_FILES] = SC_AC_NEVER; + break; + } + } + } + + if (file->record_length) + file->record_count = file->size / file->record_length; + file->magic = SC_FILE_MAGIC; + + return 0; +} + +static int +gpk_select(struct sc_card *card, u_int8_t kind, + const u_int8_t *buf, size_t buflen, + struct sc_file *file) +{ + struct gpk_private_data *priv = OPSDATA(card); + struct sc_apdu apdu; + u_int8_t resbuf[SC_MAX_APDU_BUFFER_SIZE]; + int r; + + /* If we're about to select a DF, invalidate secure messaging keys */ + if (kind == GPK_SEL_MF || kind == GPK_SEL_DF) { + memset(priv->key, 0, sizeof(priv->key)); + priv->key_set = 0; + } + + /* do the apdu thing */ + sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, 0xA4, kind, 0); + apdu.data = buf; + apdu.datalen = buflen; + apdu.lc = apdu.datalen; + apdu.resp = resbuf; + apdu.resplen = file? sizeof(resbuf) : 0; + + r = sc_transmit_apdu(card, &apdu); + SC_TEST_RET(card->ctx, r, "APDU transmit failed"); + r = gpk_sw_to_errorcode(card, apdu.sw1, apdu.sw2); + SC_TEST_RET(card->ctx, r, "Card returned error"); + + /* Nothing we can say about it... invalidate + * path cache */ + if (kind == GPK_SEL_AID) { + card->cache.current_path.len = 0; + } + + if (file == NULL) + return 0; + + return gpk_parse_fileinfo(card, apdu.resp, apdu.resplen, file); +} + +static int +gpk_select_id(struct sc_card *card, u_int8_t kind, u_int16_t fid, + struct sc_file *file) +{ + struct sc_path *cp = &card->cache.current_path; + u_int8_t fbuf[2]; + int r; + + fbuf[0] = fid >> 8; + fbuf[1] = fid & 0xff; + r = gpk_select(card, kind, fbuf, 2, file); + + /* Fix up the path cache */ + if (r == 0) { + u_int16_t *path = (u_int16_t *) cp->value; + + if (fid == GPK_FID_MF) { + path[0] = fid; + cp->len = 1; + } else + if (cp->len + 1 <= SC_MAX_PATH_SIZE / 2) { + path[cp->len++] = fid; + } else { + cp->len = 0; + } + } else { + cp->len = 0; + } + return r; +} + +static int +gpk_select_file(struct sc_card *card, const struct sc_path *path, + struct sc_file *file) +{ + u_int16_t pathtmp[SC_MAX_PATH_SIZE/2]; + u_int16_t *pathptr; + size_t pathlen, n; + int locked = 0, r = 0, use_relative = 0; + u_int8_t leaf_type; + + SC_FUNC_CALLED(card->ctx, 3); + + /* Handle the AID case first */ + if (path->type == SC_PATH_TYPE_DF_NAME) { + if (path->len > 16) + return SC_ERROR_INVALID_ARGUMENTS; + r = gpk_select(card, GPK_SEL_AID, + path->value, path->len, file); + goto done; + } + + /* Now we know we're dealing with 16bit FIDs, either as + * an absolute path name (SC_PATH_TYPE_PATH) or a relative + * FID (SC_PATH_TYPE_FILE_ID) + * + * The API should really tell us whether this is a DF or EF + * we're selecting. All we can do is read tea leaves... + */ + leaf_type = GPK_SEL_EF; + +try_again: + if ((path->len & 1) || path->len > sizeof(pathtmp)) + return SC_ERROR_INVALID_ARGUMENTS; + pathptr = pathtmp; + for (n = 0; n < path->len; n += 2) + pathptr[n>>1] = (path->value[n] << 8)|path->value[n+1]; + pathlen = path->len >> 1; + + /* See whether we can skip an initial portion of the + * (absolute) path */ + if (path->type == SC_PATH_TYPE_PATH) { + use_relative = match_path(card, &pathptr, &pathlen, file != 0); + if (pathlen == 0) + goto done; + } else { + /* SC_PATH_TYPE_FILEID */ + if (pathlen > 1) + return SC_ERROR_INVALID_ARGUMENTS; + use_relative = 1; + } + + if (pathlen == 1 && pathptr[0] == GPK_FID_MF) { + /* Select just the MF */ + leaf_type = GPK_SEL_MF; + } else { + if (!locked++) { + r = sc_lock(card); + SC_TEST_RET(card->ctx, r, "sc_lock() failed"); + } + + /* Do we need to select the MF first? */ + if (!use_relative) { + r = gpk_select_id(card, GPK_SEL_MF, GPK_FID_MF, NULL); + if (r) + sc_unlock(card); + SC_TEST_RET(card->ctx, r, "Unable to select MF"); + + /* Consume the MF FID if it's there */ + if (pathptr[0] == GPK_FID_MF) { + pathptr++; + pathlen--; + } + if (pathlen == 0) + goto done; + } + + /* Next comes a DF, if at all. + * This loop can deal with nesting levels > 1 even + * though the GPK4000 doesn't support it. */ + while (pathlen > 1) { + r = gpk_select_id(card, GPK_SEL_DF, pathptr[0], NULL); + if (r) + sc_unlock(card); + SC_TEST_RET(card->ctx, r, "Unable to select DF"); + pathptr++; + pathlen--; + } + } + + /* Remaining component will be a DF or EF. How do we find out? + * All we can do is try */ + r = gpk_select_id(card, leaf_type, pathptr[0], file); + if (r) { + /* Did we guess EF, and were wrong? If so, invalidate + * path cache and try again; this time aiming for a DF */ + if (leaf_type == GPK_SEL_EF) { + card->cache.current_path.len = 0; + leaf_type = GPK_SEL_DF; + goto try_again; + } + } + +done: + if (locked) + sc_unlock(card); + return r; +} + +/* + * Secure messaging + */ +static int +gpk_compute_crycks(struct sc_card *card, struct sc_apdu *apdu, + u_int8_t *crycks1) +{ + struct gpk_private_data *priv = OPSDATA(card); + des_key_schedule k1, k2; + u_int8_t in[8], out[8], block[64]; + unsigned int len = 0, i, j; + + /* Set the key schedule */ + des_set_key_unchecked((des_cblock *) priv->key, k1); + des_set_key_unchecked((des_cblock *) (priv->key+8), k2); + + /* Fill block with 0x00 and then with the data. */ + memset(block, 0x00, sizeof(block)); + block[len++] = apdu->cla; + block[len++] = apdu->ins; + block[len++] = apdu->p1; + block[len++] = apdu->p2; + block[len++] = apdu->lc + 3; + if ((i = apdu->datalen) + len > sizeof(block)) + i = sizeof(block) - len; + memcpy(block+len, apdu->data, i); + len += i; + + /* Set IV */ + memset(in, 0x00, 8); + + for (j = 0; j < len; ) { + for (i = 0; i < 8; i++, j++) + in[i] ^= block[j]; + des_ecb3_encrypt((des_cblock *)in, + (des_cblock *)out, + k1, k2, k1, DES_ENCRYPT); + memcpy(in, out, 8); + } + + memcpy((u_int8_t *) (apdu->data + apdu->datalen), out + 5, 3); + apdu->datalen += 3; + apdu->lc += 3; + apdu->le = 3; + if (crycks1) + memcpy(crycks1, out, 3); + memset(k1, 0, sizeof(k1)); + memset(k2, 0, sizeof(k2)); + memset(in, 0, sizeof(in)); + memset(out, 0, sizeof(out)); + memset(block, 0, sizeof(block)); + return 0; +} + +/* + * Create a file or directory. + * This is a bit tricky because we abuse the ef_structure + * field to transport file types that are non-standard + * (the GPK4000 has lots of bizarre file types). + */ +static int +gpk_create_file(struct sc_card *card, struct sc_file *file) +{ + struct gpk_private_data *priv = OPSDATA(card); + struct sc_apdu apdu; + u_int8_t data[28+3], crycks[3], resp[3]; + size_t datalen, namelen; + int r; + + /* Prepare APDU */ + memset(&apdu, 0, sizeof(apdu)); + apdu.cla = 0x80; /* assume no secure messaging */ + apdu.cse = SC_APDU_CASE_3_SHORT; + apdu.ins = 0xE0; + apdu.p2 = 0x00; + + /* clear data */ + memset(data, 0, sizeof(data)); + datalen = 12; + + /* FID */ + data[0] = file->id >> 8; + data[1] = file->id & 0xFF; + + /* encode ACLs */ + if (file->type == SC_FILE_TYPE_DF) { + /* The GPK4000 has separate AC bits for + * creating sensitive files and creating + * data files. Since OpenSC has just the notion + * of "file" we use the same ACL for both AC words + */ + apdu.p1 = 0x01; /* create DF */ + data[2] = 0x38; + acl_to_ac(file->acl[SC_AC_OP_CREATE], data + 6); + acl_to_ac(file->acl[SC_AC_OP_CREATE], data + 8); + if ((namelen = file->namelen) != 0) { + if (namelen > 16) + return SC_ERROR_INVALID_ARGUMENTS; + memcpy(data+datalen, file->name, namelen); + data[5] = namelen; + datalen += namelen; + } + } else { + apdu.p1 = 0x02; /* create EF */ + data[2] = file->ef_structure; + data[3] = file->record_length; + data[4] = file->size >> 8; + data[5] = file->size & 0xff; + acl_to_ac(file->acl[SC_AC_OP_UPDATE], data + 6); + acl_to_ac(file->acl[SC_AC_OP_WRITE], data + 8); + acl_to_ac(file->acl[SC_AC_OP_READ], data + 10); + } + + apdu.data = data; + apdu.datalen = datalen; + apdu.lc = datalen; + + if (priv->key_set) { + apdu.cla = 0x84; + apdu.cse = SC_APDU_CASE_4_SHORT; + r = gpk_compute_crycks(card, &apdu, crycks); + if (r) + return r; + apdu.resp = resp; + apdu.resplen = sizeof(resp); /* XXX? */ + } + + r = sc_transmit_apdu(card, &apdu); + SC_TEST_RET(card->ctx, r, "APDU transmit failed"); + r = gpk_sw_to_errorcode(card, apdu.sw1, apdu.sw2); + SC_TEST_RET(card->ctx, r, "Card returned error"); + + if (priv->key_set) { + /* verify CRYCKS response? */ + if (apdu.resplen != 3 + || memcmp(resp, crycks, 3)) { + printf("XXX Secure messaging: bad resp\n"); + } + } + + return r; +} + +/* + * Set the secure messaging key following a Select FileKey + */ +static int +gpk_set_filekey(const u_int8_t *key, const u_int8_t *challenge, + const u_int8_t *r_rn, u_int8_t *kats) +{ + des_key_schedule k1, k2; + des_cblock out; + int r = 0; + + des_set_key_unchecked((des_cblock *) key, k1); + des_set_key_unchecked((des_cblock *) (key+8), k2); + + des_ecb3_encrypt((des_cblock *)(r_rn+4), (des_cblock *) kats, + k1, k2, k1, DES_ENCRYPT); + des_ecb3_encrypt((des_cblock *)(r_rn+4), (des_cblock *) (kats+8), + k2, k1, k2, DES_ENCRYPT); + + /* Verify Cryptogram presented by the card terminal + * XXX: what is the appropriate error code to return + * here? INVALID_ARGS doesn't seem quite right + */ + des_set_key_unchecked((des_cblock *) kats, k1); + des_set_key_unchecked((des_cblock *) (kats+8), k2); + + des_ecb3_encrypt((des_cblock *) challenge, &out, + k1, k2, k1, DES_ENCRYPT ); + if (memcmp(r_rn, out+4, 4) != 0) + r = SC_ERROR_INVALID_ARGUMENTS; + + memset(k1, 0, sizeof(k1)); + memset(k2, 0, sizeof(k2)); + memset(out, 0, sizeof(out)); + return r; +} + +/* + * Verify a key presented by the user for secure messaging + */ +static int +gpk_select_key(struct sc_card *card, int ref, const u8 *buf, size_t buflen) +{ + struct gpk_private_data *priv = OPSDATA(card); + struct sc_apdu apdu; + u_int8_t random[8], resp[258]; + unsigned int n, sfi, key_sfi = 0; + int r; + + if (buflen != 16) + return SC_ERROR_INVALID_ARGUMENTS; + + /* The opensc API doesn't tell us what key it wants to + * select, and why. We need to look at the ACs of + * the most recently selected file and guess + */ + key_sfi = 0; + for (n = 0; n < 3; n++) { + sfi = (priv->ac[n] >> 8) & 0x3F; + if (sfi & 0xF) { + if (key_sfi && key_sfi != sfi) { + /* Hm, the file has ACLs with two + * different keys. I'm unable to guess + * which one I should use, so I throw + * up my hands in disgust. + */ + /* XXX fix errror code? */ + return SC_ERROR_INVALID_ARGUMENTS; + } + key_sfi = sfi; + } + } + + /* If no key required, assume transport key :-/ */ + if (key_sfi == 0) + key_sfi = 0x01; + + /* XXX now do the SelFk */ + RAND_pseudo_bytes(random, sizeof(random)); + memset(&apdu, 0, sizeof(apdu)); + apdu.cla = 0x80; + apdu.cse = SC_APDU_CASE_4_SHORT; + apdu.ins = 0x28; + apdu.p1 = ref << 1; + apdu.p2 = key_sfi; + apdu.data = random; + apdu.datalen = sizeof(random); + apdu.lc = apdu.datalen; + apdu.resp = resp; + apdu.resplen = sizeof(resp); + + r = sc_transmit_apdu(card, &apdu); + SC_TEST_RET(card->ctx, r, "APDU transmit failed"); + r = gpk_sw_to_errorcode(card, apdu.sw1, apdu.sw2); + SC_TEST_RET(card->ctx, r, "Card returned error"); + + if (apdu.resplen != 12) { + r = SC_ERROR_UNKNOWN_REPLY; + } else + if ((r = gpk_set_filekey(buf, random, resp, priv->key)) == 0) { + priv->key_set = 1; + priv->key_local = (key_sfi & 0x20)? 1 : 0; + priv->key_sfi = key_sfi & 0x1f; + } + + memset(resp, 0, sizeof(resp)); + return r; +} + +/* + * Verify key (for external auth/secure messaging) or PIN + * presented by the user + */ +static int +gpk_verify(struct sc_card *card, unsigned int type, int ref, + const u8 *buf, size_t buflen, int *tries_left) +{ + if (tries_left) + *tries_left = -1; + switch (type) { + case SC_AC_PRO: + return gpk_select_key(card, ref, buf, buflen); + } + return SC_ERROR_INVALID_ARGUMENTS; +} + +/* + * Initialize the driver struct + */ +static const struct sc_card_driver * +sc_get_driver() +{ + if (gpk_ops.match_card == NULL) { + const struct sc_card_driver *iso_drv; + + iso_drv = sc_get_iso7816_driver(); + gpk_ops = *iso_drv->ops; + + gpk_ops.match_card = gpk_match; + gpk_ops.init = gpk_init; + gpk_ops.finish = gpk_finish; + gpk_ops.select_file = gpk_select_file; + /* The GPK4000 doesn't have a read directory command. */ + //gpk_ops.list_files = gpk_list_files; + gpk_ops.verify = gpk_verify; + gpk_ops.create_file = gpk_create_file; + } + return &gpk_drv; +} + +const struct sc_card_driver * +sc_get_gpk_driver() +{ + return sc_get_driver(); +} + +#endif /* ifdef OPENSSL */ diff --git a/src/libopensc/iso7816.c b/src/libopensc/iso7816.c index 233e423c..9fc139b1 100644 --- a/src/libopensc/iso7816.c +++ b/src/libopensc/iso7816.c @@ -57,7 +57,7 @@ static int iso7816_read_record(struct sc_card *card, int r; sc_format_apdu(card, &apdu, SC_APDU_CASE_2_SHORT, 0xB2, rec_nr, 0); - apdu.p2 = (rec_nr & SC_READ_RECORD_EF_ID_MASK) << 3; + apdu.p2 = (flags & SC_READ_RECORD_EF_ID_MASK) << 3; if (flags & SC_READ_RECORD_BY_REC_NR) apdu.p2 |= 0x04; @@ -663,6 +663,86 @@ static int iso7816_compute_signature(struct sc_card *card, SC_FUNC_RETURN(card->ctx, 4, sc_sw_to_errorcode(card, apdu.sw1, apdu.sw2)); } +static int iso7816_change_reference_data(struct sc_card *card, unsigned int type, + int ref, const u8 *old, size_t oldlen, + const u8 *new, size_t newlen, + int *tries_left) +{ + struct sc_apdu apdu; + u8 sbuf[SC_MAX_APDU_BUFFER_SIZE]; + int r, p1 = 0, len = oldlen + newlen; + + if (len >= SC_MAX_APDU_BUFFER_SIZE) + SC_FUNC_RETURN(card->ctx, 1, SC_ERROR_INVALID_ARGUMENTS); + switch (type) { + case SC_AC_CHV1: + case SC_AC_CHV2: + break; + default: + return SC_ERROR_INVALID_ARGUMENTS; + } + if (oldlen == 0) + p1 = 1; + sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, 0x24, p1, ref); + memcpy(sbuf, old, oldlen); + memcpy(sbuf + oldlen, new, newlen); + apdu.lc = len; + apdu.datalen = len; + apdu.data = sbuf; + apdu.resplen = 0; + + r = sc_transmit_apdu(card, &apdu); + memset(sbuf, 0, len); + SC_TEST_RET(card->ctx, r, "APDU transmit failed"); + if (apdu.sw1 == 0x63 && (apdu.sw2 & 0xF0) == 0xC0) { + if (tries_left != NULL) + *tries_left = apdu.sw2 & 0x0F; + SC_FUNC_RETURN(card->ctx, 1, SC_ERROR_PIN_CODE_INCORRECT); + } + return sc_sw_to_errorcode(card, apdu.sw1, apdu.sw2); +} + +static int iso7816_reset_retry_counter(struct sc_card *card, unsigned int type, int ref, + const u8 *puk, size_t puklen, const u8 *new, + size_t newlen) +{ + struct sc_apdu apdu; + u8 sbuf[MAX_BUFFER_SIZE]; + int r, p1 = 0, len = puklen + newlen; + + if (len >= MAX_BUFFER_SIZE) + SC_FUNC_RETURN(card->ctx, 1, SC_ERROR_INVALID_ARGUMENTS); + switch (type) { + case SC_AC_CHV1: + case SC_AC_CHV2: + break; + default: + return SC_ERROR_INVALID_ARGUMENTS; + } + if (puklen == 0) { + if (newlen == 0) + p1 = 3; + else + p1 = 2; + } else { + if (newlen == 0) + p1 = 1; + else + p1 = 0; + } + sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, 0x2C, p1, ref); + memcpy(sbuf, puk, puklen); + memcpy(sbuf + puklen, new, newlen); + apdu.lc = len; + apdu.datalen = len; + apdu.data = sbuf; + apdu.resplen = 0; + + r = sc_transmit_apdu(card, &apdu); + memset(sbuf, 0, len); + SC_TEST_RET(card->ctx, r, "APDU transmit failed"); + return sc_sw_to_errorcode(card, apdu.sw1, apdu.sw2); +} static struct sc_card_operations iso_ops = { NULL, @@ -698,6 +778,8 @@ const struct sc_card_driver * sc_get_iso7816_driver(void) iso_ops.set_security_env = iso7816_set_security_env; iso_ops.restore_security_env = iso7816_restore_security_env; iso_ops.compute_signature = iso7816_compute_signature; + iso_ops.reset_retry_counter = iso7816_reset_retry_counter; + iso_ops.change_reference_data = iso7816_change_reference_data; } return &iso_driver; } diff --git a/src/libopensc/opensc.h b/src/libopensc/opensc.h index 791e3f89..2720dcfc 100644 --- a/src/libopensc/opensc.h +++ b/src/libopensc/opensc.h @@ -347,11 +347,13 @@ struct sc_card_operations { * to the function decipher. */ int (*compute_signature)(struct sc_card *card, const u8 * data, size_t data_len, u8 * out, size_t outlen); - int (*change_reference_data)(struct sc_card *card, int ref_qualifier, + int (*change_reference_data)(struct sc_card *card, unsigned int type, + int ref_qualifier, const u8 *old, size_t oldlen, const u8 *newref, size_t newlen, int *tries_left); - int (*reset_retry_counter)(struct sc_card *card, int ref_qualifier, + int (*reset_retry_counter)(struct sc_card *card, unsigned int type, + int ref_qualifier, const u8 *puk, size_t puklen, const u8 *newref, size_t newlen); /* @@ -517,11 +519,13 @@ int sc_compute_signature(struct sc_card *card, const u8 * data, size_t data_len, u8 * out, size_t outlen); int sc_verify(struct sc_card *card, unsigned int type, int ref, const u8 *buf, size_t buflen, int *tries_left); -int sc_change_reference_data(struct sc_card *card, int ref, const u8 *old, - size_t oldlen, const u8 *newref, size_t newlen, +int sc_change_reference_data(struct sc_card *card, unsigned int type, + int ref, const u8 *old, size_t oldlen, + const u8 *newref, size_t newlen, int *tries_left); -int sc_reset_retry_counter(struct sc_card *card, int ref, const u8 *puk, - size_t puklen, const u8 *newref, size_t newlen); +int sc_reset_retry_counter(struct sc_card *card, unsigned int type, + int ref, const u8 *puk, size_t puklen, + const u8 *newref, size_t newlen); /* ISO 7816-9 */ int sc_create_file(struct sc_card *card, struct sc_file *file); @@ -545,6 +549,7 @@ extern const struct sc_card_driver *sc_get_iso7816_driver(void); extern const struct sc_card_driver *sc_get_emv_driver(void); extern const struct sc_card_driver *sc_get_setec_driver(void); extern const struct sc_card_driver *sc_get_flex_driver(void); +extern const struct sc_card_driver *sc_get_gpk_driver(void); extern const struct sc_card_driver *sc_get_default_driver(void); #ifdef __cplusplus diff --git a/src/libopensc/pkcs15-pin.c b/src/libopensc/pkcs15-pin.c index 661137d0..6a1121f3 100644 --- a/src/libopensc/pkcs15-pin.c +++ b/src/libopensc/pkcs15-pin.c @@ -288,7 +288,7 @@ int sc_pkcs15_change_pin(struct sc_pkcs15_card *p15card, memset(pinbuf, pin->pad_char, pin->stored_length * 2); memcpy(pinbuf, oldpin, oldpinlen); memcpy(pinbuf + pin->stored_length, newpin, newpinlen); - r = sc_change_reference_data(card, pin->auth_id.value[0], pinbuf, + r = sc_change_reference_data(card, SC_AC_CHV1, pin->auth_id.value[0], pinbuf, pin->stored_length, pinbuf+pin->stored_length, pin->stored_length, &pin->tries_left); memset(pinbuf, 0, pin->stored_length * 2); diff --git a/src/libopensc/sc.c b/src/libopensc/sc.c index cee30bdd..d9c70c58 100644 --- a/src/libopensc/sc.c +++ b/src/libopensc/sc.c @@ -182,12 +182,12 @@ int sc_establish_context(struct sc_context **ctx_out) #if 1 ctx->card_drivers[i++] = sc_get_flex_driver(); #endif -#if 1 - ctx->card_drivers[i++] = sc_get_iso7816_driver(); -#endif #if 1 ctx->card_drivers[i++] = sc_get_emv_driver(); #endif +#if 1 && defined(HAVE_OPENSSL) + ctx->card_drivers[i++] = sc_get_gpk_driver(); +#endif #if 1 /* this should be last in line */ ctx->card_drivers[i++] = sc_get_default_driver(); diff --git a/src/libopensc/sec.c b/src/libopensc/sec.c index 1ee661b0..cd0ebd2f 100644 --- a/src/libopensc/sec.c +++ b/src/libopensc/sec.c @@ -113,68 +113,33 @@ int sc_verify(struct sc_card *card, unsigned int type, int ref, SC_FUNC_RETURN(card->ctx, 2, r); } -int sc_change_reference_data(struct sc_card *card, int ref, const u8 *old, - size_t oldlen, const u8 *new, size_t newlen, +int sc_change_reference_data(struct sc_card *card, unsigned int type, + int ref, const u8 *old, size_t oldlen, + const u8 *newref, size_t newlen, int *tries_left) { - struct sc_apdu apdu; - u8 sbuf[MAX_BUFFER_SIZE]; - int r, p1 = 0, len = oldlen + newlen; + int r; + assert(card != NULL); SC_FUNC_CALLED(card->ctx, 1); - if (len >= MAX_BUFFER_SIZE) - SC_FUNC_RETURN(card->ctx, 1, SC_ERROR_INVALID_ARGUMENTS); - if (oldlen == 0) - p1 = 1; - sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, 0x24, p1, ref); - memcpy(sbuf, old, oldlen); - memcpy(sbuf + oldlen, new, newlen); - apdu.lc = len; - apdu.datalen = len; - apdu.data = sbuf; - apdu.resplen = 0; - - r = sc_transmit_apdu(card, &apdu); - memset(sbuf, 0, len); - SC_TEST_RET(card->ctx, r, "APDU transmit failed"); - if (apdu.sw1 == 0x63 && (apdu.sw2 & 0xF0) == 0xC0) { - if (tries_left != NULL) - *tries_left = apdu.sw2 & 0x0F; - SC_FUNC_RETURN(card->ctx, 1, SC_ERROR_PIN_CODE_INCORRECT); - } - SC_FUNC_RETURN(card->ctx, 1, sc_sw_to_errorcode(card, apdu.sw1, apdu.sw2)); + if (card->ops->change_reference_data == NULL) + SC_FUNC_RETURN(card->ctx, 2, SC_ERROR_NOT_SUPPORTED); + r = card->ops->change_reference_data(card, type, ref, old, oldlen, + newref, newlen, tries_left); + SC_FUNC_RETURN(card->ctx, 1, r); } -int sc_reset_retry_counter(struct sc_card *card, int ref, const u8 *puk, - size_t puklen, const u8 *new, size_t newlen) +int sc_reset_retry_counter(struct sc_card *card, unsigned int type, int ref, + const u8 *puk, size_t puklen, const u8 *newref, + size_t newlen) { - struct sc_apdu apdu; - u8 sbuf[MAX_BUFFER_SIZE]; - int r, p1 = 0, len = puklen + newlen; + int r; - if (len >= MAX_BUFFER_SIZE) - SC_FUNC_RETURN(card->ctx, 1, SC_ERROR_INVALID_ARGUMENTS); - if (puklen == 0) { - if (newlen == 0) - p1 = 3; - else - p1 = 2; - } else { - if (newlen == 0) - p1 = 1; - else - p1 = 0; - } - sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, 0x2C, p1, ref); - memcpy(sbuf, puk, puklen); - memcpy(sbuf + puklen, new, newlen); - apdu.lc = len; - apdu.datalen = len; - apdu.data = sbuf; - apdu.resplen = 0; - - r = sc_transmit_apdu(card, &apdu); - memset(sbuf, 0, len); - SC_TEST_RET(card->ctx, r, "APDU transmit failed"); - SC_FUNC_RETURN(card->ctx, 1, sc_sw_to_errorcode(card, apdu.sw1, apdu.sw2)); + assert(card != NULL); + SC_FUNC_CALLED(card->ctx, 1); + if (card->ops->reset_retry_counter == NULL) + SC_FUNC_RETURN(card->ctx, 2, SC_ERROR_NOT_SUPPORTED); + r = card->ops->reset_retry_counter(card, type, ref, puk, puklen, + newref, newlen); + SC_FUNC_RETURN(card->ctx, 1, r); } diff --git a/src/tools/Makefile.am b/src/tools/Makefile.am index 4ca878d2..6e4d0b0b 100644 --- a/src/tools/Makefile.am +++ b/src/tools/Makefile.am @@ -13,6 +13,7 @@ endif opensc_tool_SOURCES = opensc-tool.c util.c opensc_tool_LDADD = @GETOPTSRC@ opensc_explorer_SOURCES = opensc-explorer.c util.c +## FIXME: conditionally add -lreadline opensc_explorer_LDADD = @GETOPTSRC@ pkcs15_tool_SOURCES = pkcs15-tool.c util.c pkcs15_tool_LDADD = @GETOPTSRC@ diff --git a/src/tools/opensc-explorer.c b/src/tools/opensc-explorer.c index f2e9167b..cf4cda11 100644 --- a/src/tools/opensc-explorer.c +++ b/src/tools/opensc-explorer.c @@ -22,6 +22,13 @@ #include #include #include + +#undef USE_READLINE + +#ifdef USE_READLINE +#include +#endif + #include "util.h" int opt_reader = 0; @@ -171,6 +178,7 @@ int do_ls() check_ret(r, SC_AC_OP_SELECT, "unable to select file", ¤t_file); return -1; } + file.id = (cur[0] << 8) | cur[1]; cur += 2; count -= 2; print_file(&file); @@ -229,12 +237,56 @@ int do_cd(const char *arg) return 0; } -int do_cat(const char *arg) +int read_and_print_binary_file(struct sc_file *file) +{ + unsigned int idx = 0; + u8 buf[128]; + size_t count; + int r; + + count = file->size; + while (count) { + int c = count > sizeof(buf) ? sizeof(buf) : count; + + r = sc_read_binary(card, idx, buf, c, 0); + if (r < 0) { + check_ret(r, SC_AC_OP_READ, "read failed", file); + return -1; + } + if (r != c) { + printf("expecting %d, got only %d bytes.\n", c, r); + return -1; + } + hex_dump_asc(stdout, buf, c, idx); + idx += c; + count -= c; + } + return 0; +} + +int read_and_print_record_file(struct sc_file *file) { u8 buf[256]; + int rec, r; + + for (rec = 0; ; rec++) { + r = sc_read_record(card, rec, buf, sizeof(buf), SC_READ_RECORD_BY_REC_NR); + if (r == SC_ERROR_RECORD_NOT_FOUND) + return 0; + if (r < 0) { + check_ret(r, SC_AC_OP_READ, "read failed", file); + return -1; + } + printf("Record %d:\n", rec); + hex_dump_asc(stdout, buf, r, 0); + } + + return 0; +} + +int do_cat(const char *arg) +{ int r, error = 0; - size_t count = 0; - unsigned int idx = 0; struct sc_path path; struct sc_file file; int not_current = 1; @@ -254,26 +306,10 @@ int do_cat(const char *arg) return -1; } } - count = file.size; - while (count) { - int c = count > sizeof(buf) ? sizeof(buf) : count; - - r = sc_read_binary(card, idx, buf, c, 0); - if (r < 0) { - check_ret(r, SC_AC_OP_READ, "read failed", &file); - error = 1; - goto err; - } - if (r != c) { - printf("expecting %d, got only %d bytes.\n", c, r); - error = 1; - goto err; - } - hex_dump_asc(stdout, buf, c, idx); - idx += c; - count -= c; - } -err: + if (file.ef_structure == SC_FILE_EF_TRANSPARENT) + read_and_print_binary_file(&file); + else + read_and_print_record_file(&file); if (not_current) { r = sc_select_file(card, ¤t_path, NULL); if (r) { @@ -478,7 +514,7 @@ usage: int do_verify(const char *arg, const char *arg2) { const char *types[] = { - "CHV", "KEY" + "CHV", "KEY", "PRO" }; int i, type = -1, ref, r, tries_left = -1; u8 buf[30]; @@ -486,7 +522,7 @@ int do_verify(const char *arg, const char *arg2) if (strlen(arg) == 0 || strlen(arg2) == 0) goto usage; - for (i = 0; i < 2; i++) + for (i = 0; i < 3; i++) if (strncasecmp(arg, types[i], 3) == 0) { type = i; break; @@ -499,6 +535,11 @@ int do_verify(const char *arg, const char *arg2) printf("Invalid key reference.\n"); goto usage; } + if (arg2[0] == '"') { + for (++arg2, i = 0; i < sizeof(buf) && arg2[i] != '"'; i++) + buf[i] = arg2[i]; + buflen = i; + } else if (sc_hex_to_bin(arg2, buf, &buflen) != 0) { printf("Invalid key value.\n"); goto usage; @@ -510,6 +551,9 @@ int do_verify(const char *arg, const char *arg2) case 1: type = SC_AC_AUT; break; + case 2: + type = SC_AC_PRO; + break; } r = sc_verify(card, type, ref, buf, buflen, &tries_left); if (r) { @@ -721,10 +765,52 @@ void usage() printf(" %s\n", cmds[i]); } +static int parse_line(char *in, char **argv) +{ + int argc; + + for (argc = 0; argc < 3; argc++) { + in += strspn(in, " \t\n"); + if (*in == '\0') + return argc; + if (*in == '"') { + /* Parse quoted string */ + argv[argc] = in++; + in += strcspn(in, "\""); + if (*in++ != '"') + return 0; + } else { + /* White space delimited word */ + argv[argc] = in; + in += strcspn(in, " \t\n"); + } + if (*in != '\0') + *in++ = '\0'; + } + return argc; +} + +#ifndef USE_READLINE +char * readline(const char *prompt) +{ + static char buf[128]; + + printf("%s", prompt); + fflush(stdout); + if (fgets(buf, sizeof(buf), stdin) == NULL) + return NULL; + if (strlen(buf) == 0) + return NULL; + if (buf[strlen(buf)-1] == '\n') + buf[strlen(buf)-1] = '\0'; + return buf; +} +#endif + int main(int argc, char * const argv[]) { int r, c, long_optind = 0, err = 0; - char line[80], cmd[80], arg[80], arg2[80]; + char *line, *cargv[3]; printf("OpenSC Explorer version %s\n", sc_version); @@ -789,33 +875,29 @@ int main(int argc, char * const argv[]) } while (1) { int i; + char prompt[40]; - printf("OpenSC ["); + sprintf(prompt, "OpenSC ["); for (i = 0; i < current_path.len; i++) { if ((i & 1) == 0 && i) - printf("/"); - printf("%02X", current_path.value[i]); + sprintf(prompt+strlen(prompt), "/"); + sprintf(prompt+strlen(prompt), "%02X", current_path.value[i]); } - printf("]> "); - fflush(stdout); - fflush(stdin); - if (fgets(line, sizeof(line), stdin) == NULL) - break; - if (strlen(line) == 0) - break; - r = sscanf(line, "%s %s %s", cmd, arg, arg2); + sprintf(prompt+strlen(prompt), "]> "); + line = readline(prompt); + if (line == NULL) + break; + r = parse_line(line, cargv); if (r < 1) continue; - if (r < 3) - arg2[0] = 0; - if (r < 2) - arg[0] = 0; - r = ambiguous_match(cmds, nr_cmds, cmd); + while (r < 3) + cargv[r++] = ""; + r = ambiguous_match(cmds, nr_cmds, cargv[0]); if (r < 0) { usage(); continue; } - handle_cmd(r, arg, arg2); + handle_cmd(r, cargv[1], cargv[2]); } end: die(err);