ID TECH
Kontakt
Alle technischen Beiträge

Technischer Beitrag

So werden TLVs in JavaScript geparst

Einer der Vorteile von Chipkarten (ICCs) besteht darin, dass die aus ihnen ausgelesenen Daten nahezu immer in einem standardisierten Format bereitgestellt werden, dem sogenannten BER-TLV. Auf Deutsch: Basic Encoding Rules, Tag-Length-Value (einen kurzweiligen, aber informativen 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 Normenwerk, das aus der Frühzeit des Internets stammt.

Chipkarten verwenden das TLV-Schema zur Kodierung von Kartendaten. Vereinfacht ausgedrückt bedeutet das Tag-Length-Value-Prinzip Folgendes: Wenn ein Tag beispielsweise „5A" lautet und sein Wert aus 8 Oktetten besteht, die durch die aufeinanderfolgenden Hexadezimalwerte „41 11 12 34 56 78 9A BC" dargestellt werden, sieht die TLV-Kodierung wie folgt aus: 5A084111123456789ABC – dabei ist 5A das Tag, 08 die Länge und 4111123456789ABC der Wert.

EMVCo (das Kartenhersteller-Konsortium hinter dem gesamten Chipkarten-Standard) definiert eine Reihe standardisierter Tags für Chipkartentransaktionen. So kodiert beispielsweise 5A stets die PAN (Primary Account Number bzw. Kartennummer), 9F02 den autorisierten Betrag einer Transaktion, 5F2D die Sprachpräferenz usw. Die vollständige Liste der von EMVCo definierten Tags (samt ihrer Bedeutung) finden Sie 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 ein Kinderspiel sein, oder?

Nun ja. Größtenteils schon. Mehr oder weniger.

Wenn jedes Tag einen einfachen Ein-Byte-Bezeichner hätte (wie etwa 5A), wäre das Parsen eines TLV-Datenstroms tatsächlich denkbar einfach. Allerdings wäre das TLV-Schema kaum praxistauglich, wenn Bezeichner nur einen von lediglich 256 möglichen Werten annehmen könnten.

Um Tag-Bezeichner erweiterbar zu gestalten, sehen die Basic Encoding Rules die Möglichkeit von mehrbytigen Tags vor. Die Regeln besagen: Wenn die unteren 5 Bits des ersten Tag-Bytes gesetzt sind, folgen weitere Tag-Bezeichner-Bytes. In den nachfolgenden Bytes ist das oberste Bit gesetzt, sofern weitere Bytes folgen; im letzten Byte hingegen ist das oberste Bit null. 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 per Verweis in Book 3, Annex B der EMV-Spezifikationen einbezieht) erlaubt darüber hinaus das Konzept sogenannter „Wrapper"-Tags, um hierarchische Eltern-Kind-Beziehungen (bzw. Verschachtelungen) zwischen TLVs zu ermöglichen. Gemäß den EMV-Regeln gilt ein Tag als „konstruiert", wenn das sechste Bit seines ersten Bytes gesetzt ist (ich bevorzuge den Begriff zusammengesetzt). So könnte beispielsweise ein 3-Byte-Tag FFEE01 verwendet werden, um die (fiktiven) TLVs 3F0188 und 3F025544 wie folgt einzuschließen: FFEE01073F01883F025544. Das übergeordnete Tag FFEE01 enthält 7 Datenbytes, bestehend aus einem 3-Byte-TLV und einem 4-Byte-TLV. Mit diesem Schema lassen sich Tag-Gruppen beliebig tief verschachteln.

Wichtig zu beachten: Auch das Length-Byte eines TLV kann mehrbytiger Natur sein. Die Erweiterungsregel (entnommen aus EMV Book 3 Annex B2) lautet dabei:

Ein Length-Byte mit gesetztem oberstem Bit bedeutet, dass die unteren 7 Bits als „Länge der Length-Angabe" zu interpretieren sind. Ein Length-Byte mit dem Wert 0x82 gibt also an, dass die eigentliche Längeninformation zwei Bytes umfasst (in den zwei darauffolgenden Bytes). Im (fiktiven) TLV 5F0F8103AABBCC ist das Tag 5F0F, die Länge der Length-Angabe beträgt ein Byte, die eigentliche Länge ist 3 Bytes, und der Value lautet AABBCC.

Klar wie Kloßbrühe, oder?

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 ein einziger langer String.

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

// 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-Kennungen 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 eine Kennung 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:

Es werden jeweils zwei Nibbles in eine tag Variable, und prüfen Sie, ob das Tag im Dictionary vorhanden ist. Alle Tags im Dictionary sind ein, zwei oder drei Bytes lang. Wenn also 6 Nibbles gelesen werden, ohne ein bekanntes Tag zu finden, soll der Leserahmen einfach um 2 Nibbles weitergerückt und der Vorgang fortgesetzt werden, als wäre nichts gewesen (nachdem eine Konsolenmeldung mit „Expected a tag, found none" ausgegeben wurde). Wer möchte, kann hier auch eine Ausnahme auslösen – meine Philosophie ist jedoch, dass ein Parser standardmäßig fehlertolerант (fault-tolerant) sein sollte (je nach den Umständen natürlich), falls die restlichen geparsten Daten noch verwendet werden sollen.

Sobald ein Tag gefunden wurde, liest eine Hilfsmethode – in diesem Fall eine innere Funktion namens readData() – über das Tag hinaus, liest die Länge (Length) und verwendet diese, um den Wert (Value) zu lesen. (Dabei ist darauf zu achten, das oberste Bit der vermeintlichen Length zu prüfen, um festzustellen, ob die zuvor erwähnte Erweiterungsregel „Length-of-the-Length" angewendet werden muss.)

Den Value in einem Speicherobjekt unter einem Lookup-Schlüssel des tag.

Am Ende wird das Speicherobjekt zurückgegeben.

Sehen wir uns ein Praxisbeispiel an. 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:

Dies ist ein großer Block TLV-Daten, der mit dem proprietären ID TECH-Tag DFEE25 beginnt. (Mehr darüber, was die Tags von ID TECH bedeuten, erfahren Sie durch den Download des ID TECH TLV Tag Reference Guide von https://idtechproducts.atlassian.net/wiki/spaces/KB/overview.) Die meisten Tags in diesem Block sind jedoch branchenübliche EMVCo-Tags. Wenn wir den Block als String einer JS-Variable namens tagblock zuweisen und anschließend den oben genannten 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. Einige sind verschlüsselt. Aber im Grunde genommen haben Sie hier alle Tags, die Sie für eine EMV-Transaktion benötigen.

Warum JavaScript für das TLV-Parsing verwenden? Nun, wenn ich Ihnen die ehrliche Antwort darauf verraten würde, müsste ich Sie um die Spannung bringen, die Sie zweifellos verspüren, wenn ich andeutungsweise auf Möglichkeiten hinweise, wie man Node.js in der Zahlungsanwendungsumgebung einsetzt, wie man über JavaScript mit Kreditkartenlesern kommuniziert, wie man Back-End-Testserver über Servlets und AJAX anspricht usw. All das erwartet Sie schon bald hier – also setzen Sie diesen Blog als Lesezeichen und schauen Sie bald wieder vorbei!