ID TECH
Kontakt
Alle technischen Beiträge

Technischer Beitrag

TLVs in JavaScript parsen

Eines der praktischen Merkmale von Chipkarten (ICCs) ist, dass die daraus ausgelesenen Daten nahezu immer in einem standardisierten Format bereitgestellt werden: BER-TLV. Auf Deutsch: Basic Encoding Rules, Tag-Length-Value (einen informativen, wenn auch etwas antiquierten Artikel dazu finden Sie hier).

Das BER-TLV-Format ist eine der ASN.1-Kodierungen (Abstract Syntax Notation), die in ITU X.690definiert sind – einem sehr alten Regelwerk, das aus der Frühzeit des Internets stammt.

Chipkarten verwenden das TLV-Schema zur Kodierung von Kartendaten. Im Kern bedeutet das Tag-Length-Value-Schema Folgendes: Hat man einen Tag wie beispielsweise „5A" und sein Wert besteht aus 8 Oktetten, dargestellt durch die aufeinanderfolgenden Hexadezimalwerte „41 11 12 34 56 78 9A BC", dann sieht die TLV-Kodierung so aus: 5A084111123456789ABC – wobei 5A der Tag ist, 08 die Länge und 4111123456789ABC der Wert.

EMVCo – das Kartennetzwerk-Konsortium hinter dem gesamten Chipkartensystem – definiert eine Reihe standardisierter Tags für Chipkartentransaktionen. So kodiert 5A beispielsweise stets die PAN (Primary Account Number bzw. Kartennummer), 9F02 den autorisierten Betrag einer Transaktion, 5F2D die Sprachpräferenz und so weiter. Die vollständige Liste der von EMVCo definierten Tags (und ihrer Bedeutungen) ist abrufbar unter https://www.eftlab.co.uk/index.php/site-map/knowledge-base/145-emv-nfc-tags.

Da TLVs ihre eigene Länge kodieren, sollte das Parsen von TLV-Daten doch eigentlich ein Kinderspiel sein, oder?

Nun ja. Meistens. Irgendwie schon.

Hätte jeder Tag einen einfachen Ein-Byte-Bezeichner (wie 5A), wäre das Parsen eines TLV-Datenstroms tatsächlich denkbar einfach. Allerdings wäre das TLV-Schema wenig praxistauglich, wenn Bezeichner nur 256 verschiedene Werte annehmen könnten.

Um Tag-Bezeichner erweiterbar zu gestalten, erlauben die Basic Encoding Rules die Möglichkeit von mehrbyte-Tags. Die Regeln besagen: Wenn die untersten 5 Bits des ersten Tag-Bytes gesetzt sind, folgen weitere Tag-Bezeichner-Bytes. In den nachfolgenden Bytes ist das oberste Bit gesetzt, wenn weitere Bytes folgen, während das oberste Bit im letzten Byte null ist. So ist beispielsweise 5F24 ein gültiger 2-Byte-Tag-Bezeichner, DFEF01 ein gültiger 3-Byte-Tag und so weiter.

EMVCo (das BER-TLV in Book 3, Annex B der EMV-Spezifikationen per Verweis einbezieht) ermöglicht auch das Konzept von „Wrapper"-Tags, um hierarchische Eltern-Kind-Beziehungen (oder Verschachtelungen) zwischen TLVs abzubilden. Nach den EMV-Regeln gilt ein Tag als „constructed" (ich bevorzuge den Begriff zusammengesetzt), wenn das sechste Bit des ersten Bytes eines Tags gesetzt ist. So könnte beispielsweise ein 3-Byte-Tag FFEE01 verwendet werden, um die (fiktiven) TLVs 3F0188 und 3F025544 wie folgt zu kapseln: FFEE01073F01883F025544. Das übergeordnete Tag FFEE01 enthält 7 Datenbytes, bestehend aus einem 3-Byte-TLV und einem 4-Byte-TLV. Tag-Gruppen können mit diesem Schema beliebig tief verschachtelt werden.

Wichtiger Hinweis: Auch das Length-Byte eines TLV kann mehrbyte-fähig sein. Die Erweiterbarkeitsregel (entnommen aus EMV Book 3 Annex B2) lautet:

Ein Length-Byte mit gesetztem obersten Bit bedeutet, dass die untersten 7 Bits als „Länge der Length" zu interpretieren sind. Anders ausgedrückt: Ein Length-Byte mit dem Wert 0x82 bedeutet, dass zwei Bytes an Length-Informationen folgen. Im (fiktiven) TLV 5F0F8103AABBCC ist 5F0F der Tag, die Länge der Length beträgt ein Byte, die eigentliche Length umfasst 3 Bytes und der Value ist AABBCC.

