| /*! \file reader.c |
| * Card reader abstraction for libosmosim. */ |
| /* |
| * (C) 2012 by Harald Welte <laforge@gnumonks.org> |
| * |
| * All Rights Reserved |
| * |
| * SPDX-License-Identifier: GPL-2.0+ |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program 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 General Public License for more details. |
| * |
| */ |
| |
| |
| #include <errno.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <stdio.h> |
| |
| #include <netinet/in.h> |
| |
| #include <osmocom/core/msgb.h> |
| #include <osmocom/sim/sim.h> |
| |
| #include "config.h" |
| |
| #include "sim_int.h" |
| |
| /* remove the SW from end of the message */ |
| static int get_sw(struct msgb *resp) |
| { |
| int ret; |
| |
| if (!resp->l2h || msgb_apdu_le(resp) < 2) |
| return -EIO; |
| |
| ret = msgb_get_u16(resp); |
| |
| return ret; |
| } |
| |
| /* According to ISO7816-4 Annex A */ |
| static int transceive_apdu_t0(struct osim_card_hdl *st, struct msgb *amsg) |
| { |
| struct osim_reader_hdl *rh = st->reader; |
| struct msgb *tmsg = msgb_alloc(1024, "TPDU"); |
| struct osim_apdu_cmd_hdr *tpduh; |
| uint8_t *cur; |
| uint16_t sw; |
| int rc, num_resp = 0; |
| |
| if (!tmsg) |
| return -ENOMEM; |
| |
| /* create TPDU header from APDU header */ |
| tpduh = (struct osim_apdu_cmd_hdr *) msgb_put(tmsg, sizeof(*tpduh)); |
| memcpy(tpduh, msgb_apdu_h(amsg), sizeof(*tpduh)); |
| |
| switch (msgb_apdu_case(amsg)) { |
| case APDU_CASE_1: |
| tpduh->p3 = 0x00; |
| break; |
| case APDU_CASE_2S: |
| tpduh->p3 = msgb_apdu_le(amsg); |
| break; |
| case APDU_CASE_2E: |
| if (msgb_apdu_le(amsg) <= 256) { |
| /* case 2E.1 */ |
| tpduh->p3 = msgb_apdu_le(amsg) & 0xff; |
| } else { |
| /* case 2E.2 */ |
| tpduh->p3 = 0; |
| msgb_put_u16(tmsg, msgb_apdu_le(amsg)); |
| } |
| break; |
| case APDU_CASE_3S: |
| case APDU_CASE_4S: |
| tpduh->p3 = msgb_apdu_lc(amsg); |
| cur = msgb_put(tmsg, tpduh->p3); |
| memcpy(cur, msgb_apdu_dc(amsg), tpduh->p3); |
| break; |
| case APDU_CASE_3E: |
| case APDU_CASE_4E: |
| if (msgb_apdu_lc(amsg) < 256) { |
| /* Case 3E.1 */ |
| tpduh->p3 = msgb_apdu_lc(amsg); |
| } else { |
| /* Case 3E.2 */ |
| /* FXIME: Split using ENVELOPE! */ |
| return -1; |
| } |
| break; |
| } |
| |
| transceive_again: |
| |
| /* store pointer to start of response */ |
| tmsg->l3h = tmsg->tail; |
| |
| /* transceive */ |
| rc = rh->ops->transceive(st->reader, tmsg); |
| if (rc < 0) { |
| msgb_free(tmsg); |
| return rc; |
| } |
| msgb_apdu_sw(tmsg) = get_sw(tmsg); |
| |
| /* increase number of responsese received */ |
| num_resp++; |
| |
| /* save SW */ |
| sw = msgb_apdu_sw(tmsg); |
| msgb_apdu_sw(amsg) = sw; |
| |
| switch (msgb_apdu_case(amsg)) { |
| case APDU_CASE_1: |
| case APDU_CASE_3S: |
| /* just copy SW */ |
| break; |
| case APDU_CASE_2S: |
| case_2s: |
| switch (sw >> 8) { |
| case 0x67: /* Case 2S.2: Le definitely not accepted */ |
| break; |
| case 0x6c: /* Case 2S.3: Le not accepted, La indicated */ |
| tpduh->p3 = sw & 0xff; |
| /* re-issue the command with La as */ |
| goto transceive_again; |
| break; |
| case 0x90: |
| /* Case 2S.1, fall-through */ |
| case 0x91: case 0x92: case 0x93: case 0x94: case 0x95: |
| case 0x96: case 0x97: case 0x98: case 0x99: case 0x9a: |
| case 0x9b: case 0x9c: case 0x9d: case 0x9e: case 0x9f: |
| /* Case 2S.4 */ |
| /* copy response data over */ |
| cur = msgb_put(amsg, msgb_l3len(tmsg)); |
| memcpy(cur, tmsg->l3h, msgb_l3len(tmsg)); |
| } |
| break; |
| case APDU_CASE_4S: |
| /* FIXME: this is 4S.2 only for 2nd... response: */ |
| if (num_resp >= 2) |
| goto case_2s; |
| |
| switch (sw >> 8) { |
| case 0x60: case 0x62: case 0x63: case 0x64: case 0x65: |
| case 0x66: case 0x67: case 0x68: case 0x69: case 0x6a: |
| case 0x6b: case 0x6c: case 0x6d: case 0x6e: case 0x6f: |
| /* Case 4S.1: Command not accepted: just copy SW */ |
| break; |
| case 0x90: |
| /* case 4S.2: Command accepted */ |
| tpduh->ins = 0xC0; |
| tpduh->p1 = tpduh->p2 = 0; |
| tpduh->p3 = msgb_apdu_le(amsg); |
| /* strip off current result */ |
| msgb_get(tmsg, msgb_length(tmsg)-sizeof(*tpduh)); |
| goto transceive_again; |
| break; |
| case 0x61: /* Case 4S.3: command accepted with info added */ |
| case 0x9F: /* FIXME: This is specific to SIM cards */ |
| tpduh->ins = 0xC0; |
| tpduh->p1 = tpduh->p2 = 0; |
| tpduh->p3 = OSMO_MIN(msgb_apdu_le(amsg), sw & 0xff); |
| /* strip off current result */ |
| msgb_get(tmsg, msgb_length(tmsg)-sizeof(*tpduh)); |
| goto transceive_again; |
| break; |
| } |
| /* Case 4S.2: Command accepted: just copy SW */ |
| /* Case 4S.4: Just copy SW */ |
| break; |
| case APDU_CASE_2E: |
| if (msgb_apdu_le(amsg) <= 256) { |
| /* Case 2E.1: Le <= 256 */ |
| goto case_2s; |
| } |
| switch (sw >> 8) { |
| case 0x67: |
| /* Case 2E.2a: wrong length, abort */ |
| break; |
| case 0x6c: |
| /* Case 2E.2b: wrong length, La given */ |
| tpduh->p3 = sw & 0xff; |
| /* re-issue the command with La as given */ |
| goto transceive_again; |
| break; |
| case 0x90: |
| /* Case 2E.2c: */ |
| break; |
| case 0x61: |
| /* Case 2E.2d: more data available */ |
| /* FIXME: issue yet another GET RESPONSE */ |
| break; |
| } |
| break; |
| case APDU_CASE_3E: |
| /* FIXME: handling for ENVELOPE splitting */ |
| break; |
| case APDU_CASE_4E: |
| break; |
| } |
| |
| msgb_free(tmsg); |
| |
| /* compute total length of response data */ |
| msgb_apdu_le(amsg) = amsg->tail - msgb_apdu_de(amsg); |
| |
| return sw; |
| } |
| |
| /* FIXME: T=1 According to ISO7816-4 Annex B */ |
| |
| int osim_transceive_apdu(struct osim_chan_hdl *st, struct msgb *amsg) |
| { |
| switch (st->card->proto) { |
| case OSIM_PROTO_T0: |
| return transceive_apdu_t0(st->card, amsg); |
| default: |
| return -ENOTSUP; |
| } |
| } |
| |
| struct osim_reader_hdl *osim_reader_open(enum osim_reader_driver driver, int idx, |
| const char *name, void *ctx) |
| { |
| const struct osim_reader_ops *ops; |
| struct osim_reader_hdl *rh; |
| |
| switch (driver) { |
| #ifdef HAVE_PCSC |
| case OSIM_READER_DRV_PCSC: |
| ops = &pcsc_reader_ops; |
| break; |
| #endif |
| default: |
| return NULL; |
| } |
| |
| rh = ops->reader_open(idx, name, ctx); |
| if (!rh) |
| return NULL; |
| rh->ops = ops; |
| |
| /* FIXME: for now we only do T=0 on all readers */ |
| rh->proto_supported = (1 << OSIM_PROTO_T0); |
| |
| return rh; |
| } |
| |
| struct osim_card_hdl *osim_card_open(struct osim_reader_hdl *rh, enum osim_proto proto) |
| { |
| struct osim_card_hdl *ch; |
| |
| if (!(rh->proto_supported & (1 << proto))) |
| return NULL; |
| |
| ch = rh->ops->card_open(rh, proto); |
| if (!ch) |
| return NULL; |
| |
| ch->proto = proto; |
| |
| return ch; |
| } |
| |
| int osim_card_reset(struct osim_card_hdl *card, bool cold_reset) |
| { |
| struct osim_reader_hdl *rh = card->reader; |
| |
| return rh->ops->card_reset(card, cold_reset); |
| } |
| |
| int osim_card_close(struct osim_card_hdl *card) |
| { |
| struct osim_reader_hdl *rh = card->reader; |
| int rc; |
| |
| rc = rh->ops->card_close(card); |
| |
| card->reader = NULL; |
| talloc_free(card); |
| rh->card = NULL; |
| |
| return rc; |
| } |