Tutoriel sur la programmation des cartes à puce en Java Card

Zeineb Medimagh

Ce tutoriel aborde la programmation des cartes à puce en langage Java Card. Il introduit la programmation de l'application serveur ainsi que l'application client. Une partie de simulation (JCWDE/CREF) est développée pour chaque partie.

Cet article est inspiré de celui rédigé en 2008 Introduction à la programmation de Javacard sous Windows

Pour réagir au contenu de cet article, un espace de dialogue vous est proposé sur le forum Commentez .

- Titre : Tutoriel sur la programmation des cartes à puce en Java Card

- Auteur : Zeineb Medimagh

- Parution : 12 mars 2015

- Licence : Copyright ® 2015 Zeineb Medimagh. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Présentation de Java Card

- I-A. Introduction des cartes à puce

- I-A-1. Exemples d'utilisations

- I-A-2. Types de cartes

- I-A-3. Architecture d'une Smart Card avec contact

- I-B. C'est quoi Java Card ?

- I-B-1. Hardware

- I-B-2. Software

- I-B-3. Architecture de Java Card

- I-B-4. Framework Java Card

- I-B-5. Cycle de vie d'une applet

- II. Préparation de l'environnement

- II-A. Installation de l'éditeur

- II-A-1. Téléchargement

- II-A-2. Manipulation

- II-B. Installation de JDK

- II-B-1. Téléchargement

- II-B-2. Manipulation

- II-C. Installation de JCDK

- II-C-1. Téléchargement

- II-C-2. Manipulation

- II-D. Ajout des plugins Eclipse-JCDE

- II-D-1. Téléchargement

- II-D-2. Manipulation

- III. Application serveur

- III-A. Langage Java Card

- III-A-1. AID

- III-A-2. APDU

- III-B. Application serveur

- III-B-1. Création de l'applet

- III-B-2. Code

- III-C. Simulation

- III-C-1. JCWDE

- III-C-2. CREF

- IV. Application client

- IV-A. Programmation

- IV-A-1. Création du projet

- IV-A-1-a. Création nouveau projet

- IV-A-1-b. Ajout bibliothèque apduio.jar

- IV-A-1-c. Ajout de classe

- IV-A-2. Code

- IV-A-3. Simulation

- IV-A-3-a. JCWDE

- IV-A-3-b. CREF

- V. Conclusion et remerciements

I - Présentation de Java Card

I-A - Introduction des cartes à puce

I-A-1 - Exemples d'utilisations

On retrouve les cartes à puce dans plusieurs domaines. On cite quelques exemples de cartes :

- carte bancaire : Carte Bleue (France), MasterCard (USA) ;

- carte d'assurance maladie : Vitale (France) ;

- porte-monnaie électronique : Moneo (France) ;

- carte étudiant ;

- carte SIM.

I-A-2 - Types de cartes

Il existe deux grandes familles de cartes à puce :

- cartes synchrones : ce type de cartes est doté soit d'une mémoire EPROM soit d'une mémoire EEPROM

- carte à mémoire EPROM : ce type de cartes peut être effacée et reprogrammée plusieurs fois. Le processus d'effacement et de reprogrammation nécessite le déplacement de la carte vers un support physique dédié à cette opération. Elle est utilisée comme une carte de prépaiement du téléphone ; dans ce cas, elle est considérée comme carte jetable. Elle peut aussi servir de carte d'identification utilisée pendant des années,

- carte à mémoire EEPROM : c'est une carte EPROM, mais elle possède l'avantage d'être effacée et reprogrammée sans qu'il soit nécessaire de la retirer du support ou de l'appareil qui l'utilise. Elle est souvent utilisée comme carte de fidélité ;

- cartes asynchrones : appelées aussi cartes à microcontrôleur. Ce type de cartes est composé de :

- unité centrale de microprocesseur,

- mémoire morte : PROM,

- mémoire vive : RAM,

- mémoire EEPROM,

- interface d'entrée/sortie,

- un cryptoprocesseur.

Avec ce type de carte, toutes les applications sont possibles et imaginables comme les cartes bancaires, porte-monnaie électroniques, cartes de santé, etc.

Parmi les types de cartes asynchrones, on retrouve les Smart Cards.

On distingue deux manières de communication avec une carte à puce :

- carte à puce avec contact en utilisant la norme ISO 7816 ;

- carte à puce sans contact en utilisant la norme ISO 1443 ou la norme ISO 15693.

I-A-3 - Architecture d'une Smart Card avec contact

La carte est composée de 8 connecteurs selon la norme ISO 7816 représentée comme suit :

- VCC : tension d'alimentation de la carte fournie par le lecteur ;

- RST (Reset) ;

- CLK : horloge ;

- GND (Ground) : masse ;

