Some Linux experiments with the ACR38 smartcards reader

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

Nom :

25 février 2007

The program

This program only needs 'libusb-dev'...
The Makefile can look like this

ci2html: ci2html.c
gcc -o ci2html ci2html.c -lusb

Maybe one needs to 'get the device' for the program to use, by (under Ubuntu)

$ sudo /etc/init.d/pcscd stop
$ sudo chmod 0666 /dev/bus/usb/003/002

And now, the 300+ lines program...

#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <time.h>
#include <usb.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
/*
** (c) xof, may 2007
** This program 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.
**
** This program 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.
**
*/
fxdump(FILE *fp, char *txt, u_char *ucp, int len)
{
fprintf(fp, "%s : ", txt);
while (len-- > 0)
fprintf(fp, "%02x ", *ucp++);
fprintf(fp, "\n");
}
int reader_status; /* 0 == OK; card reader error code */
int card_status; /* 0x9000 == OK; card command status */
eid_error(char *txt)
{
perror(txt);
fprintf(stderr, "reader_status : 0x%02x\n", reader_status);
fprintf(stderr, "card_status : 0x%04x\n", card_status);
return(-1);
}
/*----------- ACR38 Reader handling -----------*/
#define TIMEOUT 20*100 /*HZ*/
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, 0x02 /* EP_OUT */, (char *)outp, out_len, TIMEOUT) < 0)
return(eid_error("usb_bulk_write"));
if (usb_bulk_read(udp, 0x82 /* EP_IN */, (char *)inp, in_len, TIMEOUT) < 0)
return(eid_error("usb_bulk_read"));
if (reader_status = inp[1])
return(eid_error("ACR_xchange"));
return(0);
}
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)
return(eid_error("ACR_card_present"));
// fxdump(stderr, "GET_ACR_STAT:", buf_in, 20);
return(buf_in[19] & 1); /* card present */
}
/*----------- Card handling -----------*/
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];

