Some Linux experiments with the ACR38 smartcards reader

Trying to read smartcards using only libusb. For fun and learning.

Nom :

10 mars 2007

Detecting and powering the card up...

One will need a routine to exchange messages with the card reader, something like :

/*
** ACR_xchange(udp, *outp, out_len, *inp, in_len)
** send a command to the reader and get the answer
** returns 0 if all goes well; -1 if not
*/
int ACR_xchange(struct usb_dev_handle* udp, u_char *outp, int out_len, u_char *inp, int in_len)
{
if (usb_bulk_write(udp, EP_OUT, (char *)outp, out_len, TIMEOUT) < 0)
{
perror("usb_bulk_write");
return(-1);
}
if (usb_bulk_read(udp, EP_IN, (char *)inp, in_len, TIMEOUT) < 0)
{
perror("usb_bulk_read");
return(-1);
}
if (inp[1])
return(-1); /* bad status -- should set errno */
return(0);
}

Now, one can see if a card is present by requesting the reader's ACR_STAT. The last byte ([19]) of the answer is 0 if there is no card, 1 if there is a card in the reader and 3 if it is powered.

/*
** ACR_card_present(udp)
** returns 1 if a card is present, 0 otherwise
** -1 if something goes wrong
*/
int ACR_card_present(struct usb_dev_handle* udp)
{
u_char buf_out[] = { 0x01, 0x01, 0x00, 0x00, 0x00, 0x00 }; /* GET_ACR_STAT */
u_char buf_in[64];

if (ACR_xchange(udp, buf_out, 6, buf_in, 64) < 0)
{
perror("ACR_xchange");
return(-1);
}
return(buf_in[19] & 1); /* card present */
}

Once we detect a card, we power it up as in the file ./bel-eid-fr-linux/Etape_1/ACR38_LINUX_100706_P/src/driver/AdmHndlr.c.

/*
** MCU_powerup(udp)
*/
int MCU_powerup(struct usb_dev_handle *udp)
{
u_char MCU_EMV[] = { 0x01, 0x07, 0x00, 0x01, 0x00}; /* [No EMV + No Mem Card] */
u_char MCU_TYPE[] = { 0x01, 0x02, 0x00, 0x01, 0x00}; /* auto select */
u_char MCU_POWER[] = { 0x01, 0x80, 0x00, 0x00};
u_char buf_in[64];

/*
** EMV OPTIONS : no EMV no Mem (?!)
*/
if (ACR_xchange(udp, MCU_EMV, 5, buf_in, sizeof(buf_in)) < 0)
{
perror("MCU_EMV");
return(-1);
}
/*
** card type auto select
*/
if (ACR_xchange(udp, MCU_TYPE, 5, buf_in, sizeof(buf_in)) < 0)
{
perror("MCU_TYPE");
return(-1);
}
/*
** Power up the card
*/
if (ACR_xchange(udp, MCU_POWER, 4, buf_in, sizeof(buf_in)) < 0)
{
perror("MCU_POWER");
return(-1);
}
xdump("----- ATR:", &buf_in[4], buf_in[3]);
return(0);
}

and power it off...

/*
** MCU_powerdown(udp)
*/
int MCU_powerdown(struct usb_dev_handle *udp)
{
u_char MCU_POWER_DOWN[] = { 0x01, 0x81, 0x00, 0x00};
u_char buf_in[64];

if (ACR_xchange(udp, MCU_POWER_DOWN, 4, buf_in, sizeof(buf_in)) < 0)
{
perror("MCU_POWER_DOWN");
return(-1);
}
return(0);
}

So, the main code becomes

main()
{
...
if ((udp = usb_open(dev)) == NULL)
{
perror("usb_open");
exit(-1);
}

while (ACR_card_present(udp) != 1)
sleep(1); /* wait for card insertion */

MCU_powerup(udp);
sleep(10);
MCU_powerdown(udp);

if (usb_close(udp) < 0)
{
perror("usb_close");
exit(-1);
}
exit(0);
}

The 'sleep(10)' is there to see if the LED (steady) shines as documented.
What is interesting is that we get the Answer-To-Reset (ATR) of the inserted card...
For example :

Dexia bank card (Belgium)
----- ATR: : 3b 67 00 00 2d 20 36 00 78 90 00
VISA Dexia (Belgium)
----- ATR: : 3b 67 00 00 00 00 00 00 00 90 00
FNAC Finaref (Belgium; exp. 2006)
----- ATR: : 3b 6d 00 00 00 31 c0 71 d6 64 34 c7 02 00 84 90 00

Googling these byte sequences, one finds a smardcard_list.txt (from Ludovic Rousseau) and other informations to explore.

Is there a standard way to extract data from a card? a 'READ BINARY' APDU?