Make the package signature verification actually work - #39
Open
WhiteStarSupreme wants to merge 4 commits into
Open
Make the package signature verification actually work#39WhiteStarSupreme wants to merge 4 commits into
WhiteStarSupreme wants to merge 4 commits into
Conversation
The Docker image has not been buildable since v1.4.1. Adding packages/CHECKSUMS.sha256.asc on 2026-07-15 switched on a verification branch in the Dockerfile that had never once executed, and it failed immediately: python:3.12.11-slim ships no gpg. Building v1.4.1 today reproduces it exactly. The native path was broken too, for a different reason. bootstrap-linux.sh and bootstrap-macos.sh call gpg --verify without ever importing the public key, so on any machine that does not already have it in its keyring gpg exits 2 with "No public key" and the script aborts the install. That is almost every user. The public key is now in the repo. The copy published on the docs site could not be imported at all: it was missing the blank line the armor format requires between the header and the base64 body, so gpg rejected it as an invalid armor header. That is fixed here. The Dockerfile installs gnupg, imports the key, verifies, then purges gnupg in the same layer so it does not reach the final image. The bootstrap scripts import the bundled key, and when gpg is absent they now warn loudly and fall through to the SHA-256 check instead of aborting: this tool is meant to run on air-gapped machines where installing gnupg may be impossible, and refusing to install there would fail in exactly the case it exists for. Verified: the build prints Good signature from S.A.S.U. KATVIO (Fractum Release Signing), the image is 260 MB with gpg absent from it, an encrypt/decrypt round trip through the image with --network=none returns an identical SHA-256, two shares below the threshold still fail, and the suite is 107 tests, 8/8 files.
Two findings from a full read of src/, both reproduced before fixing. Extracted share archives were left on disk whenever anything went wrong. The rmtree sat at the end of the loop body, so it was skipped both by the `raise` on an invalid share and by the `continue` on an unreadable one. A single failed decrypt left /tmp/fractum_share_* holding share_N.txt and, when the archive had been built with --bundle-encrypted, the ciphertext beside it. Accumulate enough of those and the leftovers alone are enough to reconstruct. Cleanup moves into a finally. Nothing set permissions on any output, so the process umask decided them: the share archives, the .enc file and the reconstructed plaintext all landed as -rw-r--r--, readable by every user on the machine. They are now created 0600. Verified: a share with corrupt JSON now leaves zero temp directories, the three outputs are -rw-------, the encrypt/decrypt round trip through the official image with --network=none returns an identical SHA-256, and the suite is still 107 tests, 8/8 files.
Neither is fixed here, and the second is the reason why. secure_clear cannot clear a bytes or a str, both immutable: it copies, wipes the copy, and leaves the original in memory. The four call sites in commands.py pass bytearray(share_bytes), so today they wipe nothing at all. Making that real is a two-line change, decode the share into a bytearray and clear the buffer itself. Tried it: the wipe becomes effective and 14 tests fail with "MAC check failed", because the shares are read again after being cleared. The inert call was load-bearing. Fixing it properly means moving the clear past the last use of each share, which is a change to key lifetime and belongs in its own commit with its own tests, not in a release fix. The trap is written into the docstring so the next person does not repeat the attempt blind. get_enhanced_random_bytes mixes no entropy of its own any more, it just calls os.urandom, which is the correct behaviour. Renaming it touches src/utils exports and about fifty call sites across seven test files for no behavioural gain, so the name stays and the docstring now says what it does and why.
The release workflow zips a source package, and the file list did not include fractum-signing-key.asc. Without this the published archive would be worse than before: its Dockerfile now does COPY fractum-signing-key.asc, so the build would fail on a missing file, and bootstrap would fall through to gpg without a key and abort on "No public key". Verified by zipping the same file list by hand, unpacking it into a clean directory and building the image from it: the build succeeds. Note for whoever publishes: the release notes tell users to fetch the key from https://fractum.katvio.com/fractum-signing-key.asc. That URL answers 200 today but serves a file gpg refuses with "invalid armor header". fractum-frontend#17 fixes it, and should be merged before the tag or those instructions stay broken.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Cyril asked me to test main before tagging v1.4.2. The tests and the round trip pass, but the release does not build. This fixes that.
The Docker image has not been buildable since v1.4.1
Adding
packages/CHECKSUMS.sha256.ascon 2026-07-15 switched on a verification branch in the Dockerfile that had never once executed, because it is guarded by[ -f CHECKSUMS.sha256.asc ]. It failed immediately:python:3.12.11-slimships nogpg.Building the v1.4.1 tag today reproduces it exactly, so this is inherited, not introduced by 1.4.2.
The native path was broken too
bootstrap-linux.shandbootstrap-macos.shcallgpg --verifywithout ever importing the public key. On any machine that does not already have it in its keyring, gpg exits 2 withNo public keyand the script runs its|| exit 1, aborting the install. That is almost every user.The published key could not be imported
fractum-signing-key.ascon the docs site is missing the blank line the OpenPGP armor format requires between the header line and the base64 body, so gpg rejects it withinvalid armor header. Anyone following the verification guide would have failed. The corrected key is added to this repo.What this changes
The Dockerfile installs gnupg, imports the key, verifies, then purges gnupg in the same layer so it never reaches the final image.
The bootstrap scripts import the bundled key. When gpg is absent they now warn explicitly about what was not checked and fall through to the SHA-256 verification instead of aborting. That is a deliberate call: this tool is built to run on air-gapped machines where installing gnupg may be impossible, and refusing to install there would fail in precisely the case the tool exists for. The hashes are still checked in every path.
Verified
gpg: Good signature from "S.A.S.U. KATVIO (Fractum Release Signing) <contact@katvio.com>"gpgis absent from it--network=none, using shares 1, 3 and 5: identical SHA-256verify_packagesexits 0 both with gpg (signature valid) and without it (warning, hashes checked)