if (ACR_xchange(udp, MCU_EMV, 5, buf_in, sizeof(buf_in)) < 0)
return(eid_error("MCU_EMV"));
if (ACR_xchange(udp, MCU_TYPE, 5, buf_in, sizeof(buf_in)) < 0)
return(eid_error("MCU_TYPE"));
if (ACR_xchange(udp, MCU_POWER, 4, buf_in, sizeof(buf_in)) < 0)
return(eid_error("MCU_POWER"));
fxdump(stderr, "AnswerToReset:", &buf_in[4], buf_in[3]);
return(0);
}
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)
return(eid_error("MCU_POWER_DOWN"));
return(0);
}
/*----------- Accessing the card files -----------*/
/* the layers reader/card should be separated... */
int MCU_select_belpicfile_by_num(struct usb_dev_handle *udp, int dnum, int fnum)
{
const u_char MCU_FILE_SELECT[] = {0x01, 0xa0, 0x00, 0x0a, /* <- reader header for APDUs */
/* ISO file_select -> */ 0x00, 0xA4, 0x08, 0x0c, 0x04, 0xdf, 0x01, 0x00, 0x00, 0x10};
u_char buf_in[128];
u_char cmd[32];

memcpy(cmd, MCU_FILE_SELECT, sizeof(cmd));
cmd[ 9] = (dnum >> 8) & 0xff; /* directory */
cmd[10] = (dnum ) & 0xff;
cmd[11] = (fnum >> 8) & 0xff; /* file */
cmd[12] = (fnum ) & 0xff;
if (ACR_xchange(udp, cmd, 14, buf_in, sizeof(buf_in)) < 0)
return(eid_error("MCU_file_select"));
// fxdump(stderr, "MCU_file_select:", buf_in, 4+buf_in[3]);
card_status = (buf_in[4+buf_in[3]-2]*256) + buf_in[4+buf_in[3]-1];
if (card_status != 0x9000)
return(eid_error("MCU_select_belpicfile_by_num"));
return(0);
}
int MCU_read(struct usb_dev_handle *udp, int address, u_char *buf, int len)
{ /* length should be in the range 1..250 */
u_char MCU_READ[] = {0x01, 0xa0, 0x00, 0x05, /* <- reader header for APDUs */
/* ISO read_binary -> */ 0x00, 0xb0, 0x00, 0x00, 0xf0};
u_char buf_in[512];
u_char cmd[9];

memcpy(cmd, MCU_READ, sizeof(MCU_READ));
cmd[6] = (address >> 8) & 0xff; /* file offset */
cmd[7] = (address ) & 0xff;
cmd[8] = len; /* number of bytes to read */

if (ACR_xchange(udp, cmd, 9, buf_in, sizeof(buf_in)) < 0)
return(eid_error("MCU_read"));
if (buf_in[3]==2 && buf_in[4] == 0x6c) /* incomplete read; asking too much */
{
cmd[8] = buf_in[5]; /* number of available bytes */
if (ACR_xchange(udp, cmd, 9, buf_in, sizeof(buf_in)) < 0)
return(eid_error("MCU_read2"));
}
// fxdump(stderr, "MCU_READ:", buf_in, 4+buf_in[3]);
card_status = (buf_in[4+buf_in[3]-2]*256) + buf_in[4+buf_in[3]-1];
if (card_status != 0x9000 && card_status != 0x6b00)
return(eid_error("MCU_read"));
memcpy(buf, &buf_in[4], buf_in[3]-2);
return(buf_in[3]-2); /* number of bytes read */
}
/*----------- Printing the result in an .html file -----------*/
char *id_tags[] = {
/*0*/ NULL, /* file structure version */
/*1*/ "Card number",
/*2*/ NULL, /* chip number */
/*3*/ "Card validity from",
/*4*/ "Card validity until",
/*5*/ "Card delivery municipality",
/*6*/ "National Number",
/*7*/ "Name",
/*8*/ "Given names",
/*9*/ "(given 3)",
/*10*/ "Nationality",
/*11*/ "Birth Location",
/*12*/ "Birth date",
/*13*/ "Sex",
/*14*/ "Noble condition",
/*15*/ "Document type",
/*16*/ "Special status",
/*17*/ NULL /* photo hash */
};
char *ad_tags[] = {
/*0*/ NULL, /* file structure version */
/*1*/ "Street + Nr",
/*2*/ "ZIP code",
/*3*/ "Municipality"
};
char *nn_fname(u_char *idp, char *suffix)
{
static char fname[20]; /* yymmddaaabb.html */

while (*idp++ != 6) /* we are looking for tag6 */
idp += *idp++; /* skip this one */
if (*idp++ != 11) /* NN length should be 11 */
return("noname");
memcpy(fname, idp, 11);
strcpy(&fname[11], suffix);
return(fname);
}
#define nelem(p) (sizeof(p)/sizeof(p[0]))
html_dump(char *fname, u_char *idp, u_char *adp)
{
FILE *fp;
int i;
u_char *ucp;

if ((fp = fopen(fname, "w+")) == NULL)
return(eid_error(fname));
fprintf(fp, "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">");
fprintf(fp, "<html>\n<head>\n");
fprintf(fp, " <meta content=\"text/html; charset=UTF-8\" http-equiv=\"content-type\">\n");
fprintf(fp, " <title>Belgian Electronic Id Card (%s)</title>\n", fname);
fprintf(fp, "</head><body style=\"background-color:rgb(140,208,165)\">\n");
for (ucp = idp, i = 0; i < nelem(id_tags); i++)
{
if (id_tags[*ucp])
{
fprintf(fp, "<br><b>%s : </b>", id_tags[*ucp]);
fwrite(ucp+2, ucp[1], 1, fp);
}
else fxdump(fp, "<br>binary data : ", ucp+2, ucp[1]);
ucp += ucp[1]+2; /* skip */
}
fprintf(fp, "<hr>");
for (ucp = adp, i = 0; i < nelem(ad_tags); i++)
{
if (ad_tags[*ucp])
{
fprintf(fp, "<br><b>%-20s</b>", ad_tags[*ucp]);
fwrite(ucp+2, ucp[1], 1, fp);
}
ucp += ucp[1]+2; /* skip */
}
fprintf(fp, "<hr><center><img src=\"%s\">\n", nn_fname(idp, ".jpg"));
fprintf(fp, "</body>\n</html>\n");
if (fclose(fp) != 0)
return(eid_error(fname));
return(0);
}
jpeg_dump(char *fname, u_char *ucp, int size)
{
FILE *fp;

if ((fp = fopen(fname, "w+")) == NULL)
return(eid_error(fname));
if (fwrite(ucp, size, 1, fp) != 1)
return(eid_error(fname));
if (fclose(fp) != 0)
return(eid_error(fname));
}
/*----------- Doing the job -----------*/
int BEID_dump(struct usb_dev_handle *udp)
{
int n, ofs = 0;
u_char buf_id[1024], buf_ad[256];
u_char buf_pic[5120];

/*
** get Identity file
*/
if (MCU_select_belpicfile_by_num(udp, 0xdf01, 0x4031) < 0) /* Id data */
return(eid_error("selecting Id file"));
for (ofs = 0; (n = MCU_read(udp, ofs, &buf_id[ofs], 0xf0)) > 0; ofs += n)
continue; /* partial read == EOF */
if (n < 0 && card_status != 0x6b00)
return(eid_error("reading Id file"));
/*
** get Address file
*/
if (MCU_select_belpicfile_by_num(udp, 0xdf01, 0x4033) < 0) /* Address */
return(eid_error("selecting Address file"));
if (MCU_read(udp, 0, buf_ad, 0xf0) < 0) /* enough : file length is 0x75 */
return(eid_error("reading Address file"));
html_dump(nn_fname(buf_id, ".html"), buf_id, buf_ad);
/*
** get the photo
*/
if (MCU_select_belpicfile_by_num(udp, 0xdf01, 0x4035) < 0) /* Photo */
return(eid_error("selecting Photo file"));
for (ofs = 0; (n = MCU_read(udp, ofs, &buf_pic[ofs], 0xf0)) > 0; ofs += n)
continue;
if (n < 0 && card_status != 0x6b00)
return(eid_error("reading Photo file"));

jpeg_dump(nn_fname(buf_id, ".jpg"), buf_pic, ofs);
return(0);
}
//----------------------------------------------------------------
usage()
{
fprintf(stderr, "usage is 'ci2html [options]\n");
fprintf(stderr, "\tno option (yet)\n\n");
exit(-1);
}
main(int argc, char **argv)
{
struct usb_bus *busses, *bus;
struct usb_device *dev;
usb_dev_handle *udp;

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 */
break;
}
if (dev)
break;
}
if (dev == NULL)
{
fprintf(stderr, "No ACR38-card-Reader on this system!\n");
exit(-1);
}
if ((udp = usb_open(dev)) == NULL)
{
perror("usb_open");
exit(-1);
}
for (;;)
{
fprintf(stderr, "\n\n\nInsert a belgian identity card, please.\n(<ctrl>-C to exit)\n");
while (ACR_card_present(udp) != 1)
sleep(1); /* wait for card insertion */
MCU_powerup(udp);
fprintf(stderr, "Reading the card...\n");
if (BEID_dump(udp) < 0)
fprintf(stderr, "?Not a beId card?\n");
else fprintf(stderr, "*** .html file created. ***\n");
MCU_powerdown(udp);
fprintf(stderr, "Done.\n\n\nRemove the card, please.\n");
while (ACR_card_present(udp) != 0)
sleep(1); /* wait for card extraction */
}
if (usb_close(udp) < 0)
{
perror("usb_close");
exit(-1);
}
exit(0);
}



