ID TECH
Contact
Tous les articles techniques

Article technique

Comment analyser les TLV en JavaScript

L'un des avantages des cartes à puce (ICC) est que les données qu'elles produisent sont pratiquement toujours fournies dans un format standard, appelé BER-TLV. En termes simples : Basic Encoding Rules, Tag-Length-Value (un article succinct mais instructif à ce sujet peut être trouvé ici).

Le format BER-TLV est l'un des encodages ASN.1 (Abstract Syntax Notation) définis par ITU X.690, un ensemble de normes très anciennes remontant aux premières heures d'Internet.

Les cartes à puce utilisent le schéma TLV pour encoder les données de carte. Dans sa forme la plus simple, le schéma Tag-Length-Value signifie que si vous avez un tag appelé (par exemple) « 5A » dont la valeur est constituée de 8 octets représentés par (par exemple) les valeurs hexadécimales successives « 41 11 12 34 56 78 9A BC », l'encodage TLV ressemblera à 5A084111123456789ABC, où 5A est le tag, 08 est la longueur et 4111123456789ABC est la valeur.

EMVCo (le consortium d'émetteurs de cartes à l'origine de la technologie des cartes à puce) définit un ensemble de tags standard pour les transactions par carte à puce. Par exemple, 5A encode toujours le PAN (numéro de compte principal, ou numéro de carte), 9F02 encode le montant autorisé d'une transaction, 5F2D encode la préférence de langue, et ainsi de suite. La liste complète des tags définis par EMVCo (et leur signification) est disponible à l'adresse https://www.eftlab.co.uk/index.php/site-map/knowledge-base/145-emv-nfc-tags.

Étant donné que les TLV encodent leur propre longueur, l'analyse d'un flux TLV devrait être un jeu d'enfant, n'est-ce pas ?

Eh bien, oui. En grande partie. Plus ou moins.

Si chaque tag disposait d'un identifiant simple sur un octet (comme 5A), l'analyse d'un flux TLV serait effectivement extrêmement simple. Mais le schéma TLV ne serait guère utile si les identifiants ne pouvaient prendre que 256 valeurs possibles.

Pour permettre l'extensibilité des identifiants de balises, les règles de codage de base (Basic Encoding Rules) autorisent des balises multi-octets. Ces règles stipulent que si les 5 bits de poids faible du premier octet de balise sont tous à 1, d'autres octets d'identification suivent. Dans les octets suivants, le bit de poids fort est à 1 si d'autres octets suivent, et à 0 dans le dernier octet. Ainsi, par exemple, 5F24 est un identifiant de balise légal sur 2 octets, DFEF01 est une balise légale sur 3 octets, et ainsi de suite.

EMVCo (qui intègre le BER-TLV par référence dans le Livre 3, Annexe B, des spécifications EMV) autorise également le concept de balises « enveloppes », afin de permettre des relations hiérarchiques parent-enfant (ou l'imbrication) entre des TLV. Selon les règles EMV, si le sixième bit du premier octet d'une balise est à 1, la balise est dite « construite » (je préfère le terme composée). Ainsi, une balise sur 3 octets FFEE01 pourrait servir à encapsuler des TLV (fictifs) 3F0188 et 3F025544 de la façon suivante : FFEE01073F01883F025544. La balise parente, FFEE01, contient 7 octets de données, composés d'un TLV de 3 octets et d'un TLV de 4 octets. Des groupes de balises peuvent être imbriqués à n'importe quelle profondeur grâce à ce mécanisme.

À noter avec attention : l'octet Longueur d'un TLV peut également être multi-octet. La règle d'extensibilité applicable (tirée du Livre 3 Annexe B2 des spécifications EMV) est la suivante :

Un octet Longueur dont le bit de poids fort est à 1 indique que les 7 bits de poids faible représentent la « longueur de la Longueur ». Autrement dit, un octet Longueur de valeur 0x82 signifie que deux octets d'information de Longueur suivent. Dans le TLV (fictif) représenté par 5F0F8103AABBCC, la balise est 5F0F, la longueur de la Longueur occupe un octet, la Longueur effective est de 3 octets, et la Valeur est AABBCC.

Limpide, n'est-ce pas ?

Forts de tout cela, nous sommes en mesure de créer un analyseur TLV récursif-descendant entièrement générique en environ 75 lignes de JavaScript, comme suit.

// 'data' doit ressembler à "95050010203000…" etc.

// Autrement dit : des TLV sérialisés, sous forme d'une seule grande chaîne de caractères.

// Un objet TLV est retourné. Utilisez-le pour rechercher des Valeurs par nom de Balise.

// TLV['95'] contiendra la valeur du tag 95.

// TLV['9F26'] contiendra la valeur du tag 9F26, etc.

La tactique employée ici est d'une simplicité absolue :

Premièrement, mettre à disposition un large dictionnaire d'identifiants de tags, contenant tous les tags EMVCo connus (norme industrielle), ainsi que tous les tags propriétaires ID TECH connus. Nous appelons ce dictionnaire _KnownTags, et vous pouvez tester l'existence d'un identifiant tel que '5A' en vérifiant si _KnownTags[ '5A' ] retourne true.

Ensuite : l'analyse syntaxique !

Notre algorithme d'analyse est extrêmement simple :

Lire deux nibbles à la fois dans une variable tag variable, puis vérifier si le tag existe dans le dictionnaire. Tous les tags du dictionnaire auront une longueur d'un, deux ou trois octets ; ainsi, si nous lisons 6 demi-octets (nibbles) sans trouver un tag connu, il suffit d'avancer le cadre de lecture de 2 demi-octets et de poursuivre comme si de rien n'était (après avoir émis un message dans la console indiquant « Tag attendu, aucun trouvé »). Si vous souhaitez être rigoureux et lever une exception, c'est possible, mais ma philosophie est qu'un analyseur (parser) devrait par défaut être tolérant aux pannes (fault-tolerant) — selon les circonstances, bien entendu —, au cas où vous souhaiteriez tout de même exploiter le reste des données analysées.

Une fois un tag trouvé, utilisez une méthode auxiliaire — en l'occurrence une fonction interne appelée readData() — pour lire au-delà du tag, lire la Longueur, puis utiliser cette Longueur pour lire la Valeur. (Il convient ici de vérifier soigneusement le bit de poids fort de la Longueur présumée, afin de déterminer s'il est nécessaire d'appliquer la règle d'extensibilité « longueur de la Longueur » mentionnée précédemment.)

Placez la Valeur dans un objet de stockage, sous une clé de recherche correspondant à tag.

À la fin, retournez l'objet de stockage.

Voyons maintenant un exemple concret. Supposons que vous disposiez d'un lecteur de carte à puce ID TECH Augustaet que vous l'utilisiez en mode clavier pour capturer des données Quick Chip. Les données transmises par l'appareil lors de l'insertion d'une carte pourraient ressembler à ceci :

Il s'agit d'un bloc TLV volumineux qui commence par un tag propriétaire ID TECH portant la valeur DFEE25. (Pour en savoir plus sur la signification des tags ID TECH, vous pouvez télécharger le Guide de référence des tags TLV ID TECH depuis https://idtechproducts.atlassian.net/wiki/spaces/KB/overview.) La plupart des tags de ce bloc sont toutefois des tags standard du secteur, définis par EMVCo. Si nous affectons ce bloc, sous forme de chaîne de caractères, à une variable JS appelée tagblock, puis que nous chargeons l'analyseur ci-dessus et l'exécutons avec parseTags( tagblock ), nous obtiendrons en retour un objet contenant les tags et leurs valeurs, comme suit :

Certains de ces tags sont vides. Certains (comme le 9F27) contiennent une valeur égale à 00. D'autres sont chiffrés. Mais en définitive, vous disposez ici de tous les tags nécessaires pour effectuer une transaction EMV.

Pourquoi utiliser JavaScript pour le parsing TLV ? Si je vous donnais la vraie réponse, je devrais vous éliminer gâcher la surprise que vous ressentez sans doute en ce moment, si je vous glisse quelques indices sur les façons d'utiliser Node.js dans un environnement d'application de paiement, comment dialoguer avec des lecteurs de cartes de crédit en JavaScript, comment interroger des serveurs de test back-end via des Servlets et AJAX, etc. Tout cela arrive très bientôt ici même, alors ajoutez ce blog à vos favoris et revenez nous voir !