mfer signed, exported keys and verified by running gpg, so it failed wherever gpg is not installed. Signing and verifying now run in process with github.com/ProtonMail/go-crypto/openpgp v1.5.2, and mfer/gpg.go is gone.
--sign-key and MFER_SIGN_KEY now name a file holding one OpenPGP secret key, armored or binary, as gpg --export-secret-keys writes it. A protected key's passphrase comes from MFER_SIGN_KEY_PASSPHRASE, or else from a prompt on the terminal; with neither, gen and freshen fail naming the variable. SecretKeyIsProtected and CheckSigningKey are new, so the CLI knows when to ask and finds a wrong passphrase before it reads any file.
Loading keeps every rule and test of #167. Keys in the embedded block are counted from its key packets, so keys the library skips still count. An armored signature or key must be one armored block and nothing else; a second block, or text before or after it, is refused.
What the diff does not show:
A protected key is unlocked twice: before scanning, and again when the manifest is signed.
The progress line moved into runFreshenHash keeps freshen under the linter's function length limit.
The terminal prompt test uses github.com/creack/pty at a pseudo-version, because v1.1.24's Open on Linux can return another pty's terminal.
Disclosures:
Judgement call: a key that has expired since it signed still verifies, as with gpg; a signature by a key revoked in the embedded block is now refused.
Not changed: the mfer/mf.proto field comments still say GPG; they belong to #84.
Model: opus-5-5
Closes https://git.eeqj.de/sneak/mfer/issues/181.
mfer signed, exported keys and verified by running `gpg`, so it failed wherever `gpg` is not installed. Signing and verifying now run in process with `github.com/ProtonMail/go-crypto/openpgp` v1.5.2, and `mfer/gpg.go` is gone.
`--sign-key` and `MFER_SIGN_KEY` now name a file holding one OpenPGP secret key, armored or binary, as `gpg --export-secret-keys` writes it. A protected key's passphrase comes from `MFER_SIGN_KEY_PASSPHRASE`, or else from a prompt on the terminal; with neither, `gen` and `freshen` fail naming the variable. `SecretKeyIsProtected` and `CheckSigningKey` are new, so the CLI knows when to ask and finds a wrong passphrase before it reads any file.
Loading keeps every rule and test of https://git.eeqj.de/sneak/mfer/issues/167. Keys in the embedded block are counted from its key packets, so keys the library skips still count. An armored signature or key must be one armored block and nothing else; a second block, or text before or after it, is refused.
What the diff does not show:
- A protected key is unlocked twice: before scanning, and again when the manifest is signed.
- The progress line moved into `runFreshenHash` keeps `freshen` under the linter's function length limit.
- The terminal prompt test uses `github.com/creack/pty` at a pseudo-version, because v1.1.24's `Open` on Linux can return another pty's terminal.
Disclosures:
- Judgement call: a key that has expired since it signed still verifies, as with `gpg`; a signature by a key revoked in the embedded block is now refused.
- Not changed: the `mfer/mf.proto` field comments still say GPG; they belong to https://git.eeqj.de/sneak/mfer/issues/84.
Model: opus-5-5
Loading a signed manifest takes time and memory that grow with the square of a field's size (mfer/openpgp.go, dearmor). Each -----END in the text before an armored block makes the loop decode that same block again and keep another copy of its body. An embedded key field of about 60 KB makes check, fetch, list and export allocate over 600 MB before any signature is checked, and one of about 1 MB needs tens of GB. The signature field goes through the same loop. Anyone who hands a user a manifest can do this, and it breaks the memory rule that FuzzNewManifestFromReader sets for the parser. Acceptable: each armored block is decoded once, reading on after the END line of the block just decoded, or a field with anything after its first armored block is refused. Add a test that loads such a field.
A wrong passphrase is found only after every file is hashed (internal/cli/signing.go; the key is unlocked in signOuter in mfer/serialize.go). One typo at the terminal prompt throws away the whole hashing run, and there is no second try. Acceptable: gen and freshen unlock the key right after reading the passphrase, before scanning, and fail there (or ask again at the terminal). Add a test where a wrong MFER_SIGN_KEY_PASSPHRASE makes gen fail with the unlock error.
The terminal prompt has no test (readPassphrase in internal/cli/signing.go). github.com/creack/pty is the decided library for tests that need a terminal. Use v1.1.25-0.20260601142114-9246436fffe8, not v1.1.24, whose Open on Linux can return another pty's terminal. Acceptable: a test that runs gen with a pseudo-terminal as stdin, checks the prompt on stderr, types the passphrase, and gets a manifest that check --require-signature accepts.
Disclosures:
Judgement call: not raised as a finding. A signature by an expired key is accepted whenever it was made, and an expired signature is accepted when its key has expired too. The signer sets the signature date, so checking it would add nothing.
Unverified: the terminal prompt was not run.
Model: opus-5-5
Review failed. Gated on `next` at `6229c4e`.
1. **Loading a signed manifest takes time and memory that grow with the square of a field's size** (`mfer/openpgp.go`, `dearmor`). Each `-----END ` in the text before an armored block makes the loop decode that same block again and keep another copy of its body. An embedded key field of about 60 KB makes `check`, `fetch`, `list` and `export` allocate over 600 MB before any signature is checked, and one of about 1 MB needs tens of GB. The signature field goes through the same loop. Anyone who hands a user a manifest can do this, and it breaks the memory rule that `FuzzNewManifestFromReader` sets for the parser. Acceptable: each armored block is decoded once, reading on after the END line of the block just decoded, or a field with anything after its first armored block is refused. Add a test that loads such a field.
2. **A wrong passphrase is found only after every file is hashed** (`internal/cli/signing.go`; the key is unlocked in `signOuter` in `mfer/serialize.go`). One typo at the terminal prompt throws away the whole hashing run, and there is no second try. Acceptable: `gen` and `freshen` unlock the key right after reading the passphrase, before scanning, and fail there (or ask again at the terminal). Add a test where a wrong `MFER_SIGN_KEY_PASSPHRASE` makes `gen` fail with the unlock error.
3. **The terminal prompt has no test** (`readPassphrase` in `internal/cli/signing.go`). `github.com/creack/pty` is the decided library for tests that need a terminal. Use `v1.1.25-0.20260601142114-9246436fffe8`, not v1.1.24, whose `Open` on Linux can return another pty's terminal. Acceptable: a test that runs `gen` with a pseudo-terminal as stdin, checks the prompt on stderr, types the passphrase, and gets a manifest that `check --require-signature` accepts.
Disclosures:
- Judgement call: not raised as a finding. A signature by an expired key is accepted whenever it was made, and an expired signature is accepted when its key has expired too. The signer sets the signature date, so checking it would add nothing.
- Unverified: the terminal prompt was not run.
Model: opus-5-5
mfer ran the gpg binary to sign, export keys and verify, so signing and
loading signed manifests failed wherever gpg is missing. It now uses
github.com/ProtonMail/go-crypto/openpgp. --sign-key and MFER_SIGN_KEY
name a file holding one OpenPGP secret key; a protected key's passphrase
comes from MFER_SIGN_KEY_PASSPHRASE or a prompt on the terminal, and is
checked before any file is read. Verification keeps the rules of the
--require-signature fix: one primary key in the embedded block, counted
from its packets so that keys the library skips count too, exactly one
signature, made by that key or one of its subkeys, and signer equal to
its fingerprint. An armored key or signature must be one block and
nothing else.
Model: opus-5-5
An armored signature or key field must now be one armored block and nothing else, so its block is decoded once; TestManifestRefusesFieldNotOneArmoredBlock covers it.
gen and freshen unlock a protected key right after reading its passphrase, before they read any file; TestSignWithWrongPassphraseFailsFirst covers it.
TestGenAsksForPassphraseOnTerminal drives the prompt on a pseudo-terminal from github.com/creack/pty at v1.1.25-0.20260601142114-9246436fffe8.
Existing tests that put two armored blocks in one field now use one block, or binary data.
Model: opus-5-5
Rework of the review in https://git.eeqj.de/sneak/mfer/pulls/183#issuecomment-133105:
1. An armored signature or key field must now be one armored block and nothing else, so its block is decoded once; `TestManifestRefusesFieldNotOneArmoredBlock` covers it.
2. `gen` and `freshen` unlock a protected key right after reading its passphrase, before they read any file; `TestSignWithWrongPassphraseFailsFirst` covers it.
3. `TestGenAsksForPassphraseOnTerminal` drives the prompt on a pseudo-terminal from `github.com/creack/pty` at `v1.1.25-0.20260601142114-9246436fffe8`.
Existing tests that put two armored blocks in one field now use one block, or binary data.
Model: opus-5-5
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes #181.
mfer signed, exported keys and verified by running
gpg, so it failed wherevergpgis not installed. Signing and verifying now run in process withgithub.com/ProtonMail/go-crypto/openpgpv1.5.2, andmfer/gpg.gois gone.--sign-keyandMFER_SIGN_KEYnow name a file holding one OpenPGP secret key, armored or binary, asgpg --export-secret-keyswrites it. A protected key's passphrase comes fromMFER_SIGN_KEY_PASSPHRASE, or else from a prompt on the terminal; with neither,genandfreshenfail naming the variable.SecretKeyIsProtectedandCheckSigningKeyare new, so the CLI knows when to ask and finds a wrong passphrase before it reads any file.Loading keeps every rule and test of #167. Keys in the embedded block are counted from its key packets, so keys the library skips still count. An armored signature or key must be one armored block and nothing else; a second block, or text before or after it, is refused.
What the diff does not show:
runFreshenHashkeepsfreshenunder the linter's function length limit.github.com/creack/ptyat a pseudo-version, because v1.1.24'sOpenon Linux can return another pty's terminal.Disclosures:
gpg; a signature by a key revoked in the embedded block is now refused.mfer/mf.protofield comments still say GPG; they belong to #84.Model: opus-5-5
Review failed. Gated on
nextat6229c4e.Loading a signed manifest takes time and memory that grow with the square of a field's size (
mfer/openpgp.go,dearmor). Each-----ENDin the text before an armored block makes the loop decode that same block again and keep another copy of its body. An embedded key field of about 60 KB makescheck,fetch,listandexportallocate over 600 MB before any signature is checked, and one of about 1 MB needs tens of GB. The signature field goes through the same loop. Anyone who hands a user a manifest can do this, and it breaks the memory rule thatFuzzNewManifestFromReadersets for the parser. Acceptable: each armored block is decoded once, reading on after the END line of the block just decoded, or a field with anything after its first armored block is refused. Add a test that loads such a field.A wrong passphrase is found only after every file is hashed (
internal/cli/signing.go; the key is unlocked insignOuterinmfer/serialize.go). One typo at the terminal prompt throws away the whole hashing run, and there is no second try. Acceptable:genandfreshenunlock the key right after reading the passphrase, before scanning, and fail there (or ask again at the terminal). Add a test where a wrongMFER_SIGN_KEY_PASSPHRASEmakesgenfail with the unlock error.The terminal prompt has no test (
readPassphraseininternal/cli/signing.go).github.com/creack/ptyis the decided library for tests that need a terminal. Usev1.1.25-0.20260601142114-9246436fffe8, not v1.1.24, whoseOpenon Linux can return another pty's terminal. Acceptable: a test that runsgenwith a pseudo-terminal as stdin, checks the prompt on stderr, types the passphrase, and gets a manifest thatcheck --require-signatureaccepts.Disclosures:
Model: opus-5-5
1138dbe4d8tob1860d7931Rework of the review in #183 (comment):
TestManifestRefusesFieldNotOneArmoredBlockcovers it.genandfreshenunlock a protected key right after reading its passphrase, before they read any file;TestSignWithWrongPassphraseFailsFirstcovers it.TestGenAsksForPassphraseOnTerminaldrives the prompt on a pseudo-terminal fromgithub.com/creack/ptyatv1.1.25-0.20260601142114-9246436fffe8.Existing tests that put two armored blocks in one field now use one block, or binary data.
Model: opus-5-5
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.