Basically made Belgian EID cards work.

Signature creation has not yet been tested.
Also other changes to better cope with T=0 cards.
This commit is contained in:
Werner Koch 2005-09-05 14:36:36 +00:00
parent 0a09a6316e
commit 1b2f7cbe3b
7 changed files with 349 additions and 44 deletions

View File

@ -1,3 +1,30 @@
2005-09-05 Werner Koch <wk@g10code.com>
* iso7816.c (iso7816_select_path): New.
* app-p15.c (select_ef_by_path): Allow for direct path selection.
(app_select_p15): Try using the beigian variant of pkcs#15.
(read_home_df): New.
(read_ef_odf): Generalized.
(read_ef_tokeninfo): New.
(read_p15_info): Set serialnumber from TokenInfo.
(app_select_p15): Don't munge serialNumber - that must be done
only once.
* iso7816.c (iso7816_read_binary): Use Le=0 when reading all
data. Handle 6C00 error and take 6B00 as indication for EOF.
* apdu.h (SW_EXACT_LENGTH_P): New.
* apdu.c (new_reader_slot, reset_pcsc_reader, pcsc_get_status)
(open_pcsc_reader): Set new reader state IS_T0.
(apdu_send_le): When doing T=0 make sure not to send Lc and Le.
Problem reported by Carl Meijer.
(apdu_send_direct): Initialize RESULTLEN.
* pcsc-wrapper.c (handle_status): Return the current protocol as
a new third word.
2005-08-05 Werner Koch <wk@g10code.com>
* apdu.c (open_rapdu_reader): Set the reader number.
2005-07-05 Werner Koch <wk@g10code.com>
* app-openpgp.c (do_readkey): Return a mallcoed copy of the key as

View File

