Verify signed files and releases
Check a download against the signature beside it and the card that holds the key: in the browser, with the API, or in a GitHub workflow.
On veil.rip/verify, choose File or release and drop the file with its signature, or paste the signature. Your browser hashes the file in pieces and never uploads it. It reads the signature, finds the card that holds the key, checks the card against the public log and its witnesses, and then says who signed, with which key, and whether that key is on the card today.
It reads four kinds of signature. An SSH signature made for files with ssh-keygen -Y sign -n file. An OpenPGP detached signature (.asc or .sig) or a clear-signed text. A .minisig file, in its prehashed or legacy form. A Veil block whose message is sha256:<hex> <name>, or a checksum list.
ssh-keygen -Y sign -n file -f ~/.ssh/id_ed25519 veil-1.2.tar.gz
gpg --armor --detach-sign veil-1.2.tar.gz
minisign -Sm veil-1.2.tar.gzAn OpenPGP signature states a date, written by the signer’s clock. The page checks the key’s state at that date and says the date comes from the signer, since someone with a stolen key can write any date. SSH and minisign signatures carry no date, so for a key the card has removed the page can’t say whether it signed before the removal.
A release with a checksum list#
Sign one SHA256SUMS or SHA512SUMS file instead of every download. Drop the list, its signature and the files you downloaded: the page checks the signature once, then each file against its line, and lists the files that match, the ones that don’t, the ones the list doesn’t name and the lines with no file.
sha256sum veil-1.2.tar.gz veil-1.2.zip > SHA256SUMS
gpg --armor --detach-sign SHA256SUMSSign with your card#
On your own card, next to Sign a message, Sign a file hashes a file in your browser and signs sha256:<hex> <name> with this browser’s device key, as a Veil block. Sign a release does the same for several files: it writes their SHA256SUMS list and signs it as a block. Publish the block beside the files.
Link a release to its check#
A release page can link to veil.rip/verify?u=<card>&sha256=<hex>. The page opens with the card and the expected hash filled in, and the visitor drops the file, which stays in their browser. veil.rip/verify#b=<block> opens a Veil block the same way, and no server sees what follows the #.
Ask the API#
POST /v1/verify takes the signature with the file’s hashes, which you compute: the server never sees the file. It checks SSH signatures, prehashed minisign signatures and Veil blocks from a hash, and every format over a checksum list you send. An OpenPGP detached signature or a legacy minisign signature of the file itself answers 422 file_needed, because only the bytes can check it.
sha512=$(sha512sum veil-1.2.tar.gz | cut -d' ' -f1)
jq -n --rawfile sig veil-1.2.tar.gz.sig --arg h "$sha512" \
'{signature: $sig, sha512: $h, name: "veil-1.2.tar.gz"}' \
| curl -fsS https://veil.rip/v1/verify --json @-In a GitHub workflow#
The action in integrations/github-action-veil-verify checks a release’s files and signatures in a job and fails it on a bad signature, a file that doesn’t match its list or a file without a signature. It reads the card’s keys from GET /u/{card}/keys.txt and verifies on the runner, so files of any size work.