242 lines
5.5 KiB
C
242 lines
5.5 KiB
C
/*
|
|
* card-acos5.c: Support for ACS ACOS5 cards.
|
|
*
|
|
* Copyright (C) 2007 Ian A. Young<ian@iay.org.uk>
|
|
*
|
|
* 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
|
|
*/
|
|
|
|
#if HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <string.h>
|
|
|
|
#include "internal.h"
|
|
#include "cardctl.h"
|
|
|
|
static struct sc_atr_table acos5_atrs[] = {
|
|
{"3b:be:96:00:00:41:05:20:00:00:00:00:00:00:00:00:00:90:00", NULL, NULL,
|
|
SC_CARD_TYPE_ACOS5_GENERIC, 0, NULL},
|
|
{"3b:be:18:00:00:41:05:10:00:00:00:00:00:00:00:00:00:90:00", NULL, NULL,
|
|
SC_CARD_TYPE_ACOS5_GENERIC, 0, NULL},
|
|
{NULL, NULL, NULL, 0, 0, NULL}
|
|
};
|
|
|
|
static struct sc_card_operations *iso_ops;
|
|
static struct sc_card_operations acos5_ops;
|
|
static struct sc_card_driver acos5_drv = {
|
|
"ACS ACOS5 card",
|
|
"acos5",
|
|
&acos5_ops,
|
|
NULL, 0, NULL
|
|
};
|
|
|
|
static int acos5_match_card(sc_card_t * card)
|
|
{
|
|
int i;
|
|
|
|
i = _sc_match_atr(card, acos5_atrs, &card->type);
|
|
if (i < 0)
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
static int acos5_init(sc_card_t * card)
|
|
{
|
|
card->max_recv_size = 128;
|
|
card->max_send_size = 128;
|
|
return SC_SUCCESS;
|
|
}
|
|
|
|
static int acos5_select_file_by_path(sc_card_t * card,
|
|
const sc_path_t * in_path,
|
|
sc_file_t ** file_out)
|
|
{
|
|
int in_len = in_path->len;
|
|
const u8 *in_pos = in_path->value;
|
|
sc_path_t path;
|
|
|
|
memset(&path, 0, sizeof(sc_path_t));
|
|
path.len = 2; /* one component at a time */
|
|
path.type = SC_PATH_TYPE_FILE_ID;
|
|
|
|
/*
|
|
* Check parameters.
|
|
*/
|
|
if (in_len % 2 != 0)
|
|
return SC_ERROR_INVALID_ARGUMENTS;
|
|
|
|
/*
|
|
* File ID by file ID...
|
|
*/
|
|
while (in_len) {
|
|
int result;
|
|
memcpy(path.value, in_pos, 2);
|
|
result = iso_ops->select_file(card, &path, file_out);
|
|
if (result != SC_SUCCESS)
|
|
return result;
|
|
in_len -= 2;
|
|
in_pos += 2;
|
|
}
|
|
return SC_SUCCESS;
|
|
}
|
|
|
|
static int acos5_select_file(sc_card_t * card,
|
|
const sc_path_t * in_path, sc_file_t ** file_out)
|
|
{
|
|
switch (in_path->type) {
|
|
|
|
case SC_PATH_TYPE_PATH:
|
|
return acos5_select_file_by_path(card, in_path, file_out);
|
|
|
|
default:
|
|
return iso_ops->select_file(card, in_path, file_out);
|
|
}
|
|
}
|
|
|
|
static int acos5_get_serialnr(sc_card_t * card, sc_serial_number_t * serial)
|
|
{
|
|
int r;
|
|
u8 rbuf[SC_MAX_APDU_BUFFER_SIZE];
|
|
sc_apdu_t apdu;
|
|
|
|
/*
|
|
* Check arguments.
|
|
*/
|
|
if (!serial)
|
|
return SC_ERROR_INVALID_ARGUMENTS;
|
|
|
|
/*
|
|
* Return a cached serial number, if we have one.
|
|
*/
|
|
if (card->serialnr.len) {
|
|
memcpy(serial, &card->serialnr, sizeof(*serial));
|
|
return SC_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Fetch serial number using GET CARD INFO.
|
|
*/
|
|
sc_format_apdu(card, &apdu, SC_APDU_CASE_2_SHORT, 0x14, 0, 0);
|
|
apdu.cla |= 0x80;
|
|
apdu.resp = rbuf;
|
|
apdu.resplen = sizeof(rbuf);
|
|
apdu.le = 6;
|
|
r = sc_transmit_apdu(card, &apdu);
|
|
SC_TEST_RET(card->ctx, SC_LOG_DEBUG_NORMAL, r, "APDU transmit failed");
|
|
if (apdu.sw1 != 0x90 || apdu.sw2 != 0x00)
|
|
return SC_ERROR_INTERNAL;
|
|
|
|
/*
|
|
* Cache serial number.
|
|
*/
|
|
memcpy(card->serialnr.value, apdu.resp, MIN(apdu.resplen, SC_MAX_SERIALNR));
|
|
card->serialnr.len = MIN(apdu.resplen, SC_MAX_SERIALNR);
|
|
|
|
/*
|
|
* Copy and return serial number.
|
|
*/
|
|
memcpy(serial, &card->serialnr, sizeof(*serial));
|
|
return SC_SUCCESS;
|
|
}
|
|
|
|
static int acos5_card_ctl(sc_card_t * card, unsigned long cmd, void *ptr)
|
|
{
|
|
switch (cmd) {
|
|
|
|
case SC_CARDCTL_GET_SERIALNR:
|
|
return acos5_get_serialnr(card, (sc_serial_number_t *) ptr);
|
|
|
|
default:
|
|
return SC_ERROR_NOT_SUPPORTED;
|
|
}
|
|
}
|
|
|
|
static int acos5_list_files(sc_card_t * card, u8 * buf, size_t buflen)
|
|
{
|
|
sc_apdu_t apdu;
|
|
int r;
|
|
size_t count;
|
|
u8 *bufp = buf; /* pointer into buf */
|
|
int fno = 0; /* current file index */
|
|
|
|
/*
|
|
* Check parameters.
|
|
*/
|
|
if (!buf || (buflen & 1))
|
|
return SC_ERROR_INVALID_ARGUMENTS;
|
|
|
|
/*
|
|
* Use CARD GET INFO to fetch the number of files under the
|
|
* curently selected DF.
|
|
*/
|
|
sc_format_apdu(card, &apdu, SC_APDU_CASE_1, 0x14, 0x01, 0x00);
|
|
apdu.cla |= 0x80;
|
|
r = sc_transmit_apdu(card, &apdu);
|
|
SC_TEST_RET(card->ctx, SC_LOG_DEBUG_NORMAL, r, "APDU transmit failed");
|
|
if (apdu.sw1 != 0x90)
|
|
return SC_ERROR_INTERNAL;
|
|
count = apdu.sw2;
|
|
|
|
while (count--) {
|
|
u8 info[8];
|
|
|
|
/*
|
|
* Truncate the scan if no more room left in output buffer.
|
|
*/
|
|
if (buflen == 0)
|
|
break;
|
|
|
|
sc_format_apdu(card, &apdu, SC_APDU_CASE_2_SHORT, 0x14, 0x02,
|
|
fno++);
|
|
apdu.cla |= 0x80;
|
|
apdu.resp = info;
|
|
apdu.resplen = sizeof(info);
|
|
apdu.le = sizeof(info);
|
|
r = sc_transmit_apdu(card, &apdu);
|
|
SC_TEST_RET(card->ctx, SC_LOG_DEBUG_NORMAL, r, "APDU transmit failed");
|
|
if (apdu.sw1 != 0x90 || apdu.sw2 != 0x00)
|
|
return SC_ERROR_INTERNAL;
|
|
|
|
*bufp++ = info[2];
|
|
*bufp++ = info[3];
|
|
buflen -= 2;
|
|
}
|
|
|
|
return (bufp - buf);
|
|
}
|
|
|
|
static struct sc_card_driver *sc_get_driver(void)
|
|
{
|
|
struct sc_card_driver *iso_drv = sc_get_iso7816_driver();
|
|
|
|
iso_ops = iso_drv->ops;
|
|
acos5_ops = *iso_ops;
|
|
|
|
acos5_ops.match_card = acos5_match_card;
|
|
acos5_ops.init = acos5_init;
|
|
acos5_ops.select_file = acos5_select_file;
|
|
acos5_ops.card_ctl = acos5_card_ctl;
|
|
acos5_ops.list_files = acos5_list_files;
|
|
|
|
return &acos5_drv;
|
|
}
|
|
|
|
struct sc_card_driver *sc_get_acos5_driver(void)
|
|
{
|
|
return sc_get_driver();
|
|
}
|