1 Commits
Author SHA1 Message Date
sneak 2ef7a4ab5d Refuse a signature made when its key was not valid (closes #209)
check / check (push) Failing after 6s
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
2026-10-13 19:57:35 +00:00
2 changed files with 18 additions and 21 deletions
+10 -12
View File
@@ -174,18 +174,16 @@ also refuses a manifest whose `signingPubKey` holds a DSA key or subkey, or any
secret key or subkey, since checking the self-signatures of a DSA key or the secret key or subkey, since checking the self-signatures of a DSA key or the
numbers of a secret key can take hours when those numbers are very large. numbers of a secret key can take hours when those numbers are very large.
A signature is good only if the primary key, and the subkey that made the A signature is good only if the key that made it was valid at the signature's
signature, were valid at the signature's creation time under the key validity creation time. The primary key is judged by the newest self-signature on its
rules of RFC 9580. The key is judged by its current self-signatures and binding primary user ID, and a subkey by its newest binding signature. By the
signatures: for a version 4 key the newest self-signature on its primary user signature's creation time the primary key and that self-signature must have been
ID, for a version 6 key its newest direct-key signature, and for a subkey its made and neither may have expired, and when a subkey made the signature the same
newest binding signature with the signature embedded in it. So a signature made holds of the subkey and its binding signature. A signature made before that
before the newest of those is not good, even if an older one made the key valid self-signature or binding signature is not good, even if an older one made the
at that time. The reference implementation checks this by verifying the key valid at that time. A key that has expired since does not make the signature
signature a second time with its clock set to the signature's creation time. A bad, because a manifest outlives its key. A signature past its own expiry time
key that has expired since does not make the signature bad, because a manifest is not good, whether or not the key has expired too.
outlives its key. A signature past its own expiry time is not good, whether or
not the key has expired too.
## Deterministic Serialization ## Deterministic Serialization
+8 -9
View File
@@ -284,15 +284,14 @@ func verifySignature(data, signature, pubKey []byte) (string, error) {
return "", fmt.Errorf("verify signature: %w", err) return "", fmt.Errorf("verify signature: %w", err)
} }
// The check above judges the key as of now. Checking the signature a // The library checks whether the key is valid at the time its config
// second time with the library's clock set to its creation time // gives, which above is now. Verifying again as of the signature's
// requires the primary key, and the subkey that made the signature, to // creation time refuses a signature made before the key or subkey that
// have been valid then under the key validity rules of RFC 9580. The // made it was made, after it expired, or after its self-signature or
// key is judged by its current self-signatures and binding signatures: // binding signature expired. The library judges the primary key by the
// for a version 4 key the newest self-signature on its primary user // newest self-signature on its primary user ID and a subkey by its
// ID, for a version 6 key its newest direct-key signature, and for a // newest binding signature, so this also refuses a signature made
// subkey its newest binding signature with the signature embedded in // before either of those, even if an older one was valid then.
// it. So a signature made before the newest of those is refused.
signedAt := &packet.Config{ signedAt := &packet.Config{
Time: func() time.Time { return sig.CreationTime }, Time: func() time.Time { return sig.CreationTime },
} }