Result
The result is an .html file with a photo. Something like




Trouble shooting
* "Operation not permitted" (you can't write on the device)

$ lsusb
...
$ sudo chmod 0666 /dev/bus/usb/003/002
(with the right bus/device)

* "Device or resource busy" (the device is used by somethingelse)

$ sudo /etc/init.d/pcscd stop

* "Resource temporarily unavailable" (reader slow to catch on // bad card)
solves itself after a couple of retry // bad card

* ^C to exit

3 Comments:

Blogger Paco Rivière said...

Ce commentaire a été supprimé par l'auteur.

7/12/2009 09:52:00 AM  
Blogger Paco Rivière said...

Compiles perfectly, but dont want to execute!

user$system:~$ sudo apt-get install libusb-dev
[sudo] password for user:
...
Processing 1 added doc-base file(s)...
Registering documents with scrollkeeper...
S'estan processant els gallets per a man-db ...
S'està configurant libusb-dev (2:0.1.12-13) ...
(success!)
user$system:~$ gcc -o ci2html ci2html.c -lusb
user$system:~$ ci12html
bash: ci12html: command not found
user$system:~$ ls ci*
ci2html ci2html.c
user$system:~$ sudo /etc/init.d/pcscd stop
user$system:~$ sudo chmod 0666 /dev/bus/usb/003/002
user$system:~$ ci2html
bash: ci2html: command not found
user$system:~$ lsusb
...
Bus 004 Device 002: ID 072f:9000 Advanced Card Systems, Ltd ACR38 AC1038-based Smart Card Reader
...
user$system:~$ sudo chmod 0666 /dev/bus/usb/004/002
user$system:~$ ci2html
bash: ci2html: command not found

7/12/2009 09:54:00 AM  
Blogger xofc said...

maybe
$ ./ci2html
(if the curent directory is not in your $PATH)

Otherwise, check it is executable with 'ls -l ci2html' and/or 'file ci2html'.

-- xof

7/12/2009 10:57:00 AM  

Enregistrer un commentaire

<< Home