ID TECH
Contatto
Tutti gli articoli tecnici

Post tecnico

Come analizzare i TLV in JavaScript

Uno dei vantaggi delle carte con chip (ICC) è che i dati che ne provengono sono quasi sempre forniti in un formato standard chiamato BER-TLV. In parole semplici: Basic Encoding Rules, Tag-Length-Value (un articolo curioso ma esaustivo sull'argomento è disponibile qui).

Il formato BER-TLV è una delle codifiche ASN.1 (Abstract Syntax Notation) definite da ITU X.690, un insieme di standard molto datato, risalente all'alba dei primordi di Internet.

Le carte con chip utilizzano lo schema TLV per codificare i dati della carta. Nella sua forma più semplice, lo schema Tag-Length-Value prevede che, se si dispone di un tag denominato (ad esempio) "5A" il cui valore è composto da 8 ottetti rappresentati da valori esadecimali consecutivi come "41 11 12 34 56 78 9A BC", la codifica TLV risultante sarà 5A084111123456789ABC, dove 5A è il tag, 08 è la lunghezza e 4111123456789ABC è il valore.

EMVCo (il consorzio degli emittenti di carte che sta dietro all'intero sistema delle carte con chip) definisce una serie di tag standard per le transazioni con carta a chip. Ad esempio, 5A codifica sempre il PAN (Primary Account Number, ovvero il numero della carta), 9F02 codifica l'importo autorizzato di una transazione, 5F2D codifica la preferenza di lingua e così via. L'elenco completo dei tag definiti da EMVCo (e dei relativi significati) è disponibile all'indirizzo https://www.eftlab.co.uk/index.php/site-map/knowledge-base/145-emv-nfc-tags.

Dato che i TLV codificano la propria lunghezza, analizzare un flusso di dati TLV dovrebbe essere un gioco da ragazzi, no?

Beh, sì. Più o meno. In un certo senso.

Se ogni tag avesse un identificatore semplice a un byte (come 5A), analizzare un flusso TLV sarebbe davvero semplicissimo. Tuttavia, lo schema TLV non sarebbe molto utile se gli identificatori potessero assumere solo 256 valori possibili.

Per rendere estensibili gli identificatori di tag, le Basic Encoding Rules prevedono la possibilità di tag multi-byte. Le regole stabiliscono che, se i 5 bit inferiori del primo byte del tag sono tutti impostati a 1, seguono ulteriori byte di identificazione del tag. Nei byte successivi, il bit più significativo è impostato a 1 se seguono altri byte, mentre è impostato a 0 nell'ultimo byte. Ad esempio, 5F24 è un identificatore di tag valido su 2 byte, DFEF01 è un tag valido su 3 byte, e così via.

EMVCo (che incorpora per riferimento le BER-TLV nel Book 3, Annex B, delle specifiche EMV) prevede anche il concetto di tag "wrapper", per consentire relazioni gerarchiche padre-figlio (o annidamento) tra TLV. In base alle regole EMV, se il sesto bit del primo byte di un tag è impostato a 1, il tag è definito "constructed" (preferisco il termine composto). Pertanto, un tag su 3 byte FFEE01 potrebbe essere utilizzato per racchiudere (a titolo esemplificativo) i TLV 3F0188 e 3F025544 nel modo seguente: FFEE01073F01883F025544. Il tag padre, FFEE01, contiene 7 byte di dati, composti da un TLV di 3 byte e uno di 4 byte. Gruppi di tag possono essere annidati a qualsiasi profondità desiderata utilizzando questo schema.

Si noti attentamente che anche il byte Length di un TLV può essere multi-byte. In questo caso, la regola di estensibilità (tratta dall'EMV Book 3 Annex B2) è la seguente:

Un byte Length con il bit più significativo impostato a 1 indica che i 7 bit inferiori devono essere interpretati come la "lunghezza del Length". In altre parole, un byte Length pari a 0x82 significa che ci sono due byte di informazioni sul Length (nei due byte successivi). Nel TLV (fittizio) rappresentato da 5F0F8103AABBCC, il tag è 5F0F, la lunghezza del Length occupa un byte, il Length effettivo è di 3 byte e il Value è AABBCC.

Chiaro come il fango, vero?

Sapendo tutto questo, siamo in grado di creare un parser TLV ricorsivo discendente completamente generale in circa 75 righe di JavaScript, come segue.

// 'data' dovrebbe essere nel formato "95050010203000…" ecc.

// In altre parole: TLV serializzati come un'unica stringa.

// Viene restituito un oggetto TLV. Utilizzarlo per cercare i Value tramite il nome del Tag.

// TLV['95'] conterrà il valore del tag 95.

// TLV['9F26'] conterrà il valore del tag 9F26, ecc.

La strategia che utilizziamo qui è estremamente semplice:

Prima di tutto, si rende disponibile un ampio dizionario di identificatori di tag, contenente tutti i tag EMVCo noti (standard di settore) e tutti i tag proprietari ID TECH noti. Questo dizionario lo chiamiamo _KnownTags, e puoi verificare l'esistenza di un identificatore come '5A' controllando se _KnownTags[ '5A' ] restituisce true.

Successivamente: Parsing!

Il nostro algoritmo di parsing è molto semplice:

Si leggono due nibble alla volta in un tag come variabile, e verificare se il tag esiste nel dizionario. Tutti i tag nel dizionario saranno lunghi uno, due o tre byte, quindi se si leggono 6 nibble senza trovare un tag noto, è sufficiente avanzare il frame di lettura di 2 nibble e continuare come se nulla fosse accaduto (dopo aver emesso un messaggio in console con "Expected a tag, found none"). Se si preferisce generare un'eccezione in questo punto, è possibile farlo, ma la mia filosofia è che (a seconda, ovviamente, delle circostanze) un parser dovrebbe, per impostazione predefinita, essere fail-soft (tollerante ai guasti), nel caso in cui si desideri comunque utilizzare il resto dei dati analizzati.

Una volta trovato un tag, si utilizza un metodo ausiliario — in questo caso una funzione interna denominata readData() — per leggere oltre il tag, leggere la Lunghezza e utilizzare quest'ultima per leggere il Valore. (Qui è necessario controllare attentamente il bit più significativo della presunta Lunghezza, per verificare se occorre applicare la regola di estensibilità "lunghezza-della-Lunghezza" menzionata in precedenza.)

Inserire il Valore in un oggetto di memorizzazione utilizzando come chiave di ricerca tag.

Al termine, restituire l'oggetto di memorizzazione.

Proviamo ora con un esempio reale. Supponiamo di disporre di un ID TECH Augusta lettore di carte con chipe di utilizzarlo in modalità tastiera per acquisire dati Quick Chip. I dati trasmessi dal dispositivo quando si inserisce una carta potrebbero essere simili ai seguenti:

Si tratta di un blocco TLV di grandi dimensioni che inizia con un tag proprietario ID TECH DFEE25. (Per saperne di più sul significato dei tag ID TECH, è possibile scaricare la ID TECH TLV Tag Reference Guide da https://idtechproducts.atlassian.net/wiki/spaces/KB/overview.) La maggior parte dei tag in questo blocco sono tuttavia tag standard EMVCo. Se si assegna il blocco, come stringa, a una variabile JS denominata tagblock, si carica il parser descritto sopra e lo si esegue con parseTags( tagblock ), si otterrà un oggetto contenente tag e valori, come il seguente:

Alcuni di questi tag sono vuoti. Alcuni (come il 9F27) contengono un valore pari a 00. Altri sono cifrati. In ogni caso, qui sono presenti tutti i tag necessari per eseguire una transazione EMV.

Perché usare JavaScript per il parsing TLV? Beh, se vi rivelassi la vera risposta, dovrei rovinarvi la suspense che sicuramente state già avvertendo, accennando ai modi in cui è possibile utilizzare Node.js nell'ambiente delle applicazioni di pagamento, a come comunicare con i lettori di carte di credito tramite JavaScript, a come interagire con server di test back-end mediante Servlet e AJAX, e molto altro ancora. Tutto questo sarà presto disponibile qui, quindi aggiungete questo blog ai preferiti e tornate a trovarci!