jeudi 21 juillet 2016

Discuter avec une carte à puce en langage C

Dans un article précédent nous avons utilisé gscriptor pour envoyer un APDU à une carte à puce. Voyons donc maintenant comment faire exactement la même chose mais en langage C.

En effet, gscriptor c'est bien beau, mais si l'on veut créer une application à nous à base de cartes à puce, on aura très certainement besoin que notre application puisse discuter avec la carte.

Le lecteur de carte à puce est branché sur le port USB, et le port USB c'est coton à programmer en direct. La bonne nouvelle c'est qu'on va utiliser l'API PCSC qui va nous simplifier la vie puisqu'elle nous cache toute cette gestion de l'USB.

La mauvaise nouvelle c'est que, comme à chaque fois avec ce genre d'API, le code à écrire est absolument imbitable ! Désolé mais il n'y a pas d'autre mot :)

En fait, c'est peu important. L'important c'est de chopper une routine qui marche bien et de la reprendre dans son programme quand on en a besoin. Et la routine qui marche, la voici. Je peux vous dire que j'ai eu du mal à la faire marcher mais au final elle marche tip top.

Enregistrez ça dans un fichier discut.c

#include <stdio.h>
#include <stdlib.h>
#include <PCSC/winscard.h>

#define CHECK(f, rv) \
 if (SCARD_S_SUCCESS != rv) \
 { \
  printf(f ": %s\n", pcsc_stringify_error(rv)); \
  return -1; \
 }

int main()
{
int i;
LONG rv;
SCARDCONTEXT hContext;
SCARDHANDLE hCard;
DWORD dwActiveProtocol,dwRecvLength,dwReaders;
LPTSTR mszReaders;
SCARD_IO_REQUEST pioSendPci;
BYTE pbRecvBuffer[258];
DWORD dwState, dwProtocol, dwAtrLen, dwReaderLen;
BYTE *pbAtr = NULL;
char *pcReader = NULL;
BYTE cmd1[] = {00,0x84,00,00,0x08};

rv=SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, &hContext);
CHECK("SCardEstablishContext", rv)
rv=SCardListReaders(hContext, NULL, NULL, &dwReaders);
CHECK("SCardListReaders", rv)
mszReaders=calloc(dwReaders, sizeof(char));
rv=SCardListReaders(hContext, NULL, mszReaders, &dwReaders);
printf("Reader name: %s\n", mszReaders);
rv = SCardConnect(hContext, mszReaders, SCARD_SHARE_SHARED,SCARD_PROTOCOL_T0, &hCard, &dwActiveProtocol);
CHECK("SCardConnect", rv)
dwReaderLen = SCARD_AUTOALLOCATE;
dwAtrLen = SCARD_AUTOALLOCATE;
rv = SCardStatus(hCard, (LPSTR)&pcReader, &dwReaderLen, &dwState,    &dwProtocol, (LPBYTE)&pbAtr, &dwAtrLen);
CHECK("SCardStatus", rv)
printf("ATR : ");
for(i=0;i<dwAtrLen;i++) printf("%x ",pbAtr[i]);
printf("\n");
pioSendPci = *SCARD_PCI_T0;
dwRecvLength = sizeof(pbRecvBuffer);
rv = SCardTransmit(hCard, &pioSendPci, cmd1, sizeof(cmd1), NULL, pbRecvBuffer, &dwRecvLength);
CHECK("SCardTransmit", rv)
printf("response: ");
for(i=0; i<dwRecvLength; i++)
printf("%02X ", pbRecvBuffer[i]);
printf("\n");
}
Et compilez le sous Linux en tapant la commande :
gcc discut.c -o discut -I /usr/include/PCSC -lpcsclite

Cela crée l'exécutable discut qui fait exactement ce qu'on a fait avec gscriptor, à savoir envoyer à la carte l'ADPU 00 84 00 00 08 et affiche en retour les 8 octets aléatoires renvoyés par la carte (+ le 90 00 final).

Voyons ce que cela donne à l'exécution :

D'abord, je lance le programme sans qu'aucun lecteur ne soit connecté au port USB :
./discut
SCardListReaders: Cannot find a smart card reader.

Ensuite, je relance le programme, avec un lecteur de carte connecté mais aucune carte dans le lecteur :
./discut
Reader name: Gemalto PC Twin Reader (73131FC4) 00 00
SCardConnect: No smart card inserted.


Et pour finir, je relance le programme en ayant au préalable branché le lecteur sur le port USB avec ma carte bancaire EMV dedans :
./discut
Reader name: Gemalto PC Twin Reader (73131FC4) 00 00
ATR : 3b 65 0 0 20 63 cb 6a 0
response: E4 5E 36 A7 9D B9 B8 DF 90 00


./discut
Reader name: Gemalto PC Twin Reader (73131FC4) 00 00
ATR : 3b 65 0 0 20 63 cb 6a 0
response: 23 31 9F 6D AE BD 1C 55 90 00


Conclusion : le programme est parfaitement fonctionnel, et réutilisable dans une application. On constate également que l'ATR lu est bien le bon.

Nous voyons, pour faire simple et sans rentrer dans les détails, qu'il faut successivement appeler les fonctions :

SCardEstablishContext =>pour initialiser le contexte
SCardListReaders => pour lire la liste des lecteurs de cartes détectés par la couche PCSC
SCardConnect => pour se connecter à la carte
SCardStatus => pour lire le statut de la carte (et entre autres son ATR)
SCardTransmit => pour envoyer dans la carte un APDU et lire la réponse à l'APDU

Et à chaque fois avec un passage de paramètres pas piqué des vers. Pourquoi faire simple quand on peut faire compliqué.

Bon, la bonne nouvelle, c'est qu'au final, en quelques lignes de C, on a quasiment ré-écrit gscriptor (à l'interface graphique près).

Publié le 21 juillet 2016 par Alan Cartman

Aucun commentaire: