La prima parte del codice che segue serve solo per aprire la comunicazione tra il lettore di smart card (con card già inserita).
from smartcard.System import readers
import array
r = readers()
reader = r[0]
print "Sto usando: ", reader
connection = reader.createConnection()
connection.connect()
Il codice è solo a scopo dimostrativo. Non fa alcun test sull'esito della connessione e si collega al primo reader che dovesse trovare.
Se tutto è andato bene fino a qui, possiamo proseguire. Ora bisogna aprire il file contenente le informazioni anagrafiche. Per farlo sarà necessario inviare un comando APDU (Application Protocol Data Unit).
Quello che segue è la teoria semplificata dell'accesso alla lettura delle smart card. Per lo scopo dell'articolo, si può allegramente saltare.
I comandi APDU sono formati da 7 campi:
-CLA: Classe dell'istruzione
-INS: Istruzione
-P1 e P2: parametri 1 e 2
-Lc: Dimensione dei dati del comando o Lenght of the Command data
-Data: I dati del comando
-Le: Dimensione attesa della risposta o Length of the Expected response
La classe di istruzioni che ci interessa è la 0 (o 0x00 in esadecimale).
Le istruzioni che ci serviranno sono la 0xA4 (selezionare un file) e la 0xB0 (leggere il contenuto di un file.
Il filesystem della smartcard è del tutto simile a quello del computer.
Prevede un MF o Master Folder o radice del filesystem. All'interno dell'MF si trovano sia EF o Elementary File o file semplici sia DF o Directory File o sotto cartelle.
Il file che ci interessa si trova al seguente path:
MF/DF1/EF_Dati_personali
Cioè, la cartella DF1, che sta all'interno della radice, ospita il file EF_Dati_personali che contiene le nostre informazioni.
Cartelle e file hanno un ID ben definito, univoco e uguale in tutte le TSN del SSN.
Per l'MF, l'id è 0x3F00, per DF1 è 0x1100 e per il file EF_Dati_personali è 0x1102.
Probabilmente esiste un sistema per accedere al filesystem in modo semplice come nei normali filesysem ma, fino a che non lo trovo, l'unico modo che sono riuscito ad usare è stato quello di spostarmi passo passo
con gli id.
Ecco come procedere (qui i pigri possono ricominciare a leggere):
#Seleziona del MF
#CLS 00, istruzione A4 (seleziona file), P1 = P2 = 0 (seleziona per ID),
#Lc: 2, Data: 3F00 (id del MF)
SELECT_MF = [0x00, 0xA4, 0x00, 0x00, 0x02, 0x3F, 0x00]
data, sw1, sw2 = connection.transmit(SELECT_MF)
#se tutto è andato a buon fine sw1 e sw2 contengono
#rispettivamente i valori 0x90 e 0x00 il corrispettivo del 200 in HTTP
#Seleziona del DF1...vedi sopra
SELECT_DF1 = [0x00, 0xA4, 0x00, 0x00, 0x02, 0x11, 0x00]
data, sw1, sw2 = connection.transmit(SELECT_DF1)
#Seleziona del file EF.Dati_personali... vedi sopra sopra
SELECT_EF_PERS = [0x00, 0xA4, 0x00, 0x00, 0x02, 0x11, 0x02]
data, sw1, sw2 = connection.transmit(SELECT_EF_PERS)
#leggiamo i dati
#CLS 00, istruzione B0 (leggi i dati binari contenuti nel file
READ_BIN = [0x00, 0xB0, 0x00, 0x00, 0x00, 0x00]
data, sw1, sw2 = connection.transmit(READ_BIN)
#data contiene i dati anagrafici in formato binario
#trasformiamo il tutto in una stringa
stringa_dati_personali = array.array('B', data).tostring()
La stringa restituita è un mix di esadecimali e testo ascii. Eccone un esempio:
000063080460020803042012080104201804SABA04LUCA08010197001M00....
I primi 6 caratteri contengono la dimensione in numero di byte del dato espresso in esamedicmale.
Quindi:
dimensione = int(stringa_dati_personali[0:6],16)
print "Dimensione in byte dei dati: %d" % dimensione
scriverà la dimensione del dato (99 byte in questo esampio: 0x63=99).
Il resto del testo è da leggersi nel seguente modo:
i primi due caratteri esprimono la dimensione del campo sucessivo (sempre
in numero di byte in esadecimale). Se il dato non c'è, come nel caso dell'altezza,
la dimensione è 0.
I campi estraibili dal file dei dati personale sono i seguenti:
- Codice Emettitore
- Data di emissione del documento Formato GGMMAAAA
- Data di scadenza del documento Formato GGMMAAAA
- Cognome (Max 26 caratteri)
- Nome (Max 26 caratteri)
- Data di Nascita Formato GGMMAAAA
- Sesso 'M' per maschio, 'F' per femmina
- Statura (cm)
- Codice fiscale
- Cittadinanza
- Comune di Nascita
- Stato estero di Nascita
- Estremi atto di nascita
- Comune di residenza al momento dell’emissione
- Indirizzo di residenza
- Eventuale annotazione in caso di non validità del documento per l’espatrio
Perciò, ecco il codice (parziale) per leggere il resto dei dati:
prox_field_size = int(stringa_dati_personali[6:8], 16)
da = 8
a = da + prox_field_size
if prox_field_size > 0:
codice_emettitore = stringa_dati_personali[da:a]
print "Codice emettitore: ", codice_emettitore
da = a
a +=2
prox_field_size = int(stringa_dati_personali[da:a], 16)
da=a
a += prox_field_size
if prox_field_size > 0:
data_rilascio_tessera = stringa_dati_personali[da:a]
print "Data rilascio tessera: ", data_rilascio_tessera[0:2]+"/"+data_rilascio_tessera[2:4]+"/"+data_rilascio_tessera[-4:]
da = a
a +=2
prox_field_size = int(stringa_dati_personali[da:a], 16)
da=a
a += prox_field_size
if prox_field_size > 0:
data_scadenza_tessera = stringa_dati_personali[da:a]
print "Data scadenza tessera: ", data_scadenza_tessera[0:2]+"/"+data_scadenza_tessera[2:4]+"/"+data_scadenza_tessera[-4:]
da = a
a +=2
prox_field_size = int(stringa_dati_personali[da:a], 16)
da=a
a += prox_field_size
if prox_field_size > 0:
cognome = stringa_dati_personali[da:a]
print "Cognome: ", cognome
Riferimenti: