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 standardisé, appelé BER-TLV. En termes simples : Basic Encoding Rules, Tag-Length-Value (un article succinct mais instructif à ce sujet peut être consulté 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 ancien qui remonte 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 » et que sa 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 », alors l'encodage TLV aura l'aspect suivant : 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 avait un identifiant simple d'un octet (comme 5A), l'analyse d'un flux TLV serait effectivement extrêmement facile. Mais le schéma TLV ne serait pas très utile si les identifiants ne pouvaient prendre que 256 valeurs possibles.

Pour permettre l'extensibilité des identifiants de balise, les Basic Encoding Rules autorisent l'utilisation de balises multi-octets. Selon ces règles, si les 5 bits de poids faible du premier octet de balise sont tous à 1, alors d'autres octets d'identifiant de balise suivent. Dans les octets suivants, le bit de poids fort est à 1 si d'autres octets suivent, tandis qu'il est à 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 BER-TLV par référence dans le Livre 3, Annexe B, des spécifications EMV) permet également le concept de balises « enveloppes », afin d'établir des relations hiérarchiques parent-enfant (ou d'imbrication) entre les 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 de 3 octets FFEE01 pourrait être utilisée pour encapsuler des TLV (fictifs) 3F0188 et 3F025544 de la manière 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 souhaitée grâce à ce mécanisme.

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

Un octet de Longueur dont le bit de poids fort est à 1 indique que les 7 bits de poids faible représentent la « longueur de la Longueur ». En d'autres termes, un octet de Longueur égal à 0x82 signifie qu'il y a deux octets d'information de Longueur (dans les deux octets qui suivent). Dans le TLV (fictif) représenté par 5F0F8103AABBCC, la balise est 5F0F, la longueur de la Longueur est d'un octet, la Longueur réelle est de 3 octets, et la Valeur est AABBCC.

Limpide, n'est-ce pas ?

Fort de tout cela, nous sommes en mesure de créer un analyseur TLV récursif-descendant entièrement générique en une soixantaine de 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 technique que nous utilisons ici est d'une simplicité absolue :

Tout d'abord, mettre à disposition un grand dictionnaire d'identifiants de tags, contenant tous les tags EMVCo standard 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 : Analyser !

Notre algorithme d'analyse est extrêmement simple :

Lire deux nibbles à la fois dans un 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 sans trouver de tag connu, il suffit d'avancer le cadre de lecture de 2 demi-octets et de continuer comme si de rien n'était (après avoir émis un message dans la console indiquant « Tag attendu, aucun trouvé »). Si vous préférez lever une exception à ce stade, c'est tout à fait possible, mais ma philosophie est qu'un analyseur syntaxique doit, par défaut, être tolérant aux pannes (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 utilitaire — en l'occurrence une fonction interne appelée readData() — pour dépasser le tag, lire la longueur (Length), puis utiliser cette longueur pour lire la valeur (Value). (Il convient ici de vérifier attentivement le bit de poids fort de la longueur présumée, afin de déterminer si la règle d'extensibilité « longueur de la longueur » mentionnée précédemment doit être appliquée.)

Stocker la valeur (Value) dans un objet de stockage sous une clé de recherche correspondant à tag.

À la fin, retourner l'objet de stockage.

Prenons 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 QuickChip. Les données émises par l'appareil lors de l'insertion d'une carte pourraient ressembler à ceci :

Il s'agit d'un bloc de données 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 ID TECH TLV Tag Reference Guide depuis https://idtechproducts.atlassian.net/wiki/spaces/KB/overview.) La plupart des tags de ce bloc sont toutefois des tags EMVCo standard. Si nous affectons le bloc, sous forme de chaîne de caractères, à une variable JS nommée tagblock, puis 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 :

Certaines de ces balises sont vides. Certaines (comme 9F27) contiennent une valeur de 00. D'autres sont chiffrées. Mais globalement, vous disposez ici de toutes les balises nécessaires pour effectuer une transaction EMV.

Pourquoi utiliser JavaScript pour analyser les TLV ? Si je vous révélais la vraie raison, je gâcherais le suspense que vous ressentez sans doute en ce moment, lorsque j'évoque des pistes sur la façon d'utiliser Node.js dans un environnement d'application de paiement, comment communiquer 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 prochainement ici même, alors ajoutez ce blog à vos favoris et revenez nous voir bientôt !