- VPP : tension de programmation fournie par le lecteur (inutilisé aujourd'hui) ;

- I/O : entrée/sortie des données ;

- RFU (Reserved for Future Use) : connecter d'autres composants.

I-B - C'est quoi Java Card ?

I-B-1 - Hardware

Java Card est une carte asynchrone de type Smart Card. Donc elle possède une mémoire EEPROM. Les cartes standard ont une EEPROM de taille variant de 1 Ko à 128 Ko. Tandis que la carte Java Card est distinguée d'une EEPROM de taille égale à 256 Ko due à la présence de la machine virtuelle.

I-B-2 - Software

Java Card est un langage de programmation spécifique à la réalisation des cartes à puce de type Java Card.

Le langage Java Card est une version allégée du langage de programmation Java. Comme l'espace mémoire alloué dans la carte est de taille réduite, il y a certaines caractéristiques qui ne sont pas supportées par Java Card telles :

- les types simples : int, float, double… à l'exception du byte, short et boolean ;

- les chaînes de caractères ;

- manipulation des tableaux multidimensionnels ;

- multithreading ;

- garbage collector (supporté uniquement à partir de la version 2.2 de Java Card).

- Le programme réalisé en Java Card est appelé une Applet ou bien Cardlet. Les applets sont chargées sur la carte dans l'EEPROM. L'espace mémoire utilisé par l'applet est appelé Sandbox.

Pour pouvoir exécuter une applet, il faut avoir un minimum de caractéristiques de la carte Java Card :

- μp : 30 KI/s ;

- ROM : 12 Ko ;

- RAM : 512 Ko ;

- EEPROM : un minimum de 4 ko .

Java Card comporte les versions 1.0, 2.0 et 3.0 sortie récemment.

On utilisera par la suite la version 2.0 de Java Card qui est la plus stable.

I-B-3 - Architecture de Java Card

- La gestion du matériel se fait à travers les méthodes natives de bas niveau (écrire dans la RAM, lire à partir de la RAM, gestion de la mémoire, etc.). Ce sont des méthodes de base et existent toujours.

- L'interpréteur (JVM) permet d'exécuter les applets sélectionnées.

- Standard Class Librairies est un ensemble d'API provenant du Java Card Framework et contenant les classes nécessaires à la création d'une applet telle que (APDU, AID, Applet, etc.).

- Card Executive permet de charger l'applet sur la carte.

- Il peut coexister une ou plusieurs applets sur la même carte. Ce sont des cartes multiservices (exemple : carte étudiant + carte Moneo). Une seule applet à la fois est exécutée.

Native methods + JVM + API + Card executive => Java Card Runtime Environment.

I-B-4 - Framework Java Card

Java Card comporte quatre packages :

- javacard.framework : comporte les classes les plus importantes telles que APDU, AID, PIN, ISO, etc. ;

- javacardx.framework : comporte des classes telles que DedicatedFile, elm EntryFile ;

- javacardx.crypto : c'est un package qui contient des classes de cryptographie ;

- javacardx.cryptoEnc : c'est un package qui contient d'autres classes de cryptographie.

I-B-5 - Cycle de vie d'une applet

Hors Carte

Étape 1 :

Implémenter le code de l'applet sous un éditeur tel que Eclipse en important les API nécessaires existant dans le SE. Un fichier d'extension .java est obtenu.

Étape 2 :

Compiler le fichier .java à l'aide du compilateur javac existant sous le JDK. On obtient un fichier .class qui contient du byte code Java et un fichier .link qui permet de faire la liaison avec les API appelées.

Étape 3 :

Le convertisseur existant sous JCDK permet de convertir les deux fichiers .class et .link en un nouveau fichier d'extension .cap contenant du byte code Java Card.

Dans la Carte

Étape 4 :

Cette étape consiste à charger le fichier .cap obtenu précédemment dans notre carte à puce de type Java Card. La JCRE alloue un espace mémoire dans l'EEPROM appelé sandbox pour l'applet à charger.

Étape 5 :

La JCRE fait appel à la méthode install(). Cette méthode permet de :

- créer une instance de l'applet ;

- réaliser l'identification de l'applet grâce à l'AID ;

- enregistrer l'applet.

Communication carte-lecteur

Étape 6 :

Un APDU est un format de communication entre la carte à puce et le lecteur de cartes. Donc pour pouvoir communiquer avec l'applet souhaitée, le lecteur initie la communication en envoyant un APDU de sélection. Ce dernier contient l'AID de l'applet à sélectionner. Si tout est correct, la carte renvoie un APDU de réponse indiquant le succès de l'opération.

La JCRE sélectionne l'applet en utilisant la méthode select().

Étape 7 :

Ensuite le lecteur poursuit la communication en envoyant des APDU de commande pour interroger la carte. La méthode process() a pour rôle de gérer les différents APDU de commandes. À noter que cette dernière gère aussi la réception de l'APDU de sélection. Une réponse est retournée selon la commande demandée par le CAD (lecteur).

II - Préparation de l'environnement

II-A - Installation de l'éditeur

Il faut avoir un éditeur pour pouvoir implémenter et tester notre applet.

- On choisit l'éditeur Eclipse IDE for Java Developpers.

II-A-1 - Téléchargement

Selon votre Système d'exploitation, choisissez le lien correspondant :

Windows 32 bits :

http://www.eclipse.org/downloads/download.php?file=/technology/epp/downloads/release/luna/SR1/eclipse-java-luna-SR1-win32.zip

Windows 64 bits :

http://www.eclipse.org/downloads/download.php?file=/technology/epp/downloads/release/luna/SR1/eclipse-java-luna-SR1-win32-x86_64.zip

II-A-2 - Manipulation

Décompresser le fichier .zip téléchargé sous C:\Eclipse.

- Vérifier l'existence du fichier Eclipse.exeet du dossier plugins.

- De préférence, on place le dossier workspace, qui contiendra tous les projets, sous C:\Eclipse.

II-B - Installation de JDK

II-B-1 - Téléchargement

On installe la version 7 de JDK (à choisir le dernier update).

Windows 32 bits/64 bits :

http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html

II-B-2 - Manipulation

Installer le fichier téléchargé.

- L'espace présent dans le mot  Program Files du chemin de JDK présente une éventuelle source de problèmes lors de la compilation.

- Pour y remédier, on change l'emplacement de  C:\Program Files\Java  vers  C:\Java.

Ensuite, on ajoute une variable d'environnement JAVA_HOME pour pouvoir compiler dans la console sans souci du chemin actuel.

Le processus d'ajout de la variable d'environnement se fait comme suit :

- Cliquer avec le bouton droit sur l'icône de Poste de travail, puis sur Propriétés > Paramètres système Avancés > Variables d'environnement.

- Dans la zone Variable Utilisateur, on crée une nouvelle variable d'environnement nommée JAVA_HOME contenant le chemin  C:\Java\jdk1.7.0_67  (varie selon votre version d'update de JDK).

- Dans la zone Variable Système, on modifie la variable Path en ajoutant à la fin %JAVA_HOME%\bin;.

- %JAVA_HOME% représente le contenu de la variable JAVA_HOME.

Il faut faire attention lors de la manipulation de la variable Path pour ne pas effacer l'ancien contenu.

Pour vérifier le bon fonctionnement, on tape la commande javacdans la console DOS.

En cas de dysfonctionnement de l'ajout de la variable d'environnement JAVA_HOME, on procède par un forçage de fonctionnement en ajoutant directement dans la variable PATH le chemin d'accès C:\Java\ jdk1.7.0_67\bin; .

II-C - Installation de JCDK

II-C-1 - Téléchargement

On installe la version 2.2.2 de JCDK ( la plus stable).

Windows 32 bits/64 bits :

