Some Linux experiments with the ACR38 smartcards reader

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

Nom :

02 avril 2007

What is this all about...

The ACR38U is the smartcard reader (from ACS) distributed by the belgian gouvernment to read the new (electronic) Id cards. It seems well documented by the manufacturer. There is a lot of informations about the beId (Belgian Identity Card) and the ISO smart cards in general. Card Reader driver and applications are available for three main operating systems (MS-Windows, MacOSX and Linux). The sources are available for Linux and it is easy to install the binaries on the latest Ubuntu distributions (since Edgy Eft), one just has to search for 'beid' in Synaptic and select the gui.

The card reader should be able to read other cards...

The (Linux) sources are rather complex and it is difficult to see what's happening.

So came the idea of writing simple programs to explore USB, the card reader and the cards...

The following code is NOT (yet) fool proof. It is more an exploration than a real driver. There should be more checks for EINVALs, some extentions (get the ATR out of the powerup function; allow for more than 64 (?) bytes I/O;...). The aim is to have the more concise and readable code, a working 'exercice' with libusb. The dates of the blog are irrelevant, it is just a way to get the articles in the right order.

Notice that I put a new version of the program on GitHub : Beid2html (30Jan2015). It uses an USB/CCID class device : The Digipass-870 from VASCO proposed by the Belfius bank and uses libusb 1.0

01 avril 2007

Using libusb with the ACR38...

Connecting with libusb

!!! DRAFT !!!

The first thing to do is to find the device on the USB bus. This is described in various 'libusb tutorials' (see here to find one).


/*
** ACR38_get_dev()
** find the first ACR38 device
*/
struct usb_device *ACR38_get_dev()
{
struct usb_bus *busses, *bus;
struct usb_device *dev;

usb_init();
usb_find_busses();
usb_find_devices();

busses = usb_get_busses();
for (bus = busses; bus; bus = bus->next)
{
for (dev = bus->devices; dev; dev = dev->next)
{
if (dev->descriptor.idVendor == 0x072f && /* ACS */
dev->descriptor.idProduct == 0x9000) /* ACR38 */
{
printf("found ACR38\n");
break;
}
}
if (dev)
break;
}
return(dev);
}

An application now looks something like this :

main()
{
struct usb_device *dev;
struct usb_dev_handle* udp;

if ((dev = ACR38_get_dev()) == NULL)
exit(-1);
if ((udp = usb_open(dev)) == NULL)
{
error("usb_open");
exit(-1);
}
/*
** ... do something with libusb functions (using udp)
*/
if (usb_close(udp) < 0)
{
perror("usb_close");
exit(-1);
}
}

Now, one has to read the card manufacturer's Reference document, (REF_ACR38x_v1.9.pdf) to find out what to do next. By example, one can send simple commands to the card reader, or ask if there is a card in the reader... 'lsusb -v' gives the endpoints adresses, there is no need to write code to get it.

#define TIMEOUT 20*100 /*HZ*/

#define EP_IN 0x82 /* bulk in endpoint */
#define EP_OUT 0x02 /* bulk out endpoint */
#define EP_INT_IN 0x81 /* interrupt endpoint */
/*
** ACR38_get_acr_stat(udp)
** get information about the card reader
** GET_ACR_STAT 8.1.1 in REF_ACR38x_v1.9.pdf
*/
u_char *ACR38_get_acr_stat(struct usb_dev_handle* udp)
{
u_char buf_out[] = { 0x01, 0x01, 0x00, 0x00 };
static u_char buf_in[64];

if (usb_bulk_write(udp, EP_OUT, (char *)buf_out, 4, TIMEOUT) < 0)
{
perror("usb_bulk_write");
return(NULL);
}
if (usb_bulk_read(udp, EP_IN, (char *)buf_in, 20, TIMEOUT) < 0)
{
perror("usb_bulk_read");
return(NULL);
}
return(buf_in); /* 20 bytes to be interpreted elsewhere */
}

One has to have the permission to handle the USB device, and, when there is a card in the reader, ACR38_get_acr_stat() returns :

00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 (index)
01 00 00 10 41 43 52 33 38 2d 31 30 34 30 ff ff 00 06 0c 01 (returned)

Last byte is 0 when there is no card in the reader (and will be 3 when the card is powered).

It seems that using the next code crashes the reader... So, I won't use EP_INT_IN any more until I find an explanation. It doesn't seem to be used in the 'bel-eid-fr-linux.tar.gz' sources either...

/*
** ACR38_getstatus(udp)
** tells if there is a card in the reader
*/
u_char *ACR38_getstatus(struct usb_dev_handle *udp)
{
static u_char buf_in[8];

if (usb_interrupt_read(udp, EP_INT_IN, (char *)buf_in, 8, TIMEOUT) < 0)
{
perror("usb_interrup_read");
return(NULL);
}
return(buf_in); /* card status message 7.3 */
}

When it works, as documented, ACR38_getstatus() returns 01 c1 when there is a card in the reader and 01 c0 when there is none. But I can't do it twice, it seems that I have to unplug/replug the device to get it working again... Reads return an error 'Resource temporarily unavailable'... Why? That's the question!...

