0f5c642d49
When trying to build with latest libcacard-2.5.1, I hit the following error: In file included from hw/usb/ccid-card-passthru.c:12:0: /usr/include/cacard/vscard_common.h:26:2: error: #warning "Only <libcacard.h> can be included directly" [-Werror=cpp] #warning "Only <libcacard.h> can be included directly" While it was fixed in libcacard upstream (so that individual files can be included directly), it doesn't make much sense. Let's switch to including the main libcacard.h and also require at least libcacard-2.5.1 which introduced it. It's available since late 2015. Signed-off-by: Michal Privoznik <mprivozn@redhat.com> Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com> Message-id: 3c36db1dc0702763ebb7966cc27428ed67d43804.1522751624.git.mprivozn@redhat.com [ kraxel: fix include path ] Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
412 lines
13 KiB
C
412 lines
13 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 "qemu/osdep.h"
|
|
#include <libcacard.h>
|
|
#include "chardev/char-fe.h"
|
|
#include "qemu/error-report.h"
|
|
#include "qemu/sockets.h"
|
|
#include "ccid.h"
|
|
#include "qapi/error.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 VSCARD_IN_SIZE 65536
|
|
|
|
/* maximum size of ATR - from 7816-3 */
|
|
#define MAX_ATR_SIZE 40
|
|
|
|
typedef struct PassthruState PassthruState;
|
|
|
|
struct PassthruState {
|
|
CCIDCardState base;
|
|
CharBackend 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;
|
|
};
|
|
|
|
#define TYPE_CCID_PASSTHRU "ccid-card-passthru"
|
|
#define PASSTHRU_CCID_CARD(obj) \
|
|
OBJECT_CHECK(PassthruState, (obj), TYPE_CCID_PASSTHRU)
|
|
|
|
/*
|
|
* 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);
|
|
/* XXX this blocks entire thread. Rewrite to use
|
|
* qemu_chr_fe_write and background I/O callbacks */
|
|
qemu_chr_fe_write_all(&s->cs, (uint8_t *)&scr_msg_header,
|
|
sizeof(VSCMsgHeader));
|
|
qemu_chr_fe_write_all(&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_fe_deinit(&card->cs, true);
|
|
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_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 = PASSTHRU_CCID_CARD(base);
|
|
|
|
if (!qemu_chr_fe_backend_connected(&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 = PASSTHRU_CCID_CARD(base);
|
|
|
|
*len = card->atr_length;
|
|
return card->atr;
|
|
}
|
|
|
|
static void passthru_realize(CCIDCardState *base, Error **errp)
|
|
{
|
|
PassthruState *card = PASSTHRU_CCID_CARD(base);
|
|
|
|
card->vscard_in_pos = 0;
|
|
card->vscard_in_hdr = 0;
|
|
if (qemu_chr_fe_backend_connected(&card->cs)) {
|
|
error_setg(errp, "ccid-card-passthru: initing chardev");
|
|
qemu_chr_fe_set_handlers(&card->cs,
|
|
ccid_card_vscard_can_read,
|
|
ccid_card_vscard_read,
|
|
ccid_card_vscard_event, NULL, card, NULL, true);
|
|
ccid_card_vscard_send_init(card);
|
|
} else {
|
|
error_setg(errp, "missing chardev");
|
|
return;
|
|
}
|
|
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);
|
|
}
|
|
|
|
static VMStateDescription passthru_vmstate = {
|
|
.name = "ccid-card-passthru",
|
|
.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->realize = passthru_realize;
|
|
cc->get_atr = passthru_get_atr;
|
|
cc->apdu_from_guest = passthru_apdu_from_guest;
|
|
set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
|
|
dc->desc = "passthrough smartcard";
|
|
dc->vmsd = &passthru_vmstate;
|
|
dc->props = passthru_card_properties;
|
|
}
|
|
|
|
static const TypeInfo passthru_card_info = {
|
|
.name = TYPE_CCID_PASSTHRU,
|
|
.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)
|