SD-JWT · Token Status List · base45
A resposta já está dentro do código
Uma credencial que prova a si mesma. Assinatura, validade, revogação e claims, tudo conferido sem uma única requisição sair do dispositivo.
TypeScript, zero dependências de runtime, só WebCrypto. Node, navegadores e React Native, porque quem verifica costuma ser um celular.
O que isto faz, na prática
Uma credencial é uma afirmação que alguém assinou. “Esta pessoa tem mais de 18 anos.” “Esta pessoa pode dirigir.” A assinatura é o que dá valor à frase: ela diz que uma autoridade responde por aquilo e que ninguém alterou nada depois.
No papel, e num PDF, uma assinatura cobre o documento inteiro. Para provar uma única linha, você entrega tudo. Mostre a carteira de motorista só para provar a idade e a outra pessoa fica sabendo também o seu endereço, o número do documento e a sua data de nascimento exata. Ela não pediu nada disso. E agora tem.
A divulgação seletiva desfaz essa troca. O emissor assina cada dado separadamente, e a credencial carrega apenas uma impressão digital de cada um. Você escolhe o que revelar, e o resto continua sendo impressão digital, que não diz nada sobre o conteúdo. A assinatura continua válida, porque nunca foi uma assinatura sobre um bloco indivisível.
Tudo isso cabe dentro do código que o verificador lê: a assinatura, as impressões digitais e os dados que você escolheu mostrar. Nada é buscado durante a verificação, então uma porta, um ônibus, um posto de saúde no interior ou um avião em pleno voo conferem a credencial sem rede nenhuma.
Falta uma peça, e é justamente a que costuma ser esquecida. Tudo o que pode ser lido pode ser fotografado. Por isso quem apresenta também precisa assinar um desafio novo com uma chave privada que nunca sai do aparelho dele. Sem esse passo, o print da credencial de outra pessoa passaria. Esta biblioteca recusa qualquer apresentação que não traga isso.
Feita para o momento em que não há sinal
Eu construí o eCNH, a habilitação digital brasileira, usada por mais de 40 milhões de pessoas. A parte que mais me ensinou não foi o aplicativo. Foi a beira da estrada: um agente lendo uma habilitação numa rodovia com uma barra de sinal, ou nenhuma, precisando de um sim ou não em menos de um segundo.
Tudo de que você precisa para essa resposta cabe no código. A assinatura prova o emissor, os claims estão ali mesmo, e a única coisa que vem de fora é a chave pública do emissor, que muda tão raramente que dá para distribuir junto e atualizar uma vez por semana.
Bibliotecas para isso existem. São SDKs corporativos: pesados, amarrados ao perfil de um país, e escritos como se você já trabalhasse no setor de identidade. Esta é a versão que um engenheiro de produto adiciona numa terça-feira.
Três partes, e nenhuma delas é um servidor
Assinar cada claim uma vez
O emissor decide quais claims poderão ser omitidos depois, e assina um digest para cada um deles.
Enviar só o que foi pedido
A carteira descarta os claims que vai guardar. A assinatura do emissor continua válida sobre o que restou.
Responder sem perguntar a ninguém
Assinatura, validade, revogação e cada disclosure, conferidos contra uma chave fixada no dispositivo.
Prove que tem mais de 18 sem entregar sua data de nascimento
As leis de verificação de idade estão chegando mais rápido que as ferramentas. A implementação de sempre faz o cliente enviar foto do documento para um terceiro, o que é um desastre de privacidade e um vazamento à espera de acontecer.
A divulgação seletiva resolve isso direito. O verificador não consegue aprender a data de nascimento nem se quiser, porque aquele valor nunca saiu da carteira. A propriedade é criptográfica, não é promessa em política de privacidade.
// A credencial guarda nome, endereço, data de nascimento e número do documento.
// O bar recebe um booleano.
const presentation = await present(credential, { disclose: ['over_18'] })
const result = await verify(presentation, { trust })
result.claims // { over_18: true }
result.claims.birth_date // undefined, e nunca foi transmitido
result.withheld // 4, e ele não consegue dizer quais quatro
Números reais, inclusive o incômodo
Uma habilitação realista, oito claims, validade de cinco anos, ponteiro de lista de status. Medido, não estimado:
| Credencial | Caracteres | Versão do QR |
|---|---|---|
| tudo visível | ~740 | 18, escaneia bem |
| os oito claims divulgáveis | ~1590 | 27, denso demais |
presenting only over_18 | ~1115 | 22, ainda denso |
fits() te dizer onde você está antes de imprimir qualquer coisa.
Não acredite em mim
O playground roda a biblioteca inteira no seu navegador e dispara oito ataques reais contra ela. Cada um imprime o código de recusa que espera, então dá para conferir a biblioteca contra as próprias afirmações em vez de confiar num README.
Por que esta, e quando não
Cinco motivos para escolher, e depois a parte honesta.
- Cerca de 1.300 linhas que dá para lerPequena o bastante para uma pessoa ler tudo numa tarde e entender o que faz. Segurança que você não consegue ler é segurança que você aceita por fé.
- Zero dependênciasNada vindo do npm em tempo de execução. Não há cadeia de suprimentos para auditar além desta, e nada que possa mudar debaixo de você na semana que vem.
- Recusa o que não consegue checar por inteiroSem aprovação parcial, sem aviso que dá para ignorar. Toda falha volta como um motivo nomeado, que você registra e trata, nunca como um falso seco.
- Roda onde a verificação aconteceNode, Deno, Bun, navegadores, React Native e edge workers. O mesmo código em todos, sobre a criptografia padrão que já vem na plataforma.
- Testada de três formasTestes unitários, os vetores oficiais publicados junto com a RFC 9901, e testes de propriedade que jogam entrada aleatória e malformada nela procurando um caso que escape.
E quando esta é a ferramenta errada.
- Você precisa de carteira de motorista ISO 18013-5É outra codificação, CBOR e COSE, e outro padrão. Esta biblioteca fala SD-JWT, o formato que as carteiras europeias e as especificações OpenID usam. Problema parecido, trabalho diferente.
- Você quer uma carteira inteiraEla verifica e apresenta. Não guarda credenciais, não gerencia dispositivos e não cuida de cadastro. É uma peça, feita para morar dentro do seu produto.
- Você precisa de auditoria externa antes de um uso reguladoAinda não existe. Os testes e os vetores da RFC são públicos, e cada linha também, mas para uma implantação regulada vale reservar orçamento para a sua própria revisão.
Instalar
npm i qredential
Node 20 ou mais novo, todos os navegadores atuais, React Native. Cerca de 1.300 linhas de código sem dependência de runtime, exatamente para que ler antes de confiar seja realista.
Padrões, não invenções
- SD-JWT, RFC 9901divulgação seletiva e key binding, aninhada e recursiva, o mecanismo que a carteira de identidade europeia usa
- SD-JWT VCo formato da credencial
- Token Status Listrevogação que funciona a partir de uma cópia em cache
- base45, RFC 9285o envelope do QR, escolhido por compatibilidade de leitor
Não é mDL ISO 18013-5, que é CBOR e COSE em vez de JWT. Está no roteiro, e uma credencial que use algo não suportado é recusada em vez de entendida pela metade. Afirmar metade de um padrão de conformidade é pior que não afirmar nada.