http://www.oracle.com/technetwork/java/javasebusiness/downloads/java-archive-downloads-javame-419430.html#java_card_kit-2.2.2-oth-JPR

II-C-2 - Manipulation

Sous  C:\JCDKdézipper le fichier téléchargé.

Sous  C:\JCDK\java_card_kit-2_2_2 dézipper directement les 4 fichiers .zip.

Ensuite, ajouter la variable d'environnement JC_HOME.

(cliquez ici pour aggrandir l'image)

Le processus d'ajout de la variable d'environnement se fait comme suit :

- Cliquer avec le bouton droit sur l'icône du Poste de travail, puis sur Propriétés > Paramètres> système Avancés > Variables d'environnement.

- Dans la zone Variable Utilisateur, on crée une nouvelle variable d'environnement nommée JC_HOME contenant le chemin  C:\JCDK\java_card_kit-2_2_2.

- Dans la zone Variable Système, on modifie la variable Path en ajoutant  %JC_HOME%\bin;  .

Pour vérifier le bon fonctionnement, on tape par exemple la commande converter sous DOS.

En cas de dysfonctionnement de l'ajout de la variable d'environnement JC_HOME, on procède par un forçage de fonctionnement en ajoutant directement dans la variable PATH le chemin d'accès  C:\JCDK\java_card_kit-2_2_2\bin;.

II-D - Ajout des plugins Eclipse-JCDE

II-D-1 - Téléchargement

Windows 32 bits/64 bits :

http://sourceforge.net/projects/eclipse-jcde/files/latest/download?source=files

II-D-2 - Manipulation

Dézipper le fichier téléchargé dans le dossier C:\Eclipse\plugins.

Deux dossiers sont ajoutés Plugins et  _MACOSX.

Transférer le contenu de C:\Eclipse\plugins\plugins vers C:\Eclipse\plugins.

III - Application serveur

III-A - Langage Java Card

III-A-1 - AID

L'AID permet d'identifier une applet installée sur la carte.

Une carte peut contenir une ou plusieurs applets. La sélection de l'applet souhaitée se fait à travers son AID.

Il existe aussi un AID pour les packages.

L'AID est composé de deux parties :

- RID (5 octets) : une compagnie obtient son propre et unique RID de la norme ISO;

- PIX (0 - 11 octets) : chaque compagnie affecte des PIX selon son choix à ses applets.

D'autre part, il existe des AID par défaut attribués par la norme ISO.

(cliquez ici pour aggrandir l'image)

III-A-2 - APDU

La communication reliant le lecteur avec la carte se fait à travers l'APDU.

C'est toujours le lecteur qui initie la communication avec la carte et non le contraire.

Il existe trois types d'APDU :

- APDU de commande: c'est à travers cet APDU que le lecteur demande l'action souhaitée. Par exemple incrémenter un compteur.

La forme de l'APDU est comme suit :

- C: classe de l'applet ;

- INS: instruction ;

- P1, P2 : paramètres ;

- Lc: taille du champ data ;

- Data field : la data envoyée dans l'APDU;

- Le: taille de DATA reçu dans la réponse de la carte .

Exemple d'un APDU :

C'est un tableau d'octets (byte[])

- Les champs sont en format hexadécimal.

- Les valeurs de CLA et INS sont déclarées dans notre applet où on définit leurs valeurs nous-même.

- P1 et P2 sont généralement envoyés à 0.

- La longueur de la data est égale à 2, défini par le champ Lc.

- Le champ data comporte donc deux valeurs qui sont envoyées dans l'APDU.

- Le champ Le définit la taille 7F (en hexadécimale) équivalent de 127 octets en valeur décimale.

- APDU de sélection : il permet de sélectionner l'applet souhaitée en fonction de son AID.

L'APDU de sélection est caractérisé par :

- CLA= 0x00 ;

- INS = 0xA4 ;

- P1= 0x04 & P2 = 0x00 ;

- Lc= 0x0B qui est égal à 11 octets au format décimal => taille de l'AID ;

- Data contient l'AID de l'applet à sélectionner. Ceci est l'AID par défaut attribué par la norme ISO ;

- Le= 0x7F ;

- APDU de réponse : c'est l'APDU envoyé par la carte pour répondre au lecteur. Il est composé de deux champs nommés SW1et SW2 qui retournent l'état du traitement de l'APDU envoyé par le lecteur.

SW1 = 0x90 & SW2 = 0x00 => Si le traitement est effectué avec succès. Sinon ils renvoient d'autres valeurs. Chaque valeur correspond à une erreur définie dans la norme ISO7816.

On retrouve dans le lien ci-dessous plusieurs valeurs de SW ainsi que l'erreur correspondante.

http://www.win.tue.nl/pinpasjc/docs/apis/jc222/javacard/framework/ISO7816.html

L'APDU de réponse peut contenir aussi un champ DATA. Mais uniquement lorsqu'on veut récupérer une valeur.

III-B - Application serveur

L'application serveur représente l'applet. On implémente une application qui effectue quatre opérations sur un compteur : incrémentation, décrémentation, interrogation et initialisation du compteur par une valeur donnée.

III-B-1 - Création de l'applet

Pour créer un projet Java Card : File > New > Other > Java Card > Java Card Projet.

Créer un projet Java Card nommé TP JC App Serveur.

Manque d'une bibliothèque de Java Card.

Il faut ajouter le chemin du dossier lib contenu dans C:\JCDK\java_card_kit-2_2_2.

Pour créer une applet Java Card => File > new > Other > Java Card > Java Card Applet.

Nom du package : monpackage.

Nom de l'applet : MonApplet.

Code initialement généré

1. package monpackage;

3. import javacard.framework.APDU;

4. import javacard.framework.Applet;

5. import javacard.framework.ISOException;

7. public class App extends Applet {

9. private App() {

10. }

12. public static void install(byte bArray[], short bOffset, byte bLength)

13. throws ISOException {

14. new App().register();

15. }

17. @Override

18. public void process(APDU arg0) throws ISOException {

19. // TODO Auto-generated method stub

20. }

21. }

Il faut supprimer le mot @Override pour éliminer l'erreur.

Dans la méthode process(APDU arg0) générée, on remplace arg0 par apdu.

Finalement, on attribue un AID par défaut à l'applet. Cliquer le bouton droit sur monpackage > Java Card Tools > Set Package AID.

III-B-2 - Code

Code intégral

1. package monpackage;

3. import javacard.framework.APDU;

4. import javacard.framework.Applet;

5. import javacard.framework.ISOException;

6. import javacard.framework.ISO7816;

8. public class MonApplet extends Applet {

10. /******************** Constantes ************************/

12. public static final byte CLA_MONAPPLET= (byte) 0xB0;

14. public static final byte INS_INCREMENTER_COMPTEUR= 0x00;

15. public static final byte INS_DECREMENTER_COMPTEUR= 0x01;

16. public static final byte INS_INTERROGER_COMPTEUR= 0x02;

17. public static final byte INS_INITIALISER_COMPTEUR= 0x03;

19. /*** Attributs ***/

20. private byte compteur;

22. private MonApplet() {

23. compteur=0;

24. }

26. public static void install(byte bArray[], short bOffset, byte bLength)

27. throws ISOException {

28. new MonApplet().register();

29. }

31. public void process(APDU apdu) throws ISOException {

32. byte[] buffer= apdu.getBuffer();

34. if(this.selectingApplet()) return;

36. if(buffer[ISO7816.OFFSET_CLA]!=CLA_MONAPPLET)

37. ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);

39. switch(buffer[ISO7816.OFFSET_INS]){

41. case INS_INCREMENTER_COMPTEUR:

42. compteur++;

43. break;

45. case INS_DECREMENTER_COMPTEUR:

46. compteur--;

47. break;

49. case INS_INTERROGER_COMPTEUR:

50. buffer[0]=compteur;

51. apdu.setOutgoingAndSend((short)0, (short) 1);

52. break;

54. case INS_INITIALISER_COMPTEUR:

55. apdu.setIncomingAndReceive();

56. compteur=buffer[ISO7816.OFFSET_CDATA];

57. break;

59. default:

60. ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);

61. }

62. }

63. }