@ -15,7 +15,8 @@
*
* 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
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*
* $Id$
*/
@ -126,6 +127,7 @@ struct reader_table_s {
char *rdrname; /* Name of the connected reader or NULL if unknown. */
int last_status;
int status;
int is_t0; /* True if we know that we are running T=0. */
unsigned char atr[33];
size_t atrlen; /* A zero length indicates that the ATR has
not yet been read; i.e. the card is not
@ -275,6 +277,9 @@ long (* DLSTDCALL pcsc_set_timeout) (unsigned long context,
unsigned long timeout);
/* Prototypes. */
static int pcsc_get_status (int slot, unsigned int *status);
/*
@ -319,6 +324,7 @@ new_reader_slot (void)
reader_table[reader].used = 1;
reader_table[reader].last_status = 0;
reader_table[reader].is_t0 = 1;
#ifdef NEED_PCSC_WRAPPER
reader_table[reader].pcsc.req_fd = -1;
reader_table[reader].pcsc.rsp_fd = -1;
@ -768,6 +774,7 @@ reset_pcsc_reader (int slot)
size_t len;
int i, n;
unsigned char msgbuf[9];
unsigned int dummy_status;
int sw = SW_HOST_CARD_IO_ERROR;
slotp = reader_table + slot;
@ -841,6 +848,9 @@ reset_pcsc_reader (int slot)
}
slotp->atrlen = len;
/* Read the status so that IS_T0 will be set. */
pcsc_get_status (slot, &dummy_status);
return 0;
command_failed:
@ -902,6 +912,7 @@ reset_pcsc_reader (int slot)
if (atrlen >= DIM (reader_table[0].atr))
log_bug ("ATR returned by pcsc_status is too large\n");
reader_table[slot].atrlen = atrlen;
reader_table[slot].is_t0 = !!(card_protocol & PCSC_PROTOCOL_T0);
return 0;
#endif /* !NEED_PCSC_WRAPPER */
@ -917,7 +928,7 @@ pcsc_get_status (int slot, unsigned int *status)
size_t len, full_len;
int i, n;
unsigned char msgbuf[9];
unsigned char buffer[12];
unsigned char buffer[16];
int sw = SW_HOST_CARD_IO_ERROR;
slotp = reader_table + slot;
@ -968,14 +979,20 @@ pcsc_get_status (int slot, unsigned int *status)
full_len = len;
n = 8 < len ? 8 : len;
if ((i=readn (slotp->pcsc.rsp_fd, buffer, n, &len)) || len != 8)
/* The current version returns 3 words but we allow also for old
versions returning only 2 words. */
n = 12 < len ? 12 : len;
if ((i=readn (slotp->pcsc.rsp_fd, buffer, n, &len))
|| (len != 8 && len != 12))
{
log_error ("error receiving PC/SC STATUS response: %s\n",
i? strerror (errno) : "premature EOF");
goto command_failed;
}
slotp->is_t0 = (len == 12 && !!(buffer[11] & PCSC_PROTOCOL_T0));
full_len -= len;
/* Newer versions of the wrapper might send more status bytes.
Read them. */
@ -1296,6 +1313,7 @@ open_pcsc_reader (const char *portstr)
size_t len;
unsigned char msgbuf[9];
int err;
unsigned int dummy_status;
int sw = SW_HOST_CARD_IO_ERROR;
slot = new_reader_slot ();
@ -1440,7 +1458,7 @@ open_pcsc_reader (const char *portstr)
slotp->last_status = 0;
/* The open fucntion may return a zero for the ATR length to
/* The open request may return a zero for the ATR length to
indicate that no card is present. */
n = len;
if (n)
@ -1463,6 +1481,9 @@ open_pcsc_reader (const char *portstr)
reader_table[slot].send_apdu_reader = pcsc_send_apdu;
reader_table[slot].dump_status_reader = dump_pcsc_reader_status;
/* Read the status so that IS_T0 will be set. */
pcsc_get_status (slot, &dummy_status);
dump_reader_status (slot);
return slot;
@ -1596,6 +1617,7 @@ open_pcsc_reader (const char *portstr)
/* If we got to here we know that a card is present
and usable. Thus remember this. */
reader_table[slot].last_status = (1|2|4| 0x8000);
reader_table[slot].is_t0 = !!(card_protocol & PCSC_PROTOCOL_T0);
}
}
@ -1997,6 +2019,7 @@ open_rapdu_reader (int portno,
return -1;
}
rapdu_set_reader (slotp->rapdu.handle, portno);
rapdu_set_iofunc (slotp->rapdu.handle,
readfnc, readfnc_value,
@ -2518,7 +2541,7 @@ apdu_send_le(int slot, int class, int ins, int p0, int p1,
if (lc != -1 && (lc > 255 || lc < 0))
return SW_WRONG_LENGTH;
if (le != -1 && (le > 256 || le < 1))
if (le != -1 && (le > 256 || le < 0))
return SW_WRONG_LENGTH;
if ((!data && lc != -1) || (data && lc == -1))
return SW_HOST_INV_VALUE;
@ -2536,9 +2559,13 @@ apdu_send_le(int slot, int class, int ins, int p0, int p1,
apdu[apdulen++] = lc;
memcpy (apdu+apdulen, data, lc);
apdulen += lc;
/* T=0 does not allow the use of Lc together with Le; thus
disable Le in this case. */
if (reader_table[slot].is_t0)
le = -1;
}
if (le != -1)
apdu[apdulen++] = le; /* Truncation is okay becuase 0 means 256. */
apdu[apdulen++] = le; /* Truncation is okay because 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);
@ -2736,14 +2763,14 @@ apdu_send_direct (int slot, const unsigned char *apdudata, size_t apdudatalen,
if ((sw = trylock_slot (slot)))
return sw;
/* We simply trucntate a too long APDU. */
/* We simply trunctate a too long APDU. */
if (apdudatalen > sizeof apdu)
apdudatalen = sizeof apdu;
apdulen = apdudatalen;
memcpy (apdu, apdudata, apdudatalen);
class = apdulen? *apdu : 0;
resultlen = RESULTLEN;
rc = send_apdu (slot, apdu, apdulen, result, &resultlen);
if (rc || resultlen < 2)
{

View File

@ -15,7 +15,8 @@
*
* 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
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*
* $Id$
*/
@ -41,6 +42,7 @@ enum {
SW_RECORD_NOT_FOUND = 0x6a83,
SW_REF_NOT_FOUND = 0x6a88,
SW_BAD_P0_P1 = 0x6b00,
SW_EXACT_LENGTH = 0x6c00,
SW_INS_NOT_SUP = 0x6d00,
SW_CLA_NOT_SUP = 0x6e00,
SW_SUCCESS = 0x9000,
@ -65,6 +67,8 @@ enum {
};
#define SW_EXACT_LENGTH_P(a) (((a)&~0xff) == SW_EXACT_LENGTH)
/* Note , that apdu_open_reader returns no status word but -1 on error. */
int apdu_open_reader (const char *portstr);

View File

@ -74,7 +74,17 @@ static struct
#undef X
/* The Pin Types as defined in pkcs#15 v1.1 */
/* The AID of PKCS15. */
static char const pkcs15_aid[] = { 0xA0, 0, 0, 0, 0x63,
0x50, 0x4B, 0x43, 0x53, 0x2D, 0x31, 0x35 };
/* The Belgian eID variant - they didn't understood why a shared AID
is useful for a standard. Oh well. */
static char const pkcs15be_aid[] = { 0xA0, 0, 0, 0x01, 0x77,
0x50, 0x4B, 0x43, 0x53, 0x2D, 0x31, 0x35 };
/* The PIN types as defined in pkcs#15 v1.1 */
typedef enum
{
PIN_TYPE_BCD = 0,
@ -261,6 +271,9 @@ struct app_local_s
/* The type of the card. */
card_type_t card_type;
/* Flag indicating whether we may use direct path selection. */
int direct_path_selection;
/* Structure with the EFIDs of the objects described in the ODF
file. */
struct
@ -276,6 +289,10 @@ struct app_local_s
unsigned short auth_objects;
} odf;
/* The PKCS#15 serialnumber from EF(TokeiNFo) or NULL. Malloced. */
unsigned char *serialno;
size_t serialnolen;
/* Information on all certificates. */
cdf_object_t certificate_info;
/* Information on all trusted certificates. */
@ -363,6 +380,7 @@ do_deinit (app_t app)
release_cdflist (app->app_local->useful_certificate_info);
release_prkdflist (app->app_local->private_key_info);
release_aodflist (app->app_local->auth_object_info);
xfree (app->app_local->serialno);
xfree (app->app_local);
app->app_local = NULL;
}
@ -406,26 +424,44 @@ select_ef_by_path (app_t app, const unsigned short *path, size_t pathlen)
gpg_error_t err;
int i, j;
/* FIXME: Need code to remember the last PATH so that we can decide
what select commands to send in case the path does not start off
with 3F00. We might also want to use direct path selection if
supported by the card. */
if (!pathlen)
return gpg_error (GPG_ERR_INV_VALUE);
if (pathlen && *path != 0x3f00 )
log_debug ("WARNING: relative path selection not yet implemented\n");
for (i=0; i < pathlen; i++)
if (app->app_local->direct_path_selection)
{
err = iso7816_select_file (app->slot, path[i],
!(i+1 == pathlen), NULL, NULL);
err = iso7816_select_path (app->slot, path+1, pathlen-1, NULL, NULL);
if (err)
{
log_error ("error selecting part %d from path ", i);
log_error ("error selecting path ");
for (j=0; j < pathlen; j++)
log_printf ("%04hX", path[j]);
log_printf (": %s\n", gpg_strerror (err));
return err;
}
}
else
{
/* FIXME: Need code to remember the last PATH so that we can decide
what select commands to send in case the path does not start off
with 3F00. We might also want to use direct path selection if
supported by the card. */
for (i=0; i < pathlen; i++)
{
err = iso7816_select_file (app->slot, path[i],
!(i+1 == pathlen), NULL, NULL);
if (err)
{
log_error ("error selecting part %d from path ", i);
for (j=0; j < pathlen; j++)
log_printf ("%04hX", path[j]);
log_printf (": %s\n", gpg_strerror (err));
return err;
}
}
}
return 0;
}
@ -586,12 +622,13 @@ read_ef_odf (app_t app, unsigned short odf_fid)
}
else if ( buflen >= 12
&& (p[0] & 0xf0) == 0xA0
&& !memcmp (p+1, "\x0a\x30\x08\x04\x06\x3F\x00\x50\x15", 9)
&& app->app_local->home_df == 0x5015 )
&& !memcmp (p+1, "\x0a\x30\x08\x04\x06\x3F\x00", 7)
&& app->app_local->home_df == ((p[8]<<8)|p[9]) )
{
/* This format using a full path is used by a self-created
test card of mine. I have not checked whether this is
legal. We assume a home DF of 0x5015 here. */
/* We only allow a full path if all files are at the same
level and below the home directory. The extend this we
would need to make use of new data type capable of
keeping a full path. */
offset = 10;
}
else
@ -2158,12 +2195,81 @@ TokenFlags ::= BIT STRING {
*/
/* static gpg_error_t */
/* read_ef_tokeninfo (app_t app) */
/* { */
/* unsigned short efid = 0x5032; */
/* return 0; */
/* } */
static gpg_error_t
read_ef_tokeninfo (app_t app)
{
gpg_error_t err;
unsigned char *buffer = NULL;
size_t buflen;
const unsigned char *p;
size_t n, objlen, hdrlen;
int class, tag, constructed, ndef;
unsigned long ul;
err = select_and_read_binary (app->slot, 0x5032, "TokenInfo",
&buffer, &buflen);
if (err)
return err;
p = buffer;
n = buflen;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > n || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
{
log_error ("error parsing TokenInfo: %s\n", gpg_strerror (err));
goto leave;
}
n = objlen;
/* Version. */
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > n || tag != TAG_INTEGER))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto leave;
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*p++) & 0xff;
n--;
}
if (ul)
{
log_error ("invalid version %lu in TokenInfo\n", ul);
err = gpg_error (GPG_ERR_INV_OBJ);
goto leave;
}
/* serialNumber. */
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > n || tag != TAG_OCTET_STRING || !objlen))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto leave;
xfree (app->app_local->serialno);
app->app_local->serialno = xtrymalloc (objlen);
if (!app->app_local->serialno)
{
err = gpg_error_from_errno (errno);
goto leave;
}
memcpy (app->app_local->serialno, p, objlen);
app->app_local->serialnolen = objlen;
log_printhex ("Serialnumber from EF(TokenInfo) is:", p, objlen);
leave:
xfree (buffer);
return err;
}
/* Get all the basic information from the pkcs#15 card, check the
@ -2174,11 +2280,25 @@ read_p15_info (app_t app)
{
gpg_error_t err;
/* Fixme: We might need to read the tokeninfo to get a non-standard
ODF FID. */
if (!read_ef_tokeninfo (app))
{
/* If we don't have a serial number yet but the TokenInfo provides
one, use that. */
if (!app->serialno && app->app_local->serialno)
{
app->serialno = app->app_local->serialno;
app->serialnolen = app->app_local->serialnolen;
app->app_local->serialno = NULL;
app->app_local->serialnolen = 0;
err = app_munge_serialno (app);
if (err)
return err;
}
}
/* Read the ODF so that we know the location of all directory
files. */
/* Fixme: We might need to get a non-standard ODF FID from TokenInfo. */
err = read_ef_odf (app, 0x5031);
if (err)
return err;
@ -2895,22 +3015,88 @@ do_sign (app_t app, const char *keyidstr, int hashalgo,
}
/* Assume that EF(DIR) has been selected. Read its content and figure
out the home EF of pkcs#15. Return that home DF or 0 if not
found. */
static unsigned short
read_home_df (int slot)
{
gpg_error_t err;
unsigned char *buffer;
const unsigned char *p, *pp;
size_t buflen, n, nn;
unsigned short result = 0;
err = iso7816_read_binary (slot, 0, 0, &buffer, &buflen);
if (err)
{
log_error ("error reading EF{DIR}: %s\n", gpg_strerror (err));
return 0;
}
/* FIXME: We need to scan all records. */
p = find_tlv (buffer, buflen, 0x61, &n);
if (p && n)
{
pp = find_tlv (p, n, 0x4f, &nn);
if (pp
&& ((nn == sizeof pkcs15_aid && !memcmp (pp, pkcs15_aid, nn))
||(nn == sizeof pkcs15be_aid && !memcmp (pp, pkcs15be_aid, nn))))
{
pp = find_tlv (p, n, 0x50, &nn);
if (pp) /* fixme: Filter log value? */
log_info ("pkcs#15 application label from EF(DIR) is `%.*s'\n",
(int)nn, pp);
pp = find_tlv (p, n, 0x51, &nn);
if (pp && nn == 4 && *pp == 0x3f && !pp[1])
{
result = ((pp[2] << 8) | pp[3]);
log_info ("pkcs#15 application directory is 0x%04hX\n", result);
}
}
}
xfree (buffer);
return result;
}
/* Select the PKCS#15 application on the card in SLOT. */
/*
Select the PKCS#15 application on the card in SLOT.
*/
gpg_error_t
app_select_p15 (app_t app)
{
static char const aid[] = { 0xA0, 0, 0, 0, 0x63,
0x50, 0x4B, 0x43, 0x53, 0x2D, 0x31, 0x35 };
int slot = app->slot;
int rc;
unsigned short def_home_df = 0;
card_type_t card_type = CARD_TYPE_UNKNOWN;
int direct = 0;
rc = iso7816_select_application (slot, aid, sizeof aid);
rc = iso7816_select_application (slot, pkcs15_aid, sizeof pkcs15_aid);
if (rc)
{
rc = iso7816_select_application (slot, pkcs15be_aid, sizeof pkcs15be_aid);
if (rc)
{ /* Not found: Try to locate it from 2F00. We use direct path
selection here because it seems that the Belgian eID card
does only allow for that. Many other cards supports this
selection method too. */
unsigned short path[1] = { 0x2f00 };
rc = iso7816_select_path (app->slot, path, 1, NULL, NULL);
if (!rc)
{
direct = 1;
def_home_df = read_home_df (slot);
if (def_home_df)
{
path[0] = def_home_df;
rc = iso7816_select_path (app->slot, path, 1, NULL, NULL);
}
}
}
if (rc)
{ /* Still not found: Try the default DF. */
def_home_df = 0x5015;
rc = iso7816_select_file (slot, def_home_df, 1, NULL, NULL);
}
@ -2958,6 +3144,9 @@ app_select_p15 (app_t app)
the common APP structure. */
app->app_local->card_type = card_type;
/* Store whether we may and should use direct path selection. */
app->app_local->direct_path_selection = direct;
/* Read basic information and thus check whether this is a real
card. */
rc = read_p15_info (app);
@ -2989,8 +3178,6 @@ app_select_p15 (app_t app)
app->serialno = p;
}
}
else /* Use standard munging code. */
rc = app_munge_serialno (app);
app->fnc.deinit = do_deinit;
app->fnc.learn_status = do_learn_status;

