Titre: Programmation d'un lecteur de carte d'identité belge en Delphi, l'ACR38U (14/03/2006 Par zion)
Introduction
Voici un mois, nous avons testé le lecteur ACR38U en tant qu'utilisateur, voyons maintenant ce que nous pouvons en tirer en tant que développeurs. Le but de cet article sera donc d'essayer de lire au moins le nom en provenance de la carte d'identité. Paris tenu!

images/articles/article490/001.jpg

Pour l'installation et l'utilisation du lecteur, je vous conseille de lire le test de l'ACR38U pour vous donner une première idée du produit, on rentre ici dans le vif du sujet, la programmation.

Première tentative, avec un composant utilisant la librairie SmartCard de Microsoft, après plus d'une heure de tentatives en tout genre, le seul résultat positif était de savoir détecter l'insertion d'une carte dans le lecteur... Sympathique, mais pas très utile si on ne sait rien en lire.

A la recherche donc d'une solution plus simple, je retourne finalement sur le site officiel de notre gouvernement pour voir ce qu'ils en pensent. Et oh, joie, oh bonheur, un PDF pour les développeurs avec les explications sur leur kit indispensable pour pouvoir utiliser le lecteur.

Après plusieurs minutes de lecture, voici que le PDF explique que nos chers amis proposent un ActiveX qui permet d'accéder aux informations sur la carte d'identité... Que demander de plus, je me lance à la tâche.

images/articles/article490/002.png
On code!
Importons donc la description de cet ActiveX dans Delphi, pour cela rien de plus simple, Component/Import ActiveX Control, et il faut chercher la librairie EIDLibCtrl.

images/articles/article490/003.png

Une fois importée, nous avons une unité avec toutes les fonctions nécessaires pour accéder à la carte d'identité, y compris le support du lecteur SmartCard. Cela semble donc un jeu d'enfant.

  1.   TEIDlib = class(TOleControl) 
  2.   private 
  3.     FIntf: IEIDlib; 
  4.     function  GetControlInterface: IEIDlib; 
  5.   protected 
  6.     procedure CreateControl; 
  7.     procedure InitControlData; override; 
  8.   public 
  9.     function Init(const strReaderName: WideString; lOCSP: Integer; lCRL: Integer;  
  10.                   out plHandle: Integer): IRetStatus; 
  11.     function Exit: IRetStatus; 
  12.     function GetID(out ppMapCollection: IMapCollection; out ppCertifCheck: ICertifCheck): IRetStatus; 
  13.     function GetAddress(out ppMapCollection: IMapCollection; out ppCertifCheck: ICertifCheck): IRetStatus; 
  14.     function GetPicture(out ppMapCollection: IMapCollection; out ppCertifCheck: ICertifCheck): IRetStatus; 
  15.     function GetVersionInfo(bSignature: Integer; out ppMapCollection: IMapCollection;  
  16.                             out pvtSignature: OleVariant): IRetStatus; 
  17.     function BeginTransaction: IRetStatus; 
  18.     function EndTransaction: IRetStatus; 
  19.     function FlushCache: IRetStatus; 
  20.     function SelectApplication(vtApplication: OleVariant): IRetStatus; 
  21.     function SendAPDU(vtCommand: OleVariant; const pPIN: IPin; out pvtResponse: OleVariant): IRetStatus; 
  22.     function VerifyPIN(const pPIN: IPin; const bstrPin: WideString; out plTriesLeft: Integer): IRetStatus; 
  23.     function ChangePin(const pPIN: IPin; const strOldPin: WideString; const strNewPin: WideString;  
  24.                        out plTriesLeft: Integer): IRetStatus; 
  25.     function GetPinStatus(const pPIN: IPin; bSignature: Integer; out pvtSignature: OleVariant;  
  26.                           out plTriesLeft: Integer): IRetStatus; 
  27.     function ReadFile(const pPIN: IPin; vtFileId: OleVariant; out pvtData: OleVariant): IRetStatus; 
  28.     function WriteFile(const pPIN: IPin; vtFileId: OleVariant; vtData: OleVariant): IRetStatus; 
  29.     function SetRawData(const pRaw: IRaw): IRetStatus; 
  30.     function GetRawData(out ppRaw: IRaw): IRetStatus; 
  31.     property  ControlInterface: IEIDlib read GetControlInterface; 
  32.     property  DefaultInterface: IEIDlib read GetControlInterface; 
  33.   published 
  34.     property Anchors; 
  35.   end;


