Trustvice está em beta restrito. Ao se cadastrar ou entrar, você precisa informar a senha de beta tester que recebeu ou solicitar uma em preview@trustvice.com. Obrigado.
Carregando…
Engenharia
Verificação
Tutorial
Verifique um recibo offline, em umas quarenta linhas de código
Um exemplo prático que monta um verificador de recibos sem dependências com WebCrypto, o confere com um vetor de teste assinado, prova que ele rejeita recibos adulterados e depois leva o mesmo procedimento a Python e ao shell.
Por The Trustvice team··Leitura 5 min
Comece grátis
Trustvice 360
Declare as regras. Deixe cada execução ser verificada.
Agentes, evidências, governação, comércio e divulgação estão no Trustvice 360. Registe-se grátis e abra todas as suítes a partir de um só painel.
A forma mais forte de conferir um recibo Trustvice é com código que você controla, numa máquina que você controla, contra chaves que você mesmo fixou. Este post monta esse verificador do zero. No fim você terá um único módulo JavaScript sem dependências, um vetor de teste que prova que ele funciona e uma lista de casos que provam que ele falha quando deve.
Node 20 ou superior, ou qualquer runtime cujo crypto.subtle suporte Ed25519: navegadores atuais, Deno, Bun e Cloudflare Workers servem. Nenhum pacote a instalar. Nem conta na Trustvice: o vetor de teste abaixo é assinado por uma chave descartável gerada para este post.
Salve isto como receipt.json. É um recibo de teste assinado com essa chave descartável. O body é só ilustrativo; o que importa é que ele é assinado de verdade.
Para recibos reais, você troca keys.json pelo conjunto publicado em https://trustvice.com/.well-known/receipt-keys.json, de preferência uma cópia que você fixou. O procedimento não muda.
A assinatura cobre a forma canônica RFC 8785 do recibo sem o membro signature. Em JavaScript, JSON.stringify já produz a forma numérica do ECMAScript e o escape mínimo de strings. Falta ordenar as chaves dos objetos, recursivamente, por unidade de código UTF-16, que é a ordem padrão de Array.prototype.sort:
js
function canonicalize(value) {
if (Array.isArray(value)) return value.map(canonicalize);
if (value && typeof value === "object") {
return Object.fromEntries(
Object.keys(value).sort().map((key) => [key, canonicalize(value[key])]),
);
}
return value;
}
exportconst jcs = (value) => JSON.stringify(canonicalize(value));
Para o vetor de teste, a string canônica é uma linha sem espaços:
Repare na linha de desestruturação. Ela remove signature e mantém todo o resto, inclusive membros que este código nunca viu. É proposital: todo membro exceto signature é assinado, então um verificador que descarta um membro que não reconhece rejeitaria recibos válidos.
Junte os três trechos em verify-receipt.mjs. Esse é o verificador inteiro.
Um pequeno wrapper de linha de comando lê os arquivos e sai com 0 para um recibo válido e 1 caso contrário. Ele também aceita o wrapper { "receipt": … } que a API devolve:
O kid deriva da chave: é o base64url dos 16 primeiros bytes do SHA-256 dos 32 bytes crus de x. Para a chave de teste, o resultado é LfPAIisXFvNmFhD6vBQbBw. Recalculá-lo confirma que a entrada no seu arquivo de chaves é coerente:
js
exportasyncfunction keyIdOf(x) {
const digest = await crypto.subtle.digest("SHA-256", base64urlToBytes(x));
let bin = "";
for (const b ofnew Uint8Array(digest).slice(0, 16)) bin += String.fromCharCode(b);
return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}
O procedimento é o mesmo em todo lugar, mas duas linguagens pedem cuidado.
Python.json.dumps não é JCS: ele imprime floats inteiros como 1.0, preenche expoentes e escapa não ASCII por padrão. Um port em Python precisa de um pequeno serializador RFC 8785 que ordene chaves por unidade de código UTF-16, e pode verificar com Ed25519PublicKey do pacote cryptography.
Shell. Com jq e OpenSSL 3, jq -S -c 'del(.signature)' produz os bytes canônicos de recibos, porque seus bodies só contêm strings, inteiros, booleanos, null, arrays e objetos. Não é uma implementação JCS geral, mas para um recibo ou bate ou falha; nunca aprova um que não deveria. A chave pública vira um SubjectPublicKeyInfo DER com um cabeçalho fixo de 12 bytes antes dos 32 bytes crus, e openssl pkeyutl -verify -rawin faz o resto. O LibreSSL que vem no macOS não consegue; instale o OpenSSL 3.
Uma assinatura válida prova origem, integridade e um horário assinado: quem detém uma chave de assinatura da Trustvice assinou exatamente estes bytes, nada mudou desde então, e a Trustvice afirma o horário em issuedAt. Ela não prova que um fato que você forneceu é verdadeiro, e não contém seu conteúdo. Para mostrar que um documento corresponde a um recibo, gere você mesmo o hash do documento e compare. Tratamos desses limites em O que um recibo prova, e o que não prova.
Para uma checagem rápida sem escrever código, cole qualquer recibo em /verify. Ele roda o mesmo procedimento no seu navegador e nunca nos envia o recibo.