/* * Implementation of T=1 * * Copyright (C) 2003, Olaf Kirch * * improvements by: * Copyright (C) 2004 Ludovic Rousseau */ #include #include #include #include "commands.h" #include "buffer.h" #include "debug.h" #include "proto-t1.h" #include "checksum.h" #include "ccid.h" #ifdef HAVE_STRING_H #include #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, retries, resyncs; 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; unsigned 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); /* Send the block */ n = t1_xcv(t1, sdata, slen, sizeof(sdata)); retries--; /* ISO 7816-3 Rule 7.4.2 */ if (retries <= 0) goto error; 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; }