opensc/src/libopensc/reader-ctapi.c

577 lines
15 KiB
C

/*
* reader-ctapi.c: Reader driver for CT-API
*
* Copyright (C) 2002 Juha Yrjölä <juha.yrjola@iki.fi>
*
* 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
#ifdef ENABLE_CTAPI
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include "common/libscdl.h"
#include "internal.h"
#include "ctbcs.h"
#define GET_PRIV_DATA(r) ((struct ctapi_private_data *) (r)->drv_data)
#ifdef _WIN32
typedef char pascal CT_INIT_TYPE(unsigned short ctn, unsigned short Pn);
typedef char pascal CT_CLOSE_TYPE(unsigned short ctn);
typedef char pascal CT_DATA_TYPE(unsigned short ctn, unsigned char *dad, \
unsigned char *sad, unsigned short lc, \
unsigned char *cmd, unsigned short *lr, \
unsigned char *rsp);
#else
typedef char CT_INIT_TYPE(unsigned short ctn, unsigned short Pn);
typedef char CT_CLOSE_TYPE(unsigned short ctn);
typedef char CT_DATA_TYPE(unsigned short ctn, unsigned char *dad, \
unsigned char *sad, unsigned short lc, \
unsigned char *cmd, unsigned short *lr, \
unsigned char *rsp);
#endif
struct ctapi_module {
char *name;
void *dlhandle;
int ctn_count;
};
struct ctapi_global_private_data {
int module_count;
struct ctapi_module *modules;
};
struct ctapi_functions {
CT_INIT_TYPE *CT_init;
CT_CLOSE_TYPE *CT_close;
CT_DATA_TYPE *CT_data;
};
/* Reader specific private data */
#define CTAPI_FU_KEYBOARD 0x1
#define CTAPI_FU_DISPLAY 0x2
#define CTAPI_FU_BIOMETRIC 0x4
#define CTAPI_FU_PRINTER 0x8
struct ctapi_private_data {
struct ctapi_functions funcs;
unsigned short ctn;
int ctapi_functional_units;
int slot;
};
/* Reset reader */
static int ctapi_reset(sc_reader_t *reader)
{
struct ctapi_private_data *priv = GET_PRIV_DATA(reader);
char rv;
u8 cmd[5], rbuf[256], sad, dad;
unsigned short lr;
cmd[0] = CTBCS_CLA;
cmd[1] = CTBCS_INS_RESET;
cmd[2] = priv->slot ? CTBCS_P1_INTERFACE1 + priv->slot : CTBCS_P1_CT_KERNEL;
cmd[3] = 0x00; /* No response. We might also use 0x01 (return ATR) or 0x02 (return historical bytes) here */
cmd[4] = 0x00;
dad = 1;
sad = 2;
lr = 256;
rv = priv->funcs.CT_data(priv->ctn, &dad, &sad, 5, cmd, &lr, rbuf);
if (rv || (lr < 2)) {
sc_log(reader->ctx, "Error getting status of terminal: %d, using defaults", rv);
return SC_ERROR_TRANSMIT_FAILED;
}
if (rbuf[lr-2] != 0x90) {
sc_log(reader->ctx, "SW1/SW2: 0x%x/0x%x", rbuf[lr-2], rbuf[lr-1]);
return SC_ERROR_TRANSMIT_FAILED;
}
return 0;
}
static int refresh_attributes(sc_reader_t *reader)
{
struct ctapi_private_data *priv = GET_PRIV_DATA(reader);
char rv;
u8 cmd[5], rbuf[256], sad, dad;
unsigned short lr;
if (reader->ctx->flags & SC_CTX_FLAG_TERMINATE)
return SC_ERROR_NOT_ALLOWED;
cmd[0] = CTBCS_CLA;
cmd[1] = CTBCS_INS_STATUS;
cmd[2] = CTBCS_P1_CT_KERNEL;
cmd[3] = CTBCS_P2_STATUS_ICC;
cmd[4] = 0x00;
dad = 1;
sad = 2;
lr = 256;
reader->flags = 0;
rv = priv->funcs.CT_data(priv->ctn, &dad, &sad, 5, cmd, &lr, rbuf);
if (rv || (lr < 3) || (rbuf[lr-2] != 0x90)) {
sc_log(reader->ctx, "Error getting status of terminal: %d/%d/0x%x", rv, lr, rbuf[lr-2]);
return SC_ERROR_TRANSMIT_FAILED;
}
if (lr < 4) {
if (rbuf[0] & CTBCS_DATA_STATUS_CARD)
reader->flags = SC_READER_CARD_PRESENT;
} else {
if (rbuf[0] != CTBCS_P2_STATUS_ICC) {
/* Should we be more tolerant here? I do not think so... */
sc_log(reader->ctx, "Invalid data object returned on CTBCS_P2_STATUS_ICC: 0x%x", rbuf[0]);
return SC_ERROR_TRANSMIT_FAILED;
}
/* Fixme - should not be reached */
sc_log(reader->ctx, "Returned status for %d slots", rbuf[1]);
reader->flags = SC_READER_CARD_PRESENT;
}
return 0;
}
static int ctapi_internal_transmit(sc_reader_t *reader,
const u8 *sendbuf, size_t sendsize,
u8 *recvbuf, size_t *recvsize,
unsigned long control)
{
struct ctapi_private_data *priv = GET_PRIV_DATA(reader);
u8 dad, sad;
unsigned short lr;
char rv;
if (reader->ctx->flags & SC_CTX_FLAG_TERMINATE)
return SC_ERROR_NOT_ALLOWED;
if (control)
dad = 1;
else
dad = 0;
sad = 2;
lr = *recvsize;
rv = priv->funcs.CT_data(priv->ctn, &dad, &sad, (unsigned short)sendsize, (u8 *) sendbuf, &lr, recvbuf);
if (rv != 0) {
sc_log(reader->ctx, "Error transmitting APDU: %d", rv);
return SC_ERROR_TRANSMIT_FAILED;
}
*recvsize = lr;
return 0;
}
static int ctapi_transmit(sc_reader_t *reader, sc_apdu_t *apdu)
{
size_t ssize, rsize, rbuflen = 0;
u8 *sbuf = NULL, *rbuf = NULL;
int r;
rsize = rbuflen = apdu->resplen + 2;
rbuf = malloc(rbuflen);
if (rbuf == NULL) {
r = SC_ERROR_OUT_OF_MEMORY;
goto out;
}
/* encode and log the APDU */
r = sc_apdu_get_octets(reader->ctx, apdu, &sbuf, &ssize, SC_PROTO_RAW);
if (r != SC_SUCCESS)
goto out;
sc_apdu_log(reader->ctx, SC_LOG_DEBUG_NORMAL, sbuf, ssize, 1);
r = ctapi_internal_transmit(reader, sbuf, ssize,
rbuf, &rsize, apdu->control);
if (r < 0) {
/* unable to transmit ... most likely a reader problem */
sc_log(reader->ctx, "unable to transmit");
goto out;
}
sc_apdu_log(reader->ctx, SC_LOG_DEBUG_NORMAL, rbuf, rsize, 0);
/* set response */
r = sc_apdu_set_resp(reader->ctx, apdu, rbuf, rsize);
out:
if (sbuf != NULL) {
sc_mem_clear(sbuf, ssize);
free(sbuf);
}
if (rbuf != NULL) {
sc_mem_clear(rbuf, rbuflen);
free(rbuf);
}
return r;
}
static int ctapi_detect_card_presence(sc_reader_t *reader)
{
int r;
r = refresh_attributes(reader);
if (r)
return r;
return reader->flags;
}
static int ctapi_connect(sc_reader_t *reader)
{
struct ctapi_private_data *priv = GET_PRIV_DATA(reader);
char rv;
u8 cmd[9], rbuf[256], sad, dad;
unsigned short lr;
int r;
if (reader->ctx->flags & SC_CTX_FLAG_TERMINATE)
return SC_ERROR_NOT_ALLOWED;
cmd[0] = CTBCS_CLA;
cmd[1] = CTBCS_INS_REQUEST;
cmd[2] = CTBCS_P1_INTERFACE1;
cmd[3] = CTBCS_P2_REQUEST_GET_ATR;
cmd[4] = 0x00;
dad = 1;
sad = 2;
lr = 256;
rv = priv->funcs.CT_data(priv->ctn, &dad, &sad, 5, cmd, &lr, rbuf);
if (rv || rbuf[lr-2] != 0x90) {
sc_log(reader->ctx, "Error activating card: %d", rv);
return SC_ERROR_TRANSMIT_FAILED;
}
if (lr < 2)
SC_FUNC_RETURN(reader->ctx, SC_LOG_DEBUG_NORMAL, SC_ERROR_INTERNAL);
lr -= 2;
if (lr > SC_MAX_ATR_SIZE)
return SC_ERROR_INTERNAL;
reader->atr.len = lr;
memcpy(reader->atr.value, rbuf, lr);
r = _sc_parse_atr(reader);
return 0;
}
static int ctapi_disconnect(sc_reader_t *reader)
{
return 0;
}
static int ctapi_lock(sc_reader_t *reader)
{
return 0;
}
static int ctapi_unlock(sc_reader_t *reader)
{
return 0;
}
static int ctapi_release(sc_reader_t *reader)
{
struct ctapi_private_data *priv = GET_PRIV_DATA(reader);
if (!(reader->ctx->flags & SC_CTX_FLAG_TERMINATE))
priv->funcs.CT_close(priv->ctn);
free(priv);
return 0;
}
static struct sc_reader_operations ctapi_ops;
static struct sc_reader_driver ctapi_drv = {
"CT-API module",
"ctapi",
&ctapi_ops,
NULL
};
static struct ctapi_module * add_module(struct ctapi_global_private_data *gpriv,
const char *name, void *dlhandle)
{
int i;
struct ctapi_module *p;
i = gpriv->module_count;
p = (struct ctapi_module *) realloc(gpriv->modules, sizeof(struct ctapi_module) * (i+1));
if (!p) {
return NULL;
}
gpriv->modules = p;
gpriv->modules[i].name = strdup(name);
gpriv->modules[i].dlhandle = dlhandle;
gpriv->modules[i].ctn_count = 0;
gpriv->module_count++;
return &gpriv->modules[i];
}
static int ctapi_load_module(sc_context_t *ctx,
struct ctapi_global_private_data *gpriv, scconf_block *conf)
{
const char *val;
struct ctapi_functions funcs;
struct ctapi_module *mod;
const scconf_list *list;
scconf_block *conf_block = NULL;
void *dlh;
int r, i, NumUnits;
u8 cmd[5], rbuf[256], sad, dad;
unsigned short lr;
list = scconf_find_list(conf, "ports");
if (list == NULL) {
sc_log(ctx, "No ports configured.");
return -1;
}
val = conf->name->data;
dlh = sc_dlopen(val);
if (!dlh) {
sc_log(ctx, "Unable to open shared library '%s': %s", val, sc_dlerror());
return -1;
}
funcs.CT_init = (CT_INIT_TYPE *) sc_dlsym(dlh, "CT_init");
if (!funcs.CT_init)
goto symerr;
funcs.CT_close = (CT_CLOSE_TYPE *) sc_dlsym(dlh, "CT_close");
if (!funcs.CT_close)
goto symerr;
funcs.CT_data = (CT_DATA_TYPE *) sc_dlsym(dlh, "CT_data");
if (!funcs.CT_data)
goto symerr;
mod = add_module(gpriv, val, dlh);
if (!mod)
goto symerr;
for (; list != NULL; list = list->next) {
int port;
char namebuf[128];
char rv;
sc_reader_t *reader;
struct ctapi_private_data *priv;
if (sscanf(list->data, "%d", &port) != 1) {
sc_log(ctx, "Port '%s' is not a number.", list->data);
continue;
}
rv = funcs.CT_init((unsigned short)mod->ctn_count, (unsigned short)port);
if (rv) {
sc_log(ctx, "CT_init() failed with %d", rv);
continue;
}
reader = calloc(1, sizeof(sc_reader_t));
priv = calloc(1, sizeof(struct ctapi_private_data));
if (!priv || !reader) {
free(reader);
free(priv);
return SC_ERROR_OUT_OF_MEMORY;
}
reader->drv_data = priv;
reader->ops = &ctapi_ops;
reader->driver = &ctapi_drv;
snprintf(namebuf, sizeof(namebuf), "CT-API %s, port %d", mod->name, port);
reader->name = strdup(namebuf);
priv->funcs = funcs;
priv->ctn = mod->ctn_count;
reader->max_send_size = SC_READER_SHORT_APDU_MAX_SEND_SIZE;
reader->max_recv_size = SC_READER_SHORT_APDU_MAX_RECV_SIZE;
conf_block = sc_get_conf_block(ctx, "reader_driver", "ctapi", 1);
if (conf_block) {
reader->max_send_size = scconf_get_int(conf_block, "max_send_size", reader->max_send_size);
reader->max_recv_size = scconf_get_int(conf_block, "max_recv_size", reader->max_recv_size);
if (scconf_get_bool(conf_block, "enable_escape", 0))
reader->flags |= SC_READER_ENABLE_ESCAPE;
}
r = _sc_add_reader(ctx, reader);
if (r) {
funcs.CT_close((unsigned short)mod->ctn_count);
free(priv);
free(reader->name);
free(reader);
break;
}
/* Detect functional units of the reader according to CT-BCS spec version 1.0
(14.04.2004, http://www.teletrust.de/down/mct1-0_t4.zip) */
cmd[0] = CTBCS_CLA;
cmd[1] = CTBCS_INS_STATUS;
cmd[2] = CTBCS_P1_CT_KERNEL;
cmd[3] = CTBCS_P2_STATUS_TFU;
cmd[4] = 0x00;
dad = 1;
sad = 2;
lr = 256;
rv = priv->funcs.CT_data(priv->ctn, &dad, &sad, 5, cmd, &lr, rbuf);
if (rv || (lr < 4) || (rbuf[lr-2] != 0x90)) {
sc_log(reader->ctx, "Error getting status of terminal: %d, using defaults", rv);
}
if (rbuf[0] != CTBCS_P2_STATUS_TFU) {
/* Number of slots might also detected by using CTBCS_P2_STATUS_ICC.
If you think that's important please do it... ;) */
sc_log(reader->ctx, "Invalid data object returned on CTBCS_P2_STATUS_TFU: 0x%x", rbuf[0]);
}
NumUnits = rbuf[1];
if (NumUnits + 4 > lr) {
sc_log(reader->ctx, "Invalid data returned: %d functional units, size %d", NumUnits, rv);
}
priv->ctapi_functional_units = 0;
for(i = 0; i < NumUnits; i++) {
switch(rbuf[i+2]) {
case CTBCS_P1_INTERFACE1:
case CTBCS_P1_INTERFACE2:
case CTBCS_P1_INTERFACE3:
case CTBCS_P1_INTERFACE4:
case CTBCS_P1_INTERFACE5:
case CTBCS_P1_INTERFACE6:
case CTBCS_P1_INTERFACE7:
case CTBCS_P1_INTERFACE8:
case CTBCS_P1_INTERFACE9:
case CTBCS_P1_INTERFACE10:
case CTBCS_P1_INTERFACE11:
case CTBCS_P1_INTERFACE12:
case CTBCS_P1_INTERFACE13:
case CTBCS_P1_INTERFACE14:
/* Maybe a weak point here if multiple interfaces are present and not returned
in the "canonical" order. This is not forbidden by the specs, but why should
anyone want to do that? */
sc_log(reader->ctx, "Found slot id 0x%x", rbuf[i+2]);
break;
case CTBCS_P1_DISPLAY:
priv->ctapi_functional_units |= CTAPI_FU_DISPLAY;
sc_log(reader->ctx, "Display detected");
break;
case CTBCS_P1_KEYPAD:
priv->ctapi_functional_units |= CTAPI_FU_KEYBOARD;
sc_log(reader->ctx, "Keypad detected");
break;
case CTBCS_P1_PRINTER:
priv->ctapi_functional_units |= CTAPI_FU_PRINTER;
sc_log(reader->ctx, "Printer detected");
break;
case CTBCS_P1_FINGERPRINT:
case CTBCS_P1_VOICEPRINT:
case CTBCS_P1_DSV:
case CTBCS_P1_FACE_RECOGNITION:
case CTBCS_P1_IRISSCAN:
priv->ctapi_functional_units |= CTAPI_FU_BIOMETRIC;
sc_log(reader->ctx, "Biometric sensor detected");
break;
default:
sc_log(reader->ctx, "Unknown functional unit 0x%x", rbuf[i+2]);
}
}
/* CT-BCS does not define Keyboard/Display for each slot, so I assume
those additional units can be used for each slot */
if (priv->ctapi_functional_units) {
if (priv->ctapi_functional_units & CTAPI_FU_KEYBOARD)
reader->capabilities |= SC_READER_CAP_PIN_PAD;
if (priv->ctapi_functional_units & CTAPI_FU_DISPLAY)
reader->capabilities |= SC_READER_CAP_DISPLAY;
}
ctapi_reset(reader);
refresh_attributes(reader);
mod->ctn_count++;
}
return 0;
symerr:
sc_log(ctx, "Unable to resolve CT-API symbols.");
sc_dlclose(dlh);
return -1;
}
static int ctapi_init(sc_context_t *ctx)
{
int i;
struct ctapi_global_private_data *gpriv;
scconf_block **blocks = NULL, *conf_block = NULL;
gpriv = calloc(1, sizeof(struct ctapi_global_private_data));
if (gpriv == NULL)
return SC_ERROR_OUT_OF_MEMORY;
ctx->reader_drv_data = gpriv;
conf_block = sc_get_conf_block(ctx, "reader_driver", "ctapi", 1);
if (conf_block) {
blocks = scconf_find_blocks(ctx->conf, conf_block, "module", NULL);
for (i = 0; blocks != NULL && blocks[i] != NULL; i++)
ctapi_load_module(ctx, gpriv, blocks[i]);
free(blocks);
}
return 0;
}
static int ctapi_finish(sc_context_t *ctx)
{
struct ctapi_global_private_data *priv = (struct ctapi_global_private_data *) ctx->reader_drv_data;
if (priv) {
int i;
for (i = 0; i < priv->module_count; i++) {
struct ctapi_module *mod = &priv->modules[i];
free(mod->name);
sc_dlclose(mod->dlhandle);
}
if (priv->module_count)
free(priv->modules);
free(priv);
}
return 0;
}
struct sc_reader_driver * sc_get_ctapi_driver(void)
{
ctapi_ops.init = ctapi_init;
ctapi_ops.finish = ctapi_finish;
ctapi_ops.detect_readers = NULL;
ctapi_ops.transmit = ctapi_transmit;
ctapi_ops.detect_card_presence = ctapi_detect_card_presence;
ctapi_ops.lock = ctapi_lock;
ctapi_ops.unlock = ctapi_unlock;
ctapi_ops.release = ctapi_release;
ctapi_ops.connect = ctapi_connect;
ctapi_ops.disconnect = ctapi_disconnect;
ctapi_ops.perform_verify = ctbcs_pin_cmd;
ctapi_ops.perform_pace = NULL;
ctapi_ops.use_reader = NULL;
return &ctapi_drv;
}
#endif