Verify a download

Your router does this automatically. Do it by hand when you are flashing an image, restoring a device, or you simply do not want to take this server's word for anything.

1. Get the release public key

From the repository, or from a running router at /etc/gadnet/release-public-key-slhdsa.pem. Do not take it from the same place you took the file you are checking - a key and an artifact fetched from one compromised source verify against each other perfectly.

2. Verify the signature

openssl pkeyutl -verify -pubin \
    -inkey release-public-key-slhdsa.pem \
    -rawin -in <file> \
    -sigfile <file>.slhdsa.sig

Success prints Signature Verified Successfully. Anything else means stop. Requires OpenSSL 3.5 or newer for ML-DSA/SLH-DSA support.

3. Check the digest

sha256sum <file>

Compare against the sha256 field for that artifact in the manifest — the manifest you verified in step 2, not the one you just downloaded unverified. Signature first, digest second: a digest taken from an unverified document proves only that the file matches what the attacker intended.

Worked example: stable 0.1.0

curl -fsSLO https://updates.gadnet.pl/dist/stable/manifest.json
curl -fsSLO https://updates.gadnet.pl/dist/stable/manifest.json.slhdsa.sig

openssl pkeyutl -verify -pubin -inkey release-public-key-slhdsa.pem \
    -rawin -in manifest.json -sigfile manifest.json.slhdsa.sig

curl -fsSLO https://updates.gadnet.pl/dist/stable/bundles/gadnet-0.1.0.tar.gz
sha256sum gadnet-0.1.0.tar.gz
# expect: 0c586aa7c48cd1cf1faaf8d8b04206d41b47dc7591782615828b39e90fcf4eb7

Point a router at this server

# /etc/gadnet/system.conf
UPDATE_MANIFEST_URL=https://updates.gadnet.pl/v1/manifest.json

This setting is safe to expose to an operator. A hostile host can serve bytes but cannot produce a valid signature, so the worst outcome of a wrong URL is a failed update. HTTPS is still required: plain HTTP leaks which version a device runs and makes withholding an update harder to notice.

What verification does not tell you

A valid signature proves the release came from the GADNET release process. It does not prove the release is current. A server that keeps serving last month's signed manifest is performing a downgrade-by-withholding attack, and every signature still checks out. That is why devices record the manifest's generated timestamp and why the admin panel shows the last successful check - a check that stops succeeding is the signal.