184 lines
7.9 KiB
Markdown
184 lines
7.9 KiB
Markdown
# 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](https://git.mial.net/mokhtar/nxdns/releases) has five files for each version:
|
|
|
|
| File | What it is |
|
|
| --- | --- |
|
|
| `nxdns-<version>-x86_64-linux-musl.tar.gz` | nxdns for x86_64 Linux |
|
|
| `nxdns-<version>-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
|
|
|
|
```sh
|
|
mkdir -p ~/nxdns-release && cd ~/nxdns-release
|
|
BASE=https://git.mial.net/mokhtar/nxdns/releases/download/v<version>
|
|
curl -fLO "$BASE/nxdns-<version>-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:
|
|
|
|
```
|
|
A2061F6AB24DF2C0E92346FD1509B54946D08A95
|
|
```
|
|
|
|
```sh
|
|
curl -fsSL https://keys.openpgp.org/vks/v1/by-fingerprint/A2061F6AB24DF2C0E92346FD1509B54946D08A95 |
|
|
gpg --import
|
|
```
|
|
|
|
The same key signs every commit and tag in the repository, so `git verify-tag v<version>` in a clone confirms it independently.
|
|
|
|
## 3. Check the signature
|
|
|
|
```sh
|
|
gpg --verify SHA256SUMS.txt.asc SHA256SUMS.txt
|
|
```
|
|
|
|
```
|
|
gpg: Signature made <date and time of the release>
|
|
gpg: using EDDSA key 019D00DF8417EBFDA5471E5EF7319CC024FB5A96
|
|
gpg: 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 5A96
|
|
```
|
|
|
|
Two 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:
|
|
|
|
```sh
|
|
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
|
|
|
|
```sh
|
|
sha256sum -c --ignore-missing SHA256SUMS.txt
|
|
```
|
|
|
|
```
|
|
nxdns-<version>-x86_64-linux-musl.tar.gz: OK
|
|
IMAGE-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
|
|
|
|
```sh
|
|
tar -tvzf nxdns-<version>-x86_64-linux-musl.tar.gz
|
|
```
|
|
|
|
```
|
|
drwxr-xr-x 0/0 0 1970-01-01 01:00 nxdns-<version>-x86_64-linux-musl
|
|
-rw-r--r-- 0/0 4229 1970-01-01 01:00 nxdns-<version>-x86_64-linux-musl/INSTALL.md
|
|
-rw-r--r-- 0/0 14210 1970-01-01 01:00 nxdns-<version>-x86_64-linux-musl/LICENSE
|
|
-rw-r--r-- 0/0 72967 1970-01-01 01:00 nxdns-<version>-x86_64-linux-musl/THIRD-PARTY-NOTICES
|
|
-rwxr-xr-x 0/0 7257952 1970-01-01 01:00 nxdns-<version>-x86_64-linux-musl/nxdns
|
|
-rw-r--r-- 0/0 31 1970-01-01 01:00 nxdns-<version>-x86_64-linux-musl/nxdns.conf
|
|
-rw-r--r-- 0/0 1547 1970-01-01 01:00 nxdns-<version>-x86_64-linux-musl/nxdns.service
|
|
```
|
|
|
|
Expect 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:
|
|
|
|
```sh
|
|
tar -xzf nxdns-<version>-x86_64-linux-musl.tar.gz
|
|
./nxdns-<version>-x86_64-linux-musl/nxdns version
|
|
```
|
|
|
|
The first line it prints must be `nxdns <version>`, 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:<version>@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:
|
|
|
|
```sh
|
|
docker pull "$(cat IMAGE-DIGEST.txt)"
|
|
```
|
|
|
|
Or check that the tag still points at it, comparing the two lines it prints:
|
|
|
|
```sh
|
|
cut -d@ -f2 IMAGE-DIGEST.txt
|
|
docker buildx imagetools inspect git.mial.net/mokhtar/nxdns:<version> --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:
|
|
|
|
```sh
|
|
docker create --name nxdns-verify "$(cat IMAGE-DIGEST.txt)"
|
|
docker cp nxdns-verify:/nxdns ./nxdns-from-image
|
|
docker rm nxdns-verify
|
|
sha256sum ./nxdns-from-image ./nxdns-<version>-x86_64-linux-musl/nxdns
|
|
```
|
|
|
|
## If a check fails
|
|
|
|
Do not run the binary.
|
|
|
|
- `sha256sum` says `FAILED` after a good signature: the download is damaged or was replaced. Delete it and download it again, over another network if you can.
|
|
- `gpg` says `BAD signature`: `SHA256SUMS.txt` and its signature do not belong together. Download both again; a signature left over from another version is the usual cause.
|
|
- `gpg` says `No 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.
|
|
|
|
```sh
|
|
git clone https://git.mial.net/mokhtar/nxdns
|
|
cd nxdns
|
|
git checkout v<version>
|
|
git verify-tag v<version>
|
|
|
|
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:
|
|
|
|
```sh
|
|
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=<version> \
|
|
-Dadmin-dist=admin/dist -Doptimize=ReleaseSafe \
|
|
--cache-dir "$(mktemp -d)"'
|
|
|
|
sha256sum zig-out/dist/nxdns-<version>-*.tar.gz
|
|
```
|
|
|
|
Both 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.
|