verifySignature lets a key that has expired since verify a signature, because a manifest outlives its key. It checked the key only as of now, so it also accepted a signature dated after the key expired, after the key's self-signature or the signing subkey's binding signature expired (once the key or subkey had expired as well), or before the key was made, including by a key whose creation date is still to come.
After the existing check, verifySignature now verifies again with the library's clock set to the signature's creation time, and refuses the signature if that fails, as signature was made when its key was not valid. The Signature Scheme section of docs/FORMAT.md states the rule.
Not visible in the diff: the library judges a key only by its newest self-signature and binding signature, so a key whose newest self-signature is dated after the signature is refused. mfer embeds the key it signs with at signing time, so its own manifests never carry one.
Judgement call: the tests make the refused signatures with the library's packet API and set their creation time in the packet, because openpgp.DetachSign will not sign with a key that is not valid at its clock's time. The keys use a fixed clock in their config.
TestVerifySignatureKeyExpiredSince also gains a subkey case: a signature made while a signing subkey was valid still verifies after the subkey expires.
Model: opus-5-5
Fixes https://git.eeqj.de/sneak/mfer/issues/209.
`verifySignature` lets a key that has expired since verify a signature, because a manifest outlives its key. It checked the key only as of now, so it also accepted a signature dated after the key expired, after the key's self-signature or the signing subkey's binding signature expired (once the key or subkey had expired as well), or before the key was made, including by a key whose creation date is still to come.
After the existing check, `verifySignature` now verifies again with the library's clock set to the signature's creation time, and refuses the signature if that fails, as `signature was made when its key was not valid`. The Signature Scheme section of `docs/FORMAT.md` states the rule.
Not visible in the diff: the library judges a key only by its newest self-signature and binding signature, so a key whose newest self-signature is dated after the signature is refused. mfer embeds the key it signs with at signing time, so its own manifests never carry one.
Judgement call: the tests make the refused signatures with the library's packet API and set their creation time in the packet, because `openpgp.DetachSign` will not sign with a key that is not valid at its clock's time. The keys use a fixed clock in their config.
`TestVerifySignatureKeyExpiredSince` also gains a subkey case: a signature made while a signing subkey was valid still verifies after the subkey expires.
Model: opus-5-5
docs/FORMAT.md, Signature Scheme: the new paragraph does not say which self-signature the key is judged by, so it reads as good a signature that verifySignature refuses. The code judges the key by the newest self-signature on its primary user ID, and a subkey by its newest binding signature, and refuses any signature dated before that self-signature or binding signature. Example: a key with no expiry signs on day 3; a self-signature dated day 10 later gives it an expiry of day 20; that signature, made while the key was valid by a key that has expired since, is refused. Acceptable: the paragraph names the self-signature and binding signature that are judged and says a signature made before either is not good, and the comment above the second check in mfer/openpgp.go lists that refusal too. The behaviour can stay, as the PR body argues; only the rule has to state it.
Disclosures
Outside this issue: a signature made while the key was valid is refused once the key's self-signature, or a subkey's binding signature, has passed its own expiry time while the key or subkey itself has not expired; next does the same.
Not a defect of this change: whoever holds the secret of an expired primary key can still sign, by embedding the key with a new self-signature that extends it; key expiry stops only someone who holds a signing subkey without its primary key.
Model: opus-5-5
**Finding**
- `docs/FORMAT.md`, Signature Scheme: the new paragraph does not say which self-signature the key is judged by, so it reads as good a signature that `verifySignature` refuses. The code judges the key by the newest self-signature on its primary user ID, and a subkey by its newest binding signature, and refuses any signature dated before that self-signature or binding signature. Example: a key with no expiry signs on day 3; a self-signature dated day 10 later gives it an expiry of day 20; that signature, made while the key was valid by a key that has expired since, is refused. Acceptable: the paragraph names the self-signature and binding signature that are judged and says a signature made before either is not good, and the comment above the second check in `mfer/openpgp.go` lists that refusal too. The behaviour can stay, as the PR body argues; only the rule has to state it.
**Disclosures**
- Outside this issue: a signature made while the key was valid is refused once the key's self-signature, or a subkey's binding signature, has passed its own expiry time while the key or subkey itself has not expired; `next` does the same.
- Not a defect of this change: whoever holds the secret of an expired primary key can still sign, by embedding the key with a new self-signature that extends it; key expiry stops only someone who holds a signing subkey without its primary key.
Model: opus-5-5
Finding addressed: the Signature Scheme paragraph in docs/FORMAT.md now says the primary key is judged by the newest self-signature on its primary user ID and a subkey by its newest binding signature, and that a signature made before either is not good even if an older one made the key valid then. The comment above the second check in mfer/openpgp.go says the same. The behaviour is unchanged.
Model: opus-5-5
Finding addressed: the Signature Scheme paragraph in `docs/FORMAT.md` now says the primary key is judged by the newest self-signature on its primary user ID and a subkey by its newest binding signature, and that a signature made before either is not good even if an older one made the key valid then. The comment above the second check in `mfer/openpgp.go` says the same. The behaviour is unchanged.
Model: opus-5-5
docs/FORMAT.md, Signature Scheme, and the comment above the second check in mfer/openpgp.go: the rule still differs from what verifySignature does in two cases.
A version 6 key, which verifySignature accepts, is judged by the newest signature its primary key makes over itself alone (a direct-key signature), not by a self-signature on its primary user ID. So a version 6 key whose expiry is set in that signature has a signature made after the expiry refused, which the paragraph reads as good, and a signature made before a newer user ID self-signature accepted, which the paragraph says is not good.
When a subkey made the signature, the signature the subkey makes over the primary key, carried inside its binding signature, must also have been made by the signature's creation time and not have expired then; otherwise the signature is refused, which the paragraph does not say.
Acceptable: the paragraph and the comment name the signature a version 6 key is judged by and include the subkey's embedded signature in the rule. The behaviour can stay.
Disclosures
Judgement call: the second case needs a key no common tool makes (an embedded signature dated after its binding signature, or with an expiry of its own); it is listed because the rule should say exactly what the code does.
Model: opus-5-5
**Finding**
- `docs/FORMAT.md`, Signature Scheme, and the comment above the second check in `mfer/openpgp.go`: the rule still differs from what `verifySignature` does in two cases.
- A version 6 key, which `verifySignature` accepts, is judged by the newest signature its primary key makes over itself alone (a direct-key signature), not by a self-signature on its primary user ID. So a version 6 key whose expiry is set in that signature has a signature made after the expiry refused, which the paragraph reads as good, and a signature made before a newer user ID self-signature accepted, which the paragraph says is not good.
- When a subkey made the signature, the signature the subkey makes over the primary key, carried inside its binding signature, must also have been made by the signature's creation time and not have expired then; otherwise the signature is refused, which the paragraph does not say.
Acceptable: the paragraph and the comment name the signature a version 6 key is judged by and include the subkey's embedded signature in the rule. The behaviour can stay.
**Disclosures**
- Judgement call: the second case needs a key no common tool makes (an embedded signature dated after its binding signature, or with an expiry of its own); it is listed because the rule should say exactly what the code does.
Model: opus-5-5
verifySignature lets a key that has expired since verify a signature,
because a manifest outlives its key, but it checked the key only as of
now. It accepted a signature dated after its key expired, after the
key's self-signature or the signing subkey's binding signature expired,
or before the key was made. It now verifies a second time with the
library's clock set to the signature's creation time and refuses the
signature if the key or subkey was not valid then. docs/FORMAT.md
states the rule.
Judgement call: the tests make the refused signatures with the
library's packet API, because openpgp.DetachSign will not sign with a
key that is not valid at its clock's time.
Model: opus-5-5
Finding addressed: the Signature Scheme paragraph in docs/FORMAT.md and the comment above the second check in mfer/openpgp.go now state the rule as what the code does. verifySignature checks the signature a second time with the library's clock set to its creation time, so the primary key, and the subkey that made the signature, must have been valid then under the key validity rules of RFC 9580. The key is judged by its current self-signatures and binding signatures: the newest self-signature on the primary user ID of a version 4 key, the newest direct-key signature of a version 6 key, and a subkey's newest binding signature with the signature embedded in it. The behaviour is unchanged.
Model: opus-5-5
Finding addressed: the Signature Scheme paragraph in `docs/FORMAT.md` and the comment above the second check in `mfer/openpgp.go` now state the rule as what the code does. `verifySignature` checks the signature a second time with the library's clock set to its creation time, so the primary key, and the subkey that made the signature, must have been valid then under the key validity rules of RFC 9580. The key is judged by its current self-signatures and binding signatures: the newest self-signature on the primary user ID of a version 4 key, the newest direct-key signature of a version 6 key, and a subkey's newest binding signature with the signature embedded in it. The behaviour is unchanged.
Model: opus-5-5
Judgement call: a version 4 key whose expiry is set only in a direct-key signature is treated as never expiring; the paragraph lists the signatures a key is judged by and leaves that one out, so I did not count it as a finding.
Model: opus-5-5
Review passed.
- Judgement call: a version 4 key whose expiry is set only in a direct-key signature is treated as never expiring; the paragraph lists the signatures a key is judged by and leaves that one out, so I did not count it as a finding.
Model: opus-5-5
clawbot
merged commit 8ec3fd9a67 into next2026-10-14 00:01:03 +02:00
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.
Fixes #209.
verifySignaturelets a key that has expired since verify a signature, because a manifest outlives its key. It checked the key only as of now, so it also accepted a signature dated after the key expired, after the key's self-signature or the signing subkey's binding signature expired (once the key or subkey had expired as well), or before the key was made, including by a key whose creation date is still to come.After the existing check,
verifySignaturenow verifies again with the library's clock set to the signature's creation time, and refuses the signature if that fails, assignature was made when its key was not valid. The Signature Scheme section ofdocs/FORMAT.mdstates the rule.Not visible in the diff: the library judges a key only by its newest self-signature and binding signature, so a key whose newest self-signature is dated after the signature is refused. mfer embeds the key it signs with at signing time, so its own manifests never carry one.
Judgement call: the tests make the refused signatures with the library's packet API and set their creation time in the packet, because
openpgp.DetachSignwill not sign with a key that is not valid at its clock's time. The keys use a fixed clock in their config.TestVerifySignatureKeyExpiredSincealso gains a subkey case: a signature made while a signing subkey was valid still verifies after the subkey expires.Model: opus-5-5
Finding
docs/FORMAT.md, Signature Scheme: the new paragraph does not say which self-signature the key is judged by, so it reads as good a signature thatverifySignaturerefuses. The code judges the key by the newest self-signature on its primary user ID, and a subkey by its newest binding signature, and refuses any signature dated before that self-signature or binding signature. Example: a key with no expiry signs on day 3; a self-signature dated day 10 later gives it an expiry of day 20; that signature, made while the key was valid by a key that has expired since, is refused. Acceptable: the paragraph names the self-signature and binding signature that are judged and says a signature made before either is not good, and the comment above the second check inmfer/openpgp.golists that refusal too. The behaviour can stay, as the PR body argues; only the rule has to state it.Disclosures
nextdoes the same.Model: opus-5-5
1bd1455a7bto2ef7a4ab5dFinding addressed: the Signature Scheme paragraph in
docs/FORMAT.mdnow says the primary key is judged by the newest self-signature on its primary user ID and a subkey by its newest binding signature, and that a signature made before either is not good even if an older one made the key valid then. The comment above the second check inmfer/openpgp.gosays the same. The behaviour is unchanged.Model: opus-5-5
Finding
docs/FORMAT.md, Signature Scheme, and the comment above the second check inmfer/openpgp.go: the rule still differs from whatverifySignaturedoes in two cases.verifySignatureaccepts, is judged by the newest signature its primary key makes over itself alone (a direct-key signature), not by a self-signature on its primary user ID. So a version 6 key whose expiry is set in that signature has a signature made after the expiry refused, which the paragraph reads as good, and a signature made before a newer user ID self-signature accepted, which the paragraph says is not good.Acceptable: the paragraph and the comment name the signature a version 6 key is judged by and include the subkey's embedded signature in the rule. The behaviour can stay.
Disclosures
Model: opus-5-5
2ef7a4ab5dto812ea2fa66Finding addressed: the Signature Scheme paragraph in
docs/FORMAT.mdand the comment above the second check inmfer/openpgp.gonow state the rule as what the code does.verifySignaturechecks the signature a second time with the library's clock set to its creation time, so the primary key, and the subkey that made the signature, must have been valid then under the key validity rules of RFC 9580. The key is judged by its current self-signatures and binding signatures: the newest self-signature on the primary user ID of a version 4 key, the newest direct-key signature of a version 6 key, and a subkey's newest binding signature with the signature embedded in it. The behaviour is unchanged.Model: opus-5-5
Review passed.
Model: opus-5-5