ccid/src/openct/proto-t1.c

802 lines
16 KiB
C

/*
* Implementation of T=1
*
* Copyright (C) 2003, Olaf Kirch <okir@suse.de>
*
* improvements by:
* Copyright (C) 2004 Ludovic Rousseau <ludovic.rousseau@free.fr>
*/
#include <config.h>
#include <pcsclite.h>
#include <ifdhandler.h>
#include "commands.h"
#include "buffer.h"
#include "debug.h"
#include "proto-t1.h"
#include "checksum.h"
#include "ccid.h"
#ifdef HAVE_STRING_H
#include <string.h>
#endif
/* I block */
#define T1_I_SEQ_SHIFT 6
/* R block */
#define T1_IS_ERROR(pcb) ((pcb) & 0x0F)
#define T1_EDC_ERROR 0x01
#define T1_OTHER_ERROR 0x02
#define T1_R_SEQ_SHIFT 4
/* S block stuff */
#define T1_S_IS_RESPONSE(pcb) ((pcb) & T1_S_RESPONSE)
#define T1_S_TYPE(pcb) ((pcb) & 0x0F)
#define T1_S_RESPONSE 0x20
#define T1_S_RESYNC 0x00
#define T1_S_IFS 0x01
#define T1_S_ABORT 0x02
#define T1_S_WTX 0x03
#define swap_nibbles(x) ( (x >> 4) | ((x & 0xF) << 4) )
#ifndef TRUE
#define TRUE 1
#define FALSE 0
#endif
#define NAD 0
#define PCB 1
#define LEN 2
#define DATA 3
/* internal state, do not mess with it. */
/* should be != DEAD after reset/init */
enum {
SENDING, RECEIVING, RESYNCH, DEAD
};
static void t1_set_checksum(t1_state_t *, int);
static unsigned int t1_block_type(unsigned char);
static unsigned int t1_seq(unsigned char);
static unsigned int t1_rebuild(t1_state_t *t1, unsigned char *block);
static unsigned int t1_compute_checksum(t1_state_t *, unsigned char *, size_t);
static int t1_verify_checksum(t1_state_t *, unsigned char *, size_t);
static int t1_xcv(t1_state_t *, unsigned char *, size_t, size_t);
/*
* Set default T=1 protocol parameters
*/
static void t1_set_defaults(t1_state_t * t1)
{
t1->retries = 3;
/* This timeout is rather insane, but we need this right now
* to support cryptoflex keygen */
t1->ifsc = 32;
t1->ifsd = 32;
t1->nr = 0;
t1->ns = 0;
t1->wtx = 0;
}
static void t1_set_checksum(t1_state_t * t1, int csum)
{
switch (csum) {
case IFD_PROTOCOL_T1_CHECKSUM_LRC:
t1->rc_bytes = 1;
t1->checksum = csum_lrc_compute;
break;
case IFD_PROTOCOL_T1_CHECKSUM_CRC:
t1->rc_bytes = 2;
t1->checksum = csum_crc_compute;
break;
}
}
/*
* Attach t1 protocol
*/
int t1_init(t1_state_t * t1, int lun)
{
t1_set_defaults(t1);
t1_set_param(t1, IFD_PROTOCOL_T1_CHECKSUM_LRC, 0);
t1_set_param(t1, IFD_PROTOCOL_T1_STATE, SENDING);
t1_set_param(t1, IFD_PROTOCOL_T1_MORE, FALSE);
t1->lun = lun;
return 0;
}
/*
* Detach t1 protocol
*/
void t1_release(/*@unused@*/ t1_state_t * t1)
{
(void)t1;
/* NOP */
}
/*
* Get/set parmaters for T1 protocol
*/
int t1_set_param(t1_state_t * t1, int type, long value)
{
switch (type) {
case IFD_PROTOCOL_T1_CHECKSUM_LRC:
case IFD_PROTOCOL_T1_CHECKSUM_CRC:
t1_set_checksum(t1, type);
break;
case IFD_PROTOCOL_T1_IFSC:
t1->ifsc = value;
break;
case IFD_PROTOCOL_T1_IFSD:
t1->ifsd = value;
break;
case IFD_PROTOCOL_T1_STATE:
t1->state = value;
break;
case IFD_PROTOCOL_T1_MORE:
t1->more = value;
break;
default:
DEBUG_INFO2("Unsupported parameter %d", type);
return -1;
}
return 0;
}
/*
* Send an APDU through T=1
*/
int t1_transceive(t1_state_t * t1, unsigned int dad,
const void *snd_buf, size_t snd_len,
void *rcv_buf, size_t rcv_len)
{
ct_buf_t sbuf, rbuf, tbuf;
unsigned char sdata[T1_BUFFER_SIZE], sblk[5];
unsigned int slen, resyncs;
int retries;
size_t last_send = 0;
if (snd_len == 0)
return -1;
/* we can't talk to a dead card / reader. Reset it! */
if (t1->state == DEAD)
{
DEBUG_CRITICAL("T=1 state machine is DEAD. Reset the card first.");
return -1;
}
t1->state = SENDING;
retries = t1->retries;
resyncs = 3;
/* Initialize send/recv buffer */
ct_buf_set(&sbuf, (void *)snd_buf, snd_len);
ct_buf_init(&rbuf, rcv_buf, rcv_len);
/* Send the first block */
slen = t1_build(t1, sdata, dad, T1_I_BLOCK, &sbuf, &last_send);
while (1) {
unsigned char pcb;
int n;
retries--;
n = t1_xcv(t1, sdata, slen, sizeof(sdata));
if (-2 == n)
{
DEBUG_COMM("Parity error");
/* ISO 7816-3 Rule 7.4.2 */
if (retries <= 0)
goto resync;
/* ISO 7816-3 Rule 7.2 */
if (T1_R_BLOCK == t1_block_type(t1->previous_block[PCB]))
{
DEBUG_COMM("Rule 7.2");
slen = t1_rebuild(t1, sdata);
continue;
}
slen = t1_build(t1, sdata,
dad, T1_R_BLOCK | T1_EDC_ERROR,
NULL, NULL);
continue;
}
if (n < 0) {
DEBUG_CRITICAL("fatal: transmit/receive failed");
t1->state = DEAD;
goto error;
}
if ((sdata[NAD] != swap_nibbles(dad)) /* wrong NAD */
|| (sdata[LEN] == 0xFF)) /* length == 0xFF (illegal) */
{
DEBUG_COMM("R-BLOCK required");
/* ISO 7816-3 Rule 7.4.2 */
if (retries <= 0)
goto resync;
/* ISO 7816-3 Rule 7.2 */
if (T1_R_BLOCK == t1_block_type(t1->previous_block[PCB]))
{
DEBUG_COMM("Rule 7.2");
slen = t1_rebuild(t1, sdata);
continue;
}
slen = t1_build(t1, sdata,
dad, T1_R_BLOCK | T1_OTHER_ERROR,
NULL, NULL);
continue;
}
if (!t1_verify_checksum(t1, sdata, n)) {
DEBUG_COMM("checksum failed");
/* ISO 7816-3 Rule 7.4.2 */
if (retries <= 0)
goto resync;
/* ISO 7816-3 Rule 7.2 */
if (T1_R_BLOCK == t1_block_type(t1->previous_block[PCB]))
{
DEBUG_COMM("Rule 7.2");
slen = t1_rebuild(t1, sdata);
continue;
}
slen = t1_build(t1, sdata,
dad, T1_R_BLOCK | T1_EDC_ERROR,
NULL, NULL);
continue;
}
pcb = sdata[PCB];
switch (t1_block_type(pcb)) {
case T1_R_BLOCK:
if ((sdata[LEN] != 0x00) /* length != 0x00 (illegal) */
|| (pcb & 0x20) /* b6 of pcb is set */
)
{
DEBUG_COMM("R-Block required");
/* ISO 7816-3 Rule 7.4.2 */
if (retries <= 0)
goto resync;
/* ISO 7816-3 Rule 7.2 */
if (T1_R_BLOCK == t1_block_type(t1->previous_block[1]))
{
DEBUG_COMM("Rule 7.2");
slen = t1_rebuild(t1, sdata);
continue;
}
slen = t1_build(t1, sdata,
dad, T1_R_BLOCK | T1_OTHER_ERROR,
NULL, NULL);
continue;
}
if (((t1_seq(pcb) != t1->ns) /* wrong sequence number & no bit more */
&& ! t1->more)
)
{
DEBUG_COMM4("received: %d, expected: %d, more: %d",
t1_seq(pcb), t1->ns, t1->more);
/* ISO 7816-3 Rule 7.4.2 */
if (retries <= 0)
goto resync;
/* ISO 7816-3 Rule 7.2 */
if (T1_R_BLOCK == t1_block_type(t1->previous_block[PCB]))
{
DEBUG_COMM("Rule 7.2");
slen = t1_rebuild(t1, sdata);
continue;
}
DEBUG_COMM("R-Block required");
slen = t1_build(t1, sdata,
dad, T1_R_BLOCK | T1_OTHER_ERROR,
NULL, NULL);
continue;
}
if (t1->state == RECEIVING) {
/* ISO 7816-3 Rule 7.2 */
if (T1_R_BLOCK == t1_block_type(t1->previous_block[1]))
{
/* ISO 7816-3 Rule 7.4.2 */
if (retries <= 0)
goto resync;
DEBUG_COMM("Rule 7.2");
slen = t1_rebuild(t1, sdata);
continue;
}
DEBUG_COMM("");
slen = t1_build(t1, sdata,
dad, T1_R_BLOCK,
NULL, NULL);
break;
}
/* If the card terminal requests the next
* sequence number, it received the previous
* block successfully */
if (t1_seq(pcb) != t1->ns) {
ct_buf_get(&sbuf, NULL, last_send);
last_send = 0;
t1->ns ^= 1;
}
/* If there's no data available, the ICC
* shouldn't be asking for more */
if (ct_buf_avail(&sbuf) == 0)
goto resync;
slen = t1_build(t1, sdata, dad, T1_I_BLOCK,
&sbuf, &last_send);
break;
case T1_I_BLOCK:
/* The first I-block sent by the ICC indicates
* the last block we sent was received successfully. */
if (t1->state == SENDING) {
DEBUG_COMM("");
ct_buf_get(&sbuf, NULL, last_send);
last_send = 0;
t1->ns ^= 1;
}
t1->state = RECEIVING;
/* If the block sent by the card doesn't match
* what we expected it to send, reply with
* an R block */
if (t1_seq(pcb) != t1->nr) {
DEBUG_COMM("wrong nr");
/* ISO 7816-3 Rule 7.4.2 */
if (retries <= 0)
goto resync;
slen = t1_build(t1, sdata, dad,
T1_R_BLOCK | T1_OTHER_ERROR,
NULL, NULL);
continue;
}
t1->nr ^= 1;
if (ct_buf_put(&rbuf, sdata + 3, sdata[LEN]) < 0)
{
DEBUG_CRITICAL2("buffer overrun by %d bytes", sdata[LEN] - (rbuf.size - rbuf.tail));
goto error;
}
if ((pcb & T1_MORE_BLOCKS) == 0)
goto done;
slen = t1_build(t1, sdata, dad, T1_R_BLOCK, NULL, NULL);
break;
case T1_S_BLOCK:
if (T1_S_IS_RESPONSE(pcb) && t1->state == RESYNCH) {
/* ISO 7816-3 Rule 6.2 */
DEBUG_COMM("S-Block answer received");
/* ISO 7816-3 Rule 6.3 */
t1->state = SENDING;
last_send = 0;
resyncs = 3;
retries = t1->retries;
ct_buf_init(&rbuf, rcv_buf, rcv_len);
slen = t1_build(t1, sdata, dad, T1_I_BLOCK,
&sbuf, &last_send);
continue;
}
if (T1_S_IS_RESPONSE(pcb))
{
/* ISO 7816-3 Rule 7.4.2 */
if (retries <= 0)
goto resync;
/* ISO 7816-3 Rule 7.2 */
if (T1_R_BLOCK == t1_block_type(t1->previous_block[PCB]))
{
DEBUG_COMM("Rule 7.2");
slen = t1_rebuild(t1, sdata);
continue;
}
DEBUG_CRITICAL("wrong response S-BLOCK received");
slen = t1_build(t1, sdata,
dad, T1_R_BLOCK | T1_OTHER_ERROR,
NULL, NULL);
continue;
}
ct_buf_init(&tbuf, sblk, sizeof(sblk));
DEBUG_COMM("S-Block request received");
switch (T1_S_TYPE(pcb)) {
case T1_S_RESYNC:
if (sdata[LEN] != 0)
{
DEBUG_COMM2("Wrong length: %d", sdata[LEN]);
slen = t1_build(t1, sdata, dad,
T1_R_BLOCK | T1_OTHER_ERROR,
NULL, NULL);
continue;
}
DEBUG_COMM("Resync requested");
/* the card is not allowed to send a resync. */
goto resync;
case T1_S_ABORT:
if (sdata[LEN] != 0)
{
DEBUG_COMM2("Wrong length: %d", sdata[LEN]);
slen = t1_build(t1, sdata, dad,
T1_R_BLOCK | T1_OTHER_ERROR,
NULL, NULL);
continue;
}
/* ISO 7816-3 Rule 9 */
DEBUG_CRITICAL("abort requested");
break;
case T1_S_IFS:
if (sdata[LEN] != 1)
{
DEBUG_COMM2("Wrong length: %d", sdata[LEN]);
slen = t1_build(t1, sdata, dad,
T1_R_BLOCK | T1_OTHER_ERROR,
NULL, NULL);
continue;
}
DEBUG_CRITICAL2("CT sent S-block with ifs=%u", sdata[DATA]);
if (sdata[DATA] == 0)
goto resync;
t1->ifsc = sdata[DATA];
ct_buf_putc(&tbuf, sdata[DATA]);
break;
case T1_S_WTX:
if (sdata[LEN] != 1)
{
DEBUG_COMM2("Wrong length: %d", sdata[LEN]);
slen = t1_build(t1, sdata, dad,
T1_R_BLOCK | T1_OTHER_ERROR,
NULL, NULL);
continue;
}
DEBUG_COMM2("CT sent S-block with wtx=%u", sdata[DATA]);
t1->wtx = sdata[DATA];
ct_buf_putc(&tbuf, sdata[DATA]);
break;
default:
DEBUG_CRITICAL2("T=1: Unknown S block type 0x%02x", T1_S_TYPE(pcb));
goto resync;
}
slen = t1_build(t1, sdata, dad,
T1_S_BLOCK | T1_S_RESPONSE | T1_S_TYPE(pcb),
&tbuf, NULL);
}
/* Everything went just splendid */
retries = t1->retries;
continue;
resync:
/* the number or resyncs is limited, too */
/* ISO 7816-3 Rule 6.4 */
if (resyncs == 0)
goto error;
/* ISO 7816-3 Rule 6 */
resyncs--;
t1->ns = 0;
t1->nr = 0;
slen = t1_build(t1, sdata, dad, T1_S_BLOCK | T1_S_RESYNC, NULL,
NULL);
t1->state = RESYNCH;
t1->more = FALSE;
retries = 1;
continue;
}
done:
return ct_buf_avail(&rbuf);
error:
t1->state = DEAD;
return -1;
}
static unsigned t1_block_type(unsigned char pcb)
{
switch (pcb & 0xC0) {
case T1_R_BLOCK:
return T1_R_BLOCK;
case T1_S_BLOCK:
return T1_S_BLOCK;
default:
return T1_I_BLOCK;
}
}
static unsigned int t1_seq(unsigned char pcb)
{
switch (pcb & 0xC0) {
case T1_R_BLOCK:
return (pcb >> T1_R_SEQ_SHIFT) & 1;
case T1_S_BLOCK:
return 0;
default:
return (pcb >> T1_I_SEQ_SHIFT) & 1;
}
}
unsigned int t1_build(t1_state_t * t1, unsigned char *block,
unsigned char dad, unsigned char pcb,
ct_buf_t *bp, size_t *lenp)
{
unsigned int len;
char more = FALSE;
len = bp ? ct_buf_avail(bp) : 0;
if (len > t1->ifsc) {
pcb |= T1_MORE_BLOCKS;
len = t1->ifsc;
more = TRUE;
}
/* Add the sequence number */
switch (t1_block_type(pcb)) {
case T1_R_BLOCK:
pcb |= t1->nr << T1_R_SEQ_SHIFT;
break;
case T1_I_BLOCK:
pcb |= t1->ns << T1_I_SEQ_SHIFT;
t1->more = more;
DEBUG_COMM2("more bit: %d", more);
break;
}
block[0] = dad;
block[1] = pcb;
block[2] = len;
if (len)
memcpy(block + 3, ct_buf_head(bp), len);
if (lenp)
*lenp = len;
len = t1_compute_checksum(t1, block, len + 3);
/* memorize the last sent block */
/* only 4 bytes since we are only interesed in R-blocks */
memcpy(t1->previous_block, block, 4);
return len;
}
static unsigned int
t1_rebuild(t1_state_t *t1, unsigned char *block)
{
unsigned char pcb = t1 -> previous_block[1];
/* copy the last sent block */
if (T1_R_BLOCK == t1_block_type(pcb))
memcpy(block, t1 -> previous_block, 4);
else
{
DEBUG_CRITICAL2("previous block was not R-Block: %02X", pcb);
return 0;
}
return 4;
}
/*
* Build/verify checksum
*/
static unsigned int t1_compute_checksum(t1_state_t * t1,
unsigned char *data, size_t len)
{
return len + t1->checksum(data, len, data + len);
}
static int t1_verify_checksum(t1_state_t * t1, unsigned char *rbuf,
size_t len)
{
unsigned char csum[2];
int m, n;
m = len - t1->rc_bytes;
n = t1->rc_bytes;
if (m < 0)
return 0;
t1->checksum(rbuf, m, csum);
if (!memcmp(rbuf + m, csum, n))
return 1;
return 0;
}
/*
* Send/receive block
*/
static int t1_xcv(t1_state_t * t1, unsigned char *block, size_t slen,
size_t rmax)
{
int n;
_ccid_descriptor *ccid_desc ;
int oldReadTimeout;
unsigned int rmax_int;
DEBUG_XXD("sending: ", block, slen);
ccid_desc = get_ccid_descriptor(t1->lun);
oldReadTimeout = ccid_desc->readTimeout;
if (t1->wtx > 1)
{
/* set the new temporary timeout at WTX card request */
ccid_desc->readTimeout *= t1->wtx;
DEBUG_INFO2("New timeout at WTX request: %d sec",
ccid_desc->readTimeout);
}
if (isCharLevel(t1->lun))
{
rmax = 3;
n = CCID_Transmit(t1 -> lun, slen, block, rmax, t1->wtx);
if (n != IFD_SUCCESS)
return -1;
/* the second argument of CCID_Receive() is (unsigned int *)
* so we can't use &rmax since &rmax is a (size_t *) and may not
* be the same on 64-bits architectures for example (iMac G5) */
rmax_int = rmax;
n = CCID_Receive(t1 -> lun, &rmax_int, block, NULL);
if (n == IFD_PARITY_ERROR)
return -2;
if (n != IFD_SUCCESS)
return -1;
rmax = block[2] + 1;
n = CCID_Transmit(t1 -> lun, 0, block, rmax, t1->wtx);
if (n != IFD_SUCCESS)
return -1;
rmax_int = rmax;
n = CCID_Receive(t1 -> lun, &rmax_int, &block[3], NULL);
rmax = rmax_int;
if (n == IFD_PARITY_ERROR)
return -2;
if (n != IFD_SUCCESS)
return -1;
n = rmax + 3;
}
else
{
n = CCID_Transmit(t1 -> lun, slen, block, 0, t1->wtx);
t1->wtx = 0; /* reset to default value */
if (n != IFD_SUCCESS)
return -1;
/* Get the response en bloc */
rmax_int = rmax;
n = CCID_Receive(t1 -> lun, &rmax_int, block, NULL);
rmax = rmax_int;
if (n == IFD_PARITY_ERROR)
return -2;
if (n != IFD_SUCCESS)
return -1;
n = rmax;
}
if (n >= 0)
{
int m;
m = block[2] + 3 + t1->rc_bytes;
if (m < n)
n = m;
}
if (n >= 0)
DEBUG_XXD("received: ", block, n);
/* Restore initial timeout */
ccid_desc->readTimeout = oldReadTimeout;
return n;
}
int t1_negotiate_ifsd(t1_state_t * t1, unsigned int dad, int ifsd)
{
ct_buf_t sbuf;
unsigned char sdata[T1_BUFFER_SIZE];
unsigned int slen;
int retries;
size_t snd_len;
int n;
unsigned char snd_buf[1];
retries = t1->retries;
/* S-block IFSD request */
snd_buf[0] = ifsd;
snd_len = 1;
/* Initialize send/recv buffer */
ct_buf_set(&sbuf, (void *)snd_buf, snd_len);
while (TRUE)
{
/* Build the block */
slen = t1_build(t1, sdata, 0, T1_S_BLOCK | T1_S_IFS, &sbuf, NULL);
retries--;
/* ISO 7816-3 Rule 7.4.2 */
if (retries < 0)
goto error;
/* Send the block */
n = t1_xcv(t1, sdata, slen, sizeof(sdata));
if (-1 == n)
{
DEBUG_CRITICAL("fatal: transmit/receive failed");
goto error;
}
if ((-2 == n) /* Parity error */
|| (sdata[DATA] != ifsd) /* Wrong ifsd received */
|| (sdata[NAD] != swap_nibbles(dad)) /* wrong NAD */
|| (!t1_verify_checksum(t1, sdata, n)) /* checksum failed */
|| (n != 4 + (int)t1->rc_bytes) /* wrong frame length */
|| (sdata[LEN] != 1) /* wrong data length */
|| (sdata[PCB] != (T1_S_BLOCK | T1_S_RESPONSE | T1_S_IFS))) /* wrong PCB */
continue;
/* no more error */
goto done;
}
done:
return n;
error:
t1->state = DEAD;
return -1;
}