Verify a release
Check that an nxdns release you downloaded is the one the project published, and that it arrived intact. Do it before you run the binary.
What a release contains
The release page has five files for each version:
| File | What it is |
|---|---|
nxdns-0.0.25-x86_64-linux-musl.tar.gz | nxdns for x86_64 Linux |
nxdns-0.0.25-aarch64-linux-musl.tar.gz | nxdns for 64-bit ARM Linux, such as a Raspberry Pi |
SHA256SUMS.txt | The SHA-256 of both tarballs and of IMAGE-DIGEST.txt |
SHA256SUMS.txt.asc | An OpenPGP signature over SHA256SUMS.txt |
IMAGE-DIGEST.txt | The container image of this version, pinned by digest |
The signature covers SHA256SUMS.txt, and SHA256SUMS.txt covers everything else. Someone who can replace a tarball on its way to you can replace the checksum file next to it, but cannot sign it.
1. Download
mkdir -p ~/nxdns-release && cd ~/nxdns-releaseBASE=https://git.mial.net/mokhtar/nxdns/releases/download/v0.0.25curl -fLO "$BASE/nxdns-0.0.25-x86_64-linux-musl.tar.gz"curl -fLO "$BASE/SHA256SUMS.txt"curl -fLO "$BASE/SHA256SUMS.txt.asc"curl -fLO "$BASE/IMAGE-DIGEST.txt"Take the aarch64 tarball instead for a Raspberry Pi.
2. Import the signing key
Releases are signed by a subkey of this key:
A2061F6AB24DF2C0E92346FD1509B54946D08A95curl -fsSL https://keys.openpgp.org/vks/v1/by-fingerprint/A2061F6AB24DF2C0E92346FD1509B54946D08A95 | gpg --importThe same key signs every commit and tag in the repository, so git verify-tag v0.0.25 in a clone confirms it independently.
3. Check the signature
gpg --verify SHA256SUMS.txt.asc SHA256SUMS.txtgpg: Signature made <date and time of the release>gpg: using EDDSA key 019D00DF8417EBFDA5471E5EF7319CC024FB5A96gpg: Good signature from "Mokhtar Mial (pc) <…>" [unknown]gpg: WARNING: This key is not certified with a trusted signature!gpg: There is no indication that the signature belongs to the owner.Primary key fingerprint: A206 1F6A B24D F2C0 E923 46FD 1509 B549 46D0 8A95 Subkey fingerprint: 019D 00DF 8417 EBFD A547 1E5E F731 9CC0 24FB 5A96Two things must hold: the output says Good signature, and the Primary key fingerprint is the fingerprint in step 2. GnuPG prints it in groups of four; this prints it unspaced for comparison:
gpg --verify SHA256SUMS.txt.asc SHA256SUMS.txt 2>&1 | sed -n 's/^Primary key fingerprint: //p' | tr -d ' 'Do not skip the fingerprint. gpg --verify accepts a good signature from any key in your keyring. The WARNING lines are normal: they only mean you have not told GnuPG that you trust the key.
4. Check the hashes
sha256sum -c --ignore-missing SHA256SUMS.txtnxdns-0.0.25-x86_64-linux-musl.tar.gz: OKIMAGE-DIGEST.txt: OK--ignore-missing skips the tarball you did not download. A file that does not match prints FAILED and sha256sum exits 1.
5. Look inside the tarball
tar -tvzf nxdns-0.0.25-x86_64-linux-musl.tar.gzdrwxr-xr-x 0/0 0 1970-01-01 01:00 nxdns-0.0.25-x86_64-linux-musl-rw-r--r-- 0/0 4229 1970-01-01 01:00 nxdns-0.0.25-x86_64-linux-musl/INSTALL.md-rw-r--r-- 0/0 14210 1970-01-01 01:00 nxdns-0.0.25-x86_64-linux-musl/LICENSE-rw-r--r-- 0/0 72967 1970-01-01 01:00 nxdns-0.0.25-x86_64-linux-musl/THIRD-PARTY-NOTICES-rwxr-xr-x 0/0 7257952 1970-01-01 01:00 nxdns-0.0.25-x86_64-linux-musl/nxdns-rw-r--r-- 0/0 31 1970-01-01 01:00 nxdns-0.0.25-x86_64-linux-musl/nxdns.conf-rw-r--r-- 0/0 1547 1970-01-01 01:00 nxdns-0.0.25-x86_64-linux-musl/nxdns.serviceExpect one directory and these six files, with no links and no path that starts with / or contains ... The sizes differ from release to release; the dates are always the epoch, shown in your time zone. Then:
tar -xzf nxdns-0.0.25-x86_64-linux-musl.tar.gz./nxdns-0.0.25-x86_64-linux-musl/nxdns versionThe first line it prints must be nxdns 0.0.25, the version you downloaded.
6. Verify the container image
IMAGE-DIGEST.txt names the image this version published, pinned by digest:
git.mial.net/mokhtar/nxdns:0.0.25@sha256:<64 hex digits>The signature you checked covers this line. A tag in a registry can be moved; a digest cannot. Pull the pinned reference:
docker pull "$(cat IMAGE-DIGEST.txt)"Or check that the tag still points at it, comparing the two lines it prints:
cut -d@ -f2 IMAGE-DIGEST.txtdocker buildx imagetools inspect git.mial.net/mokhtar/nxdns:0.0.25 --format '{{.Manifest.Digest}}'The image holds exactly linux/amd64 and linux/arm64, and its binary is the same file as the one in the x86_64 tarball:
docker create --name nxdns-verify "$(cat IMAGE-DIGEST.txt)"docker cp nxdns-verify:/nxdns ./nxdns-from-imagedocker rm nxdns-verifysha256sum ./nxdns-from-image ./nxdns-0.0.25-x86_64-linux-musl/nxdnsIf a check fails
Do not run the binary.
sha256sumsaysFAILEDafter a good signature: the download is damaged or was replaced. Delete it and download it again, over another network if you can.gpgsaysBAD signature:SHA256SUMS.txtand its signature do not belong together. Download both again; a signature left over from another version is the usual cause.gpgsaysNo public key: import the key from step 2.- The fingerprint does not match step 2: take this one seriously. Do not extract the tarball, and do not import other keys to make it pass.
What the signature does not prove
The signature proves the files came from the project’s release pipeline and that you have the bytes it uploaded. It does not prove that the binary was built from the published source: the machine that built it also signed it.
The release builds are reproducible, so you can check that yourself by building the same version from source and comparing hashes. It takes the exact toolchain the release used, named at the top of .gitea/workflows/gates.yml: ZIG_VERSION with its ZIG_TARBALL_SHA256, NODE_VERSION and NPM_VERSION. Use the official Zig tarball from ziglang.org, not a distribution’s package of the same version: a distribution build of Zig produces different machine code while printing the same version. The rebuild reproduces the release only on x86_64 Linux with glibc.
git clone https://git.mial.net/mokhtar/nxdnscd nxdnsgit checkout v0.0.25git verify-tag v0.0.25
ZIG_VERSION=$(sed -n 's/^ ZIG_VERSION: "\(.*\)"$/\1/p' .gitea/workflows/gates.yml)ZIG_TARBALL_SHA256=$(sed -n 's/^ ZIG_TARBALL_SHA256: "\(.*\)"$/\1/p' .gitea/workflows/gates.yml)curl -fsSL -o ../zig.tar.xz "https://ziglang.org/download/$ZIG_VERSION/zig-x86_64-linux-$ZIG_VERSION.tar.xz"(cd .. && echo "$ZIG_TARBALL_SHA256 zig.tar.xz" | sha256sum -c - && tar -xJf zig.tar.xz)ZIG="$(cd .. && pwd)/zig-x86_64-linux-$ZIG_VERSION/zig"Then build in a clean environment. A locale, a time zone or a umask picked up from your shell changes the output, and the two npm_config_* paths must not exist:
env -i PATH="$PATH" HOME="$HOME" LC_ALL=C LANG=C TZ=UTC SOURCE_DATE_EPOCH=0 CI=true npm_config_userconfig=/nonexistent/npmrc-user npm_config_globalconfig=/nonexistent/npmrc-global \ sh -c 'cd admin && umask 022 && npm ci && npm run build'
env -i PATH="$PATH" HOME="$HOME" LC_ALL=C LANG=C TZ=UTC SOURCE_DATE_EPOCH=0 CI=true npm_config_userconfig=/nonexistent/npmrc-user npm_config_globalconfig=/nonexistent/npmrc-global \ sh -c 'umask 022 && exec "'"$ZIG"'" build dist \ -Dversion-string=0.0.25 \ -Dadmin-dist=admin/dist -Doptimize=ReleaseSafe \ --cache-dir "$(mktemp -d)"'
sha256sum zig-out/dist/nxdns-0.0.25-*.tar.gzBoth hashes must match the tarball lines of the published SHA256SUMS.txt. If they do not, check the toolchain and the environment first: a distribution’s Zig, a different Node or npm, or a build outside env -i are the usual causes.