Post Técnico
Como Analisar TLVs em JavaScript
Uma das vantagens dos cartões com chip (ICCs) é que os dados gerados por eles são praticamente sempre fornecidos 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 você tiver uma tag chamada (por exemplo) "5A" e seu valor for de 8 octetos representados por (por exemplo) valores hexadecimais sucessivos "41 11 12 34 56 78 9A BC", 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 responsável pelo padrão de cartões com chip) define um conjunto de tags padrão para transações com cartão com chip. Por exemplo, 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, e assim por diante. 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.
Dado que os TLVs codificam seu próprio comprimento, analisar dados TLV deveria ser algo simples, certo?
Bem, sim. Na maior parte. Mais ou menos.
Se cada tag tivesse um identificador simples de um byte (como 5A), analisar um fluxo TLV seria realmente muito fácil. Mas 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 existência de tags com múltiplos bytes. As regras estabelecem que, se os 5 bits inferiores do primeiro byte de tag estiverem definidos, há mais bytes de identificação de tag a seguir. Nos bytes subsequentes, o bit superior é definido se houver mais bytes adiante, 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 o BER-TLV por referência no Livro 3, Anexo B, das especificações EMV) também prevê o conceito de tags "encapsulantes", para permitir relacionamentos hierárquicos do tipo pai-filho (ou aninhamento) entre TLVs. De acordo com as regras EMV, se o sexto bit do primeiro byte de uma tag estiver definido, diz-se que a tag é "construída" (prefiro o termo composta). Assim, uma tag de 3 bytes FFEE01 poderia ser usada para encapsular TLVs (fictícios) de 3F0188 e 3F025544 da seguinte forma: FFEE01073F01883F025544. A tag pai, FFEE01, contém 7 bytes de dados, compostos por um TLV de 3 bytes e um TLV de 4 bytes. Grupos de tags podem ser aninhados a qualquer profundidade desejada utilizando este esquema.
Observe com atenção: o byte de Comprimento de um TLV também pode ser composto por múltiplos bytes. Nesse caso, a regra de extensibilidade (extraída do Anexo B2 do Livro 3 do EMV) é a seguinte:
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.
Simples assim, não é?
Com todo esse conhecimento, somos capazes de criar um parser TLV recursivo de descida totalmente genérico em cerca de 75 linhas de JavaScript, conforme apresentado a seguir.
// 'data' deve ter o formato "95050010203000…" etc.
// Em outras palavras: TLVs serializados como uma única string.
// 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 grande dicionário de identificadores de tag, contendo todas as tags padrão do setor (EMVCo) conhecidas, 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.
Em seguida: Analise!
Nosso algoritmo de análise é bastante 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, você pode fazê-lo, mas minha filosofia é que (dependendo, evidentemente, das circunstâncias) um parser 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 campo Length e usar esse valor para ler o campo Value. (Aqui, é preciso verificar o bit mais significativo do presumido Length, para determinar se é necessário seguir a regra de extensibilidade "comprimento do comprimento" mencionada anteriormente.)
Armazene o Value em um objeto de armazenamento usando como chave de consulta o valor de 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 QuickChip. 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. (Você pode saber mais sobre o significado das tags da ID TECH baixando 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 do setor EMVCo. Se atribuirmos o bloco, como string, a uma variável JS chamada tagblock e, em seguida, carregarmos o parser acima e executá-lo 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 bem aqui para executar uma transação EMV.
Por que usar JavaScript para fazer o parsing de TLV? Bem, se eu revelasse a verdadeira resposta, estaria estragando a surpresa que você certamente está sentindo agora, caso eu dê algumas dicas sobre como usar Node.js no ambiente de aplicativos de pagamento, como se comunicar com leitores de cartão de crédito usando JavaScript, como acessar servidores de teste de back-end usando Servlets e AJAX, entre outros. Tudo isso estará disponível em breve aqui mesmo, então salve este blog nos favoritos e volte logo!
