b16352acf3
Introduces a new utility function: parse_debug_env to avoid code duplication. This overrides whatever debug value is set on the corresponding devices from the command line, and is meant to ease the usage with any management stack. For libvirt you can set environment variables by extending the dom namespace, i.e: <domain type='kvm' id='3' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'> <qemu:commandline> <qemu:env name='QEMU_CCID_PASSTHRU_DEBUG' value='4'/> <qemu:env name='QEMU_CCID_DEBUG' value='4'/> </qemu:commandline> </domain> Signed-off-by: Alon Levy <alevy@redhat.com> Reviewed-by: Marc-André Lureau <mlureau@redhat.com>
413 lines
12 KiB
C
413 lines
12 KiB
C
/*
|
|
* CCID Passthru Card Device emulation
|
|
*
|
|
* Copyright (c) 2011 Red Hat.
|
|
* Written by Alon Levy.
|
|
*
|
|
* This work is licensed under the terms of the GNU GPL, version 2.1 or later.
|
|
* See the COPYING file in the top-level directory.
|
|
*/
|
|
|
|
#include "sysemu/char.h"
|
|
#include "qemu/sockets.h"
|
|
#include "monitor/monitor.h"
|
|
#include "ccid.h"
|
|
#include "libcacard/vscard_common.h"
|
|
|
|
#define DPRINTF(card, lvl, fmt, ...) \
|
|
do { \
|
|
if (lvl <= card->debug) { \
|
|
printf("ccid-card-passthru: " fmt , ## __VA_ARGS__); \
|
|
} \
|
|
} while (0)
|
|
|
|
#define D_WARN 1
|
|
#define D_INFO 2
|
|
#define D_MORE_INFO 3
|
|
#define D_VERBOSE 4
|
|
|
|
/* TODO: do we still need this? */
|
|
static const uint8_t DEFAULT_ATR[] = {
|
|
/*
|
|
* From some example somewhere
|
|
* 0x3B, 0xB0, 0x18, 0x00, 0xD1, 0x81, 0x05, 0xB1, 0x40, 0x38, 0x1F, 0x03, 0x28
|
|
*/
|
|
|
|
/* From an Athena smart card */
|
|
0x3B, 0xD5, 0x18, 0xFF, 0x80, 0x91, 0xFE, 0x1F, 0xC3, 0x80, 0x73, 0xC8, 0x21,
|
|
0x13, 0x08
|
|
};
|
|
|
|
|
|
#define PASSTHRU_DEV_NAME "ccid-card-passthru"
|
|
#define VSCARD_IN_SIZE 65536
|
|
|
|
/* maximum size of ATR - from 7816-3 */
|
|
#define MAX_ATR_SIZE 40
|
|
|
|
typedef struct PassthruState PassthruState;
|
|
|
|
struct PassthruState {
|
|
CCIDCardState base;
|
|
CharDriverState *cs;
|
|
uint8_t vscard_in_data[VSCARD_IN_SIZE];
|
|
uint32_t vscard_in_pos;
|
|
uint32_t vscard_in_hdr;
|
|
uint8_t atr[MAX_ATR_SIZE];
|
|
uint8_t atr_length;
|
|
uint8_t debug;
|
|
};
|
|
|
|
/*
|
|
* VSCard protocol over chardev
|
|
* This code should not depend on the card type.
|
|
*/
|
|
|
|
static void ccid_card_vscard_send_msg(PassthruState *s,
|
|
VSCMsgType type, uint32_t reader_id,
|
|
const uint8_t *payload, uint32_t length)
|
|
{
|
|
VSCMsgHeader scr_msg_header;
|
|
|
|
scr_msg_header.type = htonl(type);
|
|
scr_msg_header.reader_id = htonl(reader_id);
|
|
scr_msg_header.length = htonl(length);
|
|
qemu_chr_fe_write(s->cs, (uint8_t *)&scr_msg_header, sizeof(VSCMsgHeader));
|
|
qemu_chr_fe_write(s->cs, payload, length);
|
|
}
|
|
|
|
static void ccid_card_vscard_send_apdu(PassthruState *s,
|
|
const uint8_t *apdu, uint32_t length)
|
|
{
|
|
ccid_card_vscard_send_msg(
|
|
s, VSC_APDU, VSCARD_MINIMAL_READER_ID, apdu, length);
|
|
}
|
|
|
|
static void ccid_card_vscard_send_error(PassthruState *s,
|
|
uint32_t reader_id, VSCErrorCode code)
|
|
{
|
|
VSCMsgError msg = {.code = htonl(code)};
|
|
|
|
ccid_card_vscard_send_msg(
|
|
s, VSC_Error, reader_id, (uint8_t *)&msg, sizeof(msg));
|
|
}
|
|
|
|
static void ccid_card_vscard_send_init(PassthruState *s)
|
|
{
|
|
VSCMsgInit msg = {
|
|
.version = htonl(VSCARD_VERSION),
|
|
.magic = VSCARD_MAGIC,
|
|
.capabilities = {0}
|
|
};
|
|
|
|
ccid_card_vscard_send_msg(s, VSC_Init, VSCARD_UNDEFINED_READER_ID,
|
|
(uint8_t *)&msg, sizeof(msg));
|
|
}
|
|
|
|
static int ccid_card_vscard_can_read(void *opaque)
|
|
{
|
|
PassthruState *card = opaque;
|
|
|
|
return VSCARD_IN_SIZE >= card->vscard_in_pos ?
|
|
VSCARD_IN_SIZE - card->vscard_in_pos : 0;
|
|
}
|
|
|
|
static void ccid_card_vscard_handle_init(
|
|
PassthruState *card, VSCMsgHeader *hdr, VSCMsgInit *init)
|
|
{
|
|
uint32_t *capabilities;
|
|
int num_capabilities;
|
|
int i;
|
|
|
|
capabilities = init->capabilities;
|
|
num_capabilities =
|
|
1 + ((hdr->length - sizeof(VSCMsgInit)) / sizeof(uint32_t));
|
|
init->version = ntohl(init->version);
|
|
for (i = 0 ; i < num_capabilities; ++i) {
|
|
capabilities[i] = ntohl(capabilities[i]);
|
|
}
|
|
if (init->magic != VSCARD_MAGIC) {
|
|
error_report("wrong magic");
|
|
/* we can't disconnect the chardev */
|
|
}
|
|
if (init->version != VSCARD_VERSION) {
|
|
DPRINTF(card, D_WARN,
|
|
"got version %d, have %d", init->version, VSCARD_VERSION);
|
|
}
|
|
/* future handling of capabilities, none exist atm */
|
|
ccid_card_vscard_send_init(card);
|
|
}
|
|
|
|
static int check_atr(PassthruState *card, uint8_t *data, int len)
|
|
{
|
|
int historical_length, opt_bytes;
|
|
int td_count = 0;
|
|
int td;
|
|
|
|
if (len < 2) {
|
|
return 0;
|
|
}
|
|
historical_length = data[1] & 0xf;
|
|
opt_bytes = 0;
|
|
if (data[0] != 0x3b && data[0] != 0x3f) {
|
|
DPRINTF(card, D_WARN, "atr's T0 is 0x%X, not in {0x3b, 0x3f}\n",
|
|
data[0]);
|
|
return 0;
|
|
}
|
|
td_count = 0;
|
|
td = data[1] >> 4;
|
|
while (td && td_count < 2 && opt_bytes + historical_length + 2 < len) {
|
|
td_count++;
|
|
if (td & 0x1) {
|
|
opt_bytes++;
|
|
}
|
|
if (td & 0x2) {
|
|
opt_bytes++;
|
|
}
|
|
if (td & 0x4) {
|
|
opt_bytes++;
|
|
}
|
|
if (td & 0x8) {
|
|
opt_bytes++;
|
|
td = data[opt_bytes + 2] >> 4;
|
|
}
|
|
}
|
|
if (len < 2 + historical_length + opt_bytes) {
|
|
DPRINTF(card, D_WARN,
|
|
"atr too short: len %d, but historical_len %d, T1 0x%X\n",
|
|
len, historical_length, data[1]);
|
|
return 0;
|
|
}
|
|
if (len > 2 + historical_length + opt_bytes) {
|
|
DPRINTF(card, D_WARN,
|
|
"atr too long: len %d, but hist/opt %d/%d, T1 0x%X\n",
|
|
len, historical_length, opt_bytes, data[1]);
|
|
/* let it through */
|
|
}
|
|
DPRINTF(card, D_VERBOSE,
|
|
"atr passes check: %d total length, %d historical, %d optional\n",
|
|
len, historical_length, opt_bytes);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void ccid_card_vscard_handle_message(PassthruState *card,
|
|
VSCMsgHeader *scr_msg_header)
|
|
{
|
|
uint8_t *data = (uint8_t *)&scr_msg_header[1];
|
|
|
|
switch (scr_msg_header->type) {
|
|
case VSC_ATR:
|
|
DPRINTF(card, D_INFO, "VSC_ATR %d\n", scr_msg_header->length);
|
|
if (scr_msg_header->length > MAX_ATR_SIZE) {
|
|
error_report("ATR size exceeds spec, ignoring");
|
|
ccid_card_vscard_send_error(card, scr_msg_header->reader_id,
|
|
VSC_GENERAL_ERROR);
|
|
break;
|
|
}
|
|
if (!check_atr(card, data, scr_msg_header->length)) {
|
|
error_report("ATR is inconsistent, ignoring");
|
|
ccid_card_vscard_send_error(card, scr_msg_header->reader_id,
|
|
VSC_GENERAL_ERROR);
|
|
break;
|
|
}
|
|
memcpy(card->atr, data, scr_msg_header->length);
|
|
card->atr_length = scr_msg_header->length;
|
|
ccid_card_card_inserted(&card->base);
|
|
ccid_card_vscard_send_error(card, scr_msg_header->reader_id,
|
|
VSC_SUCCESS);
|
|
break;
|
|
case VSC_APDU:
|
|
ccid_card_send_apdu_to_guest(
|
|
&card->base, data, scr_msg_header->length);
|
|
break;
|
|
case VSC_CardRemove:
|
|
DPRINTF(card, D_INFO, "VSC_CardRemove\n");
|
|
ccid_card_card_removed(&card->base);
|
|
ccid_card_vscard_send_error(card,
|
|
scr_msg_header->reader_id, VSC_SUCCESS);
|
|
break;
|
|
case VSC_Init:
|
|
ccid_card_vscard_handle_init(
|
|
card, scr_msg_header, (VSCMsgInit *)data);
|
|
break;
|
|
case VSC_Error:
|
|
ccid_card_card_error(&card->base, *(uint32_t *)data);
|
|
break;
|
|
case VSC_ReaderAdd:
|
|
if (ccid_card_ccid_attach(&card->base) < 0) {
|
|
ccid_card_vscard_send_error(card, VSCARD_UNDEFINED_READER_ID,
|
|
VSC_CANNOT_ADD_MORE_READERS);
|
|
} else {
|
|
ccid_card_vscard_send_error(card, VSCARD_MINIMAL_READER_ID,
|
|
VSC_SUCCESS);
|
|
}
|
|
break;
|
|
case VSC_ReaderRemove:
|
|
ccid_card_ccid_detach(&card->base);
|
|
ccid_card_vscard_send_error(card,
|
|
scr_msg_header->reader_id, VSC_SUCCESS);
|
|
break;
|
|
default:
|
|
printf("usb-ccid: chardev: unexpected message of type %X\n",
|
|
scr_msg_header->type);
|
|
ccid_card_vscard_send_error(card, scr_msg_header->reader_id,
|
|
VSC_GENERAL_ERROR);
|
|
}
|
|
}
|
|
|
|
static void ccid_card_vscard_drop_connection(PassthruState *card)
|
|
{
|
|
qemu_chr_delete(card->cs);
|
|
card->vscard_in_pos = card->vscard_in_hdr = 0;
|
|
}
|
|
|
|
static void ccid_card_vscard_read(void *opaque, const uint8_t *buf, int size)
|
|
{
|
|
PassthruState *card = opaque;
|
|
VSCMsgHeader *hdr;
|
|
|
|
if (card->vscard_in_pos + size > VSCARD_IN_SIZE) {
|
|
error_report(
|
|
"no room for data: pos %d + size %d > %d. dropping connection.",
|
|
card->vscard_in_pos, size, VSCARD_IN_SIZE);
|
|
ccid_card_vscard_drop_connection(card);
|
|
return;
|
|
}
|
|
assert(card->vscard_in_pos < VSCARD_IN_SIZE);
|
|
assert(card->vscard_in_hdr < VSCARD_IN_SIZE);
|
|
memcpy(card->vscard_in_data + card->vscard_in_pos, buf, size);
|
|
card->vscard_in_pos += size;
|
|
hdr = (VSCMsgHeader *)(card->vscard_in_data + card->vscard_in_hdr);
|
|
|
|
while ((card->vscard_in_pos - card->vscard_in_hdr >= sizeof(VSCMsgHeader))
|
|
&&(card->vscard_in_pos - card->vscard_in_hdr >=
|
|
sizeof(VSCMsgHeader) + ntohl(hdr->length))) {
|
|
hdr->reader_id = ntohl(hdr->reader_id);
|
|
hdr->length = ntohl(hdr->length);
|
|
hdr->type = ntohl(hdr->type);
|
|
ccid_card_vscard_handle_message(card, hdr);
|
|
card->vscard_in_hdr += hdr->length + sizeof(VSCMsgHeader);
|
|
hdr = (VSCMsgHeader *)(card->vscard_in_data + card->vscard_in_hdr);
|
|
}
|
|
if (card->vscard_in_hdr == card->vscard_in_pos) {
|
|
card->vscard_in_pos = card->vscard_in_hdr = 0;
|
|
}
|
|
}
|
|
|
|
static void ccid_card_vscard_event(void *opaque, int event)
|
|
{
|
|
PassthruState *card = opaque;
|
|
|
|
switch (event) {
|
|
case CHR_EVENT_BREAK:
|
|
card->vscard_in_pos = card->vscard_in_hdr = 0;
|
|
break;
|
|
case CHR_EVENT_FOCUS:
|
|
break;
|
|
case CHR_EVENT_OPENED:
|
|
DPRINTF(card, D_INFO, "%s: CHR_EVENT_OPENED\n", __func__);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* End VSCard handling */
|
|
|
|
static void passthru_apdu_from_guest(
|
|
CCIDCardState *base, const uint8_t *apdu, uint32_t len)
|
|
{
|
|
PassthruState *card = DO_UPCAST(PassthruState, base, base);
|
|
|
|
if (!card->cs) {
|
|
printf("ccid-passthru: no chardev, discarding apdu length %d\n", len);
|
|
return;
|
|
}
|
|
ccid_card_vscard_send_apdu(card, apdu, len);
|
|
}
|
|
|
|
static const uint8_t *passthru_get_atr(CCIDCardState *base, uint32_t *len)
|
|
{
|
|
PassthruState *card = DO_UPCAST(PassthruState, base, base);
|
|
|
|
*len = card->atr_length;
|
|
return card->atr;
|
|
}
|
|
|
|
static int passthru_initfn(CCIDCardState *base)
|
|
{
|
|
PassthruState *card = DO_UPCAST(PassthruState, base, base);
|
|
|
|
card->vscard_in_pos = 0;
|
|
card->vscard_in_hdr = 0;
|
|
if (card->cs) {
|
|
DPRINTF(card, D_INFO, "initing chardev\n");
|
|
qemu_chr_add_handlers(card->cs,
|
|
ccid_card_vscard_can_read,
|
|
ccid_card_vscard_read,
|
|
ccid_card_vscard_event, card);
|
|
ccid_card_vscard_send_init(card);
|
|
} else {
|
|
error_report("missing chardev");
|
|
return -1;
|
|
}
|
|
card->debug = parse_debug_env("QEMU_CCID_PASSTHRU_DEBUG", D_VERBOSE,
|
|
card->debug);
|
|
assert(sizeof(DEFAULT_ATR) <= MAX_ATR_SIZE);
|
|
memcpy(card->atr, DEFAULT_ATR, sizeof(DEFAULT_ATR));
|
|
card->atr_length = sizeof(DEFAULT_ATR);
|
|
return 0;
|
|
}
|
|
|
|
static int passthru_exitfn(CCIDCardState *base)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static VMStateDescription passthru_vmstate = {
|
|
.name = PASSTHRU_DEV_NAME,
|
|
.version_id = 1,
|
|
.minimum_version_id = 1,
|
|
.fields = (VMStateField[]) {
|
|
VMSTATE_BUFFER(vscard_in_data, PassthruState),
|
|
VMSTATE_UINT32(vscard_in_pos, PassthruState),
|
|
VMSTATE_UINT32(vscard_in_hdr, PassthruState),
|
|
VMSTATE_BUFFER(atr, PassthruState),
|
|
VMSTATE_UINT8(atr_length, PassthruState),
|
|
VMSTATE_END_OF_LIST()
|
|
}
|
|
};
|
|
|
|
static Property passthru_card_properties[] = {
|
|
DEFINE_PROP_CHR("chardev", PassthruState, cs),
|
|
DEFINE_PROP_UINT8("debug", PassthruState, debug, 0),
|
|
DEFINE_PROP_END_OF_LIST(),
|
|
};
|
|
|
|
static void passthru_class_initfn(ObjectClass *klass, void *data)
|
|
{
|
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
|
CCIDCardClass *cc = CCID_CARD_CLASS(klass);
|
|
|
|
cc->initfn = passthru_initfn;
|
|
cc->exitfn = passthru_exitfn;
|
|
cc->get_atr = passthru_get_atr;
|
|
cc->apdu_from_guest = passthru_apdu_from_guest;
|
|
dc->desc = "passthrough smartcard";
|
|
dc->vmsd = &passthru_vmstate;
|
|
dc->props = passthru_card_properties;
|
|
}
|
|
|
|
static const TypeInfo passthru_card_info = {
|
|
.name = PASSTHRU_DEV_NAME,
|
|
.parent = TYPE_CCID_CARD,
|
|
.instance_size = sizeof(PassthruState),
|
|
.class_init = passthru_class_initfn,
|
|
};
|
|
|
|
static void ccid_card_passthru_register_types(void)
|
|
{
|
|
type_register_static(&passthru_card_info);
|
|
}
|
|
|
|
type_init(ccid_card_passthru_register_types)
|