Explication détaillée du code :

Packages

1. import javacard.framework.APDU;

2. import javacard.framework.Applet;

3. import javacard.framework.ISOException;

4. import javacard.framework.ISO7816;

Eclipse génère les quatre classes ci-dessus depuis javacard.framework.

- javacard.framework.APDU : APDU est une classe contenant plusieurs méthodes parmi lesquelles on utilisera quelques-unes.

- javacard.framework.Applet : la classe Applet contient la méthode install() dont notre applet hérite.

- javacard.framework.ISOException : cette classe traite les exceptions rencontrées lors de la communication avec les APDU.

- javacard.framework.ISO7816 : cette classe contient différentes constantes parmi elles les SW correspondant aux erreurs de traitement des APDU.

Les attributs

1. Public class MonApplet extends Applet {

3. /******************** Constantes ************************/

4. public static final byte CLA_MONAPPLET= (byte) 0xB0;

5. public static final byte INS_INCREMENTER_COMPTEUR= 0x00;

6. public static final byte INS_DECREMENTER_COMPTEUR= 0x01;

7. public static final byte INS_INTERROGER_COMPTEUR= 0x02;

8. public static final byte INS_INITIALISER_COMPTEUR= 0x03;

10. /*********************** Variables ***************************/

11. private byte compteur;

L'applet doit hériter de la classe Applet.

On déclare le CLA de notre applet à une valeur de 0xB0.

On a quatre opérations à réaliser avec le compteur. Donc on doit déclarer quatre INS :

- INS_INCREMENTER_COMPTEUR ayant une valeur 0x00 ;

- INS_DECREMENTER_COMPTEUR ayant une valeur 0x01 ;

- INS_INTERROGER_COMPTEURayant une valeur 0x02 ;

- INS_INITIALISER_COMPTEUR ayant une valeur 0x03 .

Les valeurs et l'ordre des déclarations sont librement choisis.

Un warning apparaît lorsqu'on utilise un INS supérieur ou égal à 0x60 prévenant que cet INS peut ne pas être supporté par la carte.

On rappelle que :

1 byte = 1 octet = 8 bits

1 short = 2 byte = 16 bits

Méthode install()

1. /************ Constructor **************/

2. private MonApplet() {

3. compteur=0;

4. }

6. public static void install(byte bArray[], short bOffset, byte bLength)

7. throws ISOException {

8. new MonApplet().register();

9. }

La méthode install() est évoquée pour pouvoir installer l'applet sur la carte.

Elle fait appel au constructeur MonApplet() et crée une nouvelle instance de l'applet puis fait appel à la méthode register() pour l'enregistrer dans la carte.

Les paramètres de install() :

- bArray[] : tableau de type byte ;

- bOffset: un short indiquant la position de lecture dans bArray[] ;

- bLength : un byte indiquant la longueur de Data à lire dans bArray[]. La méthode process(APDU apdu) est la méthode principale qui va gérer toutes les opérations. Elle reçoit en paramètre l'objet apdu de la classe APDU.

Méthode process()

1. public void process(APDU apdu) throws ISOException {

2. byte[] buffer= apdu.getBuffer();

3. if(buffer[ISO7816.OFFSET_CLA]!=CLA_MONAPPLET)

4. ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);

La méthode apdu.getBuffer() permet d'extraire les cinq premiers champs de l'APDU envoyé : CLA, INS, P1, P2 et Lc.

La méthode process() reçoit l'APDU de commande et l'APDU de sélection.

Elle ne traite que l'APDU de commande, donc elle doit renvoyer l'APDU de sélection.

On utilise la méthode this.selectingApplet() qui retourne Truesi l'apdu entré est un APDU de sélection. Dans ce cas, on doit quitter.

Lorsque l'instruction return est exécutée, on sort de la méthode et comme le type de retour de la méthode process() est void donc on ne retourne rien  return ; .

La première étape à réaliser est de vérifier si le CLA entré en APDU de commande correspond à notre applet. Le CLA se trouve dans le buffer (qui contient les cinq premiers champs de l'APDU). Pour y accéder, on utilise buffer[ISO7816.OFFSET_CLA].

Puis on le compare avec notre CLA_MONAPPLET (déclaré constante de valeur 0xB0).

S'il existe une différence alors une exception de ISO7816.SW_CLA_NOT_SUPPORTEDest propagée par le process().

À noter que le SW_CLA_NOT_SUPPORTED est une constante prédéfinie dans la classe ISO7816 ayant une valeur égale à 0x6E00. Cette valeur est retournée à travers l'APDU de réponse et précisément dans les champs : SW1 = 0x6E et SW2=0x00.

Incrémenter le compteur

1. switch(buffer[ISO7816.OFFSET_INS]){

3. case INS_INCREMENTER_COMPTEUR:

4. compteur++;

5. break;

On traite selon la valeur de l'INS envoyée en APDU de commande.

On accède à l'INS en utilisant buffer[ISO7816.OFFSET_INS].

Les valeurs possibles sont : 0x00 / 0x01 /0x02 / 0x03 ayant leur nom de constante équivalant.

Dans l'APDU de réponse, on obtient juste SW1 et SW2 qui indiqueront respectivement 0x90et 0x00 en cas de réussite.

Décrémenter le compteur

1. Case INS_DECREMENTER_COMPTEUR:

2. compteur--;

3. break;

Dans l'APDU de réponse, on obtient juste SW1 et SW2 qui indiqueront respectivement 0x90 et 0x00 en cas de réussite.

Interroger le compteur

1. case INS_INTERROGER_COMPTEUR:

2. buffer[0]=compteur;

3. apdu.setOutgoingAndSend((short)0, (short) 1);

4. break;

Si l'INS correspond à la valeur 0x02 donc interrogation du compteur.

L'interrogation du compteur nécessite une DATA retournée dans l'APDU de réponse.

Le buffer utilisé pour la récupération de l'APDU de commande est lui-même utilisé en APDU de réponse.

On utilise la première case du buffer en lui affectant la valeur du compteur (en écrasant la valeur du CLA contenue auparavant dans buffer[0] mais qui est sans intérêt dans cette étape).

On rappelle que l'APDU de réponse contient DATA + SW1 + SW2 et que le champ DATA est facultatif selon les besoins.

La méthode apdu.setOutgoingAndSend() permet d'envoyer l'APDU de réponse en utilisant le buffer.

Void setOutgoingAndSend( short bOff , short len )

Le premier paramètre bOff indique la position de lecture à partir du buffer. On a affecté la valeur du compteur à la case 0.

Le deuxième paramètre reflète la longueur du Data à envoyer. On a un seul byte à envoyer donc la valeur est 1.

Intialiser le compteur

1. case INS_INITIALISER_COMPTEUR:

2. apdu.setIncomingAndReceive();

3. compteur=buffer[ISO7816.OFFSET_CDATA];

4. break;

Dans ce cas, on aura un APDU de commande contenant dans le champ DATA une valeur qu'on affectera au compteur.

La méthode apdu.setIncomingAndReceive() permet d'aspirer la DATA d'un seul coup si elle est de taille réduite.

Maintenant, le champ Data est ajouté au buffer et on peut y accéder à travers buffer[ISO7816.OFFSET_CDATA] et on affecte la valeur passée par l'APDU dans compteur.

III-C - Simulation

Avant de commencer la simulation, on génère le script en cliquant sur le bouton droit sur le nom du package > Java Card Tools > Generate script.

Erreur de compilation.

Il faut changer le compilateur de 1.7 vers 1.5 qui est supporté par Java Card.

- Project > Properties > Java Compiler .

- Cocher  Enable project specific settings.

- Changer 1.7 par 1.5.

- Régénérer le script une autre fois.

On obtient plusieurs fichiers, dont create-MonApplet.script et select-MonApplet.script utilisés ultérieurement pour l'installation et la sélection de l'applet.

Il existe deux types de simulateurs.

III-C-1 - JCWDE

Le simulateur JCWDE ne conserve pas l'état de la carte après la modification de la valeur du compteur.

- Lancer le simulateur JCWDE sur Eclipse en appuyant sur Start (barre de menu).

- Ouvrir l'invite de commande et taper la commande apdutool.

- Sélection de l'installeur d'applets : APDU existant dans le fichier create-MonApplet.script.

Il ne faut pas emporter  powerdown ; .

=> APDU de sélection réussi : sw1 : 90 , sw2 :00

- Création de l'applet : APDU existant dans le fichier create-MonApplet.script.

=> APDU de commande réussi : sw1 : 90 , sw2 :00

- Sélection de l'applet : APDU existant dans le fichier select-MonApplet.script.

=> APDU de sélection réussi : sw1 : 90 , sw2 :00

Consulter la valeur du compteur : CLA=0xB0 et INS= 0x02

=> La valeur du compteur est contenue dans le champ DATA de l'APDU de réponse qui retourne 0.

Incrémenter la valeur du compteur : CLA=0xB0 et INS= 0x00.

Puis consulter la valeur du compteur : CLA=0xB0 et INS= 0x02.

=> Dans la DATA du l'APDU de réponse, on récupère la valeur 01.

Initialiser la valeur du compteur à 0x4A = 74: CLA=0xB0 et INS= 0x03.

Décrémenter la valeur du compteur : CLA=0xB0 et INS= 0x01.

Puis consulter la valeur du compteur : CLA=0xB0 et INS= 0x02

=> La réponse est 49 = 73 en décimal, donc opération de décrémentation réussie.

III-C-2 - CREF

Le simulateur CREF est caractérisé par la conservation de l'état de la carte après la fin de simulation.

Le simulateur possède parmi ses options :

-o nomfichier.eeprom: enregistrer l'état de la carte dans un fichier eeprom ;

-i nomfichier.eeprom : initialiser la carte à partir du fichier eeprom précédemment crée.

Les étapes de simulation :

- ouvrir l'invite de commande et taper la commande « cref -o monapplet.eeprom ».

=> Initialement le fichier monapplet.eeprom est vide. Après le lancement de la commande ci-dessus, toute opération effectuée sur la carte sera enregistrée dans le fichier ;

- réaliser l'upload de l'applet directement sur Eclipse : monpackage > Java Card Tools > Deploy

Le fichier monapplet.eeprom contient à présent le chargement de l'applet ;

- il faut relancer l'invite de commande et taper la commande : cref -i monapplet.eeprom -o monapplet.eeprom .

=> On initialise la carte à partir du fichier monapplet.eeprom (qui contient le chargement de la carte) et on prépare l'enregistrement de la carte de(s) opération(s) suivante(s) ;

- sous Eclipse, on installe l'applet en cliquant avec le bouton droit sur create-MonApplet.script puis Java Card Tools > Run Script.

=> L'installation de l'applet est, par conséquent, enregistrée dans le fichier eeprom.

Dans la console, retaper la commande  cref -i monapplet.eeprom -o monapplet.eeprom .

Ouvrir une deuxième console et taper la commande apdutool.

Sous notre projet, monpackage.javacard >select-MonApplet.script, sélectionner l'APDU de sélection et le taper à la suite de apdutool.

=> Sélection de l'APDU avec succès SW1 :90 , SW2 : 00

Incrémenter le compteur : CLA = 0xb0 et INS = 0x00

=> La valeur actuelle du compteur devient égale à 1.

=> Cette valeur est enregistrée dans le fichier eeprom.

Pour tester la conservation d'état, on ferme la simulation et on reprend juste l'étape de sélection de l'APDU, car le chargement et l'installation sont déjà enregistrés dans la le fichier eeprom. Il suffit d'initialiser la carte depuis le fichier eeprom.

Dans la console, retaper la commande cref -i monapplet.eeprom -o monapplet.eeprom.

Ouvrir une deuxième console et taper la commande apdutool .

Sous notre projet, monpackage.javacard > select-MonApplet.script, sélectionner l'APDU de sélection et le taper à la suite de apdutool.

Consulter la valeur du compteur : CLA = 0xb0 ; INS = 0x02.

Effectivement, on obtient la valeur 1 dans l'APDU de réponse.

IV - Application client

IV-A - Programmation

IV-A-1 - Création du projet

IV-A-1-a - Création nouveau projet

Créer un nouveau projet java nommé  TP JC App Client .

IV-A-1-b - Ajout bibliothèque apduio.jar

La bibliothèque  apduio.jar  contient les classes permettant la communication avec notre application serveur Java Card précédemment implémentée.

Cette bibliothèque existe sous  C:\JCDK\java_card_kit-2_2_2\lib .

Cliquer sur le bouton droit de projet > Propriétés > Java Build Path.

Cliquer sur  Add External JARs  et sélectionner la bibliothèque  apduio.jar.

IV-A-1-c - Ajout de classe

Créer d'abord un nouveau package nommé  monpackageclient.

Ensuite, créer une nouvelle classe nommée  MaClasse.

IV-A-2 - Code

Code intégral

1. package monpackageclient;

3. import java.io.BufferedInputStream;

4. import java.io.BufferedOutputStream;

5. import java.io.IOException;

6. import java.net.Socket;

7. import java.nio.ByteBuffer;

8. import javax.print.DocFlavor.BYTE_ARRAY;

10. import com.sun.javacard.apduio.Apdu;

11. import com.sun.javacard.apduio.CadT1Client;

12. import com.sun.javacard.apduio.CadTransportException;

14. public class MaClasse {

16. private static final byte CLA_MONAPPLET = (byte) 0xB0;

17. public static final byte INS_INCREMENTER_COMPTEUR= 0x00;

18. public static final byte INS_DECREMENTER_COMPTEUR= 0x01;

19. public static final byte INS_INTERROGER_COMPTEUR= 0x02;

20. public static final byte INS_INITIALISER_COMPTEUR= 0x03;

22. public static void main(String[] args) throws IOException, CadTransportException {

24. /********** Connexion à la carte *************/

26. CadT1Client cad;

27. Socket sckClient;

29. try{

31. sckClient=new Socket("localhost",9025);

32. sckClient.setTcpNoDelay(true);

33. BufferedInputStream input= new

34. BufferedInputStream(sckClient.getInputStream());

35. BufferedOutputStream output=new

36. BufferedOutputStream(sckClient.getOutputStream());

37. cad= new CadT1Client(input,output);

38. }

39. catch(Exception e){

40. System.out.println("Impossible de se connecter à la carte");

41. return;

42. }

45. /**********Mise sous tension de la carte ******************/

47. try{

48. cad.powerUp();

49. }

50. catch(Exception e){

51. System.out.println("Erreur lors de l'envoi de la commande powerup à la carte");

52. return;

53. }

55. /******* Sélection de l'applet : Commande APDU de type SELECT************/

57. Apdu apdu = new Apdu();

58. apdu.command[Apdu.CLA] = 0x00;

59. apdu.command[Apdu.INS] = (byte) 0xA4;

60. apdu.command[Apdu.P1] = 0x04;

61. apdu.command[Apdu.P2] = 0x00;

62. byte[] appletAID = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,0x07, 0x08,

63. 0x09, 0x00, 0x00 };

64. apdu.setDataIn(appletAID);

65. cad.exchangeApdu(apdu);

67. if (apdu.getStatus() != 0x9000) {

68. System.out.println("Erreur lors de la sélection de l'applet");

69. System.exit(1);

70. }

73. /************************ Menu principal *********************/

75. boolean fin=false;

77. while(!fin){

79. System.out.println();

80. System.out.println("Application client JavaCard");

81. System.out.println("---------------------------");

82. System.out.println();

83. System.out.println("1- Interroger le compteur");

84. System.out.println("2- Incrementer le compteur");

85. System.out.println("3- Decrementer le compteur");

86. System.out.println("4- Reinitialiser le compteur");

87. System.out.println("5- Quitter");

88. System.out.println("Votre choix :");

90. int choix = System.in.read();

91. while (!(choix >= '1' &amp;&amp; choix &lt;= '5')) {

92. choix = System.in.read();

93. }

95. apdu=new Apdu();

96. apdu.command[Apdu.CLA]=MaClasse.CLA_MONAPPLET;

97. apdu.command[Apdu.P1]=0x00;

98. apdu.command[Apdu.P2]=0x00;

99. apdu.setLe(0x7F);

102. switch (choix) {

103. case '1':

104. apdu.command[Apdu.INS] = MaClasse.INS_INTERROGER_COMPTEUR;

105. cad.exchangeApdu(apdu);

106. if (apdu.getStatus() != 0x9000)

107. System.out.println("Erreur : status word different de

108. 0x9000");

109. else

110. System.out.println("Valeur du compteur : " +apdu.dataOut[0]);

111. break;

113. case '2':

114. apdu.command[Apdu.INS] =MaClasse.INS_INCREMENTER_COMPTEUR;

115. cad.exchangeApdu(apdu);

116. if (apdu.getStatus() != 0x9000)

117. System.out.println("Erreur : status word different de

118. 0x9000");

119. else

120. System.out.println("OK");

121. break;

123. case '3':

124. apdu.command[Apdu.INS] = MaClasse.INS_DECREMENTER_COMPTEUR;

125. cad.exchangeApdu(apdu);

126. if (apdu.getStatus() != 0x9000)

127. System.out.println("Erreur : status word different de

128. 0x9000");

129. else

130. System.out.println("OK");

131. break;

133. case '4':

134. apdu.command[Apdu.INS] = MaClasse.INS_INITIALISER_COMPTEUR;

135. byte[] donnees = new byte[1];

136. donnees[0] =5;

137. apdu.setDataIn(donnees);

138. cad.exchangeApdu(apdu);

139. if (apdu.getStatus() != 0x9000)

140. System.out.println("Erreur : status word different de

141. 0x9000");

142. else {

143. System.out.println("Valeur d'initialisation est "+

144. donnees[0]);

145. System.out.println("OK");

146. }

147. break;

149. case '5':

150. fin=true;

151. break;

152. }

153. }

155. /**************** Mise hors tension de la carte *****************/

157. try {

158. cad.powerDown();

159. }

160. catch (Exception e){

161. System.out.println("Erreur lors de l'envoi de la commande Powerdown à la carte");

162. return;

163. } } }

Explication détaillée du code :

Les attributs

1. public class MaClasse {

2. private static final byte CLA_MONAPPLET = (byte) 0xB0;

3. public static final byte INS_INCREMENTER_COMPTEUR= 0x00;

4. public static final byte INS_DECREMENTER_COMPTEUR= 0x01;

5. public static final byte INS_INTERROGER_COMPTEUR= 0x02;

6. public static final byte INS_INITIALISER_COMPTEUR= 0x03;

Dans la classe  MaClasse, on déclare le CLA de l'applet compteur ainsi que les différents INS dont on aura besoin à la suite selon le type d'opération souhaitée.

Ils possèdent les mêmes valeurs que celles déclarées dans l'application serveur et ce, afin de garantir la communication entre le client et le serveur.

Connexion à la carte

1. public static void main(String[] args) throws IOException, CadTransportException {

2. CadT1Client cad;

3. Socket sckClient;

5. /**************** Connexion à la carte ****************/

6. try{

7. sckClient=new Socket("localhost",9025);

8. sckClient.setTcpNoDelay(true);

9. BufferedInputStream input= new

10. BufferedInputStream(sckClient.getInputStream());

11. BufferedOutputStream output=new

12. BufferedOutputStream(sckClient.getOutputStream());

13. cad= new CadT1Client(input,output);

14. }

15. catch(Exception e){

16. System.out.println("Impossible de se connecter à la carte");

17. return;

18. }

- Instanciation d'un Socket pour la connexion au localhost sur le port 9025.

- Éliminer le temps d'attente et l'envoi du packet directement.

- Récupérer l'input stream (flux de données) du socket.

- Récupérer l'output stream du socket.

- Instance du lecteur de carte.

- En cas d'erreur de connexion, un message est affiché.

Mise sous tension de la carte

1. /************* Mise sous tension de la carte ******************/

2. try{

3. cad.powerUp();

4. }

5. catch(Exception e){

6. System.out.println("Erreur lors de l'envoi de la commande powerup à la carte");

7. return;

8. }

Mise sous tension de la carte avec la méthode powerUp().

Un message d'erreur est affiché en cas d'erreur de mise sous tension.

Apdu de sélection

1. /********* Sélection de l'applet: Commande APDU de type SELECT *********/

2. Apdu apdu = new Apdu();

3. apdu.command[Apdu.CLA] = 0x00;

4. apdu.command[Apdu.INS] = (byte) 0xA4;

5. apdu.command[Apdu.P1] = 0x04;

6. apdu.command[Apdu.P2] = 0x00;

7. byte[] appletAID = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,0x07, 0x08, 0x09, 0x00, 0x00 };

8. apdu.setDataIn(appletAID);

9. cad.exchangeApdu(apdu);

10. if (apdu.getStatus() != 0x9000) {

11. System.out.println("Erreur lors de la sélection de l'applet");

12. System.exit(1);

13. }

L'étape suivante consiste dans la sélection de l'applet souhaitée. On rappelle que la sélection est réalisée grâce à un Apdu de sélection comportant l'AID de l'applet.

Avant d'envoyer un Apdu, on doit remplir ses différents champs.

Remplir le tableau apdu.command comme suit :

apdu.command[Apdu.CLA] = 0x00;

apdu.command[Apdu.INS] = (byte) 0xA4;

apdu.command[Apdu.P1] = 0x04;

apdu.command[Apdu.P2] = 0x00;

- Ce sont les valeurs propres à l'APDU de sélection

Déclarer un tableau de bytes et l'initialiser avec l'AID de l'applet dans l'application serveur. La valeur de l'AID correspondant à l'AID par défaut généré par la norme ISO7816.

Le rôle de la méthode apdu.setDataIn(appletAID) est de remplir le champ Data de la commande apdu ainsi que la taille correspondante c.-à-d. le champ Lc.

Finalement l'envoi de l'apdu vers la carte se fait à travers la méthode apdu.exchangeApdu(apdu).

La méthode apdu.getStatus() récupère les SW envoyés dans l'apdu de réponse par la carte. Si le getStatus() est différent de 0x9000 signifie qu'il existe une erreur.

Menu principal

1. /************************* Menu principal ***************************/

2. boolean fin=false;

3. while(!fin){

4. System.out.println();

5. System.out.println("Application client JavaCard");

6. System.out.println("---------------------------");

7. System.out.println();

8. System.out.println("1- Interroger le compteur");

9. System.out.println("2- Incrementer le compteur");

10. System.out.println("3- Decrementer le compteur");

11. System.out.println("4- Reinitialiser le compteur");

12. System.out.println("5- Quitter");

13. System.out.println("Votre choix :");

14. int choix = System.in.read();

15. while (!(choix >= '1' && choix<= '5')) {

16. choix = System.in.read();

17. }

Apdu de commande

1. apdu=new Apdu();

2. apdu.command[Apdu.CLA]=MaClasse.CLA_MONAPPLET;

3. apdu.command[Apdu.P1]=0x00;

4. apdu.command[Apdu.P2]=0x00;

5. apdu.setLe(0x7F);

Selon l'opération choisie, on envoie l'apdu correspondant à l'applet. Mais pour toutes les opérations citées, il existe des champs en commun. Donc on remplit les champs CLA , P1 , P2 et Le à part.

Interroger le compteur

1. switch (choix) {

2. case '1':

3. apdu.command[Apdu.INS] = MaClasse.INS_INTERROGER_COMPTEUR;

4. cad.exchangeApdu(apdu);

5. if (apdu.getStatus() != 0x9000)

6. System.out.println("Erreur : status word different de 0x9000");

7. else

8. System.out.println("Valeur du compteur : " +apdu.dataOut[0]);

9. break;

Ensuite, selon le choix dans le menu, on affecte l'INS correspondant dans l'apdu de commande.

Le premier choix correspondant à l'interrogation du compteur.

Il n'existe pas de Data dans ce cas, mais on recevra une Data dans l'apdu de réponse.

En cas de bon déroulement , on affiche sur la console la valeur du compteur.

Décrémenter le compteur

1. case '2':

2. apdu.command[Apdu.INS] =MaClasse.INS_INCREMENTER_COMPTEUR;

3. cad.exchangeApdu(apdu);

4. if (apdu.getStatus() != 0x9000)

5. System.out.println("Erreur : status word different de 0x9000");

6. else

7. System.out.println("OK");

8. break;

Le deuxième choix consiste à incrémenter le compteur.

De même cette opération ne nécessite pas de Data. Donc il suffit d'affecter l'INS correspondant et d'envoyer l'apdu.

Décrémenter le compteur

1. case '3':

2. apdu.command[Apdu.INS] = MaClasse.INS_DECREMENTER_COMPTEUR;

3. cad.exchangeApdu(apdu);

4. if (apdu.getStatus() != 0x9000)

5. System.out.println("Erreur : status word different de 0x9000");

6. else

7. System.out.println("OK");

8. break;

Le troisième choix consiste à décrémenter le compteur. Identique à la précédente sauf l'INS correspondant est affecté.

Initialiser le compteur

1. case '4':

2. apdu.command[Apdu.INS] = MaClasse.INS_INITIALISER_COMPTEUR;

3. byte[] donnees = new byte[1];

4. donnees[0] =5;

5. apdu.setDataIn(donnees);

6. cad.exchangeApdu(apdu);

7. if (apdu.getStatus() != 0x9000)

8. System.out.println("Erreur : status word different de 0x9000");

9. else

10. System.out.println("OK");

11. break;

Le quatrième choix consiste à initialiser le compteur. Une valeur est envoyée dans l'apdu qui sera attribuée au compteur par l'application serveur.

Toute data est envoyée à travers un tableau de bytes. Dans ce cas, on a besoin d'un tableau de bytes d'une seule case. On affecte par exemple la valeur 5 au tableau qui sera par la suite intégré dans l'apdu de commande par la méthode apdu.setDataIn(données).

Quitter

1. case '5':

2. fin=true;

3. break;

Mise hors tension de la carte

1. /******************* Mise hors tension de la carte ******************/

2. try {

3. cad.powerDown();

4. }

5. catch (Exception e){

6. System.out.println("Erreur lors de l'envoi de la commande Powerdown à la carte");

7. return; }

IV-A-3 - Simulation

IV-A-3-a - JCWDE

Créer un fichier de configuration qui contiendra la liste des applets JavaCard à installer pour la simulation et spécifier leurs AID.

Dans notre cas, on possède une seule applet.

Le fichier est créé sous C:\Eclipse\workspace\TP JC App Serveur\bin  et nommé monapplet.app.

monpackage.MonApplet 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x00 0x00

Lancer le simulateur et se placer dans le répertoire  C:\Eclipse\workspace\TP JC App Serveur\bin  et taper la commande jcwde monapplet.app.

Lancer l'application client sous Eclipse.

- Interrogation du compteur :

- Incrémentation du compteur : choix 2 ;

- Réinitialisation du compteur : choix 4 ;

- Décrémentation et interrogation du compteur :

IV-A-3-b - CREF

Le CREF est caractérisé par la conservation d'état de la carte.

Dans l'application serveur, on a lancé la simulation CREF de l'applet pendant laquelle on a enregistré dans le fichier monapplet.eeprom le chargement et l'installation de l'applet.

Sous Eclipse, on se positionne dans le fichier de l'applet puis ouvrir la console.

Exécuter la commande cref -i monapplet.eeprom -o monapplet.eeprom.

Ensuite, exécuter le code de l'application client .On obtient le menu principal.

Initialiser le compteur à 5 puis interroger pour vérifier la valeur.

Par la suite, on quitte en choisissant l'option 5 du menu. Puis on exécute de nouveau la commande cref -i monapplet.eeprom -o monapplet.eeprom après s'être positionné dans l'applet sous Eclipse.

Et finalement, on interroge de nouveau la carte et on récupère une valeur du compteur égale à 5.

V - Conclusion et remerciements

Au cours de ce tutoriel, on a travaillé une simple application de compteur. Elle vise à faciliter la compréhension de l'application serveur, qui joue le rôle d'un lecteur de carte. L'application serveur est codée en langage Java Card.

Une deuxième partie concernant le développement d'une application client, qui joue le rôle de la carte. L'application client est codée en langage Java dont le rôle est de communiquer avec l'application serveur.

Afin de tester notre application, on procède par une simulation réalisée sur l'ordinateur. Et ceci est notre point d'arrêt. On ne va pas aborder le chargement de l'application sur une carte à puce réelle.

Je tiens à remercier Logan Mauzaize pour ses conseils, Claude LELOUP pour sa relecture orthographique attentive de cet article et Mickaël Baron pour la mise au gabarit.

Un remerciement est également présenté à l'auteur juju_41 pour son article Introduction à la programmation de Javacard sous Windows.

Retour à l'article

Retour à l'article