Post Técnico
Como Analisar TLVs em JavaScript
Um dos pontos positivos dos cartões com chip (ICCs) é que os dados provenientes deles são fornecidos quase sempre em um formato padrão chamado BER-TLV. Em termos simples: Basic Encoding Rules, Tag-Length-Value (um artigo curioso e informativo sobre o assunto pode ser encontrado aqui).
O formato BER-TLV é uma das codificações ASN.1 (Abstract Syntax Notation) definidas pela ITU X.690, um conjunto de normas muito antigo que remonta aos primórdios da Internet.
Os cartões com chip utilizam o esquema TLV para codificar os dados do cartão. Em sua forma mais simples, o esquema Tag-Length-Value significa que, se houver uma tag chamada (por exemplo) "5A" e seu valor for composto por 8 octetos representados pelos valores hexadecimais sucessivos "41 11 12 34 56 78 9A BC", então a codificação TLV terá o seguinte aspecto: 5A084111123456789ABC, onde 5A é a tag, 08 é o comprimento e 4111123456789ABC é o valor.
A EMVCo (o consórcio de emissores de cartões por trás de toda a tecnologia de cartões com chip) define um conjunto de tags padrão para transações com cartão com chip. Por exemplo, a tag 5A sempre codifica o PAN (número de conta principal, ou número do cartão), 9F02 codifica o Valor Autorizado de uma transação, 5F2D codifica a Preferência de Idioma, entre outros. A lista completa de tags definidas pela EMVco (e seus significados) pode ser encontrada em https://www.eftlab.co.uk/index.php/site-map/knowledge-base/145-emv-nfc-tags.
Considerando que os TLVs codificam seu próprio comprimento, analisar dados TLV deveria ser algo simples, não é?
Bem, sim. Na maioria das vezes. Mais ou menos.
Se cada tag tivesse um identificador simples de um byte (como 5A), analisar um fluxo TLV seria realmente muito fácil. Porém, o esquema TLV não seria muito útil se os identificadores pudessem assumir apenas um dos 256 valores possíveis.
Para tornar os identificadores de tag extensíveis, as Regras de Codificação Básica (Basic Encoding Rules) permitem a utilização de tags de múltiplos bytes. As regras determinam que, se os 5 bits inferiores do primeiro byte de tag estiverem definidos, haverá mais bytes de identificação de tag a seguir. Nos bytes subsequentes, o bit superior é definido se houver mais bytes a seguir, enquanto o bit superior é zero no byte final. Assim, por exemplo, 5F24 é um identificador de tag válido de 2 bytes, DFEF01 é uma tag válida de 3 bytes, e assim por diante.
A EMVCo (que incorpora BER-TLV por referência no Livro 3, Anexo B, das especificações EMV) também permite o conceito de tags "wrapper", para viabilizar relacionamentos hierárquicos pai-filho (ou aninhamento) entre TLVs. Segundo as regras EMV, se o sexto bit do primeiro byte de uma tag estiver definido, a tag é denominada "construída" (prefiro o termo composta). Assim, uma tag de 3 bytes FFEE01 poderia ser usada para encapsular TLVs fictícios 3F0188 e 3F025544 da seguinte forma: FFEE01073F01883F025544. A tag pai, FFEE01, possui 7 bytes de dados, compostos por um TLV de 3 bytes e um TLV de 4 bytes. Grupos de tags podem ser aninhados em qualquer profundidade desejada com esse esquema.
Atenção: o byte de Comprimento de um TLV também pode ser de múltiplos bytes. Nesse caso, a regra de extensibilidade (extraída do Anexo B2 do Livro 3 EMV) é:
Um byte de comprimento com o bit superior definido indica que os 7 bits inferiores devem ser interpretados como o "comprimento do Comprimento". Em outras palavras, um byte de Comprimento igual a 0x82 significa que há dois bytes de informação de Comprimento (nos dois bytes seguintes). No TLV fictício representado por 5F0F8103AABBCC, a tag é 5F0F, o comprimento do Comprimento ocupa um byte, o Comprimento real é de 3 bytes, e o Valor é AABBCC.
Ficou bem claro, não é?
Sabendo de tudo isso, somos capazes de criar um parser TLV recursivo descendente totalmente genérico em cerca de 75 linhas de JavaScript, conforme a seguir.
// 'data' deve ter o formato "95050010203000…" etc.
// Em outras palavras: TLVs serializados como uma única string longa.
// Um objeto TLV é retornado. Utilize-o para consultar Valores pelo nome da Tag.
// TLV['95'] conterá o valor da tag 95.
// TLV['9F26'] conterá o valor da tag 9F26, etc.
A tática que usamos aqui é extremamente simples:
Primeiro, disponibilize um dicionário completo de identificadores de tag, contendo todas as tags EMVCo conhecidas (padrão do setor), além de todas as tags proprietárias ID TECH conhecidas. Chamamos esse dicionário de _KnownTags, e você pode verificar a existência de um identificador como '5A' consultando se _KnownTags[ '5A' ] retorna verdadeiro.
A seguir: Análise!
Nosso algoritmo de análise é extremamente simples:
Leia dois nibbles por vez em uma variável tag variável, e verificar se a tag existe no dicionário. Todas as tags no dicionário terão um, dois ou três bytes de comprimento; portanto, se lermos 6 nibbles sem encontrar uma tag conhecida, basta avançar o quadro de leitura em 2 nibbles e continuar normalmente (após emitir uma mensagem no console informando "Era esperada uma tag, mas nenhuma foi encontrada"). Se preferir ser mais rigoroso e lançar uma exceção, isso é possível, mas minha filosofia é que (dependendo, claro, das circunstâncias) um analisador deve, por padrão, ser tolerante a falhas, caso ainda se queira utilizar o restante dos dados analisados.
Após encontrar uma tag, utilize um método auxiliar — neste caso, uma função interna chamada readData() — para avançar além da tag, ler o comprimento (Length) e utilizá-lo para ler o valor (Value). (Aqui, é necessário verificar o bit mais significativo do suposto Length, a fim de determinar se é preciso seguir a regra de extensibilidade "comprimento do comprimento" mencionada anteriormente.)
Armazene o Value em um objeto de armazenamento sob uma chave de pesquisa correspondente a tag.
Ao final, retorne o objeto de armazenamento.
Vejamos um exemplo do mundo real. Suponha que você tenha um ID TECH Augusta leitor de cartão com chipe esteja utilizando-o no modo teclado para capturar dados do Quick Chip. Os dados transmitidos pelo dispositivo ao inserir um cartão podem ter a seguinte aparência:
Este é um grande bloco de dados TLV que começa com uma tag proprietária da ID TECH: DFEE25. (Para saber mais sobre o significado das tags da ID TECH, baixe o ID TECH TLV Tag Reference Guide em https://idtechproducts.atlassian.net/wiki/spaces/KB/overview.) A maioria das tags neste bloco, no entanto, são tags padrão da indústria EMVCo. Se atribuirmos o bloco, como uma string, a uma variável JS chamada tagblock e, em seguida, carregarmos o analisador acima e o executarmos com parseTags( tagblock ), obteremos de volta um objeto com tags e valores, como este:
Algumas dessas tags estão vazias. Algumas (como a 9F27) contêm um valor 00. Algumas são criptografadas. Mas, basicamente, você tem todas as tags necessárias aqui mesmo para executar uma transação EMV.
Por que usar JavaScript para fazer o parsing de TLV? Bem, se eu te dissesse a resposta real, teria que te matar estragaria toda a suspense que você certamente está sentindo agora, se eu der uma dica sobre maneiras de usar Node.js no ambiente de aplicativos de pagamento, como se comunicar com leitores de cartão de crédito usando JavaScript, como acionar servidores de teste de back-end usando Servlets e AJAX, etc. Tudo isso está chegando em breve aqui, então salve este blog nos favoritos e volte logo!