Klingt kompliziert? Das ist es auch.

Mit diesem Wissen lässt sich ein vollständig allgemeiner, rekursiv-absteigender TLV-Parser in etwa 75 Zeilen JavaScript wie folgt erstellen.

// 'data' sollte wie folgt aussehen: "95050010203000…" usw.

// Anders gesagt: TLVs, serialisiert, als eine einzige große Zeichenkette.

// Es wird ein TLV-Objekt zurückgegeben. Damit lassen sich Values anhand des Tag-Namens abrufen.

// TLV['95'] enthält den Wert von Tag 95.

// TLV['9F26'] enthält den Wert von Tag 9F26 usw.

Die hier verwendete Methode ist denkbar einfach:

Zunächst wird ein umfangreiches Wörterbuch mit Tag-Bezeichnern bereitgestellt, das alle bekannten EMVCo-Tags (Branchenstandard) sowie alle bekannten proprietären ID TECH-Tags enthält. Dieses Wörterbuch nennen wir _KnownTags, und Sie können prüfen, ob ein Bezeichner wie '5A' vorhanden ist, indem Sie testen, ob _KnownTags[ '5A' ] den Wert true zurückgibt.

Als Nächstes: Parsen!

Unser Parse-Algorithmus ist denkbar einfach:

Jeweils zwei Nibbles werden in ein tag Variable und prüfen, ob das Tag im Wörterbuch vorhanden ist. Da alle Tags im Wörterbuch ein, zwei oder drei Bytes lang sind, genügt es, den Leserahmen um 2 Nibbles weiterzurücken und normal fortzufahren, wenn nach 6 Nibbles kein bekanntes Tag gefunden wurde (nachdem eine Konsolenmeldung mit „Expected a tag, found none" ausgegeben wurde). Wer möchte, kann an dieser Stelle auch eine Ausnahme auslösen – meine Philosophie ist jedoch, dass ein Parser standardmäßig fehlertolerant sein sollte (natürlich je nach Situation), damit die übrigen geparsten Daten weiterhin verwendet werden können.

Sobald ein Tag gefunden wurde, liest eine Hilfsmethode – in diesem Fall eine innere Funktion namens readData() – am Tag vorbei, ermittelt die Länge (Length) und nutzt diese, um den Wert (Value) auszulesen. (Dabei muss sorgfältig geprüft werden, ob das oberste Bit der vermuteten Länge gesetzt ist, um festzustellen, ob die zuvor erwähnte Erweiterungsregel „Länge der Länge" angewendet werden muss.)

Den Wert (Value) unter einem Lookup-Schlüssel des entsprechenden Tags in einem Speicherobjekt ablegen. tag.

Am Ende das Speicherobjekt zurückgeben.

Betrachten wir nun ein Praxisbeispiel. Angenommen, Sie verwenden einen ID TECH Augusta Chip-Kartenleserim Tastaturmodus, um Quick Chip-Daten zu erfassen. Die Daten, die beim Einstecken einer Karte vom Gerät ausgegeben werden, könnten wie folgt aussehen:

Dabei handelt es sich um einen großen TLV-Datenblock, der mit dem ID TECH-proprietären Tag DFEE25 beginnt. (Weitere Informationen zur Bedeutung der ID TECH-Tags finden Sie im ID TECH TLV Tag Referenzhandbuch , der unter https://idtechproducts.atlassian.net/wiki/spaces/KB/overviewzum Download bereitsteht.) Die meisten Tags in diesem Block entsprechen jedoch branchenüblichen EMVCo-Tags. Wenn wir den Block als Zeichenkette einer JS-Variable namens tagblock zuweisen und anschließend den obigen Parser laden und mit parseTags( tagblock ) ausführen, erhalten wir ein Objekt mit Tags und Werten zurück, das etwa so aussieht:

Einige dieser Tags sind leer. Einige (wie 9F27) enthalten den Wert 00. Andere sind verschlüsselt. Aber im Grunde haben Sie alle Tags, die Sie für eine EMV-Transaktion benötigen, direkt hier verfügbar.

Warum JavaScript für das TLV-Parsing verwenden? Nun, wenn ich Ihnen die eigentliche Antwort darauf verraten würde, müsste ich Sie um den Spaß bringen – denn die Spannung, die Sie zweifellos spüren, wenn ich andeutungsweise darauf hinweise, wie man Node.js in der Payment-App-Umgebung einsetzt, wie man über JavaScript mit Kreditkartenlesern kommuniziert, wie man Back-End-Testserver mithilfe von Servlets und AJAX anspricht usw. All das folgt schon bald hier – also speichern Sie diesen Blog als Lesezeichen und schauen Sie bald wieder vorbei!