-
Notifications
You must be signed in to change notification settings - Fork 103
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for keyserver preferences and preferred keyserver (closes #…
…206) (#232) * Parse keyserver prefs and preferred keyserver subpackets; update refs to RFC 9580 * Serialise keyserver preferences and preferred keyserver; add tests
- Loading branch information
1 parent
2add693
commit 7852179
Showing
2 changed files
with
155 additions
and
44 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,14 +5,14 @@ | |
package packet | ||
|
||
import ( | ||
"math/big" | ||
"bytes" | ||
"crypto" | ||
"crypto/dsa" | ||
"encoding/asn1" | ||
"encoding/binary" | ||
"hash" | ||
"io" | ||
"math/big" | ||
"strconv" | ||
"time" | ||
|
||
|
@@ -26,7 +26,8 @@ import ( | |
) | ||
|
||
const ( | ||
// See RFC 4880, section 5.2.3.21 for details. | ||
// First octet of key flags. | ||
// See RFC 9580, section 5.2.3.29 for details. | ||
KeyFlagCertify = 1 << iota | ||
KeyFlagSign | ||
KeyFlagEncryptCommunications | ||
|
@@ -37,16 +38,29 @@ const ( | |
KeyFlagGroupKey | ||
) | ||
|
||
const ( | ||
// First octet of keyserver preference flags. | ||
// See RFC 9580, section 5.2.3.25 for details. | ||
_ = 1 << iota | ||
_ | ||
_ | ||
_ | ||
_ | ||
_ | ||
_ | ||
KeyserverPrefNoModify | ||
) | ||
|
||
const SaltNotationName = "[email protected]" | ||
|
||
// Signature represents a signature. See RFC 4880, section 5.2. | ||
// Signature represents a signature. See RFC 9580, section 5.2. | ||
type Signature struct { | ||
Version int | ||
SigType SignatureType | ||
PubKeyAlgo PublicKeyAlgorithm | ||
Hash crypto.Hash | ||
// salt contains a random salt value for v6 signatures | ||
// See RFC the crypto refresh Section 5.2.3. | ||
// See RFC 9580 Section 5.2.4. | ||
salt []byte | ||
|
||
// HashSuffix is extra data that is hashed in after the signed data. | ||
|
@@ -87,27 +101,37 @@ type Signature struct { | |
// TrustLevel and TrustAmount can be set by the signer to assert that | ||
// the key is not only valid but also trustworthy at the specified | ||
// level. | ||
// See RFC 4880, section 5.2.3.13 for details. | ||
// See RFC 9580, section 5.2.3.21 for details. | ||
TrustLevel TrustLevel | ||
TrustAmount TrustAmount | ||
|
||
// TrustRegularExpression can be used in conjunction with trust Signature | ||
// packets to limit the scope of the trust that is extended. | ||
// See RFC 4880, section 5.2.3.14 for details. | ||
// See RFC 9580, section 5.2.3.22 for details. | ||
TrustRegularExpression *string | ||
|
||
// KeyserverPrefsValid is set if any keyserver preferences were given. See RFC 9580, section | ||
// 5.2.3.25 for details. | ||
KeyserverPrefsValid bool | ||
KeyserverPrefNoModify bool | ||
|
||
// PreferredKeyserver can be set to a URI where the latest version of the | ||
// key that this signature is made over can be found. See RFC 9580, section | ||
// 5.2.3.26 for details. | ||
PreferredKeyserver string | ||
|
||
// PolicyURI can be set to the URI of a document that describes the | ||
// policy under which the signature was issued. See RFC 4880, section | ||
// 5.2.3.20 for details. | ||
// policy under which the signature was issued. See RFC 9580, section | ||
// 5.2.3.28 for details. | ||
PolicyURI string | ||
|
||
// FlagsValid is set if any flags were given. See RFC 4880, section | ||
// 5.2.3.21 for details. | ||
// FlagsValid is set if any flags were given. See RFC 9580, section | ||
// 5.2.3.29 for details. | ||
FlagsValid bool | ||
FlagCertify, FlagSign, FlagEncryptCommunications, FlagEncryptStorage, FlagSplitKey, FlagAuthenticate, FlagGroupKey bool | ||
|
||
// RevocationReason is set if this signature has been revoked. | ||
// See RFC 4880, section 5.2.3.23 for details. | ||
// See RFC 9580, section 5.2.3.31 for details. | ||
RevocationReason *ReasonForRevocation | ||
RevocationReasonText string | ||
|
||
|
@@ -147,7 +171,7 @@ func (sig *Signature) Salt() []byte { | |
} | ||
|
||
func (sig *Signature) parse(r io.Reader) (err error) { | ||
// RFC 4880, section 5.2.3 | ||
// RFC 9580, section 5.2.3 | ||
var buf [7]byte | ||
_, err = readFull(r, buf[:1]) | ||
if err != nil { | ||
|
@@ -319,7 +343,7 @@ func (sig *Signature) parse(r io.Reader) (err error) { | |
} | ||
|
||
// parseSignatureSubpackets parses subpackets of the main signature packet. See | ||
// RFC 4880, section 5.2.3.1. | ||
// RFC 9580, section 5.2.3.1. | ||
func parseSignatureSubpackets(sig *Signature, subpackets []byte, isHashed bool) (err error) { | ||
for len(subpackets) > 0 { | ||
subpackets, err = parseSignatureSubpacket(sig, subpackets, isHashed) | ||
|
@@ -349,6 +373,8 @@ const ( | |
notationDataSubpacket signatureSubpacketType = 20 | ||
prefHashAlgosSubpacket signatureSubpacketType = 21 | ||
prefCompressionSubpacket signatureSubpacketType = 22 | ||
keyserverPrefsSubpacket signatureSubpacketType = 23 | ||
prefKeyserverSubpacket signatureSubpacketType = 24 | ||
primaryUserIdSubpacket signatureSubpacketType = 25 | ||
policyUriSubpacket signatureSubpacketType = 26 | ||
keyFlagsSubpacket signatureSubpacketType = 27 | ||
|
@@ -363,7 +389,7 @@ const ( | |
|
||
// parseSignatureSubpacket parses a single subpacket. len(subpacket) is >= 1. | ||
func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (rest []byte, err error) { | ||
// RFC 4880, section 5.2.3.1 | ||
// RFC 9580, section 5.2.3.7 | ||
var ( | ||
length uint32 | ||
packetType signatureSubpacketType | ||
|
@@ -421,7 +447,7 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r | |
t := binary.BigEndian.Uint32(subpacket) | ||
sig.CreationTime = time.Unix(int64(t), 0) | ||
case signatureExpirationSubpacket: | ||
// Signature expiration time, section 5.2.3.10 | ||
// Signature expiration time, section 5.2.3.18 | ||
if len(subpacket) != 4 { | ||
err = errors.StructuralError("expiration subpacket with bad length") | ||
return | ||
|
@@ -438,15 +464,15 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r | |
err = errors.StructuralError("trust subpacket with bad length") | ||
return | ||
} | ||
// Trust level and amount, section 5.2.3.13 | ||
// Trust level and amount, section 5.2.3.21 | ||
sig.TrustLevel = TrustLevel(subpacket[0]) | ||
sig.TrustAmount = TrustAmount(subpacket[1]) | ||
case regularExpressionSubpacket: | ||
if len(subpacket) == 0 { | ||
err = errors.StructuralError("regexp subpacket with bad length") | ||
return | ||
} | ||
// Trust regular expression, section 5.2.3.14 | ||
// Trust regular expression, section 5.2.3.22 | ||
// RFC specifies the string should be null-terminated; remove a null byte from the end | ||
if subpacket[len(subpacket)-1] != 0x00 { | ||
err = errors.StructuralError("expected regular expression to be null-terminated") | ||
|
@@ -455,19 +481,19 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r | |
trustRegularExpression := string(subpacket[:len(subpacket)-1]) | ||
sig.TrustRegularExpression = &trustRegularExpression | ||
case keyExpirationSubpacket: | ||
// Key expiration time, section 5.2.3.6 | ||
// Key expiration time, section 5.2.3.13 | ||
if len(subpacket) != 4 { | ||
err = errors.StructuralError("key expiration subpacket with bad length") | ||
return | ||
} | ||
sig.KeyLifetimeSecs = new(uint32) | ||
*sig.KeyLifetimeSecs = binary.BigEndian.Uint32(subpacket) | ||
case prefSymmetricAlgosSubpacket: | ||
// Preferred symmetric algorithms, section 5.2.3.7 | ||
// Preferred symmetric algorithms, section 5.2.3.14 | ||
sig.PreferredSymmetric = make([]byte, len(subpacket)) | ||
copy(sig.PreferredSymmetric, subpacket) | ||
case issuerSubpacket: | ||
// Issuer, section 5.2.3.5 | ||
// Issuer, section 5.2.3.12 | ||
if sig.Version > 4 && isHashed { | ||
err = errors.StructuralError("issuer subpacket found in v6 key") | ||
return | ||
|
@@ -481,7 +507,7 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r | |
*sig.IssuerKeyId = binary.BigEndian.Uint64(subpacket) | ||
} | ||
case notationDataSubpacket: | ||
// Notation data, section 5.2.3.16 | ||
// Notation data, section 5.2.3.24 | ||
if len(subpacket) < 8 { | ||
err = errors.StructuralError("notation data subpacket with bad length") | ||
return | ||
|
@@ -503,15 +529,27 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r | |
|
||
sig.Notations = append(sig.Notations, ¬ation) | ||
case prefHashAlgosSubpacket: | ||
// Preferred hash algorithms, section 5.2.3.8 | ||
// Preferred hash algorithms, section 5.2.3.16 | ||
sig.PreferredHash = make([]byte, len(subpacket)) | ||
copy(sig.PreferredHash, subpacket) | ||
case prefCompressionSubpacket: | ||
// Preferred compression algorithms, section 5.2.3.9 | ||
// Preferred compression algorithms, section 5.2.3.17 | ||
sig.PreferredCompression = make([]byte, len(subpacket)) | ||
copy(sig.PreferredCompression, subpacket) | ||
case keyserverPrefsSubpacket: | ||
// Keyserver preferences, section 5.2.3.25 | ||
sig.KeyserverPrefsValid = true | ||
if len(subpacket) == 0 { | ||
return | ||
} | ||
if subpacket[0]&KeyserverPrefNoModify != 0 { | ||
sig.KeyserverPrefNoModify = true | ||
} | ||
case prefKeyserverSubpacket: | ||
// Preferred keyserver, section 5.2.3.26 | ||
sig.PreferredKeyserver = string(subpacket) | ||
case primaryUserIdSubpacket: | ||
// Primary User ID, section 5.2.3.19 | ||
// Primary User ID, section 5.2.3.27 | ||
if len(subpacket) != 1 { | ||
err = errors.StructuralError("primary user id subpacket with bad length") | ||
return | ||
|
@@ -521,7 +559,7 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r | |
*sig.IsPrimaryId = true | ||
} | ||
case keyFlagsSubpacket: | ||
// Key flags, section 5.2.3.21 | ||
// Key flags, section 5.2.3.29 | ||
sig.FlagsValid = true | ||
if len(subpacket) == 0 { | ||
return | ||
|
@@ -551,7 +589,7 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r | |
userId := string(subpacket) | ||
sig.SignerUserId = &userId | ||
case reasonForRevocationSubpacket: | ||
// Reason For Revocation, section 5.2.3.23 | ||
// Reason For Revocation, section 5.2.3.31 | ||
if len(subpacket) == 0 { | ||
err = errors.StructuralError("empty revocation reason subpacket") | ||
return | ||
|
@@ -560,7 +598,7 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r | |
*sig.RevocationReason = NewReasonForRevocation(subpacket[0]) | ||
sig.RevocationReasonText = string(subpacket[1:]) | ||
case featuresSubpacket: | ||
// Features subpacket, section 5.2.3.24 specifies a very general | ||
// Features subpacket, section 5.2.3.32 specifies a very general | ||
// mechanism for OpenPGP implementations to signal support for new | ||
// features. | ||
if len(subpacket) > 0 { | ||
|
@@ -574,24 +612,21 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r | |
} | ||
case embeddedSignatureSubpacket: | ||
// Only usage is in signatures that cross-certify | ||
// signing subkeys. section 5.2.3.26 describes the | ||
// signing subkeys. section 5.2.3.34 describes the | ||
// format, with its usage described in section 11.1 | ||
if sig.EmbeddedSignature != nil { | ||
err = errors.StructuralError("Cannot have multiple embedded signatures") | ||
return | ||
} | ||
sig.EmbeddedSignature = new(Signature) | ||
// Embedded signatures are required to be v4 signatures see | ||
// section 12.1. However, we only parse v4 signatures in this | ||
// file anyway. | ||
if err := sig.EmbeddedSignature.parse(bytes.NewBuffer(subpacket)); err != nil { | ||
return nil, err | ||
} | ||
if sigType := sig.EmbeddedSignature.SigType; sigType != SigTypePrimaryKeyBinding { | ||
return nil, errors.StructuralError("cross-signature has unexpected type " + strconv.Itoa(int(sigType))) | ||
} | ||
case policyUriSubpacket: | ||
// Policy URI, section 5.2.3.20 | ||
// Policy URI, section 5.2.3.28 | ||
sig.PolicyURI = string(subpacket) | ||
case issuerFingerprintSubpacket: | ||
if len(subpacket) == 0 { | ||
|
@@ -611,8 +646,7 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r | |
*sig.IssuerKeyId = binary.BigEndian.Uint64(subpacket[13:21]) | ||
} | ||
case intendedRecipientSubpacket: | ||
// Intended Recipient Fingerprint | ||
// https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh#name-intended-recipient-fingerpr | ||
// Intended Recipient Fingerprint, section 5.2.3.36 | ||
if len(subpacket) < 1 { | ||
return nil, errors.StructuralError("invalid intended recipient fingerpring length") | ||
} | ||
|
@@ -624,8 +658,7 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r | |
copy(fingerprint, subpacket[1:]) | ||
sig.IntendedRecipients = append(sig.IntendedRecipients, &Recipient{int(version), fingerprint}) | ||
case prefCipherSuitesSubpacket: | ||
// Preferred AEAD cipher suites | ||
// See https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#name-preferred-aead-ciphersuites | ||
// Preferred AEAD cipher suites, section 5.2.3.15 | ||
if len(subpacket)%2 != 0 { | ||
err = errors.StructuralError("invalid aead cipher suite length") | ||
return | ||
|
@@ -676,7 +709,7 @@ func (sig *Signature) CheckKeyIdOrFingerprintExplicit(fingerprint []byte, keyId | |
|
||
// serializeSubpacketLength marshals the given length into to. | ||
func serializeSubpacketLength(to []byte, length int) int { | ||
// RFC 4880, Section 4.2.2. | ||
// RFC 9580, Section 4.2.1. | ||
if length < 192 { | ||
to[0] = byte(length) | ||
return 1 | ||
|
@@ -819,7 +852,7 @@ func (sig *Signature) signPrepareHash(h hash.Hash) (digest []byte, err error) { | |
// The created hash object initially hashes a randomly generated salt | ||
// as required by v6 signatures. The generated salt is stored in sig. If the signature is not v6, | ||
// the method returns an empty hash object. | ||
// See RFC the crypto refresh Section 3.2.4. | ||
// See RFC 9580 Section 5.2.4. | ||
func (sig *Signature) PrepareSign(config *Config) (hash.Hash, error) { | ||
if !sig.Hash.Available() { | ||
return nil, errors.UnsupportedError("hash function") | ||
|
@@ -843,7 +876,7 @@ func (sig *Signature) PrepareSign(config *Config) (hash.Hash, error) { | |
// If the signature is not v6, the method ignores the salt. | ||
// Use PrepareSign whenever possible instead of generating and | ||
// hashing the salt externally. | ||
// See RFC the crypto refresh Section 3.2.4. | ||
// See RFC 9580 Section 5.2.4. | ||
func (sig *Signature) SetSalt(salt []byte) error { | ||
if sig.Version == 6 { | ||
expectedSaltLength, err := SaltLengthForHash(sig.Hash) | ||
|
@@ -861,7 +894,7 @@ func (sig *Signature) SetSalt(salt []byte) error { | |
// PrepareVerify must be called to create a hash object before verifying v6 signatures. | ||
// The created hash object initially hashes the internally stored salt. | ||
// If the signature is not v6, the method returns an empty hash object. | ||
// See crypto refresh Section 3.2.4. | ||
// See RFC 9580 Section 5.2.4. | ||
func (sig *Signature) PrepareVerify() (hash.Hash, error) { | ||
if !sig.Hash.Available() { | ||
return nil, errors.UnsupportedError("hash function") | ||
|
@@ -1276,6 +1309,19 @@ func (sig *Signature) buildSubpackets(issuer PublicKey) (subpackets []outputSubp | |
if len(sig.PreferredCompression) > 0 { | ||
subpackets = append(subpackets, outputSubpacket{true, prefCompressionSubpacket, false, sig.PreferredCompression}) | ||
} | ||
// Keyserver Preferences | ||
// Keyserver preferences may only appear in self-signatures or certification signatures. | ||
if sig.KeyserverPrefsValid { | ||
var prefs byte | ||
if sig.KeyserverPrefNoModify { | ||
prefs |= KeyserverPrefNoModify | ||
} | ||
subpackets = append(subpackets, outputSubpacket{true, keyserverPrefsSubpacket, false, []byte{prefs}}) | ||
} | ||
// Preferred Keyserver | ||
if len(sig.PreferredKeyserver) > 0 { | ||
subpackets = append(subpackets, outputSubpacket{true, prefKeyserverSubpacket, false, []uint8(sig.PreferredKeyserver)}) | ||
} | ||
// Primary User ID | ||
if sig.IsPrimaryId != nil && *sig.IsPrimaryId { | ||
subpackets = append(subpackets, outputSubpacket{true, primaryUserIdSubpacket, false, []byte{1}}) | ||
|
@@ -1316,7 +1362,7 @@ func (sig *Signature) buildSubpackets(issuer PublicKey) (subpackets []outputSubp | |
subpackets = append(subpackets, outputSubpacket{true, signerUserIdSubpacket, false, []byte(*sig.SignerUserId)}) | ||
} | ||
// Reason for Revocation | ||
// Revocation reason appears only in revocation signatures and is serialized as per section 5.2.3.23. | ||
// Revocation reason appears only in revocation signatures and is serialized as per section 5.2.3.31. | ||
if sig.RevocationReason != nil { | ||
subpackets = append(subpackets, outputSubpacket{true, reasonForRevocationSubpacket, true, | ||
append([]uint8{uint8(*sig.RevocationReason)}, []uint8(sig.RevocationReasonText)...)}) | ||
|
@@ -1333,7 +1379,7 @@ func (sig *Signature) buildSubpackets(issuer PublicKey) (subpackets []outputSubp | |
subpackets = append(subpackets, outputSubpacket{true, featuresSubpacket, false, []byte{features}}) | ||
} | ||
// Embedded Signature | ||
// EmbeddedSignature appears only in subkeys capable of signing and is serialized as per section 5.2.3.26. | ||
// EmbeddedSignature appears only in subkeys capable of signing and is serialized as per section 5.2.3.34. | ||
if sig.EmbeddedSignature != nil { | ||
var buf bytes.Buffer | ||
err = sig.EmbeddedSignature.serializeBody(&buf) | ||
|
@@ -1418,7 +1464,7 @@ func (sig *Signature) AddMetadataToHashSuffix() { | |
|
||
// SaltLengthForHash selects the required salt length for the given hash algorithm, | ||
// as per Table 23 (Hash algorithm registry) of the crypto refresh. | ||
// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh#section-9.5|Crypto Refresh Section 9.5. | ||
// See RFC 9580 Section 9.5. | ||
func SaltLengthForHash(hash crypto.Hash) (int, error) { | ||
switch hash { | ||
case crypto.SHA256, crypto.SHA224, crypto.SHA3_256: | ||
|
@@ -1434,7 +1480,7 @@ func SaltLengthForHash(hash crypto.Hash) (int, error) { | |
|
||
// SignatureSaltForHash generates a random signature salt | ||
// with the length for the given hash algorithm. | ||
// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh#section-9.5|Crypto Refresh Section 9.5. | ||
// See RFC 9580 Section 9.5. | ||
func SignatureSaltForHash(hash crypto.Hash, randReader io.Reader) ([]byte, error) { | ||
saltLength, err := SaltLengthForHash(hash) | ||
if err != nil { | ||
|
Oops, something went wrong.