/* * Process commands from remote clients (i.e. those offering a device). */ static int ria_svc_dev_handler(ct_socket_t * sock, header_t * hdr, ct_buf_t * args, ct_buf_t * resp) { unsigned char cmd; ria_peer_t *clnt, *peer; ria_device_t devinfo; int rc; clnt = (ria_peer_t *) sock->user_data; ria_print_packet(sock, 2, "dev <<", hdr, args); /* bounce response to peer right away */ if (hdr->dest) goto bounce_to_peer; if (ct_buf_get(args, &cmd, 1) < 0) return IFD_ERROR_INVALID_MSG; switch (cmd) { case RIA_MGR_REGISTER: if (clnt->device.name[0]) return IFD_ERROR_INVALID_ARG; if ((rc = ct_buf_get(args, &devinfo, sizeof(devinfo))) < 0) return IFD_ERROR_INVALID_ARG; if (devinfo.type[0] == '\0') return IFD_ERROR_INVALID_ARG; /* For security reasons, don't allow the handle counter * to wrap around. */ if (dev_handle == 0) return IFD_ERROR_GENERIC; memcpy(&devinfo.address, clnt->device.address, RIA_NAME_MAX); clnt->device = devinfo; snprintf(clnt->device.handle, RIA_NAME_MAX, "%s%u", clnt->device.type, dev_handle++); ifd_debug(1, "%s registered new %s device , handle '%s', name `%s'", clnt->device.address, clnt->device.type, clnt->device.handle, clnt->device.name); return 0; } if (cmd < __RIA_PEER_CMD_BASE) return IFD_ERROR_INVALID_CMD; /* Push back the command byte */ ct_buf_push(args, &cmd, 1); bounce_to_peer: if ((peer = clnt->peer) == NULL) return IFD_ERROR_NOT_CONNECTED; rc = ct_socket_put_packet(peer->sock, hdr, args); /* Tell the caller not to send a response */ hdr->xid = 0; return rc; }
/* * Eat initial white space from buffer */ static int skipws(void) { unsigned int m, n, in_comment = 0; char *s; again: s = (char *)ct_buf_head(&config_buf); n = ct_buf_avail(&config_buf); for (m = 0; m < n; m++, s++) { if (*s == '#') { in_comment = 1; } else if (!in_comment && !isspace((int)*s)) { break; } else if (*s == '\n') { config_line++; in_comment = 0; } } ct_buf_get(&config_buf, NULL, m); if (in_comment) { if (ct_buf_read(&config_buf, config_fd) < 0) { ct_error("%s: error while reading file: %m", config_filename); return -1; } goto again; } return 0; }
static int t0_send(ifd_protocol_t * prot, ct_buf_t * bp, int count) { int n, avail; avail = ct_buf_avail(bp); if (count < 0) count = avail; if (count > avail || !avail) return -1; n = ifd_send_command(prot, ct_buf_head(bp), count); if (n >= 0) ct_buf_get(bp, NULL, count); return n; }
/* * Tokenizer */ static int get_token(char **tok) { static char buffer[512]; unsigned int m, n, copy, retry = 1; char *s; /* consume initial white space */ if (skipws() < 0) return -1; again: s = (char *)ct_buf_head(&config_buf); n = ct_buf_avail(&config_buf); if (n && issepa(*s)) { m = 1; } else { for (m = 0; !isspace((int)s[m]) && !issepa(s[m]) && m < n; m++) ; } /* If we hit the end of the buffer while scanning * for white space, read more data and try * again */ if (m >= n && retry) { if (ct_buf_read(&config_buf, config_fd) < 0) { ct_error("%s: error while reading file: %m", config_filename); return -1; } retry = 0; goto again; } if (m == 0) return -1; if ((copy = m) >= sizeof(buffer)) copy = sizeof(buffer) - 1; memcpy(buffer, s, copy); buffer[copy] = '\0'; ct_buf_get(&config_buf, NULL, m); ifd_debug(5, "ifd_config_parse: token=\"%s\"", buffer); *tok = buffer; return 0; }
/* * Parse TLV data */ int ct_tlv_parse(ct_tlv_parser_t * parser, ct_buf_t * bp) { unsigned int avail, len; unsigned char *p, tag; /* Code below relies on it */ assert(((ifd_tag_t) - 1) == 255); while ((avail = ct_buf_avail(bp)) != 0) { unsigned int header = 2; if (avail < 2) return -1; p = (unsigned char *)ct_buf_head(bp); tag = p[0]; len = p[1]; if (tag & __CT_TAG_LARGE) { parser->use_large_tags = 1; tag &= ~__CT_TAG_LARGE; if (avail < 3) return -1; len = (len << 8) | p[header++]; } if (len == 0 || header + len > avail) return -1; parser->val[tag] = p + header; parser->len[tag] = len; ct_buf_get(bp, NULL, header + len); } return 0; }
int ifdhandler_process(ct_socket_t * sock, ifd_reader_t * reader, ct_buf_t * argbuf, ct_buf_t * resbuf) { unsigned char cmd, unit; ct_tlv_parser_t args; ct_tlv_builder_t resp; int rc; /* Get command and target unit */ if (ct_buf_get(argbuf, &cmd, 1) < 0 || ct_buf_get(argbuf, &unit, 1) < 0) return IFD_ERROR_INVALID_MSG; ifd_debug(1, "ifdhandler_process(cmd=%s, unit=%u)", get_cmd_name(cmd), unit); /* First, handle commands that don't do TLV encoded * arguments - currently this is only CT_CMD_TRANSACT. */ if (cmd == CT_CMD_TRANSACT_OLD) { /* Security - deny any APDUs if there's an * exclusive lock held by some other client. */ if ((rc = ifdhandler_check_lock(sock, unit, IFD_LOCK_EXCLUSIVE)) < 0) return rc; return do_transact_old(reader, unit, argbuf, resbuf); } if ((rc = do_before_command(reader)) < 0) { return rc; } memset(&args, 0, sizeof(args)); if (ct_tlv_parse(&args, argbuf) < 0) return IFD_ERROR_INVALID_MSG; if (args.use_large_tags) sock->use_large_tags = 1; ct_tlv_builder_init(&resp, resbuf, sock->use_large_tags); switch (cmd) { case CT_CMD_STATUS: rc = do_status(reader, unit, &args, &resp); break; case CT_CMD_OUTPUT: rc = do_output(reader, unit, &args, &resp); break; case CT_CMD_RESET: case CT_CMD_REQUEST_ICC: rc = do_reset(reader, unit, &args, &resp); break; case CT_CMD_EJECT_ICC: rc = do_eject(reader, unit, &args, &resp); break; case CT_CMD_PERFORM_VERIFY: rc = do_verify(reader, unit, &args, &resp); break; case CT_CMD_LOCK: rc = do_lock(sock, reader, unit, &args, &resp); break; case CT_CMD_UNLOCK: rc = do_unlock(sock, reader, unit, &args, &resp); break; case CT_CMD_MEMORY_READ: rc = do_memory_read(reader, unit, &args, &resp); break; case CT_CMD_MEMORY_WRITE: rc = do_memory_write(reader, unit, &args, &resp); break; case CT_CMD_TRANSACT: rc = do_transact(reader, unit, &args, &resp); break; case CT_CMD_SET_PROTOCOL: rc = do_set_protocol(reader, unit, &args, &resp); break; default: return IFD_ERROR_INVALID_CMD; } if (rc >= 0) rc = resp.error; /* * TODO consider checking error */ do_after_command(reader); return rc; }
/* * Send an APDU through T=1 */ static int t1_transceive(ifd_protocol_t * prot, int dad, const void *snd_buf, size_t snd_len, void *rcv_buf, size_t rcv_len) { t1_state_t *t1 = (t1_state_t *) prot; ct_buf_t sbuf, rbuf, tbuf; unsigned char sdata[T1_BUFFER_SIZE], sblk[5]; unsigned int slen, retries, resyncs, sent_length = 0; 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) 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--; if ((n = t1_xcv(t1, sdata, slen, sizeof(sdata))) < 0) { ifd_debug(1, "fatal: transmit/receive failed"); t1->state = DEAD; goto error; } if (!t1_verify_checksum(t1, sdata, n)) { ifd_debug(1, "checksum failed"); if (retries == 0 || sent_length) goto resync; 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 (T1_IS_ERROR(pcb)) { ifd_debug(1, "received error block, err=%d", T1_IS_ERROR(pcb)); goto resync; } if (t1->state == RECEIVING) { 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); sent_length += 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) { 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) { 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) 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) { t1->state = SENDING; sent_length = 0; 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)) goto resync; ct_buf_init(&tbuf, sblk, sizeof(sblk)); switch (T1_S_TYPE(pcb)) { case T1_S_RESYNC: /* the card is not allowed to send a resync. */ goto resync; case T1_S_ABORT: ifd_debug(1, "abort requested"); break; case T1_S_IFS: ifd_debug(1, "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: /* We don't handle the wait time extension * yet */ ifd_debug(1, "CT sent S-block with wtx=%u", sdata[DATA]); t1->wtx = sdata[DATA]; ct_buf_putc(&tbuf, sdata[DATA]); break; default: ct_error("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 */ if (resyncs == 0) goto error; resyncs--; t1->ns = 0; t1->nr = 0; slen = t1_build(t1, sdata, dad, T1_S_BLOCK | T1_S_RESYNC, NULL, NULL); t1->state = RESYNCH; continue; } done: return ct_buf_avail(&rbuf); error: t1->state = DEAD; return -1; }
/* * 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, sent_length = 0; 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.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"); /* 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; } if (t1->state == RECEIVING) { /* 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; } 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); sent_length += 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"); 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; sent_length = 0; 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; }
/* * Process commands from local clients (i.e. those allowed * to claim a device). */ static int ria_svc_app_handler(ct_socket_t * sock, header_t * hdr, ct_buf_t * args, ct_buf_t * resp) { unsigned char cmd; ria_peer_t *clnt, *peer; int rc; clnt = (ria_peer_t *) sock->user_data; ria_print_packet(sock, 2, "app >>", hdr, args); if (ct_buf_get(args, &cmd, 1) < 0) return IFD_ERROR_INVALID_MSG; switch (cmd) { case RIA_MGR_LIST: peer = &clients; ifd_debug(1, "%s requests a device listing", clnt->device.address); while ((peer = peer->next) != &clients) { if (peer->device.name[0] != '\0') ct_buf_put(resp, &peer->device, sizeof(peer->device)); } return 0; case RIA_MGR_INFO: peer = ria_find_device((const char *)ct_buf_head(args), ct_buf_avail(args)); if (peer == NULL) return IFD_ERROR_UNKNOWN_DEVICE; ct_buf_put(resp, &peer->device, sizeof(peer->device)); return 0; case RIA_MGR_CLAIM: peer = ria_find_device((const char *)ct_buf_head(args), ct_buf_avail(args)); if (peer == NULL) return IFD_ERROR_UNKNOWN_DEVICE; if (peer->peer) return IFD_ERROR_DEVICE_BUSY; ifd_debug(1, "%s claimed %s device %s/%s", clnt->device.address, peer->device.type, peer->device.address, peer->device.name); ct_buf_put(resp, &peer->device, sizeof(peer->device)); clnt->peer = peer; peer->peer = clnt; return 0; } if (cmd < __RIA_PEER_CMD_BASE) return IFD_ERROR_INVALID_CMD; /* All subsequent commands require a device */ if ((peer = clnt->peer) == NULL) return IFD_ERROR_NOT_CONNECTED; /* Push back the command byte */ ct_buf_push(args, &cmd, 1); rc = ct_socket_put_packet(peer->sock, hdr, args); /* Tell the caller not to send a response */ hdr->xid = 0; return rc; }