This still is experimental.

20 mars 2007

In the ACR38 manual...

*** draft ***

The ACR38 USB Smart Card Reader/Writer Reference Manual (.pdf) is 43 pages long...

There are four USB End Points :
* control
* 0x02 bulk out for commands from computer (64 bytes)
* 0x82 bulk in for response from reader (64 bytes)
* 0x81 interrupt in for card status messages (8 bytes)


Commands are in the form of <01><cmd><len-hi><len-low><data><data>...
Responses are in the form of <01><status><len-hi><len-low><data><data>...
Card status are in the form of <01><status : C0/C1><00><00>

There are two types of commands : control commands to the ACR38 and card commands to the inserted card.

A/ Control commands

A-1 : GET_ACR_STATUS

cmd : <01><01><00><00>
rsp : <01><status><00><10> (len=0x10)
<10 bytes undocumented internals>
<ff><ff> (max cmd/rsp len)
<ctype><ctype> (bitmaps card types)
<c_cel> (selected card type)
<c_stat 00/01/03> (empty/loaded/powered)

A-2 : SELECT_CARD_TYPE

cmd : <01><02><00><00><c_type>
rsp : <01><status><00><00>

A-3 : SET_OPTION

cmd : <01><07><00><01><option>
rsp : <01><status><00><00>

A-4 : SET_CARD_PPS

cmd : <01><04><00><04><..><..><..><..> (??)
rsp : <01><status><00><00>

A-5 : SET_READER_PPS

cmd : <01><0b><00><04><..><..><..><..> (??)
rsp : <01><status><00><00>


B/ Card commands (for MCU based cards)


B-1 : RESET_WITH_5_VOLTS_DEFAULT

cmd : <01><80><00><00>
rsp : <01><status><len-hi><len-lo><...card's Answer-To-Reset (ATR) data...>

B-2 : RESET_WITH_SPECIFIC_VOLTAGE

cmd : <01><80><00><01><00/01/02/03> (voltages)
rsp : <01><status><len-hi><len-lo><...card's Answer-To-Reset (ATR) data...>

B-3 : POWER_OFF

cmd : <01><81><00><00>
rsp : <01><status><00><00>

B-4 : EXCHANGE_TPDU_T0

cmd : <01><a0><len-hi><len-lo><...APDU data...>
rsp : <01><status><len-hi><len-lo><...APDU response...><crc><crc>

B-5 : EXCHANGE_TPDU_T1

cmd : <01><a1><len-hi><len-lo><...APDU data...>
rsp : <01><status><len-hi><len-lo><...APDU response...>

APDU format is described elsewhere...

So, that can't be that complicated (but doesn't seem to work that way)... Anyway, to be sure, it is now time to look at the sources to know what commands and parameters are actualy used...


Error Codes
00 - no error
F4 - SLOTERROT_PROCEDURE_BYTE_CONFLICT
F6 - SLOTERROR_BAD_LENGTH
F7 - SLOTERROR_BAD_FIDI
F8 - SLOTERROR_BAD_ATR_TS
F9 - SLOTERROR_ICC_NOT_POWERED_UP
FA - SLOTERROR_ICC_NOT_INSERTED
FB - SLOTERROR_HW_ERROR
FC - SLOTERROR_XFE_OVERRUN
FD - SLOTERROR_XFE_PARITY_ERROR
FE - SLOTERROR_ICC_MUTE
FF - SLOTERROR_CMD_ABORTED



There are also other manuals available from ACS...

15 mars 2007

Digging the sources : bel-eid-fr-linux.tar.gz

It is time to have a look at the sources. First, the sources from the government : bel-eid-fr-linux.tar.gz

(the site also mentions 'PC/SC Lite v1.2'...)

Let see if it uses libusb


$ cd bel-eid-fr-linux
$ find . -exec grep usb_open \{} \; -a -print
$

no.
The code uses opendir("/proc/bus/usb") to find the 072f:9000 device in ./Etape_1/ACR38_LINUX_100706_P/src/driver/usblinux.c (via usbserial_linux.c)

in usbserial_linux.c, one finds :
- OpenUSB()
- ReadUSB()
- WriteUSB()
- CloseUSB()
they call functions defined in usblinux.c :
- open_linux_usb_dev() does the opendir() and open a file in /proc/bus/usb/...
- control_linux_usb_dev() does an ioctl(IOCTL_USB_CTL)
- bulk_linux_usb_dev() does an ioctl(IOCTL_USB_BULK) to implement reads/writes

OpenUSB() is used in ./Etape_1/ACR38_LINUX_100706_P/src/driver/AdmHndlr.c where one finds an
- Adm_Transmit() that does a WriteUSB() followed by a ReadUSB()
and a lot of dialog functions with the card reader. It is in this file that one will find the values to use to initialize the beast. :-)

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?

05 mars 2007

Time to dialog with the smartcard...

It is now time to exchange APDU with the smartcard. By example, a file_select...

