ID TECH
Contatto
Tutti gli articoli tecnici

Post tecnico

Come analizzare i TLV in JavaScript

Uno dei vantaggi delle chip card (ICC) è che i dati che ne provengono sono quasi sempre forniti in un formato standard, denominato BER-TLV. In parole semplici: Basic Encoding Rules, Tag-Length-Value (un articolo curioso ma informativo al riguardo è 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 agli albori di Internet.

Le chip card utilizzano lo schema TLV per codificare i dati della carta. Nella sua forma più semplice, lo schema Tag-Length-Value significa che se si ha un tag denominato (ad esempio) "5A" e il suo valore è composto da 8 ottetti rappresentati da (per esempio) i valori esadecimali successivi "41 11 12 34 56 78 9A BC", la codifica TLV avrà l'aspetto seguente: 5A084111123456789ABC, dove 5A è il tag, 08 è la lunghezza e 4111123456789ABC è il valore.

EMVCo (il consorzio degli emittenti di carte alla base dell'intera tecnologia chip card) definisce una serie di tag standard per le transazioni con chip card. Ad esempio, 5A codifica sempre il PAN (numero di conto primario, o numero di carta), 9F02 codifica l'importo autorizzato di una transazione, 5F2D codifica la preferenza linguistica e così via. L'elenco completo dei tag definiti da EMVco (e il loro significato) è disponibile all'indirizzo https://www.eftlab.co.uk/index.php/site-map/knowledge-base/145-emv-nfc-tags.

Considerato che i TLV codificano la propria lunghezza, analizzare i dati TLV dovrebbe essere un gioco da ragazzi, giusto?

Beh, sì. Quasi. In un certo senso.

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

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

EMVCo (che incorpora per riferimento BER-TLV nel Book 3, Annex B, delle specifiche EMV) prevede anche il concetto di tag "wrapper", per abilitare relazioni gerarchiche padre-figlio (o di annidamento) tra TLV. In base alle regole EMV, se il sesto bit del primo byte di un tag è impostato, il tag viene definito "constructed" (preferisco il termine composto). Pertanto, un tag a 3 byte FFEE01 potrebbe essere utilizzato per racchiudere (fittizi) TLV di 3F0188 e 3F025544 come segue: FFEE01073F01883F025544. Il tag padre, FFEE01, contiene 7 byte di dati, composti da un TLV a 3 byte e un TLV a 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 indica che i 7 bit meno significativi 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 sulla lunghezza (nei due byte successivi). Nel (fittizio) TLV 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 l'acqua sporca, vero?

Sulla base di tutto ciò, siamo in grado di creare un parser TLV ricorsivo discendente completamente generale in circa 75 righe di JavaScript, come segue.

// 'data' dovrebbe avere il formato "95050010203000…" ecc.

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

// Viene restituito un oggetto TLV. Utilizzarlo per ricercare 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 tecnica che utilizziamo qui è di una semplicità disarmante:

Prima di tutto, si rende disponibile un ampio dizionario di identificatori di tag, contenente tutti i tag EMVCo noti (standard di settore), oltre a 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.

Poi: si effettua il parsing!

Il nostro algoritmo di parsing è estremamente semplice:

Si leggono due nibble alla volta in un tag variabile e verificare se il tag è presente nel dizionario. Tutti i tag nel dizionario avranno una lunghezza di uno, due o tre byte; pertanto, se si leggono 6 nibble senza trovare un tag noto, è sufficiente avanzare il frame di lettura di 2 nibble e proseguire come se nulla fosse accaduto (dopo aver emesso un messaggio in console con il testo "Expected a tag, found none"). Se si preferisce essere rigorosi e generare un'eccezione, è possibile farlo; tuttavia, la mia filosofia è che — a seconda 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 il campo Length e utilizzarlo per leggere il campo Value. (In questo passaggio è necessario verificare con attenzione il bit più significativo del presunto Length, per determinare se occorre applicare la regola di estensibilità "length-of-the-Length" menzionata in precedenza.)

Inserire il valore Value in un oggetto di archiviazione utilizzando come chiave di ricerca tag.

Al termine, restituire l'oggetto di archiviazione.

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 all'inserimento di una carta potrebbero avere il seguente aspetto:

Si tratta di un ampio blocco di dati TLV che inizia con un tag proprietario ID TECH con valore DFEE25. (Per ulteriori informazioni sul significato dei tag ID TECH, è possibile scaricare il documento ID TECH TLV Tag Reference Guide dal sito https://idtechproducts.atlassian.net/wiki/spaces/KB/overview.) La maggior parte dei tag in questo blocco sono tuttavia tag EMVCo standard di settore. Se si assegna il blocco, come stringa, a una variabile JS denominata tagblock, quindi si carica il parser descritto sopra e lo si esegue con parseTags( tagblock ), si otterrà un oggetto con tag e valori simile al seguente:

Alcuni di questi tag sono vuoti. Alcuni (come 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? Se vi dicessi la vera risposta, dovrei rovinarvi la suspense che state sicuramente provando, se vi accenno alla possibilità di utilizzare Node.js nell'ambiente delle applicazioni di pagamento, per comunicare con i lettori di carte di credito tramite JavaScript, per interagire con server di test back-end mediante Servlet e AJAX, e molto altro ancora. Tutto questo sarà disponibile a breve proprio qui, quindi mettete questo blog tra i preferiti e tornate presto!