View File

@ -77,6 +77,7 @@ map_sw (int sw)
case SW_RECORD_NOT_FOUND:ec= GPG_ERR_NOT_FOUND; break;
case SW_REF_NOT_FOUND: ec = GPG_ERR_NO_OBJ; break;
case SW_BAD_P0_P1: ec = GPG_ERR_INV_VALUE; break;
case SW_EXACT_LENGTH: ec = GPG_ERR_INV_VALUE; break;
case SW_INS_NOT_SUP: ec = GPG_ERR_CARD; break;
case SW_CLA_NOT_SUP: ec = GPG_ERR_CARD; break;
case SW_SUCCESS: ec = 0; break;
@ -161,6 +162,39 @@ iso7816_select_file (int slot, int tag, int is_dir,
}
/* Do a select file command with a direct path. */
gpg_error_t
iso7816_select_path (int slot, const unsigned short *path, size_t pathlen,
unsigned char **result, size_t *resultlen)
{
int sw, p0, p1;
unsigned char buffer[100];
int buflen;
if (result || resultlen)
{
*result = NULL;
*resultlen = 0;
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
}
if (pathlen/2 >= sizeof buffer)
return gpg_error (GPG_ERR_TOO_LARGE);
for (buflen = 0; pathlen; pathlen--, path++)
{
buffer[buflen++] = (*path >> 8);
buffer[buflen++] = *path;
}
p0 = 0x08;
p1 = 0x0c; /* No FC return. */
sw = apdu_send_simple (slot, 0x00, CMD_SELECT_FILE,
p0, p1, buflen, (char*)buffer );
return map_sw (sw);
}
/* This is a private command currently only working for TCOS cards. */
gpg_error_t
iso7816_list_directory (int slot, int list_dirs,
@ -524,8 +558,10 @@ iso7816_read_binary (int slot, size_t offset, size_t nmax,
{
buffer = NULL;
bufferlen = 0;
/* Fixme: Either the ccid driver or the TCOS cards have problems
with an Le of 0. */
/* Note, that we to set N to 254 due to problems either with the
ccid driver or some TCOS cards. It actually should be 0
which is the official ISO value to read a variable length
object. */
if (read_all || nmax > 254)
n = 254;
else
@ -533,6 +569,21 @@ iso7816_read_binary (int slot, size_t offset, size_t nmax,
sw = apdu_send_le (slot, 0x00, CMD_READ_BINARY,
((offset>>8) & 0xff), (offset & 0xff) , -1, NULL,
n, &buffer, &bufferlen);
if ( SW_EXACT_LENGTH_P(sw) )
{
n = (sw & 0x00ff);
sw = apdu_send_le (slot, 0x00, CMD_READ_BINARY,
((offset>>8) & 0xff), (offset & 0xff) , -1, NULL,
n, &buffer, &bufferlen);
}
if (*result && sw == SW_BAD_P0_P1)
{
/* Bad Parameter means that the offset is outside of the
EF. When reading all data we take this as an indication
for EOF. */
break;
}
if (sw != SW_SUCCESS && sw != SW_EOF_REACHED)
{

View File

@ -33,6 +33,9 @@ gpg_error_t iso7816_select_application (int slot,
const char *aid, size_t aidlen);
gpg_error_t iso7816_select_file (int slot, int tag, int is_dir,
unsigned char **result, size_t *resultlen);
gpg_error_t iso7816_select_path (int slot,
const unsigned short *path, size_t pathlen,
unsigned char **result, size_t *resultlen);
gpg_error_t iso7816_list_directory (int slot, int list_dirs,
unsigned char **result, size_t *resultlen);
gpg_error_t iso7816_verify (int slot,

View File

@ -15,7 +15,8 @@
*
* 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
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*/
/*
@ -587,6 +588,11 @@ handle_status (unsigned char *argbuf, size_t arglen)
buf[5] = (rdrstates[0].event_state >> 16);
buf[6] = (rdrstates[0].event_state >> 8);
buf[7] = (rdrstates[0].event_state >> 0);
/* The third word is the protocol. */
buf[8] = (pcsc_protocol >> 24);
buf[9] = (pcsc_protocol >> 16);
buf[10] = (pcsc_protocol >> 8);
buf[11] = (pcsc_protocol);
request_succeeded (buf, 8);
}