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