La documentation étant assez complète, on nous explique qu'il suffit de faire un Init, puis un GetID pour avoir les informations contenues sur la carte. Qu'il en soit donc ainsi:

  1. procedure TForm1.FormCreate(Sender: TObject); 
  2. var 
  3.  lHandler: Integer; 
  4. begin 
  5.   FEID := TEIDlib.Create(self); 
  6.   FEID.Init(''00, lHandler); 
  7. end
  8. procedure TForm1.FormDestroy(Sender: TObject); 
  9. begin 
  10.   FEID.Exit; 
  11.   FreeAndNil(FEID); 
  12. end;


Pour pouvoir compiler facilement le projet sans devoir installer le composant, le TEIDLib est créé à la volée, rien de très complexe en soit. Vu qu'un Init doit être terminé par un Exit, il suffit de rajouter un Exit dans le Destroy du form et le tour est joué.
On code (suite)
Passons maintenant à la lecture des informations. Le GetID nous renvoie une interface que l'on peut ensuite interroger pour récupérer une chaîne de caractère. Pas question de faire le tour de tout ce qui existe, on doit connaître le nom de chaque variable. Pas très pratique, heureusement que la documentation vient à notre secours:

images/articles/article490/004.png

Voici le code correspondant en Delphi avec une toute petite gestion d'erreur en cas de non présence de la carte. Malheureusement, il semble impossible avec cet ActiveX de détecter l'arrivée du carte dans le lecteur, peut être dans le futur?

  1. procedure TForm1.Button1Click(Sender: TObject); 
  2. var 
  3.  lMap: IMapCollection; 
  4.  lCert: ICertifCheck; 
  5.  lPicture: Pointer; 
  6.  lStream: TMemoryStream; 
  7.  lPictureVar: OleVariant; 
  8.  lJPEG: TJPEGImage; 
  9. begin 
  10.   FEID.GetID(lMap, lCert); 
  11.   if lMap = nil then 
  12.     ShowMessage('Impossible de lire la carte'
  13.   else 
  14.   begin 
  15.     self.Memo1.Lines.Add('Nom: '+lMap.GetValue('Name')); 
  16.     self.Memo1.Lines.Add('Nationalité: '+lMap.GetValue('Nationality')); 
  17.     self.Memo1.Lines.Add('Date de naissance: '+lMap.GetValue('BirthDate')); 
  18.   end
  19. end;


Vous ne rêvez pas, c'est aussi simple que cela. Pris dans le feu de l'action, voyons voir si on peut également récupérer la photo sans difficulté.

  1.   FEID.GetPicture(lMap, lCert); 
  2.   lPictureVar := lMap.GetValue('Picture'); 
  3.   lPicture := VarArrayLock(lPictureVar); 
  4.   lStream := TMemoryStream.Create; 
  5.   try 
  6.     lStream.Write(lPicture^, VarArrayHighBound(lPictureVar, 1) - VarArrayLowBound(lPictureVar, 1) + 1); 
  7.     VarArrayUnlock(lPictureVar); 
  8.     lStream.Position := 0
  9.     lJPEG := TJPEGImage.Create; 
  10.     try 
  11.       lJPEG.LoadFromStream(lStream); 
  12.       Image1.Picture.Assign(lJPEG); 
  13.     finally 
  14.       FreeAndNil(lJPEG); 
  15.     end
  16.   finally 
  17.     FreeAndNil(lStream); 
  18.   end;


Un rien plus long pour la récupération de l'image mais cela fonctionne, c'est le principal. Il faudra un petit peu bidouiller pour pouvoir récupérer les informations à partir de l'OleVariant, mais tout est dans le code.
Et l'application?
Lors de l'appel de la fonction GetID, une fenêtre s'ouvre, indépendemment de nous, pour demander l'autorisation à l'utilisateur d'accéder à la carte d'identité. Un simple clic sur le bouton de droite nous permettra de ne plus nous en soucier.

images/articles/article490/005.png

Pareil pour la photo, il faut également autoriser l'application à accéder à la photo sur la carte d'identité...

images/articles/article490/006.png

Et voici le résultat sur l'application, la photo est présente et le memo contient bien le nom, la nationalité et la date de naissance. Défi relevé!

images/articles/article490/007.png
Retour