opensc/src/libopensc/reader-pcsc.c

940 lines
26 KiB
C
Raw Normal View History

/*
* reader-pcsc.c: Reader driver for PC/SC interface
*
* Copyright (C) 2002 Juha Yrj<EFBFBD>l<EFBFBD> <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
*/
#include "internal.h"
#ifdef HAVE_PCSC
#include "ctbcs.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#ifdef __APPLE__
#include <PCSC/wintypes.h>
#include <PCSC/winscard.h>
#else
#include <winscard.h>
#endif
/* Default timeout value for SCardGetStatusChange
* Needs to be increased for some broken PC/SC
* Lite implementations.
*/
#ifndef SC_CUSTOM_STATUS_TIMEOUT
#define SC_STATUS_TIMEOUT 0
#else
#define SC_STATUS_TIMEOUT SC_CUSTOM_STATUS_TIMEOUT
#endif
/* Some windows specific kludge */
#undef SCARD_PROTOCOL_ANY
#define SCARD_PROTOCOL_ANY (SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1)
#ifdef _WIN32
#define SCARD_SCOPE_GLOBAL SCARD_SCOPE_USER
/* Error printing */
#define PCSC_ERROR(ctx, desc, rv) sc_error(ctx, desc ": %lx\n", rv);
#else
#define PCSC_ERROR(ctx, desc, rv) sc_error(ctx, desc ": %s\n", pcsc_stringify_error(rv));
/* For mangling ioctl codes from IFDHandlers */
#define dw2i(a, x) ((((((a[x+3] << 8) + a[x+2]) << 8) + a[x+1]) << 8) + a[x])
#endif
#define GET_SLOT_PTR(s, i) (&(s)->slot[(i)])
#define GET_PRIV_DATA(r) ((struct pcsc_private_data *) (r)->drv_data)
#define GET_SLOT_DATA(r) ((struct pcsc_slot_data *) (r)->drv_data)
static int class2_pin_cmd(sc_reader_t *reader, sc_slot_info_t *slot,
struct sc_pin_cmd_data *data);
struct pcsc_global_private_data {
SCARDCONTEXT pcsc_ctx;
};
struct pcsc_private_data {
SCARDCONTEXT pcsc_ctx;
char *reader_name;
struct pcsc_global_private_data *gpriv;
};
struct pcsc_slot_data {
SCARDHANDLE pcsc_card;
SCARD_READERSTATE_A reader_state;
DWORD verify_ioctl;
DWORD modify_ioctl;
};
static int pcsc_detect_card_presence(sc_reader_t *reader, sc_slot_info_t *slot);
static int pcsc_ret_to_error(long rv)
{
switch (rv) {
case SCARD_W_REMOVED_CARD:
return SC_ERROR_CARD_REMOVED;
case SCARD_W_RESET_CARD:
return SC_ERROR_CARD_RESET;
case SCARD_E_NOT_TRANSACTED:
return SC_ERROR_TRANSMIT_FAILED;
case SCARD_W_UNRESPONSIVE_CARD:
return SC_ERROR_CARD_UNRESPONSIVE;
default:
return SC_ERROR_UNKNOWN;
}
}
static unsigned int pcsc_proto_to_opensc(DWORD proto)
{
switch (proto) {
case SCARD_PROTOCOL_T0:
return SC_PROTO_T0;
case SCARD_PROTOCOL_T1:
return SC_PROTO_T1;
case SCARD_PROTOCOL_RAW:
return SC_PROTO_RAW;
default:
return 0;
}
}
static DWORD opensc_proto_to_pcsc(unsigned int proto)
{
switch (proto) {
case SC_PROTO_T0:
return SCARD_PROTOCOL_T0;
case SC_PROTO_T1:
return SCARD_PROTOCOL_T1;
case SC_PROTO_RAW:
return SCARD_PROTOCOL_RAW;
default:
return 0;
}
}
static int pcsc_transmit(sc_reader_t *reader, sc_slot_info_t *slot,
const u8 *sendbuf, size_t sendsize,
u8 *recvbuf, size_t *recvsize,
unsigned long control)
{
SCARD_IO_REQUEST sSendPci, sRecvPci;
DWORD dwSendLength, dwRecvLength;
LONG rv;
SCARDHANDLE card;
struct pcsc_slot_data *pslot = GET_SLOT_DATA(slot);
assert(pslot != NULL);
card = pslot->pcsc_card;
sSendPci.dwProtocol = opensc_proto_to_pcsc(slot->active_protocol);
sSendPci.cbPciLength = sizeof(sSendPci);
sRecvPci.dwProtocol = opensc_proto_to_pcsc(slot->active_protocol);
sRecvPci.cbPciLength = sizeof(sRecvPci);
dwSendLength = sendsize;
dwRecvLength = *recvsize;
if (dwRecvLength > 258)
dwRecvLength = 258;
if (!control) {
rv = SCardTransmit(card, &sSendPci, sendbuf, dwSendLength,
&sRecvPci, recvbuf, &dwRecvLength);
} else {
#ifdef HAVE_PCSC_OLD
rv = SCardControl(card, sendbuf, dwSendLength,
recvbuf, &dwRecvLength);
#else
rv = SCardControl(card, (DWORD) control, sendbuf, dwSendLength,
recvbuf, dwRecvLength, &dwRecvLength);
#endif
}
if (rv != SCARD_S_SUCCESS) {
switch (rv) {
case SCARD_W_REMOVED_CARD:
return SC_ERROR_CARD_REMOVED;
case SCARD_W_RESET_CARD:
return SC_ERROR_CARD_RESET;
case SCARD_E_NOT_TRANSACTED:
if ((pcsc_detect_card_presence(reader, slot) &
SC_SLOT_CARD_PRESENT) == 0)
return SC_ERROR_CARD_REMOVED;
return SC_ERROR_TRANSMIT_FAILED;
default:
/* Windows' PC/SC returns 0x8010002f (??) if a card is removed */
if (pcsc_detect_card_presence(reader, slot) != 1)
return SC_ERROR_CARD_REMOVED;
PCSC_ERROR(reader->ctx, "SCardTransmit failed", rv);
return SC_ERROR_TRANSMIT_FAILED;
}
}
if (dwRecvLength < 2)
return SC_ERROR_UNKNOWN_DATA_RECEIVED;
*recvsize = dwRecvLength;
return 0;
}
static int refresh_slot_attributes(sc_reader_t *reader, sc_slot_info_t *slot)
{
struct pcsc_private_data *priv = GET_PRIV_DATA(reader);
struct pcsc_slot_data *pslot = GET_SLOT_DATA(slot);
LONG ret;
if (pslot->reader_state.szReader == NULL) {
pslot->reader_state.szReader = priv->reader_name;
pslot->reader_state.dwCurrentState = SCARD_STATE_UNAWARE;
pslot->reader_state.dwEventState = SCARD_STATE_UNAWARE;
} else {
pslot->reader_state.dwCurrentState = pslot->reader_state.dwEventState;
}
ret = SCardGetStatusChange(priv->pcsc_ctx, SC_STATUS_TIMEOUT, &pslot->reader_state, 1);
if (ret == (LONG)SCARD_E_TIMEOUT) { /* timeout: nothing changed */
slot->flags &= ~SCARD_STATE_CHANGED;
return 0;
}
if (ret != 0) {
PCSC_ERROR(reader->ctx, "SCardGetStatusChange failed", ret);
return pcsc_ret_to_error(ret);
}
if (pslot->reader_state.dwEventState & SCARD_STATE_PRESENT) {
int old_flags = slot->flags;
int maybe_changed = 0;
slot->flags |= SC_SLOT_CARD_PRESENT;
slot->atr_len = pslot->reader_state.cbAtr;
if (slot->atr_len > SC_MAX_ATR_SIZE)
slot->atr_len = SC_MAX_ATR_SIZE;
memcpy(slot->atr, pslot->reader_state.rgbAtr, slot->atr_len);
#ifndef _WIN32
/* On Linux, SCARD_STATE_CHANGED always means there was an
* insert or removal. But we may miss events that way. */
if (pslot->reader_state.dwEventState & SCARD_STATE_CHANGED) {
slot->flags |= SC_SLOT_CARD_CHANGED;
} else {
maybe_changed = 1;
}
#else
/* On windows, SCARD_STATE_CHANGED is turned on by lots of
* other events, so it gives us a lot of false positives.
* But if it's off, there really no change */
if (pslot->reader_state.dwEventState & SCARD_STATE_CHANGED) {
maybe_changed = 1;
}
#endif
/* If we aren't sure if the card state changed, check if
* the card handle is still valid. If the card changed,
* the handle will be invalid. */
slot->flags &= ~SC_SLOT_CARD_CHANGED;
if (maybe_changed && (old_flags & SC_SLOT_CARD_PRESENT)) {
DWORD readers_len = 0, state, prot, atr_len = 32;
unsigned char atr[32];
LONG rv = SCardStatus(pslot->pcsc_card, NULL, &readers_len,
&state, &prot, atr, &atr_len);
if (rv == (LONG)SCARD_W_REMOVED_CARD)
slot->flags |= SC_SLOT_CARD_CHANGED;
}
} else {
slot->flags &= ~(SC_SLOT_CARD_PRESENT|SC_SLOT_CARD_CHANGED);
}
return 0;
}
static int pcsc_detect_card_presence(sc_reader_t *reader, sc_slot_info_t *slot)
{
int rv;
if ((rv = refresh_slot_attributes(reader, slot)) < 0)
return rv;
return slot->flags;
}
/* Wait for an event to occur.
* This function ignores the list of slots, because with
* pcsc we have a 1:1 mapping of readers and slots anyway
*/
static int pcsc_wait_for_event(sc_reader_t **readers,
sc_slot_info_t **slots,
size_t nslots,
unsigned int event_mask,
int *reader,
unsigned int *event, int timeout)
{
sc_context_t *ctx;
SCARDCONTEXT pcsc_ctx;
LONG ret;
SCARD_READERSTATE_A rgReaderStates[SC_MAX_READERS];
unsigned long on_bits, off_bits;
time_t end_time, now, delta;
size_t i;
/* Prevent buffer overflow */
if (nslots >= SC_MAX_READERS)
return SC_ERROR_INVALID_ARGUMENTS;
on_bits = off_bits = 0;
if (event_mask & SC_EVENT_CARD_INSERTED) {
event_mask &= ~SC_EVENT_CARD_INSERTED;
on_bits |= SCARD_STATE_PRESENT;
}
if (event_mask & SC_EVENT_CARD_REMOVED) {
event_mask &= ~SC_EVENT_CARD_REMOVED;
off_bits |= SCARD_STATE_PRESENT;
}
if (event_mask != 0)
return SC_ERROR_INVALID_ARGUMENTS;
/* Find out the current status */
ctx = readers[0]->ctx;
pcsc_ctx = GET_PRIV_DATA(readers[0])->pcsc_ctx;
for (i = 0; i < nslots; i++) {
struct pcsc_private_data *priv = GET_PRIV_DATA(readers[i]);
rgReaderStates[i].szReader = priv->reader_name;
rgReaderStates[i].dwCurrentState = SCARD_STATE_UNAWARE;
rgReaderStates[i].dwEventState = SCARD_STATE_UNAWARE;
/* Can we handle readers from different PCSC contexts? */
if (priv->pcsc_ctx != pcsc_ctx)
return SC_ERROR_INVALID_ARGUMENTS;
}
ret = SCardGetStatusChange(pcsc_ctx, 0, rgReaderStates, nslots);
if (ret != 0) {
PCSC_ERROR(ctx, "SCardGetStatusChange(1) failed", ret);
return pcsc_ret_to_error(ret);
}
time(&now);
end_time = now + (timeout + 999) / 1000;
/* Wait for a status change and return if it's a card insert/removal
*/
for( ; ; ) {
SCARD_READERSTATE_A *rsp;
/* Scan the current state of all readers to see if they
* match any of the events we're polling for */
*event = 0;
for (i = 0, rsp = rgReaderStates; i < nslots; i++, rsp++) {
unsigned long state, prev_state;
prev_state = rsp->dwCurrentState;
state = rsp->dwEventState;
if ((state & on_bits & SCARD_STATE_PRESENT) &&
(prev_state & SCARD_STATE_EMPTY))
*event |= SC_EVENT_CARD_INSERTED;
if ((~state & off_bits & SCARD_STATE_PRESENT) &&
(prev_state & SCARD_STATE_PRESENT))
*event |= SC_EVENT_CARD_REMOVED;
if (*event) {
*reader = i;
return 0;
}
/* No match - copy the state so pcscd knows
* what to watch out for */
rsp->dwCurrentState = rsp->dwEventState;
}
/* Set the timeout if caller wants to time out */
if (timeout == 0)
return SC_ERROR_EVENT_TIMEOUT;
if (timeout > 0) {
time(&now);
if (now >= end_time)
return SC_ERROR_EVENT_TIMEOUT;
delta = end_time - now;
} else {
delta = 3600;
}
ret = SCardGetStatusChange(pcsc_ctx, 1000 * delta,
rgReaderStates, nslots);
if (ret == (LONG)SCARD_E_TIMEOUT) {
if (timeout < 0)
continue;
return SC_ERROR_EVENT_TIMEOUT;
}
if (ret != 0) {
PCSC_ERROR(ctx, "SCardGetStatusChange(2) failed", ret);
return pcsc_ret_to_error(ret);
}
}
}
static int pcsc_connect(sc_reader_t *reader, sc_slot_info_t *slot)
{
DWORD active_proto, protocol;
SCARDHANDLE card_handle;
LONG rv;
struct pcsc_private_data *priv = GET_PRIV_DATA(reader);
struct pcsc_slot_data *pslot = GET_SLOT_DATA(slot);
int r, i;
u8 feature_buf[256];
DWORD feature_len;
r = refresh_slot_attributes(reader, slot);
if (r)
return r;
if (!(slot->flags & SC_SLOT_CARD_PRESENT))
return SC_ERROR_CARD_NOT_PRESENT;
if (_sc_check_forced_protocol(reader->ctx, slot->atr, slot->atr_len, (unsigned int *) &protocol)) {
protocol = opensc_proto_to_pcsc(protocol);
} else {
protocol = SCARD_PROTOCOL_ANY;
}
rv = SCardConnect(priv->pcsc_ctx, priv->reader_name,
SCARD_SHARE_SHARED, protocol, &card_handle, &active_proto);
if (rv != 0) {
PCSC_ERROR(reader->ctx, "SCardConnect failed", rv);
return pcsc_ret_to_error(rv);
}
slot->active_protocol = pcsc_proto_to_opensc(active_proto);
pslot->pcsc_card = card_handle;
/* check for pinpad support */
#ifdef CM_IOCTL_GET_FEATURE_REQUEST
sc_debug(reader->ctx, "Requesting for pinpad reader features ... ");
rv = SCardControl(pslot->pcsc_card, CM_IOCTL_GET_FEATURE_REQUEST, NULL, 0,
feature_buf, sizeof(feature_buf), &feature_len);
if (rv == SCARD_S_SUCCESS) {
if (!(feature_len % 6)) {
for (i = 0; i < feature_len; i += 6) {
if (feature_buf[i] == FEATURE_VERIFY_PIN_DIRECT) {
sc_debug(reader->ctx, "Reader supports pinpad verification");
pslot->verify_ioctl = dw2i(feature_buf, i + 2);
slot->capabilities |= SC_SLOT_CAP_PIN_PAD;
} else if (feature_buf[i] == FEATURE_MODIFY_PIN_DIRECT) {
sc_debug(reader->ctx, "Reader supports pinpad modification");
pslot->modify_ioctl = dw2i(feature_buf, i + 2);
slot->capabilities |= SC_SLOT_CAP_PIN_PAD;
} else {
sc_debug(reader->ctx, "Reader pinpad feature: %c not recognized", feature_buf[i]);
}
}
} else
sc_debug(reader->ctx, "Inconsistent TLV from reader!");
} else {
PCSC_ERROR(reader->ctx, "SCardControl failed", rv)
}
#endif /* CM_IOCTL_GET_FEATURE_REQUEST */
return SC_SUCCESS;
}
static int pcsc_disconnect(sc_reader_t *reader, sc_slot_info_t *slot,
int action)
{
struct pcsc_slot_data *pslot = GET_SLOT_DATA(slot);
/* FIXME: check action */
SCardDisconnect(pslot->pcsc_card, SCARD_LEAVE_CARD);
memset(pslot, 0, sizeof(*pslot));
slot->flags = 0;
return 0;
}
static int pcsc_lock(sc_reader_t *reader, sc_slot_info_t *slot)
{
long rv;
struct pcsc_slot_data *pslot = GET_SLOT_DATA(slot);
assert(pslot != NULL);
rv = SCardBeginTransaction(pslot->pcsc_card);
if (rv != SCARD_S_SUCCESS) {
PCSC_ERROR(reader->ctx, "SCardBeginTransaction failed", rv);
return pcsc_ret_to_error(rv);
}
return 0;
}
static int pcsc_unlock(sc_reader_t *reader, sc_slot_info_t *slot)
{
long rv;
struct pcsc_slot_data *pslot = GET_SLOT_DATA(slot);
assert(pslot != NULL);
rv = SCardEndTransaction(pslot->pcsc_card, SCARD_LEAVE_CARD);
if (rv != SCARD_S_SUCCESS) {
PCSC_ERROR(reader->ctx, "SCardEndTransaction failed", rv);
return pcsc_ret_to_error(rv);
}
return 0;
}
static int pcsc_release(sc_reader_t *reader)
{
int i;
struct pcsc_private_data *priv = GET_PRIV_DATA(reader);
free(priv->reader_name);
free(priv);
for (i = 0; i < reader->slot_count; i++) {
if (reader->slot[i].drv_data != NULL) {
free(reader->slot[i].drv_data);
reader->slot[i].drv_data = NULL;
}
}
return 0;
}
static struct sc_reader_operations pcsc_ops;
static struct sc_reader_driver pcsc_drv = {
"PC/SC reader",
"pcsc",
&pcsc_ops
};
static int pcsc_init(sc_context_t *ctx, void **reader_data)
{
LONG rv;
DWORD reader_buf_size;
char *reader_buf, *p;
const char *mszGroups = NULL;
SCARDCONTEXT pcsc_ctx;
int r;
struct pcsc_global_private_data *gpriv;
rv = SCardEstablishContext(SCARD_SCOPE_GLOBAL,
NULL, NULL, &pcsc_ctx);
if (rv != SCARD_S_SUCCESS)
return pcsc_ret_to_error(rv);
rv = SCardListReaders(pcsc_ctx, NULL, NULL,
(LPDWORD) &reader_buf_size);
if (rv != SCARD_S_SUCCESS || reader_buf_size < 2) {
SCardReleaseContext(pcsc_ctx);
return pcsc_ret_to_error(rv); /* No readers configured */
}
gpriv = (struct pcsc_global_private_data *) malloc(sizeof(struct pcsc_global_private_data));
if (gpriv == NULL) {
SCardReleaseContext(pcsc_ctx);
return SC_ERROR_OUT_OF_MEMORY;
}
gpriv->pcsc_ctx = pcsc_ctx;
*reader_data = gpriv;
reader_buf = (char *) malloc(sizeof(char) * reader_buf_size);
if (!reader_buf) {
free(gpriv);
*reader_data = NULL;
SCardReleaseContext(pcsc_ctx);
return SC_ERROR_OUT_OF_MEMORY;
}
rv = SCardListReaders(pcsc_ctx, mszGroups, reader_buf,
(LPDWORD) &reader_buf_size);
if (rv != SCARD_S_SUCCESS) {
free(reader_buf);
free(gpriv);
*reader_data = NULL;
SCardReleaseContext(pcsc_ctx);
return pcsc_ret_to_error(rv);
}
p = reader_buf;
do {
sc_reader_t *reader = (sc_reader_t *) malloc(sizeof(sc_reader_t));
struct pcsc_private_data *priv = (struct pcsc_private_data *) malloc(sizeof(struct pcsc_private_data));
struct pcsc_slot_data *pslot = (struct pcsc_slot_data *) malloc(sizeof(struct pcsc_slot_data));
sc_slot_info_t *slot;
if (reader == NULL || priv == NULL || pslot == NULL) {
if (reader)
free(reader);
if (priv)
free(priv);
if (pslot)
free(pslot);
break;
}
memset(reader, 0, sizeof(*reader));
reader->drv_data = priv;
reader->ops = &pcsc_ops;
reader->driver = &pcsc_drv;
reader->slot_count = 1;
reader->name = strdup(p);
priv->gpriv = gpriv;
priv->pcsc_ctx = pcsc_ctx;
priv->reader_name = strdup(p);
r = _sc_add_reader(ctx, reader);
if (r) {
free(priv->reader_name);
free(priv);
free(reader->name);
free(reader);
free(pslot);
break;
}
slot = &reader->slot[0];
memset(slot, 0, sizeof(*slot));
slot->drv_data = pslot;
memset(pslot, 0, sizeof(*pslot));
refresh_slot_attributes(reader, slot);
while (*p++ != 0);
} while (p < (reader_buf + reader_buf_size - 1));
free(reader_buf);
return 0;
}
static int pcsc_finish(sc_context_t *ctx, void *prv_data)
{
struct pcsc_global_private_data *priv = (struct pcsc_global_private_data *) prv_data;
if (priv) {
SCardReleaseContext(priv->pcsc_ctx);
free(priv);
}
return 0;
}
static int
pcsc_pin_cmd(sc_reader_t *reader, sc_slot_info_t * slot, struct sc_pin_cmd_data *data)
{
/* XXX: temporary */
if (slot->capabilities & SC_SLOT_CAP_PIN_PAD) {
return class2_pin_cmd(reader, slot, data);
} else {
return ctbcs_pin_cmd(reader, slot, data);
}
}
struct sc_reader_driver * sc_get_pcsc_driver(void)
{
pcsc_ops.init = pcsc_init;
pcsc_ops.finish = pcsc_finish;
pcsc_ops.transmit = pcsc_transmit;
pcsc_ops.detect_card_presence = pcsc_detect_card_presence;
pcsc_ops.lock = pcsc_lock;
pcsc_ops.unlock = pcsc_unlock;
pcsc_ops.release = pcsc_release;
pcsc_ops.connect = pcsc_connect;
pcsc_ops.disconnect = pcsc_disconnect;
pcsc_ops.perform_verify = pcsc_pin_cmd;
pcsc_ops.wait_for_event = pcsc_wait_for_event;
return &pcsc_drv;
}
/*
* Pinpad support, based on TeleTrust Class 2 PC/SC reader interface
* Similar to CCID in spirit.
*/
/* Local definitions */
#define SC_CCID_PIN_TIMEOUT 30
/* CCID definitions */
#define SC_CCID_PIN_ENCODING_BIN 0x00
#define SC_CCID_PIN_ENCODING_BCD 0x01
#define SC_CCID_PIN_ENCODING_ASCII 0x02
#define SC_CCID_PIN_UNITS_BYTES 0x80
/* Build a pin verification block + APDU */
static int class2_build_verify_pin_block(u8 * buf, size_t * size, struct sc_pin_cmd_data *data)
{
size_t buflen, count = 0;
sc_apdu_t *apdu = data->apdu;
u8 tmp;
buflen = sizeof(buf);
/* PIN verification control message */
buf[count++] = SC_CCID_PIN_TIMEOUT; /* bTimeOut */
buf[count++] = SC_CCID_PIN_TIMEOUT; /* bTimeOut2 */
/* bmFormatString */
tmp = 0x00;
if (data->pin1.encoding == SC_PIN_ENCODING_ASCII) {
tmp |= SC_CCID_PIN_ENCODING_ASCII;
/* if the effective pin length offset is specified, use it*/
if (data->pin1.length_offset > 3) {
tmp |= SC_CCID_PIN_UNITS_BYTES;
tmp |= (data->pin1.length_offset - 5) << 3;
}
} else if (data->pin1.encoding == SC_PIN_ENCODING_BCD) {
tmp |= SC_CCID_PIN_ENCODING_BCD;
tmp |= SC_CCID_PIN_UNITS_BYTES;
} else if (data->pin1.encoding == SC_PIN_ENCODING_GLP) {
/* see comment about GLP pins in sec.c */
tmp |= SC_CCID_PIN_ENCODING_BCD;
tmp |= 0x04 << 3;
} else
return SC_ERROR_NOT_SUPPORTED;
buf[count++] = tmp; /* bmFormatString */
/* bmPINBlockString */
tmp = 0x00;
if (data->pin1.encoding == SC_PIN_ENCODING_GLP) {
/* GLP pin length is encoded in 4 bits and block size is always 8 bytes */
tmp |= 0x40 | 0x08;
}
buf[count++] = tmp; /* bmPINBlockString */
/* bmPINLengthFormat */
tmp = 0x00;
if (data->pin1.encoding == SC_PIN_ENCODING_GLP) {
/* GLP pins expect the effective pin length from bit 4 */
tmp |= 0x04;
}
buf[count++] = tmp; /* bmPINLengthFormat */
if (!data->pin1.min_length || !data->pin1.max_length)
return SC_ERROR_INVALID_ARGUMENTS;
buf[count++] = data->pin1.max_length; /* wPINMaxExtraDigit: max */
buf[count++] = data->pin1.min_length; /* wPINMaxExtraDigit: min */
buf[count++] = 0x02; /* bEntryValidationCondition, keypress only */
/* Ignore language and T=1 parameters. */
buf[count++] = 0x00; /* bNumberMessage */
buf[count++] = 0x00; /* wLangId */
buf[count++] = 0x00; /* " */
buf[count++] = 0x00; /* bMsgIndex */
buf[count++] = 0x00; /* bTeoPrologue */
buf[count++] = 0x00; /* " */
buf[count++] = 0x00; /* " */
/* ulDataLength */
if (data->pin1.length_offset > 4)
tmp = apdu->datalen + 4;
else
tmp = 4;
buf[count++] = tmp; /* ulDataLength */
/* APDU itself */
buf[count++] = apdu->cla;
buf[count++] = apdu->ins;
buf[count++] = apdu->p1;
buf[count++] = apdu->p2;
/* Copy data if not Case 1 */
if (data->pin1.length_offset > 4) {
memcpy(&buf[count], apdu->data, apdu->datalen);
count += apdu->datalen;
}
*size = count;
return SC_SUCCESS;
}
/* Build a pin modification block + APDU */
static int class2_build_modify_pin_block(u8 * buf, size_t * size, struct sc_pin_cmd_data *data)
{
size_t buflen, count = 0;
sc_apdu_t *apdu = data->apdu;
u8 tmp;
buflen = sizeof(buf);
/* PIN verification control message */
buf[count++] = SC_CCID_PIN_TIMEOUT; /* bTimeOut */
buf[count++] = SC_CCID_PIN_TIMEOUT; /* bTimeOut2 */
/* bmFormatString */
tmp = 0x00;
if (data->pin1.encoding == SC_PIN_ENCODING_ASCII) {
tmp |= SC_CCID_PIN_ENCODING_ASCII;
/* if the effective pin length offset is specified, use it*/
if (data->pin1.length_offset > 3) {
tmp |= SC_CCID_PIN_UNITS_BYTES;
tmp |= (data->pin1.length_offset - 5) << 3;
}
} else if (data->pin1.encoding == SC_PIN_ENCODING_BCD) {
tmp |= SC_CCID_PIN_ENCODING_BCD;
tmp |= SC_CCID_PIN_UNITS_BYTES;
} else if (data->pin1.encoding == SC_PIN_ENCODING_GLP) {
/* see comment about GLP pins in sec.c */
tmp |= SC_CCID_PIN_ENCODING_BCD;
tmp |= 0x04 << 3;
} else
return SC_ERROR_NOT_SUPPORTED;
buf[count++] = tmp; /* bmFormatString */
/* bmPINBlockString */
tmp = 0x00;
if (data->pin1.encoding == SC_PIN_ENCODING_GLP) {
/* GLP pin length is encoded in 4 bits and block size is always 8 bytes */
tmp |= 0x40 | 0x08;
}
buf[count++] = tmp; /* bmPINBlockString */
/* bmPINLengthFormat */
tmp = 0x00;
if (data->pin1.encoding == SC_PIN_ENCODING_GLP) {
/* GLP pins expect the effective pin length from bit 4 */
tmp |= 0x04;
}
buf[count++] = tmp; /* bmPINLengthFormat */
buf[count++] = 0x00; /* bOffsetOld */
buf[count++] = 0x00; /* bOffsetNew */
if (!data->pin1.min_length || !data->pin1.max_length)
return SC_ERROR_INVALID_ARGUMENTS;
buf[count++] = data->pin1.max_length; /* wPINMaxExtraDigit: max */
buf[count++] = data->pin1.min_length; /* wPINMaxExtraDigit: min */
buf[count++] = 0x03; /* bConfirmPIN, all */
buf[count++] = 0x02; /* bEntryValidationCondition, keypress only */
/* Ignore language and T=1 parameters. */
buf[count++] = 0x00; /* bNumberMessage */
buf[count++] = 0x00; /* wLangId */
buf[count++] = 0x00; /* " */
/* FIXME: Only as many as bNumberMessage messages! */
buf[count++] = 0x00; /* bMsgIndex1 */
buf[count++] = 0x00; /* bMsgIndex2 */
buf[count++] = 0x00; /* bMsgIndex3 */
buf[count++] = 0x00; /* bTeoPrologue */
buf[count++] = 0x00; /* " */
buf[count++] = 0x00; /* " */
/* ulDataLength */
if (data->pin1.length_offset > 4)
tmp = apdu->datalen + 4;
else
tmp = 4;
buf[count++] = tmp; /* ulDataLength */
/* APDU itself */
buf[count++] = apdu->cla;
buf[count++] = apdu->ins;
buf[count++] = apdu->p1;
buf[count++] = apdu->p2;
/* Copy full APDU if not Case 1 */
if (data->pin1.length_offset > 4) {
memcpy(&buf[count], apdu->data, apdu->datalen);
count += apdu->datalen;
}
*size = count;
return SC_SUCCESS;
}
/* Do the PIN command */
static int
class2_pin_cmd(sc_reader_t *reader, sc_slot_info_t *slot,
struct sc_pin_cmd_data *data)
{
u8 rbuf[SC_MAX_APDU_BUFFER_SIZE], sbuf[SC_MAX_APDU_BUFFER_SIZE], dbuf[SC_MAX_APDU_BUFFER_SIZE * 3];
size_t rcount = sizeof(rbuf), scount = 0;
int r;
unsigned long ioctl = 0;
sc_apdu_t *apdu;
struct pcsc_slot_data *pslot = (struct pcsc_slot_data *) slot->drv_data;
SC_FUNC_CALLED(reader->ctx, 3);
assert(pslot != NULL);
/* The APDU must be provided by the card driver */
if (!data->apdu) {
sc_error(reader->ctx, "No APDU provided for Class 2 pinpad verification!");
return SC_ERROR_NOT_SUPPORTED;
}
/* Only T=0 is currently supported */
if (slot->active_protocol != SC_PROTO_T0) {
sc_error(reader->ctx, "Only T=0 is currently supported!");
return SC_ERROR_NOT_SUPPORTED;
}
apdu = data->apdu;
switch (data->cmd) {
case SC_PIN_CMD_VERIFY:
if (!pslot->verify_ioctl) {
sc_error(reader->ctx, "Pinpad reader does not support verification!");
return SC_ERROR_NOT_SUPPORTED;
}
r = class2_build_verify_pin_block(sbuf, &scount, data);
ioctl = pslot->verify_ioctl;
break;
case SC_PIN_CMD_CHANGE:
case SC_PIN_CMD_UNBLOCK:
if (!pslot->modify_ioctl) {
sc_error(reader->ctx, "Pinpad reader does not support modification!");
return SC_ERROR_NOT_SUPPORTED;
}
r = class2_build_modify_pin_block(sbuf, &scount, data);
ioctl = pslot->modify_ioctl;
break;
default:
sc_error(reader->ctx, "Unknown PIN command %d", data->cmd);
return SC_ERROR_NOT_SUPPORTED;
}
/* If PIN block building failed, we fail too */
SC_TEST_RET(reader->ctx, r, "Class 2 PIN block building failed!");
/* If not, debug it, just for fun */
sc_bin_to_hex(sbuf, scount, dbuf, sizeof(dbuf), ':');
sc_debug(reader->ctx, "Class 2 block: %s", dbuf);
/* The slot must be manually locked, as the control does not pass through card.c
* wrappers that lock the card (card_transmit is not OK in this case, as it assumes
* a proper APDU as a parameter, not an arbitary binary blob to be sent to the reader)
*/
r = reader->ops->lock(reader, slot);
SC_TEST_RET(reader->ctx, r, "Class 2: could not lock!");
r = reader->ops->transmit(reader, slot, sbuf, scount, rbuf, &rcount, ioctl);
reader->ops->unlock(reader, slot);
SC_TEST_RET(reader->ctx, r, "Class 2: block transmit failed!");
/* We expect only two bytes of result data (SW1 and SW2) */
if (rcount != 2) {
SC_FUNC_RETURN(reader->ctx, 2, SC_ERROR_UNKNOWN_DATA_RECEIVED);
}
/* Extract the SWs for the result APDU */
apdu->sw1 = (unsigned int) rbuf[rcount - 2];
apdu->sw2 = (unsigned int) rbuf[rcount - 1];
r = SC_SUCCESS;
switch (((unsigned int) apdu->sw1 << 8) | apdu->sw2) {
case 0x6402: /* Input timed out */
r = SC_ERROR_KEYPAD_TIMEOUT;
break;
case 0x6401: /* Input cancelled */
r = SC_ERROR_KEYPAD_CANCELLED;
break;
}
SC_TEST_RET(reader->ctx, r, "PIN command failed");
/* PIN command completed, all is good */
return SC_SUCCESS;
}
#endif /* HAVE_PCSC */