gnupg/scd/apdu.c

559 lines
16 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* apdu.c - ISO 7816 APDU functions and low level I/O
* Copyright (C) 2003 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG 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.
*
* GnuPG 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dlfcn.h>
#include <assert.h>
#include "scdaemon.h"
#include "apdu.h"
#define HAVE_CTAPI 1
#define MAX_READER 4 /* Number of readers we support concurrently. */
#define CARD_CONNECT_TIMEOUT 1 /* Number of seconds to wait for
insertion of the card (1 = don't wait). */
/* A global table to keep track of active readers. */
static struct {
int used; /* True if slot is used. */
unsigned short port; /* port number0 = unused, 1 - dev/tty */
int status;
unsigned char atr[33];
size_t atrlen;
} reader_table[MAX_READER];
/* ct API function pointer. */
static char (*CT_init) (unsigned short ctn, unsigned short Pn);
static char (*CT_data) (unsigned short ctn, unsigned char *dad,
unsigned char *sad, unsigned short lc,
unsigned char *cmd, unsigned short *lr,
unsigned char *rsp);
static char (*CT_close) (unsigned short ctn);
/*
Helper
*/
/* Find an unused reader slot for PORT and put it into the reader
table. Return -1 on error or the index into the reader table. */
static int
new_reader_slot (int port)
{
int i, reader = -1;
if (port < 0 || port > 0xffff)
{
log_error ("new_reader_slot: invalid port %d requested\n", port);
return -1;
}
for (i=0; i < MAX_READER; i++)
{
if (reader_table[i].used && reader_table[i].port == port)
{
log_error ("new_reader_slot: requested port %d already in use\n",
reader);
return -1;
}
else if (!reader_table[i].used && reader == -1)
reader = i;
}
if (reader == -1)
{
log_error ("new_reader_slot: out of slots\n");
return -1;
}
reader_table[reader].used = 1;
reader_table[reader].port = port;
return reader;
}
static void
dump_reader_status (int reader)
{
log_info ("reader %d: %s\n", reader,
reader_table[reader].status == 1? "Processor ICC present" :
reader_table[reader].status == 0? "Memory ICC present" :
"ICC not present" );
if (reader_table[reader].status != -1)
{
log_info ("reader %d: ATR=", reader);
log_printhex ("", reader_table[reader].atr,
reader_table[reader].atrlen);
}
}
#ifdef HAVE_CTAPI
/*
ct API Interface
*/
static const char *
ct_error_string (int err)
{
switch (err)
{
case 0: return "okay";
case -1: return "invalid data";
case -8: return "ct error";
case -10: return "transmission error";
case -11: return "memory allocation error";
case -128: return "HTSI error";
default: return "unknown CT-API error";
}
}
/* Wait for the card in READER and activate it. Return -1 on error or
0 on success. */
static int
ct_activate_card (int reader)
{
int rc, count;
for (count = 0; count < CARD_CONNECT_TIMEOUT; count++)
{
unsigned char dad[1], sad[1], cmd[11], buf[256];
unsigned short buflen;
if (count)
sleep (1); /* FIXME: we should use a more reliable timer. */
/* Check whether card has been inserted. */
dad[0] = 1; /* Destination address: CT. */
sad[0] = 2; /* Source address: Host. */
cmd[0] = 0x20; /* Class byte. */
cmd[1] = 0x13; /* Request status. */
cmd[2] = 0x00; /* From kernel. */
cmd[3] = 0x80; /* Return card's DO. */
cmd[4] = 0x00;
buflen = DIM(buf);
rc = CT_data (reader, dad, sad, 5, cmd, &buflen, buf);
if (rc || buflen < 2 || buf[buflen-2] != 0x90)
{
log_error ("ct_activate_card: can't get status of reader %d: %s\n",
reader, ct_error_string (rc));
return -1;
}
if (buf[0] == 0x05)
{ /* Connected, now activate the card. */
dad[0] = 1; /* Destination address: CT. */
sad[0] = 2; /* Source address: Host. */
cmd[0] = 0x20; /* Class byte. */
cmd[1] = 0x12; /* Request ICC. */
cmd[2] = 0x01; /* From first interface. */
cmd[3] = 0x01; /* Return card's ATR. */
cmd[4] = 0x00;
buflen = DIM(buf);
rc = CT_data (reader, dad, sad, 5, cmd, &buflen, buf);
if (rc || buflen < 2 || buf[buflen-2] != 0x90)
{
log_error ("ct_activate_card(%d): activation failed: %s\n",
reader, ct_error_string (rc));
return -1;
}
/* Store the type and the ATR. */
if (buflen - 2 > DIM (reader_table[0].atr))
{
log_error ("ct_activate_card(%d): ATR too long\n", reader);
return -1;
}
reader_table[reader].status = buf[buflen - 1];
memcpy (reader_table[reader].atr, buf, buflen - 2);
reader_table[reader].atrlen = buflen - 2;
return 0;
}
}
log_info ("ct_activate_card(%d): timeout waiting for card\n", reader);
return -1;
}
/* Open a reader and return an internal handle for it. PORT is a
non-negative value with the port number of the reader. USB readers
do have port numbers starting at 32769. */
static int
open_ct_reader (int port)
{
int rc, reader;
reader = new_reader_slot (port);
if (reader == -1)
return reader;
rc = CT_init (reader, (unsigned short)port);
if (rc)
{
log_error ("apdu_open_ct_reader failed on port %d: %s\n",
port, ct_error_string (rc));
reader_table[reader].used = 0;
return -1;
}
rc = ct_activate_card (reader);
if (rc)
{
reader_table[reader].used = 0;
return -1;
}
dump_reader_status (reader);
return reader;
}
/* Actually send the APDU of length APDULEN to SLOT and return a
maximum of *BUFLEN data in BUFFER, the actual retruned size will be
set to BUFLEN. Returns: CT API error code. */
static int
ct_send_apdu (int slot, unsigned char *apdu, size_t apdulen,
unsigned char *buffer, size_t *buflen)
{
int rc;
unsigned char dad[1], sad[1];
unsigned short ctbuflen;
dad[0] = 0; /* Destination address: Card. */
sad[0] = 2; /* Source address: Host. */
ctbuflen = *buflen;
if (DBG_CARD_IO)
log_printhex (" CT_data:", apdu, apdulen);
rc = CT_data (slot, dad, sad, apdulen, apdu, &ctbuflen, buffer);
*buflen = ctbuflen;
/* FIXME: map the errorcodes to GNUPG ones, so that they can be
shared between CTAPI and PCSC. */
return rc;
}
#endif /*HAVE_CTAPI*/
#ifdef HAVE_PCSC
/*
PC/SC Interface
*/
#endif /*HAVE_PCSC*/
/*
Driver Access
*/
/* Open the reader and return an internal slot number or -1 on
error. */
int
apdu_open_reader (int port)
{
static int ct_api_loaded;
if (!ct_api_loaded)
{
void *handle;
handle = dlopen ("libtowitoko.so", RTLD_LAZY);
if (!handle)
{
log_error ("apdu_open_reader: failed to open driver: %s",
dlerror ());
return -1;
}
CT_init = dlsym (handle, "CT_init");
CT_data = dlsym (handle, "CT_data");
CT_close = dlsym (handle, "CT_close");
if (!CT_init || !CT_data || !CT_close)
{
log_error ("apdu_open_reader: invalid driver\n");
dlclose (handle);
return -1;
}
ct_api_loaded = 1;
}
return open_ct_reader (port);
}
unsigned char *
apdu_get_atr (int slot, size_t *atrlen)
{
char *buf;
if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
return NULL;
buf = xtrymalloc (reader_table[slot].atrlen);
if (!buf)
return NULL;
memcpy (buf, reader_table[slot].atr, reader_table[slot].atrlen);
*atrlen = reader_table[slot].atrlen;
return buf;
}
static const char *
error_string (int slot, int rc)
{
#ifdef HAVE_CTAPI
return ct_error_string (rc);
#elif defined(HAVE_PCSC)
return "?";
#else
return "?";
#endif
}
/* Dispatcher for the actual send_apdu fucntion. */
static int
send_apdu (int slot, unsigned char *apdu, size_t apdulen,
unsigned char *buffer, size_t *buflen)
{
#ifdef HAVE_CTAPI
return ct_send_apdu (slot, apdu, apdulen, buffer, buflen);
#elif defined(HAVE_PCSC)
return SW_HOST_NO_DRIVER;
#else
return SW_HOST_NO_DRIVER;
#endif
}
/* Send an APDU to the card in SLOT. The APDU is created from all
given parameters: CLASS, INS, P0, P1, LC, DATA, LE. A value of -1
for LC won't sent this field and the data field; in this case DATA
must also be passed as NULL. The return value is the status word
or -1 for an invalid SLOT or other non card related error. If
RETBUF is not NULL, it will receive an allocated buffer with the
returned data. The length of that data will be put into
*RETBUFLEN. The caller is reponsible for releasing the buffer even
in case of errors. */
int
apdu_send_le(int slot, int class, int ins, int p0, int p1,
int lc, const char *data, int le,
unsigned char **retbuf, size_t *retbuflen)
{
unsigned char result[256+10]; /* 10 extra in case of bugs in the driver. */
size_t resultlen = 256;
unsigned char apdu[5+256+1];
size_t apdulen;
int rc, sw;
if (DBG_CARD_IO)
log_debug ("send apdu: c=%02X i=%02X p0=%02X p1=%02X lc=%d le=%d\n",
class, ins, p0, p1, lc, le);
if (lc != -1 && (lc > 255 || lc < 0))
return SW_WRONG_LENGTH;
if (le != -1 && (le > 256 || le < 1))
return SW_WRONG_LENGTH;
if ((!data && lc != -1) || (data && lc == -1))
return SW_HOST_INV_VALUE;
apdulen = 0;
apdu[apdulen++] = class;
apdu[apdulen++] = ins;
apdu[apdulen++] = p0;
apdu[apdulen++] = p1;
if (lc != -1)
{
apdu[apdulen++] = lc;
memcpy (apdu+apdulen, data, lc);
apdulen += lc;
}
if (le != -1)
apdu[apdulen++] = le; /* Truncation is okay becuase 0 means 256. */
assert (sizeof (apdu) >= apdulen);
/* As safeguard don't pass any garbage from the stack to the driver. */
memset (apdu+apdulen, 0, sizeof (apdu) - apdulen);
rc = send_apdu (slot, apdu, apdulen, result, &resultlen);
if (rc || resultlen < 2)
{
log_error ("apdu_send_simple(%d) failed: %s\n",
slot, error_string (slot, rc));
return SW_HOST_INCOMPLETE_CARD_RESPONSE;
}
sw = (result[resultlen-2] << 8) | result[resultlen-1];
/* store away the returned data but strip the statusword. */
resultlen -= 2;
if (DBG_CARD_IO)
{
log_debug (" response: sw=%04X datalen=%d\n", sw, resultlen);
if ( !retbuf && (sw == SW_SUCCESS || (sw & 0xff00) == SW_MORE_DATA))
log_printhex (" dump: ", result, resultlen);
}
if (sw == SW_SUCCESS)
{
if (retbuf)
{
*retbuf = xtrymalloc (resultlen? resultlen : 1);
if (!*retbuf)
return SW_HOST_OUT_OF_CORE;
*retbuflen = resultlen;
memcpy (*retbuf, result, resultlen);
}
}
else if ((sw & 0xff00) == SW_MORE_DATA)
{
unsigned char *p = NULL, *tmp;
size_t bufsize = 4096;
/* It is likely that we need to return much more data, so we
start off with a large buffer. */
if (retbuf)
{
*retbuf = p = xtrymalloc (bufsize);
if (!*retbuf)
return SW_HOST_OUT_OF_CORE;
assert (resultlen < bufsize);
memcpy (p, result, resultlen);
p += resultlen;
}
do
{
int len = (sw & 0x00ff);
log_debug ("apdu_send_simple(%d): %d more bytes available\n",
slot, len);
apdulen = 0;
apdu[apdulen++] = class;
apdu[apdulen++] = 0xC0;
apdu[apdulen++] = 0;
apdu[apdulen++] = 0;
apdu[apdulen++] = 64; /* that is 256 bytes for Le */
memset (apdu+apdulen, 0, sizeof (apdu) - apdulen);
rc = send_apdu (slot, apdu, apdulen, result, &resultlen);
if (rc || resultlen < 2)
{
log_error ("apdu_send_simple(%d) for get response failed: %s\n",
slot, error_string (slot, rc));
return SW_HOST_INCOMPLETE_CARD_RESPONSE;
}
sw = (result[resultlen-2] << 8) | result[resultlen-1];
resultlen -= 2;
if (DBG_CARD_IO)
{
log_debug (" more: sw=%04X datalen=%d\n", sw, resultlen);
if (!retbuf && (sw==SW_SUCCESS || (sw&0xff00)==SW_MORE_DATA))
log_printhex (" dump: ", result, resultlen);
}
if ((sw & 0xff00) == SW_MORE_DATA || sw == SW_SUCCESS)
{
if (retbuf)
{
if (p - *retbuf + resultlen > bufsize)
{
bufsize += resultlen > 4096? resultlen: 4096;
tmp = xtryrealloc (*retbuf, bufsize);
if (!tmp)
return SW_HOST_OUT_OF_CORE;
p = tmp + (p - *retbuf);
*retbuf = tmp;
}
memcpy (p, result, resultlen);
p += resultlen;
}
}
else
log_info ("apdu_send_simple(%d) "
"got unexpected status %04X from get response\n",
slot, sw);
}
while ((sw & 0xff00) == SW_MORE_DATA);
if (retbuf)
{
*retbuflen = p - *retbuf;
tmp = xtryrealloc (*retbuf, *retbuflen);
if (tmp)
*retbuf = tmp;
}
}
if (DBG_CARD_IO && retbuf && sw == SW_SUCCESS)
log_printhex (" dump: ", *retbuf, *retbuflen);
return sw;
}
/* Send an APDU to the card in SLOT. The APDU is created from all
given parameters: CLASS, INS, P0, P1, LC, DATA. A value of -1 for
LC won't sent this field and the data field; in this case DATA must
also be passed as NULL. The return value is the status word or -1
for an invalid SLOT or other non card related error. If RETBUF is
not NULL, it will receive an allocated buffer with the returned
data. The length of that data will be put into *RETBUFLEN. The
caller is reponsible for releasing the buffer even in case of
errors. */
int
apdu_send (int slot, int class, int ins, int p0, int p1,
int lc, const char *data, unsigned char **retbuf, size_t *retbuflen)
{
return apdu_send_le (slot, class, ins, p0, p1, lc, data, 256,
retbuf, retbuflen);
}
/* Send an APDU to the card in SLOT. The APDU is created from all
given parameters: CLASS, INS, P0, P1, LC, DATA. A value of -1 for
LC won't sent this field and the data field; in this case DATA must
also be passed as NULL. The return value is the status word or -1
for an invalid SLOT or other non card related error. No data will be
returned. */
int
apdu_send_simple (int slot, int class, int ins, int p0, int p1,
int lc, const char *data)
{
return apdu_send_le (slot, class, ins, p0, p1, lc, data, -1, NULL, NULL);
}