int MCU_root_select(struct usb_dev_handle *udp)
{
const u_char MCU_FILE_SELECT[] = {0x01, 0xa0, 0x00, 0x05, 0x00, 0xA4, 0x00, 0x00, 0x00};
u_char buf_in[128];
u_char cmd[9];

if (ACR_xchange(udp, MCU_FILE_SELECT, 9, buf_in, sizeof(buf_in)) < 0)
{
perror("MCU_file_select");
return(-1);
}
printf("MCU_root_select status : 0x%02X\n", buf_in[1]);
xdump("MCU_root_select:", buf_in, 32);
return(0);
}

This does not work with the FNAC card :

SND -> 01 a0 00 05 00 a4 00 00 00 (?select master file)
RCV <- 01 00 00 02 6a 87

And, according to the ISO7816-4 standard, the code <6A><87> is an error code for 'Lc inconsistent with P1-P2'. There is something wrong with our APDU. ...But, the smartcard responds!, we are a step further. :-)

If I try a 'read_binary' (command ),

SND -> 01 a0 00 05 00 b0 00 00 00
RCV <- 01 00 00 02 6d 00
I get a <6D><00> error, which is ?? unexpected.

Time to read some doc again.

However, let's try to select all files (by number?) from 0000 to FFFF. Allmost all attempts (on the FNAC card) return 6A:82 (ISO: File not found) except a few which return 61:0C (? more data available (0xC bytes) with a get_response?) ...

sending '01 a0 00 08 00 A4 00 00 02 xx yy 10' (select file 'xxyy')

00 01| : : 01 00 00 02 6a 82
(a couple of 6a:82)
00 13| : : 01 00 00 02 61 0c
00 14| : : 01 00 00 02 61 0c
00 15| : : 01 00 00 02 61 0c
(a couple of 6a:82)
00 1a| : : 01 00 00 02 61 0c
(a lot of 6a:82)
3f 00| : : 01 00 00 02 61 0c
(a lot of 6a:82)
ff ff| : : 01 00 00 02 6a 82

If I do a get_response (with the right length (0x0c)), I get :

snd-> 01 a0 00 05 00 C0 00 00 0c (get_response() with length=0x0c)

MCU_get_response: : 01 00 00 0e 6f 0a c5 08 00 13 04 00 01 0f ff ff 90 00 (file 0013)
MCU_get_response: : 01 00 00 0e 6f 0a c5 08 00 14 04 00 02 ff f4 ff 90 00 (file 0014)
MCU_get_response: : 01 00 00 0e 6f 0a c5 08 00 15 04 00 02 ff f4 ff 90 00 (file 0015)
MCU_get_response: : 01 00 00 0e 6f 0a c5 08 00 1a 04 00 03 08 f4 ff 90 00 (file 001a)

(I hope there is nothing I should keep secret here...) After a get_response (90:00 seems good news), a read_binary returns 6D:00 (?)

01 mars 2007

The belgian Id card

The trouble with smart cards is that they are all different. There exist a standard ISO7816-4 for the protocol to use with smartcards but there are so many options that it is illusory to expect dumping a card content without previous knowlege of the application running on the smartcard.

The belgian Id card is well documented...

The protocol is described in eik_bestek_bijlage5.doc and the data content, in belgian_electronic_identity_card_content_v2.8.a.pdf. These documents are available, but not specially easy to read... Fortunately we just need a couple of commands to read the content of the card (file_select and read_binary).

Sources of programs interacting with the card are available too. It helps. But, it should be more easy to find the information. In fact, (aside pks#15 cryptography) there are just a couple of files of interest on the card : the card holder identity file (names, birth place/date,...), his address is in another file and a (jpeg) picture in a third file.

You could not find the file #id just by scanning all files (from 0000 to ffff) in the root directory because these files are 'hidden' in a #df01 directory. So, to select the files, you have to select 'df01/<some-file-id>'. Of course, it is not 1-2-3 (it would be to simple?), it is '0x4031' (identity), '0x4033' (address) and '0x4035' for the picture...

To select those files, you have to use the APDU :

00 A4 08 0c 04 df 01 40 xx Lc
00 : ISO CLAss (~standard ISO APDU)
A4 : ISO file_select INStruction
08 0c : P1 P2 for this operation (otherwise you get an error...)
04 : length of the 'path'
df 01 : directory for Id data
40 xx : the files id
31 : owner's identity (names, birth,...)
33 : owner's address
35 : owner's picture
Lc is the expected answer length (?)

Once the file is successfully selected (SW1:SW2 == 90:00), one can 'binary_read' the content with the APDU

00 b0 xx yy zz
00 : ISO CLAss (~standard ISO APDU)
b0 : ISO read_binary INStruction
xx yy : offset in the file (256*xx)+yy
zz : number of bytes to read

Once you know these very simple facts, it is very easy to get (and dump) the card content. I don't know why this simple information is so hard to find in the documentation or in the code...

The first two files contains a couple of fields, the last one, just a standard JPEG file of about 4 kilobytes. The fields are coded :

<tag> <length> <data> <tag>...
tag is one byte 00..ff
length is one byte if < 255,
ff followed by length-255
or ff ff followed by length-510 (to be confirmed)
data can be ASCII/UTF-8/binary depending of the tag
(not necessarily null terminated strings)