feat(packaging/bazzite): the sysext feed is signed, and the client refuses one that isn't
`punktfunk-sysext` checked the SHA256 of every image it downloaded, which sounds like verification but isn't: SHA256SUMS lives on the same registry as the images it describes, so anything able to replace an image could replace its checksum in the same request. The checksum only ever proved the download wasn't corrupted. Each feed now carries SHA256SUMS.asc, a detached OpenPGP signature over the manifest, and the client verifies it before believing a line of it. The key is packages@unom.io (AF245C506F4E4763) — the same one that already signs our RPMs, so boxes have one key to trust and we have one key to rotate. Its public half is baked into the script rather than fetched from the feed, because a key you fetch from the thing you're authenticating authenticates nothing; gpg (present on Bazzite) does the verifying against a throwaway keyring holding only that key, so "good signature" and "signed by us" are the same statement. Rollout: stable feeds only publish on a tag, so a `--seal` mode re-signs an existing manifest without rebuilding an image, and every rpm.yml run seals the OTHER channel of its Fedora major too. Canary pushes are frequent, so all live feeds seal within a day of this landing and a key rotation propagates without republishing anything. Until a feed is sealed the client refuses it and says so, naming PUNKTFUNK_SYSEXT_ALLOW_UNSIGNED=1 as the informed way through. Two things testing changed. The baked-key fingerprint check compared against an empty string — the armor block is a single-quoted shell literal, so the extracted range carried `FEED_KEY='` on its first line and gpg saw no armor at all; every publish would have "mismatched" and, on a tag, failed the release. And `status` captured fetch_manifest's stderr but only printed it on failure, swallowing the ALLOW_UNSIGNED warning precisely when someone was running unverified. Verified end to end on Bazzite 44 against a file:// feed with a throwaway key: unsigned feed refused; ALLOW_UNSIGNED=1 proceeds and says so; wrong signer rejected; tampered manifest rejected; good signature accepted; `update` exits 1 before touching anything on a bad feed. Publisher side: signs when the baked key matches, refuses on mismatch, refuses with no key, and its signature round-trips through the real client. shellcheck clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+23
@@ -56,6 +56,29 @@ https://docs.punktfunk.unom.io/docs/security):
|
||||
|
||||
If you're unsure whether something is in scope, report it anyway — we'd rather hear about it.
|
||||
|
||||
## Verifying what you downloaded
|
||||
|
||||
Every distribution path is authenticated. Nothing below needs an account or a network round trip to
|
||||
us beyond the download itself.
|
||||
|
||||
- **Release-page downloads** (DMG, MSIX, setup.exe, APK, decky zip, .deb/.rpm) each ship a
|
||||
`<file>.sha256` next to them. In your download directory:
|
||||
`sha256sum -c punktfunk-1.2.3.dmg.sha256` (macOS: `shasum -a 256 -c …`).
|
||||
- **RPMs** from the dnf repo are OpenPGP-signed with `packages@unom.io` (`AF245C506F4E4763`); the
|
||||
repo file in [`packaging/rpm/README.md`](packaging/rpm/README.md) sets `gpgcheck=1`, so dnf
|
||||
checks every package for you. `rpmkeys --checksig` on a downloaded RPM verifies it by hand.
|
||||
- **The Bazzite sysext feed** carries a detached signature over its `SHA256SUMS`, from that same
|
||||
key. `punktfunk-sysext` verifies it before installing and refuses a feed it cannot verify — the
|
||||
public key is baked into the script rather than fetched from the feed.
|
||||
- **Windows installers and MSIX packages** are Authenticode-signed; a release build that cannot
|
||||
reach its code-signing certificate fails to build rather than falling back to a self-signed one.
|
||||
Check with `Get-AuthenticodeSignature punktfunk-host-setup-1.2.3.exe`.
|
||||
|
||||
A checksum on its own only tells you the download wasn't corrupted in transit — it says nothing
|
||||
about who produced the file, since anyone able to replace an artifact can replace its checksum.
|
||||
Where that distinction matters (the update feeds, the package repos), the checksums are covered by
|
||||
a signature. If a signature check fails, please don't work around it; report it.
|
||||
|
||||
## Safe harbor
|
||||
|
||||
We consider good-faith security research that follows this policy to be authorized, and we won't
|
||||
|
||||
Reference in New Issue
Block a user