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.