From 0eb9fa52d6811c203153992e0a5e0c1e9d1790ae Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Tue, 11 Apr 2023 11:29:29 +0200 Subject: [PATCH 001/116] Add support for v6 public key and secret key packets Updates the parsing and serialization methods to support v6 keys. Adds the AEAD s2k type for secret key encryption but does not implement the feature yet. --- openpgp/packet/private_key.go | 54 ++++++++++++++++++++++++++++++----- openpgp/packet/public_key.go | 25 +++++++++++----- 2 files changed, 65 insertions(+), 14 deletions(-) diff --git a/openpgp/packet/private_key.go b/openpgp/packet/private_key.go index 2fc438643..998ced5a8 100644 --- a/openpgp/packet/private_key.go +++ b/openpgp/packet/private_key.go @@ -12,6 +12,7 @@ import ( "crypto/rand" "crypto/rsa" "crypto/sha1" + "fmt" "io" "io/ioutil" "math/big" @@ -42,7 +43,7 @@ type PrivateKey struct { iv []byte // Type of encryption of the S2K packet - // Allowed values are 0 (Not encrypted), 254 (SHA1), or + // Allowed values are 0 (Not encrypted), 253 AEAD, 254 (SHA1), or // 255 (2-byte checksum) s2kType S2KType // Full parameters of the S2K packet @@ -55,6 +56,8 @@ type S2KType uint8 const ( // S2KNON unencrypt S2KNON S2KType = 0 + // S2KAEAD use authenticated encryption + S2KAEAD S2KType = 253 // S2KSHA1 sha1 sum check S2KSHA1 S2KType = 254 // S2KCHECKSUM sum check @@ -152,6 +155,7 @@ func (pk *PrivateKey) parse(r io.Reader) (err error) { return } v5 := pk.PublicKey.Version == 5 + v6 := pk.PublicKey.Version == 6 var buf [1]byte _, err = readFull(r, buf[:]) @@ -160,7 +164,7 @@ func (pk *PrivateKey) parse(r io.Reader) (err error) { } pk.s2kType = S2KType(buf[0]) var optCount [1]byte - if v5 { + if v5 || (v6 && pk.s2kType != S2KNON) { if _, err = readFull(r, optCount[:]); err != nil { return } @@ -170,9 +174,9 @@ func (pk *PrivateKey) parse(r io.Reader) (err error) { case S2KNON: pk.s2k = nil pk.Encrypted = false - case S2KSHA1, S2KCHECKSUM: - if v5 && pk.s2kType == S2KCHECKSUM { - return errors.StructuralError("wrong s2k identifier for version 5") + case S2KSHA1, S2KCHECKSUM, S2KAEAD: + if (v5 || v6) && pk.s2kType == S2KCHECKSUM { + return errors.StructuralError(fmt.Sprintf("wrong s2k identifier for version %d", pk.Version)) } _, err = readFull(r, buf[:]) if err != nil { @@ -182,6 +186,26 @@ func (pk *PrivateKey) parse(r io.Reader) (err error) { if pk.cipher != 0 && !pk.cipher.IsSupported() { return errors.UnsupportedError("unsupported cipher function in private key") } + // [Optional] If string-to-key usage octet was 253, + // a one-octet AEAD algorithm. + if pk.s2kType == S2KAEAD { + _, err = readFull(r, buf[:]) + if err != nil { + return + } + // TODO: Set aead fields accordingly + } + + // [Optional] Only for a version 6 packet, + // and if string-to-key usage octet was 255, 254, or 253, + // an one-octet count of the following field. + if v6 { + _, err = readFull(r, buf[:]) + if err != nil { + return + } + } + pk.s2kParams, err = s2k.ParseIntoParams(r) if err != nil { return @@ -203,6 +227,7 @@ func (pk *PrivateKey) parse(r io.Reader) (err error) { if pk.Encrypted { blockSize := pk.cipher.blockSize() + // TODO: if pk.s2kType == S2KAEAD use aead nonce size here if blockSize == 0 { return errors.UnsupportedError("unsupported cipher in private key: " + strconv.Itoa(int(pk.cipher))) } @@ -278,19 +303,34 @@ func (pk *PrivateKey) Serialize(w io.Writer) (err error) { return } + + optional := bytes.NewBuffer(nil) if pk.Encrypted || pk.Dummy() { optional.Write([]byte{uint8(pk.cipher)}) - if err := pk.s2kParams.Serialize(optional); err != nil { + + if pk.s2kType == S2KAEAD { + // Write a one-octet AEAD algorithm. + } + + s2kBuffer := bytes.NewBuffer(nil) + if err := pk.s2kParams.Serialize(s2kBuffer); err != nil { return err } + if pk.Version == 6 { + optional.Write([]byte{uint8(s2kBuffer.Len())}) + } + io.Copy(optional, s2kBuffer) + if pk.Encrypted { + // TODO: different field for AEAD? optional.Write(pk.iv) } } - if pk.Version == 5 { + if pk.Version == 5 || (pk.Version == 6 && pk.s2kType != S2KNON) { contents.Write([]byte{uint8(optional.Len())}) } + io.Copy(contents, optional) if !pk.Dummy() { diff --git a/openpgp/packet/public_key.go b/openpgp/packet/public_key.go index 3402b8c14..1cc3dbe7f 100644 --- a/openpgp/packet/public_key.go +++ b/openpgp/packet/public_key.go @@ -61,6 +61,14 @@ func (pk *PublicKey) UpgradeToV5() { pk.setFingerprintAndKeyId() } +// UpgradeToV6 updates the version of the key to v6, and updates all necessary +// fields. +func (pk *PublicKey) UpgradeToV6() { + pk.Version = 6 + pk.setFingerprintAndKeyId() +} + + // signingKey provides a convenient abstraction over signature verification // for v3 and v4 public keys. type signingKey interface { @@ -181,12 +189,14 @@ func (pk *PublicKey) parse(r io.Reader) (err error) { if err != nil { return } - if buf[0] != 4 && buf[0] != 5 { + if buf[0] != 4 && buf[0] != 5 && buf[0] != 6 { return errors.UnsupportedError("public key version " + strconv.Itoa(int(buf[0]))) } pk.Version = int(buf[0]) - if pk.Version == 5 { + if pk.Version >= 5 { + // Read the four-octet scalar octet count + // The count is not used in this implementation var n [4]byte _, err = readFull(r, n[:]) if err != nil { @@ -195,6 +205,7 @@ func (pk *PublicKey) parse(r io.Reader) (err error) { } pk.CreationTime = time.Unix(int64(uint32(buf[1])<<24|uint32(buf[2])<<16|uint32(buf[3])<<8|uint32(buf[4])), 0) pk.PubKeyAlgo = PublicKeyAlgorithm(buf[5]) + // Ignore four-ocet switch pk.PubKeyAlgo { case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly: err = pk.parseRSA(r) @@ -221,7 +232,7 @@ func (pk *PublicKey) parse(r io.Reader) (err error) { func (pk *PublicKey) setFingerprintAndKeyId() { // RFC 4880, section 12.2 - if pk.Version == 5 { + if pk.Version >= 5 { fingerprint := sha256.New() pk.SerializeForHash(fingerprint) pk.Fingerprint = make([]byte, 32) @@ -447,10 +458,10 @@ func (pk *PublicKey) SerializeForHash(w io.Writer) error { // RFC 4880, section 5.2.4. func (pk *PublicKey) SerializeSignaturePrefix(w io.Writer) { var pLength = pk.algorithmSpecificByteCount() - if pk.Version == 5 { + if pk.Version >= 5 { pLength += 10 // version, timestamp (4), algorithm, key octet count (4). w.Write([]byte{ - 0x9A, + 0x95 + byte(pk.Version), byte(pLength >> 24), byte(pLength >> 16), byte(pLength >> 8), @@ -465,7 +476,7 @@ func (pk *PublicKey) SerializeSignaturePrefix(w io.Writer) { func (pk *PublicKey) Serialize(w io.Writer) (err error) { length := 6 // 6 byte header length += pk.algorithmSpecificByteCount() - if pk.Version == 5 { + if pk.Version >= 5 { length += 4 // octet key count } packetType := packetTypePublicKey @@ -522,7 +533,7 @@ func (pk *PublicKey) serializeWithoutHeaders(w io.Writer) (err error) { return } - if pk.Version == 5 { + if pk.Version >= 5 { n := pk.algorithmSpecificByteCount() if _, err = w.Write([]byte{ byte(n >> 24), byte(n >> 16), byte(n >> 8), byte(n), From 6199e4756eb98b27b7718cf16d4c24005262a713 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Fri, 24 Mar 2023 10:49:24 +0100 Subject: [PATCH 002/116] Replace config v5Keys field with config v6Keys field --- openpgp/integration_tests/utils_test.go | 10 +++++----- openpgp/key_generation.go | 12 ++++++------ openpgp/keys_v5_test.go | 6 +++--- openpgp/packet/config.go | 6 +++--- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/openpgp/integration_tests/utils_test.go b/openpgp/integration_tests/utils_test.go index e1ce81fe8..8a82156ee 100644 --- a/openpgp/integration_tests/utils_test.go +++ b/openpgp/integration_tests/utils_test.go @@ -26,8 +26,8 @@ func generateFreshTestVectors() (vectors []testVector, err error) { // Only for verbose display v := "v4" - if config.V5Keys { - v = "v5" + if config.V6Keys { + v = "v6" } pkAlgoNames := map[packet.PublicKeyAlgorithm]string{ packet.PubKeyAlgoRSA: "rsa_" + v, @@ -263,9 +263,9 @@ func randConfig() *packet.Config { level := mathrand.Intn(11) - 1 compConf := &packet.CompressionConfig{level} - var v5 bool + var v6 bool if mathrand.Int()%2 == 0 { - v5 = true + v6 = true } var s2kConf *s2k.Config @@ -282,7 +282,7 @@ func randConfig() *packet.Config { } return &packet.Config{ - V5Keys: v5, + V6Keys: v6, Rand: rand.Reader, DefaultHash: hash, DefaultCipher: ciph, diff --git a/openpgp/key_generation.go b/openpgp/key_generation.go index 0e71934cd..79c1f9a77 100644 --- a/openpgp/key_generation.go +++ b/openpgp/key_generation.go @@ -36,8 +36,8 @@ func NewEntity(name, comment, email string, config *packet.Config) (*Entity, err return nil, err } primary := packet.NewSignerPrivateKey(creationTime, primaryPrivRaw) - if config != nil && config.V5Keys { - primary.UpgradeToV5() + if config != nil && config.V6Keys { + primary.UpgradeToV6() } e := &Entity{ @@ -158,8 +158,8 @@ func (e *Entity) AddSigningSubkey(config *packet.Config) error { } sub := packet.NewSignerPrivateKey(creationTime, subPrivRaw) sub.IsSubkey = true - if config != nil && config.V5Keys { - sub.UpgradeToV5() + if config != nil && config.V6Keys { + sub.UpgradeToV6() } subkey := Subkey{ @@ -203,8 +203,8 @@ func (e *Entity) addEncryptionSubkey(config *packet.Config, creationTime time.Ti } sub := packet.NewDecrypterPrivateKey(creationTime, subPrivRaw) sub.IsSubkey = true - if config != nil && config.V5Keys { - sub.UpgradeToV5() + if config != nil && config.V6Keys { + sub.UpgradeToV6() } subkey := Subkey{ diff --git a/openpgp/keys_v5_test.go b/openpgp/keys_v5_test.go index 8a38bd902..103351be6 100644 --- a/openpgp/keys_v5_test.go +++ b/openpgp/keys_v5_test.go @@ -60,7 +60,7 @@ func testV5ForeignSignedMessage(t *testing.T) { } func TestReadPrivateEncryptedV5Key(t *testing.T) { - c := &packet.Config{V5Keys: true} + c := &packet.Config{V6Keys: true} e, err := NewEntity("V5 Key Owner", "V5 Key", "v5@pm.me", c) if err != nil { t.Fatal(err) @@ -116,7 +116,7 @@ func TestReadPrivateSerializeForeignV5Key(t *testing.T) { } func TestNewEntitySerializeV5Key(t *testing.T) { - c := &packet.Config{V5Keys: true} + c := &packet.Config{V6Keys: true} e, err := NewEntity("V5 Key Owner", "V5 Key", "v5@pm.me", c) if err != nil { t.Fatal(err) @@ -126,7 +126,7 @@ func TestNewEntitySerializeV5Key(t *testing.T) { func TestNewEntityV5Key(t *testing.T) { c := &packet.Config{ - V5Keys: true, + V6Keys: true, } e, err := NewEntity("V5 Key Owner", "V5 Key", "v5@pm.me", c) if err != nil { diff --git a/openpgp/packet/config.go b/openpgp/packet/config.go index 04994bec9..475a0b7ba 100644 --- a/openpgp/packet/config.go +++ b/openpgp/packet/config.go @@ -73,9 +73,9 @@ type Config struct { // **Note: using this option may break compatibility with other OpenPGP // implementations, as well as future versions of this library.** AEADConfig *AEADConfig - // V5Keys configures version 5 key generation. If false, this package still - // supports version 5 keys, but produces version 4 keys. - V5Keys bool + // V6Keys configures version 6 key generation. If false, this package still + // supports version 6 keys, but produces version 4 keys. + V6Keys bool // "The validity period of the key. This is the number of seconds after // the key creation time that the key expires. If this is not present // or has a value of zero, the key never expires. This is found only on From fecd4136f374618b89373bca179976b30a9d5094 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Mon, 27 Mar 2023 10:07:00 +0200 Subject: [PATCH 003/116] Add support for v6 signatures This change implements the necessary updates for parsing and serializing v6 signature packets. Further it implements the v6 signature salt. V6 signatures require that a salt is prepended to the signing content before creating the signature hash. In this commit, we add this feature, which requires a change in the API for signing content. The Sign API takes an externally created hasher object as an argument to sign. To ensure that the salt is hashed before the conten in v6 singatures, we introduce a PrepareSignature method that creates a hasher object with prepended salt. --- openpgp/keys_v5_test.go | 16 ++- openpgp/keys_v6_test.go | 139 +++++++++++++++++++++++ openpgp/packet/public_key.go | 52 +++++---- openpgp/packet/signature.go | 206 +++++++++++++++++++++++++++++++---- 4 files changed, 363 insertions(+), 50 deletions(-) create mode 100644 openpgp/keys_v6_test.go diff --git a/openpgp/keys_v5_test.go b/openpgp/keys_v5_test.go index 103351be6..28caef9b5 100644 --- a/openpgp/keys_v5_test.go +++ b/openpgp/keys_v5_test.go @@ -7,13 +7,13 @@ import ( "testing" "github.com/ProtonMail/go-crypto/openpgp/armor" - "github.com/ProtonMail/go-crypto/openpgp/packet" ) var foreignKeys = []string{ v5PrivKey, } + func TestReadPrivateForeignV5Key(t *testing.T) { for _, str := range foreignKeys { kring, err := ReadArmoredKeyRing(strings.NewReader(str)) @@ -59,8 +59,10 @@ func testV5ForeignSignedMessage(t *testing.T) { } } +// Depricated +/* func TestReadPrivateEncryptedV5Key(t *testing.T) { - c := &packet.Config{V6Keys: true} + c := &packet.Config{V5Keys: true} e, err := NewEntity("V5 Key Owner", "V5 Key", "v5@pm.me", c) if err != nil { t.Fatal(err) @@ -103,7 +105,7 @@ func TestReadPrivateEncryptedV5Key(t *testing.T) { } checkV5Key(t, el[0]) -} +}*/ func TestReadPrivateSerializeForeignV5Key(t *testing.T) { for _, str := range foreignKeys { @@ -115,8 +117,10 @@ func TestReadPrivateSerializeForeignV5Key(t *testing.T) { } } +// Depricated +/* func TestNewEntitySerializeV5Key(t *testing.T) { - c := &packet.Config{V6Keys: true} + c := &packet.Config{V5Keys: true} e, err := NewEntity("V5 Key Owner", "V5 Key", "v5@pm.me", c) if err != nil { t.Fatal(err) @@ -126,14 +130,14 @@ func TestNewEntitySerializeV5Key(t *testing.T) { func TestNewEntityV5Key(t *testing.T) { c := &packet.Config{ - V6Keys: true, + V5Keys: true, } e, err := NewEntity("V5 Key Owner", "V5 Key", "v5@pm.me", c) if err != nil { t.Fatal(err) } checkV5Key(t, e) -} +}*/ func checkV5Key(t *testing.T, ent *Entity) { key := ent.PrimaryKey diff --git a/openpgp/keys_v6_test.go b/openpgp/keys_v6_test.go new file mode 100644 index 000000000..b260ade46 --- /dev/null +++ b/openpgp/keys_v6_test.go @@ -0,0 +1,139 @@ +package openpgp + +import ( + "bytes" + "testing" + + "github.com/ProtonMail/go-crypto/openpgp/packet" +) + +func TestReadPrivateEncryptedV6Key(t *testing.T) { + c := &packet.Config{V6Keys: true} + e, err := NewEntity("V6 Key Owner", "V6 Key", "v6@pm.me", c) + if err != nil { + t.Fatal(err) + } + password := []byte("test v6 key # password") + // Encrypt private key + if err = e.PrivateKey.Encrypt(password); err != nil { + t.Fatal(err) + } + // Encrypt subkeys + for _, sub := range e.Subkeys { + if err = sub.PrivateKey.Encrypt(password); err != nil { + t.Fatal(err) + } + } + // Serialize, Read + serializedEntity := bytes.NewBuffer(nil) + err = e.SerializePrivateWithoutSigning(serializedEntity, nil) + if err != nil { + t.Fatal(err) + } + el, err := ReadKeyRing(serializedEntity) + if err != nil { + t.Fatal(err) + } + + // Decrypt + if el[0].PrivateKey == nil { + t.Fatal("No private key found") + } + if err = el[0].PrivateKey.Decrypt(password); err != nil { + t.Error(err) + } + + // Decrypt subkeys + for _, sub := range e.Subkeys { + if err = sub.PrivateKey.Decrypt(password); err != nil { + t.Error(err) + } + } + + checkV6Key(t, el[0]) +} + +func TestNewEntitySerializeV6Key(t *testing.T) { + c := &packet.Config{V6Keys: true} + e, err := NewEntity("V6 Key Owner", "V6 Key", "v6@pm.me", c) + if err != nil { + t.Fatal(err) + } + checkSerializeReadv6(t, e) +} + +func TestNewEntityV6Key(t *testing.T) { + c := &packet.Config{ + V6Keys: true, + } + e, err := NewEntity("V6 Key Owner", "V6 Key", "v6@pm.me", c) + if err != nil { + t.Fatal(err) + } + checkV6Key(t, e) +} + +func checkV6Key(t *testing.T, ent *Entity) { + key := ent.PrimaryKey + if key.Version != 6 { + t.Errorf("wrong key version %d", key.Version) + } + if len(key.Fingerprint) != 32 { + t.Errorf("Wrong fingerprint length: %d", len(key.Fingerprint)) + } + signatures := ent.Revocations + for _, id := range ent.Identities { + signatures = append(signatures, id.SelfSignature) + signatures = append(signatures, id.Signatures...) + } + for _, sig := range signatures { + if sig == nil { + continue + } + if sig.Version != 6 { + t.Errorf("wrong signature version %d", sig.Version) + } + fgptLen := len(sig.IssuerFingerprint) + if fgptLen!= 32 { + t.Errorf("Wrong fingerprint length in signature: %d", fgptLen) + } + } +} + +func checkSerializeReadv6(t *testing.T, e *Entity) { + // Entity serialize + serializedEntity := bytes.NewBuffer(nil) + err := e.Serialize(serializedEntity) + if err != nil { + t.Fatal(err) + } + el, err := ReadKeyRing(serializedEntity) + if err != nil { + t.Fatal(err) + } + checkV6Key(t, el[0]) + + // Without signing + serializedEntity = bytes.NewBuffer(nil) + err = e.SerializePrivateWithoutSigning(serializedEntity, nil) + if err != nil { + t.Fatal(err) + } + el, err = ReadKeyRing(serializedEntity) + if err != nil { + t.Fatal(err) + } + checkV6Key(t, el[0]) + + // Private + serializedEntity = bytes.NewBuffer(nil) + err = e.SerializePrivate(serializedEntity, nil) + if err != nil { + t.Fatal(err) + } + el, err = ReadKeyRing(serializedEntity) + if err != nil { + t.Fatal(err) + } + checkV6Key(t, el[0]) +} diff --git a/openpgp/packet/public_key.go b/openpgp/packet/public_key.go index 1cc3dbe7f..b9d3db471 100644 --- a/openpgp/packet/public_key.go +++ b/openpgp/packet/public_key.go @@ -5,7 +5,6 @@ package packet import ( - "crypto" "crypto/dsa" "crypto/rsa" "crypto/sha1" @@ -657,11 +656,8 @@ func (pk *PublicKey) VerifySignature(signed hash.Hash, sig *Signature) (err erro // keySignatureHash returns a Hash of the message that needs to be signed for // pk to assert a subkey relationship to signed. -func keySignatureHash(pk, signed signingKey, hashFunc crypto.Hash) (h hash.Hash, err error) { - if !hashFunc.Available() { - return nil, errors.UnsupportedError("hash function") - } - h = hashFunc.New() +func keySignatureHash(pk, signed signingKey, hashFunc hash.Hash) (h hash.Hash, err error) { + h = hashFunc // RFC 4880, section 5.2.4 err = pk.SerializeForHash(h) @@ -676,7 +672,11 @@ func keySignatureHash(pk, signed signingKey, hashFunc crypto.Hash) (h hash.Hash, // VerifyKeySignature returns nil iff sig is a valid signature, made by this // public key, of signed. func (pk *PublicKey) VerifyKeySignature(signed *PublicKey, sig *Signature) error { - h, err := keySignatureHash(pk, signed, sig.Hash) + preparedHash, err := sig.PrepareSignature(sig.Hash, nil) + if err != nil { + return err + } + h, err := keySignatureHash(pk, signed, preparedHash) if err != nil { return err } @@ -690,10 +690,14 @@ func (pk *PublicKey) VerifyKeySignature(signed *PublicKey, sig *Signature) error if sig.EmbeddedSignature == nil { return errors.StructuralError("signing subkey is missing cross-signature") } + preparedHashEmbedded, err := sig.PrepareSignature(sig.EmbeddedSignature.Hash, nil) + if err != nil { + return err + } // Verify the cross-signature. This is calculated over the same // data as the main signature, so we cannot just recursively // call signed.VerifyKeySignature(...) - if h, err = keySignatureHash(pk, signed, sig.EmbeddedSignature.Hash); err != nil { + if h, err = keySignatureHash(pk, signed, preparedHashEmbedded); err != nil { return errors.StructuralError("error while hashing for cross-signature: " + err.Error()) } if err := signed.VerifySignature(h, sig.EmbeddedSignature); err != nil { @@ -704,11 +708,8 @@ func (pk *PublicKey) VerifyKeySignature(signed *PublicKey, sig *Signature) error return nil } -func keyRevocationHash(pk signingKey, hashFunc crypto.Hash) (h hash.Hash, err error) { - if !hashFunc.Available() { - return nil, errors.UnsupportedError("hash function") - } - h = hashFunc.New() +func keyRevocationHash(pk signingKey, hashFunc hash.Hash) (h hash.Hash, err error) { + h = hashFunc // RFC 4880, section 5.2.4 err = pk.SerializeForHash(h) @@ -719,7 +720,11 @@ func keyRevocationHash(pk signingKey, hashFunc crypto.Hash) (h hash.Hash, err er // VerifyRevocationSignature returns nil iff sig is a valid signature, made by this // public key. func (pk *PublicKey) VerifyRevocationSignature(sig *Signature) (err error) { - h, err := keyRevocationHash(pk, sig.Hash) + preparedHash, err := sig.PrepareSignature(sig.Hash, nil) + if err != nil { + return err + } + h, err := keyRevocationHash(pk, preparedHash) if err != nil { return err } @@ -729,7 +734,11 @@ func (pk *PublicKey) VerifyRevocationSignature(sig *Signature) (err error) { // VerifySubkeyRevocationSignature returns nil iff sig is a valid subkey revocation signature, // made by this public key, of signed. func (pk *PublicKey) VerifySubkeyRevocationSignature(sig *Signature, signed *PublicKey) (err error) { - h, err := keySignatureHash(pk, signed, sig.Hash) + preparedHash, err := sig.PrepareSignature(sig.Hash, nil) + if err != nil { + return err + } + h, err := keySignatureHash(pk, signed, preparedHash) if err != nil { return err } @@ -738,11 +747,8 @@ func (pk *PublicKey) VerifySubkeyRevocationSignature(sig *Signature, signed *Pub // userIdSignatureHash returns a Hash of the message that needs to be signed // to assert that pk is a valid key for id. -func userIdSignatureHash(id string, pk *PublicKey, hashFunc crypto.Hash) (h hash.Hash, err error) { - if !hashFunc.Available() { - return nil, errors.UnsupportedError("hash function") - } - h = hashFunc.New() +func userIdSignatureHash(id string, pk *PublicKey, hash hash.Hash) (h hash.Hash, err error) { + h = hash // RFC 4880, section 5.2.4 pk.SerializeSignaturePrefix(h) @@ -763,7 +769,11 @@ func userIdSignatureHash(id string, pk *PublicKey, hashFunc crypto.Hash) (h hash // VerifyUserIdSignature returns nil iff sig is a valid signature, made by this // public key, that id is the identity of pub. func (pk *PublicKey) VerifyUserIdSignature(id string, pub *PublicKey, sig *Signature) (err error) { - h, err := userIdSignatureHash(id, pub, sig.Hash) + preparedHash, err := sig.PrepareSignature(sig.Hash, nil) + if err != nil { + return err + } + h, err := userIdSignatureHash(id, pub, preparedHash) if err != nil { return err } diff --git a/openpgp/packet/signature.go b/openpgp/packet/signature.go index 80d0bb98e..e978a6e47 100644 --- a/openpgp/packet/signature.go +++ b/openpgp/packet/signature.go @@ -39,6 +39,9 @@ type Signature struct { 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. + salt []byte // HashSuffix is extra data that is hashed in after the signed data. HashSuffix []byte @@ -115,17 +118,21 @@ type Signature struct { func (sig *Signature) parse(r io.Reader) (err error) { // RFC 4880, section 5.2.3 - var buf [5]byte + var buf [7]byte _, err = readFull(r, buf[:1]) if err != nil { return } - if buf[0] != 4 && buf[0] != 5 { + if buf[0] != 4 && buf[0] != 5 && buf[0] != 6 { err = errors.UnsupportedError("signature packet version " + strconv.Itoa(int(buf[0]))) return } sig.Version = int(buf[0]) - _, err = readFull(r, buf[:5]) + if sig.Version == 6 { + _, err = readFull(r, buf[:7]) + } else { + _, err = readFull(r, buf[:5]) + } if err != nil { return } @@ -150,7 +157,16 @@ func (sig *Signature) parse(r io.Reader) (err error) { return errors.UnsupportedError("hash function " + strconv.Itoa(int(buf[2]))) } - hashedSubpacketsLength := int(buf[3])<<8 | int(buf[4]) + var hashedSubpacketsLength int + if sig.Version == 6 { + // For a v6 signature, a four-octet are required for the length. + hashedSubpacketsLength = int(buf[3])<<24 | + int(buf[4])<<16 | + int(buf[5])<<8 | + int(buf[6]) + } else { + hashedSubpacketsLength = int(buf[3])<<8 | int(buf[4]) + } hashedSubpackets := make([]byte, hashedSubpacketsLength) _, err = readFull(r, hashedSubpackets) if err != nil { @@ -166,11 +182,21 @@ func (sig *Signature) parse(r io.Reader) (err error) { return } - _, err = readFull(r, buf[:2]) + if sig.Version == 6 { + _, err = readFull(r, buf[:4]) + } else { + _, err = readFull(r, buf[:2]) + } + if err != nil { return } - unhashedSubpacketsLength := int(buf[0])<<8 | int(buf[1]) + var unhashedSubpacketsLength int + if sig.Version == 6 { + unhashedSubpacketsLength = int(buf[3])<<24 | int(buf[2])<<16 | int(buf[1])<<8 | int(buf[0]) + } else { + unhashedSubpacketsLength = int(buf[0])<<8 | int(buf[1]) + } unhashedSubpackets := make([]byte, unhashedSubpacketsLength) _, err = readFull(r, unhashedSubpackets) if err != nil { @@ -186,6 +212,30 @@ func (sig *Signature) parse(r io.Reader) (err error) { return } + if sig.Version == 6 { + // Only for v6 signatures, a variable-length field containing the salt + _, err = readFull(r, buf[:1]) + if err != nil { + return + } + saltLength := int(buf[0]) + var expectedSaltLength int + expectedSaltLength, err = saltLengthForHash(sig.Hash) + if err != nil { + return + } + if saltLength != expectedSaltLength { + err = errors.StructuralError("unexpected salt size for the given hash algorithm") + return + } + salt := make([]byte, expectedSaltLength) + _, err = readFull(r, salt) + if err != nil { + return + } + sig.salt = salt + } + switch sig.PubKeyAlgo { case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly: sig.RSASignature = new(encoding.MPI) @@ -495,13 +545,13 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r return } v, l := subpacket[0], len(subpacket[1:]) - if v == 5 && l != 32 || v != 5 && l != 20 { + if v >= 5 && l != 32 || v < 5 && l != 20 { return nil, errors.StructuralError("bad fingerprint length") } sig.IssuerFingerprint = make([]byte, l) copy(sig.IssuerFingerprint, subpacket[1:]) sig.IssuerKeyId = new(uint64) - if v == 5 { + if v >= 5 { *sig.IssuerKeyId = binary.BigEndian.Uint64(subpacket[1:9]) } else { *sig.IssuerKeyId = binary.BigEndian.Uint64(subpacket[13:21]) @@ -635,20 +685,36 @@ func (sig *Signature) buildHashSuffix(hashedSubpackets []byte) (err error) { uint8(sig.SigType), uint8(sig.PubKeyAlgo), uint8(hashId), - uint8(len(hashedSubpackets) >> 8), - uint8(len(hashedSubpackets)), }) + hashedSubpacketsLength := len(hashedSubpackets) + if sig.Version == 6 { + // v6 keys store the length in 4 ocets + hashedFields.Write([]byte{ + uint8(hashedSubpacketsLength >> 24), + uint8(hashedSubpacketsLength >> 16), + uint8(hashedSubpacketsLength >> 8), + uint8(hashedSubpacketsLength), + }) + } else { + hashedFields.Write([]byte{ + uint8(hashedSubpacketsLength >> 8), + uint8(hashedSubpacketsLength), + }) + } + lenPrefix := hashedFields.Len() hashedFields.Write(hashedSubpackets) - var l uint64 = uint64(6 + len(hashedSubpackets)) + var l uint64 = uint64(lenPrefix + len(hashedSubpackets)) if sig.Version == 5 { + // v5 case hashedFields.Write([]byte{0x05, 0xff}) hashedFields.Write([]byte{ uint8(l >> 56), uint8(l >> 48), uint8(l >> 40), uint8(l >> 32), uint8(l >> 24), uint8(l >> 16), uint8(l >> 8), uint8(l), }) } else { - hashedFields.Write([]byte{0x04, 0xff}) + // v4 and v6 case + hashedFields.Write([]byte{byte(sig.Version), 0xff}) hashedFields.Write([]byte{ uint8(l >> 24), uint8(l >> 16), uint8(l >> 8), uint8(l), }) @@ -676,6 +742,30 @@ func (sig *Signature) signPrepareHash(h hash.Hash) (digest []byte, err error) { return } +// PrepareSignature must be called to create a hash object before Sign or Verify for v6 signatures. +// The created hash object initially hashes a randomly generated salt +// as required by v6 signatures. If the signature is not v6, +// the method returns an empty hash object +// See RFC the crypto refresh Section 3.2.4. +func (sig *Signature) PrepareSignature(hash crypto.Hash, config *Config) (hash.Hash, error) { + if !hash.Available() { + return nil, errors.UnsupportedError("hash function") + } + hasher := hash.New() + if sig.Version == 6 { + if sig.salt == nil { + saltLength, err := saltLengthForHash(hash) + if err != nil { + return nil, err + } + sig.salt = make([]byte, saltLength) + config.Random().Read(sig.salt) + } + hasher.Write(sig.salt) + } + return hasher, nil +} + // Sign signs a message with a private key. The hash, h, must contain // the hash of the message to be signed and will be mutated by this function. // On success, the signature is stored in sig. Call Serialize to write it out. @@ -744,7 +834,11 @@ func (sig *Signature) SignUserId(id string, pub *PublicKey, priv *PrivateKey, co if priv.Dummy() { return errors.ErrDummyPrivateKey("dummy key found") } - h, err := userIdSignatureHash(id, pub, sig.Hash) + prepareHash, err := sig.PrepareSignature(sig.Hash, config) + if err != nil { + return err + } + h, err := userIdSignatureHash(id, pub, prepareHash) if err != nil { return err } @@ -756,7 +850,11 @@ func (sig *Signature) SignUserId(id string, pub *PublicKey, priv *PrivateKey, co // If config is nil, sensible defaults will be used. func (sig *Signature) CrossSignKey(pub *PublicKey, hashKey *PublicKey, signingKey *PrivateKey, config *Config) error { - h, err := keySignatureHash(hashKey, pub, sig.Hash) + prepareHash, err := sig.PrepareSignature(sig.Hash, config) + if err != nil { + return err + } + h, err := keySignatureHash(hashKey, pub, prepareHash) if err != nil { return err } @@ -770,7 +868,11 @@ func (sig *Signature) SignKey(pub *PublicKey, priv *PrivateKey, config *Config) if priv.Dummy() { return errors.ErrDummyPrivateKey("dummy key found") } - h, err := keySignatureHash(&priv.PublicKey, pub, sig.Hash) + prepareHash, err := sig.PrepareSignature(sig.Hash, config) + if err != nil { + return err + } + h, err := keySignatureHash(&priv.PublicKey, pub, prepareHash) if err != nil { return err } @@ -781,7 +883,11 @@ func (sig *Signature) SignKey(pub *PublicKey, priv *PrivateKey, config *Config) // stored in sig. Call Serialize to write it out. // If config is nil, sensible defaults will be used. func (sig *Signature) RevokeKey(pub *PublicKey, priv *PrivateKey, config *Config) error { - h, err := keyRevocationHash(pub, sig.Hash) + prepareHash, err := sig.PrepareSignature(sig.Hash, config) + if err != nil { + return err + } + h, err := keyRevocationHash(pub, prepareHash) if err != nil { return err } @@ -830,6 +936,12 @@ func (sig *Signature) Serialize(w io.Writer) (err error) { if sig.Version == 5 { length -= 4 // eight-octet instead of four-octet big endian } + if sig.Version == 6 { + // unhashed length is four-octet instead + // salt len 1 ocet + // len(salt) ocets + length += 3 + len(sig.salt) + } err = serializeHeader(w, packetTypeSignature, length) if err != nil { return @@ -842,18 +954,41 @@ func (sig *Signature) Serialize(w io.Writer) (err error) { } func (sig *Signature) serializeBody(w io.Writer) (err error) { - hashedSubpacketsLen := uint16(uint16(sig.HashSuffix[4])<<8) | uint16(sig.HashSuffix[5]) - fields := sig.HashSuffix[:6+hashedSubpacketsLen] + var fields []byte + if sig.Version == 6 { + // v6 signatures use 4 ocets for length + hashedSubpacketsLen := uint32(uint32(sig.HashSuffix[4])<<24) | + uint32(uint32(sig.HashSuffix[5])<<16) | + uint32(uint32(sig.HashSuffix[6])<<8) | + uint32(sig.HashSuffix[7]) + fields = sig.HashSuffix[:8+hashedSubpacketsLen] + } else { + hashedSubpacketsLen := uint16(uint16(sig.HashSuffix[4])<<8) | + uint16(sig.HashSuffix[5]) + fields = sig.HashSuffix[:6+hashedSubpacketsLen] + + } _, err = w.Write(fields) if err != nil { return } unhashedSubpacketsLen := subpacketsLength(sig.outSubpackets, false) - unhashedSubpackets := make([]byte, 2+unhashedSubpacketsLen) - unhashedSubpackets[0] = byte(unhashedSubpacketsLen >> 8) - unhashedSubpackets[1] = byte(unhashedSubpacketsLen) - serializeSubpackets(unhashedSubpackets[2:], sig.outSubpackets, false) + var unhashedSubpackets []byte + if sig.Version == 6 { + unhashedSubpackets = make([]byte, 4+unhashedSubpacketsLen) + unhashedSubpackets[0] = byte(unhashedSubpacketsLen >> 24) + unhashedSubpackets[1] = byte(unhashedSubpacketsLen >> 16) + unhashedSubpackets[2] = byte(unhashedSubpacketsLen >> 8) + unhashedSubpackets[3] = byte(unhashedSubpacketsLen) + serializeSubpackets(unhashedSubpackets[4:], sig.outSubpackets, false) + } else { + unhashedSubpackets = make([]byte, 2+unhashedSubpacketsLen) + unhashedSubpackets[0] = byte(unhashedSubpacketsLen >> 8) + unhashedSubpackets[1] = byte(unhashedSubpacketsLen) + serializeSubpackets(unhashedSubpackets[2:], sig.outSubpackets, false) + } + _, err = w.Write(unhashedSubpackets) if err != nil { @@ -864,6 +999,18 @@ func (sig *Signature) serializeBody(w io.Writer) (err error) { return } + if sig.Version == 6 { + // write salt for v6 signatures + _, err = w.Write([]byte{uint8(len(sig.salt))}) + if err != nil { + return + } + w.Write(sig.salt) + if err != nil { + return + } + } + switch sig.PubKeyAlgo { case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly: _, err = w.Write(sig.RSASignature.EncodedBytes()) @@ -908,7 +1055,7 @@ func (sig *Signature) buildSubpackets(issuer PublicKey) (subpackets []outputSubp } if sig.IssuerFingerprint != nil { contents := append([]uint8{uint8(issuer.Version)}, sig.IssuerFingerprint...) - subpackets = append(subpackets, outputSubpacket{true, issuerFingerprintSubpacket, sig.Version == 5, contents}) + subpackets = append(subpackets, outputSubpacket{true, issuerFingerprintSubpacket, sig.Version >= 5, contents}) } if sig.SignerUserId != nil { subpackets = append(subpackets, outputSubpacket{true, signerUserIdSubpacket, false, []byte(*sig.SignerUserId)}) @@ -1082,3 +1229,16 @@ func (sig *Signature) AddMetadataToHashSuffix() { }) sig.HashSuffix = suffix.Bytes() } + +// Select 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 +func saltLengthForHash(hash crypto.Hash) (int, error) { + switch (hash) { + case crypto.SHA256: return 16, nil + case crypto.SHA384: return 24, nil + case crypto.SHA512: return 32, nil + case crypto.SHA224: return 16, nil + default: return 0, errors.UnsupportedError("hash function not supported for V6 signatures") + } +} From c0784f98736029c49233334b8d269896e3cd2a81 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Mon, 27 Mar 2023 17:36:36 +0200 Subject: [PATCH 004/116] Add support for direct-key signatures and v6 key messages This commit introduces support for direct-key signatures and ensures that version 6 keys store their properties in it. Instead of calling PrimaryIdentity to access a keys properties, one should now call PrimarySelfSignature to get the primary self-signature of a key. --- openpgp/key_generation.go | 62 ++++++++++++------ openpgp/keys.go | 110 ++++++++++++++++++++++++-------- openpgp/keys_test.go | 41 ++++++++++++ openpgp/keys_v6_test.go | 39 +++++++++++ openpgp/packet/public_key.go | 26 ++++++++ openpgp/packet/signature.go | 19 ++++++ openpgp/read.go | 12 ++-- openpgp/read_write_test_data.go | 31 +++++++++ openpgp/write.go | 22 +++++-- 9 files changed, 300 insertions(+), 62 deletions(-) diff --git a/openpgp/key_generation.go b/openpgp/key_generation.go index 79c1f9a77..366bc4561 100644 --- a/openpgp/key_generation.go +++ b/openpgp/key_generation.go @@ -29,6 +29,7 @@ import ( func NewEntity(name, comment, email string, config *packet.Config) (*Entity, error) { creationTime := config.Now() keyLifetimeSecs := config.KeyLifetime() + useV6Keys := config != nil && config.V6Keys // Generate a primary signing key primaryPrivRaw, err := newSigner(config) @@ -36,7 +37,7 @@ func NewEntity(name, comment, email string, config *packet.Config) (*Entity, err return nil, err } primary := packet.NewSignerPrivateKey(creationTime, primaryPrivRaw) - if config != nil && config.V6Keys { + if useV6Keys { primary.UpgradeToV6() } @@ -45,9 +46,24 @@ func NewEntity(name, comment, email string, config *packet.Config) (*Entity, err PrivateKey: primary, Identities: make(map[string]*Identity), Subkeys: []Subkey{}, + DirectSignatures: []*packet.Signature{}, } - err = e.addUserId(name, comment, email, config, creationTime, keyLifetimeSecs) + if useV6Keys { + // In v6 keys algorithm preferences should be stored in direct key signatures + selfSignature := createSignaturePacket(&primary.PublicKey, packet.SigTypeDirectSignature, config) + err = writeKeyProperties(selfSignature, creationTime, keyLifetimeSecs, config) + if err != nil { + return nil, err + } + err = selfSignature.SignDirectKeyBinding(&primary.PublicKey, primary, config) + if err != nil { + return nil, err + } + e.DirectSignatures = append(e.DirectSignatures, selfSignature) + } + + err = e.addUserId(name, comment, email, config, creationTime, keyLifetimeSecs, !useV6Keys) if err != nil { return nil, err } @@ -65,27 +81,12 @@ func NewEntity(name, comment, email string, config *packet.Config) (*Entity, err func (t *Entity) AddUserId(name, comment, email string, config *packet.Config) error { creationTime := config.Now() keyLifetimeSecs := config.KeyLifetime() - return t.addUserId(name, comment, email, config, creationTime, keyLifetimeSecs) + return t.addUserId(name, comment, email, config, creationTime, keyLifetimeSecs, !(config != nil && config.V6Keys)) } -func (t *Entity) addUserId(name, comment, email string, config *packet.Config, creationTime time.Time, keyLifetimeSecs uint32) error { - uid := packet.NewUserId(name, comment, email) - if uid == nil { - return errors.InvalidArgumentError("user id field contained invalid characters") - } - - if _, ok := t.Identities[uid.Id]; ok { - return errors.InvalidArgumentError("user id exist") - } - - primary := t.PrivateKey - - isPrimaryId := len(t.Identities) == 0 - - selfSignature := createSignaturePacket(&primary.PublicKey, packet.SigTypePositiveCert, config) +func writeKeyProperties(selfSignature *packet.Signature, creationTime time.Time, keyLifetimeSecs uint32, config *packet.Config) error { selfSignature.CreationTime = creationTime selfSignature.KeyLifetimeSecs = &keyLifetimeSecs - selfSignature.IsPrimaryId = &isPrimaryId selfSignature.FlagsValid = true selfSignature.FlagSign = true selfSignature.FlagCertify = true @@ -131,6 +132,29 @@ func (t *Entity) addUserId(name, comment, email string, config *packet.Config, c selfSignature.PreferredCipherSuites = append(selfSignature.PreferredCipherSuites, [2]uint8{cipher, mode}) } } + return nil +} + +func (t *Entity) addUserId(name, comment, email string, config *packet.Config, creationTime time.Time, keyLifetimeSecs uint32, writeProperties bool) error { + uid := packet.NewUserId(name, comment, email) + if uid == nil { + return errors.InvalidArgumentError("user id field contained invalid characters") + } + + if _, ok := t.Identities[uid.Id]; ok { + return errors.InvalidArgumentError("user id exist") + } + + primary := t.PrivateKey + isPrimaryId := len(t.Identities) == 0 + selfSignature := createSignaturePacket(&primary.PublicKey, packet.SigTypePositiveCert, config) + if writeProperties { + err := writeKeyProperties(selfSignature, creationTime, keyLifetimeSecs, config) + if err != nil { + return err + } + } + selfSignature.IsPrimaryId = &isPrimaryId // User ID binding signature err := selfSignature.SignUserId(uid.Id, &primary.PublicKey, primary, config) diff --git a/openpgp/keys.go b/openpgp/keys.go index 2d7b0cf37..f29124021 100644 --- a/openpgp/keys.go +++ b/openpgp/keys.go @@ -29,6 +29,7 @@ type Entity struct { Identities map[string]*Identity // indexed by Identity.Name Revocations []*packet.Signature Subkeys []Subkey + DirectSignatures []*packet.Signature // Direct-key signatures for the PrimaryKey } // An Identity represents an identity claimed by an Entity and zero or more @@ -99,33 +100,36 @@ func shouldPreferIdentity(existingId, potentialNewId *Identity) bool { return false } - if existingId.SelfSignature == nil { + return shouldPreferSelfSignature(existingId.SelfSignature, potentialNewId.SelfSignature) +} + +func shouldPreferSelfSignature(existingSignature, potentialSignature *packet.Signature) bool { + if existingSignature == nil { return true } - if existingId.SelfSignature.IsPrimaryId != nil && *existingId.SelfSignature.IsPrimaryId && - !(potentialNewId.SelfSignature.IsPrimaryId != nil && *potentialNewId.SelfSignature.IsPrimaryId) { + if existingSignature.IsPrimaryId != nil && *existingSignature.IsPrimaryId && + !(potentialSignature.IsPrimaryId != nil && *potentialSignature.IsPrimaryId) { return false } - if !(existingId.SelfSignature.IsPrimaryId != nil && *existingId.SelfSignature.IsPrimaryId) && - potentialNewId.SelfSignature.IsPrimaryId != nil && *potentialNewId.SelfSignature.IsPrimaryId { + if !(existingSignature.IsPrimaryId != nil && *existingSignature.IsPrimaryId) && + potentialSignature.IsPrimaryId != nil && *potentialSignature.IsPrimaryId { return true } - return potentialNewId.SelfSignature.CreationTime.After(existingId.SelfSignature.CreationTime) + return potentialSignature.CreationTime.After(existingSignature.CreationTime) } // EncryptionKey returns the best candidate Key for encrypting a message to the // given Entity. func (e *Entity) EncryptionKey(now time.Time) (Key, bool) { // Fail to find any encryption key if the... - i := e.PrimaryIdentity() - if e.PrimaryKey.KeyExpired(i.SelfSignature, now) || // primary key has expired - i.SelfSignature == nil || // user ID has no self-signature - i.SelfSignature.SigExpired(now) || // user ID self-signature has expired + primarySelfSignature, primaryIdentity := e.primarySelfSignature() + if primarySelfSignature == nil || // no self-signature found + e.PrimaryKey.KeyExpired(primarySelfSignature, now) || // primary key has expired e.Revoked(now) || // primary key has been revoked - i.Revoked(now) { // user ID has been revoked + (primaryIdentity != nil && primaryIdentity.Revoked(now)) { // user id has been revoked return Key{}, false } @@ -152,9 +156,9 @@ func (e *Entity) EncryptionKey(now time.Time) (Key, bool) { // If we don't have any subkeys for encryption and the primary key // is marked as OK to encrypt with, then we can use it. - if i.SelfSignature.FlagsValid && i.SelfSignature.FlagEncryptCommunications && + if primarySelfSignature.FlagsValid && primarySelfSignature.FlagEncryptCommunications && e.PrimaryKey.PubKeyAlgo.CanEncrypt() { - return Key{e, e.PrimaryKey, e.PrivateKey, i.SelfSignature, e.Revocations}, true + return Key{e, e.PrimaryKey, e.PrivateKey, primarySelfSignature, e.Revocations}, true } return Key{}, false @@ -186,12 +190,11 @@ func (e *Entity) SigningKeyById(now time.Time, id uint64) (Key, bool) { func (e *Entity) signingKeyByIdUsage(now time.Time, id uint64, flags int) (Key, bool) { // Fail to find any signing key if the... - i := e.PrimaryIdentity() - if e.PrimaryKey.KeyExpired(i.SelfSignature, now) || // primary key has expired - i.SelfSignature == nil || // user ID has no self-signature - i.SelfSignature.SigExpired(now) || // user ID self-signature has expired - e.Revoked(now) || // primary key has been revoked - i.Revoked(now) { // user ID has been revoked + primarySelfSignature, primaryIdentity := e.primarySelfSignature() + if primarySelfSignature == nil || // no self-signature found + e.PrimaryKey.KeyExpired(primarySelfSignature, now) || // primary key has expired + e.Revoked(now) || // primary key has been revoked + (primaryIdentity != nil && primaryIdentity.Revoked(now)) { // user id has been revoked v4 keys return Key{}, false } @@ -220,12 +223,12 @@ func (e *Entity) signingKeyByIdUsage(now time.Time, id uint64, flags int) (Key, // If we don't have any subkeys for signing and the primary key // is marked as OK to sign with, then we can use it. - if i.SelfSignature.FlagsValid && - (flags&packet.KeyFlagCertify == 0 || i.SelfSignature.FlagCertify) && - (flags&packet.KeyFlagSign == 0 || i.SelfSignature.FlagSign) && + if primarySelfSignature.FlagsValid && + (flags&packet.KeyFlagCertify == 0 || primarySelfSignature.FlagCertify) && + (flags&packet.KeyFlagSign == 0 || primarySelfSignature.FlagSign) && e.PrimaryKey.PubKeyAlgo.CanSign() && (id == 0 || e.PrimaryKey.KeyId == id) { - return Key{e, e.PrimaryKey, e.PrivateKey, i.SelfSignature, e.Revocations}, true + return Key{e, e.PrimaryKey, e.PrivateKey, primarySelfSignature, e.Revocations}, true } // No keys with a valid Signing Flag or no keys matched the id passed in @@ -479,6 +482,7 @@ func ReadEntity(packets *packet.Reader) (*Entity, error) { } var revocations []*packet.Signature + var directSignatures []*packet.Signature EachPacket: for { p, err := packets.Next() @@ -497,9 +501,7 @@ EachPacket: if pkt.SigType == packet.SigTypeKeyRevocation { revocations = append(revocations, pkt) } else if pkt.SigType == packet.SigTypeDirectSignature { - // TODO: RFC4880 5.2.1 permits signatures - // directly on keys (eg. to bind additional - // revocation keys). + directSignatures = append(directSignatures, pkt) } // Else, ignoring the signature as it does not follow anything // we would know to attach it to. @@ -526,8 +528,22 @@ EachPacket: } } - if len(e.Identities) == 0 { - return nil, errors.StructuralError("entity without any identities") + if len(e.Identities) == 0 && e.PrimaryKey.Version < 6 { + return nil, errors.StructuralError("v4 entity without any identities") + } + + // An implementation MUST ensure that a valid direct-key signature is present before using a v6 key + if e.PrimaryKey.Version == 6 { + if len(directSignatures) == 0 { + return nil, errors.StructuralError("v6 entity without a valid direct-key signature") + } + for _, directSignature := range directSignatures { + err = e.PrimaryKey.VerifyDirectKeySignature(directSignature) + if err != nil { + return nil, errors.StructuralError("direct-key signature signed by alternate key") + } + } + e.DirectSignatures = directSignatures } for _, revocation := range revocations { @@ -672,6 +688,12 @@ func (e *Entity) serializePrivate(w io.Writer, config *packet.Config, reSign boo return err } } + for _, directSignature := range e.DirectSignatures { + err := directSignature.Serialize(w) + if err != nil { + return err + } + } for _, ident := range e.Identities { err = ident.UserId.Serialize(w) if err != nil { @@ -738,6 +760,12 @@ func (e *Entity) Serialize(w io.Writer) error { return err } } + for _, directSignature := range e.DirectSignatures { + err := directSignature.Serialize(w) + if err != nil { + return err + } + } for _, ident := range e.Identities { err = ident.UserId.Serialize(w) if err != nil { @@ -840,3 +868,29 @@ func (e *Entity) RevokeSubkey(sk *Subkey, reason packet.ReasonForRevocation, rea sk.Revocations = append(sk.Revocations, revSig) return nil } + +func (e *Entity) primaryDirectSignature() *packet.Signature { + var primaryDirectSignature *packet.Signature + for _, candidate := range e.DirectSignatures { + if shouldPreferSelfSignature(primaryDirectSignature, candidate) { + primaryDirectSignature = candidate + } + } + return primaryDirectSignature +} + +// primarySelfSignature searches the entitity for the self-signature that stores key prefrences. +// For V4 keys, returns the self-signature of the primary indentity, and the identity. +// For V6 keys, returns the latest valid direct-key self-signature, and no identity (nil). +// This self-signature is to be used to check the key expiration, +// algorithm preferences, and so on. +func (e *Entity) primarySelfSignature() (*packet.Signature, *Identity) { + if e.PrimaryKey.Version == 6 { + return e.primaryDirectSignature(), nil + } + primaryIdentity := e.PrimaryIdentity() + if primaryIdentity == nil { + return nil, nil + } + return primaryIdentity.SelfSignature, primaryIdentity +} diff --git a/openpgp/keys_test.go b/openpgp/keys_test.go index 35bb495b9..94ef58189 100644 --- a/openpgp/keys_test.go +++ b/openpgp/keys_test.go @@ -1796,6 +1796,47 @@ func testKeyValidateDsaElGamalOnDecrypt(t *testing.T, randomPassword []byte) { } } +var foreignKeysv4 = []string{ + v4Key25519, +} + +func TestReadPrivateForeignV4Key(t *testing.T) { + for _, str := range foreignKeysv4 { + kring, err := ReadArmoredKeyRing(strings.NewReader(str)) + if err != nil { + t.Fatal(err) + } + checkV4Key(t, kring[0]) + } +} + +func checkV4Key(t *testing.T, ent *Entity) { + key := ent.PrimaryKey + if key.Version != 4 { + t.Errorf("wrong key version %d", key.Version) + } + if len(key.Fingerprint) != 20 { + t.Errorf("Wrong fingerprint length: %d", len(key.Fingerprint)) + } + signatures := ent.Revocations + for _, id := range ent.Identities { + signatures = append(signatures, id.SelfSignature) + signatures = append(signatures, id.Signatures...) + } + for _, sig := range signatures { + if sig == nil { + continue + } + if sig.Version != 4 { + t.Errorf("wrong signature version %d", sig.Version) + } + fgptLen := len(sig.IssuerFingerprint) + if fgptLen!= 20 { + t.Errorf("Wrong fingerprint length in signature: %d", fgptLen) + } + } +} + // Should not panic (generated with go-fuzz) func TestCorruptKeys(t *testing.T) { data := `-----BEGIN PGP PUBLIC KEY BLOCK00000 diff --git a/openpgp/keys_v6_test.go b/openpgp/keys_v6_test.go index b260ade46..ef01c6c08 100644 --- a/openpgp/keys_v6_test.go +++ b/openpgp/keys_v6_test.go @@ -2,11 +2,26 @@ package openpgp import ( "bytes" + "crypto" "testing" "github.com/ProtonMail/go-crypto/openpgp/packet" ) +var foreignKeysV6 = []string{ + v6PrivKey, +} + +/*func TestReadPrivateForeignV6Key(t *testing.T) { + for _, str := range foreignKeysV6 { + kring, err := ReadArmoredKeyRing(strings.NewReader(str)) + if err != nil { + t.Fatal(err) + } + checkV6Key(t, kring[0]) + } +}*/ // TODO: need x25519 support + func TestReadPrivateEncryptedV6Key(t *testing.T) { c := &packet.Config{V6Keys: true} e, err := NewEntity("V6 Key Owner", "V6 Key", "v6@pm.me", c) @@ -137,3 +152,27 @@ func checkSerializeReadv6(t *testing.T, e *Entity) { } checkV6Key(t, el[0]) } + +func TestNewEntityWithDefaultHashv6(t *testing.T) { + for _, hash := range hashes[:5] { + c := &packet.Config{ + V6Keys: true, + DefaultHash: hash, + } + entity, err := NewEntity("Golang Gopher", "Test Key", "no-reply@golang.com", c) + if hash == crypto.SHA1 { + if err == nil { + t.Fatal("should fail on SHA1 key creation") + } + continue + } + + for _, signature := range entity.DirectSignatures { + prefs := signature.PreferredHash + if prefs == nil { + t.Fatal(err) + } + } + + } +} diff --git a/openpgp/packet/public_key.go b/openpgp/packet/public_key.go index b9d3db471..da5c504b1 100644 --- a/openpgp/packet/public_key.go +++ b/openpgp/packet/public_key.go @@ -218,6 +218,7 @@ func (pk *PublicKey) parse(r io.Reader) (err error) { err = pk.parseECDH(r) case PubKeyAlgoEdDSA: err = pk.parseEdDSA(r) + default: err = errors.UnsupportedError("public key type: " + strconv.Itoa(int(pk.PubKeyAlgo))) } @@ -766,6 +767,17 @@ func userIdSignatureHash(id string, pk *PublicKey, hash hash.Hash) (h hash.Hash, return } +// directSignatureHash returns a Hash of the message that needs to be signed +func directKeySignatureHash(pk *PublicKey, hash hash.Hash) (h hash.Hash, err error) { + h = hash + + // RFC 4880, section 5.2.4 + pk.SerializeSignaturePrefix(h) + pk.serializeWithoutHeaders(h) + + return +} + // VerifyUserIdSignature returns nil iff sig is a valid signature, made by this // public key, that id is the identity of pub. func (pk *PublicKey) VerifyUserIdSignature(id string, pub *PublicKey, sig *Signature) (err error) { @@ -780,6 +792,20 @@ func (pk *PublicKey) VerifyUserIdSignature(id string, pub *PublicKey, sig *Signa return pk.VerifySignature(h, sig) } +// VerifyUserIdSignature returns nil iff sig is a valid signature, made by this +// public key +func (pk *PublicKey) VerifyDirectKeySignature(sig *Signature) (err error) { + preparedHash, err := sig.PrepareSignature(sig.Hash, nil) + if err != nil { + return err + } + h, err := directKeySignatureHash(pk, preparedHash) + if err != nil { + return err + } + return pk.VerifySignature(h, sig) +} + // KeyIdString returns the public key's fingerprint in capital hex // (e.g. "6C7EE1B8621CC013"). func (pk *PublicKey) KeyIdString() string { diff --git a/openpgp/packet/signature.go b/openpgp/packet/signature.go index e978a6e47..13b7a8f9c 100644 --- a/openpgp/packet/signature.go +++ b/openpgp/packet/signature.go @@ -845,6 +845,25 @@ func (sig *Signature) SignUserId(id string, pub *PublicKey, priv *PrivateKey, co return sig.Sign(h, priv, config) } +// SignUserId computes a signature from priv +// On success, the signature is stored in sig. +// Call Serialize to write it out. +// If config is nil, sensible defaults will be used. +func (sig *Signature) SignDirectKeyBinding(pub *PublicKey, priv *PrivateKey, config *Config) error { + if priv.Dummy() { + return errors.ErrDummyPrivateKey("dummy key found") + } + prepareHash, err := sig.PrepareSignature(sig.Hash, config) + if err != nil { + return err + } + h, err := directKeySignatureHash(pub, prepareHash) + if err != nil { + return err + } + return sig.Sign(h, priv, config) +} + // CrossSignKey computes a signature from signingKey on pub hashed using hashKey. On success, // the signature is stored in sig. Call Serialize to write it out. // If config is nil, sensible defaults will be used. diff --git a/openpgp/read.go b/openpgp/read.go index 8499c7379..adf1d576c 100644 --- a/openpgp/read.go +++ b/openpgp/read.go @@ -551,15 +551,11 @@ func CheckArmoredDetachedSignature(keyring KeyRing, signed, signature io.Reader, // NOTE: The order of these checks is important, as the caller may choose to // ignore ErrSignatureExpired or ErrKeyExpired errors, but should never // ignore any other errors. -// -// TODO: Also return an error if: -// - The primary key is expired according to a direct-key signature -// - (For V5 keys only:) The direct-key signature (exists and) is expired func checkSignatureDetails(key *Key, signature *packet.Signature, config *packet.Config) error { now := config.Now() - primaryIdentity := key.Entity.PrimaryIdentity() + primarySelfSignature, primaryIdentity := key.Entity.primarySelfSignature() signedBySubKey := key.PublicKey != key.Entity.PrimaryKey - sigsToCheck := []*packet.Signature{signature, primaryIdentity.SelfSignature} + sigsToCheck := []*packet.Signature{ signature, primarySelfSignature } if signedBySubKey { sigsToCheck = append(sigsToCheck, key.SelfSignature, key.SelfSignature.EmbeddedSignature) } @@ -572,10 +568,10 @@ func checkSignatureDetails(key *Key, signature *packet.Signature, config *packet } if key.Entity.Revoked(now) || // primary key is revoked (signedBySubKey && key.Revoked(now)) || // subkey is revoked - primaryIdentity.Revoked(now) { // primary identity is revoked + (primaryIdentity != nil && primaryIdentity.Revoked(now)) { // primary identity is revoked for v4 return errors.ErrKeyRevoked } - if key.Entity.PrimaryKey.KeyExpired(primaryIdentity.SelfSignature, now) { // primary key is expired + if key.Entity.PrimaryKey.KeyExpired(primarySelfSignature, now) { // primary key is expired return errors.ErrKeyExpired } if signedBySubKey { diff --git a/openpgp/read_write_test_data.go b/openpgp/read_write_test_data.go index db6dad5c0..7b5338ea1 100644 --- a/openpgp/read_write_test_data.go +++ b/openpgp/read_write_test_data.go @@ -172,6 +172,37 @@ UQdl5MlBka1QSNbMq2Bz7XwNPg4= =6lbM -----END PGP MESSAGE-----` +// See OpenPGP crypto refresh Section A.3. +const v6PrivKey = `-----BEGIN PGP PRIVATE KEY BLOCK----- + +xUsGY4d/4xsAAAAg+U2nu0jWCmHlZ3BqZYfQMxmZu52JGggkLq2EVD34laMAGXKB +exK+cH6NX1hs5hNhIB00TrJmosgv3mg1ditlsLfCsQYfGwoAAABCBYJjh3/jAwsJ +BwUVCg4IDAIWAAKbAwIeCSIhBssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce6 +2azJBScJAgcCAAAAAK0oIBA+LX0ifsDm185Ecds2v8lwgyU2kCcUmKfvBXbAf6rh +RYWzuQOwEn7E/aLwIwRaLsdry0+VcallHhSu4RN6HWaEQsiPlR4zxP/TP7mhfVEe +7XWPxtnMUMtf15OyA51YBMdLBmOHf+MZAAAAIIaTJINn+eUBXbki+PSAld2nhJh/ +LVmFsS+60WyvXkQ1AE1gCk95TUR3XFeibg/u/tVY6a//1q0NWC1X+yui3O24wpsG +GBsKAAAALAWCY4d/4wKbDCIhBssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce6 +2azJAAAAAAQBIKbpGG2dWTX8j+VjFM21J0hqWlEg+bdiojWnKfA5AQpWUWtnNwDE +M0g12vYxoWM8Y81W+bHBw805I8kWVkXU6vFOi+HWvv/ira7ofJu16NnoUkhclkUr +k0mXubZvyl4GBg== +-----END PGP PRIVATE KEY BLOCK-----` + +const v4Key25519 = `-----BEGIN PGP PRIVATE KEY BLOCK----- + +xUkEZB3qzRto01j2k2pwN5ux9w70stPinAdXULLr20CRW7U7h2GSeACch0M+ +qzQg8yjFQ8VBvu3uwgKH9senoHmj72lLSCLTmhFKzQR0ZXN0wogEEBsIAD4F +gmQd6s0ECwkHCAmQIf45+TuC+xMDFQgKBBYAAgECGQECmwMCHgEWIQSWEzMi +jJUHvyIbVKIh/jn5O4L7EwAAUhaHNlgudvxARdPPETUzVgjuWi+YIz8w1xIb +lHQMvIrbe2sGCQIethpWofd0x7DHuv/ciHg+EoxJ/Td6h4pWtIoKx0kEZB3q +zRm4CyA7quliq7yx08AoOqHTuuCgvpkSdEhpp3pEyejQOgBo0p6ywIiLPllY +0t+jpNspHpAGfXID6oqjpYuJw3AfVRBlwnQEGBsIACoFgmQd6s0JkCH+Ofk7 +gvsTApsMFiEElhMzIoyVB78iG1SiIf45+TuC+xMAAGgQuN9G73446ykvJ/mL +sCZ7zGFId2gBd1EnG0FTC4npfOKpck0X8dngByrCxU8LDSfvjsEp/xDAiKsQ +aU71tdtNBQ== +=e7jT +-----END PGP PRIVATE KEY BLOCK-----` + const keyWithExpiredCrossSig = `-----BEGIN PGP PUBLIC KEY BLOCK----- xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv diff --git a/openpgp/write.go b/openpgp/write.go index 7fdd13a3d..99da90692 100644 --- a/openpgp/write.go +++ b/openpgp/write.go @@ -380,15 +380,19 @@ func encrypt(keyWriter io.Writer, dataWriter io.Writer, to []*Entity, signed *En return nil, errors.InvalidArgumentError("cannot encrypt a message to key id " + strconv.FormatUint(to[i].PrimaryKey.KeyId, 16) + " because it has no valid encryption keys") } - sig := to[i].PrimaryIdentity().SelfSignature - if !sig.SEIPDv2 { + primarySelfSignature, _ := to[i].primarySelfSignature() + if primarySelfSignature == nil { + return nil, errors.InvalidArgumentError("entity without a self-signature") + } + + if !primarySelfSignature.SEIPDv2 { aeadSupported = false } - candidateCiphers = intersectPreferences(candidateCiphers, sig.PreferredSymmetric) - candidateHashes = intersectPreferences(candidateHashes, sig.PreferredHash) - candidateCipherSuites = intersectCipherSuites(candidateCipherSuites, sig.PreferredCipherSuites) - candidateCompression = intersectPreferences(candidateCompression, sig.PreferredCompression) + candidateCiphers = intersectPreferences(candidateCiphers, primarySelfSignature.PreferredSymmetric) + candidateHashes = intersectPreferences(candidateHashes, primarySelfSignature.PreferredHash) + candidateCipherSuites = intersectCipherSuites(candidateCipherSuites, primarySelfSignature.PreferredCipherSuites) + candidateCompression = intersectPreferences(candidateCompression, primarySelfSignature.PreferredCompression) } // In the event that the intersection of supported algorithms is empty we use the ones @@ -465,7 +469,11 @@ func Sign(output io.Writer, signed *Entity, hints *FileHints, config *packet.Con hashToHashId(crypto.SHA3_512), } defaultHashes := candidateHashes[0:1] - preferredHashes := signed.PrimaryIdentity().SelfSignature.PreferredHash + primarySelfSignature, _ := signed.primarySelfSignature() + if primarySelfSignature == nil { + return nil, errors.InvalidArgumentError("signed entity has no self-signature") + } + preferredHashes := primarySelfSignature.PreferredHash if len(preferredHashes) == 0 { preferredHashes = defaultHashes } From 35770b9843ab06f1cbdba2db8ac74d9a61396105 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Tue, 28 Mar 2023 17:30:07 +0200 Subject: [PATCH 005/116] Add support for v6 one-pass-signatures and update read/write API This commit implements version 6 one-pass-signatures. Further, it updates the read/write API to support v6 signatures with salt and fingerprints. --- openpgp/integration_tests/end_to_end_test.go | 15 +- openpgp/packet/one_pass_signature.go | 142 ++++++++++++++++--- openpgp/packet/public_key.go | 12 +- openpgp/packet/signature.go | 92 +++++++++--- openpgp/read.go | 38 ++++- openpgp/write.go | 30 +++- 6 files changed, 268 insertions(+), 61 deletions(-) diff --git a/openpgp/integration_tests/end_to_end_test.go b/openpgp/integration_tests/end_to_end_test.go index bda40adeb..e2ea20ef0 100644 --- a/openpgp/integration_tests/end_to_end_test.go +++ b/openpgp/integration_tests/end_to_end_test.go @@ -5,15 +5,16 @@ package integrationtests import ( "bytes" "encoding/json" - "github.com/ProtonMail/go-crypto/openpgp" - "github.com/ProtonMail/go-crypto/openpgp/armor" - "github.com/ProtonMail/go-crypto/openpgp/packet" "io" "io/ioutil" "os" "strings" "testing" "time" + + "github.com/ProtonMail/go-crypto/openpgp" + "github.com/ProtonMail/go-crypto/openpgp/armor" + "github.com/ProtonMail/go-crypto/openpgp/packet" ) ///////////////////////////////////////////////////////////////////////////// @@ -202,11 +203,17 @@ func encDecTest(t *testing.T, from testVector, testVectors []testVector) { } signKey, _ := signer.SigningKey(time.Now()) expectedKeyID := signKey.PublicKey.KeyId - if md.SignedByKeyId != expectedKeyID { + expectedFingerprint := signKey.PublicKey.Fingerprint + if signKey.PublicKey.Version != 6 && md.SignedByKeyId != expectedKeyID { t.Fatalf( "Message signed by wrong key id, got: %v, want: %v", *md.SignedBy, expectedKeyID) } + if signKey.PublicKey.Version == 6 && bytes.Compare(md.SignedByFingerprint, expectedFingerprint) != 0 { + t.Fatalf( + "Message signed by wrong key id, got: %x, want: %x", + md.SignedByFingerprint, expectedFingerprint) + } if md.SignedBy == nil { t.Fatalf("Failed to find the signing Entity") } diff --git a/openpgp/packet/one_pass_signature.go b/openpgp/packet/one_pass_signature.go index 033fb2d7e..91fd7362d 100644 --- a/openpgp/packet/one_pass_signature.go +++ b/openpgp/packet/one_pass_signature.go @@ -7,34 +7,37 @@ package packet import ( "crypto" "encoding/binary" - "github.com/ProtonMail/go-crypto/openpgp/errors" - "github.com/ProtonMail/go-crypto/openpgp/internal/algorithm" "io" "strconv" + + "github.com/ProtonMail/go-crypto/openpgp/errors" + "github.com/ProtonMail/go-crypto/openpgp/internal/algorithm" ) // OnePassSignature represents a one-pass signature packet. See RFC 4880, // section 5.4. type OnePassSignature struct { - SigType SignatureType - Hash crypto.Hash - PubKeyAlgo PublicKeyAlgorithm - KeyId uint64 - IsLast bool + Version int + SigType SignatureType + Hash crypto.Hash + PubKeyAlgo PublicKeyAlgorithm + KeyId uint64 + IsLast bool + Salt []byte // v6 only + KeyFingerprint []byte // v6 only } -const onePassSignatureVersion = 3 - func (ops *OnePassSignature) parse(r io.Reader) (err error) { - var buf [13]byte - - _, err = readFull(r, buf[:]) + var buf [8]byte + // Read: version | signature type | hash algorithm | public-key algorithm + _, err = readFull(r, buf[:4]) if err != nil { return } - if buf[0] != onePassSignatureVersion { + if buf[0] != 3 && buf[0] != 6 { err = errors.UnsupportedError("one-pass-signature packet version " + strconv.Itoa(int(buf[0]))) } + ops.Version = int(buf[0]) var ok bool ops.Hash, ok = algorithm.HashIdToHashWithSha1(buf[2]) @@ -44,15 +47,77 @@ func (ops *OnePassSignature) parse(r io.Reader) (err error) { ops.SigType = SignatureType(buf[1]) ops.PubKeyAlgo = PublicKeyAlgorithm(buf[3]) - ops.KeyId = binary.BigEndian.Uint64(buf[4:12]) - ops.IsLast = buf[12] != 0 + + if ops.Version == 6 { + // Only for v6, a variable-length field containing the salt + _, err = readFull(r, buf[:1]) + if err != nil { + return + } + saltLength := int(buf[0]) + var expectedSaltLength int + expectedSaltLength, err = SaltLengthForHash(ops.Hash) + if err != nil { + return + } + if saltLength != expectedSaltLength { + err = errors.StructuralError("unexpected salt size for the given hash algorithm") + return + } + salt := make([]byte, expectedSaltLength) + _, err = readFull(r, salt) + if err != nil { + return + } + ops.Salt = salt + } else if ops.Version == 3 { + _, err = readFull(r, buf[:8]) + if err != nil { + return + } + ops.KeyId = binary.BigEndian.Uint64(buf[:8]) + } + + if ops.Version == 6 { + _, err = readFull(r, buf[:1]) + if err != nil { + return + } + if int(buf[0]) != 6 { + err = errors.StructuralError("version 6 signatures require version 6 keys") + return + } + fingerprint := make([]byte, 32) + _, err = readFull(r, fingerprint) + if err != nil { + return + } + ops.KeyFingerprint = fingerprint + ops.KeyId = binary.BigEndian.Uint64(ops.KeyFingerprint[:8]) + } + + _, err = readFull(r, buf[:1]) + if err != nil { + return + } + ops.IsLast = buf[0] != 0 return } // Serialize marshals the given OnePassSignature to w. func (ops *OnePassSignature) Serialize(w io.Writer) error { - var buf [13]byte - buf[0] = onePassSignatureVersion + packetLength := 13 + if ops.Version == 6 { + // v6 length + packetLength = 39 + len(ops.Salt) + } + + if err := serializeHeader(w, packetTypeOnePassSignature, packetLength); err != nil { + return err + } + + var buf [8]byte + buf[0] = byte(ops.Version) buf[1] = uint8(ops.SigType) var ok bool buf[2], ok = algorithm.HashToHashIdWithSha1(ops.Hash) @@ -60,14 +125,45 @@ func (ops *OnePassSignature) Serialize(w io.Writer) error { return errors.UnsupportedError("hash type: " + strconv.Itoa(int(ops.Hash))) } buf[3] = uint8(ops.PubKeyAlgo) - binary.BigEndian.PutUint64(buf[4:12], ops.KeyId) - if ops.IsLast { - buf[12] = 1 - } - if err := serializeHeader(w, packetTypeOnePassSignature, len(buf)); err != nil { + _, err := w.Write(buf[:4]) + if err != nil { return err } - _, err := w.Write(buf[:]) + + if ops.Version == 6 { + // write salt for v6 signatures + _, err := w.Write([]byte{uint8(len(ops.Salt))}) + if err != nil { + return err + } + _, err = w.Write(ops.Salt) + if err != nil { + return err + } + + // write fingerprint v6 signatures + _, err = w.Write([]byte{byte(ops.Version)}) + if err != nil { + return err + } + _, err = w.Write(ops.KeyFingerprint) + if err != nil { + return err + } + } else { + binary.BigEndian.PutUint64(buf[:8], ops.KeyId) + _, err := w.Write(buf[:8]) + if err != nil { + return err + } + } + + isLast := []byte{byte(0)} + if ops.IsLast { + isLast[0] = 1 + } + + _, err = w.Write(isLast) return err } diff --git a/openpgp/packet/public_key.go b/openpgp/packet/public_key.go index da5c504b1..db345c2ea 100644 --- a/openpgp/packet/public_key.go +++ b/openpgp/packet/public_key.go @@ -673,7 +673,7 @@ func keySignatureHash(pk, signed signingKey, hashFunc hash.Hash) (h hash.Hash, e // VerifyKeySignature returns nil iff sig is a valid signature, made by this // public key, of signed. func (pk *PublicKey) VerifyKeySignature(signed *PublicKey, sig *Signature) error { - preparedHash, err := sig.PrepareSignature(sig.Hash, nil) + preparedHash, err := sig.PrepareVerify() if err != nil { return err } @@ -691,7 +691,7 @@ func (pk *PublicKey) VerifyKeySignature(signed *PublicKey, sig *Signature) error if sig.EmbeddedSignature == nil { return errors.StructuralError("signing subkey is missing cross-signature") } - preparedHashEmbedded, err := sig.PrepareSignature(sig.EmbeddedSignature.Hash, nil) + preparedHashEmbedded, err := sig.EmbeddedSignature.PrepareVerify() if err != nil { return err } @@ -721,7 +721,7 @@ func keyRevocationHash(pk signingKey, hashFunc hash.Hash) (h hash.Hash, err erro // VerifyRevocationSignature returns nil iff sig is a valid signature, made by this // public key. func (pk *PublicKey) VerifyRevocationSignature(sig *Signature) (err error) { - preparedHash, err := sig.PrepareSignature(sig.Hash, nil) + preparedHash, err := sig.PrepareVerify() if err != nil { return err } @@ -735,7 +735,7 @@ func (pk *PublicKey) VerifyRevocationSignature(sig *Signature) (err error) { // VerifySubkeyRevocationSignature returns nil iff sig is a valid subkey revocation signature, // made by this public key, of signed. func (pk *PublicKey) VerifySubkeyRevocationSignature(sig *Signature, signed *PublicKey) (err error) { - preparedHash, err := sig.PrepareSignature(sig.Hash, nil) + preparedHash, err := sig.PrepareVerify() if err != nil { return err } @@ -781,7 +781,7 @@ func directKeySignatureHash(pk *PublicKey, hash hash.Hash) (h hash.Hash, err err // VerifyUserIdSignature returns nil iff sig is a valid signature, made by this // public key, that id is the identity of pub. func (pk *PublicKey) VerifyUserIdSignature(id string, pub *PublicKey, sig *Signature) (err error) { - preparedHash, err := sig.PrepareSignature(sig.Hash, nil) + preparedHash, err := sig.PrepareVerify() if err != nil { return err } @@ -795,7 +795,7 @@ func (pk *PublicKey) VerifyUserIdSignature(id string, pub *PublicKey, sig *Signa // VerifyUserIdSignature returns nil iff sig is a valid signature, made by this // public key func (pk *PublicKey) VerifyDirectKeySignature(sig *Signature) (err error) { - preparedHash, err := sig.PrepareSignature(sig.Hash, nil) + preparedHash, err := sig.PrepareVerify() if err != nil { return err } diff --git a/openpgp/packet/signature.go b/openpgp/packet/signature.go index 13b7a8f9c..d6d9b39d1 100644 --- a/openpgp/packet/signature.go +++ b/openpgp/packet/signature.go @@ -116,6 +116,13 @@ type Signature struct { outSubpackets []outputSubpacket } +func (sig *Signature) Salt() []byte { + if sig == nil { + return nil + } + return sig.salt +} + func (sig *Signature) parse(r io.Reader) (err error) { // RFC 4880, section 5.2.3 var buf [7]byte @@ -220,7 +227,7 @@ func (sig *Signature) parse(r io.Reader) (err error) { } saltLength := int(buf[0]) var expectedSaltLength int - expectedSaltLength, err = saltLengthForHash(sig.Hash) + expectedSaltLength, err = SaltLengthForHash(sig.Hash) if err != nil { return } @@ -742,24 +749,60 @@ func (sig *Signature) signPrepareHash(h hash.Hash) (digest []byte, err error) { return } -// PrepareSignature must be called to create a hash object before Sign or Verify for v6 signatures. +// PrepareSign must be called to create a hash object before Sign for v6 signatures. // The created hash object initially hashes a randomly generated salt -// as required by v6 signatures. If the signature is not v6, -// the method returns an empty hash object +// 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. -func (sig *Signature) PrepareSignature(hash crypto.Hash, config *Config) (hash.Hash, error) { - if !hash.Available() { +func (sig *Signature) PrepareSign(config *Config) (hash.Hash, error) { + if !sig.Hash.Available() { return nil, errors.UnsupportedError("hash function") } - hasher := hash.New() + hasher := sig.Hash.New() if sig.Version == 6 { if sig.salt == nil { - saltLength, err := saltLengthForHash(hash) + var err error + sig.salt, err = SignatureSaltForHash(sig.Hash, config.Random()) if err != nil { return nil, err } - sig.salt = make([]byte, saltLength) - config.Random().Read(sig.salt) + } + hasher.Write(sig.salt) + } + return hasher, nil +} + +// PrepareSignWithSalt stores a signature salt within sig for v6 signatrues. +// Assumes salt is generated correctly and checks if length matches. +// If the signature is not v6, the method ignores the salt. +// Use PrepareSign whenever possible instead of this method. +// See RFC the crypto refresh Section 3.2.4. +func (sig *Signature) PrepareSignWithSalt(salt []byte) (error) { + if sig.Version == 6 { + expectedSaltLength, err := SaltLengthForHash(sig.Hash) + if err != nil { + return err + } + if salt == nil || len(salt) != expectedSaltLength { + return errors.InvalidArgumentError("unexpected salt size for the given hash algorithm") + } + sig.salt = salt + } + return nil +} + +// PrepareVerify must be called to create a hash object before verifying v6 signatures. +// The created hash object initially hashes the inernally stored salt. +// If the signature is not v6, the method returns an empty hash object. +// See RFC the crypto refresh Section 3.2.4. +func (sig *Signature) PrepareVerify() (hash.Hash, error) { + if !sig.Hash.Available() { + return nil, errors.UnsupportedError("hash function") + } + hasher := sig.Hash.New() + if sig.Version == 6 { + if sig.salt == nil { + return nil, errors.StructuralError("v6 requires a salt for the hash to be signed") } hasher.Write(sig.salt) } @@ -834,7 +877,7 @@ func (sig *Signature) SignUserId(id string, pub *PublicKey, priv *PrivateKey, co if priv.Dummy() { return errors.ErrDummyPrivateKey("dummy key found") } - prepareHash, err := sig.PrepareSignature(sig.Hash, config) + prepareHash, err := sig.PrepareSign(config) if err != nil { return err } @@ -853,7 +896,7 @@ func (sig *Signature) SignDirectKeyBinding(pub *PublicKey, priv *PrivateKey, con if priv.Dummy() { return errors.ErrDummyPrivateKey("dummy key found") } - prepareHash, err := sig.PrepareSignature(sig.Hash, config) + prepareHash, err := sig.PrepareSign(config) if err != nil { return err } @@ -869,7 +912,7 @@ func (sig *Signature) SignDirectKeyBinding(pub *PublicKey, priv *PrivateKey, con // If config is nil, sensible defaults will be used. func (sig *Signature) CrossSignKey(pub *PublicKey, hashKey *PublicKey, signingKey *PrivateKey, config *Config) error { - prepareHash, err := sig.PrepareSignature(sig.Hash, config) + prepareHash, err := sig.PrepareSign(config) if err != nil { return err } @@ -887,7 +930,7 @@ func (sig *Signature) SignKey(pub *PublicKey, priv *PrivateKey, config *Config) if priv.Dummy() { return errors.ErrDummyPrivateKey("dummy key found") } - prepareHash, err := sig.PrepareSignature(sig.Hash, config) + prepareHash, err := sig.PrepareSign(config) if err != nil { return err } @@ -902,7 +945,7 @@ func (sig *Signature) SignKey(pub *PublicKey, priv *PrivateKey, config *Config) // stored in sig. Call Serialize to write it out. // If config is nil, sensible defaults will be used. func (sig *Signature) RevokeKey(pub *PublicKey, priv *PrivateKey, config *Config) error { - prepareHash, err := sig.PrepareSignature(sig.Hash, config) + prepareHash, err := sig.PrepareSign(config) if err != nil { return err } @@ -1024,7 +1067,7 @@ func (sig *Signature) serializeBody(w io.Writer) (err error) { if err != nil { return } - w.Write(sig.salt) + _, err = w.Write(sig.salt) if err != nil { return } @@ -1249,10 +1292,10 @@ func (sig *Signature) AddMetadataToHashSuffix() { sig.HashSuffix = suffix.Bytes() } -// Select the required salt length for the given hash algorithm, +// 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 -func saltLengthForHash(hash crypto.Hash) (int, error) { +func SaltLengthForHash(hash crypto.Hash) (int, error) { switch (hash) { case crypto.SHA256: return 16, nil case crypto.SHA384: return 24, nil @@ -1261,3 +1304,16 @@ func saltLengthForHash(hash crypto.Hash) (int, error) { default: return 0, errors.UnsupportedError("hash function not supported for V6 signatures") } } + +// 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 +func SignatureSaltForHash(hash crypto.Hash, randReader io.Reader) ([]byte, error) { + saltLength, err := SaltLengthForHash(hash) + if err != nil { + return nil, err + } + salt := make([]byte, saltLength) + randReader.Read(salt) + return salt, nil +} diff --git a/openpgp/read.go b/openpgp/read.go index adf1d576c..f36ae032a 100644 --- a/openpgp/read.go +++ b/openpgp/read.go @@ -46,6 +46,7 @@ type MessageDetails struct { DecryptedWith Key // the private key used to decrypt the message, if any. IsSigned bool // true if the message is signed. SignedByKeyId uint64 // the key id of the signer, if any. + SignedByFingerprint []byte // the key fingerprint of the signer, if any. (only v6) SignedBy *Key // the key of the signer, if available. LiteralData *packet.LiteralData // the metadata of the contents UnverifiedBody io.Reader // the contents of the message. @@ -270,13 +271,17 @@ FindLiteralData: prevLast = true } - h, wrappedHash, err = hashForSignature(p.Hash, p.SigType) + h, wrappedHash, err = hashForSignature(p.Hash, p.SigType, p.Salt) if err != nil { md.SignatureError = err } md.IsSigned = true + if p.Version == 6 { + md.SignedByFingerprint = p.KeyFingerprint + } md.SignedByKeyId = p.KeyId + if keyring != nil { keys := keyring.KeysByIdUsage(p.KeyId, packet.KeyFlagSign) if len(keys) > 0 { @@ -300,12 +305,22 @@ FindLiteralData: return md, nil } +func wrapHashForSignature(hashFunc hash.Hash, sigType packet.SignatureType) (hash.Hash, error) { + switch sigType { + case packet.SigTypeBinary: + return hashFunc, nil + case packet.SigTypeText: + return NewCanonicalTextHash(hashFunc), nil + } + return nil, errors.UnsupportedError("unsupported signature type: " + strconv.Itoa(int(sigType))) +} + // hashForSignature returns a pair of hashes that can be used to verify a // signature. The signature may specify that the contents of the signed message // should be preprocessed (i.e. to normalize line endings). Thus this function // returns two hashes. The second should be used to hash the message itself and // performs any needed preprocessing. -func hashForSignature(hashFunc crypto.Hash, sigType packet.SignatureType) (hash.Hash, hash.Hash, error) { +func hashForSignature(hashFunc crypto.Hash, sigType packet.SignatureType, sigSalt []byte) (hash.Hash, hash.Hash, error) { if _, ok := algorithm.HashToHashIdWithSha1(hashFunc); !ok { return nil, nil, errors.UnsupportedError("unsupported hash function") } @@ -313,14 +328,19 @@ func hashForSignature(hashFunc crypto.Hash, sigType packet.SignatureType) (hash. return nil, nil, errors.UnsupportedError("hash not available: " + strconv.Itoa(int(hashFunc))) } h := hashFunc.New() - + if sigSalt != nil { + h.Write(sigSalt) + } + wrappedHash, err := wrapHashForSignature(h, sigType) + if err != nil { + return nil, nil, err + } switch sigType { case packet.SigTypeBinary: - return h, h, nil + return h, wrappedHash, nil case packet.SigTypeText: - return h, NewCanonicalTextHash(h), nil + return h, wrappedHash, nil } - return nil, nil, errors.UnsupportedError("unsupported signature type: " + strconv.Itoa(int(sigType))) } @@ -503,7 +523,11 @@ func verifyDetachedSignature(keyring KeyRing, signed, signature io.Reader, expec panic("unreachable") } - h, wrappedHash, err := hashForSignature(hashFunc, sigType) + h, err := sig.PrepareVerify() + if err != nil { + return nil, nil, err + } + wrappedHash, err := wrapHashForSignature(h, sigType) if err != nil { return nil, nil, err } diff --git a/openpgp/write.go b/openpgp/write.go index 99da90692..f7182ed6a 100644 --- a/openpgp/write.go +++ b/openpgp/write.go @@ -76,7 +76,11 @@ func detachSign(w io.Writer, signer *Entity, message io.Reader, sigType packet.S sig := createSignaturePacket(signingKey.PublicKey, sigType, config) - h, wrappedHash, err := hashForSignature(sig.Hash, sig.SigType) + h, err := sig.PrepareSign(config) + if err != nil { + return + } + wrappedHash, err := wrapHashForSignature(h, sig.SigType) if err != nil { return } @@ -275,14 +279,29 @@ func writeAndSign(payload io.WriteCloser, candidateHashes []uint8, signed *Entit return nil, errors.InvalidArgumentError("cannot encrypt because no candidate hash functions are compiled in. (Wanted " + name + " in this case.)") } + var salt []byte + var sigVersion = 3 + if config != nil && config.V6Keys { + // use v6 signatures and one pass signatures + sigVersion = 6 + } if signer != nil { ops := &packet.OnePassSignature{ + Version: sigVersion, SigType: sigType, Hash: hash, PubKeyAlgo: signer.PubKeyAlgo, KeyId: signer.KeyId, IsLast: true, } + if sigVersion == 6 { + ops.KeyFingerprint = signer.Fingerprint + salt, err = packet.SignatureSaltForHash(hash, config.Random()) + if err != nil { + return nil, err + } + ops.Salt = salt + } if err := ops.Serialize(payload); err != nil { return nil, err } @@ -310,7 +329,7 @@ func writeAndSign(payload io.WriteCloser, candidateHashes []uint8, signed *Entit } if signer != nil { - h, wrappedHash, err := hashForSignature(hash, sigType) + h, wrappedHash, err := hashForSignature(hash, sigType, salt) if err != nil { return nil, err } @@ -322,7 +341,7 @@ func writeAndSign(payload io.WriteCloser, candidateHashes []uint8, signed *Entit if hints.IsBinary { metadata.Format = 'b' } - return signatureWriter{payload, literalData, hash, wrappedHash, h, signer, sigType, config, metadata}, nil + return signatureWriter{payload, literalData, hash, wrappedHash, h, salt, signer, sigType, config, metadata}, nil } return literalData, nil } @@ -494,6 +513,7 @@ type signatureWriter struct { hashType crypto.Hash wrappedHash hash.Hash h hash.Hash + salt []byte // v6 only signer *packet.PrivateKey sigType packet.SignatureType config *packet.Config @@ -517,6 +537,10 @@ func (s signatureWriter) Close() error { sig.Hash = s.hashType sig.Metadata = s.metadata + if err := sig.PrepareSignWithSalt(s.salt); err != nil { + return err + } + if err := sig.Sign(s.h, s.signer, s.config); err != nil { return err } From 7ed878a3901207323daee112ac9d93aad561a59a Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Wed, 29 Mar 2023 11:27:57 +0200 Subject: [PATCH 006/116] Remove armor checksums This change removes armor checksums and removes checks accordingly. --- openpgp/armor/armor.go | 52 +++---------------------------------- openpgp/armor/armor_test.go | 6 +++-- openpgp/armor/encode.go | 14 +--------- 3 files changed, 8 insertions(+), 64 deletions(-) diff --git a/openpgp/armor/armor.go b/openpgp/armor/armor.go index d7af9141e..b8978d7a2 100644 --- a/openpgp/armor/armor.go +++ b/openpgp/armor/armor.go @@ -23,7 +23,7 @@ import ( // Headers // // base64-encoded Bytes -// '=' base64 encoded checksum +// '=' base64 encoded checksum (optional) not checked anymore // -----END Type----- // // where Headers is a possibly empty sequence of Key: Value lines. @@ -40,36 +40,15 @@ type Block struct { var ArmorCorrupt error = errors.StructuralError("armor invalid") -const crc24Init = 0xb704ce -const crc24Poly = 0x1864cfb -const crc24Mask = 0xffffff - -// crc24 calculates the OpenPGP checksum as specified in RFC 4880, section 6.1 -func crc24(crc uint32, d []byte) uint32 { - for _, b := range d { - crc ^= uint32(b) << 16 - for i := 0; i < 8; i++ { - crc <<= 1 - if crc&0x1000000 != 0 { - crc ^= crc24Poly - } - } - } - return crc -} - var armorStart = []byte("-----BEGIN ") var armorEnd = []byte("-----END ") var armorEndOfLine = []byte("-----") -// lineReader wraps a line based reader. It watches for the end of an armor -// block and records the expected CRC value. +// lineReader wraps a line based reader. It watches for the end of an armor block type lineReader struct { in *bufio.Reader buf []byte eof bool - crc uint32 - crcSet bool } func (l *lineReader) Read(p []byte) (n int, err error) { @@ -98,26 +77,9 @@ func (l *lineReader) Read(p []byte) (n int, err error) { if len(line) == 5 && line[0] == '=' { // This is the checksum line - var expectedBytes [3]byte - var m int - m, err = base64.StdEncoding.Decode(expectedBytes[0:], line[1:]) - if m != 3 || err != nil { - return - } - l.crc = uint32(expectedBytes[0])<<16 | - uint32(expectedBytes[1])<<8 | - uint32(expectedBytes[2]) - - line, _, err = l.in.ReadLine() - if err != nil && err != io.EOF { - return - } - if !bytes.HasPrefix(line, armorEnd) { - return 0, ArmorCorrupt - } + // Don't check the checksum l.eof = true - l.crcSet = true return 0, io.EOF } @@ -144,17 +106,10 @@ func (l *lineReader) Read(p []byte) (n int, err error) { type openpgpReader struct { lReader *lineReader b64Reader io.Reader - currentCRC uint32 } func (r *openpgpReader) Read(p []byte) (n int, err error) { n, err = r.b64Reader.Read(p) - r.currentCRC = crc24(r.currentCRC, p[:n]) - - if err == io.EOF && r.lReader.crcSet && r.lReader.crc != uint32(r.currentCRC&crc24Mask) { - return 0, ArmorCorrupt - } - return } @@ -222,7 +177,6 @@ TryNextBlock: } p.lReader.in = r - p.oReader.currentCRC = crc24Init p.oReader.lReader = &p.lReader p.oReader.b64Reader = base64.NewDecoder(base64.StdEncoding, &p.lReader) p.Body = &p.oReader diff --git a/openpgp/armor/armor_test.go b/openpgp/armor/armor_test.go index 595612493..6d0566c15 100644 --- a/openpgp/armor/armor_test.go +++ b/openpgp/armor/armor_test.go @@ -8,6 +8,7 @@ import ( "bytes" "hash/adler32" "io/ioutil" + "strings" "testing" ) @@ -49,8 +50,9 @@ func TestDecodeEncode(t *testing.T) { } w.Close() - if !bytes.Equal(buf.Bytes(), []byte(armorExample1)) { - t.Errorf("got: %s\nwant: %s", buf.String(), armorExample1) + expected := strings.Replace(armorExample1, "=/teI\n", "", 1) // CRC should not be present + if !bytes.Equal(buf.Bytes(), []byte(expected)) { + t.Errorf("got: %s\nwant: %s", buf.String(), expected) } } diff --git a/openpgp/armor/encode.go b/openpgp/armor/encode.go index 5b6e16c19..f2d6bc823 100644 --- a/openpgp/armor/encode.go +++ b/openpgp/armor/encode.go @@ -10,7 +10,6 @@ import ( ) var armorHeaderSep = []byte(": ") -var blockEnd = []byte("\n=") var newline = []byte("\n") var armorEndOfLineOut = []byte("-----\n") @@ -102,12 +101,10 @@ type encoding struct { out io.Writer breaker *lineBreaker b64 io.WriteCloser - crc uint32 blockType []byte } func (e *encoding) Write(data []byte) (n int, err error) { - e.crc = crc24(e.crc, data) return e.b64.Write(data) } @@ -118,15 +115,7 @@ func (e *encoding) Close() (err error) { } e.breaker.Close() - var checksumBytes [3]byte - checksumBytes[0] = byte(e.crc >> 16) - checksumBytes[1] = byte(e.crc >> 8) - checksumBytes[2] = byte(e.crc) - - var b64ChecksumBytes [4]byte - base64.StdEncoding.Encode(b64ChecksumBytes[:], checksumBytes[:]) - - return writeSlices(e.out, blockEnd, b64ChecksumBytes[:], newline, armorEnd, e.blockType, armorEndOfLine) + return writeSlices(e.out, newline, armorEnd, e.blockType, armorEndOfLine) } // Encode returns a WriteCloser which will encode the data written to it in @@ -153,7 +142,6 @@ func Encode(out io.Writer, blockType string, headers map[string]string) (w io.Wr e := &encoding{ out: out, breaker: newLineBreaker(out, 64), - crc: crc24Init, blockType: bType, } e.b64 = base64.NewEncoder(base64.StdEncoding, e.breaker) From b5fb1e398b2ac4986a7ab36921f316305588e70b Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Wed, 29 Mar 2023 12:13:38 +0200 Subject: [PATCH 007/116] FIX: Ensure all IVs and salts contain randomness equal to their length --- openpgp/packet/signature.go | 4 ++++ openpgp/packet/symmetrically_encrypted_aead.go | 2 +- openpgp/packet/symmetrically_encrypted_mdc.go | 5 ++++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/openpgp/packet/signature.go b/openpgp/packet/signature.go index d6d9b39d1..e18366a00 100644 --- a/openpgp/packet/signature.go +++ b/openpgp/packet/signature.go @@ -1314,6 +1314,10 @@ func SignatureSaltForHash(hash crypto.Hash, randReader io.Reader) ([]byte, error return nil, err } salt := make([]byte, saltLength) + _, err = io.ReadFull(randReader, salt) + if err != nil { + return nil, err + } randReader.Read(salt) return salt, nil } diff --git a/openpgp/packet/symmetrically_encrypted_aead.go b/openpgp/packet/symmetrically_encrypted_aead.go index e96252c19..a8ef0bbbe 100644 --- a/openpgp/packet/symmetrically_encrypted_aead.go +++ b/openpgp/packet/symmetrically_encrypted_aead.go @@ -115,7 +115,7 @@ func serializeSymmetricallyEncryptedAead(ciphertext io.WriteCloser, cipherSuite // Random salt salt := make([]byte, aeadSaltSize) - if _, err := rand.Read(salt); err != nil { + if _, err := io.ReadFull(rand, salt); err != nil { return nil, err } diff --git a/openpgp/packet/symmetrically_encrypted_mdc.go b/openpgp/packet/symmetrically_encrypted_mdc.go index fa26bebe3..645963fa7 100644 --- a/openpgp/packet/symmetrically_encrypted_mdc.go +++ b/openpgp/packet/symmetrically_encrypted_mdc.go @@ -237,7 +237,10 @@ func serializeSymmetricallyEncryptedMdc(ciphertext io.WriteCloser, c CipherFunct block := c.new(key) blockSize := block.BlockSize() iv := make([]byte, blockSize) - _, err = config.Random().Read(iv) + _, err = io.ReadFull(config.Random(), iv) + if err != nil { + return nil, err + } if err != nil { return } From d1a77a12656f1d41ca7cec224966ce366d17144c Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Wed, 29 Mar 2023 13:45:50 +0200 Subject: [PATCH 008/116] FIX: SignatureType constants All these constants should have the type SignatureType and not int. --- openpgp/packet/packet.go | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/openpgp/packet/packet.go b/openpgp/packet/packet.go index 4d86a7da8..57a452689 100644 --- a/openpgp/packet/packet.go +++ b/openpgp/packet/packet.go @@ -385,17 +385,17 @@ type SignatureType uint8 const ( SigTypeBinary SignatureType = 0x00 - SigTypeText = 0x01 - SigTypeGenericCert = 0x10 - SigTypePersonaCert = 0x11 - SigTypeCasualCert = 0x12 - SigTypePositiveCert = 0x13 - SigTypeSubkeyBinding = 0x18 - SigTypePrimaryKeyBinding = 0x19 - SigTypeDirectSignature = 0x1F - SigTypeKeyRevocation = 0x20 - SigTypeSubkeyRevocation = 0x28 - SigTypeCertificationRevocation = 0x30 + SigTypeText SignatureType = 0x01 + SigTypeGenericCert SignatureType = 0x10 + SigTypePersonaCert SignatureType = 0x11 + SigTypeCasualCert SignatureType = 0x12 + SigTypePositiveCert SignatureType = 0x13 + SigTypeSubkeyBinding SignatureType = 0x18 + SigTypePrimaryKeyBinding SignatureType = 0x19 + SigTypeDirectSignature SignatureType = 0x1F + SigTypeKeyRevocation SignatureType = 0x20 + SigTypeSubkeyRevocation SignatureType = 0x28 + SigTypeCertificationRevocation SignatureType = 0x30 ) // PublicKeyAlgorithm represents the different public key system specified for From b11525dec3eaa242703fffe74016b3128f4768e7 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Wed, 29 Mar 2023 13:46:27 +0200 Subject: [PATCH 009/116] Add support for Padding packets --- openpgp/packet/packet.go | 5 ++++- openpgp/packet/padding.go | 21 +++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 openpgp/packet/padding.go diff --git a/openpgp/packet/packet.go b/openpgp/packet/packet.go index 57a452689..b0c0dced1 100644 --- a/openpgp/packet/packet.go +++ b/openpgp/packet/packet.go @@ -317,6 +317,7 @@ const ( packetTypeUserAttribute packetType = 17 packetTypeSymmetricallyEncryptedIntegrityProtected packetType = 18 packetTypeAEADEncrypted packetType = 20 + packetPadding packetType = 21 ) // EncryptedDataPacket holds encrypted data. It is currently implemented by @@ -328,7 +329,7 @@ type EncryptedDataPacket interface { // Read reads a single OpenPGP packet from the given io.Reader. If there is an // error parsing a packet, the whole packet is consumed from the input. func Read(r io.Reader) (p Packet, err error) { - tag, _, contents, err := readHeader(r) + tag, len, contents, err := readHeader(r) if err != nil { return } @@ -367,6 +368,8 @@ func Read(r io.Reader) (p Packet, err error) { p = se case packetTypeAEADEncrypted: p = new(AEADEncrypted) + case packetPadding: + p = Padding(len) default: err = errors.UnknownPacketTypeError(tag) } diff --git a/openpgp/packet/padding.go b/openpgp/packet/padding.go new file mode 100644 index 000000000..2ceab9346 --- /dev/null +++ b/openpgp/packet/padding.go @@ -0,0 +1,21 @@ +package packet + +import "io" + +// Padding type represents a Padding Packet (Tag 21). +// The padding type is represented by the length of its padding. +// see https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh#name-padding-packet-tag-21 +type Padding int + +// parse just ignores the padding content. +func (pad Padding) parse(reader io.Reader) error { + buffer := make([]byte, int64(pad)) + _, err := io.ReadFull(reader, buffer) + return err +} + +// SerializePadding writes the padding to writer. +func (pad Padding) SerializePadding(writer io.Writer, rand io.Reader) error { + _, err := io.CopyN(writer, rand, int64(pad)) + return err +} From ff41495db8923099c24197dcc839efdbecad7625 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Wed, 29 Mar 2023 14:39:15 +0200 Subject: [PATCH 010/116] Add support for SKESK v6 --- openpgp/packet/symmetric_key_encrypted.go | 71 ++++++++++++++----- .../symmetric_key_encrypted_data_test.go | 9 ++- .../packet/symmetric_key_encrypted_test.go | 2 +- 3 files changed, 64 insertions(+), 18 deletions(-) diff --git a/openpgp/packet/symmetric_key_encrypted.go b/openpgp/packet/symmetric_key_encrypted.go index bac2b132e..392f1d79b 100644 --- a/openpgp/packet/symmetric_key_encrypted.go +++ b/openpgp/packet/symmetric_key_encrypted.go @@ -7,11 +7,13 @@ package packet import ( "bytes" "crypto/cipher" + "crypto/sha256" "io" "strconv" "github.com/ProtonMail/go-crypto/openpgp/errors" "github.com/ProtonMail/go-crypto/openpgp/s2k" + "golang.org/x/crypto/hkdf" ) // This is the largest session key that we'll support. Since at most 256-bit cipher @@ -39,10 +41,17 @@ func (ske *SymmetricKeyEncrypted) parse(r io.Reader) error { return err } ske.Version = int(buf[0]) - if ske.Version != 4 && ske.Version != 5 { + if ske.Version != 4 && ske.Version != 5 && ske.Version != 6 { return errors.UnsupportedError("unknown SymmetricKeyEncrypted version") } + if ske.Version > 5 { + // Scalar octet count + if _, err := readFull(r, buf[:]); err != nil { + return err + } + } + // Cipher function if _, err := readFull(r, buf[:]); err != nil { return err @@ -52,7 +61,7 @@ func (ske *SymmetricKeyEncrypted) parse(r io.Reader) error { return errors.UnsupportedError("unknown cipher: " + strconv.Itoa(int(buf[0]))) } - if ske.Version == 5 { + if ske.Version >= 5 { // AEAD mode if _, err := readFull(r, buf[:]); err != nil { return errors.StructuralError("cannot read AEAD octet from packet") @@ -60,6 +69,13 @@ func (ske *SymmetricKeyEncrypted) parse(r io.Reader) error { ske.Mode = AEADMode(buf[0]) } + if ske.Version > 5 { + // Scalar octet count + if _, err := readFull(r, buf[:]); err != nil { + return err + } + } + var err error if ske.s2k, err = s2k.Parse(r); err != nil { if _, ok := err.(errors.ErrDummyPrivateKey); ok { @@ -68,7 +84,7 @@ func (ske *SymmetricKeyEncrypted) parse(r io.Reader) error { return err } - if ske.Version == 5 { + if ske.Version >= 5 { // AEAD IV iv := make([]byte, ske.Mode.IvLength()) _, err := readFull(r, iv) @@ -109,8 +125,8 @@ func (ske *SymmetricKeyEncrypted) Decrypt(passphrase []byte) ([]byte, CipherFunc case 4: plaintextKey, cipherFunc, err := ske.decryptV4(key) return plaintextKey, cipherFunc, err - case 5: - plaintextKey, err := ske.decryptV5(key) + case 5, 6: + plaintextKey, err := ske.decryptWithV5orV6(ske.Version, key) return plaintextKey, CipherFunction(0), err } err := errors.UnsupportedError("unknown SymmetricKeyEncrypted version") @@ -136,9 +152,9 @@ func (ske *SymmetricKeyEncrypted) decryptV4(key []byte) ([]byte, CipherFunction, return plaintextKey, cipherFunc, nil } -func (ske *SymmetricKeyEncrypted) decryptV5(key []byte) ([]byte, error) { - adata := []byte{0xc3, byte(5), byte(ske.CipherFunc), byte(ske.Mode)} - aead := getEncryptedKeyAeadInstance(ske.CipherFunc, ske.Mode, key, adata) +func (ske *SymmetricKeyEncrypted) decryptWithV5orV6(version int, key []byte) ([]byte, error) { + adata := []byte{0xc3, byte(version), byte(ske.CipherFunc), byte(ske.Mode)} + aead := getEncryptedKeyAeadInstance(ske.CipherFunc, ske.Mode, key, adata, version) plaintextKey, err := aead.Open(nil, ske.iv, ske.encryptedKey, adata) if err != nil { @@ -178,7 +194,7 @@ func SerializeSymmetricKeyEncrypted(w io.Writer, passphrase []byte, config *Conf func SerializeSymmetricKeyEncryptedReuseKey(w io.Writer, sessionKey []byte, passphrase []byte, config *Config) (err error) { var version int if config.AEAD() != nil { - version = 5 + version = 6 } else { version = 4 } @@ -203,11 +219,15 @@ func SerializeSymmetricKeyEncryptedReuseKey(w io.Writer, sessionKey []byte, pass switch version { case 4: packetLength = 2 /* header */ + len(s2kBytes) + 1 /* cipher type */ + keySize - case 5: + case 5, 6: ivLen := config.AEAD().Mode().IvLength() tagLen := config.AEAD().Mode().TagLength() packetLength = 3 + len(s2kBytes) + ivLen + keySize + tagLen } + if version > 5 { + packetLength += 2 // additional octet count fields + } + err = serializeHeader(w, packetTypeSymmetricKeyEncrypted, packetLength) if err != nil { return @@ -216,13 +236,22 @@ func SerializeSymmetricKeyEncryptedReuseKey(w io.Writer, sessionKey []byte, pass // Symmetric Key Encrypted Version buf := []byte{byte(version)} + if version > 5 { + // Scalar octet count + buf = append(buf, byte(3+len(s2kBytes)+config.AEAD().Mode().IvLength())) + } + // Cipher function buf = append(buf, byte(cipherFunc)) - if version == 5 { + if version >= 5 { // AEAD mode buf = append(buf, byte(config.AEAD().Mode())) } + if version > 5 { + // Scalar octet count + buf = append(buf, byte(len(s2kBytes))) + } _, err = w.Write(buf) if err != nil { return @@ -243,10 +272,10 @@ func SerializeSymmetricKeyEncryptedReuseKey(w io.Writer, sessionKey []byte, pass if err != nil { return } - case 5: + case 5, 6: mode := config.AEAD().Mode() - adata := []byte{0xc3, byte(5), byte(cipherFunc), byte(mode)} - aead := getEncryptedKeyAeadInstance(cipherFunc, mode, keyEncryptingKey, adata) + adata := []byte{0xc3, byte(version), byte(cipherFunc), byte(mode)} + aead := getEncryptedKeyAeadInstance(cipherFunc, mode, keyEncryptingKey, adata, version) // Sample iv using random reader iv := make([]byte, config.AEAD().Mode().IvLength()) @@ -270,7 +299,17 @@ func SerializeSymmetricKeyEncryptedReuseKey(w io.Writer, sessionKey []byte, pass return } -func getEncryptedKeyAeadInstance(c CipherFunction, mode AEADMode, inputKey, associatedData []byte) (aead cipher.AEAD) { - blockCipher := c.new(inputKey) +func getEncryptedKeyAeadInstance(c CipherFunction, mode AEADMode, inputKey, associatedData []byte, version int) (aead cipher.AEAD) { + var blockCipher cipher.Block + if version > 5 { + hkdfReader := hkdf.New(sha256.New, inputKey, []byte{}, associatedData) + + encryptionKey := make([]byte, c.KeySize()) + _, _ = readFull(hkdfReader, encryptionKey) + + blockCipher = c.new(encryptionKey) + } else { + blockCipher = c.new(inputKey) + } return mode.new(blockCipher) } diff --git a/openpgp/packet/symmetric_key_encrypted_data_test.go b/openpgp/packet/symmetric_key_encrypted_data_test.go index 119405cb8..1d3249eb4 100644 --- a/openpgp/packet/symmetric_key_encrypted_data_test.go +++ b/openpgp/packet/symmetric_key_encrypted_data_test.go @@ -9,7 +9,7 @@ type packetSequence struct { contents string } -var keyAndIpePackets = []*packetSequence{aeadEaxRFC, aeadOcbRFC} +var keyAndIpePackets = []*packetSequence{symEncTestv6, aeadEaxRFC, aeadOcbRFC} // https://www.ietf.org/archive/id/draft-koch-openpgp-2015-rfc4880bis-00.html#name-complete-aead-eax-encrypted- var aeadEaxRFC = &packetSequence{ @@ -24,3 +24,10 @@ var aeadOcbRFC = &packetSequence{ packets: "c33d05070203089f0b7da3e5ea64779099e326e5400a90936cefb4e8eba08c6773716d1f2714540a38fcac529949dac529d3de31e15b4aeb729e330033dbedd4490107020e5ed2bc1e470abe8f1d644c7a6c8a567b0f7701196611a154ba9c2574cd056284a8ef68035c623d93cc708a43211bb6eaf2b27f7c18d571bcd83b20add3a08b73af15b9a098", contents: "cb1462000000000048656c6c6f2c20776f726c64210a", } + +// OpenPGP crypto refresh A.7.1. +var symEncTestv6 = &packetSequence{ + password: "password", + packets: "c33c061a07030b0308e9d39785b2070008ffb42e7c483ef4884457cb3726b9b3db9ff776e5f4d9a40952e2447298851abfff7526df2dd554417579a7799fd26902070306fcb94490bcb98bbdc9d106c6090266940f72e89edc21b5596b1576b101ed0f9ffc6fc6d65bbfd24dcd0790966e6d1e85a30053784cb1d8b6a0699ef12155a7b2ad6258531b57651fd7777912fa95e35d9b40216f69a4c248db28ff4331f1632907399e6ff9", + contents: "cb1362000000000048656c6c6f2c20776f726c6421d50e1ce2269a9eddef81032172b7ed7c", +} diff --git a/openpgp/packet/symmetric_key_encrypted_test.go b/openpgp/packet/symmetric_key_encrypted_test.go index 8294adb3a..e609993e6 100644 --- a/openpgp/packet/symmetric_key_encrypted_test.go +++ b/openpgp/packet/symmetric_key_encrypted_test.go @@ -67,7 +67,7 @@ func TestDecryptSymmetricKeyAndEncryptedDataPacket(t *testing.T) { } } -func TestSerializeSymmetricKeyEncryptedV5RandomizeSlow(t *testing.T) { +func TestSerializeSymmetricKeyEncryptedV6RandomizeSlow(t *testing.T) { ciphers := map[string]CipherFunction{ "AES128": CipherAES128, "AES192": CipherAES192, From 741dddf7a2b31b58038278ff265b23efd8f15ca7 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Wed, 29 Mar 2023 17:02:45 +0200 Subject: [PATCH 011/116] Add support for PKESK v6 --- openpgp/packet/encrypted_key.go | 202 +++++++++++++++++++++------ openpgp/packet/encrypted_key_test.go | 52 ++++++- openpgp/packet/private_key.go | 11 +- openpgp/write.go | 2 +- openpgp/write_test.go | 11 ++ 5 files changed, 231 insertions(+), 47 deletions(-) diff --git a/openpgp/packet/encrypted_key.go b/openpgp/packet/encrypted_key.go index eeff2902c..17de8c80a 100644 --- a/openpgp/packet/encrypted_key.go +++ b/openpgp/packet/encrypted_key.go @@ -5,9 +5,11 @@ package packet import ( + "bytes" "crypto" "crypto/rsa" "encoding/binary" + "encoding/hex" "io" "math/big" "strconv" @@ -18,30 +20,68 @@ import ( "github.com/ProtonMail/go-crypto/openpgp/internal/encoding" ) -const encryptedKeyVersion = 3 - // EncryptedKey represents a public-key encrypted session key. See RFC 4880, // section 5.1. type EncryptedKey struct { - KeyId uint64 - Algo PublicKeyAlgorithm - CipherFunc CipherFunction // only valid after a successful Decrypt for a v3 packet - Key []byte // only valid after a successful Decrypt + Version int + KeyId uint64 + KeyVersion int // v6 + KeyFingerprint []byte // v6 + Algo PublicKeyAlgorithm + CipherFunc CipherFunction // only valid after a successful Decrypt for a v3 packet + Key []byte // only valid after a successful Decrypt encryptedMPI1, encryptedMPI2 encoding.Field } func (e *EncryptedKey) parse(r io.Reader) (err error) { - var buf [10]byte - _, err = readFull(r, buf[:]) + var buf [8]byte + _, err = readFull(r, buf[:1]) if err != nil { return } - if buf[0] != encryptedKeyVersion { + e.Version = int(buf[0]) + if e.Version != 3 && e.Version != 6 { return errors.UnsupportedError("unknown EncryptedKey version " + strconv.Itoa(int(buf[0]))) } - e.KeyId = binary.BigEndian.Uint64(buf[1:9]) - e.Algo = PublicKeyAlgorithm(buf[9]) + if e.Version == 6 { + _, err = readFull(r, buf[:1]) + if err != nil { + return + } + e.KeyVersion = int(buf[0]) + if e.KeyVersion != 0 && e.KeyVersion != 4 && e.KeyVersion != 6 { + return errors.UnsupportedError("unknown public key version " + strconv.Itoa(e.KeyVersion)) + } + var fingerprint []byte + if e.KeyVersion == 6 { + fingerprint = make([]byte, 32) + } else if e.KeyVersion == 4 { + fingerprint = make([]byte, 20) + } + _, err = readFull(r, fingerprint) + if err != nil { + return + } + e.KeyFingerprint = fingerprint + if e.KeyVersion == 6 { + e.KeyId = binary.BigEndian.Uint64(e.KeyFingerprint[:8]) + } else if e.KeyVersion == 4 { + e.KeyId = binary.BigEndian.Uint64(e.KeyFingerprint[12:20]) + } + } else { + _, err = readFull(r, buf[:8]) + if err != nil { + return + } + e.KeyId = binary.BigEndian.Uint64(buf[:8]) + } + + _, err = readFull(r, buf[:1]) + if err != nil { + return + } + e.Algo = PublicKeyAlgorithm(buf[0]) switch e.Algo { case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly: e.encryptedMPI1 = new(encoding.MPI) @@ -85,9 +125,12 @@ func checksumKeyMaterial(key []byte) uint16 { // private key must have been decrypted first. // If config is nil, sensible defaults will be used. func (e *EncryptedKey) Decrypt(priv *PrivateKey, config *Config) error { - if e.KeyId != 0 && e.KeyId != priv.KeyId { + if e.Version < 6 && e.KeyId != 0 && e.KeyId != priv.KeyId { return errors.InvalidArgumentError("cannot decrypt encrypted session key for key id " + strconv.FormatUint(e.KeyId, 16) + " with private key id " + strconv.FormatUint(priv.KeyId, 16)) } + if e.Version == 6 && e.KeyVersion != 0 && !bytes.Equal(e.KeyFingerprint, priv.Fingerprint) { + return errors.InvalidArgumentError("cannot decrypt encrypted session key for key fingerprint " + hex.EncodeToString(e.KeyFingerprint) + " with private key fingerprint " + hex.EncodeToString(priv.Fingerprint) ) + } if e.Algo != priv.PubKeyAlgo { return errors.InvalidArgumentError("cannot decrypt encrypted session key of type " + strconv.Itoa(int(e.Algo)) + " with private key of type " + strconv.Itoa(int(priv.PubKeyAlgo))) } @@ -121,13 +164,17 @@ func (e *EncryptedKey) Decrypt(priv *PrivateKey, config *Config) error { if err != nil { return err } - - e.CipherFunc = CipherFunction(b[0]) - if !e.CipherFunc.IsSupported() { - return errors.UnsupportedError("unsupported encryption function") + + keyOffset := 0 + if e.Version < 6 { + keyOffset = 1 + e.CipherFunc = CipherFunction(b[0]) + if !e.CipherFunc.IsSupported() { + return errors.UnsupportedError("unsupported encryption function") + } } - - e.Key = b[1 : len(b)-2] + + e.Key = b[keyOffset : len(b)-2] expectedChecksum := uint16(b[len(b)-2])<<8 | uint16(b[len(b)-1]) checksum := checksumKeyMaterial(e.Key) if checksum != expectedChecksum { @@ -151,15 +198,50 @@ func (e *EncryptedKey) Serialize(w io.Writer) error { return errors.InvalidArgumentError("don't know how to serialize encrypted key type " + strconv.Itoa(int(e.Algo))) } - err := serializeHeader(w, packetTypeEncryptedKey, 1 /* version */ +8 /* key id */ +1 /* algo */ +mpiLen) + packetLen := 1 /* version */ +8 /* key id */ +1 /* algo */ + mpiLen + if e.Version == 6 { + packetLen = 1 /* version */ +1 /* algo */ + mpiLen + 1 /* key version */ + if e.KeyVersion == 6 { + packetLen += 32 + } else if e.KeyVersion == 4 { + packetLen += 20 + } + } + + err := serializeHeader(w, packetTypeEncryptedKey, packetLen) if err != nil { return err } - w.Write([]byte{encryptedKeyVersion}) - binary.Write(w, binary.BigEndian, e.KeyId) - w.Write([]byte{byte(e.Algo)}) - + _, err = w.Write([]byte{byte(e.Version)}) + if err != nil { + return err + } + if e.Version == 6 { + _, err = w.Write([]byte{byte(e.KeyVersion)}) + if err != nil { + return err + } + // The key version number may also be zero, + // and the fingerprint omitted + if e.KeyVersion != 0 { + _, err = w.Write(e.KeyFingerprint) + if err != nil { + return err + } + } + } else { + // Write KeyID + err = binary.Write(w, binary.BigEndian, e.KeyId) + if err != nil { + return err + } + } + _, err = w.Write([]byte{byte(e.Algo)}) + if err != nil { + return err + } + switch e.Algo { case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly: _, err := w.Write(e.encryptedMPI1.EncodedBytes()) @@ -184,26 +266,60 @@ func (e *EncryptedKey) Serialize(w io.Writer) error { // SerializeEncryptedKey serializes an encrypted key packet to w that contains // key, encrypted to pub. // If config is nil, sensible defaults will be used. -func SerializeEncryptedKey(w io.Writer, pub *PublicKey, cipherFunc CipherFunction, key []byte, config *Config) error { - var buf [10]byte - buf[0] = encryptedKeyVersion - binary.BigEndian.PutUint64(buf[1:9], pub.KeyId) - buf[9] = byte(pub.PubKeyAlgo) - - keyBlock := make([]byte, 1 /* cipher type */ +len(key)+2 /* checksum */) - keyBlock[0] = byte(cipherFunc) - copy(keyBlock[1:], key) +func SerializeEncryptedKey(w io.Writer, pub *PublicKey, cipherFunc CipherFunction, aeadSupported bool, key []byte, config *Config) error { + var buf [35]byte // max possible header size is v6 + lenHeaderWritten := 1 + version := 3 + + if aeadSupported { + version = 6 + } + // An implementation MUST NOT generate ElGamal v6 PKESKs. + if version == 6 && pub.PubKeyAlgo == PubKeyAlgoElGamal { + return errors.InvalidArgumentError("ElGamal v6 PKESK are not allowed") + } + buf[0] = byte(version) + + if version == 6 { + if pub != nil { + buf[1] = byte(pub.Version) + copy(buf[2: len(pub.Fingerprint)+2], pub.Fingerprint) + lenHeaderWritten += len(pub.Fingerprint) + 1 + } else { + // anonymous case + buf[1] = 0 + lenHeaderWritten += 1 + } + } else { + binary.BigEndian.PutUint64(buf[1:9], pub.KeyId) + lenHeaderWritten += 8 + } + buf[lenHeaderWritten] = byte(pub.PubKeyAlgo) + lenHeaderWritten += 1 + + lenKeyBlock := 1 /* cipher type */ +len(key)+2 /* checksum */ + if version == 6 { + lenKeyBlock = len(key) + 2 // no cipher type + } + keyBlock := make([]byte, lenKeyBlock) + keyOffset := 0 + if version < 6 { + keyBlock[0] = byte(cipherFunc) + keyOffset = 1 + } + + copy(keyBlock[keyOffset:], key) checksum := checksumKeyMaterial(key) - keyBlock[1+len(key)] = byte(checksum >> 8) - keyBlock[1+len(key)+1] = byte(checksum) + keyBlock[keyOffset+len(key)] = byte(checksum >> 8) + keyBlock[keyOffset+len(key)+1] = byte(checksum) switch pub.PubKeyAlgo { case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly: - return serializeEncryptedKeyRSA(w, config.Random(), buf, pub.PublicKey.(*rsa.PublicKey), keyBlock) + return serializeEncryptedKeyRSA(w, config.Random(), buf[:lenHeaderWritten], pub.PublicKey.(*rsa.PublicKey), keyBlock) case PubKeyAlgoElGamal: - return serializeEncryptedKeyElGamal(w, config.Random(), buf, pub.PublicKey.(*elgamal.PublicKey), keyBlock) + return serializeEncryptedKeyElGamal(w, config.Random(), buf[:lenHeaderWritten], pub.PublicKey.(*elgamal.PublicKey), keyBlock) case PubKeyAlgoECDH: - return serializeEncryptedKeyECDH(w, config.Random(), buf, pub.PublicKey.(*ecdh.PublicKey), keyBlock, pub.oid, pub.Fingerprint) + return serializeEncryptedKeyECDH(w, config.Random(), buf[:lenHeaderWritten], pub.PublicKey.(*ecdh.PublicKey), keyBlock, pub.oid, pub.Fingerprint) case PubKeyAlgoDSA, PubKeyAlgoRSASignOnly: return errors.InvalidArgumentError("cannot encrypt to public key of type " + strconv.Itoa(int(pub.PubKeyAlgo))) } @@ -211,14 +327,14 @@ func SerializeEncryptedKey(w io.Writer, pub *PublicKey, cipherFunc CipherFunctio return errors.UnsupportedError("encrypting a key to public key of type " + strconv.Itoa(int(pub.PubKeyAlgo))) } -func serializeEncryptedKeyRSA(w io.Writer, rand io.Reader, header [10]byte, pub *rsa.PublicKey, keyBlock []byte) error { +func serializeEncryptedKeyRSA(w io.Writer, rand io.Reader, header []byte, pub *rsa.PublicKey, keyBlock []byte) error { cipherText, err := rsa.EncryptPKCS1v15(rand, pub, keyBlock) if err != nil { return errors.InvalidArgumentError("RSA encryption failed: " + err.Error()) } cipherMPI := encoding.NewMPI(cipherText) - packetLen := 10 /* header length */ + int(cipherMPI.EncodedLength()) + packetLen := len(header) /* header length */ + int(cipherMPI.EncodedLength()) err = serializeHeader(w, packetTypeEncryptedKey, packetLen) if err != nil { @@ -232,13 +348,13 @@ func serializeEncryptedKeyRSA(w io.Writer, rand io.Reader, header [10]byte, pub return err } -func serializeEncryptedKeyElGamal(w io.Writer, rand io.Reader, header [10]byte, pub *elgamal.PublicKey, keyBlock []byte) error { +func serializeEncryptedKeyElGamal(w io.Writer, rand io.Reader, header []byte, pub *elgamal.PublicKey, keyBlock []byte) error { c1, c2, err := elgamal.Encrypt(rand, pub, keyBlock) if err != nil { return errors.InvalidArgumentError("ElGamal encryption failed: " + err.Error()) } - packetLen := 10 /* header length */ + packetLen := len(header) /* header length */ packetLen += 2 /* mpi size */ + (c1.BitLen()+7)/8 packetLen += 2 /* mpi size */ + (c2.BitLen()+7)/8 @@ -257,7 +373,7 @@ func serializeEncryptedKeyElGamal(w io.Writer, rand io.Reader, header [10]byte, return err } -func serializeEncryptedKeyECDH(w io.Writer, rand io.Reader, header [10]byte, pub *ecdh.PublicKey, keyBlock []byte, oid encoding.Field, fingerprint []byte) error { +func serializeEncryptedKeyECDH(w io.Writer, rand io.Reader, header []byte, pub *ecdh.PublicKey, keyBlock []byte, oid encoding.Field, fingerprint []byte) error { vsG, c, err := ecdh.Encrypt(rand, pub, keyBlock, oid.EncodedBytes(), fingerprint) if err != nil { return errors.InvalidArgumentError("ECDH encryption failed: " + err.Error()) @@ -266,7 +382,7 @@ func serializeEncryptedKeyECDH(w io.Writer, rand io.Reader, header [10]byte, pub g := encoding.NewMPI(vsG) m := encoding.NewOID(c) - packetLen := 10 /* header length */ + packetLen := len(header) /* header length */ packetLen += int(g.EncodedLength()) + int(m.EncodedLength()) err = serializeHeader(w, packetTypeEncryptedKey, packetLen) diff --git a/openpgp/packet/encrypted_key_test.go b/openpgp/packet/encrypted_key_test.go index fea229bd8..57b4c1258 100644 --- a/openpgp/packet/encrypted_key_test.go +++ b/openpgp/packet/encrypted_key_test.go @@ -11,6 +11,7 @@ import ( "io" "math/big" "testing" + "time" "crypto" "crypto/rsa" @@ -164,7 +165,7 @@ func TestEncryptingEncryptedKey(t *testing.T) { } buf := new(bytes.Buffer) - err := SerializeEncryptedKey(buf, pub, CipherAES128, key, nil) + err := SerializeEncryptedKey(buf, pub, CipherAES128, false, key, nil) if err != nil { t.Errorf("error writing encrypted key packet: %s", err) } @@ -202,6 +203,55 @@ func TestEncryptingEncryptedKey(t *testing.T) { } } +func TestEncryptingEncryptedKeyV6(t *testing.T) { + key := []byte{1, 2, 3, 4} + config := &Config{ + AEADConfig: &AEADConfig{ + }, + } + rsaKey, _ := rsa.GenerateKey(config.Random(), 2048) + rsaWrappedKey := NewRSAPrivateKey(time.Now(), rsaKey) + rsaWrappedKey.UpgradeToV6() + rsaWrappedKeyPub := &rsaWrappedKey.PublicKey + + buf := new(bytes.Buffer) + err := SerializeEncryptedKey(buf, rsaWrappedKeyPub, CipherAES128, true, key, config) + + if err != nil { + t.Errorf("error writing encrypted key packet: %s", err) + } + + p, err := Read(buf) + if err != nil { + t.Errorf("error from Read: %s", err) + return + } + ek, ok := p.(*EncryptedKey) + if !ok { + t.Errorf("didn't parse an EncryptedKey, got %#v", p) + return + } + + if !bytes.Equal(ek.KeyFingerprint, rsaWrappedKey.Fingerprint) || + ek.Algo != PubKeyAlgoRSA || + ek.KeyVersion != rsaWrappedKey.Version { + t.Errorf("unexpected EncryptedKey contents: %#v", ek) + return + } + + err = ek.Decrypt(rsaWrappedKey, nil) + if err != nil { + t.Errorf("error from Decrypt: %s", err) + return + } + + keyHex := fmt.Sprintf("%x", ek.Key) + expectedKeyHex := fmt.Sprintf("%x", key) + if keyHex != expectedKeyHex { + t.Errorf("bad key, got %s want %s", keyHex, expectedKeyHex) + } +} + func TestSerializingEncryptedKey(t *testing.T) { const encryptedKeyHex = "c18c032a67d68660df41c70104005789d0de26b6a50c985a02a13131ca829c413a35d0e6fa8d6842599252162808ac7439c72151c8c6183e76923fe3299301414d0c25a2f06a2257db3839e7df0ec964773f6e4c4ac7ff3b48c444237166dd46ba8ff443a5410dc670cb486672fdbe7c9dfafb75b4fea83af3a204fe2a7dfa86bd20122b4f3d2646cbeecb8f7be8" diff --git a/openpgp/packet/private_key.go b/openpgp/packet/private_key.go index 998ced5a8..cd1fecb89 100644 --- a/openpgp/packet/private_key.go +++ b/openpgp/packet/private_key.go @@ -264,6 +264,11 @@ func (pk *PrivateKey) parse(r io.Reader) (err error) { if len(privateKeyData) < 2 { return errors.StructuralError("truncated private key data") } + + if pk.Version == 6 { + // No checksum + return pk.parsePrivateKey(privateKeyData) + } var sum uint16 for i := 0; i < len(privateKeyData)-2; i++ { sum += uint16(privateKeyData[i]) @@ -343,8 +348,10 @@ func (pk *PrivateKey) Serialize(w io.Writer) (err error) { return err } l = buf.Len() - checksum := mod64kHash(buf.Bytes()) - buf.Write([]byte{byte(checksum >> 8), byte(checksum)}) + if pk.Version != 6 { + checksum := mod64kHash(buf.Bytes()) + buf.Write([]byte{byte(checksum >> 8), byte(checksum)}) + } priv = buf.Bytes() } else { priv, l = pk.encryptedData, len(pk.encryptedData) diff --git a/openpgp/write.go b/openpgp/write.go index f7182ed6a..65a0c12f7 100644 --- a/openpgp/write.go +++ b/openpgp/write.go @@ -451,7 +451,7 @@ func encrypt(keyWriter io.Writer, dataWriter io.Writer, to []*Entity, signed *En } for _, key := range encryptKeys { - if err := packet.SerializeEncryptedKey(keyWriter, key.PublicKey, cipher, symKey, config); err != nil { + if err := packet.SerializeEncryptedKey(keyWriter, key.PublicKey, cipher, aeadSupported, symKey, config); err != nil { return nil, err } } diff --git a/openpgp/write_test.go b/openpgp/write_test.go index 28f5a96d3..38a759d4f 100644 --- a/openpgp/write_test.go +++ b/openpgp/write_test.go @@ -428,22 +428,27 @@ func TestSymmetricEncryptionV5RandomizeSlow(t *testing.T) { var testEncryptionTests = []struct { keyRingHex string isSigned bool + okV6 bool }{ { testKeys1And2PrivateHex, false, + true, }, { testKeys1And2PrivateHex, true, + true, }, { dsaElGamalTestKeysHex, false, + false, }, { dsaElGamalTestKeysHex, true, + false, }, } @@ -497,7 +502,13 @@ func TestEncryption(t *testing.T) { config.AEADConfig = &aeadConf } + w, err := Encrypt(buf, kring[:1], signed, nil /* no hints */, config) + if err != nil && config.AEAD() != nil && !test.okV6 { + // ElGamal is not allowed with v6 + continue + } + if err != nil { t.Errorf("#%d: error in Encrypt: %s", i, err) continue From ed35ec16c5ba86fb897cc7bb2d9cd2347445aa86 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Thu, 30 Mar 2023 17:02:19 +0200 Subject: [PATCH 012/116] Add support for v6 AEAD private key encryption The OpenPGP crypto refresh allows to encrypt secret keys with AEAD encryption. In this commit, we add AEAD secret key encryption and the addtional HKDF step in key derivation. The packet.Config contains a new field to configure the type of secret key encryption. --- openpgp/packet/config.go | 13 ++ openpgp/packet/packet.go | 5 + openpgp/packet/private_key.go | 260 +++++++++++++++++++++-------- openpgp/packet/private_key_test.go | 132 ++++++++------- 4 files changed, 279 insertions(+), 131 deletions(-) diff --git a/openpgp/packet/config.go b/openpgp/packet/config.go index 475a0b7ba..63ae4cc48 100644 --- a/openpgp/packet/config.go +++ b/openpgp/packet/config.go @@ -54,6 +54,12 @@ type Config struct { // // Deprecated: SK2Count should be configured in S2KConfig instead. S2KCount int + // An S2K specifier can be stored in the secret keyring to specify + // how to convert the passphrase to a key that unlocks the secret data. + // This config allows to set this key encryption parameters. + // If nil, the default parameters are used. + // See OpenPGP crypto refresh 3.7.2.1. + S2KSecretKey *S2KType // RSABits is the number of bits in new RSA keys made with NewEntity. // If zero, then 2048 bit keys are created. RSABits int @@ -246,3 +252,10 @@ func (c *Config) Notations() []*Notation { } return c.SignatureNotations } + +func (c *Config) S2KKey() S2KType { + if c == nil || c.S2KSecretKey == nil { + return S2KSHA1 + } + return *c.S2KSecretKey +} diff --git a/openpgp/packet/packet.go b/openpgp/packet/packet.go index b0c0dced1..f8d7e11d2 100644 --- a/openpgp/packet/packet.go +++ b/openpgp/packet/packet.go @@ -515,6 +515,11 @@ func (mode AEADMode) TagLength() int { return algorithm.AEADMode(mode).TagLength() } +// IsSupported returns true if the aead mode is supported from the library +func (mode AEADMode) IsSupported() bool { + return algorithm.AEADMode(mode).TagLength() > 0 +} + // new returns a fresh instance of the given mode. func (mode AEADMode) new(block cipher.Block) cipher.AEAD { return algorithm.AEADMode(mode).New(block) diff --git a/openpgp/packet/private_key.go b/openpgp/packet/private_key.go index cd1fecb89..75f7ada1e 100644 --- a/openpgp/packet/private_key.go +++ b/openpgp/packet/private_key.go @@ -9,9 +9,9 @@ import ( "crypto" "crypto/cipher" "crypto/dsa" - "crypto/rand" "crypto/rsa" "crypto/sha1" + "crypto/sha256" "fmt" "io" "io/ioutil" @@ -26,6 +26,7 @@ import ( "github.com/ProtonMail/go-crypto/openpgp/errors" "github.com/ProtonMail/go-crypto/openpgp/internal/encoding" "github.com/ProtonMail/go-crypto/openpgp/s2k" + "golang.org/x/crypto/hkdf" ) // PrivateKey represents a possibly encrypted private key. See RFC 4880, @@ -36,10 +37,10 @@ type PrivateKey struct { encryptedData []byte cipher CipherFunction s2k func(out, in []byte) + aead AEADMode // only relevant if S2KAEAD is enabled // An *{rsa|dsa|elgamal|ecdh|ecdsa|ed25519}.PrivateKey or // crypto.Signer/crypto.Decrypter (Decryptor RSA only). PrivateKey interface{} - sha1Checksum bool iv []byte // Type of encryption of the S2K packet @@ -193,7 +194,10 @@ func (pk *PrivateKey) parse(r io.Reader) (err error) { if err != nil { return } - // TODO: Set aead fields accordingly + pk.aead = AEADMode(buf[0]) + if !pk.aead.IsSupported() { + return errors.UnsupportedError("unsupported aead mode in private key") + } } // [Optional] Only for a version 6 packet, @@ -218,24 +222,32 @@ func (pk *PrivateKey) parse(r io.Reader) (err error) { return } pk.Encrypted = true - if pk.s2kType == S2KSHA1 { - pk.sha1Checksum = true - } default: return errors.UnsupportedError("deprecated s2k function in private key") } if pk.Encrypted { - blockSize := pk.cipher.blockSize() - // TODO: if pk.s2kType == S2KAEAD use aead nonce size here - if blockSize == 0 { + var ivSize int + // special case for version 5: + // an Initial Vector (IV) of the same length as the cipher's block size. + // If string-to-key usage octet was 253 the IV is used as the nonce + if !v5 && pk.s2kType == S2KAEAD { + ivSize = pk.aead.IvLength() + } else { + ivSize = pk.cipher.blockSize() + } + + if ivSize == 0 { return errors.UnsupportedError("unsupported cipher in private key: " + strconv.Itoa(int(pk.cipher))) } - pk.iv = make([]byte, blockSize) + pk.iv = make([]byte, ivSize) _, err = readFull(r, pk.iv) if err != nil { return } + if v5 && pk.s2kType == S2KAEAD { + pk.iv = pk.iv[:pk.aead.IvLength()] + } } var privateKeyData []byte @@ -312,24 +324,50 @@ func (pk *PrivateKey) Serialize(w io.Writer) (err error) { optional := bytes.NewBuffer(nil) if pk.Encrypted || pk.Dummy() { - optional.Write([]byte{uint8(pk.cipher)}) - + // [Optional] If string-to-key usage octet was 255, 254, or 253, + // a one-octet symmetric encryption algorithm. + if _, err = optional.Write([]byte{uint8(pk.cipher)}); err != nil { + return + } + // [Optional] If string-to-key usage octet was 253, + // a one-octet AEAD algorithm. if pk.s2kType == S2KAEAD { - // Write a one-octet AEAD algorithm. + if _, err = optional.Write([]byte{uint8(pk.aead)}); err != nil { + return + } } s2kBuffer := bytes.NewBuffer(nil) if err := pk.s2kParams.Serialize(s2kBuffer); err != nil { return err } + // [Optional] Only for a version 6 packet, and if string-to-key + // usage octet was 255, 254, or 253, an one-octet + // count of the following field. if pk.Version == 6 { - optional.Write([]byte{uint8(s2kBuffer.Len())}) + if _, err = optional.Write([]byte{uint8(s2kBuffer.Len())}); err != nil { + return + } } - io.Copy(optional, s2kBuffer) + // [Optional] If string-to-key usage octet was 255, 254, or 253, + // a string-to-key (S2K) specifier. The length of the string-to-key specifier + // depends on its type + if _, err = io.Copy(optional, s2kBuffer); err != nil { + return + } + // IV if pk.Encrypted { - // TODO: different field for AEAD? - optional.Write(pk.iv) + if _, err = optional.Write(pk.iv); err != nil { + return + } + if pk.Version == 5 && len(pk.iv) > 0 { + // Add padding for version 5 + padding := make([]byte, pk.cipher.blockSize() - len(pk.iv)) + if _, err = optional.Write(padding); err != nil { + return + } + } } } if pk.Version == 5 || (pk.Version == 6 && pk.s2kType != S2KNON) { @@ -425,37 +463,51 @@ func (pk *PrivateKey) decrypt(decryptionKey []byte) error { if !pk.Encrypted { return nil } - block := pk.cipher.new(decryptionKey) - cfb := cipher.NewCFBDecrypter(block, pk.iv) - - data := make([]byte, len(pk.encryptedData)) - cfb.XORKeyStream(data, pk.encryptedData) - - if pk.sha1Checksum { - if len(data) < sha1.Size { - return errors.StructuralError("truncated private key data") - } - h := sha1.New() - h.Write(data[:len(data)-sha1.Size]) - sum := h.Sum(nil) - if !bytes.Equal(sum, data[len(data)-sha1.Size:]) { - return errors.StructuralError("private key checksum failure") - } - data = data[:len(data)-sha1.Size] - } else { - if len(data) < 2 { - return errors.StructuralError("truncated private key data") + var data []byte + switch pk.s2kType { + case S2KAEAD: + aead := pk.aead.new(block) + additionalData, err := pk.additionalData() + if err != nil { + return err } - var sum uint16 - for i := 0; i < len(data)-2; i++ { - sum += uint16(data[i]) + // Decrypt the encrypted key material with aead + data, err = aead.Open(nil, pk.iv, pk.encryptedData, additionalData) + if err != nil { + return err } - if data[len(data)-2] != uint8(sum>>8) || - data[len(data)-1] != uint8(sum) { - return errors.StructuralError("private key checksum failure") + case S2KSHA1, S2KCHECKSUM: + cfb := cipher.NewCFBDecrypter(block, pk.iv) + data = make([]byte, len(pk.encryptedData)) + cfb.XORKeyStream(data, pk.encryptedData) + if pk.s2kType == S2KSHA1 { + if len(data) < sha1.Size { + return errors.StructuralError("truncated private key data") + } + h := sha1.New() + h.Write(data[:len(data)-sha1.Size]) + sum := h.Sum(nil) + if !bytes.Equal(sum, data[len(data)-sha1.Size:]) { + return errors.StructuralError("private key checksum failure") + } + data = data[:len(data)-sha1.Size] + } else { + if len(data) < 2 { + return errors.StructuralError("truncated private key data") + } + var sum uint16 + for i := 0; i < len(data)-2; i++ { + sum += uint16(data[i]) + } + if data[len(data)-2] != uint8(sum>>8) || + data[len(data)-1] != uint8(sum) { + return errors.StructuralError("private key checksum failure") + } + data = data[:len(data)-2] } - data = data[:len(data)-2] + default: + return errors.InvalidArgumentError("invalid s2k type") } err := pk.parsePrivateKey(data) @@ -471,7 +523,6 @@ func (pk *PrivateKey) decrypt(decryptionKey []byte) error { pk.s2k = nil pk.Encrypted = false pk.encryptedData = nil - return nil } @@ -487,6 +538,9 @@ func (pk *PrivateKey) decryptWithCache(passphrase []byte, keyCache *s2k.Cache) e if err != nil { return err } + if pk.s2kType == S2KAEAD { + key = pk.applyHKDF(key) + } return pk.decrypt(key) } @@ -501,6 +555,9 @@ func (pk *PrivateKey) Decrypt(passphrase []byte) error { key := make([]byte, pk.cipher.KeySize()) pk.s2k(key, passphrase) + if pk.s2kType == S2KAEAD { + key = pk.applyHKDF(key) + } return pk.decrypt(key) } @@ -521,7 +578,7 @@ func DecryptPrivateKeys(keys []*PrivateKey, passphrase []byte) error { } // encrypt encrypts an unencrypted private key. -func (pk *PrivateKey) encrypt(key []byte, params *s2k.Params, cipherFunction CipherFunction) error { +func (pk *PrivateKey) encrypt(key []byte, params *s2k.Params, s2kType S2KType, cipherFunction CipherFunction, rand io.Reader) error { if pk.Dummy() { return errors.ErrDummyPrivateKey("dummy key found") } @@ -547,32 +604,50 @@ func (pk *PrivateKey) encrypt(key []byte, params *s2k.Params, cipherFunction Cip } privateKeyBytes := priv.Bytes() - pk.sha1Checksum = true + pk.s2kType = s2kType block := pk.cipher.new(key) - pk.iv = make([]byte, pk.cipher.blockSize()) - _, err = rand.Read(pk.iv) - if err != nil { - return err - } - cfb := cipher.NewCFBEncrypter(block, pk.iv) - - if pk.sha1Checksum { - pk.s2kType = S2KSHA1 - h := sha1.New() - h.Write(privateKeyBytes) - sum := h.Sum(nil) - privateKeyBytes = append(privateKeyBytes, sum...) - } else { - pk.s2kType = S2KCHECKSUM - var sum uint16 - for _, b := range privateKeyBytes { - sum += uint16(b) + switch s2kType { + case S2KAEAD: + if pk.aead == 0 { + return errors.StructuralError("aead mode is not set on key") + } + aead := pk.aead.new(block) + additionalData, err := pk.additionalData() + if err != nil { + return err + } + pk.iv = make([]byte, aead.NonceSize()) + _, err = io.ReadFull(rand, pk.iv) + if err != nil { + return err + } + // Decrypt the encrypted key material with aead + pk.encryptedData = aead.Seal(nil, pk.iv, privateKeyBytes, additionalData) + case S2KSHA1, S2KCHECKSUM: + pk.iv = make([]byte, pk.cipher.blockSize()) + _, err = io.ReadFull(rand, pk.iv) + if err != nil { + return err + } + cfb := cipher.NewCFBEncrypter(block, pk.iv) + if s2kType == S2KSHA1 { + h := sha1.New() + h.Write(privateKeyBytes) + sum := h.Sum(nil) + privateKeyBytes = append(privateKeyBytes, sum...) + } else { + var sum uint16 + for _, b := range privateKeyBytes { + sum += uint16(b) + } + privateKeyBytes = append(privateKeyBytes, []byte{uint8(sum >> 8), uint8(sum)}...) } - priv.Write([]byte{uint8(sum >> 8), uint8(sum)}) + pk.encryptedData = make([]byte, len(privateKeyBytes)) + cfb.XORKeyStream(pk.encryptedData, privateKeyBytes) + default: + return errors.InvalidArgumentError("invalid s2k type for encryption") } - pk.encryptedData = make([]byte, len(privateKeyBytes)) - cfb.XORKeyStream(pk.encryptedData, privateKeyBytes) pk.Encrypted = true pk.PrivateKey = nil return err @@ -591,8 +666,13 @@ func (pk *PrivateKey) EncryptWithConfig(passphrase []byte, config *Config) error return err } s2k(key, passphrase) + if config.S2KKey() == S2KAEAD { + pk.aead = config.AEAD().Mode() + pk.cipher = config.Cipher() + key = pk.applyHKDF(key) + } // Encrypt the private key with the derived encryption key. - return pk.encrypt(key, params, config.Cipher()) + return pk.encrypt(key, params, config.S2KKey(), config.Cipher(), config.Random()) } // EncryptPrivateKeys encrypts all unencrypted keys with the given config and passphrase. @@ -611,7 +691,12 @@ func EncryptPrivateKeys(keys []*PrivateKey, passphrase []byte, config *Config) e s2k(encryptionKey, passphrase) for _, key := range keys { if key != nil && !key.Dummy() && !key.Encrypted { - err = key.encrypt(encryptionKey, params, config.Cipher()) + if config.S2KKey() == S2KAEAD { + key.aead = config.AEAD().Mode() + key.cipher = config.Cipher() + encryptionKey = key.applyHKDF(encryptionKey) + } + err = key.encrypt(encryptionKey, params, config.S2KKey(), config.Cipher(), config.Random()) if err != nil { return err } @@ -814,6 +899,41 @@ func (pk *PrivateKey) parseEdDSAPrivateKey(data []byte) (err error) { return nil } +func (pk *PrivateKey) additionalData() ([]byte, error) { + additionalData := bytes.NewBuffer(nil) + // Write additional data prefix based on packet type + var packetByte byte + if pk.PublicKey.IsSubkey { + packetByte = 0xc7 + } else { + packetByte = 0xc5 + } + // Write public key to additional data + _, err := additionalData.Write([]byte{packetByte}) + if err != nil { + return nil, err + } + err = pk.PublicKey.serializeWithoutHeaders(additionalData) + if err != nil { + return nil, err + } + return additionalData.Bytes(), nil +} + +func (pk *PrivateKey) applyHKDF(inputKey []byte) []byte { + var packetByte byte + if pk.PublicKey.IsSubkey { + packetByte = 0xc7 + } else { + packetByte = 0xc5 + } + associatedData := []byte{packetByte, byte(pk.Version), byte(pk.cipher), byte(pk.aead)} + hkdfReader := hkdf.New(sha256.New, inputKey, []byte{}, associatedData) + encryptionKey := make([]byte, pk.cipher.KeySize()) + _, _ = readFull(hkdfReader, encryptionKey) + return encryptionKey +} + func validateDSAParameters(priv *dsa.PrivateKey) error { p := priv.P // group prime q := priv.Q // subgroup order diff --git a/openpgp/packet/private_key_test.go b/openpgp/packet/private_key_test.go index 154766f94..1e52fd0d6 100644 --- a/openpgp/packet/private_key_test.go +++ b/openpgp/packet/private_key_test.go @@ -13,6 +13,7 @@ import ( "crypto/rsa" "crypto/x509" "encoding/hex" + "fmt" "hash" "math/big" mathrand "math/rand" @@ -138,67 +139,76 @@ func TestExternalPrivateKeyEncryptDecryptRandomizeSlow(t *testing.T) { } } -func TestExternalPrivateKeyEncryptDecryptArgon2(t *testing.T) { - config := &Config{ - S2KConfig: &s2k.Config{S2KMode: s2k.Argon2S2K}, - } - for i, test := range privateKeyTests { - packet, err := Read(readerFromHex(test.privateKeyHex)) - if err != nil { - t.Errorf("#%d: failed to parse: %s", i, err) - continue - } - - privKey := packet.(*PrivateKey) - - if !privKey.Encrypted { - t.Errorf("#%d: private key isn't encrypted", i) - continue - } - - // Decrypt with the correct password - err = privKey.Decrypt([]byte("testing")) - if err != nil { - t.Errorf("#%d: failed to decrypt: %s", i, err) - continue - } - - // Encrypt with another (possibly empty) password - randomPassword := make([]byte, mathrand.Intn(30)) - rand.Read(randomPassword) - err = privKey.EncryptWithConfig(randomPassword, config) - if err != nil { - t.Errorf("#%d: failed to encrypt: %s", i, err) - continue - } - - // Try to decrypt with incorrect password - incorrect := make([]byte, 1+mathrand.Intn(30)) - for rand.Read(incorrect); bytes.Equal(incorrect, randomPassword); { - rand.Read(incorrect) - } - err = privKey.Decrypt(incorrect) - if err == nil { - t.Errorf("#%d: decrypted with incorrect password\nPassword is:%vDecrypted with:%v", i, randomPassword, incorrect) - continue - } - - // Try to decrypt with old password - err = privKey.Decrypt([]byte("testing")) - if err == nil { - t.Errorf("#%d: decrypted with old password", i) - continue - } - - // Decrypt with correct password - err = privKey.Decrypt(randomPassword) - if err != nil { - t.Errorf("#%d: failed to decrypt: %s", i, err) - continue - } - - if !privKey.CreationTime.Equal(test.creationTime) || privKey.Encrypted { - t.Errorf("#%d: bad result, got: %#v", i, privKey) +func TestExternalPrivateKeyEncryptDecryptS2KModes(t *testing.T) { + sk2Modes := []s2k.Mode{s2k.IteratedSaltedS2K, s2k.Argon2S2K} + sk2KeyTypes := []S2KType{S2KCHECKSUM, S2KAEAD, S2KSHA1} + for _, s2kMode := range sk2Modes { + for _, sk2KeyType := range sk2KeyTypes { + t.Run(fmt.Sprintf("s2kMode:%d-s2kType:%d", s2kMode, sk2KeyType), func(t *testing.T) { + config := &Config{ + S2KConfig: &s2k.Config{S2KMode: s2kMode}, + S2KSecretKey: &sk2KeyType, + } + for i, test := range privateKeyTests { + packet, err := Read(readerFromHex(test.privateKeyHex)) + if err != nil { + t.Errorf("#%d: failed to parse: %s", i, err) + continue + } + + privKey := packet.(*PrivateKey) + + if !privKey.Encrypted { + t.Errorf("#%d: private key isn't encrypted", i) + continue + } + + // Decrypt with the correct password + err = privKey.Decrypt([]byte("testing")) + if err != nil { + t.Errorf("#%d: failed to decrypt: %s", i, err) + continue + } + + // Encrypt with another (possibly empty) password + randomPassword := make([]byte, mathrand.Intn(30)) + rand.Read(randomPassword) + err = privKey.EncryptWithConfig(randomPassword, config) + if err != nil { + t.Errorf("#%d: failed to encrypt: %s", i, err) + continue + } + + // Try to decrypt with incorrect password + incorrect := make([]byte, 1+mathrand.Intn(30)) + for rand.Read(incorrect); bytes.Equal(incorrect, randomPassword); { + rand.Read(incorrect) + } + err = privKey.Decrypt(incorrect) + if err == nil { + t.Errorf("#%d: decrypted with incorrect password\nPassword is:%vDecrypted with:%v", i, randomPassword, incorrect) + continue + } + + // Try to decrypt with old password + err = privKey.Decrypt([]byte("testing")) + if err == nil { + t.Errorf("#%d: decrypted with old password", i) + continue + } + + // Decrypt with correct password + err = privKey.Decrypt(randomPassword) + if err != nil { + t.Errorf("#%d: failed to decrypt: %s", i, err) + continue + } + + if !privKey.CreationTime.Equal(test.creationTime) || privKey.Encrypted { + t.Errorf("#%d: bad result, got: %#v", i, privKey) + } + } + }) } } } From dc99efdbf4bb41c9250466a20199d49245c2512f Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Fri, 31 Mar 2023 14:45:43 +0200 Subject: [PATCH 013/116] Add support for X25519 and X448 session key encryption --- openpgp/key_generation.go | 6 + openpgp/packet/encrypted_key.go | 152 ++++++++++++++++++---- openpgp/packet/encrypted_key_test.go | 67 ++++++++++ openpgp/packet/packet.go | 4 + openpgp/packet/private_key.go | 82 +++++++++++- openpgp/packet/public_key.go | 63 ++++++++- openpgp/read.go | 2 +- openpgp/x25519/x25519.go | 186 +++++++++++++++++++++++++++ openpgp/x25519/x25519_test.go | 60 +++++++++ openpgp/x448/x448.go | 186 +++++++++++++++++++++++++++ openpgp/x448/x448_test.go | 60 +++++++++ 11 files changed, 841 insertions(+), 27 deletions(-) create mode 100644 openpgp/x25519/x25519.go create mode 100644 openpgp/x25519/x25519_test.go create mode 100644 openpgp/x448/x448.go create mode 100644 openpgp/x448/x448_test.go diff --git a/openpgp/key_generation.go b/openpgp/key_generation.go index 366bc4561..ccc10d93b 100644 --- a/openpgp/key_generation.go +++ b/openpgp/key_generation.go @@ -20,6 +20,8 @@ import ( "github.com/ProtonMail/go-crypto/openpgp/internal/algorithm" "github.com/ProtonMail/go-crypto/openpgp/internal/ecc" "github.com/ProtonMail/go-crypto/openpgp/packet" + "github.com/ProtonMail/go-crypto/openpgp/x25519" + "github.com/ProtonMail/go-crypto/openpgp/x448" ) // NewEntity returns an Entity that contains a fresh RSA/RSA keypair with a @@ -318,6 +320,10 @@ func newDecrypter(config *packet.Config) (decrypter interface{}, err error) { return nil, errors.InvalidArgumentError("unsupported curve") } return ecdh.GenerateKey(config.Random(), curve, kdf) + case packet.PubKeyAlgoX25519: + return x25519.GenerateKey(config.Random()) + case packet.PubKeyAlgoX448: + return x448.GenerateKey(config.Random()) default: return nil, errors.InvalidArgumentError("unsupported public key algorithm") } diff --git a/openpgp/packet/encrypted_key.go b/openpgp/packet/encrypted_key.go index 17de8c80a..26239d0d5 100644 --- a/openpgp/packet/encrypted_key.go +++ b/openpgp/packet/encrypted_key.go @@ -18,6 +18,8 @@ import ( "github.com/ProtonMail/go-crypto/openpgp/elgamal" "github.com/ProtonMail/go-crypto/openpgp/errors" "github.com/ProtonMail/go-crypto/openpgp/internal/encoding" + "github.com/ProtonMail/go-crypto/openpgp/x25519" + "github.com/ProtonMail/go-crypto/openpgp/x448" ) // EncryptedKey represents a public-key encrypted session key. See RFC 4880, @@ -32,6 +34,9 @@ type EncryptedKey struct { Key []byte // only valid after a successful Decrypt encryptedMPI1, encryptedMPI2 encoding.Field + ephemeralPublicX25519 *x25519.PublicKey // used for x25519 + ephemeralPublicX448 *x448.PublicKey // used for x448 + encryptedSession []byte // used for x25519 and Ed448 } func (e *EncryptedKey) parse(r io.Reader) (err error) { @@ -108,6 +113,16 @@ func (e *EncryptedKey) parse(r io.Reader) (err error) { if _, err = e.encryptedMPI2.ReadFrom(r); err != nil { return } + case PubKeyAlgoX25519: + e.ephemeralPublicX25519, e.encryptedSession, err = x25519.DecodeFields(r) + if err != nil { + return + } + case PubKeyAlgoX448: + e.ephemeralPublicX448, e.encryptedSession, err = x448.DecodeFields(r) + if err != nil { + return + } } _, err = consumeAll(r) return @@ -157,14 +172,32 @@ func (e *EncryptedKey) Decrypt(priv *PrivateKey, config *Config) error { m := e.encryptedMPI2.Bytes() oid := priv.PublicKey.oid.EncodedBytes() b, err = ecdh.Decrypt(priv.PrivateKey.(*ecdh.PrivateKey), vsG, m, oid, priv.PublicKey.Fingerprint[:]) + case PubKeyAlgoX25519: + b, err = x25519.Decrypt(priv.PrivateKey.(*x25519.PrivateKey), e.ephemeralPublicX25519, e.encryptedSession) + case PubKeyAlgoX448: + b, err = x448.Decrypt(priv.PrivateKey.(*x448.PrivateKey), e.ephemeralPublicX448, e.encryptedSession) default: err = errors.InvalidArgumentError("cannot decrypt encrypted session key with private key of type " + strconv.Itoa(int(priv.PubKeyAlgo))) } - if err != nil { return err } - + + // Handle the no checksum cases + if priv.PubKeyAlgo == PubKeyAlgoX25519 || priv.PubKeyAlgo == PubKeyAlgoX448 { + if e.Version < 6 { + e.Key = b[8:] + e.CipherFunc = CipherFunction(b[0]) + if !e.CipherFunc.IsSupported() { + return errors.UnsupportedError("unsupported encryption function") + } + } else { + e.Key = b + } + return nil + } + + // Handle the checksum cases keyOffset := 0 if e.Version < 6 { keyOffset = 1 @@ -186,21 +219,25 @@ func (e *EncryptedKey) Decrypt(priv *PrivateKey, config *Config) error { // Serialize writes the encrypted key packet, e, to w. func (e *EncryptedKey) Serialize(w io.Writer) error { - var mpiLen int + var encodedLength int switch e.Algo { case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly: - mpiLen = int(e.encryptedMPI1.EncodedLength()) + encodedLength = int(e.encryptedMPI1.EncodedLength()) case PubKeyAlgoElGamal: - mpiLen = int(e.encryptedMPI1.EncodedLength()) + int(e.encryptedMPI2.EncodedLength()) + encodedLength = int(e.encryptedMPI1.EncodedLength()) + int(e.encryptedMPI2.EncodedLength()) case PubKeyAlgoECDH: - mpiLen = int(e.encryptedMPI1.EncodedLength()) + int(e.encryptedMPI2.EncodedLength()) + encodedLength = int(e.encryptedMPI1.EncodedLength()) + int(e.encryptedMPI2.EncodedLength()) + case PubKeyAlgoX25519: + encodedLength = x25519.EncodedFieldsLength(e.encryptedSession) + case PubKeyAlgoX448: + encodedLength = x448.EncodedFieldsLength(e.encryptedSession) default: return errors.InvalidArgumentError("don't know how to serialize encrypted key type " + strconv.Itoa(int(e.Algo))) } - packetLen := 1 /* version */ +8 /* key id */ +1 /* algo */ + mpiLen + packetLen := 1 /* version */ +8 /* key id */ +1 /* algo */ + encodedLength if e.Version == 6 { - packetLen = 1 /* version */ +1 /* algo */ + mpiLen + 1 /* key version */ + packetLen = 1 /* version */ +1 /* algo */ + encodedLength + 1 /* key version */ if e.KeyVersion == 6 { packetLen += 32 } else if e.KeyVersion == 4 { @@ -258,6 +295,12 @@ func (e *EncryptedKey) Serialize(w io.Writer) error { } _, err := w.Write(e.encryptedMPI2.EncodedBytes()) return err + case PubKeyAlgoX25519: + err := x25519.EncodeFields(w, e.ephemeralPublicX25519, e.encryptedSession) + return err + case PubKeyAlgoX448: + err := x448.EncodeFields(w, e.ephemeralPublicX448, e.encryptedSession) + return err default: panic("internal error") } @@ -297,21 +340,38 @@ func SerializeEncryptedKey(w io.Writer, pub *PublicKey, cipherFunc CipherFunctio buf[lenHeaderWritten] = byte(pub.PubKeyAlgo) lenHeaderWritten += 1 - lenKeyBlock := 1 /* cipher type */ +len(key)+2 /* checksum */ - if version == 6 { - lenKeyBlock = len(key) + 2 // no cipher type + var keyBlock []byte + // for X25519 there is no checksum and a special encoding + if pub.PubKeyAlgo == PubKeyAlgoX25519 || pub.PubKeyAlgo == PubKeyAlgoX448 { + // no checksum is added + if version < 6 { + // For v3 PKESK packets, seven zero-octets are + // added as padding after the algorithm identifier + keyBlock = make([]byte, len(key) + 8) + keyBlock[0] = byte(cipherFunc) + copy(keyBlock[8:], key) + } else { + // For v6 PKESK packets, no checksum or padding + // are added to the session key before keywrapping + keyBlock = key + } + } else { + lenKeyBlock := 1 /* cipher type */ +len(key)+2 /* checksum */ + if version == 6 { + lenKeyBlock -= 1 // no cipher type + } + keyBlock = make([]byte, lenKeyBlock) + keyOffset := 0 + if version < 6 { + keyBlock[0] = byte(cipherFunc) + keyOffset = 1 + } + + copy(keyBlock[keyOffset:], key) + checksum := checksumKeyMaterial(key) + keyBlock[keyOffset+len(key)] = byte(checksum >> 8) + keyBlock[keyOffset+len(key)+1] = byte(checksum) } - keyBlock := make([]byte, lenKeyBlock) - keyOffset := 0 - if version < 6 { - keyBlock[0] = byte(cipherFunc) - keyOffset = 1 - } - - copy(keyBlock[keyOffset:], key) - checksum := checksumKeyMaterial(key) - keyBlock[keyOffset+len(key)] = byte(checksum >> 8) - keyBlock[keyOffset+len(key)+1] = byte(checksum) switch pub.PubKeyAlgo { case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly: @@ -320,6 +380,10 @@ func SerializeEncryptedKey(w io.Writer, pub *PublicKey, cipherFunc CipherFunctio return serializeEncryptedKeyElGamal(w, config.Random(), buf[:lenHeaderWritten], pub.PublicKey.(*elgamal.PublicKey), keyBlock) case PubKeyAlgoECDH: return serializeEncryptedKeyECDH(w, config.Random(), buf[:lenHeaderWritten], pub.PublicKey.(*ecdh.PublicKey), keyBlock, pub.oid, pub.Fingerprint) + case PubKeyAlgoX25519: + return serializeEncryptedKeyX25519(w, config.Random(), buf[:lenHeaderWritten], pub.PublicKey.(*x25519.PublicKey), keyBlock) + case PubKeyAlgoX448: + return serializeEncryptedKeyX448(w, config.Random(), buf[:lenHeaderWritten], pub.PublicKey.(*x448.PublicKey), keyBlock) case PubKeyAlgoDSA, PubKeyAlgoRSASignOnly: return errors.InvalidArgumentError("cannot encrypt to public key of type " + strconv.Itoa(int(pub.PubKeyAlgo))) } @@ -400,3 +464,47 @@ func serializeEncryptedKeyECDH(w io.Writer, rand io.Reader, header []byte, pub * _, err = w.Write(m.EncodedBytes()) return err } + +func serializeEncryptedKeyX25519(w io.Writer, rand io.Reader, header []byte, pub *x25519.PublicKey, keyBlock []byte) error { + ephemeralPublicX25519, ciphertext, err := x25519.Encrypt(rand, pub, keyBlock) + if err != nil { + return errors.InvalidArgumentError("X25519 encryption failed: " + err.Error()) + } + + packetLen := len(header) /* header length */ + packetLen += x25519.EncodedFieldsLength(ciphertext) + + err = serializeHeader(w, packetTypeEncryptedKey, packetLen) + if err != nil { + return err + } + + _, err = w.Write(header[:]) + if err != nil { + return err + } + err = x25519.EncodeFields(w, ephemeralPublicX25519, ciphertext) + return err +} + +func serializeEncryptedKeyX448(w io.Writer, rand io.Reader, header []byte, pub *x448.PublicKey, keyBlock []byte) error { + ephemeralPublicX448, ciphertext, err := x448.Encrypt(rand, pub, keyBlock) + if err != nil { + return errors.InvalidArgumentError("x448 encryption failed: " + err.Error()) + } + + packetLen := len(header) /* header length */ + packetLen += x448.EncodedFieldsLength(ciphertext) + + err = serializeHeader(w, packetTypeEncryptedKey, packetLen) + if err != nil { + return err + } + + _, err = w.Write(header[:]) + if err != nil { + return err + } + err = x448.EncodeFields(w, ephemeralPublicX448, ciphertext) + return err +} diff --git a/openpgp/packet/encrypted_key_test.go b/openpgp/packet/encrypted_key_test.go index 57b4c1258..fc13d2228 100644 --- a/openpgp/packet/encrypted_key_test.go +++ b/openpgp/packet/encrypted_key_test.go @@ -15,6 +15,9 @@ import ( "crypto" "crypto/rsa" + + "github.com/ProtonMail/go-crypto/openpgp/x25519" + "github.com/ProtonMail/go-crypto/openpgp/x448" ) func bigFromBase10(s string) *big.Int { @@ -252,6 +255,70 @@ func TestEncryptingEncryptedKeyV6(t *testing.T) { } } +func TestEncryptingEncryptedKeyXAlgorithms(t *testing.T) { + key := []byte{1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4} + config := &Config{ + AEADConfig: &AEADConfig{ + }, + } + x25519Gen := func () (*PrivateKey, PublicKeyAlgorithm) { + x25519Key, _ := x25519.GenerateKey(config.Random()) + x25519WrappedKey := NewX25519PrivateKey(time.Now(), x25519Key) + x25519WrappedKey.UpgradeToV6() + return x25519WrappedKey, PubKeyAlgoX25519 + } + x448Gen := func () (*PrivateKey, PublicKeyAlgorithm) { + x448Key, _ := x448.GenerateKey(config.Random()) + x448WrappedKey := NewX448PrivateKey(time.Now(), x448Key) + x448WrappedKey.UpgradeToV6() + return x448WrappedKey, PubKeyAlgoX448 + } + testCaseFunc := []func()(*PrivateKey, PublicKeyAlgorithm) {x25519Gen, x448Gen} + + for _, genFunc := range testCaseFunc { + wrappedKey, pubType:= genFunc() + wrappedKeyPub := &wrappedKey.PublicKey + + + buf := new(bytes.Buffer) + err := SerializeEncryptedKey(buf, wrappedKeyPub, CipherAES128, true, key, config) + + if err != nil { + t.Errorf("error writing encrypted key packet: %s", err) + } + + p, err := Read(buf) + if err != nil { + t.Errorf("error from Read: %s", err) + return + } + ek, ok := p.(*EncryptedKey) + if !ok { + t.Errorf("didn't parse an EncryptedKey, got %#v", p) + return + } + + if !bytes.Equal(ek.KeyFingerprint, wrappedKey.Fingerprint) || + ek.Algo != pubType || + ek.KeyVersion != wrappedKey.Version { + t.Errorf("unexpected EncryptedKey contents: %#v", ek) + return + } + + err = ek.Decrypt(wrappedKey, nil) + if err != nil { + t.Errorf("error from Decrypt: %s", err) + return + } + + keyHex := fmt.Sprintf("%x", ek.Key) + expectedKeyHex := fmt.Sprintf("%x", key) + if keyHex != expectedKeyHex { + t.Errorf("bad key, got %s want %s", keyHex, expectedKeyHex) + } + } +} + func TestSerializingEncryptedKey(t *testing.T) { const encryptedKeyHex = "c18c032a67d68660df41c70104005789d0de26b6a50c985a02a13131ca829c413a35d0e6fa8d6842599252162808ac7439c72151c8c6183e76923fe3299301414d0c25a2f06a2257db3839e7df0ec964773f6e4c4ac7ff3b48c444237166dd46ba8ff443a5410dc670cb486672fdbe7c9dfafb75b4fea83af3a204fe2a7dfa86bd20122b4f3d2646cbeecb8f7be8" diff --git a/openpgp/packet/packet.go b/openpgp/packet/packet.go index f8d7e11d2..3f1ea5e8b 100644 --- a/openpgp/packet/packet.go +++ b/openpgp/packet/packet.go @@ -415,6 +415,10 @@ const ( PubKeyAlgoECDSA PublicKeyAlgorithm = 19 // https://www.ietf.org/archive/id/draft-koch-eddsa-for-openpgp-04.txt PubKeyAlgoEdDSA PublicKeyAlgorithm = 22 + // https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh + PubKeyAlgoX25519 PublicKeyAlgorithm = 25 + // https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh + PubKeyAlgoX448 PublicKeyAlgorithm = 26 // Deprecated in RFC 4880, Section 13.5. Use key flags instead. PubKeyAlgoRSAEncryptOnly PublicKeyAlgorithm = 2 diff --git a/openpgp/packet/private_key.go b/openpgp/packet/private_key.go index 75f7ada1e..e497a7c9b 100644 --- a/openpgp/packet/private_key.go +++ b/openpgp/packet/private_key.go @@ -26,6 +26,8 @@ import ( "github.com/ProtonMail/go-crypto/openpgp/errors" "github.com/ProtonMail/go-crypto/openpgp/internal/encoding" "github.com/ProtonMail/go-crypto/openpgp/s2k" + "github.com/ProtonMail/go-crypto/openpgp/x25519" + "github.com/ProtonMail/go-crypto/openpgp/x448" "golang.org/x/crypto/hkdf" ) @@ -107,6 +109,20 @@ func NewECDHPrivateKey(creationTime time.Time, priv *ecdh.PrivateKey) *PrivateKe return pk } +func NewX25519PrivateKey(creationTime time.Time, priv *x25519.PrivateKey) *PrivateKey { + pk := new(PrivateKey) + pk.PublicKey = *NewX25519PublicKey(creationTime, &priv.PublicKey) + pk.PrivateKey = priv + return pk +} + +func NewX448PrivateKey(creationTime time.Time, priv *x448.PrivateKey) *PrivateKey { + pk := new(PrivateKey) + pk.PublicKey = *NewX448PublicKey(creationTime, &priv.PublicKey) + pk.PrivateKey = priv + return pk +} + // NewSignerPrivateKey creates a PrivateKey from a crypto.Signer that // implements RSA, ECDSA or EdDSA. func NewSignerPrivateKey(creationTime time.Time, signer interface{}) *PrivateKey { @@ -133,7 +149,7 @@ func NewSignerPrivateKey(creationTime time.Time, signer interface{}) *PrivateKey return pk } -// NewDecrypterPrivateKey creates a PrivateKey from a *{rsa|elgamal|ecdh}.PrivateKey. +// NewDecrypterPrivateKey creates a PrivateKey from a *{rsa|elgamal|ecdh|x25519}.PrivateKey. func NewDecrypterPrivateKey(creationTime time.Time, decrypter interface{}) *PrivateKey { pk := new(PrivateKey) switch priv := decrypter.(type) { @@ -143,6 +159,10 @@ func NewDecrypterPrivateKey(creationTime time.Time, decrypter interface{}) *Priv pk.PublicKey = *NewElGamalPublicKey(creationTime, &priv.PublicKey) case *ecdh.PrivateKey: pk.PublicKey = *NewECDHPublicKey(creationTime, &priv.PublicKey) + case *x25519.PrivateKey: + pk.PublicKey = *NewX25519PublicKey(creationTime, &priv.PublicKey) + case *x448.PrivateKey: + pk.PublicKey = *NewX448PublicKey(creationTime, &priv.PublicKey) default: panic("openpgp: unknown decrypter type in NewDecrypterPrivateKey") } @@ -455,6 +475,16 @@ func serializeECDHPrivateKey(w io.Writer, priv *ecdh.PrivateKey) error { return err } +func serializeX25519PrivateKey(w io.Writer, priv *x25519.PrivateKey) error { + _, err := w.Write(priv.Secret) + return err +} + +func serializeX448PrivateKey(w io.Writer, priv *x448.PrivateKey) error { + _, err := w.Write(priv.Secret) + return err +} + // decrypt decrypts an encrypted private key using a decryption key. func (pk *PrivateKey) decrypt(decryptionKey []byte) error { if pk.Dummy() { @@ -733,6 +763,10 @@ func (pk *PrivateKey) serializePrivateKey(w io.Writer) (err error) { err = serializeEdDSAPrivateKey(w, priv) case *ecdh.PrivateKey: err = serializeECDHPrivateKey(w, priv) + case *x25519.PrivateKey: + err = serializeX25519PrivateKey(w, priv) + case *x448.PrivateKey: + err = serializeX448PrivateKey(w, priv) default: err = errors.InvalidArgumentError("unknown private key type") } @@ -753,8 +787,14 @@ func (pk *PrivateKey) parsePrivateKey(data []byte) (err error) { return pk.parseECDHPrivateKey(data) case PubKeyAlgoEdDSA: return pk.parseEdDSAPrivateKey(data) + case PubKeyAlgoX25519: + return pk.parseX25519PrivateKey(data) + case PubKeyAlgoX448: + return pk.parseX448PrivateKey(data) + default: + err = errors.InvalidArgumentError("unknown private key type") + return } - panic("impossible") } func (pk *PrivateKey) parseRSAPrivateKey(data []byte) (err error) { @@ -875,6 +915,44 @@ func (pk *PrivateKey) parseECDHPrivateKey(data []byte) (err error) { return nil } +func (pk *PrivateKey) parseX25519PrivateKey(data []byte) (err error) { + publicKey := pk.PublicKey.PublicKey.(*x25519.PublicKey) + privateKey := x25519.NewPrivateKey(*publicKey) + privateKey.PublicKey = *publicKey + + privateKey.Secret = make([]byte, x25519.PointSize) + + if len(data) != x25519.PointSize { + err = errors.StructuralError("wrong X25519 key size") + } + copy(privateKey.Secret, data) + err = x25519.Validate(privateKey) + if err != nil { + return err + } + pk.PrivateKey = privateKey + return nil +} + +func (pk *PrivateKey) parseX448PrivateKey(data []byte) (err error) { + publicKey := pk.PublicKey.PublicKey.(*x448.PublicKey) + privateKey := x448.NewPrivateKey(*publicKey) + privateKey.PublicKey = *publicKey + + privateKey.Secret = make([]byte, x448.PointSize) + + if len(data) != x448.PointSize { + err = errors.StructuralError("wrong x448 key size") + } + copy(privateKey.Secret, data) + err = x448.Validate(privateKey) + if err != nil { + return err + } + pk.PrivateKey = privateKey + return nil +} + func (pk *PrivateKey) parseEdDSAPrivateKey(data []byte) (err error) { eddsaPub := pk.PublicKey.PublicKey.(*eddsa.PublicKey) eddsaPriv := eddsa.NewPrivateKey(*eddsaPub) diff --git a/openpgp/packet/public_key.go b/openpgp/packet/public_key.go index db345c2ea..b14c50315 100644 --- a/openpgp/packet/public_key.go +++ b/openpgp/packet/public_key.go @@ -26,6 +26,8 @@ import ( "github.com/ProtonMail/go-crypto/openpgp/internal/algorithm" "github.com/ProtonMail/go-crypto/openpgp/internal/ecc" "github.com/ProtonMail/go-crypto/openpgp/internal/encoding" + "github.com/ProtonMail/go-crypto/openpgp/x25519" + "github.com/ProtonMail/go-crypto/openpgp/x448" ) type kdfHashFunction byte @@ -36,7 +38,7 @@ type PublicKey struct { Version int CreationTime time.Time PubKeyAlgo PublicKeyAlgorithm - PublicKey interface{} // *rsa.PublicKey, *dsa.PublicKey, *ecdsa.PublicKey or *eddsa.PublicKey + PublicKey interface{} // *rsa.PublicKey, *dsa.PublicKey, *ecdsa.PublicKey or *eddsa.PublicKey, *x25519.PublicKey, *x448.PublicKey Fingerprint []byte KeyId uint64 IsSubkey bool @@ -181,6 +183,30 @@ func NewEdDSAPublicKey(creationTime time.Time, pub *eddsa.PublicKey) *PublicKey return pk } +func NewX25519PublicKey(creationTime time.Time, pub *x25519.PublicKey) *PublicKey { + pk := &PublicKey{ + Version: 4, + CreationTime: creationTime, + PubKeyAlgo: PubKeyAlgoX25519, + PublicKey: pub, + } + + pk.setFingerprintAndKeyId() + return pk +} + +func NewX448PublicKey(creationTime time.Time, pub *x448.PublicKey) *PublicKey { + pk := &PublicKey{ + Version: 4, + CreationTime: creationTime, + PubKeyAlgo: PubKeyAlgoX448, + PublicKey: pub, + } + + pk.setFingerprintAndKeyId() + return pk +} + func (pk *PublicKey) parse(r io.Reader) (err error) { // RFC 4880, section 5.5.2 var buf [6]byte @@ -218,7 +244,10 @@ func (pk *PublicKey) parse(r io.Reader) (err error) { err = pk.parseECDH(r) case PubKeyAlgoEdDSA: err = pk.parseEdDSA(r) - + case PubKeyAlgoX25519: + err = pk.parseX25519(r) + case PubKeyAlgoX448: + err = pk.parseX448(r) default: err = errors.UnsupportedError("public key type: " + strconv.Itoa(int(pk.PubKeyAlgo))) } @@ -446,6 +475,32 @@ func (pk *PublicKey) parseEdDSA(r io.Reader) (err error) { return } +func (pk *PublicKey) parseX25519(r io.Reader) (err error) { + point := make([]byte, x25519.PointSize) + _, err = io.ReadFull(r, point) + if err != nil { + return + } + pub := x25519.PublicKey { + Point: point, + } + pk.PublicKey = pub + return +} + +func (pk *PublicKey) parseX448(r io.Reader) (err error) { + point := make([]byte, x448.PointSize) + _, err = io.ReadFull(r, point) + if err != nil { + return + } + pub := x448.PublicKey { + Point: point, + } + pk.PublicKey = pub + return +} + // SerializeForHash serializes the PublicKey to w with the special packet // header format needed for hashing. func (pk *PublicKey) SerializeForHash(w io.Writer) error { @@ -515,6 +570,10 @@ func (pk *PublicKey) algorithmSpecificByteCount() int { case PubKeyAlgoEdDSA: length += int(pk.oid.EncodedLength()) length += int(pk.p.EncodedLength()) + case PubKeyAlgoX25519: + length += x25519.PointSize + case PubKeyAlgoX448: + length += x448.PointSize default: panic("unknown public key algorithm") } diff --git a/openpgp/read.go b/openpgp/read.go index f36ae032a..c3a817349 100644 --- a/openpgp/read.go +++ b/openpgp/read.go @@ -118,7 +118,7 @@ ParsePackets: // This packet contains the decryption key encrypted to a public key. md.EncryptedToKeyIds = append(md.EncryptedToKeyIds, p.KeyId) switch p.Algo { - case packet.PubKeyAlgoRSA, packet.PubKeyAlgoRSAEncryptOnly, packet.PubKeyAlgoElGamal, packet.PubKeyAlgoECDH: + case packet.PubKeyAlgoRSA, packet.PubKeyAlgoRSAEncryptOnly, packet.PubKeyAlgoElGamal, packet.PubKeyAlgoECDH, packet.PubKeyAlgoX25519, packet.PubKeyAlgoX448: break default: continue diff --git a/openpgp/x25519/x25519.go b/openpgp/x25519/x25519.go new file mode 100644 index 000000000..2ab09f7e5 --- /dev/null +++ b/openpgp/x25519/x25519.go @@ -0,0 +1,186 @@ +package x25519 + +import ( + "crypto/sha256" + "crypto/subtle" + "io" + + "github.com/ProtonMail/go-crypto/openpgp/aes/keywrap" + "github.com/ProtonMail/go-crypto/openpgp/errors" + x25519lib "github.com/cloudflare/circl/dh/x25519" + "golang.org/x/crypto/hkdf" +) + +const hkdfInfo = "OpenPGP X25519" +const aes128KeySize = 16 +const PointSize = 32 + +type PublicKey struct { + Point []byte +} + +type PrivateKey struct { + PublicKey + Secret []byte +} + +func NewPrivateKey(key PublicKey) *PrivateKey { + return &PrivateKey{ + PublicKey: key, + } +} + +// Validate validates that the provided public key matches +// the private key. +func Validate(pk *PrivateKey) (err error) { + var expectedPublicKey, privateKey x25519lib.Key + subtle.ConstantTimeCopy(1, privateKey[:], pk.Secret) + x25519lib.KeyGen(&expectedPublicKey, &privateKey) + if subtle.ConstantTimeCompare(expectedPublicKey[:], pk.PublicKey.Point) == 0 { + return errors.KeyInvalidError("x25519: invalid key") + } + return nil +} + +// GenerateKey generates a new x25519 key pair +func GenerateKey(rand io.Reader) (*PrivateKey, error) { + var privateKey, publicKey x25519lib.Key + privateKeyOut := new(PrivateKey) + err := generateKey(rand, &privateKey, &publicKey) + if err != nil { + return nil, err + } + privateKeyOut.PublicKey.Point = publicKey[:] + privateKeyOut.Secret = privateKey[:] + return privateKeyOut, nil +} + +func generateKey(rand io.Reader, privateKey *x25519lib.Key, publicKey *x25519lib.Key) error { + maxRounds := 10 + isZero := true + for round := 0; isZero; round++ { + if round == maxRounds { + return errors.InvalidArgumentError("x25519: zero keys only, randomness source might be corrupt") + } + _, err := io.ReadFull(rand, privateKey[:]) + if err != nil { + return err + } + isZero = constantTimeIsZero(privateKey[:]) + } + x25519lib.KeyGen(publicKey, privateKey) + return nil +} + +// Encrypt encrpyts a sessionKey with x25519 according to +// the OpenPGP crypto refresh specification section 5.1.6. The function assumes that the +// sessionKey has the correct format and padding according to the specification. +func Encrypt(rand io.Reader, publicKey *PublicKey, sessionKey []byte) (ephemeralPublicKey *PublicKey, encryptedSessionKey []byte, err error) { + var ephemeralPrivate, ephemeralPublic, staticPublic, shared x25519lib.Key + // Check that the input static public key has 32 bytes + if len(publicKey.Point) != PointSize { + err = errors.KeyInvalidError("x25519: the public key has the wrong size") + return + } + copy(staticPublic[:], publicKey.Point) + // Generate ephemeral keyPair + err = generateKey(rand, &ephemeralPrivate, &ephemeralPublic) + if err != nil { + return + } + // Compute shared key + ok := x25519lib.Shared(&shared, &ephemeralPrivate, &staticPublic) + if !ok { + err = errors.KeyInvalidError("x25519: the public key is a low order point") + return + } + // Derive the encryption key from the shared secret + encryptionKey := applyHKDF(shared[:]) + ephemeralPublicKey = &PublicKey{ + Point: ephemeralPublic[:], + } + // Encrypt the sessionKey with aes key wrapping + encryptedSessionKey, err = keywrap.Wrap(encryptionKey, sessionKey) + return +} + +// Decrypt decrypts a session key stored in ciphertext with the provided x25519 +// private key and ephemeral public key +func Decrypt(privateKey *PrivateKey, ephemeralPublicKey *PublicKey, ciphertext []byte) (encodedSessionKey []byte, err error) { + var ephemeralPublic, staticPrivate, shared x25519lib.Key + // Check that the input ephemeral public key has 32 bytes + if len(ephemeralPublicKey.Point) != PointSize { + err = errors.KeyInvalidError("x25519: the public key has the wrong size") + return + } + copy(ephemeralPublic[:], ephemeralPublicKey.Point) + subtle.ConstantTimeCopy(1, staticPrivate[:], privateKey.Secret) + // Compute shared key + ok := x25519lib.Shared(&shared, &staticPrivate, &ephemeralPublic) + if !ok { + err = errors.KeyInvalidError("x25519: the ephemeral public key is a low order point") + return + } + // Derive the encryption key from the shared secret + encryptionKey := applyHKDF(shared[:]) + // Decrypt the session key with aes key wrapping + encodedSessionKey, err = keywrap.Unwrap(encryptionKey, ciphertext) + return +} + +func applyHKDF(inputKey []byte) []byte { + hkdfReader := hkdf.New(sha256.New, inputKey, []byte{}, []byte(hkdfInfo)) + encryptionKey := make([]byte, aes128KeySize) + _, _ = io.ReadFull(hkdfReader, encryptionKey) + return encryptionKey +} + +func constantTimeIsZero(bytes []byte) bool { + isZero := byte(0) + for _, b := range bytes { + isZero |= b + } + return isZero == 0 +} + +// ENCODING/DECODING ciphertexts: + +// EncodeFieldsLength returns the length of the ciphertext encoding +// given the encrpyted session key. +func EncodedFieldsLength(encryptedSessionKey []byte) int { + return PointSize + 1 + len(encryptedSessionKey) +} + +// EncodeField encodes X25519 session key encryption as +// ephemeral X25519 public key | encryptedSessionKey length | encryptedSessionKey +// and writes it to writer +func EncodeFields(writer io.Writer, ephemeralPublicKey *PublicKey, encryptedSessionKey []byte) (err error) { + if _, err = writer.Write(ephemeralPublicKey.Point); err != nil { + return + } + if _, err = writer.Write([]byte{byte(len(encryptedSessionKey))}); err != nil { + return + } + _, err = writer.Write(encryptedSessionKey) + return +} + +// DecodeField decodes a X25519 session key encryption as +// ephemeral X25519 public key | encryptedSessionKey length | encryptedSessionKey +func DecodeFields(reader io.Reader) (ephemeralPublicKey *PublicKey, encryptedSessionKey []byte, err error) { + var buf [1]byte + ephemeralPublicKey = &PublicKey{ + Point: make([]byte, PointSize), + } + _, err = io.ReadFull(reader, ephemeralPublicKey.Point) + if err != nil { + return + } + _, err = io.ReadFull(reader, buf[:]) + if err != nil { + return + } + encryptedSessionKey = make([]byte, buf[0]) + _, err = io.ReadFull(reader, encryptedSessionKey) + return +} diff --git a/openpgp/x25519/x25519_test.go b/openpgp/x25519/x25519_test.go new file mode 100644 index 000000000..555bb31be --- /dev/null +++ b/openpgp/x25519/x25519_test.go @@ -0,0 +1,60 @@ +package x25519 + +import ( + "bytes" + "crypto/rand" + "testing" +) + + +func TestGenerate(t *testing.T) { + privateKey, err := GenerateKey(rand.Reader) + if err != nil { + t.Fatal(err) + } + if len(privateKey.Secret) != PointSize { + t.Fatal("key has the wrong size") + } + if len(privateKey.PublicKey.Point) != PointSize { + t.Fatal("key has the wrong size") + } +} + +func TestValidate(t *testing.T) { + privateKey, err := GenerateKey(rand.Reader) + if err != nil { + t.Fatal(err) + } + + err = Validate(privateKey) + if err != nil { + t.Fatal(err) + } + privateKey.PublicKey.Point[0] = privateKey.PublicKey.Point[0] + byte(1) + err = Validate(privateKey) + if err == nil { + t.Fatal("validation failed") + } +} + +func TestEncryptDecrypt(t *testing.T) { + sessionKey := []byte("session.........") + privateKey, err := GenerateKey(rand.Reader) + if err != nil { + t.Fatal(err) + } + + ephemeralPublic, ctxt, err := Encrypt(rand.Reader, &privateKey.PublicKey, sessionKey) + if err != nil { + t.Errorf("error encrypting: %s", err) + } + + sessionKeyAfter, err := Decrypt(privateKey, ephemeralPublic, ctxt) + if err != nil { + t.Errorf("error decrypting: %s", err) + } + + if !bytes.Equal(sessionKeyAfter, sessionKey) { + t.Errorf("decryption failed, got: %x, want: %x", sessionKeyAfter, sessionKey) + } +} diff --git a/openpgp/x448/x448.go b/openpgp/x448/x448.go new file mode 100644 index 000000000..8de3b53a1 --- /dev/null +++ b/openpgp/x448/x448.go @@ -0,0 +1,186 @@ +package x448 + +import ( + "crypto/sha512" + "crypto/subtle" + "io" + + "github.com/ProtonMail/go-crypto/openpgp/aes/keywrap" + "github.com/ProtonMail/go-crypto/openpgp/errors" + x448lib "github.com/cloudflare/circl/dh/x448" + "golang.org/x/crypto/hkdf" +) + +const hkdfInfo = "OpenPGP X448" +const aesKeySize = 32 +const PointSize = 56 + +type PublicKey struct { + Point []byte +} + +type PrivateKey struct { + PublicKey + Secret []byte +} + +func NewPrivateKey(key PublicKey) *PrivateKey { + return &PrivateKey{ + PublicKey: key, + } +} + +// Validate validates that the provided public key matches +// the private key. +func Validate(pk *PrivateKey) (err error) { + var expectedPublicKey, privateKey x448lib.Key + subtle.ConstantTimeCopy(1, privateKey[:], pk.Secret) + x448lib.KeyGen(&expectedPublicKey, &privateKey) + if subtle.ConstantTimeCompare(expectedPublicKey[:], pk.PublicKey.Point) == 0 { + return errors.KeyInvalidError("x448: invalid key") + } + return nil +} + +// GenerateKey generates a new x448 key pair +func GenerateKey(rand io.Reader) (*PrivateKey, error) { + var privateKey, publicKey x448lib.Key + privateKeyOut := new(PrivateKey) + err := generateKey(rand, &privateKey, &publicKey) + if err != nil { + return nil, err + } + privateKeyOut.PublicKey.Point = publicKey[:] + privateKeyOut.Secret = privateKey[:] + return privateKeyOut, nil +} + +func generateKey(rand io.Reader, privateKey *x448lib.Key, publicKey *x448lib.Key) error { + maxRounds := 10 + isZero := true + for round := 0; isZero; round++ { + if round == maxRounds { + return errors.InvalidArgumentError("x448: zero keys only, randomness source might be corrupt") + } + _, err := io.ReadFull(rand, privateKey[:]) + if err != nil { + return err + } + isZero = constantTimeIsZero(privateKey[:]) + } + x448lib.KeyGen(publicKey, privateKey) + return nil +} + +// Encrypt encrpyts a sessionKey with x448 according to +// the OpenPGP crypto refresh specification section 5.1.6. The function assumes that the +// sessionKey has the correct format and padding according to the specification. +func Encrypt(rand io.Reader, publicKey *PublicKey, sessionKey []byte) (ephemeralPublicKey *PublicKey, encryptedSessionKey []byte, err error) { + var ephemeralPrivate, ephemeralPublic, staticPublic, shared x448lib.Key + // Check that the input static public key has 32 bytes + if len(publicKey.Point) != PointSize { + err = errors.KeyInvalidError("x448: the public key has the wrong size") + return + } + copy(staticPublic[:], publicKey.Point) + // Generate ephemeral keyPair + err = generateKey(rand, &ephemeralPrivate, &ephemeralPublic) + if err != nil { + return + } + // Compute shared key + ok := x448lib.Shared(&shared, &ephemeralPrivate, &staticPublic) + if !ok { + err = errors.KeyInvalidError("x448: the public key is a low order point") + return + } + // Derive the encryption key from the shared secret + encryptionKey := applyHKDF(shared[:]) + ephemeralPublicKey = &PublicKey{ + Point: ephemeralPublic[:], + } + // Encrypt the sessionKey with aes key wrapping + encryptedSessionKey, err = keywrap.Wrap(encryptionKey, sessionKey) + return +} + +// Decrypt decrypts a session key stored in ciphertext with the provided x448 +// private key and ephemeral public key +func Decrypt(privateKey *PrivateKey, ephemeralPublicKey *PublicKey, ciphertext []byte) (encodedSessionKey []byte, err error) { + var ephemeralPublic, staticPrivate, shared x448lib.Key + // Check that the input ephemeral public key has 32 bytes + if len(ephemeralPublicKey.Point) != PointSize { + err = errors.KeyInvalidError("x448: the public key has the wrong size") + return + } + copy(ephemeralPublic[:], ephemeralPublicKey.Point) + subtle.ConstantTimeCopy(1, staticPrivate[:], privateKey.Secret) + // Compute shared key + ok := x448lib.Shared(&shared, &staticPrivate, &ephemeralPublic) + if !ok { + err = errors.KeyInvalidError("x448: the ephemeral public key is a low order point") + return + } + // Derive the encryption key from the shared secret + encryptionKey := applyHKDF(shared[:]) + // Decrypt the session key with aes key wrapping + encodedSessionKey, err = keywrap.Unwrap(encryptionKey, ciphertext) + return +} + +func applyHKDF(inputKey []byte) []byte { + hkdfReader := hkdf.New(sha512.New, inputKey, []byte{}, []byte(hkdfInfo)) + encryptionKey := make([]byte, aesKeySize) + _, _ = io.ReadFull(hkdfReader, encryptionKey) + return encryptionKey +} + +func constantTimeIsZero(bytes []byte) bool { + isZero := byte(0) + for _, b := range bytes { + isZero |= b + } + return isZero == 0 +} + +// ENCODING/DECODING ciphertexts: + +// EncodeFieldsLength returns the length of the ciphertext encoding +// given the encrpyted session key. +func EncodedFieldsLength(encryptedSessionKey []byte) int { + return PointSize + 1 + len(encryptedSessionKey) +} + +// EncodeField encodes x448 session key encryption as +// ephemeral x448 public key | encryptedSessionKey length | encryptedSessionKey +// and writes it to writer +func EncodeFields(writer io.Writer, ephemeralPublicKey *PublicKey, encryptedSessionKey []byte) (err error) { + if _, err = writer.Write(ephemeralPublicKey.Point); err != nil { + return + } + if _, err = writer.Write([]byte{byte(len(encryptedSessionKey))}); err != nil { + return + } + _, err = writer.Write(encryptedSessionKey) + return +} + +// DecodeField decodes a x448 session key encryption as +// ephemeral x448 public key | encryptedSessionKey length | encryptedSessionKey +func DecodeFields(reader io.Reader) (ephemeralPublicKey *PublicKey, encryptedSessionKey []byte, err error) { + var buf [1]byte + ephemeralPublicKey = &PublicKey{ + Point: make([]byte, PointSize), + } + _, err = io.ReadFull(reader, ephemeralPublicKey.Point) + if err != nil { + return + } + _, err = io.ReadFull(reader, buf[:]) + if err != nil { + return + } + encryptedSessionKey = make([]byte, buf[0]) + _, err = io.ReadFull(reader, encryptedSessionKey) + return +} diff --git a/openpgp/x448/x448_test.go b/openpgp/x448/x448_test.go new file mode 100644 index 000000000..a62ed58a0 --- /dev/null +++ b/openpgp/x448/x448_test.go @@ -0,0 +1,60 @@ +package x448 + +import ( + "bytes" + "crypto/rand" + "testing" +) + + +func TestGenerate(t *testing.T) { + privateKey, err := GenerateKey(rand.Reader) + if err != nil { + t.Fatal(err) + } + if len(privateKey.Secret) != PointSize { + t.Fatal("key has the wrong size") + } + if len(privateKey.PublicKey.Point) != PointSize { + t.Fatal("key has the wrong size") + } +} + +func TestValidate(t *testing.T) { + privateKey, err := GenerateKey(rand.Reader) + if err != nil { + t.Fatal(err) + } + + err = Validate(privateKey) + if err != nil { + t.Fatal(err) + } + privateKey.PublicKey.Point[0] = privateKey.PublicKey.Point[0] + byte(1) + err = Validate(privateKey) + if err == nil { + t.Fatal("validation failed") + } +} + +func TestEncryptDecrypt(t *testing.T) { + sessionKey := []byte("session.........") + privateKey, err := GenerateKey(rand.Reader) + if err != nil { + t.Fatal(err) + } + + ephemeralPublic, ctxt, err := Encrypt(rand.Reader, &privateKey.PublicKey, sessionKey) + if err != nil { + t.Errorf("error encrypting: %s", err) + } + + sessionKeyAfter, err := Decrypt(privateKey, ephemeralPublic, ctxt) + if err != nil { + t.Errorf("error decrypting: %s", err) + } + + if !bytes.Equal(sessionKeyAfter, sessionKey) { + t.Errorf("decryption failed, got: %x, want: %x", sessionKeyAfter, sessionKey) + } +} From 306ec0d7bde7fdd06cd432ce68f9c55175791aa1 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Mon, 3 Apr 2023 14:26:24 +0200 Subject: [PATCH 014/116] FIX: KeysByID with direct signatures and correct one pass signature version --- openpgp/keys.go | 3 +-- openpgp/write.go | 9 ++++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/openpgp/keys.go b/openpgp/keys.go index f29124021..f6f075b0f 100644 --- a/openpgp/keys.go +++ b/openpgp/keys.go @@ -321,8 +321,7 @@ type EntityList []*Entity func (el EntityList) KeysById(id uint64) (keys []Key) { for _, e := range el { if e.PrimaryKey.KeyId == id { - ident := e.PrimaryIdentity() - selfSig := ident.SelfSignature + selfSig, _ := e.primarySelfSignature() keys = append(keys, Key{e, e.PrimaryKey, e.PrivateKey, selfSig, e.Revocations}) } diff --git a/openpgp/write.go b/openpgp/write.go index 65a0c12f7..af1d1d96f 100644 --- a/openpgp/write.go +++ b/openpgp/write.go @@ -280,12 +280,11 @@ func writeAndSign(payload io.WriteCloser, candidateHashes []uint8, signed *Entit } var salt []byte - var sigVersion = 3 - if config != nil && config.V6Keys { - // use v6 signatures and one pass signatures - sigVersion = 6 - } if signer != nil { + var sigVersion = 3 + if signer.Version == 6 { + sigVersion = signer.Version + } ops := &packet.OnePassSignature{ Version: sigVersion, SigType: sigType, From 253d004ad8de13dcbbfbf26a8e17d0e7a9458090 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Mon, 3 Apr 2023 14:27:19 +0200 Subject: [PATCH 015/116] Add support for Ed2551 and Ed448 keys and signatures --- openpgp/ed25519/ed25519.go | 99 ++++++++++++++++++ openpgp/ed25519/ed25519_test.go | 63 ++++++++++++ openpgp/ed448/ed448.go | 103 +++++++++++++++++++ openpgp/ed448/ed448_test.go | 63 ++++++++++++ openpgp/integration_tests/end_to_end_test.go | 2 +- openpgp/integration_tests/utils_test.go | 14 ++- openpgp/key_generation.go | 18 +++- openpgp/keys_v6_test.go | 5 +- openpgp/packet/packet.go | 17 +-- openpgp/packet/private_key.go | 86 +++++++++++++++- openpgp/packet/public_key.go | 102 +++++++++++++++++- openpgp/packet/signature.go | 39 ++++++- openpgp/read_write_test_data.go | 2 + openpgp/write_test.go | 5 +- 14 files changed, 592 insertions(+), 26 deletions(-) create mode 100644 openpgp/ed25519/ed25519.go create mode 100644 openpgp/ed25519/ed25519_test.go create mode 100644 openpgp/ed448/ed448.go create mode 100644 openpgp/ed448/ed448_test.go diff --git a/openpgp/ed25519/ed25519.go b/openpgp/ed25519/ed25519.go new file mode 100644 index 000000000..4e4656514 --- /dev/null +++ b/openpgp/ed25519/ed25519.go @@ -0,0 +1,99 @@ +// Package ed25519 implements the ed25519 signature algorithm for OpenPGP +// as defined in the Open PGP crypto refresh. +package ed25519 + +import ( + "crypto/subtle" + "io" + + "github.com/ProtonMail/go-crypto/openpgp/errors" + ed25519lib "github.com/cloudflare/circl/sign/ed25519" +) + +const PointSize = 32 +const PrivateKeySize = 64 +const SignatureSize = 64 + +type PublicKey struct { + Point []byte +} + +type PrivateKey struct { + PublicKey + Key []byte // encoded as seed | pub key point +} + +func NewPublicKey() *PublicKey { + return &PublicKey{ + } +} + +func NewPrivateKey(key PublicKey) *PrivateKey { + return &PrivateKey{ + PublicKey: key, + } +} + +func (pk *PrivateKey) Seed() []byte { + return pk.Key[:PointSize] +} + +// MarshalByteSecret returns the underlying 32 byte seed of the private key +func (pk *PrivateKey) MarshalByteSecret() []byte { + return pk.Seed() +} + +// UnmarshalByteSecret computes the private key from the secret seed +// and stores it in the private key object. +func (sk *PrivateKey) UnmarshalByteSecret(seed []byte) error { + sk.Key = ed25519lib.NewKeyFromSeed(seed) + return nil +} + +func GenerateKey(rand io.Reader) (*PrivateKey, error) { + publicKey, privateKey, err := ed25519lib.GenerateKey(rand) + if err != nil { + return nil, err + } + privateKeyOut := new(PrivateKey) + privateKeyOut.PublicKey.Point = publicKey[:] + privateKeyOut.Key = privateKey[:] + return privateKeyOut, nil +} + +// Sign signs a message with the ed25519 algorithm. +func Sign(priv *PrivateKey, message []byte) ([]byte, error) { + return ed25519lib.Sign(priv.Key, message), nil +} + +// Verify verifies a ed25519 signature +func Verify(pub *PublicKey, message []byte, signature []byte) bool { + return ed25519lib.Verify(pub.Point, message, signature) +} + +// Validate checks if the ed25519 private key is valid +func Validate(priv *PrivateKey) error { + expectedPrivateKey := ed25519lib.NewKeyFromSeed(priv.Seed()) + if subtle.ConstantTimeCompare(priv.Key, expectedPrivateKey) == 0 { + return errors.KeyInvalidError("ed25519: invalid ed25519 secret") + } + if subtle.ConstantTimeCompare(priv.PublicKey.Point, expectedPrivateKey[PointSize:]) == 0 { + return errors.KeyInvalidError("ed25519: invalid ed25519 public key") + } + return nil +} + +// ENCODING/DECODING signature: + +func WriteSignature(writer io.Writer, signature []byte) error { + _, err := writer.Write(signature) + return err +} + +func ReadSignature(reader io.Reader) ([]byte, error) { + signature := make([]byte, SignatureSize) + if _, err := io.ReadFull(reader, signature); err != nil { + return nil, err + } + return signature, nil +} diff --git a/openpgp/ed25519/ed25519_test.go b/openpgp/ed25519/ed25519_test.go new file mode 100644 index 000000000..15643de02 --- /dev/null +++ b/openpgp/ed25519/ed25519_test.go @@ -0,0 +1,63 @@ +package ed25519 + +import ( + "crypto/rand" + "io" + "testing" +) + +func TestGenerate(t *testing.T){ + priv, err := GenerateKey(rand.Reader) + if err != nil { + t.Fatal(err) + } + if len(priv.Key) != 64 && len(priv.Point) != 32 { + t.Error("gnerated wrong key sizes") + } +} + +func TestSignVerify(t *testing.T) { + digest := make([]byte, 32) + _, err := io.ReadFull(rand.Reader, digest[:]) + if err != nil { + t.Fatal(err) + } + + priv, err := GenerateKey(rand.Reader) + if err != nil { + t.Fatal(err) + } + + signature, err := Sign(priv, digest) + if err != nil { + t.Errorf("error signing: %s", err) + } + + result := Verify(&priv.PublicKey, digest, signature) + + if !result { + t.Error("unable to verify message") + } + + digest[0] += 1 + result = Verify(&priv.PublicKey, digest, signature) + + if result { + t.Error("signature should be invalid") + } +} + +func TestValidation(t *testing.T) { + priv, err := GenerateKey(rand.Reader) + if err != nil { + t.Fatal(err) + } + if err := Validate(priv); err != nil { + t.Fatalf("valid key marked as invalid: %s", err) + } + + priv.Key[0] += 1 + if err := Validate(priv); err == nil { + t.Fatal("failed to detect invalid key") + } +} diff --git a/openpgp/ed448/ed448.go b/openpgp/ed448/ed448.go new file mode 100644 index 000000000..9a67ac9cf --- /dev/null +++ b/openpgp/ed448/ed448.go @@ -0,0 +1,103 @@ +// Package ed448 implements the ed448 signature algorithm for OpenPGP +// as defined in the Open PGP crypto refresh. +package ed448 + +import ( + "crypto/subtle" + "io" + + "github.com/ProtonMail/go-crypto/openpgp/errors" + ed448lib "github.com/cloudflare/circl/sign/ed448" +) + +const PointSize = 57 +const PrivateKeySize = 114 +const SignatureSize = 114 + +type PublicKey struct { + Point []byte +} + +type PrivateKey struct { + PublicKey + Key []byte // encoded as seed | pub key point +} + +func NewPublicKey() *PublicKey { + return &PublicKey{ + } +} + +func NewPrivateKey(key PublicKey) *PrivateKey { + return &PrivateKey{ + PublicKey: key, + } +} + +func (pk *PrivateKey) Seed() []byte { + return pk.Key[:PointSize] +} + +// MarshalByteSecret returns the underlying 32 byte seed of the private key +func (pk *PrivateKey) MarshalByteSecret() []byte { + return pk.Seed() +} + +// UnmarshalByteSecret computes the private key from the secret seed +// and stores it in the private key object. +func (sk *PrivateKey) UnmarshalByteSecret(seed []byte) error { + sk.Key = ed448lib.NewKeyFromSeed(seed) + return nil +} + +func GenerateKey(rand io.Reader) (*PrivateKey, error) { + publicKey, privateKey, err := ed448lib.GenerateKey(rand) + if err != nil { + return nil, err + } + privateKeyOut := new(PrivateKey) + privateKeyOut.PublicKey.Point = publicKey[:] + privateKeyOut.Key = privateKey[:] + return privateKeyOut, nil +} + +// Sign signs a message with the ed448 algorithm. +func Sign(priv *PrivateKey, message []byte) ([]byte, error) { + // Ed448 is used with the empty string as a context string. + // See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-08#section-13.7 + return ed448lib.Sign(priv.Key, message, ""), nil +} + +// Verify verifies a ed448 signature +func Verify(pub *PublicKey, message []byte, signature []byte) bool { + // Ed448 is used with the empty string as a context string. + // See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-08#section-13.7 + return ed448lib.Verify(pub.Point, message, signature, "") +} + +// Validate checks if the ed448 private key is valid +func Validate(priv *PrivateKey) error { + expectedPrivateKey := ed448lib.NewKeyFromSeed(priv.Seed()) + if subtle.ConstantTimeCompare(priv.Key, expectedPrivateKey) == 0 { + return errors.KeyInvalidError("ed448: invalid ed448 secret") + } + if subtle.ConstantTimeCompare(priv.PublicKey.Point, expectedPrivateKey[PointSize:]) == 0 { + return errors.KeyInvalidError("ed448: invalid ed448 public key") + } + return nil +} + +// ENCODING/DECODING signature: + +func WriteSignature(writer io.Writer, signature []byte) error { + _, err := writer.Write(signature) + return err +} + +func ReadSignature(reader io.Reader) ([]byte, error) { + signature := make([]byte, SignatureSize) + if _, err := io.ReadFull(reader, signature); err != nil { + return nil, err + } + return signature, nil +} diff --git a/openpgp/ed448/ed448_test.go b/openpgp/ed448/ed448_test.go new file mode 100644 index 000000000..b6b593c0e --- /dev/null +++ b/openpgp/ed448/ed448_test.go @@ -0,0 +1,63 @@ +package ed448 + +import ( + "crypto/rand" + "io" + "testing" +) + +func TestGenerate(t *testing.T){ + priv, err := GenerateKey(rand.Reader) + if err != nil { + t.Fatal(err) + } + if len(priv.Key) != 114 && len(priv.Point) != 57 { + t.Error("gnerated wrong key sizes") + } +} + +func TestSignVerify(t *testing.T) { + digest := make([]byte, 32) + _, err := io.ReadFull(rand.Reader, digest[:]) + if err != nil { + t.Fatal(err) + } + + priv, err := GenerateKey(rand.Reader) + if err != nil { + t.Fatal(err) + } + + signature, err := Sign(priv, digest) + if err != nil { + t.Errorf("error signing: %s", err) + } + + result := Verify(&priv.PublicKey, digest, signature) + + if !result { + t.Error("unable to verify message") + } + + digest[0] += 1 + result = Verify(&priv.PublicKey, digest, signature) + + if result { + t.Error("signature should be invalid") + } +} + +func TestValidation(t *testing.T) { + priv, err := GenerateKey(rand.Reader) + if err != nil { + t.Fatal(err) + } + if err := Validate(priv); err != nil { + t.Fatalf("valid key marked as invalid: %s", err) + } + + priv.Key[0] += 1 + if err := Validate(priv); err == nil { + t.Fatal("failed to detect invalid key") + } +} diff --git a/openpgp/integration_tests/end_to_end_test.go b/openpgp/integration_tests/end_to_end_test.go index e2ea20ef0..b5fecf6bf 100644 --- a/openpgp/integration_tests/end_to_end_test.go +++ b/openpgp/integration_tests/end_to_end_test.go @@ -57,7 +57,7 @@ func TestEndToEnd(t *testing.T) { } // Generate random test vectors - freshTestVectors, err := generateFreshTestVectors() + freshTestVectors, err := generateFreshTestVectors(20) if err != nil { t.Fatal("Cannot proceed without generated keys: " + err.Error()) } diff --git a/openpgp/integration_tests/utils_test.go b/openpgp/integration_tests/utils_test.go index 8a82156ee..5ff1d7e5b 100644 --- a/openpgp/integration_tests/utils_test.go +++ b/openpgp/integration_tests/utils_test.go @@ -17,9 +17,9 @@ import ( // This function produces random test vectors: generates keys according to the // given settings, associates a random message for each key. It returns the // test vectors. -func generateFreshTestVectors() (vectors []testVector, err error) { +func generateFreshTestVectors(num int) (vectors []testVector, err error) { mathrand.Seed(time.Now().UTC().UnixNano()) - for i := 0; i < 3; i++ { + for i := 0; i < num; i++ { config := randConfig() // Sample random email, comment, password and message name, email, comment, password, message := randEntityData() @@ -31,7 +31,9 @@ func generateFreshTestVectors() (vectors []testVector, err error) { } pkAlgoNames := map[packet.PublicKeyAlgorithm]string{ packet.PubKeyAlgoRSA: "rsa_" + v, - packet.PubKeyAlgoEdDSA: "ed25519_" + v, + packet.PubKeyAlgoEdDSA: "EdDSA_" + v, + packet.PubKeyAlgoEd25519: "ed25519_" + v, + packet.PubKeyAlgoEd448: "ed448_" + v, } newVector := testVector{ @@ -226,14 +228,16 @@ func randConfig() *packet.Config { compAlgos := []packet.CompressionAlgo{ packet.CompressionNone, - packet.CompressionZIP, - packet.CompressionZLIB, + //packet.CompressionZIP, + //packet.CompressionZLIB, } compAlgo := compAlgos[mathrand.Intn(len(compAlgos))] pkAlgos := []packet.PublicKeyAlgorithm{ packet.PubKeyAlgoRSA, packet.PubKeyAlgoEdDSA, + packet.PubKeyAlgoEd25519, + packet.PubKeyAlgoEd448, } pkAlgo := pkAlgos[mathrand.Intn(len(pkAlgos))] diff --git a/openpgp/key_generation.go b/openpgp/key_generation.go index ccc10d93b..b07afb684 100644 --- a/openpgp/key_generation.go +++ b/openpgp/key_generation.go @@ -15,6 +15,8 @@ import ( "github.com/ProtonMail/go-crypto/openpgp/ecdh" "github.com/ProtonMail/go-crypto/openpgp/ecdsa" + "github.com/ProtonMail/go-crypto/openpgp/ed25519" + "github.com/ProtonMail/go-crypto/openpgp/ed448" "github.com/ProtonMail/go-crypto/openpgp/eddsa" "github.com/ProtonMail/go-crypto/openpgp/errors" "github.com/ProtonMail/go-crypto/openpgp/internal/algorithm" @@ -289,6 +291,18 @@ func newSigner(config *packet.Config) (signer interface{}, err error) { return nil, err } return priv, nil + case packet.PubKeyAlgoEd25519: + priv, err := ed25519.GenerateKey(config.Random()) + if err != nil { + return nil, err + } + return priv, nil + case packet.PubKeyAlgoEd448: + priv, err := ed448.GenerateKey(config.Random()) + if err != nil { + return nil, err + } + return priv, nil default: return nil, errors.InvalidArgumentError("unsupported public key algorithm") } @@ -320,9 +334,9 @@ func newDecrypter(config *packet.Config) (decrypter interface{}, err error) { return nil, errors.InvalidArgumentError("unsupported curve") } return ecdh.GenerateKey(config.Random(), curve, kdf) - case packet.PubKeyAlgoX25519: + case packet.PubKeyAlgoEd25519, packet.PubKeyAlgoX25519: // When passing Ed25519, we generate an X25519 subkey return x25519.GenerateKey(config.Random()) - case packet.PubKeyAlgoX448: + case packet.PubKeyAlgoEd448, packet.PubKeyAlgoX448: // When passing Ed448, we generate an X448 subkey return x448.GenerateKey(config.Random()) default: return nil, errors.InvalidArgumentError("unsupported public key algorithm") diff --git a/openpgp/keys_v6_test.go b/openpgp/keys_v6_test.go index ef01c6c08..3231b73ec 100644 --- a/openpgp/keys_v6_test.go +++ b/openpgp/keys_v6_test.go @@ -3,6 +3,7 @@ package openpgp import ( "bytes" "crypto" + "strings" "testing" "github.com/ProtonMail/go-crypto/openpgp/packet" @@ -12,7 +13,7 @@ var foreignKeysV6 = []string{ v6PrivKey, } -/*func TestReadPrivateForeignV6Key(t *testing.T) { +func TestReadPrivateForeignV6Key(t *testing.T) { for _, str := range foreignKeysV6 { kring, err := ReadArmoredKeyRing(strings.NewReader(str)) if err != nil { @@ -20,7 +21,7 @@ var foreignKeysV6 = []string{ } checkV6Key(t, kring[0]) } -}*/ // TODO: need x25519 support +} func TestReadPrivateEncryptedV6Key(t *testing.T) { c := &packet.Config{V6Keys: true} diff --git a/openpgp/packet/packet.go b/openpgp/packet/packet.go index 3f1ea5e8b..18f19d0f4 100644 --- a/openpgp/packet/packet.go +++ b/openpgp/packet/packet.go @@ -411,14 +411,15 @@ const ( PubKeyAlgoElGamal PublicKeyAlgorithm = 16 PubKeyAlgoDSA PublicKeyAlgorithm = 17 // RFC 6637, Section 5. - PubKeyAlgoECDH PublicKeyAlgorithm = 18 - PubKeyAlgoECDSA PublicKeyAlgorithm = 19 + PubKeyAlgoECDH PublicKeyAlgorithm = 18 + PubKeyAlgoECDSA PublicKeyAlgorithm = 19 // https://www.ietf.org/archive/id/draft-koch-eddsa-for-openpgp-04.txt - PubKeyAlgoEdDSA PublicKeyAlgorithm = 22 + PubKeyAlgoEdDSA PublicKeyAlgorithm = 22 // https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh - PubKeyAlgoX25519 PublicKeyAlgorithm = 25 - // https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh - PubKeyAlgoX448 PublicKeyAlgorithm = 26 + PubKeyAlgoX25519 PublicKeyAlgorithm = 25 + PubKeyAlgoX448 PublicKeyAlgorithm = 26 + PubKeyAlgoEd25519 PublicKeyAlgorithm = 27 + PubKeyAlgoEd448 PublicKeyAlgorithm = 28 // Deprecated in RFC 4880, Section 13.5. Use key flags instead. PubKeyAlgoRSAEncryptOnly PublicKeyAlgorithm = 2 @@ -429,7 +430,7 @@ const ( // key of the given type. func (pka PublicKeyAlgorithm) CanEncrypt() bool { switch pka { - case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoElGamal, PubKeyAlgoECDH: + case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoElGamal, PubKeyAlgoECDH, PubKeyAlgoX25519, PubKeyAlgoX448: return true } return false @@ -439,7 +440,7 @@ func (pka PublicKeyAlgorithm) CanEncrypt() bool { // sign a message. func (pka PublicKeyAlgorithm) CanSign() bool { switch pka { - case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly, PubKeyAlgoDSA, PubKeyAlgoECDSA, PubKeyAlgoEdDSA: + case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly, PubKeyAlgoDSA, PubKeyAlgoECDSA, PubKeyAlgoEdDSA, PubKeyAlgoEd25519, PubKeyAlgoEd448: return true } return false diff --git a/openpgp/packet/private_key.go b/openpgp/packet/private_key.go index e497a7c9b..4325038a3 100644 --- a/openpgp/packet/private_key.go +++ b/openpgp/packet/private_key.go @@ -21,6 +21,8 @@ import ( "github.com/ProtonMail/go-crypto/openpgp/ecdh" "github.com/ProtonMail/go-crypto/openpgp/ecdsa" + "github.com/ProtonMail/go-crypto/openpgp/ed25519" + "github.com/ProtonMail/go-crypto/openpgp/ed448" "github.com/ProtonMail/go-crypto/openpgp/eddsa" "github.com/ProtonMail/go-crypto/openpgp/elgamal" "github.com/ProtonMail/go-crypto/openpgp/errors" @@ -40,7 +42,7 @@ type PrivateKey struct { cipher CipherFunction s2k func(out, in []byte) aead AEADMode // only relevant if S2KAEAD is enabled - // An *{rsa|dsa|elgamal|ecdh|ecdsa|ed25519}.PrivateKey or + // An *{rsa|dsa|elgamal|ecdh|ecdsa|ed25519|ed448}.PrivateKey or // crypto.Signer/crypto.Decrypter (Decryptor RSA only). PrivateKey interface{} iv []byte @@ -123,6 +125,20 @@ func NewX448PrivateKey(creationTime time.Time, priv *x448.PrivateKey) *PrivateKe return pk } +func NewEd25519PrivateKey(creationTime time.Time, priv *ed25519.PrivateKey) *PrivateKey { + pk := new(PrivateKey) + pk.PublicKey = *NewEd25519PublicKey(creationTime, &priv.PublicKey) + pk.PrivateKey = priv + return pk +} + +func NewEd448PrivateKey(creationTime time.Time, priv *ed448.PrivateKey) *PrivateKey { + pk := new(PrivateKey) + pk.PublicKey = *NewEd448PublicKey(creationTime, &priv.PublicKey) + pk.PrivateKey = priv + return pk +} + // NewSignerPrivateKey creates a PrivateKey from a crypto.Signer that // implements RSA, ECDSA or EdDSA. func NewSignerPrivateKey(creationTime time.Time, signer interface{}) *PrivateKey { @@ -142,6 +158,14 @@ func NewSignerPrivateKey(creationTime time.Time, signer interface{}) *PrivateKey pk.PublicKey = *NewEdDSAPublicKey(creationTime, &pubkey.PublicKey) case eddsa.PrivateKey: pk.PublicKey = *NewEdDSAPublicKey(creationTime, &pubkey.PublicKey) + case *ed25519.PrivateKey: + pk.PublicKey = *NewEd25519PublicKey(creationTime, &pubkey.PublicKey) + case ed25519.PrivateKey: + pk.PublicKey = *NewEd25519PublicKey(creationTime, &pubkey.PublicKey) + case *ed448.PrivateKey: + pk.PublicKey = *NewEd448PublicKey(creationTime, &pubkey.PublicKey) + case ed448.PrivateKey: + pk.PublicKey = *NewEd448PublicKey(creationTime, &pubkey.PublicKey) default: panic("openpgp: unknown signer type in NewSignerPrivateKey") } @@ -149,7 +173,7 @@ func NewSignerPrivateKey(creationTime time.Time, signer interface{}) *PrivateKey return pk } -// NewDecrypterPrivateKey creates a PrivateKey from a *{rsa|elgamal|ecdh|x25519}.PrivateKey. +// NewDecrypterPrivateKey creates a PrivateKey from a *{rsa|elgamal|ecdh|x25519|x448}.PrivateKey. func NewDecrypterPrivateKey(creationTime time.Time, decrypter interface{}) *PrivateKey { pk := new(PrivateKey) switch priv := decrypter.(type) { @@ -485,6 +509,16 @@ func serializeX448PrivateKey(w io.Writer, priv *x448.PrivateKey) error { return err } +func serializeEd25519PrivateKey(w io.Writer, priv *ed25519.PrivateKey) error { + _, err := w.Write(priv.MarshalByteSecret()) + return err +} + +func serializeEd448PrivateKey(w io.Writer, priv *ed448.PrivateKey) error { + _, err := w.Write(priv.MarshalByteSecret()) + return err +} + // decrypt decrypts an encrypted private key using a decryption key. func (pk *PrivateKey) decrypt(decryptionKey []byte) error { if pk.Dummy() { @@ -767,6 +801,10 @@ func (pk *PrivateKey) serializePrivateKey(w io.Writer) (err error) { err = serializeX25519PrivateKey(w, priv) case *x448.PrivateKey: err = serializeX448PrivateKey(w, priv) + case *ed25519.PrivateKey: + err = serializeEd25519PrivateKey(w, priv) + case *ed448.PrivateKey: + err = serializeEd448PrivateKey(w, priv) default: err = errors.InvalidArgumentError("unknown private key type") } @@ -791,6 +829,10 @@ func (pk *PrivateKey) parsePrivateKey(data []byte) (err error) { return pk.parseX25519PrivateKey(data) case PubKeyAlgoX448: return pk.parseX448PrivateKey(data) + case PubKeyAlgoEd25519: + return pk.parseEd25519PrivateKey(data) + case PubKeyAlgoEd448: + return pk.parseEd448PrivateKey(data) default: err = errors.InvalidArgumentError("unknown private key type") return @@ -953,6 +995,46 @@ func (pk *PrivateKey) parseX448PrivateKey(data []byte) (err error) { return nil } +func (pk *PrivateKey) parseEd25519PrivateKey(data []byte) (err error) { + publicKey := pk.PublicKey.PublicKey.(*ed25519.PublicKey) + privateKey := ed25519.NewPrivateKey(*publicKey) + privateKey.PublicKey = *publicKey + + if len(data) != ed25519.PointSize { + err = errors.StructuralError("wrong ed25519 key size") + } + err = privateKey.UnmarshalByteSecret(data) + if err != nil { + return err + } + err = ed25519.Validate(privateKey) + if err != nil { + return err + } + pk.PrivateKey = privateKey + return nil +} + +func (pk *PrivateKey) parseEd448PrivateKey(data []byte) (err error) { + publicKey := pk.PublicKey.PublicKey.(*ed448.PublicKey) + privateKey := ed448.NewPrivateKey(*publicKey) + privateKey.PublicKey = *publicKey + + if len(data) != ed448.PointSize { + err = errors.StructuralError("wrong ed448 key size") + } + err = privateKey.UnmarshalByteSecret(data) + if err != nil { + return err + } + err = ed448.Validate(privateKey) + if err != nil { + return err + } + pk.PrivateKey = privateKey + return nil +} + func (pk *PrivateKey) parseEdDSAPrivateKey(data []byte) (err error) { eddsaPub := pk.PublicKey.PublicKey.(*eddsa.PublicKey) eddsaPriv := eddsa.NewPrivateKey(*eddsaPub) diff --git a/openpgp/packet/public_key.go b/openpgp/packet/public_key.go index b14c50315..6488d3dbe 100644 --- a/openpgp/packet/public_key.go +++ b/openpgp/packet/public_key.go @@ -20,6 +20,8 @@ import ( "github.com/ProtonMail/go-crypto/openpgp/ecdh" "github.com/ProtonMail/go-crypto/openpgp/ecdsa" + "github.com/ProtonMail/go-crypto/openpgp/ed25519" + "github.com/ProtonMail/go-crypto/openpgp/ed448" "github.com/ProtonMail/go-crypto/openpgp/eddsa" "github.com/ProtonMail/go-crypto/openpgp/elgamal" "github.com/ProtonMail/go-crypto/openpgp/errors" @@ -38,7 +40,7 @@ type PublicKey struct { Version int CreationTime time.Time PubKeyAlgo PublicKeyAlgorithm - PublicKey interface{} // *rsa.PublicKey, *dsa.PublicKey, *ecdsa.PublicKey or *eddsa.PublicKey, *x25519.PublicKey, *x448.PublicKey + PublicKey interface{} // *rsa.PublicKey, *dsa.PublicKey, *ecdsa.PublicKey or *eddsa.PublicKey, *x25519.PublicKey, *x448.PublicKey, *ed25519.PublicKey, *ed448.PublicKey Fingerprint []byte KeyId uint64 IsSubkey bool @@ -207,6 +209,30 @@ func NewX448PublicKey(creationTime time.Time, pub *x448.PublicKey) *PublicKey { return pk } +func NewEd25519PublicKey(creationTime time.Time, pub *ed25519.PublicKey) *PublicKey { + pk := &PublicKey{ + Version: 4, + CreationTime: creationTime, + PubKeyAlgo: PubKeyAlgoEd25519, + PublicKey: pub, + } + + pk.setFingerprintAndKeyId() + return pk +} + +func NewEd448PublicKey(creationTime time.Time, pub *ed448.PublicKey) *PublicKey { + pk := &PublicKey{ + Version: 4, + CreationTime: creationTime, + PubKeyAlgo: PubKeyAlgoEd448, + PublicKey: pub, + } + + pk.setFingerprintAndKeyId() + return pk +} + func (pk *PublicKey) parse(r io.Reader) (err error) { // RFC 4880, section 5.5.2 var buf [6]byte @@ -248,6 +274,10 @@ func (pk *PublicKey) parse(r io.Reader) (err error) { err = pk.parseX25519(r) case PubKeyAlgoX448: err = pk.parseX448(r) + case PubKeyAlgoEd25519: + err = pk.parseEd25519(r) + case PubKeyAlgoEd448: + err = pk.parseEd448(r) default: err = errors.UnsupportedError("public key type: " + strconv.Itoa(int(pk.PubKeyAlgo))) } @@ -481,7 +511,7 @@ func (pk *PublicKey) parseX25519(r io.Reader) (err error) { if err != nil { return } - pub := x25519.PublicKey { + pub := &x25519.PublicKey { Point: point, } pk.PublicKey = pub @@ -494,7 +524,33 @@ func (pk *PublicKey) parseX448(r io.Reader) (err error) { if err != nil { return } - pub := x448.PublicKey { + pub := &x448.PublicKey { + Point: point, + } + pk.PublicKey = pub + return +} + +func (pk *PublicKey) parseEd25519(r io.Reader) (err error) { + point := make([]byte, ed25519.PointSize) + _, err = io.ReadFull(r, point) + if err != nil { + return + } + pub := &ed25519.PublicKey { + Point: point, + } + pk.PublicKey = pub + return +} + +func (pk *PublicKey) parseEd448(r io.Reader) (err error) { + point := make([]byte, ed448.PointSize) + _, err = io.ReadFull(r, point) + if err != nil { + return + } + pub := &ed448.PublicKey { Point: point, } pk.PublicKey = pub @@ -574,6 +630,10 @@ func (pk *PublicKey) algorithmSpecificByteCount() int { length += x25519.PointSize case PubKeyAlgoX448: length += x448.PointSize + case PubKeyAlgoEd25519: + length += ed25519.PointSize + case PubKeyAlgoEd448: + length += ed448.PointSize default: panic("unknown public key algorithm") } @@ -650,6 +710,22 @@ func (pk *PublicKey) serializeWithoutHeaders(w io.Writer) (err error) { } _, err = w.Write(pk.p.EncodedBytes()) return + case PubKeyAlgoX25519: + publicKey := pk.PublicKey.(*x25519.PublicKey) + _, err = w.Write(publicKey.Point) + return + case PubKeyAlgoX448: + publicKey := pk.PublicKey.(*x448.PublicKey) + _, err = w.Write(publicKey.Point) + return + case PubKeyAlgoEd25519: + publicKey := pk.PublicKey.(*ed25519.PublicKey) + _, err = w.Write(publicKey.Point) + return + case PubKeyAlgoEd448: + publicKey := pk.PublicKey.(*ed448.PublicKey) + _, err = w.Write(publicKey.Point) + return } return errors.InvalidArgumentError("bad public-key algorithm") } @@ -709,6 +785,18 @@ func (pk *PublicKey) VerifySignature(signed hash.Hash, sig *Signature) (err erro return errors.SignatureError("EdDSA verification failure") } return nil + case PubKeyAlgoEd25519: + ed25519PublicKey := pk.PublicKey.(*ed25519.PublicKey) + if !ed25519.Verify(ed25519PublicKey, hashBytes, sig.EdSig) { + return errors.SignatureError("Ed25519 verification failure") + } + return nil + case PubKeyAlgoEd448: + ed448PublicKey := pk.PublicKey.(*ed448.PublicKey) + if !ed448.Verify(ed448PublicKey, hashBytes, sig.EdSig) { + return errors.SignatureError("ed448 verification failure") + } + return nil default: return errors.SignatureError("Unsupported public key algorithm used in signature") } @@ -892,6 +980,14 @@ func (pk *PublicKey) BitLength() (bitLength uint16, err error) { bitLength = pk.p.BitLength() case PubKeyAlgoEdDSA: bitLength = pk.p.BitLength() + case PubKeyAlgoX25519: + bitLength = x25519.PointSize * 8 + case PubKeyAlgoX448: + bitLength = x448.PointSize * 8 + case PubKeyAlgoEd25519: + bitLength = ed25519.PointSize * 8 + case PubKeyAlgoEd448: + bitLength = ed448.PointSize * 8 default: err = errors.InvalidArgumentError("bad public-key algorithm") } diff --git a/openpgp/packet/signature.go b/openpgp/packet/signature.go index e18366a00..40568867f 100644 --- a/openpgp/packet/signature.go +++ b/openpgp/packet/signature.go @@ -15,6 +15,8 @@ import ( "time" "github.com/ProtonMail/go-crypto/openpgp/ecdsa" + "github.com/ProtonMail/go-crypto/openpgp/ed25519" + "github.com/ProtonMail/go-crypto/openpgp/ed448" "github.com/ProtonMail/go-crypto/openpgp/eddsa" "github.com/ProtonMail/go-crypto/openpgp/errors" "github.com/ProtonMail/go-crypto/openpgp/internal/algorithm" @@ -60,6 +62,7 @@ type Signature struct { DSASigR, DSASigS encoding.Field ECDSASigR, ECDSASigS encoding.Field EdDSASigR, EdDSASigS encoding.Field + EdSig []byte // rawSubpackets contains the unparsed subpackets, in order. rawSubpackets []outputSubpacket @@ -146,7 +149,7 @@ func (sig *Signature) parse(r io.Reader) (err error) { sig.SigType = SignatureType(buf[0]) sig.PubKeyAlgo = PublicKeyAlgorithm(buf[1]) switch sig.PubKeyAlgo { - case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly, PubKeyAlgoDSA, PubKeyAlgoECDSA, PubKeyAlgoEdDSA: + case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly, PubKeyAlgoDSA, PubKeyAlgoECDSA, PubKeyAlgoEdDSA, PubKeyAlgoEd25519, PubKeyAlgoEd448: default: err = errors.UnsupportedError("public key algorithm " + strconv.Itoa(int(sig.PubKeyAlgo))) return @@ -273,6 +276,16 @@ func (sig *Signature) parse(r io.Reader) (err error) { if _, err = sig.EdDSASigS.ReadFrom(r); err != nil { return } + case PubKeyAlgoEd25519: + sig.EdSig, err = ed25519.ReadSignature(r) + if err != nil { + return + } + case PubKeyAlgoEd448: + sig.EdSig, err = ed448.ReadSignature(r) + if err != nil { + return + } default: panic("unreachable") } @@ -862,6 +875,18 @@ func (sig *Signature) Sign(h hash.Hash, priv *PrivateKey, config *Config) (err e sig.EdDSASigR = encoding.NewMPI(r) sig.EdDSASigS = encoding.NewMPI(s) } + case PubKeyAlgoEd25519: + sk := priv.PrivateKey.(*ed25519.PrivateKey) + signature, err := ed25519.Sign(sk, digest) + if err == nil { + sig.EdSig = signature + } + case PubKeyAlgoEd448: + sk := priv.PrivateKey.(*ed448.PrivateKey) + signature, err := ed448.Sign(sk, digest) + if err == nil { + sig.EdSig = signature + } default: err = errors.UnsupportedError("public key algorithm: " + strconv.Itoa(int(sig.PubKeyAlgo))) } @@ -970,7 +995,7 @@ func (sig *Signature) Serialize(w io.Writer) (err error) { if len(sig.outSubpackets) == 0 { sig.outSubpackets = sig.rawSubpackets } - if sig.RSASignature == nil && sig.DSASigR == nil && sig.ECDSASigR == nil && sig.EdDSASigR == nil { + if sig.RSASignature == nil && sig.DSASigR == nil && sig.ECDSASigR == nil && sig.EdDSASigR == nil && sig.EdSig == nil { return errors.InvalidArgumentError("Signature: need to call Sign, SignUserId or SignKey before Serialize") } @@ -987,6 +1012,10 @@ func (sig *Signature) Serialize(w io.Writer) (err error) { case PubKeyAlgoEdDSA: sigLength = int(sig.EdDSASigR.EncodedLength()) sigLength += int(sig.EdDSASigS.EncodedLength()) + case PubKeyAlgoEd25519: + sigLength = ed25519.SignatureSize + case PubKeyAlgoEd448: + sigLength = ed448.SignatureSize default: panic("impossible") } @@ -1091,6 +1120,10 @@ func (sig *Signature) serializeBody(w io.Writer) (err error) { return } _, err = w.Write(sig.EdDSASigS.EncodedBytes()) + case PubKeyAlgoEd25519: + err = ed25519.WriteSignature(w, sig.EdSig) + case PubKeyAlgoEd448: + err = ed448.WriteSignature(w, sig.EdSig) default: panic("impossible") } @@ -1301,6 +1334,8 @@ func SaltLengthForHash(hash crypto.Hash) (int, error) { case crypto.SHA384: return 24, nil case crypto.SHA512: return 32, nil case crypto.SHA224: return 16, nil + case crypto.SHA3_256: return 16, nil + case crypto.SHA3_512: return 32, nil default: return 0, errors.UnsupportedError("hash function not supported for V6 signatures") } } diff --git a/openpgp/read_write_test_data.go b/openpgp/read_write_test_data.go index 7b5338ea1..917a106e0 100644 --- a/openpgp/read_write_test_data.go +++ b/openpgp/read_write_test_data.go @@ -26,6 +26,8 @@ const testKeys1And2PrivateHex = "9501d8044d3c5c10010400b1d13382944bd5aba23a43129 const dsaElGamalTestKeysHex = "9501e1044dfcb16a110400aa3e5c1a1f43dd28c2ffae8abf5cfce555ee874134d8ba0a0f7b868ce2214beddc74e5e1e21ded354a95d18acdaf69e5e342371a71fbb9093162e0c5f3427de413a7f2c157d83f5cd2f9d791256dc4f6f0e13f13c3302af27f2384075ab3021dff7a050e14854bbde0a1094174855fc02f0bae8e00a340d94a1f22b32e48485700a0cec672ac21258fb95f61de2ce1af74b2c4fa3e6703ff698edc9be22c02ae4d916e4fa223f819d46582c0516235848a77b577ea49018dcd5e9e15cff9dbb4663a1ae6dd7580fa40946d40c05f72814b0f88481207e6c0832c3bded4853ebba0a7e3bd8e8c66df33d5a537cd4acf946d1080e7a3dcea679cb2b11a72a33a2b6a9dc85f466ad2ddf4c3db6283fa645343286971e3dd700703fc0c4e290d45767f370831a90187e74e9972aae5bff488eeff7d620af0362bfb95c1a6c3413ab5d15a2e4139e5d07a54d72583914661ed6a87cce810be28a0aa8879a2dd39e52fb6fe800f4f181ac7e328f740cde3d09a05cecf9483e4cca4253e60d4429ffd679d9996a520012aad119878c941e3cf151459873bdfc2a9563472fe0303027a728f9feb3b864260a1babe83925ce794710cfd642ee4ae0e5b9d74cee49e9c67b6cd0ea5dfbb582132195a121356a1513e1bca73e5b80c58c7ccb4164453412f456c47616d616c2054657374204b65792031886204131102002205024dfcb16a021b03060b090807030206150802090a0b0416020301021e01021780000a091033af447ccd759b09fadd00a0b8fd6f5a790bad7e9f2dbb7632046dc4493588db009c087c6a9ba9f7f49fab221587a74788c00db4889ab00200009d0157044dfcb16a1004008dec3f9291205255ccff8c532318133a6840739dd68b03ba942676f9038612071447bf07d00d559c5c0875724ea16a4c774f80d8338b55fca691a0522e530e604215b467bbc9ccfd483a1da99d7bc2648b4318fdbd27766fc8bfad3fddb37c62b8ae7ccfe9577e9b8d1e77c1d417ed2c2ef02d52f4da11600d85d3229607943700030503ff506c94c87c8cab778e963b76cf63770f0a79bf48fb49d3b4e52234620fc9f7657f9f8d56c96a2b7c7826ae6b57ebb2221a3fe154b03b6637cea7e6d98e3e45d87cf8dc432f723d3d71f89c5192ac8d7290684d2c25ce55846a80c9a7823f6acd9bb29fa6cd71f20bc90eccfca20451d0c976e460e672b000df49466408d527affe0303027a728f9feb3b864260abd761730327bca2aaa4ea0525c175e92bf240682a0e83b226f97ecb2e935b62c9a133858ce31b271fa8eb41f6a1b3cd72a63025ce1a75ee4180dcc284884904181102000905024dfcb16a021b0c000a091033af447ccd759b09dd0b009e3c3e7296092c81bee5a19929462caaf2fff3ae26009e218c437a2340e7ea628149af1ec98ec091a43992b00200009501e1044dfcb1be1104009f61faa61aa43df75d128cbe53de528c4aec49ce9360c992e70c77072ad5623de0a3a6212771b66b39a30dad6781799e92608316900518ec01184a85d872365b7d2ba4bacfb5882ea3c2473d3750dc6178cc1cf82147fb58caa28b28e9f12f6d1efcb0534abed644156c91cca4ab78834268495160b2400bc422beb37d237c2300a0cac94911b6d493bda1e1fbc6feeca7cb7421d34b03fe22cec6ccb39675bb7b94a335c2b7be888fd3906a1125f33301d8aa6ec6ee6878f46f73961c8d57a3e9544d8ef2a2cbfd4d52da665b1266928cfe4cb347a58c412815f3b2d2369dec04b41ac9a71cc9547426d5ab941cccf3b18575637ccfb42df1a802df3cfe0a999f9e7109331170e3a221991bf868543960f8c816c28097e503fe319db10fb98049f3a57d7c80c420da66d56f3644371631fad3f0ff4040a19a4fedc2d07727a1b27576f75a4d28c47d8246f27071e12d7a8de62aad216ddbae6aa02efd6b8a3e2818cda48526549791ab277e447b3a36c57cefe9b592f5eab73959743fcc8e83cbefec03a329b55018b53eec196765ae40ef9e20521a603c551efe0303020950d53a146bf9c66034d00c23130cce95576a2ff78016ca471276e8227fb30b1ffbd92e61804fb0c3eff9e30b1a826ee8f3e4730b4d86273ca977b4164453412f456c47616d616c2054657374204b65792032886204131102002205024dfcb1be021b03060b090807030206150802090a0b0416020301021e01021780000a0910a86bf526325b21b22bd9009e34511620415c974750a20df5cb56b182f3b48e6600a0a9466cb1a1305a84953445f77d461593f1d42bc1b00200009d0157044dfcb1be1004009565a951da1ee87119d600c077198f1c1bceb0f7aa54552489298e41ff788fa8f0d43a69871f0f6f77ebdfb14a4260cf9fbeb65d5844b4272a1904dd95136d06c3da745dc46327dd44a0f16f60135914368c8039a34033862261806bb2c5ce1152e2840254697872c85441ccb7321431d75a747a4bfb1d2c66362b51ce76311700030503fc0ea76601c196768070b7365a200e6ddb09307f262d5f39eec467b5f5784e22abdf1aa49226f59ab37cb49969d8f5230ea65caf56015abda62604544ed526c5c522bf92bed178a078789f6c807b6d34885688024a5bed9e9f8c58d11d4b82487b44c5f470c5606806a0443b79cadb45e0f897a561a53f724e5349b9267c75ca17fe0303020950d53a146bf9c660bc5f4ce8f072465e2d2466434320c1e712272fafc20e342fe7608101580fa1a1a367e60486a7cd1246b7ef5586cf5e10b32762b710a30144f12dd17dd4884904181102000905024dfcb1be021b0c000a0910a86bf526325b21b2904c00a0b2b66b4b39ccffda1d10f3ea8d58f827e30a8b8e009f4255b2d8112a184e40cde43a34e8655ca7809370b0020000" +const ed25519wX25519Key = "c54b0663877fe31b00000020f94da7bb48d60a61e567706a6587d0331999bb9d891a08242ead84543df895a3001972817b12be707e8d5f586ce61361201d344eb266a2c82fde6835762b65b0b7c2b1061f1b0a00000042058263877fe3030b090705150a0e080c021600029b03021e09222106cb186c4f0609a697e4d52dfa6c722b0c1f1e27c18a56708f6525ec27bad9acc905270902070200000000ad2820103e2d7d227ec0e6d7ce4471db36bfc97083253690271498a7ef0576c07faae14585b3b903b0127ec4fda2f023045a2ec76bcb4f9571a9651e14aee1137a1d668442c88f951e33c4ffd33fb9a17d511eed758fc6d9cc50cb5fd793b2039d5804c74b0663877fe319000000208693248367f9e5015db922f8f48095dda784987f2d5985b12fbad16caf5e4435004d600a4f794d44775c57a26e0feefed558e9afffd6ad0d582d57fb2ba2dcedb8c29b06181b0a0000002c050263877fe322a106cb186c4f0609a697e4d52dfa6c722b0c1f1e27c18a56708f6525ec27bad9acc9021b0c00000000defa20a6e9186d9d5935fc8fe56314cdb527486a5a5120f9b762a235a729f039010a56b89c658568341fbef3b894e9834ad9bc72afae2f4c9c47a43855e65f1cb0a3f77bbc5f61085c1f8249fe4e7ca59af5f0bcee9398e0fa8d76e522e1d8ab42bb0d" + const signedMessageHex = "a3019bc0cbccc0c4b8d8b74ee2108fe16ec6d3ca490cbe362d3f8333d3f352531472538b8b13d353b97232f352158c20943157c71c16064626063656269052062e4e01987e9b6fccff4b7df3a34c534b23e679cbec3bc0f8f6e64dfb4b55fe3f8efa9ce110ddb5cd79faf1d753c51aecfa669f7e7aa043436596cccc3359cb7dd6bbe9ecaa69e5989d9e57209571edc0b2fa7f57b9b79a64ee6e99ce1371395fee92fec2796f7b15a77c386ff668ee27f6d38f0baa6c438b561657377bf6acff3c5947befd7bf4c196252f1d6e5c524d0300" const signedTextMessageHex = "a3019bc0cbccc8c4b8d8b74ee2108fe16ec6d36a250cbece0c178233d3f352531472538b8b13d35379b97232f352158ca0b4312f57c71c1646462606365626906a062e4e019811591798ff99bf8afee860b0d8a8c2a85c3387e3bcf0bb3b17987f2bbcfab2aa526d930cbfd3d98757184df3995c9f3e7790e36e3e9779f06089d4c64e9e47dd6202cb6e9bc73c5d11bb59fbaf89d22d8dc7cf199ddf17af96e77c5f65f9bbed56f427bd8db7af37f6c9984bf9385efaf5f184f986fb3e6adb0ecfe35bbf92d16a7aa2a344fb0bc52fb7624f0200" diff --git a/openpgp/write_test.go b/openpgp/write_test.go index 38a759d4f..a0a5ef7c0 100644 --- a/openpgp/write_test.go +++ b/openpgp/write_test.go @@ -580,6 +580,9 @@ var testSigningTests = []struct { { dsaElGamalTestKeysHex, }, + { + ed25519wX25519Key, + }, } func TestSigning(t *testing.T) { @@ -631,7 +634,7 @@ func TestSigning(t *testing.T) { continue } - testTime, _ := time.Parse("2006-01-02", "2013-07-01") + testTime, _ := time.Parse("2006-01-02", "2022-12-01") signKey, _ := kring[0].SigningKey(testTime) expectedKeyId := signKey.PublicKey.KeyId if md.SignedByKeyId != expectedKeyId { From 25812485dfb38644bbf27d5399f29ef3f089b73f Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Thu, 6 Apr 2023 14:23:36 +0200 Subject: [PATCH 016/116] Mandate AES for X25519 and X448 in v3 PKESKs --- openpgp/packet/encrypted_key.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/openpgp/packet/encrypted_key.go b/openpgp/packet/encrypted_key.go index 26239d0d5..9b5f1eb3b 100644 --- a/openpgp/packet/encrypted_key.go +++ b/openpgp/packet/encrypted_key.go @@ -321,6 +321,16 @@ func SerializeEncryptedKey(w io.Writer, pub *PublicKey, cipherFunc CipherFunctio if version == 6 && pub.PubKeyAlgo == PubKeyAlgoElGamal { return errors.InvalidArgumentError("ElGamal v6 PKESK are not allowed") } + // In v3 PKESKs, for X25519 and X448, mandate using AES + if version == 3 && (pub.PubKeyAlgo == PubKeyAlgoX25519 || pub.PubKeyAlgo == PubKeyAlgoX448) { + switch cipherFunc { + case CipherAES128, CipherAES192, CipherAES256: + break + default: + return errors.InvalidArgumentError("v3 PKESK mandates AES for x25519 and x448") + } + } + buf[0] = byte(version) if version == 6 { From f3bce804530c1d1e08df334fe4792d613f37b809 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Wed, 5 Apr 2023 14:37:16 +0200 Subject: [PATCH 017/116] Remove S2K secret key encryption type from config The aead secret key encrypton mode is now enabled by setting the aead config in config. --- openpgp/packet/config.go | 10 ---------- openpgp/packet/private_key.go | 12 ++++++++---- openpgp/packet/private_key_test.go | 10 +++++++--- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/openpgp/packet/config.go b/openpgp/packet/config.go index 63ae4cc48..2ccf67f0f 100644 --- a/openpgp/packet/config.go +++ b/openpgp/packet/config.go @@ -59,9 +59,6 @@ type Config struct { // This config allows to set this key encryption parameters. // If nil, the default parameters are used. // See OpenPGP crypto refresh 3.7.2.1. - S2KSecretKey *S2KType - // RSABits is the number of bits in new RSA keys made with NewEntity. - // If zero, then 2048 bit keys are created. RSABits int // The public key algorithm to use - will always create a signing primary // key and encryption subkey. @@ -252,10 +249,3 @@ func (c *Config) Notations() []*Notation { } return c.SignatureNotations } - -func (c *Config) S2KKey() S2KType { - if c == nil || c.S2KSecretKey == nil { - return S2KSHA1 - } - return *c.S2KSecretKey -} diff --git a/openpgp/packet/private_key.go b/openpgp/packet/private_key.go index 4325038a3..2e8cdbf9e 100644 --- a/openpgp/packet/private_key.go +++ b/openpgp/packet/private_key.go @@ -730,13 +730,15 @@ func (pk *PrivateKey) EncryptWithConfig(passphrase []byte, config *Config) error return err } s2k(key, passphrase) - if config.S2KKey() == S2KAEAD { + s2kType := S2KSHA1 + if config.AEAD() != nil { + s2kType = S2KAEAD pk.aead = config.AEAD().Mode() pk.cipher = config.Cipher() key = pk.applyHKDF(key) } // Encrypt the private key with the derived encryption key. - return pk.encrypt(key, params, config.S2KKey(), config.Cipher(), config.Random()) + return pk.encrypt(key, params, s2kType, config.Cipher(), config.Random()) } // EncryptPrivateKeys encrypts all unencrypted keys with the given config and passphrase. @@ -755,12 +757,14 @@ func EncryptPrivateKeys(keys []*PrivateKey, passphrase []byte, config *Config) e s2k(encryptionKey, passphrase) for _, key := range keys { if key != nil && !key.Dummy() && !key.Encrypted { - if config.S2KKey() == S2KAEAD { + s2kType := S2KSHA1 + if config.AEAD() != nil { + s2kType = S2KAEAD key.aead = config.AEAD().Mode() key.cipher = config.Cipher() encryptionKey = key.applyHKDF(encryptionKey) } - err = key.encrypt(encryptionKey, params, config.S2KKey(), config.Cipher(), config.Random()) + err = key.encrypt(encryptionKey, params, s2kType, config.Cipher(), config.Random()) if err != nil { return err } diff --git a/openpgp/packet/private_key_test.go b/openpgp/packet/private_key_test.go index 1e52fd0d6..6b06b1002 100644 --- a/openpgp/packet/private_key_test.go +++ b/openpgp/packet/private_key_test.go @@ -141,13 +141,17 @@ func TestExternalPrivateKeyEncryptDecryptRandomizeSlow(t *testing.T) { func TestExternalPrivateKeyEncryptDecryptS2KModes(t *testing.T) { sk2Modes := []s2k.Mode{s2k.IteratedSaltedS2K, s2k.Argon2S2K} - sk2KeyTypes := []S2KType{S2KCHECKSUM, S2KAEAD, S2KSHA1} + sk2KeyTypes := []S2KType{S2KAEAD, S2KSHA1} for _, s2kMode := range sk2Modes { for _, sk2KeyType := range sk2KeyTypes { t.Run(fmt.Sprintf("s2kMode:%d-s2kType:%d", s2kMode, sk2KeyType), func(t *testing.T) { + var configAEAD *AEADConfig + if sk2KeyType == S2KAEAD { + configAEAD = &AEADConfig{} + } config := &Config{ - S2KConfig: &s2k.Config{S2KMode: s2kMode}, - S2KSecretKey: &sk2KeyType, + S2KConfig: &s2k.Config{S2KMode: s2kMode}, + AEADConfig: configAEAD, } for i, test := range privateKeyTests { packet, err := Read(readerFromHex(test.privateKeyHex)) From d0b8f3099754d23b6593832633e01dee8df6ec48 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Tue, 11 Apr 2023 14:28:31 +0200 Subject: [PATCH 018/116] Make PrimarySelfSignature public on Entity --- openpgp/keys.go | 8 ++++---- openpgp/read.go | 2 +- openpgp/write.go | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/openpgp/keys.go b/openpgp/keys.go index f6f075b0f..9360b569f 100644 --- a/openpgp/keys.go +++ b/openpgp/keys.go @@ -125,7 +125,7 @@ func shouldPreferSelfSignature(existingSignature, potentialSignature *packet.Sig // given Entity. func (e *Entity) EncryptionKey(now time.Time) (Key, bool) { // Fail to find any encryption key if the... - primarySelfSignature, primaryIdentity := e.primarySelfSignature() + primarySelfSignature, primaryIdentity := e.PrimarySelfSignature() if primarySelfSignature == nil || // no self-signature found e.PrimaryKey.KeyExpired(primarySelfSignature, now) || // primary key has expired e.Revoked(now) || // primary key has been revoked @@ -190,7 +190,7 @@ func (e *Entity) SigningKeyById(now time.Time, id uint64) (Key, bool) { func (e *Entity) signingKeyByIdUsage(now time.Time, id uint64, flags int) (Key, bool) { // Fail to find any signing key if the... - primarySelfSignature, primaryIdentity := e.primarySelfSignature() + primarySelfSignature, primaryIdentity := e.PrimarySelfSignature() if primarySelfSignature == nil || // no self-signature found e.PrimaryKey.KeyExpired(primarySelfSignature, now) || // primary key has expired e.Revoked(now) || // primary key has been revoked @@ -321,7 +321,7 @@ type EntityList []*Entity func (el EntityList) KeysById(id uint64) (keys []Key) { for _, e := range el { if e.PrimaryKey.KeyId == id { - selfSig, _ := e.primarySelfSignature() + selfSig, _ := e.PrimarySelfSignature() keys = append(keys, Key{e, e.PrimaryKey, e.PrivateKey, selfSig, e.Revocations}) } @@ -883,7 +883,7 @@ func (e *Entity) primaryDirectSignature() *packet.Signature { // For V6 keys, returns the latest valid direct-key self-signature, and no identity (nil). // This self-signature is to be used to check the key expiration, // algorithm preferences, and so on. -func (e *Entity) primarySelfSignature() (*packet.Signature, *Identity) { +func (e *Entity) PrimarySelfSignature() (*packet.Signature, *Identity) { if e.PrimaryKey.Version == 6 { return e.primaryDirectSignature(), nil } diff --git a/openpgp/read.go b/openpgp/read.go index c3a817349..d21092180 100644 --- a/openpgp/read.go +++ b/openpgp/read.go @@ -577,7 +577,7 @@ func CheckArmoredDetachedSignature(keyring KeyRing, signed, signature io.Reader, // ignore any other errors. func checkSignatureDetails(key *Key, signature *packet.Signature, config *packet.Config) error { now := config.Now() - primarySelfSignature, primaryIdentity := key.Entity.primarySelfSignature() + primarySelfSignature, primaryIdentity := key.Entity.PrimarySelfSignature() signedBySubKey := key.PublicKey != key.Entity.PrimaryKey sigsToCheck := []*packet.Signature{ signature, primarySelfSignature } if signedBySubKey { diff --git a/openpgp/write.go b/openpgp/write.go index af1d1d96f..89e67ea24 100644 --- a/openpgp/write.go +++ b/openpgp/write.go @@ -398,7 +398,7 @@ func encrypt(keyWriter io.Writer, dataWriter io.Writer, to []*Entity, signed *En return nil, errors.InvalidArgumentError("cannot encrypt a message to key id " + strconv.FormatUint(to[i].PrimaryKey.KeyId, 16) + " because it has no valid encryption keys") } - primarySelfSignature, _ := to[i].primarySelfSignature() + primarySelfSignature, _ := to[i].PrimarySelfSignature() if primarySelfSignature == nil { return nil, errors.InvalidArgumentError("entity without a self-signature") } @@ -487,7 +487,7 @@ func Sign(output io.Writer, signed *Entity, hints *FileHints, config *packet.Con hashToHashId(crypto.SHA3_512), } defaultHashes := candidateHashes[0:1] - primarySelfSignature, _ := signed.primarySelfSignature() + primarySelfSignature, _ := signed.PrimarySelfSignature() if primarySelfSignature == nil { return nil, errors.InvalidArgumentError("signed entity has no self-signature") } From eb368c2a042511f90bfc1d8fa5a9783f02a53158 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Wed, 12 Apr 2023 09:32:04 +0200 Subject: [PATCH 019/116] Add Argon2-AEAD encrypted secret key test case Added a test case for v6 Argon2-AEAD encrypted secret keys, which was freshly included in the crypto refresh rfc: https://gitlab.com/openpgp-wg/rfc4880bis/-/merge_requests/274 --- openpgp/keys_v6_test.go | 23 +++++++++++++++++++++++ openpgp/read_write_test_data.go | 19 +++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/openpgp/keys_v6_test.go b/openpgp/keys_v6_test.go index 3231b73ec..48dd6b48d 100644 --- a/openpgp/keys_v6_test.go +++ b/openpgp/keys_v6_test.go @@ -11,6 +11,7 @@ import ( var foreignKeysV6 = []string{ v6PrivKey, + v6ArgonSealedPrivKey, } func TestReadPrivateForeignV6Key(t *testing.T) { @@ -23,6 +24,28 @@ func TestReadPrivateForeignV6Key(t *testing.T) { } } +func TestReadPrivateForeignV6KeyAndDecrypt(t *testing.T) { + password := []byte("correct horse battery staple") + kring, err := ReadArmoredKeyRing(strings.NewReader(v6ArgonSealedPrivKey)) + if err != nil { + t.Fatal(err) + } + key := kring[0] + if key.PrivateKey != nil && key.PrivateKey.Encrypted { + if err := key.PrivateKey.Decrypt(password); err != nil { + t.Fatal(err) + } + } + for _, sub := range key.Subkeys { + if sub.PrivateKey != nil && sub.PrivateKey.Encrypted { + if err := key.PrivateKey.Decrypt(password); err != nil { + t.Fatal(err) + } + } + } + checkV6Key(t, kring[0]) +} + func TestReadPrivateEncryptedV6Key(t *testing.T) { c := &packet.Config{V6Keys: true} e, err := NewEntity("V6 Key Owner", "V6 Key", "v6@pm.me", c) diff --git a/openpgp/read_write_test_data.go b/openpgp/read_write_test_data.go index 917a106e0..19d806490 100644 --- a/openpgp/read_write_test_data.go +++ b/openpgp/read_write_test_data.go @@ -190,6 +190,25 @@ M0g12vYxoWM8Y81W+bHBw805I8kWVkXU6vFOi+HWvv/ira7ofJu16NnoUkhclkUr k0mXubZvyl4GBg== -----END PGP PRIVATE KEY BLOCK-----` +// See https://gitlab.com/openpgp-wg/rfc4880bis/-/merge_requests/274 +// decryption password: "correct horse battery staple" +const v6ArgonSealedPrivKey = `-----BEGIN PGP PRIVATE KEY BLOCK----- + +xYIGY4d/4xsAAAAg+U2nu0jWCmHlZ3BqZYfQMxmZu52JGggkLq2EVD34laP9JgkC +FARdb9ccngltHraRe25uHuyuAQQVtKipJ0+r5jL4dacGWSAheCWPpITYiyfyIOPS +3gIDyg8f7strd1OB4+LZsUhcIjOMpVHgmiY/IutJkulneoBYwrEGHxsKAAAAQgWC +Y4d/4wMLCQcFFQoOCAwCFgACmwMCHgkiIQbLGGxPBgmml+TVLfpscisMHx4nwYpW +cI9lJewnutmsyQUnCQIHAgAAAACtKCAQPi19In7A5tfORHHbNr/JcIMlNpAnFJin +7wV2wH+q4UWFs7kDsBJ+xP2i8CMEWi7Ha8tPlXGpZR4UruETeh1mhELIj5UeM8T/ +0z+5oX1RHu11j8bZzFDLX9eTsgOdWATHggZjh3/jGQAAACCGkySDZ/nlAV25Ivj0 +gJXdp4SYfy1ZhbEvutFsr15ENf0mCQIUBA5hhGgp2oaavg6mFUXcFMwBBBUuE8qf +9Ock+xwusd+GAglBr5LVyr/lup3xxQvHXFSjjA2haXfoN6xUGRdDEHI6+uevKjVR +v5oAxgu7eJpaXNjCmwYYGwoAAAAsBYJjh3/jApsMIiEGyxhsTwYJppfk1S36bHIr +DB8eJ8GKVnCPZSXsJ7rZrMkAAAAABAEgpukYbZ1ZNfyP5WMUzbUnSGpaUSD5t2Ki +Nacp8DkBClZRa2c3AMQzSDXa9jGhYzxjzVb5scHDzTkjyRZWRdTq8U6L4da+/+Kt +ruh8m7Xo2ehSSFyWRSuTSZe5tm/KXgYG +-----END PGP PRIVATE KEY BLOCK-----` + const v4Key25519 = `-----BEGIN PGP PRIVATE KEY BLOCK----- xUkEZB3qzRto01j2k2pwN5ux9w70stPinAdXULLr20CRW7U7h2GSeACch0M+ From 4147fe2a40541396edd8388f0ab5230bb2c5b3f5 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Thu, 13 Apr 2023 11:02:30 +0200 Subject: [PATCH 020/116] Add support for v6 signatures in cleartext messages This commit, updates the cleartext signature api to support v6 signatures. Cleartext messages with v6 signatures require a new header, which specifies the used hash function in combination with the used salt. It now checks that the salt and hash matches with the contained v6 signature. --- openpgp/clearsign/clearsign.go | 123 ++++++++++++++++++++++++---- openpgp/clearsign/clearsign_test.go | 63 +++++++++++++- openpgp/read.go | 42 +++++++--- 3 files changed, 198 insertions(+), 30 deletions(-) diff --git a/openpgp/clearsign/clearsign.go b/openpgp/clearsign/clearsign.go index 8f70b3730..c5cefbee3 100644 --- a/openpgp/clearsign/clearsign.go +++ b/openpgp/clearsign/clearsign.go @@ -13,6 +13,7 @@ import ( "bufio" "bytes" "crypto" + "encoding/base64" "fmt" "hash" "io" @@ -29,6 +30,7 @@ import ( // A Block represents a clearsigned message. A signature on a Block can // be checked by calling Block.VerifySignature. type Block struct { + V6 bool // Indicates if a v6 header is present Headers textproto.MIMEHeader // Optional unverified Hash headers Plaintext []byte // The original message text Bytes []byte // The signed message @@ -127,10 +129,22 @@ func Decode(data []byte) (b *Block, rest []byte) { key, val := string(line[0:i]), string(line[i+1:]) key = strings.TrimSpace(key) - if key != "Hash" { + if key != "Hash" && key != "SaltedHash" { return nil, data } - for _, val := range strings.Split(val, ",") { + if key == "Hash" { + if b.V6 { + return nil, data + } + for _, val := range strings.Split(val, ",") { + val = strings.TrimSpace(val) + b.Headers.Add(key, val) + } + } else if key == "SaltedHash" { + if !b.V6 && len(b.Headers) > 0 { + return nil, data + } + b.V6 = true val = strings.TrimSpace(val) b.Headers.Add(key, val) } @@ -203,6 +217,7 @@ type dashEscaper struct { hashers []hash.Hash // one per key in privateKeys hashType crypto.Hash toHash io.Writer // writes to all the hashes in hashers + salts [][]byte // salts for the signatures if v6 atBeginningOfLine bool isFirstLine bool @@ -288,6 +303,7 @@ func (d *dashEscaper) Write(data []byte) (n int, err error) { } func (d *dashEscaper) Close() (err error) { + v6 := len(d.salts) != 0 if !d.atBeginningOfLine { if err = d.buffered.WriteByte(lf); err != nil { return @@ -300,8 +316,13 @@ func (d *dashEscaper) Close() (err error) { } t := d.config.Now() + sigVersion := 4 + if v6 { + sigVersion = 6 + } for i, k := range d.privateKeys { sig := new(packet.Signature) + sig.Version = sigVersion sig.SigType = packet.SigTypeText sig.PubKeyAlgo = k.PubKeyAlgo sig.Hash = d.hashType @@ -311,7 +332,11 @@ func (d *dashEscaper) Close() (err error) { sig.Notations = d.config.Notations() sigLifetimeSecs := d.config.SigLifetime() sig.SigLifetimeSecs = &sigLifetimeSecs - + if v6 { + if err = sig.PrepareSignWithSalt(d.salts[i]); err != nil { + return + } + } if err = sig.Sign(d.hashers[i], k, d.config); err != nil { return } @@ -339,11 +364,19 @@ func Encode(w io.Writer, privateKey *packet.PrivateKey, config *packet.Config) ( // private keys indicated and write it to w. If config is nil, sensible defaults // are used. func EncodeMulti(w io.Writer, privateKeys []*packet.PrivateKey, config *packet.Config) (plaintext io.WriteCloser, err error) { + keyVersion := -1 for _, k := range privateKeys { if k.Encrypted { return nil, errors.InvalidArgumentError(fmt.Sprintf("signing key %s is encrypted", k.KeyIdString())) } + if keyVersion == -1 { + keyVersion = k.Version + } + if keyVersion != k.Version { + return nil, errors.InvalidArgumentError("the signing keys must have the same version") + } } + v6 := keyVersion == 6 hashType := config.Hash() name := nameOfHash(hashType) @@ -356,8 +389,21 @@ func EncodeMulti(w io.Writer, privateKeys []*packet.PrivateKey, config *packet.C } var hashers []hash.Hash var ws []io.Writer + var salts [][]byte for range privateKeys { h := hashType.New() + if v6 { + // generate salts + var salt []byte + salt, err = packet.SignatureSaltForHash(hashType, config.Random()) + if err != nil { + return + } + if _, err = h.Write(salt); err != nil { + return + } + salts = append(salts, salt) + } hashers = append(hashers, h) ws = append(ws, h) } @@ -371,11 +417,30 @@ func EncodeMulti(w io.Writer, privateKeys []*packet.PrivateKey, config *packet.C if err = buffered.WriteByte(lf); err != nil { return } - if _, err = buffered.WriteString("Hash: "); err != nil { - return - } - if _, err = buffered.WriteString(name); err != nil { - return + if v6 { + for index, salt := range salts { + if _, err = buffered.WriteString("SaltedHash: "); err != nil { + return + } + if _, err = buffered.WriteString(fmt.Sprintf("%s:", name)); err != nil { + return + } + if _, err = buffered.WriteString(base64.RawStdEncoding.EncodeToString(salt)); err != nil { + return + } + if index != len(salts) - 1 { + if err = buffered.WriteByte(lf); err != nil { + return + } + } + } + } else { + if _, err = buffered.WriteString("Hash: "); err != nil { + return + } + if _, err = buffered.WriteString(name); err != nil { + return + } } if err = buffered.WriteByte(lf); err != nil { return @@ -389,6 +454,7 @@ func EncodeMulti(w io.Writer, privateKeys []*packet.PrivateKey, config *packet.C hashers: hashers, hashType: hashType, toHash: toHash, + salts: salts, atBeginningOfLine: true, isFirstLine: true, @@ -406,19 +472,35 @@ func EncodeMulti(w io.Writer, privateKeys []*packet.PrivateKey, config *packet.C // hash algorithm in the header matches the hash algorithm in the signature. func (b *Block) VerifySignature(keyring openpgp.KeyRing, config *packet.Config) (signer *openpgp.Entity, err error) { var expectedHashes []crypto.Hash - for _, v := range b.Headers { - for _, name := range v { - expectedHash := nameToHash(name) + expectedSalts := [][]byte{} + if b.V6 { + for _, value := range b.Headers["Saltedhash"] { + expectedHash, expectedSalt := getAlgorithmAndSalt(value) if uint8(expectedHash) == 0 { return nil, errors.StructuralError("unknown hash algorithm in cleartext message headers") - } + } expectedHashes = append(expectedHashes, expectedHash) + expectedSalts = append(expectedSalts, expectedSalt) + } + } else { + expectedSalts = nil + for _, v := range b.Headers { + for _, name := range v { + expectedHash := nameToHash(name) + if uint8(expectedHash) == 0 { + return nil, errors.StructuralError("unknown hash algorithm in cleartext message headers") + } + expectedHashes = append(expectedHashes, expectedHash) + } } } + // If neither a "Hash" nor a "SaltedHash" Armor Header is given, or the message + // digest algorithms (and salts) used in the signatures do not match the information in the headers, + // the signature MUST be considered invalid. if len(expectedHashes) == 0 { - expectedHashes = append(expectedHashes, crypto.MD5) + return nil, errors.StructuralError("no Hash or SaltedHash header present in message") } - return openpgp.CheckDetachedSignatureAndHash(keyring, bytes.NewBuffer(b.Bytes), b.ArmoredSignature.Body, expectedHashes, config) + return openpgp.CheckDetachedSignatureAndHash(keyring, bytes.NewBuffer(b.Bytes), b.ArmoredSignature.Body, expectedHashes, expectedSalts, config) } // nameOfHash returns the OpenPGP name for the given hash, or the empty string @@ -462,3 +544,16 @@ func nameToHash(h string) crypto.Hash { } return crypto.Hash(0) } + +func getAlgorithmAndSalt(value string) (crypto.Hash, []byte) { + params := strings.Split(value, ":") + if len(params) != 2 { + return crypto.Hash(0), nil + } + algo := nameToHash(strings.TrimSpace(params[0])) + salt, err := base64.RawStdEncoding.DecodeString(strings.TrimSpace(params[1])) + if err != nil { + return crypto.Hash(0), nil + } + return algo, salt +} diff --git a/openpgp/clearsign/clearsign_test.go b/openpgp/clearsign/clearsign_test.go index ffd5660f3..f93ec206e 100644 --- a/openpgp/clearsign/clearsign_test.go +++ b/openpgp/clearsign/clearsign_test.go @@ -7,10 +7,11 @@ package clearsign import ( "bytes" "fmt" - "github.com/ProtonMail/go-crypto/openpgp" - "github.com/ProtonMail/go-crypto/openpgp/packet" "io" "testing" + + "github.com/ProtonMail/go-crypto/openpgp" + "github.com/ProtonMail/go-crypto/openpgp/packet" ) func testParse(t *testing.T, input []byte, expected, expectedPlaintext string) { @@ -85,6 +86,18 @@ var signingTests = []struct { {"a\n \n \nb\n", "a\r\n\r\n\r\nb", "a\n\n\nb\n"}, } +func TestVerifyV6(t *testing.T) { + keyring, err := openpgp.ReadArmoredKeyRing(bytes.NewBufferString(clearSignV6PublicKey)) + if err != nil { + t.Errorf("failed to parse public key: %s", err) + } + b, _ := Decode([]byte(clearSignV6)) + _, err = b.VerifySignature(keyring, nil) + if err != nil { + t.Errorf("failed to verify signature: %s", err) + } +} + func TestSigning(t *testing.T) { keyring, err := openpgp.ReadArmoredKeyRing(bytes.NewBufferString(signingKey)) if err != nil { @@ -140,13 +153,15 @@ func (qr *quickRand) Read(p []byte) (int, error) { return len(p), nil } -func TestMultiSign(t *testing.T) { + +func testMultiSign(t *testing.T, v6 bool) { if testing.Short() { t.Skip("skipping long test in -short mode") } zero := quickRand(0) - config := packet.Config{Rand: &zero} + config := packet.Config{Rand: &zero, V6Keys: v6} + for nKeys := 0; nKeys < 4; nKeys++ { nextTest: @@ -199,6 +214,14 @@ func TestMultiSign(t *testing.T) { } } +func TestMultiSignV4(t *testing.T) { + testMultiSign(t, false) +} + +func TestMultiSignV6(t *testing.T) { + testMultiSign(t, true) +} + func TestDecodeMissingCRC(t *testing.T) { block, rest := Decode(clearsignInput3) if block == nil { @@ -403,3 +426,35 @@ VrM0m72/jnpKo04= =zNCn -----END PGP PRIVATE KEY BLOCK----- ` + +// https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-08.html#name-sample-v6-certificate-trans +const clearSignV6PublicKey = `-----BEGIN PGP PUBLIC KEY BLOCK----- + +xioGY4d/4xsAAAAg+U2nu0jWCmHlZ3BqZYfQMxmZu52JGggkLq2EVD34laPCsQYf +GwoAAABCBYJjh3/jAwsJBwUVCg4IDAIWAAKbAwIeCSIhBssYbE8GCaaX5NUt+mxy +KwwfHifBilZwj2Ul7Ce62azJBScJAgcCAAAAAK0oIBA+LX0ifsDm185Ecds2v8lw +gyU2kCcUmKfvBXbAf6rhRYWzuQOwEn7E/aLwIwRaLsdry0+VcallHhSu4RN6HWaE +QsiPlR4zxP/TP7mhfVEe7XWPxtnMUMtf15OyA51YBM4qBmOHf+MZAAAAIIaTJINn ++eUBXbki+PSAld2nhJh/LVmFsS+60WyvXkQ1wpsGGBsKAAAALAWCY4d/4wKbDCIh +BssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce62azJAAAAAAQBIKbpGG2dWTX8 +j+VjFM21J0hqWlEg+bdiojWnKfA5AQpWUWtnNwDEM0g12vYxoWM8Y81W+bHBw805 +I8kWVkXU6vFOi+HWvv/ira7ofJu16NnoUkhclkUrk0mXubZvyl4GBg== +-----END PGP PUBLIC KEY BLOCK-----` + +// https://gitlab.com/openpgp-wg/rfc4880bis/-/merge_requests/275 +const clearSignV6 = `-----BEGIN PGP SIGNED MESSAGE----- +SaltedHash: SHA512:dklfUCGIkPf14u48GCJRT3BQD1UdhuXJIeQE40pT+6w + +What we need from the grocery store: + +- - tofu +- - vegetables +- - noodles + +-----BEGIN PGP SIGNATURE----- + +wpgGARsKAAAAKQWCY5ijYyIhBssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce6 +2azJAAAAAGk2IHZJX1AhiJD39eLuPBgiUU9wUA9VHYblySHkBONKU/usJ9BvuAqo +/FvLFuGWMbKAdA+epq7V4HOtAPlBWmU8QOd6aud+aSunHQaaEJ+iTFjP2OMW0KBr +NK2ay45cX1IVAQ== +-----END PGP SIGNATURE-----` diff --git a/openpgp/read.go b/openpgp/read.go index d21092180..432843d7a 100644 --- a/openpgp/read.go +++ b/openpgp/read.go @@ -6,6 +6,7 @@ package openpgp // import "github.com/ProtonMail/go-crypto/openpgp" import ( + "bytes" "crypto" _ "crypto/sha256" _ "crypto/sha512" @@ -449,13 +450,13 @@ func (scr *signatureCheckReader) Read(buf []byte) (int, error) { // If the signer isn't known, ErrUnknownIssuer is returned. func VerifyDetachedSignature(keyring KeyRing, signed, signature io.Reader, config *packet.Config) (sig *packet.Signature, signer *Entity, err error) { var expectedHashes []crypto.Hash - return verifyDetachedSignature(keyring, signed, signature, expectedHashes, config) + return verifyDetachedSignature(keyring, signed, signature, expectedHashes, nil, config) } // VerifyDetachedSignatureAndHash performs the same actions as // VerifyDetachedSignature and checks that the expected hash functions were used. func VerifyDetachedSignatureAndHash(keyring KeyRing, signed, signature io.Reader, expectedHashes []crypto.Hash, config *packet.Config) (sig *packet.Signature, signer *Entity, err error) { - return verifyDetachedSignature(keyring, signed, signature, expectedHashes, config) + return verifyDetachedSignature(keyring, signed, signature, expectedHashes, nil, config) } // CheckDetachedSignature takes a signed file and a detached signature and @@ -464,17 +465,19 @@ func VerifyDetachedSignatureAndHash(keyring KeyRing, signed, signature io.Reader // ErrUnknownIssuer is returned. func CheckDetachedSignature(keyring KeyRing, signed, signature io.Reader, config *packet.Config) (signer *Entity, err error) { var expectedHashes []crypto.Hash - return CheckDetachedSignatureAndHash(keyring, signed, signature, expectedHashes, config) + return CheckDetachedSignatureAndHash(keyring, signed, signature, expectedHashes, nil, config) } // CheckDetachedSignatureAndHash performs the same actions as // CheckDetachedSignature and checks that the expected hash functions were used. -func CheckDetachedSignatureAndHash(keyring KeyRing, signed, signature io.Reader, expectedHashes []crypto.Hash, config *packet.Config) (signer *Entity, err error) { - _, signer, err = verifyDetachedSignature(keyring, signed, signature, expectedHashes, config) +// If expectedSalts is not nil, it additionally checks that the signature is version 6 and contains +// the expected salt for the given hash. +func CheckDetachedSignatureAndHash(keyring KeyRing, signed, signature io.Reader, expectedHashes []crypto.Hash, expectedSalts [][]byte, config *packet.Config) (signer *Entity, err error) { + _, signer, err = verifyDetachedSignature(keyring, signed, signature, expectedHashes, expectedSalts, config) return } -func verifyDetachedSignature(keyring KeyRing, signed, signature io.Reader, expectedHashes []crypto.Hash, config *packet.Config) (sig *packet.Signature, signer *Entity, err error) { +func verifyDetachedSignature(keyring KeyRing, signed, signature io.Reader, expectedHashes []crypto.Hash, expectedSalts [][]byte, config *packet.Config) (sig *packet.Signature, signer *Entity, err error) { var issuerKeyId uint64 var hashFunc crypto.Hash var sigType packet.SignatureType @@ -503,13 +506,28 @@ func verifyDetachedSignature(keyring KeyRing, signed, signature io.Reader, expec issuerKeyId = *sig.IssuerKeyId hashFunc = sig.Hash sigType = sig.SigType - - for i, expectedHash := range expectedHashes { - if hashFunc == expectedHash { - break + if expectedSalts != nil { + // signature should be v6 + if sig.Version != 6 { + return nil, nil, errors.StructuralError("expected version 6 signature") } - if i+1 == expectedHashesLen { - return nil, nil, errors.StructuralError("hash algorithm mismatch with cleartext message headers") + for i, expectedHash := range expectedHashes { + expectedSalt := expectedSalts[i] + if hashFunc == expectedHash && bytes.Equal(sig.Salt(), expectedSalt){ + break + } + if i+1 == expectedHashesLen { + return nil, nil, errors.StructuralError("hash algorithm and salt mismatch with cleartext message headers") + } + } + } else { + for i, expectedHash := range expectedHashes { + if hashFunc == expectedHash { + break + } + if i+1 == expectedHashesLen { + return nil, nil, errors.StructuralError("hash algorithm mismatch with cleartext message headers") + } } } From c62d9737d62bb282e3ccc436d125821e9981ba7d Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Tue, 18 Apr 2023 09:14:30 +0200 Subject: [PATCH 021/116] Implement suggested formatting changes by twiss --- openpgp/clearsign/clearsign.go | 4 ++-- openpgp/keys.go | 2 +- openpgp/packet/private_key.go | 12 ++++++------ openpgp/packet/public_key.go | 2 +- openpgp/packet/signature.go | 20 +++++++++++--------- openpgp/write.go | 6 +++--- 6 files changed, 24 insertions(+), 22 deletions(-) diff --git a/openpgp/clearsign/clearsign.go b/openpgp/clearsign/clearsign.go index c5cefbee3..bd6372368 100644 --- a/openpgp/clearsign/clearsign.go +++ b/openpgp/clearsign/clearsign.go @@ -30,7 +30,7 @@ import ( // A Block represents a clearsigned message. A signature on a Block can // be checked by calling Block.VerifySignature. type Block struct { - V6 bool // Indicates if a v6 header is present + V6 bool // Indicates if a v6 header is present Headers textproto.MIMEHeader // Optional unverified Hash headers Plaintext []byte // The original message text Bytes []byte // The signed message @@ -478,7 +478,7 @@ func (b *Block) VerifySignature(keyring openpgp.KeyRing, config *packet.Config) expectedHash, expectedSalt := getAlgorithmAndSalt(value) if uint8(expectedHash) == 0 { return nil, errors.StructuralError("unknown hash algorithm in cleartext message headers") - } + } expectedHashes = append(expectedHashes, expectedHash) expectedSalts = append(expectedSalts, expectedSalt) } diff --git a/openpgp/keys.go b/openpgp/keys.go index 9360b569f..6577c2c94 100644 --- a/openpgp/keys.go +++ b/openpgp/keys.go @@ -129,7 +129,7 @@ func (e *Entity) EncryptionKey(now time.Time) (Key, bool) { if primarySelfSignature == nil || // no self-signature found e.PrimaryKey.KeyExpired(primarySelfSignature, now) || // primary key has expired e.Revoked(now) || // primary key has been revoked - (primaryIdentity != nil && primaryIdentity.Revoked(now)) { // user id has been revoked + (primaryIdentity != nil && primaryIdentity.Revoked(now)) { // user ID has been revoked return Key{}, false } diff --git a/openpgp/packet/private_key.go b/openpgp/packet/private_key.go index 2e8cdbf9e..a21c54db2 100644 --- a/openpgp/packet/private_key.go +++ b/openpgp/packet/private_key.go @@ -48,7 +48,7 @@ type PrivateKey struct { iv []byte // Type of encryption of the S2K packet - // Allowed values are 0 (Not encrypted), 253 AEAD, 254 (SHA1), or + // Allowed values are 0 (Not encrypted), 253 (AEAD), 254 (SHA1), or // 255 (2-byte checksum) s2kType S2KType // Full parameters of the S2K packet @@ -272,10 +272,10 @@ func (pk *PrivateKey) parse(r io.Reader) (err error) { if pk.Encrypted { var ivSize int - // special case for version 5: - // an Initial Vector (IV) of the same length as the cipher's block size. - // If string-to-key usage octet was 253 the IV is used as the nonce - if !v5 && pk.s2kType == S2KAEAD { + // If the S2K usage octet was 253, the IV is of the size expected by the AEAD mode, + // unless it's a version 5 key, in which case it's the size of the symmetric cipher's block size. + // For all other S2K modes, it's always the block size. + if !v5 && pk.s2kType == S2KAEAD { ivSize = pk.aead.IvLength() } else { ivSize = pk.cipher.blockSize() @@ -289,7 +289,7 @@ func (pk *PrivateKey) parse(r io.Reader) (err error) { if err != nil { return } - if v5 && pk.s2kType == S2KAEAD { + if v5 && pk.s2kType == S2KAEAD { pk.iv = pk.iv[:pk.aead.IvLength()] } } diff --git a/openpgp/packet/public_key.go b/openpgp/packet/public_key.go index 6488d3dbe..367ae3e88 100644 --- a/openpgp/packet/public_key.go +++ b/openpgp/packet/public_key.go @@ -256,7 +256,7 @@ func (pk *PublicKey) parse(r io.Reader) (err error) { } pk.CreationTime = time.Unix(int64(uint32(buf[1])<<24|uint32(buf[2])<<16|uint32(buf[3])<<8|uint32(buf[4])), 0) pk.PubKeyAlgo = PublicKeyAlgorithm(buf[5]) - // Ignore four-ocet + // Ignore four-ocet length switch pk.PubKeyAlgo { case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly: err = pk.parseRSA(r) diff --git a/openpgp/packet/signature.go b/openpgp/packet/signature.go index 40568867f..2775de836 100644 --- a/openpgp/packet/signature.go +++ b/openpgp/packet/signature.go @@ -169,11 +169,12 @@ func (sig *Signature) parse(r io.Reader) (err error) { var hashedSubpacketsLength int if sig.Version == 6 { - // For a v6 signature, a four-octet are required for the length. - hashedSubpacketsLength = int(buf[3])<<24 | - int(buf[4])<<16 | - int(buf[5])<<8 | - int(buf[6]) + // For a v6 signature, a four-octet length is used. + hashedSubpacketsLength = + int(buf[3])<<24 | + int(buf[4])<<16 | + int(buf[5])<<8 | + int(buf[6]) } else { hashedSubpacketsLength = int(buf[3])<<8 | int(buf[4]) } @@ -1048,10 +1049,11 @@ func (sig *Signature) serializeBody(w io.Writer) (err error) { var fields []byte if sig.Version == 6 { // v6 signatures use 4 ocets for length - hashedSubpacketsLen := uint32(uint32(sig.HashSuffix[4])<<24) | - uint32(uint32(sig.HashSuffix[5])<<16) | - uint32(uint32(sig.HashSuffix[6])<<8) | - uint32(sig.HashSuffix[7]) + hashedSubpacketsLen := + uint32(uint32(sig.HashSuffix[4])<<24) | + uint32(uint32(sig.HashSuffix[5])<<16) | + uint32(uint32(sig.HashSuffix[6])<<8) | + uint32(sig.HashSuffix[7]) fields = sig.HashSuffix[:8+hashedSubpacketsLen] } else { hashedSubpacketsLen := uint16(uint16(sig.HashSuffix[4])<<8) | diff --git a/openpgp/write.go b/openpgp/write.go index 89e67ea24..c65074583 100644 --- a/openpgp/write.go +++ b/openpgp/write.go @@ -286,7 +286,7 @@ func writeAndSign(payload io.WriteCloser, candidateHashes []uint8, signed *Entit sigVersion = signer.Version } ops := &packet.OnePassSignature{ - Version: sigVersion, + Version: sigVersion, SigType: sigType, Hash: hash, PubKeyAlgo: signer.PubKeyAlgo, @@ -295,7 +295,7 @@ func writeAndSign(payload io.WriteCloser, candidateHashes []uint8, signed *Entit } if sigVersion == 6 { ops.KeyFingerprint = signer.Fingerprint - salt, err = packet.SignatureSaltForHash(hash, config.Random()) + salt, err = packet.SignatureSaltForHash(hash, config.Random()) if err != nil { return nil, err } @@ -512,7 +512,7 @@ type signatureWriter struct { hashType crypto.Hash wrappedHash hash.Hash h hash.Hash - salt []byte // v6 only + salt []byte // v6 only signer *packet.PrivateKey sigType packet.SignatureType config *packet.Config From 051084867ce9b03a0dfbc20ac8acba33d9373fa6 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Tue, 18 Apr 2023 11:51:10 +0200 Subject: [PATCH 022/116] Implements clearsign message for v6 correctly This commit ensures that cleatext messages with v6 signatures contain a salted hash header with the matching hash and salt. Allows for both v4 and v6 signatures in the same message with mixed headers. --- openpgp/clearsign/clearsign.go | 105 +++++++++++++--------------- openpgp/clearsign/clearsign_test.go | 29 ++++++-- openpgp/packet/signature.go | 7 ++ openpgp/read.go | 70 +++++++++++-------- 4 files changed, 119 insertions(+), 92 deletions(-) diff --git a/openpgp/clearsign/clearsign.go b/openpgp/clearsign/clearsign.go index bd6372368..b25aaae00 100644 --- a/openpgp/clearsign/clearsign.go +++ b/openpgp/clearsign/clearsign.go @@ -30,7 +30,6 @@ import ( // A Block represents a clearsigned message. A signature on a Block can // be checked by calling Block.VerifySignature. type Block struct { - V6 bool // Indicates if a v6 header is present Headers textproto.MIMEHeader // Optional unverified Hash headers Plaintext []byte // The original message text Bytes []byte // The signed message @@ -54,6 +53,10 @@ var end = []byte("\n-----END PGP SIGNATURE-----") var crlf = []byte("\r\n") var lf = byte('\n') +const saltedHashHeader string = "SaltedHash" +const saltedHashKey string = "Saltedhash" +const hashHeader string = "Hash" + // getLine returns the first \r\n or \n delineated line from the given byte // array. The line does not include the \r\n or \n. The remainder of the byte // array (also not including the new line bytes) is also returned and this will @@ -129,24 +132,16 @@ func Decode(data []byte) (b *Block, rest []byte) { key, val := string(line[0:i]), string(line[i+1:]) key = strings.TrimSpace(key) - if key != "Hash" && key != "SaltedHash" { - return nil, data - } - if key == "Hash" { - if b.V6 { - return nil, data - } + if key == hashHeader { for _, val := range strings.Split(val, ",") { val = strings.TrimSpace(val) b.Headers.Add(key, val) } - } else if key == "SaltedHash" { - if !b.V6 && len(b.Headers) > 0 { - return nil, data - } - b.V6 = true + } else if key == saltedHashHeader { val = strings.TrimSpace(val) - b.Headers.Add(key, val) + b.Headers.Add(saltedHashKey, val) + } else { + return nil, data } } @@ -217,7 +212,7 @@ type dashEscaper struct { hashers []hash.Hash // one per key in privateKeys hashType crypto.Hash toHash io.Writer // writes to all the hashes in hashers - salts [][]byte // salts for the signatures if v6 + salts [][]byte // salts for the signatures if v6 atBeginningOfLine bool isFirstLine bool @@ -397,10 +392,10 @@ func EncodeMulti(w io.Writer, privateKeys []*packet.PrivateKey, config *packet.C var salt []byte salt, err = packet.SignatureSaltForHash(hashType, config.Random()) if err != nil { - return + return } if _, err = h.Write(salt); err != nil { - return + return } salts = append(salts, salt) } @@ -419,26 +414,18 @@ func EncodeMulti(w io.Writer, privateKeys []*packet.PrivateKey, config *packet.C } if v6 { for index, salt := range salts { - if _, err = buffered.WriteString("SaltedHash: "); err != nil { - return - } - if _, err = buffered.WriteString(fmt.Sprintf("%s:", name)); err != nil { - return - } - if _, err = buffered.WriteString(base64.RawStdEncoding.EncodeToString(salt)); err != nil { + encodedSalt := base64.RawStdEncoding.EncodeToString(salt) + if _, err = buffered.WriteString(fmt.Sprintf("%s: %s:%s", saltedHashHeader, name, encodedSalt)); err != nil { return } - if index != len(salts) - 1 { + if index != len(salts)-1 { if err = buffered.WriteByte(lf); err != nil { return } } } } else { - if _, err = buffered.WriteString("Hash: "); err != nil { - return - } - if _, err = buffered.WriteString(name); err != nil { + if _, err = buffered.WriteString(fmt.Sprintf("%s: %s", hashHeader, name)); err != nil { return } } @@ -472,35 +459,33 @@ func EncodeMulti(w io.Writer, privateKeys []*packet.PrivateKey, config *packet.C // hash algorithm in the header matches the hash algorithm in the signature. func (b *Block) VerifySignature(keyring openpgp.KeyRing, config *packet.Config) (signer *openpgp.Entity, err error) { var expectedHashes []crypto.Hash - expectedSalts := [][]byte{} - if b.V6 { - for _, value := range b.Headers["Saltedhash"] { - expectedHash, expectedSalt := getAlgorithmAndSalt(value) - if uint8(expectedHash) == 0 { - return nil, errors.StructuralError("unknown hash algorithm in cleartext message headers") - } - expectedHashes = append(expectedHashes, expectedHash) - expectedSalts = append(expectedSalts, expectedSalt) + var expectedSaltedHashes []*packet.SaltedHashSpecifier + + // Process salted hash headers (v6) + for _, value := range b.Headers[saltedHashKey] { + var expectedSaltedHash *packet.SaltedHashSpecifier + expectedSaltedHash, err = getAlgorithmAndSalt(value) + if err != nil { + return } - } else { - expectedSalts = nil - for _, v := range b.Headers { - for _, name := range v { - expectedHash := nameToHash(name) - if uint8(expectedHash) == 0 { - return nil, errors.StructuralError("unknown hash algorithm in cleartext message headers") - } - expectedHashes = append(expectedHashes, expectedHash) - } + expectedSaltedHashes = append(expectedSaltedHashes, expectedSaltedHash) + } + // Process hash headers + for _, name := range b.Headers[hashHeader] { + expectedHash := nameToHash(name) + if uint8(expectedHash) == 0 { + return nil, errors.StructuralError("unknown hash algorithm in cleartext message headers") } + expectedHashes = append(expectedHashes, expectedHash) } - // If neither a "Hash" nor a "SaltedHash" Armor Header is given, or the message - // digest algorithms (and salts) used in the signatures do not match the information in the headers, + + // If neither a "Hash" nor a "SaltedHash" Armor Header is given, or the message + // digest algorithms (and salts) used in the signatures do not match the information in the headers, // the signature MUST be considered invalid. - if len(expectedHashes) == 0 { - return nil, errors.StructuralError("no Hash or SaltedHash header present in message") + if len(expectedHashes) == 0 && len(expectedSaltedHashes) == 0 { + return nil, errors.StructuralError("signature is invalid: no hash or salted hash header present in message") } - return openpgp.CheckDetachedSignatureAndHash(keyring, bytes.NewBuffer(b.Bytes), b.ArmoredSignature.Body, expectedHashes, expectedSalts, config) + return openpgp.CheckDetachedSignatureAndSaltedHash(keyring, bytes.NewBuffer(b.Bytes), b.ArmoredSignature.Body, expectedHashes, expectedSaltedHashes, config) } // nameOfHash returns the OpenPGP name for the given hash, or the empty string @@ -545,15 +530,21 @@ func nameToHash(h string) crypto.Hash { return crypto.Hash(0) } -func getAlgorithmAndSalt(value string) (crypto.Hash, []byte) { +func getAlgorithmAndSalt(value string) (*packet.SaltedHashSpecifier, error) { params := strings.Split(value, ":") if len(params) != 2 { - return crypto.Hash(0), nil + return nil, errors.StructuralError(fmt.Sprintf("salted hash cleartext message header value has the wrong format: %s", value)) } algo := nameToHash(strings.TrimSpace(params[0])) + if uint8(algo) == 0 { + return nil, errors.StructuralError("unknown hash algorithm in cleartext message headers") + } salt, err := base64.RawStdEncoding.DecodeString(strings.TrimSpace(params[1])) if err != nil { - return crypto.Hash(0), nil + return nil, errors.StructuralError(fmt.Sprintf("salted hash cleartext message header value has the wrong format: %s", value)) } - return algo, salt + return &packet.SaltedHashSpecifier{ + Hash: algo, + Salt: salt, + }, nil } diff --git a/openpgp/clearsign/clearsign_test.go b/openpgp/clearsign/clearsign_test.go index f93ec206e..02e71a9bc 100644 --- a/openpgp/clearsign/clearsign_test.go +++ b/openpgp/clearsign/clearsign_test.go @@ -96,6 +96,11 @@ func TestVerifyV6(t *testing.T) { if err != nil { t.Errorf("failed to verify signature: %s", err) } + b, _ = Decode([]byte(clearSignV6Error)) + _, err = b.VerifySignature(keyring, nil) + if err == nil { + t.Error("signature with no salted header should be invalid") + } } func TestSigning(t *testing.T) { @@ -153,7 +158,6 @@ func (qr *quickRand) Read(p []byte) (int, error) { return len(p), nil } - func testMultiSign(t *testing.T, v6 bool) { if testing.Short() { t.Skip("skipping long test in -short mode") @@ -161,7 +165,6 @@ func testMultiSign(t *testing.T, v6 bool) { zero := quickRand(0) config := packet.Config{Rand: &zero, V6Keys: v6} - for nKeys := 0; nKeys < 4; nKeys++ { nextTest: @@ -439,7 +442,7 @@ QsiPlR4zxP/TP7mhfVEe7XWPxtnMUMtf15OyA51YBM4qBmOHf+MZAAAAIIaTJINn BssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce62azJAAAAAAQBIKbpGG2dWTX8 j+VjFM21J0hqWlEg+bdiojWnKfA5AQpWUWtnNwDEM0g12vYxoWM8Y81W+bHBw805 I8kWVkXU6vFOi+HWvv/ira7ofJu16NnoUkhclkUrk0mXubZvyl4GBg== ------END PGP PUBLIC KEY BLOCK-----` +-----END PGP PUBLIC KEY BLOCK-----` // https://gitlab.com/openpgp-wg/rfc4880bis/-/merge_requests/275 const clearSignV6 = `-----BEGIN PGP SIGNED MESSAGE----- @@ -457,4 +460,22 @@ wpgGARsKAAAAKQWCY5ijYyIhBssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce6 2azJAAAAAGk2IHZJX1AhiJD39eLuPBgiUU9wUA9VHYblySHkBONKU/usJ9BvuAqo /FvLFuGWMbKAdA+epq7V4HOtAPlBWmU8QOd6aud+aSunHQaaEJ+iTFjP2OMW0KBr NK2ay45cX1IVAQ== ------END PGP SIGNATURE-----` +-----END PGP SIGNATURE-----` + +// https://gitlab.com/openpgp-wg/rfc4880bis/-/merge_requests/275 +const clearSignV6Error = `-----BEGIN PGP SIGNED MESSAGE----- +Hash: SHA512 + +What we need from the grocery store: + +- - tofu +- - vegetables +- - noodles + +-----BEGIN PGP SIGNATURE----- + +wpgGARsKAAAAKQWCY5ijYyIhBssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce6 +2azJAAAAAGk2IHZJX1AhiJD39eLuPBgiUU9wUA9VHYblySHkBONKU/usJ9BvuAqo +/FvLFuGWMbKAdA+epq7V4HOtAPlBWmU8QOd6aud+aSunHQaaEJ+iTFjP2OMW0KBr +NK2ay45cX1IVAQ== +-----END PGP SIGNATURE-----` diff --git a/openpgp/packet/signature.go b/openpgp/packet/signature.go index 2775de836..be93fd7d4 100644 --- a/openpgp/packet/signature.go +++ b/openpgp/packet/signature.go @@ -119,6 +119,13 @@ type Signature struct { outSubpackets []outputSubpacket } +// SaltedHashSpecifier specifies that the given salt and hash are +// used by a v6 signature. +type SaltedHashSpecifier struct { + Hash crypto.Hash + Salt []byte +} + func (sig *Signature) Salt() []byte { if sig == nil { return nil diff --git a/openpgp/read.go b/openpgp/read.go index 432843d7a..91d35aaa5 100644 --- a/openpgp/read.go +++ b/openpgp/read.go @@ -449,14 +449,19 @@ func (scr *signatureCheckReader) Read(buf []byte) (int, error) { // if any, and a possible signature verification error. // If the signer isn't known, ErrUnknownIssuer is returned. func VerifyDetachedSignature(keyring KeyRing, signed, signature io.Reader, config *packet.Config) (sig *packet.Signature, signer *Entity, err error) { - var expectedHashes []crypto.Hash - return verifyDetachedSignature(keyring, signed, signature, expectedHashes, nil, config) + return verifyDetachedSignature(keyring, signed, signature, nil, nil, false, config) } // VerifyDetachedSignatureAndHash performs the same actions as // VerifyDetachedSignature and checks that the expected hash functions were used. func VerifyDetachedSignatureAndHash(keyring KeyRing, signed, signature io.Reader, expectedHashes []crypto.Hash, config *packet.Config) (sig *packet.Signature, signer *Entity, err error) { - return verifyDetachedSignature(keyring, signed, signature, expectedHashes, nil, config) + return verifyDetachedSignature(keyring, signed, signature, expectedHashes, nil, true, config) +} + +// VerifyDetachedSignatureAndSaltedHash performs the same actions as +// VerifyDetachedSignature and checks that the expected hash functions and salts were used. +func VerifyDetachedSignatureAndSaltedHash(keyring KeyRing, signed, signature io.Reader, expectedHashes []crypto.Hash, expectedSaltedHashes []*packet.SaltedHashSpecifier, config *packet.Config) (sig *packet.Signature, signer *Entity, err error) { + return verifyDetachedSignature(keyring, signed, signature, expectedHashes, expectedSaltedHashes, true, config) } // CheckDetachedSignature takes a signed file and a detached signature and @@ -464,27 +469,31 @@ func VerifyDetachedSignatureAndHash(keyring KeyRing, signed, signature io.Reader // signature verification error. If the signer isn't known, // ErrUnknownIssuer is returned. func CheckDetachedSignature(keyring KeyRing, signed, signature io.Reader, config *packet.Config) (signer *Entity, err error) { - var expectedHashes []crypto.Hash - return CheckDetachedSignatureAndHash(keyring, signed, signature, expectedHashes, nil, config) + _, signer, err = verifyDetachedSignature(keyring, signed, signature, nil, nil, false, config) + return +} + +// CheckDetachedSignatureAndSaltedHash performs the same actions as +// CheckDetachedSignature and checks that the expected hash functions or salted hash functions were used. +func CheckDetachedSignatureAndSaltedHash(keyring KeyRing, signed, signature io.Reader, expectedHashes []crypto.Hash, expectedSaltedHashes []*packet.SaltedHashSpecifier, config *packet.Config) (signer *Entity, err error) { + _, signer, err = verifyDetachedSignature(keyring, signed, signature, expectedHashes, expectedSaltedHashes, true, config) + return } // CheckDetachedSignatureAndHash performs the same actions as // CheckDetachedSignature and checks that the expected hash functions were used. -// If expectedSalts is not nil, it additionally checks that the signature is version 6 and contains -// the expected salt for the given hash. -func CheckDetachedSignatureAndHash(keyring KeyRing, signed, signature io.Reader, expectedHashes []crypto.Hash, expectedSalts [][]byte, config *packet.Config) (signer *Entity, err error) { - _, signer, err = verifyDetachedSignature(keyring, signed, signature, expectedHashes, expectedSalts, config) +func CheckDetachedSignatureAndHash(keyring KeyRing, signed, signature io.Reader, expectedHashes []crypto.Hash, config *packet.Config) (signer *Entity, err error) { + _, signer, err = verifyDetachedSignature(keyring, signed, signature, expectedHashes, nil, true, config) return } -func verifyDetachedSignature(keyring KeyRing, signed, signature io.Reader, expectedHashes []crypto.Hash, expectedSalts [][]byte, config *packet.Config) (sig *packet.Signature, signer *Entity, err error) { +func verifyDetachedSignature(keyring KeyRing, signed, signature io.Reader, expectedHashes []crypto.Hash, expectedSaltedHashes []*packet.SaltedHashSpecifier, checkHashes bool, config *packet.Config) (sig *packet.Signature, signer *Entity, err error) { var issuerKeyId uint64 var hashFunc crypto.Hash var sigType packet.SignatureType var keys []Key var p packet.Packet - expectedHashesLen := len(expectedHashes) packets := packet.NewReader(signature) for { p, err = packets.Next() @@ -506,31 +515,30 @@ func verifyDetachedSignature(keyring KeyRing, signed, signature io.Reader, expec issuerKeyId = *sig.IssuerKeyId hashFunc = sig.Hash sigType = sig.SigType - if expectedSalts != nil { - // signature should be v6 - if sig.Version != 6 { - return nil, nil, errors.StructuralError("expected version 6 signature") - } - for i, expectedHash := range expectedHashes { - expectedSalt := expectedSalts[i] - if hashFunc == expectedHash && bytes.Equal(sig.Salt(), expectedSalt){ - break + if checkHashes { + matchFound := false + if sig.Version == 6 { + // check for salted hashes + for _, expectedSaltedHash := range expectedSaltedHashes { + if hashFunc == expectedSaltedHash.Hash && bytes.Equal(sig.Salt(), expectedSaltedHash.Salt){ + matchFound = true + break + } } - if i+1 == expectedHashesLen { - return nil, nil, errors.StructuralError("hash algorithm and salt mismatch with cleartext message headers") + + } else { + // check for hashes + for _, expectedHash := range expectedHashes { + if hashFunc == expectedHash { + matchFound = true + break + } } } - } else { - for i, expectedHash := range expectedHashes { - if hashFunc == expectedHash { - break - } - if i+1 == expectedHashesLen { - return nil, nil, errors.StructuralError("hash algorithm mismatch with cleartext message headers") - } + if !matchFound { + return nil, nil, errors.StructuralError("hash algorithm or salt mismatch with cleartext message headers") } } - keys = keyring.KeysByIdUsage(issuerKeyId, packet.KeyFlagSign) if len(keys) > 0 { break From 79a381f5878acd23d0571976a8a66f0f4e4f640c Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Wed, 19 Apr 2023 17:33:48 +0200 Subject: [PATCH 023/116] Rename PrepareSignWithSalt to SetSalt --- openpgp/clearsign/clearsign.go | 2 +- openpgp/packet/signature.go | 11 ++++++----- openpgp/write.go | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/openpgp/clearsign/clearsign.go b/openpgp/clearsign/clearsign.go index b25aaae00..136fb2a84 100644 --- a/openpgp/clearsign/clearsign.go +++ b/openpgp/clearsign/clearsign.go @@ -328,7 +328,7 @@ func (d *dashEscaper) Close() (err error) { sigLifetimeSecs := d.config.SigLifetime() sig.SigLifetimeSecs = &sigLifetimeSecs if v6 { - if err = sig.PrepareSignWithSalt(d.salts[i]); err != nil { + if err = sig.SetSalt(d.salts[i]); err != nil { return } } diff --git a/openpgp/packet/signature.go b/openpgp/packet/signature.go index be93fd7d4..a410fe3ab 100644 --- a/openpgp/packet/signature.go +++ b/openpgp/packet/signature.go @@ -793,12 +793,13 @@ func (sig *Signature) PrepareSign(config *Config) (hash.Hash, error) { return hasher, nil } -// PrepareSignWithSalt stores a signature salt within sig for v6 signatrues. +// SetSalt sets the signature salt for v6 signatures. // Assumes salt is generated correctly and checks if length matches. // If the signature is not v6, the method ignores the salt. -// Use PrepareSign whenever possible instead of this method. +// Use PrepareSign whenever possible instead of generating and +// hashing the salt externally. // See RFC the crypto refresh Section 3.2.4. -func (sig *Signature) PrepareSignWithSalt(salt []byte) (error) { +func (sig *Signature) SetSalt(salt []byte) (error) { if sig.Version == 6 { expectedSaltLength, err := SaltLengthForHash(sig.Hash) if err != nil { @@ -813,9 +814,9 @@ func (sig *Signature) PrepareSignWithSalt(salt []byte) (error) { } // PrepareVerify must be called to create a hash object before verifying v6 signatures. -// The created hash object initially hashes the inernally stored salt. +// The created hash object initially hashes the internally stored salt. // If the signature is not v6, the method returns an empty hash object. -// See RFC the crypto refresh Section 3.2.4. +// See crypto refresh Section 3.2.4. func (sig *Signature) PrepareVerify() (hash.Hash, error) { if !sig.Hash.Available() { return nil, errors.UnsupportedError("hash function") diff --git a/openpgp/write.go b/openpgp/write.go index c65074583..c25e227fb 100644 --- a/openpgp/write.go +++ b/openpgp/write.go @@ -536,7 +536,7 @@ func (s signatureWriter) Close() error { sig.Hash = s.hashType sig.Metadata = s.metadata - if err := sig.PrepareSignWithSalt(s.salt); err != nil { + if err := sig.SetSalt(s.salt); err != nil { return err } From 7f0b978be5eb51640660d63843ef63279e21bc7d Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Thu, 20 Apr 2023 08:22:18 +0200 Subject: [PATCH 024/116] Update clearetext message creation --- openpgp/clearsign/clearsign.go | 47 +++++++++++++--------------------- 1 file changed, 18 insertions(+), 29 deletions(-) diff --git a/openpgp/clearsign/clearsign.go b/openpgp/clearsign/clearsign.go index 136fb2a84..69d2eb0c6 100644 --- a/openpgp/clearsign/clearsign.go +++ b/openpgp/clearsign/clearsign.go @@ -298,7 +298,6 @@ func (d *dashEscaper) Write(data []byte) (n int, err error) { } func (d *dashEscaper) Close() (err error) { - v6 := len(d.salts) != 0 if !d.atBeginningOfLine { if err = d.buffered.WriteByte(lf); err != nil { return @@ -311,13 +310,10 @@ func (d *dashEscaper) Close() (err error) { } t := d.config.Now() - sigVersion := 4 - if v6 { - sigVersion = 6 - } + indexSalt := 0 for i, k := range d.privateKeys { sig := new(packet.Signature) - sig.Version = sigVersion + sig.Version = k.Version sig.SigType = packet.SigTypeText sig.PubKeyAlgo = k.PubKeyAlgo sig.Hash = d.hashType @@ -327,10 +323,11 @@ func (d *dashEscaper) Close() (err error) { sig.Notations = d.config.Notations() sigLifetimeSecs := d.config.SigLifetime() sig.SigLifetimeSecs = &sigLifetimeSecs - if v6 { - if err = sig.SetSalt(d.salts[i]); err != nil { + if k.Version == 6 { + if err = sig.SetSalt(d.salts[indexSalt]); err != nil { return } + indexSalt++ } if err = sig.Sign(d.hashers[i], k, d.config); err != nil { return @@ -359,19 +356,11 @@ func Encode(w io.Writer, privateKey *packet.PrivateKey, config *packet.Config) ( // private keys indicated and write it to w. If config is nil, sensible defaults // are used. func EncodeMulti(w io.Writer, privateKeys []*packet.PrivateKey, config *packet.Config) (plaintext io.WriteCloser, err error) { - keyVersion := -1 for _, k := range privateKeys { if k.Encrypted { return nil, errors.InvalidArgumentError(fmt.Sprintf("signing key %s is encrypted", k.KeyIdString())) } - if keyVersion == -1 { - keyVersion = k.Version - } - if keyVersion != k.Version { - return nil, errors.InvalidArgumentError("the signing keys must have the same version") - } } - v6 := keyVersion == 6 hashType := config.Hash() name := nameOfHash(hashType) @@ -385,10 +374,10 @@ func EncodeMulti(w io.Writer, privateKeys []*packet.PrivateKey, config *packet.C var hashers []hash.Hash var ws []io.Writer var salts [][]byte - for range privateKeys { + for _, sk := range privateKeys { h := hashType.New() - if v6 { - // generate salts + if sk.Version == 6 { + // generate salt var salt []byte salt, err = packet.SignatureSaltForHash(hashType, config.Random()) if err != nil { @@ -412,25 +401,25 @@ func EncodeMulti(w io.Writer, privateKeys []*packet.PrivateKey, config *packet.C if err = buffered.WriteByte(lf); err != nil { return } - if v6 { - for index, salt := range salts { + // write headers + if len(salts) > 0 { + for _, salt := range salts { encodedSalt := base64.RawStdEncoding.EncodeToString(salt) if _, err = buffered.WriteString(fmt.Sprintf("%s: %s:%s", saltedHashHeader, name, encodedSalt)); err != nil { return } - if index != len(salts)-1 { - if err = buffered.WriteByte(lf); err != nil { - return - } + if err = buffered.WriteByte(lf); err != nil { + return } } - } else { + } + if len(salts) < len(hashers) { if _, err = buffered.WriteString(fmt.Sprintf("%s: %s", hashHeader, name)); err != nil { return } - } - if err = buffered.WriteByte(lf); err != nil { - return + if err = buffered.WriteByte(lf); err != nil { + return + } } if err = buffered.WriteByte(lf); err != nil { return From 848b22ff4c562b10d2ed3ca22be3f6b609afdee0 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Tue, 18 Apr 2023 11:54:34 +0200 Subject: [PATCH 025/116] Add V6 getter method in config --- openpgp/integration_tests/utils_test.go | 2 +- openpgp/key_generation.go | 13 ++++++------- openpgp/packet/config.go | 7 +++++++ 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/openpgp/integration_tests/utils_test.go b/openpgp/integration_tests/utils_test.go index 5ff1d7e5b..51d04e480 100644 --- a/openpgp/integration_tests/utils_test.go +++ b/openpgp/integration_tests/utils_test.go @@ -26,7 +26,7 @@ func generateFreshTestVectors(num int) (vectors []testVector, err error) { // Only for verbose display v := "v4" - if config.V6Keys { + if config.V6() { v = "v6" } pkAlgoNames := map[packet.PublicKeyAlgorithm]string{ diff --git a/openpgp/key_generation.go b/openpgp/key_generation.go index b07afb684..4dd0d2d2c 100644 --- a/openpgp/key_generation.go +++ b/openpgp/key_generation.go @@ -33,7 +33,6 @@ import ( func NewEntity(name, comment, email string, config *packet.Config) (*Entity, error) { creationTime := config.Now() keyLifetimeSecs := config.KeyLifetime() - useV6Keys := config != nil && config.V6Keys // Generate a primary signing key primaryPrivRaw, err := newSigner(config) @@ -41,7 +40,7 @@ func NewEntity(name, comment, email string, config *packet.Config) (*Entity, err return nil, err } primary := packet.NewSignerPrivateKey(creationTime, primaryPrivRaw) - if useV6Keys { + if config.V6() { primary.UpgradeToV6() } @@ -53,7 +52,7 @@ func NewEntity(name, comment, email string, config *packet.Config) (*Entity, err DirectSignatures: []*packet.Signature{}, } - if useV6Keys { + if config.V6() { // In v6 keys algorithm preferences should be stored in direct key signatures selfSignature := createSignaturePacket(&primary.PublicKey, packet.SigTypeDirectSignature, config) err = writeKeyProperties(selfSignature, creationTime, keyLifetimeSecs, config) @@ -67,7 +66,7 @@ func NewEntity(name, comment, email string, config *packet.Config) (*Entity, err e.DirectSignatures = append(e.DirectSignatures, selfSignature) } - err = e.addUserId(name, comment, email, config, creationTime, keyLifetimeSecs, !useV6Keys) + err = e.addUserId(name, comment, email, config, creationTime, keyLifetimeSecs, !config.V6()) if err != nil { return nil, err } @@ -85,7 +84,7 @@ func NewEntity(name, comment, email string, config *packet.Config) (*Entity, err func (t *Entity) AddUserId(name, comment, email string, config *packet.Config) error { creationTime := config.Now() keyLifetimeSecs := config.KeyLifetime() - return t.addUserId(name, comment, email, config, creationTime, keyLifetimeSecs, !(config != nil && config.V6Keys)) + return t.addUserId(name, comment, email, config, creationTime, keyLifetimeSecs, !config.V6()) } func writeKeyProperties(selfSignature *packet.Signature, creationTime time.Time, keyLifetimeSecs uint32, config *packet.Config) error { @@ -186,7 +185,7 @@ func (e *Entity) AddSigningSubkey(config *packet.Config) error { } sub := packet.NewSignerPrivateKey(creationTime, subPrivRaw) sub.IsSubkey = true - if config != nil && config.V6Keys { + if config.V6() { sub.UpgradeToV6() } @@ -231,7 +230,7 @@ func (e *Entity) addEncryptionSubkey(config *packet.Config, creationTime time.Ti } sub := packet.NewDecrypterPrivateKey(creationTime, subPrivRaw) sub.IsSubkey = true - if config != nil && config.V6Keys { + if config.V6() { sub.UpgradeToV6() } diff --git a/openpgp/packet/config.go b/openpgp/packet/config.go index 2ccf67f0f..331832a21 100644 --- a/openpgp/packet/config.go +++ b/openpgp/packet/config.go @@ -249,3 +249,10 @@ func (c *Config) Notations() []*Notation { } return c.SignatureNotations } + +func (c *Config) V6() bool { + if c == nil { + return false + } + return c.V6Keys +} From 2141f18f46847996dc7f57d0dfc4988492326b5b Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Tue, 18 Apr 2023 12:09:13 +0200 Subject: [PATCH 026/116] Add missing check for self-signature expiration --- openpgp/keys.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpgp/keys.go b/openpgp/keys.go index 6577c2c94..e2c180f20 100644 --- a/openpgp/keys.go +++ b/openpgp/keys.go @@ -129,7 +129,8 @@ func (e *Entity) EncryptionKey(now time.Time) (Key, bool) { if primarySelfSignature == nil || // no self-signature found e.PrimaryKey.KeyExpired(primarySelfSignature, now) || // primary key has expired e.Revoked(now) || // primary key has been revoked - (primaryIdentity != nil && primaryIdentity.Revoked(now)) { // user ID has been revoked + primarySelfSignature.SigExpired(now) || // user ID self-signature has expired + (primaryIdentity != nil && primaryIdentity.Revoked(now)) { // user ID has been revoked (for v4 keys) return Key{}, false } @@ -194,7 +195,8 @@ func (e *Entity) signingKeyByIdUsage(now time.Time, id uint64, flags int) (Key, if primarySelfSignature == nil || // no self-signature found e.PrimaryKey.KeyExpired(primarySelfSignature, now) || // primary key has expired e.Revoked(now) || // primary key has been revoked - (primaryIdentity != nil && primaryIdentity.Revoked(now)) { // user id has been revoked v4 keys + primarySelfSignature.SigExpired(now) || // user ID self-signature has expired + (primaryIdentity != nil && primaryIdentity.Revoked(now)) { // user ID has been revoked (for v4 keys) return Key{}, false } From bd16267c146198a7cb46bcbdcf14b649d439ef9e Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Tue, 18 Apr 2023 12:34:01 +0200 Subject: [PATCH 027/116] Fix naming issues --- openpgp/packet/symmetric_key_encrypted.go | 4 ++-- openpgp/write.go | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/openpgp/packet/symmetric_key_encrypted.go b/openpgp/packet/symmetric_key_encrypted.go index 392f1d79b..c97b98b93 100644 --- a/openpgp/packet/symmetric_key_encrypted.go +++ b/openpgp/packet/symmetric_key_encrypted.go @@ -126,7 +126,7 @@ func (ske *SymmetricKeyEncrypted) Decrypt(passphrase []byte) ([]byte, CipherFunc plaintextKey, cipherFunc, err := ske.decryptV4(key) return plaintextKey, cipherFunc, err case 5, 6: - plaintextKey, err := ske.decryptWithV5orV6(ske.Version, key) + plaintextKey, err := ske.aeadDecrypt(ske.Version, key) return plaintextKey, CipherFunction(0), err } err := errors.UnsupportedError("unknown SymmetricKeyEncrypted version") @@ -152,7 +152,7 @@ func (ske *SymmetricKeyEncrypted) decryptV4(key []byte) ([]byte, CipherFunction, return plaintextKey, cipherFunc, nil } -func (ske *SymmetricKeyEncrypted) decryptWithV5orV6(version int, key []byte) ([]byte, error) { +func (ske *SymmetricKeyEncrypted) aeadDecrypt(version int, key []byte) ([]byte, error) { adata := []byte{0xc3, byte(version), byte(ske.CipherFunc), byte(ske.Mode)} aead := getEncryptedKeyAeadInstance(ske.CipherFunc, ske.Mode, key, adata, version) diff --git a/openpgp/write.go b/openpgp/write.go index c25e227fb..0323ba176 100644 --- a/openpgp/write.go +++ b/openpgp/write.go @@ -281,19 +281,19 @@ func writeAndSign(payload io.WriteCloser, candidateHashes []uint8, signed *Entit var salt []byte if signer != nil { - var sigVersion = 3 + var opsVersion = 3 if signer.Version == 6 { - sigVersion = signer.Version + opsVersion = signer.Version } ops := &packet.OnePassSignature{ - Version: sigVersion, + Version: opsVersion, SigType: sigType, Hash: hash, PubKeyAlgo: signer.PubKeyAlgo, KeyId: signer.KeyId, IsLast: true, } - if sigVersion == 6 { + if opsVersion == 6 { ops.KeyFingerprint = signer.Fingerprint salt, err = packet.SignatureSaltForHash(hash, config.Random()) if err != nil { From 26afebe5862c3dec1b7f4f1a2532ae90047399cb Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Tue, 18 Apr 2023 15:35:15 +0200 Subject: [PATCH 028/116] Augment SerializeEncryptedKey API to ensure backwards compatibility --- openpgp/packet/encrypted_key.go | 11 ++++++++++- openpgp/packet/encrypted_key_test.go | 6 +++--- openpgp/write.go | 2 +- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/openpgp/packet/encrypted_key.go b/openpgp/packet/encrypted_key.go index 9b5f1eb3b..a1c3da045 100644 --- a/openpgp/packet/encrypted_key.go +++ b/openpgp/packet/encrypted_key.go @@ -308,8 +308,9 @@ func (e *EncryptedKey) Serialize(w io.Writer) error { // SerializeEncryptedKey serializes an encrypted key packet to w that contains // key, encrypted to pub. +// If aeadSupported is set, PKESK v6 is used else v4. // If config is nil, sensible defaults will be used. -func SerializeEncryptedKey(w io.Writer, pub *PublicKey, cipherFunc CipherFunction, aeadSupported bool, key []byte, config *Config) error { +func SerializeEncryptedKeyAEAD(w io.Writer, pub *PublicKey, cipherFunc CipherFunction, aeadSupported bool, key []byte, config *Config) error { var buf [35]byte // max possible header size is v6 lenHeaderWritten := 1 version := 3 @@ -401,6 +402,14 @@ func SerializeEncryptedKey(w io.Writer, pub *PublicKey, cipherFunc CipherFunctio return errors.UnsupportedError("encrypting a key to public key of type " + strconv.Itoa(int(pub.PubKeyAlgo))) } +// SerializeEncryptedKey serializes an encrypted key packet to w that contains +// key, encrypted to pub. +// PKESKv6 is used if config.AEAD() is not nil. +// If config is nil, sensible defaults will be used. +func SerializeEncryptedKey(w io.Writer, pub *PublicKey, cipherFunc CipherFunction, key []byte, config *Config) error { + return SerializeEncryptedKeyAEAD(w, pub, cipherFunc, config.AEAD()!=nil, key, config) +} + func serializeEncryptedKeyRSA(w io.Writer, rand io.Reader, header []byte, pub *rsa.PublicKey, keyBlock []byte) error { cipherText, err := rsa.EncryptPKCS1v15(rand, pub, keyBlock) if err != nil { diff --git a/openpgp/packet/encrypted_key_test.go b/openpgp/packet/encrypted_key_test.go index fc13d2228..83b948827 100644 --- a/openpgp/packet/encrypted_key_test.go +++ b/openpgp/packet/encrypted_key_test.go @@ -168,7 +168,7 @@ func TestEncryptingEncryptedKey(t *testing.T) { } buf := new(bytes.Buffer) - err := SerializeEncryptedKey(buf, pub, CipherAES128, false, key, nil) + err := SerializeEncryptedKey(buf, pub, CipherAES128, key, nil) if err != nil { t.Errorf("error writing encrypted key packet: %s", err) } @@ -218,7 +218,7 @@ func TestEncryptingEncryptedKeyV6(t *testing.T) { rsaWrappedKeyPub := &rsaWrappedKey.PublicKey buf := new(bytes.Buffer) - err := SerializeEncryptedKey(buf, rsaWrappedKeyPub, CipherAES128, true, key, config) + err := SerializeEncryptedKey(buf, rsaWrappedKeyPub, CipherAES128, key, config) if err != nil { t.Errorf("error writing encrypted key packet: %s", err) @@ -281,7 +281,7 @@ func TestEncryptingEncryptedKeyXAlgorithms(t *testing.T) { buf := new(bytes.Buffer) - err := SerializeEncryptedKey(buf, wrappedKeyPub, CipherAES128, true, key, config) + err := SerializeEncryptedKey(buf, wrappedKeyPub, CipherAES128, key, config) if err != nil { t.Errorf("error writing encrypted key packet: %s", err) diff --git a/openpgp/write.go b/openpgp/write.go index 0323ba176..a32d435db 100644 --- a/openpgp/write.go +++ b/openpgp/write.go @@ -450,7 +450,7 @@ func encrypt(keyWriter io.Writer, dataWriter io.Writer, to []*Entity, signed *En } for _, key := range encryptKeys { - if err := packet.SerializeEncryptedKey(keyWriter, key.PublicKey, cipher, aeadSupported, symKey, config); err != nil { + if err := packet.SerializeEncryptedKeyAEAD(keyWriter, key.PublicKey, cipher, aeadSupported, symKey, config); err != nil { return nil, err } } From 7f25dd60226a21ef4e92c7d9e2594f9b02329a27 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Wed, 19 Apr 2023 09:24:13 +0200 Subject: [PATCH 029/116] Imitate model for v6 direct key self-signatures from identity self-signatures Ensures that a v6 key contains a valid direct-key signature for the primary key that stores its properties. On parsing a v6 key, the newest direct-key signature is selected and checked for validity while other signatures are stored but ignored. --- openpgp/key_generation.go | 5 ++- openpgp/keys.go | 80 ++++++++++++++++++++------------------- openpgp/keys_v6_test.go | 10 ++--- 3 files changed, 48 insertions(+), 47 deletions(-) diff --git a/openpgp/key_generation.go b/openpgp/key_generation.go index 4dd0d2d2c..553907974 100644 --- a/openpgp/key_generation.go +++ b/openpgp/key_generation.go @@ -49,7 +49,7 @@ func NewEntity(name, comment, email string, config *packet.Config) (*Entity, err PrivateKey: primary, Identities: make(map[string]*Identity), Subkeys: []Subkey{}, - DirectSignatures: []*packet.Signature{}, + Signatures: []*packet.Signature{}, } if config.V6() { @@ -63,7 +63,8 @@ func NewEntity(name, comment, email string, config *packet.Config) (*Entity, err if err != nil { return nil, err } - e.DirectSignatures = append(e.DirectSignatures, selfSignature) + e.Signatures = append(e.Signatures, selfSignature) + e.SelfSignature = selfSignature } err = e.addUserId(name, comment, email, config, creationTime, keyLifetimeSecs, !config.V6()) diff --git a/openpgp/keys.go b/openpgp/keys.go index e2c180f20..b849337a7 100644 --- a/openpgp/keys.go +++ b/openpgp/keys.go @@ -24,12 +24,13 @@ var PrivateKeyType = "PGP PRIVATE KEY BLOCK" // (which must be a signing key), one or more identities claimed by that key, // and zero or more subkeys, which may be encryption keys. type Entity struct { - PrimaryKey *packet.PublicKey - PrivateKey *packet.PrivateKey - Identities map[string]*Identity // indexed by Identity.Name - Revocations []*packet.Signature - Subkeys []Subkey - DirectSignatures []*packet.Signature // Direct-key signatures for the PrimaryKey + PrimaryKey *packet.PublicKey + PrivateKey *packet.PrivateKey + Identities map[string]*Identity // indexed by Identity.Name + Revocations []*packet.Signature + Subkeys []Subkey + SelfSignature *packet.Signature // Direct-key self signature of the PrimaryKey (containts primary key properties in v6) + Signatures []*packet.Signature // all (potentially unverified) self-signatures, revocations, and third-party signatures } // An Identity represents an identity claimed by an Entity and zero or more @@ -100,25 +101,21 @@ func shouldPreferIdentity(existingId, potentialNewId *Identity) bool { return false } - return shouldPreferSelfSignature(existingId.SelfSignature, potentialNewId.SelfSignature) -} - -func shouldPreferSelfSignature(existingSignature, potentialSignature *packet.Signature) bool { - if existingSignature == nil { + if existingId.SelfSignature == nil { return true } - if existingSignature.IsPrimaryId != nil && *existingSignature.IsPrimaryId && - !(potentialSignature.IsPrimaryId != nil && *potentialSignature.IsPrimaryId) { + if existingId.SelfSignature.IsPrimaryId != nil && *existingId.SelfSignature.IsPrimaryId && + !(potentialNewId.SelfSignature.IsPrimaryId != nil && *potentialNewId.SelfSignature.IsPrimaryId) { return false } - if !(existingSignature.IsPrimaryId != nil && *existingSignature.IsPrimaryId) && - potentialSignature.IsPrimaryId != nil && *potentialSignature.IsPrimaryId { + if !(existingId.SelfSignature.IsPrimaryId != nil && *existingId.SelfSignature.IsPrimaryId) && + potentialNewId.SelfSignature.IsPrimaryId != nil && *potentialNewId.SelfSignature.IsPrimaryId { return true } - return potentialSignature.CreationTime.After(existingSignature.CreationTime) + return potentialNewId.SelfSignature.CreationTime.After(existingId.SelfSignature.CreationTime) } // EncryptionKey returns the best candidate Key for encrypting a message to the @@ -130,7 +127,7 @@ func (e *Entity) EncryptionKey(now time.Time) (Key, bool) { e.PrimaryKey.KeyExpired(primarySelfSignature, now) || // primary key has expired e.Revoked(now) || // primary key has been revoked primarySelfSignature.SigExpired(now) || // user ID self-signature has expired - (primaryIdentity != nil && primaryIdentity.Revoked(now)) { // user ID has been revoked (for v4 keys) + (primaryIdentity != nil && primaryIdentity.Revoked(now)) { // user ID has been revoked (for v4 keys) return Key{}, false } @@ -192,9 +189,9 @@ func (e *Entity) SigningKeyById(now time.Time, id uint64) (Key, bool) { func (e *Entity) signingKeyByIdUsage(now time.Time, id uint64, flags int) (Key, bool) { // Fail to find any signing key if the... primarySelfSignature, primaryIdentity := e.PrimarySelfSignature() - if primarySelfSignature == nil || // no self-signature found + if primarySelfSignature == nil || // no self-signature found e.PrimaryKey.KeyExpired(primarySelfSignature, now) || // primary key has expired - e.Revoked(now) || // primary key has been revoked + e.Revoked(now) || // primary key has been revoked primarySelfSignature.SigExpired(now) || // user ID self-signature has expired (primaryIdentity != nil && primaryIdentity.Revoked(now)) { // user ID has been revoked (for v4 keys) return Key{}, false @@ -264,7 +261,7 @@ func (e *Entity) EncryptPrivateKeys(passphrase []byte, config *packet.Config) er var keysToEncrypt []*packet.PrivateKey // Add entity private key to encrypt. if e.PrivateKey != nil && !e.PrivateKey.Dummy() && !e.PrivateKey.Encrypted { - keysToEncrypt = append(keysToEncrypt, e.PrivateKey) + keysToEncrypt = append(keysToEncrypt, e.PrivateKey) } // Add subkeys to encrypt. @@ -289,7 +286,7 @@ func (e *Entity) DecryptPrivateKeys(passphrase []byte) error { // Add subkeys to decrypt. for _, sub := range e.Subkeys { if sub.PrivateKey != nil && !sub.PrivateKey.Dummy() && sub.PrivateKey.Encrypted { - keysToDecrypt = append(keysToDecrypt, sub.PrivateKey) + keysToDecrypt = append(keysToDecrypt, sub.PrivateKey) } } return packet.DecryptPrivateKeys(keysToDecrypt, passphrase) @@ -529,22 +526,35 @@ EachPacket: } } - if len(e.Identities) == 0 && e.PrimaryKey.Version < 6 { + if len(e.Identities) == 0 && e.PrimaryKey.Version < 6 { return nil, errors.StructuralError("v4 entity without any identities") } // An implementation MUST ensure that a valid direct-key signature is present before using a v6 key - if e.PrimaryKey.Version == 6 { + if e.PrimaryKey.Version == 6 { if len(directSignatures) == 0 { return nil, errors.StructuralError("v6 entity without a valid direct-key signature") } + // Select main direct key signature + var mainDirectKeySelfSignature *packet.Signature for _, directSignature := range directSignatures { - err = e.PrimaryKey.VerifyDirectKeySignature(directSignature) - if err != nil { - return nil, errors.StructuralError("direct-key signature signed by alternate key") + if directSignature.SigType == packet.SigTypeDirectSignature && + directSignature.CheckKeyIdOrFingerprint(e.PrimaryKey) && + (mainDirectKeySelfSignature == nil || + directSignature.CreationTime.After(mainDirectKeySelfSignature.CreationTime)) { + mainDirectKeySelfSignature = directSignature } } - e.DirectSignatures = directSignatures + if mainDirectKeySelfSignature == nil { + return nil, errors.StructuralError("no valid direct-key self-signature for v6 primary key found") + } + // Check that the main self-signature is valid + err = e.PrimaryKey.VerifyDirectKeySignature(mainDirectKeySelfSignature) + if err != nil { + return nil, errors.StructuralError("invalid direct-key self-signature for v6 xprimary key") + } + e.SelfSignature = mainDirectKeySelfSignature + e.Signatures = directSignatures } for _, revocation := range revocations { @@ -689,7 +699,7 @@ func (e *Entity) serializePrivate(w io.Writer, config *packet.Config, reSign boo return err } } - for _, directSignature := range e.DirectSignatures { + for _, directSignature := range e.Signatures { err := directSignature.Serialize(w) if err != nil { return err @@ -761,7 +771,7 @@ func (e *Entity) Serialize(w io.Writer) error { return err } } - for _, directSignature := range e.DirectSignatures { + for _, directSignature := range e.Signatures { err := directSignature.Serialize(w) if err != nil { return err @@ -871,17 +881,11 @@ func (e *Entity) RevokeSubkey(sk *Subkey, reason packet.ReasonForRevocation, rea } func (e *Entity) primaryDirectSignature() *packet.Signature { - var primaryDirectSignature *packet.Signature - for _, candidate := range e.DirectSignatures { - if shouldPreferSelfSignature(primaryDirectSignature, candidate) { - primaryDirectSignature = candidate - } - } - return primaryDirectSignature + return e.SelfSignature } -// primarySelfSignature searches the entitity for the self-signature that stores key prefrences. -// For V4 keys, returns the self-signature of the primary indentity, and the identity. +// primarySelfSignature searches the entitity for the self-signature that stores key prefrences. +// For V4 keys, returns the self-signature of the primary indentity, and the identity. // For V6 keys, returns the latest valid direct-key self-signature, and no identity (nil). // This self-signature is to be used to check the key expiration, // algorithm preferences, and so on. diff --git a/openpgp/keys_v6_test.go b/openpgp/keys_v6_test.go index 48dd6b48d..a1e5b96a1 100644 --- a/openpgp/keys_v6_test.go +++ b/openpgp/keys_v6_test.go @@ -190,13 +190,9 @@ func TestNewEntityWithDefaultHashv6(t *testing.T) { } continue } - - for _, signature := range entity.DirectSignatures { - prefs := signature.PreferredHash - if prefs == nil { - t.Fatal(err) - } + prefs := entity.SelfSignature.PreferredHash + if prefs == nil { + t.Fatal(err) } - } } From a2c729e172f9bfc0a0109d15a36abd0f179b5fba Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Wed, 19 Apr 2023 14:28:26 +0200 Subject: [PATCH 030/116] Refactor private key as suggested by twiss --- openpgp/packet/private_key.go | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/openpgp/packet/private_key.go b/openpgp/packet/private_key.go index a21c54db2..7d29b9593 100644 --- a/openpgp/packet/private_key.go +++ b/openpgp/packet/private_key.go @@ -320,21 +320,22 @@ func (pk *PrivateKey) parse(r io.Reader) (err error) { if len(privateKeyData) < 2 { return errors.StructuralError("truncated private key data") } - - if pk.Version == 6 { + if pk.Version != 6 { + // checksum + var sum uint16 + for i := 0; i < len(privateKeyData)-2; i++ { + sum += uint16(privateKeyData[i]) + } + if privateKeyData[len(privateKeyData)-2] != uint8(sum>>8) || + privateKeyData[len(privateKeyData)-1] != uint8(sum) { + return errors.StructuralError("private key checksum failure") + } + privateKeyData = privateKeyData[:len(privateKeyData)-2] + return pk.parsePrivateKey(privateKeyData) + } else { // No checksum return pk.parsePrivateKey(privateKeyData) - } - var sum uint16 - for i := 0; i < len(privateKeyData)-2; i++ { - sum += uint16(privateKeyData[i]) - } - if privateKeyData[len(privateKeyData)-2] != uint8(sum>>8) || - privateKeyData[len(privateKeyData)-1] != uint8(sum) { - return errors.StructuralError("private key checksum failure") } - privateKeyData = privateKeyData[:len(privateKeyData)-2] - return pk.parsePrivateKey(privateKeyData) } pk.encryptedData = privateKeyData @@ -405,7 +406,7 @@ func (pk *PrivateKey) Serialize(w io.Writer) (err error) { if _, err = optional.Write(pk.iv); err != nil { return } - if pk.Version == 5 && len(pk.iv) > 0 { + if pk.Version == 5 && pk.s2kType == S2KAEAD { // Add padding for version 5 padding := make([]byte, pk.cipher.blockSize() - len(pk.iv)) if _, err = optional.Write(padding); err != nil { From d84a83c0b6501ae97a29e2806f9a9d94a7bc3db5 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Wed, 19 Apr 2023 17:20:33 +0200 Subject: [PATCH 031/116] Update PKESK to the latest version of the crypto refresh This commit changes the PKESK parsing and serialization methods to the updated encodings of x25519 and x488 ciphertexts for v3 PKESK. Instead of encoding the symmetric key algorithm in the ciphertext with padding, the symmetric key algorithm is now encoded unencrypted. Further, the commit refactors the checksum handling and enforces AES with v3 PKESK for the above algorithms. --- openpgp/packet/encrypted_key.go | 140 +++++++++++++++++--------------- openpgp/x25519/x25519.go | 42 ++++++++-- openpgp/x448/x448.go | 42 ++++++++-- 3 files changed, 142 insertions(+), 82 deletions(-) diff --git a/openpgp/packet/encrypted_key.go b/openpgp/packet/encrypted_key.go index a1c3da045..65fae04c5 100644 --- a/openpgp/packet/encrypted_key.go +++ b/openpgp/packet/encrypted_key.go @@ -87,6 +87,7 @@ func (e *EncryptedKey) parse(r io.Reader) (err error) { return } e.Algo = PublicKeyAlgorithm(buf[0]) + var cipherFunction byte switch e.Algo { case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly: e.encryptedMPI1 = new(encoding.MPI) @@ -114,28 +115,28 @@ func (e *EncryptedKey) parse(r io.Reader) (err error) { return } case PubKeyAlgoX25519: - e.ephemeralPublicX25519, e.encryptedSession, err = x25519.DecodeFields(r) + e.ephemeralPublicX25519, e.encryptedSession, cipherFunction, err = x25519.DecodeFields(r, e.Version == 6) if err != nil { return } case PubKeyAlgoX448: - e.ephemeralPublicX448, e.encryptedSession, err = x448.DecodeFields(r) + e.ephemeralPublicX448, e.encryptedSession, cipherFunction, err = x448.DecodeFields(r, e.Version == 6) if err != nil { return } } + if e.Version < 6 { + switch e.Algo { + case PubKeyAlgoX25519, PubKeyAlgoX448: + e.CipherFunc = CipherFunction(cipherFunction) + // Check for validiy is in the Decrypt method + } + } + _, err = consumeAll(r) return } -func checksumKeyMaterial(key []byte) uint16 { - var checksum uint16 - for _, v := range key { - checksum += uint16(v) - } - return checksum -} - // Decrypt decrypts an encrypted session key with the given private key. The // private key must have been decrypted first. // If config is nil, sensible defaults will be used. @@ -183,37 +184,33 @@ func (e *EncryptedKey) Decrypt(priv *PrivateKey, config *Config) error { return err } - // Handle the no checksum cases - if priv.PubKeyAlgo == PubKeyAlgoX25519 || priv.PubKeyAlgo == PubKeyAlgoX448 { + var key []byte + switch priv.PubKeyAlgo { + case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoElGamal, PubKeyAlgoECDH: + keyOffset := 0 if e.Version < 6 { - e.Key = b[8:] e.CipherFunc = CipherFunction(b[0]) + keyOffset = 1 if !e.CipherFunc.IsSupported() { return errors.UnsupportedError("unsupported encryption function") } - } else { - e.Key = b } - return nil - } - - // Handle the checksum cases - keyOffset := 0 - if e.Version < 6 { - keyOffset = 1 - e.CipherFunc = CipherFunction(b[0]) - if !e.CipherFunc.IsSupported() { - return errors.UnsupportedError("unsupported encryption function") + key, err = decodeChecksumKey(b[keyOffset:]) + case PubKeyAlgoX25519, PubKeyAlgoX448: + if e.Version < 6 { + switch e.CipherFunc { + case CipherAES128, CipherAES192, CipherAES256: + break + default: + return errors.StructuralError("v3 PKESK mandates AES as cipher function for x25519 and x448") + } } + key = b[:] } - - e.Key = b[keyOffset : len(b)-2] - expectedChecksum := uint16(b[len(b)-2])<<8 | uint16(b[len(b)-1]) - checksum := checksumKeyMaterial(e.Key) - if checksum != expectedChecksum { - return errors.StructuralError("EncryptedKey checksum incorrect") + if err != nil { + return err } - + e.Key = key return nil } @@ -228,9 +225,9 @@ func (e *EncryptedKey) Serialize(w io.Writer) error { case PubKeyAlgoECDH: encodedLength = int(e.encryptedMPI1.EncodedLength()) + int(e.encryptedMPI2.EncodedLength()) case PubKeyAlgoX25519: - encodedLength = x25519.EncodedFieldsLength(e.encryptedSession) + encodedLength = x25519.EncodedFieldsLength(e.encryptedSession, e.Version == 6) case PubKeyAlgoX448: - encodedLength = x448.EncodedFieldsLength(e.encryptedSession) + encodedLength = x448.EncodedFieldsLength(e.encryptedSession, e.Version == 6) default: return errors.InvalidArgumentError("don't know how to serialize encrypted key type " + strconv.Itoa(int(e.Algo))) } @@ -296,10 +293,10 @@ func (e *EncryptedKey) Serialize(w io.Writer) error { _, err := w.Write(e.encryptedMPI2.EncodedBytes()) return err case PubKeyAlgoX25519: - err := x25519.EncodeFields(w, e.ephemeralPublicX25519, e.encryptedSession) + err := x25519.EncodeFields(w, e.ephemeralPublicX25519, e.encryptedSession, byte(e.CipherFunc), e.Version == 6) return err case PubKeyAlgoX448: - err := x448.EncodeFields(w, e.ephemeralPublicX448, e.encryptedSession) + err := x448.EncodeFields(w, e.ephemeralPublicX448, e.encryptedSession, byte(e.CipherFunc), e.Version == 6) return err default: panic("internal error") @@ -352,24 +349,11 @@ func SerializeEncryptedKeyAEAD(w io.Writer, pub *PublicKey, cipherFunc CipherFun lenHeaderWritten += 1 var keyBlock []byte - // for X25519 there is no checksum and a special encoding - if pub.PubKeyAlgo == PubKeyAlgoX25519 || pub.PubKeyAlgo == PubKeyAlgoX448 { - // no checksum is added + switch pub.PubKeyAlgo { + case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoElGamal, PubKeyAlgoECDH: + lenKeyBlock := len(key)+2 if version < 6 { - // For v3 PKESK packets, seven zero-octets are - // added as padding after the algorithm identifier - keyBlock = make([]byte, len(key) + 8) - keyBlock[0] = byte(cipherFunc) - copy(keyBlock[8:], key) - } else { - // For v6 PKESK packets, no checksum or padding - // are added to the session key before keywrapping - keyBlock = key - } - } else { - lenKeyBlock := 1 /* cipher type */ +len(key)+2 /* checksum */ - if version == 6 { - lenKeyBlock -= 1 // no cipher type + lenKeyBlock += 1 // cipher type included } keyBlock = make([]byte, lenKeyBlock) keyOffset := 0 @@ -377,11 +361,10 @@ func SerializeEncryptedKeyAEAD(w io.Writer, pub *PublicKey, cipherFunc CipherFun keyBlock[0] = byte(cipherFunc) keyOffset = 1 } - - copy(keyBlock[keyOffset:], key) - checksum := checksumKeyMaterial(key) - keyBlock[keyOffset+len(key)] = byte(checksum >> 8) - keyBlock[keyOffset+len(key)+1] = byte(checksum) + encodeChecksumKey(keyBlock[keyOffset:], key) + case PubKeyAlgoX25519, PubKeyAlgoX448: + // algorithm is added in plaintext below + keyBlock = key } switch pub.PubKeyAlgo { @@ -392,9 +375,9 @@ func SerializeEncryptedKeyAEAD(w io.Writer, pub *PublicKey, cipherFunc CipherFun case PubKeyAlgoECDH: return serializeEncryptedKeyECDH(w, config.Random(), buf[:lenHeaderWritten], pub.PublicKey.(*ecdh.PublicKey), keyBlock, pub.oid, pub.Fingerprint) case PubKeyAlgoX25519: - return serializeEncryptedKeyX25519(w, config.Random(), buf[:lenHeaderWritten], pub.PublicKey.(*x25519.PublicKey), keyBlock) + return serializeEncryptedKeyX25519(w, config.Random(), buf[:lenHeaderWritten], pub.PublicKey.(*x25519.PublicKey), keyBlock, byte(cipherFunc), version) case PubKeyAlgoX448: - return serializeEncryptedKeyX448(w, config.Random(), buf[:lenHeaderWritten], pub.PublicKey.(*x448.PublicKey), keyBlock) + return serializeEncryptedKeyX448(w, config.Random(), buf[:lenHeaderWritten], pub.PublicKey.(*x448.PublicKey), keyBlock, byte(cipherFunc), version) case PubKeyAlgoDSA, PubKeyAlgoRSASignOnly: return errors.InvalidArgumentError("cannot encrypt to public key of type " + strconv.Itoa(int(pub.PubKeyAlgo))) } @@ -484,14 +467,14 @@ func serializeEncryptedKeyECDH(w io.Writer, rand io.Reader, header []byte, pub * return err } -func serializeEncryptedKeyX25519(w io.Writer, rand io.Reader, header []byte, pub *x25519.PublicKey, keyBlock []byte) error { +func serializeEncryptedKeyX25519(w io.Writer, rand io.Reader, header []byte, pub *x25519.PublicKey, keyBlock []byte, cipherFunc byte, version int) error { ephemeralPublicX25519, ciphertext, err := x25519.Encrypt(rand, pub, keyBlock) if err != nil { return errors.InvalidArgumentError("X25519 encryption failed: " + err.Error()) } packetLen := len(header) /* header length */ - packetLen += x25519.EncodedFieldsLength(ciphertext) + packetLen += x25519.EncodedFieldsLength(ciphertext, version == 6) err = serializeHeader(w, packetTypeEncryptedKey, packetLen) if err != nil { @@ -502,18 +485,18 @@ func serializeEncryptedKeyX25519(w io.Writer, rand io.Reader, header []byte, pub if err != nil { return err } - err = x25519.EncodeFields(w, ephemeralPublicX25519, ciphertext) + err = x25519.EncodeFields(w, ephemeralPublicX25519, ciphertext, cipherFunc, version == 6) return err } -func serializeEncryptedKeyX448(w io.Writer, rand io.Reader, header []byte, pub *x448.PublicKey, keyBlock []byte) error { +func serializeEncryptedKeyX448(w io.Writer, rand io.Reader, header []byte, pub *x448.PublicKey, keyBlock []byte, cipherFunc byte, version int) error { ephemeralPublicX448, ciphertext, err := x448.Encrypt(rand, pub, keyBlock) if err != nil { return errors.InvalidArgumentError("x448 encryption failed: " + err.Error()) } packetLen := len(header) /* header length */ - packetLen += x448.EncodedFieldsLength(ciphertext) + packetLen += x448.EncodedFieldsLength(ciphertext, version == 6) err = serializeHeader(w, packetTypeEncryptedKey, packetLen) if err != nil { @@ -524,6 +507,31 @@ func serializeEncryptedKeyX448(w io.Writer, rand io.Reader, header []byte, pub * if err != nil { return err } - err = x448.EncodeFields(w, ephemeralPublicX448, ciphertext) + err = x448.EncodeFields(w, ephemeralPublicX448, ciphertext, cipherFunc, version == 6) return err } + +func checksumKeyMaterial(key []byte) uint16 { + var checksum uint16 + for _, v := range key { + checksum += uint16(v) + } + return checksum +} + +func decodeChecksumKey(msg []byte) (key []byte, err error) { + key = msg[:len(msg)-2] + expectedChecksum := uint16(msg[len(msg)-2])<<8 | uint16(msg[len(msg)-1]) + checksum := checksumKeyMaterial(key) + if checksum != expectedChecksum { + err = errors.StructuralError("session key checksum is incorrect") + } + return +} + +func encodeChecksumKey(buffer []byte, key []byte) { + copy(buffer, key) + checksum := checksumKeyMaterial(key) + buffer[len(key)] = byte(checksum >> 8) + buffer[len(key)+1] = byte(checksum) +} diff --git a/openpgp/x25519/x25519.go b/openpgp/x25519/x25519.go index 2ab09f7e5..5648d839b 100644 --- a/openpgp/x25519/x25519.go +++ b/openpgp/x25519/x25519.go @@ -147,40 +147,66 @@ func constantTimeIsZero(bytes []byte) bool { // EncodeFieldsLength returns the length of the ciphertext encoding // given the encrpyted session key. -func EncodedFieldsLength(encryptedSessionKey []byte) int { - return PointSize + 1 + len(encryptedSessionKey) +func EncodedFieldsLength(encryptedSessionKey []byte, v6 bool) int { + lenCipherFunction := 0 + if !v6 { + lenCipherFunction = 1 + } + return PointSize + 1 + len(encryptedSessionKey) + lenCipherFunction } // EncodeField encodes X25519 session key encryption as -// ephemeral X25519 public key | encryptedSessionKey length | encryptedSessionKey +// ephemeral X25519 public key | follow byte length | cipherFunction (v3 only) | encryptedSessionKey // and writes it to writer -func EncodeFields(writer io.Writer, ephemeralPublicKey *PublicKey, encryptedSessionKey []byte) (err error) { +func EncodeFields(writer io.Writer, ephemeralPublicKey *PublicKey, encryptedSessionKey []byte, cipherFunction byte, v6 bool) (err error) { + lenAlgorithm := 0 + if !v6 { + lenAlgorithm = 1 + } if _, err = writer.Write(ephemeralPublicKey.Point); err != nil { return } - if _, err = writer.Write([]byte{byte(len(encryptedSessionKey))}); err != nil { + if _, err = writer.Write([]byte{byte(len(encryptedSessionKey) + lenAlgorithm)}); err != nil { return } + if !v6 { + if _, err = writer.Write([]byte{cipherFunction}); err != nil { + return + } + } _, err = writer.Write(encryptedSessionKey) return } // DecodeField decodes a X25519 session key encryption as -// ephemeral X25519 public key | encryptedSessionKey length | encryptedSessionKey -func DecodeFields(reader io.Reader) (ephemeralPublicKey *PublicKey, encryptedSessionKey []byte, err error) { +// ephemeral X25519 public key | follow byte length | cipherFunction (v3 only) | encryptedSessionKey +func DecodeFields(reader io.Reader, v6 bool) (ephemeralPublicKey *PublicKey, encryptedSessionKey []byte, cipherFunction byte, err error) { var buf [1]byte ephemeralPublicKey = &PublicKey{ Point: make([]byte, PointSize), } + // 32 octets representing an ephemeral X25519 public key. _, err = io.ReadFull(reader, ephemeralPublicKey.Point) if err != nil { return } + // A one-octet size of the following fields. _, err = io.ReadFull(reader, buf[:]) if err != nil { return } - encryptedSessionKey = make([]byte, buf[0]) + followingLen := buf[0] + // The one-octet algorithm identifier, if it was passed (in the case of a v3 PKESK packet). + if !v6 { + _, err = io.ReadFull(reader, buf[:]) + if err != nil { + return + } + cipherFunction = buf[0] + followingLen -= 1 + } + // The encrypted session key. + encryptedSessionKey = make([]byte, followingLen) _, err = io.ReadFull(reader, encryptedSessionKey) return } diff --git a/openpgp/x448/x448.go b/openpgp/x448/x448.go index 8de3b53a1..b35f5e503 100644 --- a/openpgp/x448/x448.go +++ b/openpgp/x448/x448.go @@ -147,40 +147,66 @@ func constantTimeIsZero(bytes []byte) bool { // EncodeFieldsLength returns the length of the ciphertext encoding // given the encrpyted session key. -func EncodedFieldsLength(encryptedSessionKey []byte) int { - return PointSize + 1 + len(encryptedSessionKey) +func EncodedFieldsLength(encryptedSessionKey []byte, v6 bool) int { + lenCipherFunction := 0 + if !v6 { + lenCipherFunction = 1 + } + return PointSize + 1 + len(encryptedSessionKey) + lenCipherFunction } // EncodeField encodes x448 session key encryption as -// ephemeral x448 public key | encryptedSessionKey length | encryptedSessionKey +// ephemeral x448 public key | follow byte length | cipherFunction (v3 only) | encryptedSessionKey // and writes it to writer -func EncodeFields(writer io.Writer, ephemeralPublicKey *PublicKey, encryptedSessionKey []byte) (err error) { +func EncodeFields(writer io.Writer, ephemeralPublicKey *PublicKey, encryptedSessionKey []byte, cipherFunction byte, v6 bool) (err error) { + lenAlgorithm := 0 + if !v6 { + lenAlgorithm = 1 + } if _, err = writer.Write(ephemeralPublicKey.Point); err != nil { return } - if _, err = writer.Write([]byte{byte(len(encryptedSessionKey))}); err != nil { + if _, err = writer.Write([]byte{byte(len(encryptedSessionKey) + lenAlgorithm)}); err != nil { return } + if !v6 { + if _, err = writer.Write([]byte{cipherFunction}); err != nil { + return + } + } _, err = writer.Write(encryptedSessionKey) return } // DecodeField decodes a x448 session key encryption as -// ephemeral x448 public key | encryptedSessionKey length | encryptedSessionKey -func DecodeFields(reader io.Reader) (ephemeralPublicKey *PublicKey, encryptedSessionKey []byte, err error) { +// ephemeral x448 public key | follow byte length | cipherFunction (v3 only) | encryptedSessionKey +func DecodeFields(reader io.Reader, v6 bool) (ephemeralPublicKey *PublicKey, encryptedSessionKey []byte, cipherFunction byte, err error) { var buf [1]byte ephemeralPublicKey = &PublicKey{ Point: make([]byte, PointSize), } + // 56 octets representing an ephemeral X448 public key. _, err = io.ReadFull(reader, ephemeralPublicKey.Point) if err != nil { return } + // A one-octet size of the following fields. _, err = io.ReadFull(reader, buf[:]) if err != nil { return } - encryptedSessionKey = make([]byte, buf[0]) + followingLen := buf[0] + // The one-octet algorithm identifier, if it was passed (in the case of a v3 PKESK packet). + if !v6 { + _, err = io.ReadFull(reader, buf[:]) + if err != nil { + return + } + cipherFunction = buf[0] + followingLen -= 1 + } + // The encrypted session key. + encryptedSessionKey = make([]byte, followingLen) _, err = io.ReadFull(reader, encryptedSessionKey) return } From a4e36b045a190d0689adbfa18f1b1fb9e66fe271 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Wed, 19 Apr 2023 17:29:36 +0200 Subject: [PATCH 032/116] Run gofmt on project --- openpgp/armor/armor.go | 10 +-- openpgp/clearsign/clearsign.go | 2 +- openpgp/ed25519/ed25519.go | 3 +- openpgp/ed25519/ed25519_test.go | 2 +- openpgp/ed448/ed448.go | 3 +- openpgp/ed448/ed448_test.go | 2 +- openpgp/integration_tests/end_to_end_test.go | 2 +- openpgp/integration_tests/utils_test.go | 6 +- openpgp/keys_test.go | 23 ++++--- openpgp/keys_v5_test.go | 1 - openpgp/keys_v6_test.go | 16 ++--- openpgp/packet/config.go | 4 +- openpgp/packet/encrypted_key.go | 52 +++++++-------- openpgp/packet/encrypted_key_test.go | 27 ++++---- openpgp/packet/one_pass_signature.go | 20 +++--- openpgp/packet/packet.go | 6 +- openpgp/packet/private_key.go | 38 ++++++----- openpgp/packet/public_key.go | 9 ++- openpgp/packet/signature.go | 68 +++++++++++--------- openpgp/read.go | 14 ++-- openpgp/s2k/s2k.go | 15 +++-- openpgp/s2k/s2k_cache.go | 2 +- openpgp/s2k/s2k_config.go | 6 +- openpgp/s2k/s2k_test.go | 15 ++--- openpgp/write.go | 4 +- openpgp/write_test.go | 5 +- openpgp/x25519/x25519.go | 8 +-- openpgp/x25519/x25519_test.go | 1 - openpgp/x448/x448.go | 8 +-- openpgp/x448/x448_test.go | 1 - 30 files changed, 184 insertions(+), 189 deletions(-) diff --git a/openpgp/armor/armor.go b/openpgp/armor/armor.go index b8978d7a2..bc13306b6 100644 --- a/openpgp/armor/armor.go +++ b/openpgp/armor/armor.go @@ -46,9 +46,9 @@ var armorEndOfLine = []byte("-----") // lineReader wraps a line based reader. It watches for the end of an armor block type lineReader struct { - in *bufio.Reader - buf []byte - eof bool + in *bufio.Reader + buf []byte + eof bool } func (l *lineReader) Read(p []byte) (n int, err error) { @@ -104,8 +104,8 @@ func (l *lineReader) Read(p []byte) (n int, err error) { // a running CRC of the resulting data and checks the CRC against the value // found by the lineReader at EOF. type openpgpReader struct { - lReader *lineReader - b64Reader io.Reader + lReader *lineReader + b64Reader io.Reader } func (r *openpgpReader) Read(p []byte) (n int, err error) { diff --git a/openpgp/clearsign/clearsign.go b/openpgp/clearsign/clearsign.go index 69d2eb0c6..e168d14d9 100644 --- a/openpgp/clearsign/clearsign.go +++ b/openpgp/clearsign/clearsign.go @@ -412,7 +412,7 @@ func EncodeMulti(w io.Writer, privateKeys []*packet.PrivateKey, config *packet.C return } } - } + } if len(salts) < len(hashers) { if _, err = buffered.WriteString(fmt.Sprintf("%s: %s", hashHeader, name)); err != nil { return diff --git a/openpgp/ed25519/ed25519.go b/openpgp/ed25519/ed25519.go index 4e4656514..7de3e7268 100644 --- a/openpgp/ed25519/ed25519.go +++ b/openpgp/ed25519/ed25519.go @@ -24,8 +24,7 @@ type PrivateKey struct { } func NewPublicKey() *PublicKey { - return &PublicKey{ - } + return &PublicKey{} } func NewPrivateKey(key PublicKey) *PrivateKey { diff --git a/openpgp/ed25519/ed25519_test.go b/openpgp/ed25519/ed25519_test.go index 15643de02..184ebb704 100644 --- a/openpgp/ed25519/ed25519_test.go +++ b/openpgp/ed25519/ed25519_test.go @@ -6,7 +6,7 @@ import ( "testing" ) -func TestGenerate(t *testing.T){ +func TestGenerate(t *testing.T) { priv, err := GenerateKey(rand.Reader) if err != nil { t.Fatal(err) diff --git a/openpgp/ed448/ed448.go b/openpgp/ed448/ed448.go index 9a67ac9cf..fb0457023 100644 --- a/openpgp/ed448/ed448.go +++ b/openpgp/ed448/ed448.go @@ -24,8 +24,7 @@ type PrivateKey struct { } func NewPublicKey() *PublicKey { - return &PublicKey{ - } + return &PublicKey{} } func NewPrivateKey(key PublicKey) *PrivateKey { diff --git a/openpgp/ed448/ed448_test.go b/openpgp/ed448/ed448_test.go index b6b593c0e..ed900f30e 100644 --- a/openpgp/ed448/ed448_test.go +++ b/openpgp/ed448/ed448_test.go @@ -6,7 +6,7 @@ import ( "testing" ) -func TestGenerate(t *testing.T){ +func TestGenerate(t *testing.T) { priv, err := GenerateKey(rand.Reader) if err != nil { t.Fatal(err) diff --git a/openpgp/integration_tests/end_to_end_test.go b/openpgp/integration_tests/end_to_end_test.go index b5fecf6bf..02c0406be 100644 --- a/openpgp/integration_tests/end_to_end_test.go +++ b/openpgp/integration_tests/end_to_end_test.go @@ -204,7 +204,7 @@ func encDecTest(t *testing.T, from testVector, testVectors []testVector) { signKey, _ := signer.SigningKey(time.Now()) expectedKeyID := signKey.PublicKey.KeyId expectedFingerprint := signKey.PublicKey.Fingerprint - if signKey.PublicKey.Version != 6 && md.SignedByKeyId != expectedKeyID { + if signKey.PublicKey.Version != 6 && md.SignedByKeyId != expectedKeyID { t.Fatalf( "Message signed by wrong key id, got: %v, want: %v", *md.SignedBy, expectedKeyID) diff --git a/openpgp/integration_tests/utils_test.go b/openpgp/integration_tests/utils_test.go index 51d04e480..b3ce312e3 100644 --- a/openpgp/integration_tests/utils_test.go +++ b/openpgp/integration_tests/utils_test.go @@ -30,10 +30,10 @@ func generateFreshTestVectors(num int) (vectors []testVector, err error) { v = "v6" } pkAlgoNames := map[packet.PublicKeyAlgorithm]string{ - packet.PubKeyAlgoRSA: "rsa_" + v, - packet.PubKeyAlgoEdDSA: "EdDSA_" + v, + packet.PubKeyAlgoRSA: "rsa_" + v, + packet.PubKeyAlgoEdDSA: "EdDSA_" + v, packet.PubKeyAlgoEd25519: "ed25519_" + v, - packet.PubKeyAlgoEd448: "ed448_" + v, + packet.PubKeyAlgoEd448: "ed448_" + v, } newVector := testVector{ diff --git a/openpgp/keys_test.go b/openpgp/keys_test.go index 94ef58189..b05eb4707 100644 --- a/openpgp/keys_test.go +++ b/openpgp/keys_test.go @@ -1489,7 +1489,7 @@ func TestEncryptAndDecryptPrivateKeys(t *testing.T) { if err != nil { t.Fatal(err) } - + if !entity.PrivateKey.Encrypted { t.Fatal("Expected encrypted private key") } @@ -1498,12 +1498,12 @@ func TestEncryptAndDecryptPrivateKeys(t *testing.T) { t.Fatal("Expected encrypted private key") } } - + err = entity.DecryptPrivateKeys(passphrase) if err != nil { t.Fatal(err) } - + if entity.PrivateKey.Encrypted { t.Fatal("Expected plaintext private key") } @@ -1514,7 +1514,6 @@ func TestEncryptAndDecryptPrivateKeys(t *testing.T) { } }) } - } @@ -1811,13 +1810,13 @@ func TestReadPrivateForeignV4Key(t *testing.T) { } func checkV4Key(t *testing.T, ent *Entity) { - key := ent.PrimaryKey - if key.Version != 4 { - t.Errorf("wrong key version %d", key.Version) - } - if len(key.Fingerprint) != 20 { - t.Errorf("Wrong fingerprint length: %d", len(key.Fingerprint)) - } + key := ent.PrimaryKey + if key.Version != 4 { + t.Errorf("wrong key version %d", key.Version) + } + if len(key.Fingerprint) != 20 { + t.Errorf("Wrong fingerprint length: %d", len(key.Fingerprint)) + } signatures := ent.Revocations for _, id := range ent.Identities { signatures = append(signatures, id.SelfSignature) @@ -1831,7 +1830,7 @@ func checkV4Key(t *testing.T, ent *Entity) { t.Errorf("wrong signature version %d", sig.Version) } fgptLen := len(sig.IssuerFingerprint) - if fgptLen!= 20 { + if fgptLen != 20 { t.Errorf("Wrong fingerprint length in signature: %d", fgptLen) } } diff --git a/openpgp/keys_v5_test.go b/openpgp/keys_v5_test.go index 28caef9b5..6ed3b975e 100644 --- a/openpgp/keys_v5_test.go +++ b/openpgp/keys_v5_test.go @@ -13,7 +13,6 @@ var foreignKeys = []string{ v5PrivKey, } - func TestReadPrivateForeignV5Key(t *testing.T) { for _, str := range foreignKeys { kring, err := ReadArmoredKeyRing(strings.NewReader(str)) diff --git a/openpgp/keys_v6_test.go b/openpgp/keys_v6_test.go index a1e5b96a1..fc9ba776d 100644 --- a/openpgp/keys_v6_test.go +++ b/openpgp/keys_v6_test.go @@ -114,12 +114,12 @@ func TestNewEntityV6Key(t *testing.T) { func checkV6Key(t *testing.T, ent *Entity) { key := ent.PrimaryKey - if key.Version != 6 { - t.Errorf("wrong key version %d", key.Version) - } - if len(key.Fingerprint) != 32 { - t.Errorf("Wrong fingerprint length: %d", len(key.Fingerprint)) - } + if key.Version != 6 { + t.Errorf("wrong key version %d", key.Version) + } + if len(key.Fingerprint) != 32 { + t.Errorf("Wrong fingerprint length: %d", len(key.Fingerprint)) + } signatures := ent.Revocations for _, id := range ent.Identities { signatures = append(signatures, id.SelfSignature) @@ -133,7 +133,7 @@ func checkV6Key(t *testing.T, ent *Entity) { t.Errorf("wrong signature version %d", sig.Version) } fgptLen := len(sig.IssuerFingerprint) - if fgptLen!= 32 { + if fgptLen != 32 { t.Errorf("Wrong fingerprint length in signature: %d", fgptLen) } } @@ -180,7 +180,7 @@ func checkSerializeReadv6(t *testing.T, e *Entity) { func TestNewEntityWithDefaultHashv6(t *testing.T) { for _, hash := range hashes[:5] { c := &packet.Config{ - V6Keys: true, + V6Keys: true, DefaultHash: hash, } entity, err := NewEntity("Golang Gopher", "Test Key", "no-reply@golang.com", c) diff --git a/openpgp/packet/config.go b/openpgp/packet/config.go index 331832a21..6a72b9ee0 100644 --- a/openpgp/packet/config.go +++ b/openpgp/packet/config.go @@ -54,8 +54,8 @@ type Config struct { // // Deprecated: SK2Count should be configured in S2KConfig instead. S2KCount int - // An S2K specifier can be stored in the secret keyring to specify - // how to convert the passphrase to a key that unlocks the secret data. + // An S2K specifier can be stored in the secret keyring to specify + // how to convert the passphrase to a key that unlocks the secret data. // This config allows to set this key encryption parameters. // If nil, the default parameters are used. // See OpenPGP crypto refresh 3.7.2.1. diff --git a/openpgp/packet/encrypted_key.go b/openpgp/packet/encrypted_key.go index 65fae04c5..e37ab82a9 100644 --- a/openpgp/packet/encrypted_key.go +++ b/openpgp/packet/encrypted_key.go @@ -25,18 +25,18 @@ import ( // EncryptedKey represents a public-key encrypted session key. See RFC 4880, // section 5.1. type EncryptedKey struct { - Version int - KeyId uint64 - KeyVersion int // v6 + Version int + KeyId uint64 + KeyVersion int // v6 KeyFingerprint []byte // v6 - Algo PublicKeyAlgorithm - CipherFunc CipherFunction // only valid after a successful Decrypt for a v3 packet - Key []byte // only valid after a successful Decrypt + Algo PublicKeyAlgorithm + CipherFunc CipherFunction // only valid after a successful Decrypt for a v3 packet + Key []byte // only valid after a successful Decrypt encryptedMPI1, encryptedMPI2 encoding.Field - ephemeralPublicX25519 *x25519.PublicKey // used for x25519 - ephemeralPublicX448 *x448.PublicKey // used for x448 - encryptedSession []byte // used for x25519 and Ed448 + ephemeralPublicX25519 *x25519.PublicKey // used for x25519 + ephemeralPublicX448 *x448.PublicKey // used for x448 + encryptedSession []byte // used for x25519 and Ed448 } func (e *EncryptedKey) parse(r io.Reader) (err error) { @@ -45,7 +45,7 @@ func (e *EncryptedKey) parse(r io.Reader) (err error) { if err != nil { return } - e.Version = int(buf[0]) + e.Version = int(buf[0]) if e.Version != 3 && e.Version != 6 { return errors.UnsupportedError("unknown EncryptedKey version " + strconv.Itoa(int(buf[0]))) } @@ -54,7 +54,7 @@ func (e *EncryptedKey) parse(r io.Reader) (err error) { if err != nil { return } - e.KeyVersion = int(buf[0]) + e.KeyVersion = int(buf[0]) if e.KeyVersion != 0 && e.KeyVersion != 4 && e.KeyVersion != 6 { return errors.UnsupportedError("unknown public key version " + strconv.Itoa(e.KeyVersion)) } @@ -114,20 +114,20 @@ func (e *EncryptedKey) parse(r io.Reader) (err error) { if _, err = e.encryptedMPI2.ReadFrom(r); err != nil { return } - case PubKeyAlgoX25519: - e.ephemeralPublicX25519, e.encryptedSession, cipherFunction, err = x25519.DecodeFields(r, e.Version == 6) + case PubKeyAlgoX25519: + e.ephemeralPublicX25519, e.encryptedSession, cipherFunction, err = x25519.DecodeFields(r, e.Version == 6) if err != nil { return } case PubKeyAlgoX448: - e.ephemeralPublicX448, e.encryptedSession, cipherFunction, err = x448.DecodeFields(r, e.Version == 6) + e.ephemeralPublicX448, e.encryptedSession, cipherFunction, err = x448.DecodeFields(r, e.Version == 6) if err != nil { return } } if e.Version < 6 { switch e.Algo { - case PubKeyAlgoX25519, PubKeyAlgoX448: + case PubKeyAlgoX25519, PubKeyAlgoX448: e.CipherFunc = CipherFunction(cipherFunction) // Check for validiy is in the Decrypt method } @@ -145,7 +145,7 @@ func (e *EncryptedKey) Decrypt(priv *PrivateKey, config *Config) error { return errors.InvalidArgumentError("cannot decrypt encrypted session key for key id " + strconv.FormatUint(e.KeyId, 16) + " with private key id " + strconv.FormatUint(priv.KeyId, 16)) } if e.Version == 6 && e.KeyVersion != 0 && !bytes.Equal(e.KeyFingerprint, priv.Fingerprint) { - return errors.InvalidArgumentError("cannot decrypt encrypted session key for key fingerprint " + hex.EncodeToString(e.KeyFingerprint) + " with private key fingerprint " + hex.EncodeToString(priv.Fingerprint) ) + return errors.InvalidArgumentError("cannot decrypt encrypted session key for key fingerprint " + hex.EncodeToString(e.KeyFingerprint) + " with private key fingerprint " + hex.EncodeToString(priv.Fingerprint)) } if e.Algo != priv.PubKeyAlgo { return errors.InvalidArgumentError("cannot decrypt encrypted session key of type " + strconv.Itoa(int(e.Algo)) + " with private key of type " + strconv.Itoa(int(priv.PubKeyAlgo))) @@ -232,9 +232,9 @@ func (e *EncryptedKey) Serialize(w io.Writer) error { return errors.InvalidArgumentError("don't know how to serialize encrypted key type " + strconv.Itoa(int(e.Algo))) } - packetLen := 1 /* version */ +8 /* key id */ +1 /* algo */ + encodedLength + packetLen := 1 /* version */ + 8 /* key id */ + 1 /* algo */ + encodedLength if e.Version == 6 { - packetLen = 1 /* version */ +1 /* algo */ + encodedLength + 1 /* key version */ + packetLen = 1 /* version */ + 1 /* algo */ + encodedLength + 1 /* key version */ if e.KeyVersion == 6 { packetLen += 32 } else if e.KeyVersion == 4 { @@ -256,7 +256,7 @@ func (e *EncryptedKey) Serialize(w io.Writer) error { if err != nil { return err } - // The key version number may also be zero, + // The key version number may also be zero, // and the fingerprint omitted if e.KeyVersion != 0 { _, err = w.Write(e.KeyFingerprint) @@ -275,7 +275,7 @@ func (e *EncryptedKey) Serialize(w io.Writer) error { if err != nil { return err } - + switch e.Algo { case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly: _, err := w.Write(e.encryptedMPI1.EncodedBytes()) @@ -334,7 +334,7 @@ func SerializeEncryptedKeyAEAD(w io.Writer, pub *PublicKey, cipherFunc CipherFun if version == 6 { if pub != nil { buf[1] = byte(pub.Version) - copy(buf[2: len(pub.Fingerprint)+2], pub.Fingerprint) + copy(buf[2:len(pub.Fingerprint)+2], pub.Fingerprint) lenHeaderWritten += len(pub.Fingerprint) + 1 } else { // anonymous case @@ -351,7 +351,7 @@ func SerializeEncryptedKeyAEAD(w io.Writer, pub *PublicKey, cipherFunc CipherFun var keyBlock []byte switch pub.PubKeyAlgo { case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoElGamal, PubKeyAlgoECDH: - lenKeyBlock := len(key)+2 + lenKeyBlock := len(key) + 2 if version < 6 { lenKeyBlock += 1 // cipher type included } @@ -360,7 +360,7 @@ func SerializeEncryptedKeyAEAD(w io.Writer, pub *PublicKey, cipherFunc CipherFun if version < 6 { keyBlock[0] = byte(cipherFunc) keyOffset = 1 - } + } encodeChecksumKey(keyBlock[keyOffset:], key) case PubKeyAlgoX25519, PubKeyAlgoX448: // algorithm is added in plaintext below @@ -387,10 +387,10 @@ func SerializeEncryptedKeyAEAD(w io.Writer, pub *PublicKey, cipherFunc CipherFun // SerializeEncryptedKey serializes an encrypted key packet to w that contains // key, encrypted to pub. -// PKESKv6 is used if config.AEAD() is not nil. +// PKESKv6 is used if config.AEAD() is not nil. // If config is nil, sensible defaults will be used. func SerializeEncryptedKey(w io.Writer, pub *PublicKey, cipherFunc CipherFunction, key []byte, config *Config) error { - return SerializeEncryptedKeyAEAD(w, pub, cipherFunc, config.AEAD()!=nil, key, config) + return SerializeEncryptedKeyAEAD(w, pub, cipherFunc, config.AEAD() != nil, key, config) } func serializeEncryptedKeyRSA(w io.Writer, rand io.Reader, header []byte, pub *rsa.PublicKey, keyBlock []byte) error { @@ -524,7 +524,7 @@ func decodeChecksumKey(msg []byte) (key []byte, err error) { expectedChecksum := uint16(msg[len(msg)-2])<<8 | uint16(msg[len(msg)-1]) checksum := checksumKeyMaterial(key) if checksum != expectedChecksum { - err = errors.StructuralError("session key checksum is incorrect") + err = errors.StructuralError("session key checksum is incorrect") } return } diff --git a/openpgp/packet/encrypted_key_test.go b/openpgp/packet/encrypted_key_test.go index 83b948827..79bcb3a13 100644 --- a/openpgp/packet/encrypted_key_test.go +++ b/openpgp/packet/encrypted_key_test.go @@ -209,8 +209,7 @@ func TestEncryptingEncryptedKey(t *testing.T) { func TestEncryptingEncryptedKeyV6(t *testing.T) { key := []byte{1, 2, 3, 4} config := &Config{ - AEADConfig: &AEADConfig{ - }, + AEADConfig: &AEADConfig{}, } rsaKey, _ := rsa.GenerateKey(config.Random(), 2048) rsaWrappedKey := NewRSAPrivateKey(time.Now(), rsaKey) @@ -218,7 +217,7 @@ func TestEncryptingEncryptedKeyV6(t *testing.T) { rsaWrappedKeyPub := &rsaWrappedKey.PublicKey buf := new(bytes.Buffer) - err := SerializeEncryptedKey(buf, rsaWrappedKeyPub, CipherAES128, key, config) + err := SerializeEncryptedKey(buf, rsaWrappedKeyPub, CipherAES128, key, config) if err != nil { t.Errorf("error writing encrypted key packet: %s", err) @@ -235,7 +234,7 @@ func TestEncryptingEncryptedKeyV6(t *testing.T) { return } - if !bytes.Equal(ek.KeyFingerprint, rsaWrappedKey.Fingerprint) || + if !bytes.Equal(ek.KeyFingerprint, rsaWrappedKey.Fingerprint) || ek.Algo != PubKeyAlgoRSA || ek.KeyVersion != rsaWrappedKey.Version { t.Errorf("unexpected EncryptedKey contents: %#v", ek) @@ -247,7 +246,7 @@ func TestEncryptingEncryptedKeyV6(t *testing.T) { t.Errorf("error from Decrypt: %s", err) return } - + keyHex := fmt.Sprintf("%x", ek.Key) expectedKeyHex := fmt.Sprintf("%x", key) if keyHex != expectedKeyHex { @@ -258,30 +257,28 @@ func TestEncryptingEncryptedKeyV6(t *testing.T) { func TestEncryptingEncryptedKeyXAlgorithms(t *testing.T) { key := []byte{1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4} config := &Config{ - AEADConfig: &AEADConfig{ - }, + AEADConfig: &AEADConfig{}, } - x25519Gen := func () (*PrivateKey, PublicKeyAlgorithm) { + x25519Gen := func() (*PrivateKey, PublicKeyAlgorithm) { x25519Key, _ := x25519.GenerateKey(config.Random()) x25519WrappedKey := NewX25519PrivateKey(time.Now(), x25519Key) x25519WrappedKey.UpgradeToV6() return x25519WrappedKey, PubKeyAlgoX25519 } - x448Gen := func () (*PrivateKey, PublicKeyAlgorithm) { + x448Gen := func() (*PrivateKey, PublicKeyAlgorithm) { x448Key, _ := x448.GenerateKey(config.Random()) x448WrappedKey := NewX448PrivateKey(time.Now(), x448Key) x448WrappedKey.UpgradeToV6() return x448WrappedKey, PubKeyAlgoX448 } - testCaseFunc := []func()(*PrivateKey, PublicKeyAlgorithm) {x25519Gen, x448Gen} + testCaseFunc := []func() (*PrivateKey, PublicKeyAlgorithm){x25519Gen, x448Gen} for _, genFunc := range testCaseFunc { - wrappedKey, pubType:= genFunc() + wrappedKey, pubType := genFunc() wrappedKeyPub := &wrappedKey.PublicKey - buf := new(bytes.Buffer) - err := SerializeEncryptedKey(buf, wrappedKeyPub, CipherAES128, key, config) + err := SerializeEncryptedKey(buf, wrappedKeyPub, CipherAES128, key, config) if err != nil { t.Errorf("error writing encrypted key packet: %s", err) @@ -298,7 +295,7 @@ func TestEncryptingEncryptedKeyXAlgorithms(t *testing.T) { return } - if !bytes.Equal(ek.KeyFingerprint, wrappedKey.Fingerprint) || + if !bytes.Equal(ek.KeyFingerprint, wrappedKey.Fingerprint) || ek.Algo != pubType || ek.KeyVersion != wrappedKey.Version { t.Errorf("unexpected EncryptedKey contents: %#v", ek) @@ -310,7 +307,7 @@ func TestEncryptingEncryptedKeyXAlgorithms(t *testing.T) { t.Errorf("error from Decrypt: %s", err) return } - + keyHex := fmt.Sprintf("%x", ek.Key) expectedKeyHex := fmt.Sprintf("%x", key) if keyHex != expectedKeyHex { diff --git a/openpgp/packet/one_pass_signature.go b/openpgp/packet/one_pass_signature.go index 91fd7362d..ceef20fdd 100644 --- a/openpgp/packet/one_pass_signature.go +++ b/openpgp/packet/one_pass_signature.go @@ -17,20 +17,20 @@ import ( // OnePassSignature represents a one-pass signature packet. See RFC 4880, // section 5.4. type OnePassSignature struct { - Version int - SigType SignatureType - Hash crypto.Hash - PubKeyAlgo PublicKeyAlgorithm - KeyId uint64 - IsLast bool - Salt []byte // v6 only - KeyFingerprint []byte // v6 only + Version int + SigType SignatureType + Hash crypto.Hash + PubKeyAlgo PublicKeyAlgorithm + KeyId uint64 + IsLast bool + Salt []byte // v6 only + KeyFingerprint []byte // v6 only } func (ops *OnePassSignature) parse(r io.Reader) (err error) { var buf [8]byte // Read: version | signature type | hash algorithm | public-key algorithm - _, err = readFull(r, buf[:4]) + _, err = readFull(r, buf[:4]) if err != nil { return } @@ -110,7 +110,7 @@ func (ops *OnePassSignature) Serialize(w io.Writer) error { if ops.Version == 6 { // v6 length packetLength = 39 + len(ops.Salt) - } + } if err := serializeHeader(w, packetTypeOnePassSignature, packetLength); err != nil { return err diff --git a/openpgp/packet/packet.go b/openpgp/packet/packet.go index 18f19d0f4..ba91ce880 100644 --- a/openpgp/packet/packet.go +++ b/openpgp/packet/packet.go @@ -411,10 +411,10 @@ const ( PubKeyAlgoElGamal PublicKeyAlgorithm = 16 PubKeyAlgoDSA PublicKeyAlgorithm = 17 // RFC 6637, Section 5. - PubKeyAlgoECDH PublicKeyAlgorithm = 18 - PubKeyAlgoECDSA PublicKeyAlgorithm = 19 + PubKeyAlgoECDH PublicKeyAlgorithm = 18 + PubKeyAlgoECDSA PublicKeyAlgorithm = 19 // https://www.ietf.org/archive/id/draft-koch-eddsa-for-openpgp-04.txt - PubKeyAlgoEdDSA PublicKeyAlgorithm = 22 + PubKeyAlgoEdDSA PublicKeyAlgorithm = 22 // https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh PubKeyAlgoX25519 PublicKeyAlgorithm = 25 PubKeyAlgoX448 PublicKeyAlgorithm = 26 diff --git a/openpgp/packet/private_key.go b/openpgp/packet/private_key.go index 7d29b9593..9a54829de 100644 --- a/openpgp/packet/private_key.go +++ b/openpgp/packet/private_key.go @@ -44,8 +44,8 @@ type PrivateKey struct { aead AEADMode // only relevant if S2KAEAD is enabled // An *{rsa|dsa|elgamal|ecdh|ecdsa|ed25519|ed448}.PrivateKey or // crypto.Signer/crypto.Decrypter (Decryptor RSA only). - PrivateKey interface{} - iv []byte + PrivateKey interface{} + iv []byte // Type of encryption of the S2K packet // Allowed values are 0 (Not encrypted), 253 (AEAD), 254 (SHA1), or @@ -231,7 +231,7 @@ func (pk *PrivateKey) parse(r io.Reader) (err error) { if pk.cipher != 0 && !pk.cipher.IsSupported() { return errors.UnsupportedError("unsupported cipher function in private key") } - // [Optional] If string-to-key usage octet was 253, + // [Optional] If string-to-key usage octet was 253, // a one-octet AEAD algorithm. if pk.s2kType == S2KAEAD { _, err = readFull(r, buf[:]) @@ -244,7 +244,7 @@ func (pk *PrivateKey) parse(r io.Reader) (err error) { } } - // [Optional] Only for a version 6 packet, + // [Optional] Only for a version 6 packet, // and if string-to-key usage octet was 255, 254, or 253, // an one-octet count of the following field. if v6 { @@ -365,8 +365,6 @@ func (pk *PrivateKey) Serialize(w io.Writer) (err error) { return } - - optional := bytes.NewBuffer(nil) if pk.Encrypted || pk.Dummy() { // [Optional] If string-to-key usage octet was 255, 254, or 253, @@ -374,7 +372,7 @@ func (pk *PrivateKey) Serialize(w io.Writer) (err error) { if _, err = optional.Write([]byte{uint8(pk.cipher)}); err != nil { return } - // [Optional] If string-to-key usage octet was 253, + // [Optional] If string-to-key usage octet was 253, // a one-octet AEAD algorithm. if pk.s2kType == S2KAEAD { if _, err = optional.Write([]byte{uint8(pk.aead)}); err != nil { @@ -386,17 +384,17 @@ func (pk *PrivateKey) Serialize(w io.Writer) (err error) { if err := pk.s2kParams.Serialize(s2kBuffer); err != nil { return err } - // [Optional] Only for a version 6 packet, and if string-to-key - // usage octet was 255, 254, or 253, an one-octet + // [Optional] Only for a version 6 packet, and if string-to-key + // usage octet was 255, 254, or 253, an one-octet // count of the following field. if pk.Version == 6 { if _, err = optional.Write([]byte{uint8(s2kBuffer.Len())}); err != nil { return } - } - // [Optional] If string-to-key usage octet was 255, 254, or 253, - // a string-to-key (S2K) specifier. The length of the string-to-key specifier - // depends on its type + } + // [Optional] If string-to-key usage octet was 255, 254, or 253, + // a string-to-key (S2K) specifier. The length of the string-to-key specifier + // depends on its type if _, err = io.Copy(optional, s2kBuffer); err != nil { return } @@ -408,11 +406,11 @@ func (pk *PrivateKey) Serialize(w io.Writer) (err error) { } if pk.Version == 5 && pk.s2kType == S2KAEAD { // Add padding for version 5 - padding := make([]byte, pk.cipher.blockSize() - len(pk.iv)) + padding := make([]byte, pk.cipher.blockSize()-len(pk.iv)) if _, err = optional.Write(padding); err != nil { return } - } + } } } if pk.Version == 5 || (pk.Version == 6 && pk.s2kType != S2KNON) { @@ -627,7 +625,7 @@ func (pk *PrivateKey) Decrypt(passphrase []byte) error { } // DecryptPrivateKeys decrypts all encrypted keys with the given config and passphrase. -// Avoids recomputation of similar s2k key derivations. +// Avoids recomputation of similar s2k key derivations. func DecryptPrivateKeys(keys []*PrivateKey, passphrase []byte) error { // Create a cache to avoid recomputation of key derviations for the same passphrase. s2kCache := &s2k.Cache{} @@ -654,7 +652,7 @@ func (pk *PrivateKey) encrypt(key []byte, params *s2k.Params, s2kType S2KType, c if len(key) != cipherFunction.KeySize() { return errors.InvalidArgumentError("supplied encryption key has the wrong size") } - + priv := bytes.NewBuffer(nil) err := pk.serializePrivateKey(priv) if err != nil { @@ -666,7 +664,7 @@ func (pk *PrivateKey) encrypt(key []byte, params *s2k.Params, s2kType S2KType, c pk.s2k, err = pk.s2kParams.Function() if err != nil { return err - } + } privateKeyBytes := priv.Bytes() pk.s2kType = s2kType @@ -782,7 +780,7 @@ func (pk *PrivateKey) Encrypt(passphrase []byte) error { S2KMode: s2k.IteratedSaltedS2K, S2KCount: 65536, Hash: crypto.SHA256, - } , + }, DefaultCipher: CipherAES256, } return pk.EncryptWithConfig(passphrase, config) @@ -1073,7 +1071,7 @@ func (pk *PrivateKey) additionalData() ([]byte, error) { } else { packetByte = 0xc5 } - // Write public key to additional data + // Write public key to additional data _, err := additionalData.Write([]byte{packetByte}) if err != nil { return nil, err diff --git a/openpgp/packet/public_key.go b/openpgp/packet/public_key.go index 367ae3e88..0e3ae7048 100644 --- a/openpgp/packet/public_key.go +++ b/openpgp/packet/public_key.go @@ -71,7 +71,6 @@ func (pk *PublicKey) UpgradeToV6() { pk.setFingerprintAndKeyId() } - // signingKey provides a convenient abstraction over signature verification // for v3 and v4 public keys. type signingKey interface { @@ -511,7 +510,7 @@ func (pk *PublicKey) parseX25519(r io.Reader) (err error) { if err != nil { return } - pub := &x25519.PublicKey { + pub := &x25519.PublicKey{ Point: point, } pk.PublicKey = pub @@ -524,7 +523,7 @@ func (pk *PublicKey) parseX448(r io.Reader) (err error) { if err != nil { return } - pub := &x448.PublicKey { + pub := &x448.PublicKey{ Point: point, } pk.PublicKey = pub @@ -537,7 +536,7 @@ func (pk *PublicKey) parseEd25519(r io.Reader) (err error) { if err != nil { return } - pub := &ed25519.PublicKey { + pub := &ed25519.PublicKey{ Point: point, } pk.PublicKey = pub @@ -550,7 +549,7 @@ func (pk *PublicKey) parseEd448(r io.Reader) (err error) { if err != nil { return } - pub := &ed448.PublicKey { + pub := &ed448.PublicKey{ Point: point, } pk.PublicKey = pub diff --git a/openpgp/packet/signature.go b/openpgp/packet/signature.go index a410fe3ab..09096abba 100644 --- a/openpgp/packet/signature.go +++ b/openpgp/packet/signature.go @@ -62,7 +62,7 @@ type Signature struct { DSASigR, DSASigS encoding.Field ECDSASigR, ECDSASigS encoding.Field EdDSASigR, EdDSASigS encoding.Field - EdSig []byte + EdSig []byte // rawSubpackets contains the unparsed subpackets, in order. rawSubpackets []outputSubpacket @@ -119,8 +119,8 @@ type Signature struct { outSubpackets []outputSubpacket } -// SaltedHashSpecifier specifies that the given salt and hash are -// used by a v6 signature. +// SaltedHashSpecifier specifies that the given salt and hash are +// used by a v6 signature. type SaltedHashSpecifier struct { Hash crypto.Hash Salt []byte @@ -178,10 +178,10 @@ func (sig *Signature) parse(r io.Reader) (err error) { if sig.Version == 6 { // For a v6 signature, a four-octet length is used. hashedSubpacketsLength = - int(buf[3])<<24 | - int(buf[4])<<16 | - int(buf[5])<<8 | - int(buf[6]) + int(buf[3])<<24 | + int(buf[4])<<16 | + int(buf[5])<<8 | + int(buf[6]) } else { hashedSubpacketsLength = int(buf[3])<<8 | int(buf[4]) } @@ -772,7 +772,7 @@ func (sig *Signature) signPrepareHash(h hash.Hash) (digest []byte, err error) { // PrepareSign must be called to create a hash object before Sign for v6 signatures. // 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, +// 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. func (sig *Signature) PrepareSign(config *Config) (hash.Hash, error) { @@ -796,10 +796,10 @@ func (sig *Signature) PrepareSign(config *Config) (hash.Hash, error) { // SetSalt sets the signature salt for v6 signatures. // Assumes salt is generated correctly and checks if length matches. // If the signature is not v6, the method ignores the salt. -// Use PrepareSign whenever possible instead of generating and +// Use PrepareSign whenever possible instead of generating and // hashing the salt externally. // See RFC the crypto refresh Section 3.2.4. -func (sig *Signature) SetSalt(salt []byte) (error) { +func (sig *Signature) SetSalt(salt []byte) error { if sig.Version == 6 { expectedSaltLength, err := SaltLengthForHash(sig.Hash) if err != nil { @@ -923,7 +923,7 @@ func (sig *Signature) SignUserId(id string, pub *PublicKey, priv *PrivateKey, co } // SignUserId computes a signature from priv -// On success, the signature is stored in sig. +// On success, the signature is stored in sig. // Call Serialize to write it out. // If config is nil, sensible defaults will be used. func (sig *Signature) SignDirectKeyBinding(pub *PublicKey, priv *PrivateKey, config *Config) error { @@ -1024,7 +1024,7 @@ func (sig *Signature) Serialize(w io.Writer) (err error) { case PubKeyAlgoEd25519: sigLength = ed25519.SignatureSize case PubKeyAlgoEd448: - sigLength = ed448.SignatureSize + sigLength = ed448.SignatureSize default: panic("impossible") } @@ -1040,7 +1040,7 @@ func (sig *Signature) Serialize(w io.Writer) (err error) { // unhashed length is four-octet instead // salt len 1 ocet // len(salt) ocets - length += 3 + len(sig.salt) + length += 3 + len(sig.salt) } err = serializeHeader(w, packetTypeSignature, length) if err != nil { @@ -1058,16 +1058,16 @@ func (sig *Signature) serializeBody(w io.Writer) (err error) { if sig.Version == 6 { // v6 signatures use 4 ocets for length hashedSubpacketsLen := - uint32(uint32(sig.HashSuffix[4])<<24) | - uint32(uint32(sig.HashSuffix[5])<<16) | - uint32(uint32(sig.HashSuffix[6])<<8) | - uint32(sig.HashSuffix[7]) + uint32(uint32(sig.HashSuffix[4])<<24) | + uint32(uint32(sig.HashSuffix[5])<<16) | + uint32(uint32(sig.HashSuffix[6])<<8) | + uint32(sig.HashSuffix[7]) fields = sig.HashSuffix[:8+hashedSubpacketsLen] } else { - hashedSubpacketsLen := uint16(uint16(sig.HashSuffix[4])<<8) | - uint16(sig.HashSuffix[5]) + hashedSubpacketsLen := uint16(uint16(sig.HashSuffix[4])<<8) | + uint16(sig.HashSuffix[5]) fields = sig.HashSuffix[:6+hashedSubpacketsLen] - + } _, err = w.Write(fields) if err != nil { @@ -1090,7 +1090,6 @@ func (sig *Signature) serializeBody(w io.Writer) (err error) { serializeSubpackets(unhashedSubpackets[2:], sig.outSubpackets, false) } - _, err = w.Write(unhashedSubpackets) if err != nil { return @@ -1335,22 +1334,29 @@ func (sig *Signature) AddMetadataToHashSuffix() { sig.HashSuffix = suffix.Bytes() } -// SaltLengthForHash selects the required salt length for the given hash algorithm, +// 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 func SaltLengthForHash(hash crypto.Hash) (int, error) { - switch (hash) { - case crypto.SHA256: return 16, nil - case crypto.SHA384: return 24, nil - case crypto.SHA512: return 32, nil - case crypto.SHA224: return 16, nil - case crypto.SHA3_256: return 16, nil - case crypto.SHA3_512: return 32, nil - default: return 0, errors.UnsupportedError("hash function not supported for V6 signatures") + switch hash { + case crypto.SHA256: + return 16, nil + case crypto.SHA384: + return 24, nil + case crypto.SHA512: + return 32, nil + case crypto.SHA224: + return 16, nil + case crypto.SHA3_256: + return 16, nil + case crypto.SHA3_512: + return 32, nil + default: + return 0, errors.UnsupportedError("hash function not supported for V6 signatures") } } -// SignatureSaltForHash generates a random signature salt +// 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 func SignatureSaltForHash(hash crypto.Hash, randReader io.Reader) ([]byte, error) { diff --git a/openpgp/read.go b/openpgp/read.go index 91d35aaa5..8b3970a93 100644 --- a/openpgp/read.go +++ b/openpgp/read.go @@ -47,7 +47,7 @@ type MessageDetails struct { DecryptedWith Key // the private key used to decrypt the message, if any. IsSigned bool // true if the message is signed. SignedByKeyId uint64 // the key id of the signer, if any. - SignedByFingerprint []byte // the key fingerprint of the signer, if any. (only v6) + SignedByFingerprint []byte // the key fingerprint of the signer, if any. (only v6) SignedBy *Key // the key of the signer, if available. LiteralData *packet.LiteralData // the metadata of the contents UnverifiedBody io.Reader // the contents of the message. @@ -280,9 +280,9 @@ FindLiteralData: md.IsSigned = true if p.Version == 6 { md.SignedByFingerprint = p.KeyFingerprint - } + } md.SignedByKeyId = p.KeyId - + if keyring != nil { keys := keyring.KeysByIdUsage(p.KeyId, packet.KeyFlagSign) if len(keys) > 0 { @@ -470,7 +470,7 @@ func VerifyDetachedSignatureAndSaltedHash(keyring KeyRing, signed, signature io. // ErrUnknownIssuer is returned. func CheckDetachedSignature(keyring KeyRing, signed, signature io.Reader, config *packet.Config) (signer *Entity, err error) { _, signer, err = verifyDetachedSignature(keyring, signed, signature, nil, nil, false, config) - return + return } // CheckDetachedSignatureAndSaltedHash performs the same actions as @@ -520,12 +520,12 @@ func verifyDetachedSignature(keyring KeyRing, signed, signature io.Reader, expec if sig.Version == 6 { // check for salted hashes for _, expectedSaltedHash := range expectedSaltedHashes { - if hashFunc == expectedSaltedHash.Hash && bytes.Equal(sig.Salt(), expectedSaltedHash.Salt){ + if hashFunc == expectedSaltedHash.Hash && bytes.Equal(sig.Salt(), expectedSaltedHash.Salt) { matchFound = true break } } - + } else { // check for hashes for _, expectedHash := range expectedHashes { @@ -605,7 +605,7 @@ func checkSignatureDetails(key *Key, signature *packet.Signature, config *packet now := config.Now() primarySelfSignature, primaryIdentity := key.Entity.PrimarySelfSignature() signedBySubKey := key.PublicKey != key.Entity.PrimaryKey - sigsToCheck := []*packet.Signature{ signature, primarySelfSignature } + sigsToCheck := []*packet.Signature{signature, primarySelfSignature} if signedBySubKey { sigsToCheck = append(sigsToCheck, key.SelfSignature, key.SelfSignature.EmbeddedSignature) } diff --git a/openpgp/s2k/s2k.go b/openpgp/s2k/s2k.go index a43695964..f4f5c7832 100644 --- a/openpgp/s2k/s2k.go +++ b/openpgp/s2k/s2k.go @@ -87,10 +87,10 @@ func decodeCount(c uint8) int { // encodeMemory converts the Argon2 "memory" in the range parallelism*8 to // 2**31, inclusive, to an encoded memory. The return value is the // octet that is actually stored in the GPG file. encodeMemory panics -// if is not in the above range +// if is not in the above range // See OpenPGP crypto refresh Section 3.7.1.4. func encodeMemory(memory uint32, parallelism uint8) uint8 { - if memory < (8 * uint32(parallelism)) || memory > uint32(2147483648) { + if memory < (8*uint32(parallelism)) || memory > uint32(2147483648) { panic("Memory argument memory is outside the required range") } @@ -211,7 +211,7 @@ func Generate(rand io.Reader, c *Config) (*Params, error) { c.S2KMode = IteratedSaltedS2K } params = &Params{ - mode: IteratedSaltedS2K, + mode: IteratedSaltedS2K, hashId: hashId, countByte: c.EncodedCount(), } @@ -306,9 +306,12 @@ func (params *Params) Dummy() bool { func (params *Params) salt() []byte { switch params.mode { - case SaltedS2K, IteratedSaltedS2K: return params.saltBytes[:8] - case Argon2S2K: return params.saltBytes[:Argon2SaltSize] - default: return nil + case SaltedS2K, IteratedSaltedS2K: + return params.saltBytes[:8] + case Argon2S2K: + return params.saltBytes[:Argon2SaltSize] + default: + return nil } } diff --git a/openpgp/s2k/s2k_cache.go b/openpgp/s2k/s2k_cache.go index 25a4442df..616e0d12c 100644 --- a/openpgp/s2k/s2k_cache.go +++ b/openpgp/s2k/s2k_cache.go @@ -5,7 +5,7 @@ package s2k // the same parameters. type Cache map[Params][]byte -// GetOrComputeDerivedKey tries to retrieve the key +// GetOrComputeDerivedKey tries to retrieve the key // for the given s2k parameters from the cache. // If there is no hit, it derives the key with the s2k function from the passphrase, // updates the cache, and returns the key. diff --git a/openpgp/s2k/s2k_config.go b/openpgp/s2k/s2k_config.go index b40be5228..1a6b7f141 100644 --- a/openpgp/s2k/s2k_config.go +++ b/openpgp/s2k/s2k_config.go @@ -50,9 +50,9 @@ type Config struct { type Argon2Config struct { NumberOfPasses uint8 DegreeOfParallelism uint8 - // The memory parameter for Argon2 specifies desired memory usage in kibibytes. + // The memory parameter for Argon2 specifies desired memory usage in kibibytes. // For example memory=64*1024 sets the memory cost to ~64 MB. - Memory uint32 + Memory uint32 } func (c *Config) Mode() Mode { @@ -115,7 +115,7 @@ func (c *Argon2Config) EncodedMemory() uint8 { } memory := c.Memory - lowerBound := uint32(c.Parallelism())*8 + lowerBound := uint32(c.Parallelism()) * 8 upperBound := uint32(2147483648) switch { diff --git a/openpgp/s2k/s2k_test.go b/openpgp/s2k/s2k_test.go index f89b9694b..8a0720574 100644 --- a/openpgp/s2k/s2k_test.go +++ b/openpgp/s2k/s2k_test.go @@ -44,11 +44,11 @@ func TestSalted(t *testing.T) { } var argon2EncodeTest = []struct { - in uint32 + in uint32 out uint8 }{ - {64*1024, 16}, - {64*1024+1, 17}, + {64 * 1024, 16}, + {64*1024 + 1, 17}, {2147483647, 31}, {2147483649, 31}, {1, 3}, @@ -57,8 +57,8 @@ var argon2EncodeTest = []struct { func TestArgon2EncodeTest(t *testing.T) { for i, tests := range argon2EncodeTest { - conf := &Argon2Config { - Memory: tests.in, + conf := &Argon2Config{ + Memory: tests.in, DegreeOfParallelism: 1, } out := conf.EncodedMemory() @@ -68,7 +68,6 @@ func TestArgon2EncodeTest(t *testing.T) { } } - var iteratedTests = []struct { in, out string }{ @@ -240,8 +239,8 @@ func TestSerializeSaltedIteratedOK(t *testing.T) { func TestSerializeOKArgon(t *testing.T) { config := &Config{ - S2KMode: Argon2S2K, - Argon2Config: &Argon2Config{NumberOfPasses: 3, DegreeOfParallelism: 4, Memory: 64*1024}, + S2KMode: Argon2S2K, + Argon2Config: &Argon2Config{NumberOfPasses: 3, DegreeOfParallelism: 4, Memory: 64 * 1024}, } params := testSerializeConfigOK(t, config) diff --git a/openpgp/write.go b/openpgp/write.go index a32d435db..846ae5c6a 100644 --- a/openpgp/write.go +++ b/openpgp/write.go @@ -286,7 +286,7 @@ func writeAndSign(payload io.WriteCloser, candidateHashes []uint8, signed *Entit opsVersion = signer.Version } ops := &packet.OnePassSignature{ - Version: opsVersion, + Version: opsVersion, SigType: sigType, Hash: hash, PubKeyAlgo: signer.PubKeyAlgo, @@ -300,7 +300,7 @@ func writeAndSign(payload io.WriteCloser, candidateHashes []uint8, signed *Entit return nil, err } ops.Salt = salt - } + } if err := ops.Serialize(payload); err != nil { return nil, err } diff --git a/openpgp/write_test.go b/openpgp/write_test.go index a0a5ef7c0..ca383930c 100644 --- a/openpgp/write_test.go +++ b/openpgp/write_test.go @@ -428,7 +428,7 @@ func TestSymmetricEncryptionV5RandomizeSlow(t *testing.T) { var testEncryptionTests = []struct { keyRingHex string isSigned bool - okV6 bool + okV6 bool }{ { testKeys1And2PrivateHex, @@ -502,13 +502,12 @@ func TestEncryption(t *testing.T) { config.AEADConfig = &aeadConf } - w, err := Encrypt(buf, kring[:1], signed, nil /* no hints */, config) if err != nil && config.AEAD() != nil && !test.okV6 { // ElGamal is not allowed with v6 continue } - + if err != nil { t.Errorf("#%d: error in Encrypt: %s", i, err) continue diff --git a/openpgp/x25519/x25519.go b/openpgp/x25519/x25519.go index 5648d839b..5af5f2fa9 100644 --- a/openpgp/x25519/x25519.go +++ b/openpgp/x25519/x25519.go @@ -58,7 +58,7 @@ func GenerateKey(rand io.Reader) (*PrivateKey, error) { func generateKey(rand io.Reader, privateKey *x25519lib.Key, publicKey *x25519lib.Key) error { maxRounds := 10 isZero := true - for round := 0; isZero; round++ { + for round := 0; isZero; round++ { if round == maxRounds { return errors.InvalidArgumentError("x25519: zero keys only, randomness source might be corrupt") } @@ -72,10 +72,10 @@ func generateKey(rand io.Reader, privateKey *x25519lib.Key, publicKey *x25519lib return nil } -// Encrypt encrpyts a sessionKey with x25519 according to +// Encrypt encrpyts a sessionKey with x25519 according to // the OpenPGP crypto refresh specification section 5.1.6. The function assumes that the // sessionKey has the correct format and padding according to the specification. -func Encrypt(rand io.Reader, publicKey *PublicKey, sessionKey []byte) (ephemeralPublicKey *PublicKey, encryptedSessionKey []byte, err error) { +func Encrypt(rand io.Reader, publicKey *PublicKey, sessionKey []byte) (ephemeralPublicKey *PublicKey, encryptedSessionKey []byte, err error) { var ephemeralPrivate, ephemeralPublic, staticPublic, shared x25519lib.Key // Check that the input static public key has 32 bytes if len(publicKey.Point) != PointSize { @@ -175,7 +175,7 @@ func EncodeFields(writer io.Writer, ephemeralPublicKey *PublicKey, encryptedSess } } _, err = writer.Write(encryptedSessionKey) - return + return } // DecodeField decodes a X25519 session key encryption as diff --git a/openpgp/x25519/x25519_test.go b/openpgp/x25519/x25519_test.go index 555bb31be..67747beb1 100644 --- a/openpgp/x25519/x25519_test.go +++ b/openpgp/x25519/x25519_test.go @@ -6,7 +6,6 @@ import ( "testing" ) - func TestGenerate(t *testing.T) { privateKey, err := GenerateKey(rand.Reader) if err != nil { diff --git a/openpgp/x448/x448.go b/openpgp/x448/x448.go index b35f5e503..e54ec9ef9 100644 --- a/openpgp/x448/x448.go +++ b/openpgp/x448/x448.go @@ -58,7 +58,7 @@ func GenerateKey(rand io.Reader) (*PrivateKey, error) { func generateKey(rand io.Reader, privateKey *x448lib.Key, publicKey *x448lib.Key) error { maxRounds := 10 isZero := true - for round := 0; isZero; round++ { + for round := 0; isZero; round++ { if round == maxRounds { return errors.InvalidArgumentError("x448: zero keys only, randomness source might be corrupt") } @@ -72,10 +72,10 @@ func generateKey(rand io.Reader, privateKey *x448lib.Key, publicKey *x448lib.Key return nil } -// Encrypt encrpyts a sessionKey with x448 according to +// Encrypt encrpyts a sessionKey with x448 according to // the OpenPGP crypto refresh specification section 5.1.6. The function assumes that the // sessionKey has the correct format and padding according to the specification. -func Encrypt(rand io.Reader, publicKey *PublicKey, sessionKey []byte) (ephemeralPublicKey *PublicKey, encryptedSessionKey []byte, err error) { +func Encrypt(rand io.Reader, publicKey *PublicKey, sessionKey []byte) (ephemeralPublicKey *PublicKey, encryptedSessionKey []byte, err error) { var ephemeralPrivate, ephemeralPublic, staticPublic, shared x448lib.Key // Check that the input static public key has 32 bytes if len(publicKey.Point) != PointSize { @@ -175,7 +175,7 @@ func EncodeFields(writer io.Writer, ephemeralPublicKey *PublicKey, encryptedSess } } _, err = writer.Write(encryptedSessionKey) - return + return } // DecodeField decodes a x448 session key encryption as diff --git a/openpgp/x448/x448_test.go b/openpgp/x448/x448_test.go index a62ed58a0..73d38b1da 100644 --- a/openpgp/x448/x448_test.go +++ b/openpgp/x448/x448_test.go @@ -6,7 +6,6 @@ import ( "testing" ) - func TestGenerate(t *testing.T) { privateKey, err := GenerateKey(rand.Reader) if err != nil { From 3c375b0725b452c5a708c4b29296613082e82da3 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Thu, 20 Apr 2023 15:00:09 +0200 Subject: [PATCH 033/116] Fix one-pass-signature parsing with wrong version One-pass-signature parsing now directly returns an error if an unsuported version is parsed. --- openpgp/packet/one_pass_signature.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpgp/packet/one_pass_signature.go b/openpgp/packet/one_pass_signature.go index ceef20fdd..3673032e2 100644 --- a/openpgp/packet/one_pass_signature.go +++ b/openpgp/packet/one_pass_signature.go @@ -35,7 +35,7 @@ func (ops *OnePassSignature) parse(r io.Reader) (err error) { return } if buf[0] != 3 && buf[0] != 6 { - err = errors.UnsupportedError("one-pass-signature packet version " + strconv.Itoa(int(buf[0]))) + return errors.UnsupportedError("one-pass-signature packet version " + strconv.Itoa(int(buf[0]))) } ops.Version = int(buf[0]) From c92612bca608d70a0f5caade572dc404c5dd7c4e Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Fri, 21 Apr 2023 08:53:43 +0200 Subject: [PATCH 034/116] Only check HashTag when sig.Version greater than 4 Addresses https://github.com/ProtonMail/go-crypto/issues/107. --- openpgp/packet/public_key.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpgp/packet/public_key.go b/openpgp/packet/public_key.go index 0e3ae7048..e550231d2 100644 --- a/openpgp/packet/public_key.go +++ b/openpgp/packet/public_key.go @@ -745,7 +745,8 @@ func (pk *PublicKey) VerifySignature(signed hash.Hash, sig *Signature) (err erro } signed.Write(sig.HashSuffix) hashBytes := signed.Sum(nil) - if sig.Version == 5 && (hashBytes[0] != sig.HashTag[0] || hashBytes[1] != sig.HashTag[1]) { + // see discussion https://github.com/ProtonMail/go-crypto/issues/107 + if sig.Version >= 5 && hashBytes[0] != sig.HashTag[0] || hashBytes[1] != sig.HashTag[1] { return errors.SignatureError("hash tag doesn't match") } From 51e16b3a7fab0637bf44c77b8a8c351a36d9c634 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Mon, 24 Apr 2023 10:47:00 +0200 Subject: [PATCH 035/116] Update HKDF in X25519 and X448 The input to the HKDF function now includes the ephemeral public key and the recipient public key as described in the latest version of the crypto refresh. https://gitlab.com/openpgp-wg/rfc4880bis/-/merge_requests/278 --- openpgp/x25519/x25519.go | 11 ++++++++--- openpgp/x448/x448.go | 11 ++++++++--- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/openpgp/x25519/x25519.go b/openpgp/x25519/x25519.go index 5af5f2fa9..859d9726f 100644 --- a/openpgp/x25519/x25519.go +++ b/openpgp/x25519/x25519.go @@ -95,7 +95,7 @@ func Encrypt(rand io.Reader, publicKey *PublicKey, sessionKey []byte) (ephemeral return } // Derive the encryption key from the shared secret - encryptionKey := applyHKDF(shared[:]) + encryptionKey := applyHKDF(ephemeralPublic[:], publicKey.Point[:], shared[:]) ephemeralPublicKey = &PublicKey{ Point: ephemeralPublic[:], } @@ -122,13 +122,18 @@ func Decrypt(privateKey *PrivateKey, ephemeralPublicKey *PublicKey, ciphertext [ return } // Derive the encryption key from the shared secret - encryptionKey := applyHKDF(shared[:]) + encryptionKey := applyHKDF(ephemeralPublicKey.Point[:], privateKey.PublicKey.Point[:], shared[:]) // Decrypt the session key with aes key wrapping encodedSessionKey, err = keywrap.Unwrap(encryptionKey, ciphertext) return } -func applyHKDF(inputKey []byte) []byte { +func applyHKDF(ephemeralPublicKey []byte, publicKey []byte, sharedSecret []byte) []byte { + inputKey := make([]byte, 3*PointSize) + // ephemeral public key | recipient public key | shared secret + subtle.ConstantTimeCopy(1, inputKey[:PointSize], ephemeralPublicKey) + subtle.ConstantTimeCopy(1, inputKey[PointSize:2*PointSize], publicKey) + subtle.ConstantTimeCopy(1, inputKey[2*PointSize:], sharedSecret) hkdfReader := hkdf.New(sha256.New, inputKey, []byte{}, []byte(hkdfInfo)) encryptionKey := make([]byte, aes128KeySize) _, _ = io.ReadFull(hkdfReader, encryptionKey) diff --git a/openpgp/x448/x448.go b/openpgp/x448/x448.go index e54ec9ef9..c5212e5fa 100644 --- a/openpgp/x448/x448.go +++ b/openpgp/x448/x448.go @@ -95,7 +95,7 @@ func Encrypt(rand io.Reader, publicKey *PublicKey, sessionKey []byte) (ephemeral return } // Derive the encryption key from the shared secret - encryptionKey := applyHKDF(shared[:]) + encryptionKey := applyHKDF(ephemeralPublic[:], publicKey.Point[:], shared[:]) ephemeralPublicKey = &PublicKey{ Point: ephemeralPublic[:], } @@ -122,13 +122,18 @@ func Decrypt(privateKey *PrivateKey, ephemeralPublicKey *PublicKey, ciphertext [ return } // Derive the encryption key from the shared secret - encryptionKey := applyHKDF(shared[:]) + encryptionKey := applyHKDF(ephemeralPublicKey.Point[:], privateKey.PublicKey.Point[:], shared[:]) // Decrypt the session key with aes key wrapping encodedSessionKey, err = keywrap.Unwrap(encryptionKey, ciphertext) return } -func applyHKDF(inputKey []byte) []byte { +func applyHKDF(ephemeralPublicKey []byte, publicKey []byte, sharedSecret []byte) []byte { + inputKey := make([]byte, 3*PointSize) + // ephemeral public key | recipient public key | shared secret + subtle.ConstantTimeCopy(1, inputKey[:PointSize], ephemeralPublicKey) + subtle.ConstantTimeCopy(1, inputKey[PointSize:2*PointSize], publicKey) + subtle.ConstantTimeCopy(1, inputKey[2*PointSize:], sharedSecret) hkdfReader := hkdf.New(sha512.New, inputKey, []byte{}, []byte(hkdfInfo)) encryptionKey := make([]byte, aesKeySize) _, _ = io.ReadFull(hkdfReader, encryptionKey) From f90e664d219136d49485e09755edbf1f2b81dbfc Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Mon, 24 Apr 2023 13:52:02 +0200 Subject: [PATCH 036/116] Add CRC24 armor footer When forming ASCII Armor, the CRC24 footer should not be generated according to the crypto refresh. However, due to a compatibility issue with GnuPG, we generate them for now and pospone the removal to version 2 of go-crypto. --- openpgp/armor/encode.go | 70 +++++++++++++++++++++++++++++++++++------ 1 file changed, 60 insertions(+), 10 deletions(-) diff --git a/openpgp/armor/encode.go b/openpgp/armor/encode.go index f2d6bc823..4ebaee718 100644 --- a/openpgp/armor/encode.go +++ b/openpgp/armor/encode.go @@ -10,9 +10,28 @@ import ( ) var armorHeaderSep = []byte(": ") +var blockEnd = []byte("\n=") var newline = []byte("\n") var armorEndOfLineOut = []byte("-----\n") +const crc24Init = 0xb704ce +const crc24Poly = 0x1864cfb +const crc24Mask = 0xffffff + +// crc24 calculates the OpenPGP checksum as specified in RFC 4880, section 6.1 +func crc24(crc uint32, d []byte) uint32 { + for _, b := range d { + crc ^= uint32(b) << 16 + for i := 0; i < 8; i++ { + crc <<= 1 + if crc&0x1000000 != 0 { + crc ^= crc24Poly + } + } + } + return crc +} + // writeSlices writes its arguments to the given Writer. func writeSlices(out io.Writer, slices ...[]byte) (err error) { for _, s := range slices { @@ -98,13 +117,18 @@ func (l *lineBreaker) Close() (err error) { // // encoding -> base64 encoder -> lineBreaker -> out type encoding struct { - out io.Writer - breaker *lineBreaker - b64 io.WriteCloser - blockType []byte + out io.Writer + breaker *lineBreaker + b64 io.WriteCloser + crc uint32 + crcEnabled bool + blockType []byte } func (e *encoding) Write(data []byte) (n int, err error) { + if e.crcEnabled { + e.crc = crc24(e.crc, data) + } return e.b64.Write(data) } @@ -115,12 +139,21 @@ func (e *encoding) Close() (err error) { } e.breaker.Close() + if e.crcEnabled { + var checksumBytes [3]byte + checksumBytes[0] = byte(e.crc >> 16) + checksumBytes[1] = byte(e.crc >> 8) + checksumBytes[2] = byte(e.crc) + + var b64ChecksumBytes [4]byte + base64.StdEncoding.Encode(b64ChecksumBytes[:], checksumBytes[:]) + + return writeSlices(e.out, blockEnd, b64ChecksumBytes[:], newline, armorEnd, e.blockType, armorEndOfLine) + } return writeSlices(e.out, newline, armorEnd, e.blockType, armorEndOfLine) } -// Encode returns a WriteCloser which will encode the data written to it in -// OpenPGP armor. -func Encode(out io.Writer, blockType string, headers map[string]string) (w io.WriteCloser, err error) { +func encode(out io.Writer, blockType string, headers map[string]string, checksum bool) (w io.WriteCloser, err error) { bType := []byte(blockType) err = writeSlices(out, armorStart, bType, armorEndOfLineOut) if err != nil { @@ -140,10 +173,27 @@ func Encode(out io.Writer, blockType string, headers map[string]string) (w io.Wr } e := &encoding{ - out: out, - breaker: newLineBreaker(out, 64), - blockType: bType, + out: out, + breaker: newLineBreaker(out, 64), + blockType: bType, + crc: crc24Init, + crcEnabled: checksum, } e.b64 = base64.NewEncoder(base64.StdEncoding, e.breaker) return e, nil } + +// Encode returns a WriteCloser which will encode the data written to it in +// OpenPGP armor. +func Encode(out io.Writer, blockType string, headers map[string]string) (w io.WriteCloser, err error) { + return encode(out, blockType, headers, true) +} + +// Encode returns a WriteCloser which will encode the data written to it in +// OpenPGP armor but does not generate checksum at the end. +// When forming ASCII Armor, the CRC24 footer SHOULD NOT be generated, +// unless interoperability with implementations that require the CRC24 footer +// to be present is a concern. +func EncodeWithoutChecksum(out io.Writer, blockType string, headers map[string]string) (w io.WriteCloser, err error) { + return encode(out, blockType, headers, false) +} From 98de8c72660e858a29b3ff3a1681a7b09f364a2e Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Fri, 5 May 2023 13:44:47 +0200 Subject: [PATCH 037/116] Ensure correct key derivation when encrypting multiple keys with aead Fixes an issue in EncryptPrivateKeys, where the hkdf was applied to the last hkdf derived key instead of the s2k dervided key. --- openpgp/packet/private_key.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpgp/packet/private_key.go b/openpgp/packet/private_key.go index 9a54829de..50bab10fc 100644 --- a/openpgp/packet/private_key.go +++ b/openpgp/packet/private_key.go @@ -761,9 +761,11 @@ func EncryptPrivateKeys(keys []*PrivateKey, passphrase []byte, config *Config) e s2kType = S2KAEAD key.aead = config.AEAD().Mode() key.cipher = config.Cipher() - encryptionKey = key.applyHKDF(encryptionKey) + derivedKey := key.applyHKDF(encryptionKey) + err = key.encrypt(derivedKey, params, s2kType, config.Cipher(), config.Random()) + } else { + err = key.encrypt(encryptionKey, params, s2kType, config.Cipher(), config.Random()) } - err = key.encrypt(encryptionKey, params, s2kType, config.Cipher(), config.Random()) if err != nil { return err } From 3573e94588875c6e2f7afc05e8cf2df7f0e5bf3b Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Fri, 5 May 2023 14:01:54 +0200 Subject: [PATCH 038/116] Add testcase for reading a v6 PKESK encrypted message Adds a testcase for the following crypto refresh test vector: https://gitlab.com/openpgp-wg/rfc4880bis/-/merge_requests/302 --- openpgp/read_test.go | 68 +++++++++++++++++++++++++++++++++ openpgp/read_write_test_data.go | 11 ++++++ 2 files changed, 79 insertions(+) diff --git a/openpgp/read_test.go b/openpgp/read_test.go index bffa1c538..0681f13f7 100644 --- a/openpgp/read_test.go +++ b/openpgp/read_test.go @@ -588,6 +588,74 @@ func TestSignatureV3Message(t *testing.T) { return } +func TestReadV6PKESKMessage(t *testing.T) { + key, err := ReadArmoredKeyRing(strings.NewReader(v6PrivKey)) + if err != nil { + t.Error(err) + return + } + msgReader, err := armor.Decode(strings.NewReader(v6PrivKeyMsg)) + if err != nil { + t.Error(err) + return + } + md, err := ReadMessage(msgReader.Body, key, nil, nil) + if err != nil { + t.Error(err) + return + } + contents, err := ioutil.ReadAll(md.UnverifiedBody) + if err != nil { + t.Error(err) + return + } + if string(contents) != "Hello, world!" { + t.Errorf("decrypted message is wrong: %s", contents) + } +} + +func TestSymmetricAeadGcmOpenPGPJsMessage(t *testing.T) { + passphrase := []byte("test") + file, err := os.Open("test_data/aead-sym-message.asc") + if err != nil { + t.Fatal(err) + } + armoredEncryptedMessage, err := ioutil.ReadAll(file) + if err != nil { + t.Fatal(err) + } + // Unarmor string + raw, err := armor.Decode(strings.NewReader(string(armoredEncryptedMessage))) + if err != nil { + t.Error(err) + return + } + // Mock passphrase prompt + promptFunc := func(keys []Key, symmetric bool) ([]byte, error) { + return passphrase, nil + } + // Decrypt message + md, err := ReadMessage(raw.Body, nil, promptFunc, nil) + if err != nil { + t.Error(err) + return + } + contents, err := ioutil.ReadAll(md.UnverifiedBody) + if err != nil { + t.Errorf("error reading UnverifiedBody: %s", err) + } + + // The plaintext is https://www.gutenberg.org/cache/epub/1080/pg1080.txt + // We compare the SHA512 hashes. + wantHash := modestProposalSha512 + gotHashRaw := sha512.Sum512(contents) + gotHash := base64.StdEncoding.EncodeToString(gotHashRaw[:]) + + if wantHash != gotHash { + t.Fatal("Did not decrypt OpenPGPjs message correctly") + } +} + func TestSymmetricDecryptionArgon2(t *testing.T) { // Appendix IETF OpenPGP crypto refresh draft v08 A.8.1 passphrase := []byte("password") diff --git a/openpgp/read_write_test_data.go b/openpgp/read_write_test_data.go index 19d806490..32033bd9d 100644 --- a/openpgp/read_write_test_data.go +++ b/openpgp/read_write_test_data.go @@ -190,6 +190,17 @@ M0g12vYxoWM8Y81W+bHBw805I8kWVkXU6vFOi+HWvv/ira7ofJu16NnoUkhclkUr k0mXubZvyl4GBg== -----END PGP PRIVATE KEY BLOCK-----` +// See OpenPGP crypto refresh merge request: +// https://gitlab.com/openpgp-wg/rfc4880bis/-/merge_requests/302 +const v6PrivKeyMsg = `-----BEGIN PGP MESSAGE----- + +wVwGBhLIPx5wb2MI/hUaQXdDofAzeQ6T6ZeEiNHbN42pkwiFGYfPGNXxtT+BfM5a +AEzzk8yJWL3cBl8l+Er1CbF902dkGN6jVUN5VmF5AeBpV/vKimpHpbUVPo06t9Jp +AgcCBmFkFlNb4LBxbWDgUqVsTEB/nrNrDvr+mtCg35sDPGmiG6nr0sDslb9WnSXJ +me5KPeFwWPQN+otMaCvj+7vXsn6w9Zu1AF+Ax8b0A4jDCtQGqwUT3Nb5/XN2Vihu +EXfQD4iK2zHE +-----END PGP MESSAGE-----` + // See https://gitlab.com/openpgp-wg/rfc4880bis/-/merge_requests/274 // decryption password: "correct horse battery staple" const v6ArgonSealedPrivKey = `-----BEGIN PGP PRIVATE KEY BLOCK----- From 509d55fedaf6313b4c91bfa4537ddbc4e79013a6 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Wed, 24 May 2023 12:22:28 +0200 Subject: [PATCH 039/116] Update PKESKv6 serialization/parsing to the new packet format This commit updates PKESKv6 serialization/parsing to the newest version of the standart, see: https://gitlab.com/openpgp-wg/rfc4880bis/-/merge_requests/304 --- openpgp/packet/encrypted_key.go | 63 ++++++++++++++++++++------------- openpgp/read_write_test_data.go | 12 +++---- 2 files changed, 45 insertions(+), 30 deletions(-) diff --git a/openpgp/packet/encrypted_key.go b/openpgp/packet/encrypted_key.go index e37ab82a9..cbe7cf9c1 100644 --- a/openpgp/packet/encrypted_key.go +++ b/openpgp/packet/encrypted_key.go @@ -50,29 +50,39 @@ func (e *EncryptedKey) parse(r io.Reader) (err error) { return errors.UnsupportedError("unknown EncryptedKey version " + strconv.Itoa(int(buf[0]))) } if e.Version == 6 { + //Read a one-octet size of the following two fields. _, err = readFull(r, buf[:1]) if err != nil { return } - e.KeyVersion = int(buf[0]) - if e.KeyVersion != 0 && e.KeyVersion != 4 && e.KeyVersion != 6 { - return errors.UnsupportedError("unknown public key version " + strconv.Itoa(e.KeyVersion)) - } - var fingerprint []byte - if e.KeyVersion == 6 { - fingerprint = make([]byte, 32) - } else if e.KeyVersion == 4 { - fingerprint = make([]byte, 20) - } - _, err = readFull(r, fingerprint) - if err != nil { - return - } - e.KeyFingerprint = fingerprint - if e.KeyVersion == 6 { - e.KeyId = binary.BigEndian.Uint64(e.KeyFingerprint[:8]) - } else if e.KeyVersion == 4 { - e.KeyId = binary.BigEndian.Uint64(e.KeyFingerprint[12:20]) + // The size may also be zero, and the key version and + // fingerprint omitted for an "anonymous recipient" + if buf[0] != 0 { + // non-anonymous case + _, err = readFull(r, buf[:1]) + if err != nil { + return + } + e.KeyVersion = int(buf[0]) + if e.KeyVersion != 4 && e.KeyVersion != 6 { + return errors.UnsupportedError("unknown public key version " + strconv.Itoa(e.KeyVersion)) + } + var fingerprint []byte + if e.KeyVersion == 6 { + fingerprint = make([]byte, 32) + } else if e.KeyVersion == 4 { + fingerprint = make([]byte, 20) + } + _, err = readFull(r, fingerprint) + if err != nil { + return + } + e.KeyFingerprint = fingerprint + if e.KeyVersion == 6 { + e.KeyId = binary.BigEndian.Uint64(e.KeyFingerprint[:8]) + } else if e.KeyVersion == 4 { + e.KeyId = binary.BigEndian.Uint64(e.KeyFingerprint[12:20]) + } } } else { _, err = readFull(r, buf[:8]) @@ -308,7 +318,7 @@ func (e *EncryptedKey) Serialize(w io.Writer) error { // If aeadSupported is set, PKESK v6 is used else v4. // If config is nil, sensible defaults will be used. func SerializeEncryptedKeyAEAD(w io.Writer, pub *PublicKey, cipherFunc CipherFunction, aeadSupported bool, key []byte, config *Config) error { - var buf [35]byte // max possible header size is v6 + var buf [36]byte // max possible header size is v6 lenHeaderWritten := 1 version := 3 @@ -333,11 +343,16 @@ func SerializeEncryptedKeyAEAD(w io.Writer, pub *PublicKey, cipherFunc CipherFun if version == 6 { if pub != nil { - buf[1] = byte(pub.Version) - copy(buf[2:len(pub.Fingerprint)+2], pub.Fingerprint) - lenHeaderWritten += len(pub.Fingerprint) + 1 + // A one-octet size of the following two fields. + buf[1] = byte(1 + len(pub.Fingerprint)) + // A one octet key version number. + buf[2] = byte(pub.Version) + // The fingerprint of the public key + copy(buf[3:len(pub.Fingerprint)+3], pub.Fingerprint) + lenHeaderWritten += len(pub.Fingerprint) + 2 } else { - // anonymous case + // The size may also be zero, and the key version + // and fingerprint omitted for an "anonymous recipient" buf[1] = 0 lenHeaderWritten += 1 } diff --git a/openpgp/read_write_test_data.go b/openpgp/read_write_test_data.go index 32033bd9d..f2e5ad494 100644 --- a/openpgp/read_write_test_data.go +++ b/openpgp/read_write_test_data.go @@ -191,14 +191,14 @@ k0mXubZvyl4GBg== -----END PGP PRIVATE KEY BLOCK-----` // See OpenPGP crypto refresh merge request: -// https://gitlab.com/openpgp-wg/rfc4880bis/-/merge_requests/302 +// https://gitlab.com/openpgp-wg/rfc4880bis/-/merge_requests/304 const v6PrivKeyMsg = `-----BEGIN PGP MESSAGE----- -wVwGBhLIPx5wb2MI/hUaQXdDofAzeQ6T6ZeEiNHbN42pkwiFGYfPGNXxtT+BfM5a -AEzzk8yJWL3cBl8l+Er1CbF902dkGN6jVUN5VmF5AeBpV/vKimpHpbUVPo06t9Jp -AgcCBmFkFlNb4LBxbWDgUqVsTEB/nrNrDvr+mtCg35sDPGmiG6nr0sDslb9WnSXJ -me5KPeFwWPQN+otMaCvj+7vXsn6w9Zu1AF+Ax8b0A4jDCtQGqwUT3Nb5/XN2Vihu -EXfQD4iK2zHE +wV0GIQYSyD8ecG9jCP4VGkF3Q6HwM3kOk+mXhIjR2zeNqZMIhRmHzxjV8bU/gXzO +WgBM85PMiVi93AZfJfhK9QmxfdNnZBjeo1VDeVZheQHgaVf7yopqR6W1FT6NOrfS +aQIHAgZhZBZTW+CwcW1g4FKlbExAf56zaw76/prQoN+bAzxpohup69LA7JW/Vp0l +yZnuSj3hcFj0DfqLTGgr4/u717J+sPWbtQBfgMfG9AOIwwrUBqsFE9zW+f1zdlYo +bhF30A+IitsxxA== -----END PGP MESSAGE-----` // See https://gitlab.com/openpgp-wg/rfc4880bis/-/merge_requests/274 From c1f4c6f6dffa8ee0de740e316485507da95bf53e Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Wed, 24 May 2023 12:41:19 +0200 Subject: [PATCH 040/116] Update one-pass signature v6 packets to latest crypto refresh version This commit updates the serialization/parse methods of one-pass signature packets to the latest updated version of the crypto refresh. See: https://gitlab.com/openpgp-wg/rfc4880bis/-/merge_requests/305 --- openpgp/packet/one_pass_signature.go | 32 +++++++++------------------- openpgp/read_test.go | 24 ++++++++++++++++++++- openpgp/read_write_test_data.go | 11 ++++++++++ 3 files changed, 44 insertions(+), 23 deletions(-) diff --git a/openpgp/packet/one_pass_signature.go b/openpgp/packet/one_pass_signature.go index 3673032e2..f393c4063 100644 --- a/openpgp/packet/one_pass_signature.go +++ b/openpgp/packet/one_pass_signature.go @@ -70,23 +70,8 @@ func (ops *OnePassSignature) parse(r io.Reader) (err error) { return } ops.Salt = salt - } else if ops.Version == 3 { - _, err = readFull(r, buf[:8]) - if err != nil { - return - } - ops.KeyId = binary.BigEndian.Uint64(buf[:8]) - } - if ops.Version == 6 { - _, err = readFull(r, buf[:1]) - if err != nil { - return - } - if int(buf[0]) != 6 { - err = errors.StructuralError("version 6 signatures require version 6 keys") - return - } + // Only for v6 packets, 32 octets of the fingerprint of the signing key. fingerprint := make([]byte, 32) _, err = readFull(r, fingerprint) if err != nil { @@ -94,6 +79,12 @@ func (ops *OnePassSignature) parse(r io.Reader) (err error) { } ops.KeyFingerprint = fingerprint ops.KeyId = binary.BigEndian.Uint64(ops.KeyFingerprint[:8]) + } else { + _, err = readFull(r, buf[:8]) + if err != nil { + return + } + ops.KeyId = binary.BigEndian.Uint64(buf[:8]) } _, err = readFull(r, buf[:1]) @@ -106,10 +97,11 @@ func (ops *OnePassSignature) parse(r io.Reader) (err error) { // Serialize marshals the given OnePassSignature to w. func (ops *OnePassSignature) Serialize(w io.Writer) error { + //v3 length 1+1+1+1+8+1 = packetLength := 13 if ops.Version == 6 { - // v6 length - packetLength = 39 + len(ops.Salt) + // v6 length 1+1+1+1+1+len(salt)+32+1 = + packetLength = 38 + len(ops.Salt) } if err := serializeHeader(w, packetTypeOnePassSignature, packetLength); err != nil { @@ -143,10 +135,6 @@ func (ops *OnePassSignature) Serialize(w io.Writer) error { } // write fingerprint v6 signatures - _, err = w.Write([]byte{byte(ops.Version)}) - if err != nil { - return err - } _, err = w.Write(ops.KeyFingerprint) if err != nil { return err diff --git a/openpgp/read_test.go b/openpgp/read_test.go index 0681f13f7..ac1347208 100644 --- a/openpgp/read_test.go +++ b/openpgp/read_test.go @@ -588,7 +588,7 @@ func TestSignatureV3Message(t *testing.T) { return } -func TestReadV6PKESKMessage(t *testing.T) { +func TestReadV6Messages(t *testing.T) { key, err := ReadArmoredKeyRing(strings.NewReader(v6PrivKey)) if err != nil { t.Error(err) @@ -612,6 +612,28 @@ func TestReadV6PKESKMessage(t *testing.T) { if string(contents) != "Hello, world!" { t.Errorf("decrypted message is wrong: %s", contents) } + + msgReader, err = armor.Decode(strings.NewReader(v6PrivKeyInlineSignMsg)) + if err != nil { + t.Error(err) + return + } + md, err = ReadMessage(msgReader.Body, key, nil, nil) + if err != nil { + t.Error(err) + return + } + contents, err = ioutil.ReadAll(md.UnverifiedBody) + if err != nil { + t.Error(err) + return + } + if md.SignatureError != nil { + t.Error("expected no signature error, got:", md.SignatureError) + } + if string(contents) != "Hello, world!" { + t.Errorf("inline message is wrong: %s", contents) + } } func TestSymmetricAeadGcmOpenPGPJsMessage(t *testing.T) { diff --git a/openpgp/read_write_test_data.go b/openpgp/read_write_test_data.go index f2e5ad494..c8a482145 100644 --- a/openpgp/read_write_test_data.go +++ b/openpgp/read_write_test_data.go @@ -201,6 +201,17 @@ yZnuSj3hcFj0DfqLTGgr4/u717J+sPWbtQBfgMfG9AOIwwrUBqsFE9zW+f1zdlYo bhF30A+IitsxxA== -----END PGP MESSAGE-----` +// See OpenPGP crypto refresh merge request: +// https://gitlab.com/openpgp-wg/rfc4880bis/-/merge_requests/305 +const v6PrivKeyInlineSignMsg = `-----BEGIN PGP MESSAGE----- + +wV0GIQYSyD8ecG9jCP4VGkF3Q6HwM3kOk+mXhIjR2zeNqZMIhRmHzxjV8bU/gXzO +WgBM85PMiVi93AZfJfhK9QmxfdNnZBjeo1VDeVZheQHgaVf7yopqR6W1FT6NOrfS +aQIHAgZhZBZTW+CwcW1g4FKlbExAf56zaw76/prQoN+bAzxpohup69LA7JW/Vp0l +yZnuSj3hcFj0DfqLTGgr4/u717J+sPWbtQBfgMfG9AOIwwrUBqsFE9zW+f1zdlYo +bhF30A+IitsxxA== +-----END PGP MESSAGE-----` + // See https://gitlab.com/openpgp-wg/rfc4880bis/-/merge_requests/274 // decryption password: "correct horse battery staple" const v6ArgonSealedPrivKey = `-----BEGIN PGP PRIVATE KEY BLOCK----- From 1dd6febfa5e73a86d3b55e01154b95eb5edcd122 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Tue, 30 May 2023 10:52:36 +0200 Subject: [PATCH 041/116] Remove salted hash header from signed cleartext messages The salted hash header in cleartext messages was removed from the crypto refresh: https://gitlab.com/openpgp-wg/rfc4880bis/-/merge_requests/313 This commit adds the following changes to cleartext messages: - removes the salted hash header logic - only produces ''Hash" headers if no v6 signatures are present - ignores any parsed headers in signature verification --- openpgp/clearsign/clearsign.go | 70 +++-------------------------- openpgp/clearsign/clearsign_test.go | 24 ---------- 2 files changed, 5 insertions(+), 89 deletions(-) diff --git a/openpgp/clearsign/clearsign.go b/openpgp/clearsign/clearsign.go index e168d14d9..036de7286 100644 --- a/openpgp/clearsign/clearsign.go +++ b/openpgp/clearsign/clearsign.go @@ -13,7 +13,6 @@ import ( "bufio" "bytes" "crypto" - "encoding/base64" "fmt" "hash" "io" @@ -53,8 +52,6 @@ var end = []byte("\n-----END PGP SIGNATURE-----") var crlf = []byte("\r\n") var lf = byte('\n') -const saltedHashHeader string = "SaltedHash" -const saltedHashKey string = "Saltedhash" const hashHeader string = "Hash" // getLine returns the first \r\n or \n delineated line from the given byte @@ -137,10 +134,8 @@ func Decode(data []byte) (b *Block, rest []byte) { val = strings.TrimSpace(val) b.Headers.Add(key, val) } - } else if key == saltedHashHeader { - val = strings.TrimSpace(val) - b.Headers.Add(saltedHashKey, val) } else { + // Only "Hash" headers are allowed. return nil, data } } @@ -402,18 +397,9 @@ func EncodeMulti(w io.Writer, privateKeys []*packet.PrivateKey, config *packet.C return } // write headers - if len(salts) > 0 { - for _, salt := range salts { - encodedSalt := base64.RawStdEncoding.EncodeToString(salt) - if _, err = buffered.WriteString(fmt.Sprintf("%s: %s:%s", saltedHashHeader, name, encodedSalt)); err != nil { - return - } - if err = buffered.WriteByte(lf); err != nil { - return - } - } - } - if len(salts) < len(hashers) { + nonV6 := len(salts) < len(hashers) + // Crypto refresh: Headers SHOULD NOT be emitted + if nonV6 { // Emit header if non v6 signatures are present for compatibility if _, err = buffered.WriteString(fmt.Sprintf("%s: %s", hashHeader, name)); err != nil { return } @@ -447,34 +433,7 @@ func EncodeMulti(w io.Writer, privateKeys []*packet.PrivateKey, config *packet.C // VerifySignature checks a clearsigned message signature, and checks that the // hash algorithm in the header matches the hash algorithm in the signature. func (b *Block) VerifySignature(keyring openpgp.KeyRing, config *packet.Config) (signer *openpgp.Entity, err error) { - var expectedHashes []crypto.Hash - var expectedSaltedHashes []*packet.SaltedHashSpecifier - - // Process salted hash headers (v6) - for _, value := range b.Headers[saltedHashKey] { - var expectedSaltedHash *packet.SaltedHashSpecifier - expectedSaltedHash, err = getAlgorithmAndSalt(value) - if err != nil { - return - } - expectedSaltedHashes = append(expectedSaltedHashes, expectedSaltedHash) - } - // Process hash headers - for _, name := range b.Headers[hashHeader] { - expectedHash := nameToHash(name) - if uint8(expectedHash) == 0 { - return nil, errors.StructuralError("unknown hash algorithm in cleartext message headers") - } - expectedHashes = append(expectedHashes, expectedHash) - } - - // If neither a "Hash" nor a "SaltedHash" Armor Header is given, or the message - // digest algorithms (and salts) used in the signatures do not match the information in the headers, - // the signature MUST be considered invalid. - if len(expectedHashes) == 0 && len(expectedSaltedHashes) == 0 { - return nil, errors.StructuralError("signature is invalid: no hash or salted hash header present in message") - } - return openpgp.CheckDetachedSignatureAndSaltedHash(keyring, bytes.NewBuffer(b.Bytes), b.ArmoredSignature.Body, expectedHashes, expectedSaltedHashes, config) + return openpgp.CheckDetachedSignature(keyring, bytes.NewBuffer(b.Bytes), b.ArmoredSignature.Body, config) } // nameOfHash returns the OpenPGP name for the given hash, or the empty string @@ -518,22 +477,3 @@ func nameToHash(h string) crypto.Hash { } return crypto.Hash(0) } - -func getAlgorithmAndSalt(value string) (*packet.SaltedHashSpecifier, error) { - params := strings.Split(value, ":") - if len(params) != 2 { - return nil, errors.StructuralError(fmt.Sprintf("salted hash cleartext message header value has the wrong format: %s", value)) - } - algo := nameToHash(strings.TrimSpace(params[0])) - if uint8(algo) == 0 { - return nil, errors.StructuralError("unknown hash algorithm in cleartext message headers") - } - salt, err := base64.RawStdEncoding.DecodeString(strings.TrimSpace(params[1])) - if err != nil { - return nil, errors.StructuralError(fmt.Sprintf("salted hash cleartext message header value has the wrong format: %s", value)) - } - return &packet.SaltedHashSpecifier{ - Hash: algo, - Salt: salt, - }, nil -} diff --git a/openpgp/clearsign/clearsign_test.go b/openpgp/clearsign/clearsign_test.go index 02e71a9bc..d0dbae885 100644 --- a/openpgp/clearsign/clearsign_test.go +++ b/openpgp/clearsign/clearsign_test.go @@ -96,11 +96,6 @@ func TestVerifyV6(t *testing.T) { if err != nil { t.Errorf("failed to verify signature: %s", err) } - b, _ = Decode([]byte(clearSignV6Error)) - _, err = b.VerifySignature(keyring, nil) - if err == nil { - t.Error("signature with no salted header should be invalid") - } } func TestSigning(t *testing.T) { @@ -446,25 +441,6 @@ I8kWVkXU6vFOi+HWvv/ira7ofJu16NnoUkhclkUrk0mXubZvyl4GBg== // https://gitlab.com/openpgp-wg/rfc4880bis/-/merge_requests/275 const clearSignV6 = `-----BEGIN PGP SIGNED MESSAGE----- -SaltedHash: SHA512:dklfUCGIkPf14u48GCJRT3BQD1UdhuXJIeQE40pT+6w - -What we need from the grocery store: - -- - tofu -- - vegetables -- - noodles - ------BEGIN PGP SIGNATURE----- - -wpgGARsKAAAAKQWCY5ijYyIhBssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce6 -2azJAAAAAGk2IHZJX1AhiJD39eLuPBgiUU9wUA9VHYblySHkBONKU/usJ9BvuAqo -/FvLFuGWMbKAdA+epq7V4HOtAPlBWmU8QOd6aud+aSunHQaaEJ+iTFjP2OMW0KBr -NK2ay45cX1IVAQ== ------END PGP SIGNATURE-----` - -// https://gitlab.com/openpgp-wg/rfc4880bis/-/merge_requests/275 -const clearSignV6Error = `-----BEGIN PGP SIGNED MESSAGE----- -Hash: SHA512 What we need from the grocery store: From 21092dc5b30ba246f816e5f4cd1e270767af688b Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Tue, 8 Aug 2023 11:37:44 +0200 Subject: [PATCH 042/116] Copy go-crypto v2 API code to openpgp/v2 --- openpgp/v2/canonical_text.go | 111 ++ openpgp/v2/canonical_text_test.go | 52 + openpgp/v2/hash.go | 24 + openpgp/v2/key_generation.go | 469 +++++++ openpgp/v2/keys.go | 781 +++++++++++ openpgp/v2/keys_test.go | 2078 ++++++++++++++++++++++++++++ openpgp/v2/keys_test_data.go | 538 +++++++ openpgp/v2/keys_v5_test.go | 206 +++ openpgp/v2/keys_v6_test.go | 203 +++ openpgp/v2/read.go | 779 +++++++++++ openpgp/v2/read_test.go | 1044 ++++++++++++++ openpgp/v2/read_write_test_data.go | 754 ++++++++++ openpgp/v2/subkeys.go | 204 +++ openpgp/v2/user.go | 215 +++ openpgp/v2/write.go | 866 ++++++++++++ openpgp/v2/write_test.go | 991 +++++++++++++ 16 files changed, 9315 insertions(+) create mode 100644 openpgp/v2/canonical_text.go create mode 100644 openpgp/v2/canonical_text_test.go create mode 100644 openpgp/v2/hash.go create mode 100644 openpgp/v2/key_generation.go create mode 100644 openpgp/v2/keys.go create mode 100644 openpgp/v2/keys_test.go create mode 100644 openpgp/v2/keys_test_data.go create mode 100644 openpgp/v2/keys_v5_test.go create mode 100644 openpgp/v2/keys_v6_test.go create mode 100644 openpgp/v2/read.go create mode 100644 openpgp/v2/read_test.go create mode 100644 openpgp/v2/read_write_test_data.go create mode 100644 openpgp/v2/subkeys.go create mode 100644 openpgp/v2/user.go create mode 100644 openpgp/v2/write.go create mode 100644 openpgp/v2/write_test.go diff --git a/openpgp/v2/canonical_text.go b/openpgp/v2/canonical_text.go new file mode 100644 index 000000000..64dd4006a --- /dev/null +++ b/openpgp/v2/canonical_text.go @@ -0,0 +1,111 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package v2 + +import ( + "bytes" + "hash" + "io" +) + +// NewCanonicalTextHash reformats text written to it into the canonical +// form and then applies the hash h. See RFC 4880, section 5.2.1. +func NewCanonicalTextHash(h hash.Hash) hash.Hash { + return &canonicalTextHash{h, 0} +} + +// NewCanonicalTextWriteCloser reformats text written to it into the canonical +// form. See RFC 4880, section 5.2.1. +func NewCanonicalTextWriteCloser(w io.WriteCloser) io.WriteCloser { + return &canonicalTextWriteCloser{w, 0} +} + +// NewCanonicalTextReader reformats text read from it into the canonical +// form. See RFC 4880, section 5.2.1. +func NewCanonicalTextReader(r io.Reader) io.Reader { + return &canonicalTextReader{r, bytes.NewBuffer(nil), 0} +} + +type canonicalTextHash struct { + h hash.Hash + s int +} + +var newline = []byte{'\r', '\n'} + +func writeCanonical(cw io.Writer, buf []byte, s *int) (int, error) { + start := 0 + for i, c := range buf { + switch *s { + case 0: + if c == '\r' { + *s = 1 + } else if c == '\n' { + cw.Write(buf[start:i]) + cw.Write(newline) + start = i + 1 + } + case 1: + *s = 0 + } + } + + cw.Write(buf[start:]) + return len(buf), nil +} + +func (cth *canonicalTextHash) Write(buf []byte) (int, error) { + return writeCanonical(cth.h, buf, &cth.s) +} + +func (cth *canonicalTextHash) Sum(in []byte) []byte { + return cth.h.Sum(in) +} + +func (cth *canonicalTextHash) Reset() { + cth.h.Reset() + cth.s = 0 +} + +func (cth *canonicalTextHash) Size() int { + return cth.h.Size() +} + +func (cth *canonicalTextHash) BlockSize() int { + return cth.h.BlockSize() +} + +type canonicalTextWriteCloser struct { + w io.WriteCloser + s int +} + +func (tw *canonicalTextWriteCloser) Write(buf []byte) (int, error) { + return writeCanonical(tw.w, buf, &tw.s) +} + +func (tw *canonicalTextWriteCloser) Close() error { + return tw.w.Close() +} + +type canonicalTextReader struct { + r io.Reader + buffer *bytes.Buffer + s int +} + +func (tr *canonicalTextReader) Read(buf []byte) (int, error) { + if tr.buffer.Len() > 0 { + return tr.buffer.Read(buf) + } + n, err := tr.r.Read(buf) + if err != nil { + return n, err + } + if _, err = writeCanonical(tr.buffer, buf[:n], &tr.s); err != nil { + return 0, err + } + return tr.buffer.Read(buf) +} diff --git a/openpgp/v2/canonical_text_test.go b/openpgp/v2/canonical_text_test.go new file mode 100644 index 000000000..0491d7455 --- /dev/null +++ b/openpgp/v2/canonical_text_test.go @@ -0,0 +1,52 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package v2 + +import ( + "bytes" + "testing" +) + +type recordingHash struct { + buf *bytes.Buffer +} + +func (r recordingHash) Write(b []byte) (n int, err error) { + return r.buf.Write(b) +} + +func (r recordingHash) Sum(in []byte) []byte { + return append(in, r.buf.Bytes()...) +} + +func (r recordingHash) Reset() { + panic("shouldn't be called") +} + +func (r recordingHash) Size() int { + panic("shouldn't be called") +} + +func (r recordingHash) BlockSize() int { + panic("shouldn't be called") +} + +func testCanonicalText(t *testing.T, input, expected string) { + r := recordingHash{bytes.NewBuffer(nil)} + c := NewCanonicalTextHash(r) + c.Write([]byte(input)) + result := c.Sum(nil) + if expected != string(result) { + t.Errorf("input: %x got: %x want: %x", input, result, expected) + } +} + +func TestCanonicalText(t *testing.T) { + testCanonicalText(t, "foo\n", "foo\r\n") + testCanonicalText(t, "foo", "foo") + testCanonicalText(t, "foo\r\n", "foo\r\n") + testCanonicalText(t, "foo\r\nbar", "foo\r\nbar") + testCanonicalText(t, "foo\r\nbar\n\n", "foo\r\nbar\r\n\r\n") +} diff --git a/openpgp/v2/hash.go b/openpgp/v2/hash.go new file mode 100644 index 000000000..6bcb64b9e --- /dev/null +++ b/openpgp/v2/hash.go @@ -0,0 +1,24 @@ +package v2 + +import ( + "crypto" + + "github.com/ProtonMail/go-crypto/openpgp/internal/algorithm" +) + +// HashIdToHash returns a crypto.Hash which corresponds to the given OpenPGP +// hash id. +func HashIdToHash(id byte) (h crypto.Hash, ok bool) { + return algorithm.HashIdToHash(id) +} + +// HashIdToString returns the name of the hash function corresponding to the +// given OpenPGP hash id. +func HashIdToString(id byte) (name string, ok bool) { + return algorithm.HashIdToString(id) +} + +// HashToHashId returns an OpenPGP hash id which corresponds the given Hash. +func HashToHashId(h crypto.Hash) (id byte, ok bool) { + return algorithm.HashToHashId(h) +} diff --git a/openpgp/v2/key_generation.go b/openpgp/v2/key_generation.go new file mode 100644 index 000000000..49f9acef7 --- /dev/null +++ b/openpgp/v2/key_generation.go @@ -0,0 +1,469 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package v2 + +import ( + "crypto" + "crypto/rand" + "crypto/rsa" + goerrors "errors" + "io" + "math/big" + "time" + + "github.com/ProtonMail/go-crypto/openpgp/ecdh" + "github.com/ProtonMail/go-crypto/openpgp/ecdsa" + "github.com/ProtonMail/go-crypto/openpgp/ed25519" + "github.com/ProtonMail/go-crypto/openpgp/ed448" + "github.com/ProtonMail/go-crypto/openpgp/eddsa" + "github.com/ProtonMail/go-crypto/openpgp/errors" + "github.com/ProtonMail/go-crypto/openpgp/internal/algorithm" + "github.com/ProtonMail/go-crypto/openpgp/internal/ecc" + "github.com/ProtonMail/go-crypto/openpgp/packet" + "github.com/ProtonMail/go-crypto/openpgp/x25519" + "github.com/ProtonMail/go-crypto/openpgp/x448" +) + +type userIdData struct { + name, comment, email string +} + +// NewEntityWithoutId returns an Entity that contains fresh keys for signing and +// encrypting pgp messages. The key is not associated with an identity. +// This is only allowed for v6 key generation. If v6 is not enabled, +// it will return an error. +// If config is nil, sensible defaults will be used. +func NewEntityWithoutId(config *packet.Config) (*Entity, error) { + return newEntity(nil, config) +} + +// NewEntity returns an Entity that contains fresh keys with a for signing and +// encrypting pgp messages. The key is associated with a +// single identity composed of the given full name, comment and email, any of +// which may be empty but must not contain any of "()<>\x00". +// If config is nil, sensible defaults will be used. +func NewEntity(name, comment, email string, config *packet.Config) (*Entity, error) { + return newEntity(&userIdData{name, comment, email}, config) +} + +func newEntity(uid *userIdData, config *packet.Config) (*Entity, error) { + if uid == nil && !config.V6() { + return nil, errors.InvalidArgumentError("user id has to be set for non-v6 keys") + } + + creationTime := config.Now() + keyLifetimeSecs := config.KeyLifetime() + + // Generate a primary signing key + primaryPrivRaw, err := newSigner(config) + if err != nil { + return nil, err + } + primary := packet.NewSignerPrivateKey(creationTime, primaryPrivRaw) + if config.V6() { + primary.UpgradeToV6() + } + + e := &Entity{ + PrimaryKey: &primary.PublicKey, + PrivateKey: primary, + Identities: make(map[string]*Identity), + Subkeys: []Subkey{}, + DirectSignatures: []*packet.VerifiableSignature{}, + } + + if config.V6() { + e.AddDirectKeySignature(config) + } + if uid != nil { + err = e.addUserId(*uid, config, creationTime, keyLifetimeSecs, !config.V6()) + if err != nil { + return nil, err + } + } + + // NOTE: No key expiry here, but we will not return this subkey in EncryptionKey() + // if the primary/master key has expired. + err = e.addEncryptionSubkey(config, creationTime, 0) + if err != nil { + return nil, err + } + + return e, nil +} + +func (t *Entity) AddUserId(name, comment, email string, config *packet.Config) error { + creationTime := config.Now() + keyLifetimeSecs := config.KeyLifetime() + return t.addUserId(userIdData{name, comment, email}, config, creationTime, keyLifetimeSecs, !config.V6()) +} + +func (t *Entity) AddDirectKeySignature(config *packet.Config) error { + selfSignature := createSignaturePacket(&t.PrivateKey.PublicKey, packet.SigTypeDirectSignature, config) + creationTime := config.Now() + keyLifetimeSecs := config.KeyLifetime() + err := writeKeyProperties(selfSignature, creationTime, keyLifetimeSecs, config) + if err != nil { + return err + } + err = selfSignature.SignDirectKeyBinding(&t.PrivateKey.PublicKey, t.PrivateKey, config) + if err != nil { + return err + } + t.DirectSignatures = append(t.DirectSignatures, packet.NewVerifiableSig(selfSignature)) + return nil +} + +func writeKeyProperties(selfSignature *packet.Signature, creationTime time.Time, keyLifetimeSecs uint32, config *packet.Config) error { + selfSignature.CreationTime = creationTime + selfSignature.KeyLifetimeSecs = &keyLifetimeSecs + selfSignature.FlagsValid = true + selfSignature.FlagSign = true + selfSignature.FlagCertify = true + selfSignature.SEIPDv1 = true // true by default, see 5.8 vs. 5.14 + selfSignature.SEIPDv2 = config.AEAD() != nil + + // Set the PreferredHash for the SelfSignature from the packet.Config. + // If it is not the must-implement algorithm from rfc4880bis, append that. + hash, ok := algorithm.HashToHashId(config.Hash()) + if !ok { + return errors.UnsupportedError("unsupported preferred hash function") + } + + selfSignature.PreferredHash = []uint8{hash} + if config.Hash() != crypto.SHA256 { + selfSignature.PreferredHash = append(selfSignature.PreferredHash, hashToHashId(crypto.SHA256)) + } + + // Likewise for DefaultCipher. + selfSignature.PreferredSymmetric = []uint8{uint8(config.Cipher())} + if config.Cipher() != packet.CipherAES128 { + selfSignature.PreferredSymmetric = append(selfSignature.PreferredSymmetric, uint8(packet.CipherAES128)) + } + + // We set CompressionNone as the preferred compression algorithm because + // of compression side channel attacks, then append the configured + // DefaultCompressionAlgo if any is set (to signal support for cases + // where the application knows that using compression is safe). + selfSignature.PreferredCompression = []uint8{uint8(packet.CompressionNone)} + if config.Compression() != packet.CompressionNone { + selfSignature.PreferredCompression = append(selfSignature.PreferredCompression, uint8(config.Compression())) + } + + // And for DefaultMode. + modes := []uint8{uint8(config.AEAD().Mode())} + if config.AEAD().Mode() != packet.AEADModeOCB { + modes = append(modes, uint8(packet.AEADModeOCB)) + } + + // For preferred (AES256, GCM), we'll generate (AES256, GCM), (AES256, OCB), (AES128, GCM), (AES128, OCB) + for _, cipher := range selfSignature.PreferredSymmetric { + for _, mode := range modes { + selfSignature.PreferredCipherSuites = append(selfSignature.PreferredCipherSuites, [2]uint8{cipher, mode}) + } + } + return nil +} + +func (t *Entity) addUserId(userIdData userIdData, config *packet.Config, creationTime time.Time, keyLifetimeSecs uint32, writeProperties bool) error { + uid := packet.NewUserId(userIdData.name, userIdData.comment, userIdData.email) + if uid == nil { + return errors.InvalidArgumentError("user id field contained invalid characters") + } + + if _, ok := t.Identities[uid.Id]; ok { + return errors.InvalidArgumentError("user id exist") + } + + primary := t.PrivateKey + isPrimaryId := len(t.Identities) == 0 + selfSignature := createSignaturePacket(&primary.PublicKey, packet.SigTypePositiveCert, config) + if writeProperties { + err := writeKeyProperties(selfSignature, creationTime, keyLifetimeSecs, config) + if err != nil { + return err + } + } + selfSignature.IsPrimaryId = &isPrimaryId + + // User ID binding signature + err := selfSignature.SignUserId(uid.Id, &primary.PublicKey, primary, config) + if err != nil { + return err + } + t.Identities[uid.Id] = &Identity{ + Primary: t, + Name: uid.Id, + UserId: uid, + SelfCertifications: []*packet.VerifiableSignature{packet.NewVerifiableSig(selfSignature)}, + } + return nil +} + +// AddSigningSubkey adds a signing keypair as a subkey to the Entity. +// If config is nil, sensible defaults will be used. +func (e *Entity) AddSigningSubkey(config *packet.Config) error { + creationTime := config.Now() + keyLifetimeSecs := config.KeyLifetime() + + subPrivRaw, err := newSigner(config) + if err != nil { + return err + } + sub := packet.NewSignerPrivateKey(creationTime, subPrivRaw) + sub.IsSubkey = true + // Every subkey for a v6 primary key MUST be a v6 subkey. + if e.PrimaryKey.Version == 6 { + sub.UpgradeToV6() + } + + subkey := Subkey{ + PublicKey: &sub.PublicKey, + PrivateKey: sub, + } + sig := createSignaturePacket(e.PrimaryKey, packet.SigTypeSubkeyBinding, config) + sig.CreationTime = creationTime + sig.KeyLifetimeSecs = &keyLifetimeSecs + sig.FlagsValid = true + sig.FlagSign = true + sig.EmbeddedSignature = createSignaturePacket(subkey.PublicKey, packet.SigTypePrimaryKeyBinding, config) + sig.EmbeddedSignature.CreationTime = creationTime + + err = sig.EmbeddedSignature.CrossSignKey(subkey.PublicKey, e.PrimaryKey, subkey.PrivateKey, config) + if err != nil { + return err + } + + err = sig.SignKey(subkey.PublicKey, e.PrivateKey, config) + if err != nil { + return err + } + + subkey.Bindings = []*packet.VerifiableSignature{packet.NewVerifiableSig(sig)} + subkey.Primary = e + + e.Subkeys = append(e.Subkeys, subkey) + return nil +} + +// AddEncryptionSubkey adds an encryption keypair as a subkey to the Entity. +// If config is nil, sensible defaults will be used. +func (e *Entity) AddEncryptionSubkey(config *packet.Config) error { + creationTime := config.Now() + keyLifetimeSecs := config.KeyLifetime() + return e.addEncryptionSubkey(config, creationTime, keyLifetimeSecs) +} + +func (e *Entity) addEncryptionSubkey(config *packet.Config, creationTime time.Time, keyLifetimeSecs uint32) error { + subPrivRaw, err := newDecrypter(config) + if err != nil { + return err + } + sub := packet.NewDecrypterPrivateKey(creationTime, subPrivRaw) + sub.IsSubkey = true + // Every subkey for a v6 primary key MUST be a v6 subkey. + if e.PrimaryKey.Version == 6 { + sub.UpgradeToV6() + } + + subkey := Subkey{ + PublicKey: &sub.PublicKey, + PrivateKey: sub, + } + sig := createSignaturePacket(e.PrimaryKey, packet.SigTypeSubkeyBinding, config) + sig.CreationTime = creationTime + sig.KeyLifetimeSecs = &keyLifetimeSecs + sig.FlagsValid = true + sig.FlagEncryptStorage = true + sig.FlagEncryptCommunications = true + + err = sig.SignKey(subkey.PublicKey, e.PrivateKey, config) + if err != nil { + return err + } + + subkey.Bindings = []*packet.VerifiableSignature{packet.NewVerifiableSig(sig)} + + subkey.Primary = e + e.Subkeys = append(e.Subkeys, subkey) + return nil +} + +// Generates a signing key +func newSigner(config *packet.Config) (signer interface{}, err error) { + switch config.PublicKeyAlgorithm() { + case packet.PubKeyAlgoRSA: + bits := config.RSAModulusBits() + if bits < 1024 { + return nil, errors.InvalidArgumentError("bits must be >= 1024") + } + if config != nil && len(config.RSAPrimes) >= 2 { + primes := config.RSAPrimes[0:2] + config.RSAPrimes = config.RSAPrimes[2:] + return generateRSAKeyWithPrimes(config.Random(), 2, bits, primes) + } + return rsa.GenerateKey(config.Random(), bits) + case packet.PubKeyAlgoEdDSA: + curve := ecc.FindEdDSAByGenName(string(config.CurveName())) + if curve == nil { + return nil, errors.InvalidArgumentError("unsupported curve") + } + + priv, err := eddsa.GenerateKey(config.Random(), curve) + if err != nil { + return nil, err + } + return priv, nil + case packet.PubKeyAlgoECDSA: + curve := ecc.FindECDSAByGenName(string(config.CurveName())) + if curve == nil { + return nil, errors.InvalidArgumentError("unsupported curve") + } + + priv, err := ecdsa.GenerateKey(config.Random(), curve) + if err != nil { + return nil, err + } + return priv, nil + case packet.PubKeyAlgoEd25519: + priv, err := ed25519.GenerateKey(config.Random()) + if err != nil { + return nil, err + } + return priv, nil + case packet.PubKeyAlgoEd448: + priv, err := ed448.GenerateKey(config.Random()) + if err != nil { + return nil, err + } + return priv, nil + default: + return nil, errors.InvalidArgumentError("unsupported public key algorithm") + } +} + +// Generates an encryption/decryption key +func newDecrypter(config *packet.Config) (decrypter interface{}, err error) { + switch config.PublicKeyAlgorithm() { + case packet.PubKeyAlgoRSA: + bits := config.RSAModulusBits() + if bits < 1024 { + return nil, errors.InvalidArgumentError("bits must be >= 1024") + } + if config != nil && len(config.RSAPrimes) >= 2 { + primes := config.RSAPrimes[0:2] + config.RSAPrimes = config.RSAPrimes[2:] + return generateRSAKeyWithPrimes(config.Random(), 2, bits, primes) + } + return rsa.GenerateKey(config.Random(), bits) + case packet.PubKeyAlgoEdDSA, packet.PubKeyAlgoECDSA: + fallthrough // When passing EdDSA or ECDSA, we generate an ECDH subkey + case packet.PubKeyAlgoECDH: + var kdf = ecdh.KDF{ + Hash: algorithm.SHA512, + Cipher: algorithm.AES256, + } + curve := ecc.FindECDHByGenName(string(config.CurveName())) + if curve == nil { + return nil, errors.InvalidArgumentError("unsupported curve") + } + return ecdh.GenerateKey(config.Random(), curve, kdf) + case packet.PubKeyAlgoEd25519, packet.PubKeyAlgoX25519: // When passing Ed25519, we generate an X25519 subkey + return x25519.GenerateKey(config.Random()) + case packet.PubKeyAlgoEd448, packet.PubKeyAlgoX448: // When passing Ed448, we generate an X448 subkey + return x448.GenerateKey(config.Random()) + default: + return nil, errors.InvalidArgumentError("unsupported public key algorithm") + } +} + +var bigOne = big.NewInt(1) + +// generateRSAKeyWithPrimes generates a multi-prime RSA keypair of the +// given bit size, using the given random source and prepopulated primes. +func generateRSAKeyWithPrimes(random io.Reader, nprimes int, bits int, prepopulatedPrimes []*big.Int) (*rsa.PrivateKey, error) { + priv := new(rsa.PrivateKey) + priv.E = 65537 + + if nprimes < 2 { + return nil, goerrors.New("generateRSAKeyWithPrimes: nprimes must be >= 2") + } + + if bits < 1024 { + return nil, goerrors.New("generateRSAKeyWithPrimes: bits must be >= 1024") + } + + primes := make([]*big.Int, nprimes) + +NextSetOfPrimes: + for { + todo := bits + // crypto/rand should set the top two bits in each prime. + // Thus each prime has the form + // p_i = 2^bitlen(p_i) × 0.11... (in base 2). + // And the product is: + // P = 2^todo × α + // where α is the product of nprimes numbers of the form 0.11... + // + // If α < 1/2 (which can happen for nprimes > 2), we need to + // shift todo to compensate for lost bits: the mean value of 0.11... + // is 7/8, so todo + shift - nprimes * log2(7/8) ~= bits - 1/2 + // will give good results. + if nprimes >= 7 { + todo += (nprimes - 2) / 5 + } + for i := 0; i < nprimes; i++ { + var err error + if len(prepopulatedPrimes) == 0 { + primes[i], err = rand.Prime(random, todo/(nprimes-i)) + if err != nil { + return nil, err + } + } else { + primes[i] = prepopulatedPrimes[0] + prepopulatedPrimes = prepopulatedPrimes[1:] + } + + todo -= primes[i].BitLen() + } + + // Make sure that primes is pairwise unequal. + for i, prime := range primes { + for j := 0; j < i; j++ { + if prime.Cmp(primes[j]) == 0 { + continue NextSetOfPrimes + } + } + } + + n := new(big.Int).Set(bigOne) + totient := new(big.Int).Set(bigOne) + pminus1 := new(big.Int) + for _, prime := range primes { + n.Mul(n, prime) + pminus1.Sub(prime, bigOne) + totient.Mul(totient, pminus1) + } + if n.BitLen() != bits { + // This should never happen for nprimes == 2 because + // crypto/rand should set the top two bits in each prime. + // For nprimes > 2 we hope it does not happen often. + continue NextSetOfPrimes + } + + priv.D = new(big.Int) + e := big.NewInt(int64(priv.E)) + ok := priv.D.ModInverse(e, totient) + + if ok != nil { + priv.Primes = primes + priv.N = n + break + } + } + + priv.Precompute() + return priv, nil +} diff --git a/openpgp/v2/keys.go b/openpgp/v2/keys.go new file mode 100644 index 000000000..603d3ddcd --- /dev/null +++ b/openpgp/v2/keys.go @@ -0,0 +1,781 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package v2 + +import ( + goerrors "errors" + "fmt" + "io" + "time" + + "github.com/ProtonMail/go-crypto/openpgp/armor" + "github.com/ProtonMail/go-crypto/openpgp/errors" + "github.com/ProtonMail/go-crypto/openpgp/packet" +) + +// PublicKeyType is the armor type for a PGP public key. +var PublicKeyType = "PGP PUBLIC KEY BLOCK" + +// PrivateKeyType is the armor type for a PGP private key. +var PrivateKeyType = "PGP PRIVATE KEY BLOCK" + +// An Entity represents the components of an OpenPGP key: a primary public key +// (which must be a signing key), one or more identities claimed by that key, +// and zero or more subkeys, which may be encryption keys. +type Entity struct { + PrimaryKey *packet.PublicKey + PrivateKey *packet.PrivateKey + Identities map[string]*Identity // indexed by Identity.Name + Revocations []*packet.VerifiableSignature + DirectSignatures []*packet.VerifiableSignature // Direct-key self signature of the PrimaryKey (containts primary key properties in v6)} + Subkeys []Subkey +} + +// A Key identifies a specific public key in an Entity. This is either the +// Entity's primary key or a subkey. +type Key struct { + Entity *Entity + PrimarySelfSignature *packet.Signature // might be nil, if not verified + PublicKey *packet.PublicKey + PrivateKey *packet.PrivateKey + SelfSignature *packet.Signature // might be nil, if not verified +} + +// A KeyRing provides access to public and private keys. +type KeyRing interface { + // KeysById returns the set of keys that have the given key id. + // KeysById does not perform any signature validations and verification of the returned keys. + KeysById(id uint64) []Key + // EntitiesById returns the set of entities that contain a key with the given key id. + // EntitiesById does not perform any signature validations and verification of the returned keys. + EntitiesById(id uint64) []*Entity +} + +// PrimaryIdentity returns a valid non-revoked Identity while preferring +// identities marked as primary, or the latest-created identity, in that order. +func (e *Entity) PrimaryIdentity(date time.Time) (*packet.Signature, *Identity, error) { + var primaryIdentityCandidates []*Identity + var primaryIdentityCandidatesSelfSigs []*packet.Signature + for _, identity := range e.Identities { + selfSig, err := identity.Verify(date) // identity must be valid at date + if err == nil { // verification is successful + primaryIdentityCandidates = append(primaryIdentityCandidates, identity) + primaryIdentityCandidatesSelfSigs = append(primaryIdentityCandidatesSelfSigs, selfSig) + } + } + if len(primaryIdentityCandidates) == 0 { + return nil, nil, errors.StructuralError("no primary identity found") + } + primaryIdentity := -1 + for idx := range primaryIdentityCandidates { + if primaryIdentity == -1 || + shouldPreferIdentity(primaryIdentityCandidatesSelfSigs[primaryIdentity], + primaryIdentityCandidatesSelfSigs[idx]) { + primaryIdentity = idx + } + } + return primaryIdentityCandidatesSelfSigs[primaryIdentity], primaryIdentityCandidates[primaryIdentity], nil +} + +func shouldPreferIdentity(existingId, potentialNewId *packet.Signature) bool { + // Prefer identities that are marked as primary + if existingId.IsPrimaryId != nil && *existingId.IsPrimaryId && + !(potentialNewId.IsPrimaryId != nil && *potentialNewId.IsPrimaryId) { + return false + } + if !(existingId.IsPrimaryId != nil && *existingId.IsPrimaryId) && + potentialNewId.IsPrimaryId != nil && *potentialNewId.IsPrimaryId { + return true + } + // after that newer creation time + return potentialNewId.CreationTime.Unix() >= existingId.CreationTime.Unix() +} + +// EncryptionKey returns the best candidate Key for encrypting a message to the +// given Entity. +func (e *Entity) EncryptionKey(now time.Time, config *packet.Config) (Key, bool) { + // The primary key has to be valid at time now + primarySelfSignature, err := e.VerifyPrimaryKey(now) + if err != nil { // primary key is not valid + return Key{}, false + } + + if checkKeyRequirements(e.PrimaryKey, config) != nil { + // The primary key produces weak signatures + return Key{}, false + } + + // Iterate the keys to find the newest, unexpired one + candidateSubkey := -1 + var maxTime time.Time + var selectedSubkeySelfSig *packet.Signature + for i, subkey := range e.Subkeys { + subkeySelfSig, err := subkey.Verify(now) // subkey has to be valid at time now + if err == nil && + isValidEncryptionKey(subkeySelfSig, subkey.PublicKey.PubKeyAlgo) && + checkKeyRequirements(subkey.PublicKey, config) == nil && + (maxTime.IsZero() || subkeySelfSig.CreationTime.Unix() >= maxTime.Unix()) { + candidateSubkey = i + selectedSubkeySelfSig = subkeySelfSig + maxTime = subkeySelfSig.CreationTime + } + } + + if candidateSubkey != -1 { + subkey := &e.Subkeys[candidateSubkey] + return Key{ + Entity: subkey.Primary, + PrimarySelfSignature: primarySelfSignature, + PublicKey: subkey.PublicKey, + PrivateKey: subkey.PrivateKey, + SelfSignature: selectedSubkeySelfSig, + }, true + } + + // If we don't have any subkeys for encryption and the primary key + // is marked as OK to encrypt with, then we can use it. + if isValidEncryptionKey(primarySelfSignature, e.PrimaryKey.PubKeyAlgo) { + return Key{ + Entity: e, + PrimarySelfSignature: primarySelfSignature, + PublicKey: e.PrimaryKey, + PrivateKey: e.PrivateKey, + SelfSignature: primarySelfSignature, + }, true + } + + return Key{}, false +} + +// DecryptionKeys returns all keys that are available for decryption, matching the keyID when given +// If date is zero (i.e., date.IsZero() == true) the time checks are not performed, +// which should be proffered to decrypt older messages. +// If id is 0 all decryption keys are returned. +// This is useful to retrieve keys for session key decryption. +func (e *Entity) DecryptionKeys(id uint64, date time.Time) (keys []Key) { + primarySelfSignature, err := e.PrimarySelfSignature(date) + if err != nil { // primary key is not valid + return + } + for _, subkey := range e.Subkeys { + subkeySelfSig, err := subkey.LatestValidBindingSignature(date) + if err == nil && + isValidEncryptionKey(subkeySelfSig, subkey.PublicKey.PubKeyAlgo) && + (id == 0 || subkey.PublicKey.KeyId == id) { + keys = append(keys, Key{subkey.Primary, primarySelfSignature, subkey.PublicKey, subkey.PrivateKey, subkeySelfSig}) + } + } + if isValidEncryptionKey(primarySelfSignature, e.PrimaryKey.PubKeyAlgo) { + keys = append(keys, Key{e, primarySelfSignature, e.PrimaryKey, e.PrivateKey, primarySelfSignature}) + } + return +} + +// CertificationKey return the best candidate Key for certifying a key with this +// Entity. +func (e *Entity) CertificationKey(now time.Time, config *packet.Config) (Key, bool) { + return e.CertificationKeyById(now, 0, config) +} + +// CertificationKeyById return the Key for key certification with this +// Entity and keyID. +func (e *Entity) CertificationKeyById(now time.Time, id uint64, config *packet.Config) (Key, bool) { + key, err := e.signingKeyByIdUsage(now, id, packet.KeyFlagSign, config) + return key, err == nil +} + +// SigningKey return the best candidate Key for signing a message with this +// Entity. +func (e *Entity) SigningKey(now time.Time, config *packet.Config) (Key, bool) { + return e.SigningKeyById(now, 0, config) +} + +// SigningKeyById return the Key for signing a message with this +// Entity and keyID. +func (e *Entity) SigningKeyById(now time.Time, id uint64, config *packet.Config) (Key, bool) { + key, err := e.signingKeyByIdUsage(now, id, packet.KeyFlagSign, config) + return key, err == nil +} + +func (e *Entity) signingKeyByIdUsage(now time.Time, id uint64, flags int, config *packet.Config) (Key, error) { + // Fail to find any signing key if the... + primarySelfSignature, err := e.VerifyPrimaryKey(now) + if err != nil { + return Key{}, err + } + + if err = checkKeyRequirements(e.PrimaryKey, config); err != nil { + // The primary key produces weak signatures + return Key{}, err + } + + // Iterate the keys to find the newest, unexpired one + candidateSubkey := -1 + var maxTime time.Time + var selectedSubkeySelfSig *packet.Signature + for idx, subkey := range e.Subkeys { + subkeySelfSig, err := subkey.Verify(now) + if err == nil && + (flags&packet.KeyFlagCertify == 0 || isValidCertificationKey(subkeySelfSig, subkey.PublicKey.PubKeyAlgo)) && + (flags&packet.KeyFlagSign == 0 || isValidSigningKey(subkeySelfSig, subkey.PublicKey.PubKeyAlgo)) && + checkKeyRequirements(subkey.PublicKey, config) == nil && + (maxTime.IsZero() || subkeySelfSig.CreationTime.Unix() >= maxTime.Unix()) && + (id == 0 || subkey.PublicKey.KeyId == id) { + candidateSubkey = idx + maxTime = subkeySelfSig.CreationTime + selectedSubkeySelfSig = subkeySelfSig + } + } + + if candidateSubkey != -1 { + subkey := &e.Subkeys[candidateSubkey] + return Key{ + Entity: subkey.Primary, + PrimarySelfSignature: primarySelfSignature, + PublicKey: subkey.PublicKey, + PrivateKey: subkey.PrivateKey, + SelfSignature: selectedSubkeySelfSig, + }, nil + } + + // If we don't have any subkeys for signing and the primary key + // is marked as OK to sign with, then we can use it. + if (flags&packet.KeyFlagCertify == 0 || isValidCertificationKey(primarySelfSignature, e.PrimaryKey.PubKeyAlgo)) && + (flags&packet.KeyFlagSign == 0 || isValidSigningKey(primarySelfSignature, e.PrimaryKey.PubKeyAlgo)) && + (id == 0 || e.PrimaryKey.KeyId == id) { + return Key{ + Entity: e, + PrimarySelfSignature: primarySelfSignature, + PublicKey: e.PrimaryKey, + PrivateKey: e.PrivateKey, + SelfSignature: primarySelfSignature, + }, nil + } + + // No keys with a valid Signing Flag or no keys matched the id passed in + return Key{}, errors.StructuralError("no valid signing or verifying key found") +} + +// Revoked returns whether the entity has any direct key revocation signatures. +// Note that third-party revocation signatures are not supported. +// Note also that Identity and Subkey revocation should be checked separately. +func (e *Entity) Revoked(now time.Time) bool { + // Verify revocations first + for _, revocation := range e.Revocations { + if revocation.Valid == nil { + err := e.PrimaryKey.VerifyRevocationSignature(revocation.Packet) + valid := err == nil + revocation.Valid = &valid + } + if *revocation.Valid && + (revocation.Packet.RevocationReason == nil || + *revocation.Packet.RevocationReason == packet.Unknown || + *revocation.Packet.RevocationReason == packet.NoReason || + *revocation.Packet.RevocationReason == packet.KeyCompromised) { + // If the key is compromised, the key is considered revoked even before the revocation date. + return true + } + if *revocation.Valid && + !revocation.Packet.SigExpired(now) { + return true + } + } + return false +} + +// EncryptPrivateKeys encrypts all non-encrypted keys in the entity with the same key +// derived from the provided passphrase. Public keys and dummy keys are ignored, +// and don't cause an error to be returned. +func (e *Entity) EncryptPrivateKeys(passphrase []byte, config *packet.Config) error { + var keysToEncrypt []*packet.PrivateKey + // Add entity private key to encrypt. + if e.PrivateKey != nil && !e.PrivateKey.Dummy() && !e.PrivateKey.Encrypted { + keysToEncrypt = append(keysToEncrypt, e.PrivateKey) + } + + // Add subkeys to encrypt. + for _, sub := range e.Subkeys { + if sub.PrivateKey != nil && !sub.PrivateKey.Dummy() && !sub.PrivateKey.Encrypted { + keysToEncrypt = append(keysToEncrypt, sub.PrivateKey) + } + } + return packet.EncryptPrivateKeys(keysToEncrypt, passphrase, config) +} + +// DecryptPrivateKeys decrypts all encrypted keys in the entitiy with the given passphrase. +// Avoids recomputation of similar s2k key derivations. Public keys and dummy keys are ignored, +// and don't cause an error to be returned. +func (e *Entity) DecryptPrivateKeys(passphrase []byte) error { + var keysToDecrypt []*packet.PrivateKey + // Add entity private key to decrypt. + if e.PrivateKey != nil && !e.PrivateKey.Dummy() && e.PrivateKey.Encrypted { + keysToDecrypt = append(keysToDecrypt, e.PrivateKey) + } + + // Add subkeys to decrypt. + for _, sub := range e.Subkeys { + if sub.PrivateKey != nil && !sub.PrivateKey.Dummy() && sub.PrivateKey.Encrypted { + keysToDecrypt = append(keysToDecrypt, sub.PrivateKey) + } + } + return packet.DecryptPrivateKeys(keysToDecrypt, passphrase) +} + +// An EntityList contains one or more Entities. +type EntityList []*Entity + +// KeysById returns the set of keys that have the given key id. +// KeysById does not perform any key validation, and the self-signature +// fields in the returned key structs are nil. +func (el EntityList) KeysById(id uint64) (keys []Key) { + for _, e := range el { + if id == 0 || e.PrimaryKey.KeyId == id { + keys = append(keys, Key{e, nil, e.PrimaryKey, e.PrivateKey, nil}) + } + + for _, subKey := range e.Subkeys { + if id == 0 || subKey.PublicKey.KeyId == id { + keys = append(keys, Key{subKey.Primary, nil, subKey.PublicKey, subKey.PrivateKey, nil}) + } + } + } + return +} + +// EntitiesById returns the entities that contain a key with the given key id. +func (el EntityList) EntitiesById(id uint64) (entities []*Entity) { + for _, e := range el { + if id == 0 || e.PrimaryKey.KeyId == id { + entities = append(entities, e) + continue + } + + for _, subKey := range e.Subkeys { + if id == 0 || subKey.PublicKey.KeyId == id { + entities = append(entities, e) + continue + } + } + } + return +} + +// ReadArmoredKeyRing reads one or more public/private keys from an armor keyring file. +func ReadArmoredKeyRing(r io.Reader) (EntityList, error) { + block, err := armor.Decode(r) + if err == io.EOF { + return nil, errors.InvalidArgumentError("no armored data found") + } + if err != nil { + return nil, err + } + if block.Type != PublicKeyType && block.Type != PrivateKeyType { + return nil, errors.InvalidArgumentError("expected public or private key block, got: " + block.Type) + } + + return ReadKeyRing(block.Body) +} + +// ReadKeyRing reads one or more public/private keys. Unsupported keys are +// ignored as long as at least a single valid key is found. +func ReadKeyRing(r io.Reader) (el EntityList, err error) { + packets := packet.NewReader(r) + var lastUnsupportedError error + + for { + var e *Entity + e, err = ReadEntity(packets) + if err != nil { + // TODO: warn about skipped unsupported/unreadable keys + if _, ok := err.(errors.UnsupportedError); ok { + lastUnsupportedError = err + err = readToNextPublicKey(packets) + } else if _, ok := err.(errors.StructuralError); ok { + // Skip unreadable, badly-formatted keys + lastUnsupportedError = err + err = readToNextPublicKey(packets) + } + if err == io.EOF { + err = nil + break + } + if err != nil { + el = nil + break + } + } else { + el = append(el, e) + } + } + + if len(el) == 0 && err == nil { + err = lastUnsupportedError + } + return +} + +// readToNextPublicKey reads packets until the start of the entity and leaves +// the first packet of the new entity in the Reader. +func readToNextPublicKey(packets *packet.Reader) (err error) { + var p packet.Packet + for { + p, err = packets.Next() + if err == io.EOF { + return + } else if err != nil { + if _, ok := err.(errors.UnsupportedError); ok { + err = nil + continue + } + return + } + + if pk, ok := p.(*packet.PublicKey); ok && !pk.IsSubkey { + packets.Unread(p) + return + } + } +} + +// ReadEntity reads an entity (public key, identities, subkeys etc) from the +// given Reader. +func ReadEntity(packets *packet.Reader) (*Entity, error) { + e := new(Entity) + e.Identities = make(map[string]*Identity) + + p, err := packets.Next() + if err != nil { + return nil, err + } + + var ok bool + if e.PrimaryKey, ok = p.(*packet.PublicKey); !ok { + if e.PrivateKey, ok = p.(*packet.PrivateKey); !ok { + packets.Unread(p) + return nil, errors.StructuralError("first packet was not a public/private key") + } + e.PrimaryKey = &e.PrivateKey.PublicKey + } + + if !e.PrimaryKey.PubKeyAlgo.CanSign() { + return nil, errors.StructuralError("primary key cannot be used for signatures") + } + var ignoreSigs bool +EachPacket: + for { + p, err := packets.NextWithUnsupported() + if err == io.EOF { + break + } else if err != nil { + return nil, err + } + + var unsupported bool + if unsupportedPacket, ok := p.(*packet.UnsupportedPacket); ok { + unsupported = true + p = unsupportedPacket.IncompletePacket + } + + // Handle unsupported keys + switch p.(type) { + case *packet.PublicKey, *packet.PrivateKey: + if unsupported { + // Skip following signature packets + ignoreSigs = true + } + case *packet.Signature: + if ignoreSigs { + continue + } + default: + ignoreSigs = false + } + // Unsupported packages are handled continue + // if the packet is unsupported + if unsupported { + continue + } + + switch pkt := p.(type) { + case *packet.UserId: + err := readUser(e, packets, pkt) + if err != nil { + return nil, err + } + case *packet.Signature: + if pkt.SigType == packet.SigTypeKeyRevocation { + e.Revocations = append(e.Revocations, packet.NewVerifiableSig(pkt)) + } else if pkt.SigType == packet.SigTypeDirectSignature { + e.DirectSignatures = append(e.DirectSignatures, packet.NewVerifiableSig(pkt)) + } + // Else, ignoring the signature as it does not follow anything + // we would know to attach it to. + case *packet.PrivateKey: + if !pkt.IsSubkey { + packets.Unread(p) + break EachPacket + } + err = readSubkey(e, packets, &pkt.PublicKey, pkt) + if err != nil { + return nil, err + } + case *packet.PublicKey: + if !pkt.IsSubkey { + packets.Unread(p) + break EachPacket + } + err = readSubkey(e, packets, pkt, nil) + if err != nil { + return nil, err + } + default: + // we ignore unknown packets + } + } + + if len(e.Identities) == 0 && e.PrimaryKey.Version < 6 { + return nil, errors.StructuralError("v4 entity without any identities") + } + + if e.PrimaryKey.Version == 6 && len(e.DirectSignatures) == 0 { + return nil, errors.StructuralError("v6 entity without a direct-key signature") + } + return e, nil +} + +// SerializePrivate serializes an Entity, including private key material, but +// excluding signatures from other entities, to the given Writer. +// Identities and subkeys are re-signed in case they changed since NewEntry. +// If config is nil, sensible defaults will be used. +func (e *Entity) SerializePrivate(w io.Writer, config *packet.Config) (err error) { + if e.PrivateKey.Dummy() { + return errors.ErrDummyPrivateKey("dummy private key cannot re-sign identities") + } + return e.serializePrivate(w, config, true) +} + +// SerializePrivateWithoutSigning serializes an Entity, including private key +// material, but excluding signatures from other entities, to the given Writer. +// Self-signatures of identities and subkeys are not re-signed. This is useful +// when serializing GNU dummy keys, among other things. +// If config is nil, sensible defaults will be used. +func (e *Entity) SerializePrivateWithoutSigning(w io.Writer, config *packet.Config) (err error) { + return e.serializePrivate(w, config, false) +} + +func (e *Entity) serializePrivate(w io.Writer, config *packet.Config, reSign bool) (err error) { + if e.PrivateKey == nil { + return goerrors.New("openpgp: private key is missing") + } + err = e.PrivateKey.Serialize(w) + if err != nil { + return + } + for _, revocation := range e.Revocations { + if err = revocation.Packet.Serialize(w); err != nil { + return err + } + } + for _, directSignature := range e.DirectSignatures { + if err = directSignature.Packet.Serialize(w); err != nil { + return err + } + } + for _, ident := range e.Identities { + if reSign { + if err = ident.ReSign(config); err != nil { + return err + } + } + if err = ident.Serialize(w); err != nil { + return err + } + } + for _, subkey := range e.Subkeys { + if reSign { + subkey.ReSign(config) + } + if err = subkey.Serialize(w, true); err != nil { + return err + } + } + return nil +} + +// Serialize writes the public part of the given Entity to w, including +// signatures from other entities. No private key material will be output. +func (e *Entity) Serialize(w io.Writer) error { + if err := e.PrimaryKey.Serialize(w); err != nil { + return err + } + for _, revocation := range e.Revocations { + if err := revocation.Packet.Serialize(w); err != nil { + return err + } + } + for _, directSignature := range e.DirectSignatures { + err := directSignature.Packet.Serialize(w) + if err != nil { + return err + } + } + for _, ident := range e.Identities { + if err := ident.Serialize(w); err != nil { + return err + } + } + for _, subkey := range e.Subkeys { + if err := subkey.Serialize(w, false); err != nil { + return err + } + } + return nil +} + +// Revoke generates a key revocation signature (packet.SigTypeKeyRevocation) with the +// specified reason code and text (RFC4880 section-5.2.3.23). +// If config is nil, sensible defaults will be used. +func (e *Entity) Revoke(reason packet.ReasonForRevocation, reasonText string, config *packet.Config) error { + revSig := createSignaturePacket(e.PrimaryKey, packet.SigTypeKeyRevocation, config) + revSig.RevocationReason = &reason + revSig.RevocationReasonText = reasonText + + if err := revSig.RevokeKey(e.PrimaryKey, e.PrivateKey, config); err != nil { + return err + } + sig := packet.NewVerifiableSig(revSig) + valid := true + sig.Valid = &valid + e.Revocations = append(e.Revocations, sig) + return nil +} + +// SignIdentity adds a signature to e, from signer, attesting that identity is +// associated with e. The provided identity must already be an element of +// e.Identities and the private key of signer must have been decrypted if +// necessary. +// If config is nil, sensible defaults will be used. +func (e *Entity) SignIdentity(identity string, signer *Entity, config *packet.Config) error { + ident, ok := e.Identities[identity] + if !ok { + return errors.InvalidArgumentError("given identity string not found in Entity") + } + return ident.SignIdentity(signer, config) +} + +func (e *Entity) LatestValidDirectSignature(date time.Time) (selectedSig *packet.Signature, err error) { + for sigIdx := len(e.DirectSignatures) - 1; sigIdx >= 0; sigIdx-- { + sig := e.DirectSignatures[sigIdx] + if (date.IsZero() || date.Unix() >= sig.Packet.CreationTime.Unix()) && + (selectedSig == nil || selectedSig.CreationTime.Unix() < sig.Packet.CreationTime.Unix()) { + if sig.Valid == nil { + err := e.PrimaryKey.VerifyDirectKeySignature(sig.Packet) + valid := err == nil + sig.Valid = &valid + } + if *sig.Valid && (date.IsZero() || !sig.Packet.SigExpired(date)) { + selectedSig = sig.Packet + } + } + } + if selectedSig == nil { + return nil, errors.StructuralError("no valid direct key signature found") + } + return +} + +// primarySelfSignature searches the entitity for the self-signature that stores key prefrences. +// For V4 keys, returns the self-signature of the primary indentity, and the identity. +// For V6 keys, returns the latest valid direct-key self-signature, and no identity (nil). +// This self-signature is to be used to check the key expiration, +// algorithm preferences, and so on. +func (e *Entity) PrimarySelfSignature(date time.Time) (primarySig *packet.Signature, err error) { + if e.PrimaryKey.Version == 6 { + primarySig, err = e.LatestValidDirectSignature(date) + return + } + primarySig, _, err = e.PrimaryIdentity(date) + if err != nil { + return + } + return +} + +// VerifyPrimaryKey checks if the primary key is valid by checking: +// - that the primary key is has not been revoked at the given date, +// - that there is valid non-expired self-signature, +// - that the primary key is not expired given its self-signature. +// If date is zero (i.e., date.IsZero() == true) the time checks are not performed. +func (e *Entity) VerifyPrimaryKey(date time.Time) (*packet.Signature, error) { + primarySelfSignature, err := e.PrimarySelfSignature(date) + if err != nil { + return nil, goerrors.New("no valid self signature found") + } + // check for key revocation signatures + if e.Revoked(date) { + return nil, errors.ErrKeyRevoked + } + + if e.PrimaryKey.KeyExpired(primarySelfSignature, date) || // primary key has expired + primarySelfSignature.SigExpired(date) { // self-signature has expired + return primarySelfSignature, errors.ErrKeyExpired + } + + if e.PrimaryKey.Version != 6 && len(e.DirectSignatures) > 0 { + // check for expiration time in direct signatures (for V6 keys, the above already did so) + primaryDirectKeySignature, _ := e.LatestValidDirectSignature(date) + if primaryDirectKeySignature != nil && + e.PrimaryKey.KeyExpired(primaryDirectKeySignature, date) { + return primarySelfSignature, errors.ErrKeyExpired + } + } + return primarySelfSignature, nil +} + +func (k *Key) IsPrimary() bool { + if k.PrimarySelfSignature == nil || k.SelfSignature == nil { + return k.PublicKey == k.Entity.PrimaryKey + } + return k.PrimarySelfSignature == k.SelfSignature +} + +// checkKeyRequirements +func checkKeyRequirements(usedKey *packet.PublicKey, config *packet.Config) error { + algo := usedKey.PubKeyAlgo + if config.RejectPublicKeyAlgorithm(algo) { + return errors.WeakAlgorithmError("public key algorithm " + string(algo)) + } + switch algo { + case packet.PubKeyAlgoRSA, packet.PubKeyAlgoRSASignOnly: + length, err := usedKey.BitLength() + if err != nil || length < config.MinimumRSABits() { + return errors.WeakAlgorithmError(fmt.Sprintf("minimum rsa length is %d got %d", config.MinimumRSABits(), length)) + } + case packet.PubKeyAlgoECDH, packet.PubKeyAlgoEdDSA, packet.PubKeyAlgoECDSA: + curve, err := usedKey.Curve() + if err != nil || config.RejectCurve(curve) { + return errors.WeakAlgorithmError("elliptic curve " + curve) + } + } + return nil +} + +func isValidSigningKey(signature *packet.Signature, algo packet.PublicKeyAlgorithm) bool { + return algo.CanSign() && + signature.FlagsValid && + signature.FlagSign +} + +func isValidCertificationKey(signature *packet.Signature, algo packet.PublicKeyAlgorithm) bool { + return algo.CanSign() && + signature.FlagsValid && + signature.FlagCertify +} + +func isValidEncryptionKey(signature *packet.Signature, algo packet.PublicKeyAlgorithm) bool { + return algo.CanEncrypt() && + signature.FlagsValid && + signature.FlagEncryptCommunications +} diff --git a/openpgp/v2/keys_test.go b/openpgp/v2/keys_test.go new file mode 100644 index 000000000..02f2c14bd --- /dev/null +++ b/openpgp/v2/keys_test.go @@ -0,0 +1,2078 @@ +package v2 + +import ( + "bytes" + "crypto" + "crypto/dsa" + "crypto/rand" + "crypto/rsa" + "fmt" + "math/big" + "strconv" + "strings" + "testing" + "time" + + "github.com/ProtonMail/go-crypto/openpgp/armor" + "github.com/ProtonMail/go-crypto/openpgp/ecdh" + "github.com/ProtonMail/go-crypto/openpgp/ecdsa" + "github.com/ProtonMail/go-crypto/openpgp/eddsa" + "github.com/ProtonMail/go-crypto/openpgp/elgamal" + "github.com/ProtonMail/go-crypto/openpgp/errors" + "github.com/ProtonMail/go-crypto/openpgp/internal/algorithm" + "github.com/ProtonMail/go-crypto/openpgp/packet" + "github.com/ProtonMail/go-crypto/openpgp/s2k" +) + +var hashes = []crypto.Hash{ + crypto.SHA1, + crypto.SHA224, + crypto.SHA256, + crypto.SHA384, + crypto.SHA512, + crypto.SHA3_256, + crypto.SHA3_512, +} + +var ciphers = []packet.CipherFunction{ + packet.Cipher3DES, + packet.CipherCAST5, + packet.CipherAES128, + packet.CipherAES192, + packet.CipherAES256, +} + +var aeadModes = []packet.AEADMode{ + packet.AEADModeOCB, + packet.AEADModeEAX, + packet.AEADModeGCM, +} + +var allowAllAlgorithmsConfig = packet.Config{ + RejectPublicKeyAlgorithms: map[packet.PublicKeyAlgorithm]bool{}, + RejectCurves: map[packet.Curve]bool{}, + RejectMessageHashAlgorithms: map[crypto.Hash]bool{}, + MinRSABits: 512, +} + +func TestKeyExpiry(t *testing.T) { + kring, err := ReadKeyRing(readerFromHex(expiringKeyHex)) + if err != nil { + t.Fatal(err) + } + entity := kring[0] + + const timeFormat = "2006-01-02" + time1, _ := time.Parse(timeFormat, "2013-07-02") + + // The expiringKeyHex key is structured as: + // + // pub 1024R/5E237D8C created: 2013-07-01 expires: 2013-07-31 usage: SC + // sub 1024R/1ABB25A0 created: 2013-07-01 23:11:07 +0200 CEST expires: 2013-07-08 usage: E + // sub 1024R/96A672F5 created: 2013-07-01 23:11:23 +0200 CEST expires: 2013-07-31 usage: E + // + // So this should select the newest, non-expired encryption key. + key, ok := entity.EncryptionKey(time1, nil) + if !ok { + t.Fatal("No encryption key found") + } + if id, expected := key.PublicKey.KeyIdShortString(), "CD3D39FF"; id != expected { + t.Errorf("Expected key %s at time %s, but got key %s", expected, time1.Format(timeFormat), id) + } + + // Once the first encryption subkey has expired, the second should be + // selected. + time2, _ := time.Parse(timeFormat, "2013-07-09") + key, _ = entity.EncryptionKey(time2, nil) + if id, expected := key.PublicKey.KeyIdShortString(), "CD3D39FF"; id != expected { + t.Errorf("Expected key %s at time %s, but got key %s", expected, time2.Format(timeFormat), id) + } + + // Once all the keys have expired, nothing should be returned. + time3, _ := time.Parse(timeFormat, "2013-08-01") + if key, ok := entity.EncryptionKey(time3, nil); ok { + t.Errorf("Expected no key at time %s, but got key %s", time3.Format(timeFormat), key.PublicKey.KeyIdShortString()) + } +} + +// https://tests.sequoia-pgp.org/#Certificate_expiration +// P _ U f +func TestExpiringPrimaryUIDKey(t *testing.T) { + // P _ U f + kring, err := ReadArmoredKeyRing(bytes.NewBufferString((expiringPrimaryUIDKey))) + if err != nil { + t.Fatal(err) + } + entity := kring[0] + + const timeFormat string = "2006-01-02" + const expectedKeyID string = "015E7330" + + // Before the primary UID has expired, the primary key should be returned. + time1, err := time.Parse(timeFormat, "2022-02-05") + if err != nil { + t.Fatal(err) + } + key, found := entity.SigningKey(time1, nil) + if !found { + t.Errorf("Signing subkey %s not found at time %s", expectedKeyID, time1.Format(timeFormat)) + } else if observedKeyID := key.PublicKey.KeyIdShortString(); observedKeyID != expectedKeyID { + t.Errorf("Expected key %s at time %s, but got key %s", expectedKeyID, time1.Format(timeFormat), observedKeyID) + } + + // After the primary UID has expired, nothing should be returned. + time2, err := time.Parse(timeFormat, "2022-02-06") + if err != nil { + t.Fatal(err) + } + if key, ok := entity.SigningKey(time2, nil); ok { + t.Errorf("Expected no key at time %s, but got key %s", time2.Format(timeFormat), key.PublicKey.KeyIdShortString()) + } +} + +func TestReturnFirstUnexpiredSigningSubkey(t *testing.T) { + // Make a master key. + entity, err := NewEntity("Golang Gopher", "Test Key", "no-reply@golang.com", nil) + if err != nil { + t.Fatal(err) + } + + // First signing subkey does not expire. + err = entity.AddSigningSubkey(nil) + if err != nil { + t.Fatal(err) + } + // Get the first signing subkey (added after the default encryption subkey). + subkey1 := entity.Subkeys[1] + + // Second signing subkey expires in a day. + err = entity.AddSigningSubkey(&packet.Config{ + KeyLifetimeSecs: 24 * 60 * 60, + }) + if err != nil { + t.Fatal(err) + } + // Get the second signing subkey. + subkey2 := entity.Subkeys[2] + + // Before second signing subkey has expired, it should be returned. + time1 := time.Now() + expected := subkey2.PublicKey.KeyIdShortString() + subkey, found := entity.SigningKey(time1, nil) + if !found { + t.Errorf("Signing subkey %s not found at time %s", expected, time1.Format(time.UnixDate)) + } + observed := subkey.PublicKey.KeyIdShortString() + if observed != expected { + t.Errorf("Expected key %s at time %s, but got key %s", expected, time1.Format(time.UnixDate), observed) + } + + // After the second signing subkey has expired, the first one should be returned. + time2 := time1.AddDate(0, 0, 2) + expected = subkey1.PublicKey.KeyIdShortString() + subkey, found = entity.SigningKey(time2, nil) + if !found { + t.Errorf("Signing subkey %s not found at time %s", expected, time2.Format(time.UnixDate)) + } + observed = subkey.PublicKey.KeyIdShortString() + if observed != expected { + t.Errorf("Expected key %s at time %s, but got key %s", expected, time2.Format(time.UnixDate), observed) + } +} + +func TestSignatureExpiry(t *testing.T) { + // Make a master key, and attach it to a keyring. + var keyring EntityList + entity, err := NewEntity("Golang Gopher", "Test Key", "no-reply@golang.com", nil) + if err != nil { + t.Fatal(err) + } + keyring = append(keyring, entity) + + // Make a signature that never expires. + var signatureWriter1 bytes.Buffer + const input string = "Hello, world!" + message := strings.NewReader(input) + err = ArmoredDetachSign(&signatureWriter1, []*Entity{entity}, message, nil) + if err != nil { + t.Fatal(err) + } + + // Make a signature that expires in a day. + var signatureWriter2 bytes.Buffer + message = strings.NewReader(input) + err = ArmoredDetachSign(&signatureWriter2, []*Entity{entity}, message, &packet.Config{ + SigLifetimeSecs: 24 * 60 * 60, + }) + if err != nil { + t.Fatal(err) + } + + // Make a time that is day after tomorrow. + futureTime := func() time.Time { + return time.Now().AddDate(0, 0, 2) + } + + // Make a signature that was created in the future. + var signatureWriter3 bytes.Buffer + message = strings.NewReader(input) + err = ArmoredDetachSign(&signatureWriter3, []*Entity{entity}, message, &packet.Config{ + Time: futureTime, + }) + if err != nil { + t.Fatal(err) + } + + // Check that the first signature has not expired day after tomorrow. + message = strings.NewReader(input) + signatureReader1 := strings.NewReader(signatureWriter1.String()) + _, _, err = VerifyArmoredDetachedSignature(keyring, message, signatureReader1, &packet.Config{ + Time: futureTime, + }) + if err != nil { + t.Fatal(err) + } + + // Check that the second signature has expired day after tomorrow. + message = strings.NewReader(input) + signatureReader2 := strings.NewReader(signatureWriter2.String()) + const expectedErr string = "openpgp: signature expired" + _, _, observedErr := VerifyArmoredDetachedSignature(keyring, message, signatureReader2, &packet.Config{ + Time: futureTime, + }) + if observedErr.Error() != expectedErr { + t.Errorf("Expected error '%s', but got error '%s'", expectedErr, observedErr) + } + + // Check that the third signature is also considered expired even now. + message = strings.NewReader(input) + signatureReader3 := strings.NewReader(signatureWriter3.String()) + _, _, observedErr = VerifyArmoredDetachedSignature(keyring, message, signatureReader3, nil) + if observedErr.Error() != expectedErr { + t.Errorf("Expected error '%s', but got error '%s'", expectedErr, observedErr) + } +} + +func TestMissingCrossSignature(t *testing.T) { + // This public key has a signing subkey, but the subkey does not + // contain a cross-signature. + keys, _ := ReadArmoredKeyRing(bytes.NewBufferString(missingCrossSignatureKey)) + var config *packet.Config + _, err := keys[0].Subkeys[0].Verify(config.Now()) + if err == nil { + t.Fatal("Failed to detect error in keyring with missing cross signature") + } + structural, ok := err.(errors.StructuralError) + if !ok { + t.Fatalf("Unexpected class of error: %T. Wanted StructuralError", err) + } + const expectedMsg = "no valid binding signature found for subkey" + if !strings.Contains(string(structural), expectedMsg) { + t.Fatalf("Unexpected error: %q. Expected it to contain %q", err, expectedMsg) + } +} + +func TestInvalidCrossSignature(t *testing.T) { + // This public key has a signing subkey, and the subkey has an + // embedded cross-signature. However, the cross-signature does + // not correctly validate over the primary and subkey. + keys, _ := ReadArmoredKeyRing(bytes.NewBufferString(invalidCrossSignatureKey)) + var config *packet.Config + _, err := keys[0].Subkeys[0].Verify(config.Now()) + if err == nil { + t.Fatal("Failed to detect error in keyring with an invalid cross signature") + } + structural, ok := err.(errors.StructuralError) + if !ok { + t.Fatalf("Unexpected class of error: %T. Wanted StructuralError", err) + } + const expectedMsg = "no valid binding signature found for subkey" + if !strings.Contains(string(structural), expectedMsg) { + t.Fatalf("Unexpected error: %q. Expected it to contain %q", err, expectedMsg) + } +} + +func TestGoodCrossSignature(t *testing.T) { + // This public key has a signing subkey, and the subkey has an + // embedded cross-signature which correctly validates over the + // primary and subkey. + keys, err := ReadArmoredKeyRing(bytes.NewBufferString(goodCrossSignatureKey)) + if err != nil { + t.Fatal(err) + } + if len(keys) != 1 { + t.Errorf("Failed to accept key with good cross signature, %d", len(keys)) + } + if len(keys[0].Subkeys) != 1 { + t.Errorf("Failed to accept good subkey, %d", len(keys[0].Subkeys)) + } +} + +func TestRevokedUserID(t *testing.T) { + // This key contains 2 UIDs, one of which is revoked and has no valid self-signature: + // [ultimate] (1) Golang Gopher + // [ revoked] (2) Golang Gopher + keys, err := ReadArmoredKeyRing(bytes.NewBufferString(revokedUserIDKey)) + if err != nil { + t.Fatal(err) + } + + if len(keys) != 1 { + t.Fatal("Failed to read key with a revoked user id") + } + + identities := keys[0].Identities + + if numIdentities, numExpected := len(identities), 2; numIdentities != numExpected { + t.Errorf("obtained %d identities, expected %d", numIdentities, numExpected) + } + + firstIdentity, found := identities["Golang Gopher "] + if !found { + t.Errorf("missing first identity") + } + + secondIdentity, found := identities["Golang Gopher "] + if !found { + t.Errorf("missing second identity") + } + + if firstIdentity.Revoked(nil, time.Now()) { + t.Errorf("expected first identity not to be revoked") + } + + if !secondIdentity.Revoked(nil, time.Now()) { + t.Errorf("expected second identity to be revoked") + } + + const timeFormat = "2006-01-02" + time1, _ := time.Parse(timeFormat, "2020-01-01") + + if _, found := keys[0].SigningKey(time1, nil); !found { + t.Errorf("Expected SigningKey to return a signing key when one User IDs is revoked") + } + + if _, found := keys[0].EncryptionKey(time1, nil); !found { + t.Errorf("Expected EncryptionKey to return an encryption key when one User IDs is revoked") + } +} + +func TestFirstUserIDRevoked(t *testing.T) { + // Same test as above, but with the User IDs reversed: + // [ revoked] (1) Golang Gopher + // [ultimate] (2) Golang Gopher + keys, err := ReadArmoredKeyRing(bytes.NewBufferString(keyWithFirstUserIDRevoked)) + if err != nil { + t.Fatal(err) + } + + if len(keys) != 1 { + t.Fatal("Failed to read key with a revoked user id") + } + + identities := keys[0].Identities + + if numIdentities, numExpected := len(identities), 2; numIdentities != numExpected { + t.Errorf("obtained %d identities, expected %d", numIdentities, numExpected) + } + + firstIdentity, found := identities["Golang Gopher "] + if !found { + t.Errorf("missing first identity") + } + + secondIdentity, found := identities["Golang Gopher "] + if !found { + t.Errorf("missing second identity") + } + + if !firstIdentity.Revoked(nil, time.Now()) { + t.Errorf("expected first identity to be revoked") + } + + if secondIdentity.Revoked(nil, time.Now()) { + t.Errorf("expected second identity not to be revoked") + } + + const timeFormat = "2006-01-02" + time1, _ := time.Parse(timeFormat, "2020-01-01") + + if _, found := keys[0].SigningKey(time1, nil); !found { + t.Errorf("Expected SigningKey to return a signing key when first User IDs is revoked") + } + + if _, found := keys[0].EncryptionKey(time1, nil); !found { + t.Errorf("Expected EncryptionKey to return an encryption key when first User IDs is revoked") + } +} + +func TestOnlyUserIDRevoked(t *testing.T) { + // This key contains 1 UID which is revoked (but also has a self-signature) + keys, err := ReadArmoredKeyRing(bytes.NewBufferString(keyWithOnlyUserIDRevoked)) + if err != nil { + t.Fatal(err) + } + + if len(keys) != 1 { + t.Fatal("Failed to read key with a revoked user id") + } + + identities := keys[0].Identities + + if numIdentities, numExpected := len(identities), 1; numIdentities != numExpected { + t.Errorf("obtained %d identities, expected %d", numIdentities, numExpected) + } + + identity, found := identities["Revoked Primary User ID "] + if !found { + t.Errorf("missing identity") + } + + if !identity.Revoked(nil, time.Now()) { + t.Errorf("expected identity to be revoked") + } + + if _, found := keys[0].SigningKey(time.Now(), nil); found { + t.Errorf("Expected SigningKey not to return a signing key when the only User IDs is revoked") + } + + if _, found := keys[0].EncryptionKey(time.Now(), nil); found { + t.Errorf("Expected EncryptionKey not to return an encryption key when the only User IDs is revoked") + } +} + +func TestDummyPrivateKey(t *testing.T) { + // This public key has a signing subkey, but has a dummy placeholder + // instead of the real private key. It's used in scenarios where the + // main private key is withheld and only signing is allowed (e.g. build + // servers). + keys, err := ReadArmoredKeyRing(bytes.NewBufferString(onlySubkeyNoPrivateKey)) + if err != nil { + t.Fatal(err) + } + if len(keys) != 1 { + t.Errorf("Failed to accept key with dummy private key, %d", len(keys)) + } + if !keys[0].PrivateKey.Dummy() { + t.Errorf("Primary private key should be marked as a dummy key") + } + if len(keys[0].Subkeys) != 1 { + t.Errorf("Failed to accept good subkey, %d", len(keys[0].Subkeys)) + } + + // Test serialization of stub private key via entity.SerializePrivate(). + var buf bytes.Buffer + w, err := armor.Encode(&buf, PrivateKeyType, nil) + if err != nil { + t.Errorf("Failed top initialise armored key writer") + } + err = keys[0].SerializePrivateWithoutSigning(w, nil) + if err != nil { + t.Errorf("Failed to serialize entity") + } + if w.Close() != nil { + t.Errorf("Failed to close writer for armored key") + } + + keys, err = ReadArmoredKeyRing(bytes.NewBufferString(buf.String())) + if err != nil { + t.Fatal(err) + } + if len(keys) != 1 { + t.Errorf("Failed to accept key with dummy private key, %d", len(keys)) + } + if !keys[0].PrivateKey.Dummy() { + t.Errorf("Primary private key should be marked as a dummy key after serialisation") + } + if len(keys[0].Subkeys) != 1 { + t.Errorf("Failed to accept good subkey, %d", len(keys[0].Subkeys)) + } +} + +// TestExternallyRevokableKey attempts to load and parse a key with a third party revocation permission. +func TestExternallyRevocableKey(t *testing.T) { + kring, err := ReadKeyRing(readerFromHex(subkeyUsageHex)) + if err != nil { + t.Fatal(err) + } + + // The 0xA42704B92866382A key can be revoked by 0xBE3893CB843D0FE70C + // according to this signature that appears within the key: + // :signature packet: algo 1, keyid A42704B92866382A + // version 4, created 1396409682, md5len 0, sigclass 0x1f + // digest algo 2, begin of digest a9 84 + // hashed subpkt 2 len 4 (sig created 2014-04-02) + // hashed subpkt 12 len 22 (revocation key: c=80 a=1 f=CE094AA433F7040BB2DDF0BE3893CB843D0FE70C) + // hashed subpkt 7 len 1 (not revocable) + // subpkt 16 len 8 (issuer key ID A42704B92866382A) + // data: [1024 bits] + + id := uint64(0xA42704B92866382A) + keys := kring.KeysById(id) + if len(keys) != 1 { + t.Errorf("Expected to find key id %X, but got %d matches", id, len(keys)) + } +} + +func TestKeyRevocation(t *testing.T) { + kring, err := ReadKeyRing(readerFromHex(revokedKeyHex)) + if err != nil { + t.Fatal(err) + } + + if len(kring) != 1 { + t.Fatal("Failed to read key with a sub key") + } + + // revokedKeyHex contains these keys: + // pub 1024R/9A34F7C0 2014-03-25 [revoked: 2014-03-25] + // sub 1024R/1BA3CD60 2014-03-25 [revoked: 2014-03-25] + ids := []uint64{0xA401D9F09A34F7C0, 0x5CD3BE0A1BA3CD60} + + for _, id := range ids { + keys := kring.KeysById(id) + if len(keys) != 1 { + t.Errorf("Expected KeysById to find revoked key %X, but got %d matches", id, len(keys)) + } + } + + signingkey, found := kring[0].SigningKey(time.Now(), nil) + if found { + t.Errorf("Expected SigningKey not to return a signing key for a revoked key, got %X", signingkey.PublicKey.KeyId) + } + + encryptionkey, found := kring[0].EncryptionKey(time.Now(), nil) + if found { + t.Errorf("Expected EncryptionKey not to return an encryption key for a revoked key, got %X", encryptionkey.PublicKey.KeyId) + } +} + +func TestKeyWithRevokedSubKey(t *testing.T) { + // This key contains a revoked sub key: + // pub rsa1024/0x4CBD826C39074E38 2018-06-14 [SC] + // Key fingerprint = 3F95 169F 3FFA 7D3F 2B47 6F0C 4CBD 826C 3907 4E38 + // uid Golang Gopher + // sub rsa1024/0x945DB1AF61D85727 2018-06-14 [S] [revoked: 2018-06-14] + + keys, err := ReadArmoredKeyRing(bytes.NewBufferString(keyWithSubKey)) + if err != nil { + t.Fatal(err) + } + + if len(keys) != 1 { + t.Fatal("Failed to read key with a sub key") + } + + identity := keys[0].Identities["Golang Gopher "] + // Test for an issue where Subkey Binding Signatures (RFC 4880 5.2.1) were added to the identity + // preceding the Subkey Packet if the Subkey Packet was followed by more than one signature. + // For example, the current key has the following layout: + // PUBKEY UID SELFSIG SUBKEY REV SELFSIG + // The last SELFSIG would be added to the UID's signatures. This is wrong. + if numSigs, numExpected := len(identity.SelfCertifications), 1; numSigs != numExpected { + t.Fatalf("got %d signatures, expected %d", numSigs, numExpected) + } + + if numSubKeys, numExpected := len(keys[0].Subkeys), 1; numSubKeys != numExpected { + t.Fatalf("got %d subkeys, expected %d", numSubKeys, numExpected) + } + + subKey := keys[0].Subkeys[0] + if len(subKey.Bindings) == 0 { + t.Fatalf("no binding subkey signature") + } + +} + +func TestSubkeyRevocation(t *testing.T) { + kring, err := ReadKeyRing(readerFromHex(revokedSubkeyHex)) + if err != nil { + t.Fatal(err) + } + + if len(kring) != 1 { + t.Fatal("Failed to read key with a sub key") + } + + // revokedSubkeyHex contains these keys: + // pub 1024R/4EF7E4BECCDE97F0 2014-03-25 + // sub 1024R/D63636E2B96AE423 2014-03-25 + // sub 1024D/DBCE4EE19529437F 2014-03-25 + // sub 1024R/677815E371C2FD23 2014-03-25 [revoked: 2014-03-25] + validKeys := []uint64{0x4EF7E4BECCDE97F0, 0xD63636E2B96AE423, 0xDBCE4EE19529437F} + encryptionKey := uint64(0xD63636E2B96AE423) + revokedKey := uint64(0x677815E371C2FD23) + + for _, id := range validKeys { + keys := kring.KeysById(id) + if len(keys) != 1 { + t.Errorf("Expected KeysById to find key %X, but got %d matches", id, len(keys)) + } + if id == encryptionKey { + key, found := kring[0].EncryptionKey(time.Now(), &allowAllAlgorithmsConfig) + if !found || key.PublicKey.KeyId != id { + t.Errorf("Expected EncryptionKey to find key %X", id) + } + } else { + _, found := kring[0].SigningKeyById(time.Now(), id, &allowAllAlgorithmsConfig) + if !found { + t.Errorf("Expected SigningKeyById to find key %X", id) + } + } + } + + keys := kring.KeysById(revokedKey) + if len(keys) != 1 { + t.Errorf("Expected KeysById to find key %X, but got %d matches", revokedKey, len(keys)) + } + + signingkey, found := kring[0].SigningKeyById(time.Now(), revokedKey, nil) + if found { + t.Errorf("Expected SigningKeyById not to return an encryption key for a revoked key, got %X", signingkey.PublicKey.KeyId) + } +} + +func TestKeyWithSubKeyAndBadSelfSigOrder(t *testing.T) { + // This key was altered so that the self signatures following the + // subkey are in a sub-optimal order. + // + // Note: Should someone have to create a similar key again, look into + // gpgsplit, gpg --dearmor, and gpg --enarmor. + // + // The packet ordering is the following: + // PUBKEY UID UIDSELFSIG SUBKEY SELFSIG1 SELFSIG2 + // + // Where: + // SELFSIG1 expires on 2018-06-14 and was created first + // SELFSIG2 does not expire and was created after SELFSIG1 + // + // Test for RFC 4880 5.2.3.3: + // > An implementation that encounters multiple self-signatures on the + // > same object may resolve the ambiguity in any way it sees fit, but it + // > is RECOMMENDED that priority be given to the most recent self- + // > signature. + // + // This means that we should keep SELFSIG2. + + keys, err := ReadArmoredKeyRing(bytes.NewBufferString(keyWithSubKeyAndBadSelfSigOrder)) + if err != nil { + t.Fatal(err) + } + + if len(keys) != 1 { + t.Fatal("Failed to read key with a sub key and a bad selfsig packet order") + } + + key := keys[0] + + if numKeys, expected := len(key.Subkeys), 1; numKeys != expected { + t.Fatalf("Read %d subkeys, expected %d", numKeys, expected) + } + + subKey := key.Subkeys[0] + var zeroTime time.Time + selfSig, err := subKey.LatestValidBindingSignature(zeroTime) + if err != nil { + t.Fatal("expected a self signature to be found") + } + if lifetime := selfSig.KeyLifetimeSecs; lifetime != nil { + t.Errorf("The signature has a key lifetime (%d), but it should be nil", *lifetime) + } + +} + +/*func TestKeyUsage(t *testing.T) { + kring, err := ReadKeyRing(readerFromHex(subkeyUsageHex)) + if err != nil { + t.Fatal(err) + } + + // subkeyUsageHex contains these keys: + // pub 1024R/2866382A created: 2014-04-01 expires: never usage: SC + // sub 1024R/936C9153 created: 2014-04-01 expires: never usage: E + // sub 1024R/64D5F5BB created: 2014-04-02 expires: never usage: E + // sub 1024D/BC0BA992 created: 2014-04-02 expires: never usage: S + certifiers := []uint64{0xA42704B92866382A} + signers := []uint64{0xA42704B92866382A, 0x42CE2C64BC0BA992} + encrypters := []uint64{0x09C0C7D9936C9153, 0xC104E98664D5F5BB} + + for _, id := range certifiers { + keys := kring.KeysByIdUsage(id, packet.KeyFlagCertify) + if len(keys) == 1 { + if keys[0].PublicKey.KeyId != id { + t.Errorf("Expected to find certifier key id %X, but got %X", id, keys[0].PublicKey.KeyId) + } + } else { + t.Errorf("Expected one match for certifier key id %X, but got %d matches", id, len(keys)) + } + } + + for _, id := range signers { + keys := kring.KeysByIdUsage(id, packet.KeyFlagSign) + if len(keys) == 1 { + if keys[0].PublicKey.KeyId != id { + t.Errorf("Expected to find signing key id %X, but got %X", id, keys[0].PublicKey.KeyId) + } + } else { + t.Errorf("Expected one match for signing key id %X, but got %d matches", id, len(keys)) + } + + // This keyring contains no encryption keys that are also good for signing. + keys = kring.KeysByIdUsage(id, packet.KeyFlagEncryptStorage|packet.KeyFlagEncryptCommunications) + if len(keys) != 0 { + t.Errorf("Unexpected match for encryption key id %X", id) + } + } + + for _, id := range encrypters { + keys := kring.KeysByIdUsage(id, packet.KeyFlagEncryptStorage|packet.KeyFlagEncryptCommunications) + if len(keys) == 1 { + if keys[0].PublicKey.KeyId != id { + t.Errorf("Expected to find encryption key id %X, but got %X", id, keys[0].PublicKey.KeyId) + } + } else { + t.Errorf("Expected one match for encryption key id %X, but got %d matches", id, len(keys)) + } + + // This keyring contains no encryption keys that are also good for signing. + keys = kring.KeysByIdUsage(id, packet.KeyFlagSign) + if len(keys) != 0 { + t.Errorf("Unexpected match for signing key id %X", id) + } + } +}*/ + +func TestIdVerification(t *testing.T) { + kring, err := ReadKeyRing(readerFromHex(testKeys1And2PrivateHex)) + if err != nil { + t.Fatal(err) + } + if err := kring[1].PrivateKey.Decrypt([]byte("passphrase")); err != nil { + t.Fatal(err) + } + + const signedIdentity = "Test Key 1 (RSA)" + const signerIdentity = "Test Key 2 (RSA, encrypted private key)" + config := allowAllAlgorithmsConfig + config.SigLifetimeSecs = 128 + config.SigningIdentity = signerIdentity + if err := kring[0].SignIdentity(signedIdentity, kring[1], &config); err != nil { + t.Fatal(err) + } + + ident, ok := kring[0].Identities[signedIdentity] + if !ok { + t.Fatal("signed identity missing from key after signing") + } + + checked := false + for _, sig := range ident.OtherCertifications { + if sig.Packet.IssuerKeyId == nil || *sig.Packet.IssuerKeyId != kring[1].PrimaryKey.KeyId { + continue + } + + if err := kring[1].PrimaryKey.VerifyUserIdSignature(signedIdentity, kring[0].PrimaryKey, sig.Packet); err != nil { + t.Fatalf("error verifying new identity signature: %s", err) + } + + if sig.Packet.SignerUserId == nil || *sig.Packet.SignerUserId != signerIdentity { + t.Fatalf("wrong or nil signer identity") + } + + if sig.Packet.SigExpired(time.Now()) { + t.Fatalf("signature is expired") + } + + if !sig.Packet.SigExpired(time.Now().Add(129 * time.Second)) { + t.Fatalf("signature has invalid expiration") + } + + checked = true + break + } + + if !checked { + t.Fatal("didn't find identity signature in Entity") + } +} + +func TestNewEntityWithDefaultHash(t *testing.T) { + for _, hash := range hashes { + c := &packet.Config{ + DefaultHash: hash, + Algorithm: packet.PubKeyAlgoEdDSA, + } + entity, err := NewEntity("Golang Gopher", "Test Key", "no-reply@golang.com", c) + if hash == crypto.SHA1 { + if err == nil { + t.Fatal("should fail on SHA1 key creation") + } + continue + } + + if err != nil { + t.Fatal(err) + } + + for _, identity := range entity.Identities { + var zeroTime time.Time + selfSig, err := identity.LatestValidSelfCertification(zeroTime) + if err != nil { + t.Fatal("expected a self signature to be found ") + } + prefs := selfSig.PreferredHash + if len(prefs) == 0 { + t.Fatal("didn't find a preferred hash list in self signature") + } + ph := hashToHashId(c.DefaultHash) + if prefs[0] != ph { + t.Fatalf("Expected preferred hash to be %d, got %d", ph, prefs[0]) + } + } + } +} + +func TestNewEntityNilConfigPreferredHash(t *testing.T) { + entity, err := NewEntity("Golang Gopher", "Test Key", "no-reply@golang.com", nil) + if err != nil { + t.Fatal(err) + } + + for _, identity := range entity.Identities { + var zeroTime time.Time + selfSig, err := identity.LatestValidSelfCertification(zeroTime) + if err != nil { + t.Fatal("expected a self signature to be found ") + } + prefs := selfSig.PreferredHash + if len(prefs) != 1 { + t.Fatal("expected preferred hashes list to be [SHA256]") + } + } +} + +func TestNewEntityCorrectName(t *testing.T) { + entity, err := NewEntity("Golang Gopher", "Test Key", "no-reply@golang.com", nil) + if err != nil { + t.Fatal(err) + } + if len(entity.Identities) != 1 { + t.Fatalf("len(entity.Identities) = %d, want 1", len(entity.Identities)) + } + var got string + for _, i := range entity.Identities { + got = i.Name + } + want := "Golang Gopher (Test Key) " + if got != want { + t.Fatalf("Identity.Name = %q, want %q", got, want) + } +} + +func TestNewEntityWithDefaultCipher(t *testing.T) { + for _, cipher := range ciphers { + c := &packet.Config{ + DefaultCipher: cipher, + } + entity, err := NewEntity("Golang Gopher", "Test Key", "no-reply@golang.com", c) + if err != nil { + t.Fatal(err) + } + + for _, identity := range entity.Identities { + var zeroTime time.Time + selfSig, err := identity.LatestValidSelfCertification(zeroTime) + if err != nil { + t.Fatal("expected a self signature to be found ") + } + prefs := selfSig.PreferredSymmetric + if len(prefs) == 0 { + t.Fatal("didn't find a preferred cipher list") + } + if prefs[0] != uint8(c.DefaultCipher) { + t.Fatalf("Expected preferred cipher to be %d, got %d", uint8(c.DefaultCipher), prefs[0]) + } + } + } +} + +func TestNewEntityNilConfigPreferredSymmetric(t *testing.T) { + entity, err := NewEntity("Golang Gopher", "Test Key", "no-reply@golang.com", nil) + if err != nil { + t.Fatal(err) + } + + for _, identity := range entity.Identities { + var zeroTime time.Time + selfSig, err := identity.LatestValidSelfCertification(zeroTime) + if err != nil { + t.Fatal("expected a self signature to be found ") + } + prefs := selfSig.PreferredSymmetric + if len(prefs) != 1 || prefs[0] != algorithm.AES128.Id() { + t.Fatal("expected preferred ciphers list to be [AES128]") + } + } +} + +func TestNewEntityWithDefaultAead(t *testing.T) { + for _, aeadMode := range aeadModes { + cfg := &packet.Config{ + AEADConfig: &packet.AEADConfig{ + DefaultMode: aeadMode, + }, + } + entity, err := NewEntity("Botvinnik", "1.e4", "tal@chess.com", cfg) + if err != nil { + t.Fatal(err) + } + + for _, identity := range entity.Identities { + var zeroTime time.Time + selfSig, err := identity.LatestValidSelfCertification(zeroTime) + if err != nil { + t.Fatal("expected a self signature to be found ") + } + if len(selfSig.PreferredCipherSuites) == 0 { + t.Fatal("didn't find a preferred mode in self signature") + } + cipher := selfSig.PreferredCipherSuites[0][0] + if cipher != uint8(cfg.Cipher()) { + t.Fatalf("Expected preferred cipher to be %d, got %d", + uint8(cfg.Cipher()), + selfSig.PreferredCipherSuites[0][0]) + } + mode := selfSig.PreferredCipherSuites[0][1] + if mode != uint8(cfg.AEAD().DefaultMode) { + t.Fatalf("Expected preferred mode to be %d, got %d", + uint8(cfg.AEAD().DefaultMode), + selfSig.PreferredCipherSuites[0][1]) + } + } + } +} + +func TestNewEntityPublicSerialization(t *testing.T) { + entity, err := NewEntity("Golang Gopher", "Test Key", "no-reply@golang.com", nil) + if err != nil { + t.Fatal(err) + } + serializedEntity := bytes.NewBuffer(nil) + err = entity.Serialize(serializedEntity) + if err != nil { + t.Fatal(err) + } + + _, err = ReadEntity(packet.NewReader(bytes.NewBuffer(serializedEntity.Bytes()))) + if err != nil { + t.Fatal(err) + } +} + +func TestNewEntityPrivateSerialization(t *testing.T) { + entity, err := NewEntity("Golang Gopher", "Test Key", "no-reply@golang.com", nil) + if err != nil { + t.Fatal(err) + } + serializedEntity := bytes.NewBuffer(nil) + err = entity.SerializePrivateWithoutSigning(serializedEntity, nil) + if err != nil { + t.Fatal(err) + } + + _, err = ReadEntity(packet.NewReader(bytes.NewBuffer(serializedEntity.Bytes()))) + if err != nil { + t.Fatal(err) + } +} + +func TestNotationPacket(t *testing.T) { + keys, err := ReadArmoredKeyRing(bytes.NewBufferString(keyWithNotation)) + if err != nil { + t.Fatal(err) + } + + assertNotationPackets(t, keys) + + serializedEntity := bytes.NewBuffer(nil) + err = keys[0].SerializePrivate(serializedEntity, nil) + if err != nil { + t.Fatal(err) + } + + keys, err = ReadKeyRing(serializedEntity) + if err != nil { + t.Fatal(err) + } + + assertNotationPackets(t, keys) +} + +func assertNotationPackets(t *testing.T, keys EntityList) { + if len(keys) != 1 { + t.Errorf("Failed to accept key, %d", len(keys)) + } + + identity := keys[0].Identities["Test "] + + if numSigs, numExpected := len(identity.SelfCertifications), 1; numSigs != numExpected { + t.Fatalf("got %d signatures, expected %d", numSigs, numExpected) + } + + notations := identity.SelfCertifications[0].Packet.Notations + if numNotations, numExpected := len(notations), 2; numNotations != numExpected { + t.Fatalf("got %d Notation Data subpackets, expected %d", numNotations, numExpected) + } + + if notations[0].IsHumanReadable != true { + t.Fatalf("got false, expected true") + } + + if notations[0].Name != "text@example.com" { + t.Fatalf("got %s, expected text@example.com", notations[0].Name) + } + + if string(notations[0].Value) != "test" { + t.Fatalf("got %s, expected \"test\"", string(notations[0].Value)) + } + + if notations[1].IsHumanReadable != false { + t.Fatalf("got true, expected false") + } + + if notations[1].Name != "binary@example.com" { + t.Fatalf("got %s, expected binary@example.com", notations[1].Name) + } + + if !bytes.Equal(notations[1].Value, []byte{0, 1, 2, 3}) { + t.Fatalf("got %s, expected {0, 1, 2, 3}", string(notations[1].Value)) + } +} + +func TestEntityPrivateSerialization(t *testing.T) { + keys, err := ReadArmoredKeyRing(bytes.NewBufferString(armoredPrivateKeyBlock)) + if err != nil { + t.Fatal(err) + } + + for _, entity := range keys { + serializedEntity := bytes.NewBuffer(nil) + err = entity.SerializePrivateWithoutSigning(serializedEntity, nil) + if err != nil { + t.Fatal(err) + } + + _, err := ReadEntity(packet.NewReader(bytes.NewBuffer(serializedEntity.Bytes()))) + if err != nil { + t.Fatal(err) + } + } +} + +func TestAddUserId(t *testing.T) { + entity, err := NewEntity("Golang Gopher", "Test Key", "no-reply@golang.com", nil) + if err != nil { + t.Fatal(err) + } + + err = entity.AddUserId("Golang Gopher", "Test Key", "add1---@golang.com", nil) + if err != nil { + t.Fatal(err) + } + + err = entity.AddUserId("Golang Gopher", "Test Key", "add2---@golang.com", nil) + if err != nil { + t.Fatal(err) + } + + ignore_err := entity.AddUserId("Golang Gopher", "Test Key", "no-reply@golang.com", nil) + if ignore_err == nil { + t.Fatal(err) + } + + if len(entity.Identities) != 3 { + t.Fatalf("Expected 3 id, got %d", len(entity.Identities)) + } + + for _, sk := range entity.Identities { + var zeroTime time.Time + selfSig, err := sk.LatestValidSelfCertification(zeroTime) + if err != nil { + t.Fatal("expected a self signature to be found") + } + err = entity.PrimaryKey.VerifyUserIdSignature(sk.UserId.Id, entity.PrimaryKey, selfSig) + if err != nil { + t.Errorf("Invalid subkey signature: %v", err) + } + } + + serializedEntity := bytes.NewBuffer(nil) + entity.SerializePrivate(serializedEntity, nil) + + _, err = ReadEntity(packet.NewReader(bytes.NewBuffer(serializedEntity.Bytes()))) + if err != nil { + t.Fatal(err) + } +} +func TestAddSubkey(t *testing.T) { + entity, err := NewEntity("Golang Gopher", "Test Key", "no-reply@golang.com", nil) + if err != nil { + t.Fatal(err) + } + + err = entity.AddSigningSubkey(nil) + if err != nil { + t.Fatal(err) + } + + err = entity.AddEncryptionSubkey(nil) + if err != nil { + t.Fatal(err) + } + + if len(entity.Subkeys) != 3 { + t.Fatalf("Expected 3 subkeys, got %d", len(entity.Subkeys)) + } + + for _, sk := range entity.Subkeys { + var zeroTime time.Time + selfSig, err := sk.LatestValidBindingSignature(zeroTime) + if err != nil { + t.Fatal("expected a self signature to be found") + } + err = entity.PrimaryKey.VerifyKeySignature(sk.PublicKey, selfSig) + if err != nil { + t.Errorf("Invalid subkey signature: %v", err) + } + } + + serializedEntity := bytes.NewBuffer(nil) + entity.SerializePrivate(serializedEntity, nil) + + _, err = ReadEntity(packet.NewReader(bytes.NewBuffer(serializedEntity.Bytes()))) + if err != nil { + t.Fatal(err) + } +} + +func TestAddSubkeySerialized(t *testing.T) { + entity, err := NewEntity("Golang Gopher", "Test Key", "no-reply@golang.com", nil) + if err != nil { + t.Fatal(err) + } + + err = entity.AddSigningSubkey(nil) + if err != nil { + t.Fatal(err) + } + + err = entity.AddEncryptionSubkey(nil) + if err != nil { + t.Fatal(err) + } + + serializedEntity := bytes.NewBuffer(nil) + entity.SerializePrivateWithoutSigning(serializedEntity, nil) + + entity, err = ReadEntity(packet.NewReader(bytes.NewBuffer(serializedEntity.Bytes()))) + if err != nil { + t.Fatal(err) + } + + if len(entity.Subkeys) != 3 { + t.Fatalf("Expected 3 subkeys, got %d", len(entity.Subkeys)) + } + + for _, sk := range entity.Subkeys { + var zeroTime time.Time + selfSig, err := sk.LatestValidBindingSignature(zeroTime) + if err != nil { + t.Fatal("expected a self signature to be found") + } + err = entity.PrimaryKey.VerifyKeySignature(sk.PublicKey, selfSig) + if err != nil { + t.Errorf("Invalid subkey signature: %v", err) + } + } +} + +func TestAddSubkeyWithConfig(t *testing.T) { + c := &packet.Config{ + DefaultHash: crypto.SHA512, + Algorithm: packet.PubKeyAlgoEdDSA, + } + entity, err := NewEntity("Golang Gopher", "Test Key", "no-reply@golang.com", nil) + if err != nil { + t.Fatal(err) + } + + err = entity.AddSigningSubkey(c) + if err != nil { + t.Fatal(err) + } + + err = entity.AddEncryptionSubkey(c) + if err != nil { + t.Fatal(err) + } + + if len(entity.Subkeys) != 3 { + t.Fatalf("Expected 3 subkeys, got %d", len(entity.Subkeys)) + } + + if entity.Subkeys[1].PublicKey.PubKeyAlgo != packet.PubKeyAlgoEdDSA { + t.Fatalf("Expected subkey algorithm: %v, got: %v", packet.PubKeyAlgoEdDSA, + entity.Subkeys[1].PublicKey.PubKeyAlgo) + } + + if entity.Subkeys[2].PublicKey.PubKeyAlgo != packet.PubKeyAlgoECDH { + t.Fatalf("Expected subkey algorithm: %v, got: %v", packet.PubKeyAlgoECDH, + entity.Subkeys[2].PublicKey.PubKeyAlgo) + } + + var zeroTime time.Time + selfSig1, err := entity.Subkeys[1].LatestValidBindingSignature(zeroTime) + if err != nil { + t.Fatal("expected a self signature to be found") + } + if selfSig1.Hash != c.DefaultHash { + t.Fatalf("Expected subkey hash method: %v, got: %v", c.DefaultHash, + selfSig1.Hash) + } + if selfSig1.EmbeddedSignature.Hash != c.DefaultHash { + t.Fatalf("Expected subkey hash method: %v, got: %v", c.DefaultHash, + selfSig1.EmbeddedSignature.Hash) + } + err = entity.PrimaryKey.VerifyKeySignature(entity.Subkeys[1].PublicKey, selfSig1) + if err != nil { + t.Errorf("Invalid subkey signature: %v", err) + } + + selfSig2, err := entity.Subkeys[2].LatestValidBindingSignature(zeroTime) + if err != nil { + t.Fatal("expected a self signature to be found") + } + if selfSig2.Hash != c.DefaultHash { + t.Fatalf("Expected subkey hash method: %v, got: %v", c.DefaultHash, + selfSig2.Hash) + } + err = entity.PrimaryKey.VerifyKeySignature(entity.Subkeys[2].PublicKey, selfSig2) + if err != nil { + t.Errorf("Invalid subkey signature: %v", err) + } + + serializedEntity := bytes.NewBuffer(nil) + entity.SerializePrivate(serializedEntity, nil) + + _, err = ReadEntity(packet.NewReader(bytes.NewBuffer(serializedEntity.Bytes()))) + if err != nil { + t.Fatal(err) + } +} + +func TestAddSubkeyWithConfigSerialized(t *testing.T) { + c := &packet.Config{ + DefaultHash: crypto.SHA512, + Algorithm: packet.PubKeyAlgoEdDSA, + } + entity, err := NewEntity("Golang Gopher", "Test Key", "no-reply@golang.com", nil) + if err != nil { + t.Fatal(err) + } + + err = entity.AddSigningSubkey(c) + if err != nil { + t.Fatal(err) + } + + err = entity.AddEncryptionSubkey(c) + if err != nil { + t.Fatal(err) + } + + serializedEntity := bytes.NewBuffer(nil) + entity.SerializePrivateWithoutSigning(serializedEntity, nil) + + entity, err = ReadEntity(packet.NewReader(bytes.NewBuffer(serializedEntity.Bytes()))) + if err != nil { + t.Fatal(err) + } + + if len(entity.Subkeys) != 3 { + t.Fatalf("Expected 3 subkeys, got %d", len(entity.Subkeys)) + } + + var zeroTime time.Time + selfSig1, err := entity.Subkeys[1].LatestValidBindingSignature(zeroTime) + if err != nil { + t.Fatal("expected a self signature to be found") + } + if entity.Subkeys[1].PublicKey.PubKeyAlgo != packet.PubKeyAlgoEdDSA { + t.Fatalf("Expected subkey algorithm: %v, got: %v", packet.PubKeyAlgoEdDSA, + entity.Subkeys[1].PublicKey.PubKeyAlgo) + } + + if entity.Subkeys[2].PublicKey.PubKeyAlgo != packet.PubKeyAlgoECDH { + t.Fatalf("Expected subkey algorithm: %v, got: %v", packet.PubKeyAlgoECDH, + entity.Subkeys[2].PublicKey.PubKeyAlgo) + } + + if selfSig1.Hash != c.DefaultHash { + t.Fatalf("Expected subkey hash method: %v, got: %v", c.DefaultHash, + selfSig1.Hash) + } + + if selfSig1.EmbeddedSignature.Hash != c.DefaultHash { + t.Fatalf("Expected subkey hash method: %v, got: %v", c.DefaultHash, + selfSig1.EmbeddedSignature.Hash) + } + + selfSig2, err := entity.Subkeys[2].LatestValidBindingSignature(zeroTime) + if err != nil { + t.Fatal("expected a self signature to be found") + } + if selfSig2.Hash != c.DefaultHash { + t.Fatalf("Expected subkey hash method: %v, got: %v", c.DefaultHash, + selfSig2.Hash) + } + err = entity.PrimaryKey.VerifyKeySignature(entity.Subkeys[1].PublicKey, selfSig1) + if err != nil { + t.Errorf("Invalid subkey signature: %v", err) + } + err = entity.PrimaryKey.VerifyKeySignature(entity.Subkeys[2].PublicKey, selfSig2) + if err != nil { + t.Errorf("Invalid subkey signature: %v", err) + } +} + +func TestRevokeKey(t *testing.T) { + entity, err := NewEntity("Golang Gopher", "Test Key", "no-reply@golang.com", nil) + if err != nil { + t.Fatal(err) + } + + err = entity.Revoke(packet.NoReason, "Key revocation", nil) + if err != nil { + t.Fatal(err) + } + + if len(entity.Revocations) == 0 { + t.Fatal("Revocation signature missing from entity") + } + + for _, r := range entity.Revocations { + err = entity.PrimaryKey.VerifyRevocationSignature(r.Packet) + if err != nil { + t.Errorf("Invalid revocation: %v", err) + } + } +} + +func TestRevokeKeyWithConfig(t *testing.T) { + c := &packet.Config{ + DefaultHash: crypto.SHA512, + } + + entity, err := NewEntity("Golang Gopher", "Test Key", "no-reply@golang.com", &packet.Config{ + Algorithm: packet.PubKeyAlgoEdDSA, + }) + if err != nil { + t.Fatal(err) + } + + err = entity.Revoke(packet.NoReason, "Key revocation", c) + if err != nil { + t.Fatal(err) + } + + if len(entity.Revocations) == 0 { + t.Fatal("Revocation signature missing from entity") + } + + if entity.Revocations[0].Packet.Hash != c.DefaultHash { + t.Fatalf("Expected signature hash method: %v, got: %v", c.DefaultHash, + entity.Revocations[0].Packet.Hash) + } + + for _, r := range entity.Revocations { + err = entity.PrimaryKey.VerifyRevocationSignature(r.Packet) + if err != nil { + t.Errorf("Invalid revocation: %v", err) + } + } +} + +func TestRevokeSubkey(t *testing.T) { + entity, err := NewEntity("Golang Gopher", "Test Key", "no-reply@golang.com", nil) + if err != nil { + t.Fatal(err) + } + + err = entity.Subkeys[0].Revoke(packet.NoReason, "Key revocation", nil) + if err != nil { + t.Fatal(err) + } + + if len(entity.Subkeys[0].Revocations) != 1 { + t.Fatalf("Expected 1 subkey revocation signature, got %v", len(entity.Subkeys[0].Revocations)) + } + + revSig := entity.Subkeys[0].Revocations[0] + + err = entity.PrimaryKey.VerifySubkeyRevocationSignature(revSig.Packet, entity.Subkeys[0].PublicKey) + if err != nil { + t.Fatal(err) + } + + if revSig.Packet.RevocationReason == nil { + t.Fatal("Revocation reason was not set") + } + if revSig.Packet.RevocationReasonText == "" { + t.Fatal("Revocation reason text was not set") + } + + serializedEntity := bytes.NewBuffer(nil) + entity.SerializePrivate(serializedEntity, nil) + + // Make sure revocation reason subpackets are not lost during serialization. + newEntity, err := ReadEntity(packet.NewReader(bytes.NewBuffer(serializedEntity.Bytes()))) + if err != nil { + t.Fatal(err) + } + + if newEntity.Subkeys[0].Revocations[0].Packet.RevocationReason == nil { + t.Fatal("Revocation reason lost after serialization of entity") + } + if newEntity.Subkeys[0].Revocations[0].Packet.RevocationReasonText == "" { + t.Fatal("Revocation reason text lost after serialization of entity") + } +} + +/*func TestRevokeSubkeyWithAnotherEntity(t *testing.T) { + entity, err := NewEntity("Golang Gopher", "Test Key", "no-reply@golang.com", nil) + if err != nil { + t.Fatal(err) + } + + sk := entity.Subkeys[0] + + newEntity, err := NewEntity("Golang Gopher", "Test Key", "no-reply@golang.com", nil) + if err != nil { + t.Fatal(err) + } + + err = newEntity.RevokeSubkey(&sk, packet.NoReason, "Key revocation", nil) + if err == nil { + t.Fatal("Entity was able to revoke a subkey owned by a different entity") + } +}*/ + +func TestRevokeSubkeyWithInvalidSignature(t *testing.T) { + entity, err := NewEntity("Golang Gopher", "Test Key", "no-reply@golang.com", nil) + if err != nil { + t.Fatal(err) + } + + sk := entity.Subkeys[0] + sk.Bindings[0].Packet = &packet.Signature{Version: 4} + + err = sk.Revoke(packet.NoReason, "Key revocation", nil) + if err == nil { + t.Fatal("Entity was able to revoke a subkey with invalid signature") + } +} + +func TestRevokeSubkeyWithConfig(t *testing.T) { + c := &packet.Config{ + DefaultHash: crypto.SHA512, + } + + entity, err := NewEntity("Golang Gopher", "Test Key", "no-reply@golang.com", nil) + if err != nil { + t.Fatal(err) + } + + sk := entity.Subkeys[0] + err = sk.Revoke(packet.NoReason, "Key revocation", c) + if err != nil { + t.Fatal(err) + } + + if len(sk.Revocations) != 1 { + t.Fatalf("Expected 1 subkey revocation signature, got %v", len(sk.Revocations)) + } + + revSig := sk.Revocations[0].Packet + + if revSig.Hash != c.DefaultHash { + t.Fatalf("Expected signature hash method: %v, got: %v", c.DefaultHash, revSig.Hash) + } + + err = entity.PrimaryKey.VerifySubkeyRevocationSignature(revSig, sk.PublicKey) + if err != nil { + t.Fatal(err) + } +} + +func TestEncryptAndDecryptPrivateKeys(t *testing.T) { + s2kModesToTest := []s2k.Mode{s2k.IteratedSaltedS2K, s2k.Argon2S2K} + + entity, err := NewEntity("Golang Gopher", "Test Key", "no-reply@golang.com", nil) + if err != nil { + t.Fatal(err) + } + + err = entity.AddSigningSubkey(nil) + if err != nil { + t.Fatal(err) + } + + err = entity.AddEncryptionSubkey(nil) + if err != nil { + t.Fatal(err) + } + for _, mode := range s2kModesToTest { + t.Run(fmt.Sprintf("S2KMode %d", mode), func(t *testing.T) { + passphrase := []byte("password") + config := &packet.Config{ + S2KConfig: &s2k.Config{ + S2KMode: mode, + }, + } + err = entity.EncryptPrivateKeys(passphrase, config) + if err != nil { + t.Fatal(err) + } + + if !entity.PrivateKey.Encrypted { + t.Fatal("Expected encrypted private key") + } + for _, subkey := range entity.Subkeys { + if !subkey.PrivateKey.Encrypted { + t.Fatal("Expected encrypted private key") + } + } + + err = entity.DecryptPrivateKeys(passphrase) + if err != nil { + t.Fatal(err) + } + + if entity.PrivateKey.Encrypted { + t.Fatal("Expected plaintext private key") + } + for _, subkey := range entity.Subkeys { + if subkey.PrivateKey.Encrypted { + t.Fatal("Expected plaintext private key") + } + } + }) + } + +} + +func TestKeyValidateOnDecrypt(t *testing.T) { + randomPassword := make([]byte, 128) + _, err := rand.Read(randomPassword) + if err != nil { + t.Fatal(err) + } + + t.Run("RSA", func(t *testing.T) { + t.Run("Hardcoded:2048 bits", func(t *testing.T) { + keys, err := ReadArmoredKeyRing(bytes.NewBufferString(rsa2048PrivateKey)) + if err != nil { + t.Fatal("Unable to parse hardcoded key: ", err) + } + + if err := keys[0].PrivateKey.Decrypt([]byte("password")); err != nil { + t.Fatal("Unable to decrypt hardcoded key: ", err) + } + + testKeyValidateRsaOnDecrypt(t, keys[0], randomPassword) + }) + + for _, bits := range []int{2048, 3072, 4096} { + t.Run("Generated:"+strconv.Itoa(bits)+" bits", func(t *testing.T) { + key := testGenerateRSA(t, bits) + testKeyValidateRsaOnDecrypt(t, key, randomPassword) + }) + } + }) + + t.Run("ECDSA", func(t *testing.T) { + t.Run("Hardcoded:NIST P-256", func(t *testing.T) { + keys, err := ReadArmoredKeyRing(bytes.NewBufferString(ecdsaPrivateKey)) + if err != nil { + t.Fatal("Unable to parse hardcoded key: ", err) + } + + if err := keys[0].PrivateKey.Decrypt([]byte("password")); err != nil { + t.Fatal("Unable to decrypt hardcoded key: ", err) + } + + if err := keys[0].Subkeys[0].PrivateKey.Decrypt([]byte("password")); err != nil { + t.Fatal("Unable to decrypt hardcoded subkey: ", err) + } + + testKeyValidateEcdsaOnDecrypt(t, keys[0], randomPassword) + }) + + ecdsaCurves := map[string]packet.Curve{ + "NIST P-256": packet.CurveNistP256, + "NIST P-384": packet.CurveNistP384, + "NIST P-521": packet.CurveNistP521, + "Brainpool P-256": packet.CurveBrainpoolP256, + "Brainpool P-384": packet.CurveBrainpoolP384, + "Brainpool P-512": packet.CurveBrainpoolP512, + "SecP256k1": packet.CurveSecP256k1, + } + + for name, curveType := range ecdsaCurves { + t.Run("Generated:"+name, func(t *testing.T) { + key := testGenerateEC(t, packet.PubKeyAlgoECDSA, curveType) + testKeyValidateEcdsaOnDecrypt(t, key, randomPassword) + }) + } + }) + + t.Run("EdDSA", func(t *testing.T) { + eddsaHardcoded := map[string]string{ + "Curve25519": curve25519PrivateKey, + "Curve448": curve448PrivateKey, + } + + for name, skData := range eddsaHardcoded { + t.Run("Hardcoded:"+name, func(t *testing.T) { + keys, err := ReadArmoredKeyRing(bytes.NewBufferString(skData)) + if err != nil { + t.Fatal("Unable to parse hardcoded key: ", err) + } + + testKeyValidateEddsaOnDecrypt(t, keys[0], randomPassword) + }) + } + + eddsaCurves := map[string]packet.Curve{ + "Curve25519": packet.Curve25519, + "Curve448": packet.Curve448, + } + + for name, curveType := range eddsaCurves { + t.Run("Generated:"+name, func(t *testing.T) { + key := testGenerateEC(t, packet.PubKeyAlgoEdDSA, curveType) + testKeyValidateEddsaOnDecrypt(t, key, randomPassword) + }) + } + }) + + t.Run("DSA With El Gamal Subkey", func(t *testing.T) { + testKeyValidateDsaElGamalOnDecrypt(t, randomPassword) + }) +} + +func testGenerateRSA(t *testing.T, bits int) *Entity { + config := &packet.Config{Algorithm: packet.PubKeyAlgoRSA, RSABits: bits} + rsaEntity, err := NewEntity("Golang Gopher", "Test Key", "no-reply@golang.com", config) + if err != nil { + t.Fatal(err) + } + + return rsaEntity +} + +func testKeyValidateRsaOnDecrypt(t *testing.T, rsaEntity *Entity, password []byte) { + var err error + rsaPrimaryKey := rsaEntity.PrivateKey + if err = rsaPrimaryKey.Encrypt(password); err != nil { + t.Fatal(err) + } + if err = rsaPrimaryKey.Decrypt(password); err != nil { + t.Fatal("Valid RSA key was marked as invalid: ", err) + } + + if err = rsaPrimaryKey.Encrypt(password); err != nil { + t.Fatal(err) + } + + // Corrupt public modulo n in primary key + n := rsaPrimaryKey.PublicKey.PublicKey.(*rsa.PublicKey).N + rsaPrimaryKey.PublicKey.PublicKey.(*rsa.PublicKey).N = new(big.Int).Add(n, big.NewInt(2)) + err = rsaPrimaryKey.Decrypt(password) + if _, ok := err.(errors.KeyInvalidError); !ok { + t.Fatal("Failed to detect invalid RSA key") + } +} + +func testGenerateEC(t *testing.T, algorithm packet.PublicKeyAlgorithm, curve packet.Curve) *Entity { + config := &packet.Config{Algorithm: algorithm, Curve: curve} + rsaEntity, err := NewEntity("Golang Gopher", "Test Key", "no-reply@golang.com", config) + if err != nil { + t.Fatal(err) + } + + return rsaEntity +} + +func testKeyValidateEcdsaOnDecrypt(t *testing.T, ecdsaKey *Entity, password []byte) { + var err error + ecdsaPrimaryKey := ecdsaKey.PrivateKey + + if err = ecdsaPrimaryKey.Encrypt(password); err != nil { + t.Fatal(err) + } + + if err := ecdsaPrimaryKey.Decrypt(password); err != nil { + t.Fatal("Valid ECDSA key was marked as invalid: ", err) + } + + if err = ecdsaPrimaryKey.Encrypt(password); err != nil { + t.Fatal(err) + } + + // Corrupt public X in primary key + X := ecdsaPrimaryKey.PublicKey.PublicKey.(*ecdsa.PublicKey).X + ecdsaPrimaryKey.PublicKey.PublicKey.(*ecdsa.PublicKey).X = new(big.Int).Add(X, big.NewInt(1)) + err = ecdsaPrimaryKey.Decrypt(password) + if _, ok := err.(errors.KeyInvalidError); !ok { + t.Fatal("Failed to detect invalid ECDSA key") + } + + // ECDH + ecdsaSubkey := ecdsaKey.Subkeys[0].PrivateKey + if err = ecdsaSubkey.Encrypt(password); err != nil { + t.Fatal(err) + } + + if err := ecdsaSubkey.Decrypt(password); err != nil { + t.Fatal("Valid ECDH key was marked as invalid: ", err) + } + + if err = ecdsaSubkey.Encrypt(password); err != nil { + t.Fatal(err) + } + + // Corrupt public X in subkey + ecdsaSubkey.PublicKey.PublicKey.(*ecdh.PublicKey).Point[5] ^= 1 + + err = ecdsaSubkey.Decrypt(password) + if _, ok := err.(errors.KeyInvalidError); !ok { + t.Fatal("Failed to detect invalid ECDH key") + } +} + +func testKeyValidateEddsaOnDecrypt(t *testing.T, eddsaEntity *Entity, password []byte) { + var err error + + eddsaPrimaryKey := eddsaEntity.PrivateKey // already encrypted + if err = eddsaPrimaryKey.Encrypt(password); err != nil { + t.Fatal(err) + } + + if err := eddsaPrimaryKey.Decrypt(password); err != nil { + t.Fatal("Valid EdDSA key was marked as invalid: ", err) + } + + if err = eddsaPrimaryKey.Encrypt(password); err != nil { + t.Fatal(err) + } + + pubKey := *eddsaPrimaryKey.PublicKey.PublicKey.(*eddsa.PublicKey) + pubKey.X[10] ^= 1 + err = eddsaPrimaryKey.Decrypt(password) + if _, ok := err.(errors.KeyInvalidError); !ok { + t.Fatal("Failed to detect invalid EdDSA key") + } + + // ECDH + ecdhSubkey := eddsaEntity.Subkeys[len(eddsaEntity.Subkeys)-1].PrivateKey + if err = ecdhSubkey.Encrypt(password); err != nil { + t.Fatal(err) + } + + if err := ecdhSubkey.Decrypt(password); err != nil { + t.Fatal("Valid ECDH key was marked as invalid: ", err) + } + + if err = ecdhSubkey.Encrypt(password); err != nil { + t.Fatal(err) + } + + // Corrupt public X in subkey + ecdhSubkey.PublicKey.PublicKey.(*ecdh.PublicKey).Point[5] ^= 1 + err = ecdhSubkey.Decrypt(password) + if _, ok := err.(errors.KeyInvalidError); !ok { + t.Fatal("Failed to detect invalid ECDH key") + } +} + +// ...the legacy bits +func testKeyValidateDsaElGamalOnDecrypt(t *testing.T, randomPassword []byte) { + var err error + + dsaKeys, err := ReadArmoredKeyRing(bytes.NewBufferString(dsaPrivateKeyWithElGamalSubkey)) + if err != nil { + t.Fatal(err) + } + dsaPrimaryKey := dsaKeys[0].PrivateKey // already encrypted + if err := dsaPrimaryKey.Decrypt([]byte("password")); err != nil { + t.Fatal("Valid DSA key was marked as invalid: ", err) + } + + if err = dsaPrimaryKey.Encrypt(randomPassword); err != nil { + t.Fatal(err) + } + // corrupt DSA generator + G := dsaPrimaryKey.PublicKey.PublicKey.(*dsa.PublicKey).G + dsaPrimaryKey.PublicKey.PublicKey.(*dsa.PublicKey).G = new(big.Int).Add(G, big.NewInt(1)) + err = dsaPrimaryKey.Decrypt(randomPassword) + if _, ok := err.(errors.KeyInvalidError); !ok { + t.Fatal("Failed to detect invalid DSA key") + } + + // ElGamal + elGamalSubkey := dsaKeys[0].Subkeys[0].PrivateKey // already encrypted + if err := elGamalSubkey.Decrypt([]byte("password")); err != nil { + t.Fatal("Valid ElGamal key was marked as invalid: ", err) + } + + if err = elGamalSubkey.Encrypt(randomPassword); err != nil { + t.Fatal(err) + } + + // corrupt ElGamal generator + G = elGamalSubkey.PublicKey.PublicKey.(*elgamal.PublicKey).G + elGamalSubkey.PublicKey.PublicKey.(*elgamal.PublicKey).G = new(big.Int).Add(G, big.NewInt(1)) + err = elGamalSubkey.Decrypt(randomPassword) + if _, ok := err.(errors.KeyInvalidError); !ok { + t.Fatal("Failed to detect invalid ElGamal key") + } +} + +var foreignKeysv4 = []string{ + v4Key25519, +} + +func TestReadPrivateForeignV4Key(t *testing.T) { + for _, str := range foreignKeysv4 { + kring, err := ReadArmoredKeyRing(strings.NewReader(str)) + if err != nil { + t.Fatal(err) + } + checkV4Key(t, kring[0]) + } +} + +func checkV4Key(t *testing.T, ent *Entity) { + key := ent.PrimaryKey + if key.Version != 4 { + t.Errorf("wrong key version %d", key.Version) + } + if len(key.Fingerprint) != 20 { + t.Errorf("Wrong fingerprint length: %d", len(key.Fingerprint)) + } + signatures := ent.Revocations + for _, id := range ent.Identities { + signatures = append(signatures, id.SelfCertifications...) + } + for _, sig := range signatures { + if sig == nil { + continue + } + if sig.Packet.Version != 4 { + t.Errorf("wrong signature version %d", sig.Packet.Version) + } + fgptLen := len(sig.Packet.IssuerFingerprint) + if fgptLen != 20 { + t.Errorf("Wrong fingerprint length in signature: %d", fgptLen) + } + } +} + +// Should not panic (generated with go-fuzz) +func TestCorruptKeys(t *testing.T) { + data := `-----BEGIN PGP PUBLIC KEY BLOCK00000 + +mQ00BF00000BCAD0000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000ABE000G0Dn000000000000000000iQ00BB0BAgAGBCG00000` + ReadArmoredKeyRing(strings.NewReader(data)) +} + +func TestMultiIdentity(t *testing.T) { + data := `-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +zaXZE2aAMQ== +=Ty4h +-----END PGP PUBLIC KEY BLOCK-----` + key, err := ReadArmoredKeyRing(strings.NewReader(data)) + if err != nil { + t.Fatal(err) + } + var config *packet.Config + sig, _, err := key[0].PrimaryIdentity(config.Now()) + if err != nil { + t.Fatal(err) + } + if sig.IsPrimaryId == nil || !*sig.IsPrimaryId { + t.Fatal("expected primary identity to be selected") + } +} + +func TestParseKeyWithUnsupportedSubkey(t *testing.T) { + data := `-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +ruxTPhR2zcmefHKGU7cCC/uo +=KLLX +-----END PGP PUBLIC KEY BLOCK-----` + _, err := ReadArmoredKeyRing(strings.NewReader(data)) + if err != nil { + t.Fatal(err) + } +} + +func TestParseUnsupportedSubkey(t *testing.T) { + data := `-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +NciH07RTRuMS/aRhRg4OB8PQROmTnZ+iZS0= +=7DF0 +-----END PGP PUBLIC KEY BLOCK----- +` + _, err := ReadArmoredKeyRing(strings.NewReader(data)) + if err != nil { + t.Fatal(err) + } +} diff --git a/openpgp/v2/keys_test_data.go b/openpgp/v2/keys_test_data.go new file mode 100644 index 000000000..4e0fed110 --- /dev/null +++ b/openpgp/v2/keys_test_data.go @@ -0,0 +1,538 @@ +package v2 + +const expiringKeyHex = "c6c04d0451d0c680010800abbb021fd03ffc4e96618901180c3fdcb060ee69eeead97b91256d11420d80b5f1b51930248044130bd300605cf8a05b7a40d3d8cfb0a910be2e3db50dcd50a9c54064c2a5550801daa834ff4480b33d3d3ca495ff8a4e84a886977d17d998f881241a874083d8b995beab555b6d22b8a4817ab17ac3e7304f7d4d2c05c495fb2218348d3bc13651db1d92732e368a9dd7dcefa6eddff30b94706a9aaee47e9d39321460b740c59c6fc3c2fd8ab6c0fb868cb87c0051f0321301fe0f0e1820b15e7fb7063395769b525005c7e30a7ce85984f5cac00504e7b4fdc45d74958de8388436fd5c7ba9ea121f1c851b5911dd1b47a14d81a09e92ef37721e2325b6790011010001cd00c2c07b041001080025050251d0c680050900278d00060b09070803020415080a0203160201021901021b03021e01000a0910e7b484133a890a35ae4b0800a1beb82e7f28eaf5273d6af9d3391314f6280b2b624eaca2851f89a9ebcaf80ac589ebd509f168bc4322106ca2e2ce77a76e071a3c7444787d65216b5f05e82c77928860b92aace3b7d0327db59492f422eb9dfab7249266d37429870b091a98aba8724c2259ebf8f85093f21255eafa75aa841e31d94f2ac891b9755fed455e539044ee69fc47950b80e003fc9f298d695660f28329eaa38037c367efde1727458e514faf990d439a21461b719edaddf9296d3d0647b43ca56cb8dbf63b4fcf8b9968e7928c463470fab3b98e44d0d95645062f94b2d04fe56bd52822b71934db8ce845622c40b92fcbe765a142e7f38b61a6aa9606c8e8858dcd3b6eb1894acec04d0451d1f06b01080088bea67444e1789390e7c0335c86775502d58ec783d99c8ef4e06de235ed3dd4b0467f6f358d818c7d8989d43ec6d69fcbc8c32632d5a1b605e3fa8e41d695fcdcaa535936cd0157f9040dce362519803b908eafe838bb13216c885c6f93e9e8d5745607f0d062322085d6bdc760969149a8ff8dd9f5c18d9bfe2e6f63a06e17694cf1f67587c6fb70e9aebf90ffc528ca3b615ac7c9d4a21ea4f7c06f2e98fbbd90a859b8608bf9ea638e3a54289ce44c283110d0c45fa458de6251cd6e7baf71f80f12c8978340490fd90c92b81736ae902ed958e478dceae2835953d189c45d182aff02ea2be61b81d8e94430f041d638647b43e2fcb45fd512fbf5068b810011010001c2c06504180108000f050251d1f06b050900081095021b0c000a0910e7b484133a890a35e63407fe2ec88d6d1e6c9ce7553ece0cb2524747217bad29f251d33df84599ffcc900141a355abd62126800744068a5e05dc167056aa9205273dc7765a2ed49db15c2a83b8d6e6429c902136f1e12229086c1c10c0053242c2a4ae1930db58163387a48cad64607ff2153c320e42843dec28e3fce90e7399d63ac0affa2fee1f0adc0953c89eb3f46ef1d6c04328ed13b491669d5120a3782e3ffb7c69575fb77eebd108794f4dda9d34be2bae57e8e59ec8ebfda2f6f06104b2321be408ea146e2db482b00c5055c8618de36ac9716f80da2617e225556d0fce61b01c8cea2d1e0ea982c31711060ca370f2739366e1e708f38405d784b49d16a26cf62d152eae734327cec04d0451d1f07b010800d5af91c5e7c2fd8951c8d254eab0c97cdcb66822f868b79b78c366255059a68fd74ebca9adb9b970cd9e586690e6e0756705432306878c897b10a4b4ca0005966f99ac8fa4e6f9caf54bf8e53844544beee9872a7ac64c119cf1393d96e674254b661f61ee975633d0e8a8672531edb6bb8e211204e7754a9efa802342118eee850beea742bac95a3f706cc2024cf6037a308bb68162b2f53b9a6346a96e6d31871a2456186e24a1c7a82b82ac04afdfd57cd7fb9ba77a9c760d40b76a170f7be525e5fb6a9848cc726e806187710d9b190387df28700f321f988a392899f93815cc937f309129eb94d5299c5547cb2c085898e6639496e70d746c9d3fb9881d0011010001c2c06504180108000f050251d1f07b050900266305021b0c000a0910e7b484133a890a35bff207fd10dfe8c4a6ea1dd30568012b6fd6891a763c87ad0f7a1d112aad9e8e3239378a3b85588c235865bac2e614348cb4f216d7217f53b3ef48c192e0a4d31d64d7bfa5faccf21155965fa156e887056db644a05ad08a85cc6152d1377d9e37b46f4ff462bbe68ace2dc586ef90070314576c985d8037c2ba63f0a7dc17a62e15bd77e88bc61d9d00858979709f12304264a4cf4225c5cf86f12c8e19486cb9cdcc69f18f027e5f16f4ca8b50e28b3115eaff3a345acd21f624aef81f6ede515c1b55b26b84c1e32264754eab672d5489b287e7277ea855e0a5ff2aa9e8b8c76d579a964ec225255f4d57bf66639ccb34b64798846943e162a41096a7002ca21c7f56" +const subkeyUsageHex = "988d04533a52bc010400d26af43085558f65b9e7dbc90cb9238015259aed5e954637adcfa2181548b2d0b60c65f1f42ec5081cbf1bc0a8aa4900acfb77070837c58f26012fbce297d70afe96e759ad63531f0037538e70dbf8e384569b9720d99d8eb39d8d0a2947233ed242436cb6ac7dfe74123354b3d0119b5c235d3dd9c9d6c004f8ffaf67ad8583001101000188b7041f010200210502533b8552170c8001ce094aa433f7040bb2ddf0be3893cb843d0fe70c020700000a0910a42704b92866382aa98404009d63d916a27543da4221c60087c33f1c44bec9998c5438018ed370cca4962876c748e94b73eb39c58eb698063f3fd6346d58dd2a11c0247934c4a9d71f24754f7468f96fb24c3e791dd2392b62f626148ad724189498cbf993db2df7c0cdc2d677c35da0f16cb16c9ce7c33b4de65a4a91b1d21a130ae9cc26067718910ef8e2b417556d627261203c756d627261407379642e65642e61753e88b80413010200220502533a52bc021b03060b090807030206150802090a0b0416020301021e01021780000a0910a42704b92866382a47840400c0c2bd04f5fca586de408b395b3c280a278259c93eaaa8b79a53b97003f8ed502a8a00446dd9947fb462677e4fcac0dac2f0701847d15130aadb6cd9e0705ea0cf5f92f129136c7be21a718d46c8e641eb7f044f2adae573e11ae423a0a9ca51324f03a8a2f34b91fa40c3cc764bee4dccadedb54c768ba0469b683ea53f1c29b88d04533a52bc01040099c92a5d6f8b744224da27bc2369127c35269b58bec179de6bbc038f749344222f85a31933224f26b70243c4e4b2d242f0c4777eaef7b5502f9dad6d8bf3aaeb471210674b74de2d7078af497d55f5cdad97c7bedfbc1b41e8065a97c9c3d344b21fc81d27723af8e374bc595da26ea242dccb6ae497be26eea57e563ed517e90011010001889f0418010200090502533a52bc021b0c000a0910a42704b92866382afa1403ff70284c2de8a043ff51d8d29772602fa98009b7861c540535f874f2c230af8caf5638151a636b21f8255003997ccd29747fdd06777bb24f9593bd7d98a3e887689bf902f999915fcc94625ae487e5d13e6616f89090ebc4fdc7eb5cad8943e4056995bb61c6af37f8043016876a958ec7ebf39c43d20d53b7f546cfa83e8d2604b88d04533b8283010400c0b529316dbdf58b4c54461e7e669dc11c09eb7f73819f178ccd4177b9182b91d138605fcf1e463262fabefa73f94a52b5e15d1904635541c7ea540f07050ce0fb51b73e6f88644cec86e91107c957a114f69554548a85295d2b70bd0b203992f76eb5d493d86d9eabcaa7ef3fc7db7e458438db3fcdb0ca1cc97c638439a9170011010001889f0418010200090502533b8283021b0c000a0910a42704b92866382adc6d0400cfff6258485a21675adb7a811c3e19ebca18851533f75a7ba317950b9997fda8d1a4c8c76505c08c04b6c2cc31dc704d33da36a21273f2b388a1a706f7c3378b66d887197a525936ed9a69acb57fe7f718133da85ec742001c5d1864e9c6c8ea1b94f1c3759cebfd93b18606066c063a63be86085b7e37bdbc65f9a915bf084bb901a204533b85cd110400aed3d2c52af2b38b5b67904b0ef73d6dd7aef86adb770e2b153cd22489654dcc91730892087bb9856ae2d9f7ed1eb48f214243fe86bfe87b349ebd7c30e630e49c07b21fdabf78b7a95c8b7f969e97e3d33f2e074c63552ba64a2ded7badc05ce0ea2be6d53485f6900c7860c7aa76560376ce963d7271b9b54638a4028b573f00a0d8854bfcdb04986141568046202192263b9b67350400aaa1049dbc7943141ef590a70dcb028d730371d92ea4863de715f7f0f16d168bd3dc266c2450457d46dcbbf0b071547e5fbee7700a820c3750b236335d8d5848adb3c0da010e998908dfd93d961480084f3aea20b247034f8988eccb5546efaa35a92d0451df3aaf1aee5aa36a4c4d462c760ecd9cebcabfbe1412b1f21450f203fd126687cd486496e971a87fd9e1a8a765fe654baa219a6871ab97768596ab05c26c1aeea8f1a2c72395a58dbc12ef9640d2b95784e974a4d2d5a9b17c25fedacfe551bda52602de8f6d2e48443f5dd1a2a2a8e6a5e70ecdb88cd6e766ad9745c7ee91d78cc55c3d06536b49c3fee6c3d0b6ff0fb2bf13a314f57c953b8f4d93bf88e70418010200090502533b85cd021b0200520910a42704b92866382a47200419110200060502533b85cd000a091042ce2c64bc0ba99214b2009e26b26852c8b13b10c35768e40e78fbbb48bd084100a0c79d9ea0844fa5853dd3c85ff3ecae6f2c9dd6c557aa04008bbbc964cd65b9b8299d4ebf31f41cc7264b8cf33a00e82c5af022331fac79efc9563a822497ba012953cefe2629f1242fcdcb911dbb2315985bab060bfd58261ace3c654bdbbe2e8ed27a46e836490145c86dc7bae15c011f7e1ffc33730109b9338cd9f483e7cef3d2f396aab5bd80efb6646d7e778270ee99d934d187dd98" +const revokedKeyHex = "988d045331ce82010400c4fdf7b40a5477f206e6ee278eaef888ca73bf9128a9eef9f2f1ddb8b7b71a4c07cfa241f028a04edb405e4d916c61d6beabc333813dc7b484d2b3c52ee233c6a79b1eea4e9cc51596ba9cd5ac5aeb9df62d86ea051055b79d03f8a4fa9f38386f5bd17529138f3325d46801514ea9047977e0829ed728e68636802796801be10011010001889f04200102000905025331d0e3021d03000a0910a401d9f09a34f7c042aa040086631196405b7e6af71026b88e98012eab44aa9849f6ef3fa930c7c9f23deaedba9db1538830f8652fb7648ec3fcade8dbcbf9eaf428e83c6cbcc272201bfe2fbb90d41963397a7c0637a1a9d9448ce695d9790db2dc95433ad7be19eb3de72dacf1d6db82c3644c13eae2a3d072b99bb341debba012c5ce4006a7d34a1f4b94b444526567205265766f6b657220283c52656727732022424d204261726973746122204b657920262530305c303e5c29203c72656740626d626172697374612e636f2e61753e88b704130102002205025331ce82021b03060b090807030206150802090a0b0416020301021e01021780000a0910a401d9f09a34f7c0019c03f75edfbeb6a73e7225ad3cc52724e2872e04260d7daf0d693c170d8c4b243b8767bc7785763533febc62ec2600c30603c433c095453ede59ff2fcabeb84ce32e0ed9d5cf15ffcbc816202b64370d4d77c1e9077d74e94a16fb4fa2e5bec23a56d7a73cf275f91691ae1801a976fcde09e981a2f6327ac27ea1fecf3185df0d56889c04100102000605025331cfb5000a0910fe9645554e8266b64b4303fc084075396674fb6f778d302ac07cef6bc0b5d07b66b2004c44aef711cbac79617ef06d836b4957522d8772dd94bf41a2f4ac8b1ee6d70c57503f837445a74765a076d07b829b8111fc2a918423ddb817ead7ca2a613ef0bfb9c6b3562aec6c3cf3c75ef3031d81d95f6563e4cdcc9960bcb386c5d757b104fcca5fe11fc709df884604101102000605025331cfe7000a09107b15a67f0b3ddc0317f6009e360beea58f29c1d963a22b962b80788c3fa6c84e009d148cfde6b351469b8eae91187eff07ad9d08fcaab88d045331ce820104009f25e20a42b904f3fa555530fe5c46737cf7bd076c35a2a0d22b11f7e0b61a69320b768f4a80fe13980ce380d1cfc4a0cd8fbe2d2e2ef85416668b77208baa65bf973fe8e500e78cc310d7c8705cdb34328bf80e24f0385fce5845c33bc7943cf6b11b02348a23da0bf6428e57c05135f2dc6bd7c1ce325d666d5a5fd2fd5e410011010001889f04180102000905025331ce82021b0c000a0910a401d9f09a34f7c0418003fe34feafcbeaef348a800a0d908a7a6809cc7304017d820f70f0474d5e23cb17e38b67dc6dca282c6ca00961f4ec9edf2738d0f087b1d81e4871ef08e1798010863afb4eac4c44a376cb343be929c5be66a78cfd4456ae9ec6a99d97f4e1c3ff3583351db2147a65c0acef5c003fb544ab3a2e2dc4d43646f58b811a6c3a369d1f" +const revokedSubkeyHex = "988d04533121f6010400aefc803a3e4bb1a61c86e8a86d2726c6a43e0079e9f2713f1fa017e9854c83877f4aced8e331d675c67ea83ddab80aacbfa0b9040bb12d96f5a3d6be09455e2a76546cbd21677537db941cab710216b6d24ec277ee0bd65b910f416737ed120f6b93a9d3b306245c8cfd8394606fdb462e5cf43c551438d2864506c63367fc890011010001b41d416c696365203c616c69636540626d626172697374612e636f2e61753e88bb041301020025021b03060b090807030206150802090a0b0416020301021e01021780050253312798021901000a09104ef7e4beccde97f015a803ff5448437780f63263b0df8442a995e7f76c221351a51edd06f2063d8166cf3157aada4923dfc44aa0f2a6a4da5cf83b7fe722ba8ab416c976e77c6b5682e7f1069026673bd0de56ba06fd5d7a9f177607f277d9b55ff940a638c3e68525c67517e2b3d976899b93ca267f705b3e5efad7d61220e96b618a4497eab8d04403d23f8846041011020006050253312910000a09107b15a67f0b3ddc03d96e009f50b6365d86c4be5d5e9d0ea42d5e56f5794c617700a0ab274e19c2827780016d23417ce89e0a2c0d987d889c04100102000605025331cf7a000a0910a401d9f09a34f7c0ee970400aca292f213041c9f3b3fc49148cbda9d84afee6183c8dd6c5ff2600b29482db5fecd4303797be1ee6d544a20a858080fec43412061c9a71fae4039fd58013b4ae341273e6c66ad4c7cdd9e68245bedb260562e7b166f2461a1032f2b38c0e0e5715fb3d1656979e052b55ca827a76f872b78a9fdae64bc298170bfcebedc1271b41a416c696365203c616c696365407379646973702e6f722e61753e88b804130102002205025331278b021b03060b090807030206150802090a0b0416020301021e01021780000a09104ef7e4beccde97f06a7003fa03c3af68d272ebc1fa08aa72a03b02189c26496a2833d90450801c4e42c5b5f51ad96ce2d2c9cef4b7c02a6a2fcf1412d6a2d486098eb762f5010a201819c17fd2888aec8eda20c65a3b75744de7ee5cc8ac7bfc470cbe3cb982720405a27a3c6a8c229cfe36905f881b02ed5680f6a8f05866efb9d6c5844897e631deb949ca8846041011020006050253312910000a09107b15a67f0b3ddc0347bc009f7fa35db59147469eb6f2c5aaf6428accb138b22800a0caa2f5f0874bacc5909c652a57a31beda65eddd5889c04100102000605025331cf7a000a0910a401d9f09a34f7c0316403ff46f2a5c101256627f16384d34a38fb47a6c88ba60506843e532d91614339fccae5f884a5741e7582ffaf292ba38ee10a270a05f139bde3814b6a077e8cd2db0f105ebea2a83af70d385f13b507fac2ad93ff79d84950328bb86f3074745a8b7f9b64990fb142e2a12976e27e8d09a28dc5621f957ac49091116da410ac3cbde1b88d04533121f6010400cbd785b56905e4192e2fb62a720727d43c4fa487821203cf72138b884b78b701093243e1d8c92a0248a6c0203a5a88693da34af357499abacaf4b3309c640797d03093870a323b4b6f37865f6eaa2838148a67df4735d43a90ca87942554cdf1c4a751b1e75f9fd4ce4e97e278d6c1c7ed59d33441df7d084f3f02beb68896c70011010001889f0418010200090502533121f6021b0c000a09104ef7e4beccde97f0b98b03fc0a5ccf6a372995835a2f5da33b282a7d612c0ab2a97f59cf9fff73e9110981aac2858c41399afa29624a7fd8a0add11654e3d882c0fd199e161bdad65e5e2548f7b68a437ea64293db1246e3011cbb94dc1bcdeaf0f2539bd88ff16d95547144d97cead6a8c5927660a91e6db0d16eb36b7b49a3525b54d1644e65599b032b7eb901a204533127a0110400bd3edaa09eff9809c4edc2c2a0ebe52e53c50a19c1e49ab78e6167bf61473bb08f2050d78a5cbbc6ed66aff7b42cd503f16b4a0b99fa1609681fca9b7ce2bbb1a5b3864d6cdda4d7ef7849d156d534dea30fb0efb9e4cf8959a2b2ce623905882d5430b995a15c3b9fe92906086788b891002924f94abe139b42cbbfaaabe42f00a0b65dc1a1ad27d798adbcb5b5ad02d2688c89477b03ff4eebb6f7b15a73b96a96bed201c0e5e4ea27e4c6e2dd1005b94d4b90137a5b1cf5e01c6226c070c4cc999938101578877ee76d296b9aab8246d57049caacf489e80a3f40589cade790a020b1ac146d6f7a6241184b8c7fcde680eae3188f5dcbe846d7f7bdad34f6fcfca08413e19c1d5df83fc7c7c627d493492e009c2f52a80400a2fe82de87136fd2e8845888c4431b032ba29d9a29a804277e31002a8201fb8591a3e55c7a0d0881496caf8b9fb07544a5a4879291d0dc026a0ea9e5bd88eb4aa4947bbd694b25012e208a250d65ddc6f1eea59d3aed3b4ec15fcab85e2afaa23a40ab1ef9ce3e11e1bc1c34a0e758e7aa64deb8739276df0af7d4121f834a9b88e70418010200090502533127a0021b02005209104ef7e4beccde97f047200419110200060502533127a0000a0910dbce4ee19529437fe045009c0b32f5ead48ee8a7e98fac0dea3d3e6c0e2c552500a0ad71fadc5007cfaf842d9b7db3335a8cdad15d3d1a6404009b08e2c68fe8f3b45c1bb72a4b3278cdf3012aa0f229883ad74aa1f6000bb90b18301b2f85372ca5d6b9bf478d235b733b1b197d19ccca48e9daf8e890cb64546b4ce1b178faccfff07003c172a2d4f5ebaba9f57153955f3f61a9b80a4f5cb959908f8b211b03b7026a8a82fc612bfedd3794969bcf458c4ce92be215a1176ab88d045331d144010400a5063000c5aaf34953c1aa3bfc95045b3aab9882b9a8027fecfe2142dc6b47ba8aca667399990244d513dd0504716908c17d92c65e74219e004f7b83fc125e575dd58efec3ab6dd22e3580106998523dea42ec75bf9aa111734c82df54630bebdff20fe981cfc36c76f865eb1c2fb62c9e85bc3a6e5015a361a2eb1c8431578d0011010001889f04280102000905025331d433021d03000a09104ef7e4beccde97f02e5503ff5e0630d1b65291f4882b6d40a29da4616bb5088717d469fbcc3648b8276de04a04988b1f1b9f3e18f52265c1f8b6c85861691c1a6b8a3a25a1809a0b32ad330aec5667cb4262f4450649184e8113849b05e5ad06a316ea80c001e8e71838190339a6e48bbde30647bcf245134b9a97fa875c1d83a9862cae87ffd7e2c4ce3a1b89013d04180102000905025331d144021b0200a809104ef7e4beccde97f09d2004190102000605025331d144000a0910677815e371c2fd23522203fe22ab62b8e7a151383cea3edd3a12995693911426f8ccf125e1f6426388c0010f88d9ca7da2224aee8d1c12135998640c5e1813d55a93df472faae75bef858457248db41b4505827590aeccf6f9eb646da7f980655dd3050c6897feddddaca90676dee856d66db8923477d251712bb9b3186b4d0114daf7d6b59272b53218dd1da94a03ff64006fcbe71211e5daecd9961fba66cdb6de3f914882c58ba5beddeba7dcb950c1156d7fba18c19ea880dccc800eae335deec34e3b84ac75ffa24864f782f87815cda1c0f634b3dd2fa67cea30811d21723d21d9551fa12ccbcfa62b6d3a15d01307b99925707992556d50065505b090aadb8579083a20fe65bd2a270da9b011" + +const missingCrossSignatureKey = `-----BEGIN PGP PUBLIC KEY BLOCK----- +Charset: UTF-8 + +mQENBFMYynYBCACVOZ3/e8Bm2b9KH9QyIlHGo/i1bnkpqsgXj8tpJ2MIUOnXMMAY +ztW7kKFLCmgVdLIC0vSoLA4yhaLcMojznh/2CcUglZeb6Ao8Gtelr//Rd5DRfPpG +zqcfUo+m+eO1co2Orabw0tZDfGpg5p3AYl0hmxhUyYSc/xUq93xL1UJzBFgYXY54 +QsM8dgeQgFseSk/YvdP5SMx1ev+eraUyiiUtWzWrWC1TdyRa5p4UZg6Rkoppf+WJ +QrW6BWrhAtqATHc8ozV7uJjeONjUEq24roRc/OFZdmQQGK6yrzKnnbA6MdHhqpdo +9kWDcXYb7pSE63Lc+OBa5X2GUVvXJLS/3nrtABEBAAG0F2ludmFsaWQtc2lnbmlu +Zy1zdWJrZXlziQEoBBMBAgASBQJTnKB5AhsBAgsHAhUIAh4BAAoJEO3UDQUIHpI/ +dN4H/idX4FQ1LIZCnpHS/oxoWQWfpRgdKAEM0qCqjMgiipJeEwSQbqjTCynuh5/R +JlODDz85ABR06aoF4l5ebGLQWFCYifPnJZ/Yf5OYcMGtb7dIbqxWVFL9iLMO/oDL +ioI3dotjPui5e+2hI9pVH1UHB/bZ/GvMGo6Zg0XxLPolKQODMVjpjLAQ0YJ3spew +RAmOGre6tIvbDsMBnm8qREt7a07cBJ6XK7xjxYaZHQBiHVxyEWDa6gyANONx8duW +/fhQ/zDTnyVM/ik6VO0Ty9BhPpcEYLFwh5c1ilFari1ta3e6qKo6ZGa9YMk/REhu +yBHd9nTkI+0CiQUmbckUiVjDKKe5AQ0EUxjKdgEIAJcXQeP+NmuciE99YcJoffxv +2gVLU4ZXBNHEaP0mgaJ1+tmMD089vUQAcyGRvw8jfsNsVZQIOAuRxY94aHQhIRHR +bUzBN28ofo/AJJtfx62C15xt6fDKRV6HXYqAiygrHIpEoRLyiN69iScUsjIJeyFL +C8wa72e8pSL6dkHoaV1N9ZH/xmrJ+k0vsgkQaAh9CzYufncDxcwkoP+aOlGtX1gP +WwWoIbz0JwLEMPHBWvDDXQcQPQTYQyj+LGC9U6f9VZHN25E94subM1MjuT9OhN9Y +MLfWaaIc5WyhLFyQKW2Upofn9wSFi8ubyBnv640Dfd0rVmaWv7LNTZpoZ/GbJAMA +EQEAAYkBHwQYAQIACQUCU5ygeQIbAgAKCRDt1A0FCB6SP0zCB/sEzaVR38vpx+OQ +MMynCBJrakiqDmUZv9xtplY7zsHSQjpd6xGflbU2n+iX99Q+nav0ETQZifNUEd4N +1ljDGQejcTyKD6Pkg6wBL3x9/RJye7Zszazm4+toJXZ8xJ3800+BtaPoI39akYJm ++ijzbskvN0v/j5GOFJwQO0pPRAFtdHqRs9Kf4YanxhedB4dIUblzlIJuKsxFit6N +lgGRblagG3Vv2eBszbxzPbJjHCgVLR3RmrVezKOsZjr/2i7X+xLWIR0uD3IN1qOW +CXQxLBizEEmSNVNxsp7KPGTLnqO3bPtqFirxS9PJLIMPTPLNBY7ZYuPNTMqVIUWF +4artDmrG +=7FfJ +-----END PGP PUBLIC KEY BLOCK-----` + +const invalidCrossSignatureKey = `-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQENBFMYynYBCACVOZ3/e8Bm2b9KH9QyIlHGo/i1bnkpqsgXj8tpJ2MIUOnXMMAY +ztW7kKFLCmgVdLIC0vSoLA4yhaLcMojznh/2CcUglZeb6Ao8Gtelr//Rd5DRfPpG +zqcfUo+m+eO1co2Orabw0tZDfGpg5p3AYl0hmxhUyYSc/xUq93xL1UJzBFgYXY54 +QsM8dgeQgFseSk/YvdP5SMx1ev+eraUyiiUtWzWrWC1TdyRa5p4UZg6Rkoppf+WJ +QrW6BWrhAtqATHc8ozV7uJjeONjUEq24roRc/OFZdmQQGK6yrzKnnbA6MdHhqpdo +9kWDcXYb7pSE63Lc+OBa5X2GUVvXJLS/3nrtABEBAAG0F2ludmFsaWQtc2lnbmlu +Zy1zdWJrZXlziQEoBBMBAgASBQJTnKB5AhsBAgsHAhUIAh4BAAoJEO3UDQUIHpI/ +dN4H/idX4FQ1LIZCnpHS/oxoWQWfpRgdKAEM0qCqjMgiipJeEwSQbqjTCynuh5/R +JlODDz85ABR06aoF4l5ebGLQWFCYifPnJZ/Yf5OYcMGtb7dIbqxWVFL9iLMO/oDL +ioI3dotjPui5e+2hI9pVH1UHB/bZ/GvMGo6Zg0XxLPolKQODMVjpjLAQ0YJ3spew +RAmOGre6tIvbDsMBnm8qREt7a07cBJ6XK7xjxYaZHQBiHVxyEWDa6gyANONx8duW +/fhQ/zDTnyVM/ik6VO0Ty9BhPpcEYLFwh5c1ilFari1ta3e6qKo6ZGa9YMk/REhu +yBHd9nTkI+0CiQUmbckUiVjDKKe5AQ0EUxjKdgEIAIINDqlj7X6jYKc6DjwrOkjQ +UIRWbQQar0LwmNilehmt70g5DCL1SYm9q4LcgJJ2Nhxj0/5qqsYib50OSWMcKeEe +iRXpXzv1ObpcQtI5ithp0gR53YPXBib80t3bUzomQ5UyZqAAHzMp3BKC54/vUrSK +FeRaxDzNLrCeyI00+LHNUtwghAqHvdNcsIf8VRumK8oTm3RmDh0TyjASWYbrt9c8 +R1Um3zuoACOVy+mEIgIzsfHq0u7dwYwJB5+KeM7ZLx+HGIYdUYzHuUE1sLwVoELh ++SHIGHI1HDicOjzqgajShuIjj5hZTyQySVprrsLKiXS6NEwHAP20+XjayJ/R3tEA +EQEAAYkCPgQYAQIBKAUCU5ygeQIbAsBdIAQZAQIABgUCU5ygeQAKCRCpVlnFZmhO +52RJB/9uD1MSa0wjY6tHOIgquZcP3bHBvHmrHNMw9HR2wRCMO91ZkhrpdS3ZHtgb +u3/55etj0FdvDo1tb8P8FGSVtO5Vcwf5APM8sbbqoi8L951Q3i7qt847lfhu6sMl +w0LWFvPTOLHrliZHItPRjOltS1WAWfr2jUYhsU9ytaDAJmvf9DujxEOsN5G1YJep +54JCKVCkM/y585Zcnn+yxk/XwqoNQ0/iJUT9qRrZWvoeasxhl1PQcwihCwss44A+ +YXaAt3hbk+6LEQuZoYS73yR3WHj+42tfm7YxRGeubXfgCEz/brETEWXMh4pe0vCL +bfWrmfSPq2rDegYcAybxRQz0lF8PAAoJEO3UDQUIHpI/exkH/0vQfdHA8g/N4T6E +i6b1CUVBAkvtdJpCATZjWPhXmShOw62gkDw306vHPilL4SCvEEi4KzG72zkp6VsB +DSRcpxCwT4mHue+duiy53/aRMtSJ+vDfiV1Vhq+3sWAck/yUtfDU9/u4eFaiNok1 +8/Gd7reyuZt5CiJnpdPpjCwelK21l2w7sHAnJF55ITXdOxI8oG3BRKufz0z5lyDY +s2tXYmhhQIggdgelN8LbcMhWs/PBbtUr6uZlNJG2lW1yscD4aI529VjwJlCeo745 +U7pO4eF05VViUJ2mmfoivL3tkhoTUWhx8xs8xCUcCg8DoEoSIhxtOmoTPR22Z9BL +6LCg2mg= +=Dhm4 +-----END PGP PUBLIC KEY BLOCK-----` + +const goodCrossSignatureKey = `-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1 + +mI0EVUqeVwEEAMufHRrMPWK3gyvi0O0tABCs/oON9zV9KDZlr1a1M91ShCSFwCPo +7r80PxdWVWcj0V5h50/CJYtpN3eE/mUIgW2z1uDYQF1OzrQ8ubrksfsJvpAhENom +lTQEppv9mV8qhcM278teb7TX0pgrUHLYF5CfPdp1L957JLLXoQR/lwLVABEBAAG0 +E2dvb2Qtc2lnbmluZy1zdWJrZXmIuAQTAQIAIgUCVUqeVwIbAwYLCQgHAwIGFQgC +CQoLBBYCAwECHgECF4AACgkQNRjL95IRWP69XQQAlH6+eyXJN4DZTLX78KGjHrsw +6FCvxxClEPtPUjcJy/1KCRQmtLAt9PbbA78dvgzjDeZMZqRAwdjyJhjyg/fkU2OH +7wq4ktjUu+dLcOBb+BFMEY+YjKZhf6EJuVfxoTVr5f82XNPbYHfTho9/OABKH6kv +X70PaKZhbwnwij8Nts65AaIEVUqftREEAJ3WxZfqAX0bTDbQPf2CMT2IVMGDfhK7 +GyubOZgDFFjwUJQvHNvsrbeGLZ0xOBumLINyPO1amIfTgJNm1iiWFWfmnHReGcDl +y5mpYG60Mb79Whdcer7CMm3AqYh/dW4g6IB02NwZMKoUHo3PXmFLxMKXnWyJ0clw +R0LI/Qn509yXAKDh1SO20rqrBM+EAP2c5bfI98kyNwQAi3buu94qo3RR1ZbvfxgW +CKXDVm6N99jdZGNK7FbRifXqzJJDLcXZKLnstnC4Sd3uyfyf1uFhmDLIQRryn5m+ +LBYHfDBPN3kdm7bsZDDq9GbTHiFZUfm/tChVKXWxkhpAmHhU/tH6GGzNSMXuIWSO +aOz3Rqq0ED4NXyNKjdF9MiwD/i83S0ZBc0LmJYt4Z10jtH2B6tYdqnAK29uQaadx +yZCX2scE09UIm32/w7pV77CKr1Cp/4OzAXS1tmFzQ+bX7DR+Gl8t4wxr57VeEMvl +BGw4Vjh3X8//m3xynxycQU18Q1zJ6PkiMyPw2owZ/nss3hpSRKFJsxMLhW3fKmKr +Ey2KiOcEGAECAAkFAlVKn7UCGwIAUgkQNRjL95IRWP5HIAQZEQIABgUCVUqftQAK +CRD98VjDN10SqkWrAKDTpEY8D8HC02E/KVC5YUI01B30wgCgurpILm20kXEDCeHp +C5pygfXw1DJrhAP+NyPJ4um/bU1I+rXaHHJYroYJs8YSweiNcwiHDQn0Engh/mVZ +SqLHvbKh2dL/RXymC3+rjPvQf5cup9bPxNMa6WagdYBNAfzWGtkVISeaQW+cTEp/ +MtgVijRGXR/lGLGETPg2X3Afwn9N9bLMBkBprKgbBqU7lpaoPupxT61bL70= +=vtbN +-----END PGP PUBLIC KEY BLOCK-----` + +const revokedUserIDKey = `-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +7qTZOahrETw= +=IKnw +-----END PGP PUBLIC KEY BLOCK-----` + +const keyWithFirstUserIDRevoked = `-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: OpenPGP.js v4.10.10 +Comment: https://openpgpjs.org + +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 +=+2T8 +-----END PGP PUBLIC KEY BLOCK----- +` + +const keyWithOnlyUserIDRevoked = `-----BEGIN PGP PUBLIC KEY BLOCK----- + +mDMEYYwB7RYJKwYBBAHaRw8BAQdARimqhPPzyGAXmfQJjcqM1QVPzLtURJSzNVll +JV4tEaW0KVJldm9rZWQgUHJpbWFyeSBVc2VyIElEIDxyZXZva2VkQGtleS5jb20+ +iHgEMBYIACAWIQSpyJZAXYqVEFkjyKutFcS0yeB0LQUCYYwCtgIdAAAKCRCtFcS0 +yeB0LbSsAQD8OYMaaBjrdzzpwIkP1stgmPd4/kzN/ZG28Ywl6a5F5QEA5Xg7aq4e +/t6Fsb4F5iqB956kSPe6YJrikobD/tBbMwSIkAQTFggAOBYhBKnIlkBdipUQWSPI +q60VxLTJ4HQtBQJhjAHtAhsDBQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJEK0V +xLTJ4HQtBaoBAPZL7luTCji+Tqhn7XNfFE/0QIahCt8k9wfO1cGlB3inAQDf8Tzw +ZGR5fNluUcNoVxQT7bUSFStbaGo3k0BaOYPbCLg4BGGMAe0SCisGAQQBl1UBBQEB +B0DLwSpveSrbIO/IVZD13yrs1XuB3FURZUnafGrRq7+jUAMBCAeIeAQYFggAIBYh +BKnIlkBdipUQWSPIq60VxLTJ4HQtBQJhjAHtAhsMAAoJEK0VxLTJ4HQtZ1oA/j9u +8+p3xTNzsmabTL6BkNbMeB/RUKCrlm6woM6AV+vxAQCcXTn3JC2sNoNrLoXuVzaA +mcG3/TwG5GSQUUPkrDsGDA== +=mFWy +-----END PGP PUBLIC KEY BLOCK----- +` + +const keyWithSubKey = `-----BEGIN PGP PUBLIC KEY BLOCK----- + +mI0EWyKwKQEEALwXhKBnyaaNFeK3ljfc/qn9X/QFw+28EUfgZPHjRmHubuXLE2uR +s3ZoSXY2z7Dkv+NyHYMt8p+X8q5fR7JvUjK2XbPyKoiJVnHINll83yl67DaWfKNL +EjNoO0kIfbXfCkZ7EG6DL+iKtuxniGTcnGT47e+HJSqb/STpLMnWwXjBABEBAAG0 +I0dvbGFuZyBHb3BoZXIgPG5vLXJlcGx5QGdvbGFuZy5jb20+iM4EEwEKADgWIQQ/ +lRafP/p9PytHbwxMvYJsOQdOOAUCWyKwKQIbAwULCQgHAwUVCgkICwUWAgMBAAIe +AQIXgAAKCRBMvYJsOQdOOOsFBAC62mXww8XuqvYLcVOvHkWLT6mhxrQOJXnlfpn7 +2uBV9CMhoG/Ycd43NONsJrB95Apr9TDIqWnVszNbqPCuBhZQSGLdbiDKjxnCWBk0 +69qv4RNtkpOhYB7jK4s8F5oQZqId6JasT/PmJTH92mhBYhhTQr0GYFuPX2UJdkw9 +Sn9C67iNBFsisDUBBAC3A+Yo9lgCnxi/pfskyLrweYif6kIXWLAtLTsM6g/6jt7b +wTrknuCPyTv0QKGXsAEe/cK/Xq3HvX9WfXPGIHc/X56ZIsHQ+RLowbZV/Lhok1IW +FAuQm8axr/by80cRwFnzhfPc/ukkAq2Qyj4hLsGblu6mxeAhzcp8aqmWOO2H9QAR +AQABiLYEKAEKACAWIQQ/lRafP/p9PytHbwxMvYJsOQdOOAUCWyK16gIdAAAKCRBM +vYJsOQdOOB1vA/4u4uLONsE+2GVOyBsHyy7uTdkuxaR9b54A/cz6jT/tzUbeIzgx +22neWhgvIEghnUZd0vEyK9k1wy5vbDlEo6nKzHso32N1QExGr5upRERAxweDxGOj +7luDwNypI7QcifE64lS/JmlnunwRCdRWMKc0Fp+7jtRc5mpwyHN/Suf5RokBagQY +AQoAIBYhBD+VFp8/+n0/K0dvDEy9gmw5B044BQJbIrA1AhsCAL8JEEy9gmw5B044 +tCAEGQEKAB0WIQSNdnkaWY6t62iX336UXbGvYdhXJwUCWyKwNQAKCRCUXbGvYdhX +JxJSA/9fCPHP6sUtGF1o3G1a3yvOUDGr1JWcct9U+QpbCt1mZoNopCNDDQAJvDWl +mvDgHfuogmgNJRjOMznvahbF+wpTXmB7LS0SK412gJzl1fFIpK4bgnhu0TwxNsO1 +8UkCZWqxRMgcNUn9z6XWONK8dgt5JNvHSHrwF4CxxwjL23AAtK+FA/UUoi3U4kbC +0XnSr1Sl+mrzQi1+H7xyMe7zjqe+gGANtskqexHzwWPUJCPZ5qpIa2l8ghiUim6b +4ymJ+N8/T8Yva1FaPEqfMzzqJr8McYFm0URioXJPvOAlRxdHPteZ0qUopt/Jawxl +Xt6B9h1YpeLoJwjwsvbi98UTRs0jXwoY +=3fWu +-----END PGP PUBLIC KEY BLOCK-----` + +const keyWithSubKeyAndBadSelfSigOrder = `-----BEGIN PGP PUBLIC KEY BLOCK----- + +mI0EWyLLDQEEAOqIOpJ/ha1OYAGduu9tS3rBz5vyjbNgJO4sFveEM0mgsHQ0X9/L +plonW+d0gRoO1dhJ8QICjDAc6+cna1DE3tEb5m6JtQ30teLZuqrR398Cf6w7NNVz +r3lrlmnH9JaKRuXl7tZciwyovneBfZVCdtsRZjaLI1uMQCz/BToiYe3DABEBAAG0 +I0dvbGFuZyBHb3BoZXIgPG5vLXJlcGx5QGdvbGFuZy5jb20+iM4EEwEKADgWIQRZ +sixZOfQcZdW0wUqmgmdsv1O9xgUCWyLLDQIbAwULCQgHAwUVCgkICwUWAgMBAAIe +AQIXgAAKCRCmgmdsv1O9xql2A/4pix98NxjhdsXtazA9agpAKeADf9tG4Za27Gj+ +3DCww/E4iP2X35jZimSm/30QRB6j08uGCqd9vXkkJxtOt63y/IpVOtWX6vMWSTUm +k8xKkaYMP0/IzKNJ1qC/qYEUYpwERBKg9Z+k99E2Ql4kRHdxXUHq6OzY79H18Y+s +GdeM/riNBFsiyxsBBAC54Pxg/8ZWaZX1phGdwfe5mek27SOYpC0AxIDCSOdMeQ6G +HPk38pywl1d+S+KmF/F4Tdi+kWro62O4eG2uc/T8JQuRDUhSjX0Qa51gPzJrUOVT +CFyUkiZ/3ZDhtXkgfuso8ua2ChBgR9Ngr4v43tSqa9y6AK7v0qjxD1x+xMrjXQAR +AQABiQFxBBgBCgAmAhsCFiEEWbIsWTn0HGXVtMFKpoJnbL9TvcYFAlsizTIFCQAN +MRcAv7QgBBkBCgAdFiEEJcoVUVJIk5RWj1c/o62jUpRPICQFAlsiyxsACgkQo62j +UpRPICQq5gQApoWIigZxXFoM0uw4uJBS5JFZtirTANvirZV5RhndwHeMN6JttaBS +YnjyA4+n1D+zB2VqliD2QrsX12KJN6rGOehCtEIClQ1Hodo9nC6kMzzAwW1O8bZs +nRJmXV+bsvD4sidLZLjdwOVa3Cxh6pvq4Uur6a7/UYx121hEY0Qx0s8JEKaCZ2y/ +U73GGi0D/i20VW8AWYAPACm2zMlzExKTOAV01YTQH/3vW0WLrOse53WcIVZga6es +HuO4So0SOEAvxKMe5HpRIu2dJxTvd99Bo9xk9xJU0AoFrO0vNCRnL+5y68xMlODK +lEw5/kl0jeaTBp6xX0HDQOEVOpPGUwWV4Ij2EnvfNDXaE1vK1kffiQFrBBgBCgAg +AhsCFiEEWbIsWTn0HGXVtMFKpoJnbL9TvcYFAlsi0AYAv7QgBBkBCgAdFiEEJcoV +UVJIk5RWj1c/o62jUpRPICQFAlsiyxsACgkQo62jUpRPICQq5gQApoWIigZxXFoM +0uw4uJBS5JFZtirTANvirZV5RhndwHeMN6JttaBSYnjyA4+n1D+zB2VqliD2QrsX +12KJN6rGOehCtEIClQ1Hodo9nC6kMzzAwW1O8bZsnRJmXV+bsvD4sidLZLjdwOVa +3Cxh6pvq4Uur6a7/UYx121hEY0Qx0s8JEKaCZ2y/U73GRl0EAJokkXmy4zKDHWWi +wvK9gi2gQgRkVnu2AiONxJb5vjeLhM/07BRmH6K1o+w3fOeEQp4FjXj1eQ5fPSM6 +Hhwx2CTl9SDnPSBMiKXsEFRkmwQ2AAsQZLmQZvKBkLZYeBiwf+IY621eYDhZfo+G +1dh1WoUCyREZsJQg2YoIpWIcvw+a +=bNRo +-----END PGP PUBLIC KEY BLOCK----- +` + +const onlySubkeyNoPrivateKey = `-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: GnuPG v1 + +lQCVBFggvocBBAC7vBsHn7MKmS6IiiZNTXdciplVgS9cqVd+RTdIAoyNTcsiV1H0 +GQ3QtodOPeDlQDNoqinqaobd7R9g3m3hS53Nor7yBZkCWQ5x9v9JxRtoAq0sklh1 +I1X2zEqZk2l6YrfBF/64zWrhjnW3j23szkrAIVu0faQXbQ4z56tmZrw11wARAQAB +/gdlAkdOVQG0CUdOVSBEdW1teYi4BBMBAgAiBQJYIL6HAhsDBgsJCAcDAgYVCAIJ +CgsEFgIDAQIeAQIXgAAKCRCd1xxWp1CYAnjGA/9synn6ZXJUKAXQzySgmCZvCIbl +rqBfEpxwLG4Q/lONhm5vthAE0z49I8hj5Gc5e2tLYUtq0o0OCRdCrYHa/efOYWpJ +6RsK99bePOisVzmOABLIgZkcr022kHoMCmkPgv9CUGKP1yqbGl+zzAwQfUjRUmvD +ZIcWLHi2ge4GzPMPi50B2ARYIL6cAQQAxWHnicKejAFcFcF1/3gUSgSH7eiwuBPX +M7vDdgGzlve1o1jbV4tzrjN9jsCl6r0nJPDMfBSzgLr1auNTRG6HpJ4abcOx86ED +Ad+avDcQPZb7z3dPhH/gb2lQejZsHh7bbeOS8WMSzHV3RqCLd8J/xwWPNR5zKn1f +yp4IGfopidMAEQEAAQAD+wQOelnR82+dxyM2IFmZdOB9wSXQeCVOvxSaNMh6Y3lk +UOOkO8Nlic4x0ungQRvjoRs4wBmCuwFK/MII6jKui0B7dn/NDf51i7rGdNGuJXDH +e676By1sEY/NGkc74jr74T+5GWNU64W0vkpfgVmjSAzsUtpmhJMXsc7beBhJdnVl +AgDKCb8hZqj1alcdmLoNvb7ibA3K/V8J462CPD7bMySPBa/uayoFhNxibpoXml2r +oOtHa5izF3b0/9JY97F6rqkdAgD6GdTJ+xmlCoz1Sewoif1I6krq6xoa7gOYpIXo +UL1Afr+LiJeyAnF/M34j/kjIVmPanZJjry0kkjHE5ILjH3uvAf4/6n9np+Th8ujS +YDCIzKwR7639+H+qccOaddCep8Y6KGUMVdD/vTKEx1rMtK+hK/CDkkkxnFslifMJ +kqoqv3WUqCWJAT0EGAECAAkFAlggvpwCGwIAqAkQndccVqdQmAKdIAQZAQIABgUC +WCC+nAAKCRDmGUholQPwvQk+A/9latnSsR5s5/1A9TFki11GzSEnfLbx46FYOdkW +n3YBxZoPQGxNA1vIn8GmouxZInw9CF4jdOJxEdzLlYQJ9YLTLtN5tQEMl/19/bR8 +/qLacAZ9IOezYRWxxZsyn6//jfl7A0Y+FV59d4YajKkEfItcIIlgVBSW6T+TNQT3 +R+EH5HJ/A/4/AN0CmBhhE2vGzTnVU0VPrE4V64pjn1rufFdclgpixNZCuuqpKpoE +VVHn6mnBf4njKjZrAGPs5kfQ+H4NsM7v3Zz4yV6deu9FZc4O6E+V1WJ38rO8eBix +7G2jko106CC6vtxsCPVIzY7aaG3H5pjRtomw+pX7SzrQ7FUg2PGumg== +=F/T0 +-----END PGP PRIVATE KEY BLOCK-----` + +const ecdsaPrivateKey = `-----BEGIN PGP PRIVATE KEY BLOCK----- + +xaUEX1KsSRMIKoZIzj0DAQcCAwTpYqJsnJiFhKKh+8TulWD+lVmerBFNS+Ii +B+nlG3T0xQQ4Sy5eIjJ0CExIQQzi3EElF/Z2l4F3WC5taFA11NgA/gkDCHSS +PThf1M2K4LN8F1MRcvR+sb7i0nH55ojkwuVB1DE6jqIT9m9i+mX1tzjSAS+6 +lPQiweCJvG7xTC7Hs3AzRapf/r1At4TB+v+5G2/CKynNFEJpbGwgPGJpbGxA +aG9tZS5jb20+wncEEBMIAB8FAl9SrEkGCwkHCAMCBBUICgIDFgIBAhkBAhsD +Ah4BAAoJEMpwT3+q3+xqw5UBAMebZN9isEZ1ML+R/jWAAWMwa/knMugrEZ1v +Bl9+ZwM0AQCZdf80/wYY4Nve01qSRFv8OmKswLli3TvDv6FKc4cLz8epBF9S +rEkSCCqGSM49AwEHAgMEAjKnT9b5wY2bf9TpAV3d7OUfPOxKj9c4VzeVzSrH +AtQgo/MuI1cdYVURicV4i76DNjFhQHQFTk7BrC+C2u1yqQMBCAf+CQMIHImA +iYfzQtjgQWSFZYUkCFpbbwhNF0ch+3HNaZkaHCnZRIsWsRnc6FCb6lRQyK9+ +Dq59kHlduE5QgY40894jfmP2JdJHU6nBdYrivbEdbMJhBBgTCAAJBQJfUqxJ +AhsMAAoJEMpwT3+q3+xqUI0BAMykhV08kQ4Ip9Qlbss6Jdufv7YrU0Vd5hou +b5TmiPd0APoDBh3qIic+aLLUcAuG3+Gt1P1AbUlmqV61ozn1WfHxfw== +=KLN8 +-----END PGP PRIVATE KEY BLOCK-----` + +const dsaPrivateKeyWithElGamalSubkey = `-----BEGIN PGP PRIVATE KEY BLOCK----- + +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 +4g== +=XZm8 +-----END PGP PRIVATE KEY BLOCK-----` + +// https://tests.sequoia-pgp.org/#Certificate_expiration +// P _ U p +const expiringPrimaryUIDKey = `-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +=AmgT +-----END PGP PUBLIC KEY BLOCK-----` + +const rsa2048PrivateKey = `-----BEGIN PGP PRIVATE KEY BLOCK----- +Comment: gpg (GnuPG) 2.2.27 with libgcrypt 1.9.4 + +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 +=u442 +-----END PGP PRIVATE KEY BLOCK-----` + +const curve25519PrivateKey = `-----BEGIN PGP PRIVATE KEY BLOCK----- +Comment: gpg (GnuPG) 2.2.27 with libgcrypt 1.9.4 + +lFgEYvTtQBYJKwYBBAHaRw8BAQdAxsNXLbrk5xOjpO24VhOMvQ0/F+JcyIkckMDH +X3FIGxcAAQDFOlunZWYuPsCx5JLp78vKqUTfgef9TGG4oD6I/Sa0zBMstCJHb2xh +bmcgR29waGVyIDxnb2xhbmdAZXhhbXBsZS5vcmc+iJAEExYIADgWIQSFQHEOazmo +h1ldII4MvfnLQ4JBNwUCYvTtQAIbAwULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAK +CRAMvfnLQ4JBN5yeAQCKdry8B5ScCPrev2+UByMCss7Sdu5RhomCFsHdNPLcKAEA +8ugei+1owHsV+3cGwWWzKk6sLa8ZN87i3SKuOGp9DQycXQRi9O1AEgorBgEEAZdV +AQUBAQdA5CubPp8l7lrVQ25h7Hx5XN2C8xanRnnpcjzEooCaEA0DAQgHAAD/Rpc+ +sOZUXrFk9HOWB1XU41LoWbDBoG8sP8RWAVYwD5AQRYh4BBgWCAAgFiEEhUBxDms5 +qIdZXSCODL35y0OCQTcFAmL07UACGwwACgkQDL35y0OCQTcvdwEA7lb5g/YisrEf +iq660uwMGoepLUfvtqKzuQ6heYe83y0BAN65Ffg5HYOJzUEi0kZQRf7OhdtuL2kJ +SRXn8DmCTfEB +=cELM +-----END PGP PRIVATE KEY BLOCK-----` + +const curve448PrivateKey = `-----BEGIN PGP PRIVATE KEY BLOCK----- +Comment: C1DB 65D5 80D7 B922 7254 4B1E A699 9895 FABA CE52 + +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 +QgqsfguR1PqPuJxpXV4bSr6CGAAAAA== +=MSvh +-----END PGP PRIVATE KEY BLOCK-----` + +const keyWithNotation = `-----BEGIN PGP PRIVATE KEY BLOCK----- + +xVgEY9gIshYJKwYBBAHaRw8BAQdAF25fSM8OpFlXZhop4Qpqo5ywGZ4jgWlR +ppjhIKDthREAAQC+LFpzFcMJYcjxGKzBGHN0Px2jU4d04YSRnFAik+lVVQ6u +zRdUZXN0IDx0ZXN0QGV4YW1wbGUuY29tPsLACgQQFgoAfAUCY9gIsgQLCQcI +CRD/utJOCym8pR0UgAAAAAAQAAR0ZXh0QGV4YW1wbGUuY29tdGVzdB8UAAAA +AAASAARiaW5hcnlAZXhhbXBsZS5jb20AAQIDAxUICgQWAAIBAhkBAhsDAh4B +FiEEEMCQTUVGKgCX5rDQ/7rSTgspvKUAAPl5AP9Npz90LxzrB97Qr2DrGwfG +wuYn4FSYwtuPfZHHeoIabwD/QEbvpQJ/NBb9EAZuow4Rirlt1yv19mmnF+j5 +8yUzhQjHXQRj2AiyEgorBgEEAZdVAQUBAQdARXAo30DmKcyUg6co7OUm0RNT +z9iqFbDBzA8A47JEt1MDAQgHAAD/XKK3lBm0SqMR558HLWdBrNG6NqKuqb5X +joCML987ZNgRD8J4BBgWCAAqBQJj2AiyCRD/utJOCym8pQIbDBYhBBDAkE1F +RioAl+aw0P+60k4LKbylAADRxgEAg7UfBDiDPp5LHcW9D+SgFHk6+GyEU4ev +VppQxdtxPvAA/34snHBX7Twnip1nMt7P4e2hDiw/hwQ7oqioOvc6jMkP +=Z8YJ +-----END PGP PRIVATE KEY BLOCK----- +` diff --git a/openpgp/v2/keys_v5_test.go b/openpgp/v2/keys_v5_test.go new file mode 100644 index 000000000..16fa2be24 --- /dev/null +++ b/openpgp/v2/keys_v5_test.go @@ -0,0 +1,206 @@ +package v2 + +import ( + "bytes" + "io/ioutil" + "strings" + "testing" + + "github.com/ProtonMail/go-crypto/openpgp/armor" +) + +var foreignKeys = []string{ + v5PrivKey, +} + +func TestReadPrivateForeignV5Key(t *testing.T) { + for _, str := range foreignKeys { + kring, err := ReadArmoredKeyRing(strings.NewReader(str)) + if err != nil { + t.Fatal(err) + } + checkV5Key(t, kring[0]) + } +} + +// TODO: Replace message with a correctly generated one. +func testV5ForeignSignedMessage(t *testing.T) { + kring, err := ReadArmoredKeyRing(strings.NewReader(v5PrivKey)) + if err != nil { + t.Fatal(err) + } + msg := strings.NewReader(v5PrivKeyMsg) + // Unarmor + block, err := armor.Decode(msg) + if err != nil { + return + } + md, err := ReadMessage(block.Body, kring, nil, nil) + if len(md.SignatureCandidates) < 1 { + t.Fatal("no signature found") + } + if md.SignatureCandidates[0].SignedBy == nil { + t.Fatal("incorrect signer") + } + if err != nil { + t.Fatal(err) + } + // Consume UnverifiedBody + body, err := ioutil.ReadAll(md.UnverifiedBody) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(body, []byte("test")) { + t.Fatal("bad body") + } + if md.SignatureError != nil { + t.Fatal(md.SignatureError) + } + if err != nil { + t.Fatal(err) + } +} + +// Depricated +/* +func TestReadPrivateEncryptedV5Key(t *testing.T) { + c := &packet.Config{V5Keys: true} + e, err := NewEntity("V5 Key Owner", "V5 Key", "v5@pm.me", c) + if err != nil { + t.Fatal(err) + } + password := []byte("test v5 key # password") + // Encrypt private key + if err = e.PrivateKey.Encrypt(password); err != nil { + t.Fatal(err) + } + // Encrypt subkeys + for _, sub := range e.Subkeys { + if err = sub.PrivateKey.Encrypt(password); err != nil { + t.Fatal(err) + } + } + // Serialize, Read + serializedEntity := bytes.NewBuffer(nil) + err = e.SerializePrivateWithoutSigning(serializedEntity, nil) + if err != nil { + t.Fatal(err) + } + el, err := ReadKeyRing(serializedEntity) + if err != nil { + t.Fatal(err) + } + + // Decrypt + if el[0].PrivateKey == nil { + t.Fatal("No private key found") + } + if err = el[0].PrivateKey.Decrypt(password); err != nil { + t.Error(err) + } + + // Decrypt subkeys + for _, sub := range e.Subkeys { + if err = sub.PrivateKey.Decrypt(password); err != nil { + t.Error(err) + } + } + + checkV5Key(t, el[0]) +}*/ + +func TestReadPrivateSerializeForeignV5Key(t *testing.T) { + for _, str := range foreignKeys { + el, err := ReadArmoredKeyRing(strings.NewReader(str)) + if err != nil { + t.Fatal(err) + } + checkSerializeRead(t, el[0]) + } +} + +// Depricated +/* +func TestNewEntitySerializeV5Key(t *testing.T) { + c := &packet.Config{V5Keys: true} + e, err := NewEntity("V5 Key Owner", "V5 Key", "v5@pm.me", c) + if err != nil { + t.Fatal(err) + } + checkSerializeRead(t, e) +} + +func TestNewEntityV5Key(t *testing.T) { + c := &packet.Config{ + V5Keys: true, + } + e, err := NewEntity("V5 Key Owner", "V5 Key", "v5@pm.me", c) + if err != nil { + t.Fatal(err) + } + checkV5Key(t, e) +}*/ + +func checkV5Key(t *testing.T, ent *Entity) { + key := ent.PrimaryKey + if key.Version != 5 { + t.Errorf("wrong key version %d", key.Version) + } + if len(key.Fingerprint) != 32 { + t.Errorf("Wrong fingerprint length: %d", len(key.Fingerprint)) + } + signatures := ent.Revocations + for _, id := range ent.Identities { + signatures = append(signatures, id.SelfCertifications...) + } + for _, sig := range signatures { + if sig == nil { + continue + } + if sig.Packet.Version != 5 { + t.Errorf("wrong signature version %d", sig.Packet.Version) + } + fgptLen := len(sig.Packet.IssuerFingerprint) + if fgptLen != 32 { + t.Errorf("Wrong fingerprint length in signature: %d", fgptLen) + } + } +} + +func checkSerializeRead(t *testing.T, e *Entity) { + // Entity serialize + serializedEntity := bytes.NewBuffer(nil) + err := e.Serialize(serializedEntity) + if err != nil { + t.Fatal(err) + } + el, err := ReadKeyRing(serializedEntity) + if err != nil { + t.Fatal(err) + } + checkV5Key(t, el[0]) + + // Without signing + serializedEntity = bytes.NewBuffer(nil) + err = e.SerializePrivateWithoutSigning(serializedEntity, nil) + if err != nil { + t.Fatal(err) + } + el, err = ReadKeyRing(serializedEntity) + if err != nil { + t.Fatal(err) + } + checkV5Key(t, el[0]) + + // Private + serializedEntity = bytes.NewBuffer(nil) + err = e.SerializePrivate(serializedEntity, nil) + if err != nil { + t.Fatal(err) + } + el, err = ReadKeyRing(serializedEntity) + if err != nil { + t.Fatal(err) + } + checkV5Key(t, el[0]) +} diff --git a/openpgp/v2/keys_v6_test.go b/openpgp/v2/keys_v6_test.go new file mode 100644 index 000000000..abab52cad --- /dev/null +++ b/openpgp/v2/keys_v6_test.go @@ -0,0 +1,203 @@ +package v2 + +import ( + "bytes" + "crypto" + "strings" + "testing" + "time" + + "github.com/ProtonMail/go-crypto/openpgp/packet" +) + +var foreignKeysV6 = []string{ + v6PrivKey, + v6ArgonSealedPrivKey, +} + +func TestReadPrivateForeignV6Key(t *testing.T) { + for _, str := range foreignKeysV6 { + kring, err := ReadArmoredKeyRing(strings.NewReader(str)) + if err != nil { + t.Fatal(err) + } + checkV6Key(t, kring[0]) + } +} + +func TestReadPrivateForeignV6KeyAndDecrypt(t *testing.T) { + password := []byte("correct horse battery staple") + kring, err := ReadArmoredKeyRing(strings.NewReader(v6ArgonSealedPrivKey)) + if err != nil { + t.Fatal(err) + } + key := kring[0] + if key.PrivateKey != nil && key.PrivateKey.Encrypted { + if err := key.PrivateKey.Decrypt(password); err != nil { + t.Fatal(err) + } + } + for _, sub := range key.Subkeys { + if sub.PrivateKey != nil && sub.PrivateKey.Encrypted { + if err := key.PrivateKey.Decrypt(password); err != nil { + t.Fatal(err) + } + } + } + checkV6Key(t, kring[0]) +} + +func TestReadPrivateEncryptedV6Key(t *testing.T) { + c := &packet.Config{V6Keys: true} + e, err := NewEntity("V6 Key Owner", "V6 Key", "v6@pm.me", c) + if err != nil { + t.Fatal(err) + } + password := []byte("test v6 key # password") + // Encrypt private key + if err = e.PrivateKey.Encrypt(password); err != nil { + t.Fatal(err) + } + // Encrypt subkeys + for _, sub := range e.Subkeys { + if err = sub.PrivateKey.Encrypt(password); err != nil { + t.Fatal(err) + } + } + // Serialize, Read + serializedEntity := bytes.NewBuffer(nil) + err = e.SerializePrivateWithoutSigning(serializedEntity, nil) + if err != nil { + t.Fatal(err) + } + el, err := ReadKeyRing(serializedEntity) + if err != nil { + t.Fatal(err) + } + + // Decrypt + if el[0].PrivateKey == nil { + t.Fatal("No private key found") + } + if err = el[0].PrivateKey.Decrypt(password); err != nil { + t.Error(err) + } + + // Decrypt subkeys + for _, sub := range e.Subkeys { + if err = sub.PrivateKey.Decrypt(password); err != nil { + t.Error(err) + } + } + + checkV6Key(t, el[0]) +} + +func TestNewEntitySerializeV6Key(t *testing.T) { + c := &packet.Config{V6Keys: true} + e, err := NewEntity("V6 Key Owner", "V6 Key", "v6@pm.me", c) + if err != nil { + t.Fatal(err) + } + checkSerializeReadv6(t, e) +} + +func TestNewEntityV6Key(t *testing.T) { + c := &packet.Config{ + V6Keys: true, + } + e, err := NewEntity("V6 Key Owner", "V6 Key", "v6@pm.me", c) + if err != nil { + t.Fatal(err) + } + checkV6Key(t, e) +} + +func checkV6Key(t *testing.T, ent *Entity) { + key := ent.PrimaryKey + if key.Version != 6 { + t.Errorf("wrong key version %d", key.Version) + } + if len(key.Fingerprint) != 32 { + t.Errorf("Wrong fingerprint length: %d", len(key.Fingerprint)) + } + signatures := ent.Revocations + for _, id := range ent.Identities { + signatures = append(signatures, id.SelfCertifications...) + } + for _, sig := range signatures { + if sig.Packet == nil { + continue + } + if sig.Packet.Version != 6 { + t.Errorf("wrong signature version %d", sig.Packet.Version) + } + fgptLen := len(sig.Packet.IssuerFingerprint) + if fgptLen != 32 { + t.Errorf("Wrong fingerprint length in signature: %d", fgptLen) + } + } +} + +func checkSerializeReadv6(t *testing.T, e *Entity) { + // Entity serialize + serializedEntity := bytes.NewBuffer(nil) + err := e.Serialize(serializedEntity) + if err != nil { + t.Fatal(err) + } + el, err := ReadKeyRing(serializedEntity) + if err != nil { + t.Fatal(err) + } + checkV6Key(t, el[0]) + + // Without signing + serializedEntity = bytes.NewBuffer(nil) + err = e.SerializePrivateWithoutSigning(serializedEntity, nil) + if err != nil { + t.Fatal(err) + } + el, err = ReadKeyRing(serializedEntity) + if err != nil { + t.Fatal(err) + } + checkV6Key(t, el[0]) + + // Private + serializedEntity = bytes.NewBuffer(nil) + err = e.SerializePrivate(serializedEntity, nil) + if err != nil { + t.Fatal(err) + } + el, err = ReadKeyRing(serializedEntity) + if err != nil { + t.Fatal(err) + } + checkV6Key(t, el[0]) +} + +func TestNewEntityWithDefaultHashV6(t *testing.T) { + for _, hash := range hashes[:5] { + c := &packet.Config{ + V6Keys: true, + DefaultHash: hash, + } + entity, err := NewEntity("Golang Gopher", "Test Key", "no-reply@golang.com", c) + if hash == crypto.SHA1 { + if err == nil { + t.Fatal("should fail on SHA1 key creation") + } + continue + } + var zeroTime time.Time + selfSig, err := entity.PrimarySelfSignature(zeroTime) + if err != nil { + t.Fatal("self-signature should be found") + } + prefs := selfSig.PreferredHash + if prefs == nil { + t.Fatal(err) + } + } +} diff --git a/openpgp/v2/read.go b/openpgp/v2/read.go new file mode 100644 index 000000000..a52221f11 --- /dev/null +++ b/openpgp/v2/read.go @@ -0,0 +1,779 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package openpgp implements high level operations on OpenPGP messages. +package v2 // import "github.com/ProtonMail/go-crypto/openpgp" + +import ( + "bytes" + "crypto" + _ "crypto/sha256" + _ "crypto/sha512" + "hash" + "io" + "io/ioutil" + "strconv" + "time" + + "github.com/ProtonMail/go-crypto/openpgp/armor" + "github.com/ProtonMail/go-crypto/openpgp/errors" + "github.com/ProtonMail/go-crypto/openpgp/internal/algorithm" + "github.com/ProtonMail/go-crypto/openpgp/packet" + _ "golang.org/x/crypto/sha3" +) + +// SignatureType is the armor type for a PGP signature. +var SignatureType = "PGP SIGNATURE" + +// readArmored reads an armored block with the given type. +func readArmored(r io.Reader, expectedType string) (body io.Reader, err error) { + block, err := armor.Decode(r) + if err != nil { + return + } + + if block.Type != expectedType { + return nil, errors.InvalidArgumentError("expected '" + expectedType + "', got: " + block.Type) + } + + return block.Body, nil +} + +// MessageDetails contains the result of parsing an OpenPGP encrypted and/or +// signed message. +type MessageDetails struct { + IsEncrypted bool // true if the message was encrypted. + EncryptedToKeyIds []uint64 // the list of recipient key ids. + IsSymmetricallyEncrypted bool // true if a passphrase could have decrypted the message. + DecryptedWith Key // the private key used to decrypt the message, if any. + DecryptedWithAlgorithm packet.CipherFunction // Stores the algorithm used to decrypt the message, if any. + IsSigned bool // true if the message is signed. + LiteralData *packet.LiteralData // the metadata of the contents + UnverifiedBody io.Reader // the contents of the message. + CheckRecipients bool // Indicates if the intended recipients should be checked + + SessionKey []byte // Caches the session key if the flag in packet.Config is set to true and a session key was present. + + // If IsSigned is true then the signature candidates will + // be verified as UnverifiedBody is read. The signature cannot be + // checked until the whole of UnverifiedBody is read so UnverifiedBody + // must be consumed until EOF before the data can be trusted. Even if a + // message isn't signed (or the signer is unknown) the data may contain + // an authentication code that is only checked once UnverifiedBody has + // been consumed. Once EOF has been seen, the following fields are + // valid. (An authentication code failure is reported as a + // SignatureError error when reading from UnverifiedBody.) + IsVerified bool // true if the signatures have been verified else false + SignatureCandidates []*SignatureCandidate // stores state for all signatures of this message + SignedBy *Key // the issuer key of the fist successfully verified signature, if any found. + Signature *packet.Signature // the first successfully verified signature, if any found. + // SignatureError is nil if one of the signatures in the message verifies successfully + // else it points to the last observed signature error. + // The error of each signature verification can be inspected by iterating trough + // SignatureCandidates. + SignatureError error + // SelectedCandidate points to the signature candidate the SignatureError error stems from or + // the selected successfully verified signature candidate. + SelectedCandidate *SignatureCandidate + + decrypted io.ReadCloser +} + +// A PromptFunction is used as a callback by functions that may need to decrypt +// a private key, or prompt for a passphrase. It is called with a list of +// acceptable, encrypted private keys and a boolean that indicates whether a +// passphrase is usable. It should either decrypt a private key or return a +// passphrase to try. If the decrypted private key or given passphrase isn't +// correct, the function will be called again, forever. Any error returned will +// be passed up. +type PromptFunction func(keys []Key, symmetric bool) ([]byte, error) + +// A keyEnvelopePair is used to store a private key with the envelope that +// contains a symmetric key, encrypted with that key. +type keyEnvelopePair struct { + key Key + encryptedKey *packet.EncryptedKey +} + +// ReadMessage parses an OpenPGP message that may be signed and/or encrypted. +// The given KeyRing should contain both public keys (for signature +// verification) and, possibly encrypted, private keys for decrypting. +// If config is nil, sensible defaults will be used. +func ReadMessage(r io.Reader, keyring KeyRing, prompt PromptFunction, config *packet.Config) (md *MessageDetails, err error) { + var p packet.Packet + + var symKeys []*packet.SymmetricKeyEncrypted + var pubKeys []keyEnvelopePair + // Integrity protected encrypted packet: SymmetricallyEncrypted or AEADEncrypted + var edp packet.EncryptedDataPacket + var packets packet.PacketReader + if config.StrictPacketSequence() { + packets = packet.NewCheckReader(r) + } else { + packets = packet.NewReader(r) + } + md = new(MessageDetails) + md.IsEncrypted = true + md.CheckRecipients = config.IntendedRecipients() + + // The message, if encrypted, starts with a number of packets + // containing an encrypted decryption key. The decryption key is either + // encrypted to a public key, or with a passphrase. This loop + // collects these packets. +ParsePackets: + for { + p, err = packets.Next() + if err != nil { + return nil, err + } + switch p := p.(type) { + case *packet.SymmetricKeyEncrypted: + // This packet contains the decryption key encrypted with a passphrase. + md.IsSymmetricallyEncrypted = true + symKeys = append(symKeys, p) + case *packet.EncryptedKey: + // This packet contains the decryption key encrypted to a public key. + md.EncryptedToKeyIds = append(md.EncryptedToKeyIds, p.KeyId) + switch p.Algo { + case packet.PubKeyAlgoRSA, packet.PubKeyAlgoRSAEncryptOnly, + packet.PubKeyAlgoElGamal, packet.PubKeyAlgoECDH, + packet.PubKeyAlgoX25519, packet.PubKeyAlgoX448: + break + default: + continue + } + if keyring != nil { + unverifiedEntities := keyring.EntitiesById(p.KeyId) + for _, unverifiedEntity := range unverifiedEntities { + // Do not check key expiration to allow decryption of old messages + keys := unverifiedEntity.DecryptionKeys(p.KeyId, time.Time{}) + for _, key := range keys { + pubKeys = append(pubKeys, keyEnvelopePair{key, p}) + } + } + } + case *packet.SymmetricallyEncrypted: + if !p.IntegrityProtected && !config.AllowUnauthenticatedMessages() { + return nil, errors.UnsupportedError("message is not integrity protected") + } + edp = p + if p.Version == 2 { // SEIPD v2 packet stores the decryption algorithm + md.DecryptedWithAlgorithm = p.Cipher + } + break ParsePackets + case *packet.AEADEncrypted: + edp = p + break ParsePackets + case *packet.Compressed, *packet.LiteralData, *packet.OnePassSignature, *packet.Signature: + // This message isn't encrypted. + if len(symKeys) != 0 || len(pubKeys) != 0 { + return nil, errors.StructuralError("key material not followed by encrypted message") + } + packets.Unread(p) + md.IsEncrypted = false + return readSignedMessage(packets, md, keyring, config) + } + } + + var candidates []Key + var decrypted io.ReadCloser + + // Now that we have the list of encrypted keys we need to decrypt at + // least one of them or, if we cannot, we need to call the prompt + // function so that it can decrypt a key or give us a passphrase. +FindKey: + for { + // See if any of the keys already have a private key available + candidates = candidates[:0] + candidateFingerprints := make(map[string]bool) + + for _, pk := range pubKeys { + if pk.key.PrivateKey == nil { + continue + } + if !pk.key.PrivateKey.Encrypted { + if len(pk.encryptedKey.Key) == 0 { + errDec := pk.encryptedKey.Decrypt(pk.key.PrivateKey, config) + if errDec != nil { + continue + } + } + // Try to decrypt symmetrically encrypted + decrypted, err = edp.Decrypt(pk.encryptedKey.CipherFunc, pk.encryptedKey.Key) + if err != nil && err != errors.ErrKeyIncorrect { + return nil, err + } + if decrypted != nil { + md.DecryptedWith = pk.key + if md.DecryptedWithAlgorithm == 0 { // if no SEIPD v2 packet, key packet stores the decryption algorithm + md.DecryptedWithAlgorithm = pk.encryptedKey.CipherFunc + } + if config.RetrieveSessionKey() { + md.SessionKey = pk.encryptedKey.Key + } + break FindKey + } + } else { + fpr := string(pk.key.PublicKey.Fingerprint[:]) + if v := candidateFingerprints[fpr]; v { + continue + } + candidates = append(candidates, pk.key) + candidateFingerprints[fpr] = true + } + } + + if len(candidates) == 0 && len(symKeys) == 0 { + return nil, errors.ErrKeyIncorrect + } + + if prompt == nil { + return nil, errors.ErrKeyIncorrect + } + + passphrase, err := prompt(candidates, len(symKeys) != 0) + if err != nil { + return nil, err + } + + // Try the symmetric passphrase first + if len(symKeys) != 0 && passphrase != nil { + for _, s := range symKeys { + key, cipherFunc, err := s.Decrypt(passphrase) + // In v4, on wrong passphrase, session key decryption is very likely to result in an invalid cipherFunc: + // only for < 5% of cases we will proceed to decrypt the data + if err == nil { + decrypted, err = edp.Decrypt(cipherFunc, key) + if err != nil { + return nil, err + } + if decrypted != nil { + if md.DecryptedWithAlgorithm == 0 { // if no SEIPD v2 packet, key packet stores the decryption algorithm + md.DecryptedWithAlgorithm = cipherFunc + } + if config.RetrieveSessionKey() { + md.SessionKey = key + } + break FindKey + } + } + } + } + } + + md.decrypted = decrypted + if err := packets.Push(decrypted); err != nil { + return nil, err + } + mdFinal, sensitiveParsingErr := readSignedMessage(packets, md, keyring, config) + if sensitiveParsingErr != nil { + return nil, errors.StructuralError("parsing error") + } + return mdFinal, nil +} + +// SignatureCandidate keeps state about a signature that can be potentially verified. +type SignatureCandidate struct { + OPSVersion int + SigType packet.SignatureType + HashAlgorithm crypto.Hash + PubKeyAlgo packet.PublicKeyAlgorithm + IssuerKeyId uint64 + IssuerFingerprint []byte // v6 only + Salt []byte // v6 only + + SignedByEntity *Entity + SignedBy *Key // the key of the signer, if available. (OPS) + SignatureError error // nil if the signature is valid or not checked. + CorrespondingSig *packet.Signature // the candidate's signature packet + Hash, WrappedHash hash.Hash // hashes for this signature candidate (OPS) +} + +func newSignatureCandidate(ops *packet.OnePassSignature) (sigCandidate *SignatureCandidate) { + sigCandidate = &SignatureCandidate{ + OPSVersion: ops.Version, + SigType: ops.SigType, + HashAlgorithm: ops.Hash, + PubKeyAlgo: ops.PubKeyAlgo, + IssuerKeyId: ops.KeyId, + Salt: ops.Salt, + IssuerFingerprint: ops.KeyFingerprint, + } + sigCandidate.Hash, sigCandidate.WrappedHash, sigCandidate.SignatureError = hashForSignature( + sigCandidate.HashAlgorithm, + sigCandidate.SigType, + sigCandidate.Salt, + ) + return +} + +func newSignatureCandidateFromSignature(sig *packet.Signature) (sigCandidate *SignatureCandidate) { + sigCandidate = &SignatureCandidate{ + SigType: sig.SigType, + HashAlgorithm: sig.Hash, + PubKeyAlgo: sig.PubKeyAlgo, + IssuerKeyId: *sig.IssuerKeyId, + IssuerFingerprint: sig.IssuerFingerprint, + Salt: sig.Salt(), + } + sigCandidate.OPSVersion = 3 + if sig.Version == 6 { + sigCandidate.OPSVersion = sig.Version + } + sigCandidate.Hash, sigCandidate.WrappedHash, sigCandidate.SignatureError = hashForSignature( + sigCandidate.HashAlgorithm, + sigCandidate.SigType, + sigCandidate.Salt, + ) + sigCandidate.CorrespondingSig = sig + return +} + +func (sc *SignatureCandidate) validate() bool { + correspondingSig := sc.CorrespondingSig + invalidV3 := sc.OPSVersion == 3 && correspondingSig.Version == 6 + invalidV6 := (sc.OPSVersion == 6 && correspondingSig.Version != 6) || + (sc.OPSVersion == 6 && !bytes.Equal(sc.IssuerFingerprint, correspondingSig.IssuerFingerprint)) || + (sc.OPSVersion == 6 && !bytes.Equal(sc.Salt, correspondingSig.Salt())) + return correspondingSig != nil && + sc.SigType == correspondingSig.SigType && + sc.HashAlgorithm == correspondingSig.Hash && + sc.PubKeyAlgo == correspondingSig.PubKeyAlgo && + sc.IssuerKeyId == *correspondingSig.IssuerKeyId && + !invalidV3 && + !invalidV6 +} + +// readSignedMessage reads a possibly signed message if mdin is non-zero then +// that structure is updated and returned. Otherwise a fresh MessageDetails is +// used. +func readSignedMessage(packets packet.PacketReader, mdin *MessageDetails, keyring KeyRing, config *packet.Config) (md *MessageDetails, err error) { + if mdin == nil { + mdin = new(MessageDetails) + } + md = mdin + + var p packet.Packet + var prevLast bool +FindLiteralData: + for { + p, err = packets.Next() + if err != nil { + return nil, err + } + switch p := p.(type) { + case *packet.Compressed: + if err := packets.Push(p.Body); err != nil { + return nil, err + } + case *packet.OnePassSignature: + if prevLast { + return nil, errors.UnsupportedError("nested signature packets") + } + + if p.IsLast { + prevLast = true + } + + sigCandidate := newSignatureCandidate(p) + md.IsSigned = true + if keyring != nil { + keys := keyring.EntitiesById(p.KeyId) + if len(keys) > 0 { + sigCandidate.SignedByEntity = keys[0] + } + } + // If a message contains more than one one-pass signature, then the Signature packets bracket the message + md.SignatureCandidates = append([]*SignatureCandidate{sigCandidate}, md.SignatureCandidates...) + case *packet.Signature: + // Old style signature i.e., sig | literal + sigCandidate := newSignatureCandidateFromSignature(p) + md.IsSigned = true + if keyring != nil { + keys := keyring.EntitiesById(sigCandidate.IssuerKeyId) + if len(keys) > 0 { + sigCandidate.SignedByEntity = keys[0] + } + } + md.SignatureCandidates = append([]*SignatureCandidate{sigCandidate}, md.SignatureCandidates...) + case *packet.LiteralData: + md.LiteralData = p + break FindLiteralData + case *packet.EncryptedKey, + *packet.SymmetricKeyEncrypted, + *packet.AEADEncrypted, + *packet.SymmetricallyEncrypted: + return nil, errors.UnsupportedError("cannot read signed message with encrypted data") + } + } + + if md.IsSigned { + md.UnverifiedBody = &signatureCheckReader{packets, md, config, md.LiteralData.Body} + } else { + md.UnverifiedBody = checkReader{md, packets} + } + + return md, nil +} + +func wrapHashForSignature(hashFunc hash.Hash, sigType packet.SignatureType) (hash.Hash, error) { + switch sigType { + case packet.SigTypeBinary: + return hashFunc, nil + case packet.SigTypeText: + return NewCanonicalTextHash(hashFunc), nil + } + return nil, errors.UnsupportedError("unsupported signature type: " + strconv.Itoa(int(sigType))) +} + +// hashForSignature returns a pair of hashes that can be used to verify a +// signature. The signature may specify that the contents of the signed message +// should be preprocessed (i.e. to normalize line endings). Thus this function +// returns two hashes. The second should be used to hash the message itself and +// performs any needed preprocessing. +func hashForSignature(hashFunc crypto.Hash, sigType packet.SignatureType, sigSalt []byte) (hash.Hash, hash.Hash, error) { + if _, ok := algorithm.HashToHashIdWithSha1(hashFunc); !ok { + return nil, nil, errors.UnsupportedError("unsupported hash function") + } + if !hashFunc.Available() { + return nil, nil, errors.UnsupportedError("hash not available: " + strconv.Itoa(int(hashFunc))) + } + h := hashFunc.New() + if sigSalt != nil { + h.Write(sigSalt) + } + wrappedHash, err := wrapHashForSignature(h, sigType) + if err != nil { + return nil, nil, err + } + switch sigType { + case packet.SigTypeBinary: + return h, wrappedHash, nil + case packet.SigTypeText: + return h, wrappedHash, nil + } + return nil, nil, errors.UnsupportedError("unsupported signature type: " + strconv.Itoa(int(sigType))) +} + +// checkReader wraps an io.Reader from a LiteralData packet. When it sees EOF +// it closes the ReadCloser from any SymmetricallyEncrypted packet to trigger +// MDC checks. +type checkReader struct { + md *MessageDetails + packets packet.PacketReader +} + +func (cr checkReader) Read(buf []byte) (int, error) { + n, sensitiveParsingError := cr.md.LiteralData.Body.Read(buf) + if sensitiveParsingError == io.EOF { + for { + _, err := cr.packets.Next() + if err == io.EOF { + break + } + if err != nil { + return 0, err + } + } + if cr.md.decrypted != nil { + if mdcErr := cr.md.decrypted.Close(); mdcErr != nil { + return n, mdcErr + } + } + return n, io.EOF + } + if sensitiveParsingError != nil { + return n, errors.StructuralError("parsing error") + } + return n, nil +} + +// signatureCheckReader wraps an io.Reader from a LiteralData packet and hashes +// the data as it is read. When it sees an EOF from the underlying io.Reader +// it parses and checks a trailing Signature packet and triggers any MDC checks. +type signatureCheckReader struct { + packets packet.PacketReader + md *MessageDetails + config *packet.Config + data io.Reader +} + +func (scr *signatureCheckReader) Read(buf []byte) (int, error) { + n, sensitiveParsingError := scr.data.Read(buf) + + for _, candidate := range scr.md.SignatureCandidates { + if candidate.SignatureError == nil && candidate.SignedByEntity != nil { + candidate.WrappedHash.Write(buf[:n]) + } + } + + if sensitiveParsingError == io.EOF { + var signatures []*packet.Signature + + // Read all signature packets. + + for { + p, err := scr.packets.Next() + if err == io.EOF { + break + } + if err != nil { + return 0, errors.StructuralError("parsing error") + } + if sig, ok := p.(*packet.Signature); ok { + if sig.Version == 5 && scr.md.LiteralData != nil && (sig.SigType == 0x00 || sig.SigType == 0x01) { + sig.Metadata = scr.md.LiteralData + } + signatures = append(signatures, sig) + } + } + numberOfOpsSignatures := 0 + for _, candidate := range scr.md.SignatureCandidates { + if candidate.CorrespondingSig == nil { + numberOfOpsSignatures++ + } + } + + if len(signatures) != numberOfOpsSignatures { + // Cannot handle this case yet with no information about invalid packets, should fail. + // This case can happen if a known OPS version is used but an unknown signature version. + noMatchError := errors.StructuralError("number of ops signature candidates does not match the number of signature packets") + for _, candidate := range scr.md.SignatureCandidates { + candidate.SignatureError = noMatchError + } + signatures = nil + } else { + var sigIndex int + // Verify all signature candidates. + for _, candidate := range scr.md.SignatureCandidates { + if candidate.CorrespondingSig == nil { + candidate.CorrespondingSig = signatures[sigIndex] + sigIndex++ + } + if !candidate.validate() { + candidate.SignatureError = errors.StructuralError("signature does not match the expected ops data") + } + if candidate.SignatureError == nil { + sig := candidate.CorrespondingSig + if candidate.SignedByEntity == nil { + candidate.SignatureError = errors.ErrUnknownIssuer + scr.md.SignatureError = candidate.SignatureError + } else { + // Verify and retrieve signing key at signature creation time + key, err := candidate.SignedByEntity.signingKeyByIdUsage(sig.CreationTime, candidate.IssuerKeyId, packet.KeyFlagSign, scr.config) + if err != nil { + candidate.SignatureError = err + continue + } else { + candidate.SignedBy = &key + } + signatureError := key.PublicKey.VerifySignature(candidate.Hash, sig) + if signatureError == nil { + signatureError = checkSignatureDetails(&key, sig, scr.config) + } + if !scr.md.IsSymmetricallyEncrypted && len(sig.IntendedRecipients) > 0 && scr.md.CheckRecipients && signatureError == nil { + if !scr.md.IsEncrypted { + signatureError = errors.SignatureError("intended recipients in non-encrypted message") + } else { + // Check signature matches one of the recipients + signatureError = checkIntendedRecipientsMatch(&scr.md.DecryptedWith, sig) + } + } + candidate.SignatureError = signatureError + } + } + } + } + + // Check if there is a valid candidate. + for _, candidate := range scr.md.SignatureCandidates { + // md.SignatureError points to the last error, if + // all signature verifications have failed. + scr.md.SignatureError = candidate.SignatureError + scr.md.SelectedCandidate = candidate + if candidate.SignatureError == nil { + // There is a valid signature. + scr.md.Signature = candidate.CorrespondingSig + scr.md.SignedBy = candidate.SignedBy + break + } + } + + if len(scr.md.SignatureCandidates) == 0 { + scr.md.SignatureError = errors.StructuralError("no signature found") + } + + if scr.md.SignatureError == nil && scr.md.Signature == nil { + scr.md.SignatureError = errors.StructuralError("no matching signature found") + } + + scr.md.IsVerified = true + + for { + _, err := scr.packets.Next() + if err == io.EOF { + break + } + if err != nil { + return 0, errors.StructuralError("parsing error") + } + + } + + // The SymmetricallyEncrypted packet, if any, might have an + // unsigned hash of its own. In order to check this we need to + // close that Reader. + if scr.md.decrypted != nil { + mdcErr := scr.md.decrypted.Close() + if mdcErr != nil { + return n, mdcErr + } + } + return n, io.EOF + } + + if sensitiveParsingError != nil { + return n, errors.StructuralError("parsing error") + } + + return n, nil +} + +// VerifyDetachedSignature takes a signed file and a detached signature and +// returns the signature packet and the entity the signature was signed by, +// if any, and a possible signature verification error. +// If the signer isn't known, ErrUnknownIssuer is returned. +func VerifyDetachedSignature(keyring KeyRing, signed, signature io.Reader, config *packet.Config) (sig *packet.Signature, signer *Entity, err error) { + return verifyDetachedSignature(keyring, signed, signature, config) +} + +// VerifyDetachedSignatureReader takes a signed file and a detached signature and +// returns message details struct similar to the ReadMessage function. +// Once all data is read from md.UnverifiedBody the detached signature is verified. +// If a verification error occurs it is stored in md.SignatureError +// If the signer isn't known, ErrUnknownIssuer is returned. +// If expectedHashes or expectedSaltedHashes is not nil, the method checks +// if they match the signatures metadata or else return an error +func VerifyDetachedSignatureReader(keyring KeyRing, signed, signature io.Reader, config *packet.Config) (md *MessageDetails, err error) { + return verifyDetachedSignatureReader(keyring, signed, signature, config) +} + +// VerifyArmoredDetachedSignature performs the same actions as +// VerifyDetachedSignature but expects the signature to be armored. +func VerifyArmoredDetachedSignature(keyring KeyRing, signed, signature io.Reader, config *packet.Config) (sig *packet.Signature, signer *Entity, err error) { + body, err := readArmored(signature, SignatureType) + if err != nil { + return + } + + return VerifyDetachedSignature(keyring, signed, body, config) +} + +func verifyDetachedSignature(keyring KeyRing, signed, signature io.Reader, config *packet.Config) (sig *packet.Signature, signer *Entity, err error) { + md, err := verifyDetachedSignatureReader(keyring, signed, signature, config) + if err != nil { + return nil, nil, err + } + _, err = io.Copy(ioutil.Discard, md.UnverifiedBody) + if err != nil { + return nil, nil, err + } + if md.SignatureError != nil { + return nil, nil, md.SignatureError + } + return md.Signature, md.SignedBy.Entity, nil +} + +func verifyDetachedSignatureReader(keyring KeyRing, signed, signature io.Reader, config *packet.Config) (md *MessageDetails, err error) { + var p packet.Packet + md = &MessageDetails{ + IsEncrypted: false, + CheckRecipients: false, + IsSigned: true, + } + + packets := packet.NewReader(signature) + for { + p, err = packets.Next() + if err == io.EOF { + break + } + if err != nil { + return nil, err + } + sig, ok := p.(*packet.Signature) + if !ok { + continue + } + if sig.IssuerKeyId == nil { + return nil, errors.StructuralError("signature doesn't have an issuer") + } + candidate := newSignatureCandidateFromSignature(sig) + md.SignatureCandidates = append(md.SignatureCandidates, candidate) + + keys := keyring.EntitiesById(candidate.IssuerKeyId) + if len(keys) > 0 { + candidate.SignedByEntity = keys[0] + } + } + + if len(md.SignatureCandidates) == 0 { + return nil, errors.ErrUnknownIssuer + } + md.UnverifiedBody = &signatureCheckReader{packets, md, config, signed} + return md, nil +} + +// checkSignatureDetails returns an error if: +func checkSignatureDetails(verifiedKey *Key, signature *packet.Signature, config *packet.Config) error { + var collectedErrors []error + now := config.Now() + + if config.RejectMessageHashAlgorithm(signature.Hash) { + return errors.SignatureError("insecure message hash algorithm: " + signature.Hash.String()) + } + + if verifiedKey.PublicKey.CreationTime.Unix() > signature.CreationTime.Unix() { + collectedErrors = append(collectedErrors, errors.ErrSignatureOlderThanKey) + } + + sigsToCheck := []*packet.Signature{signature, verifiedKey.PrimarySelfSignature} + + if !verifiedKey.IsPrimary() { + sigsToCheck = append(sigsToCheck, verifiedKey.SelfSignature, verifiedKey.SelfSignature.EmbeddedSignature) + } + for _, sig := range sigsToCheck { + for _, notation := range sig.Notations { + if notation.IsCritical && !config.KnownNotation(notation.Name) { + return errors.SignatureError("unknown critical notation: " + notation.Name) + } + } + } + + if signature.SigExpired(now) { + return errors.ErrSignatureExpired + } + + if len(collectedErrors) > 0 { + // TODO: Is there a better priority for errors? + return collectedErrors[len(collectedErrors)-1] + } + return nil +} + +// checkIntendedRecipientsMatch checks if the fingerprint of the primary key matching the decryption key +// is found in the signature's intended recipients list. +func checkIntendedRecipientsMatch(decryptionKey *Key, sig *packet.Signature) error { + match := false + for _, recipient := range sig.IntendedRecipients { + if bytes.Equal(recipient.Fingerprint, decryptionKey.Entity.PrimaryKey.Fingerprint) { + match = true + break + } + } + if !match { + return errors.SignatureError("intended recipients in the signature does not match the decryption key") + } + return nil +} diff --git a/openpgp/v2/read_test.go b/openpgp/v2/read_test.go new file mode 100644 index 000000000..d393c7766 --- /dev/null +++ b/openpgp/v2/read_test.go @@ -0,0 +1,1044 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package v2 + +import ( + "bytes" + "crypto/sha512" + "encoding/base64" + "encoding/hex" + "io" + "io/ioutil" + "os" + "strings" + "testing" + + "github.com/ProtonMail/go-crypto/openpgp/armor" + "github.com/ProtonMail/go-crypto/openpgp/errors" + "github.com/ProtonMail/go-crypto/openpgp/packet" +) + +func readerFromHex(s string) io.Reader { + data, err := hex.DecodeString(s) + if err != nil { + panic("readerFromHex: bad input") + } + return bytes.NewBuffer(data) +} + +func TestReadKeyRing(t *testing.T) { + kring, err := ReadKeyRing(readerFromHex(testKeys1And2Hex)) + if err != nil { + t.Error(err) + return + } + if len(kring) != 2 || uint32(kring[0].PrimaryKey.KeyId) != 0xC20C31BB || uint32(kring[1].PrimaryKey.KeyId) != 0x1E35246B { + t.Errorf("bad keyring: %#v", kring) + } +} + +func TestRereadKeyRing(t *testing.T) { + kring, err := ReadKeyRing(readerFromHex(testKeys1And2Hex)) + if err != nil { + t.Errorf("error in initial parse: %s", err) + return + } + out := new(bytes.Buffer) + err = kring[0].Serialize(out) + if err != nil { + t.Errorf("error in serialization: %s", err) + return + } + kring, err = ReadKeyRing(out) + if err != nil { + t.Errorf("error in second parse: %s", err) + return + } + + if len(kring) != 1 || uint32(kring[0].PrimaryKey.KeyId) != 0xC20C31BB { + t.Errorf("bad keyring: %#v", kring) + } +} + +func TestReadPrivateKeyRing(t *testing.T) { + kring, err := ReadKeyRing(readerFromHex(testKeys1And2PrivateHex)) + if err != nil { + t.Error(err) + return + } + if len(kring) != 2 || uint32(kring[0].PrimaryKey.KeyId) != 0xC20C31BB || uint32(kring[1].PrimaryKey.KeyId) != 0x1E35246B || kring[0].PrimaryKey == nil { + t.Errorf("bad keyring: %#v", kring) + } +} + +func TestReadDSAKey(t *testing.T) { + kring, err := ReadKeyRing(readerFromHex(dsaTestKeyHex)) + if err != nil { + t.Error(err) + return + } + if len(kring) != 1 || uint32(kring[0].PrimaryKey.KeyId) != 0x0CCC0360 { + t.Errorf("bad parse: %#v", kring) + } +} + +func TestReadP256Key(t *testing.T) { + kring, err := ReadKeyRing(readerFromHex(p256TestKeyHex)) + if err != nil { + t.Error(err) + return + } + if len(kring) != 1 || uint32(kring[0].PrimaryKey.KeyId) != 0x5918513E { + t.Errorf("bad parse: %#v", kring) + } +} + +func TestDSAHashTruncatation(t *testing.T) { + // dsaKeyWithSHA512 was generated with GnuPG and --cert-digest-algo + // SHA512 in order to require DSA hash truncation to verify correctly. + _, err := ReadKeyRing(readerFromHex(dsaKeyWithSHA512)) + if err != nil { + t.Error(err) + } +} + +func TestGetKeyById(t *testing.T) { + kring, _ := ReadKeyRing(readerFromHex(testKeys1And2Hex)) + + keys := kring.KeysById(0xa34d7e18c20c31bb) + if len(keys) != 1 || keys[0].Entity != kring[0] { + t.Errorf("bad result for 0xa34d7e18c20c31bb: %#v", keys) + } + + keys = kring.KeysById(0xfd94408d4543314f) + if len(keys) != 1 || keys[0].Entity != kring[0] { + t.Errorf("bad result for 0xa34d7e18c20c31bb: %#v", keys) + } +} + +func checkSignedMessage(t *testing.T, signedHex, expected string) { + kring, _ := ReadKeyRing(readerFromHex(testKeys1And2Hex)) + + md, err := ReadMessage(readerFromHex(signedHex), kring, nil, &allowAllAlgorithmsConfig) + if err != nil { + t.Error(err) + return + } + + if len(md.SignatureCandidates) < 1 { + t.Errorf("bad MessageDetails: %#v", md) + } + + if !md.IsSigned || md.SignatureCandidates[0].IssuerKeyId != 0xa34d7e18c20c31bb || md.SignatureCandidates[0].SignedByEntity == nil || md.IsEncrypted || md.IsSymmetricallyEncrypted || len(md.EncryptedToKeyIds) != 0 || md.DecryptedWith.Entity != nil { + t.Errorf("bad MessageDetails: %#v", md) + } + + contents, err := ioutil.ReadAll(md.UnverifiedBody) + if err != nil { + t.Errorf("error reading UnverifiedBody: %s", err) + } + if !md.IsVerified { + t.Errorf("not verified despite all data read") + } + if string(contents) != expected { + t.Errorf("bad UnverifiedBody got:%s want:%s", string(contents), expected) + } + if md.SignatureError != nil || md.Signature == nil { + t.Errorf("failed to validate: %s", md.SignatureError) + } +} + +func TestSignedMessage(t *testing.T) { + checkSignedMessage(t, signedMessageHex, signedInput) +} + +func TestTextSignedMessage(t *testing.T) { + checkSignedMessage(t, signedTextMessageHex, signedTextInput) +} + +// The reader should detect "compressed quines", which are compressed +// packets that expand into themselves and cause an infinite recursive +// parsing loop. +// The packet in this test case comes from Taylor R. Campbell at +// http://mumble.net/~campbell/misc/pgp-quine/ +func TestCampbellQuine(t *testing.T) { + md, err := ReadMessage(readerFromHex(campbellQuine), nil, nil, nil) + if md != nil { + t.Errorf("Reading a compressed quine should not return any data: %#v", md) + } + structural, ok := err.(errors.StructuralError) + if !ok { + t.Fatalf("Unexpected class of error: %T", err) + } + if !strings.Contains(string(structural), "too many layers of packets") { + t.Fatalf("Unexpected error: %s", err) + } +} + +func TestSignedEncryptedMessage(t *testing.T) { + var signedEncryptedMessageTests = []struct { + keyRingHex string + messageHex string + signedByKeyId uint64 + encryptedToKeyId uint64 + verifiedSigHex string + unverifiedSigHex string + }{ + { + testKeys1And2PrivateHex, + signedEncryptedMessageHex, + 0xa34d7e18c20c31bb, + 0x2a67d68660df41c7, + verifiedSignatureEncryptedMessageHex, + unverifiedSignatureEncryptedMessageHex, + }, + { + dsaElGamalTestKeysHex, + signedEncryptedMessage2Hex, + 0x33af447ccd759b09, + 0xcf6a7abcd43e3673, + signatureEncryptedMessage2Hex, + "", + }, + } + for i, test := range signedEncryptedMessageTests { + expected := "Signed and encrypted message\n" + kring, _ := ReadKeyRing(readerFromHex(test.keyRingHex)) + prompt := func(keys []Key, symmetric bool) ([]byte, error) { + if symmetric { + t.Errorf("prompt: message was marked as symmetrically encrypted") + return nil, errors.ErrKeyIncorrect + } + + if len(keys) == 0 { + t.Error("prompt: no keys requested") + return nil, errors.ErrKeyIncorrect + } + + err := keys[0].PrivateKey.Decrypt([]byte("passphrase")) + if err != nil { + t.Errorf("prompt: error decrypting key: %s", err) + return nil, errors.ErrKeyIncorrect + } + + return nil, nil + } + + md, err := ReadMessage(readerFromHex(test.messageHex), kring, prompt, &allowAllAlgorithmsConfig) + if err != nil { + t.Errorf("#%d: error reading message: %s", i, err) + return + } + + if len(md.SignatureCandidates) < 1 { + t.Errorf("#%d: bad MessageDetails: %#v", i, md) + } + + if !md.IsSigned || md.SignatureCandidates[0].IssuerKeyId != test.signedByKeyId || md.SignatureCandidates[0].SignedByEntity == nil || !md.IsEncrypted || md.IsSymmetricallyEncrypted || len(md.EncryptedToKeyIds) == 0 || md.EncryptedToKeyIds[0] != test.encryptedToKeyId { + t.Errorf("#%d: bad MessageDetails: %#v", i, md) + } + + contents, err := ioutil.ReadAll(md.UnverifiedBody) + if err != nil { + t.Errorf("#%d: error reading UnverifiedBody: %s", i, err) + } + if !md.IsVerified { + t.Errorf("not verified despite all data read") + } + if string(contents) != expected { + t.Errorf("#%d: bad UnverifiedBody got:%s want:%s", i, string(contents), expected) + } + + if md.SignatureError != nil || md.Signature == nil { + t.Errorf("#%d: failed to validate: %s", i, md.SignatureError) + } + + if test.verifiedSigHex != "" { + var sig bytes.Buffer + err = md.Signature.Serialize(&sig) + if err != nil { + t.Errorf("#%d: error serializing verified signature: %s", i, err) + } + + sigData, err := ioutil.ReadAll(&sig) + if err != nil { + t.Errorf("#%d: error reading verified signature: %s", i, err) + } + + if hex.EncodeToString(sigData) != test.verifiedSigHex { + t.Errorf("#%d: verified signature does not match: %s, %s", i, hex.EncodeToString(sigData), test.verifiedSigHex) + } + } + } +} + +func TestUnspecifiedRecipient(t *testing.T) { + expected := "Recipient unspecified\n" + kring, _ := ReadKeyRing(readerFromHex(testKeys1And2PrivateHex)) + + md, err := ReadMessage(readerFromHex(recipientUnspecifiedHex), kring, nil, nil) + if err != nil { + t.Errorf("error reading message: %s", err) + return + } + + contents, err := ioutil.ReadAll(md.UnverifiedBody) + if err != nil { + t.Errorf("error reading UnverifiedBody: %s", err) + } + if string(contents) != expected { + t.Errorf("bad UnverifiedBody got:%s want:%s", string(contents), expected) + } +} + +func TestSymmetricallyEncrypted(t *testing.T) { + firstTimeCalled := true + + prompt := func(keys []Key, symmetric bool) ([]byte, error) { + if len(keys) != 0 { + t.Errorf("prompt: len(keys) = %d (want 0)", len(keys)) + } + + if !symmetric { + t.Errorf("symmetric is not set") + } + + if firstTimeCalled { + firstTimeCalled = false + return []byte("wrongpassword"), nil + } + + return []byte("password"), nil + } + + md, err := ReadMessage(readerFromHex(symmetricallyEncryptedCompressedHex), nil, prompt, nil) + if err != nil { + t.Errorf("ReadMessage: %s", err) + return + } + + contents, err := ioutil.ReadAll(md.UnverifiedBody) + if err != nil { + t.Errorf("ReadAll: %s", err) + } + + expectedCreationTime := uint32(1555107469) + if md.LiteralData.Time != expectedCreationTime { + t.Errorf("LiteralData.Time is %d, want %d", md.LiteralData.Time, expectedCreationTime) + } + + const expected = "Symmetrically encrypted.\r\n" + if string(contents) != expected { + t.Errorf("contents got: %s want: %s", string(contents), expected) + } +} + +func testDetachedSignature(t *testing.T, kring KeyRing, signature io.Reader, sigInput, tag string, expectedSignerKeyId uint64) { + signed := bytes.NewBufferString(sigInput) + _, signer, err := VerifyDetachedSignature(kring, signed, signature, &allowAllAlgorithmsConfig) + if err != nil { + t.Errorf("%s: signature error: %s", tag, err) + return + } + if signer == nil { + t.Errorf("%s: signer is nil", tag) + return + } + if signer.PrimaryKey.KeyId != expectedSignerKeyId { + t.Errorf("%s: wrong signer: got %x, expected %x", tag, signer.PrimaryKey.KeyId, expectedSignerKeyId) + } +} + +func TestDetachedSignature(t *testing.T) { + kring, _ := ReadKeyRing(readerFromHex(testKeys1And2Hex)) + testDetachedSignature(t, kring, readerFromHex(detachedSignatureHex), signedInput, "binary", testKey1KeyId) + testDetachedSignature(t, kring, readerFromHex(detachedSignatureTextHex), signedInput, "text", testKey1KeyId) + + incorrectSignedInput := signedInput + "X" + config := &packet.Config{} + _, _, err := VerifyDetachedSignature(kring, bytes.NewBufferString(incorrectSignedInput), readerFromHex(detachedSignatureHex), config) + if err == nil { + t.Fatal("CheckDetachedSignature returned without error for bad signature") + } + if err == errors.ErrUnknownIssuer { + t.Fatal("CheckDetachedSignature returned ErrUnknownIssuer when the signer was known, but the signature invalid") + } +} + +func TestDetachedSignatureDSA(t *testing.T) { + kring, _ := ReadKeyRing(readerFromHex(dsaTestKeyHex)) + testDetachedSignature(t, kring, readerFromHex(detachedSignatureDSAHex), signedInput, "binary", testKey3KeyId) +} + +func TestMultipleSignaturePacketsDSA(t *testing.T) { + kring, _ := ReadKeyRing(readerFromHex(dsaTestKeyHex)) + testDetachedSignature(t, kring, readerFromHex(missingHashFunctionHex+detachedSignatureDSAHex), signedInput, "binary", testKey3KeyId) +} + +func TestDetachedSignatureP256(t *testing.T) { + kring, _ := ReadKeyRing(readerFromHex(p256TestKeyHex)) + testDetachedSignature(t, kring, readerFromHex(detachedSignatureP256Hex), signedInput, "binary", testKeyP256KeyId) +} + +func testHashFunctionError(t *testing.T, signatureHex string) { + kring, _ := ReadKeyRing(readerFromHex(testKeys1And2Hex)) + config := &packet.Config{} + _, _, err := VerifyDetachedSignature(kring, nil, readerFromHex(signatureHex), config) + if err == nil { + t.Fatal("Packet with bad hash type was correctly parsed") + } + if err != errors.ErrUnknownIssuer { + t.Fatalf("Unexpected class of error: %s", err) + } +} + +func TestUnknownHashFunction(t *testing.T) { + // unknownHashFunctionHex contains a signature packet with hash function type + // 153 (which isn't a real hash function id). Since that's the only signature + // packet we don't find any suitable packets and end up with ErrUnknownIssuer. + testHashFunctionError(t, unknownHashFunctionHex) +} + +func TestMissingHashFunction(t *testing.T) { + // missingHashFunctionHex contains a signature packet that uses RIPEMD160, + // which isn't compiled in. Since that's the only signature packet we don't + // find any suitable packets and end up with ErrUnknownIssuer. + testHashFunctionError(t, missingHashFunctionHex) +} + +func TestRSASignatureBadMPILength(t *testing.T) { + kring, _ := ReadKeyRing(readerFromHex(testKeys1And2Hex)) + config := &packet.Config{} + _, _, err := VerifyDetachedSignature(kring, nil, readerFromHex(rsaSignatureBadMPIlength), config) + if err == nil { + t.Fatal("RSA Signature with malformed MPI was correctly parsed") + } +} + +func TestDetachedSignatureExpiredCrossSig(t *testing.T) { + kring, _ := ReadArmoredKeyRing(bytes.NewBufferString(keyWithExpiredCrossSig)) + config := &packet.Config{} + _, _, err := VerifyArmoredDetachedSignature(kring, bytes.NewBufferString("Hello World :)"), bytes.NewBufferString(sigFromKeyWithExpiredCrossSig), config) + if err == nil { + t.Fatal("Signature from key with expired subkey binding embedded signature was accepted") + } +} + +func TestSignatureUnknownNotation(t *testing.T) { + el, err := ReadArmoredKeyRing(bytes.NewBufferString(criticalNotationSigner)) + if err != nil { + t.Error(err) + } + raw, err := armor.Decode(strings.NewReader(signedMessageWithCriticalNotation)) + if err != nil { + t.Error(err) + return + } + md, err := ReadMessage(raw.Body, el, nil, &allowAllAlgorithmsConfig) + if err != nil { + t.Error(err) + return + } + _, err = ioutil.ReadAll(md.UnverifiedBody) + if err != nil { + t.Error(err) + return + } + const expectedErr string = "openpgp: invalid signature: unknown critical notation: test@example.com" + if md.SignatureError == nil || md.SignatureError.Error() != expectedErr { + t.Errorf("Expected error '%s', but got error '%s'", expectedErr, md.SignatureError) + } +} + +func TestSignatureKnownNotation(t *testing.T) { + el, err := ReadArmoredKeyRing(bytes.NewBufferString(criticalNotationSigner)) + if err != nil { + t.Error(err) + } + raw, err := armor.Decode(strings.NewReader(signedMessageWithCriticalNotation)) + if err != nil { + t.Error(err) + return + } + config := allowAllAlgorithmsConfig + config.KnownNotations = map[string]bool{ + "test@example.com": true, + } + md, err := ReadMessage(raw.Body, el, nil, &config) + if err != nil { + t.Error(err) + return + } + _, err = ioutil.ReadAll(md.UnverifiedBody) + if err != nil { + t.Error(err) + return + } + if !md.IsVerified { + t.Errorf("not verified despite all data read") + } + if md.SignatureError != nil { + t.Error(md.SignatureError) + return + } +} + +func TestReadingArmoredPrivateKey(t *testing.T) { + el, err := ReadArmoredKeyRing(bytes.NewBufferString(armoredPrivateKeyBlock)) + if err != nil { + t.Error(err) + } + if len(el) != 1 { + t.Errorf("got %d entities, wanted 1\n", len(el)) + } +} + +func TestReadingArmoredPublicKey(t *testing.T) { + el, err := ReadArmoredKeyRing(bytes.NewBufferString(e2ePublicKey)) + if err != nil { + t.Error(err) + } + if len(el) != 1 { + t.Errorf("didn't get a valid entity") + } +} + +func TestNoArmoredData(t *testing.T) { + _, err := ReadArmoredKeyRing(bytes.NewBufferString("foo")) + if _, ok := err.(errors.InvalidArgumentError); !ok { + t.Errorf("error was not an InvalidArgumentError: %s", err) + } +} + +func testReadMessageError(t *testing.T, messageHex string) { + buf, err := hex.DecodeString(messageHex) + if err != nil { + t.Errorf("hex.DecodeString(): %v", err) + } + + kr, err := ReadKeyRing(new(bytes.Buffer)) + if err != nil { + t.Errorf("ReadKeyring(): %v", err) + } + + _, err = ReadMessage(bytes.NewBuffer(buf), kr, + func([]Key, bool) ([]byte, error) { + return []byte("insecure"), nil + }, nil) + + if err == nil { + t.Errorf("ReadMessage(): Unexpected nil error") + } +} + +func TestIssue11503(t *testing.T) { + testReadMessageError(t, "8c040402000aa430aa8228b9248b01fc899a91197130303030") +} + +func TestIssue11504(t *testing.T) { + testReadMessageError(t, "9303000130303030303030303030983002303030303030030000000130") +} + +// TestSignatureV3Message tests the verification of V3 signature, generated +// with a modern V4-style key. Some people have their clients set to generate +// V3 signatures, so it's useful to be able to verify them. +func TestSignatureV3Message(t *testing.T) { + sig, err := armor.Decode(strings.NewReader(signedMessageV3)) + if err != nil { + t.Error(err) + return + } + key, err := ReadArmoredKeyRing(strings.NewReader(keyV4forVerifyingSignedMessageV3)) + if err != nil { + t.Error(err) + return + } + md, err := ReadMessage(sig.Body, key, nil, nil) + if err != nil { + t.Error(err) + return + } + + _, err = ioutil.ReadAll(md.UnverifiedBody) + if err != nil { + t.Error(err) + return + } + if !md.IsVerified { + t.Errorf("not verified despite all data read") + } + // We'll see a sig error here after reading in the UnverifiedBody above, + // if there was one to see. + if err = md.SignatureError; err == nil { + t.Errorf("Expected a signature error") + return + } + + if md.Signature != nil { + t.Errorf("Did not expect a signature V4 back") + return + } +} + +func TestSignatureOldStyleMessage(t *testing.T) { + sig, err := armor.Decode(strings.NewReader(signedMessageOldStyle)) + if err != nil { + t.Error(err) + return + } + key, err := ReadArmoredKeyRing(strings.NewReader(signedMessageOldStyleKey)) + if err != nil { + t.Error(err) + return + } + md, err := ReadMessage(sig.Body, key, nil, nil) + if err != nil { + t.Error(err) + return + } + + _, err = ioutil.ReadAll(md.UnverifiedBody) + if err != nil { + t.Error(err) + return + } + if !md.IsVerified { + t.Errorf("not verified despite all data read") + } + // We'll see a sig error here after reading in the UnverifiedBody above, + // if there was one to see. + if err = md.SignatureError; err != nil { + t.Error("Expected no signature error, got: ", err) + return + } +} + +func TestReadV6Messages(t *testing.T) { + key, err := ReadArmoredKeyRing(strings.NewReader(v6PrivKey)) + if err != nil { + t.Error(err) + return + } + msgReader, err := armor.Decode(strings.NewReader(v6PrivKeyMsg)) + if err != nil { + t.Error(err) + return + } + md, err := ReadMessage(msgReader.Body, key, nil, nil) + if err != nil { + t.Error(err) + return + } + contents, err := ioutil.ReadAll(md.UnverifiedBody) + if err != nil { + t.Error(err) + return + } + if string(contents) != "Hello, world!" { + t.Errorf("decrypted message is wrong: %s", contents) + } + + msgReader, err = armor.Decode(strings.NewReader(v6PrivKeyInlineSignMsg)) + if err != nil { + t.Error(err) + return + } + md, err = ReadMessage(msgReader.Body, key, nil, nil) + if err != nil { + t.Error(err) + return + } + contents, err = ioutil.ReadAll(md.UnverifiedBody) + if err != nil { + t.Error(err) + return + } + if !md.IsVerified { + t.Errorf("not verified despite all data read") + } + if md.SignatureError != nil { + t.Error("expected no signature error, got:", md.SignatureError) + } + if string(contents) != "What we need from the grocery store:\n\n- tofu\n- vegetables\n- noodles\n" { + t.Errorf("inline message is wrong: %s", contents) + } +} + +func TestSymmetricAeadGcmOpenPGPJsMessage(t *testing.T) { + passphrase := []byte("test") + file, err := os.Open("../test_data/aead-sym-message.asc") + if err != nil { + t.Fatal(err) + } + armoredEncryptedMessage, err := ioutil.ReadAll(file) + if err != nil { + t.Fatal(err) + } + // Unarmor string + raw, err := armor.Decode(strings.NewReader(string(armoredEncryptedMessage))) + if err != nil { + t.Error(err) + return + } + // Mock passphrase prompt + promptFunc := func(keys []Key, symmetric bool) ([]byte, error) { + return passphrase, nil + } + // Decrypt message + md, err := ReadMessage(raw.Body, nil, promptFunc, nil) + if err != nil { + t.Error(err) + return + } + contents, err := ioutil.ReadAll(md.UnverifiedBody) + if err != nil { + t.Errorf("error reading UnverifiedBody: %s", err) + } + + // The plaintext is https://www.gutenberg.org/cache/epub/1080/pg1080.txt + // We compare the SHA512 hashes. + wantHash := modestProposalSha512 + gotHashRaw := sha512.Sum512(contents) + gotHash := base64.StdEncoding.EncodeToString(gotHashRaw[:]) + + if wantHash != gotHash { + t.Fatal("Did not decrypt OpenPGPjs message correctly") + } +} + +func TestSymmetricDecryptionArgon2(t *testing.T) { + // Appendix IETF OpenPGP crypto refresh draft v08 A.8.1 + passphrase := []byte("password") + file, err := os.Open("../test_data/argon2-sym-message.asc") + if err != nil { + t.Fatal(err) + } + armoredEncryptedMessage, err := ioutil.ReadAll(file) + if err != nil { + t.Fatal(err) + } + // Unarmor string + raw, err := armor.Decode(strings.NewReader(string(armoredEncryptedMessage))) + if err != nil { + t.Error(err) + return + } + // Mock passphrase prompt + promptFunc := func(keys []Key, symmetric bool) ([]byte, error) { + return passphrase, nil + } + // Decrypt message + md, err := ReadMessage(raw.Body, nil, promptFunc, nil) + if err != nil { + t.Error(err) + return + } + contents, err := ioutil.ReadAll(md.UnverifiedBody) + if err != nil { + t.Errorf("error reading UnverifiedBody: %s", err) + } + + if string(contents) != "Hello, world!" { + t.Fatal("Did not decrypt Argon message correctly") + } +} + +func TestAsymmestricAeadOcbOpenPGPjsCompressedMessage(t *testing.T) { + // Read key from file + armored, err := os.Open("../test_data/aead-ocb-asym-key.asc") + if err != nil { + t.Fatal(err) + } + el, err := ReadArmoredKeyRing(armored) + if err != nil { + t.Fatal(err) + } + // Read ciphertext from file + ciphertext, err := os.Open("../test_data/aead-ocb-asym-message.asc") + if err != nil { + t.Fatal(err) + } + armoredEncryptedMessage, err := ioutil.ReadAll(ciphertext) + if err != nil { + t.Fatal(err) + } + // Unarmor string + raw, err := armor.Decode(strings.NewReader(string(armoredEncryptedMessage))) + if err != nil { + t.Error(err) + return + } + // Decrypt message + md, err := ReadMessage(raw.Body, el, nil, nil) + if err != nil { + t.Error(err) + return + } + // Read contents + contents, err := ioutil.ReadAll(md.UnverifiedBody) + if err != nil && err != io.ErrUnexpectedEOF { + t.Errorf("error reading UnverifiedBody: %s", err) + } + + wantHash := modestProposalSha512 + gotHashRaw := sha512.Sum512(contents) + gotHash := base64.StdEncoding.EncodeToString(gotHashRaw[:]) + + if wantHash != gotHash { + t.Fatal("Did not decrypt OpenPGPjs message correctly") + } +} + +func TestSymmetricAeadEaxOpenPGPJsMessage(t *testing.T) { + key := []byte{79, 41, 206, 112, 224, 133, 140, 223, 27, 61, 227, 57, 114, + 118, 64, 60, 177, 26, 42, 174, 151, 5, 186, 74, 226, 97, 214, 63, 114, 77, + 215, 121} + + file, err := os.Open("../test_data/aead-eax-packet.b64") + if err != nil { + t.Fatal(err) + } + fileBytes, err := ioutil.ReadAll(file) + if err != nil { + t.Fatal(err) + } + // Decode from base 64 + raw, err := base64.StdEncoding.DecodeString(string(fileBytes)) + if err != nil { + t.Fatal(err) + } + r := bytes.NewBuffer(raw) + // Read packet + p, err := packet.Read(r) + if err != nil { + panic(err) + } + + // Decrypt with key + var edp packet.EncryptedDataPacket + if err != nil { + t.Fatal(err) + } + edp = p.(*packet.AEADEncrypted) + rc, err := edp.Decrypt(packet.CipherFunction(0), key) + if err != nil { + panic(err) + } + // Read literal data packet + p, err = packet.Read(rc) + if err != nil { + t.Fatal(err) + } + ld := p.(*packet.LiteralData) + + // Read contents + contents, err := ioutil.ReadAll(ld.Body) + if err != nil && err != io.ErrUnexpectedEOF { + t.Errorf("error reading UnverifiedBody: %s", err) + } + + wantHash := modestProposalSha512 + gotHashRaw := sha512.Sum512(contents) + gotHash := base64.StdEncoding.EncodeToString(gotHashRaw[:]) + + if wantHash != gotHash { + t.Fatal("Did not decrypt OpenPGPjs message correctly") + } +} + +func TestCorruptedMessageInvalidSigHeader(t *testing.T) { + // Decrypt message with corrupted MDC and invalid one-pass-signature header + // Expect parsing errors over unverified decrypted data to be opaque + passphrase := []byte("password") + file, err := os.Open("../test_data/sym-corrupted-message-invalid-sig-header.asc") + if err != nil { + t.Fatal(err) + } + armoredEncryptedMessage, err := ioutil.ReadAll(file) + if err != nil { + t.Fatal(err) + } + // Unarmor string + raw, err := armor.Decode(strings.NewReader(string(armoredEncryptedMessage))) + if err != nil { + t.Error(err) + return + } + // Mock passphrase prompt + promptFunc := func(keys []Key, symmetric bool) ([]byte, error) { + return passphrase, nil + } + const expectedErr string = "openpgp: invalid data: parsing error" + _, observedErr := ReadMessage(raw.Body, nil, promptFunc, nil) + if observedErr.Error() != expectedErr { + t.Errorf("Expected error '%s', but got error '%s'", expectedErr, observedErr) + } +} + +func TestCorruptedMessageWrongLength(t *testing.T) { + // Decrypt message with wrong length in Literal packet header (length too long) + // Expect parsing errors over unverified decrypted data to be opaque + passphrase := []byte("password") + promptFunc := func(keys []Key, symmetric bool) ([]byte, error) { + return passphrase, nil + } + const expectedErr string = "openpgp: invalid data: parsing error" + + file, err := os.Open("../test_data/sym-corrupted-message-long-length.asc") + if err != nil { + t.Fatal(err) + } + armoredEncryptedMessage, err := ioutil.ReadAll(file) + if err != nil { + t.Fatal(err) + } + raw, err := armor.Decode(strings.NewReader(string(armoredEncryptedMessage))) + if err != nil { + t.Error(err) + return + } + md, err := ReadMessage(raw.Body, nil, promptFunc, nil) + if err != nil { + t.Error(err) + return + } + _, err = ioutil.ReadAll(md.UnverifiedBody) + if err == nil { + t.Fatal("Parsing error expected") + } + if err.Error() != expectedErr { + t.Errorf("Expected error '%s', but got error '%s'", expectedErr, err) + } +} + +func TestMessageWithoutMdc(t *testing.T) { + armored, err := os.Open("../test_data/aead-ocb-asym-key.asc") + if err != nil { + t.Fatal(err) + } + defer armored.Close() + + el, err := ReadArmoredKeyRing(armored) + if err != nil { + t.Fatal(err) + } + + armoredMessageWithoutMdc, err := ioutil.ReadFile("../test_data/sym-message-without-mdc.asc") + if err != nil { + t.Fatal(err) + } + + t.Run("fails with InsecureAllowUnauthenticatedMessages disabled", func(t *testing.T) { + messageWithoutMdc, err := armor.Decode(bytes.NewReader(armoredMessageWithoutMdc)) + if err != nil { + t.Fatal(err) + } + + _, err = ReadMessage(messageWithoutMdc.Body, el, nil, nil) + if err == nil { + t.Fatal("reading the message should have failed") + } + }) + + t.Run("succeeds with InsecureAllowUnauthenticatedMessages enabled", func(t *testing.T) { + messageWithoutMdc, err := armor.Decode(bytes.NewReader(armoredMessageWithoutMdc)) + if err != nil { + t.Fatal(err) + } + + md, err := ReadMessage(messageWithoutMdc.Body, el, nil, &packet.Config{ + InsecureAllowUnauthenticatedMessages: true, + }) + if err != nil { + t.Fatal("reading the message should have worked") + } + + b, err := ioutil.ReadAll(md.UnverifiedBody) + if err != nil { + t.Fatal("reading the message should have worked") + } + + if !bytes.Equal(b, []byte("message without mdc\n")) { + t.Error("unexpected message content") + } + }) +} + +func TestMultiSignedMessage(t *testing.T) { + messageBlock, err := armor.Decode(strings.NewReader(multiSignMessage)) + if err != nil { + t.Fatal(err) + } + + keyring, err := ReadArmoredKeyRing(strings.NewReader(multiSignMessageKey)) + if err != nil { + t.Fatal(err) + } + + md, err := ReadMessage(messageBlock.Body, keyring, nil, nil) + if err != nil { + t.Fatal(err) + } + + if len(md.SignatureCandidates) != 2 { + t.Errorf("expected 2 signature candidates, got: %d", len(md.SignatureCandidates)) + } + + _, err = io.ReadAll(md.UnverifiedBody) + if err != nil { + t.Fatal(err) + } + if !md.IsVerified { + t.Errorf("not verified despite all data read") + } + + if md.Signature == nil { + t.Error("expected a matched verified signatures, got: nil") + } + + if md.SignatureError != nil { + t.Error("expected no signature error, got: ", md.SignatureError) + } + + if md.SignatureCandidates[0].SignatureError == nil { + t.Error("First candidate should fail in verification, got: nil") + } +} + +func TestMalformedMessage(t *testing.T) { + keyring, err := ReadArmoredKeyRing(strings.NewReader(malformedKeyTest)) + if err != nil { + t.Fatal(err) + } + t.Run("Signature + Literal", func(t *testing.T) { + testMalformedMessage(t, keyring, malformedLiteralSignature) + }) + t.Run("Two literals, 1st compressed 4 times", func(t *testing.T) { + testMalformedMessage(t, keyring, malformedTwoLiteralsCompressed) + }) + t.Run("PKESK + Literal", func(t *testing.T) { + testMalformedMessage(t, keyring, malformedPKESKLiteral) + }) + t.Run("OPS + Literal", func(t *testing.T) { + testMalformedMessage(t, keyring, malformedOPSLiteral) + }) +} + +func testMalformedMessage(t *testing.T, keyring EntityList, message string) { + raw, err := armor.Decode(strings.NewReader(message)) + if err != nil { + t.Error(err) + return + } + md, err := ReadMessage(raw.Body, keyring, nil, nil) + if err != nil { + return + } + _, err = io.ReadAll(md.UnverifiedBody) + if err == nil { + t.Error("Expected malformed message error") + return + } +} diff --git a/openpgp/v2/read_write_test_data.go b/openpgp/v2/read_write_test_data.go new file mode 100644 index 000000000..ba4e4b837 --- /dev/null +++ b/openpgp/v2/read_write_test_data.go @@ -0,0 +1,754 @@ +package v2 + +const testKey1KeyId uint64 = 0xA34D7E18C20C31BB +const testKey3KeyId uint64 = 0x338934250CCC0360 +const testKeyP256KeyId uint64 = 0xd44a2c495918513e + +const signedInput = "Signed message\nline 2\nline 3\n" +const signedTextInput = "Signed message\r\nline 2\r\nline 3\r\n" + +const recipientUnspecifiedHex = "848c0300000000000000000103ff62d4d578d03cf40c3da998dfe216c074fa6ddec5e31c197c9666ba292830d91d18716a80f699f9d897389a90e6d62d0238f5f07a5248073c0f24920e4bc4a30c2d17ee4e0cae7c3d4aaa4e8dced50e3010a80ee692175fa0385f62ecca4b56ee6e9980aa3ec51b61b077096ac9e800edaf161268593eedb6cc7027ff5cb32745d250010d407a6221ae22ef18469b444f2822478c4d190b24d36371a95cb40087cdd42d9399c3d06a53c0673349bfb607927f20d1e122bde1e2bf3aa6cae6edf489629bcaa0689539ae3b718914d88ededc3b" + +const detachedSignatureHex = "889c04000102000605024d449cd1000a0910a34d7e18c20c31bb167603ff57718d09f28a519fdc7b5a68b6a3336da04df85e38c5cd5d5bd2092fa4629848a33d85b1729402a2aab39c3ac19f9d573f773cc62c264dc924c067a79dfd8a863ae06c7c8686120760749f5fd9b1e03a64d20a7df3446ddc8f0aeadeaeba7cbaee5c1e366d65b6a0c6cc749bcb912d2f15013f812795c2e29eb7f7b77f39ce77" + +const detachedSignatureTextHex = "889c04010102000605024d449d21000a0910a34d7e18c20c31bbc8c60400a24fbef7342603a41cb1165767bd18985d015fb72fe05db42db36cfb2f1d455967f1e491194fbf6cf88146222b23bf6ffbd50d17598d976a0417d3192ff9cc0034fd00f287b02e90418bbefe609484b09231e4e7a5f3562e199bf39909ab5276c4d37382fe088f6b5c3426fc1052865da8b3ab158672d58b6264b10823dc4b39" + +const detachedSignatureDSAHex = "884604001102000605024d6c4eac000a0910338934250ccc0360f18d00a087d743d6405ed7b87755476629600b8b694a39e900a0abff8126f46faf1547c1743c37b21b4ea15b8f83" + +const detachedSignatureP256Hex = "885e0400130a0006050256e5bb00000a0910d44a2c495918513edef001009841a4f792beb0befccb35c8838a6a87d9b936beaa86db6745ddc7b045eee0cf00fd1ac1f78306b17e965935dd3f8bae4587a76587e4af231efe19cc4011a8434817" + +// The plaintext is https://www.gutenberg.org/cache/epub/1080/pg1080.txt +const modestProposalSha512 = "lbbrB1+WP3T9AaC9OQqBdOcCjgeEQadlulXsNPgVx0tyqPzDHwUugZ2gE7V0ESKAw6kAVfgkcuvfgxAAGaeHtw==" + +const testKeys1And2Hex = "988d044d3c5c10010400b1d13382944bd5aba23a4312968b5095d14f947f600eb478e14a6fcb16b0e0cac764884909c020bc495cfcc39a935387c661507bdb236a0612fb582cac3af9b29cc2c8c70090616c41b662f4da4c1201e195472eb7f4ae1ccbcbf9940fe21d985e379a5563dde5b9a23d35f1cfaa5790da3b79db26f23695107bfaca8e7b5bcd0011010001b41054657374204b6579203120285253412988b804130102002205024d3c5c10021b03060b090807030206150802090a0b0416020301021e01021780000a0910a34d7e18c20c31bbb5b304009cc45fe610b641a2c146331be94dade0a396e73ca725e1b25c21708d9cab46ecca5ccebc23055879df8f99eea39b377962a400f2ebdc36a7c99c333d74aeba346315137c3ff9d0a09b0273299090343048afb8107cf94cbd1400e3026f0ccac7ecebbc4d78588eb3e478fe2754d3ca664bcf3eac96ca4a6b0c8d7df5102f60f6b0020003b88d044d3c5c10010400b201df61d67487301f11879d514f4248ade90c8f68c7af1284c161098de4c28c2850f1ec7b8e30f959793e571542ffc6532189409cb51c3d30dad78c4ad5165eda18b20d9826d8707d0f742e2ab492103a85bbd9ddf4f5720f6de7064feb0d39ee002219765bb07bcfb8b877f47abe270ddeda4f676108cecb6b9bb2ad484a4f0011010001889f04180102000905024d3c5c10021b0c000a0910a34d7e18c20c31bb1a03040085c8d62e16d05dc4e9dad64953c8a2eed8b6c12f92b1575eeaa6dcf7be9473dd5b24b37b6dffbb4e7c99ed1bd3cb11634be19b3e6e207bed7505c7ca111ccf47cb323bf1f8851eb6360e8034cbff8dd149993c959de89f8f77f38e7e98b8e3076323aa719328e2b408db5ec0d03936efd57422ba04f925cdc7b4c1af7590e40ab0020003988d044d3c5c33010400b488c3e5f83f4d561f317817538d9d0397981e9aef1321ca68ebfae1cf8b7d388e19f4b5a24a82e2fbbf1c6c26557a6c5845307a03d815756f564ac7325b02bc83e87d5480a8fae848f07cb891f2d51ce7df83dcafdc12324517c86d472cc0ee10d47a68fd1d9ae49a6c19bbd36d82af597a0d88cc9c49de9df4e696fc1f0b5d0011010001b42754657374204b6579203220285253412c20656e637279707465642070726976617465206b65792988b804130102002205024d3c5c33021b03060b090807030206150802090a0b0416020301021e01021780000a0910d4984f961e35246b98940400908a73b6a6169f700434f076c6c79015a49bee37130eaf23aaa3cfa9ce60bfe4acaa7bc95f1146ada5867e0079babb38804891f4f0b8ebca57a86b249dee786161a755b7a342e68ccf3f78ed6440a93a6626beb9a37aa66afcd4f888790cb4bb46d94a4ae3eb3d7d3e6b00f6bfec940303e89ec5b32a1eaaacce66497d539328b0020003b88d044d3c5c33010400a4e913f9442abcc7f1804ccab27d2f787ffa592077ca935a8bb23165bd8d57576acac647cc596b2c3f814518cc8c82953c7a4478f32e0cf645630a5ba38d9618ef2bc3add69d459ae3dece5cab778938d988239f8c5ae437807075e06c828019959c644ff05ef6a5a1dab72227c98e3a040b0cf219026640698d7a13d8538a570011010001889f04180102000905024d3c5c33021b0c000a0910d4984f961e35246b26c703ff7ee29ef53bc1ae1ead533c408fa136db508434e233d6e62be621e031e5940bbd4c08142aed0f82217e7c3e1ec8de574bc06ccf3c36633be41ad78a9eacd209f861cae7b064100758545cc9dd83db71806dc1cfd5fb9ae5c7474bba0c19c44034ae61bae5eca379383339dece94ff56ff7aa44a582f3e5c38f45763af577c0934b0020003" + +const testKeys1And2PrivateHex = "9501d8044d3c5c10010400b1d13382944bd5aba23a4312968b5095d14f947f600eb478e14a6fcb16b0e0cac764884909c020bc495cfcc39a935387c661507bdb236a0612fb582cac3af9b29cc2c8c70090616c41b662f4da4c1201e195472eb7f4ae1ccbcbf9940fe21d985e379a5563dde5b9a23d35f1cfaa5790da3b79db26f23695107bfaca8e7b5bcd00110100010003ff4d91393b9a8e3430b14d6209df42f98dc927425b881f1209f319220841273a802a97c7bdb8b3a7740b3ab5866c4d1d308ad0d3a79bd1e883aacf1ac92dfe720285d10d08752a7efe3c609b1d00f17f2805b217be53999a7da7e493bfc3e9618fd17018991b8128aea70a05dbce30e4fbe626aa45775fa255dd9177aabf4df7cf0200c1ded12566e4bc2bb590455e5becfb2e2c9796482270a943343a7835de41080582c2be3caf5981aa838140e97afa40ad652a0b544f83eb1833b0957dce26e47b0200eacd6046741e9ce2ec5beb6fb5e6335457844fb09477f83b050a96be7da043e17f3a9523567ed40e7a521f818813a8b8a72209f1442844843ccc7eb9805442570200bdafe0438d97ac36e773c7162028d65844c4d463e2420aa2228c6e50dc2743c3d6c72d0d782a5173fe7be2169c8a9f4ef8a7cf3e37165e8c61b89c346cdc6c1799d2b41054657374204b6579203120285253412988b804130102002205024d3c5c10021b03060b090807030206150802090a0b0416020301021e01021780000a0910a34d7e18c20c31bbb5b304009cc45fe610b641a2c146331be94dade0a396e73ca725e1b25c21708d9cab46ecca5ccebc23055879df8f99eea39b377962a400f2ebdc36a7c99c333d74aeba346315137c3ff9d0a09b0273299090343048afb8107cf94cbd1400e3026f0ccac7ecebbc4d78588eb3e478fe2754d3ca664bcf3eac96ca4a6b0c8d7df5102f60f6b00200009d01d8044d3c5c10010400b201df61d67487301f11879d514f4248ade90c8f68c7af1284c161098de4c28c2850f1ec7b8e30f959793e571542ffc6532189409cb51c3d30dad78c4ad5165eda18b20d9826d8707d0f742e2ab492103a85bbd9ddf4f5720f6de7064feb0d39ee002219765bb07bcfb8b877f47abe270ddeda4f676108cecb6b9bb2ad484a4f00110100010003fd17a7490c22a79c59281fb7b20f5e6553ec0c1637ae382e8adaea295f50241037f8997cf42c1ce26417e015091451b15424b2c59eb8d4161b0975630408e394d3b00f88d4b4e18e2cc85e8251d4753a27c639c83f5ad4a571c4f19d7cd460b9b73c25ade730c99df09637bd173d8e3e981ac64432078263bb6dc30d3e974150dd0200d0ee05be3d4604d2146fb0457f31ba17c057560785aa804e8ca5530a7cd81d3440d0f4ba6851efcfd3954b7e68908fc0ba47f7ac37bf559c6c168b70d3a7c8cd0200da1c677c4bce06a068070f2b3733b0a714e88d62aa3f9a26c6f5216d48d5c2b5624144f3807c0df30be66b3268eeeca4df1fbded58faf49fc95dc3c35f134f8b01fd1396b6c0fc1b6c4f0eb8f5e44b8eace1e6073e20d0b8bc5385f86f1cf3f050f66af789f3ef1fc107b7f4421e19e0349c730c68f0a226981f4e889054fdb4dc149e8e889f04180102000905024d3c5c10021b0c000a0910a34d7e18c20c31bb1a03040085c8d62e16d05dc4e9dad64953c8a2eed8b6c12f92b1575eeaa6dcf7be9473dd5b24b37b6dffbb4e7c99ed1bd3cb11634be19b3e6e207bed7505c7ca111ccf47cb323bf1f8851eb6360e8034cbff8dd149993c959de89f8f77f38e7e98b8e3076323aa719328e2b408db5ec0d03936efd57422ba04f925cdc7b4c1af7590e40ab00200009501fe044d3c5c33010400b488c3e5f83f4d561f317817538d9d0397981e9aef1321ca68ebfae1cf8b7d388e19f4b5a24a82e2fbbf1c6c26557a6c5845307a03d815756f564ac7325b02bc83e87d5480a8fae848f07cb891f2d51ce7df83dcafdc12324517c86d472cc0ee10d47a68fd1d9ae49a6c19bbd36d82af597a0d88cc9c49de9df4e696fc1f0b5d0011010001fe030302e9030f3c783e14856063f16938530e148bc57a7aa3f3e4f90df9dceccdc779bc0835e1ad3d006e4a8d7b36d08b8e0de5a0d947254ecfbd22037e6572b426bcfdc517796b224b0036ff90bc574b5509bede85512f2eefb520fb4b02aa523ba739bff424a6fe81c5041f253f8d757e69a503d3563a104d0d49e9e890b9d0c26f96b55b743883b472caa7050c4acfd4a21f875bdf1258d88bd61224d303dc9df77f743137d51e6d5246b88c406780528fd9a3e15bab5452e5b93970d9dcc79f48b38651b9f15bfbcf6da452837e9cc70683d1bdca94507870f743e4ad902005812488dd342f836e72869afd00ce1850eea4cfa53ce10e3608e13d3c149394ee3cbd0e23d018fcbcb6e2ec5a1a22972d1d462ca05355d0d290dd2751e550d5efb38c6c89686344df64852bf4ff86638708f644e8ec6bd4af9b50d8541cb91891a431326ab2e332faa7ae86cfb6e0540aa63160c1e5cdd5a4add518b303fff0a20117c6bc77f7cfbaf36b04c865c6c2b42754657374204b6579203220285253412c20656e637279707465642070726976617465206b65792988b804130102002205024d3c5c33021b03060b090807030206150802090a0b0416020301021e01021780000a0910d4984f961e35246b98940400908a73b6a6169f700434f076c6c79015a49bee37130eaf23aaa3cfa9ce60bfe4acaa7bc95f1146ada5867e0079babb38804891f4f0b8ebca57a86b249dee786161a755b7a342e68ccf3f78ed6440a93a6626beb9a37aa66afcd4f888790cb4bb46d94a4ae3eb3d7d3e6b00f6bfec940303e89ec5b32a1eaaacce66497d539328b00200009d01fe044d3c5c33010400a4e913f9442abcc7f1804ccab27d2f787ffa592077ca935a8bb23165bd8d57576acac647cc596b2c3f814518cc8c82953c7a4478f32e0cf645630a5ba38d9618ef2bc3add69d459ae3dece5cab778938d988239f8c5ae437807075e06c828019959c644ff05ef6a5a1dab72227c98e3a040b0cf219026640698d7a13d8538a570011010001fe030302e9030f3c783e148560f936097339ae381d63116efcf802ff8b1c9360767db5219cc987375702a4123fd8657d3e22700f23f95020d1b261eda5257e9a72f9a918e8ef22dd5b3323ae03bbc1923dd224db988cadc16acc04b120a9f8b7e84da9716c53e0334d7b66586ddb9014df604b41be1e960dcfcbc96f4ed150a1a0dd070b9eb14276b9b6be413a769a75b519a53d3ecc0c220e85cd91ca354d57e7344517e64b43b6e29823cbd87eae26e2b2e78e6dedfbb76e3e9f77bcb844f9a8932eb3db2c3f9e44316e6f5d60e9e2a56e46b72abe6b06dc9a31cc63f10023d1f5e12d2a3ee93b675c96f504af0001220991c88db759e231b3320dcedf814dcf723fd9857e3d72d66a0f2af26950b915abdf56c1596f46a325bf17ad4810d3535fb02a259b247ac3dbd4cc3ecf9c51b6c07cebb009c1506fba0a89321ec8683e3fd009a6e551d50243e2d5092fefb3321083a4bad91320dc624bd6b5dddf93553e3d53924c05bfebec1fb4bd47e89a1a889f04180102000905024d3c5c33021b0c000a0910d4984f961e35246b26c703ff7ee29ef53bc1ae1ead533c408fa136db508434e233d6e62be621e031e5940bbd4c08142aed0f82217e7c3e1ec8de574bc06ccf3c36633be41ad78a9eacd209f861cae7b064100758545cc9dd83db71806dc1cfd5fb9ae5c7474bba0c19c44034ae61bae5eca379383339dece94ff56ff7aa44a582f3e5c38f45763af577c0934b0020000" + +const dsaElGamalTestKeysHex = "9501e1044dfcb16a110400aa3e5c1a1f43dd28c2ffae8abf5cfce555ee874134d8ba0a0f7b868ce2214beddc74e5e1e21ded354a95d18acdaf69e5e342371a71fbb9093162e0c5f3427de413a7f2c157d83f5cd2f9d791256dc4f6f0e13f13c3302af27f2384075ab3021dff7a050e14854bbde0a1094174855fc02f0bae8e00a340d94a1f22b32e48485700a0cec672ac21258fb95f61de2ce1af74b2c4fa3e6703ff698edc9be22c02ae4d916e4fa223f819d46582c0516235848a77b577ea49018dcd5e9e15cff9dbb4663a1ae6dd7580fa40946d40c05f72814b0f88481207e6c0832c3bded4853ebba0a7e3bd8e8c66df33d5a537cd4acf946d1080e7a3dcea679cb2b11a72a33a2b6a9dc85f466ad2ddf4c3db6283fa645343286971e3dd700703fc0c4e290d45767f370831a90187e74e9972aae5bff488eeff7d620af0362bfb95c1a6c3413ab5d15a2e4139e5d07a54d72583914661ed6a87cce810be28a0aa8879a2dd39e52fb6fe800f4f181ac7e328f740cde3d09a05cecf9483e4cca4253e60d4429ffd679d9996a520012aad119878c941e3cf151459873bdfc2a9563472fe0303027a728f9feb3b864260a1babe83925ce794710cfd642ee4ae0e5b9d74cee49e9c67b6cd0ea5dfbb582132195a121356a1513e1bca73e5b80c58c7ccb4164453412f456c47616d616c2054657374204b65792031886204131102002205024dfcb16a021b03060b090807030206150802090a0b0416020301021e01021780000a091033af447ccd759b09fadd00a0b8fd6f5a790bad7e9f2dbb7632046dc4493588db009c087c6a9ba9f7f49fab221587a74788c00db4889ab00200009d0157044dfcb16a1004008dec3f9291205255ccff8c532318133a6840739dd68b03ba942676f9038612071447bf07d00d559c5c0875724ea16a4c774f80d8338b55fca691a0522e530e604215b467bbc9ccfd483a1da99d7bc2648b4318fdbd27766fc8bfad3fddb37c62b8ae7ccfe9577e9b8d1e77c1d417ed2c2ef02d52f4da11600d85d3229607943700030503ff506c94c87c8cab778e963b76cf63770f0a79bf48fb49d3b4e52234620fc9f7657f9f8d56c96a2b7c7826ae6b57ebb2221a3fe154b03b6637cea7e6d98e3e45d87cf8dc432f723d3d71f89c5192ac8d7290684d2c25ce55846a80c9a7823f6acd9bb29fa6cd71f20bc90eccfca20451d0c976e460e672b000df49466408d527affe0303027a728f9feb3b864260abd761730327bca2aaa4ea0525c175e92bf240682a0e83b226f97ecb2e935b62c9a133858ce31b271fa8eb41f6a1b3cd72a63025ce1a75ee4180dcc284884904181102000905024dfcb16a021b0c000a091033af447ccd759b09dd0b009e3c3e7296092c81bee5a19929462caaf2fff3ae26009e218c437a2340e7ea628149af1ec98ec091a43992b00200009501e1044dfcb1be1104009f61faa61aa43df75d128cbe53de528c4aec49ce9360c992e70c77072ad5623de0a3a6212771b66b39a30dad6781799e92608316900518ec01184a85d872365b7d2ba4bacfb5882ea3c2473d3750dc6178cc1cf82147fb58caa28b28e9f12f6d1efcb0534abed644156c91cca4ab78834268495160b2400bc422beb37d237c2300a0cac94911b6d493bda1e1fbc6feeca7cb7421d34b03fe22cec6ccb39675bb7b94a335c2b7be888fd3906a1125f33301d8aa6ec6ee6878f46f73961c8d57a3e9544d8ef2a2cbfd4d52da665b1266928cfe4cb347a58c412815f3b2d2369dec04b41ac9a71cc9547426d5ab941cccf3b18575637ccfb42df1a802df3cfe0a999f9e7109331170e3a221991bf868543960f8c816c28097e503fe319db10fb98049f3a57d7c80c420da66d56f3644371631fad3f0ff4040a19a4fedc2d07727a1b27576f75a4d28c47d8246f27071e12d7a8de62aad216ddbae6aa02efd6b8a3e2818cda48526549791ab277e447b3a36c57cefe9b592f5eab73959743fcc8e83cbefec03a329b55018b53eec196765ae40ef9e20521a603c551efe0303020950d53a146bf9c66034d00c23130cce95576a2ff78016ca471276e8227fb30b1ffbd92e61804fb0c3eff9e30b1a826ee8f3e4730b4d86273ca977b4164453412f456c47616d616c2054657374204b65792032886204131102002205024dfcb1be021b03060b090807030206150802090a0b0416020301021e01021780000a0910a86bf526325b21b22bd9009e34511620415c974750a20df5cb56b182f3b48e6600a0a9466cb1a1305a84953445f77d461593f1d42bc1b00200009d0157044dfcb1be1004009565a951da1ee87119d600c077198f1c1bceb0f7aa54552489298e41ff788fa8f0d43a69871f0f6f77ebdfb14a4260cf9fbeb65d5844b4272a1904dd95136d06c3da745dc46327dd44a0f16f60135914368c8039a34033862261806bb2c5ce1152e2840254697872c85441ccb7321431d75a747a4bfb1d2c66362b51ce76311700030503fc0ea76601c196768070b7365a200e6ddb09307f262d5f39eec467b5f5784e22abdf1aa49226f59ab37cb49969d8f5230ea65caf56015abda62604544ed526c5c522bf92bed178a078789f6c807b6d34885688024a5bed9e9f8c58d11d4b82487b44c5f470c5606806a0443b79cadb45e0f897a561a53f724e5349b9267c75ca17fe0303020950d53a146bf9c660bc5f4ce8f072465e2d2466434320c1e712272fafc20e342fe7608101580fa1a1a367e60486a7cd1246b7ef5586cf5e10b32762b710a30144f12dd17dd4884904181102000905024dfcb1be021b0c000a0910a86bf526325b21b2904c00a0b2b66b4b39ccffda1d10f3ea8d58f827e30a8b8e009f4255b2d8112a184e40cde43a34e8655ca7809370b0020000" + +const ed25519wX25519Key = "c54b0663877fe31b00000020f94da7bb48d60a61e567706a6587d0331999bb9d891a08242ead84543df895a3001972817b12be707e8d5f586ce61361201d344eb266a2c82fde6835762b65b0b7c2b1061f1b0a00000042058263877fe3030b090705150a0e080c021600029b03021e09222106cb186c4f0609a697e4d52dfa6c722b0c1f1e27c18a56708f6525ec27bad9acc905270902070200000000ad2820103e2d7d227ec0e6d7ce4471db36bfc97083253690271498a7ef0576c07faae14585b3b903b0127ec4fda2f023045a2ec76bcb4f9571a9651e14aee1137a1d668442c88f951e33c4ffd33fb9a17d511eed758fc6d9cc50cb5fd793b2039d5804c74b0663877fe319000000208693248367f9e5015db922f8f48095dda784987f2d5985b12fbad16caf5e4435004d600a4f794d44775c57a26e0feefed558e9afffd6ad0d582d57fb2ba2dcedb8c29b06181b0a0000002c050263877fe322a106cb186c4f0609a697e4d52dfa6c722b0c1f1e27c18a56708f6525ec27bad9acc9021b0c00000000defa20a6e9186d9d5935fc8fe56314cdb527486a5a5120f9b762a235a729f039010a56b89c658568341fbef3b894e9834ad9bc72afae2f4c9c47a43855e65f1cb0a3f77bbc5f61085c1f8249fe4e7ca59af5f0bcee9398e0fa8d76e522e1d8ab42bb0d" + +const signedMessageHex = "a3019bc0cbccc0c4b8d8b74ee2108fe16ec6d3ca490cbe362d3f8333d3f352531472538b8b13d353b97232f352158c20943157c71c16064626063656269052062e4e01987e9b6fccff4b7df3a34c534b23e679cbec3bc0f8f6e64dfb4b55fe3f8efa9ce110ddb5cd79faf1d753c51aecfa669f7e7aa043436596cccc3359cb7dd6bbe9ecaa69e5989d9e57209571edc0b2fa7f57b9b79a64ee6e99ce1371395fee92fec2796f7b15a77c386ff668ee27f6d38f0baa6c438b561657377bf6acff3c5947befd7bf4c196252f1d6e5c524d0300" + +const signedTextMessageHex = "a3019bc0cbccc8c4b8d8b74ee2108fe16ec6d36a250cbece0c178233d3f352531472538b8b13d35379b97232f352158ca0b4312f57c71c1646462606365626906a062e4e019811591798ff99bf8afee860b0d8a8c2a85c3387e3bcf0bb3b17987f2bbcfab2aa526d930cbfd3d98757184df3995c9f3e7790e36e3e9779f06089d4c64e9e47dd6202cb6e9bc73c5d11bb59fbaf89d22d8dc7cf199ddf17af96e77c5f65f9bbed56f427bd8db7af37f6c9984bf9385efaf5f184f986fb3e6adb0ecfe35bbf92d16a7aa2a344fb0bc52fb7624f0200" + +const signedEncryptedMessageHex = "c18c032a67d68660df41c70103ff5a84c9a72f80e74ef0384c2d6a9ebfe2b09e06a8f298394f6d2abf174e40934ab0ec01fb2d0ddf21211c6fe13eb238563663b017a6b44edca552eb4736c4b7dc6ed907dd9e12a21b51b64b46f902f76fb7aaf805c1db8070574d8d0431a23e324a750f77fb72340a17a42300ee4ca8207301e95a731da229a63ab9c6b44541fbd2c11d016d810b3b3b2b38f15b5b40f0a4910332829c2062f1f7cc61f5b03677d73c54cafa1004ced41f315d46444946faae571d6f426e6dbd45d9780eb466df042005298adabf7ce0ef766dfeb94cd449c7ed0046c880339599c4711af073ce649b1e237c40b50a5536283e03bdbb7afad78bd08707715c67fb43295f905b4c479178809d429a8e167a9a8c6dfd8ab20b4edebdc38d6dec879a3202e1b752690d9bb5b0c07c5a227c79cc200e713a99251a4219d62ad5556900cf69bd384b6c8e726c7be267471d0d23af956da165af4af757246c2ebcc302b39e8ef2fccb4971b234fcda22d759ddb20e27269ee7f7fe67898a9de721bfa02ab0becaa046d00ea16cb1afc4e2eab40d0ac17121c565686e5cbd0cbdfbd9d6db5c70278b9c9db5a83176d04f61fbfbc4471d721340ede2746e5c312ded4f26787985af92b64fae3f253dbdde97f6a5e1996fd4d865599e32ff76325d3e9abe93184c02988ee89a4504356a4ef3b9b7a57cbb9637ca90af34a7676b9ef559325c3cca4e29d69fec1887f5440bb101361d744ad292a8547f22b4f22b419a42aa836169b89190f46d9560824cb2ac6e8771de8223216a5e647e132ab9eebcba89569ab339cb1c3d70fe806b31f4f4c600b4103b8d7583ebff16e43dcda551e6530f975122eb8b29" + +const verifiedSignatureEncryptedMessageHex = "c2b304000108000605026048f6d600210910a34d7e18c20c31bb1621045fb74b1d03b1e3cb31bc2f8aa34d7e18c20c31bb9a3b0400a32ddac1af259c1b0abab0041327ea04970944401978fb647dd1cf9aba4f164e43f0d8a9389501886474bdd4a6e77f6aea945c07dfbf87743835b44cc2c39a1f9aeecfa83135abc92e18e50396f2e6a06c44e0188b0081effbfb4160d28f118d4ff73dd199a102e47cffd8c7ff2bacd83ae72b5820c021a486766dd587b5da61" + +const unverifiedSignatureEncryptedMessageHex = "c2b304000108000605026048f6d600210910d4984f961e35246b162104f7745a3c5e5fce108c1f128bd4984f961e35246b9a3b04009f3f90cf495f4e1cf759d1b596eea8fd31f3ac9ffb71e5bd5412e1834410dc4ba7f5a73481f48feabadd676c81e7329f894dedf87b3a74e01465515744baee12c9bcd7f16722f831f09459c12fd822f492da4236a7728b4e3f4e68f1eee4e68d21b9e71dfd7832cb42725a69b9c6468d47b01d9d51f57f64b47f291983a3f635" + +const signedEncryptedMessage2Hex = "85010e03cf6a7abcd43e36731003fb057f5495b79db367e277cdbe4ab90d924ddee0c0381494112ff8c1238fb0184af35d1731573b01bc4c55ecacd2aafbe2003d36310487d1ecc9ac994f3fada7f9f7f5c3a64248ab7782906c82c6ff1303b69a84d9a9529c31ecafbcdb9ba87e05439897d87e8a2a3dec55e14df19bba7f7bd316291c002ae2efd24f83f9e3441203fc081c0c23dc3092a454ca8a082b27f631abf73aca341686982e8fbda7e0e7d863941d68f3de4a755c2964407f4b5e0477b3196b8c93d551dd23c8beef7d0f03fbb1b6066f78907faf4bf1677d8fcec72651124080e0b7feae6b476e72ab207d38d90b958759fdedfc3c6c35717c9dbfc979b3cfbbff0a76d24a5e57056bb88acbd2a901ef64bc6e4db02adc05b6250ff378de81dca18c1910ab257dff1b9771b85bb9bbe0a69f5989e6d1710a35e6dfcceb7d8fb5ccea8db3932b3d9ff3fe0d327597c68b3622aec8e3716c83a6c93f497543b459b58ba504ed6bcaa747d37d2ca746fe49ae0a6ce4a8b694234e941b5159ff8bd34b9023da2814076163b86f40eed7c9472f81b551452d5ab87004a373c0172ec87ea6ce42ccfa7dbdad66b745496c4873d8019e8c28d6b3" + +const signatureEncryptedMessage2Hex = "c24604001102000605024dfd0166000a091033af447ccd759b09bae600a096ec5e63ecf0a403085e10f75cc3bab327663282009f51fad9df457ed8d2b70d8a73c76e0443eac0f377" + +const symmetricallyEncryptedCompressedHex = "c32e040903085a357c1a7b5614ed00cc0d1d92f428162058b3f558a0fb0980d221ebac6c97d5eda4e0fe32f6e706e94dd263012d6ca1ef8c4bbd324098225e603a10c85ebf09cbf7b5aeeb5ce46381a52edc51038b76a8454483be74e6dcd1e50d5689a8ae7eceaeefed98a0023d49b22eb1f65c2aa1ef1783bb5e1995713b0457102ec3c3075fe871267ffa4b686ad5d52000d857" + +const dsaTestKeyHex = "9901a2044d6c49de110400cb5ce438cf9250907ac2ba5bf6547931270b89f7c4b53d9d09f4d0213a5ef2ec1f26806d3d259960f872a4a102ef1581ea3f6d6882d15134f21ef6a84de933cc34c47cc9106efe3bd84c6aec12e78523661e29bc1a61f0aab17fa58a627fd5fd33f5149153fbe8cd70edf3d963bc287ef875270ff14b5bfdd1bca4483793923b00a0fe46d76cb6e4cbdc568435cd5480af3266d610d303fe33ae8273f30a96d4d34f42fa28ce1112d425b2e3bf7ea553d526e2db6b9255e9dc7419045ce817214d1a0056dbc8d5289956a4b1b69f20f1105124096e6a438f41f2e2495923b0f34b70642607d45559595c7fe94d7fa85fc41bf7d68c1fd509ebeaa5f315f6059a446b9369c277597e4f474a9591535354c7e7f4fd98a08aa60400b130c24ff20bdfbf683313f5daebf1c9b34b3bdadfc77f2ddd72ee1fb17e56c473664bc21d66467655dd74b9005e3a2bacce446f1920cd7017231ae447b67036c9b431b8179deacd5120262d894c26bc015bffe3d827ba7087ad9b700d2ca1f6d16cc1786581e5dd065f293c31209300f9b0afcc3f7c08dd26d0a22d87580b4db41054657374204b65792033202844534129886204131102002205024d6c49de021b03060b090807030206150802090a0b0416020301021e01021780000a0910338934250ccc03607e0400a0bdb9193e8a6b96fc2dfc108ae848914b504481f100a09c4dc148cb693293a67af24dd40d2b13a9e36794" + +const dsaTestKeyPrivateHex = "9501bb044d6c49de110400cb5ce438cf9250907ac2ba5bf6547931270b89f7c4b53d9d09f4d0213a5ef2ec1f26806d3d259960f872a4a102ef1581ea3f6d6882d15134f21ef6a84de933cc34c47cc9106efe3bd84c6aec12e78523661e29bc1a61f0aab17fa58a627fd5fd33f5149153fbe8cd70edf3d963bc287ef875270ff14b5bfdd1bca4483793923b00a0fe46d76cb6e4cbdc568435cd5480af3266d610d303fe33ae8273f30a96d4d34f42fa28ce1112d425b2e3bf7ea553d526e2db6b9255e9dc7419045ce817214d1a0056dbc8d5289956a4b1b69f20f1105124096e6a438f41f2e2495923b0f34b70642607d45559595c7fe94d7fa85fc41bf7d68c1fd509ebeaa5f315f6059a446b9369c277597e4f474a9591535354c7e7f4fd98a08aa60400b130c24ff20bdfbf683313f5daebf1c9b34b3bdadfc77f2ddd72ee1fb17e56c473664bc21d66467655dd74b9005e3a2bacce446f1920cd7017231ae447b67036c9b431b8179deacd5120262d894c26bc015bffe3d827ba7087ad9b700d2ca1f6d16cc1786581e5dd065f293c31209300f9b0afcc3f7c08dd26d0a22d87580b4d00009f592e0619d823953577d4503061706843317e4fee083db41054657374204b65792033202844534129886204131102002205024d6c49de021b03060b090807030206150802090a0b0416020301021e01021780000a0910338934250ccc03607e0400a0bdb9193e8a6b96fc2dfc108ae848914b504481f100a09c4dc148cb693293a67af24dd40d2b13a9e36794" + +const p256TestKeyHex = "98520456e5b83813082a8648ce3d030107020304a2072cd6d21321266c758cc5b83fab0510f751cb8d91897cddb7047d8d6f185546e2107111b0a95cb8ef063c33245502af7a65f004d5919d93ee74eb71a66253b424502d3235362054657374204b6579203c696e76616c6964406578616d706c652e636f6d3e8879041313080021050256e5b838021b03050b09080702061508090a0b020416020301021e01021780000a0910d44a2c495918513e54e50100dfa64f97d9b47766fc1943c6314ba3f2b2a103d71ad286dc5b1efb96a345b0c80100dbc8150b54241f559da6ef4baacea6d31902b4f4b1bdc09b34bf0502334b7754b8560456e5b83812082a8648ce3d030107020304bfe3cea9cee13486f8d518aa487fecab451f25467d2bf08e58f63e5fa525d5482133e6a79299c274b068ef0be448152ad65cf11cf764348588ca4f6a0bcf22b6030108078861041813080009050256e5b838021b0c000a0910d44a2c495918513e4a4800ff49d589fa64024ad30be363a032e3a0e0e6f5db56ba4c73db850518bf0121b8f20100fd78e065f4c70ea5be9df319ea67e493b936fc78da834a71828043d3154af56e" + +const p256TestKeyPrivateHex = "94a50456e5b83813082a8648ce3d030107020304a2072cd6d21321266c758cc5b83fab0510f751cb8d91897cddb7047d8d6f185546e2107111b0a95cb8ef063c33245502af7a65f004d5919d93ee74eb71a66253fe070302f0c2bfb0b6c30f87ee1599472b8636477eab23ced13b271886a4b50ed34c9d8436af5af5b8f88921f0efba6ef8c37c459bbb88bc1c6a13bbd25c4ce9b1e97679569ee77645d469bf4b43de637f5561b424502d3235362054657374204b6579203c696e76616c6964406578616d706c652e636f6d3e8879041313080021050256e5b838021b03050b09080702061508090a0b020416020301021e01021780000a0910d44a2c495918513e54e50100dfa64f97d9b47766fc1943c6314ba3f2b2a103d71ad286dc5b1efb96a345b0c80100dbc8150b54241f559da6ef4baacea6d31902b4f4b1bdc09b34bf0502334b77549ca90456e5b83812082a8648ce3d030107020304bfe3cea9cee13486f8d518aa487fecab451f25467d2bf08e58f63e5fa525d5482133e6a79299c274b068ef0be448152ad65cf11cf764348588ca4f6a0bcf22b603010807fe0703027510012471a603cfee2968dce19f732721ddf03e966fd133b4e3c7a685b788705cbc46fb026dc94724b830c9edbaecd2fb2c662f23169516cacd1fe423f0475c364ecc10abcabcfd4bbbda1a36a1bd8861041813080009050256e5b838021b0c000a0910d44a2c495918513e4a4800ff49d589fa64024ad30be363a032e3a0e0e6f5db56ba4c73db850518bf0121b8f20100fd78e065f4c70ea5be9df319ea67e493b936fc78da834a71828043d3154af56e" + +const armoredPrivateKeyBlock = `-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: GnuPG v1.4.10 (GNU/Linux) + +lQHYBE2rFNoBBADFwqWQIW/DSqcB4yCQqnAFTJ27qS5AnB46ccAdw3u4Greeu3Bp +idpoHdjULy7zSKlwR1EA873dO/k/e11Ml3dlAFUinWeejWaK2ugFP6JjiieSsrKn +vWNicdCS4HTWn0X4sjl0ZiAygw6GNhqEQ3cpLeL0g8E9hnYzJKQ0LWJa0QARAQAB +AAP/TB81EIo2VYNmTq0pK1ZXwUpxCrvAAIG3hwKjEzHcbQznsjNvPUihZ+NZQ6+X +0HCfPAdPkGDCLCb6NavcSW+iNnLTrdDnSI6+3BbIONqWWdRDYJhqZCkqmG6zqSfL +IdkJgCw94taUg5BWP/AAeQrhzjChvpMQTVKQL5mnuZbUCeMCAN5qrYMP2S9iKdnk +VANIFj7656ARKt/nf4CBzxcpHTyB8+d2CtPDKCmlJP6vL8t58Jmih+kHJMvC0dzn +gr5f5+sCAOOe5gt9e0am7AvQWhdbHVfJU0TQJx+m2OiCJAqGTB1nvtBLHdJnfdC9 +TnXXQ6ZXibqLyBies/xeY2sCKL5qtTMCAKnX9+9d/5yQxRyrQUHt1NYhaXZnJbHx +q4ytu0eWz+5i68IYUSK69jJ1NWPM0T6SkqpB3KCAIv68VFm9PxqG1KmhSrQIVGVz +dCBLZXmIuAQTAQIAIgUCTasU2gIbAwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AA +CgkQO9o98PRieSoLhgQAkLEZex02Qt7vGhZzMwuN0R22w3VwyYyjBx+fM3JFETy1 +ut4xcLJoJfIaF5ZS38UplgakHG0FQ+b49i8dMij0aZmDqGxrew1m4kBfjXw9B/v+ +eIqpODryb6cOSwyQFH0lQkXC040pjq9YqDsO5w0WYNXYKDnzRV0p4H1pweo2VDid +AdgETasU2gEEAN46UPeWRqKHvA99arOxee38fBt2CI08iiWyI8T3J6ivtFGixSqV +bRcPxYO/qLpVe5l84Nb3X71GfVXlc9hyv7CD6tcowL59hg1E/DC5ydI8K8iEpUmK +/UnHdIY5h8/kqgGxkY/T/hgp5fRQgW1ZoZxLajVlMRZ8W4tFtT0DeA+JABEBAAEA +A/0bE1jaaZKj6ndqcw86jd+QtD1SF+Cf21CWRNeLKnUds4FRRvclzTyUMuWPkUeX +TaNNsUOFqBsf6QQ2oHUBBK4VCHffHCW4ZEX2cd6umz7mpHW6XzN4DECEzOVksXtc +lUC1j4UB91DC/RNQqwX1IV2QLSwssVotPMPqhOi0ZLNY7wIA3n7DWKInxYZZ4K+6 +rQ+POsz6brEoRHwr8x6XlHenq1Oki855pSa1yXIARoTrSJkBtn5oI+f8AzrnN0BN +oyeQAwIA/7E++3HDi5aweWrViiul9cd3rcsS0dEnksPhvS0ozCJiHsq/6GFmy7J8 +QSHZPteedBnZyNp5jR+H7cIfVN3KgwH/Skq4PsuPhDq5TKK6i8Pc1WW8MA6DXTdU +nLkX7RGmMwjC0DBf7KWAlPjFaONAX3a8ndnz//fy1q7u2l9AZwrj1qa1iJ8EGAEC +AAkFAk2rFNoCGwwACgkQO9o98PRieSo2/QP/WTzr4ioINVsvN1akKuekmEMI3LAp +BfHwatufxxP1U+3Si/6YIk7kuPB9Hs+pRqCXzbvPRrI8NHZBmc8qIGthishdCYad +AHcVnXjtxrULkQFGbGvhKURLvS9WnzD/m1K2zzwxzkPTzT9/Yf06O6Mal5AdugPL +VrM0m72/jnpKo04= +=zNCn +-----END PGP PRIVATE KEY BLOCK-----` + +const e2ePublicKey = `-----BEGIN PGP PUBLIC KEY BLOCK----- +Charset: UTF-8 + +xv8AAABSBAAAAAATCCqGSM49AwEHAgME1LRoXSpOxtHXDUdmuvzchyg6005qIBJ4 +sfaSxX7QgH9RV2ONUhC+WiayCNADq+UMzuR/vunSr4aQffXvuGnR383/AAAAFDxk +Z2lsQHlhaG9vLWluYy5jb20+wv8AAACGBBATCAA4/wAAAAWCVGvAG/8AAAACiwn/ +AAAACZC2VkQCOjdvYf8AAAAFlQgJCgv/AAAAA5YBAv8AAAACngEAAE1BAP0X8veD +24IjmI5/C6ZAfVNXxgZZFhTAACFX75jUA3oD6AEAzoSwKf1aqH6oq62qhCN/pekX ++WAsVMBhNwzLpqtCRjLO/wAAAFYEAAAAABIIKoZIzj0DAQcCAwT50ain7vXiIRv8 +B1DO3x3cE/aattZ5sHNixJzRCXi2vQIA5QmOxZ6b5jjUekNbdHG3SZi1a2Ak5mfX +fRxC/5VGAwEIB8L/AAAAZQQYEwgAGP8AAAAFglRrwBz/AAAACZC2VkQCOjdvYQAA +FJAA9isX3xtGyMLYwp2F3nXm7QEdY5bq5VUcD/RJlj792VwA/1wH0pCzVLl4Q9F9 +ex7En5r7rHR5xwX82Msc+Rq9dSyO +=7MrZ +-----END PGP PUBLIC KEY BLOCK-----` + +const dsaKeyWithSHA512 = `9901a2044f04b07f110400db244efecc7316553ee08d179972aab87bb1214de7692593fcf5b6feb1c80fba268722dd464748539b85b81d574cd2d7ad0ca2444de4d849b8756bad7768c486c83a824f9bba4af773d11742bdfb4ac3b89ef8cc9452d4aad31a37e4b630d33927bff68e879284a1672659b8b298222fc68f370f3e24dccacc4a862442b9438b00a0ea444a24088dc23e26df7daf8f43cba3bffc4fe703fe3d6cd7fdca199d54ed8ae501c30e3ec7871ea9cdd4cf63cfe6fc82281d70a5b8bb493f922cd99fba5f088935596af087c8d818d5ec4d0b9afa7f070b3d7c1dd32a84fca08d8280b4890c8da1dde334de8e3cad8450eed2a4a4fcc2db7b8e5528b869a74a7f0189e11ef097ef1253582348de072bb07a9fa8ab838e993cef0ee203ff49298723e2d1f549b00559f886cd417a41692ce58d0ac1307dc71d85a8af21b0cf6eaa14baf2922d3a70389bedf17cc514ba0febbd107675a372fe84b90162a9e88b14d4b1c6be855b96b33fb198c46f058568817780435b6936167ebb3724b680f32bf27382ada2e37a879b3d9de2abe0c3f399350afd1ad438883f4791e2e3b4184453412068617368207472756e636174696f6e207465737488620413110a002205024f04b07f021b03060b090807030206150802090a0b0416020301021e01021780000a0910ef20e0cefca131581318009e2bf3bf047a44d75a9bacd00161ee04d435522397009a03a60d51bd8a568c6c021c8d7cf1be8d990d6417b0020003` + +const unknownHashFunctionHex = `8a00000040040001990006050253863c24000a09103b4fe6acc0b21f32ffff0101010101010101010101010101010101010101010101010101010101010101010101010101` + +const rsaSignatureBadMPIlength = `8a00000040040001030006050253863c24000a09103b4fe6acc0b21f32ffff0101010101010101010101010101010101010101010101010101010101010101010101010101` + +const missingHashFunctionHex = `8a00000040040001030006050253863c24000a09103b4fe6acc0b21f32ffff0101010101010101010101010101010101010101010101010101010101010101010101010101` + +const campbellQuine = `a0b001000300fcffa0b001000d00f2ff000300fcffa0b001000d00f2ff8270a01c00000500faff8270a01c00000500faff000500faff001400ebff8270a01c00000500faff000500faff001400ebff428821c400001400ebff428821c400001400ebff428821c400001400ebff428821c400001400ebff428821c400000000ffff000000ffff000b00f4ff428821c400000000ffff000000ffff000b00f4ff0233214c40000100feff000233214c40000100feff0000` + +const keyV4forVerifyingSignedMessageV3 = `-----BEGIN PGP PUBLIC KEY BLOCK----- +Comment: GPGTools - https://gpgtools.org + +mI0EVfxoFQEEAMBIqmbDfYygcvP6Phr1wr1XI41IF7Qixqybs/foBF8qqblD9gIY +BKpXjnBOtbkcVOJ0nljd3/sQIfH4E0vQwK5/4YRQSI59eKOqd6Fx+fWQOLG+uu6z +tewpeCj9LLHvibx/Sc7VWRnrznia6ftrXxJ/wHMezSab3tnGC0YPVdGNABEBAAG0 +JEdvY3J5cHRvIFRlc3QgS2V5IDx0aGVtYXhAZ21haWwuY29tPoi5BBMBCgAjBQJV +/GgVAhsDBwsJCAcDAgEGFQgCCQoLBBYCAwECHgECF4AACgkQeXnQmhdGW9PFVAP+ +K7TU0qX5ArvIONIxh/WAweyOk884c5cE8f+3NOPOOCRGyVy0FId5A7MmD5GOQh4H +JseOZVEVCqlmngEvtHZb3U1VYtVGE5WZ+6rQhGsMcWP5qaT4soYwMBlSYxgYwQcx +YhN9qOr292f9j2Y//TTIJmZT4Oa+lMxhWdqTfX+qMgG4jQRV/GgVAQQArhFSiij1 +b+hT3dnapbEU+23Z1yTu1DfF6zsxQ4XQWEV3eR8v+8mEDDNcz8oyyF56k6UQ3rXi +UMTIwRDg4V6SbZmaFbZYCOwp/EmXJ3rfhm7z7yzXj2OFN22luuqbyVhuL7LRdB0M +pxgmjXb4tTvfgKd26x34S+QqUJ7W6uprY4sAEQEAAYifBBgBCgAJBQJV/GgVAhsM +AAoJEHl50JoXRlvT7y8D/02ckx4OMkKBZo7viyrBw0MLG92i+DC2bs35PooHR6zz +786mitjOp5z2QWNLBvxC70S0qVfCIz8jKupO1J6rq6Z8CcbLF3qjm6h1omUBf8Nd +EfXKD2/2HV6zMKVknnKzIEzauh+eCKS2CeJUSSSryap/QLVAjRnckaES/OsEWhNB +=RZia +-----END PGP PUBLIC KEY BLOCK----- +` + +const signedMessageV3 = `-----BEGIN PGP MESSAGE----- +Comment: GPGTools - https://gpgtools.org + +owGbwMvMwMVYWXlhlrhb9GXG03JJDKF/MtxDMjKLFYAoUaEktbhEITe1uDgxPVWP +q5NhKjMrWAVcC9evD8z/bF/uWNjqtk/X3y5/38XGRQHm/57rrDRYuGnTw597Xqka +uM3137/hH3Os+Jf2dc0fXOITKwJvXJvecPVs0ta+Vg7ZO1MLn8w58Xx+6L58mbka +DGHyU9yTueZE8D+QF/Tz28Y78dqtF56R1VPn9Xw4uJqrWYdd7b3vIZ1V6R4Nh05d +iT57d/OhWwA= +=hG7R +-----END PGP MESSAGE----- +` + +const signedMessageOldStyle = `-----BEGIN PGP MESSAGE----- + +wsE7BAABCgBvBYJknZ5zCRD7/MgqAV5zMEcUAAAAAAAeACBzYWx0QG5vdGF0aW9u +cy5zZXF1b2lhLXBncC5vcmciNnrFGxZikAZXMAlYswJyEjuD4sHc2prQL+BM7agB +aRYhBNGmbhojsYLJmA94jPv8yCoBXnMwAABKUQv/XNqMZrPtp8w8oC+9dA8AxqDi +7Icq1pORiPXiTfcVhad0k2wI9c4Xt6u+ndJuaTgSouFkaFYjp1yiZfOdj/PwtdO2 +4MQSogOMyQT3jjw+lF+uK4zqFzxs+EzqTdPcMgn30/AXGPIggoQHid1/z//jbn5e +TUZB4cAE2RfBbQ0Yg/QS7PbkRS7P6hDrCKCcKYuQMQDUymGViounz0Sf4CBm++9x +1KyyXIHdtOFcSJZRH2nd2qM94kO++J8zxKt5yFaIctxOUPi6MdNhkCW4DuOtpqRF +xR4O3G6x1LcdNh3NzaiKUKmW+JY1PBWJaoL5Tfy9PwxttuuO8azEkAOFkRbz3yzT ++zLmIQIAsD5taXhJjmAlberhDIe6WUyYodKU8j61Z3kqD78QTD1xpqdAj2Xl1Dxk +PtdMR+yF8zOGn+X0xg08e/AEXDWMF7wcDj2XnHt+N+Xe1Bi9SNKTavsBPVuFDxSz +Rx1AJ2YkWexm44UdW9t7fQ4krEWPjG2Z9GzUg+dNxA0DAAoBzjJvjmOvI0EByxRi +AAAAAABIZWxsbw0KV29ybGQhCsLBOwQAAQoAbwWCZJ2ecwkQzjJvjmOvI0FHFAAA +AAAAHgAgc2FsdEBub3RhdGlvbnMuc2VxdW9pYS1wZ3Aub3Jnu6VKgSUjJVHfBIsa +u3WDOdUie4oCxfpRdKFJq6FDwp4WIQShwFEH+rDNIMZmfoDOMm+OY68jQQAA0bkL +/3zi/gB/VDRWdCKMHb8D+54y1JE2kAF6+s7ttx6C9eeaMtOOghD3rSeLWOGp1Ff9 +OFysla15+yXJTnc+/F0dO4dF1w3r4Y6gbJc36m5FVtNx3cuxsYPH0fu8GnkA9jHQ +FGHmOiUtCVjH98HwLZuCgJuSRuvzKvgONYnKYdy0inO3OSaYCPKlQvFL5S0vTHAJ +D7hq2Nld9Ac7iJIq0Wv5m36Qat32q11inMhUG+Qg45Eyc9dj0phy/J0dgfluNHZZ +rxHnNVSeWbhXk8blsckay05zbQs4uupLxHWy1VmxhULrpdkJSQ8s0uWVZz4kMOLM +eSPfEZRYu6+6eD6W9g3iLZ9sAq5lpcnGItPaaS7YMkjfR2y6lzhHCOAUf6dbTm8c +dJjpJlKdnJAhsth5MfLl+b94ZjYBWYhPIVTkuUuzK6bL46PywRGhx2v5JKoj2Sph +1imuaC76JixESDhKAM5GafpcmoymWuqJBB9lqxOMxfR3LDMaqwyfJUvM9vT2RMux +Lg== +=+yph +-----END PGP MESSAGE----- +` + +const signedMessageOldStyleKey = `-----BEGIN PGP PRIVATE KEY BLOCK----- +Comment: Bob's OpenPGP Transferable Secret Key + +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 +=miES +-----END PGP PRIVATE KEY BLOCK----- +` + +// https://mailarchive.ietf.org/arch/msg/openpgp/9SheW_LENE0Kxf7haNllovPyAdY/ +const v5PrivKey = `-----BEGIN PGP PRIVATE KEY BLOCK----- + +lGEFXJH05BYAAAAtCSsGAQQB2kcPAQEHQFhZlVcVVtwf+21xNQPX+ecMJJBL0MPd +fj75iux+my8QAAAAAAAiAQCHZ1SnSUmWqxEsoI6facIVZQu6mph3cBFzzTvcm5lA +Ng5ctBhlbW1hLmdvbGRtYW5AZXhhbXBsZS5uZXSIlgUTFggASCIhBRk0e8mHJGQC +X5nfPsLgAA7ZiEiS4fez6kyUAJFZVptUBQJckfTkAhsDBQsJCAcCAyICAQYVCgkI +CwIEFgIDAQIeBwIXgAAA9cAA/jiR3yMsZMeEQ40u6uzEoXa6UXeV/S3wwJAXRJy9 +M8s0AP9vuL/7AyTfFXwwzSjDnYmzS0qAhbLDQ643N+MXGBJ2BZxmBVyR9OQSAAAA +MgorBgEEAZdVAQUBAQdA+nysrzml2UCweAqtpDuncSPlvrcBWKU0yfU0YvYWWAoD +AQgHAAAAAAAiAP9OdAPppjU1WwpqjIItkxr+VPQRT8Zm/Riw7U3F6v3OiBFHiHoF +GBYIACwiIQUZNHvJhyRkAl+Z3z7C4AAO2YhIkuH3s+pMlACRWVabVAUCXJH05AIb +DAAAOSQBAP4BOOIR/sGLNMOfeb5fPs/02QMieoiSjIBnijhob2U5AQC+RtOHCHx7 +TcIYl5/Uyoi+FOvPLcNw4hOv2nwUzSSVAw== +=IiS2 +-----END PGP PRIVATE KEY BLOCK-----` + +// Generated with the above private key +const v5PrivKeyMsg = `-----BEGIN PGP MESSAGE----- +Version: OpenPGP.js v4.10.7 +Comment: https://openpgpjs.org + +xA0DAQoWGTR7yYckZAIByxF1B21zZy50eHRfbIGSdGVzdMJ3BQEWCgAGBQJf +bIGSACMiIQUZNHvJhyRkAl+Z3z7C4AAO2YhIkuH3s+pMlACRWVabVDQvAP9G +y29VPonFXqi2zKkpZrvyvZxg+n5e8Nt9wNbuxeCd3QD/TtO2s+JvjrE4Siwv +UQdl5MlBka1QSNbMq2Bz7XwNPg4= +=6lbM +-----END PGP MESSAGE-----` + +// See OpenPGP crypto refresh Section A.3. +const v6PrivKey = `-----BEGIN PGP PRIVATE KEY BLOCK----- + +xUsGY4d/4xsAAAAg+U2nu0jWCmHlZ3BqZYfQMxmZu52JGggkLq2EVD34laMAGXKB +exK+cH6NX1hs5hNhIB00TrJmosgv3mg1ditlsLfCsQYfGwoAAABCBYJjh3/jAwsJ +BwUVCg4IDAIWAAKbAwIeCSIhBssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce6 +2azJBScJAgcCAAAAAK0oIBA+LX0ifsDm185Ecds2v8lwgyU2kCcUmKfvBXbAf6rh +RYWzuQOwEn7E/aLwIwRaLsdry0+VcallHhSu4RN6HWaEQsiPlR4zxP/TP7mhfVEe +7XWPxtnMUMtf15OyA51YBMdLBmOHf+MZAAAAIIaTJINn+eUBXbki+PSAld2nhJh/ +LVmFsS+60WyvXkQ1AE1gCk95TUR3XFeibg/u/tVY6a//1q0NWC1X+yui3O24wpsG +GBsKAAAALAWCY4d/4wKbDCIhBssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce6 +2azJAAAAAAQBIKbpGG2dWTX8j+VjFM21J0hqWlEg+bdiojWnKfA5AQpWUWtnNwDE +M0g12vYxoWM8Y81W+bHBw805I8kWVkXU6vFOi+HWvv/ira7ofJu16NnoUkhclkUr +k0mXubZvyl4GBg== +-----END PGP PRIVATE KEY BLOCK-----` + +// See OpenPGP crypto refresh merge request: +// https://gitlab.com/openpgp-wg/rfc4880bis/-/merge_requests/304 +const v6PrivKeyMsg = `-----BEGIN PGP MESSAGE----- + +wV0GIQYSyD8ecG9jCP4VGkF3Q6HwM3kOk+mXhIjR2zeNqZMIhRmHzxjV8bU/gXzO +WgBM85PMiVi93AZfJfhK9QmxfdNnZBjeo1VDeVZheQHgaVf7yopqR6W1FT6NOrfS +aQIHAgZhZBZTW+CwcW1g4FKlbExAf56zaw76/prQoN+bAzxpohup69LA7JW/Vp0l +yZnuSj3hcFj0DfqLTGgr4/u717J+sPWbtQBfgMfG9AOIwwrUBqsFE9zW+f1zdlYo +bhF30A+IitsxxA== +-----END PGP MESSAGE-----` + +// See OpenPGP crypto refresh merge request: +// https://gitlab.com/openpgp-wg/rfc4880bis/-/merge_requests/305 +const v6PrivKeyInlineSignMsg = `-----BEGIN PGP MESSAGE----- + +xEYGAQobIHZJX1AhiJD39eLuPBgiUU9wUA9VHYblySHkBONKU/usyxhsTwYJppfk +1S36bHIrDB8eJ8GKVnCPZSXsJ7rZrMkBy0p1AAAAAABXaGF0IHdlIG5lZWQgZnJv +bSB0aGUgZ3JvY2VyeSBzdG9yZToKCi0gdG9mdQotIHZlZ2V0YWJsZXMKLSBub29k +bGVzCsKYBgEbCgAAACkFgmOYo2MiIQbLGGxPBgmml+TVLfpscisMHx4nwYpWcI9l +JewnutmsyQAAAABpNiB2SV9QIYiQ9/Xi7jwYIlFPcFAPVR2G5ckh5ATjSlP7rCfQ +b7gKqPxbyxbhljGygHQPnqau1eBzrQD5QVplPEDnemrnfmkrpx0GmhCfokxYz9jj +FtCgazStmsuOXF9SFQE= +-----END PGP MESSAGE-----` + +// See https://gitlab.com/openpgp-wg/rfc4880bis/-/merge_requests/274 +// decryption password: "correct horse battery staple" +const v6ArgonSealedPrivKey = `-----BEGIN PGP PRIVATE KEY BLOCK----- + +xYIGY4d/4xsAAAAg+U2nu0jWCmHlZ3BqZYfQMxmZu52JGggkLq2EVD34laP9JgkC +FARdb9ccngltHraRe25uHuyuAQQVtKipJ0+r5jL4dacGWSAheCWPpITYiyfyIOPS +3gIDyg8f7strd1OB4+LZsUhcIjOMpVHgmiY/IutJkulneoBYwrEGHxsKAAAAQgWC +Y4d/4wMLCQcFFQoOCAwCFgACmwMCHgkiIQbLGGxPBgmml+TVLfpscisMHx4nwYpW +cI9lJewnutmsyQUnCQIHAgAAAACtKCAQPi19In7A5tfORHHbNr/JcIMlNpAnFJin +7wV2wH+q4UWFs7kDsBJ+xP2i8CMEWi7Ha8tPlXGpZR4UruETeh1mhELIj5UeM8T/ +0z+5oX1RHu11j8bZzFDLX9eTsgOdWATHggZjh3/jGQAAACCGkySDZ/nlAV25Ivj0 +gJXdp4SYfy1ZhbEvutFsr15ENf0mCQIUBA5hhGgp2oaavg6mFUXcFMwBBBUuE8qf +9Ock+xwusd+GAglBr5LVyr/lup3xxQvHXFSjjA2haXfoN6xUGRdDEHI6+uevKjVR +v5oAxgu7eJpaXNjCmwYYGwoAAAAsBYJjh3/jApsMIiEGyxhsTwYJppfk1S36bHIr +DB8eJ8GKVnCPZSXsJ7rZrMkAAAAABAEgpukYbZ1ZNfyP5WMUzbUnSGpaUSD5t2Ki +Nacp8DkBClZRa2c3AMQzSDXa9jGhYzxjzVb5scHDzTkjyRZWRdTq8U6L4da+/+Kt +ruh8m7Xo2ehSSFyWRSuTSZe5tm/KXgYG +-----END PGP PRIVATE KEY BLOCK-----` + +const v4Key25519 = `-----BEGIN PGP PRIVATE KEY BLOCK----- + +xUkEZB3qzRto01j2k2pwN5ux9w70stPinAdXULLr20CRW7U7h2GSeACch0M+ +qzQg8yjFQ8VBvu3uwgKH9senoHmj72lLSCLTmhFKzQR0ZXN0wogEEBsIAD4F +gmQd6s0ECwkHCAmQIf45+TuC+xMDFQgKBBYAAgECGQECmwMCHgEWIQSWEzMi +jJUHvyIbVKIh/jn5O4L7EwAAUhaHNlgudvxARdPPETUzVgjuWi+YIz8w1xIb +lHQMvIrbe2sGCQIethpWofd0x7DHuv/ciHg+EoxJ/Td6h4pWtIoKx0kEZB3q +zRm4CyA7quliq7yx08AoOqHTuuCgvpkSdEhpp3pEyejQOgBo0p6ywIiLPllY +0t+jpNspHpAGfXID6oqjpYuJw3AfVRBlwnQEGBsIACoFgmQd6s0JkCH+Ofk7 +gvsTApsMFiEElhMzIoyVB78iG1SiIf45+TuC+xMAAGgQuN9G73446ykvJ/mL +sCZ7zGFId2gBd1EnG0FTC4npfOKpck0X8dngByrCxU8LDSfvjsEp/xDAiKsQ +aU71tdtNBQ== +=e7jT +-----END PGP PRIVATE KEY BLOCK-----` + +const keyWithExpiredCrossSig = `-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +hW1Hj9AO8lzggBQ= +=Nt+N +-----END PGP PUBLIC KEY BLOCK----- +` + +const sigFromKeyWithExpiredCrossSig = `-----BEGIN PGP SIGNATURE----- + +wsDzBAABCgAGBYJfLAFsACEJEHwvqk35PDeyFiEEHdzhXwkhfO4vOzdgfC+qTfk8 +N7KiqwwAts4QGB7v9bABCC2qkTxJhmStC0wQMcHRcjL/qAiVnmasQWmvE9KVsdm3 +AaXd8mIx4a37/RRvr9dYrY2eE4uw72cMqPxNja2tvVXkHQvk1oEUqfkvbXs4ypKI +NyeTWjXNOTZEbg0hbm3nMy+Wv7zgB1CEvAsEboLDJlhGqPcD+X8a6CJGrBGUBUrv +KVmZr3U6vEzClz3DBLpoddCQseJRhT4YM1nKmBlZ5quh2LFgTSpajv5OsZheqt9y +EZAPbqmLhDmWRQwGzkWHKceKS7nZ/ox2WK6OS7Ob8ZGZkM64iPo6/EGj5Yc19vQN +AGiIaPEGszBBWlOpHTPhNm0LB0nMWqqaT87oNYwP8CQuuxDb6rKJ2lffCmZH27Lb +UbQZcH8J+0UhpeaiadPZxH5ATJAcenmVtVVMLVOFnm+eIlxzov9ntpgGYt8hLdXB +ITEG9mMgp3TGS9ZzSifMZ8UGtHdp9QdBg8NEVPFzDOMGxpc/Bftav7RRRuPiAER+ +7A5CBid5 +=aQkm +-----END PGP SIGNATURE----- +` + +const signedMessageWithCriticalNotation = `-----BEGIN PGP MESSAGE----- + +owGbwMvMwMH4oOW7S46CznTG09xJDDE3Wl1KUotLuDousDAwcjBYiSmyXL+48d6x +U1PSGUxcj8IUszKBVMpMaWAAAgEGZpAeh9SKxNyCnFS95PzcytRiBi5OAZjyXXzM +f8WYLqv7TXP61Sa4rqT12CI3xaN73YS2pt089f96odCKaEPnWJ3iSGmzJaW/ug10 +2Zo8Wj2k4s7t8wt4H3HtTu+y5UZfV3VOO+l//sdE/o+Lsub8FZH7/eOq7OnbNp4n +vwjE8mqJXetNMfj8r2SCyvkEnlVRYR+/mnge+ib56FdJ8uKtqSxyvgA= +=fRXs +-----END PGP MESSAGE-----` + +const criticalNotationSigner = `-----BEGIN PGP PUBLIC KEY BLOCK----- + +mI0EUmEvTgEEANyWtQQMOybQ9JltDqmaX0WnNPJeLILIM36sw6zL0nfTQ5zXSS3+ +fIF6P29lJFxpblWk02PSID5zX/DYU9/zjM2xPO8Oa4xo0cVTOTLj++Ri5mtr//f5 +GLsIXxFrBJhD/ghFsL3Op0GXOeLJ9A5bsOn8th7x6JucNKuaRB6bQbSPABEBAAG0 +JFRlc3QgTWNUZXN0aW5ndG9uIDx0ZXN0QGV4YW1wbGUuY29tPoi5BBMBAgAjBQJS +YS9OAhsvBwsJCAcDAgEGFQgCCQoLBBYCAwECHgECF4AACgkQSmNhOk1uQJQwDAP6 +AgrTyqkRlJVqz2pb46TfbDM2TDF7o9CBnBzIGoxBhlRwpqALz7z2kxBDmwpQa+ki +Bq3jZN/UosY9y8bhwMAlnrDY9jP1gdCo+H0sD48CdXybblNwaYpwqC8VSpDdTndf +9j2wE/weihGp/DAdy/2kyBCaiOY1sjhUfJ1GogF49rC4jQRSYS9OAQQA6R/PtBFa +JaT4jq10yqASk4sqwVMsc6HcifM5lSdxzExFP74naUMMyEsKHP53QxTF0Grqusag +Qg/ZtgT0CN1HUM152y7ACOdp1giKjpMzOTQClqCoclyvWOFB+L/SwGEIJf7LSCEr +woBuJifJc8xAVr0XX0JthoW+uP91eTQ3XpsAEQEAAYkBPQQYAQIACQUCUmEvTgIb +LgCoCRBKY2E6TW5AlJ0gBBkBAgAGBQJSYS9OAAoJEOCE90RsICyXuqIEANmmiRCA +SF7YK7PvFkieJNwzeK0V3F2lGX+uu6Y3Q/Zxdtwc4xR+me/CSBmsURyXTO29OWhP +GLszPH9zSJU9BdDi6v0yNprmFPX/1Ng0Abn/sCkwetvjxC1YIvTLFwtUL/7v6NS2 +bZpsUxRTg9+cSrMWWSNjiY9qUKajm1tuzPDZXAUEAMNmAN3xXN/Kjyvj2OK2ck0X +W748sl/tc3qiKPMJ+0AkMF7Pjhmh9nxqE9+QCEl7qinFqqBLjuzgUhBU4QlwX1GD +AtNTq6ihLMD5v1d82ZC7tNatdlDMGWnIdvEMCv2GZcuIqDQ9rXWs49e7tq1NncLY +hz3tYjKhoFTKEIq3y3Pp +=h/aX +-----END PGP PUBLIC KEY BLOCK-----` + +const multiSignMessageKey = `-----BEGIN PGP PRIVATE KEY BLOCK----- +Comment: Bob's OpenPGP Transferable Secret Key + +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 +=miES +-----END PGP PRIVATE KEY BLOCK-----` + +const multiSignMessage = `-----BEGIN PGP MESSAGE----- + +wcDMA3wvqk35PDeyAQv/cAVNFU5mNqBCCiF7gK8satSxOfOkG0LXRiHYsdtl8tKY +l1uPceZgLEq34IML+5xx9Rf1oUj+b3hiuhPbYjHU1eka9wMeSAuD/xOA7btYf33g +wFNVI/V+H73UVPLATRFhyJHIzPpefs181P+x111sU4n7eA4gGHAR7SYnp+ffVPnY +5Fvl6wIUT7hLuePdGYkfAD8CnhlmCai+TJofE70fHEB0eC3Rij/gDygeVG2YWotu +arJ3vK6BwZXuQ2dQ1z5XMs6+bTpLkW8VN6ELy+yUNm10R6v98Mfz4xEwPgHdAkNG +gNHT8XblADCovY4dGHAewnZO13OiiVlNhNS+cnvtA1MoIUKzx020T5JNHcR3iUi2 +iu9efDYli762wdX0H/dkFmAzddOOoKJMywpKNi5n6U08Lt0/3Fe6FTurEMm14O2k +3oR6l2BG9c4nnYrPHgONCVII9d1h4tgsXKJKNNCnT/sQYxA+pOvEl4KhhCqLtpNm +IX0+KbtTVPyWCJUV5OUL0sOZATtvadf1Sm2MOxUuXtF0wx73YgUhqZGPJtkpG8A8 +olGi+OmzC2uYjoxhXGJrqVScmXhPxOeYWqUzQP9vygIp1Kt9MFwhItT2CI4FGpSP +vQ8C6ha5zcZLXtjBKhFILeuCfKseAbxIYechYtw7Ds98Q1Jn878hnD0mVdarz7ZO +w5G/mdXvxnni96trihe9soosLyEzyIxsaJ9MWLoe+k9tylzQHezFFZg7rWtmt6qX +KHwJjeU37mj++JN7KHxkIyZkVlbUntizAO/ZL0RfQjmi9bguod+suhHuY77Q/X9+ +Wuip3Rerv7UhKjsjn+xkoLSADE5RUfsC8CTdI3zPZvN2J4DYEbVm4lZlU8V9V2lJ +MnC9ak8w/bBUPlkKvMP6ECxSSQ31qHpB9GQIlbwhzD6NRnTSYMHkCgthr2EABo63 +BpfrUNeF1exYDf/DBE1V9UHDaQ/RUcdwxa9M1KO78c1uiPBuE/G3tGa9Y1yg1/nv +odQhsH7S9vuYu1Iu0b7Z3otJ1txa5ubTZGGSbBOuyLKMLTVpjLq7RKzfKovoI+4q +/jNSi3nom6477UXqAqK3CwlCbAC4v0nr3lUMOE9Zadd6i6CqFyvyuL9ASUZjvtnQ +qNNOIipPZOU6G1XJyYAAHsFvkTu25cNFdRSp0J5Lef04T4eXD58WW+8WaBuBKbS3 +YmKMYviBoE3MHbllBZmtVLXfAin9OV0M9CwhPSWe6xaVwNkcexB8QjkhcSXRFZS3 +1jkx364ZmPPaffgjz/PU8xfG8RUoU96O3m6n1vQnGaDchsKjHHvHLxrD8EPeiWjc +MLvKJ21IQfOTksBhbukItyqlIYcgbp91qC1lz2BM+6ldfvmHSgcmtuUvfpvZgh3o +c+DdlIBzhugLAIwSe9ihMhrmS7SrBG3Y7AbADoRaz5SfrsEBjGGbL3s9SW4SDfCU +LvD9xYyRJGlXTEC98ZbtzFAyq08OlEMUp2N2MCq6RNQKL0BW6AFEr+JX+SgQT6Xe +IdtCVbbajz+bt6I9bQv+7feR6iv2llo1A8GtF2NO3XCKeLFnhV0QzG8Aols+NgUd +EvMtY/qtCJihfsRI86C3/sQc7rBeQQiE/95quwQANyf1Zlh7koviNXNN1qegbMc5 +lgFGXHa0YO9Em4CU7bLcgIkVKxb3Ybz7iBwL90O5Z1KG47BBc/z+cxqSl3DvVZpM +DKryeSyRtdDxFrYDuF55g4oqSn3akLnYF1xmr/eViS0eDeUA/TNxgYyFdXXzZCtQ +EUXecqvAi49AW5ikQP+v0I8D/tqJRBtX5T1G6moueSkzZn+bLV3McroQTmaJ8UGb +Ny+aqLUpesyOo4drRl1E9Ibs5D0zvvBuRQd03o6s0KvZac6nZ/v7jXf/JtiBfL2t +xP9SiQFMf/Fnji/JEw4TNBkVVmrNijo6ohz11IbVaKkUKzjpGN2zuo6ibc0QZGP1 +xXuU4F38CnE/rCwvrXuKCfUeX3hUDl4TtRIg +=Du+Y +-----END PGP MESSAGE-----` + +const malformedLiteralSignature = `-----BEGIN PGP MESSAGE----- +Comment: Plaintext is "Hello World :)" +Comment: Encrypted using AES-256 +Comment: Session key: 4875CCE42B4BFCD2748EA4E1FD53E71B3C01ACC231CB5DDE6B33858B4DDE0788 + +wcDMA3wvqk35PDeyAQwA0GJCAQbgtF4YKAqNaHCAhkFtyvS0b6BFfPP5MT8uB7PA +MKgcl8kkYMyY0JO49HwPG/0jTWdfYrpP7hSwH1Ofq9HqAxJM4as138KAHi6TKwzg +Brn4KonZysttYUEhyqfkCsWzAhJfWKYaO2IG1W9K7nxvL3WAFjB70/SX3nJqitnw +GbeBhRJerlq9f4QsIfz9eiQClYn5Cy1VWORO4BpgvQpKMaY6mBGPcycynycQlnhX +vKS5I04zOepTGdUko7koo8ebh7TrtcYXOjFRy+VJvloU3VYoGOjZ9LrQcHbt7mQi +HOmMHxdXH0b3WwGq3XrDYErT42RQGxA1iVyb6gZpWg7aNByYsQYfymdEl18stULb +26+mdrma0x3WMQ2YHrgJcLAv+qou4iE3Y3c8CpP+qDVJiSid1BhDvcOXP3W74GQ5 +PVtx1VWCxYMBaSbGXLFMXF4bD24UQiw2maq1pEWOjdIZB7B3jxY0Pd3nZWB9zraS +8LeRG+eXmQHXf7lyC3GX0sF9ASHRiWi+o7ptTb47rTTp7fQDbS07oRyceuQKakTW +cf+//2s3FAiWK3eUQclGSuXCd+tXPGbHnNtBxkEfV5cMeSc9E1pCYJKFbhw6w2Lc +/wJEoPMUihe/gD7tlir9NUgt4IoS3WZ36lQ8/kpMWBBWhM9Fa/3X6n/M+2cU41eE +7BqXhyWlv5WDbgWhOtBAkpziWoBKrzvZUy8pnpGtpxo3r1hntjerYck7OC1FtlRI +uUOZRObvb0F1YvdpQu+DWHMKHz9JgZf0M37hnMJWgU63+NZGTdZlnOfiQPwB6z0e +y5RkaeuL6EASrT75qrjo545ahwu8UKJnS0MiU9sceN8T0YaKHI2tV2ujWL7B8PEP +ik4JX1ieB0mb1RKRPmuG/b5TPmFCxS3cxr8MXfEbuSrfk0rmi4NGhuAnXN2FeXQr +kc+qLfMA4RPUAPez3mBIrggB/FCu4b+LoQlVy5XJSrtqJZdzDZ31K65xdFkPTto3 +CtYAqAtqb0nbOknK5YgmkdOHbECWA1umrrwyU8d5gYfweccycpaKVl2q3atNipRC +okOBWGJ+5mvim7K3fHmDaClMi3X2cnUIG1c+SEM1MAUHJpbCVLRH6PETYNh4iMx7 +WwVgUNP+BbQChmoTgGESm23MquSle3RclvSfXDIzcH2pwHc9gafK8Eau4A/U7sFH +s/ikCGhW85qxUxdWdSvJ3KJ5Clxz62vLWrBi1Z0qdefZhDmVzPv+9UAb2+drLsyz +WxvpR7aTncLHHiJel8AT +=B+9z +-----END PGP MESSAGE----- +` + +const malformedOPSLiteral = `-----BEGIN PGP MESSAGE----- + +wcDMA3wvqk35PDeyAQwAuwIwz2aT6DILqIsAZhlPdmg4MPagOMB3vSpp16cZx2Fu +UMoM8Mw2Iv6JLUg9MGZ/ngN6Fciq+F1+D+8iycznah/OJ8cL7YhSFbGCM4PtcumY +/xMafd7VH89LW+Z0lBmPl82HIYACFseUG/T+xPYgEYyQ+5LL1EGNf5EWYzmiwrUy +BV3cnpiAhTHwl28RttoFCyHlFi3t37rUu12oUvbamPd0Hr3PPy1M48RZFaN4YlzS +1MS2vchFOwaCWrdobX2/b/F/S4q5KLh+T/nIjOakEKWX6LaDwNf6UmQjlr3nO1jO +BVzY5ki8qgUUbEZXklYx+Z5UariZLXyS5OqoNkjbmT8uUlWzk2M3nlBalVDCJOCb +mV9I2XjLxY+2m39LOCHP2KtkKSLqFAEYnl7zI9/Jlh0NwqMfxjH7CKphItTXan3D +CfYBn5fhZKuLDDSJy2FDgE4u3yEixUP484PY9nJkVEzelszvfKh+2mEG9AyCGlul +XfAoNVmFSeOnzWwofFC10k4BngbmDnzcsGj0lNK/HVGjNc6I5eUxvxE3ctpfTVxu +xsrcHkZ/WTIUtgTVOmFs4voLGHF4X24Dba4Ky16FWj+OVI+0HAY/dxF5hgZGTiU= +=GGy+ +-----END PGP MESSAGE----- + +` + +const malformedTwoLiteralsCompressed = `-----BEGIN PGP MESSAGE----- + +wcDMA3wvqk35PDeyAQwAg3lhuA+osFEHny7ymoJxb9uM0SMysKPHB3aUkpFYWeWx +5F8qsk6dnK2yJ1KK4R2MGHustgusMPmZ8xzyDDQ45+PiCrDXDLemikg4eR/Ypi4g +mrExbL4dNleGgyNNjoLLbXQsK/invXe4gxio238LVR2sEmHUSZiuCrZMKhy8ZVX3 +2bmMBhLWeCOjUkbIb0d6vkRRQay2v+pmH9lJm4Exm9ZIHQKJEV8TgPd0HI4RI1T7 +RdPIqzts8gwEloItBEymRwP3nwx9d4wAZg6S75fQo3Zy4qnLIGqo/LLjIlzyKmed +wNaOoJfNbTyL5Hqt2isqd2wMWv7h638BACDrbBZF6PXpzZpGZ+Ay0CUdcq3gM00A +52hHNqIkhcHZDInRB+aHrfCxYYpNsvou08rcJrGPhOue6hXz2NCb/aGMueIVF6/G +OGBa5/qtsWe6cz/iUMOi9xG7XoCXfwPs/6h74NmzwsDwG+OCZpw0WfJET6liEpJM +yJf5tj3ur3Eg0FCgd20x0lcBVhzPjawsmdCxGvUNW8379EqcM4ZYB18djDh5MO/6 +u652ZZclXDlpXHpoRDH+fkTwKO5gkukOXyRAlqpctTq0kOMYD5Dc6ByhU9TMWEpK +kKBxYwHkxJo= +=GV4u +-----END PGP MESSAGE----- +` + +const malformedPKESKLiteral = `-----BEGIN PGP MESSAGE----- + +wcDMA3wvqk35PDeyAQv/Wt7MVNdahLe8iT/kE4REc7YAE8+SSk+0l4aMWxwsuu2X +pQm+y8YNNW8QqgkSDBK2xXuYGixCH6qTa+1gtc1kN5+dnT93zJLfGXjvSucfKcau +YRNlwe6B4oQ9GHqNEQHalXJhxlxgrlrA8esrBtKNWVfR5dzB7MxbWdxwOi6pvAft ++pt6fz4+5AGffGTXpKNqpjnil+XA2JYEnhry/GKDPONmK1mk4dWMfUcLac77w+Vm +wIfviqDyv6urJy1FK8Eg/FzOL9hosPZxDTdHeF+DM2HwxQWtftRs4nmGFkYyT9Yx +N+T6Dz1sVYMtHNa0tQLJ4VTzgPY7cc34ShMIi0FDUYa83KqgBufdoH4UlqGkljTe +bT1I5txQQpO7wsrx0pxnPqRjlw0dabjyF2oxWSmMVGA76Htd/hW5PQ56g/smpwf8 +DLFATxdt8boGUMwDC0/PftH+c3FfYjGj24uesC14BS+S8HW4+uLLAoQixraRpJ9v +UGrrBw2oDHaFnJIPQM240sEOAUVlV7k/hCnyDlk4tBHDcXUoekojLbncW2x+lY26 +/VX6+gka96sWyNgVzFaoGf6OYVhtpvXEhECXKYP5sf3a4Gn+bQSZgKr2tf9N9l7s +L3akqkorBWPHWnsnrV/Iuiv7SyALLVPjAJcGLfCx4/MClDOA4XZfz4pEzW6fyzKg +L1yknBIpx9vUSQ4Sb1dptuvd79pUXfgK+xfNentShz34lge49vS3+pZ1WAbvf7cg +wZRnHRxCTO2qOACEKoz+lG2RFfzp+Tf4qbEaCsBFvjT7GCjLyZZbaUqfuB4mSpid +KUwS4jWFDrtGSE1CTDyp4QL5HDY6SNegY5iy2dud1RJrCvWgJtGWBdqOLnnvvH9t +aJZn7rW+8VReLNP4XuAHVbVNEIXMPVZ/A1noZHSzEVSwIhuzH7ZrAJt6ca/DJoID +K+6+WlNsPHg9ItcM1vMKTqwEvw8f7t2khQbLJRNAESp9Bh8Ruphb80txanI7PZCS +niGt565u5focQLyRCuCc2m5eg34dpCVnXGCI3XSMAg2rq9iIxBrp0jj+jpitO+Bq +Li+3mrVJktp74N+ocjr54FZMd4rWxev317MUi9UB6LeDLRhrvRsucAmypLpkkGuU +=9ciZ +-----END PGP MESSAGE----- +` + +const malformedKeyTest = `-----BEGIN PGP PRIVATE KEY BLOCK----- +Comment: Bob's OpenPGP Transferable Secret Key + +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 +=miES +-----END PGP PRIVATE KEY BLOCK-----` diff --git a/openpgp/v2/subkeys.go b/openpgp/v2/subkeys.go new file mode 100644 index 000000000..eae757292 --- /dev/null +++ b/openpgp/v2/subkeys.go @@ -0,0 +1,204 @@ +package v2 + +import ( + "io" + "time" + + "github.com/ProtonMail/go-crypto/openpgp/errors" + "github.com/ProtonMail/go-crypto/openpgp/packet" +) + +// A Subkey is an additional public key in an Entity. Subkeys can be used for +// encryption. +type Subkey struct { + Primary *Entity + PublicKey *packet.PublicKey + PrivateKey *packet.PrivateKey + Bindings []*packet.VerifiableSignature + Revocations []*packet.VerifiableSignature +} + +func readSubkey(primary *Entity, packets *packet.Reader, pub *packet.PublicKey, priv *packet.PrivateKey) error { + subKey := Subkey{ + PublicKey: pub, + PrivateKey: priv, + Primary: primary, + } + + for { + p, err := packets.Next() + if err == io.EOF { + break + } else if err != nil { + return errors.StructuralError("subkey signature invalid: " + err.Error()) + } + + sig, ok := p.(*packet.Signature) + if !ok { + packets.Unread(p) + break + } + + if sig.SigType != packet.SigTypeSubkeyBinding && sig.SigType != packet.SigTypeSubkeyRevocation { + // Ignore signatures with wrong type + continue + } + switch sig.SigType { + case packet.SigTypeSubkeyRevocation: + subKey.Revocations = append(subKey.Revocations, packet.NewVerifiableSig(sig)) + case packet.SigTypeSubkeyBinding: + subKey.Bindings = append(subKey.Bindings, packet.NewVerifiableSig(sig)) + } + } + primary.Subkeys = append(primary.Subkeys, subKey) + return nil +} + +func (s *Subkey) Serialize(w io.Writer, includeSecrets bool) error { + if includeSecrets { + if err := s.PrivateKey.Serialize(w); err != nil { + return err + } + } else { + if err := s.PublicKey.Serialize(w); err != nil { + return err + } + } + for _, revocation := range s.Revocations { + if err := revocation.Packet.Serialize(w); err != nil { + return err + } + } + for _, bindingSig := range s.Bindings { + if err := bindingSig.Packet.Serialize(w); err != nil { + return err + } + } + return nil +} + +func (s *Subkey) ReSign(config *packet.Config) error { + selectedSig, err := s.LatestValidBindingSignature(time.Time{}) + if err != nil { + return err + } + err = selectedSig.SignKey(s.PublicKey, s.Primary.PrivateKey, config) + if err != nil { + return err + } + if selectedSig.EmbeddedSignature != nil { + err = selectedSig.EmbeddedSignature.CrossSignKey(s.PublicKey, s.Primary.PrimaryKey, + s.PrivateKey, config) + if err != nil { + return err + } + } + return nil +} + +// Verify checks if the subkey is valid by checking: +// - that the key is not revoked +// - that there is valid non-expired binding self-signature +// - that the subkey is not expired +// If date is zero (i.e., date.IsZero() == true) the time checks are not performed. +func (s *Subkey) Verify(date time.Time) (selfSig *packet.Signature, err error) { + selfSig, err = s.LatestValidBindingSignature(date) + if err != nil { + return nil, err + } + if s.Revoked(selfSig, date) { + return nil, errors.ErrKeyRevoked + } + if !date.IsZero() && s.Expired(selfSig, date) { + return nil, errors.ErrKeyExpired + } + return +} + +// Expired checks if given the selected self-signature if the subkey is expired. +func (s *Subkey) Expired(selectedSig *packet.Signature, date time.Time) bool { + return s.PublicKey.KeyExpired(selectedSig, date) || selectedSig.SigExpired(date) +} + +// Revoked returns whether the subkey has been revoked by a self-signature. +// Note that third-party revocation signatures are not supported. +func (s *Subkey) Revoked(selfCertification *packet.Signature, date time.Time) bool { + // Verify revocations first + for _, revocation := range s.Revocations { + if revocation.Valid == nil { + err := s.Primary.PrimaryKey.VerifySubkeyRevocationSignature(revocation.Packet, s.PublicKey) + valid := err == nil + revocation.Valid = &valid + } + if *revocation.Valid && + (revocation.Packet.RevocationReason == nil || + *revocation.Packet.RevocationReason == packet.Unknown || + *revocation.Packet.RevocationReason == packet.NoReason || + *revocation.Packet.RevocationReason == packet.KeyCompromised) { + // If the key is compromised, the key is considered revoked even before the revocation date. + return true + } + if *revocation.Valid && (date.IsZero() || + !revocation.Packet.SigExpired(date) && + (selfCertification == nil || + selfCertification.CreationTime.Unix() <= revocation.Packet.CreationTime.Unix())) { + return true + } + } + return false +} + +// Revoke generates a subkey revocation signature (packet.SigTypeSubkeyRevocation) for +// a subkey with the specified reason code and text (RFC4880 section-5.2.3.23). +// If config is nil, sensible defaults will be used. +func (s *Subkey) Revoke(reason packet.ReasonForRevocation, reasonText string, config *packet.Config) error { + // Check that the subkey is valid (not considering expiration) + if _, err := s.Verify(time.Time{}); err != nil { + return err + } + + revSig := createSignaturePacket(s.Primary.PrimaryKey, packet.SigTypeSubkeyRevocation, config) + revSig.RevocationReason = &reason + revSig.RevocationReasonText = reasonText + + if err := revSig.RevokeSubkey(s.PublicKey, s.Primary.PrivateKey, config); err != nil { + return err + } + sig := packet.NewVerifiableSig(revSig) + valid := true + sig.Valid = &valid + s.Revocations = append(s.Revocations, sig) + return nil +} + +// LatestValidBindingSignature returns the latest valid self-signature of this subkey +// that is not newer than the provided date. +// Does not consider signatures/embedded signatures that are expired. +// If date is zero (i.e., date.IsZero() == true) the expiration checks are not performed. +// Returns a StructuralError if no valid self-signature is found. +func (s *Subkey) LatestValidBindingSignature(date time.Time) (selectedSig *packet.Signature, err error) { + for sigIdx := len(s.Bindings) - 1; sigIdx >= 0; sigIdx-- { + sig := s.Bindings[sigIdx] + if (date.IsZero() || date.Unix() >= sig.Packet.CreationTime.Unix()) && + (selectedSig == nil || selectedSig.CreationTime.Unix() < sig.Packet.CreationTime.Unix()) { + if sig.Valid == nil { + err := s.Primary.PrimaryKey.VerifyKeySignature(s.PublicKey, sig.Packet) + valid := err == nil + sig.Valid = &valid + } + mainSigExpired := !date.IsZero() && + sig.Packet.SigExpired(date) + embeddedSigExpired := !date.IsZero() && + sig.Packet.FlagSign && + sig.Packet.EmbeddedSignature != nil && + sig.Packet.EmbeddedSignature.SigExpired(date) + if *sig.Valid && !mainSigExpired && !embeddedSigExpired { + selectedSig = sig.Packet + } + } + } + if selectedSig == nil { + return nil, errors.StructuralError("no valid binding signature found for subkey") + } + return +} diff --git a/openpgp/v2/user.go b/openpgp/v2/user.go new file mode 100644 index 000000000..2b85f2b1f --- /dev/null +++ b/openpgp/v2/user.go @@ -0,0 +1,215 @@ +package v2 + +import ( + "io" + "time" + + "github.com/ProtonMail/go-crypto/openpgp/errors" + "github.com/ProtonMail/go-crypto/openpgp/packet" +) + +// An Identity represents an identity claimed by an Entity and zero or more +// assertions by other entities about that claim. +type Identity struct { + Primary *Entity + Name string // by convention, has the form "Full Name (comment) " + UserId *packet.UserId + SelfCertifications []*packet.VerifiableSignature + OtherCertifications []*packet.VerifiableSignature + Revocations []*packet.VerifiableSignature +} + +func readUser(e *Entity, packets *packet.Reader, pkt *packet.UserId) error { + identity := Identity{ + Primary: e, + Name: pkt.Id, + UserId: pkt, + } + for { + p, err := packets.NextWithUnsupported() + if err == io.EOF { + break + } else if err != nil { + return err + } + + unsupportedPacket, unsupported := p.(*packet.UnsupportedPacket) + sigCandidate := p + if unsupported { + sigCandidate = unsupportedPacket.IncompletePacket + } + sig, ok := sigCandidate.(*packet.Signature) + if !ok { + // sigCandidate is a not a signature packet, reset and stop. + packets.Unread(p) + break + } else if unsupported { + // sigCandidate is a signature packet but unsupported. + continue + } + + if sig.SigType != packet.SigTypeGenericCert && + sig.SigType != packet.SigTypePersonaCert && + sig.SigType != packet.SigTypeCasualCert && + sig.SigType != packet.SigTypePositiveCert && + sig.SigType != packet.SigTypeCertificationRevocation { + // Ignore signatures with wrong type + continue + } + + if sig.CheckKeyIdOrFingerprint(e.PrimaryKey) { + if sig.SigType == packet.SigTypeCertificationRevocation { + identity.Revocations = append(identity.Revocations, packet.NewVerifiableSig(sig)) + } else { + identity.SelfCertifications = append(identity.SelfCertifications, packet.NewVerifiableSig(sig)) + } + e.Identities[pkt.Id] = &identity + } else { + identity.OtherCertifications = append(identity.OtherCertifications, packet.NewVerifiableSig(sig)) + } + } + return nil +} + +func (i *Identity) Serialize(w io.Writer) error { + if err := i.UserId.Serialize(w); err != nil { + return err + } + for _, sig := range i.Revocations { + if err := sig.Packet.Serialize(w); err != nil { + return err + } + } + for _, sig := range i.SelfCertifications { + if err := sig.Packet.Serialize(w); err != nil { + return err + } + } + for _, sig := range i.OtherCertifications { + if err := sig.Packet.Serialize(w); err != nil { + return err + } + } + return nil +} + +// Verify checks if the user-id is valid by checking: +// - that a valid self-certification exists and is not expired +// - that user-id has not been revoked at the given point in time +// If date is zero (i.e., date.IsZero() == true) the time checks are not performed. +func (i *Identity) Verify(date time.Time) (selfSignature *packet.Signature, err error) { + if selfSignature, err = i.LatestValidSelfCertification(date); err != nil { + return + } + if i.Revoked(selfSignature, date) { + return nil, errors.StructuralError("user-id is revoked") + } + return +} + +// Revoked returns whether the identity has been revoked by a self-signature. +// Note that third-party revocation signatures are not supported. +func (i *Identity) Revoked(selfCertification *packet.Signature, date time.Time) bool { + // Verify revocations first + for _, revocation := range i.Revocations { + if selfCertification == nil || // if there is not selfCertification verify revocation + selfCertification.IssuerKeyId == nil || + revocation.Packet.IssuerKeyId == nil || + (*selfCertification.IssuerKeyId == *revocation.Packet.IssuerKeyId) { // check matching key id + if revocation.Valid == nil { + // Verify revocation signature (not verified yet). + err := i.Primary.PrimaryKey.VerifyUserIdSignature(i.Name, i.Primary.PrimaryKey, revocation.Packet) + valid := err == nil + revocation.Valid = &valid + } + + if *revocation.Valid && + (date.IsZero() || // Check revocation not expired + !revocation.Packet.SigExpired(date)) && + (selfCertification == nil || // Check that revocation is not older than the selfCertification + selfCertification.CreationTime.Unix() <= revocation.Packet.CreationTime.Unix()) { + return true + } + } + } + return false +} + +func (i *Identity) ReSign(config *packet.Config) error { + selectedSig, err := i.LatestValidSelfCertification(config.Now()) + if err != nil { + return err + } + if err = selectedSig.SignUserId( + i.UserId.Id, + i.Primary.PrimaryKey, + i.Primary.PrivateKey, + config, + ); err != nil { + return err + } + return nil +} + +// SignIdentity adds a signature to e, from signer, attesting that identity is +// associated with e. The provided identity must already be an element of +// e.Identities and the private key of signer must have been decrypted if +// necessary. +// If config is nil, sensible defaults will be used. +func (ident *Identity) SignIdentity(signer *Entity, config *packet.Config) error { + certificationKey, ok := signer.CertificationKey(config.Now(), config) + if !ok { + return errors.InvalidArgumentError("no valid certification key found") + } + + if certificationKey.PrivateKey.Encrypted { + return errors.InvalidArgumentError("signing Entity's private key must be decrypted") + } + + if !ok { + return errors.InvalidArgumentError("given identity string not found in Entity") + } + + sig := createSignaturePacket(certificationKey.PublicKey, packet.SigTypeGenericCert, config) + + signingUserID := config.SigningUserId() + if signingUserID != "" { + if _, ok := signer.Identities[signingUserID]; !ok { + return errors.InvalidArgumentError("signer identity string not found in signer Entity") + } + sig.SignerUserId = &signingUserID + } + + if err := sig.SignUserId(ident.Name, ident.Primary.PrimaryKey, certificationKey.PrivateKey, config); err != nil { + return err + } + ident.OtherCertifications = append(ident.OtherCertifications, packet.NewVerifiableSig(sig)) + return nil +} + +// LatestValidSelfCertification returns the latest valid self-signature of this user-id +// that is not newer than the provided date. +// Does not consider signatures that are expired. +// If date is zero (i.e., date.IsZero() == true) the expiration checks are not performed. +// Returns a StructuralError if no valid self-certification is found. +func (i *Identity) LatestValidSelfCertification(date time.Time) (selectedSig *packet.Signature, err error) { + for sigIdx := len(i.SelfCertifications) - 1; sigIdx >= 0; sigIdx-- { + sig := i.SelfCertifications[sigIdx] + if (date.IsZero() || date.Unix() >= sig.Packet.CreationTime.Unix()) && // SelfCertification must be older than date + (selectedSig == nil || selectedSig.CreationTime.Unix() < sig.Packet.CreationTime.Unix()) { // Newer ones are preferred + if sig.Valid == nil { + // Verify revocation signature (not verified yet). + err = i.Primary.PrimaryKey.VerifyUserIdSignature(i.Name, i.Primary.PrimaryKey, sig.Packet) + valid := err == nil + sig.Valid = &valid + } + if *sig.Valid && (date.IsZero() || !sig.Packet.SigExpired(date)) { + selectedSig = sig.Packet + } + } + } + if selectedSig == nil { + return nil, errors.StructuralError("no valid certification signature found for identity") + } + return +} diff --git a/openpgp/v2/write.go b/openpgp/v2/write.go new file mode 100644 index 000000000..b21c6061d --- /dev/null +++ b/openpgp/v2/write.go @@ -0,0 +1,866 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package v2 + +import ( + "crypto" + "hash" + "io" + "strconv" + "time" + + "github.com/ProtonMail/go-crypto/openpgp/armor" + "github.com/ProtonMail/go-crypto/openpgp/errors" + "github.com/ProtonMail/go-crypto/openpgp/internal/algorithm" + "github.com/ProtonMail/go-crypto/openpgp/packet" +) + +// DetachSign signs message with the private key from signer (which must +// already have been decrypted) and writes the signature to w. +// If config is nil, sensible defaults will be used. +func DetachSign(w io.Writer, signers []*Entity, message io.Reader, config *packet.Config) error { + return detachSign(w, signers, message, packet.SigTypeBinary, config) +} + +// ArmoredDetachSign signs message with the private key from signer (which +// must already have been decrypted) and writes an armored signature to w. +// If config is nil, sensible defaults will be used. +func ArmoredDetachSign(w io.Writer, signers []*Entity, message io.Reader, config *packet.Config) (err error) { + return armoredDetachSign(w, signers, message, packet.SigTypeBinary, config) +} + +// DetachSignText signs message (after canonicalising the line endings) with +// the private key from signer (which must already have been decrypted) and +// writes the signature to w. +// If config is nil, sensible defaults will be used. +func DetachSignText(w io.Writer, signers []*Entity, message io.Reader, config *packet.Config) error { + return detachSign(w, signers, message, packet.SigTypeText, config) +} + +// ArmoredDetachSignText signs message (after canonicalising the line endings) +// with the private key from signer (which must already have been decrypted) +// and writes an armored signature to w. +// If config is nil, sensible defaults will be used. +func ArmoredDetachSignText(w io.Writer, signers []*Entity, message io.Reader, config *packet.Config) error { + return armoredDetachSign(w, signers, message, packet.SigTypeText, config) +} + +// DetachSignWriter signs a message with the private key from a signer (which must +// already have been decrypted) and writes the signature to w. +// DetachSignWriter returns a WriteCloser to which the message can be written to. +// The resulting WriteCloser must be closed after the contents of the message have +// been written. If utf8Message is set to true, the line endings of the message are +// canonicalised and the type of the signature will be SigTypeText. +// If config is nil, sensible defaults will be used. +func DetachSignWriter(w io.Writer, signers []*Entity, utf8Message bool, config *packet.Config) (io.WriteCloser, error) { + sigType := packet.SigTypeBinary + if utf8Message { + sigType = packet.SigTypeText + } + return detachSignWithWriter(w, signers, sigType, config) +} + +func armoredDetachSign(w io.Writer, signers []*Entity, message io.Reader, sigType packet.SignatureType, config *packet.Config) (err error) { + out, err := armor.Encode(w, SignatureType, nil) + if err != nil { + return + } + err = detachSign(out, signers, message, sigType, config) + if err != nil { + return + } + return out.Close() +} + +func detachSign(w io.Writer, signers []*Entity, message io.Reader, sigType packet.SignatureType, config *packet.Config) (err error) { + ptWriter, err := detachSignWithWriter(w, signers, sigType, config) + if err != nil { + return + } + _, err = io.Copy(ptWriter, message) + if err != nil { + return + } + return ptWriter.Close() +} + +type detachSignWriter struct { + signatureWriter io.Writer + signatures []*detachSignContext + config *packet.Config +} + +type detachSignContext struct { + wrappedHash hash.Hash + h hash.Hash + signer *packet.PrivateKey + sig *packet.Signature +} + +func (s detachSignWriter) Write(data []byte) (int, error) { + for _, signature := range s.signatures { + if _, err := signature.wrappedHash.Write(data); err != nil { + return 0, err + } + } + return len(data), nil +} + +func (s detachSignWriter) Close() error { + for _, signature := range s.signatures { + err := signature.sig.Sign(signature.h, signature.signer, s.config) + if err != nil { + return err + } + err = signature.sig.Serialize(s.signatureWriter) + if err != nil { + return err + } + } + return nil +} + +func detachSignWithWriter(w io.Writer, signers []*Entity, sigType packet.SignatureType, config *packet.Config) (ptWriter io.WriteCloser, err error) { + var detachSignContexts []*detachSignContext + for _, signer := range signers { + signingKey, ok := signer.SigningKeyById(config.Now(), config.SigningKey(), config) + if !ok { + return nil, errors.InvalidArgumentError("no valid signing keys") + } + if signingKey.PrivateKey == nil { + return nil, errors.InvalidArgumentError("signing key doesn't have a private key") + } + if signingKey.PrivateKey.Encrypted { + return nil, errors.InvalidArgumentError("signing key is encrypted") + } + candidateHashes := []uint8{ + hashToHashId(crypto.SHA256), + hashToHashId(crypto.SHA384), + hashToHashId(crypto.SHA512), + hashToHashId(crypto.SHA3_256), + hashToHashId(crypto.SHA3_512), + } + defaultHashes := candidateHashes[0:1] + primarySelfSignature, _ := signer.PrimarySelfSignature(config.Now()) + if primarySelfSignature == nil { + return nil, errors.InvalidArgumentError("signed entity has no valid self-signature") + } + preferredHashes := primarySelfSignature.PreferredHash + if len(preferredHashes) == 0 { + preferredHashes = defaultHashes + } + candidateHashes = intersectPreferences(candidateHashes, preferredHashes) + + var hash crypto.Hash + if hash, err = selectHash(candidateHashes, config.Hash()); err != nil { + return + } + + detachSignCtx := detachSignContext{ + signer: signingKey.PrivateKey, + } + + detachSignCtx.sig = createSignaturePacket(signingKey.PublicKey, sigType, config) + detachSignCtx.sig.Hash = hash + + detachSignCtx.h, err = detachSignCtx.sig.PrepareSign(config) + if err != nil { + return + } + detachSignCtx.wrappedHash, err = wrapHashForSignature(detachSignCtx.h, sigType) + if err != nil { + return + } + detachSignContexts = append(detachSignContexts, &detachSignCtx) + } + + return &detachSignWriter{ + signatureWriter: w, + signatures: detachSignContexts, + config: config, + }, nil +} + +// FileHints contains metadata about encrypted files. This metadata is, itself, +// encrypted. +type FileHints struct { + // IsUTF8 can be set to hint that the contents are utf8 encoded data + IsUTF8 bool + // FileName hints at the name of the file that should be written. It's + // truncated to 255 bytes if longer. It may be empty to suggest that the + // file should not be written to disk. It may be equal to "_CONSOLE" to + // suggest the data should not be written to disk. + FileName string + // ModTime contains the modification time of the file, or the zero time if not applicable. + ModTime time.Time +} + +type EncryptParams struct { + // KeyWriter is a Writer to which the encrypted + // session keys are written to. + // If nil, DataWriter is used instead. + KeyWriter io.Writer + // Hints contains file metadata for the literal data packet. + // If nil, default is used. + Hints *FileHints + // SiningEntities contains the private keys to produce signatures with + // If nil, no signatures are created + Signers []*Entity + // TextSig indicates if signatures of type SigTypeText should be produced + TextSig bool + // SessionKey provides a session key to be used for encryption. + // If nil, a one-time session key is generated + SessionKey []byte + // Config provides the config to be used. + // If Config is nil, sensible defaults will be used. + Config *packet.Config +} + +// SymmetricallyEncrypt acts like gpg -c: it encrypts a file with a passphrase. +// The resulting WriteCloser must be closed after the contents of the file have +// been written. +// If config is nil, sensible defaults will be used. +func SymmetricallyEncrypt(ciphertext io.Writer, passphrase []byte, hints *FileHints, config *packet.Config) (plaintext io.WriteCloser, err error) { + return SymmetricallyEncryptWithParams(passphrase, ciphertext, &EncryptParams{ + Hints: hints, + Config: config, + }) +} + +// SymmetricallyEncryptWithParams acts like SymmetricallyEncrypt: but provides more configuration options +// EncryptParams provides the optional parameters. +// The resulting WriteCloser must be closed after the contents of the file have been written. +func SymmetricallyEncryptWithParams(passphrase []byte, dataWriter io.Writer, params *EncryptParams) (plaintext io.WriteCloser, err error) { + if params == nil { + params = &EncryptParams{} + } + return symmetricallyEncrypt(passphrase, dataWriter, params) +} + +func symmetricallyEncrypt(passphrase []byte, dataWriter io.Writer, params *EncryptParams) (plaintext io.WriteCloser, err error) { + if params.KeyWriter == nil { + params.KeyWriter = dataWriter + } + if params.Hints == nil { + params.Hints = &FileHints{} + } + if params.SessionKey == nil { + params.SessionKey, err = packet.SerializeSymmetricKeyEncrypted(params.KeyWriter, passphrase, params.Config) + defer func() { + // zero the session key after we are done + for i := range params.SessionKey { + params.SessionKey[i] = 0 + } + params.SessionKey = nil + }() + } else { + err = packet.SerializeSymmetricKeyEncryptedReuseKey(params.KeyWriter, params.SessionKey, passphrase, params.Config) + } + if err != nil { + return + } + config := params.Config + candidateCompression := []uint8{uint8(config.Compression())} + cipherSuite := packet.CipherSuite{ + Cipher: config.Cipher(), + Mode: config.AEAD().Mode(), + } + var candidateHashesPerSignature [][]uint8 + if params.Signers != nil { + for _, signer := range params.Signers { + // candidateHashes := []uint8{hashToHashId(config.Hash())} + // Check what the preferred hashes are for the signing key + candidateHashes := []uint8{ + hashToHashId(crypto.SHA256), + hashToHashId(crypto.SHA384), + hashToHashId(crypto.SHA512), + hashToHashId(crypto.SHA3_256), + hashToHashId(crypto.SHA3_512), + } + defaultHashes := candidateHashes[0:1] + primarySelfSignature, _ := signer.PrimarySelfSignature(params.Config.Now()) + if primarySelfSignature == nil { + return nil, errors.InvalidArgumentError("signed entity has no self-signature") + } + preferredHashes := primarySelfSignature.PreferredHash + if len(preferredHashes) == 0 { + preferredHashes = defaultHashes + } + candidateHashes = intersectPreferences(candidateHashes, preferredHashes) + if len(candidateHashes) == 0 { + candidateHashes = []uint8{hashToHashId(crypto.SHA256)} + } + candidateHashesPerSignature = append(candidateHashesPerSignature, candidateHashes) + } + } + return encryptDataAndSign(dataWriter, params, candidateHashesPerSignature, candidateCompression, config.Cipher(), config.AEAD() != nil, cipherSuite, nil) +} + +// intersectPreferences mutates and returns a prefix of a that contains only +// the values in the intersection of a and b. The order of a is preserved. +func intersectPreferences(a []uint8, b []uint8) (intersection []uint8) { + var j int + for _, v := range a { + for _, v2 := range b { + if v == v2 { + a[j] = v + j++ + break + } + } + } + + return a[:j] +} + +// intersectPreferences mutates and returns a prefix of a that contains only +// the values in the intersection of a and b. The order of a is preserved. +func intersectCipherSuites(a [][2]uint8, b [][2]uint8) (intersection [][2]uint8) { + var j int + for _, v := range a { + for _, v2 := range b { + if v[0] == v2[0] && v[1] == v2[1] { + a[j] = v + j++ + break + } + } + } + + return a[:j] +} + +func hashToHashId(h crypto.Hash) uint8 { + v, ok := algorithm.HashToHashId(h) + if !ok { + panic("tried to convert unknown hash") + } + return v +} + +// EncryptWithParams encrypts a message to a number of recipients and, optionally, +// signs it. The resulting WriteCloser must be closed after the contents of the file have been written. +// The to argument contains recipients that are explicitly mentioned in signatures and encrypted keys, +// whereas the toHidden argument contains recipients that will be hidden and not mentioned. +// Params contains all optional parameters. +func EncryptWithParams(ciphertext io.Writer, to, toHidden []*Entity, params *EncryptParams) (plaintext io.WriteCloser, err error) { + if params == nil { + params = &EncryptParams{} + } + if params.KeyWriter == nil { + params.KeyWriter = ciphertext + } + return encrypt(to, toHidden, ciphertext, params) +} + +// Encrypt encrypts a message to a number of recipients and, optionally, signs +// it. hints contains optional information, that is also encrypted, that aids +// the recipients in processing the message. The resulting WriteCloser must +// be closed after the contents of the file have been written. +// The to argument contains recipients that are explicitly mentioned in signatures and encrypted keys, +// whereas the toHidden argument contains recipients that will be hidden and not mentioned. +// If config is nil, sensible defaults will be used. +func Encrypt(ciphertext io.Writer, to, toHidden []*Entity, signers []*Entity, hints *FileHints, config *packet.Config) (plaintext io.WriteCloser, err error) { + return EncryptWithParams(ciphertext, to, toHidden, &EncryptParams{ + Signers: signers, + Hints: hints, + Config: config, + }) +} + +// writeAndSign writes the data as a payload package and, optionally, signs +// it. hints contains optional information, that is also encrypted, +// that aids the recipients in processing the message. The resulting +// WriteCloser must be closed after the contents of the file have been +// written. If config is nil, sensible defaults will be used. +func writeAndSign(payload io.WriteCloser, candidateHashes [][]uint8, signEntities []*Entity, hints *FileHints, sigType packet.SignatureType, intendedRecipients []*packet.Recipient, config *packet.Config) (plaintext io.WriteCloser, err error) { + var signers []*signatureContext + for signEntityIdx, signEntity := range signEntities { + if signEntity == nil { + continue + } + signKey, ok := signEntity.SigningKeyById(config.Now(), config.SigningKey(), config) + if !ok { + return nil, errors.InvalidArgumentError("no valid signing keys") + } + signer := signKey.PrivateKey + if signer == nil { + return nil, errors.InvalidArgumentError("no private key in signing key") + } + if signer.Encrypted { + return nil, errors.InvalidArgumentError("signing key must be decrypted") + } + sigContext := signatureContext{ + signer: signer, + } + hash, err := selectHash(candidateHashes[signEntityIdx], config.Hash()) + if err != nil { + return nil, err + } + sigContext.hashType = hash + + var opsVersion = 3 + if signer.Version == 6 { + opsVersion = signer.Version + } + isLast := signEntityIdx == len(signEntities)-1 + ops := &packet.OnePassSignature{ + Version: opsVersion, + SigType: sigType, + Hash: hash, + PubKeyAlgo: signer.PubKeyAlgo, + KeyId: signer.KeyId, + IsLast: isLast, + } + if opsVersion == 6 { + ops.KeyFingerprint = signer.Fingerprint + sigContext.salt, err = packet.SignatureSaltForHash(hash, config.Random()) + if err != nil { + return nil, err + } + ops.Salt = sigContext.salt + } + if err := ops.Serialize(payload); err != nil { + return nil, err + } + + sigContext.h, sigContext.wrappedHash, err = hashForSignature(hash, sigType, sigContext.salt) + if err != nil { + return nil, err + } + // Prepend since the last signature has to be written first + signers = append([]*signatureContext{&sigContext}, signers...) + } + + if signEntities != nil && len(signers) < 1 { + return nil, errors.InvalidArgumentError("no valid signing key") + } + + if hints == nil { + hints = &FileHints{} + } + + w := payload + if signers != nil { + // If we need to write a signature packet after the literal + // data then we need to stop literalData from closing + // encryptedData. + w = noOpCloser{w} + + } + var epochSeconds uint32 + if !hints.ModTime.IsZero() { + epochSeconds = uint32(hints.ModTime.Unix()) + } + literalData, err := packet.SerializeLiteral(w, !hints.IsUTF8, hints.FileName, epochSeconds) + if err != nil { + return nil, err + } + + if signers != nil { + metadata := &packet.LiteralData{ + Format: 'b', + FileName: hints.FileName, + Time: epochSeconds, + } + if hints.IsUTF8 { + metadata.Format = 'u' + } + return signatureWriter{payload, literalData, signers, sigType, config, metadata, intendedRecipients, 0}, nil + } + return literalData, nil +} + +// encrypt encrypts a message to a number of recipients and, optionally, signs +// it. The resulting WriteCloser must +// be closed after the contents of the file have been written. +func encrypt( + to, toHidden []*Entity, + dataWriter io.Writer, + params *EncryptParams, +) (plaintext io.WriteCloser, err error) { + if len(to)+len(toHidden) == 0 { + return nil, errors.InvalidArgumentError("no encryption recipient provided") + } + + // These are the possible ciphers that we'll use for the message. + candidateCiphers := []uint8{ + uint8(packet.CipherAES256), + uint8(packet.CipherAES128), + } + + // These are the possible hash functions that we'll use for the signature. + candidateHashes := []uint8{ + hashToHashId(crypto.SHA256), + hashToHashId(crypto.SHA384), + hashToHashId(crypto.SHA512), + hashToHashId(crypto.SHA3_256), + hashToHashId(crypto.SHA3_512), + } + + // Prefer GCM if everyone supports it + candidateCipherSuites := [][2]uint8{ + {uint8(packet.CipherAES256), uint8(packet.AEADModeGCM)}, + {uint8(packet.CipherAES256), uint8(packet.AEADModeEAX)}, + {uint8(packet.CipherAES256), uint8(packet.AEADModeOCB)}, + {uint8(packet.CipherAES128), uint8(packet.AEADModeGCM)}, + {uint8(packet.CipherAES128), uint8(packet.AEADModeEAX)}, + {uint8(packet.CipherAES128), uint8(packet.AEADModeOCB)}, + } + + candidateCompression := []uint8{ + uint8(packet.CompressionNone), + uint8(packet.CompressionZIP), + uint8(packet.CompressionZLIB), + } + + encryptKeys := make([]Key, len(to)+len(toHidden)) + + config := params.Config + // AEAD is used only if config enables it and every key supports it + aeadSupported := config.AEAD() != nil + + var intendedRecipients []*packet.Recipient + // Intended Recipient Fingerprint subpacket SHOULD be used when creating a signed and encrypted message + for _, publicRecipient := range to { + intendedRecipients = append(intendedRecipients, &packet.Recipient{KeyVersion: publicRecipient.PrimaryKey.Version, Fingerprint: publicRecipient.PrimaryKey.Fingerprint}) + } + + for i, recipient := range append(to, toHidden...) { + var ok bool + encryptKeys[i], ok = recipient.EncryptionKey(config.Now(), config) + if !ok { + return nil, errors.InvalidArgumentError("cannot encrypt a message to key id " + strconv.FormatUint(to[i].PrimaryKey.KeyId, 16) + " because it has no valid encryption keys") + } + + primarySelfSignature, _ := recipient.PrimarySelfSignature(config.Now()) + if primarySelfSignature == nil { + return nil, errors.InvalidArgumentError("entity without a self-signature") + } + + if !primarySelfSignature.SEIPDv2 { + aeadSupported = false + } + + candidateCiphers = intersectPreferences(candidateCiphers, primarySelfSignature.PreferredSymmetric) + candidateHashes = intersectPreferences(candidateHashes, primarySelfSignature.PreferredHash) + candidateCipherSuites = intersectCipherSuites(candidateCipherSuites, primarySelfSignature.PreferredCipherSuites) + candidateCompression = intersectPreferences(candidateCompression, primarySelfSignature.PreferredCompression) + } + + // In the event that the intersection of supported algorithms is empty we use the ones + // labelled as MUST that every implementation supports. + if len(candidateCiphers) == 0 { + // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#section-9.3 + candidateCiphers = []uint8{uint8(packet.CipherAES128)} + } + if len(candidateHashes) == 0 { + // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#hash-algos + candidateHashes = []uint8{hashToHashId(crypto.SHA256)} + } + if len(candidateCipherSuites) == 0 { + // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#section-9.6 + candidateCipherSuites = [][2]uint8{{uint8(packet.CipherAES128), uint8(packet.AEADModeOCB)}} + } + + cipher := packet.CipherFunction(candidateCiphers[0]) + aeadCipherSuite := packet.CipherSuite{ + Cipher: packet.CipherFunction(candidateCipherSuites[0][0]), + Mode: packet.AEADMode(candidateCipherSuites[0][1]), + } + + // If the cipher specified by config is a candidate, we'll use that. + configuredCipher := config.Cipher() + for _, c := range candidateCiphers { + cipherFunc := packet.CipherFunction(c) + if cipherFunc == configuredCipher { + cipher = cipherFunc + break + } + } + + if params.SessionKey == nil { + params.SessionKey = make([]byte, cipher.KeySize()) + if _, err := io.ReadFull(config.Random(), params.SessionKey); err != nil { + return nil, err + } + defer func() { + // zero the session key after we are done + for i := range params.SessionKey { + params.SessionKey[i] = 0 + } + params.SessionKey = nil + }() + } + + for idx, key := range encryptKeys { + // hide the keys of the hidden recipients + hidden := idx >= len(to) + if err := packet.SerializeEncryptedKeyAEADwithHiddenOption(params.KeyWriter, key.PublicKey, cipher, aeadSupported, params.SessionKey, hidden, config); err != nil { + return nil, err + } + } + + var candidateHashesPerSignature [][]uint8 + for range params.Signers { + candidateHashesPerSignature = append(candidateHashesPerSignature, candidateHashes) + } + return encryptDataAndSign(dataWriter, params, candidateHashesPerSignature, candidateCompression, cipher, aeadSupported, aeadCipherSuite, intendedRecipients) +} + +func encryptDataAndSign( + dataWriter io.Writer, + params *EncryptParams, + candidateHashes [][]uint8, + candidateCompression []uint8, + cipher packet.CipherFunction, + aeadSupported bool, + aeadCipherSuite packet.CipherSuite, + intendedRecipients []*packet.Recipient, +) (plaintext io.WriteCloser, err error) { + sigType := packet.SigTypeBinary + if params.TextSig { + sigType = packet.SigTypeText + } + payload, err := packet.SerializeSymmetricallyEncrypted(dataWriter, cipher, aeadSupported, aeadCipherSuite, params.SessionKey, params.Config) + if err != nil { + return + } + payload, err = handleCompression(payload, candidateCompression, params.Config) + if err != nil { + return nil, err + } + return writeAndSign(payload, candidateHashes, params.Signers, params.Hints, sigType, intendedRecipients, params.Config) +} + +type SignParams struct { + // Hints contains file metadata for the literal data packet. + // If nil, default is used. + Hints *FileHints + // TextSig indicates if signatures of type SigTypeText should be produced + TextSig bool + // Config provides the config to be used. + // If Config is nil, sensible defaults will be used. + Config *packet.Config +} + +// SignWithParams signs a message. The resulting WriteCloser must be closed after the +// contents of the file have been written. +// SignParams can contain optional params and can be nil for defaults. +func SignWithParams(output io.Writer, signers []*Entity, params *SignParams) (input io.WriteCloser, err error) { + if params == nil { + params = &SignParams{} + } + if len(signers) < 1 { + return nil, errors.InvalidArgumentError("no signer provided") + } + var candidateHashesPerSignature [][]uint8 + candidateCompression := []uint8{ + uint8(packet.CompressionNone), + uint8(packet.CompressionZIP), + uint8(packet.CompressionZLIB), + } + for _, signer := range signers { + // These are the possible hash functions that we'll use for the signature. + candidateHashes := []uint8{ + hashToHashId(crypto.SHA256), + hashToHashId(crypto.SHA384), + hashToHashId(crypto.SHA512), + hashToHashId(crypto.SHA3_256), + hashToHashId(crypto.SHA3_512), + } + defaultHashes := candidateHashes[0:1] + primarySelfSignature, _ := signer.PrimarySelfSignature(params.Config.Now()) + if primarySelfSignature == nil { + return nil, errors.InvalidArgumentError("signed entity has no self-signature") + } + preferredHashes := primarySelfSignature.PreferredHash + if len(preferredHashes) == 0 { + preferredHashes = defaultHashes + } + candidateHashes = intersectPreferences(candidateHashes, preferredHashes) + if len(candidateHashes) == 0 { + return nil, errors.InvalidArgumentError("cannot sign because signing key shares no common algorithms with candidate hashes") + } + candidateHashesPerSignature = append(candidateHashesPerSignature, candidateHashes) + candidateCompression = intersectPreferences(candidateCompression, primarySelfSignature.PreferredCompression) + + } + + sigType := packet.SigTypeBinary + if params.TextSig { + sigType = packet.SigTypeText + } + + var payload io.WriteCloser + payload = noOpCloser{output} + payload, err = handleCompression(payload, candidateCompression, params.Config) + if err != nil { + return nil, err + } + return writeAndSign(payload, candidateHashesPerSignature, signers, params.Hints, sigType, nil, params.Config) +} + +// Sign signs a message. The resulting WriteCloser must be closed after the +// contents of the file have been written. hints contains optional information +// that aids the recipients in processing the message. +// If config is nil, sensible defaults will be used. +func Sign(output io.Writer, signers []*Entity, hints *FileHints, config *packet.Config) (input io.WriteCloser, err error) { + return SignWithParams(output, signers, &SignParams{ + Config: config, + Hints: hints, + }) +} + +// signatureWriter hashes the contents of a message while passing it along to +// literalData. When closed, it closes literalData, writes a signature packet +// to encryptedData and then also closes encryptedData. +type signatureWriter struct { + encryptedData io.WriteCloser + literalData io.WriteCloser + signatureContexts []*signatureContext + sigType packet.SignatureType + config *packet.Config + metadata *packet.LiteralData // V5 signatures protect document metadata + intendedRecipients []*packet.Recipient + flag int +} + +type signatureContext struct { + hashType crypto.Hash + wrappedHash hash.Hash + h hash.Hash + salt []byte // v6 only + signer *packet.PrivateKey +} + +func (s signatureWriter) Write(data []byte) (int, error) { + for _, ctx := range s.signatureContexts { + if _, err := ctx.wrappedHash.Write(data); err != nil { + return 0, err + } + } + switch s.sigType { + case packet.SigTypeBinary: + return s.literalData.Write(data) + case packet.SigTypeText: + return writeCanonical(s.literalData, data, &s.flag) + } + return 0, errors.UnsupportedError("unsupported signature type: " + strconv.Itoa(int(s.sigType))) +} + +func (s signatureWriter) Close() error { + if err := s.literalData.Close(); err != nil { + return err + } + for _, ctx := range s.signatureContexts { + sig := createSignaturePacket(&ctx.signer.PublicKey, s.sigType, s.config) + sig.Hash = ctx.hashType + sig.Metadata = s.metadata + sig.IntendedRecipients = s.intendedRecipients + + if err := sig.SetSalt(ctx.salt); err != nil { + return err + } + if err := sig.Sign(ctx.h, ctx.signer, s.config); err != nil { + return err + } + if err := sig.Serialize(s.encryptedData); err != nil { + return err + } + } + return s.encryptedData.Close() +} + +func createSignaturePacket(signer *packet.PublicKey, sigType packet.SignatureType, config *packet.Config) *packet.Signature { + sigLifetimeSecs := config.SigLifetime() + return &packet.Signature{ + Version: signer.Version, + SigType: sigType, + PubKeyAlgo: signer.PubKeyAlgo, + Hash: config.Hash(), + CreationTime: config.Now(), + IssuerKeyId: &signer.KeyId, + IssuerFingerprint: signer.Fingerprint, + Notations: config.Notations(), + SigLifetimeSecs: &sigLifetimeSecs, + } +} + +// noOpCloser is like an ioutil.NopCloser, but for an io.Writer. +// TODO: we have two of these in OpenPGP packages alone. This probably needs +// to be promoted somewhere more common. +type noOpCloser struct { + w io.Writer +} + +func (c noOpCloser) Write(data []byte) (n int, err error) { + return c.w.Write(data) +} + +func (c noOpCloser) Close() error { + return nil +} + +func handleCompression(compressed io.WriteCloser, candidateCompression []uint8, config *packet.Config) (data io.WriteCloser, err error) { + data = compressed + confAlgo := config.Compression() + if confAlgo == packet.CompressionNone { + return + } + + // Set algorithm labelled as MUST as fallback + // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#section-9.4 + finalAlgo := packet.CompressionNone + // if compression specified by config available we will use it + for _, c := range candidateCompression { + if uint8(confAlgo) == c { + finalAlgo = confAlgo + break + } + } + + if finalAlgo != packet.CompressionNone { + var compConfig *packet.CompressionConfig + if config != nil { + compConfig = config.CompressionConfig + } + data, err = packet.SerializeCompressed(compressed, finalAlgo, compConfig) + if err != nil { + return + } + } + return data, nil +} + +// selectHash selects the preferred hash given the candidateHashes and the configuredHash +func selectHash(candidateHashes []byte, configuredHash crypto.Hash) (hash crypto.Hash, err error) { + for _, hashId := range candidateHashes { + if h, ok := algorithm.HashIdToHash(hashId); ok && h.Available() { + hash = h + break + } + } + + // If the hash specified by config is a candidate, we'll use that. + if configuredHash.Available() { + for _, hashId := range candidateHashes { + if h, ok := algorithm.HashIdToHash(hashId); ok && h == configuredHash { + hash = h + break + } + } + } + + if hash == 0 { + hashId := candidateHashes[0] + name, ok := algorithm.HashIdToString(hashId) + if !ok { + name = "#" + strconv.Itoa(int(hashId)) + } + return 0, errors.InvalidArgumentError("no candidate hash functions are compiled in. (Wanted " + name + " in this case.)") + } + return +} diff --git a/openpgp/v2/write_test.go b/openpgp/v2/write_test.go new file mode 100644 index 000000000..b3e7d6d81 --- /dev/null +++ b/openpgp/v2/write_test.go @@ -0,0 +1,991 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package v2 + +import ( + "bytes" + "crypto/rand" + "io" + "io/ioutil" + mathrand "math/rand" + "testing" + "time" + + "github.com/ProtonMail/go-crypto/openpgp/errors" + "github.com/ProtonMail/go-crypto/openpgp/packet" + "github.com/ProtonMail/go-crypto/openpgp/s2k" +) + +const ( + maxPlaintextLen = 1 << 12 + maxPassLen = 1 << 6 +) + +func TestSignDetached(t *testing.T) { + kring, _ := ReadKeyRing(readerFromHex(testKeys1And2PrivateHex)) + out := bytes.NewBuffer(nil) + message := bytes.NewBufferString(signedInput) + err := DetachSign(out, kring[:1], message, &allowAllAlgorithmsConfig) + if err != nil { + t.Error(err) + } + + testDetachedSignature(t, kring, out, signedInput, "check", testKey1KeyId) +} + +func TestSignTextDetached(t *testing.T) { + kring, _ := ReadKeyRing(readerFromHex(testKeys1And2PrivateHex)) + out := bytes.NewBuffer(nil) + message := bytes.NewBufferString(signedInput) + err := DetachSignText(out, kring[:1], message, &allowAllAlgorithmsConfig) + if err != nil { + t.Error(err) + } + + testDetachedSignature(t, kring, out, signedInput, "check", testKey1KeyId) +} + +func TestSignDetachedDSA(t *testing.T) { + kring, _ := ReadKeyRing(readerFromHex(dsaTestKeyPrivateHex)) + out := bytes.NewBuffer(nil) + message := bytes.NewBufferString(signedInput) + err := DetachSign(out, kring[:1], message, &allowAllAlgorithmsConfig) + if err != nil { + t.Error(err) + } + + testDetachedSignature(t, kring, out, signedInput, "check", testKey3KeyId) +} + +func TestSignDetachedP256(t *testing.T) { + kring, _ := ReadKeyRing(readerFromHex(p256TestKeyPrivateHex)) + kring[0].PrivateKey.Decrypt([]byte("passphrase")) + + out := bytes.NewBuffer(nil) + message := bytes.NewBufferString(signedInput) + err := DetachSign(out, kring[:1], message, nil) + if err != nil { + t.Error(err) + } + + testDetachedSignature(t, kring, out, signedInput, "check", testKeyP256KeyId) +} + +func TestSignDetachedWithNotation(t *testing.T) { + kring, _ := ReadKeyRing(readerFromHex(testKeys1And2PrivateHex)) + signature := bytes.NewBuffer(nil) + message := bytes.NewBufferString(signedInput) + config := allowAllAlgorithmsConfig + config.SignatureNotations = []*packet.Notation{ + { + Name: "test@example.com", + Value: []byte("test"), + IsHumanReadable: true, + }, + } + err := DetachSign(signature, kring[:1], message, &config) + if err != nil { + t.Error(err) + } + + signed := bytes.NewBufferString(signedInput) + sig, signer, err := VerifyDetachedSignature(kring, signed, signature, &allowAllAlgorithmsConfig) + if err != nil { + t.Errorf("signature error: %s", err) + return + } + if sig == nil { + t.Errorf("sig is nil") + return + } + if numNotations, numExpected := len(sig.Notations), 1; numNotations != numExpected { + t.Fatalf("got %d Notation Data subpackets, expected %d", numNotations, numExpected) + } + if sig.Notations[0].IsHumanReadable != true { + t.Fatalf("got false, expected true") + } + if sig.Notations[0].Name != "test@example.com" { + t.Fatalf("got %s, expected test@example.com", sig.Notations[0].Name) + } + if string(sig.Notations[0].Value) != "test" { + t.Fatalf("got %s, expected \"test\"", string(sig.Notations[0].Value)) + } + if signer == nil { + t.Errorf("signer is nil") + return + } + if signer.PrimaryKey.KeyId != testKey1KeyId { + t.Errorf("wrong signer: got %x, expected %x", signer.PrimaryKey.KeyId, testKey1KeyId) + } +} + +func TestSignDetachedWithCriticalNotation(t *testing.T) { + kring, _ := ReadKeyRing(readerFromHex(testKeys1And2PrivateHex)) + signature := bytes.NewBuffer(nil) + message := bytes.NewBufferString(signedInput) + config := allowAllAlgorithmsConfig + config.SignatureNotations = []*packet.Notation{ + { + Name: "test@example.com", + Value: []byte("test"), + IsHumanReadable: true, + IsCritical: true, + }, + } + err := DetachSign(signature, kring[:1], message, &config) + if err != nil { + t.Error(err) + } + + signed := bytes.NewBufferString(signedInput) + config = allowAllAlgorithmsConfig + config.KnownNotations = map[string]bool{ + "test@example.com": true, + } + sig, signer, err := VerifyDetachedSignature(kring, signed, signature, &config) + if err != nil { + t.Errorf("signature error: %s", err) + return + } + if sig == nil { + t.Errorf("sig is nil") + return + } + if numNotations, numExpected := len(sig.Notations), 1; numNotations != numExpected { + t.Fatalf("got %d Notation Data subpackets, expected %d", numNotations, numExpected) + } + if sig.Notations[0].IsHumanReadable != true { + t.Fatalf("got false, expected true") + } + if sig.Notations[0].Name != "test@example.com" { + t.Fatalf("got %s, expected test@example.com", sig.Notations[0].Name) + } + if string(sig.Notations[0].Value) != "test" { + t.Fatalf("got %s, expected \"test\"", string(sig.Notations[0].Value)) + } + if signer == nil { + t.Errorf("signer is nil") + return + } + if signer.PrimaryKey.KeyId != testKey1KeyId { + t.Errorf("wrong signer: got %x, expected %x", signer.PrimaryKey.KeyId, testKey1KeyId) + } +} + +func TestNewEntity(t *testing.T) { + + // Check bit-length with no config. + e, err := NewEntity("Test User", "test", "test@example.com", nil) + if err != nil { + t.Errorf("failed to create entity: %s", err) + return + } + bl, err := e.PrimaryKey.BitLength() + if err != nil { + t.Errorf("failed to find bit length: %s", err) + } + defaultRSAKeyBits := 2048 + if int(bl) != defaultRSAKeyBits { + t.Errorf("BitLength %v, expected %v", int(bl), defaultRSAKeyBits) + } + + // Check bit-length with a config. + cfg := &packet.Config{RSABits: 1024} + e, err = NewEntity("Test User", "test", "test@example.com", cfg) + if err != nil { + t.Errorf("failed to create entity: %s", err) + return + } + bl, err = e.PrimaryKey.BitLength() + if err != nil { + t.Errorf("failed to find bit length: %s", err) + } + if int(bl) != cfg.RSABits { + t.Errorf("BitLength %v, expected %v", bl, cfg.RSABits) + } + + w := bytes.NewBuffer(nil) + if err := e.SerializePrivate(w, nil); err != nil { + t.Errorf("failed to serialize entity: %s", err) + return + } + serialized := w.Bytes() + + el, err := ReadKeyRing(w) + if err != nil { + t.Errorf("failed to reparse entity: %s", err) + return + } + + if len(el) != 1 { + t.Errorf("wrong number of entities found, got %d, want 1", len(el)) + } + + w = bytes.NewBuffer(nil) + if err := e.SerializePrivate(w, nil); err != nil { + t.Errorf("failed to serialize entity second time: %s", err) + return + } + + if !bytes.Equal(w.Bytes(), serialized) { + t.Errorf("results differed") + } + + if err := e.PrivateKey.Encrypt([]byte("password")); err != nil { + t.Errorf("failed to encrypt private key: %s", err) + } + + if err := e.PrivateKey.Decrypt([]byte("password")); err != nil { + t.Errorf("failed to decrypt private key: %s", err) + } + + w = bytes.NewBuffer(nil) + if err := e.SerializePrivate(w, nil); err != nil { + t.Errorf("failed to serialize after encryption round: %s", err) + return + } + + _, err = ReadKeyRing(w) + if err != nil { + t.Errorf("failed to reparse entity after encryption round: %s", err) + return + } +} + +func TestEncryptWithCompression(t *testing.T) { + kring, _ := ReadKeyRing(readerFromHex(testKeys1And2PrivateHex)) + passphrase := []byte("passphrase") + for _, entity := range kring { + if entity.PrivateKey != nil && entity.PrivateKey.Encrypted { + err := entity.PrivateKey.Decrypt(passphrase) + if err != nil { + t.Errorf("failed to decrypt key: %s", err) + } + } + for _, subkey := range entity.Subkeys { + if subkey.PrivateKey != nil && subkey.PrivateKey.Encrypted { + err := subkey.PrivateKey.Decrypt(passphrase) + if err != nil { + t.Errorf("failed to decrypt subkey: %s", err) + } + } + } + } + + buf := new(bytes.Buffer) + config := allowAllAlgorithmsConfig + config.DefaultCompressionAlgo = packet.CompressionZIP + config.CompressionConfig = &packet.CompressionConfig{Level: -1} + w, err := Encrypt(buf, kring[:1], nil, nil, nil /* no hints */, &config) + if err != nil { + t.Errorf("error in encrypting plaintext: %s", err) + return + } + message := []byte("hello world") + _, err = w.Write(message) + + if err != nil { + t.Errorf("error writing plaintext: %s", err) + return + } + err = w.Close() + if err != nil { + t.Errorf("error closing WriteCloser: %s", err) + return + } + err = checkCompression(buf, kring[:1]) + if err != nil { + t.Errorf("compression check failed: %s", err) + } +} + +func TestSymmetricEncryption(t *testing.T) { + modesS2K := map[string]s2k.Mode{ + "Iterated": s2k.IteratedSaltedS2K, + "Argon2": s2k.Argon2S2K, + } + for s2kName, s2ktype := range modesS2K { + t.Run(s2kName, func(t *testing.T) { + config := &packet.Config{ + S2KConfig: &s2k.Config{S2KMode: s2ktype}, + } + buf := new(bytes.Buffer) + plaintext, err := SymmetricallyEncrypt(buf, []byte("testing"), nil, config) + if err != nil { + t.Errorf("error writing headers: %s", err) + return + } + message := []byte("hello world\n") + _, err = plaintext.Write(message) + if err != nil { + t.Errorf("error writing to plaintext writer: %s", err) + } + err = plaintext.Close() + if err != nil { + t.Errorf("error closing plaintext writer: %s", err) + } + + md, err := ReadMessage(buf, nil, func(keys []Key, symmetric bool) ([]byte, error) { + return []byte("testing"), nil + }, nil) + if err != nil { + t.Errorf("error rereading message: %s", err) + } + messageBuf := bytes.NewBuffer(nil) + _, err = io.Copy(messageBuf, md.UnverifiedBody) + if err != nil { + t.Errorf("error rereading message: %s", err) + } + if !bytes.Equal(message, messageBuf.Bytes()) { + t.Errorf("recovered message incorrect got '%s', want '%s'", messageBuf.Bytes(), message) + } + }) + } +} + +func TestSymmetricEncryptionV5RandomizeSlow(t *testing.T) { + modesS2K := map[int]s2k.Mode{ + 0: s2k.IteratedSaltedS2K, + 1: s2k.Argon2S2K, + } + aeadConf := packet.AEADConfig{ + DefaultMode: aeadModes[mathrand.Intn(len(aeadModes))], + } + config := &packet.Config{AEADConfig: &aeadConf, S2KConfig: &s2k.Config{S2KMode: modesS2K[mathrand.Intn(2)]}} + buf := new(bytes.Buffer) + passphrase := make([]byte, mathrand.Intn(maxPassLen)) + _, err := rand.Read(passphrase) + if err != nil { + panic(err) + } + plaintext, err := SymmetricallyEncrypt(buf, passphrase, nil, config) + if err != nil { + t.Errorf("error writing headers: %s", err) + return + } + message := make([]byte, mathrand.Intn(maxPlaintextLen)) + _, errR := rand.Read(message) + if errR != nil { + panic(errR) + } + _, err = plaintext.Write(message) + if err != nil { + t.Errorf("error writing to plaintext writer: %s", err) + } + err = plaintext.Close() + if err != nil { + t.Errorf("error closing plaintext writer: %s", err) + } + + // Check if the packet is AEADEncrypted + copiedCiph := make([]byte, len(buf.Bytes())) + copy(copiedCiph, buf.Bytes()) + copiedBuf := bytes.NewBuffer(copiedCiph) + packets := packet.NewReader(copiedBuf) + // First a SymmetricKeyEncrypted packet + p, err := packets.Next() + if err != nil { + t.Errorf("error reading packet: %s", err) + } + switch tp := p.(type) { + case *packet.SymmetricKeyEncrypted: + default: + t.Errorf("Didn't find a SymmetricKeyEncrypted packet (found %T instead)", tp) + } + // Then an SymmetricallyEncrypted packet version 2 + p, err = packets.Next() + if err != nil { + t.Errorf("error reading packet: %s", err) + } + switch tp := p.(type) { + case *packet.SymmetricallyEncrypted: + if tp.Version != 2 { + t.Errorf("Wrong packet version, expected 2, found %d", tp.Version) + } + default: + t.Errorf("Didn't find an SymmetricallyEncrypted packet (found %T instead)", tp) + } + + promptFunc := func(keys []Key, symmetric bool) ([]byte, error) { + return passphrase, nil + } + md, err := ReadMessage(buf, nil, promptFunc, config) + if err != nil { + t.Errorf("error rereading message: %s", err) + } + messageBuf := bytes.NewBuffer(nil) + _, err = io.Copy(messageBuf, md.UnverifiedBody) + if err != nil { + t.Errorf("error rereading message: %s", err) + } + if !bytes.Equal(message, messageBuf.Bytes()) { + t.Errorf("recovered message incorrect got '%s', want '%s'", + messageBuf.Bytes(), message) + } +} + +var testEncryptionTests = []struct { + keyRingHex string + isSigned bool + okV6 bool +}{ + { + testKeys1And2PrivateHex, + false, + true, + }, + { + testKeys1And2PrivateHex, + true, + true, + }, + { + dsaElGamalTestKeysHex, + false, + false, + }, + { + dsaElGamalTestKeysHex, + true, + false, + }, +} + +func TestIntendedRecipientsEncryption(t *testing.T) { + var config = &packet.Config{ + V6Keys: true, + AEADConfig: &packet.AEADConfig{}, + Algorithm: packet.PubKeyAlgoEd25519, + } + sender, err := NewEntity("sender", "", "send@example.com", config) + if err != nil { + t.Errorf("failed to create entity: %s", err) + return + } + + publicRecipient, err := NewEntity("publicRecipient", "", "publicRecipient@example.com", config) + if err != nil { + t.Errorf("failed to create entity: %s", err) + return + } + + hiddenRecipient, err := NewEntity("hiddenRecipient", "", "hiddenRecipient@example.com", config) + if err != nil { + t.Errorf("failed to create entity: %s", err) + return + } + + outputBuffer := new(bytes.Buffer) + pWriter, err := Encrypt(outputBuffer, []*Entity{publicRecipient}, []*Entity{hiddenRecipient}, []*Entity{sender}, nil, config) + if err != nil { + t.Errorf("error in encrypt: %s", err) + } + + const message = "testing" + _, err = pWriter.Write([]byte(message)) + if err != nil { + t.Errorf("error writing plaintext: %s", err) + } + + err = pWriter.Close() + if err != nil { + t.Errorf("error closing WriteCloser: %s", err) + } + + encryptedMessage := make([]byte, len(outputBuffer.Bytes())) + copy(encryptedMessage, outputBuffer.Bytes()) + + md, err := ReadMessage(outputBuffer, EntityList{publicRecipient, sender}, nil /* no prompt */, config) + if err != nil { + t.Errorf("error reading message: %s", err) + } + + // Check reading with public recipient + if !md.CheckRecipients { + t.Error("should check for intended recipient") + } + _, err = ioutil.ReadAll(md.UnverifiedBody) + if err != nil { + t.Errorf("error reading encrypted contents: %s", err) + } + + if md.Signature == nil { + t.Error("expected matching signature") + } + + if len(md.Signature.IntendedRecipients) == 0 || + !bytes.Equal(md.Signature.IntendedRecipients[0].Fingerprint, publicRecipient.PrimaryKey.Fingerprint) { + t.Errorf("signature should contain %s as recipient", publicRecipient.PrimaryKey.Fingerprint) + } + + // Check reading with hidden recipient + outputBuffer = new(bytes.Buffer) + outputBuffer.Write(encryptedMessage) + md, err = ReadMessage(outputBuffer, EntityList{hiddenRecipient, sender}, nil /* no prompt */, config) + if err != nil { + t.Errorf("error reading message: %s", err) + } + if !md.CheckRecipients { + t.Error("should check for intended recipient") + } + _, err = ioutil.ReadAll(md.UnverifiedBody) + if err != nil { + t.Errorf("error reading encrypted contents: %s", err) + } + if !md.IsVerified { + t.Errorf("not verified despite all data read") + } + if _, ok := md.SignatureError.(errors.SignatureError); !ok { + t.Error("hidden recipient should not be in the intended recipient list") + } + + // Check reading with hidden recipient check disabled + outputBuffer = new(bytes.Buffer) + outputBuffer.Write(encryptedMessage) + check := false + config.CheckIntendedRecipients = &check + md, err = ReadMessage(outputBuffer, EntityList{hiddenRecipient, sender}, nil /* no prompt */, config) + if err != nil { + t.Errorf("error reading message: %s", err) + } + if md.CheckRecipients { + t.Error("should not check for intended recipient") + } + _, err = ioutil.ReadAll(md.UnverifiedBody) + if err != nil { + t.Errorf("error reading encrypted contents: %s", err) + } + if !md.IsVerified { + t.Errorf("not verified despite all data read") + } + if md.SignatureError != nil { + t.Error("singature verification should pass") + } +} + +func TestMultiSignEncryption(t *testing.T) { + recipient, err := NewEntity("sender", "", "send@example.com", nil) + if err != nil { + t.Errorf("failed to create entity: %s", err) + return + } + + v4Sign, err := NewEntity("signv4", "", "signv4@example.com", &packet.Config{ + Algorithm: packet.PubKeyAlgoRSA, + }) + if err != nil { + t.Errorf("failed to create entity: %s", err) + return + } + + v6Sign, err := NewEntity("signv6", "", "signv6@example.com", &packet.Config{ + V6Keys: true, + Algorithm: packet.PubKeyAlgoEd25519, + }) + if err != nil { + t.Errorf("failed to create entity: %s", err) + return + } + + outputBuffer := new(bytes.Buffer) + pWriter, err := Encrypt(outputBuffer, []*Entity{recipient}, nil, []*Entity{v4Sign, v6Sign}, nil, nil) + if err != nil { + t.Errorf("error in encrypt: %s", err) + } + + const message = "testing" + _, err = pWriter.Write([]byte(message)) + if err != nil { + t.Errorf("error writing plaintext: %s", err) + } + + err = pWriter.Close() + if err != nil { + t.Errorf("error closing WriteCloser: %s", err) + } + + encryptedMessage := make([]byte, len(outputBuffer.Bytes())) + copy(encryptedMessage, outputBuffer.Bytes()) + + md, err := ReadMessage(outputBuffer, EntityList{recipient, v4Sign}, nil /* no prompt */, nil) + if err != nil { + t.Errorf("error reading message: %s", err) + } + + // Check reading with v4 key + _, err = ioutil.ReadAll(md.UnverifiedBody) + if err != nil { + t.Errorf("error reading encrypted contents: %s", err) + } + if !md.IsVerified { + t.Errorf("not verified despite all data read") + } + if md.Signature == nil || md.SignatureError != nil { + t.Error("expected matching signature") + } + + // Check reading with v6 key + outputBuffer = new(bytes.Buffer) + outputBuffer.Write(encryptedMessage) + md, err = ReadMessage(outputBuffer, EntityList{recipient, v6Sign}, nil /* no prompt */, nil) + if err != nil { + t.Errorf("error reading message: %s", err) + } + _, err = ioutil.ReadAll(md.UnverifiedBody) + if err != nil { + t.Errorf("error reading encrypted contents: %s", err) + } + if !md.IsVerified { + t.Errorf("not verified despite all data read") + } + if md.Signature == nil || md.SignatureError != nil { + t.Error("expected matching signature") + } + + // Check reading with error + outputBuffer = new(bytes.Buffer) + outputBuffer.Write(encryptedMessage) + md, err = ReadMessage(outputBuffer, EntityList{recipient}, nil /* no prompt */, nil) + if err != nil { + t.Errorf("error reading message: %s", err) + } + _, err = ioutil.ReadAll(md.UnverifiedBody) + if err != nil { + t.Errorf("error reading encrypted contents: %s", err) + } + if !md.IsVerified { + t.Errorf("not verified despite all data read") + } + if md.Signature != nil || md.SignatureError == nil { + t.Error("expected error") + } + if md.SignatureError != errors.ErrUnknownIssuer { + t.Error("expected unknown issuer error") + } +} + +func TestEncryption(t *testing.T) { + for i, test := range testEncryptionTests { + kring, _ := ReadKeyRing(readerFromHex(test.keyRingHex)) + + passphrase := []byte("passphrase") + for _, entity := range kring { + if entity.PrivateKey != nil && entity.PrivateKey.Encrypted { + err := entity.PrivateKey.Decrypt(passphrase) + if err != nil { + t.Errorf("#%d: failed to decrypt key", i) + } + } + for _, subkey := range entity.Subkeys { + if subkey.PrivateKey != nil && subkey.PrivateKey.Encrypted { + err := subkey.PrivateKey.Decrypt(passphrase) + if err != nil { + t.Errorf("#%d: failed to decrypt subkey", i) + } + } + } + } + + var signed *Entity + if test.isSigned { + signed = kring[0] + } + + buf := new(bytes.Buffer) + // randomized compression test + compAlgos := []packet.CompressionAlgo{ + packet.CompressionNone, + packet.CompressionZIP, + packet.CompressionZLIB, + } + compAlgo := compAlgos[mathrand.Intn(len(compAlgos))] + level := mathrand.Intn(11) - 1 + compConf := &packet.CompressionConfig{Level: level} + config := allowAllAlgorithmsConfig + config.DefaultCompressionAlgo = compAlgo + config.CompressionConfig = compConf + + // Flip coin to enable AEAD mode + if mathrand.Int()%2 == 0 { + aeadConf := packet.AEADConfig{ + DefaultMode: aeadModes[mathrand.Intn(len(aeadModes))], + } + config.AEADConfig = &aeadConf + } + var signers []*Entity + if signed != nil { + signers = []*Entity{signed} + } + w, err := Encrypt(buf, kring[:1], nil, signers, nil /* no hints */, &config) + if err != nil && config.AEAD() != nil && !test.okV6 { + // ElGamal is not allowed with v6 + continue + } + + if err != nil { + t.Errorf("#%d: error in Encrypt: %s", i, err) + continue + } + + const message = "testing" + _, err = w.Write([]byte(message)) + if err != nil { + t.Errorf("#%d: error writing plaintext: %s", i, err) + continue + } + err = w.Close() + if err != nil { + t.Errorf("#%d: error closing WriteCloser: %s", i, err) + continue + } + + md, err := ReadMessage(buf, kring, nil /* no prompt */, &config) + if err != nil { + t.Errorf("#%d: error reading message: %s", i, err) + continue + } + + testTime, _ := time.Parse("2006-01-02", "2013-07-01") + if test.isSigned { + signKey, _ := kring[0].SigningKey(testTime, &allowAllAlgorithmsConfig) + expectedKeyId := signKey.PublicKey.KeyId + if len(md.SignatureCandidates) < 1 { + t.Error("no candidate signature found") + } + if md.SignatureCandidates[0].IssuerKeyId != expectedKeyId { + t.Errorf("#%d: message signed by wrong key id, got: %v, want: %v", i, *md.SignatureCandidates[0].SignedBy, expectedKeyId) + } + if md.SignatureCandidates[0].SignedByEntity == nil { + t.Errorf("#%d: failed to find the signing Entity", i) + } + } + + plaintext, err := ioutil.ReadAll(md.UnverifiedBody) + if err != nil { + t.Errorf("#%d: error reading encrypted contents: %s", i, err) + continue + } + + encryptKey, _ := kring[0].EncryptionKey(testTime, &allowAllAlgorithmsConfig) + expectedKeyId := encryptKey.PublicKey.KeyId + if len(md.EncryptedToKeyIds) != 1 || md.EncryptedToKeyIds[0] != expectedKeyId { + t.Errorf("#%d: expected message to be encrypted to %v, but got %#v", i, expectedKeyId, md.EncryptedToKeyIds) + } + + if string(plaintext) != message { + t.Errorf("#%d: got: %s, want: %s", i, string(plaintext), message) + } + + if test.isSigned { + if !md.IsVerified { + t.Errorf("not verified despite all data read") + } + if md.SignatureError != nil { + t.Errorf("#%d: signature error: %s", i, md.SignatureError) + } + if md.Signature == nil { + t.Error("signature missing") + } + } + } +} + +var testSigningTests = []struct { + keyRingHex string +}{ + { + testKeys1And2PrivateHex, + }, + { + dsaElGamalTestKeysHex, + }, + { + ed25519wX25519Key, + }, +} + +func TestSigning(t *testing.T) { + for i, test := range testSigningTests { + kring, _ := ReadKeyRing(readerFromHex(test.keyRingHex)) + + passphrase := []byte("passphrase") + for _, entity := range kring { + if entity.PrivateKey != nil && entity.PrivateKey.Encrypted { + err := entity.PrivateKey.Decrypt(passphrase) + if err != nil { + t.Errorf("#%d: failed to decrypt key", i) + } + } + for _, subkey := range entity.Subkeys { + if subkey.PrivateKey != nil && subkey.PrivateKey.Encrypted { + err := subkey.PrivateKey.Decrypt(passphrase) + if err != nil { + t.Errorf("#%d: failed to decrypt subkey", i) + } + } + } + } + + signed := kring[0] + + buf := new(bytes.Buffer) + w, err := Sign(buf, []*Entity{signed}, nil /* no hints */, &allowAllAlgorithmsConfig) + if err != nil { + t.Errorf("#%d: error in Sign: %s", i, err) + continue + } + + const message = "testing" + _, err = w.Write([]byte(message)) + if err != nil { + t.Errorf("#%d: error writing plaintext: %s", i, err) + continue + } + err = w.Close() + if err != nil { + t.Errorf("#%d: error closing WriteCloser: %s", i, err) + continue + } + + md, err := ReadMessage(buf, kring, nil /* no prompt */, &allowAllAlgorithmsConfig) + if err != nil { + t.Errorf("#%d: error reading message: %s", i, err) + continue + } + + testTime, _ := time.Parse("2006-01-02", "2022-12-01") + signKey, _ := kring[0].SigningKey(testTime, &allowAllAlgorithmsConfig) + expectedKeyId := signKey.PublicKey.KeyId + if len(md.SignatureCandidates) < 1 { + t.Error("expected a signature candidate") + } + if md.SignatureCandidates[0].IssuerKeyId != expectedKeyId { + t.Errorf("#%d: message signed by wrong key id, got: %v, want: %v", i, *md.SignatureCandidates[0].SignedBy, expectedKeyId) + } + if md.SignatureCandidates[0].SignedByEntity == nil { + t.Errorf("#%d: failed to find the signing Entity", i) + } + + plaintext, err := ioutil.ReadAll(md.UnverifiedBody) + if err != nil { + t.Errorf("#%d: error reading contents: %v", i, err) + continue + } + + if string(plaintext) != message { + t.Errorf("#%d: got: %q, want: %q", i, plaintext, message) + } + + if !md.IsVerified { + t.Errorf("not verified despite all data read") + } + if md.SignatureError != nil { + t.Errorf("#%d: signature error: %q", i, md.SignatureError) + } + if md.Signature == nil { + t.Error("signature missing") + } + } +} + +func checkCompression(r io.Reader, keyring KeyRing) (err error) { + var p packet.Packet + + var symKeys []*packet.SymmetricKeyEncrypted + var pubKeys []keyEnvelopePair + // Integrity protected encrypted packet: SymmetricallyEncrypted or AEADEncrypted + var edp packet.EncryptedDataPacket + + packets := packet.NewReader(r) + config := &packet.Config{} + + // The message, if encrypted, starts with a number of packets + // containing an encrypted decryption key. The decryption key is either + // encrypted to a public key, or with a passphrase. This loop + // collects these packets. +ParsePackets: + for { + p, err = packets.Next() + if err != nil { + return err + } + switch p := p.(type) { + case *packet.EncryptedKey: + // This packet contains the decryption key encrypted to a public key. + switch p.Algo { + case packet.PubKeyAlgoRSA, packet.PubKeyAlgoRSAEncryptOnly, packet.PubKeyAlgoElGamal, packet.PubKeyAlgoECDH: + break + default: + continue + } + keys := keyring.KeysById(p.KeyId) + for _, k := range keys { + pubKeys = append(pubKeys, keyEnvelopePair{k, p}) + } + case *packet.SymmetricallyEncrypted, *packet.AEADEncrypted: + edp = p.(packet.EncryptedDataPacket) + break ParsePackets + case *packet.Compressed, *packet.LiteralData, *packet.OnePassSignature: + // This message isn't encrypted. + return errors.StructuralError("message not encrypted") + } + } + + var candidates []Key + var decrypted io.ReadCloser + + // Now that we have the list of encrypted keys we need to decrypt at + // least one of them or, if we cannot, we need to call the prompt + // function so that it can decrypt a key or give us a passphrase. +FindKey: + for { + // See if any of the keys already have a private key available + candidates = candidates[:0] + candidateFingerprints := make(map[string]bool) + + for _, pk := range pubKeys { + if pk.key.PrivateKey == nil { + continue + } + if !pk.key.PrivateKey.Encrypted { + if len(pk.encryptedKey.Key) == 0 { + errDec := pk.encryptedKey.Decrypt(pk.key.PrivateKey, config) + if errDec != nil { + continue + } + } + // Try to decrypt symmetrically encrypted + decrypted, err = edp.Decrypt(pk.encryptedKey.CipherFunc, pk.encryptedKey.Key) + if err != nil && err != errors.ErrKeyIncorrect { + return err + } + if decrypted != nil { + break FindKey + } + } else { + fpr := string(pk.key.PublicKey.Fingerprint[:]) + if v := candidateFingerprints[fpr]; v { + continue + } + candidates = append(candidates, pk.key) + candidateFingerprints[fpr] = true + } + } + + if len(candidates) == 0 && len(symKeys) == 0 { + return errors.ErrKeyIncorrect + } + } + + decPackets, err := packet.Read(decrypted) + if err != nil { + return + } + _, ok := decPackets.(*packet.Compressed) + if !ok { + return errors.InvalidArgumentError("No compressed packets found") + } + return nil +} From 774c082cc560d49a579b6f023b93d3a23b64eed2 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Tue, 8 Aug 2023 11:38:25 +0200 Subject: [PATCH 043/116] Add dedicated v2 integration tests --- .../integration_tests/v2/end_to_end_test.go | 403 ++++++++++++++++++ .../v2/testdata/test_vectors.json | 98 +++++ openpgp/integration_tests/v2/utils_test.go | 303 +++++++++++++ 3 files changed, 804 insertions(+) create mode 100644 openpgp/integration_tests/v2/end_to_end_test.go create mode 100644 openpgp/integration_tests/v2/testdata/test_vectors.json create mode 100644 openpgp/integration_tests/v2/utils_test.go diff --git a/openpgp/integration_tests/v2/end_to_end_test.go b/openpgp/integration_tests/v2/end_to_end_test.go new file mode 100644 index 000000000..8035dc343 --- /dev/null +++ b/openpgp/integration_tests/v2/end_to_end_test.go @@ -0,0 +1,403 @@ +// Copyright (C) 2019 ProtonTech AG + +package v2 + +import ( + "bytes" + "crypto" + "encoding/json" + "io" + "io/ioutil" + "os" + "strings" + "testing" + "time" + + "github.com/ProtonMail/go-crypto/openpgp/armor" + "github.com/ProtonMail/go-crypto/openpgp/packet" + openpgp "github.com/ProtonMail/go-crypto/openpgp/v2" +) + +///////////////////////////////////////////////////////////////////////////// +// TODO: +// +// - Move signature line endings test to packet unit tests. +// +///////////////////////////////////////////////////////////////////////////// + +type testVector struct { + Message string + Name string + PrivateKey string + PublicKey string + Password string + EncryptedSignedMessage string + config *packet.Config +} + +var allowAllAlgorithmsConfig = packet.Config{ + RejectMessageHashAlgorithms: map[crypto.Hash]bool{}, + RejectPublicKeyAlgorithms: map[packet.PublicKeyAlgorithm]bool{}, + RejectCurves: map[packet.Curve]bool{}, + MinRSABits: 512, +} + +// Takes a set of different keys (some external, some generated here) and test +// interactions between them: encrypt, sign, decrypt, verify random messages. +func TestEndToEnd(t *testing.T) { + // Fetch foreign test vectors from JSON file + file, err := os.Open("testdata/test_vectors.json") + if err != nil { + panic(err) + } + raw, err := ioutil.ReadAll(file) + if err != nil { + panic(err) + } + var foreignTestVectors []testVector + err = json.Unmarshal(raw, &foreignTestVectors) + if err != nil { + panic(err) + } + + for i := 0; i < len(foreignTestVectors); i++ { + foreignTestVectors[i].Name += "_foreign" + config := allowAllAlgorithmsConfig + foreignTestVectors[i].config = &config + } + + // Generate random test vectors + freshTestVectors, err := generateFreshTestVectors(20) + if err != nil { + t.Fatal("Cannot proceed without generated keys: " + err.Error()) + } + testVectors := append(foreignTestVectors, freshTestVectors...) + + // For each testVector in testVectors, (1) Decrypt an already existing message, + // (2) Sign and verify random messages, and (3) Encrypt random messages for + // each of the other keys and then decrypt on the other end. + for _, from := range testVectors { + skFrom := readArmoredSk(t, from.PrivateKey, from.Password) + pkFrom := readArmoredPk(t, from.PublicKey) + t.Run(from.Name, func(t *testing.T) { + + // 1. Decrypt the existing message of the given test vector + t.Run("DecryptPreparedMessage", + func(t *testing.T) { + decryptionTest(t, from, skFrom) + }) + // 2. Sign a message and verify the signature. + t.Run("signVerify", func(t *testing.T) { + t.Run("binary", func(t *testing.T) { + signVerifyTest(t, from, skFrom, pkFrom, true) + }) + t.Run("text", func(t *testing.T) { + signVerifyTest(t, from, skFrom, pkFrom, false) + }) + }) + // 3. Encrypt, decrypt and verify a random message for + // every other key. + t.Run("encryptDecrypt", + func(t *testing.T) { + encDecTest(t, from, testVectors) + }) + }) + } +} + +// This subtest decrypts the existing encrypted and signed message of each +// testVector. +func decryptionTest(t *testing.T, vector testVector, sk openpgp.EntityList) { + if vector.EncryptedSignedMessage == "" { + return + } + prompt := func(keys []openpgp.Key, symmetric bool) ([]byte, error) { + err := keys[0].PrivateKey.Decrypt([]byte(vector.Password)) + if err != nil { + t.Errorf("prompt: error decrypting key: %s", err) + return nil, err + } + return nil, nil + } + sig, err := armor.Decode(strings.NewReader(vector.EncryptedSignedMessage)) + if err != nil { + t.Fatal(err) + } + md, err := openpgp.ReadMessage(sig.Body, sk, prompt, vector.config) + if err != nil { + t.Fatal(err) + } + + body, err := ioutil.ReadAll(md.UnverifiedBody) + if err != nil { + t.Fatal(err) + } + if !md.IsVerified { + t.Errorf("not verified despite all data read") + } + + stringBody := string(body) + if stringBody != vector.Message { + t.Fatal("Decrypted body did not match expected body") + } + + // We'll see a sig error here after reading in the UnverifiedBody above, + // if there was one to see. + if err = md.SignatureError; err != nil { + t.Fatal(err) + } + + if md.Signature == nil { + t.Fatal("Expected a signature to be valid") + } +} + +// Given a testVector, encrypts random messages for all given testVectors +// (including self) and verifies on the other end. +func encDecTest(t *testing.T, from testVector, testVectors []testVector) { + skFrom := readArmoredSk(t, from.PrivateKey, from.Password) + // Decrypt private key if necessary + for _, entity := range skFrom { + if err := entity.DecryptPrivateKeys([]byte(from.Password)); err != nil { + t.Error(err) + } + } + pkFrom := readArmoredPk(t, from.PublicKey) + for _, to := range testVectors { + t.Run(to.Name, func(t *testing.T) { + pkTo := readArmoredPk(t, to.PublicKey) + skTo := readArmoredSk(t, to.PrivateKey, to.Password) + message := randMessage() + hints := randFileHints() + + // Encrypt message + signer := skFrom[0] + errDec := signer.PrivateKey.Decrypt([]byte(from.Password)) + if errDec != nil { + t.Error(errDec) + } + buf := new(bytes.Buffer) + w, err := openpgp.Encrypt(buf, pkTo[:1], nil, []*openpgp.Entity{signer}, hints, from.config) + if err != nil { + t.Fatalf("Error in Encrypt: %s", err) + } + _, err = w.Write([]byte(message)) + if err != nil { + t.Fatalf("Error writing plaintext: %s", err) + } + err = w.Close() + if err != nil { + t.Fatalf("Error closing WriteCloser: %s", err) + } + + // ----------------- + // On the other end: + // ----------------- + + // Decrypt recipient key + prompt := func(keys []openpgp.Key, symm bool) ([]byte, error) { + err := keys[0].PrivateKey.Decrypt([]byte(to.Password)) + if err != nil { + t.Errorf("Prompt: error decrypting key: %s", err) + return nil, err + } + return nil, nil + } + + // Read message with recipient key + keyring := append(skTo, pkFrom[:]...) + md, err := openpgp.ReadMessage(buf, keyring, prompt, to.config) + if err != nil { + t.Fatalf("Error reading message: %s", err) + } + + // Test message details + if !md.IsEncrypted { + t.Fatal("The message should be encrypted") + } + signKey, _ := signer.SigningKey(time.Now(), &allowAllAlgorithmsConfig) + expectedKeyID := signKey.PublicKey.KeyId + expectedFingerprint := signKey.PublicKey.Fingerprint + if len(md.SignatureCandidates) != 1 { + t.Fatal("No signature candidate found") + } + if signKey.PublicKey.Version != 6 && md.SignatureCandidates[0].IssuerKeyId != expectedKeyID { + t.Fatalf( + "Message signed by wrong key id, got: %v, want: %v", + *md.SignatureCandidates[0].SignedBy, expectedKeyID) + } + if signKey.PublicKey.Version == 6 && !bytes.Equal(md.SignatureCandidates[0].IssuerFingerprint, expectedFingerprint) { + t.Fatalf( + "Message signed by wrong key id, got: %x, want: %x", + md.SignatureCandidates[0].IssuerFingerprint, expectedFingerprint) + } + if md.SignatureCandidates[0] == nil { + t.Fatalf("Failed to find the signing Entity") + } + + plaintext, err := ioutil.ReadAll(md.UnverifiedBody) + if err != nil { + t.Fatalf("Error reading encrypted contents: %s", err) + } + if !md.IsVerified { + t.Errorf("not verified despite all data read") + } + encryptKey, _ := pkTo[0].EncryptionKey(time.Now(), &allowAllAlgorithmsConfig) + expectedEncKeyID := encryptKey.PublicKey.KeyId + if len(md.EncryptedToKeyIds) != 1 || + md.EncryptedToKeyIds[0] != expectedEncKeyID { + t.Errorf("Expected message to be encrypted to %v, but got %#v", + expectedKeyID, md.EncryptedToKeyIds) + } + // Test decrypted message + if string(plaintext) != message { + t.Error("decrypted and expected message do not match") + } + + if md.SignatureError != nil { + t.Fatalf("Signature error: %s", md.SignatureError) + } + if md.Signature == nil { + t.Error("Expected valid signature") + } + }) + } +} + +// Sign a random message and verify signature against the original message, +// another message with same body but different line endings, and a corrupt +// message. +func signVerifyTest( + t *testing.T, + from testVector, + skFrom, pkFrom openpgp.EntityList, + binary bool, +) { + if err := skFrom[0].PrivateKey.Decrypt([]byte(from.Password)); err != nil { + t.Error(err) + } + + messageBody := randMessage() + + // ================================================ + // TODO: Move the line ending checks to unit tests + // ================================================ + // Add line endings to test whether the non-binary version of this + // signature normalizes the final line endings, see RFC4880bis, sec 5.2.1. + lineEnding := " \r\n \n \r\n" + otherLineEnding := " \n \r\n \n" + message := bytes.NewReader([]byte(messageBody + lineEnding)) + otherMessage := bytes.NewReader([]byte(messageBody + otherLineEnding)) + + corruptMessage := bytes.NewReader([]byte(corrupt(messageBody) + lineEnding)) + + // Sign the message + buf := new(bytes.Buffer) + var errSign error + if binary { + errSign = openpgp.ArmoredDetachSign(buf, skFrom[:1], message, &allowAllAlgorithmsConfig) + } else { + errSign = openpgp.ArmoredDetachSignText(buf, skFrom[:1], message, &allowAllAlgorithmsConfig) + } + if errSign != nil { + t.Error(errSign) + } + + // Verify the signature against the corrupt message first + signatureReader := bytes.NewReader(buf.Bytes()) + _, wrongsigner, err := openpgp.VerifyArmoredDetachedSignature( + pkFrom, corruptMessage, signatureReader, &allowAllAlgorithmsConfig) + if err == nil || wrongsigner != nil { + t.Fatal("Expected the signature to not verify") + } + + // Reset the reader and verify against the message with different line + // endings (should pass in the non-binary case) + var errSeek error + _, errSeek = signatureReader.Seek(0, io.SeekStart) + if errSeek != nil { + t.Error(errSeek) + } + + _, otherSigner, err := openpgp.VerifyArmoredDetachedSignature( + pkFrom, otherMessage, signatureReader, &allowAllAlgorithmsConfig) + if binary { + if err == nil || otherSigner != nil { + t.Fatal("Expected the signature to not verify") + return + } + } else { + if err != nil { + t.Fatalf("signature error: %s", err) + } + if otherSigner == nil { + t.Fatalf("signer is nil") + } + if otherSigner.PrimaryKey.KeyId != skFrom[0].PrimaryKey.KeyId { + t.Errorf( + "wrong signer: got %x, expected %x", otherSigner.PrimaryKey.KeyId, 0) + } + } + + // Reset the readers and verify against the exact first message. + _, errSeek = message.Seek(0, io.SeekStart) + if errSeek != nil { + t.Error(errSeek) + } + _, errSeek = signatureReader.Seek(0, io.SeekStart) + if errSeek != nil { + t.Error(errSeek) + } + + _, otherSigner, err = openpgp.VerifyArmoredDetachedSignature( + pkFrom, message, signatureReader, &allowAllAlgorithmsConfig) + + if err != nil { + t.Fatalf("signature error: %s", err) + } + if otherSigner == nil { + t.Fatalf("signer is nil") + } + if otherSigner.PrimaryKey.KeyId != skFrom[0].PrimaryKey.KeyId { + t.Errorf( + "wrong signer: got %x, expected %x", + skFrom[0].PrimaryKey.KeyId, + skFrom[0].PrimaryKey.KeyId, + ) + } +} + +func readArmoredPk(t *testing.T, publicKey string) openpgp.EntityList { + keys, err := openpgp.ReadArmoredKeyRing(bytes.NewBufferString(publicKey)) + if err != nil { + t.Fatal(err) + } + if len(keys) < 1 { + t.Errorf("Failed to read key with good cross signature, %d", len(keys)) + } + if len(keys[0].Subkeys) < 1 { + t.Errorf("Failed to read good subkey, %d", len(keys[0].Subkeys)) + } + return keys +} + +func readArmoredSk(t *testing.T, sk string, pass string) openpgp.EntityList { + keys, err := openpgp.ReadArmoredKeyRing(bytes.NewBufferString(sk)) + if err != nil { + t.Fatal(err) + } + if len(keys) != 1 { + t.Errorf("Failed to read key with good cross signature, %d", len(keys)) + } + if len(keys[0].Subkeys) < 1 { + t.Errorf("Failed to read good subkey, %d", len(keys[0].Subkeys)) + } + keyObject := keys[0].PrivateKey + if pass != "" { + corruptPassword := corrupt(pass) + if err := keyObject.Decrypt([]byte(corruptPassword)); err == nil { + t.Fatal("Decrypted key with invalid password") + } + } + return keys +} diff --git a/openpgp/integration_tests/v2/testdata/test_vectors.json b/openpgp/integration_tests/v2/testdata/test_vectors.json new file mode 100644 index 000000000..1943e6240 --- /dev/null +++ b/openpgp/integration_tests/v2/testdata/test_vectors.json @@ -0,0 +1,98 @@ +[ + { + "encryptedSignedMessage": "-----BEGIN PGP MESSAGE-----\n\nwYwD4IT3RGwgLJcBA/9txflPrGAhTRBISzQFVrMU2DYjuKy+XbOxMEsNy1H9\neXbCp6lP6AeKxAGrdDfJb209LoL6lvS4UpCV4eV+ucZ1tzZYBlqxTtMq4oC6\nkIYidGJhROe33z3S6ocPN3Q6mYpuu2IT6V1+SBBmYiu3gwMOb8TRQzFcgCOg\nyL3nx4ptEdLAQQEHUZCCsJFFw71i5oZf8/RPd0GM6L0RRG6DH+o/ab8LcT/l\nTXqOfj3KvuHyE5lfwceZUJa0p6zpMLz/Clp/JJnGXtqBPcDlRlKYlot1+LMd\nGYjUOM3S4ObnYOS9of4+6nLzWdl+kK7vHOIfTQPKfc9BuCgkBLwI2FjnJ10B\n4gT495NMTj2IFC2okD5KaGu2rXfNWbS6bJOPWK+zsdwLHUO7PTB9sIDESp+6\noUTn8Fkc5QWKHSbafWIrEWKMLvJBD3HrIbLoGGd1O+RrFuMZV1qsaNh/pqDN\nbBBgRdPauYvDNmUQb9UFfFGiD6GTqNEQd827fz+2r1Lp4OdEdkh1BMeQ\n=wyjK\n-----END PGP MESSAGE-----", + "message": "test\u554f\u91cf\u9bae\u63a7\u5230\u6848\u9032\u5e73", + "name": "rsa", + "password": "hello world", + "privateKey": "-----BEGIN PGP PRIVATE KEY BLOCK-----\n\n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\n=lw5e\n-----END PGP PRIVATE KEY BLOCK-----", + "publicKey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmI0EUmEvTgEEANyWtQQMOybQ9JltDqmaX0WnNPJeLILIM36sw6zL0nfTQ5zXSS3+\nfIF6P29lJFxpblWk02PSID5zX/DYU9/zjM2xPO8Oa4xo0cVTOTLj++Ri5mtr//f5\nGLsIXxFrBJhD/ghFsL3Op0GXOeLJ9A5bsOn8th7x6JucNKuaRB6bQbSPABEBAAG0\nJFRlc3QgTWNUZXN0aW5ndG9uIDx0ZXN0QGV4YW1wbGUuY29tPoi5BBMBAgAjBQJS\nYS9OAhsvBwsJCAcDAgEGFQgCCQoLBBYCAwECHgECF4AACgkQSmNhOk1uQJQwDAP6\nAgrTyqkRlJVqz2pb46TfbDM2TDF7o9CBnBzIGoxBhlRwpqALz7z2kxBDmwpQa+ki\nBq3jZN/UosY9y8bhwMAlnrDY9jP1gdCo+H0sD48CdXybblNwaYpwqC8VSpDdTndf\n9j2wE/weihGp/DAdy/2kyBCaiOY1sjhUfJ1GogF49rC4jQRSYS9OAQQA6R/PtBFa\nJaT4jq10yqASk4sqwVMsc6HcifM5lSdxzExFP74naUMMyEsKHP53QxTF0Grqusag\nQg/ZtgT0CN1HUM152y7ACOdp1giKjpMzOTQClqCoclyvWOFB+L/SwGEIJf7LSCEr\nwoBuJifJc8xAVr0XX0JthoW+uP91eTQ3XpsAEQEAAYkBPQQYAQIACQUCUmEvTgIb\nLgCoCRBKY2E6TW5AlJ0gBBkBAgAGBQJSYS9OAAoJEOCE90RsICyXuqIEANmmiRCA\nSF7YK7PvFkieJNwzeK0V3F2lGX+uu6Y3Q/Zxdtwc4xR+me/CSBmsURyXTO29OWhP\nGLszPH9zSJU9BdDi6v0yNprmFPX/1Ng0Abn/sCkwetvjxC1YIvTLFwtUL/7v6NS2\nbZpsUxRTg9+cSrMWWSNjiY9qUKajm1tuzPDZXAUEAMNmAN3xXN/Kjyvj2OK2ck0X\nW748sl/tc3qiKPMJ+0AkMF7Pjhmh9nxqE9+QCEl7qinFqqBLjuzgUhBU4QlwX1GD\nAtNTq6ihLMD5v1d82ZC7tNatdlDMGWnIdvEMCv2GZcuIqDQ9rXWs49e7tq1NncLY\nhz3tYjKhoFTKEIq3y3Pp\n=h/aX\n-----END PGP PUBLIC KEY BLOCK-----" + }, + { + "encryptedSignedMessage": "-----BEGIN PGP MESSAGE-----\n\nwV4DORo8tfpklnYSAQdAGZTE0CEOk3tK7hRfNjUGPulYp4hL8Y6fScvEj0BdfxAw\nlwQbcr1OhOx4eJKMkY7SK8wFTzkNX4N0FbGcebeZxHm1cP/EI6qwwOxb+XCsuVEK\n0sAUAfVy+S+FAzZpYbq57UUOXAi+be8Q4Q80ZUyFuGHDi0ks8D6b/19WV6tuUBv9\npx9X01IUcUht9rDCYTRhMRDSaUVXYf1JKEeLEiHVbG9GDXOGg167A5ajuiDM3T3G\n6ZSYdt5LSfca5J/RKIhLUvcJzzWeZMPnT0R7s9HhtOAI6eJaGY+YqEg4ilyppc9i\nW2BVaT/cYvApTt7E4ty2feoeHN/ZNBmySasNTLXhANTESEQg0MEU03w7H/yGtC67\nXKgzviBg3KJoO3o/wofmAlmS99jIxAI=\n=WzB1\n-----END PGP MESSAGE--------", + "message": "test\u554f\u91cf\u9bae\u63a7\u5230\u6848\u9032\u5e73\n", + "name": "curve25519", + "password": "", + "privateKey": "-----BEGIN PGP PRIVATE KEY BLOCK-----\n\nlFgEYvTtQBYJKwYBBAHaRw8BAQdAxsNXLbrk5xOjpO24VhOMvQ0/F+JcyIkckMDH\nX3FIGxcAAQDFOlunZWYuPsCx5JLp78vKqUTfgef9TGG4oD6I/Sa0zBMstCJHb2xh\nbmcgR29waGVyIDxnb2xhbmdAZXhhbXBsZS5vcmc+iJAEExYIADgWIQSFQHEOazmo\nh1ldII4MvfnLQ4JBNwUCYvTtQAIbAwULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAK\nCRAMvfnLQ4JBN5yeAQCKdry8B5ScCPrev2+UByMCss7Sdu5RhomCFsHdNPLcKAEA\n8ugei+1owHsV+3cGwWWzKk6sLa8ZN87i3SKuOGp9DQycXQRi9O1AEgorBgEEAZdV\nAQUBAQdA5CubPp8l7lrVQ25h7Hx5XN2C8xanRnnpcjzEooCaEA0DAQgHAAD/Rpc+\nsOZUXrFk9HOWB1XU41LoWbDBoG8sP8RWAVYwD5AQRYh4BBgWCAAgFiEEhUBxDms5\nqIdZXSCODL35y0OCQTcFAmL07UACGwwACgkQDL35y0OCQTcvdwEA7lb5g/YisrEf\niq660uwMGoepLUfvtqKzuQ6heYe83y0BAN65Ffg5HYOJzUEi0kZQRf7OhdtuL2kJ\nSRXn8DmCTfEB\n=cELM\n-----END PGP PRIVATE KEY BLOCK-----", + "publicKey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nxjMEYvTtQBYJKwYBBAHaRw8BAQdAxsNXLbrk5xOjpO24VhOMvQ0/F+JcyIkckMDH\nX3FIGxfNIkdvbGFuZyBHb3BoZXIgPGdvbGFuZ0BleGFtcGxlLm9yZz7CkAQTFggA\nOBYhBIVAcQ5rOaiHWV0gjgy9+ctDgkE3BQJi9O1AAhsDBQsJCAcCBhUKCQgLAgQW\nAgMBAh4BAheAAAoJEAy9+ctDgkE3nJ4BAIp2vLwHlJwI+t6/b5QHIwKyztJ27lGG\niYIWwd008twoAQDy6B6L7WjAexX7dwbBZbMqTqwtrxk3zuLdIq44an0NDM44BGL0\n7UASCisGAQQBl1UBBQEBB0DkK5s+nyXuWtVDbmHsfHlc3YLzFqdGeelyPMSigJoQ\nDQMBCAfCeAQYFggAIBYhBIVAcQ5rOaiHWV0gjgy9+ctDgkE3BQJi9O1AAhsMAAoJ\nEAy9+ctDgkE3L3cBAO5W+YP2IrKxH4quutLsDBqHqS1H77ais7kOoXmHvN8tAQDe\nuRX4OR2Dic1BItJGUEX+zoXbbi9pCUkV5/A5gk3xAQ==\n=OBcx\n-----END PGP PUBLIC KEY BLOCK-----" + }, + { + "encryptedSignedMessage": "-----BEGIN PGP MESSAGE-----\n\nwXYDUzQX5WbZ2DUSAcdAL8N/KsIV0/O1DJTQ9ZX2gSet+z4fpHlNGS0md8GNF8++\n7pt+XXGykKLnXDxtbMOQJGRYGbL0svgwpoHwU8eRXgDdcMr4GQwGlAz1GGQqfVyu\nkLwA3PRXR2zMtVQ3EU1+H4zrKcizA49j0sBHAQbGxg1ZU+6nbGXBLYdLs6y4+WcO\nozq45Jucl9cABn8uX9P4hXnncCAnMpMn4DkCPdQ9L+sTLER3WmqLvu/jQ7gMiqgs\nPkvgmt/qtl1NhS6ghnqrRSg3rxAZC9cA2hWV2cmLX9/h7MeiCn/78FIfH5XWOxKW\nMN9zYgzaMZ3M12hbw3/ma/qtXeHc1TeJ7fYGfwDBHIEFUPHtj685XfztcYsPoBi7\nQK4E+RFrpfyzhoU5HYJGsuuQXZIXAQlN6qCM1s9QwVk3zDw4d+H9JJI7YN3FoBrV\n5O9T2aU8Vnnpk6t+sDDZuoW8Igd/OYJkZGspm9ExY0n1mOVEW28NecVtW2nbmcDn\nwLg=\n=A1LE\n-----END PGP MESSAGE--------", + "message": "test\u554f\u91cf\u9bae\u63a7\u5230\u6848\u9032\u5e73\n", + "name": "curve448", + "password": "", + "privateKey": "-----BEGIN PGP PRIVATE KEY BLOCK-----\n\n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\nQgqsfguR1PqPuJxpXV4bSr6CGAAAAA==\n=MSvh\n-----END PGP PRIVATE KEY BLOCK-----\n", + "publicKey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nxkYEYV2UmRYDK2VxAc9AFyxgh5xnSbyt50TWl558mw9xdMN+/UBLr5+UMP8IsrvV\nMdXuTIE8CyaUQKSotHtH2RkYEXj5nsMAzRFDdXJ2ZTQ0OCBPcHRpb24gOMLARwQT\nFgoAhgWCYV2UmQWJBaSPvQMLCQcJEKaZmJX6us5SRxQAAAAAAB4AIHNhbHRAbm90\nYXRpb25zLnNlcXVvaWEtcGdwLm9yZw+eVLGDMxOjgAj/poMOc70/gQYKJfP7hUIx\n4OJK41AjAxUKCAKZAQKbAQIeARYhBMHbZdWA17kiclRLHqaZmJX6us5SAAD0CQOX\nQNF6sM5/9LsAqkZbDlQgd3+TTo9XsSp5Qb0cNMU9VQXntcsb5VLeXiAKAsVqEZVg\nSaG9005IZvQBAPHcocD5shn1h8z8OTRRt8bF62XTVzb4rfVjFQD3JrMke7Xl7g5S\nSLcUQHSSXP6nyWFWwRaGja4WAAAAzkYEYV2UmRYDK2VxAc9AS+0x2fTJwi30jydR\nTC4pvXhpENwX5zN4XuazsDt7deECNS4yPejo5HiYqTIHBQJhD9Qav2Rxju6AwsEs\nBBgWCgFrBYJhXZSZBYkFpI+9CRCmmZiV+rrOUkcUAAAAAAAeACBzYWx0QG5vdGF0\naW9ucy5zZXF1b2lhLXBncC5vcmcURFIs3U3HqQc4EjDr9lVkAlm5fvtwmRG4AFg6\n8r0rMgKbAsAxoAQZFgoAbwWCYV2UmQkQSqN0i+EuXY5HFAAAAAAAHgAgc2FsdEBu\nb3RhdGlvbnMuc2VxdW9pYS1wZ3Aub3JnpqBkw45jUIiM8OhzhHXb0tDge1Gfz9O4\nzPxFZp2MU5QWIQQMbJ5BqcdVAoh+oFFKo3SL4S5djgAAC2EDl0ADAdaCqjlsUhZr\n5oAijXGwAMgN6YheRESKrOM9O6Cb6g2Kzlre+VLBCB/9OIFP7x3OBJnxtiQyyYDi\n5ck1D5C/9PH9MnRmT6HxUAYZ/B5LhRY5zbSeaM9xiD+HHVjEBWlMNLr42zzPU5t6\nbRDRnek7NQAAABYhBMHbZdWA17kiclRLHqaZmJX6us5SAABqUwOXQE0eolNhq6VH\nEIKRvianwLBpheMgDoYBcXNVH4nRZN507qd3y4I4qZGGNe9Fi0TW8ipFTkqHcfdx\ngLOfK4sztWnAwFoi6t3MhDwRV28hU4W3DT3C5fv++1JP5oJGKz0BQEFnStZEkZuE\noswwSt+yvAoQAAAAzkkEYV2UmRIDK2VvAcdAzIc74UKPjrF3qkOkFcOLBQmnsR4e\nqy6IYe08y94UEo6Cdu31eiQZCFnZ9C36zJSSA20r9ki3ZYEDAQoJwsA5BBgWCgB4\nBYJhXZSZBYkFpI+9CRCmmZiV+rrOUkcUAAAAAAAeACBzYWx0QG5vdGF0aW9ucy5z\nZXF1b2lhLXBncC5vcmcSMExACZz/a7jm7kVAaYFADK2NIbYszclA3489WXm8SQKb\nDBYhBMHbZdWA17kiclRLHqaZmJX6us5SAABJ1gOXQNcQdD3KQ0/atk/fkRPV2aIV\nqAW1OP+EtBxaWDXouq8k0ao6Bhe6pN6tuI463TZcsIGWf7B17ClTgKER98xQNEF+\np9Byj9iwypZXtsckcA7R4L8MwoKPJT3TF0ftH91CCqx+C5HU+o+4nGldXhtKvoIY\nAAAA\n=qcl0\n-----END PGP PUBLIC KEY BLOCK-----" + }, + { + "encryptedSignedMessage": "-----BEGIN PGP MESSAGE-----\n\nwcBOA1N4OCSSjECBEAP8DhX4Ii5TxauisNiJ6ThzZVo0rDrM37eG55Z9/Fp9\nwOFcMoYiM7muadPd0jjVkGk0Y0d1QrfmAW1619L3kv4lGJcB92jEVXeg6HPq\nyLVEc2KzvyIO2ypZ6CBlYhz1iWtc29tgbf1BkVjNGk8C1OIauCqQtNHDwpso\ntFF29gfHKbwEAIkeoyCs85tAyJnNWMrEyMo+GSico4uVEiJCw4DD65O4pW3Y\ns0PUj9HhE8CY01zKADsn9CHo2P0eppbw/7H++ViHdFzkcbrz6Tqt43tC9B29\nNBPdnhMlyJJhivW1FvLoPpuLiYpNb9Dv2lTpug5UUVZR6q9HTuvhP7PJuo5J\n3MIh0qsByqXXlrAZuvZtIZVYX9hFLK7AlLQ4BIbJ5ZoTDOMlamviiKEs/Txj\npBbKbBAQW+fw6ajsKSNoWPqYriVEOGtKCfmrCTe32W0Diifyap7VbsY5q9yK\n07XbMTDZgtxByDMJ9YLdjG2+J9jkQyKoh8SioWZCeRwsUJOjMTVdfbDAeNId\n7me65b7rhtbiR3lU60l5CANdQi+cHTyh3azeFqUqZ5UFNEY8mUXzVWw=\n=+rSf\n-----END PGP MESSAGE-----", + "message": "test\u554f\u91cf\u9bae\u63a7\u5230\u6848\u9032\u5e73", + "name": "dsa", + "password": "abcd", + "privateKey": "-----BEGIN PGP PRIVATE KEY BLOCK-----\n\nlQHhBFERnrMRBADmM0hIfkI3yosjgbWo9v0Lnr3CCE+8KsMszgVS+hBu0XfGraKm\nivcA2aaJimHqVYOP7gEnwFAxHBBpeTJcu5wzCFyJwEYqVeS3nnaIhBPplSF14Duf\ni6bB9RV7KxVAg6aunmM2tAutqC+a0y2rDaf7jkJoZ9gWJe2zI+vraD6fiwCgxvHo\n3IgULB9RqIqpLoMgXfcjC+cD/1jeJlKRm+n71ryYwT/ECKsspFz7S36z6q3XyS8Q\nQfrsUz2p1fbFicvJwIOJ8B20J/N2/nit4P0gBUTUxv3QEa7XCM/56/xrGkyBzscW\nAzBoy/AK9K7GN6z13RozuAS60F1xO7MQc6Yi2VU3eASDQEKiyL/Ubf/s/rkZ+sGj\nyJizBACtwCbQzA+z9XBZNUat5NPgcZz5Qeh1nwF9Nxnr6pyBv7tkrLh/3gxRGHqG\n063dMbUk8pmUcJzBUyRsNiIPDoEUsLjY5zmZZmp/waAhpREsnK29WLCbqLdpUors\nc1JJBsObkA1IM8TZY8YUmvsMEvBLCCanuKpclZZXqeRAeOHJ0v4DAwK8WfuTe5B+\nM2BOOeZbN8BpfiA1l//fMMHLRS3UvbLBv4P1+4SyvhyYTR7M76Q0xPc03MFOWHL+\nS9VumbQWVGVzdDIgPHRlc3QyQHRlc3QuY29tPohiBBMRAgAiBQJREZ6zAhsDBgsJ\nCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRARJ5QDyxae+MXNAKCzWSDR3tMrTrDb\nTAri73N1Xb3j1ACfSl9y+SAah2q7GvmiR1+6+/ekqJGdAVgEURGesxAEANlpMZjW\n33jMxlKHDdyRFXtKOq8RreXhq00plorHbgz9zFEWm4VF53+E/KGnmHGyY5Cy8TKy\nZjaueZZ9XuG0huZg5If68irFfNZtxdA26jv8//PdZ0Uj+X6J3RVa2peMLDDswTYL\nOL1ZO1fxdtDD40fdAiIZ1QyjwEG0APtz41EfAAMFBAC5/dtgBBPtHe8UjDBaUe4n\nNzHuUBBp6XE+H7eqHNFCuZAJ7yqJLGVHNIaQR419cNy08/OO/+YUQ7rg78LxjFiv\nCH7IzhfU+6yvELSbgRMicY6EnAP2GT+b1+MtFNa3lBGtBHcJla52c2rTAHthYZWk\nfT5R5DnJuQ2cJHBMS9HWyP4DAwK8WfuTe5B+M2C7a/YJSUv6SexdGCaiaTcAm6g/\nPvA6hw/FLzIEP67QcQSSTmhftQIwnddt4S4MyJJH3U4fJaFfYQ1zCniYJohJBBgR\nAgAJBQJREZ6zAhsMAAoJEBEnlAPLFp74QbMAn3V4857xwnO9/+vzIVnL93W3k0/8\nAKC8omYPPomN1E/UJFfXdLDIMi5LoA==\n=LSrW\n-----END PGP PRIVATE KEY BLOCK-----", + "publicKey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nxsDiBFERnrMRBADmM0hIfkI3yosjgbWo9v0Lnr3CCE+8KsMszgVS+hBu0XfG\nraKmivcA2aaJimHqVYOP7gEnwFAxHBBpeTJcu5wzCFyJwEYqVeS3nnaIhBPp\nlSF14Dufi6bB9RV7KxVAg6aunmM2tAutqC+a0y2rDaf7jkJoZ9gWJe2zI+vr\naD6fiwCgxvHo3IgULB9RqIqpLoMgXfcjC+cD/1jeJlKRm+n71ryYwT/ECKss\npFz7S36z6q3XyS8QQfrsUz2p1fbFicvJwIOJ8B20J/N2/nit4P0gBUTUxv3Q\nEa7XCM/56/xrGkyBzscWAzBoy/AK9K7GN6z13RozuAS60F1xO7MQc6Yi2VU3\neASDQEKiyL/Ubf/s/rkZ+sGjyJizBACtwCbQzA+z9XBZNUat5NPgcZz5Qeh1\nnwF9Nxnr6pyBv7tkrLh/3gxRGHqG063dMbUk8pmUcJzBUyRsNiIPDoEUsLjY\n5zmZZmp/waAhpREsnK29WLCbqLdpUorsc1JJBsObkA1IM8TZY8YUmvsMEvBL\nCCanuKpclZZXqeRAeOHJ0s0WVGVzdDIgPHRlc3QyQHRlc3QuY29tPsJiBBMR\nAgAiBQJREZ6zAhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRARJ5QD\nyxae+MXNAKCzWSDR3tMrTrDbTAri73N1Xb3j1ACfSl9y+SAah2q7GvmiR1+6\n+/ekqJHOwE0EURGesxAEANlpMZjW33jMxlKHDdyRFXtKOq8RreXhq00plorH\nbgz9zFEWm4VF53+E/KGnmHGyY5Cy8TKyZjaueZZ9XuG0huZg5If68irFfNZt\nxdA26jv8//PdZ0Uj+X6J3RVa2peMLDDswTYLOL1ZO1fxdtDD40fdAiIZ1Qyj\nwEG0APtz41EfAAMFBAC5/dtgBBPtHe8UjDBaUe4nNzHuUBBp6XE+H7eqHNFC\nuZAJ7yqJLGVHNIaQR419cNy08/OO/+YUQ7rg78LxjFivCH7IzhfU+6yvELSb\ngRMicY6EnAP2GT+b1+MtFNa3lBGtBHcJla52c2rTAHthYZWkfT5R5DnJuQ2c\nJHBMS9HWyMJJBBgRAgAJBQJREZ6zAhsMAAoJEBEnlAPLFp74QbMAn3V4857x\nwnO9/+vzIVnL93W3k0/8AKC8omYPPomN1E/UJFfXdLDIMi5LoA==\n=Oa9H\n-----END PGP PUBLIC KEY BLOCK-----" + }, + { + "encryptedSignedMessage": "-----BEGIN PGP MESSAGE-----\n\nwX4DaAJTyVNTRvUSAgMERFsv0Org2FHPu0n5k6xNOv520Yh2dk2SDojc6cF3\nynPgyMftshAfmDQZ6zPDwW1Ya8EB9ihsXcbjBg4Uf1xoBjCdQHkVTjI39ehZ\nfLltFzdpW+HDNL7zx9TjqBi/dWwOIJCSccIb1iOTiCTXTh9X35LSwAMBJtMw\nmTOOAKH9hKXOTYIH3EWasWWzSOOqdndYppsiPYBFAUzq9IK07FNU2wb4/Pat\nqILTqW783FiECucSCw1gkpWEJVxmh9+gys0JrHLruECY88jK1ujcZL7079gU\n546Txtrs8qRso4zCoD/MWwawgdIzdHuRwTo/rwt7jMjMfinZ8/t/CIxyCqva\nwLtajClaU5zuD3uGSagSQ6ZZ2ga5Q9XStzZfsuS3yW6qfD/b3Vu3SuRoZGjV\nYUjQR2GU+zI4qrw=\n=GXt6\n-----END PGP MESSAGE-----", + "message": "test\u554f\u91cf\u9bae\u63a7\u5230\u6848\u9032\u5e73", + "name": "p256", + "password": "", + "privateKey": "-----BEGIN PGP PRIVATE KEY BLOCK-----\n\nlHcEWqvIexMIKoZIzj0DAQcCAwTHEN/Yb0iLnIdL1TZcPDB2k+KqSnMlOxiK2YwV\nxd9or0tNccGkt7Sg3NcNua7X/YW45Vgkxq0p9lf3pJsepydVAAD/SqMfMs2IAGx3\nm0Sv1Jr43iXBQUZiSW+YXfIvqFNm+s8RDrQfdGVzdCB0ZXN0IDxtY3Rlc3R5QGV4\nYW1wbGUub3JnPoiQBBMTCAA4FiEECufLSTsSypWIu9QDLSA1IA76sfYFAlqryHsC\nGwMFCwkIBwIGFQgJCgsCBBYCAwECHgECF4AACgkQLSA1IA76sfYnfgD+PPwhSbEA\nJaE1Vh4UIea7TYAz3XnZp9F8C6B9NWPQWT0A/R0630k5W9uSmdlqP16+LXzs4rdM\nbGxSVSBcXVyx9eIWnHsEWqvIexIIKoZIzj0DAQcCAwTwYoek6LUhUSC1ApqXj6xk\nCYRH8H+sFLWAcf+P+zMvaR6v49qJl6Y1VYtEgwnpNnQhcx7Vrlul0FkisFqcwfXV\nAwEIBwAA/j5HDZp4rZI4KIni08VGpl70oRWdud8zrP8lQpsqZ6n8EYOIeAQYEwgA\nIBYhBArny0k7EsqViLvUAy0gNSAO+rH2BQJaq8h7AhsMAAoJEC0gNSAO+rH2hhkA\n/14tdEa6SuCtFC0vE+c8pAcs2YNiu3cXDFEyg/3PAUbQAQCOfvI2+mawOH6GJzsz\nvhrof7LYUWcOUggO69XoCM9Log==\n=bVnA\n-----END PGP PRIVATE KEY BLOCK-----", + "publicKey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nxlIEWqvIexMIKoZIzj0DAQcCAwTHEN/Yb0iLnIdL1TZcPDB2k+KqSnMlOxiK\n2YwVxd9or0tNccGkt7Sg3NcNua7X/YW45Vgkxq0p9lf3pJsepydVzR90ZXN0\nIHRlc3QgPG1jdGVzdHlAZXhhbXBsZS5vcmc+wpAEExMIADgWIQQK58tJOxLK\nlYi71AMtIDUgDvqx9gUCWqvIewIbAwULCQgHAgYVCAkKCwIEFgIDAQIeAQIX\ngAAKCRAtIDUgDvqx9id+AP48/CFJsQAloTVWHhQh5rtNgDPdedmn0XwLoH01\nY9BZPQD9HTrfSTlb25KZ2Wo/Xr4tfOzit0xsbFJVIFxdXLH14hbOVgRaq8h7\nEggqhkjOPQMBBwIDBPBih6TotSFRILUCmpePrGQJhEfwf6wUtYBx/4/7My9p\nHq/j2omXpjVVi0SDCek2dCFzHtWuW6XQWSKwWpzB9dUDAQgHwngEGBMIACAW\nIQQK58tJOxLKlYi71AMtIDUgDvqx9gUCWqvIewIbDAAKCRAtIDUgDvqx9oYZ\nAP9eLXRGukrgrRQtLxPnPKQHLNmDYrt3FwxRMoP9zwFG0AEAjn7yNvpmsDh+\nhic7M74a6H+y2FFnDlIIDuvV6AjPS6I=\n=UrtO\n-----END PGP PUBLIC KEY BLOCK-----" + }, + { + "encryptedSignedMessage": "-----BEGIN PGP MESSAGE-----\n\nwZ4D3tsm495R/DQSAwME/mB7VDjlMWyJzp0BHit9A4M5hFCHSSI70xNeXAqP\n+eziDTAoo/J3ulVEVx6dyBvXizBxHIz4F5y8eQPfiz8zgj7572z5kuH+/OIh\n+AZzEdIxnH/WkQs9sT9N9ostKXUMMOimh1DXPfEExQ4IFTZDnVEA3fCpFmFO\nPzNAArFWk8lM1AaEbu5Z3EPTWQRbY6YSrdLAIwFa2gjuQViUs4akHiCXNq6C\nN31ebwBwR/Ax6BKE8H8ruYzNFviz5o6Uw0oDCbaSR9hRTptnA9Hfvr+Dp9XT\n/ojG+YYq/gOQSycYB2MrBiIFRkuxwB86Ngq/WNSzMxtsjod4BlCtqmSys9Sj\n/D9VySgTdvfBR6ukYMrC0lOgZBkJNtQNuY5qFePTTWQ4s9CdvSKoQQRbUY2V\nNWIGuDOafFICpaD5/VQakAemYOCBKks3F+zbHW6M7g/dYt9zZECOCBwrH+1m\nho/jQzANS17yy2O17IXqZUvroDfNjdY8Rw/fiCTL\n=cfGW\n-----END PGP MESSAGE-----", + "message": "test\u554f\u91cf\u9bae\u63a7\u5230\u6848\u9032\u5e73", + "name": "p384", + "password": "", + "privateKey": "-----BEGIN PGP PRIVATE KEY BLOCK-----\n\nlKQEWqvMThMFK4EEACIDAwQeVELezSIjmPkpfo3QejOWQwPxxaA6xnh3Lgu0zoTz\njbYeE6xFejlLMuHGRs/msuwkqRIEKPufVxDA9t4llIClJus82Bei2FV6gF+21xdI\nMynwilwV6hwya6TWWC6sqOMAAX9cMXKSG1fl2yoKM9G95d6+YENwzp5EdOtCKrtL\nBOPuEu4yXVNQAxlep0+/MFirWwYWF7QeQmlnIHRlc3QgPEJpZ1Rlc3RAZXhhbXBs\nZS5vcmc+iLAEExMJADgWIQRz2hPLfQfnxiN4c7b9XzwLxz7JSgUCWqvMTgIbAwUL\nCQgHAgYVCAkKCwIEFgIDAQIeAQIXgAAKCRD9XzwLxz7JSnyOAYCc0Qm/4TukrxOz\n9quCjxjXUC0FJBePOuXjMFHpbRC8/w6Sm/MmL2wv4ogfUlseh2EBgJvUcPmuLG5P\nY/sbpPFa6lkWe9mqWNZA55u8FrO8NhmoT4vpGT/L79PeFZayXR+T0pyoBFqrzE4S\nBSuBBAAiAwMEB/yKSJ1l7arWVl3fCt1NmQpGHl3kahzKcymyA0aBisLs9igedPyp\nEEnbahUpiwfrQR41hLeL07co/3dceUkcCyuuaJSnR1PrGx/WATvfHVjjFFXJK0L3\nlhHwWUISBre+AwEJCQABgN/tAcIomJ3bqh76v8X962P1h/w0W4K9K8F+cypjCm4S\n8a0Xfs7oMRTd/FDLU6OB3huKiJgEGBMJACAWIQRz2hPLfQfnxiN4c7b9XzwLxz7J\nSgUCWqvMTgIbDAAKCRD9XzwLxz7JSnwTAX9bU7QVcIKnNq15eydUBDsblk0exu0C\nepKHjB4WAp8UDKREvG4jhMxlvEa12vWb3yMBgMCjAgQ7WdzJanTZLi6bNyGO3ptg\ng++gKRKmxI7Jg0+oAOcL4v2iuUx6Yo66T67gCg==\n=CW/l\n-----END PGP PRIVATE KEY BLOCK-----", + "publicKey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nxm8EWqvMThMFK4EEACIDAwQeVELezSIjmPkpfo3QejOWQwPxxaA6xnh3Lgu0\nzoTzjbYeE6xFejlLMuHGRs/msuwkqRIEKPufVxDA9t4llIClJus82Bei2FV6\ngF+21xdIMynwilwV6hwya6TWWC6sqOPNHkJpZyB0ZXN0IDxCaWdUZXN0QGV4\nYW1wbGUub3JnPsKwBBMTCQA4FiEEc9oTy30H58YjeHO2/V88C8c+yUoFAlqr\nzE4CGwMFCwkIBwIGFQgJCgsCBBYCAwECHgECF4AACgkQ/V88C8c+yUp8jgGA\nnNEJv+E7pK8Ts/argo8Y11AtBSQXjzrl4zBR6W0QvP8OkpvzJi9sL+KIH1Jb\nHodhAYCb1HD5rixuT2P7G6TxWupZFnvZqljWQOebvBazvDYZqE+L6Rk/y+/T\n3hWWsl0fk9LOcwRaq8xOEgUrgQQAIgMDBAf8ikidZe2q1lZd3wrdTZkKRh5d\n5GocynMpsgNGgYrC7PYoHnT8qRBJ22oVKYsH60EeNYS3i9O3KP93XHlJHAsr\nrmiUp0dT6xsf1gE73x1Y4xRVyStC95YR8FlCEga3vgMBCQnCmAQYEwkAIBYh\nBHPaE8t9B+fGI3hztv1fPAvHPslKBQJaq8xOAhsMAAoJEP1fPAvHPslKfBMB\nf1tTtBVwgqc2rXl7J1QEOxuWTR7G7QJ6koeMHhYCnxQMpES8biOEzGW8RrXa\n9ZvfIwGAwKMCBDtZ3MlqdNkuLps3IY7em2CD76ApEqbEjsmDT6gA5wvi/aK5\nTHpijrpPruAK\n=DewR\n-----END PGP PUBLIC KEY BLOCK-----" + }, + { + "encryptedSignedMessage": "-----BEGIN PGP MESSAGE-----\n\nwcACAzZYNqaBUBrOEgQjBABmhjNT+HfNdK3mVvVRpIbP8BACPUzmnFagNzd7\nd4jFqfRrP3Il3ohx+scNEYxFgloGOooukRJXASauk4MUXgvpFAFtycVNTT3N\nqgeBQi+j1BLBV53KG+nyxEQRGR9QaDgYjjH0OvyhJbe7Ov9ESwN1nMUH4/Xq\n+Zm/vy3/ysRCZ/AYMDAYB238b3OAPjLsAJ+VR6YgJfYK1R4B6+bKM/LufZ7U\nN2w6dZtOANXgTfd9JMxIscXSwEUB3YZCrnOsII6ryhkZybjK00n0PRQS4jgK\nJ8zdv5MvTdYfD3sVPjp7dnZwsuwkipnZEt0l/nMtVvI7l6XnN04RNEK4Elor\nD5qRKRI0XcFLaJaZWDh51Lp0kE0iGTkzDI0zhnzh1TY0UyqpTfQ9hvvGWY+s\n71oUqUMv3Izq13LUGDFAru9WXxDddATmSZR4PAmUZnHL5cDBK+pLHLjE31c9\nIYFwLjDNuBVJ+1BAtP9kvi70j0Nvv7yFfWeeXCN2IFsWe2Mk9108GX5Ls+ta\nnmZtITdvQ334bCuvYkxUhdZxZZ4z134uHdqbC0BPtE9ohRUsuhXi+dxXE3Iz\nUC6h8O9obgz9IN4=\n=/S1e\n-----END PGP MESSAGE-----", + "message": "test\u554f\u91cf\u9bae\u63a7\u5230\u6848\u9032\u5e73", + "name": "p521", + "password": "", + "privateKey": "-----BEGIN PGP PRIVATE KEY BLOCK-----\n\nlNoEWqvMtxMFK4EEACMEIwQBG+c6zB/CLvG89VPlwcEf7lVw1o/USkR1BKwdEJX+\n6kevyCfoW9i5fr3Xj3te/KEkWbm53fYCxQxT1YPCOEQe2/gAyH+Sa+xIL3WFxOho\nHLumFLsjhTxI1HO24UNYwrtanYUXJUyZdBdbE3iC0S0pDEVYvCNjNU85tqS5lulh\nMc6xIp0AAgkBlHig6LGOdFTLzkeKJZPTvb//E6lBXuvfbA4plFlg907OM7c6PEUv\nnoRvR4cNkKDdShJxBPVlZVkbtiMT7H3JmcEgCLQbdGVzdGVyIEEgPHRlc3RAZXhh\nbXBsZS5vcmc+iNIEExMKADgWIQSlBvevyCa+SH3t6owrJYFtVTYvDgUCWqvMtwIb\nAwULCQgHAgYVCAkKCwIEFgIDAQIeAQIXgAAKCRArJYFtVTYvDn1FAgYuArHX6gpb\nFPIo97qMNU+OumyOgzn93w7RzdJUF1Pvyjfib8qAoFc2RbD2B4O10UcFGR/tvY7I\nswYU25vwrdpkwQIIy2gvuosOwiHwWOGq7vIPhZX2XiUUCg/IhbbiQVYHMJXgWFuq\nm5xbfo5yjBk7fAxyzg2JUbwr5odAGKkXM2zeVFuc3QRaq8y3EgUrgQQAIwQjBAFw\n35IH/Ksef/VTf0fuv831HngtCqcW3EBKZT2+eiQcfk6I0mY7fNIHRT2tsc9QBTNk\nD5smT8CX9ZnolCOetL9EywBJZp1c3yns6Nk091/K1d9kCLkckRbIm0yS6+/qA0cM\nFZZbRfIGHXgyKkG2IZ3diWQIGkYiwx8VtZzmVD/Nx5PhdgMBCgkAAgdbkreSrYh8\n5kgEQQsJyLOJVPyS11qqyegmBOOXu4+KDXaKNXeLRszk7mMCdUxLqUeWMrivy0vl\nSerrL+aazVYaDCEqiLoEGBMKACAWIQSlBvevyCa+SH3t6owrJYFtVTYvDgUCWqvM\ntwIbDAAKCRArJYFtVTYvDq2GAgj3HDhyoxdsFkWJ986M7zTijTDHR3D4SVoRu5IB\nAsIGDpKBYdYzOEweShT0usxORdha1pIX1M1h8nk6CMm3WHhcxAIHWV9aOdGIhvcT\nFBv4c44lu5xpvsgGgmDaIwlgLunElVMnKSXrO9Hqpn9a+pRJv5Be/BsZOW82Y2f7\n/K7TUzYNJa4=\n=dTWU\n-----END PGP PRIVATE KEY BLOCK-----", + "publicKey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nxpMEWqvMtxMFK4EEACMEIwQBG+c6zB/CLvG89VPlwcEf7lVw1o/USkR1BKwd\nEJX+6kevyCfoW9i5fr3Xj3te/KEkWbm53fYCxQxT1YPCOEQe2/gAyH+Sa+xI\nL3WFxOhoHLumFLsjhTxI1HO24UNYwrtanYUXJUyZdBdbE3iC0S0pDEVYvCNj\nNU85tqS5lulhMc6xIp3NG3Rlc3RlciBBIDx0ZXN0QGV4YW1wbGUub3JnPsLA\nEgQTEwoAOBYhBKUG96/IJr5Ife3qjCslgW1VNi8OBQJaq8y3AhsDBQsJCAcC\nBhUICQoLAgQWAgMBAh4BAheAAAoJECslgW1VNi8OfUUCBi4CsdfqClsU8ij3\nuow1T466bI6DOf3fDtHN0lQXU+/KN+JvyoCgVzZFsPYHg7XRRwUZH+29jsiz\nBhTbm/Ct2mTBAgjLaC+6iw7CIfBY4aru8g+FlfZeJRQKD8iFtuJBVgcwleBY\nW6qbnFt+jnKMGTt8DHLODYlRvCvmh0AYqRczbN5UW86XBFqrzLcSBSuBBAAj\nBCMEAXDfkgf8qx5/9VN/R+6/zfUeeC0KpxbcQEplPb56JBx+TojSZjt80gdF\nPa2xz1AFM2QPmyZPwJf1meiUI560v0TLAElmnVzfKezo2TT3X8rV32QIuRyR\nFsibTJLr7+oDRwwVlltF8gYdeDIqQbYhnd2JZAgaRiLDHxW1nOZUP83Hk+F2\nAwEKCcK6BBgTCgAgFiEEpQb3r8gmvkh97eqMKyWBbVU2Lw4FAlqrzLcCGwwA\nCgkQKyWBbVU2Lw6thgII9xw4cqMXbBZFiffOjO804o0wx0dw+ElaEbuSAQLC\nBg6SgWHWMzhMHkoU9LrMTkXYWtaSF9TNYfJ5OgjJt1h4XMQCB1lfWjnRiIb3\nExQb+HOOJbucab7IBoJg2iMJYC7pxJVTJykl6zvR6qZ/WvqUSb+QXvwbGTlv\nNmNn+/yu01M2DSWu\n=KibM\n-----END PGP PUBLIC KEY BLOCK-----" + }, + { + "encryptedSignedMessage": "-----BEGIN PGP MESSAGE-----\n\nwX4DDYFqRW5CSpsSAgMEre9Mf7Ig5et7Z+E6dTM/pTEKD8cEIfuW5yV8RL2X\n3FqGkGbhpmxgyIrWvf3cJhhmusdkzl+AisnGz71bVgfBYjCfe3olAfyvlZUj\n0Bf0pPh9EfdD5dFLf5XXXkXdOwuUeW6UffdrYA14avHkjqmoE9fSwAMBc4Pm\nEiOLe7iNZgYtTzvCXei213SCdN17sixw5c/iroY1QuFwECMdlQG0X9qG4Ddp\nJCnW+dPZ9lHCD+NyaWoY3QAAMyMTlMeysj19o7LHG4+fgUZL2XBGcrnZkgmF\ni4YsT9fLuVf/cPPnBI+grrQPNEzZim1FgBjcgD7QgoPbrATtq63lEyJbjwAw\nKC38/emOZdwHe3RyhJSFdJ0B6VVz9nJuYvncE1VkuZVxaRhtE2FmZutBC/cy\nUMcIpI0EsrGSCI8=\n=8BUx\n-----END PGP MESSAGE-----", + "message": "test\u554f\u91cf\u9bae\u63a7\u5230\u6848\u9032\u5e73", + "name": "secp256k1", + "password": "juliet", + "privateKey": "-----BEGIN PGP PRIVATE KEY BLOCK-----\n\nxaIEVjET2xMFK4EEAAoCAwS/zT2gefLhEnISXN3rvdV3eD6MVrPwxNMAR+LM\nZzFO1gdtZbf7XQSZP02CYQe3YFrNQYYuJ4CGkTvOVJSV+yrA/gkDCILD3FP2\nD6eRYNWhI+QTFWAGDw+pIhtXQ/p0zZgK6HSk68Fox0tH6TlGtPmtULkPExs0\ncnIdAVSMHI+SnZ9lIeAykAcFoqJYIO5p870XbjzNLlJvbWVvIE1vbnRhZ3Vl\nIChzZWNwMjU2azEpIDxyb21lb0BleGFtcGxlLm5ldD7CcgQQEwgAJAUCVjET\n2wULCQgHAwkQwrEjibQBpD0DFQgKAxYCAQIbAwIeAQAA6McBAMzLoQk8VrIK\nAjhlKZmVK57fEVKGP3LqFlMtmo8mq6ylAP4wxPTRrYKJyduGt3XxJBQhhFD/\n8juhdWjFk3kwDiuY4cemBFYxE9sSBSuBBAAKAgMEs9mAYLbntFpn0GDeKP06\nBKgkbCoi7TdJ9AxOdHZyseQAeotpQibP+XCGwv9Xj33NdnaASZ+goJpyuhc0\nMzznFQMBCAf+CQMIqp5StLTK+lBgqmaJ8/64E+8+OJVOgzk8EoRp8bS9IEac\nVYu2i8ARjAF3sqwGZ5hxxsniORcjQUghf+n+NwEm9LUWfbAGUlT4YfSIq5pV\nrsJhBBgTCAATBQJWMRPbCRDCsSOJtAGkPQIbDAAA5IMBAOAd5crBXv9/ihPz\n4AkmZFH8e5+8ZSqjIqq1/lTcj3o/AQDECYLTFXbJUBy6+X4aBp0aLbNyUgtI\n9tFEEDilTl2ltw==\n=C3TW\n-----END PGP PRIVATE KEY BLOCK-----", + "publicKey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nxk8EVjET2xMFK4EEAAoCAwS/zT2gefLhEnISXN3rvdV3eD6MVrPwxNMAR+LM\nZzFO1gdtZbf7XQSZP02CYQe3YFrNQYYuJ4CGkTvOVJSV+yrAzS5Sb21lbyBN\nb250YWd1ZSAoc2VjcDI1NmsxKSA8cm9tZW9AZXhhbXBsZS5uZXQ+wnIEEBMI\nACQFAlYxE9sFCwkIBwMJEMKxI4m0AaQ9AxUICgMWAgECGwMCHgEAAOjHAQDM\ny6EJPFayCgI4ZSmZlSue3xFShj9y6hZTLZqPJquspQD+MMT00a2Cicnbhrd1\n8SQUIYRQ//I7oXVoxZN5MA4rmOHOUwRWMRPbEgUrgQQACgIDBLPZgGC257Ra\nZ9Bg3ij9OgSoJGwqIu03SfQMTnR2crHkAHqLaUImz/lwhsL/V499zXZ2gEmf\noKCacroXNDM85xUDAQgHwmEEGBMIABMFAlYxE9sJEMKxI4m0AaQ9AhsMAADk\ngwEA4B3lysFe/3+KE/PgCSZkUfx7n7xlKqMiqrX+VNyPej8BAMQJgtMVdslQ\nHLr5fhoGnRots3JSC0j20UQQOKVOXaW3\n=VpL9\n-----END PGP PUBLIC KEY BLOCK-----" + }, + { + "encryptedSignedMessage": "-----BEGIN PGP MESSAGE-----\n\nwV4DzYfy+90rz7YSAQdAWBKmhkgx+WtyERt903WpExRZfyAhxji8FKhthTRI\nLw4w/vzk9zMULlXZSknznkPnRlJyFUHqH9gFt8e3EQlij62Kd5T5AQBc0CLC\nfLZzou7u0sADAdWRcTHqdoIZCDgbJQEW3kbZOP9kEliyrB2K1UYYgOnGyLe2\nxpZd5f14Lfieb/CyO+BoqCpRSKJFSuMo7V+MY/mpt/liEMDPr+aRsOlyf+KE\ni9OznbgOX1oXdHbyodjTe9H7OxgFi/BxH6zQlFhxappmD5I3fmp1/ONvfXf/\nKN5U4Rx7ftKgsaTMsEnKk/w8rEqxL8a1YtLe4X1tdecRBTi7qbndYeXp5lVl\n+tnmmywmjXk+mx/l+NluOga79j7pZ0zO\n=Co9x\n-----END PGP MESSAGE-----", + "message": "test\u554f\u91cf\u9bae\u63a7\u5230\u6848\u9032\u5e73", + "name": "ed25519", + "password": "sun", + "privateKey": "-----BEGIN PGP PRIVATE KEY BLOCK-----\n\nlIYEWkN+5BYJKwYBBAHaRw8BAQdAIGqj23Kp273IPkgjwA7ue5MDIRAfWLYRqnFy\nc2AFMcD+BwMCeaL+cNXzgI7uJQ7HBv53TAXO3y5uyJQMonkFtQtldL8YDbNP3pbd\n3zzo9fxU12bWAJyFwBlBWJqkrxZN+0jt0ElsG3kp+V67MESJkrRhKrQRTGlnaHQg\nPGxpZ2h0QHN1bj6IkAQTFggAOBYhBIZLQa5UQtPdGzTChx7N8CbAJFgwBQJaQ37k\nAhsDBQsJCAcCBhUICQoLAgQWAgMBAh4BAheAAAoJEB7N8CbAJFgwu14BAIVlyXyQ\nHEIfzrCaqlzb4q0QDyX+Gx+HuN34la7IW65EAPsE6LHxWjU/+G/ypRjBKfchFc94\nZe5yhZWgoaDFF37MA5yLBFpDfuQSCisGAQQBl1UBBQEBB0AvUqQcIXG0rUKC9mUi\ndP/Lo5B4J9eJjN3+Ljna3ZlHYAMBCAf+BwMCvyW2D5Yx6dbujE3yHi1XQ9MbhOY5\nXRFFgYIUYzzi1qmaL+8Gr9zODsUdeO60XHnMXOmqVa6/sdx32TWo5s3sgS19kRUM\nD+pbxS/aZnxvrYh4BBgWCAAgFiEEhktBrlRC090bNMKHHs3wJsAkWDAFAlpDfuQC\nGwwACgkQHs3wJsAkWDCe9QEA5qEE4N+qX465fNrK0ulz0ScZd6/+paVhmjYo9Fab\nQdsBANlddInzoZ8CCwsNagZXujp+2gWtue5axTPnDkjGhLIK\n=wo91\n-----END PGP PRIVATE KEY BLOCK-----", + "publicKey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmDMEWkN+5BYJKwYBBAHaRw8BAQdAIGqj23Kp273IPkgjwA7ue5MDIRAfWLYRqnFy\nc2AFMcC0EUxpZ2h0IDxsaWdodEBzdW4+iJAEExYIADgWIQSGS0GuVELT3Rs0woce\nzfAmwCRYMAUCWkN+5AIbAwULCQgHAgYVCAkKCwIEFgIDAQIeAQIXgAAKCRAezfAm\nwCRYMLteAQCFZcl8kBxCH86wmqpc2+KtEA8l/hsfh7jd+JWuyFuuRAD7BOix8Vo1\nP/hv8qUYwSn3IRXPeGXucoWVoKGgxRd+zAO4OARaQ37kEgorBgEEAZdVAQUBAQdA\nL1KkHCFxtK1CgvZlInT/y6OQeCfXiYzd/i452t2ZR2ADAQgHiHgEGBYIACAWIQSG\nS0GuVELT3Rs0wocezfAmwCRYMAUCWkN+5AIbDAAKCRAezfAmwCRYMJ71AQDmoQTg\n36pfjrl82srS6XPRJxl3r/6lpWGaNij0VptB2wEA2V10ifOhnwILCw1qBle6On7a\nBa257lrFM+cOSMaEsgo=\n=D8HS\n-----END PGP PUBLIC KEY BLOCK-----" + }, + { + "encryptedSignedMessage": "-----BEGIN PGP MESSAGE-----\n\nhH4DLFUmpfJ2kxcSAgMElfY0YbA1dI8s8MMhBHOXw0wwR/O+S8Pm/huBbkIbOb2c\nAL7ImXZYvPgS5tkpbxmItEedlLF439E8rwrPBqmrWTDSy/q9CyR2IKVSVNbConaz\nlyGGvVXNmGZm1jH2tDKAxqSGMUtuz4x6rgSqThRplSrSwBcBLd8NKo+3Q04AlSVf\nMdX0IZ3iualEff4RzpAwKdNO7V/y3z4Syhs2ZfXNGvt+F5Hnr9+PWnUcjQWUeWxS\nZ81hIqHWQ6paPBkM05I1P+zWuYF56UK7DGBIASBIJXaPclK1YoQ+o9ceChk9T6uy\nu3mju+/L4V2CAoY+G8DX3PtU6eFc+wVoyKFdEE9sjkVTbjK1zYqjlTR4R0zckX+L\ndiQ5YxtNVswo92sg9wpD93+EN7YSFx9ZlC7JrRSlf0betdX4iQ4VO8lG2FahgcYI\nwyZAsOwGJD4+TQ==\n=GeXG\n-----END PGP MESSAGE-----", + "message": "test\u554f\u91cf\u9bae\u63a7\u5230\u6848\u9032\u5e73\n", + "name": "brainpoolp256r1", + "password": "", + "privateKey": "-----BEGIN PGP PRIVATE KEY BLOCK-----\n\nlHgEWrpD4RMJKyQDAwIIAQEHAgMEMyiJsl3MxlZFRRg518IiUbv+/294KU+dBq/B\nQYbvt4dHh4M7O9Rgfic8EPbe47wKr6v6Z7wXgHpjqtKRoBzlxAAA/jhgOEGBKP4E\nIRHwnwkFI5FRIf6A+R9oZQ0kay8/+xNCDGy0HXRlc3RAZ29jcnlwdG8gPHRlc3RA\nZ29jcnlwdG8+iJAEExMIADgWIQTWZQHprbRVRe9D5xkELYRs3suJpAUCWrpD4QIb\nAwULCQgHAgYVCAkKCwIEFgIDAQIeAQIXgAAKCRAELYRs3suJpGVgAP48h9xGECGF\nL87pAJSRedg//d+ucwcWTRpblHskV59+HgD/T5DvU4AfmKvYUyhfkhLkqU+g2HO+\ndGU+/maeQymZBmycfARaukPhEgkrJAMDAggBAQcCAwQfD3jLYinp+eI5OjtwPhPs\nGYO/OtUhD8T9dyVJL306RgZyrtO2lwwEM3S/bN5F0lymte5XddH2wkt6Gv3stGV2\nAwEIBwAA/0vEs+P6LCQMFoxePlHi5kNvbuX0UH8YK8dAtsiY/LC3EdyIeAQYEwgA\nIBYhBNZlAemttFVF70PnGQQthGzey4mkBQJaukPhAhsMAAoJEAQthGzey4mk44wA\n/0CN2zUDoUEeVN1XouqbKgr9AxG/ffrZkvZDt4irMycOAP40NcvlLSUHrO6XQZPA\ngTHjrXXT++KbkzQRVWMO8UpRwg==\n=BkRB\n-----END PGP PRIVATE KEY BLOCK-----", + "publicKey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmFMEWrpD4RMJKyQDAwIIAQEHAgMEMyiJsl3MxlZFRRg518IiUbv+/294KU+dBq/B\nQYbvt4dHh4M7O9Rgfic8EPbe47wKr6v6Z7wXgHpjqtKRoBzlxLQddGVzdEBnb2Ny\neXB0byA8dGVzdEBnb2NyeXB0bz6IkAQTEwgAOBYhBNZlAemttFVF70PnGQQthGze\ny4mkBQJaukPhAhsDBQsJCAcCBhUICQoLAgQWAgMBAh4BAheAAAoJEAQthGzey4mk\nZWAA/jyH3EYQIYUvzukAlJF52D/9365zBxZNGluUeyRXn34eAP9PkO9TgB+Yq9hT\nKF+SEuSpT6DYc750ZT7+Zp5DKZkGbLhXBFq6Q+ESCSskAwMCCAEBBwIDBB8PeMti\nKen54jk6O3A+E+wZg7861SEPxP13JUkvfTpGBnKu07aXDAQzdL9s3kXSXKa17ld1\n0fbCS3oa/ey0ZXYDAQgHiHgEGBMIACAWIQTWZQHprbRVRe9D5xkELYRs3suJpAUC\nWrpD4QIbDAAKCRAELYRs3suJpOOMAP9Ajds1A6FBHlTdV6LqmyoK/QMRv3362ZL2\nQ7eIqzMnDgD+NDXL5S0lB6zul0GTwIEx46110/vim5M0EVVjDvFKUcI=\n=Bx7J\n-----END PGP PUBLIC KEY BLOCK-----" + }, + { + "encryptedSignedMessage": "-----BEGIN PGP MESSAGE-----\n\nhJ4D25491by3UQcSAwMEidpiLdDr/FBd9HVhN1kjJkagjbXQrKPuu47ws2k67MBS\ngNo913vEQOzhqdiVliYtMAIpEt4sNyWCQ+TUEigsFiaG6Dp0wPG4/qVhRgRB4poN\njyIOvcTkW+Ze8m5wjUHJMIuUlrcUhDKpTRUiWaCuMG099YhTndV/vHHLJLAgyn3r\nGTmT6CnwHwtrbikJAC/I09LAOgGlDrS761lbkUbWm64Zl6Tqa56ioZVe1rEXB1aG\nx8uoraH9XwrKBu99KBnXegez3NBmb8V9r1ZJ6WTYN8QnIrtZCwI2ssdNLuQRZJU3\nX/D1ZeEkQO/125Df/df5vfR9J7Vmg5R1cJ03AEv4apu3P+P8PdvONF0Xr82ZX7++\nsLfK22tMrP1W0+mxnvu45B9Gz7ymT+2kLu6iyuvAW17IkC0yUqn7Js6XdDpFk/h7\nu5G2RTWbD9Zhdmv0p8MqpvTb8DugOTjwXxWa6Mr384O0btojdAV2+T3FBko45ubV\nbmKARW86AH7YpKjCbedsy9e5SvQohv102w/mZVk=\n=C5uY\n-----END PGP MESSAGE-----", + "message": "test\u554f\u91cf\u9bae\u63a7\u5230\u6848\u9032\u5e73\n", + "name": "brainpoolp384r1", + "password": "", + "privateKey": "-----BEGIN PGP PRIVATE KEY BLOCK-----\n\nlKgEWrqJ2RMJKyQDAwIIAQELAwMELWPYFx5PjaQLkP/dNEmMYqD72jsx/IzSO9j1\nFsmwE7hmosHMjXcDWrsxDuRqPfFMN98P/8kRB4Qn+o2dNHHdRuzAm/O5XCpoFGRL\nPJ7nIFn/DMf6gmFUxDZYwxbWiK2QAAF+NpeKlJvJr2gDf/d2DaRjwOCXCjUnOQuo\n+XQAKlEp8SG3LRhnD9E/bKG4q3JHcH1jFY+0InRlc3QzODRAZ29jcnlwdG8gPHRl\nc3QzODRAZ29jcnlwdD6IsAQTEwkAOBYhBPD66ZJLMNNU+RLWJOJ39qEi22ymBQJa\nuonZAhsDBQsJCAcCBhUICQoLAgQWAgMBAh4BAheAAAoJEOJ39qEi22ym1dwBfi1T\ne1FIGoDOWl6pFY1n92Fuur4m5gJKiZ+f1Sh2ZsyE8mfxqloSZsERKx4KEVZCgwF8\nDHhvCatkh/WrEE2RXGO9fRnLXZGKGYbwuXAXDFa/cJJ+bUIDu/epm/4ge7E+X7NW\nnKwEWrqJ2RIJKyQDAwIIAQELAwMEh3cON/Xg5LSr6T9iMqgtVEIW8eAoCfi+p8NS\n4Xr+OLcEX6PhcY5lTKUZEf0zBqVMAn4yw12W389gJEzcwEq+eoyG5FYetezdhC8Z\ne+df1Ha7nnLY0TWBjTvNEnxdgx2YAwEJCQABf2uXve5jHifo/pNRLy7vXFNYN2zJ\nw7GSYvY35hmx5OjiM8HkDSYom1UB125p1b+/EhmIiJgEGBMJACAWIQTw+umSSzDT\nVPkS1iTid/ahIttspgUCWrqJ2QIbDAAKCRDid/ahIttspozuAX437euAjbm+goCs\nbWXr9j8+oRRK56CODQrwjGCdjeyFP/wEHjV96ZbBBLAspykPwL0Bf3FgnJe/mxFU\nMfjeXWX2rg7rPiWO9HU41dsEcZ2pvN3sC5mQchfqivFINTvIngmk2g==\n=8J0V\n-----END PGP PRIVATE KEY BLOCK-----", + "publicKey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmHMEWrqJ2RMJKyQDAwIIAQELAwMELWPYFx5PjaQLkP/dNEmMYqD72jsx/IzSO9j1\nFsmwE7hmosHMjXcDWrsxDuRqPfFMN98P/8kRB4Qn+o2dNHHdRuzAm/O5XCpoFGRL\nPJ7nIFn/DMf6gmFUxDZYwxbWiK2QtCJ0ZXN0Mzg0QGdvY3J5cHRvIDx0ZXN0Mzg0\nQGdvY3J5cHQ+iLAEExMJADgWIQTw+umSSzDTVPkS1iTid/ahIttspgUCWrqJ2QIb\nAwULCQgHAgYVCAkKCwIEFgIDAQIeAQIXgAAKCRDid/ahIttsptXcAX4tU3tRSBqA\nzlpeqRWNZ/dhbrq+JuYCSomfn9UodmbMhPJn8apaEmbBESseChFWQoMBfAx4bwmr\nZIf1qxBNkVxjvX0Zy12RihmG8LlwFwxWv3CSfm1CA7v3qZv+IHuxPl+zVrh3BFq6\nidkSCSskAwMCCAEBCwMDBId3Djf14OS0q+k/YjKoLVRCFvHgKAn4vqfDUuF6/ji3\nBF+j4XGOZUylGRH9MwalTAJ+MsNdlt/PYCRM3MBKvnqMhuRWHrXs3YQvGXvnX9R2\nu55y2NE1gY07zRJ8XYMdmAMBCQmImAQYEwkAIBYhBPD66ZJLMNNU+RLWJOJ39qEi\n22ymBQJauonZAhsMAAoJEOJ39qEi22ymjO4Bfjft64CNub6CgKxtZev2Pz6hFErn\noI4NCvCMYJ2N7IU//AQeNX3plsEEsCynKQ/AvQF/cWCcl7+bEVQx+N5dZfauDus+\nJY70dTjV2wRxnam83ewLmZByF+qK8Ug1O8ieCaTa\n=vEH0\n-----END PGP PUBLIC KEY BLOCK-----" + }, + { + "encryptedSignedMessage": "-----BEGIN PGP MESSAGE-----\n\nhL4DpJcoPAigmZESBAMEonrXiHjcMR/PE/ZwHEfC2rqhzugPOjxoytUCFx/WwLyI\nhREwlk3QA4wKO/xM9bgIkUg9bVlbJtsGceAcDgzxPonaeP+UhEMpi+otV4NT9y/F\nDPtdGCzM+n4rYHUzJqC5reyzjDbadiiV81YKoY67ZPulMwpqvZbCq1z9p0Qvpyww\nM4Tk2YJP1seL0WlcM3Tt7i0ZmegAa53OopqNLz5Mopba+f1aQOJeg8poY7xeR6Rq\n0sBaAf1vfSsrWQgBFxYwTWLtzysuls4l2h/xGoa2Ril9kAz0AZ7I3XdrXiaSkPAJ\nwkOI7zAdKE9urG0ZqsomInQq+u3RPQ349zms7B/+VttAlAg8WDKMDcNcFvPFDN85\nUACHO5UciN3682snpVmETKIo7jFXHj4ie9ITzpk/3MmWxvqbe1MiS1lQrRMY5PWL\nj9JULeBESodavfg2Mcf5tWGS7bMn/shojxbaYORZi70dw0Cl/e9Noh1hUeBd6ip6\nVkwhyLHSTB5tIq5pKSlNhgkuJDEhVvHsbFRjoioetH7hRkV4yolSWxB5iMT0lD1+\nKEKKTcNt1VHJ1cgSKi+S/fGYvHIPt1C2mXK1EBOqzJhVJxR8GNF3jF2aYG2r\n=zG+7\n-----END PGP MESSAGE-----", + "message": "test\u554f\u91cf\u9bae\u63a7\u5230\u6848\u9032\u5e73\n", + "name": "brainpoolp512r1", + "password": "", + "privateKey": "-----BEGIN PGP PRIVATE KEY BLOCK-----\n\nlNgEWrqKjRMJKyQDAwIIAQENBAMEDI4HTYTe2L0kzVVIJUrN7+8KivNLQNRUeLFp\noeKHeEqZdzv2zuYsqW91wTcxuobHuyhz2Rw/6GuxCjHMZKe1OEKzO73MHvk5x3Ut\ngsU3ziA4GYM1fPJobOJXwEvB7Hdt1jPnX6FDzsnnDb/V08o7oRDiFF+nakbZ4fdh\nBGPtoO8AAf94HXntn7GFtPoPKEG4GjDchW7kLoi99khL4afe/rEuuWDT4ue/osUw\ngDxddaBV2HpYlJtljbALvS+Fqlaz6+7mJOG0InRlc3Q1MTJAZ29jcnlwdG8gPHRl\nc3Q1MTJAZ29jcnlwdD6I0AQTEwoAOBYhBFZP0ImPk6gNK4o9sl/7WVyCi79OBQJa\nuoqNAhsDBQsJCAcCBhUICQoLAgQWAgMBAh4BAheAAAoJEF/7WVyCi79OH2YCAIdC\nLCIm4/8dTT3RROYFK/ERghCXH6Q42U5ojGdVc4+HSLiWyUijjqSznhrRtY4Izfcd\n23eLZCo1TjvfPWsK8N0B/1eRbHersapjqA2jhHjbhGr6pyZhmBZtMpeqJ80R86Ka\ncFcRH008VrqJoZwSRt+BQHDdwiWG+ZQiTY8Y9KmZ5pSc3ARauoqNEgkrJAMDAggB\nAQ0EAwQ0T5L5YcJMUFF8OYFvHlwcym3o3APGsbUCvfhUguYWOyEr9drNiYCaC0gl\nbO+KackLO30gWiVg1gHY3CoJfi0kRwt1tXKkiqh7wCD2BPDzP0lhF3nHaAKNpg3h\nmpGUcZui9FyTezwK6CjqBgAhLETCwevB1MXQuvvPgw9uil7rbQMBCgkAAf96G+z/\nK9vrz/nhvZessIeGAEHhmrGkHLBpw+9IGDs7hWNvisTn99JezTtPbYMInrwmEOB9\n9VoAIO8Xrom5XFXjI42IuAQYEwoAIBYhBFZP0ImPk6gNK4o9sl/7WVyCi79OBQJa\nuoqNAhsMAAoJEF/7WVyCi79O3QIB/1e/HKOHV7504x0wu14qXDN+2QW8P6j8d0qI\nGB7xOegf8Z8KgzGywZFjTT6GKBqPTz2vMd4u44/sLBVBgPgiKQgB/308ETfQaPcz\nctjlrmykdX0TrdiKLy92xAqsohFff5Ri5pr500005rTYJfNYN+Cug6u9UygWL2RY\nu4H95mtsxZo=\n=Qb7k\n-----END PGP PRIVATE KEY BLOCK-----", + "publicKey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmJMEWrqKjRMJKyQDAwIIAQENBAMEDI4HTYTe2L0kzVVIJUrN7+8KivNLQNRUeLFp\noeKHeEqZdzv2zuYsqW91wTcxuobHuyhz2Rw/6GuxCjHMZKe1OEKzO73MHvk5x3Ut\ngsU3ziA4GYM1fPJobOJXwEvB7Hdt1jPnX6FDzsnnDb/V08o7oRDiFF+nakbZ4fdh\nBGPtoO+0InRlc3Q1MTJAZ29jcnlwdG8gPHRlc3Q1MTJAZ29jcnlwdD6I0AQTEwoA\nOBYhBFZP0ImPk6gNK4o9sl/7WVyCi79OBQJauoqNAhsDBQsJCAcCBhUICQoLAgQW\nAgMBAh4BAheAAAoJEF/7WVyCi79OH2YCAIdCLCIm4/8dTT3RROYFK/ERghCXH6Q4\n2U5ojGdVc4+HSLiWyUijjqSznhrRtY4Izfcd23eLZCo1TjvfPWsK8N0B/1eRbHer\nsapjqA2jhHjbhGr6pyZhmBZtMpeqJ80R86KacFcRH008VrqJoZwSRt+BQHDdwiWG\n+ZQiTY8Y9KmZ5pS4lwRauoqNEgkrJAMDAggBAQ0EAwQ0T5L5YcJMUFF8OYFvHlwc\nym3o3APGsbUCvfhUguYWOyEr9drNiYCaC0glbO+KackLO30gWiVg1gHY3CoJfi0k\nRwt1tXKkiqh7wCD2BPDzP0lhF3nHaAKNpg3hmpGUcZui9FyTezwK6CjqBgAhLETC\nwevB1MXQuvvPgw9uil7rbQMBCgmIuAQYEwoAIBYhBFZP0ImPk6gNK4o9sl/7WVyC\ni79OBQJauoqNAhsMAAoJEF/7WVyCi79O3QIB/1e/HKOHV7504x0wu14qXDN+2QW8\nP6j8d0qIGB7xOegf8Z8KgzGywZFjTT6GKBqPTz2vMd4u44/sLBVBgPgiKQgB/308\nETfQaPczctjlrmykdX0TrdiKLy92xAqsohFff5Ri5pr500005rTYJfNYN+Cug6u9\nUygWL2RYu4H95mtsxZo=\n=3o0a\n-----END PGP PUBLIC KEY BLOCK-----" + } +] diff --git a/openpgp/integration_tests/v2/utils_test.go b/openpgp/integration_tests/v2/utils_test.go new file mode 100644 index 000000000..5383ac76b --- /dev/null +++ b/openpgp/integration_tests/v2/utils_test.go @@ -0,0 +1,303 @@ +package v2 + +import ( + "bytes" + "crypto" + "crypto/rand" + mathrand "math/rand" + "strings" + "time" + + "github.com/ProtonMail/go-crypto/openpgp/armor" + "github.com/ProtonMail/go-crypto/openpgp/packet" + "github.com/ProtonMail/go-crypto/openpgp/s2k" + openpgp "github.com/ProtonMail/go-crypto/openpgp/v2" +) + +// This function produces random test vectors: generates keys according to the +// given settings, associates a random message for each key. It returns the +// test vectors. +func generateFreshTestVectors(num int) (vectors []testVector, err error) { + mathrand.Seed(time.Now().UTC().UnixNano()) + for i := 0; i < num; i++ { + config := randConfig() + // Sample random email, comment, password and message + name, email, comment, password, message := randEntityData() + + // Only for verbose display + v := "v4" + if config.V6() { + v = "v6" + } + pkAlgoNames := map[packet.PublicKeyAlgorithm]string{ + packet.PubKeyAlgoRSA: "rsa_" + v, + packet.PubKeyAlgoEdDSA: "EdDSA_" + v, + packet.PubKeyAlgoEd25519: "ed25519_" + v, + packet.PubKeyAlgoEd448: "ed448_" + v, + } + + newVector := testVector{ + config: config, + Name: pkAlgoNames[config.Algorithm], + Password: password, + Message: message, + } + + // Generate keys + newEntity, errKG := openpgp.NewEntity(name, comment, email, config) + if errKG != nil { + panic(errKG) + } + + // Encrypt private key of entity + rawPwd := []byte(password) + if newEntity.PrivateKey != nil && !newEntity.PrivateKey.Encrypted { + if err = newEntity.PrivateKey.Encrypt(rawPwd); err != nil { + panic(err) + } + } + + // Encrypt subkeys of entity + for _, sub := range newEntity.Subkeys { + if sub.PrivateKey != nil && !sub.PrivateKey.Encrypted { + if err = sub.PrivateKey.Encrypt(rawPwd); err != nil { + panic(err) + } + } + } + + w := bytes.NewBuffer(nil) + if err = newEntity.SerializePrivateWithoutSigning(w, nil); err != nil { + return nil, err + } + + serialized := w.Bytes() + + privateKey, _ := armorWithType(serialized, "PGP PRIVATE KEY BLOCK") + newVector.PrivateKey = privateKey + newVector.PublicKey, _ = publicKey(privateKey) + vectors = append(vectors, newVector) + } + return vectors, err +} + +// armorWithType make bytes input to armor format +func armorWithType(input []byte, armorType string) (string, error) { + var b bytes.Buffer + w, err := armor.Encode(&b, armorType, nil) + if err != nil { + return "", err + } + if _, err = w.Write(input); err != nil { + return "", err + } + if err := w.Close(); err != nil { + return "", err + } + return b.String(), nil +} + +func publicKey(privateKey string) (string, error) { + privKeyReader := strings.NewReader(privateKey) + entries, err := openpgp.ReadArmoredKeyRing(privKeyReader) + if err != nil { + return "", err + } + + var outBuf bytes.Buffer + for _, e := range entries { + err := e.Serialize(&outBuf) + if err != nil { + return "", err + } + } + + outString, err := armorWithType(outBuf.Bytes(), "PGP PUBLIC KEY BLOCK") + if err != nil { + return "", err + } + + return outString, nil +} + +var runes = []rune("abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKMNOPQRSTUVWXYZ.@-_:;?/!#$%^&*{}[]'\"+~()<>") + +func randName() string { + firstName := make([]rune, 8) + lastName := make([]rune, 8) + nameRunes := runes[:26] + + for i := range firstName { + firstName[i] = nameRunes[mathrand.Intn(len(nameRunes))] + } + + for i := range lastName { + lastName[i] = nameRunes[mathrand.Intn(len(nameRunes))] + } + + return string(firstName) + " " + string(lastName) +} + +func randFileHints() *openpgp.FileHints { + fileNameRunes := runes[:66] + fileName := make([]rune, 1+mathrand.Intn(255)) + for i := range fileName { + fileName[i] = fileNameRunes[mathrand.Intn(len(fileNameRunes))] + } + + return &openpgp.FileHints{ + IsUTF8: mathrand.Intn(2) == 0, + FileName: string(fileName), + ModTime: time.Now(), + } +} + +func randEmail() string { + address := make([]rune, 20) + addressRunes := runes[:38] + domain := make([]rune, 5) + domainRunes := runes[:36] + ext := make([]rune, 3) + for i := range address { + address[i] = addressRunes[mathrand.Intn(len(addressRunes))] + } + for i := range domain { + domain[i] = domainRunes[mathrand.Intn(len(domainRunes))] + } + for i := range ext { + ext[i] = domainRunes[mathrand.Intn(len(domainRunes))] + } + email := string(address) + "@" + string(domain) + "." + string(ext) + return email +} + +// Comment does not allow the following characters: ()<>\x00 +func randComment() string { + comment := make([]rune, 140) + commentRunes := runes[:84] + for i := range comment { + comment[i] = commentRunes[mathrand.Intn(len(commentRunes))] + } + return string(comment) +} + +func randPassword() string { + maxPasswordLength := 64 + password := make([]rune, mathrand.Intn(maxPasswordLength-1)+1) + for i := range password { + password[i] = runes[mathrand.Intn(len(runes))] + } + return string(password) +} + +func randMessage() string { + maxMessageLength := 1 << 20 + message := make([]byte, 1+mathrand.Intn(maxMessageLength-1)) + if _, err := rand.Read(message); err != nil { + panic(err) + } + return string(message) +} + +// Change one char of the input +func corrupt(input string) string { + if input == "" { + return string(runes[mathrand.Intn(len(runes))]) + } + output := []rune(input) + for string(output) == input { + output[mathrand.Intn(len(output))] = runes[mathrand.Intn(len(runes))] + } + return string(output) +} + +func randEntityData() (string, string, string, string, string) { + return randName(), randEmail(), randComment(), randPassword(), randMessage() +} + +func randConfig() *packet.Config { + hashes := []crypto.Hash{ + crypto.SHA256, + } + hash := hashes[mathrand.Intn(len(hashes))] + + ciphers := []packet.CipherFunction{ + packet.CipherAES256, + } + ciph := ciphers[mathrand.Intn(len(ciphers))] + + compAlgos := []packet.CompressionAlgo{ + packet.CompressionNone, + //packet.CompressionZIP, + //packet.CompressionZLIB, + } + compAlgo := compAlgos[mathrand.Intn(len(compAlgos))] + + pkAlgos := []packet.PublicKeyAlgorithm{ + packet.PubKeyAlgoRSA, + packet.PubKeyAlgoEdDSA, + packet.PubKeyAlgoEd25519, + packet.PubKeyAlgoEd448, + } + pkAlgo := pkAlgos[mathrand.Intn(len(pkAlgos))] + + aeadModes := []packet.AEADMode{ + packet.AEADModeOCB, + packet.AEADModeEAX, + packet.AEADModeGCM, + } + var aeadConf = packet.AEADConfig{ + DefaultMode: aeadModes[mathrand.Intn(len(aeadModes))], + } + + var rsaBits int + if pkAlgo == packet.PubKeyAlgoRSA { + switch mathrand.Int() % 4 { + case 0: + rsaBits = 2048 + case 1: + rsaBits = 3072 + case 2: + rsaBits = 4096 + default: + rsaBits = 0 + } + } + + level := mathrand.Intn(11) - 1 + compConf := &packet.CompressionConfig{Level: level} + + var v6 bool + if mathrand.Int()%2 == 0 { + v6 = true + } + + var s2kConf *s2k.Config + if mathrand.Int()%2 == 0 { + s2kConf = &s2k.Config{ + S2KMode: s2k.IteratedSaltedS2K, + Hash: hash, + S2KCount: 1024 + mathrand.Intn(65010689), + } + } else { + s2kConf = &s2k.Config{ + S2KMode: s2k.Argon2S2K, + } + } + + return &packet.Config{ + V6Keys: v6, + Rand: rand.Reader, + DefaultHash: hash, + DefaultCipher: ciph, + DefaultCompressionAlgo: compAlgo, + CompressionConfig: compConf, + S2KConfig: s2kConf, + RSABits: rsaBits, + Algorithm: pkAlgo, + AEADConfig: &aeadConf, + RejectPublicKeyAlgorithms: map[packet.PublicKeyAlgorithm]bool{}, + RejectCurves: map[packet.Curve]bool{}, + MinRSABits: 512, + } +} From 1d56dee45e175deabfb217a47f5a757b6cf2d14b Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Tue, 8 Aug 2023 11:39:36 +0200 Subject: [PATCH 044/116] Merge go-crypto v2 openpgp subpackages changes --- openpgp/armor/armor.go | 2 +- openpgp/armor/armor_test.go | 20 ++- openpgp/armor/encode.go | 10 +- openpgp/clearsign/clearsign.go | 57 ++++--- openpgp/clearsign/clearsign_test.go | 77 +++++++--- openpgp/errors/errors.go | 29 +++- openpgp/packet/aead_crypter.go | 29 ++-- openpgp/packet/compressed.go | 45 +++++- openpgp/packet/config.go | 96 ++++++++++++ openpgp/packet/encrypted_key.go | 29 +++- openpgp/packet/encrypted_key_test.go | 6 +- openpgp/packet/literal.go | 6 +- openpgp/packet/marker.go | 33 ++++ openpgp/packet/packet.go | 96 +++++++++++- openpgp/packet/packet_sequence.go | 222 +++++++++++++++++++++++++++ openpgp/packet/packet_unsupported.go | 24 +++ openpgp/packet/padding.go | 6 +- openpgp/packet/public_key.go | 46 ++++-- openpgp/packet/reader.go | 149 +++++++++++++++--- openpgp/packet/recipient.go | 15 ++ openpgp/packet/signature.go | 52 ++++++- 21 files changed, 945 insertions(+), 104 deletions(-) create mode 100644 openpgp/packet/marker.go create mode 100644 openpgp/packet/packet_sequence.go create mode 100644 openpgp/packet/packet_unsupported.go create mode 100644 openpgp/packet/recipient.go diff --git a/openpgp/armor/armor.go b/openpgp/armor/armor.go index bc13306b6..21d4efb97 100644 --- a/openpgp/armor/armor.go +++ b/openpgp/armor/armor.go @@ -4,7 +4,7 @@ // Package armor implements OpenPGP ASCII Armor, see RFC 4880. OpenPGP Armor is // very similar to PEM except that it has an additional CRC checksum. -package armor // import "github.com/ProtonMail/go-crypto/openpgp/armor" +package armor // import "github.com/ProtonMail/go-crypto/v2/openpgp/armor" import ( "bufio" diff --git a/openpgp/armor/armor_test.go b/openpgp/armor/armor_test.go index 6d0566c15..050ab4239 100644 --- a/openpgp/armor/armor_test.go +++ b/openpgp/armor/armor_test.go @@ -75,6 +75,25 @@ func TestDecodeEmptyVersion(t *testing.T) { } } +func TestDecodeEmptyVersion(t *testing.T) { + buf := bytes.NewBuffer([]byte(armorExampleEmptyVersion)) + result, err := Decode(buf) + if err != nil { + t.Error(err) + } + expectedType := "PGP SIGNATURE" + if result.Type != expectedType { + t.Errorf("result.Type: got:%s want:%s", result.Type, expectedType) + } + if len(result.Header) != 1 { + t.Errorf("len(result.Header): got:%d want:1", len(result.Header)) + } + v, ok := result.Header["Version"] + if !ok || v != "" { + t.Errorf("result.Header: got:%#v", result.Header) + } +} + func TestLongHeader(t *testing.T) { buf := bytes.NewBuffer([]byte(armorLongLine)) result, err := Decode(buf) @@ -98,7 +117,6 @@ iJwEAAECAAYFAk1Fv/0ACgkQo01+GMIMMbsYTwQAiAw+QAaNfY6WBdplZ/uMAccm 4g+81QPmTSGHnetSb6WBiY13kVzK4HQiZH8JSkmmroMLuGeJwsRTEL4wbjRyUKEt p1xwUZDECs234F1xiG5enc5SGlRtP7foLBz9lOsjx+LEcA4sTl5/2eZR9zyFZqWW TxRjs+fJCIFuo71xb1g= -=/teI -----END PGP SIGNATURE-----` const armorLongLine = `-----BEGIN PGP SIGNATURE----- diff --git a/openpgp/armor/encode.go b/openpgp/armor/encode.go index 4ebaee718..943ff3ce4 100644 --- a/openpgp/armor/encode.go +++ b/openpgp/armor/encode.go @@ -186,14 +186,14 @@ func encode(out io.Writer, blockType string, headers map[string]string, checksum // Encode returns a WriteCloser which will encode the data written to it in // OpenPGP armor. func Encode(out io.Writer, blockType string, headers map[string]string) (w io.WriteCloser, err error) { - return encode(out, blockType, headers, true) + return encode(out, blockType, headers, false) } -// Encode returns a WriteCloser which will encode the data written to it in -// OpenPGP armor but does not generate checksum at the end. +// EncodeWithChecksumOption returns a WriteCloser which will encode the data written to it in +// OpenPGP armor and provides the option to include a checksum. // When forming ASCII Armor, the CRC24 footer SHOULD NOT be generated, // unless interoperability with implementations that require the CRC24 footer // to be present is a concern. -func EncodeWithoutChecksum(out io.Writer, blockType string, headers map[string]string) (w io.WriteCloser, err error) { - return encode(out, blockType, headers, false) +func EncodeWithChecksumOption(out io.Writer, blockType string, headers map[string]string, doChecksum bool) (w io.WriteCloser, err error) { + return encode(out, blockType, headers, doChecksum) } diff --git a/openpgp/clearsign/clearsign.go b/openpgp/clearsign/clearsign.go index 036de7286..d081fe1ef 100644 --- a/openpgp/clearsign/clearsign.go +++ b/openpgp/clearsign/clearsign.go @@ -7,7 +7,7 @@ // // Clearsigned messages are cryptographically signed, but the contents of the // message are kept in plaintext so that it can be read without special tools. -package clearsign // import "github.com/ProtonMail/go-crypto/openpgp/clearsign" +package clearsign // import "github.com/ProtonMail/go-crypto/v2/openpgp/clearsign" import ( "bufio" @@ -111,7 +111,7 @@ func Decode(data []byte) (b *Block, rest []byte) { return nil, data } // An empty line marks the end of the headers. - if line, rest = getLine(rest); len(line) == 0 { + if line, rest = getLine(rest); len(strings.TrimSpace(string(line))) == 0 { break } @@ -203,11 +203,12 @@ func Decode(data []byte) (b *Block, rest []byte) { // When closed, an armored signature is created and written to complete the // message. type dashEscaper struct { - buffered *bufio.Writer - hashers []hash.Hash // one per key in privateKeys - hashType crypto.Hash - toHash io.Writer // writes to all the hashes in hashers - salts [][]byte // salts for the signatures if v6 + buffered *bufio.Writer + hashers []hash.Hash // one per key in privateKeys + hashType crypto.Hash + toHash io.Writer // writes to all the hashes in hashers + salts [][]byte // salts for the signatures if v6 + armorHeader map[string]string // Armor headers atBeginningOfLine bool isFirstLine bool @@ -293,13 +294,16 @@ func (d *dashEscaper) Write(data []byte) (n int, err error) { } func (d *dashEscaper) Close() (err error) { - if !d.atBeginningOfLine { - if err = d.buffered.WriteByte(lf); err != nil { - return + if d.atBeginningOfLine { + if !d.isFirstLine { + d.toHash.Write(crlf) } } + if err = d.buffered.WriteByte(lf); err != nil { + return + } - out, err := armor.Encode(d.buffered, "PGP SIGNATURE", nil) + out, err := armor.Encode(d.buffered, "PGP SIGNATURE", d.armorHeader) if err != nil { return } @@ -343,14 +347,29 @@ func (d *dashEscaper) Close() (err error) { // Encode returns a WriteCloser which will clear-sign a message with privateKey // and write it to w. If config is nil, sensible defaults are used. -func Encode(w io.Writer, privateKey *packet.PrivateKey, config *packet.Config) (plaintext io.WriteCloser, err error) { +func Encode(w io.Writer, privateKey *packet.PrivateKey, config *packet.Config, headers map[string]string) (plaintext io.WriteCloser, err error) { return EncodeMulti(w, []*packet.PrivateKey{privateKey}, config) } +// EncodeWithHeader returns a WriteCloser which will clear-sign a message with privateKey +// and write it to w. If config is nil, sensible defaults are used. +// Additionally provides a headers argument for custom headers. +func EncodeWithHeader(w io.Writer, privateKey *packet.PrivateKey, config *packet.Config, headers map[string]string) (plaintext io.WriteCloser, err error) { + return EncodeMultiWithHeader(w, []*packet.PrivateKey{privateKey}, config, headers) +} + // EncodeMulti returns a WriteCloser which will clear-sign a message with all the // private keys indicated and write it to w. If config is nil, sensible defaults // are used. func EncodeMulti(w io.Writer, privateKeys []*packet.PrivateKey, config *packet.Config) (plaintext io.WriteCloser, err error) { + return EncodeMultiWithHeader(w, privateKeys, config, nil) +} + +// EncodeMultiWithHeader returns a WriteCloser which will clear-sign a message with all the +// private keys indicated and write it to w. If config is nil, sensible defaults +// are used. +// Additionally provides a headers argument for custom headers. +func EncodeMultiWithHeader(w io.Writer, privateKeys []*packet.PrivateKey, config *packet.Config, headers map[string]string) (plaintext io.WriteCloser, err error) { for _, k := range privateKeys { if k.Encrypted { return nil, errors.InvalidArgumentError(fmt.Sprintf("signing key %s is encrypted", k.KeyIdString())) @@ -412,11 +431,12 @@ func EncodeMulti(w io.Writer, privateKeys []*packet.PrivateKey, config *packet.C } plaintext = &dashEscaper{ - buffered: buffered, - hashers: hashers, - hashType: hashType, - toHash: toHash, - salts: salts, + buffered: buffered, + hashers: hashers, + hashType: hashType, + toHash: toHash, + salts: salts, + armorHeader: headers, atBeginningOfLine: true, isFirstLine: true, @@ -433,7 +453,8 @@ func EncodeMulti(w io.Writer, privateKeys []*packet.PrivateKey, config *packet.C // VerifySignature checks a clearsigned message signature, and checks that the // hash algorithm in the header matches the hash algorithm in the signature. func (b *Block) VerifySignature(keyring openpgp.KeyRing, config *packet.Config) (signer *openpgp.Entity, err error) { - return openpgp.CheckDetachedSignature(keyring, bytes.NewBuffer(b.Bytes), b.ArmoredSignature.Body, config) + _, signer, err = openpgp.VerifyDetachedSignature(keyring, bytes.NewBuffer(b.Bytes), b.ArmoredSignature.Body, config) + return } // nameOfHash returns the OpenPGP name for the given hash, or the empty string diff --git a/openpgp/clearsign/clearsign_test.go b/openpgp/clearsign/clearsign_test.go index d0dbae885..5d0396be4 100644 --- a/openpgp/clearsign/clearsign_test.go +++ b/openpgp/clearsign/clearsign_test.go @@ -6,14 +6,23 @@ package clearsign import ( "bytes" + "crypto" "fmt" "io" + "strings" "testing" "github.com/ProtonMail/go-crypto/openpgp" "github.com/ProtonMail/go-crypto/openpgp/packet" ) +var allowAllAlgorithmsConfig = &packet.Config{ + RejectPublicKeyAlgorithms: map[packet.PublicKeyAlgorithm]bool{}, + RejectMessageHashAlgorithms: map[crypto.Hash]bool{}, + RejectCurves: map[packet.Curve]bool{}, + MinRSABits: 512, +} + func testParse(t *testing.T, input []byte, expected, expectedPlaintext string) { b, rest := Decode(input) if b == nil { @@ -38,13 +47,12 @@ func testParse(t *testing.T, input []byte, expected, expectedPlaintext string) { t.Errorf("failed to parse public key: %s", err) } - config := &packet.Config{} - if _, err := openpgp.CheckDetachedSignature(keyring, bytes.NewBuffer(b.Bytes), b.ArmoredSignature.Body, config); err != nil { + if _, _, err := openpgp.VerifyDetachedSignature(keyring, bytes.NewBuffer(b.Bytes), b.ArmoredSignature.Body, allowAllAlgorithmsConfig); err != nil { t.Errorf("failed to check signature: %s", err) } b, _ = Decode(input) - if _, err := b.VerifySignature(keyring, config); err != nil { + if _, err := b.VerifySignature(keyring, allowAllAlgorithmsConfig); err != nil { t.Errorf("failed to check signature: %s", err) } } @@ -69,21 +77,21 @@ func TestParseWithNoNewlineAtEnd(t *testing.T) { var signingTests = []struct { in, signed, plaintext string }{ - {"", "", ""}, + {"", "", "\n"}, {"a", "a", "a\n"}, - {"a\n", "a", "a\n"}, - {"-a\n", "-a", "-a\n"}, + {"a\n", "a\r\n", "a\n\n"}, + {"-a\n", "-a\r\n", "-a\n\n"}, {"--a\nb", "--a\r\nb", "--a\nb\n"}, // leading whitespace - {" a\n", " a", " a\n"}, - {" a\n", " a", " a\n"}, + {" a\n", " a\r\n", " a\n\n"}, + {" a\n", " a\r\n", " a\n\n"}, // trailing whitespace (should be stripped) - {"a \n", "a", "a\n"}, + {"a \n", "a\r\n", "a\n\n"}, {"a ", "a", "a\n"}, - // whitespace-only lines (should be stripped) - {" \n", "", "\n"}, + {" \n", "\r\n", "\n\n"}, {" ", "", "\n"}, - {"a\n \n \nb\n", "a\r\n\r\n\r\nb", "a\n\n\nb\n"}, + {"a\n \n \nb\n", "a\r\n\r\n\r\nb\r\n", "a\n\n\nb\n\n"}, + {"a\n \n \nb\n", "a\r\n\r\n\r\nb\r\n", "a\n\n\nb\n\n"}, } func TestVerifyV6(t *testing.T) { @@ -103,11 +111,10 @@ func TestSigning(t *testing.T) { if err != nil { t.Errorf("failed to parse public key: %s", err) } - for i, test := range signingTests { var buf bytes.Buffer - plaintext, err := Encode(&buf, keyring[0].PrivateKey, nil) + plaintext, err := Encode(&buf, keyring[0].PrivateKey, allowAllAlgorithmsConfig, nil) if err != nil { t.Errorf("#%d: error from Encode: %s", i, err) continue @@ -135,13 +142,35 @@ func TestSigning(t *testing.T) { continue } - config := &packet.Config{} - if _, err := openpgp.CheckDetachedSignature(keyring, bytes.NewBuffer(b.Bytes), b.ArmoredSignature.Body, config); err != nil { + if _, _, err := openpgp.VerifyDetachedSignature(keyring, bytes.NewBuffer(b.Bytes), b.ArmoredSignature.Body, allowAllAlgorithmsConfig); err != nil { t.Errorf("#%d: failed to check signature: %s", i, err) } } } +func TestSigningInterop(t *testing.T) { + keyring, err := openpgp.ReadArmoredKeyRing(bytes.NewBufferString(signingKey)) + if err != nil { + t.Errorf("failed to parse public key: %s", err) + } + + var buf bytes.Buffer + plaintext, err := Encode(&buf, keyring[0].PrivateKey, nil, nil) + if err != nil { + t.Errorf("error from Encode") + } + if _, err := plaintext.Write([]byte(trickyGrocery)); err != nil { + t.Errorf("error from Write") + } + if err := plaintext.Close(); err != nil { + t.Fatalf("error from Close") + } + expected := "- - tofu\n- - vegetables\n- - noodles\n\n\n" + if !strings.Contains(buf.String(), expected) { + t.Fatalf("expected output to contain %s but got: %s", expected, buf.String()) + } +} + // We use this to make test keys, so that they aren't all the same. type quickRand byte @@ -161,9 +190,9 @@ func testMultiSign(t *testing.T, v6 bool) { zero := quickRand(0) config := packet.Config{Rand: &zero, V6Keys: v6} - for nKeys := 0; nKeys < 4; nKeys++ { + for nKeys := 1; nKeys < 4; nKeys++ { nextTest: - for nExtra := 0; nExtra < 4; nExtra++ { + for nExtra := 2; nExtra < 4; nExtra++ { var signKeys []*packet.PrivateKey var verifyKeys openpgp.EntityList @@ -182,7 +211,7 @@ func testMultiSign(t *testing.T, v6 bool) { input := []byte("this is random text\r\n4 17") var output bytes.Buffer - w, err := EncodeMulti(&output, signKeys, nil) + w, err := EncodeMultiWithHeader(&output, signKeys, nil, nil) if err != nil { t.Errorf("EncodeMulti (%s) failed: %v", desc, err) } @@ -198,7 +227,7 @@ func testMultiSign(t *testing.T, v6 bool) { t.Errorf("Inline data didn't match original; got %q want %q", string(block.Bytes), string(input)) } config := &packet.Config{} - _, err = openpgp.CheckDetachedSignature(verifyKeys, bytes.NewReader(block.Bytes), block.ArmoredSignature.Body, config) + _, _, err = openpgp.VerifyDetachedSignature(verifyKeys, bytes.NewReader(block.Bytes), block.ArmoredSignature.Body, config) if nKeys == 0 { if err == nil { t.Errorf("verifying inline (%s) succeeded; want failure", desc) @@ -455,3 +484,11 @@ wpgGARsKAAAAKQWCY5ijYyIhBssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce6 /FvLFuGWMbKAdA+epq7V4HOtAPlBWmU8QOd6aud+aSunHQaaEJ+iTFjP2OMW0KBr NK2ay45cX1IVAQ== -----END PGP SIGNATURE-----` + +const trickyGrocery = `From the grocery store we need: + +- tofu +- vegetables +- noodles + +` diff --git a/openpgp/errors/errors.go b/openpgp/errors/errors.go index 17e2bcfed..8d6969c0b 100644 --- a/openpgp/errors/errors.go +++ b/openpgp/errors/errors.go @@ -3,7 +3,7 @@ // license that can be found in the LICENSE file. // Package errors contains common error types for the OpenPGP packages. -package errors // import "github.com/ProtonMail/go-crypto/openpgp/errors" +package errors // import "github.com/ProtonMail/go-crypto/v2/openpgp/errors" import ( "strconv" @@ -58,6 +58,14 @@ func (ke keyExpiredError) Error() string { return "openpgp: key expired" } +var ErrSignatureOlderThanKey error = signatureOlderThanKeyError(0) + +type signatureOlderThanKeyError int + +func (ske signatureOlderThanKeyError) Error() string { + return "openpgp: signature is older than the key" +} + var ErrKeyExpired error = keyExpiredError(0) type keyIncorrectError int @@ -92,12 +100,24 @@ func (keyRevokedError) Error() string { var ErrKeyRevoked error = keyRevokedError(0) +type WeakAlgorithmError string + +func (e WeakAlgorithmError) Error() string { + return "openpgp: weak algorithms are rejected: " + string(e) +} + type UnknownPacketTypeError uint8 func (upte UnknownPacketTypeError) Error() string { return "openpgp: unknown packet type: " + strconv.Itoa(int(upte)) } +type CriticalUnknownPacketTypeError uint8 + +func (upte CriticalUnknownPacketTypeError) Error() string { + return "openpgp: unknown critical packet type: " + strconv.Itoa(int(upte)) +} + // AEADError indicates that there is a problem when initializing or using a // AEAD instance, configuration struct, nonces or index values. type AEADError string @@ -114,3 +134,10 @@ type ErrDummyPrivateKey string func (dke ErrDummyPrivateKey) Error() string { return "openpgp: s2k GNU dummy key: " + string(dke) } + +// ErrMalformedMessage results when the packet sequence is incorrect +type ErrMalformedMessage string + +func (dke ErrMalformedMessage) Error() string { + return "openpgp: malformed message " + string(dke) +} diff --git a/openpgp/packet/aead_crypter.go b/openpgp/packet/aead_crypter.go index cee83bdc7..2d1aeed65 100644 --- a/openpgp/packet/aead_crypter.go +++ b/openpgp/packet/aead_crypter.go @@ -88,17 +88,20 @@ func (ar *aeadDecrypter) Read(dst []byte) (n int, err error) { if errRead != nil && errRead != io.EOF { return 0, errRead } - decrypted, errChunk := ar.openChunk(cipherChunk) - if errChunk != nil { - return 0, errChunk - } - // Return decrypted bytes, buffering if necessary - if len(dst) < len(decrypted) { - n = copy(dst, decrypted[:len(dst)]) - ar.buffer.Write(decrypted[len(dst):]) - } else { - n = copy(dst, decrypted) + if len(cipherChunk) > 0 { + decrypted, errChunk := ar.openChunk(cipherChunk) + if errChunk != nil { + return 0, errChunk + } + + // Return decrypted bytes, buffering if necessary + if len(dst) < len(decrypted) { + n = copy(dst, decrypted[:len(dst)]) + ar.buffer.Write(decrypted[len(dst):]) + } else { + n = copy(dst, decrypted) + } } // Check final authentication tag @@ -116,6 +119,12 @@ func (ar *aeadDecrypter) Read(dst []byte) (n int, err error) { // checked in the last Read call. In the future, this function could be used to // wipe the reader and peeked, decrypted bytes, if necessary. func (ar *aeadDecrypter) Close() (err error) { + if !ar.eof { + errChunk := ar.validateFinalTag(ar.peekedBytes) + if errChunk != nil { + return errChunk + } + } return nil } diff --git a/openpgp/packet/compressed.go b/openpgp/packet/compressed.go index 2f5cad71d..334de286b 100644 --- a/openpgp/packet/compressed.go +++ b/openpgp/packet/compressed.go @@ -8,9 +8,11 @@ import ( "compress/bzip2" "compress/flate" "compress/zlib" - "github.com/ProtonMail/go-crypto/openpgp/errors" "io" + "io/ioutil" "strconv" + + "github.com/ProtonMail/go-crypto/openpgp/errors" ) // Compressed represents a compressed OpenPGP packet. The decompressed contents @@ -39,6 +41,37 @@ type CompressionConfig struct { Level int } +// decompressionReader ensures that the whole compression packet is read. +type decompressionReader struct { + compressed io.Reader + decompressed io.ReadCloser + readAll bool +} + +func newDecompressionReader(r io.Reader, decompressor io.ReadCloser) *decompressionReader { + return &decompressionReader{ + compressed: r, + decompressed: decompressor, + } +} + +func (dr *decompressionReader) Read(data []byte) (n int, err error) { + if dr.readAll { + return 0, io.EOF + } + n, err = dr.decompressed.Read(data) + if err == io.EOF { + dr.readAll = true + // Close the decompressor. + if errDec := dr.decompressed.Close(); errDec != nil { + return n, errDec + } + // Consume all remaining data from the compressed packet. + consumeAll(dr.compressed) + } + return n, err +} + func (c *Compressed) parse(r io.Reader) error { var buf [1]byte _, err := readFull(r, buf[:]) @@ -50,11 +83,15 @@ func (c *Compressed) parse(r io.Reader) error { case 0: c.Body = r case 1: - c.Body = flate.NewReader(r) + c.Body = newDecompressionReader(r, flate.NewReader(r)) case 2: - c.Body, err = zlib.NewReader(r) + decompressor, err := zlib.NewReader(r) + if err != nil { + return err + } + c.Body = newDecompressionReader(r, decompressor) case 3: - c.Body = bzip2.NewReader(r) + c.Body = newDecompressionReader(r, ioutil.NopCloser(bzip2.NewReader(r))) default: err = errors.UnsupportedError("unknown compression algorithm: " + strconv.Itoa(int(buf[0]))) } diff --git a/openpgp/packet/config.go b/openpgp/packet/config.go index 6a72b9ee0..128f13b87 100644 --- a/openpgp/packet/config.go +++ b/openpgp/packet/config.go @@ -14,6 +14,21 @@ import ( "github.com/ProtonMail/go-crypto/openpgp/s2k" ) +var ( + defaultRejectPublicKeyAlgorithms = map[PublicKeyAlgorithm]bool{ + PubKeyAlgoElGamal: true, + PubKeyAlgoDSA: true, + } + defaultRejectMessageHashAlgorithms = map[crypto.Hash]bool{ + crypto.SHA1: true, + crypto.MD5: true, + crypto.RIPEMD160: true, + } + defaultRejectCurves = map[Curve]bool{ + CurveSecP256k1: true, + } +) + // Config collects a number of parameters along with sensible defaults. // A nil *Config is valid and results in all default values. type Config struct { @@ -79,6 +94,12 @@ type Config struct { // V6Keys configures version 6 key generation. If false, this package still // supports version 6 keys, but produces version 4 keys. V6Keys bool + // Minimum RSA key size allowed for key generation and message signing, verification and encryption. + MinRSABits uint16 + // Reject insecure algorithms, only works with v2 api + RejectPublicKeyAlgorithms map[PublicKeyAlgorithm]bool + RejectMessageHashAlgorithms map[crypto.Hash]bool + RejectCurves map[Curve]bool // "The validity period of the key. This is the number of seconds after // the key creation time that the key expires. If this is not present // or has a value of zero, the key never expires. This is found only on @@ -113,6 +134,20 @@ type Config struct { KnownNotations map[string]bool // SignatureNotations is a list of Notations to be added to any signatures. SignatureNotations []*Notation + // CheckIntendedRecipients is a flag that indicates if + // a decryption key for an encrypted and signed messages should be checked + // to be present in the signatures intended recipient list. + // if config is nil or flag is nil, it defaults to true + CheckIntendedRecipients *bool + // CacheSessionKey is a flag that indicates + // if a session key if any should be cached and returned in + // a pgp message decryption. + CacheSessionKey bool + // CheckPacketSequence is a flag that indicates + // if the pgp message parser should strictly check + // that the packet sequence conforms with the grammar mandated by rfc4880. + // The default value is true. + CheckPacketSequence *bool } func (c *Config) Random() io.Reader { @@ -256,3 +291,64 @@ func (c *Config) V6() bool { } return c.V6Keys } + +func (c *Config) IntendedRecipients() bool { + if c == nil || c.CheckIntendedRecipients == nil { + return true + } + return *c.CheckIntendedRecipients +} + +func (c *Config) RetrieveSessionKey() bool { + if c == nil { + return false + } + return c.CacheSessionKey +} + +func (c *Config) MinimumRSABits() uint16 { + if c == nil || c.MinRSABits == 0 { + return 2047 + } + return c.MinRSABits +} + +func (c *Config) RejectPublicKeyAlgorithm(alg PublicKeyAlgorithm) bool { + var rejectedAlgorithms map[PublicKeyAlgorithm]bool + if c == nil || c.RejectPublicKeyAlgorithms == nil { + // Default + rejectedAlgorithms = defaultRejectPublicKeyAlgorithms + } else { + rejectedAlgorithms = c.RejectPublicKeyAlgorithms + } + return rejectedAlgorithms[alg] +} + +func (c *Config) RejectMessageHashAlgorithm(hash crypto.Hash) bool { + var rejectedAlgorithms map[crypto.Hash]bool + if c == nil || c.RejectMessageHashAlgorithms == nil { + // Default + rejectedAlgorithms = defaultRejectMessageHashAlgorithms + } else { + rejectedAlgorithms = c.RejectMessageHashAlgorithms + } + return rejectedAlgorithms[hash] +} + +func (c *Config) RejectCurve(curve Curve) bool { + var rejectedCurve map[Curve]bool + if c == nil || c.RejectCurves == nil { + // Default + rejectedCurve = defaultRejectCurves + } else { + rejectedCurve = c.RejectCurves + } + return rejectedCurve[curve] +} + +func (c *Config) StrictPacketSequence() bool { + if c == nil || c.CheckPacketSequence == nil { + return true + } + return *c.CheckPacketSequence +} diff --git a/openpgp/packet/encrypted_key.go b/openpgp/packet/encrypted_key.go index cbe7cf9c1..8ddfe0c8e 100644 --- a/openpgp/packet/encrypted_key.go +++ b/openpgp/packet/encrypted_key.go @@ -318,6 +318,15 @@ func (e *EncryptedKey) Serialize(w io.Writer) error { // If aeadSupported is set, PKESK v6 is used else v4. // If config is nil, sensible defaults will be used. func SerializeEncryptedKeyAEAD(w io.Writer, pub *PublicKey, cipherFunc CipherFunction, aeadSupported bool, key []byte, config *Config) error { + return SerializeEncryptedKeyAEADwithHiddenOption(w, pub, cipherFunc, aeadSupported, key, false, config) +} + +// SerializeEncryptedKeyAEADwithHiddenOption serializes an encrypted key packet to w that contains +// key, encrypted to pub. +// Offers the hidden flag option to indicated if the PKESK packet should include a wildcard KeyID. +// If aeadSupported is set, PKESK v6 is used else v4. +// If config is nil, sensible defaults will be used. +func SerializeEncryptedKeyAEADwithHiddenOption(w io.Writer, pub *PublicKey, cipherFunc CipherFunction, aeadSupported bool, key []byte, hidden bool, config *Config) error { var buf [36]byte // max possible header size is v6 lenHeaderWritten := 1 version := 3 @@ -341,8 +350,12 @@ func SerializeEncryptedKeyAEAD(w io.Writer, pub *PublicKey, cipherFunc CipherFun buf[0] = byte(version) + // If hidden is set, the key should be hidden + // An implementation MAY accept or use a Key ID of all zeros, + // or a key version of zero and no key fingerprint, to hide the intended decryption key. + // See Section 5.1.8. in the open pgp crypto refresh if version == 6 { - if pub != nil { + if !hidden { // A one-octet size of the following two fields. buf[1] = byte(1 + len(pub.Fingerprint)) // A one octet key version number. @@ -357,7 +370,9 @@ func SerializeEncryptedKeyAEAD(w io.Writer, pub *PublicKey, cipherFunc CipherFun lenHeaderWritten += 1 } } else { - binary.BigEndian.PutUint64(buf[1:9], pub.KeyId) + if !hidden { + binary.BigEndian.PutUint64(buf[1:9], pub.KeyId) + } lenHeaderWritten += 8 } buf[lenHeaderWritten] = byte(pub.PubKeyAlgo) @@ -404,10 +419,18 @@ func SerializeEncryptedKeyAEAD(w io.Writer, pub *PublicKey, cipherFunc CipherFun // key, encrypted to pub. // PKESKv6 is used if config.AEAD() is not nil. // If config is nil, sensible defaults will be used. -func SerializeEncryptedKey(w io.Writer, pub *PublicKey, cipherFunc CipherFunction, key []byte, config *Config) error { +func SerializeEncryptedKey(w io.Writer, pub *PublicKey, cipherFunc CipherFunction, key []byte, hidden bool, config *Config) error { return SerializeEncryptedKeyAEAD(w, pub, cipherFunc, config.AEAD() != nil, key, config) } +// SerializeEncryptedKey serializes an encrypted key packet to w that contains +// key, encrypted to pub. +// PKESKv6 is used if config.AEAD() is not nil. +// If config is nil, sensible defaults will be used. +func SerializeEncryptedKeyWithHiddenOption(w io.Writer, pub *PublicKey, cipherFunc CipherFunction, key []byte, hidden bool, config *Config) error { + return SerializeEncryptedKeyAEADwithHiddenOption(w, pub, cipherFunc, config.AEAD() != nil, key, hidden, config) +} + func serializeEncryptedKeyRSA(w io.Writer, rand io.Reader, header []byte, pub *rsa.PublicKey, keyBlock []byte) error { cipherText, err := rsa.EncryptPKCS1v15(rand, pub, keyBlock) if err != nil { diff --git a/openpgp/packet/encrypted_key_test.go b/openpgp/packet/encrypted_key_test.go index 79bcb3a13..787c7feca 100644 --- a/openpgp/packet/encrypted_key_test.go +++ b/openpgp/packet/encrypted_key_test.go @@ -168,7 +168,7 @@ func TestEncryptingEncryptedKey(t *testing.T) { } buf := new(bytes.Buffer) - err := SerializeEncryptedKey(buf, pub, CipherAES128, key, nil) + err := SerializeEncryptedKeyAEAD(buf, pub, CipherAES128, false, key, nil) if err != nil { t.Errorf("error writing encrypted key packet: %s", err) } @@ -217,7 +217,7 @@ func TestEncryptingEncryptedKeyV6(t *testing.T) { rsaWrappedKeyPub := &rsaWrappedKey.PublicKey buf := new(bytes.Buffer) - err := SerializeEncryptedKey(buf, rsaWrappedKeyPub, CipherAES128, key, config) + err := SerializeEncryptedKeyAEAD(buf, rsaWrappedKeyPub, CipherAES128, true, key, config) if err != nil { t.Errorf("error writing encrypted key packet: %s", err) @@ -278,7 +278,7 @@ func TestEncryptingEncryptedKeyXAlgorithms(t *testing.T) { wrappedKeyPub := &wrappedKey.PublicKey buf := new(bytes.Buffer) - err := SerializeEncryptedKey(buf, wrappedKeyPub, CipherAES128, key, config) + err := SerializeEncryptedKeyAEAD(buf, wrappedKeyPub, CipherAES128, true, key, config) if err != nil { t.Errorf("error writing encrypted key packet: %s", err) diff --git a/openpgp/packet/literal.go b/openpgp/packet/literal.go index 4be987609..8a028c8a1 100644 --- a/openpgp/packet/literal.go +++ b/openpgp/packet/literal.go @@ -58,9 +58,9 @@ func (l *LiteralData) parse(r io.Reader) (err error) { // on completion. The fileName is truncated to 255 bytes. func SerializeLiteral(w io.WriteCloser, isBinary bool, fileName string, time uint32) (plaintext io.WriteCloser, err error) { var buf [4]byte - buf[0] = 't' - if isBinary { - buf[0] = 'b' + buf[0] = 'b' + if !isBinary { + buf[0] = 'u' } if len(fileName) > 255 { fileName = fileName[:255] diff --git a/openpgp/packet/marker.go b/openpgp/packet/marker.go new file mode 100644 index 000000000..acafc14bc --- /dev/null +++ b/openpgp/packet/marker.go @@ -0,0 +1,33 @@ +package packet + +import ( + "io" + + "github.com/ProtonMail/go-crypto/openpgp/errors" +) + +type Marker struct{} + +const markerString = "PGP" + +// parse just ignores the padding content. +func (m *Marker) parse(reader io.Reader) error { + var buffer [3]byte + if _, err := io.ReadFull(reader, buffer[:]); err != nil { + return err + } + if string(buffer[:]) != markerString { + return errors.StructuralError("invalid marker packet") + } + return nil +} + +// SerializeMarker writes a marker packet to writer. +func SerializeMarker(writer io.Writer) error { + err := serializeHeader(writer, packetTypeMarker, len(markerString)) + if err != nil { + return err + } + _, err = writer.Write([]byte(markerString)) + return err +} diff --git a/openpgp/packet/packet.go b/openpgp/packet/packet.go index ba91ce880..62ddb4464 100644 --- a/openpgp/packet/packet.go +++ b/openpgp/packet/packet.go @@ -4,7 +4,7 @@ // Package packet implements parsing and serialization of OpenPGP packets, as // specified in RFC 4880. -package packet // import "github.com/ProtonMail/go-crypto/openpgp/packet" +package packet // import "github.com/ProtonMail/go-crypto/v2/openpgp/packet" import ( "bytes" @@ -311,7 +311,9 @@ const ( packetTypePrivateSubkey packetType = 7 packetTypeCompressed packetType = 8 packetTypeSymmetricallyEncrypted packetType = 9 + packetTypeMarker packetType = 10 packetTypeLiteralData packetType = 11 + packetTypeTrust packetType = 12 packetTypeUserId packetType = 13 packetTypePublicSubkey packetType = 14 packetTypeUserAttribute packetType = 17 @@ -370,8 +372,91 @@ func Read(r io.Reader) (p Packet, err error) { p = new(AEADEncrypted) case packetPadding: p = Padding(len) + case packetTypeMarker: + p = new(Marker) + case packetTypeTrust: + // Not implemented, just consume + err = errors.UnknownPacketTypeError(tag) default: + // Packet Tags from 0 to 39 are critical. + // Packet Tags from 40 to 63 are non-critical. + if tag < 40 { + err = errors.CriticalUnknownPacketTypeError(tag) + } else { + err = errors.UnknownPacketTypeError(tag) + } + } + if p != nil { + err = p.parse(contents) + } + if err != nil { + consumeAll(contents) + } + return +} + +// ReadWithCheck reads a single OpenPGP message packet from the given io.Reader. If there is an +// error parsing a packet, the whole packet is consumed from the input. +// ReadWithCheck additionally checks if the OpenPGP message packet sequence adheres +// to the packet composition rules in rfc4880, if not throws an error. +func ReadWithCheck(r io.Reader, sequence *SequenceVerifier) (p Packet, msgErr error, err error) { + tag, len, contents, err := readHeader(r) + if err != nil { + return + } + switch tag { + case packetTypeEncryptedKey: + msgErr = sequence.Next(ESKSymbol) + p = new(EncryptedKey) + case packetTypeSignature: + msgErr = sequence.Next(SigSymbol) + p = new(Signature) + case packetTypeSymmetricKeyEncrypted: + msgErr = sequence.Next(ESKSymbol) + p = new(SymmetricKeyEncrypted) + case packetTypeOnePassSignature: + msgErr = sequence.Next(OPSSymbol) + p = new(OnePassSignature) + case packetTypeCompressed: + msgErr = sequence.Next(CompSymbol) + p = new(Compressed) + case packetTypeSymmetricallyEncrypted: + msgErr = sequence.Next(EncSymbol) + p = new(SymmetricallyEncrypted) + case packetTypeLiteralData: + msgErr = sequence.Next(LDSymbol) + p = new(LiteralData) + case packetTypeSymmetricallyEncryptedIntegrityProtected: + msgErr = sequence.Next(EncSymbol) + se := new(SymmetricallyEncrypted) + se.IntegrityProtected = true + p = se + case packetTypeAEADEncrypted: + msgErr = sequence.Next(EncSymbol) + p = new(AEADEncrypted) + case packetPadding: + p = Padding(len) + case packetTypeMarker: + p = new(Marker) + case packetTypeTrust: + // Not implemented, just consume err = errors.UnknownPacketTypeError(tag) + case packetTypePrivateKey, + packetTypePrivateSubkey, + packetTypePublicKey, + packetTypePublicSubkey, + packetTypeUserId, + packetTypeUserAttribute: + msgErr = sequence.Next(UnknownSymbol) + consumeAll(contents) + default: + // Packet Tags from 0 to 39 are critical. + // Packet Tags from 40 to 63 are non-critical. + if tag < 40 { + err = errors.CriticalUnknownPacketTypeError(tag) + } else { + err = errors.UnknownPacketTypeError(tag) + } } if p != nil { err = p.parse(contents) @@ -539,8 +624,17 @@ const ( KeySuperseded ReasonForRevocation = 1 KeyCompromised ReasonForRevocation = 2 KeyRetired ReasonForRevocation = 3 + UserIDNotValid ReasonForRevocation = 32 + Unknown ReasonForRevocation = 200 ) +func NewReasonForRevocation(value byte) ReasonForRevocation { + if value < 4 || value == 32 { + return ReasonForRevocation(value) + } + return Unknown +} + // Curve is a mapping to supported ECC curves for key generation. // See https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-06.html#name-curve-specific-wire-formats type Curve string diff --git a/openpgp/packet/packet_sequence.go b/openpgp/packet/packet_sequence.go new file mode 100644 index 000000000..55a8a56c2 --- /dev/null +++ b/openpgp/packet/packet_sequence.go @@ -0,0 +1,222 @@ +package packet + +// This file implements the pushdown automata (PDA) from PGPainless (Paul Schaub) +// to verify pgp packet sequences. See Paul's blogpost for more details: +// https://blog.jabberhead.tk/2022/10/26/implementing-packet-sequence-validation-using-pushdown-automata/ +import ( + "fmt" + + "github.com/ProtonMail/go-crypto/openpgp/errors" +) + +func NewErrMalformedMessage(from State, input InputSymbol, stackSymbol StackSymbol) errors.ErrMalformedMessage { + return errors.ErrMalformedMessage(fmt.Sprintf("state %d, input symbol %d, stack symbol %d ", from, input, stackSymbol)) +} + +// InputSymbol defines the input alphabet of the PDA +type InputSymbol uint8 + +const ( + LDSymbol InputSymbol = iota + SigSymbol + OPSSymbol + CompSymbol + ESKSymbol + EncSymbol + EOSSymbol + UnknownSymbol +) + +// StackSymbol defines the stack alphabet of the PDA +type StackSymbol int8 + +const ( + MsgStackSymbol StackSymbol = iota + OpsStackSymbol + KeyStackSymbol + EndStackSymbol + EmptyStackSymbol +) + +// State defines the states of the PDA +type State int8 + +const ( + OpenPGPMessage State = iota + ESKMessage + LiteralMessage + CompressedMessage + EncryptedMessage + ValidMessage +) + +// transition represents a state transition in the PDA +type transition func(input InputSymbol, stackSymbol StackSymbol) (State, []StackSymbol, bool, error) + +// SequenceVerifier is a pushdown automata to verify +// PGP messages packet sequences according to rfc4880. +type SequenceVerifier struct { + stack []StackSymbol + state State +} + +// Next performs a state transition with the given input symbol. +// If the transition fails a ErrMalformedMessage is returned. +func (sv *SequenceVerifier) Next(input InputSymbol) error { + for { + stackSymbol := sv.popStack() + transitionFunc := getTransition(sv.state) + nextState, newStackSymbols, redo, err := transitionFunc(input, stackSymbol) + if err != nil { + return err + } + if redo { + sv.pushStack(stackSymbol) + } + for _, newStackSymbol := range newStackSymbols { + sv.pushStack(newStackSymbol) + } + sv.state = nextState + if !redo { + break + } + } + return nil +} + +// Valid returns true if RDA is in a valid state. +func (sv *SequenceVerifier) Valid() bool { + return sv.state == ValidMessage && len(sv.stack) == 0 +} + +func (sv *SequenceVerifier) AssertValid() error { + if !sv.Valid() { + return errors.ErrMalformedMessage("invalid message") + } + return nil +} + +func NewSequenceVerifier() *SequenceVerifier { + return &SequenceVerifier{ + stack: []StackSymbol{EndStackSymbol, MsgStackSymbol}, + state: OpenPGPMessage, + } +} + +func (sv *SequenceVerifier) popStack() StackSymbol { + if len(sv.stack) == 0 { + return EmptyStackSymbol + } + elemIndex := len(sv.stack) - 1 + stackSymbol := sv.stack[elemIndex] + sv.stack = sv.stack[:elemIndex] + return stackSymbol +} + +func (sv *SequenceVerifier) pushStack(stackSymbol StackSymbol) { + sv.stack = append(sv.stack, stackSymbol) +} + +func getTransition(from State) transition { + switch from { + case OpenPGPMessage: + return fromOpenPGPMessage + case LiteralMessage: + return fromLiteralMessage + case CompressedMessage: + return fromCompressedMessage + case EncryptedMessage: + return fromEncryptedMessage + case ESKMessage: + return fromESKMessage + case ValidMessage: + return fromValidMessage + } + return nil +} + +// fromOpenPGPMessage is the transition for the state OpenPGPMessage. +func fromOpenPGPMessage(input InputSymbol, stackSymbol StackSymbol) (State, []StackSymbol, bool, error) { + if stackSymbol != MsgStackSymbol { + return 0, nil, false, NewErrMalformedMessage(OpenPGPMessage, input, stackSymbol) + } + switch input { + case LDSymbol: + return LiteralMessage, nil, false, nil + case SigSymbol: + return OpenPGPMessage, []StackSymbol{MsgStackSymbol}, false, nil + case OPSSymbol: + return OpenPGPMessage, []StackSymbol{OpsStackSymbol, MsgStackSymbol}, false, nil + case CompSymbol: + return CompressedMessage, nil, false, nil + case ESKSymbol: + return ESKMessage, []StackSymbol{KeyStackSymbol}, false, nil + case EncSymbol: + return EncryptedMessage, nil, false, nil + } + return 0, nil, false, NewErrMalformedMessage(OpenPGPMessage, input, stackSymbol) +} + +// fromESKMessage is the transition for the state ESKMessage. +func fromESKMessage(input InputSymbol, stackSymbol StackSymbol) (State, []StackSymbol, bool, error) { + if stackSymbol != KeyStackSymbol { + return 0, nil, false, NewErrMalformedMessage(ESKMessage, input, stackSymbol) + } + switch input { + case ESKSymbol: + return ESKMessage, []StackSymbol{KeyStackSymbol}, false, nil + case EncSymbol: + return EncryptedMessage, nil, false, nil + } + return 0, nil, false, NewErrMalformedMessage(ESKMessage, input, stackSymbol) +} + +// fromLiteralMessage is the transition for the state LiteralMessage. +func fromLiteralMessage(input InputSymbol, stackSymbol StackSymbol) (State, []StackSymbol, bool, error) { + switch input { + case SigSymbol: + if stackSymbol == OpsStackSymbol { + return LiteralMessage, nil, false, nil + } + case EOSSymbol: + if stackSymbol == EndStackSymbol { + return ValidMessage, nil, false, nil + } + } + return 0, nil, false, NewErrMalformedMessage(LiteralMessage, input, stackSymbol) +} + +// fromLiteralMessage is the transition for the state CompressedMessage. +func fromCompressedMessage(input InputSymbol, stackSymbol StackSymbol) (State, []StackSymbol, bool, error) { + switch input { + case SigSymbol: + if stackSymbol == OpsStackSymbol { + return CompressedMessage, nil, false, nil + } + case EOSSymbol: + if stackSymbol == EndStackSymbol { + return ValidMessage, nil, false, nil + } + } + return OpenPGPMessage, []StackSymbol{MsgStackSymbol}, true, nil +} + +// fromEncryptedMessage is the transition for the state EncryptedMessage. +func fromEncryptedMessage(input InputSymbol, stackSymbol StackSymbol) (State, []StackSymbol, bool, error) { + switch input { + case SigSymbol: + if stackSymbol == OpsStackSymbol { + return EncryptedMessage, nil, false, nil + } + case EOSSymbol: + if stackSymbol == EndStackSymbol { + return ValidMessage, nil, false, nil + } + } + return OpenPGPMessage, []StackSymbol{MsgStackSymbol}, true, nil +} + +// fromValidMessage is the transition for the state ValidMessage. +func fromValidMessage(input InputSymbol, stackSymbol StackSymbol) (State, []StackSymbol, bool, error) { + return 0, nil, false, NewErrMalformedMessage(ValidMessage, input, stackSymbol) +} diff --git a/openpgp/packet/packet_unsupported.go b/openpgp/packet/packet_unsupported.go new file mode 100644 index 000000000..2d714723c --- /dev/null +++ b/openpgp/packet/packet_unsupported.go @@ -0,0 +1,24 @@ +package packet + +import ( + "io" + + "github.com/ProtonMail/go-crypto/openpgp/errors" +) + +// UnsupportedPackage represents a OpenPGP packet with a known packet type +// but with unsupported content. +type UnsupportedPacket struct { + IncompletePacket Packet + Error errors.UnsupportedError +} + +// Implements the Packet interface +func (up *UnsupportedPacket) parse(read io.Reader) error { + err := up.IncompletePacket.parse(read) + if castedErr, ok := err.(errors.UnsupportedError); ok { + up.Error = castedErr + return nil + } + return err +} diff --git a/openpgp/packet/padding.go b/openpgp/packet/padding.go index 2ceab9346..f61c190f2 100644 --- a/openpgp/packet/padding.go +++ b/openpgp/packet/padding.go @@ -16,6 +16,10 @@ func (pad Padding) parse(reader io.Reader) error { // SerializePadding writes the padding to writer. func (pad Padding) SerializePadding(writer io.Writer, rand io.Reader) error { - _, err := io.CopyN(writer, rand, int64(pad)) + err := serializeHeader(writer, packetPadding, int(pad)) + if err != nil { + return err + } + _, err = io.CopyN(writer, rand, int64(pad)) return err } diff --git a/openpgp/packet/public_key.go b/openpgp/packet/public_key.go index e550231d2..73fbb47c4 100644 --- a/openpgp/packet/public_key.go +++ b/openpgp/packet/public_key.go @@ -393,16 +393,17 @@ func (pk *PublicKey) parseECDSA(r io.Reader) (err error) { if _, err = pk.oid.ReadFrom(r); err != nil { return } - pk.p = new(encoding.MPI) - if _, err = pk.p.ReadFrom(r); err != nil { - return - } curveInfo := ecc.FindByOid(pk.oid) if curveInfo == nil { return errors.UnsupportedError(fmt.Sprintf("unknown oid: %x", pk.oid)) } + pk.p = new(encoding.MPI) + if _, err = pk.p.ReadFrom(r); err != nil { + return + } + c, ok := curveInfo.Curve.(ecc.ECDSACurve) if !ok { return errors.UnsupportedError(fmt.Sprintf("unsupported oid: %x", pk.oid)) @@ -422,6 +423,12 @@ func (pk *PublicKey) parseECDH(r io.Reader) (err error) { if _, err = pk.oid.ReadFrom(r); err != nil { return } + + curveInfo := ecc.FindByOid(pk.oid) + if curveInfo == nil { + return errors.UnsupportedError(fmt.Sprintf("unknown oid: %x", pk.oid)) + } + pk.p = new(encoding.MPI) if _, err = pk.p.ReadFrom(r); err != nil { return @@ -431,12 +438,6 @@ func (pk *PublicKey) parseECDH(r io.Reader) (err error) { return } - curveInfo := ecc.FindByOid(pk.oid) - - if curveInfo == nil { - return errors.UnsupportedError(fmt.Sprintf("unknown oid: %x", pk.oid)) - } - c, ok := curveInfo.Curve.(ecc.ECDHCurve) if !ok { return errors.UnsupportedError(fmt.Sprintf("unsupported oid: %x", pk.oid)) @@ -469,6 +470,7 @@ func (pk *PublicKey) parseEdDSA(r io.Reader) (err error) { if _, err = pk.oid.ReadFrom(r); err != nil { return } + curveInfo := ecc.FindByOid(pk.oid) if curveInfo == nil { return errors.UnsupportedError(fmt.Sprintf("unknown oid: %x", pk.oid)) @@ -994,15 +996,35 @@ func (pk *PublicKey) BitLength() (bitLength uint16, err error) { return } +// Curve returns the used elliptic curve of this public key. +// Returns an error if no elliptic curve is used. +func (pk *PublicKey) Curve() (curve Curve, err error) { + switch pk.PubKeyAlgo { + case PubKeyAlgoECDSA, PubKeyAlgoECDH, PubKeyAlgoEdDSA: + curveInfo := ecc.FindByOid(pk.oid) + if curveInfo == nil { + return "", errors.UnsupportedError(fmt.Sprintf("unknown oid: %x", pk.oid)) + } + curve = Curve(curveInfo.GenName) + case PubKeyAlgoEd25519, PubKeyAlgoX25519: + curve = Curve25519 + case PubKeyAlgoEd448, PubKeyAlgoX448: + curve = Curve448 + default: + err = errors.InvalidArgumentError("public key does not operate with an elliptic curve") + } + return +} + // KeyExpired returns whether sig is a self-signature of a key that has // expired or is created in the future. func (pk *PublicKey) KeyExpired(sig *Signature, currentTime time.Time) bool { - if pk.CreationTime.After(currentTime) { + if pk.CreationTime.Unix() > currentTime.Unix() { return true } if sig.KeyLifetimeSecs == nil || *sig.KeyLifetimeSecs == 0 { return false } expiry := pk.CreationTime.Add(time.Duration(*sig.KeyLifetimeSecs) * time.Second) - return currentTime.After(expiry) + return currentTime.Unix() > expiry.Unix() } diff --git a/openpgp/packet/reader.go b/openpgp/packet/reader.go index 10215fe5f..b66985f28 100644 --- a/openpgp/packet/reader.go +++ b/openpgp/packet/reader.go @@ -10,6 +10,12 @@ import ( "github.com/ProtonMail/go-crypto/openpgp/errors" ) +type PacketReader interface { + Next() (p Packet, err error) + Push(reader io.Reader) (err error) + Unread(p Packet) +} + // Reader reads packets from an io.Reader and allows packets to be 'unread' so // that they result from the next call to Next. type Reader struct { @@ -26,37 +32,71 @@ type Reader struct { const maxReaders = 32 // Next returns the most recently unread Packet, or reads another packet from -// the top-most io.Reader. Unknown packet types are skipped. +// the top-most io.Reader. Unknown/unsupported packet types are skipped. func (r *Reader) Next() (p Packet, err error) { + for { + p, err := r.read() + if err == io.EOF { + break + } else if err != nil { + if _, ok := err.(errors.UnknownPacketTypeError); ok { + continue + } + if _, ok := err.(errors.UnsupportedError); ok { + switch p.(type) { + case *SymmetricallyEncrypted, *AEADEncrypted, *Compressed, *LiteralData: + return nil, err + } + continue + } + return nil, err + } else { + return p, nil + } + } + return nil, io.EOF +} + +// Next returns the most recently unread Packet, or reads another packet from +// the top-most io.Reader. Unknown packet types are skipped while unsupported +// packets are returned as UnsupportedPacket type. +func (r *Reader) NextWithUnsupported() (p Packet, err error) { + for { + p, err = r.read() + if err == io.EOF { + break + } else if err != nil { + if _, ok := err.(errors.UnknownPacketTypeError); ok { + continue + } + if casteErr, ok := err.(errors.UnsupportedError); ok { + return &UnsupportedPacket{ + IncompletePacket: p, + Error: casteErr, + }, nil + } + return + } else { + return + } + } + return nil, io.EOF +} + +func (r *Reader) read() (p Packet, err error) { if len(r.q) > 0 { p = r.q[len(r.q)-1] r.q = r.q[:len(r.q)-1] return } - for len(r.readers) > 0 { p, err = Read(r.readers[len(r.readers)-1]) - if err == nil { - return - } if err == io.EOF { r.readers = r.readers[:len(r.readers)-1] continue } - // TODO: Add strict mode that rejects unknown packets, instead of ignoring them. - if _, ok := err.(errors.UnknownPacketTypeError); ok { - continue - } - if _, ok := err.(errors.UnsupportedError); ok { - switch p.(type) { - case *SymmetricallyEncrypted, *AEADEncrypted, *Compressed, *LiteralData: - return nil, err - } - continue - } - return nil, err + return p, err } - return nil, io.EOF } @@ -84,3 +124,76 @@ func NewReader(r io.Reader) *Reader { readers: []io.Reader{r}, } } + +// CheckReader is similar to Reader but additionally +// uses the pushdown automata to verify the read packet sequence. +type CheckReader struct { + Reader + verifier *SequenceVerifier + fullyRead bool +} + +// Next returns the most recently unread Packet, or reads another packet from +// the top-most io.Reader. Unknown packet types are skipped. +// If the read packet sequence does not conform to the packet composition +// rules in rfc4880, it returns an error. +func (r *CheckReader) Next() (p Packet, err error) { + if r.fullyRead { + return nil, io.EOF + } + if len(r.q) > 0 { + p = r.q[len(r.q)-1] + r.q = r.q[:len(r.q)-1] + return + } + var errMsg error + for len(r.readers) > 0 { + p, errMsg, err = ReadWithCheck(r.readers[len(r.readers)-1], r.verifier) + if errMsg != nil { + err = errMsg + return + } + if err == nil { + return + } + if err == io.EOF { + r.readers = r.readers[:len(r.readers)-1] + continue + } + //A marker packet MUST be ignored when received + switch p.(type) { + case *Marker: + continue + } + if _, ok := err.(errors.UnknownPacketTypeError); ok { + continue + } + if _, ok := err.(errors.UnsupportedError); ok { + switch p.(type) { + case *SymmetricallyEncrypted, *AEADEncrypted, *Compressed, *LiteralData: + return nil, err + } + continue + } + return nil, err + } + if errMsg = r.verifier.Next(EOSSymbol); errMsg != nil { + return nil, errMsg + } + if errMsg = r.verifier.AssertValid(); errMsg != nil { + return nil, errMsg + } + r.fullyRead = true + return nil, io.EOF +} + +func NewCheckReader(r io.Reader) *CheckReader { + return &CheckReader{ + Reader: Reader{ + q: nil, + readers: []io.Reader{r}, + }, + verifier: NewSequenceVerifier(), + fullyRead: false, + } +} diff --git a/openpgp/packet/recipient.go b/openpgp/packet/recipient.go new file mode 100644 index 000000000..fb2e362e4 --- /dev/null +++ b/openpgp/packet/recipient.go @@ -0,0 +1,15 @@ +package packet + +// Recipient type represents a Intended Recipient Fingerprint subpacket +// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh#name-intended-recipient-fingerpr +type Recipient struct { + KeyVersion int + Fingerprint []byte +} + +func (r *Recipient) Serialize() []byte { + packet := make([]byte, len(r.Fingerprint)+1) + packet[0] = byte(r.KeyVersion) + copy(packet[1:], r.Fingerprint) + return packet +} diff --git a/openpgp/packet/signature.go b/openpgp/packet/signature.go index 09096abba..5e9ecb39c 100644 --- a/openpgp/packet/signature.go +++ b/openpgp/packet/signature.go @@ -78,6 +78,7 @@ type Signature struct { SignerUserId *string IsPrimaryId *bool Notations []*Notation + IntendedRecipients []*Recipient // TrustLevel and TrustAmount can be set by the signer to assert that // the key is not only valid but also trustworthy at the specified @@ -119,6 +120,13 @@ type Signature struct { outSubpackets []outputSubpacket } +// VerifiableSignature internally keeps state if the +// the signature has been verified before. +type VerifiableSignature struct { + Valid *bool // nil if it has not been verified yet + Packet *Signature +} + // SaltedHashSpecifier specifies that the given salt and hash are // used by a v6 signature. type SaltedHashSpecifier struct { @@ -126,6 +134,12 @@ type SaltedHashSpecifier struct { Salt []byte } +func NewVerifiableSig(signature *Signature) *VerifiableSignature { + return &VerifiableSignature{ + Packet: signature, + } +} + func (sig *Signature) Salt() []byte { if sig == nil { return nil @@ -338,6 +352,7 @@ const ( featuresSubpacket signatureSubpacketType = 30 embeddedSignatureSubpacket signatureSubpacketType = 32 issuerFingerprintSubpacket signatureSubpacketType = 33 + intendedRecipientSubpacket signatureSubpacketType = 35 prefCipherSuitesSubpacket signatureSubpacketType = 39 ) @@ -531,7 +546,7 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r return } sig.RevocationReason = new(ReasonForRevocation) - *sig.RevocationReason = ReasonForRevocation(subpacket[0]) + *sig.RevocationReason = NewReasonForRevocation(subpacket[0]) sig.RevocationReasonText = string(subpacket[1:]) case featuresSubpacket: // Features subpacket, section 5.2.3.24 specifies a very general @@ -584,6 +599,19 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r } else { *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 + if len(subpacket) < 1 { + return nil, errors.StructuralError("invalid intended recipient fingerpring length") + } + version, length := subpacket[0], len(subpacket[1:]) + if version >= 5 && length != 32 || version < 5 && length != 20 { + return nil, errors.StructuralError("invalid fingerprint length") + } + fingerprint := make([]byte, length) + 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 @@ -628,6 +656,13 @@ func (sig *Signature) CheckKeyIdOrFingerprint(pk *PublicKey) bool { return sig.IssuerKeyId != nil && *sig.IssuerKeyId == pk.KeyId } +func (sig *Signature) CheckKeyIdOrFingerprintExplicit(fingerprint []byte, keyId uint64) bool { + if sig.IssuerFingerprint != nil && len(sig.IssuerFingerprint) >= 20 && fingerprint != nil { + return bytes.Equal(sig.IssuerFingerprint, fingerprint) + } + return sig.IssuerKeyId != nil && *sig.IssuerKeyId == keyId +} + // serializeSubpacketLength marshals the given length into to. func serializeSubpacketLength(to []byte, length int) int { // RFC 4880, Section 4.2.2. @@ -682,14 +717,14 @@ func serializeSubpackets(to []byte, subpackets []outputSubpacket, hashed bool) { // SigExpired returns whether sig is a signature that has expired or is created // in the future. func (sig *Signature) SigExpired(currentTime time.Time) bool { - if sig.CreationTime.After(currentTime) { + if sig.CreationTime.Unix() > currentTime.Unix() { return true } if sig.SigLifetimeSecs == nil || *sig.SigLifetimeSecs == 0 { return false } expiry := sig.CreationTime.Add(time.Duration(*sig.SigLifetimeSecs) * time.Second) - return currentTime.After(expiry) + return currentTime.Unix() > expiry.Unix() } // buildHashSuffix constructs the HashSuffix member of sig in preparation for signing. @@ -1209,6 +1244,17 @@ func (sig *Signature) buildSubpackets(issuer PublicKey) (subpackets []outputSubp }) } + for _, recipient := range sig.IntendedRecipients { + subpackets = append( + subpackets, + outputSubpacket{ + true, + intendedRecipientSubpacket, + false, + recipient.Serialize(), + }) + } + // The following subpackets may only appear in self-signatures. var features = byte(0x00) From 633db874c2f62263a6d3e53bb70ea9cc203e9464 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Tue, 8 Aug 2023 11:40:22 +0200 Subject: [PATCH 045/116] Update depdency versions --- go.mod | 2 +- go.sum | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index 3e49a1371..bfce768fd 100644 --- a/go.mod +++ b/go.mod @@ -4,5 +4,5 @@ go 1.13 require ( github.com/cloudflare/circl v1.3.3 - golang.org/x/crypto v0.7.0 + golang.org/x/crypto v0.9.0 ) diff --git a/go.sum b/go.sum index 90f8b274e..8085d85e1 100644 --- a/go.sum +++ b/go.sum @@ -5,8 +5,8 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -14,7 +14,7 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -26,19 +26,19 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= From 696c0232c1e4b78efdaa7b9cb800f9376148769d Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Tue, 8 Aug 2023 11:40:53 +0200 Subject: [PATCH 046/116] Update readme and changelog for v2 --- CHANGELOG-V2.md | 172 ++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 2 + 2 files changed, 174 insertions(+) create mode 100644 CHANGELOG-V2.md diff --git a/CHANGELOG-V2.md b/CHANGELOG-V2.md new file mode 100644 index 000000000..91699c9f4 --- /dev/null +++ b/CHANGELOG-V2.md @@ -0,0 +1,172 @@ +# Version 2 Update + +This document summarizes the major changes for reading and writing OpenPGP message introduced by `ProtonMail/go-crypto/openpgp/v2` compared to `ProtonMail/go-crypto/openpgp`. + + +## Import + +The v2 API can be imported as: +``` +openpgp ProtonMail/go-crypto/openpgp/v2 +``` + +## API breaking changes + +`openpgp.v2.Entity` struct fields have changed: +- `SelfSignature *packet.Signature` removed +- `Signatures []*packet.Signature` removed +- `DirectSignatures []*packet.VerifiableSignature` added +- `Revocations []*packet.VerifiableSignature` changed type + +`openpgp.v2.Entity` changed API methods: +- `PrimaryIdentity(date time.Time)` has a time argument now. +- `EncryptionKey(date time.Time, config *packet.Config)` has a new config argument. +- `CertificationKey(date time.Time, config *packet.Config)` has a new config argument. +- `CertificationKeyById(date time.Time, id uint64, config *packet.Config)` has a new config argument. +- `SigningKey(date time.Time, config *packet.Config)` has a new config argument. +- `SigningKeyById(date time.Time, id uint64, config *packet.Config)` has a new config argument. +- `Revoke(reason packet.ReasonForRevocation, reasonText string, config *packet.Config)` changed name instead of RevokeKey. + +`openpgp.v2.Entity` removed API methods: +- `RevokeSubkey(...) ` replaced by `(Subkey).Revoke(...)` + +`openpgp.v2.Subkey` struct fields have changed: +- `Sig *packet.Signature` removed +- `Bindings []*packet.VerifiableSignature` added +- `Primary *Entity` added, points to the primary key. +- `Revocations []*packet.VerifiableSignature` changed type + +`openpgp.v2.Subkey` changed API methods: +- `Revoked(selfCertification *packet.Signature, date time.Time)` has a new selfCertification argument, which points to the self signature to be used. + +`openpgp.v2.Identity` struct fields have changed: +- `SelfSignature *packet.Signature` removed +- `Signatures []*packet.Signature ` removed +- `SelfCertifications []*packet.VerifiableSignature` added +- `OtherCertifications []*packet.VerifiableSignature` added +- `Primary *Entity` added, points to the primary key. +- `Revocations []*packet.VerifiableSignature` changed type + +`openpgp.v2.Identity` changed API methods: +- `Revoked(selfCertification *packet.Signature, date time.Time)` has a new selfCertification argument, which points to the self signature to be used. + +`openpgp.v2.Key` struct fields have changed: +- `PrimarySelfSignature *packet.Signature ` added, which points to the selected self signature of the primary key. +- `Revocations []*packet.VerifiableSignature` changed type + +`openpgp.v2.KeyRing` interface has has changed: +- `KeysByIdUsage(...)` removed +- `DecryptionKeys(...)` removed +- `EntitiesById(id uint64) []*Entity` added. This is the main internal method to access keys from the keyring now. + +`openpgp.v2.FileHints` struct field has changed: +- `IsBinary` removed and `IsUTF8` added + +`openpgp.v2` API changes for reading messages: +- `VerifyDetachedSignatureAndHash(...)` removed, headers in clearsigned messages are no longer checked. +- `VerifyDetachedSignatureAndSaltedHash(...)` removed +- `CheckDetachedSignature(...)` removed, call `VerifyDetachedSignature(...)` instead +- `CheckDetachedSignatureAndSaltedHash(...)` removed +- `CheckDetachedSignatureAndHash(...)` removed +- `CheckArmoredDetachedSignature` removed call `VerifyArmoredDetachedSignature` instead + +`openpgp.v2` API changes for writing messages: +- `DetachSign(..., signers []*Entity,...)` takes now a slice of entities instead of a single entity as an argument. +- `ArmoredDetachSign(..., signers []*Entity,...)` takes now a slice of entities instead of a single entity as an argument. +- `DetachSignText(..., signers []*Entity,...)` takes now a slice of entities instead of a single entity as an argument. +- `ArmoredDetachSignText(..., signers []*Entity,...)` takes now a slice of entities instead of a single entity as an argument. +- `EncryptText(...)` removed call `EncryptWithParams(...)` instead +- `EncryptSplit(...)` removed call `EncryptWithParams(...)` instead +- `EncryptTextSplit(...)` removed call `EncryptWithParams(...)` instead +- `Encrypt(..., toHidden []*Entity, signers []*Entity)` now takes an additional toHidden recipients argument and takes now a slice of signer entities instead of a single entity as an argument. +- `Sign(..., signers []*Entity,...)` takes now a slice of entities instead of a single entity as an argument. + +## Features added + +### Intended recipients + +Version 2 of the ProtonMail/go-crypto library introduces a feature for including the recipients' key fingerprints in signatures during message encryption. +When encrypting and signing a message, the intended recipients are automatically included in the signature, unless specifically hidden (i.e., hidden recipients). +During the decryption process, if the signature contains intended recipients and the appropriate configuration flag is set, the library verifies whether the primary ID of the decryption key is present in the recipient list. +This check can be disabled in the config when a hidden recipient decrypts the message. + +### Multi-signature support + +In previous iterations of ProtonMail/go-crypto, only a single signature creation and verification were supported in a PGP message. +However, in Version 2, the library introduces the ability to sign messages with multiple signatures using different keys, such as a v4 and a v6 key. +The encryption and signing methods now accept multiple signing keys as arguments, with each key designated for a specific signature. +When reading PGP messages with Version 2, the library maintains an internal state for each known signature and verifies all of them within the message. +To facilitate this functionality, the message details struct includes a new field that stores the verification state for each signature. A message is considered valid if at least one of the signatures successfully validates without any errors. +For callers, the process of checking for signature errors remains similar to previous versions. +However, if the caller requires the verification state of all signatures, they can utilize the new field in the message details struct. + +### Rework how signatures in keys and signatures are verified + +In previous iterations of ProtonMail/go-crypto, key verification occurred during import based on the current time, while signature verification did not involve further key checks. +However, this approach had limitations, as invalid keys could have been valid at the time of signature creation and mistakenly considered invalid. + +Version 2 changes how and when signatures are verified in keys (i.e., direct-signatures, self-signatures of userids, binding signatures in subkeys, revocations, etc). +Unlike before, key signature verification no longer takes place during parsing. +Instead, keys are now validated when they are utilized, following a similar approach to key handling in OpenPGP.js. +Additionally, all signatures and expirations are validated to adhere to the key lifecycle outlined in the RFC. +The validity of keys can now be checked at different points in time, leading to the following specific modifications: +- During entity parsing, key validity is not checked. +- When used for encryption or signing, keys are verified using the current time during the writing process. +- During reading, the library verifies that each verification key was valid at the time of signature creation. +- A clear separation is maintained between Entity, Subkey, Identity, and their respective validation methods. +- Signature verification results are cached and reused to optimize computation. + +Further, version 2 includes various small improvements to increase the robustness of the key parsing functions. + +### Weak algorithm rejection + +Version 2 introduces the option to specify weak algorithms for signatures in the config. +Signatures that use weak algorithms are considered invalid. + +### Optional packet sequence checker + +Version 2 introduces a new feature that enables the validation of packet sequences in PGP messages. +This functionality can be enabled in the config struct. +In particular, it implements the pushdown automata (PDA) from PGPainless, developed by Paul Schaub. +By leveraging this feature, users can ensure that the packet sequences in their PGP messages are valid and comply with the required structure. +This addition further enhances the overall reliability and security of PGP message handling in Version 2. + +### Session key encryption and decryption + +Version 2 allows advanced users to retrieve the session key while encrypting a message by setting the respective flag in the config. +In decryption, a caller can provide a session key that should be used for decryption. + +### Unify write/read API + +Version 2 improves the compatibility between different APIs to allow combinations. +The `DetachSign` function requires the caller to provide a `Reader` for the message, while +encrypt returns a `WriteCloser` to which the message is written to. +The new version adds a function `DetachSignWriter`, which returns a `WriteCloser` similar to the encryption API. +On the reading side, the verify detached signature API now relies on the +same signature verification logic as the other read functions. +Additionally, a new `VerifyDetachedSignatureReader` method similar to the `ReadMessage` API is introduced. +It returns a message details struct that once read verifies the signature. +Allows to chain different readers from the API, for example, to have a streaming API for encrypted detached signatures. + +### Params struct as a function argument in the write API + +With the inclusion of new features, the write functions in go-crypto experienced significant growth in numbers. Each combination has its dedicated function. +Version 2 introduces an `EncryptWithParams`/`SignWithParams` function that takes an `EncryptParams`/`SignParams` struct as an argument. The struct allows configuring the different features. +This approach effectively reduces the number of API methods and simplifies the process of adding new features while maintaining compatibility with previous versions. + +### Others + +- Disable armor checksum on default `armor.Encode` method +- Make `unarmor` more robust to empty header values +- Allow key generation of v6 keys without a `Identity` +- Allow compression in inline signed messages +- Consider key preferences in detached signatures +- Only compare time at a second granularity +- Signal if tag is not verified on close in aead decryption +- Ensure that critical unknown packet tags result in message rejection +- Ensure that decompression streams are closed and that the packet is completely read +- Ensure that entity parsing does not reject keys with unknown subkeys +- Check for known curves early when parsing ECDSA and ECDH keys +- Skip signatures with the wrong type while parsing an entity +- Support for signatures that appear in front of the data +- Change file hints and literal packet field IsBinary to IsUTF8 diff --git a/README.md b/README.md index 0f2a16394..dfbc5bd1c 100644 --- a/README.md +++ b/README.md @@ -7,3 +7,5 @@ so you can simply replace all imports of `golang.org/x/crypto/openpgp` with `github.com/ProtonMail/go-crypto/openpgp`. A partial list of changes is here: https://github.com/ProtonMail/go-crypto/issues/21#issuecomment-492792917. + +For the more extended API for reading and writing OpenPGP messages use `github.com/ProtonMail/go-crypto/openpgp/v2`, but it is not fully backwards compatible with `golang.org/x/crypto/openpgp`. From 60fa7132ff3b1ad256099dce78e42a3a3be417e2 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Tue, 15 Aug 2023 14:38:37 +0200 Subject: [PATCH 047/116] feat: Unify write API by offering sign detached with params --- CHANGELOG-V2.md | 4 +- .../integration_tests/v2/end_to_end_test.go | 10 ++-- openpgp/v2/keys_test.go | 12 +++-- openpgp/v2/write.go | 48 +++++++++++-------- openpgp/v2/write_test.go | 5 +- 5 files changed, 46 insertions(+), 33 deletions(-) diff --git a/CHANGELOG-V2.md b/CHANGELOG-V2.md index 91699c9f4..606420929 100644 --- a/CHANGELOG-V2.md +++ b/CHANGELOG-V2.md @@ -72,7 +72,7 @@ openpgp ProtonMail/go-crypto/openpgp/v2 `openpgp.v2` API changes for writing messages: - `DetachSign(..., signers []*Entity,...)` takes now a slice of entities instead of a single entity as an argument. -- `ArmoredDetachSign(..., signers []*Entity,...)` takes now a slice of entities instead of a single entity as an argument. +- `ArmoredDetachSign(..., signers []*Entity,..., , params *SignParams)` takes now a slice of entities instead of a single entity as an argument and replaces arguments with a SignParams object. - `DetachSignText(..., signers []*Entity,...)` takes now a slice of entities instead of a single entity as an argument. - `ArmoredDetachSignText(..., signers []*Entity,...)` takes now a slice of entities instead of a single entity as an argument. - `EncryptText(...)` removed call `EncryptWithParams(...)` instead @@ -169,4 +169,4 @@ This approach effectively reduces the number of API methods and simplifies the p - Check for known curves early when parsing ECDSA and ECDH keys - Skip signatures with the wrong type while parsing an entity - Support for signatures that appear in front of the data -- Change file hints and literal packet field IsBinary to IsUTF8 +- Change file hints field IsBinary to IsUTF8 diff --git a/openpgp/integration_tests/v2/end_to_end_test.go b/openpgp/integration_tests/v2/end_to_end_test.go index 8035dc343..2ed9e4b2a 100644 --- a/openpgp/integration_tests/v2/end_to_end_test.go +++ b/openpgp/integration_tests/v2/end_to_end_test.go @@ -293,12 +293,10 @@ func signVerifyTest( // Sign the message buf := new(bytes.Buffer) - var errSign error - if binary { - errSign = openpgp.ArmoredDetachSign(buf, skFrom[:1], message, &allowAllAlgorithmsConfig) - } else { - errSign = openpgp.ArmoredDetachSignText(buf, skFrom[:1], message, &allowAllAlgorithmsConfig) - } + errSign := openpgp.ArmoredDetachSign(buf, skFrom[:1], message, &openpgp.SignParams{ + TextSig: !binary, + Config: &allowAllAlgorithmsConfig, + }) if errSign != nil { t.Error(errSign) } diff --git a/openpgp/v2/keys_test.go b/openpgp/v2/keys_test.go index 02f2c14bd..aa215f4ce 100644 --- a/openpgp/v2/keys_test.go +++ b/openpgp/v2/keys_test.go @@ -201,8 +201,10 @@ func TestSignatureExpiry(t *testing.T) { // Make a signature that expires in a day. var signatureWriter2 bytes.Buffer message = strings.NewReader(input) - err = ArmoredDetachSign(&signatureWriter2, []*Entity{entity}, message, &packet.Config{ - SigLifetimeSecs: 24 * 60 * 60, + err = ArmoredDetachSign(&signatureWriter2, []*Entity{entity}, message, &SignParams{ + Config: &packet.Config{ + SigLifetimeSecs: 24 * 60 * 60, + }, }) if err != nil { t.Fatal(err) @@ -216,8 +218,10 @@ func TestSignatureExpiry(t *testing.T) { // Make a signature that was created in the future. var signatureWriter3 bytes.Buffer message = strings.NewReader(input) - err = ArmoredDetachSign(&signatureWriter3, []*Entity{entity}, message, &packet.Config{ - Time: futureTime, + err = ArmoredDetachSign(&signatureWriter3, []*Entity{entity}, message, &SignParams{ + Config: &packet.Config{ + Time: futureTime, + }, }) if err != nil { t.Fatal(err) diff --git a/openpgp/v2/write.go b/openpgp/v2/write.go index b21c6061d..2a5ef84ea 100644 --- a/openpgp/v2/write.go +++ b/openpgp/v2/write.go @@ -24,27 +24,32 @@ func DetachSign(w io.Writer, signers []*Entity, message io.Reader, config *packe return detachSign(w, signers, message, packet.SigTypeBinary, config) } -// ArmoredDetachSign signs message with the private key from signer (which -// must already have been decrypted) and writes an armored signature to w. -// If config is nil, sensible defaults will be used. -func ArmoredDetachSign(w io.Writer, signers []*Entity, message io.Reader, config *packet.Config) (err error) { - return armoredDetachSign(w, signers, message, packet.SigTypeBinary, config) -} - -// DetachSignText signs message (after canonicalising the line endings) with -// the private key from signer (which must already have been decrypted) and -// writes the signature to w. +// DetachSignWithParams signs message with the private key from signer (which must +// already have been decrypted) and writes the signature to w. // If config is nil, sensible defaults will be used. -func DetachSignText(w io.Writer, signers []*Entity, message io.Reader, config *packet.Config) error { - return detachSign(w, signers, message, packet.SigTypeText, config) +func DetachSignWithParams(w io.Writer, signers []*Entity, message io.Reader, params *SignParams) error { + if params == nil { + params = &SignParams{} + } + sigType := packet.SigTypeBinary + if params.TextSig { + sigType = packet.SigTypeText + } + return detachSign(w, signers, message, sigType, params.Config) } -// ArmoredDetachSignText signs message (after canonicalising the line endings) -// with the private key from signer (which must already have been decrypted) -// and writes an armored signature to w. +// ArmoredDetachSign signs message with the private key from signer (which +// must already have been decrypted) and writes an armored signature to w. // If config is nil, sensible defaults will be used. -func ArmoredDetachSignText(w io.Writer, signers []*Entity, message io.Reader, config *packet.Config) error { - return armoredDetachSign(w, signers, message, packet.SigTypeText, config) +func ArmoredDetachSign(w io.Writer, signers []*Entity, message io.Reader, params *SignParams) (err error) { + if params == nil { + params = &SignParams{} + } + sigType := packet.SigTypeBinary + if params.TextSig { + sigType = packet.SigTypeText + } + return armoredDetachSign(w, signers, message, sigType, params.Config) } // DetachSignWriter signs a message with the private key from a signer (which must @@ -54,12 +59,15 @@ func ArmoredDetachSignText(w io.Writer, signers []*Entity, message io.Reader, co // been written. If utf8Message is set to true, the line endings of the message are // canonicalised and the type of the signature will be SigTypeText. // If config is nil, sensible defaults will be used. -func DetachSignWriter(w io.Writer, signers []*Entity, utf8Message bool, config *packet.Config) (io.WriteCloser, error) { +func DetachSignWriter(w io.Writer, signers []*Entity, params *SignParams) (io.WriteCloser, error) { + if params == nil { + params = &SignParams{} + } sigType := packet.SigTypeBinary - if utf8Message { + if params.TextSig { sigType = packet.SigTypeText } - return detachSignWithWriter(w, signers, sigType, config) + return detachSignWithWriter(w, signers, sigType, params.Config) } func armoredDetachSign(w io.Writer, signers []*Entity, message io.Reader, sigType packet.SignatureType, config *packet.Config) (err error) { diff --git a/openpgp/v2/write_test.go b/openpgp/v2/write_test.go index b3e7d6d81..77e5bbe20 100644 --- a/openpgp/v2/write_test.go +++ b/openpgp/v2/write_test.go @@ -39,7 +39,10 @@ func TestSignTextDetached(t *testing.T) { kring, _ := ReadKeyRing(readerFromHex(testKeys1And2PrivateHex)) out := bytes.NewBuffer(nil) message := bytes.NewBufferString(signedInput) - err := DetachSignText(out, kring[:1], message, &allowAllAlgorithmsConfig) + err := DetachSignWithParams(out, kring[:1], message, &SignParams{ + TextSig: true, + Config: &allowAllAlgorithmsConfig, + }) if err != nil { t.Error(err) } From 3493ab3939777406c69126610562e858466d4ad8 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Wed, 16 Aug 2023 10:24:58 +0200 Subject: [PATCH 048/116] Remove invalid testcase in v2 after rebase --- openpgp/v2/read_test.go | 42 ----------------------------------------- 1 file changed, 42 deletions(-) diff --git a/openpgp/v2/read_test.go b/openpgp/v2/read_test.go index d393c7766..e0714f1f0 100644 --- a/openpgp/v2/read_test.go +++ b/openpgp/v2/read_test.go @@ -666,48 +666,6 @@ func TestReadV6Messages(t *testing.T) { } } -func TestSymmetricAeadGcmOpenPGPJsMessage(t *testing.T) { - passphrase := []byte("test") - file, err := os.Open("../test_data/aead-sym-message.asc") - if err != nil { - t.Fatal(err) - } - armoredEncryptedMessage, err := ioutil.ReadAll(file) - if err != nil { - t.Fatal(err) - } - // Unarmor string - raw, err := armor.Decode(strings.NewReader(string(armoredEncryptedMessage))) - if err != nil { - t.Error(err) - return - } - // Mock passphrase prompt - promptFunc := func(keys []Key, symmetric bool) ([]byte, error) { - return passphrase, nil - } - // Decrypt message - md, err := ReadMessage(raw.Body, nil, promptFunc, nil) - if err != nil { - t.Error(err) - return - } - contents, err := ioutil.ReadAll(md.UnverifiedBody) - if err != nil { - t.Errorf("error reading UnverifiedBody: %s", err) - } - - // The plaintext is https://www.gutenberg.org/cache/epub/1080/pg1080.txt - // We compare the SHA512 hashes. - wantHash := modestProposalSha512 - gotHashRaw := sha512.Sum512(contents) - gotHash := base64.StdEncoding.EncodeToString(gotHashRaw[:]) - - if wantHash != gotHash { - t.Fatal("Did not decrypt OpenPGPjs message correctly") - } -} - func TestSymmetricDecryptionArgon2(t *testing.T) { // Appendix IETF OpenPGP crypto refresh draft v08 A.8.1 passphrase := []byte("password") From c695db01a998b8ab7f40ccc23d1a498b619b1bd8 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Wed, 16 Aug 2023 10:36:09 +0200 Subject: [PATCH 049/116] Remove changelog-v2 file --- CHANGELOG-V2.md | 172 ------------------------------------------------ 1 file changed, 172 deletions(-) delete mode 100644 CHANGELOG-V2.md diff --git a/CHANGELOG-V2.md b/CHANGELOG-V2.md deleted file mode 100644 index 606420929..000000000 --- a/CHANGELOG-V2.md +++ /dev/null @@ -1,172 +0,0 @@ -# Version 2 Update - -This document summarizes the major changes for reading and writing OpenPGP message introduced by `ProtonMail/go-crypto/openpgp/v2` compared to `ProtonMail/go-crypto/openpgp`. - - -## Import - -The v2 API can be imported as: -``` -openpgp ProtonMail/go-crypto/openpgp/v2 -``` - -## API breaking changes - -`openpgp.v2.Entity` struct fields have changed: -- `SelfSignature *packet.Signature` removed -- `Signatures []*packet.Signature` removed -- `DirectSignatures []*packet.VerifiableSignature` added -- `Revocations []*packet.VerifiableSignature` changed type - -`openpgp.v2.Entity` changed API methods: -- `PrimaryIdentity(date time.Time)` has a time argument now. -- `EncryptionKey(date time.Time, config *packet.Config)` has a new config argument. -- `CertificationKey(date time.Time, config *packet.Config)` has a new config argument. -- `CertificationKeyById(date time.Time, id uint64, config *packet.Config)` has a new config argument. -- `SigningKey(date time.Time, config *packet.Config)` has a new config argument. -- `SigningKeyById(date time.Time, id uint64, config *packet.Config)` has a new config argument. -- `Revoke(reason packet.ReasonForRevocation, reasonText string, config *packet.Config)` changed name instead of RevokeKey. - -`openpgp.v2.Entity` removed API methods: -- `RevokeSubkey(...) ` replaced by `(Subkey).Revoke(...)` - -`openpgp.v2.Subkey` struct fields have changed: -- `Sig *packet.Signature` removed -- `Bindings []*packet.VerifiableSignature` added -- `Primary *Entity` added, points to the primary key. -- `Revocations []*packet.VerifiableSignature` changed type - -`openpgp.v2.Subkey` changed API methods: -- `Revoked(selfCertification *packet.Signature, date time.Time)` has a new selfCertification argument, which points to the self signature to be used. - -`openpgp.v2.Identity` struct fields have changed: -- `SelfSignature *packet.Signature` removed -- `Signatures []*packet.Signature ` removed -- `SelfCertifications []*packet.VerifiableSignature` added -- `OtherCertifications []*packet.VerifiableSignature` added -- `Primary *Entity` added, points to the primary key. -- `Revocations []*packet.VerifiableSignature` changed type - -`openpgp.v2.Identity` changed API methods: -- `Revoked(selfCertification *packet.Signature, date time.Time)` has a new selfCertification argument, which points to the self signature to be used. - -`openpgp.v2.Key` struct fields have changed: -- `PrimarySelfSignature *packet.Signature ` added, which points to the selected self signature of the primary key. -- `Revocations []*packet.VerifiableSignature` changed type - -`openpgp.v2.KeyRing` interface has has changed: -- `KeysByIdUsage(...)` removed -- `DecryptionKeys(...)` removed -- `EntitiesById(id uint64) []*Entity` added. This is the main internal method to access keys from the keyring now. - -`openpgp.v2.FileHints` struct field has changed: -- `IsBinary` removed and `IsUTF8` added - -`openpgp.v2` API changes for reading messages: -- `VerifyDetachedSignatureAndHash(...)` removed, headers in clearsigned messages are no longer checked. -- `VerifyDetachedSignatureAndSaltedHash(...)` removed -- `CheckDetachedSignature(...)` removed, call `VerifyDetachedSignature(...)` instead -- `CheckDetachedSignatureAndSaltedHash(...)` removed -- `CheckDetachedSignatureAndHash(...)` removed -- `CheckArmoredDetachedSignature` removed call `VerifyArmoredDetachedSignature` instead - -`openpgp.v2` API changes for writing messages: -- `DetachSign(..., signers []*Entity,...)` takes now a slice of entities instead of a single entity as an argument. -- `ArmoredDetachSign(..., signers []*Entity,..., , params *SignParams)` takes now a slice of entities instead of a single entity as an argument and replaces arguments with a SignParams object. -- `DetachSignText(..., signers []*Entity,...)` takes now a slice of entities instead of a single entity as an argument. -- `ArmoredDetachSignText(..., signers []*Entity,...)` takes now a slice of entities instead of a single entity as an argument. -- `EncryptText(...)` removed call `EncryptWithParams(...)` instead -- `EncryptSplit(...)` removed call `EncryptWithParams(...)` instead -- `EncryptTextSplit(...)` removed call `EncryptWithParams(...)` instead -- `Encrypt(..., toHidden []*Entity, signers []*Entity)` now takes an additional toHidden recipients argument and takes now a slice of signer entities instead of a single entity as an argument. -- `Sign(..., signers []*Entity,...)` takes now a slice of entities instead of a single entity as an argument. - -## Features added - -### Intended recipients - -Version 2 of the ProtonMail/go-crypto library introduces a feature for including the recipients' key fingerprints in signatures during message encryption. -When encrypting and signing a message, the intended recipients are automatically included in the signature, unless specifically hidden (i.e., hidden recipients). -During the decryption process, if the signature contains intended recipients and the appropriate configuration flag is set, the library verifies whether the primary ID of the decryption key is present in the recipient list. -This check can be disabled in the config when a hidden recipient decrypts the message. - -### Multi-signature support - -In previous iterations of ProtonMail/go-crypto, only a single signature creation and verification were supported in a PGP message. -However, in Version 2, the library introduces the ability to sign messages with multiple signatures using different keys, such as a v4 and a v6 key. -The encryption and signing methods now accept multiple signing keys as arguments, with each key designated for a specific signature. -When reading PGP messages with Version 2, the library maintains an internal state for each known signature and verifies all of them within the message. -To facilitate this functionality, the message details struct includes a new field that stores the verification state for each signature. A message is considered valid if at least one of the signatures successfully validates without any errors. -For callers, the process of checking for signature errors remains similar to previous versions. -However, if the caller requires the verification state of all signatures, they can utilize the new field in the message details struct. - -### Rework how signatures in keys and signatures are verified - -In previous iterations of ProtonMail/go-crypto, key verification occurred during import based on the current time, while signature verification did not involve further key checks. -However, this approach had limitations, as invalid keys could have been valid at the time of signature creation and mistakenly considered invalid. - -Version 2 changes how and when signatures are verified in keys (i.e., direct-signatures, self-signatures of userids, binding signatures in subkeys, revocations, etc). -Unlike before, key signature verification no longer takes place during parsing. -Instead, keys are now validated when they are utilized, following a similar approach to key handling in OpenPGP.js. -Additionally, all signatures and expirations are validated to adhere to the key lifecycle outlined in the RFC. -The validity of keys can now be checked at different points in time, leading to the following specific modifications: -- During entity parsing, key validity is not checked. -- When used for encryption or signing, keys are verified using the current time during the writing process. -- During reading, the library verifies that each verification key was valid at the time of signature creation. -- A clear separation is maintained between Entity, Subkey, Identity, and their respective validation methods. -- Signature verification results are cached and reused to optimize computation. - -Further, version 2 includes various small improvements to increase the robustness of the key parsing functions. - -### Weak algorithm rejection - -Version 2 introduces the option to specify weak algorithms for signatures in the config. -Signatures that use weak algorithms are considered invalid. - -### Optional packet sequence checker - -Version 2 introduces a new feature that enables the validation of packet sequences in PGP messages. -This functionality can be enabled in the config struct. -In particular, it implements the pushdown automata (PDA) from PGPainless, developed by Paul Schaub. -By leveraging this feature, users can ensure that the packet sequences in their PGP messages are valid and comply with the required structure. -This addition further enhances the overall reliability and security of PGP message handling in Version 2. - -### Session key encryption and decryption - -Version 2 allows advanced users to retrieve the session key while encrypting a message by setting the respective flag in the config. -In decryption, a caller can provide a session key that should be used for decryption. - -### Unify write/read API - -Version 2 improves the compatibility between different APIs to allow combinations. -The `DetachSign` function requires the caller to provide a `Reader` for the message, while -encrypt returns a `WriteCloser` to which the message is written to. -The new version adds a function `DetachSignWriter`, which returns a `WriteCloser` similar to the encryption API. -On the reading side, the verify detached signature API now relies on the -same signature verification logic as the other read functions. -Additionally, a new `VerifyDetachedSignatureReader` method similar to the `ReadMessage` API is introduced. -It returns a message details struct that once read verifies the signature. -Allows to chain different readers from the API, for example, to have a streaming API for encrypted detached signatures. - -### Params struct as a function argument in the write API - -With the inclusion of new features, the write functions in go-crypto experienced significant growth in numbers. Each combination has its dedicated function. -Version 2 introduces an `EncryptWithParams`/`SignWithParams` function that takes an `EncryptParams`/`SignParams` struct as an argument. The struct allows configuring the different features. -This approach effectively reduces the number of API methods and simplifies the process of adding new features while maintaining compatibility with previous versions. - -### Others - -- Disable armor checksum on default `armor.Encode` method -- Make `unarmor` more robust to empty header values -- Allow key generation of v6 keys without a `Identity` -- Allow compression in inline signed messages -- Consider key preferences in detached signatures -- Only compare time at a second granularity -- Signal if tag is not verified on close in aead decryption -- Ensure that critical unknown packet tags result in message rejection -- Ensure that decompression streams are closed and that the packet is completely read -- Ensure that entity parsing does not reject keys with unknown subkeys -- Check for known curves early when parsing ECDSA and ECDH keys -- Skip signatures with the wrong type while parsing an entity -- Support for signatures that appear in front of the data -- Change file hints field IsBinary to IsUTF8 From e884ac06ef8bfbaf74e05f9b615ce2f6005d1487 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Wed, 16 Aug 2023 11:18:35 +0200 Subject: [PATCH 050/116] fix(armor): Remove duplicated test --- openpgp/armor/armor_test.go | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/openpgp/armor/armor_test.go b/openpgp/armor/armor_test.go index 050ab4239..d1c88d47d 100644 --- a/openpgp/armor/armor_test.go +++ b/openpgp/armor/armor_test.go @@ -75,25 +75,6 @@ func TestDecodeEmptyVersion(t *testing.T) { } } -func TestDecodeEmptyVersion(t *testing.T) { - buf := bytes.NewBuffer([]byte(armorExampleEmptyVersion)) - result, err := Decode(buf) - if err != nil { - t.Error(err) - } - expectedType := "PGP SIGNATURE" - if result.Type != expectedType { - t.Errorf("result.Type: got:%s want:%s", result.Type, expectedType) - } - if len(result.Header) != 1 { - t.Errorf("len(result.Header): got:%d want:1", len(result.Header)) - } - v, ok := result.Header["Version"] - if !ok || v != "" { - t.Errorf("result.Header: got:%#v", result.Header) - } -} - func TestLongHeader(t *testing.T) { buf := bytes.NewBuffer([]byte(armorLongLine)) result, err := Decode(buf) From f598b26f1b69b83869ba5acf71fcf2fa0c8fb01e Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Wed, 16 Aug 2023 11:29:27 +0200 Subject: [PATCH 051/116] fix(CI): Update github actions with gopenpgp v3 and gosop v2 --- .github/actions/build-gosop/action.yml | 13 ++++++++++++- .github/test-suite/build_gosop.sh | 2 +- .github/test-suite/build_gosop_v1.sh | 5 +++++ .github/workflows/interop-test-suite.yml | 2 ++ 4 files changed, 20 insertions(+), 2 deletions(-) create mode 100755 .github/test-suite/build_gosop_v1.sh diff --git a/.github/actions/build-gosop/action.yml b/.github/actions/build-gosop/action.yml index 5f4fa7639..a1e651b77 100644 --- a/.github/actions/build-gosop/action.yml +++ b/.github/actions/build-gosop/action.yml @@ -13,6 +13,16 @@ inputs: required: true default: './gosop-${{ github.sha }}' + branch-gosop: + description: 'Branch of the gosop to use' + required: false + default: 'main' + + gosop-build-path: + description: 'Build script of the gosop to use' + required: false + default: 'build_gosop_v1.sh' + runs: using: "composite" steps: @@ -30,6 +40,7 @@ runs: uses: actions/checkout@v3 with: repository: ProtonMail/gosop + ref: ${{ inputs.branch-gosop }} path: gosop - name: Cache go modules uses: actions/cache@v3 @@ -41,7 +52,7 @@ runs: restore-keys: | ${{ runner.os }}-go- - name: Build gosop - run: ./.github/test-suite/build_gosop.sh + run: ./.github/test-suite/${{ inputs.gosop-build-path }} shell: bash # Test the binary - name: Print gosop version diff --git a/.github/test-suite/build_gosop.sh b/.github/test-suite/build_gosop.sh index e528d36bc..d0eea1b66 100755 --- a/.github/test-suite/build_gosop.sh +++ b/.github/test-suite/build_gosop.sh @@ -1,5 +1,5 @@ cd gosop echo "replace github.com/ProtonMail/go-crypto => ../go-crypto" >> go.mod go get github.com/ProtonMail/go-crypto -go get github.com/ProtonMail/gopenpgp/v2/crypto@latest +go get github.com/ProtonMail/gopenpgp/v3/crypto@bebe9d408c2a75a4053de49b25638357bf92e692 go build . diff --git a/.github/test-suite/build_gosop_v1.sh b/.github/test-suite/build_gosop_v1.sh new file mode 100755 index 000000000..e528d36bc --- /dev/null +++ b/.github/test-suite/build_gosop_v1.sh @@ -0,0 +1,5 @@ +cd gosop +echo "replace github.com/ProtonMail/go-crypto => ../go-crypto" >> go.mod +go get github.com/ProtonMail/go-crypto +go get github.com/ProtonMail/gopenpgp/v2/crypto@latest +go build . diff --git a/.github/workflows/interop-test-suite.yml b/.github/workflows/interop-test-suite.yml index 9d6fb0d20..e20730e7f 100644 --- a/.github/workflows/interop-test-suite.yml +++ b/.github/workflows/interop-test-suite.yml @@ -16,6 +16,8 @@ jobs: uses: ./.github/actions/build-gosop with: binary-location: ./gosop-${{ github.sha }} + branch-gosop: gosop-gopenpgp-v3 + gosop-build-path: build_gosop.sh # Upload as artifact - name: Upload gosop artifact uses: actions/upload-artifact@v3 From 6e3d94be77f49c546b6c21a990681abb9f65e583 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Wed, 16 Aug 2023 12:53:25 +0200 Subject: [PATCH 052/116] fix(go1.15): Ensure compatibility with go 1.15 --- openpgp/v2/read_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpgp/v2/read_test.go b/openpgp/v2/read_test.go index e0714f1f0..8f274e215 100644 --- a/openpgp/v2/read_test.go +++ b/openpgp/v2/read_test.go @@ -944,7 +944,7 @@ func TestMultiSignedMessage(t *testing.T) { t.Errorf("expected 2 signature candidates, got: %d", len(md.SignatureCandidates)) } - _, err = io.ReadAll(md.UnverifiedBody) + _, err = ioutil.ReadAll(md.UnverifiedBody) if err != nil { t.Fatal(err) } @@ -994,7 +994,7 @@ func testMalformedMessage(t *testing.T, keyring EntityList, message string) { if err != nil { return } - _, err = io.ReadAll(md.UnverifiedBody) + _, err = ioutil.ReadAll(md.UnverifiedBody) if err == nil { t.Error("Expected malformed message error") return From 320232b8cced6031fd7afa9f21bc8882e74dff42 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Wed, 16 Aug 2023 12:58:11 +0200 Subject: [PATCH 053/116] fix: Remove testcase that is obsolete after rebase --- openpgp/read_test.go | 42 ------------------------------------------ 1 file changed, 42 deletions(-) diff --git a/openpgp/read_test.go b/openpgp/read_test.go index ac1347208..9e745d9d8 100644 --- a/openpgp/read_test.go +++ b/openpgp/read_test.go @@ -636,48 +636,6 @@ func TestReadV6Messages(t *testing.T) { } } -func TestSymmetricAeadGcmOpenPGPJsMessage(t *testing.T) { - passphrase := []byte("test") - file, err := os.Open("test_data/aead-sym-message.asc") - if err != nil { - t.Fatal(err) - } - armoredEncryptedMessage, err := ioutil.ReadAll(file) - if err != nil { - t.Fatal(err) - } - // Unarmor string - raw, err := armor.Decode(strings.NewReader(string(armoredEncryptedMessage))) - if err != nil { - t.Error(err) - return - } - // Mock passphrase prompt - promptFunc := func(keys []Key, symmetric bool) ([]byte, error) { - return passphrase, nil - } - // Decrypt message - md, err := ReadMessage(raw.Body, nil, promptFunc, nil) - if err != nil { - t.Error(err) - return - } - contents, err := ioutil.ReadAll(md.UnverifiedBody) - if err != nil { - t.Errorf("error reading UnverifiedBody: %s", err) - } - - // The plaintext is https://www.gutenberg.org/cache/epub/1080/pg1080.txt - // We compare the SHA512 hashes. - wantHash := modestProposalSha512 - gotHashRaw := sha512.Sum512(contents) - gotHash := base64.StdEncoding.EncodeToString(gotHashRaw[:]) - - if wantHash != gotHash { - t.Fatal("Did not decrypt OpenPGPjs message correctly") - } -} - func TestSymmetricDecryptionArgon2(t *testing.T) { // Appendix IETF OpenPGP crypto refresh draft v08 A.8.1 passphrase := []byte("password") From 608bc7b36952920ea4af6906da79a0d41ac16776 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Wed, 16 Aug 2023 14:26:04 +0200 Subject: [PATCH 054/116] fix(CI): Use updated openpgp-interop-test-analyzer --- .github/workflows/interop-test-suite.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/interop-test-suite.yml b/.github/workflows/interop-test-suite.yml index e20730e7f..f183d6b3e 100644 --- a/.github/workflows/interop-test-suite.yml +++ b/.github/workflows/interop-test-suite.yml @@ -119,7 +119,7 @@ jobs: with: name: test-suite-results.json - name: Compare with baseline - uses: ProtonMail/openpgp-interop-test-analyzer@v1 + uses: ProtonMail/openpgp-interop-test-analyzer@5f99c093a1f2c3baccddb46e1d684ec8b33af55d with: results: ${{ steps.download-test-results.outputs.download-path }}/test-suite-results.json output: baseline-comparison.json From 162f22f83b70669c3462ecc4b31d20b25530addb Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Thu, 17 Aug 2023 09:26:02 +0200 Subject: [PATCH 055/116] fix(packet): Ensure that packet reader skips marker packets --- openpgp/packet/reader.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/openpgp/packet/reader.go b/openpgp/packet/reader.go index b66985f28..dd8409239 100644 --- a/openpgp/packet/reader.go +++ b/openpgp/packet/reader.go @@ -32,7 +32,7 @@ type Reader struct { const maxReaders = 32 // Next returns the most recently unread Packet, or reads another packet from -// the top-most io.Reader. Unknown/unsupported packet types are skipped. +// the top-most io.Reader. Unknown/unsupported/Marker packet types are skipped. func (r *Reader) Next() (p Packet, err error) { for { p, err := r.read() @@ -51,6 +51,11 @@ func (r *Reader) Next() (p Packet, err error) { } return nil, err } else { + //A marker packet MUST be ignored when received + switch p.(type) { + case *Marker: + continue + } return p, nil } } @@ -58,7 +63,7 @@ func (r *Reader) Next() (p Packet, err error) { } // Next returns the most recently unread Packet, or reads another packet from -// the top-most io.Reader. Unknown packet types are skipped while unsupported +// the top-most io.Reader. Unknown/Marker packet types are skipped while unsupported // packets are returned as UnsupportedPacket type. func (r *Reader) NextWithUnsupported() (p Packet, err error) { for { @@ -77,6 +82,11 @@ func (r *Reader) NextWithUnsupported() (p Packet, err error) { } return } else { + //A marker packet MUST be ignored when received + switch p.(type) { + case *Marker: + continue + } return } } From d36fda76b3a979570b115ec45306c40a9ba25007 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Thu, 17 Aug 2023 09:44:57 +0200 Subject: [PATCH 056/116] feat(CI): Compare v1 and v2 API to the main branch --- .github/test-suite/config.json.template | 8 ++- .github/test-suite/prepare_config.sh | 8 ++- .github/workflows/interop-test-suite.yml | 80 +++++++++++++++++------- 3 files changed, 69 insertions(+), 27 deletions(-) diff --git a/.github/test-suite/config.json.template b/.github/test-suite/config.json.template index ac3701f34..0af1228b3 100644 --- a/.github/test-suite/config.json.template +++ b/.github/test-suite/config.json.template @@ -1,8 +1,12 @@ { "drivers": [ { - "id": "gosop-branch", - "path": "__GOSOP_BRANCH__" + "id": "gosop-branch-v1", + "path": "__GOSOP_BRANCH_V1__" + }, + { + "id": "gosop-branch-v2", + "path": "__GOSOP_BRANCH_V1__" }, { "id": "gosop-main", diff --git a/.github/test-suite/prepare_config.sh b/.github/test-suite/prepare_config.sh index ae49d01fc..cc5883ccf 100755 --- a/.github/test-suite/prepare_config.sh +++ b/.github/test-suite/prepare_config.sh @@ -1,9 +1,11 @@ CONFIG_TEMPLATE=$1 CONFIG_OUTPUT=$2 -GOSOP_BRANCH=$3 -GOSOP_MAIN=$4 +GOSOP_BRANCH_V1=$3 +GOSOP_BRANCH_V2=$4 +GOSOP_MAIN=$5 cat $CONFIG_TEMPLATE \ - | sed "s@__GOSOP_BRANCH__@${GOSOP_BRANCH}@g" \ + | sed "s@__GOSOP_BRANCH_V1__@${GOSOP_BRANCH_V1}@g" \ + | sed "s@__GOSOP_BRANCH_V2__@${GOSOP_BRANCH_V2}@g" \ | sed "s@__GOSOP_MAIN__@${GOSOP_MAIN}@g" \ | sed "s@__SQOP__@${SQOP}@g" \ | sed "s@__GPGME_SOP__@${GPGME_SOP}@g" \ diff --git a/.github/workflows/interop-test-suite.yml b/.github/workflows/interop-test-suite.yml index f183d6b3e..96c8f6f97 100644 --- a/.github/workflows/interop-test-suite.yml +++ b/.github/workflows/interop-test-suite.yml @@ -5,9 +5,8 @@ on: branches: [ main ] jobs: - - build-gosop: - name: Build gosop from branch + build-gosop-v1: + name: Build gosop from branch v1-api runs-on: ubuntu-latest steps: - name: Checkout @@ -15,15 +14,32 @@ jobs: - name: Build gosop from branch uses: ./.github/actions/build-gosop with: - binary-location: ./gosop-${{ github.sha }} + binary-location: ./gosop-${{ github.sha }}-v1 + # Upload as artifact + - name: Upload gosop artifact + uses: actions/upload-artifact@v3 + with: + name: gosop-${{ github.sha }}-v1 + path: ./gosop-${{ github.sha }}-v1 + + build-gosop-v2: + name: Build gosop from branch v2-api + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Build gosop from branch + uses: ./.github/actions/build-gosop + with: + binary-location: ./gosop-${{ github.sha }}-v2 branch-gosop: gosop-gopenpgp-v3 gosop-build-path: build_gosop.sh # Upload as artifact - name: Upload gosop artifact uses: actions/upload-artifact@v3 with: - name: gosop-${{ github.sha }} - path: ./gosop-${{ github.sha }} + name: gosop-${{ github.sha }}-v2 + path: ./gosop-${{ github.sha }}-v2 build-gosop-main: name: Build gosop from main @@ -42,7 +58,6 @@ jobs: with: name: gosop-main path: ./gosop-main - test-suite: name: Run interoperability test suite @@ -53,7 +68,8 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.github_token }} needs: - - build-gosop + - build-gosop-v1 + - build-gosop-v2 - build-gosop-main steps: - name: Checkout @@ -68,21 +84,33 @@ jobs: run: chmod +x gosop-main - name: Print gosop-main version run: ./gosop-main version --extended - # Fetch gosop from branch - - name: Download gosop-branch + # Fetch gosop from branch v1 + - name: Download gosop-branch-v1 uses: actions/download-artifact@v3 with: - name: gosop-${{ github.sha }} - - name: Rename gosop-branch - run: mv gosop-${{ github.sha }} gosop-branch - # Test gosop-branch - - name: Make gosop-branch executable - run: chmod +x gosop-branch - - name: Print gosop-branch version - run: ./gosop-branch version --extended + name: gosop-${{ github.sha }}-v1 + - name: Rename gosop-branch-v1 + run: mv gosop-${{ github.sha }}-v1 gosop-branch-v1 + # Test gosop-branch v1 + - name: Make gosop-branch-v1 executable + run: chmod +x gosop-branch-v1 + - name: Print gosop-branch-v1 version + run: ./gosop-branch-v1 version --extended + # Fetch gosop from branch v2 + - name: Download gosop-branch-v2 + uses: actions/download-artifact@v3 + with: + name: gosop-${{ github.sha }}-v2 + - name: Rename gosop-branch-v2 + run: mv gosop-${{ github.sha }}-v2 gosop-branch-v2 + # Test gosop-branch v2 + - name: Make gosop-branch-v2 executable + run: chmod +x gosop-branch-v2 + - name: Print gosop-branch-v2 version + run: ./gosop-branch-v2 version --extended # Run test suite - name: Prepare test configuration - run: ./.github/test-suite/prepare_config.sh $CONFIG_TEMPLATE $CONFIG_OUTPUT $GITHUB_WORKSPACE/gosop-branch $GITHUB_WORKSPACE/gosop-main + run: ./.github/test-suite/prepare_config.sh $CONFIG_TEMPLATE $CONFIG_OUTPUT $GITHUB_WORKSPACE/gosop-branch-v1 $GITHUB_WORKSPACE/gosop-branch-v2 $GITHUB_WORKSPACE/gosop-main env: CONFIG_TEMPLATE: .github/test-suite/config.json.template CONFIG_OUTPUT: .github/test-suite/config.json @@ -118,10 +146,18 @@ jobs: uses: actions/download-artifact@v3 with: name: test-suite-results.json - - name: Compare with baseline + - name: Compare with baseline v1 uses: ProtonMail/openpgp-interop-test-analyzer@5f99c093a1f2c3baccddb46e1d684ec8b33af55d with: results: ${{ steps.download-test-results.outputs.download-path }}/test-suite-results.json - output: baseline-comparison.json + output: baseline-comparison-v1.json baseline: gosop-main - target: gosop-branch + target: gosop-branch-v1 + - name: Compare with baseline v2 + uses: ProtonMail/openpgp-interop-test-analyzer@5f99c093a1f2c3baccddb46e1d684ec8b33af55d + with: + results: ${{ steps.download-test-results.outputs.download-path }}/test-suite-results.json + output: baseline-comparison-v2.json + baseline: gosop-main + target: gosop-branch-v2 + From 3cd52dad270e585dfd9d21e8294544df2024d739 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Thu, 17 Aug 2023 09:53:39 +0200 Subject: [PATCH 057/116] fix(packet): Remove argument for backward compatibility --- .github/test-suite/config.json.template | 2 +- openpgp/packet/encrypted_key.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/test-suite/config.json.template b/.github/test-suite/config.json.template index 0af1228b3..60529ac83 100644 --- a/.github/test-suite/config.json.template +++ b/.github/test-suite/config.json.template @@ -6,7 +6,7 @@ }, { "id": "gosop-branch-v2", - "path": "__GOSOP_BRANCH_V1__" + "path": "__GOSOP_BRANCH_V2__" }, { "id": "gosop-main", diff --git a/openpgp/packet/encrypted_key.go b/openpgp/packet/encrypted_key.go index 8ddfe0c8e..c9534d201 100644 --- a/openpgp/packet/encrypted_key.go +++ b/openpgp/packet/encrypted_key.go @@ -419,7 +419,7 @@ func SerializeEncryptedKeyAEADwithHiddenOption(w io.Writer, pub *PublicKey, ciph // key, encrypted to pub. // PKESKv6 is used if config.AEAD() is not nil. // If config is nil, sensible defaults will be used. -func SerializeEncryptedKey(w io.Writer, pub *PublicKey, cipherFunc CipherFunction, key []byte, hidden bool, config *Config) error { +func SerializeEncryptedKey(w io.Writer, pub *PublicKey, cipherFunc CipherFunction, key []byte, config *Config) error { return SerializeEncryptedKeyAEAD(w, pub, cipherFunc, config.AEAD() != nil, key, config) } From 19c7808e6d5eb05f1d8c093cdf789b1889501607 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Thu, 17 Aug 2023 10:42:01 +0200 Subject: [PATCH 058/116] fix(armor): Emit an armor checksum in v1 API --- .github/test-suite/build_gosop.sh | 2 +- openpgp/armor/armor_test.go | 7 +++---- openpgp/armor/encode.go | 2 +- openpgp/clearsign/clearsign.go | 2 +- openpgp/integration_tests/v2/utils_test.go | 2 +- openpgp/v2/keys_test.go | 2 +- openpgp/v2/write.go | 2 +- 7 files changed, 9 insertions(+), 10 deletions(-) diff --git a/.github/test-suite/build_gosop.sh b/.github/test-suite/build_gosop.sh index d0eea1b66..f26306802 100755 --- a/.github/test-suite/build_gosop.sh +++ b/.github/test-suite/build_gosop.sh @@ -1,5 +1,5 @@ cd gosop echo "replace github.com/ProtonMail/go-crypto => ../go-crypto" >> go.mod go get github.com/ProtonMail/go-crypto -go get github.com/ProtonMail/gopenpgp/v3/crypto@bebe9d408c2a75a4053de49b25638357bf92e692 +go get github.com/ProtonMail/gopenpgp/v3/crypto@26e397f0942ab02ec6649ebd30c71d0421131df8 go build . diff --git a/openpgp/armor/armor_test.go b/openpgp/armor/armor_test.go index d1c88d47d..595612493 100644 --- a/openpgp/armor/armor_test.go +++ b/openpgp/armor/armor_test.go @@ -8,7 +8,6 @@ import ( "bytes" "hash/adler32" "io/ioutil" - "strings" "testing" ) @@ -50,9 +49,8 @@ func TestDecodeEncode(t *testing.T) { } w.Close() - expected := strings.Replace(armorExample1, "=/teI\n", "", 1) // CRC should not be present - if !bytes.Equal(buf.Bytes(), []byte(expected)) { - t.Errorf("got: %s\nwant: %s", buf.String(), expected) + if !bytes.Equal(buf.Bytes(), []byte(armorExample1)) { + t.Errorf("got: %s\nwant: %s", buf.String(), armorExample1) } } @@ -98,6 +96,7 @@ iJwEAAECAAYFAk1Fv/0ACgkQo01+GMIMMbsYTwQAiAw+QAaNfY6WBdplZ/uMAccm 4g+81QPmTSGHnetSb6WBiY13kVzK4HQiZH8JSkmmroMLuGeJwsRTEL4wbjRyUKEt p1xwUZDECs234F1xiG5enc5SGlRtP7foLBz9lOsjx+LEcA4sTl5/2eZR9zyFZqWW TxRjs+fJCIFuo71xb1g= +=/teI -----END PGP SIGNATURE-----` const armorLongLine = `-----BEGIN PGP SIGNATURE----- diff --git a/openpgp/armor/encode.go b/openpgp/armor/encode.go index 943ff3ce4..d9117c145 100644 --- a/openpgp/armor/encode.go +++ b/openpgp/armor/encode.go @@ -186,7 +186,7 @@ func encode(out io.Writer, blockType string, headers map[string]string, checksum // Encode returns a WriteCloser which will encode the data written to it in // OpenPGP armor. func Encode(out io.Writer, blockType string, headers map[string]string) (w io.WriteCloser, err error) { - return encode(out, blockType, headers, false) + return encode(out, blockType, headers, true) } // EncodeWithChecksumOption returns a WriteCloser which will encode the data written to it in diff --git a/openpgp/clearsign/clearsign.go b/openpgp/clearsign/clearsign.go index d081fe1ef..4ef54049b 100644 --- a/openpgp/clearsign/clearsign.go +++ b/openpgp/clearsign/clearsign.go @@ -303,7 +303,7 @@ func (d *dashEscaper) Close() (err error) { return } - out, err := armor.Encode(d.buffered, "PGP SIGNATURE", d.armorHeader) + out, err := armor.EncodeWithChecksumOption(d.buffered, "PGP SIGNATURE", d.armorHeader, false) if err != nil { return } diff --git a/openpgp/integration_tests/v2/utils_test.go b/openpgp/integration_tests/v2/utils_test.go index 5383ac76b..74192342a 100644 --- a/openpgp/integration_tests/v2/utils_test.go +++ b/openpgp/integration_tests/v2/utils_test.go @@ -84,7 +84,7 @@ func generateFreshTestVectors(num int) (vectors []testVector, err error) { // armorWithType make bytes input to armor format func armorWithType(input []byte, armorType string) (string, error) { var b bytes.Buffer - w, err := armor.Encode(&b, armorType, nil) + w, err := armor.EncodeWithChecksumOption(&b, armorType, nil, false) if err != nil { return "", err } diff --git a/openpgp/v2/keys_test.go b/openpgp/v2/keys_test.go index aa215f4ce..4554c8176 100644 --- a/openpgp/v2/keys_test.go +++ b/openpgp/v2/keys_test.go @@ -466,7 +466,7 @@ func TestDummyPrivateKey(t *testing.T) { // Test serialization of stub private key via entity.SerializePrivate(). var buf bytes.Buffer - w, err := armor.Encode(&buf, PrivateKeyType, nil) + w, err := armor.EncodeWithChecksumOption(&buf, PrivateKeyType, nil, false) if err != nil { t.Errorf("Failed top initialise armored key writer") } diff --git a/openpgp/v2/write.go b/openpgp/v2/write.go index 2a5ef84ea..77612ded8 100644 --- a/openpgp/v2/write.go +++ b/openpgp/v2/write.go @@ -71,7 +71,7 @@ func DetachSignWriter(w io.Writer, signers []*Entity, params *SignParams) (io.Wr } func armoredDetachSign(w io.Writer, signers []*Entity, message io.Reader, sigType packet.SignatureType, config *packet.Config) (err error) { - out, err := armor.Encode(w, SignatureType, nil) + out, err := armor.EncodeWithChecksumOption(w, SignatureType, nil, false) if err != nil { return } From 3e41c8c466f1495c25c95da9d62176a3c189fa72 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Thu, 17 Aug 2023 15:10:56 +0200 Subject: [PATCH 059/116] ci: Update interop-test to latest version --- .github/test-suite/build_gosop.sh | 2 +- .github/workflows/interop-test-suite.yml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/test-suite/build_gosop.sh b/.github/test-suite/build_gosop.sh index f26306802..822f7e655 100755 --- a/.github/test-suite/build_gosop.sh +++ b/.github/test-suite/build_gosop.sh @@ -1,5 +1,5 @@ cd gosop echo "replace github.com/ProtonMail/go-crypto => ../go-crypto" >> go.mod go get github.com/ProtonMail/go-crypto -go get github.com/ProtonMail/gopenpgp/v3/crypto@26e397f0942ab02ec6649ebd30c71d0421131df8 +go get github.com/ProtonMail/gopenpgp/v3/crypto@aebe43356421c682a82b22760174db4bd60b2fa5 go build . diff --git a/.github/workflows/interop-test-suite.yml b/.github/workflows/interop-test-suite.yml index 96c8f6f97..265eaafe8 100644 --- a/.github/workflows/interop-test-suite.yml +++ b/.github/workflows/interop-test-suite.yml @@ -63,7 +63,7 @@ jobs: name: Run interoperability test suite runs-on: ubuntu-latest container: - image: ghcr.io/protonmail/openpgp-interop-test-docker:v1.1.1 + image: ghcr.io/protonmail/openpgp-interop-test-docker:v1.1.2 credentials: username: ${{ github.actor }} password: ${{ secrets.github_token }} @@ -147,14 +147,14 @@ jobs: with: name: test-suite-results.json - name: Compare with baseline v1 - uses: ProtonMail/openpgp-interop-test-analyzer@5f99c093a1f2c3baccddb46e1d684ec8b33af55d + uses: ProtonMail/openpgp-interop-test-analyzer@5d7f4b6868ebe3bfc909302828342c461f5f4940 with: results: ${{ steps.download-test-results.outputs.download-path }}/test-suite-results.json output: baseline-comparison-v1.json baseline: gosop-main target: gosop-branch-v1 - name: Compare with baseline v2 - uses: ProtonMail/openpgp-interop-test-analyzer@5f99c093a1f2c3baccddb46e1d684ec8b33af55d + uses: ProtonMail/openpgp-interop-test-analyzer@5d7f4b6868ebe3bfc909302828342c461f5f4940 with: results: ${{ steps.download-test-results.outputs.download-path }}/test-suite-results.json output: baseline-comparison-v2.json From 48f70951ca0769cf2857b75ac7a226237f638dd2 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Fri, 18 Aug 2023 14:43:20 +0200 Subject: [PATCH 060/116] ci: Update to latest OpenPGP interoperability test suite --- .github/workflows/interop-test-suite.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/interop-test-suite.yml b/.github/workflows/interop-test-suite.yml index 265eaafe8..83b8d01d3 100644 --- a/.github/workflows/interop-test-suite.yml +++ b/.github/workflows/interop-test-suite.yml @@ -63,7 +63,7 @@ jobs: name: Run interoperability test suite runs-on: ubuntu-latest container: - image: ghcr.io/protonmail/openpgp-interop-test-docker:v1.1.2 + image: ghcr.io/protonmail/openpgp-interop-test-docker:v.1.1.3 credentials: username: ${{ github.actor }} password: ${{ secrets.github_token }} From 53ffda994cc65dace8265e0167a629ee164d5c8b Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Thu, 31 Aug 2023 13:23:36 +0200 Subject: [PATCH 061/116] fix(packet): Ensure that unhashed subpackets length is parsed correctly --- openpgp/packet/signature.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpgp/packet/signature.go b/openpgp/packet/signature.go index 5e9ecb39c..dfecd971a 100644 --- a/openpgp/packet/signature.go +++ b/openpgp/packet/signature.go @@ -225,7 +225,7 @@ func (sig *Signature) parse(r io.Reader) (err error) { } var unhashedSubpacketsLength int if sig.Version == 6 { - unhashedSubpacketsLength = int(buf[3])<<24 | int(buf[2])<<16 | int(buf[1])<<8 | int(buf[0]) + unhashedSubpacketsLength = int(buf[0])<<24 | int(buf[1])<<16 | int(buf[2])<<8 | int(buf[3]) } else { unhashedSubpacketsLength = int(buf[0])<<8 | int(buf[1]) } From 3bdcc5befcfefcf221039744ee59c88c29275fdd Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Thu, 31 Aug 2023 13:59:35 +0200 Subject: [PATCH 062/116] fix(packet): Do not allocate memory for padding packet --- openpgp/packet/padding.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/openpgp/packet/padding.go b/openpgp/packet/padding.go index f61c190f2..06fa83740 100644 --- a/openpgp/packet/padding.go +++ b/openpgp/packet/padding.go @@ -1,6 +1,9 @@ package packet -import "io" +import ( + "io" + "io/ioutil" +) // Padding type represents a Padding Packet (Tag 21). // The padding type is represented by the length of its padding. @@ -9,8 +12,7 @@ type Padding int // parse just ignores the padding content. func (pad Padding) parse(reader io.Reader) error { - buffer := make([]byte, int64(pad)) - _, err := io.ReadFull(reader, buffer) + _, err := io.CopyN(ioutil.Discard, reader, int64(pad)) return err } From 778b489c1d47be0229790b8610b2b311e83756e5 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Thu, 31 Aug 2023 14:44:08 +0200 Subject: [PATCH 063/116] fix(packet): Only throw error if the issuer signature subpacket is in hashed area in v6 --- openpgp/packet/signature.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/openpgp/packet/signature.go b/openpgp/packet/signature.go index dfecd971a..0e7c43600 100644 --- a/openpgp/packet/signature.go +++ b/openpgp/packet/signature.go @@ -458,16 +458,18 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r copy(sig.PreferredSymmetric, subpacket) case issuerSubpacket: // Issuer, section 5.2.3.5 - if sig.Version > 4 { - err = errors.StructuralError("issuer subpacket found in v5 key") + if sig.Version > 4 && isHashed { + err = errors.StructuralError("issuer subpacket found in v6 key") return } if len(subpacket) != 8 { err = errors.StructuralError("issuer subpacket with bad length") return } - sig.IssuerKeyId = new(uint64) - *sig.IssuerKeyId = binary.BigEndian.Uint64(subpacket) + if sig.Version <= 4 { + sig.IssuerKeyId = new(uint64) + *sig.IssuerKeyId = binary.BigEndian.Uint64(subpacket) + } case notationDataSubpacket: // Notation data, section 5.2.3.16 if len(subpacket) < 8 { From 4294467e3f48911d94f687bff3d4751207f0e716 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Tue, 26 Sep 2023 14:31:15 +0200 Subject: [PATCH 064/116] feat(v2): Change message details signature candidate selection for no match In the case that no signature canidate verifies successfuly, the md.SignatureError should not point to the last candidate error in the list but rather to the last candidate error with a key match (if any). --- openpgp/v2/read.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/openpgp/v2/read.go b/openpgp/v2/read.go index a52221f11..4d04e4bb8 100644 --- a/openpgp/v2/read.go +++ b/openpgp/v2/read.go @@ -588,7 +588,11 @@ func (scr *signatureCheckReader) Read(buf []byte) (int, error) { // Check if there is a valid candidate. for _, candidate := range scr.md.SignatureCandidates { - // md.SignatureError points to the last error, if + if candidate.SignedBy == nil { + // Ignore candidates that have no matching key + continue + } + // md.SignatureError points to the last candidate with a key match, if // all signature verifications have failed. scr.md.SignatureError = candidate.SignatureError scr.md.SelectedCandidate = candidate @@ -604,6 +608,14 @@ func (scr *signatureCheckReader) Read(buf []byte) (int, error) { scr.md.SignatureError = errors.StructuralError("no signature found") } + if len(scr.md.SignatureCandidates) > 0 && scr.md.SelectedCandidate == nil { + // No candidate with a matching key present. + // Just point to the last candidate in this case. + candidate := scr.md.SignatureCandidates[len(scr.md.SignatureCandidates)-1] + scr.md.SignatureError = candidate.SignatureError + scr.md.SelectedCandidate = candidate + } + if scr.md.SignatureError == nil && scr.md.Signature == nil { scr.md.SignatureError = errors.StructuralError("no matching signature found") } From 344351235975ba58deb26853906a9e9ef2d76a2f Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Wed, 27 Sep 2023 08:13:26 +0200 Subject: [PATCH 065/116] ci: Update to latest gopenpgp v3 fot ci interop tests --- .github/test-suite/build_gosop.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/test-suite/build_gosop.sh b/.github/test-suite/build_gosop.sh index 822f7e655..0a031ac0e 100755 --- a/.github/test-suite/build_gosop.sh +++ b/.github/test-suite/build_gosop.sh @@ -1,5 +1,5 @@ cd gosop echo "replace github.com/ProtonMail/go-crypto => ../go-crypto" >> go.mod go get github.com/ProtonMail/go-crypto -go get github.com/ProtonMail/gopenpgp/v3/crypto@aebe43356421c682a82b22760174db4bd60b2fa5 +go get github.com/ProtonMail/gopenpgp/v3/crypto@4ec274320a814a100c71e088f66db1094497d7bb go build . From 9e188301964e362be7812c6a46f35b995a160ad7 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Thu, 28 Sep 2023 10:48:15 +0200 Subject: [PATCH 066/116] feat(v2): Allow to include external signatures in messages --- openpgp/v2/write.go | 112 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 95 insertions(+), 17 deletions(-) diff --git a/openpgp/v2/write.go b/openpgp/v2/write.go index 77612ded8..bd371a6bc 100644 --- a/openpgp/v2/write.go +++ b/openpgp/v2/write.go @@ -5,7 +5,9 @@ package v2 import ( + "bytes" "crypto" + goerrors "errors" "hash" "io" "strconv" @@ -221,6 +223,11 @@ type EncryptParams struct { // SessionKey provides a session key to be used for encryption. // If nil, a one-time session key is generated SessionKey []byte + // OutsideSig allows to set a signature that should be included + // in the message to encrypt. + // Should only be used for exceptional cases. + // If nil, ignored. + OutsideSig []byte // Config provides the config to be used. // If Config is nil, sensible defaults will be used. Config *packet.Config @@ -383,8 +390,46 @@ func Encrypt(ciphertext io.Writer, to, toHidden []*Entity, signers []*Entity, hi // that aids the recipients in processing the message. The resulting // WriteCloser must be closed after the contents of the file have been // written. If config is nil, sensible defaults will be used. -func writeAndSign(payload io.WriteCloser, candidateHashes [][]uint8, signEntities []*Entity, hints *FileHints, sigType packet.SignatureType, intendedRecipients []*packet.Recipient, config *packet.Config) (plaintext io.WriteCloser, err error) { +func writeAndSign(payload io.WriteCloser, candidateHashes [][]uint8, signEntities []*Entity, hints *FileHints, sigType packet.SignatureType, intendedRecipients []*packet.Recipient, outsideSig []byte, config *packet.Config) (plaintext io.WriteCloser, err error) { var signers []*signatureContext + var numberOfOutsideSigs int + + if outsideSig != nil { + outSigPacket, err := parseOutsideSig(outsideSig) + if err != nil { + return nil, err + } + opsVersion := 3 + if outSigPacket.Version == 6 { + opsVersion = 6 + } + opsOutside := &packet.OnePassSignature{ + Version: opsVersion, + SigType: outSigPacket.SigType, + Hash: outSigPacket.Hash, + PubKeyAlgo: outSigPacket.PubKeyAlgo, + KeyId: *outSigPacket.IssuerKeyId, + IsLast: len(signEntities) == 0, + } + sigContext := signatureContext{ + outsideSig: outSigPacket, + } + if outSigPacket.Version == 6 { + opsOutside.KeyFingerprint = outSigPacket.IssuerFingerprint + sigContext.salt = outSigPacket.Salt() + opsOutside.Salt = outSigPacket.Salt() + } + sigContext.h, sigContext.wrappedHash, err = hashForSignature(outSigPacket.Hash, sigType, sigContext.salt) + if err != nil { + return nil, err + } + if err := opsOutside.Serialize(payload); err != nil { + return nil, err + } + signers = append([]*signatureContext{&sigContext}, signers...) + numberOfOutsideSigs = 1 + } + for signEntityIdx, signEntity := range signEntities { if signEntity == nil { continue @@ -442,7 +487,7 @@ func writeAndSign(payload io.WriteCloser, candidateHashes [][]uint8, signEntitie signers = append([]*signatureContext{&sigContext}, signers...) } - if signEntities != nil && len(signers) < 1 { + if signEntities != nil && len(signEntities)+numberOfOutsideSigs != len(signers) { return nil, errors.InvalidArgumentError("no valid signing key") } @@ -451,7 +496,7 @@ func writeAndSign(payload io.WriteCloser, candidateHashes [][]uint8, signEntitie } w := payload - if signers != nil { + if signers != nil || numberOfOutsideSigs > 0 { // If we need to write a signature packet after the literal // data then we need to stop literalData from closing // encryptedData. @@ -467,7 +512,7 @@ func writeAndSign(payload io.WriteCloser, candidateHashes [][]uint8, signEntitie return nil, err } - if signers != nil { + if signers != nil || numberOfOutsideSigs > 0 { metadata := &packet.LiteralData{ Format: 'b', FileName: hints.FileName, @@ -640,7 +685,7 @@ func encryptDataAndSign( if err != nil { return nil, err } - return writeAndSign(payload, candidateHashes, params.Signers, params.Hints, sigType, intendedRecipients, params.Config) + return writeAndSign(payload, candidateHashes, params.Signers, params.Hints, sigType, intendedRecipients, params.OutsideSig, params.Config) } type SignParams struct { @@ -649,6 +694,11 @@ type SignParams struct { Hints *FileHints // TextSig indicates if signatures of type SigTypeText should be produced TextSig bool + // OutsideSig allows to set a signature that should be included + // in an inline signed message. + // Should only be used for exceptional cases. + // If nil, ignored. + OutsideSig []byte // Config provides the config to be used. // If Config is nil, sensible defaults will be used. Config *packet.Config @@ -661,7 +711,7 @@ func SignWithParams(output io.Writer, signers []*Entity, params *SignParams) (in if params == nil { params = &SignParams{} } - if len(signers) < 1 { + if len(signers) < 1 && params.OutsideSig == nil { return nil, errors.InvalidArgumentError("no signer provided") } var candidateHashesPerSignature [][]uint8 @@ -708,7 +758,7 @@ func SignWithParams(output io.Writer, signers []*Entity, params *SignParams) (in if err != nil { return nil, err } - return writeAndSign(payload, candidateHashesPerSignature, signers, params.Hints, sigType, nil, params.Config) + return writeAndSign(payload, candidateHashesPerSignature, signers, params.Hints, sigType, nil, params.OutsideSig, params.Config) } // Sign signs a message. The resulting WriteCloser must be closed after the @@ -742,6 +792,7 @@ type signatureContext struct { h hash.Hash salt []byte // v6 only signer *packet.PrivateKey + outsideSig *packet.Signature } func (s signatureWriter) Write(data []byte) (int, error) { @@ -764,16 +815,21 @@ func (s signatureWriter) Close() error { return err } for _, ctx := range s.signatureContexts { - sig := createSignaturePacket(&ctx.signer.PublicKey, s.sigType, s.config) - sig.Hash = ctx.hashType - sig.Metadata = s.metadata - sig.IntendedRecipients = s.intendedRecipients - - if err := sig.SetSalt(ctx.salt); err != nil { - return err - } - if err := sig.Sign(ctx.h, ctx.signer, s.config); err != nil { - return err + var sig *packet.Signature + if ctx.outsideSig != nil { + // Signature that was supplied outside + sig = ctx.outsideSig + } else { + sig = createSignaturePacket(&ctx.signer.PublicKey, s.sigType, s.config) + sig.Hash = ctx.hashType + sig.Metadata = s.metadata + sig.IntendedRecipients = s.intendedRecipients + if err := sig.SetSalt(ctx.salt); err != nil { + return err + } + if err := sig.Sign(ctx.h, ctx.signer, s.config); err != nil { + return err + } } if err := sig.Serialize(s.encryptedData); err != nil { return err @@ -872,3 +928,25 @@ func selectHash(candidateHashes []byte, configuredHash crypto.Hash) (hash crypto } return } + +func parseOutsideSig(outsideSig []byte) (outSigPacket *packet.Signature, err error) { + var p packet.Packet + packets := packet.NewReader(bytes.NewReader(outsideSig)) + p, err = packets.Next() + if goerrors.Is(err, io.EOF) { + return nil, errors.ErrUnknownIssuer + } + if err != nil { + return nil, err + } + + var ok bool + outSigPacket, ok = p.(*packet.Signature) + if !ok { + return nil, errors.StructuralError("non signature packet found") + } + if outSigPacket.IssuerKeyId == nil { + return nil, errors.StructuralError("signature doesn't have an issuer") + } + return outSigPacket, nil +} From 174dc6b07d03d58c0577e9f0f7ccba755b9656ac Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Thu, 28 Sep 2023 15:44:13 +0200 Subject: [PATCH 067/116] feat(v2): Allow to set the time for selecting keys in encryption This commit adds an EncryptionTime time field to the EncryptParams struct. If provided in encryption, it overrides the time for key selection. It allows to disable expiration ckecks by setting the time to zero. --- openpgp/v2/keys.go | 6 +++--- openpgp/v2/write.go | 15 +++++++++++++-- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/openpgp/v2/keys.go b/openpgp/v2/keys.go index 603d3ddcd..d72004283 100644 --- a/openpgp/v2/keys.go +++ b/openpgp/v2/keys.go @@ -718,8 +718,8 @@ func (e *Entity) VerifyPrimaryKey(date time.Time) (*packet.Signature, error) { return nil, errors.ErrKeyRevoked } - if e.PrimaryKey.KeyExpired(primarySelfSignature, date) || // primary key has expired - primarySelfSignature.SigExpired(date) { // self-signature has expired + if !date.IsZero() && (e.PrimaryKey.KeyExpired(primarySelfSignature, date) || // primary key has expired + primarySelfSignature.SigExpired(date)) { // self-signature has expired return primarySelfSignature, errors.ErrKeyExpired } @@ -727,7 +727,7 @@ func (e *Entity) VerifyPrimaryKey(date time.Time) (*packet.Signature, error) { // check for expiration time in direct signatures (for V6 keys, the above already did so) primaryDirectKeySignature, _ := e.LatestValidDirectSignature(date) if primaryDirectKeySignature != nil && - e.PrimaryKey.KeyExpired(primaryDirectKeySignature, date) { + (!date.IsZero() && e.PrimaryKey.KeyExpired(primaryDirectKeySignature, date)) { return primarySelfSignature, errors.ErrKeyExpired } } diff --git a/openpgp/v2/write.go b/openpgp/v2/write.go index bd371a6bc..eafa39726 100644 --- a/openpgp/v2/write.go +++ b/openpgp/v2/write.go @@ -228,6 +228,12 @@ type EncryptParams struct { // Should only be used for exceptional cases. // If nil, ignored. OutsideSig []byte + // EncryptionTime allows to override the time that is used + // for selecting the encryption key. + // If EncryptionTime is zero (i.e., EncryptionTime.isZero()) expiration checks + // are not performed on the encryption key. + // If nil, the default clock in config is used. + EncryptionTime *time.Time // Config provides the config to be used. // If Config is nil, sensible defaults will be used. Config *packet.Config @@ -581,14 +587,19 @@ func encrypt( intendedRecipients = append(intendedRecipients, &packet.Recipient{KeyVersion: publicRecipient.PrimaryKey.Version, Fingerprint: publicRecipient.PrimaryKey.Fingerprint}) } + timeForEncryptionKey := config.Now() + if params.EncryptionTime != nil { + // Override the time to select the encryption key with the provided one. + timeForEncryptionKey = *params.EncryptionTime + } for i, recipient := range append(to, toHidden...) { var ok bool - encryptKeys[i], ok = recipient.EncryptionKey(config.Now(), config) + encryptKeys[i], ok = recipient.EncryptionKey(timeForEncryptionKey, config) if !ok { return nil, errors.InvalidArgumentError("cannot encrypt a message to key id " + strconv.FormatUint(to[i].PrimaryKey.KeyId, 16) + " because it has no valid encryption keys") } - primarySelfSignature, _ := recipient.PrimarySelfSignature(config.Now()) + primarySelfSignature, _ := recipient.PrimarySelfSignature(timeForEncryptionKey) if primarySelfSignature == nil { return nil, errors.InvalidArgumentError("entity without a self-signature") } From 94d8fd97c909958d0ab1cc5ab6e67159fc2068f4 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Fri, 29 Sep 2023 15:17:56 +0200 Subject: [PATCH 068/116] feat: Do not allow to generate or use v6 keys with legacy oid Implementations MUST NOT accept or generate v6 key material using the deprecated OIDs. --- openpgp/key_generation.go | 12 ++++++++++++ openpgp/v2/key_generation.go | 12 ++++++++++++ openpgp/v2/keys.go | 6 ++++++ 3 files changed, 30 insertions(+) diff --git a/openpgp/key_generation.go b/openpgp/key_generation.go index 553907974..eae4853b4 100644 --- a/openpgp/key_generation.go +++ b/openpgp/key_generation.go @@ -270,6 +270,11 @@ func newSigner(config *packet.Config) (signer interface{}, err error) { } return rsa.GenerateKey(config.Random(), bits) case packet.PubKeyAlgoEdDSA: + if config.V6() { + // Implementations MUST NOT accept or generate v6 key material + // using the deprecated OIDs. + return nil, errors.InvalidArgumentError("EdDSALegacy cannot be used for v6 keys") + } curve := ecc.FindEdDSAByGenName(string(config.CurveName())) if curve == nil { return nil, errors.InvalidArgumentError("unsupported curve") @@ -325,6 +330,13 @@ func newDecrypter(config *packet.Config) (decrypter interface{}, err error) { case packet.PubKeyAlgoEdDSA, packet.PubKeyAlgoECDSA: fallthrough // When passing EdDSA or ECDSA, we generate an ECDH subkey case packet.PubKeyAlgoECDH: + if config.V6() && + (config.CurveName() == packet.Curve25519 || + config.CurveName() == packet.Curve448) { + // Implementations MUST NOT accept or generate v6 key material + // using the deprecated OIDs. + return nil, errors.InvalidArgumentError("ECDH with Curve25519/448 legacy cannot be used for v6 keys") + } var kdf = ecdh.KDF{ Hash: algorithm.SHA512, Cipher: algorithm.AES256, diff --git a/openpgp/v2/key_generation.go b/openpgp/v2/key_generation.go index 49f9acef7..46d09d3d4 100644 --- a/openpgp/v2/key_generation.go +++ b/openpgp/v2/key_generation.go @@ -306,6 +306,11 @@ func newSigner(config *packet.Config) (signer interface{}, err error) { } return rsa.GenerateKey(config.Random(), bits) case packet.PubKeyAlgoEdDSA: + if config.V6() { + // Implementations MUST NOT accept or generate v6 key material + // using the deprecated OIDs. + return nil, errors.InvalidArgumentError("EdDSALegacy cannot be used for v6 keys") + } curve := ecc.FindEdDSAByGenName(string(config.CurveName())) if curve == nil { return nil, errors.InvalidArgumentError("unsupported curve") @@ -361,6 +366,13 @@ func newDecrypter(config *packet.Config) (decrypter interface{}, err error) { case packet.PubKeyAlgoEdDSA, packet.PubKeyAlgoECDSA: fallthrough // When passing EdDSA or ECDSA, we generate an ECDH subkey case packet.PubKeyAlgoECDH: + if config.V6() && + (config.CurveName() == packet.Curve25519 || + config.CurveName() == packet.Curve448) { + // Implementations MUST NOT accept or generate v6 key material + // using the deprecated OIDs. + return nil, errors.InvalidArgumentError("ECDH with Curve25519/448 legacy cannot be used for v6 keys") + } var kdf = ecdh.KDF{ Hash: algorithm.SHA512, Cipher: algorithm.AES256, diff --git a/openpgp/v2/keys.go b/openpgp/v2/keys.go index d72004283..f83b8a650 100644 --- a/openpgp/v2/keys.go +++ b/openpgp/v2/keys.go @@ -758,6 +758,12 @@ func checkKeyRequirements(usedKey *packet.PublicKey, config *packet.Config) erro if err != nil || config.RejectCurve(curve) { return errors.WeakAlgorithmError("elliptic curve " + curve) } + if usedKey.Version == 6 && + (curve == packet.Curve25519 || + curve == packet.Curve448) { + // Implementations MUST NOT accept or generate v6 key material using the deprecated OIDs. + return errors.StructuralError("v6 key uses legacy elliptic curve " + curve) + } } return nil } From 4c9baebfadd4aeeb7751477c4db19c0244b31069 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Mon, 2 Oct 2023 09:15:44 +0200 Subject: [PATCH 069/116] fix: Do not allow EdDSA for v6 in integration tests --- openpgp/integration_tests/utils_test.go | 3 +++ openpgp/integration_tests/v2/utils_test.go | 3 +++ 2 files changed, 6 insertions(+) diff --git a/openpgp/integration_tests/utils_test.go b/openpgp/integration_tests/utils_test.go index b3ce312e3..945f0c1e1 100644 --- a/openpgp/integration_tests/utils_test.go +++ b/openpgp/integration_tests/utils_test.go @@ -270,6 +270,9 @@ func randConfig() *packet.Config { var v6 bool if mathrand.Int()%2 == 0 { v6 = true + if pkAlgo == packet.PubKeyAlgoEdDSA { + pkAlgo = packet.PubKeyAlgoEd25519 + } } var s2kConf *s2k.Config diff --git a/openpgp/integration_tests/v2/utils_test.go b/openpgp/integration_tests/v2/utils_test.go index 74192342a..624c03acf 100644 --- a/openpgp/integration_tests/v2/utils_test.go +++ b/openpgp/integration_tests/v2/utils_test.go @@ -270,6 +270,9 @@ func randConfig() *packet.Config { var v6 bool if mathrand.Int()%2 == 0 { v6 = true + if pkAlgo == packet.PubKeyAlgoEdDSA { + pkAlgo = packet.PubKeyAlgoEd25519 + } } var s2kConf *s2k.Config From d1f7ff41a7bef8f2eca1c4b52ad335e2e28e3291 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Thu, 5 Oct 2023 10:16:42 +0200 Subject: [PATCH 070/116] feat(v2): Refactor entity generation and force hash selection for signing algorithms --- openpgp/ed25519/ed25519.go | 1 + openpgp/ed448/ed448.go | 1 + openpgp/v2/key_generation.go | 93 +++++++++++++++++++++++++----------- openpgp/v2/keys_test.go | 2 +- openpgp/v2/keys_v6_test.go | 23 +++++++++ openpgp/v2/write.go | 90 +++++++++++++++++++++++++++++----- 6 files changed, 171 insertions(+), 39 deletions(-) diff --git a/openpgp/ed25519/ed25519.go b/openpgp/ed25519/ed25519.go index 7de3e7268..4dfdbd8ba 100644 --- a/openpgp/ed25519/ed25519.go +++ b/openpgp/ed25519/ed25519.go @@ -61,6 +61,7 @@ func GenerateKey(rand io.Reader) (*PrivateKey, error) { } // Sign signs a message with the ed25519 algorithm. +// priv MUST be a valid key! Check this with Validate() before use. func Sign(priv *PrivateKey, message []byte) ([]byte, error) { return ed25519lib.Sign(priv.Key, message), nil } diff --git a/openpgp/ed448/ed448.go b/openpgp/ed448/ed448.go index fb0457023..34be33895 100644 --- a/openpgp/ed448/ed448.go +++ b/openpgp/ed448/ed448.go @@ -61,6 +61,7 @@ func GenerateKey(rand io.Reader) (*PrivateKey, error) { } // Sign signs a message with the ed448 algorithm. +// priv MUST be a valid key! Check this with Validate() before use. func Sign(priv *PrivateKey, message []byte) ([]byte, error) { // Ed448 is used with the empty string as a context string. // See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-08#section-13.7 diff --git a/openpgp/v2/key_generation.go b/openpgp/v2/key_generation.go index 46d09d3d4..59ec46614 100644 --- a/openpgp/v2/key_generation.go +++ b/openpgp/v2/key_generation.go @@ -30,6 +30,16 @@ type userIdData struct { name, comment, email string } +type keyProperties struct { + primaryKey *packet.PrivateKey + creationTime time.Time + keyLifetimeSecs uint32 + hash crypto.Hash + cipher packet.CipherFunction + aead *packet.AEADConfig + compression packet.CompressionAlgo +} + // NewEntityWithoutId returns an Entity that contains fresh keys for signing and // encrypting pgp messages. The key is not associated with an identity. // This is only allowed for v6 key generation. If v6 is not enabled, @@ -48,13 +58,23 @@ func NewEntity(name, comment, email string, config *packet.Config) (*Entity, err return newEntity(&userIdData{name, comment, email}, config) } +func selectKeyProperties(creationTime time.Time, config *packet.Config, primary *packet.PrivateKey) *keyProperties { + return &keyProperties{ + primaryKey: primary, + creationTime: creationTime, + keyLifetimeSecs: config.KeyLifetime(), + hash: config.Hash(), + cipher: config.Cipher(), + aead: config.AEAD(), + compression: config.Compression(), + } +} + func newEntity(uid *userIdData, config *packet.Config) (*Entity, error) { if uid == nil && !config.V6() { return nil, errors.InvalidArgumentError("user id has to be set for non-v6 keys") } - creationTime := config.Now() - keyLifetimeSecs := config.KeyLifetime() // Generate a primary signing key primaryPrivRaw, err := newSigner(config) @@ -66,6 +86,8 @@ func newEntity(uid *userIdData, config *packet.Config) (*Entity, error) { primary.UpgradeToV6() } + keyProperties := selectKeyProperties(creationTime, config, primary) + e := &Entity{ PrimaryKey: &primary.PublicKey, PrivateKey: primary, @@ -75,10 +97,12 @@ func newEntity(uid *userIdData, config *packet.Config) (*Entity, error) { } if config.V6() { - e.AddDirectKeySignature(config) + e.AddDirectKeySignature(keyProperties, config) + keyProperties = nil } + if uid != nil { - err = e.addUserId(*uid, config, creationTime, keyLifetimeSecs, !config.V6()) + err = e.addUserId(*uid, config, keyProperties) if err != nil { return nil, err } @@ -95,16 +119,16 @@ func newEntity(uid *userIdData, config *packet.Config) (*Entity, error) { } func (t *Entity) AddUserId(name, comment, email string, config *packet.Config) error { - creationTime := config.Now() - keyLifetimeSecs := config.KeyLifetime() - return t.addUserId(userIdData{name, comment, email}, config, creationTime, keyLifetimeSecs, !config.V6()) + var keyProperties *keyProperties + if !config.V6() { + keyProperties = selectKeyProperties(config.Now(), config, t.PrivateKey) + } + return t.addUserId(userIdData{name, comment, email}, config, keyProperties) } -func (t *Entity) AddDirectKeySignature(config *packet.Config) error { +func (t *Entity) AddDirectKeySignature(selectedKeyProperties *keyProperties, config *packet.Config) error { selfSignature := createSignaturePacket(&t.PrivateKey.PublicKey, packet.SigTypeDirectSignature, config) - creationTime := config.Now() - keyLifetimeSecs := config.KeyLifetime() - err := writeKeyProperties(selfSignature, creationTime, keyLifetimeSecs, config) + err := writeKeyProperties(selfSignature, selectedKeyProperties) if err != nil { return err } @@ -116,30 +140,45 @@ func (t *Entity) AddDirectKeySignature(config *packet.Config) error { return nil } -func writeKeyProperties(selfSignature *packet.Signature, creationTime time.Time, keyLifetimeSecs uint32, config *packet.Config) error { - selfSignature.CreationTime = creationTime - selfSignature.KeyLifetimeSecs = &keyLifetimeSecs +func writeKeyProperties(selfSignature *packet.Signature, selectedKeyProperties *keyProperties) error { + selfSignature.CreationTime = selectedKeyProperties.creationTime + selfSignature.KeyLifetimeSecs = &selectedKeyProperties.keyLifetimeSecs selfSignature.FlagsValid = true selfSignature.FlagSign = true selfSignature.FlagCertify = true selfSignature.SEIPDv1 = true // true by default, see 5.8 vs. 5.14 - selfSignature.SEIPDv2 = config.AEAD() != nil + selfSignature.SEIPDv2 = selectedKeyProperties.aead != nil // Set the PreferredHash for the SelfSignature from the packet.Config. // If it is not the must-implement algorithm from rfc4880bis, append that. - hash, ok := algorithm.HashToHashId(config.Hash()) + hash, ok := algorithm.HashToHashId(selectedKeyProperties.hash) if !ok { return errors.UnsupportedError("unsupported preferred hash function") } - selfSignature.PreferredHash = []uint8{hash} - if config.Hash() != crypto.SHA256 { + selfSignature.PreferredHash = []uint8{} + // Ensure that for signing algorithms with higher security level an + // appropriate a matching hash function is available. + acceptableHashes := acceptableHashesToWrite(&selectedKeyProperties.primaryKey.PublicKey) + var match bool + for _, acceptableHashes := range acceptableHashes { + if acceptableHashes == hash { + match = true + break + } + } + if !match && len(acceptableHashes) > 0 { + selfSignature.PreferredHash = []uint8{acceptableHashes[0]} + } + + selfSignature.PreferredHash = append(selfSignature.PreferredHash, hash) + if selectedKeyProperties.hash != crypto.SHA256 { selfSignature.PreferredHash = append(selfSignature.PreferredHash, hashToHashId(crypto.SHA256)) } // Likewise for DefaultCipher. - selfSignature.PreferredSymmetric = []uint8{uint8(config.Cipher())} - if config.Cipher() != packet.CipherAES128 { + selfSignature.PreferredSymmetric = []uint8{uint8(selectedKeyProperties.cipher)} + if selectedKeyProperties.cipher != packet.CipherAES128 { selfSignature.PreferredSymmetric = append(selfSignature.PreferredSymmetric, uint8(packet.CipherAES128)) } @@ -148,13 +187,13 @@ func writeKeyProperties(selfSignature *packet.Signature, creationTime time.Time, // DefaultCompressionAlgo if any is set (to signal support for cases // where the application knows that using compression is safe). selfSignature.PreferredCompression = []uint8{uint8(packet.CompressionNone)} - if config.Compression() != packet.CompressionNone { - selfSignature.PreferredCompression = append(selfSignature.PreferredCompression, uint8(config.Compression())) + if selectedKeyProperties.compression != packet.CompressionNone { + selfSignature.PreferredCompression = append(selfSignature.PreferredCompression, uint8(selectedKeyProperties.compression)) } // And for DefaultMode. - modes := []uint8{uint8(config.AEAD().Mode())} - if config.AEAD().Mode() != packet.AEADModeOCB { + modes := []uint8{uint8(selectedKeyProperties.aead.Mode())} + if selectedKeyProperties.aead.Mode() != packet.AEADModeOCB { modes = append(modes, uint8(packet.AEADModeOCB)) } @@ -167,7 +206,7 @@ func writeKeyProperties(selfSignature *packet.Signature, creationTime time.Time, return nil } -func (t *Entity) addUserId(userIdData userIdData, config *packet.Config, creationTime time.Time, keyLifetimeSecs uint32, writeProperties bool) error { +func (t *Entity) addUserId(userIdData userIdData, config *packet.Config, selectedKeyProperties *keyProperties) error { uid := packet.NewUserId(userIdData.name, userIdData.comment, userIdData.email) if uid == nil { return errors.InvalidArgumentError("user id field contained invalid characters") @@ -180,8 +219,8 @@ func (t *Entity) addUserId(userIdData userIdData, config *packet.Config, creatio primary := t.PrivateKey isPrimaryId := len(t.Identities) == 0 selfSignature := createSignaturePacket(&primary.PublicKey, packet.SigTypePositiveCert, config) - if writeProperties { - err := writeKeyProperties(selfSignature, creationTime, keyLifetimeSecs, config) + if selectedKeyProperties != nil { + err := writeKeyProperties(selfSignature, selectedKeyProperties) if err != nil { return err } diff --git a/openpgp/v2/keys_test.go b/openpgp/v2/keys_test.go index 4554c8176..833d19d09 100644 --- a/openpgp/v2/keys_test.go +++ b/openpgp/v2/keys_test.go @@ -829,7 +829,7 @@ func TestNewEntityWithDefaultHash(t *testing.T) { t.Fatal("didn't find a preferred hash list in self signature") } ph := hashToHashId(c.DefaultHash) - if prefs[0] != ph { + if c.DefaultHash != crypto.SHA224 && prefs[0] != ph { t.Fatalf("Expected preferred hash to be %d, got %d", ph, prefs[0]) } } diff --git a/openpgp/v2/keys_v6_test.go b/openpgp/v2/keys_v6_test.go index abab52cad..cde778e2d 100644 --- a/openpgp/v2/keys_v6_test.go +++ b/openpgp/v2/keys_v6_test.go @@ -201,3 +201,26 @@ func TestNewEntityWithDefaultHashV6(t *testing.T) { } } } + +func TestKeyGenerationHighSecurityLevel(t *testing.T) { + c := &packet.Config{ + V6Keys: true, + Algorithm: packet.PubKeyAlgoEd448, + DefaultHash: crypto.SHA256, + } + entity, err := NewEntity("Golang Gopher", "Test Key", "no-reply@golang.com", c) + if err != nil { + t.Fatal(err) + } + selfSig, err := entity.PrimarySelfSignature(time.Time{}) + if err != nil { + t.Fatal(err) + } + if !(selfSig.PreferredHash[0] == hashToHashId(crypto.SHA512)) { + t.Fatal("sha 512 should be the preferred option") + } + if selfSig.Hash != crypto.SHA512 { + t.Fatal("sha 512 should be used in self signatures") + } + +} diff --git a/openpgp/v2/write.go b/openpgp/v2/write.go index eafa39726..85c9200d3 100644 --- a/openpgp/v2/write.go +++ b/openpgp/v2/write.go @@ -164,7 +164,7 @@ func detachSignWithWriter(w io.Writer, signers []*Entity, sigType packet.Signatu candidateHashes = intersectPreferences(candidateHashes, preferredHashes) var hash crypto.Hash - if hash, err = selectHash(candidateHashes, config.Hash()); err != nil { + if hash, err = selectHash(candidateHashes, config.Hash(), signingKey.PrivateKey); err != nil { return } @@ -454,7 +454,12 @@ func writeAndSign(payload io.WriteCloser, candidateHashes [][]uint8, signEntitie sigContext := signatureContext{ signer: signer, } - hash, err := selectHash(candidateHashes[signEntityIdx], config.Hash()) + + if signKey.PrimarySelfSignature == nil { + return nil, errors.InvalidArgumentError("signing key has no self-signature") + } + candidateHashes[signEntityIdx] = intersectPreferences(candidateHashes[signEntityIdx], signKey.PrimarySelfSignature.PreferredHash) + hash, err := selectHash(candidateHashes[signEntityIdx], config.Hash(), signKey.PrivateKey) if err != nil { return nil, err } @@ -849,13 +854,35 @@ func (s signatureWriter) Close() error { return s.encryptedData.Close() } +func adaptHashToSigningKey(config *packet.Config, primary *packet.PublicKey) crypto.Hash { + acceptableHashes := acceptableHashesToWrite(primary) + hash, ok := algorithm.HashToHashId(config.Hash()) + if !ok { + return config.Hash() + } + for _, acceptableHashes := range acceptableHashes { + if acceptableHashes == hash { + return config.Hash() + } + } + if len(acceptableHashes) > 0 { + defaultAcceptedHash, ok := algorithm.HashIdToHash(acceptableHashes[0]) + if !ok { + return config.Hash() + } + return defaultAcceptedHash + } + return config.Hash() +} + func createSignaturePacket(signer *packet.PublicKey, sigType packet.SignatureType, config *packet.Config) *packet.Signature { sigLifetimeSecs := config.SigLifetime() + hash := adaptHashToSigningKey(config, signer) return &packet.Signature{ Version: signer.Version, SigType: sigType, PubKeyAlgo: signer.PubKeyAlgo, - Hash: config.Hash(), + Hash: hash, CreationTime: config.Now(), IssuerKeyId: &signer.KeyId, IssuerFingerprint: signer.Fingerprint, @@ -911,7 +938,10 @@ func handleCompression(compressed io.WriteCloser, candidateCompression []uint8, } // selectHash selects the preferred hash given the candidateHashes and the configuredHash -func selectHash(candidateHashes []byte, configuredHash crypto.Hash) (hash crypto.Hash, err error) { +func selectHash(candidateHashes []byte, configuredHash crypto.Hash, signer *packet.PrivateKey) (hash crypto.Hash, err error) { + acceptableHashes := acceptableHashesToWrite(&signer.PublicKey) + candidateHashes = intersectPreferences(acceptableHashes, candidateHashes) + for _, hashId := range candidateHashes { if h, ok := algorithm.HashIdToHash(hashId); ok && h.Available() { hash = h @@ -930,20 +960,22 @@ func selectHash(candidateHashes []byte, configuredHash crypto.Hash) (hash crypto } if hash == 0 { - hashId := candidateHashes[0] - name, ok := algorithm.HashIdToString(hashId) - if !ok { - name = "#" + strconv.Itoa(int(hashId)) + if len(acceptableHashes) > 0 { + if h, ok := algorithm.HashIdToHash(acceptableHashes[0]); ok { + hash = h + } else { + return 0, errors.InvalidArgumentError("no candidate hash functions are compiled in.") + } + } else { + return 0, errors.InvalidArgumentError("no candidate hash functions are compiled in.") } - return 0, errors.InvalidArgumentError("no candidate hash functions are compiled in. (Wanted " + name + " in this case.)") } return } func parseOutsideSig(outsideSig []byte) (outSigPacket *packet.Signature, err error) { - var p packet.Packet packets := packet.NewReader(bytes.NewReader(outsideSig)) - p, err = packets.Next() + p, err := packets.Next() if goerrors.Is(err, io.EOF) { return nil, errors.ErrUnknownIssuer } @@ -961,3 +993,39 @@ func parseOutsideSig(outsideSig []byte) (outSigPacket *packet.Signature, err err } return outSigPacket, nil } + +func acceptableHashesToWrite(singingKey *packet.PublicKey) []uint8 { + switch singingKey.PubKeyAlgo { + case packet.PubKeyAlgoEd448: + return []uint8{ + hashToHashId(crypto.SHA512), + hashToHashId(crypto.SHA3_512), + } + case packet.PubKeyAlgoECDSA: + if curve, err := singingKey.Curve(); err == nil { + if curve == packet.Curve448 || + curve == packet.CurveNistP521 || + curve == packet.CurveBrainpoolP512 { + return []uint8{ + hashToHashId(crypto.SHA512), + hashToHashId(crypto.SHA3_512), + } + } else if curve == packet.CurveBrainpoolP384 || + curve == packet.CurveNistP384 { + return []uint8{ + hashToHashId(crypto.SHA256), + hashToHashId(crypto.SHA384), + hashToHashId(crypto.SHA512), + hashToHashId(crypto.SHA3_512), + } + } + } + } + return []uint8{ + hashToHashId(crypto.SHA256), + hashToHashId(crypto.SHA384), + hashToHashId(crypto.SHA512), + hashToHashId(crypto.SHA3_256), + hashToHashId(crypto.SHA3_512), + } +} From 0c444313269d519ac37f636f72e69e3a84df1fab Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Thu, 12 Oct 2023 09:29:57 +0200 Subject: [PATCH 071/116] feat(v2): Add flag that can disable intended recipient in signcrypt --- openpgp/packet/config.go | 1 + openpgp/v2/write.go | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/openpgp/packet/config.go b/openpgp/packet/config.go index 128f13b87..cc6201057 100644 --- a/openpgp/packet/config.go +++ b/openpgp/packet/config.go @@ -137,6 +137,7 @@ type Config struct { // CheckIntendedRecipients is a flag that indicates if // a decryption key for an encrypted and signed messages should be checked // to be present in the signatures intended recipient list. + // In encrypt and sign intended recipients are only included if this flag is true. // if config is nil or flag is nil, it defaults to true CheckIntendedRecipients *bool // CacheSessionKey is a flag that indicates diff --git a/openpgp/v2/write.go b/openpgp/v2/write.go index 85c9200d3..4da8bf657 100644 --- a/openpgp/v2/write.go +++ b/openpgp/v2/write.go @@ -589,7 +589,9 @@ func encrypt( var intendedRecipients []*packet.Recipient // Intended Recipient Fingerprint subpacket SHOULD be used when creating a signed and encrypted message for _, publicRecipient := range to { - intendedRecipients = append(intendedRecipients, &packet.Recipient{KeyVersion: publicRecipient.PrimaryKey.Version, Fingerprint: publicRecipient.PrimaryKey.Fingerprint}) + if config.IntendedRecipients() { + intendedRecipients = append(intendedRecipients, &packet.Recipient{KeyVersion: publicRecipient.PrimaryKey.Version, Fingerprint: publicRecipient.PrimaryKey.Fingerprint}) + } } timeForEncryptionKey := config.Now() From 4aabb698d496590e16f6219a55f82bf420f38003 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Wed, 22 Nov 2023 09:44:18 +0100 Subject: [PATCH 072/116] fix(armor/clearsign): Address review comments --- openpgp/armor/armor.go | 6 ++---- openpgp/armor/encode.go | 1 - openpgp/clearsign/clearsign.go | 22 ---------------------- 3 files changed, 2 insertions(+), 27 deletions(-) diff --git a/openpgp/armor/armor.go b/openpgp/armor/armor.go index 21d4efb97..e0a677f28 100644 --- a/openpgp/armor/armor.go +++ b/openpgp/armor/armor.go @@ -4,7 +4,7 @@ // Package armor implements OpenPGP ASCII Armor, see RFC 4880. OpenPGP Armor is // very similar to PEM except that it has an additional CRC checksum. -package armor // import "github.com/ProtonMail/go-crypto/v2/openpgp/armor" +package armor // import "github.com/ProtonMail/go-crypto/openpgp/armor" import ( "bufio" @@ -100,9 +100,7 @@ func (l *lineReader) Read(p []byte) (n int, err error) { return } -// openpgpReader passes Read calls to the underlying base64 decoder, but keeps -// a running CRC of the resulting data and checks the CRC against the value -// found by the lineReader at EOF. +// openpgpReader passes Read calls to the underlying base64 decoder. type openpgpReader struct { lReader *lineReader b64Reader io.Reader diff --git a/openpgp/armor/encode.go b/openpgp/armor/encode.go index d9117c145..112f98b83 100644 --- a/openpgp/armor/encode.go +++ b/openpgp/armor/encode.go @@ -16,7 +16,6 @@ var armorEndOfLineOut = []byte("-----\n") const crc24Init = 0xb704ce const crc24Poly = 0x1864cfb -const crc24Mask = 0xffffff // crc24 calculates the OpenPGP checksum as specified in RFC 4880, section 6.1 func crc24(crc uint32, d []byte) uint32 { diff --git a/openpgp/clearsign/clearsign.go b/openpgp/clearsign/clearsign.go index 4ef54049b..d736d6c11 100644 --- a/openpgp/clearsign/clearsign.go +++ b/openpgp/clearsign/clearsign.go @@ -476,25 +476,3 @@ func nameOfHash(h crypto.Hash) string { } return "" } - -// nameToHash returns a hash for a given OpenPGP name, or 0 -// if the name isn't known. See RFC 4880, section 9.4. -func nameToHash(h string) crypto.Hash { - switch h { - case "SHA1": - return crypto.SHA1 - case "SHA224": - return crypto.SHA224 - case "SHA256": - return crypto.SHA256 - case "SHA384": - return crypto.SHA384 - case "SHA512": - return crypto.SHA512 - case "SHA3-256": - return crypto.SHA3_256 - case "SHA3-512": - return crypto.SHA3_512 - } - return crypto.Hash(0) -} From 820b3958eed84edd6cde073170a018d4a38c838c Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Wed, 22 Nov 2023 10:07:56 +0100 Subject: [PATCH 073/116] refactor(ed25519): Address review comments --- openpgp/ed25519/ed25519.go | 33 ++++++++++++++++++++++++--------- openpgp/ed25519/ed25519_test.go | 9 ++++++--- openpgp/packet/private_key.go | 2 +- openpgp/packet/public_key.go | 6 +++--- 4 files changed, 34 insertions(+), 16 deletions(-) diff --git a/openpgp/ed25519/ed25519.go b/openpgp/ed25519/ed25519.go index 4dfdbd8ba..0f0a6e430 100644 --- a/openpgp/ed25519/ed25519.go +++ b/openpgp/ed25519/ed25519.go @@ -10,34 +10,47 @@ import ( ed25519lib "github.com/cloudflare/circl/sign/ed25519" ) -const PointSize = 32 -const PrivateKeySize = 64 -const SignatureSize = 64 +const ( + // PublicKeySize is the size, in bytes, of public keys in this package. + PublicKeySize = ed25519lib.PublicKeySize + // SeedSize is the size, in bytes, of private key seeds. + // The private key representation used by RFC 8032. + SeedSize = ed25519lib.SeedSize + // SignatureSize is the size, in bytes, of signatures generated and verified by this package. + SignatureSize = ed25519lib.SignatureSize +) type PublicKey struct { + // Point represents the elliptic curve point of the public key. Point []byte } type PrivateKey struct { PublicKey - Key []byte // encoded as seed | pub key point + // Key the private key representation by RFC 8032, + // encoded as seed | pub key point. + Key []byte } +// NewPublicKey creates a new empty ed25519 public key. func NewPublicKey() *PublicKey { return &PublicKey{} } +// NewPrivateKey creates a new empty private key referencing the public key. func NewPrivateKey(key PublicKey) *PrivateKey { return &PrivateKey{ PublicKey: key, } } +// Seed returns the ed25519 private key secret seed. +// The private key representation by RFC 8032. func (pk *PrivateKey) Seed() []byte { - return pk.Key[:PointSize] + return pk.Key[:SeedSize] } -// MarshalByteSecret returns the underlying 32 byte seed of the private key +// MarshalByteSecret returns the underlying 32 byte seed of the private key. func (pk *PrivateKey) MarshalByteSecret() []byte { return pk.Seed() } @@ -66,18 +79,18 @@ func Sign(priv *PrivateKey, message []byte) ([]byte, error) { return ed25519lib.Sign(priv.Key, message), nil } -// Verify verifies a ed25519 signature +// Verify verifies an ed25519 signature. func Verify(pub *PublicKey, message []byte, signature []byte) bool { return ed25519lib.Verify(pub.Point, message, signature) } -// Validate checks if the ed25519 private key is valid +// Validate checks if the ed25519 private key is valid. func Validate(priv *PrivateKey) error { expectedPrivateKey := ed25519lib.NewKeyFromSeed(priv.Seed()) if subtle.ConstantTimeCompare(priv.Key, expectedPrivateKey) == 0 { return errors.KeyInvalidError("ed25519: invalid ed25519 secret") } - if subtle.ConstantTimeCompare(priv.PublicKey.Point, expectedPrivateKey[PointSize:]) == 0 { + if subtle.ConstantTimeCompare(priv.PublicKey.Point, expectedPrivateKey[SeedSize:]) == 0 { return errors.KeyInvalidError("ed25519: invalid ed25519 public key") } return nil @@ -85,11 +98,13 @@ func Validate(priv *PrivateKey) error { // ENCODING/DECODING signature: +// WriteSignature encodes and writes an ed25519 signature to writer. func WriteSignature(writer io.Writer, signature []byte) error { _, err := writer.Write(signature) return err } +// ReadSignature decodes an ed25519 signature from a reader. func ReadSignature(reader io.Reader) ([]byte, error) { signature := make([]byte, SignatureSize) if _, err := io.ReadFull(reader, signature); err != nil { diff --git a/openpgp/ed25519/ed25519_test.go b/openpgp/ed25519/ed25519_test.go index 184ebb704..78f6fc900 100644 --- a/openpgp/ed25519/ed25519_test.go +++ b/openpgp/ed25519/ed25519_test.go @@ -1,23 +1,26 @@ package ed25519 import ( + "crypto/ed25519" "crypto/rand" "io" "testing" ) +const messageDigestSize = 32 + func TestGenerate(t *testing.T) { priv, err := GenerateKey(rand.Reader) if err != nil { t.Fatal(err) } - if len(priv.Key) != 64 && len(priv.Point) != 32 { - t.Error("gnerated wrong key sizes") + if len(priv.Key) != ed25519.SeedSize+ed25519.PublicKeySize && len(priv.Point) != ed25519.PublicKeySize { + t.Error("generated wrong key sizes") } } func TestSignVerify(t *testing.T) { - digest := make([]byte, 32) + digest := make([]byte, messageDigestSize) _, err := io.ReadFull(rand.Reader, digest[:]) if err != nil { t.Fatal(err) diff --git a/openpgp/packet/private_key.go b/openpgp/packet/private_key.go index 50bab10fc..3f8b5cc97 100644 --- a/openpgp/packet/private_key.go +++ b/openpgp/packet/private_key.go @@ -1005,7 +1005,7 @@ func (pk *PrivateKey) parseEd25519PrivateKey(data []byte) (err error) { privateKey := ed25519.NewPrivateKey(*publicKey) privateKey.PublicKey = *publicKey - if len(data) != ed25519.PointSize { + if len(data) != ed25519.SeedSize { err = errors.StructuralError("wrong ed25519 key size") } err = privateKey.UnmarshalByteSecret(data) diff --git a/openpgp/packet/public_key.go b/openpgp/packet/public_key.go index 73fbb47c4..813790b77 100644 --- a/openpgp/packet/public_key.go +++ b/openpgp/packet/public_key.go @@ -533,7 +533,7 @@ func (pk *PublicKey) parseX448(r io.Reader) (err error) { } func (pk *PublicKey) parseEd25519(r io.Reader) (err error) { - point := make([]byte, ed25519.PointSize) + point := make([]byte, ed25519.PublicKeySize) _, err = io.ReadFull(r, point) if err != nil { return @@ -632,7 +632,7 @@ func (pk *PublicKey) algorithmSpecificByteCount() int { case PubKeyAlgoX448: length += x448.PointSize case PubKeyAlgoEd25519: - length += ed25519.PointSize + length += ed25519.PublicKeySize case PubKeyAlgoEd448: length += ed448.PointSize default: @@ -987,7 +987,7 @@ func (pk *PublicKey) BitLength() (bitLength uint16, err error) { case PubKeyAlgoX448: bitLength = x448.PointSize * 8 case PubKeyAlgoEd25519: - bitLength = ed25519.PointSize * 8 + bitLength = ed25519.PublicKeySize * 8 case PubKeyAlgoEd448: bitLength = ed448.PointSize * 8 default: From 13e46127d1eb4616696171ac6e3cac41e2cb0956 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Wed, 22 Nov 2023 10:17:08 +0100 Subject: [PATCH 074/116] refactor(ed448): Address review comments --- openpgp/ed25519/ed25519.go | 1 + openpgp/ed448/ed448.go | 30 +++++++++++++++++++++++------- openpgp/ed448/ed448_test.go | 2 +- openpgp/packet/private_key.go | 2 +- openpgp/packet/public_key.go | 6 +++--- 5 files changed, 29 insertions(+), 12 deletions(-) diff --git a/openpgp/ed25519/ed25519.go b/openpgp/ed25519/ed25519.go index 0f0a6e430..6abdf7c44 100644 --- a/openpgp/ed25519/ed25519.go +++ b/openpgp/ed25519/ed25519.go @@ -62,6 +62,7 @@ func (sk *PrivateKey) UnmarshalByteSecret(seed []byte) error { return nil } +// GenerateKey generates a fresh private key with the provided randomness source. func GenerateKey(rand io.Reader) (*PrivateKey, error) { publicKey, privateKey, err := ed25519lib.GenerateKey(rand) if err != nil { diff --git a/openpgp/ed448/ed448.go b/openpgp/ed448/ed448.go index 34be33895..2db1208d0 100644 --- a/openpgp/ed448/ed448.go +++ b/openpgp/ed448/ed448.go @@ -10,34 +10,47 @@ import ( ed448lib "github.com/cloudflare/circl/sign/ed448" ) -const PointSize = 57 -const PrivateKeySize = 114 -const SignatureSize = 114 +const ( + // PublicKeySize is the size, in bytes, of public keys in this package. + PublicKeySize = ed448lib.PublicKeySize + // SeedSize is the size, in bytes, of private key seeds. + // The private key representation used by RFC 8032. + SeedSize = ed448lib.SeedSize + // SignatureSize is the size, in bytes, of signatures generated and verified by this package. + SignatureSize = ed448lib.SignatureSize +) type PublicKey struct { + // Point represents the elliptic curve point of the public key. Point []byte } type PrivateKey struct { PublicKey - Key []byte // encoded as seed | pub key point + // Key the private key representation by RFC 8032, + // encoded as seed | public key point. + Key []byte } +// NewPublicKey creates a new empty ed25519 public key. func NewPublicKey() *PublicKey { return &PublicKey{} } +// NewPrivateKey creates a new empty private key referencing the public key. func NewPrivateKey(key PublicKey) *PrivateKey { return &PrivateKey{ PublicKey: key, } } +// Seed returns the ed25519 private key secret seed. +// The private key representation by RFC 8032. func (pk *PrivateKey) Seed() []byte { - return pk.Key[:PointSize] + return pk.Key[:SeedSize] } -// MarshalByteSecret returns the underlying 32 byte seed of the private key +// MarshalByteSecret returns the underlying seed of the private key. func (pk *PrivateKey) MarshalByteSecret() []byte { return pk.Seed() } @@ -49,6 +62,7 @@ func (sk *PrivateKey) UnmarshalByteSecret(seed []byte) error { return nil } +// GenerateKey generates a fresh private key with the provided randomness source. func GenerateKey(rand io.Reader) (*PrivateKey, error) { publicKey, privateKey, err := ed448lib.GenerateKey(rand) if err != nil { @@ -81,7 +95,7 @@ func Validate(priv *PrivateKey) error { if subtle.ConstantTimeCompare(priv.Key, expectedPrivateKey) == 0 { return errors.KeyInvalidError("ed448: invalid ed448 secret") } - if subtle.ConstantTimeCompare(priv.PublicKey.Point, expectedPrivateKey[PointSize:]) == 0 { + if subtle.ConstantTimeCompare(priv.PublicKey.Point, expectedPrivateKey[SeedSize:]) == 0 { return errors.KeyInvalidError("ed448: invalid ed448 public key") } return nil @@ -89,11 +103,13 @@ func Validate(priv *PrivateKey) error { // ENCODING/DECODING signature: +// WriteSignature encodes and writes an ed448 signature to writer. func WriteSignature(writer io.Writer, signature []byte) error { _, err := writer.Write(signature) return err } +// ReadSignature decodes an ed448 signature from a reader. func ReadSignature(reader io.Reader) ([]byte, error) { signature := make([]byte, SignatureSize) if _, err := io.ReadFull(reader, signature); err != nil { diff --git a/openpgp/ed448/ed448_test.go b/openpgp/ed448/ed448_test.go index ed900f30e..805442c44 100644 --- a/openpgp/ed448/ed448_test.go +++ b/openpgp/ed448/ed448_test.go @@ -11,7 +11,7 @@ func TestGenerate(t *testing.T) { if err != nil { t.Fatal(err) } - if len(priv.Key) != 114 && len(priv.Point) != 57 { + if len(priv.Key) != SeedSize+PublicKeySize && len(priv.Point) != PublicKeySize { t.Error("gnerated wrong key sizes") } } diff --git a/openpgp/packet/private_key.go b/openpgp/packet/private_key.go index 3f8b5cc97..fdf45645f 100644 --- a/openpgp/packet/private_key.go +++ b/openpgp/packet/private_key.go @@ -1025,7 +1025,7 @@ func (pk *PrivateKey) parseEd448PrivateKey(data []byte) (err error) { privateKey := ed448.NewPrivateKey(*publicKey) privateKey.PublicKey = *publicKey - if len(data) != ed448.PointSize { + if len(data) != ed448.SeedSize { err = errors.StructuralError("wrong ed448 key size") } err = privateKey.UnmarshalByteSecret(data) diff --git a/openpgp/packet/public_key.go b/openpgp/packet/public_key.go index 813790b77..aef460944 100644 --- a/openpgp/packet/public_key.go +++ b/openpgp/packet/public_key.go @@ -546,7 +546,7 @@ func (pk *PublicKey) parseEd25519(r io.Reader) (err error) { } func (pk *PublicKey) parseEd448(r io.Reader) (err error) { - point := make([]byte, ed448.PointSize) + point := make([]byte, ed448.PublicKeySize) _, err = io.ReadFull(r, point) if err != nil { return @@ -634,7 +634,7 @@ func (pk *PublicKey) algorithmSpecificByteCount() int { case PubKeyAlgoEd25519: length += ed25519.PublicKeySize case PubKeyAlgoEd448: - length += ed448.PointSize + length += ed448.PublicKeySize default: panic("unknown public key algorithm") } @@ -989,7 +989,7 @@ func (pk *PublicKey) BitLength() (bitLength uint16, err error) { case PubKeyAlgoEd25519: bitLength = ed25519.PublicKeySize * 8 case PubKeyAlgoEd448: - bitLength = ed448.PointSize * 8 + bitLength = ed448.PublicKeySize * 8 default: err = errors.InvalidArgumentError("bad public-key algorithm") } From 3198ed3fada927f01bd289c9e3339891f395df17 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Wed, 22 Nov 2023 10:40:27 +0100 Subject: [PATCH 075/116] refactor(x25519): Apply review comments and improve readability --- openpgp/packet/private_key.go | 4 +- openpgp/packet/public_key.go | 6 +-- openpgp/x25519/x25519.go | 74 ++++++++++++++++++----------------- openpgp/x25519/x25519_test.go | 4 +- 4 files changed, 46 insertions(+), 42 deletions(-) diff --git a/openpgp/packet/private_key.go b/openpgp/packet/private_key.go index fdf45645f..dc7eb50a4 100644 --- a/openpgp/packet/private_key.go +++ b/openpgp/packet/private_key.go @@ -967,9 +967,9 @@ func (pk *PrivateKey) parseX25519PrivateKey(data []byte) (err error) { privateKey := x25519.NewPrivateKey(*publicKey) privateKey.PublicKey = *publicKey - privateKey.Secret = make([]byte, x25519.PointSize) + privateKey.Secret = make([]byte, x25519.KeySize) - if len(data) != x25519.PointSize { + if len(data) != x25519.KeySize { err = errors.StructuralError("wrong X25519 key size") } copy(privateKey.Secret, data) diff --git a/openpgp/packet/public_key.go b/openpgp/packet/public_key.go index aef460944..ae0a60e0c 100644 --- a/openpgp/packet/public_key.go +++ b/openpgp/packet/public_key.go @@ -507,7 +507,7 @@ func (pk *PublicKey) parseEdDSA(r io.Reader) (err error) { } func (pk *PublicKey) parseX25519(r io.Reader) (err error) { - point := make([]byte, x25519.PointSize) + point := make([]byte, x25519.KeySize) _, err = io.ReadFull(r, point) if err != nil { return @@ -628,7 +628,7 @@ func (pk *PublicKey) algorithmSpecificByteCount() int { length += int(pk.oid.EncodedLength()) length += int(pk.p.EncodedLength()) case PubKeyAlgoX25519: - length += x25519.PointSize + length += x25519.KeySize case PubKeyAlgoX448: length += x448.PointSize case PubKeyAlgoEd25519: @@ -983,7 +983,7 @@ func (pk *PublicKey) BitLength() (bitLength uint16, err error) { case PubKeyAlgoEdDSA: bitLength = pk.p.BitLength() case PubKeyAlgoX25519: - bitLength = x25519.PointSize * 8 + bitLength = x25519.KeySize * 8 case PubKeyAlgoX448: bitLength = x448.PointSize * 8 case PubKeyAlgoEd25519: diff --git a/openpgp/x25519/x25519.go b/openpgp/x25519/x25519.go index 859d9726f..c83437c84 100644 --- a/openpgp/x25519/x25519.go +++ b/openpgp/x25519/x25519.go @@ -11,27 +11,32 @@ import ( "golang.org/x/crypto/hkdf" ) -const hkdfInfo = "OpenPGP X25519" -const aes128KeySize = 16 -const PointSize = 32 +const ( + hkdfInfo = "OpenPGP X25519" + aes128KeySize = 16 + // The size of a public or private key in bytes. + KeySize = x25519lib.Size +) type PublicKey struct { + // Point represents the encoded elliptic curve point of the public key. Point []byte } type PrivateKey struct { PublicKey + // Secret represents the secret of the private key. Secret []byte } +// NewPrivateKey creates a new empty private key including the public key. func NewPrivateKey(key PublicKey) *PrivateKey { return &PrivateKey{ PublicKey: key, } } -// Validate validates that the provided public key matches -// the private key. +// Validate validates that the provided public key matches the private key. func Validate(pk *PrivateKey) (err error) { var expectedPublicKey, privateKey x25519lib.Key subtle.ConstantTimeCopy(1, privateKey[:], pk.Secret) @@ -42,7 +47,7 @@ func Validate(pk *PrivateKey) (err error) { return nil } -// GenerateKey generates a new x25519 key pair +// GenerateKey generates a new x25519 key pair. func GenerateKey(rand io.Reader) (*PrivateKey, error) { var privateKey, publicKey x25519lib.Key privateKeyOut := new(PrivateKey) @@ -72,13 +77,13 @@ func generateKey(rand io.Reader, privateKey *x25519lib.Key, publicKey *x25519lib return nil } -// Encrypt encrpyts a sessionKey with x25519 according to +// Encrypt encrypts a sessionKey with x25519 according to // the OpenPGP crypto refresh specification section 5.1.6. The function assumes that the // sessionKey has the correct format and padding according to the specification. func Encrypt(rand io.Reader, publicKey *PublicKey, sessionKey []byte) (ephemeralPublicKey *PublicKey, encryptedSessionKey []byte, err error) { var ephemeralPrivate, ephemeralPublic, staticPublic, shared x25519lib.Key // Check that the input static public key has 32 bytes - if len(publicKey.Point) != PointSize { + if len(publicKey.Point) != KeySize { err = errors.KeyInvalidError("x25519: the public key has the wrong size") return } @@ -105,11 +110,11 @@ func Encrypt(rand io.Reader, publicKey *PublicKey, sessionKey []byte) (ephemeral } // Decrypt decrypts a session key stored in ciphertext with the provided x25519 -// private key and ephemeral public key +// private key and ephemeral public key. func Decrypt(privateKey *PrivateKey, ephemeralPublicKey *PublicKey, ciphertext []byte) (encodedSessionKey []byte, err error) { var ephemeralPublic, staticPrivate, shared x25519lib.Key // Check that the input ephemeral public key has 32 bytes - if len(ephemeralPublicKey.Point) != PointSize { + if len(ephemeralPublicKey.Point) != KeySize { err = errors.KeyInvalidError("x25519: the public key has the wrong size") return } @@ -129,11 +134,11 @@ func Decrypt(privateKey *PrivateKey, ephemeralPublicKey *PublicKey, ciphertext [ } func applyHKDF(ephemeralPublicKey []byte, publicKey []byte, sharedSecret []byte) []byte { - inputKey := make([]byte, 3*PointSize) + inputKey := make([]byte, 3*KeySize) // ephemeral public key | recipient public key | shared secret - subtle.ConstantTimeCopy(1, inputKey[:PointSize], ephemeralPublicKey) - subtle.ConstantTimeCopy(1, inputKey[PointSize:2*PointSize], publicKey) - subtle.ConstantTimeCopy(1, inputKey[2*PointSize:], sharedSecret) + subtle.ConstantTimeCopy(1, inputKey[:KeySize], ephemeralPublicKey) + subtle.ConstantTimeCopy(1, inputKey[KeySize:2*KeySize], publicKey) + subtle.ConstantTimeCopy(1, inputKey[2*KeySize:], sharedSecret) hkdfReader := hkdf.New(sha256.New, inputKey, []byte{}, []byte(hkdfInfo)) encryptionKey := make([]byte, aes128KeySize) _, _ = io.ReadFull(hkdfReader, encryptionKey) @@ -151,67 +156,66 @@ func constantTimeIsZero(bytes []byte) bool { // ENCODING/DECODING ciphertexts: // EncodeFieldsLength returns the length of the ciphertext encoding -// given the encrpyted session key. +// given the encrypted session key. func EncodedFieldsLength(encryptedSessionKey []byte, v6 bool) int { lenCipherFunction := 0 if !v6 { lenCipherFunction = 1 } - return PointSize + 1 + len(encryptedSessionKey) + lenCipherFunction + return KeySize + 1 + len(encryptedSessionKey) + lenCipherFunction } -// EncodeField encodes X25519 session key encryption as +// EncodeField encodes X25519 session key encryption fields as // ephemeral X25519 public key | follow byte length | cipherFunction (v3 only) | encryptedSessionKey -// and writes it to writer +// and writes it to writer. func EncodeFields(writer io.Writer, ephemeralPublicKey *PublicKey, encryptedSessionKey []byte, cipherFunction byte, v6 bool) (err error) { lenAlgorithm := 0 if !v6 { lenAlgorithm = 1 } if _, err = writer.Write(ephemeralPublicKey.Point); err != nil { - return + return err } if _, err = writer.Write([]byte{byte(len(encryptedSessionKey) + lenAlgorithm)}); err != nil { - return + return err } if !v6 { if _, err = writer.Write([]byte{cipherFunction}); err != nil { - return + return err } } _, err = writer.Write(encryptedSessionKey) - return + return err } // DecodeField decodes a X25519 session key encryption as -// ephemeral X25519 public key | follow byte length | cipherFunction (v3 only) | encryptedSessionKey +// ephemeral X25519 public key | follow byte length | cipherFunction (v3 only) | encryptedSessionKey. func DecodeFields(reader io.Reader, v6 bool) (ephemeralPublicKey *PublicKey, encryptedSessionKey []byte, cipherFunction byte, err error) { var buf [1]byte ephemeralPublicKey = &PublicKey{ - Point: make([]byte, PointSize), + Point: make([]byte, KeySize), } // 32 octets representing an ephemeral X25519 public key. - _, err = io.ReadFull(reader, ephemeralPublicKey.Point) - if err != nil { - return + if _, err = io.ReadFull(reader, ephemeralPublicKey.Point); err != nil { + return nil, nil, 0, err } // A one-octet size of the following fields. - _, err = io.ReadFull(reader, buf[:]) - if err != nil { - return + if _, err = io.ReadFull(reader, buf[:]); err != nil { + return nil, nil, 0, err } followingLen := buf[0] // The one-octet algorithm identifier, if it was passed (in the case of a v3 PKESK packet). if !v6 { - _, err = io.ReadFull(reader, buf[:]) - if err != nil { - return + if _, err = io.ReadFull(reader, buf[:]); err != nil { + return nil, nil, 0, err } cipherFunction = buf[0] followingLen -= 1 } // The encrypted session key. encryptedSessionKey = make([]byte, followingLen) - _, err = io.ReadFull(reader, encryptedSessionKey) - return + if _, err = io.ReadFull(reader, encryptedSessionKey); err != nil { + return nil, nil, 0, err + } + return ephemeralPublicKey, encryptedSessionKey, cipherFunction, nil } diff --git a/openpgp/x25519/x25519_test.go b/openpgp/x25519/x25519_test.go index 67747beb1..d66d8fb91 100644 --- a/openpgp/x25519/x25519_test.go +++ b/openpgp/x25519/x25519_test.go @@ -11,10 +11,10 @@ func TestGenerate(t *testing.T) { if err != nil { t.Fatal(err) } - if len(privateKey.Secret) != PointSize { + if len(privateKey.Secret) != KeySize { t.Fatal("key has the wrong size") } - if len(privateKey.PublicKey.Point) != PointSize { + if len(privateKey.PublicKey.Point) != KeySize { t.Fatal("key has the wrong size") } } From 49087ba51c25d500e1a8dfe519fc219979b1d416 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Wed, 22 Nov 2023 10:54:27 +0100 Subject: [PATCH 076/116] refactor(x448): Apply review feedback and improve readability --- openpgp/packet/private_key.go | 4 +- openpgp/packet/public_key.go | 6 +- openpgp/x448/x448.go | 122 +++++++++++++++++++--------------- openpgp/x448/x448_test.go | 4 +- 4 files changed, 74 insertions(+), 62 deletions(-) diff --git a/openpgp/packet/private_key.go b/openpgp/packet/private_key.go index dc7eb50a4..3f67b8c0a 100644 --- a/openpgp/packet/private_key.go +++ b/openpgp/packet/private_key.go @@ -986,9 +986,9 @@ func (pk *PrivateKey) parseX448PrivateKey(data []byte) (err error) { privateKey := x448.NewPrivateKey(*publicKey) privateKey.PublicKey = *publicKey - privateKey.Secret = make([]byte, x448.PointSize) + privateKey.Secret = make([]byte, x448.KeySize) - if len(data) != x448.PointSize { + if len(data) != x448.KeySize { err = errors.StructuralError("wrong x448 key size") } copy(privateKey.Secret, data) diff --git a/openpgp/packet/public_key.go b/openpgp/packet/public_key.go index ae0a60e0c..a7c202d51 100644 --- a/openpgp/packet/public_key.go +++ b/openpgp/packet/public_key.go @@ -520,7 +520,7 @@ func (pk *PublicKey) parseX25519(r io.Reader) (err error) { } func (pk *PublicKey) parseX448(r io.Reader) (err error) { - point := make([]byte, x448.PointSize) + point := make([]byte, x448.KeySize) _, err = io.ReadFull(r, point) if err != nil { return @@ -630,7 +630,7 @@ func (pk *PublicKey) algorithmSpecificByteCount() int { case PubKeyAlgoX25519: length += x25519.KeySize case PubKeyAlgoX448: - length += x448.PointSize + length += x448.KeySize case PubKeyAlgoEd25519: length += ed25519.PublicKeySize case PubKeyAlgoEd448: @@ -985,7 +985,7 @@ func (pk *PublicKey) BitLength() (bitLength uint16, err error) { case PubKeyAlgoX25519: bitLength = x25519.KeySize * 8 case PubKeyAlgoX448: - bitLength = x448.PointSize * 8 + bitLength = x448.KeySize * 8 case PubKeyAlgoEd25519: bitLength = ed25519.PublicKeySize * 8 case PubKeyAlgoEd448: diff --git a/openpgp/x448/x448.go b/openpgp/x448/x448.go index c5212e5fa..c8cbeca74 100644 --- a/openpgp/x448/x448.go +++ b/openpgp/x448/x448.go @@ -11,19 +11,25 @@ import ( "golang.org/x/crypto/hkdf" ) -const hkdfInfo = "OpenPGP X448" -const aesKeySize = 32 -const PointSize = 56 +const ( + hkdfInfo = "OpenPGP X448" + aes256KeySize = 32 + // The size of a public or private key in bytes. + KeySize = x448lib.Size +) type PublicKey struct { + // Point represents the encoded elliptic curve point of the public key. Point []byte } type PrivateKey struct { PublicKey + // Secret represents the secret of the private key. Secret []byte } +// NewPrivateKey creates a new empty private key including the public key. func NewPrivateKey(key PublicKey) *PrivateKey { return &PrivateKey{ PublicKey: key, @@ -42,7 +48,7 @@ func Validate(pk *PrivateKey) (err error) { return nil } -// GenerateKey generates a new x448 key pair +// GenerateKey generates a new x448 key pair. func GenerateKey(rand io.Reader) (*PrivateKey, error) { var privateKey, publicKey x448lib.Key privateKeyOut := new(PrivateKey) @@ -72,70 +78,75 @@ func generateKey(rand io.Reader, privateKey *x448lib.Key, publicKey *x448lib.Key return nil } -// Encrypt encrpyts a sessionKey with x448 according to -// the OpenPGP crypto refresh specification section 5.1.6. The function assumes that the +// Encrypt encrypts a sessionKey with x448 according to +// the OpenPGP crypto refresh specification section 5.1.7. The function assumes that the // sessionKey has the correct format and padding according to the specification. func Encrypt(rand io.Reader, publicKey *PublicKey, sessionKey []byte) (ephemeralPublicKey *PublicKey, encryptedSessionKey []byte, err error) { var ephemeralPrivate, ephemeralPublic, staticPublic, shared x448lib.Key - // Check that the input static public key has 32 bytes - if len(publicKey.Point) != PointSize { + // Check that the input static public key has 56 bytes. + if len(publicKey.Point) != KeySize { err = errors.KeyInvalidError("x448: the public key has the wrong size") - return + return nil, nil, err } copy(staticPublic[:], publicKey.Point) - // Generate ephemeral keyPair - err = generateKey(rand, &ephemeralPrivate, &ephemeralPublic) - if err != nil { - return + // Generate ephemeral keyPair. + if err = generateKey(rand, &ephemeralPrivate, &ephemeralPublic); err != nil { + return nil, nil, err } - // Compute shared key + // Compute shared key. ok := x448lib.Shared(&shared, &ephemeralPrivate, &staticPublic) if !ok { err = errors.KeyInvalidError("x448: the public key is a low order point") - return + return nil, nil, err } - // Derive the encryption key from the shared secret + // Derive the encryption key from the shared secret. encryptionKey := applyHKDF(ephemeralPublic[:], publicKey.Point[:], shared[:]) ephemeralPublicKey = &PublicKey{ Point: ephemeralPublic[:], } - // Encrypt the sessionKey with aes key wrapping + // Encrypt the sessionKey with aes key wrapping. encryptedSessionKey, err = keywrap.Wrap(encryptionKey, sessionKey) - return + if err != nil { + return nil, nil, err + } + return ephemeralPublicKey, encryptedSessionKey, nil } // Decrypt decrypts a session key stored in ciphertext with the provided x448 -// private key and ephemeral public key +// private key and ephemeral public key. func Decrypt(privateKey *PrivateKey, ephemeralPublicKey *PublicKey, ciphertext []byte) (encodedSessionKey []byte, err error) { var ephemeralPublic, staticPrivate, shared x448lib.Key - // Check that the input ephemeral public key has 32 bytes - if len(ephemeralPublicKey.Point) != PointSize { + // Check that the input ephemeral public key has 56 bytes. + if len(ephemeralPublicKey.Point) != KeySize { err = errors.KeyInvalidError("x448: the public key has the wrong size") - return + return nil, err } copy(ephemeralPublic[:], ephemeralPublicKey.Point) subtle.ConstantTimeCopy(1, staticPrivate[:], privateKey.Secret) - // Compute shared key + // Compute shared key. ok := x448lib.Shared(&shared, &staticPrivate, &ephemeralPublic) if !ok { err = errors.KeyInvalidError("x448: the ephemeral public key is a low order point") - return + return nil, err } - // Derive the encryption key from the shared secret + // Derive the encryption key from the shared secret. encryptionKey := applyHKDF(ephemeralPublicKey.Point[:], privateKey.PublicKey.Point[:], shared[:]) - // Decrypt the session key with aes key wrapping + // Decrypt the session key with aes key wrapping. encodedSessionKey, err = keywrap.Unwrap(encryptionKey, ciphertext) - return + if err != nil { + return nil, err + } + return encodedSessionKey, nil } func applyHKDF(ephemeralPublicKey []byte, publicKey []byte, sharedSecret []byte) []byte { - inputKey := make([]byte, 3*PointSize) - // ephemeral public key | recipient public key | shared secret - subtle.ConstantTimeCopy(1, inputKey[:PointSize], ephemeralPublicKey) - subtle.ConstantTimeCopy(1, inputKey[PointSize:2*PointSize], publicKey) - subtle.ConstantTimeCopy(1, inputKey[2*PointSize:], sharedSecret) + inputKey := make([]byte, 3*KeySize) + // ephemeral public key | recipient public key | shared secret. + subtle.ConstantTimeCopy(1, inputKey[:KeySize], ephemeralPublicKey) + subtle.ConstantTimeCopy(1, inputKey[KeySize:2*KeySize], publicKey) + subtle.ConstantTimeCopy(1, inputKey[2*KeySize:], sharedSecret) hkdfReader := hkdf.New(sha512.New, inputKey, []byte{}, []byte(hkdfInfo)) - encryptionKey := make([]byte, aesKeySize) + encryptionKey := make([]byte, aes256KeySize) _, _ = io.ReadFull(hkdfReader, encryptionKey) return encryptionKey } @@ -151,67 +162,68 @@ func constantTimeIsZero(bytes []byte) bool { // ENCODING/DECODING ciphertexts: // EncodeFieldsLength returns the length of the ciphertext encoding -// given the encrpyted session key. +// given the encrypted session key. func EncodedFieldsLength(encryptedSessionKey []byte, v6 bool) int { lenCipherFunction := 0 if !v6 { lenCipherFunction = 1 } - return PointSize + 1 + len(encryptedSessionKey) + lenCipherFunction + return KeySize + 1 + len(encryptedSessionKey) + lenCipherFunction } -// EncodeField encodes x448 session key encryption as +// EncodeField encodes x448 session key encryption fields as // ephemeral x448 public key | follow byte length | cipherFunction (v3 only) | encryptedSessionKey -// and writes it to writer +// and writes it to writer. func EncodeFields(writer io.Writer, ephemeralPublicKey *PublicKey, encryptedSessionKey []byte, cipherFunction byte, v6 bool) (err error) { lenAlgorithm := 0 if !v6 { lenAlgorithm = 1 } if _, err = writer.Write(ephemeralPublicKey.Point); err != nil { - return + return err } if _, err = writer.Write([]byte{byte(len(encryptedSessionKey) + lenAlgorithm)}); err != nil { - return + return err } if !v6 { if _, err = writer.Write([]byte{cipherFunction}); err != nil { - return + return err } } - _, err = writer.Write(encryptedSessionKey) - return + if _, err = writer.Write(encryptedSessionKey); err != nil { + return err + } + return nil } // DecodeField decodes a x448 session key encryption as -// ephemeral x448 public key | follow byte length | cipherFunction (v3 only) | encryptedSessionKey +// ephemeral x448 public key | follow byte length | cipherFunction (v3 only) | encryptedSessionKey. func DecodeFields(reader io.Reader, v6 bool) (ephemeralPublicKey *PublicKey, encryptedSessionKey []byte, cipherFunction byte, err error) { var buf [1]byte ephemeralPublicKey = &PublicKey{ - Point: make([]byte, PointSize), + Point: make([]byte, KeySize), } // 56 octets representing an ephemeral X448 public key. - _, err = io.ReadFull(reader, ephemeralPublicKey.Point) - if err != nil { - return + if _, err = io.ReadFull(reader, ephemeralPublicKey.Point); err != nil { + return nil, nil, 0, err } // A one-octet size of the following fields. - _, err = io.ReadFull(reader, buf[:]) - if err != nil { - return + if _, err = io.ReadFull(reader, buf[:]); err != nil { + return nil, nil, 0, err } followingLen := buf[0] // The one-octet algorithm identifier, if it was passed (in the case of a v3 PKESK packet). if !v6 { - _, err = io.ReadFull(reader, buf[:]) - if err != nil { - return + if _, err = io.ReadFull(reader, buf[:]); err != nil { + return nil, nil, 0, err } cipherFunction = buf[0] followingLen -= 1 } // The encrypted session key. encryptedSessionKey = make([]byte, followingLen) - _, err = io.ReadFull(reader, encryptedSessionKey) - return + if _, err = io.ReadFull(reader, encryptedSessionKey); err != nil { + return nil, nil, 0, err + } + return ephemeralPublicKey, encryptedSessionKey, cipherFunction, nil } diff --git a/openpgp/x448/x448_test.go b/openpgp/x448/x448_test.go index 73d38b1da..8b3e74db0 100644 --- a/openpgp/x448/x448_test.go +++ b/openpgp/x448/x448_test.go @@ -11,10 +11,10 @@ func TestGenerate(t *testing.T) { if err != nil { t.Fatal(err) } - if len(privateKey.Secret) != PointSize { + if len(privateKey.Secret) != KeySize { t.Fatal("key has the wrong size") } - if len(privateKey.PublicKey.Point) != PointSize { + if len(privateKey.PublicKey.Point) != KeySize { t.Fatal("key has the wrong size") } } From 3d55a7e6fed727e54704208f2b2fb44cb497acb9 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Wed, 22 Nov 2023 11:08:36 +0100 Subject: [PATCH 077/116] refactor(integration_tests): Re-enable compression and resolve lint warnings --- openpgp/integration_tests/end_to_end_test.go | 2 +- openpgp/integration_tests/utils_test.go | 6 +++--- openpgp/integration_tests/v2/utils_test.go | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/openpgp/integration_tests/end_to_end_test.go b/openpgp/integration_tests/end_to_end_test.go index 02c0406be..09b2221bb 100644 --- a/openpgp/integration_tests/end_to_end_test.go +++ b/openpgp/integration_tests/end_to_end_test.go @@ -209,7 +209,7 @@ func encDecTest(t *testing.T, from testVector, testVectors []testVector) { "Message signed by wrong key id, got: %v, want: %v", *md.SignedBy, expectedKeyID) } - if signKey.PublicKey.Version == 6 && bytes.Compare(md.SignedByFingerprint, expectedFingerprint) != 0 { + if signKey.PublicKey.Version == 6 && !bytes.Equal(md.SignedByFingerprint, expectedFingerprint) { t.Fatalf( "Message signed by wrong key id, got: %x, want: %x", md.SignedByFingerprint, expectedFingerprint) diff --git a/openpgp/integration_tests/utils_test.go b/openpgp/integration_tests/utils_test.go index 945f0c1e1..6358af20b 100644 --- a/openpgp/integration_tests/utils_test.go +++ b/openpgp/integration_tests/utils_test.go @@ -228,8 +228,8 @@ func randConfig() *packet.Config { compAlgos := []packet.CompressionAlgo{ packet.CompressionNone, - //packet.CompressionZIP, - //packet.CompressionZLIB, + packet.CompressionZIP, + packet.CompressionZLIB, } compAlgo := compAlgos[mathrand.Intn(len(compAlgos))] @@ -265,7 +265,7 @@ func randConfig() *packet.Config { } level := mathrand.Intn(11) - 1 - compConf := &packet.CompressionConfig{level} + compConf := &packet.CompressionConfig{Level: level} var v6 bool if mathrand.Int()%2 == 0 { diff --git a/openpgp/integration_tests/v2/utils_test.go b/openpgp/integration_tests/v2/utils_test.go index 624c03acf..0c3c49c31 100644 --- a/openpgp/integration_tests/v2/utils_test.go +++ b/openpgp/integration_tests/v2/utils_test.go @@ -228,8 +228,8 @@ func randConfig() *packet.Config { compAlgos := []packet.CompressionAlgo{ packet.CompressionNone, - //packet.CompressionZIP, - //packet.CompressionZLIB, + packet.CompressionZIP, + packet.CompressionZLIB, } compAlgo := compAlgos[mathrand.Intn(len(compAlgos))] From 140825517cf4fe20d65246c9c12cfa55908f03e4 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Wed, 22 Nov 2023 11:45:15 +0100 Subject: [PATCH 078/116] docs(packet): Improve the docs in packet.Config --- openpgp/packet/config.go | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/openpgp/packet/config.go b/openpgp/packet/config.go index cc6201057..01ce4c341 100644 --- a/openpgp/packet/config.go +++ b/openpgp/packet/config.go @@ -69,11 +69,8 @@ type Config struct { // // Deprecated: SK2Count should be configured in S2KConfig instead. S2KCount int - // An S2K specifier can be stored in the secret keyring to specify - // how to convert the passphrase to a key that unlocks the secret data. - // This config allows to set this key encryption parameters. - // If nil, the default parameters are used. - // See OpenPGP crypto refresh 3.7.2.1. + // RSABits is the number of bits in new RSA keys made with NewEntity. + // If zero, then 2048 bit keys are created. RSABits int // The public key algorithm to use - will always create a signing primary // key and encryption subkey. @@ -134,20 +131,20 @@ type Config struct { KnownNotations map[string]bool // SignatureNotations is a list of Notations to be added to any signatures. SignatureNotations []*Notation - // CheckIntendedRecipients is a flag that indicates if - // a decryption key for an encrypted and signed messages should be checked - // to be present in the signatures intended recipient list. - // In encrypt and sign intended recipients are only included if this flag is true. - // if config is nil or flag is nil, it defaults to true + // CheckIntendedRecipients controls, whether the OpenPGP Intended Recipient Fingerprint feature + // should be enabled for encryption and decryption. + // (See https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-12.html#name-intended-recipient-fingerpr). + // When the flag is set, encryption produces Intended Recipient Fingerprint signature sub-packets and decryption + // checks whether the key it was encrypted to is one of the included fingerprints in the signature. + // If the flag is disabled, no Intended Recipient Fingerprint sub-packets are created or checked. + // The default behavior, when the config or flag is nil, is to enable the feature. CheckIntendedRecipients *bool - // CacheSessionKey is a flag that indicates - // if a session key if any should be cached and returned in - // a pgp message decryption. + // CacheSessionKey controls if decryption should return the session key used for decryption. + // If the flag is set, the session key is cached in the message details struct. CacheSessionKey bool - // CheckPacketSequence is a flag that indicates - // if the pgp message parser should strictly check + // CheckPacketSequence is a flag that controls if the pgp message reader should strictly check // that the packet sequence conforms with the grammar mandated by rfc4880. - // The default value is true. + // The default behavior, when the config or flag is nil, is to check he packet sequence. CheckPacketSequence *bool } From c10c41e5cf69280dc78b82a8e6689fc68f66957a Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Wed, 22 Nov 2023 13:38:25 +0100 Subject: [PATCH 079/116] refactor(packet): Integrate changes from review in PKESK --- openpgp/packet/encrypted_key.go | 56 +++++++++++++++++---------------- openpgp/packet/packet.go | 15 +++++++++ 2 files changed, 44 insertions(+), 27 deletions(-) diff --git a/openpgp/packet/encrypted_key.go b/openpgp/packet/encrypted_key.go index c9534d201..ef93a60df 100644 --- a/openpgp/packet/encrypted_key.go +++ b/openpgp/packet/encrypted_key.go @@ -41,7 +41,7 @@ type EncryptedKey struct { func (e *EncryptedKey) parse(r io.Reader) (err error) { var buf [8]byte - _, err = readFull(r, buf[:1]) + _, err = readFull(r, buf[:versionSize]) if err != nil { return } @@ -51,15 +51,14 @@ func (e *EncryptedKey) parse(r io.Reader) (err error) { } if e.Version == 6 { //Read a one-octet size of the following two fields. - _, err = readFull(r, buf[:1]) - if err != nil { + if _, err = readFull(r, buf[:1]); err != nil { return } // The size may also be zero, and the key version and // fingerprint omitted for an "anonymous recipient" if buf[0] != 0 { // non-anonymous case - _, err = readFull(r, buf[:1]) + _, err = readFull(r, buf[:versionSize]) if err != nil { return } @@ -69,9 +68,9 @@ func (e *EncryptedKey) parse(r io.Reader) (err error) { } var fingerprint []byte if e.KeyVersion == 6 { - fingerprint = make([]byte, 32) + fingerprint = make([]byte, fingerprintSizeV6) } else if e.KeyVersion == 4 { - fingerprint = make([]byte, 20) + fingerprint = make([]byte, fingerprintSize) } _, err = readFull(r, fingerprint) if err != nil { @@ -79,9 +78,9 @@ func (e *EncryptedKey) parse(r io.Reader) (err error) { } e.KeyFingerprint = fingerprint if e.KeyVersion == 6 { - e.KeyId = binary.BigEndian.Uint64(e.KeyFingerprint[:8]) + e.KeyId = binary.BigEndian.Uint64(e.KeyFingerprint[:keyIdSize]) } else if e.KeyVersion == 4 { - e.KeyId = binary.BigEndian.Uint64(e.KeyFingerprint[12:20]) + e.KeyId = binary.BigEndian.Uint64(e.KeyFingerprint[fingerprintSize-keyIdSize : fingerprintSize]) } } } else { @@ -89,7 +88,7 @@ func (e *EncryptedKey) parse(r io.Reader) (err error) { if err != nil { return } - e.KeyId = binary.BigEndian.Uint64(buf[:8]) + e.KeyId = binary.BigEndian.Uint64(buf[:keyIdSize]) } _, err = readFull(r, buf[:1]) @@ -206,6 +205,9 @@ func (e *EncryptedKey) Decrypt(priv *PrivateKey, config *Config) error { } } key, err = decodeChecksumKey(b[keyOffset:]) + if err != nil { + return err + } case PubKeyAlgoX25519, PubKeyAlgoX448: if e.Version < 6 { switch e.CipherFunc { @@ -216,9 +218,8 @@ func (e *EncryptedKey) Decrypt(priv *PrivateKey, config *Config) error { } } key = b[:] - } - if err != nil { - return err + default: + return errors.UnsupportedError("unsupported algorithm for decryption") } e.Key = key return nil @@ -242,13 +243,13 @@ func (e *EncryptedKey) Serialize(w io.Writer) error { return errors.InvalidArgumentError("don't know how to serialize encrypted key type " + strconv.Itoa(int(e.Algo))) } - packetLen := 1 /* version */ + 8 /* key id */ + 1 /* algo */ + encodedLength + packetLen := versionSize /* version */ + keyIdSize /* key id */ + algorithmSize /* algo */ + encodedLength if e.Version == 6 { - packetLen = 1 /* version */ + 1 /* algo */ + encodedLength + 1 /* key version */ + packetLen = versionSize /* version */ + algorithmSize /* algo */ + encodedLength + keyVersionSize /* key version */ if e.KeyVersion == 6 { - packetLen += 32 + packetLen += fingerprintSizeV6 } else if e.KeyVersion == 4 { - packetLen += 20 + packetLen += fingerprintSize } } @@ -313,7 +314,7 @@ func (e *EncryptedKey) Serialize(w io.Writer) error { } } -// SerializeEncryptedKey serializes an encrypted key packet to w that contains +// SerializeEncryptedKeyAEAD serializes an encrypted key packet to w that contains // key, encrypted to pub. // If aeadSupported is set, PKESK v6 is used else v4. // If config is nil, sensible defaults will be used. @@ -328,7 +329,7 @@ func SerializeEncryptedKeyAEAD(w io.Writer, pub *PublicKey, cipherFunc CipherFun // If config is nil, sensible defaults will be used. func SerializeEncryptedKeyAEADwithHiddenOption(w io.Writer, pub *PublicKey, cipherFunc CipherFunction, aeadSupported bool, key []byte, hidden bool, config *Config) error { var buf [36]byte // max possible header size is v6 - lenHeaderWritten := 1 + lenHeaderWritten := versionSize version := 3 if aeadSupported { @@ -357,12 +358,13 @@ func SerializeEncryptedKeyAEADwithHiddenOption(w io.Writer, pub *PublicKey, ciph if version == 6 { if !hidden { // A one-octet size of the following two fields. - buf[1] = byte(1 + len(pub.Fingerprint)) + buf[1] = byte(keyVersionSize + len(pub.Fingerprint)) // A one octet key version number. buf[2] = byte(pub.Version) + lenHeaderWritten += keyVersionSize + 1 // The fingerprint of the public key - copy(buf[3:len(pub.Fingerprint)+3], pub.Fingerprint) - lenHeaderWritten += len(pub.Fingerprint) + 2 + copy(buf[lenHeaderWritten:lenHeaderWritten+len(pub.Fingerprint)], pub.Fingerprint) + lenHeaderWritten += len(pub.Fingerprint) } else { // The size may also be zero, and the key version // and fingerprint omitted for an "anonymous recipient" @@ -371,12 +373,12 @@ func SerializeEncryptedKeyAEADwithHiddenOption(w io.Writer, pub *PublicKey, ciph } } else { if !hidden { - binary.BigEndian.PutUint64(buf[1:9], pub.KeyId) + binary.BigEndian.PutUint64(buf[versionSize:(versionSize+keyIdSize)], pub.KeyId) } - lenHeaderWritten += 8 + lenHeaderWritten += keyIdSize } buf[lenHeaderWritten] = byte(pub.PubKeyAlgo) - lenHeaderWritten += 1 + lenHeaderWritten += algorithmSize var keyBlock []byte switch pub.PubKeyAlgo { @@ -423,9 +425,9 @@ func SerializeEncryptedKey(w io.Writer, pub *PublicKey, cipherFunc CipherFunctio return SerializeEncryptedKeyAEAD(w, pub, cipherFunc, config.AEAD() != nil, key, config) } -// SerializeEncryptedKey serializes an encrypted key packet to w that contains -// key, encrypted to pub. -// PKESKv6 is used if config.AEAD() is not nil. +// SerializeEncryptedKeyWithHiddenOption serializes an encrypted key packet to w that contains +// key, encrypted to pub. PKESKv6 is used if config.AEAD() is not nil. +// The hidden option controls if the packet should be anonymous, i.e., omit key metadata. // If config is nil, sensible defaults will be used. func SerializeEncryptedKeyWithHiddenOption(w io.Writer, pub *PublicKey, cipherFunc CipherFunction, key []byte, hidden bool, config *Config) error { return SerializeEncryptedKeyAEADwithHiddenOption(w, pub, cipherFunc, config.AEAD() != nil, key, hidden, config) diff --git a/openpgp/packet/packet.go b/openpgp/packet/packet.go index 62ddb4464..aea52cabc 100644 --- a/openpgp/packet/packet.go +++ b/openpgp/packet/packet.go @@ -656,3 +656,18 @@ type TrustLevel uint8 // TrustAmount represents a trust amount per RFC4880 5.2.3.13 type TrustAmount uint8 + +const ( + // versionSize is the length in bytes of the version value. + versionSize = 1 + // algorithmSize is the length in bytes of the key algorithm value. + algorithmSize = 1 + // keyVersionSize is the length in bytes of the key version value + keyVersionSize = 1 + // keyIdSize is the length in bytes of the key identifier value. + keyIdSize = 8 + // fingerprintSizeV6 is the length in bytes of the key fingerprint in v6. + fingerprintSizeV6 = 32 + // fingerprintSize is the length in bytes of the key fingerprint. + fingerprintSize = 20 +) From 4863db4ca50f5dfe716f7ad8c53ab3609f3cf8f9 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Wed, 22 Nov 2023 13:42:55 +0100 Subject: [PATCH 080/116] docs(packet): Adapt comment in marker packet implementation --- openpgp/packet/marker.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpgp/packet/marker.go b/openpgp/packet/marker.go index acafc14bc..1ee378ba3 100644 --- a/openpgp/packet/marker.go +++ b/openpgp/packet/marker.go @@ -10,7 +10,7 @@ type Marker struct{} const markerString = "PGP" -// parse just ignores the padding content. +// parse just checks if the packet contains "PGP". func (m *Marker) parse(reader io.Reader) error { var buffer [3]byte if _, err := io.ReadFull(reader, buffer[:]); err != nil { From ddb84e3f58e191895bc0c0e56d7e9d8b898239dd Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Wed, 22 Nov 2023 13:53:30 +0100 Subject: [PATCH 081/116] fix(packet): Fix pre-check error in public key signature verification --- openpgp/packet/public_key.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpgp/packet/public_key.go b/openpgp/packet/public_key.go index a7c202d51..3be8e04fe 100644 --- a/openpgp/packet/public_key.go +++ b/openpgp/packet/public_key.go @@ -748,7 +748,7 @@ func (pk *PublicKey) VerifySignature(signed hash.Hash, sig *Signature) (err erro signed.Write(sig.HashSuffix) hashBytes := signed.Sum(nil) // see discussion https://github.com/ProtonMail/go-crypto/issues/107 - if sig.Version >= 5 && hashBytes[0] != sig.HashTag[0] || hashBytes[1] != sig.HashTag[1] { + if sig.Version >= 5 && (hashBytes[0] != sig.HashTag[0] || hashBytes[1] != sig.HashTag[1]) { return errors.SignatureError("hash tag doesn't match") } From 1dc0808cd73465db36c96854ffcef2fa544e1686 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Wed, 22 Nov 2023 14:50:53 +0100 Subject: [PATCH 082/116] refactor(packet): Implement feedback for private and pubic key packet --- openpgp/packet/packet.go | 2 + openpgp/packet/private_key.go | 2 +- openpgp/packet/public_key.go | 71 +++++++++++++++++------------------ openpgp/packet/signature.go | 10 ++--- 4 files changed, 41 insertions(+), 44 deletions(-) diff --git a/openpgp/packet/packet.go b/openpgp/packet/packet.go index aea52cabc..da12fbce0 100644 --- a/openpgp/packet/packet.go +++ b/openpgp/packet/packet.go @@ -666,6 +666,8 @@ const ( keyVersionSize = 1 // keyIdSize is the length in bytes of the key identifier value. keyIdSize = 8 + // timestampSize is the length in bytes of encoded timestamps. + timestampSize = 4 // fingerprintSizeV6 is the length in bytes of the key fingerprint in v6. fingerprintSizeV6 = 32 // fingerprintSize is the length in bytes of the key fingerprint. diff --git a/openpgp/packet/private_key.go b/openpgp/packet/private_key.go index 3f67b8c0a..cf7819f77 100644 --- a/openpgp/packet/private_key.go +++ b/openpgp/packet/private_key.go @@ -839,7 +839,7 @@ func (pk *PrivateKey) parsePrivateKey(data []byte) (err error) { case PubKeyAlgoEd448: return pk.parseEd448PrivateKey(data) default: - err = errors.InvalidArgumentError("unknown private key type") + err = errors.StructuralError("unknown private key type") return } } diff --git a/openpgp/packet/public_key.go b/openpgp/packet/public_key.go index 3be8e04fe..dcc5e7771 100644 --- a/openpgp/packet/public_key.go +++ b/openpgp/packet/public_key.go @@ -32,9 +32,6 @@ import ( "github.com/ProtonMail/go-crypto/openpgp/x448" ) -type kdfHashFunction byte -type kdfAlgorithm byte - // PublicKey represents an OpenPGP public key. See RFC 4880, section 5.5.2. type PublicKey struct { Version int @@ -570,9 +567,15 @@ func (pk *PublicKey) SerializeForHash(w io.Writer) error { // RFC 4880, section 5.2.4. func (pk *PublicKey) SerializeSignaturePrefix(w io.Writer) { var pLength = pk.algorithmSpecificByteCount() + // version, timestamp, algorithm + pLength += versionSize + timestampSize + algorithmSize if pk.Version >= 5 { - pLength += 10 // version, timestamp (4), algorithm, key octet count (4). + // key octet count (4). + pLength += 4 w.Write([]byte{ + // When a v4 signature is made over a key, the hash data starts with the octet 0x99, followed by a two-octet length + // of the key, and then the body of the key packet. When a v6 signature is made over a key, the hash data starts + // with the salt, then octet 0x9B, followed by a four-octet length of the key, and then the body of the key packet. 0x95 + byte(pk.Version), byte(pLength >> 24), byte(pLength >> 16), @@ -581,12 +584,11 @@ func (pk *PublicKey) SerializeSignaturePrefix(w io.Writer) { }) return } - pLength += 6 w.Write([]byte{0x99, byte(pLength >> 8), byte(pLength)}) } func (pk *PublicKey) Serialize(w io.Writer) (err error) { - length := 6 // 6 byte header + length := uint32(versionSize + timestampSize + algorithmSize) // 6 byte header length += pk.algorithmSpecificByteCount() if pk.Version >= 5 { length += 4 // octet key count @@ -595,38 +597,38 @@ func (pk *PublicKey) Serialize(w io.Writer) (err error) { if pk.IsSubkey { packetType = packetTypePublicSubkey } - err = serializeHeader(w, packetType, length) + err = serializeHeader(w, packetType, int(length)) if err != nil { return } return pk.serializeWithoutHeaders(w) } -func (pk *PublicKey) algorithmSpecificByteCount() int { - length := 0 +func (pk *PublicKey) algorithmSpecificByteCount() uint32 { + length := uint32(0) switch pk.PubKeyAlgo { case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly: - length += int(pk.n.EncodedLength()) - length += int(pk.e.EncodedLength()) + length += uint32(pk.n.EncodedLength()) + length += uint32(pk.e.EncodedLength()) case PubKeyAlgoDSA: - length += int(pk.p.EncodedLength()) - length += int(pk.q.EncodedLength()) - length += int(pk.g.EncodedLength()) - length += int(pk.y.EncodedLength()) + length += uint32(pk.p.EncodedLength()) + length += uint32(pk.q.EncodedLength()) + length += uint32(pk.g.EncodedLength()) + length += uint32(pk.y.EncodedLength()) case PubKeyAlgoElGamal: - length += int(pk.p.EncodedLength()) - length += int(pk.g.EncodedLength()) - length += int(pk.y.EncodedLength()) + length += uint32(pk.p.EncodedLength()) + length += uint32(pk.g.EncodedLength()) + length += uint32(pk.y.EncodedLength()) case PubKeyAlgoECDSA: - length += int(pk.oid.EncodedLength()) - length += int(pk.p.EncodedLength()) + length += uint32(pk.oid.EncodedLength()) + length += uint32(pk.p.EncodedLength()) case PubKeyAlgoECDH: - length += int(pk.oid.EncodedLength()) - length += int(pk.p.EncodedLength()) - length += int(pk.kdf.EncodedLength()) + length += uint32(pk.oid.EncodedLength()) + length += uint32(pk.p.EncodedLength()) + length += uint32(pk.kdf.EncodedLength()) case PubKeyAlgoEdDSA: - length += int(pk.oid.EncodedLength()) - length += int(pk.p.EncodedLength()) + length += uint32(pk.oid.EncodedLength()) + length += uint32(pk.p.EncodedLength()) case PubKeyAlgoX25519: length += x25519.KeySize case PubKeyAlgoX448: @@ -897,8 +899,7 @@ func (pk *PublicKey) VerifySubkeyRevocationSignature(sig *Signature, signed *Pub // userIdSignatureHash returns a Hash of the message that needs to be signed // to assert that pk is a valid key for id. -func userIdSignatureHash(id string, pk *PublicKey, hash hash.Hash) (h hash.Hash, err error) { - h = hash +func userIdSignatureHash(id string, pk *PublicKey, h hash.Hash) (err error) { // RFC 4880, section 5.2.4 pk.SerializeSignaturePrefix(h) @@ -913,13 +914,11 @@ func userIdSignatureHash(id string, pk *PublicKey, hash hash.Hash) (h hash.Hash, h.Write(buf[:]) h.Write([]byte(id)) - return + return nil } // directSignatureHash returns a Hash of the message that needs to be signed -func directKeySignatureHash(pk *PublicKey, hash hash.Hash) (h hash.Hash, err error) { - h = hash - +func directKeySignatureHash(pk *PublicKey, h hash.Hash) (err error) { // RFC 4880, section 5.2.4 pk.SerializeSignaturePrefix(h) pk.serializeWithoutHeaders(h) @@ -930,12 +929,11 @@ func directKeySignatureHash(pk *PublicKey, hash hash.Hash) (h hash.Hash, err err // VerifyUserIdSignature returns nil iff sig is a valid signature, made by this // public key, that id is the identity of pub. func (pk *PublicKey) VerifyUserIdSignature(id string, pub *PublicKey, sig *Signature) (err error) { - preparedHash, err := sig.PrepareVerify() + h, err := sig.PrepareVerify() if err != nil { return err } - h, err := userIdSignatureHash(id, pub, preparedHash) - if err != nil { + if err := userIdSignatureHash(id, pub, h); err != nil { return err } return pk.VerifySignature(h, sig) @@ -944,12 +942,11 @@ func (pk *PublicKey) VerifyUserIdSignature(id string, pub *PublicKey, sig *Signa // VerifyUserIdSignature returns nil iff sig is a valid signature, made by this // public key func (pk *PublicKey) VerifyDirectKeySignature(sig *Signature) (err error) { - preparedHash, err := sig.PrepareVerify() + h, err := sig.PrepareVerify() if err != nil { return err } - h, err := directKeySignatureHash(pk, preparedHash) - if err != nil { + if err := directKeySignatureHash(pk, h); err != nil { return err } return pk.VerifySignature(h, sig) diff --git a/openpgp/packet/signature.go b/openpgp/packet/signature.go index 0e7c43600..81b6c2ab5 100644 --- a/openpgp/packet/signature.go +++ b/openpgp/packet/signature.go @@ -952,11 +952,10 @@ func (sig *Signature) SignUserId(id string, pub *PublicKey, priv *PrivateKey, co if err != nil { return err } - h, err := userIdSignatureHash(id, pub, prepareHash) - if err != nil { + if err := userIdSignatureHash(id, pub, prepareHash); err != nil { return err } - return sig.Sign(h, priv, config) + return sig.Sign(prepareHash, priv, config) } // SignUserId computes a signature from priv @@ -971,11 +970,10 @@ func (sig *Signature) SignDirectKeyBinding(pub *PublicKey, priv *PrivateKey, con if err != nil { return err } - h, err := directKeySignatureHash(pub, prepareHash) - if err != nil { + if err := directKeySignatureHash(pub, prepareHash); err != nil { return err } - return sig.Sign(h, priv, config) + return sig.Sign(prepareHash, priv, config) } // CrossSignKey computes a signature from signingKey on pub hashed using hashKey. On success, From 708c2ad5fb0cae90ddc282fd3967b30a6e9c72d9 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Wed, 22 Nov 2023 16:50:07 +0100 Subject: [PATCH 083/116] refactor(packet): Integrate fedback in signature packet logic --- openpgp/packet/signature.go | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/openpgp/packet/signature.go b/openpgp/packet/signature.go index 81b6c2ab5..147ac6d24 100644 --- a/openpgp/packet/signature.go +++ b/openpgp/packet/signature.go @@ -134,12 +134,14 @@ type SaltedHashSpecifier struct { Salt []byte } +// NewVerifiableSig returns a struct of type VerifiableSignature referencing the input signature. func NewVerifiableSig(signature *Signature) *VerifiableSignature { return &VerifiableSignature{ Packet: signature, } } +// Salt returns the signature salt for v6 signatures. func (sig *Signature) Salt() []byte { if sig == nil { return nil @@ -223,11 +225,11 @@ func (sig *Signature) parse(r io.Reader) (err error) { if err != nil { return } - var unhashedSubpacketsLength int + var unhashedSubpacketsLength uint32 if sig.Version == 6 { - unhashedSubpacketsLength = int(buf[0])<<24 | int(buf[1])<<16 | int(buf[2])<<8 | int(buf[3]) + unhashedSubpacketsLength = uint32(buf[0])<<24 | uint32(buf[1])<<16 | uint32(buf[2])<<8 | uint32(buf[3]) } else { - unhashedSubpacketsLength = int(buf[0])<<8 | int(buf[1]) + unhashedSubpacketsLength = uint32(buf[0])<<8 | uint32(buf[1]) } unhashedSubpackets := make([]byte, unhashedSubpacketsLength) _, err = readFull(r, unhashedSubpackets) @@ -713,7 +715,6 @@ func serializeSubpackets(to []byte, subpackets []outputSubpacket, hashed bool) { to = to[n:] } } - return } // SigExpired returns whether sig is a signature that has expired or is created @@ -753,7 +754,7 @@ func (sig *Signature) buildHashSuffix(hashedSubpackets []byte) (err error) { }) hashedSubpacketsLength := len(hashedSubpackets) if sig.Version == 6 { - // v6 keys store the length in 4 ocets + // v6 keys store the length in 4 octets hashedFields.Write([]byte{ uint8(hashedSubpacketsLength >> 24), uint8(hashedSubpacketsLength >> 16), @@ -958,7 +959,7 @@ func (sig *Signature) SignUserId(id string, pub *PublicKey, priv *PrivateKey, co return sig.Sign(prepareHash, priv, config) } -// SignUserId computes a signature from priv +// SignDirectKeyBinding computes a signature from priv // On success, the signature is stored in sig. // Call Serialize to write it out. // If config is nil, sensible defaults will be used. @@ -1073,8 +1074,8 @@ func (sig *Signature) Serialize(w io.Writer) (err error) { } if sig.Version == 6 { // unhashed length is four-octet instead - // salt len 1 ocet - // len(salt) ocets + // salt len 1 octet + // len(salt) octets length += 3 + len(sig.salt) } err = serializeHeader(w, packetTypeSignature, length) @@ -1091,7 +1092,7 @@ func (sig *Signature) Serialize(w io.Writer) (err error) { func (sig *Signature) serializeBody(w io.Writer) (err error) { var fields []byte if sig.Version == 6 { - // v6 signatures use 4 ocets for length + // v6 signatures use 4 octets for length hashedSubpacketsLen := uint32(uint32(sig.HashSuffix[4])<<24) | uint32(uint32(sig.HashSuffix[5])<<16) | @@ -1382,20 +1383,14 @@ 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 https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh#section-9.5|Crypto Refresh Section 9.5. func SaltLengthForHash(hash crypto.Hash) (int, error) { switch hash { - case crypto.SHA256: + case crypto.SHA256, crypto.SHA224, crypto.SHA3_256: return 16, nil case crypto.SHA384: return 24, nil - case crypto.SHA512: - return 32, nil - case crypto.SHA224: - return 16, nil - case crypto.SHA3_256: - return 16, nil - case crypto.SHA3_512: + case crypto.SHA512, crypto.SHA3_512: return 32, nil default: return 0, errors.UnsupportedError("hash function not supported for V6 signatures") @@ -1404,7 +1399,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 https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh#section-9.5|Crypto Refresh Section 9.5. func SignatureSaltForHash(hash crypto.Hash, randReader io.Reader) ([]byte, error) { saltLength, err := SaltLengthForHash(hash) if err != nil { From fb84e1605f230b6ffd21ebb863e1fa71730ec04c Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Wed, 22 Nov 2023 17:01:37 +0100 Subject: [PATCH 084/116] refactor: Do not ignore errors in canonical hasher --- openpgp/canonical_text.go | 12 +++++++++--- openpgp/v2/canonical_text.go | 12 +++++++++--- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/openpgp/canonical_text.go b/openpgp/canonical_text.go index a94f6150c..5b40e1375 100644 --- a/openpgp/canonical_text.go +++ b/openpgp/canonical_text.go @@ -30,8 +30,12 @@ func writeCanonical(cw io.Writer, buf []byte, s *int) (int, error) { if c == '\r' { *s = 1 } else if c == '\n' { - cw.Write(buf[start:i]) - cw.Write(newline) + if _, err := cw.Write(buf[start:i]); err != nil { + return 0, err + } + if _, err := cw.Write(newline); err != nil { + return 0, err + } start = i + 1 } case 1: @@ -39,7 +43,9 @@ func writeCanonical(cw io.Writer, buf []byte, s *int) (int, error) { } } - cw.Write(buf[start:]) + if _, err := cw.Write(buf[start:]); err != nil { + return 0, err + } return len(buf), nil } diff --git a/openpgp/v2/canonical_text.go b/openpgp/v2/canonical_text.go index 64dd4006a..5c2f36213 100644 --- a/openpgp/v2/canonical_text.go +++ b/openpgp/v2/canonical_text.go @@ -43,8 +43,12 @@ func writeCanonical(cw io.Writer, buf []byte, s *int) (int, error) { if c == '\r' { *s = 1 } else if c == '\n' { - cw.Write(buf[start:i]) - cw.Write(newline) + if _, err := cw.Write(buf[start:i]); err != nil { + return 0, err + } + if _, err := cw.Write(newline); err != nil { + return 0, err + } start = i + 1 } case 1: @@ -52,7 +56,9 @@ func writeCanonical(cw io.Writer, buf []byte, s *int) (int, error) { } } - cw.Write(buf[start:]) + if _, err := cw.Write(buf[start:]); err != nil { + return 0, err + } return len(buf), nil } From 78f1529721ab40073962b9b826edb0398c4686fb Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Wed, 22 Nov 2023 17:21:36 +0100 Subject: [PATCH 085/116] refactor(v2): Implement feedback for key operations --- openpgp/keys_v5_test.go | 9 +++------ openpgp/v2/key_generation.go | 6 ++++-- openpgp/v2/keys.go | 23 +++++++++++------------ openpgp/v2/keys_test.go | 2 +- openpgp/v2/keys_v5_test.go | 10 +++------- 5 files changed, 22 insertions(+), 28 deletions(-) diff --git a/openpgp/keys_v5_test.go b/openpgp/keys_v5_test.go index 6ed3b975e..d285fed31 100644 --- a/openpgp/keys_v5_test.go +++ b/openpgp/keys_v5_test.go @@ -2,11 +2,8 @@ package openpgp import ( "bytes" - "io/ioutil" "strings" "testing" - - "github.com/ProtonMail/go-crypto/openpgp/armor" ) var foreignKeys = []string{ @@ -23,6 +20,8 @@ func TestReadPrivateForeignV5Key(t *testing.T) { } } +// Deprecated +/* // TODO: Replace message with a correctly generated one. func testV5ForeignSignedMessage(t *testing.T) { kring, err := ReadArmoredKeyRing(strings.NewReader(v5PrivKey)) @@ -58,8 +57,6 @@ func testV5ForeignSignedMessage(t *testing.T) { } } -// Depricated -/* func TestReadPrivateEncryptedV5Key(t *testing.T) { c := &packet.Config{V5Keys: true} e, err := NewEntity("V5 Key Owner", "V5 Key", "v5@pm.me", c) @@ -116,7 +113,7 @@ func TestReadPrivateSerializeForeignV5Key(t *testing.T) { } } -// Depricated +// Deprecated /* func TestNewEntitySerializeV5Key(t *testing.T) { c := &packet.Config{V5Keys: true} diff --git a/openpgp/v2/key_generation.go b/openpgp/v2/key_generation.go index 59ec46614..8f10c6cd6 100644 --- a/openpgp/v2/key_generation.go +++ b/openpgp/v2/key_generation.go @@ -118,6 +118,7 @@ func newEntity(uid *userIdData, config *packet.Config) (*Entity, error) { return e, nil } +// AddUserId adds a user-id packet to the given entity. func (t *Entity) AddUserId(name, comment, email string, config *packet.Config) error { var keyProperties *keyProperties if !config.V6() { @@ -126,6 +127,7 @@ func (t *Entity) AddUserId(name, comment, email string, config *packet.Config) e return t.addUserId(userIdData{name, comment, email}, config, keyProperties) } +// AddDirectKeySignature adds a fresh direct key signature with the selected key-properties. func (t *Entity) AddDirectKeySignature(selectedKeyProperties *keyProperties, config *packet.Config) error { selfSignature := createSignaturePacket(&t.PrivateKey.PublicKey, packet.SigTypeDirectSignature, config) err := writeKeyProperties(selfSignature, selectedKeyProperties) @@ -330,7 +332,7 @@ func (e *Entity) addEncryptionSubkey(config *packet.Config, creationTime time.Ti return nil } -// Generates a signing key +// newSigner generates a signing key. func newSigner(config *packet.Config) (signer interface{}, err error) { switch config.PublicKeyAlgorithm() { case packet.PubKeyAlgoRSA: @@ -388,7 +390,7 @@ func newSigner(config *packet.Config) (signer interface{}, err error) { } } -// Generates an encryption/decryption key +// newDecrypter generates an encryption/decryption key. func newDecrypter(config *packet.Config) (decrypter interface{}, err error) { switch config.PublicKeyAlgorithm() { case packet.PubKeyAlgoRSA: diff --git a/openpgp/v2/keys.go b/openpgp/v2/keys.go index f83b8a650..c098db871 100644 --- a/openpgp/v2/keys.go +++ b/openpgp/v2/keys.go @@ -55,7 +55,8 @@ type KeyRing interface { // PrimaryIdentity returns a valid non-revoked Identity while preferring // identities marked as primary, or the latest-created identity, in that order. -func (e *Entity) PrimaryIdentity(date time.Time) (*packet.Signature, *Identity, error) { +// Returns an nil for both return values if there is no valid primary identity. +func (e *Entity) PrimaryIdentity(date time.Time) (*packet.Signature, *Identity) { var primaryIdentityCandidates []*Identity var primaryIdentityCandidatesSelfSigs []*packet.Signature for _, identity := range e.Identities { @@ -66,7 +67,7 @@ func (e *Entity) PrimaryIdentity(date time.Time) (*packet.Signature, *Identity, } } if len(primaryIdentityCandidates) == 0 { - return nil, nil, errors.StructuralError("no primary identity found") + return nil, nil } primaryIdentity := -1 for idx := range primaryIdentityCandidates { @@ -76,7 +77,7 @@ func (e *Entity) PrimaryIdentity(date time.Time) (*packet.Signature, *Identity, primaryIdentity = idx } } - return primaryIdentityCandidatesSelfSigs[primaryIdentity], primaryIdentityCandidates[primaryIdentity], nil + return primaryIdentityCandidatesSelfSigs[primaryIdentity], primaryIdentityCandidates[primaryIdentity] } func shouldPreferIdentity(existingId, potentialNewId *packet.Signature) bool { @@ -665,6 +666,7 @@ func (e *Entity) SignIdentity(identity string, signer *Entity, config *packet.Co return ident.SignIdentity(signer, config) } +// LatestValidDirectSignature returns the latest valid direct key-signature of the entity. func (e *Entity) LatestValidDirectSignature(date time.Time) (selectedSig *packet.Signature, err error) { for sigIdx := len(e.DirectSignatures) - 1; sigIdx >= 0; sigIdx-- { sig := e.DirectSignatures[sigIdx] @@ -686,8 +688,8 @@ func (e *Entity) LatestValidDirectSignature(date time.Time) (selectedSig *packet return } -// primarySelfSignature searches the entitity for the self-signature that stores key prefrences. -// For V4 keys, returns the self-signature of the primary indentity, and the identity. +// PrimarySelfSignature searches the entity for the self-signature that stores key preferences. +// For V4 keys, returns the self-signature of the primary identity, and the identity. // For V6 keys, returns the latest valid direct-key self-signature, and no identity (nil). // This self-signature is to be used to check the key expiration, // algorithm preferences, and so on. @@ -696,9 +698,9 @@ func (e *Entity) PrimarySelfSignature(date time.Time) (primarySig *packet.Signat primarySig, err = e.LatestValidDirectSignature(date) return } - primarySig, _, err = e.PrimaryIdentity(date) - if err != nil { - return + primarySig, _ = e.PrimaryIdentity(date) + if primarySig == nil { + return nil, errors.StructuralError("no primary identity found") } return } @@ -741,7 +743,6 @@ func (k *Key) IsPrimary() bool { return k.PrimarySelfSignature == k.SelfSignature } -// checkKeyRequirements func checkKeyRequirements(usedKey *packet.PublicKey, config *packet.Config) error { algo := usedKey.PubKeyAlgo if config.RejectPublicKeyAlgorithm(algo) { @@ -758,9 +759,7 @@ func checkKeyRequirements(usedKey *packet.PublicKey, config *packet.Config) erro if err != nil || config.RejectCurve(curve) { return errors.WeakAlgorithmError("elliptic curve " + curve) } - if usedKey.Version == 6 && - (curve == packet.Curve25519 || - curve == packet.Curve448) { + if usedKey.Version == 6 && (curve == packet.Curve25519 || curve == packet.Curve448) { // Implementations MUST NOT accept or generate v6 key material using the deprecated OIDs. return errors.StructuralError("v6 key uses legacy elliptic curve " + curve) } diff --git a/openpgp/v2/keys_test.go b/openpgp/v2/keys_test.go index 833d19d09..eb665f990 100644 --- a/openpgp/v2/keys_test.go +++ b/openpgp/v2/keys_test.go @@ -1967,7 +1967,7 @@ zaXZE2aAMQ== t.Fatal(err) } var config *packet.Config - sig, _, err := key[0].PrimaryIdentity(config.Now()) + sig, _ := key[0].PrimaryIdentity(config.Now()) if err != nil { t.Fatal(err) } diff --git a/openpgp/v2/keys_v5_test.go b/openpgp/v2/keys_v5_test.go index 16fa2be24..cf72a387f 100644 --- a/openpgp/v2/keys_v5_test.go +++ b/openpgp/v2/keys_v5_test.go @@ -2,11 +2,8 @@ package v2 import ( "bytes" - "io/ioutil" "strings" "testing" - - "github.com/ProtonMail/go-crypto/openpgp/armor" ) var foreignKeys = []string{ @@ -23,7 +20,8 @@ func TestReadPrivateForeignV5Key(t *testing.T) { } } -// TODO: Replace message with a correctly generated one. +// Deprecated +/* func testV5ForeignSignedMessage(t *testing.T) { kring, err := ReadArmoredKeyRing(strings.NewReader(v5PrivKey)) if err != nil { @@ -61,8 +59,6 @@ func testV5ForeignSignedMessage(t *testing.T) { } } -// Depricated -/* func TestReadPrivateEncryptedV5Key(t *testing.T) { c := &packet.Config{V5Keys: true} e, err := NewEntity("V5 Key Owner", "V5 Key", "v5@pm.me", c) @@ -119,7 +115,7 @@ func TestReadPrivateSerializeForeignV5Key(t *testing.T) { } } -// Depricated +// Deprecated /* func TestNewEntitySerializeV5Key(t *testing.T) { c := &packet.Config{V5Keys: true} From f0a31d287aac1036a993772d8d1f8c948ad56b04 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Wed, 22 Nov 2023 17:32:55 +0100 Subject: [PATCH 086/116] docs(v2): Improve documentation in pgp message read --- openpgp/v2/read.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/openpgp/v2/read.go b/openpgp/v2/read.go index 4d04e4bb8..1a18abc5f 100644 --- a/openpgp/v2/read.go +++ b/openpgp/v2/read.go @@ -737,7 +737,12 @@ func verifyDetachedSignatureReader(keyring KeyRing, signed, signature io.Reader, return md, nil } -// checkSignatureDetails returns an error if: +// checkSignatureDetails verifies the metadata of the signature. +// Checks the following: +// - Hash function should not be invalid. +// - Verification key must be older than the signature creation time. +// - Check signature notations. +// - Signature is not expired. func checkSignatureDetails(verifiedKey *Key, signature *packet.Signature, config *packet.Config) error { var collectedErrors []error now := config.Now() From bf3712646fa1e1fae687a71733abf55227399ced Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Wed, 22 Nov 2023 17:44:18 +0100 Subject: [PATCH 087/116] ci: Update to refactored gopenpgp v3 --- .github/test-suite/build_gosop.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/test-suite/build_gosop.sh b/.github/test-suite/build_gosop.sh index 0a031ac0e..fc627d71e 100755 --- a/.github/test-suite/build_gosop.sh +++ b/.github/test-suite/build_gosop.sh @@ -1,5 +1,5 @@ cd gosop echo "replace github.com/ProtonMail/go-crypto => ../go-crypto" >> go.mod go get github.com/ProtonMail/go-crypto -go get github.com/ProtonMail/gopenpgp/v3/crypto@4ec274320a814a100c71e088f66db1094497d7bb +go get github.com/ProtonMail/gopenpgp/v3/crypto@8acccb3915b46d8765d536ff9669bb61ec567f77 go build . From cc1afdc1d4fc3bc3ba1be4f8d06ff450f8d44224 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Thu, 23 Nov 2023 09:41:49 +0100 Subject: [PATCH 088/116] docs(v2): Add missing docs in subkey implementation --- openpgp/v2/subkeys.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpgp/v2/subkeys.go b/openpgp/v2/subkeys.go index eae757292..7ef007f21 100644 --- a/openpgp/v2/subkeys.go +++ b/openpgp/v2/subkeys.go @@ -54,6 +54,8 @@ func readSubkey(primary *Entity, packets *packet.Reader, pub *packet.PublicKey, return nil } +// Serialize serializes the subkey and writes it into writer. +// The includeSecrets flag controls if the secrets should be included in the encoding or not. func (s *Subkey) Serialize(w io.Writer, includeSecrets bool) error { if includeSecrets { if err := s.PrivateKey.Serialize(w); err != nil { From be8d42d6408deb29cdf255bcc71fa0bcbfdb00d5 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Thu, 23 Nov 2023 09:59:19 +0100 Subject: [PATCH 089/116] fix(v2): SHA256 should not be accepted for 384-bit curves --- openpgp/v2/write.go | 1 - 1 file changed, 1 deletion(-) diff --git a/openpgp/v2/write.go b/openpgp/v2/write.go index 4da8bf657..5b7c8265b 100644 --- a/openpgp/v2/write.go +++ b/openpgp/v2/write.go @@ -1015,7 +1015,6 @@ func acceptableHashesToWrite(singingKey *packet.PublicKey) []uint8 { } else if curve == packet.CurveBrainpoolP384 || curve == packet.CurveNistP384 { return []uint8{ - hashToHashId(crypto.SHA256), hashToHashId(crypto.SHA384), hashToHashId(crypto.SHA512), hashToHashId(crypto.SHA3_512), From 3db51732b5b372aebda2697e4cdccbbbf3bb2fec Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Thu, 23 Nov 2023 10:00:04 +0100 Subject: [PATCH 090/116] refactor(v2): Integrate suggested changes in the write api --- openpgp/v2/write.go | 30 ++++++++++++++++++------------ openpgp/v2/write_test.go | 1 - 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/openpgp/v2/write.go b/openpgp/v2/write.go index 5b7c8265b..e577269d1 100644 --- a/openpgp/v2/write.go +++ b/openpgp/v2/write.go @@ -194,14 +194,14 @@ func detachSignWithWriter(w io.Writer, signers []*Entity, sigType packet.Signatu } // FileHints contains metadata about encrypted files. This metadata is, itself, -// encrypted. +// encrypted. OpenPGP signatures do not include the FileHints in a signature hash and +// thus those fields are not protected against tampering in a signed document. +// The crypto[refresh does not recommend to set the data in file hints. +// See https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-12.html#section-5.9. type FileHints struct { - // IsUTF8 can be set to hint that the contents are utf8 encoded data + // IsUTF8 can be set to hint that the contents are utf8 encoded data. IsUTF8 bool - // FileName hints at the name of the file that should be written. It's - // truncated to 255 bytes if longer. It may be empty to suggest that the - // file should not be written to disk. It may be equal to "_CONSOLE" to - // suggest the data should not be written to disk. + // FileName hints at the name of the file that should be written. FileName string // ModTime contains the modification time of the file, or the zero time if not applicable. ModTime time.Time @@ -377,9 +377,11 @@ func EncryptWithParams(ciphertext io.Writer, to, toHidden []*Entity, params *Enc } // Encrypt encrypts a message to a number of recipients and, optionally, signs -// it. hints contains optional information, that is also encrypted, that aids -// the recipients in processing the message. The resulting WriteCloser must -// be closed after the contents of the file have been written. +// it. Hints contains optional information, that is also encrypted, that aids +// the recipients in processing the message. The crypto-refresh recommends +// to not set file hints since the data is not included in the signature hash. +// See https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-12.html#section-5.9. +// The resulting WriteCloser must be closed after the contents of the file have been written. // The to argument contains recipients that are explicitly mentioned in signatures and encrypted keys, // whereas the toHidden argument contains recipients that will be hidden and not mentioned. // If config is nil, sensible defaults will be used. @@ -708,6 +710,8 @@ func encryptDataAndSign( type SignParams struct { // Hints contains file metadata for the literal data packet. + // The crypto-refresh recommends to not set file hints since the data is not included in the signature hash. + // See https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-12.html#section-5.9. // If nil, default is used. Hints *FileHints // TextSig indicates if signatures of type SigTypeText should be produced @@ -780,8 +784,10 @@ func SignWithParams(output io.Writer, signers []*Entity, params *SignParams) (in } // Sign signs a message. The resulting WriteCloser must be closed after the -// contents of the file have been written. hints contains optional information +// contents of the file have been written. Hints contains optional information // that aids the recipients in processing the message. +// The crypto-refresh recommends to not set file hints since the data is not included in the signature hash. +// See https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-12.html#section-5.9. // If config is nil, sensible defaults will be used. func Sign(output io.Writer, signers []*Entity, hints *FileHints, config *packet.Config) (input io.WriteCloser, err error) { return SignWithParams(output, signers, &SignParams{ @@ -966,10 +972,10 @@ func selectHash(candidateHashes []byte, configuredHash crypto.Hash, signer *pack if h, ok := algorithm.HashIdToHash(acceptableHashes[0]); ok { hash = h } else { - return 0, errors.InvalidArgumentError("no candidate hash functions are compiled in.") + return 0, errors.UnsupportedError("no candidate hash functions are compiled in.") } } else { - return 0, errors.InvalidArgumentError("no candidate hash functions are compiled in.") + return 0, errors.UnsupportedError("no candidate hash functions are compiled in.") } } return diff --git a/openpgp/v2/write_test.go b/openpgp/v2/write_test.go index 77e5bbe20..43e169806 100644 --- a/openpgp/v2/write_test.go +++ b/openpgp/v2/write_test.go @@ -178,7 +178,6 @@ func TestSignDetachedWithCriticalNotation(t *testing.T) { } func TestNewEntity(t *testing.T) { - // Check bit-length with no config. e, err := NewEntity("Test User", "test", "test@example.com", nil) if err != nil { From 1ab7fbc54e3e3a1d7504945b1f6ef005fb5472da Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Thu, 23 Nov 2023 10:07:25 +0100 Subject: [PATCH 091/116] refactor: Change write test errors --- openpgp/v2/write_test.go | 2 +- openpgp/write_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpgp/v2/write_test.go b/openpgp/v2/write_test.go index 43e169806..98d2a792f 100644 --- a/openpgp/v2/write_test.go +++ b/openpgp/v2/write_test.go @@ -987,7 +987,7 @@ FindKey: } _, ok := decPackets.(*packet.Compressed) if !ok { - return errors.InvalidArgumentError("No compressed packets found") + return errors.StructuralError("No compressed packets found") } return nil } diff --git a/openpgp/write_test.go b/openpgp/write_test.go index ca383930c..d8dd439fb 100644 --- a/openpgp/write_test.go +++ b/openpgp/write_test.go @@ -759,7 +759,7 @@ FindKey: decPackets, err := packet.Read(decrypted) _, ok := decPackets.(*packet.Compressed) if !ok { - return errors.InvalidArgumentError("No compressed packets found") + return errors.StructuralError("No compressed packets found") } return nil } From 1f9ab129a56b84efab282d651baf77c2dc4e72bb Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Thu, 23 Nov 2023 10:13:15 +0100 Subject: [PATCH 092/116] refactor: StructuralError instead of InvalidArgumentError in write api --- openpgp/v2/write.go | 10 +++++----- openpgp/write.go | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/openpgp/v2/write.go b/openpgp/v2/write.go index e577269d1..90c6f8730 100644 --- a/openpgp/v2/write.go +++ b/openpgp/v2/write.go @@ -155,7 +155,7 @@ func detachSignWithWriter(w io.Writer, signers []*Entity, sigType packet.Signatu defaultHashes := candidateHashes[0:1] primarySelfSignature, _ := signer.PrimarySelfSignature(config.Now()) if primarySelfSignature == nil { - return nil, errors.InvalidArgumentError("signed entity has no valid self-signature") + return nil, errors.StructuralError("signed entity has no valid self-signature") } preferredHashes := primarySelfSignature.PreferredHash if len(preferredHashes) == 0 { @@ -303,7 +303,7 @@ func symmetricallyEncrypt(passphrase []byte, dataWriter io.Writer, params *Encry defaultHashes := candidateHashes[0:1] primarySelfSignature, _ := signer.PrimarySelfSignature(params.Config.Now()) if primarySelfSignature == nil { - return nil, errors.InvalidArgumentError("signed entity has no self-signature") + return nil, errors.StructuralError("signed entity has no self-signature") } preferredHashes := primarySelfSignature.PreferredHash if len(preferredHashes) == 0 { @@ -610,7 +610,7 @@ func encrypt( primarySelfSignature, _ := recipient.PrimarySelfSignature(timeForEncryptionKey) if primarySelfSignature == nil { - return nil, errors.InvalidArgumentError("entity without a self-signature") + return nil, errors.StructuralError("entity without a self-signature") } if !primarySelfSignature.SEIPDv2 { @@ -754,7 +754,7 @@ func SignWithParams(output io.Writer, signers []*Entity, params *SignParams) (in defaultHashes := candidateHashes[0:1] primarySelfSignature, _ := signer.PrimarySelfSignature(params.Config.Now()) if primarySelfSignature == nil { - return nil, errors.InvalidArgumentError("signed entity has no self-signature") + return nil, errors.StructuralError("signed entity has no self-signature") } preferredHashes := primarySelfSignature.PreferredHash if len(preferredHashes) == 0 { @@ -762,7 +762,7 @@ func SignWithParams(output io.Writer, signers []*Entity, params *SignParams) (in } candidateHashes = intersectPreferences(candidateHashes, preferredHashes) if len(candidateHashes) == 0 { - return nil, errors.InvalidArgumentError("cannot sign because signing key shares no common algorithms with candidate hashes") + return nil, errors.StructuralError("cannot sign because signing key shares no common algorithms with candidate hashes") } candidateHashesPerSignature = append(candidateHashesPerSignature, candidateHashes) candidateCompression = intersectPreferences(candidateCompression, primarySelfSignature.PreferredCompression) diff --git a/openpgp/write.go b/openpgp/write.go index 846ae5c6a..d0b3887fb 100644 --- a/openpgp/write.go +++ b/openpgp/write.go @@ -489,7 +489,7 @@ func Sign(output io.Writer, signed *Entity, hints *FileHints, config *packet.Con defaultHashes := candidateHashes[0:1] primarySelfSignature, _ := signed.PrimarySelfSignature() if primarySelfSignature == nil { - return nil, errors.InvalidArgumentError("signed entity has no self-signature") + return nil, errors.StructuralError("signed entity has no self-signature") } preferredHashes := primarySelfSignature.PreferredHash if len(preferredHashes) == 0 { @@ -497,7 +497,7 @@ func Sign(output io.Writer, signed *Entity, hints *FileHints, config *packet.Con } candidateHashes = intersectPreferences(candidateHashes, preferredHashes) if len(candidateHashes) == 0 { - return nil, errors.InvalidArgumentError("cannot sign because signing key shares no common algorithms with candidate hashes") + return nil, errors.StructuralError("cannot sign because signing key shares no common algorithms with candidate hashes") } return writeAndSign(noOpCloser{output}, candidateHashes, signed, hints, packet.SigTypeBinary, config) From 59054bcfe7b22dd6249705e188335ccfea5d6ac9 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Thu, 23 Nov 2023 10:21:03 +0100 Subject: [PATCH 093/116] refactor: Resolve warnings and suggestions in read/write tests --- openpgp/v2/read_write_test_data.go | 12 ------------ openpgp/v2/write_test.go | 2 +- openpgp/write_test.go | 15 ++++++++++++--- 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/openpgp/v2/read_write_test_data.go b/openpgp/v2/read_write_test_data.go index ba4e4b837..2f0efc228 100644 --- a/openpgp/v2/read_write_test_data.go +++ b/openpgp/v2/read_write_test_data.go @@ -275,18 +275,6 @@ TcIYl5/Uyoi+FOvPLcNw4hOv2nwUzSSVAw== =IiS2 -----END PGP PRIVATE KEY BLOCK-----` -// Generated with the above private key -const v5PrivKeyMsg = `-----BEGIN PGP MESSAGE----- -Version: OpenPGP.js v4.10.7 -Comment: https://openpgpjs.org - -xA0DAQoWGTR7yYckZAIByxF1B21zZy50eHRfbIGSdGVzdMJ3BQEWCgAGBQJf -bIGSACMiIQUZNHvJhyRkAl+Z3z7C4AAO2YhIkuH3s+pMlACRWVabVDQvAP9G -y29VPonFXqi2zKkpZrvyvZxg+n5e8Nt9wNbuxeCd3QD/TtO2s+JvjrE4Siwv -UQdl5MlBka1QSNbMq2Bz7XwNPg4= -=6lbM ------END PGP MESSAGE-----` - // See OpenPGP crypto refresh Section A.3. const v6PrivKey = `-----BEGIN PGP PRIVATE KEY BLOCK----- diff --git a/openpgp/v2/write_test.go b/openpgp/v2/write_test.go index 98d2a792f..eb701a7eb 100644 --- a/openpgp/v2/write_test.go +++ b/openpgp/v2/write_test.go @@ -721,7 +721,7 @@ func TestEncryption(t *testing.T) { signers = []*Entity{signed} } w, err := Encrypt(buf, kring[:1], nil, signers, nil /* no hints */, &config) - if err != nil && config.AEAD() != nil && !test.okV6 { + if (err != nil) == (test.okV6 && config.AEAD() != nil) { // ElGamal is not allowed with v6 continue } diff --git a/openpgp/write_test.go b/openpgp/write_test.go index d8dd439fb..b87166015 100644 --- a/openpgp/write_test.go +++ b/openpgp/write_test.go @@ -281,7 +281,7 @@ func TestEncryptWithCompression(t *testing.T) { buf := new(bytes.Buffer) var config = &packet.Config{ DefaultCompressionAlgo: packet.CompressionZIP, - CompressionConfig: &packet.CompressionConfig{-1}, + CompressionConfig: &packet.CompressionConfig{Level: -1}, } w, err := Encrypt(buf, kring[:1], nil, nil /* no hints */, config) if err != nil { @@ -391,6 +391,9 @@ func TestSymmetricEncryptionV5RandomizeSlow(t *testing.T) { packets := packet.NewReader(copiedBuf) // First a SymmetricKeyEncrypted packet p, err := packets.Next() + if err != nil { + t.Fatal(err) + } switch tp := p.(type) { case *packet.SymmetricKeyEncrypted: default: @@ -398,6 +401,9 @@ func TestSymmetricEncryptionV5RandomizeSlow(t *testing.T) { } // Then an SymmetricallyEncrypted packet version 2 p, err = packets.Next() + if err != nil { + t.Fatal(err) + } switch tp := p.(type) { case *packet.SymmetricallyEncrypted: if tp.Version != 2 { @@ -488,7 +494,7 @@ func TestEncryption(t *testing.T) { } compAlgo := compAlgos[mathrand.Intn(len(compAlgos))] level := mathrand.Intn(11) - 1 - compConf := &packet.CompressionConfig{level} + compConf := &packet.CompressionConfig{Level: level} var config = &packet.Config{ DefaultCompressionAlgo: compAlgo, CompressionConfig: compConf, @@ -503,7 +509,7 @@ func TestEncryption(t *testing.T) { } w, err := Encrypt(buf, kring[:1], signed, nil /* no hints */, config) - if err != nil && config.AEAD() != nil && !test.okV6 { + if (err != nil) == (test.okV6 && config.AEAD() != nil) { // ElGamal is not allowed with v6 continue } @@ -757,6 +763,9 @@ FindKey: } decPackets, err := packet.Read(decrypted) + if err != nil { + return err + } _, ok := decPackets.(*packet.Compressed) if !ok { return errors.StructuralError("No compressed packets found") From dc7272a5ebae0567e24c41a766291d1f7a05c331 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Thu, 23 Nov 2023 10:30:40 +0100 Subject: [PATCH 094/116] docs: Pass on keys and keys test for grammar issues --- openpgp/keys.go | 21 +++++++++++---------- openpgp/keys_test.go | 1 - openpgp/v2/keys.go | 4 ++-- openpgp/v2/keys_test.go | 1 - 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/openpgp/keys.go b/openpgp/keys.go index b849337a7..e73eead3b 100644 --- a/openpgp/keys.go +++ b/openpgp/keys.go @@ -6,6 +6,7 @@ package openpgp import ( goerrors "errors" + "fmt" "io" "time" @@ -29,7 +30,7 @@ type Entity struct { Identities map[string]*Identity // indexed by Identity.Name Revocations []*packet.Signature Subkeys []Subkey - SelfSignature *packet.Signature // Direct-key self signature of the PrimaryKey (containts primary key properties in v6) + SelfSignature *packet.Signature // Direct-key self signature of the PrimaryKey (contains primary key properties in v6) Signatures []*packet.Signature // all (potentially unverified) self-signatures, revocations, and third-party signatures } @@ -273,7 +274,7 @@ func (e *Entity) EncryptPrivateKeys(passphrase []byte, config *packet.Config) er return packet.EncryptPrivateKeys(keysToEncrypt, passphrase, config) } -// DecryptPrivateKeys decrypts all encrypted keys in the entitiy with the given passphrase. +// DecryptPrivateKeys decrypts all encrypted keys in the entity with the given passphrase. // Avoids recomputation of similar s2k key derivations. Public keys and dummy keys are ignored, // and don't cause an error to be returned. func (e *Entity) DecryptPrivateKeys(passphrase []byte) error { @@ -522,20 +523,20 @@ EachPacket: return nil, err } default: - // we ignore unknown packets + // we ignore unknown packets. } } if len(e.Identities) == 0 && e.PrimaryKey.Version < 6 { - return nil, errors.StructuralError("v4 entity without any identities") + return nil, errors.StructuralError(fmt.Sprintf("v%d entity without any identities", e.PrimaryKey.Version)) } - // An implementation MUST ensure that a valid direct-key signature is present before using a v6 key + // An implementation MUST ensure that a valid direct-key signature is present before using a v6 key. if e.PrimaryKey.Version == 6 { if len(directSignatures) == 0 { return nil, errors.StructuralError("v6 entity without a valid direct-key signature") } - // Select main direct key signature + // Select main direct key signature. var mainDirectKeySelfSignature *packet.Signature for _, directSignature := range directSignatures { if directSignature.SigType == packet.SigTypeDirectSignature && @@ -548,10 +549,10 @@ EachPacket: if mainDirectKeySelfSignature == nil { return nil, errors.StructuralError("no valid direct-key self-signature for v6 primary key found") } - // Check that the main self-signature is valid + // Check that the main self-signature is valid. err = e.PrimaryKey.VerifyDirectKeySignature(mainDirectKeySelfSignature) if err != nil { - return nil, errors.StructuralError("invalid direct-key self-signature for v6 xprimary key") + return nil, errors.StructuralError("invalid direct-key self-signature for v6 primary key") } e.SelfSignature = mainDirectKeySelfSignature e.Signatures = directSignatures @@ -884,8 +885,8 @@ func (e *Entity) primaryDirectSignature() *packet.Signature { return e.SelfSignature } -// primarySelfSignature searches the entitity for the self-signature that stores key prefrences. -// For V4 keys, returns the self-signature of the primary indentity, and the identity. +// PrimarySelfSignature searches the entity for the self-signature that stores key preferences. +// For V4 keys, returns the self-signature of the primary identity, and the identity. // For V6 keys, returns the latest valid direct-key self-signature, and no identity (nil). // This self-signature is to be used to check the key expiration, // algorithm preferences, and so on. diff --git a/openpgp/keys_test.go b/openpgp/keys_test.go index b05eb4707..a3ce59406 100644 --- a/openpgp/keys_test.go +++ b/openpgp/keys_test.go @@ -1514,7 +1514,6 @@ func TestEncryptAndDecryptPrivateKeys(t *testing.T) { } }) } - } func TestKeyValidateOnDecrypt(t *testing.T) { diff --git a/openpgp/v2/keys.go b/openpgp/v2/keys.go index c098db871..162708353 100644 --- a/openpgp/v2/keys.go +++ b/openpgp/v2/keys.go @@ -29,7 +29,7 @@ type Entity struct { PrivateKey *packet.PrivateKey Identities map[string]*Identity // indexed by Identity.Name Revocations []*packet.VerifiableSignature - DirectSignatures []*packet.VerifiableSignature // Direct-key self signature of the PrimaryKey (containts primary key properties in v6)} + DirectSignatures []*packet.VerifiableSignature // Direct-key self signature of the PrimaryKey (contains primary key properties in v6)} Subkeys []Subkey } @@ -305,7 +305,7 @@ func (e *Entity) EncryptPrivateKeys(passphrase []byte, config *packet.Config) er return packet.EncryptPrivateKeys(keysToEncrypt, passphrase, config) } -// DecryptPrivateKeys decrypts all encrypted keys in the entitiy with the given passphrase. +// DecryptPrivateKeys decrypts all encrypted keys in the entity with the given passphrase. // Avoids recomputation of similar s2k key derivations. Public keys and dummy keys are ignored, // and don't cause an error to be returned. func (e *Entity) DecryptPrivateKeys(passphrase []byte) error { diff --git a/openpgp/v2/keys_test.go b/openpgp/v2/keys_test.go index eb665f990..fac1a30e2 100644 --- a/openpgp/v2/keys_test.go +++ b/openpgp/v2/keys_test.go @@ -1572,7 +1572,6 @@ func TestEncryptAndDecryptPrivateKeys(t *testing.T) { } }) } - } func TestKeyValidateOnDecrypt(t *testing.T) { From d16b56e176c0bb357beecbee46af4bd5d92332f0 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Thu, 23 Nov 2023 11:00:50 +0100 Subject: [PATCH 095/116] fix(packet): x448 and x25519 key parsing ignored error --- openpgp/packet/private_key.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpgp/packet/private_key.go b/openpgp/packet/private_key.go index cf7819f77..6f6da73d4 100644 --- a/openpgp/packet/private_key.go +++ b/openpgp/packet/private_key.go @@ -971,10 +971,10 @@ func (pk *PrivateKey) parseX25519PrivateKey(data []byte) (err error) { if len(data) != x25519.KeySize { err = errors.StructuralError("wrong X25519 key size") + return err } copy(privateKey.Secret, data) - err = x25519.Validate(privateKey) - if err != nil { + if err = x25519.Validate(privateKey); err != nil { return err } pk.PrivateKey = privateKey @@ -990,10 +990,10 @@ func (pk *PrivateKey) parseX448PrivateKey(data []byte) (err error) { if len(data) != x448.KeySize { err = errors.StructuralError("wrong x448 key size") + return err } copy(privateKey.Secret, data) - err = x448.Validate(privateKey) - if err != nil { + if err = x448.Validate(privateKey); err != nil { return err } pk.PrivateKey = privateKey From efe34510590a8724c4edf9e8667f7898da963315 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Thu, 23 Nov 2023 11:12:07 +0100 Subject: [PATCH 096/116] fix(packet): Signature salt should not be created twice --- openpgp/packet/signature.go | 1 - 1 file changed, 1 deletion(-) diff --git a/openpgp/packet/signature.go b/openpgp/packet/signature.go index 147ac6d24..41e173494 100644 --- a/openpgp/packet/signature.go +++ b/openpgp/packet/signature.go @@ -1410,6 +1410,5 @@ func SignatureSaltForHash(hash crypto.Hash, randReader io.Reader) ([]byte, error if err != nil { return nil, err } - randReader.Read(salt) return salt, nil } From bebb8bc81c0ed4cad92c8bd5050ae387ee16236f Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Thu, 23 Nov 2023 11:32:28 +0100 Subject: [PATCH 097/116] refactor: Pass with linter and check errors --- ocb/ocb.go | 5 +- openpgp/clearsign/clearsign.go | 20 ++++++-- openpgp/internal/algorithm/cipher.go | 12 +---- openpgp/key_generation.go | 2 +- openpgp/keys.go | 1 - openpgp/keys_test.go | 20 ++++++-- openpgp/packet/aead_encrypted_test.go | 18 ++++++- openpgp/packet/compressed_test.go | 3 ++ openpgp/packet/private_key.go | 6 ++- openpgp/packet/private_key_test.go | 8 ++- openpgp/packet/public_key.go | 49 +++++++++++++------ openpgp/packet/public_key_test.go | 2 +- openpgp/packet/signature_test.go | 2 +- .../packet/symmetric_key_encrypted_test.go | 8 +-- .../packet/symmetrically_encrypted_test.go | 5 +- openpgp/read_test.go | 15 ++++-- openpgp/read_write_test_data.go | 12 ----- openpgp/v2/key_generation.go | 4 +- openpgp/v2/keys.go | 4 +- openpgp/v2/keys_test.go | 20 ++++++-- openpgp/v2/read.go | 1 - openpgp/v2/write.go | 6 +-- openpgp/v2/write_test.go | 6 ++- openpgp/write_test.go | 8 +-- 24 files changed, 155 insertions(+), 82 deletions(-) diff --git a/ocb/ocb.go b/ocb/ocb.go index 1a6f73502..5022285b4 100644 --- a/ocb/ocb.go +++ b/ocb/ocb.go @@ -18,8 +18,9 @@ import ( "crypto/cipher" "crypto/subtle" "errors" - "github.com/ProtonMail/go-crypto/internal/byteutil" "math/bits" + + "github.com/ProtonMail/go-crypto/internal/byteutil" ) type ocb struct { @@ -153,7 +154,7 @@ func (o *ocb) crypt(instruction int, Y, nonce, adata, X []byte) []byte { truncatedNonce := make([]byte, len(nonce)) copy(truncatedNonce, nonce) truncatedNonce[len(truncatedNonce)-1] &= 192 - Ktop := make([]byte, blockSize) + var Ktop []byte if bytes.Equal(truncatedNonce, o.reusableKtop.noncePrefix) { Ktop = o.reusableKtop.Ktop } else { diff --git a/openpgp/clearsign/clearsign.go b/openpgp/clearsign/clearsign.go index d736d6c11..5103859cd 100644 --- a/openpgp/clearsign/clearsign.go +++ b/openpgp/clearsign/clearsign.go @@ -228,7 +228,9 @@ func (d *dashEscaper) Write(data []byte) (n int, err error) { // The final CRLF isn't included in the hash so we have to wait // until this point (the start of the next line) before writing it. if !d.isFirstLine { - d.toHash.Write(crlf) + if _, err = d.toHash.Write(crlf); err != nil { + return + } } d.isFirstLine = false } @@ -249,12 +251,16 @@ func (d *dashEscaper) Write(data []byte) (n int, err error) { if _, err = d.buffered.Write(dashEscape); err != nil { return } - d.toHash.Write(d.byteBuf) + if _, err = d.toHash.Write(d.byteBuf); err != nil { + return + } d.atBeginningOfLine = false } else if b == '\n' { // Nothing to do because we delay writing CRLF to the hash. } else { - d.toHash.Write(d.byteBuf) + if _, err = d.toHash.Write(d.byteBuf); err != nil { + return + } d.atBeginningOfLine = false } if err = d.buffered.WriteByte(b); err != nil { @@ -275,7 +281,9 @@ func (d *dashEscaper) Write(data []byte) (n int, err error) { // Any buffered whitespace wasn't at the end of the line so // we need to write it out. if len(d.whitespace) > 0 { - d.toHash.Write(d.whitespace) + if _, err = d.toHash.Write(d.whitespace); err != nil { + return + } if _, err = d.buffered.Write(d.whitespace); err != nil { return } @@ -296,7 +304,9 @@ func (d *dashEscaper) Write(data []byte) (n int, err error) { func (d *dashEscaper) Close() (err error) { if d.atBeginningOfLine { if !d.isFirstLine { - d.toHash.Write(crlf) + if _, err := d.toHash.Write(crlf); err != nil { + return err + } } } if err = d.buffered.WriteByte(lf); err != nil { diff --git a/openpgp/internal/algorithm/cipher.go b/openpgp/internal/algorithm/cipher.go index 5760cff80..c76a75bcd 100644 --- a/openpgp/internal/algorithm/cipher.go +++ b/openpgp/internal/algorithm/cipher.go @@ -51,24 +51,14 @@ func (sk CipherFunction) Id() uint8 { return uint8(sk) } -var keySizeByID = map[uint8]int{ - TripleDES.Id(): 24, - CAST5.Id(): cast5.KeySize, - AES128.Id(): 16, - AES192.Id(): 24, - AES256.Id(): 32, -} - // KeySize returns the key size, in bytes, of cipher. func (cipher CipherFunction) KeySize() int { switch cipher { - case TripleDES: - return 24 case CAST5: return cast5.KeySize case AES128: return 16 - case AES192: + case AES192, TripleDES: return 24 case AES256: return 32 diff --git a/openpgp/key_generation.go b/openpgp/key_generation.go index eae4853b4..127d72a4f 100644 --- a/openpgp/key_generation.go +++ b/openpgp/key_generation.go @@ -358,7 +358,7 @@ func newDecrypter(config *packet.Config) (decrypter interface{}, err error) { var bigOne = big.NewInt(1) // generateRSAKeyWithPrimes generates a multi-prime RSA keypair of the -// given bit size, using the given random source and prepopulated primes. +// given bit size, using the given random source and pre-populated primes. func generateRSAKeyWithPrimes(random io.Reader, nprimes int, bits int, prepopulatedPrimes []*big.Int) (*rsa.PrivateKey, error) { priv := new(rsa.PrivateKey) priv.E = 65537 diff --git a/openpgp/keys.go b/openpgp/keys.go index e73eead3b..fdb062394 100644 --- a/openpgp/keys.go +++ b/openpgp/keys.go @@ -443,7 +443,6 @@ func readToNextPublicKey(packets *packet.Reader) (err error) { return } else if err != nil { if _, ok := err.(errors.UnsupportedError); ok { - err = nil continue } return diff --git a/openpgp/keys_test.go b/openpgp/keys_test.go index a3ce59406..1b99c6fb3 100644 --- a/openpgp/keys_test.go +++ b/openpgp/keys_test.go @@ -1081,7 +1081,9 @@ func TestAddUserId(t *testing.T) { } serializedEntity := bytes.NewBuffer(nil) - entity.SerializePrivate(serializedEntity, nil) + if err := entity.SerializePrivate(serializedEntity, nil); err != nil { + t.Fatal(err) + } _, err = ReadEntity(packet.NewReader(bytes.NewBuffer(serializedEntity.Bytes()))) if err != nil { @@ -1116,7 +1118,9 @@ func TestAddSubkey(t *testing.T) { } serializedEntity := bytes.NewBuffer(nil) - entity.SerializePrivate(serializedEntity, nil) + if err := entity.SerializePrivate(serializedEntity, nil); err != nil { + t.Fatal(err) + } _, err = ReadEntity(packet.NewReader(bytes.NewBuffer(serializedEntity.Bytes()))) if err != nil { @@ -1141,7 +1145,9 @@ func TestAddSubkeySerialized(t *testing.T) { } serializedEntity := bytes.NewBuffer(nil) - entity.SerializePrivateWithoutSigning(serializedEntity, nil) + if err = entity.SerializePrivateWithoutSigning(serializedEntity, nil); err != nil { + t.Fatal(err) + } entity, err = ReadEntity(packet.NewReader(bytes.NewBuffer(serializedEntity.Bytes()))) if err != nil { @@ -1217,7 +1223,9 @@ func TestAddSubkeyWithConfig(t *testing.T) { } serializedEntity := bytes.NewBuffer(nil) - entity.SerializePrivate(serializedEntity, nil) + if err = entity.SerializePrivate(serializedEntity, nil); err != nil { + t.Fatal(err) + } _, err = ReadEntity(packet.NewReader(bytes.NewBuffer(serializedEntity.Bytes()))) if err != nil { @@ -1246,7 +1254,9 @@ func TestAddSubkeyWithConfigSerialized(t *testing.T) { } serializedEntity := bytes.NewBuffer(nil) - entity.SerializePrivateWithoutSigning(serializedEntity, nil) + if err := entity.SerializePrivateWithoutSigning(serializedEntity, nil); err != nil { + t.Fatal(err) + } entity, err = ReadEntity(packet.NewReader(bytes.NewBuffer(serializedEntity.Bytes()))) if err != nil { diff --git a/openpgp/packet/aead_encrypted_test.go b/openpgp/packet/aead_encrypted_test.go index f5827579b..97736071a 100644 --- a/openpgp/packet/aead_encrypted_test.go +++ b/openpgp/packet/aead_encrypted_test.go @@ -80,6 +80,9 @@ func TestAeadEmptyStream(t *testing.T) { } // decrypted plaintext can be read from 'rc' rc, err := packet.decrypt(key) + if err != nil { + t.Error(err) + } _, err = readDecryptedStream(rc) if err != nil { @@ -97,6 +100,9 @@ func TestAeadEmptyStream(t *testing.T) { } // decrypted plaintext can be read from 'rc' rc, err = packet.decrypt(key) + if err != nil { + t.Error(err) + } _, err = readDecryptedStream(rc) if err == nil { @@ -127,6 +133,9 @@ func TestAeadNilConfigStream(t *testing.T) { } // decrypted plaintext can be read from 'rc' rc, err := packet.decrypt(key) + if err != nil { + t.Error(err) + } got, err := readDecryptedStream(rc) if err != nil { @@ -161,6 +170,9 @@ func TestAeadStreamRandomizeSlow(t *testing.T) { } // decrypted plaintext can be read from 'rc' rc, err := packet.decrypt(key) + if err != nil { + t.Error(err) + } got, err := readDecryptedStream(rc) if err != nil { @@ -203,9 +215,13 @@ func TestAeadCorruptStreamRandomizeSlow(t *testing.T) { if err = packet.parse(contentsReader); err != nil { // Header was corrupted + t.Error(err) return } rc, err := packet.decrypt(key) + if err != nil { + t.Error(err) + } got, err := readDecryptedStream(rc) if err == nil || err == io.EOF { t.Errorf("No error raised when decrypting corrupt stream") @@ -420,7 +436,7 @@ func SerializeAEADEncrypted(w io.Writer, key []byte, config *Config) (io.WriteCl // Sample nonce nonceLen := aeadConf.Mode().IvLength() nonce := make([]byte, nonceLen) - n, err = rand.Read(nonce) + _, err = rand.Read(nonce) if err != nil { panic("Could not sample random nonce") } diff --git a/openpgp/packet/compressed_test.go b/openpgp/packet/compressed_test.go index 9e1862160..5cf3dba90 100644 --- a/openpgp/packet/compressed_test.go +++ b/openpgp/packet/compressed_test.go @@ -68,6 +68,9 @@ func TestCompressDecompressRandomizeFast(t *testing.T) { wcomp.Close() // Read the packet and decompress p, err := Read(w) + if err != nil { + t.Fatal(err) + } c, ok := p.(*Compressed) if !ok { t.Error("didn't find Compressed packet") diff --git a/openpgp/packet/private_key.go b/openpgp/packet/private_key.go index 6f6da73d4..eef631200 100644 --- a/openpgp/packet/private_key.go +++ b/openpgp/packet/private_key.go @@ -417,7 +417,9 @@ func (pk *PrivateKey) Serialize(w io.Writer) (err error) { contents.Write([]byte{uint8(optional.Len())}) } - io.Copy(contents, optional) + if _, err := io.Copy(contents, optional); err != nil { + return err + } if !pk.Dummy() { l := 0 @@ -1007,6 +1009,7 @@ func (pk *PrivateKey) parseEd25519PrivateKey(data []byte) (err error) { if len(data) != ed25519.SeedSize { err = errors.StructuralError("wrong ed25519 key size") + return err } err = privateKey.UnmarshalByteSecret(data) if err != nil { @@ -1027,6 +1030,7 @@ func (pk *PrivateKey) parseEd448PrivateKey(data []byte) (err error) { if len(data) != ed448.SeedSize { err = errors.StructuralError("wrong ed448 key size") + return err } err = privateKey.UnmarshalByteSecret(data) if err != nil { diff --git a/openpgp/packet/private_key_test.go b/openpgp/packet/private_key_test.go index 6b06b1002..46cf67aea 100644 --- a/openpgp/packet/private_key_test.go +++ b/openpgp/packet/private_key_test.go @@ -463,9 +463,13 @@ func TestEncryptDecryptEdDSAPrivateKeyRandomizeFast(t *testing.T) { copy(copiedSecret, privKey.PrivateKey.(*eddsa.PrivateKey).D) // Encrypt private key with random passphrase - privKey.Encrypt(password) + if err := privKey.Encrypt(password); err != nil { + t.Fatal(err) + } // Decrypt and check correctness - privKey.Decrypt(password) + if err := privKey.Decrypt(password); err != nil { + t.Fatal(err) + } decryptedSecret := privKey.PrivateKey.(*eddsa.PrivateKey).D if !bytes.Equal(decryptedSecret, copiedSecret) { diff --git a/openpgp/packet/public_key.go b/openpgp/packet/public_key.go index dcc5e7771..159108bd0 100644 --- a/openpgp/packet/public_key.go +++ b/openpgp/packet/public_key.go @@ -72,7 +72,7 @@ func (pk *PublicKey) UpgradeToV6() { // for v3 and v4 public keys. type signingKey interface { SerializeForHash(io.Writer) error - SerializeSignaturePrefix(io.Writer) + SerializeSignaturePrefix(io.Writer) error serializeWithoutHeaders(io.Writer) error } @@ -289,13 +289,19 @@ func (pk *PublicKey) setFingerprintAndKeyId() { // RFC 4880, section 12.2 if pk.Version >= 5 { fingerprint := sha256.New() - pk.SerializeForHash(fingerprint) + if err := pk.SerializeForHash(fingerprint); err != nil { + // Should not happen for a hash. + panic(err) + } pk.Fingerprint = make([]byte, 32) copy(pk.Fingerprint, fingerprint.Sum(nil)) pk.KeyId = binary.BigEndian.Uint64(pk.Fingerprint[:8]) } else { fingerprint := sha1.New() - pk.SerializeForHash(fingerprint) + if err := pk.SerializeForHash(fingerprint); err != nil { + // Should not happen for a hash. + panic(err) + } pk.Fingerprint = make([]byte, 20) copy(pk.Fingerprint, fingerprint.Sum(nil)) pk.KeyId = binary.BigEndian.Uint64(pk.Fingerprint[12:20]) @@ -558,21 +564,23 @@ func (pk *PublicKey) parseEd448(r io.Reader) (err error) { // SerializeForHash serializes the PublicKey to w with the special packet // header format needed for hashing. func (pk *PublicKey) SerializeForHash(w io.Writer) error { - pk.SerializeSignaturePrefix(w) + if err := pk.SerializeSignaturePrefix(w); err != nil { + return err + } return pk.serializeWithoutHeaders(w) } // SerializeSignaturePrefix writes the prefix for this public key to the given Writer. // The prefix is used when calculating a signature over this public key. See // RFC 4880, section 5.2.4. -func (pk *PublicKey) SerializeSignaturePrefix(w io.Writer) { +func (pk *PublicKey) SerializeSignaturePrefix(w io.Writer) error { var pLength = pk.algorithmSpecificByteCount() // version, timestamp, algorithm pLength += versionSize + timestampSize + algorithmSize if pk.Version >= 5 { // key octet count (4). pLength += 4 - w.Write([]byte{ + _, err := w.Write([]byte{ // When a v4 signature is made over a key, the hash data starts with the octet 0x99, followed by a two-octet length // of the key, and then the body of the key packet. When a v6 signature is made over a key, the hash data starts // with the salt, then octet 0x9B, followed by a four-octet length of the key, and then the body of the key packet. @@ -582,9 +590,15 @@ func (pk *PublicKey) SerializeSignaturePrefix(w io.Writer) { byte(pLength >> 8), byte(pLength), }) - return + if err != nil { + return err + } + return nil } - w.Write([]byte{0x99, byte(pLength >> 8), byte(pLength)}) + if _, err := w.Write([]byte{0x99, byte(pLength >> 8), byte(pLength)}); err != nil { + return err + } + return nil } func (pk *PublicKey) Serialize(w io.Writer) (err error) { @@ -902,8 +916,12 @@ func (pk *PublicKey) VerifySubkeyRevocationSignature(sig *Signature, signed *Pub func userIdSignatureHash(id string, pk *PublicKey, h hash.Hash) (err error) { // RFC 4880, section 5.2.4 - pk.SerializeSignaturePrefix(h) - pk.serializeWithoutHeaders(h) + if err := pk.SerializeSignaturePrefix(h); err != nil { + return err + } + if err := pk.serializeWithoutHeaders(h); err != nil { + return err + } var buf [5]byte buf[0] = 0xb4 @@ -920,10 +938,13 @@ func userIdSignatureHash(id string, pk *PublicKey, h hash.Hash) (err error) { // directSignatureHash returns a Hash of the message that needs to be signed func directKeySignatureHash(pk *PublicKey, h hash.Hash) (err error) { // RFC 4880, section 5.2.4 - pk.SerializeSignaturePrefix(h) - pk.serializeWithoutHeaders(h) - - return + if err := pk.SerializeSignaturePrefix(h); err != nil { + return err + } + if err := pk.serializeWithoutHeaders(h); err != nil { + return err + } + return nil } // VerifyUserIdSignature returns nil iff sig is a valid signature, made by this diff --git a/openpgp/packet/public_key_test.go b/openpgp/packet/public_key_test.go index 41586eb83..a977e6017 100644 --- a/openpgp/packet/public_key_test.go +++ b/openpgp/packet/public_key_test.go @@ -90,7 +90,7 @@ func TestPublicKeySerialize(t *testing.T) { t.Errorf("#%d: Read error (from serialized data): %s", i, err) continue } - pk, ok = packet.(*PublicKey) + _, ok = packet.(*PublicKey) if !ok { t.Errorf("#%d: failed to parse serialized data, got: %#v", i, packet) continue diff --git a/openpgp/packet/signature_test.go b/openpgp/packet/signature_test.go index 669307301..20a5e17f7 100644 --- a/openpgp/packet/signature_test.go +++ b/openpgp/packet/signature_test.go @@ -315,7 +315,7 @@ func TestSignatureWithTrustAndRegex(t *testing.T) { } // ensure we fail if the regular expression is not null-terminated - packet, err = Read(readerFromHex(signatureWithBadTrustRegexHex)) + _, err = Read(readerFromHex(signatureWithBadTrustRegexHex)) if err == nil { t.Errorf("did not receive an error when expected") } diff --git a/openpgp/packet/symmetric_key_encrypted_test.go b/openpgp/packet/symmetric_key_encrypted_test.go index e609993e6..b4cec75fc 100644 --- a/openpgp/packet/symmetric_key_encrypted_test.go +++ b/openpgp/packet/symmetric_key_encrypted_test.go @@ -42,11 +42,11 @@ func TestDecryptSymmetricKeyAndEncryptedDataPacket(t *testing.T) { } // Decrypt contents var edp EncryptedDataPacket - switch packet.(type) { + switch p := packet.(type) { case *SymmetricallyEncrypted: - edp, _ = packet.(*SymmetricallyEncrypted) + edp = p case *AEADEncrypted: - edp, _ = packet.(*AEADEncrypted) + edp = p default: t.Fatal("no integrity protected packet") } @@ -151,7 +151,7 @@ func TestSerializeSymmetricKeyEncryptedCiphersV4(t *testing.T) { config := &Config{ DefaultCipher: cipher, S2KConfig: &s2k.Config{ - S2KMode: s2ktype, + S2KMode: s2ktype, PassphraseIsHighEntropy: true, }, } diff --git a/openpgp/packet/symmetrically_encrypted_test.go b/openpgp/packet/symmetrically_encrypted_test.go index ef63f8b00..f4dc8a161 100644 --- a/openpgp/packet/symmetrically_encrypted_test.go +++ b/openpgp/packet/symmetrically_encrypted_test.go @@ -100,7 +100,10 @@ func TestSerializeMdc(t *testing.T) { contents := []byte("hello world\n") - w.Write(contents) + if _, err := w.Write(contents); err != nil { + t.Error(err) + return + } w.Close() p, err := Read(buf) diff --git a/openpgp/read_test.go b/openpgp/read_test.go index 9e745d9d8..5a3ef63e3 100644 --- a/openpgp/read_test.go +++ b/openpgp/read_test.go @@ -585,7 +585,6 @@ func TestSignatureV3Message(t *testing.T) { t.Errorf("Did not expect a signature V4 back") return } - return } func TestReadV6Messages(t *testing.T) { @@ -668,7 +667,7 @@ func TestSymmetricDecryptionArgon2(t *testing.T) { t.Errorf("error reading UnverifiedBody: %s", err) } - if "Hello, world!" != string(contents) { + if string(contents) != "Hello, world!" { t.Fatal("Did not decrypt Argon message correctly") } } @@ -680,6 +679,9 @@ func TestAsymmestricAeadOcbOpenPGPjsCompressedMessage(t *testing.T) { t.Fatal(err) } el, err := ReadArmoredKeyRing(armored) + if err != nil { + t.Fatal(err) + } // Read ciphertext from file ciphertext, err := os.Open("test_data/aead-ocb-asym-message.asc") if err != nil { @@ -731,6 +733,9 @@ func TestSymmetricAeadEaxOpenPGPJsMessage(t *testing.T) { } // Decode from base 64 raw, err := base64.StdEncoding.DecodeString(string(fileBytes)) + if err != nil { + t.Fatal(err) + } r := bytes.NewBuffer(raw) // Read packet p, err := packet.Read(r) @@ -739,14 +744,16 @@ func TestSymmetricAeadEaxOpenPGPJsMessage(t *testing.T) { } // Decrypt with key - var edp packet.EncryptedDataPacket - edp = p.(*packet.AEADEncrypted) + var edp = p.(*packet.AEADEncrypted) rc, err := edp.Decrypt(packet.CipherFunction(0), key) if err != nil { panic(err) } // Read literal data packet p, err = packet.Read(rc) + if err != nil { + t.Fatal(err) + } ld := p.(*packet.LiteralData) // Read contents diff --git a/openpgp/read_write_test_data.go b/openpgp/read_write_test_data.go index c8a482145..a7f82bd23 100644 --- a/openpgp/read_write_test_data.go +++ b/openpgp/read_write_test_data.go @@ -162,18 +162,6 @@ TcIYl5/Uyoi+FOvPLcNw4hOv2nwUzSSVAw== =IiS2 -----END PGP PRIVATE KEY BLOCK-----` -// Generated with the above private key -const v5PrivKeyMsg = `-----BEGIN PGP MESSAGE----- -Version: OpenPGP.js v4.10.7 -Comment: https://openpgpjs.org - -xA0DAQoWGTR7yYckZAIByxF1B21zZy50eHRfbIGSdGVzdMJ3BQEWCgAGBQJf -bIGSACMiIQUZNHvJhyRkAl+Z3z7C4AAO2YhIkuH3s+pMlACRWVabVDQvAP9G -y29VPonFXqi2zKkpZrvyvZxg+n5e8Nt9wNbuxeCd3QD/TtO2s+JvjrE4Siwv -UQdl5MlBka1QSNbMq2Bz7XwNPg4= -=6lbM ------END PGP MESSAGE-----` - // See OpenPGP crypto refresh Section A.3. const v6PrivKey = `-----BEGIN PGP PRIVATE KEY BLOCK----- diff --git a/openpgp/v2/key_generation.go b/openpgp/v2/key_generation.go index 8f10c6cd6..95f46db95 100644 --- a/openpgp/v2/key_generation.go +++ b/openpgp/v2/key_generation.go @@ -97,7 +97,9 @@ func newEntity(uid *userIdData, config *packet.Config) (*Entity, error) { } if config.V6() { - e.AddDirectKeySignature(keyProperties, config) + if err := e.AddDirectKeySignature(keyProperties, config); err != nil { + return nil, err + } keyProperties = nil } diff --git a/openpgp/v2/keys.go b/openpgp/v2/keys.go index 162708353..4aa403c96 100644 --- a/openpgp/v2/keys.go +++ b/openpgp/v2/keys.go @@ -596,7 +596,9 @@ func (e *Entity) serializePrivate(w io.Writer, config *packet.Config, reSign boo } for _, subkey := range e.Subkeys { if reSign { - subkey.ReSign(config) + if err := subkey.ReSign(config); err != nil { + return err + } } if err = subkey.Serialize(w, true); err != nil { return err diff --git a/openpgp/v2/keys_test.go b/openpgp/v2/keys_test.go index fac1a30e2..567a941bd 100644 --- a/openpgp/v2/keys_test.go +++ b/openpgp/v2/keys_test.go @@ -1111,7 +1111,9 @@ func TestAddUserId(t *testing.T) { } serializedEntity := bytes.NewBuffer(nil) - entity.SerializePrivate(serializedEntity, nil) + if err := entity.SerializePrivate(serializedEntity, nil); err != nil { + t.Fatal(err) + } _, err = ReadEntity(packet.NewReader(bytes.NewBuffer(serializedEntity.Bytes()))) if err != nil { @@ -1151,7 +1153,9 @@ func TestAddSubkey(t *testing.T) { } serializedEntity := bytes.NewBuffer(nil) - entity.SerializePrivate(serializedEntity, nil) + if err := entity.SerializePrivate(serializedEntity, nil); err != nil { + t.Fatal(err) + } _, err = ReadEntity(packet.NewReader(bytes.NewBuffer(serializedEntity.Bytes()))) if err != nil { @@ -1176,7 +1180,9 @@ func TestAddSubkeySerialized(t *testing.T) { } serializedEntity := bytes.NewBuffer(nil) - entity.SerializePrivateWithoutSigning(serializedEntity, nil) + if err := entity.SerializePrivateWithoutSigning(serializedEntity, nil); err != nil { + t.Fatal(err) + } entity, err = ReadEntity(packet.NewReader(bytes.NewBuffer(serializedEntity.Bytes()))) if err != nil { @@ -1266,7 +1272,9 @@ func TestAddSubkeyWithConfig(t *testing.T) { } serializedEntity := bytes.NewBuffer(nil) - entity.SerializePrivate(serializedEntity, nil) + if err := entity.SerializePrivate(serializedEntity, nil); err != nil { + t.Fatal(err) + } _, err = ReadEntity(packet.NewReader(bytes.NewBuffer(serializedEntity.Bytes()))) if err != nil { @@ -1295,7 +1303,9 @@ func TestAddSubkeyWithConfigSerialized(t *testing.T) { } serializedEntity := bytes.NewBuffer(nil) - entity.SerializePrivateWithoutSigning(serializedEntity, nil) + if err := entity.SerializePrivateWithoutSigning(serializedEntity, nil); err != nil { + t.Fatal(err) + } entity, err = ReadEntity(packet.NewReader(bytes.NewBuffer(serializedEntity.Bytes()))) if err != nil { diff --git a/openpgp/v2/read.go b/openpgp/v2/read.go index 1a18abc5f..351b88e80 100644 --- a/openpgp/v2/read.go +++ b/openpgp/v2/read.go @@ -542,7 +542,6 @@ func (scr *signatureCheckReader) Read(buf []byte) (int, error) { for _, candidate := range scr.md.SignatureCandidates { candidate.SignatureError = noMatchError } - signatures = nil } else { var sigIndex int // Verify all signature candidates. diff --git a/openpgp/v2/write.go b/openpgp/v2/write.go index 90c6f8730..96962db55 100644 --- a/openpgp/v2/write.go +++ b/openpgp/v2/write.go @@ -27,7 +27,7 @@ func DetachSign(w io.Writer, signers []*Entity, message io.Reader, config *packe } // DetachSignWithParams signs message with the private key from signer (which must -// already have been decrypted) and writes the signature to w. +// already have been decrypted) and writes the signature to the Writer. // If config is nil, sensible defaults will be used. func DetachSignWithParams(w io.Writer, signers []*Entity, message io.Reader, params *SignParams) error { if params == nil { @@ -41,7 +41,7 @@ func DetachSignWithParams(w io.Writer, signers []*Entity, message io.Reader, par } // ArmoredDetachSign signs message with the private key from signer (which -// must already have been decrypted) and writes an armored signature to w. +// must already have been decrypted) and writes an armored signature to the Writer. // If config is nil, sensible defaults will be used. func ArmoredDetachSign(w io.Writer, signers []*Entity, message io.Reader, params *SignParams) (err error) { if params == nil { @@ -55,7 +55,7 @@ func ArmoredDetachSign(w io.Writer, signers []*Entity, message io.Reader, params } // DetachSignWriter signs a message with the private key from a signer (which must -// already have been decrypted) and writes the signature to w. +// already have been decrypted) and writes the signature to the Writer. // DetachSignWriter returns a WriteCloser to which the message can be written to. // The resulting WriteCloser must be closed after the contents of the message have // been written. If utf8Message is set to true, the line endings of the message are diff --git a/openpgp/v2/write_test.go b/openpgp/v2/write_test.go index eb701a7eb..f121ef7cd 100644 --- a/openpgp/v2/write_test.go +++ b/openpgp/v2/write_test.go @@ -64,7 +64,9 @@ func TestSignDetachedDSA(t *testing.T) { func TestSignDetachedP256(t *testing.T) { kring, _ := ReadKeyRing(readerFromHex(p256TestKeyPrivateHex)) - kring[0].PrivateKey.Decrypt([]byte("passphrase")) + if err := kring[0].PrivateKey.Decrypt([]byte("passphrase")); err != nil { + t.Error(err) + } out := bytes.NewBuffer(nil) message := bytes.NewBufferString(signedInput) @@ -563,7 +565,7 @@ func TestIntendedRecipientsEncryption(t *testing.T) { t.Errorf("not verified despite all data read") } if md.SignatureError != nil { - t.Error("singature verification should pass") + t.Error("signature verification should pass") } } diff --git a/openpgp/write_test.go b/openpgp/write_test.go index b87166015..2ebeff955 100644 --- a/openpgp/write_test.go +++ b/openpgp/write_test.go @@ -61,7 +61,9 @@ func TestSignDetachedDSA(t *testing.T) { func TestSignDetachedP256(t *testing.T) { kring, _ := ReadKeyRing(readerFromHex(p256TestKeyPrivateHex)) - kring[0].PrivateKey.Decrypt([]byte("passphrase")) + if err := kring[0].PrivateKey.Decrypt([]byte("passphrase")); err != nil { + t.Error(err) + } out := bytes.NewBuffer(nil) message := bytes.NewBufferString(signedInput) @@ -219,7 +221,7 @@ func TestNewEntity(t *testing.T) { el, err := ReadKeyRing(w) if err != nil { - t.Errorf("failed to reparse entity: %s", err) + t.Errorf("failed to rephrase entity: %s", err) return } @@ -253,7 +255,7 @@ func TestNewEntity(t *testing.T) { _, err = ReadKeyRing(w) if err != nil { - t.Errorf("failed to reparse entity after encryption round: %s", err) + t.Errorf("failed to re-parse entity after encryption round: %s", err) return } } From e94987c5124f1a1680ae9a2dd26b914a13be822b Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Thu, 23 Nov 2023 11:33:45 +0100 Subject: [PATCH 098/116] chore: Update gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index d6d52e87b..2b695db7f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ # Add no patterns to .gitignore except for files generated by the build. last-change -.idea \ No newline at end of file +.idea +settings.json From 26e7edcf30731cf2764f269e07445690253f2cc6 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Thu, 23 Nov 2023 10:33:10 +0100 Subject: [PATCH 099/116] docs(packet): Fix grammar issue in config comments Co-authored-by: Aron Wussler --- openpgp/packet/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpgp/packet/config.go b/openpgp/packet/config.go index 01ce4c341..181d5d344 100644 --- a/openpgp/packet/config.go +++ b/openpgp/packet/config.go @@ -144,7 +144,7 @@ type Config struct { CacheSessionKey bool // CheckPacketSequence is a flag that controls if the pgp message reader should strictly check // that the packet sequence conforms with the grammar mandated by rfc4880. - // The default behavior, when the config or flag is nil, is to check he packet sequence. + // The default behavior, when the config or flag is nil, is to check the packet sequence. CheckPacketSequence *bool } From e3b2b43dac500bc6a40fae5331a9334ce26369a2 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Thu, 23 Nov 2023 11:42:02 +0100 Subject: [PATCH 100/116] refactor(test): Handle errors if possible --- openpgp/clearsign/clearsign.go | 4 +++- openpgp/keys_test.go | 4 +++- openpgp/packet/symmetric_key_encrypted_test.go | 3 +++ openpgp/v2/keys.go | 1 - openpgp/v2/keys_test.go | 4 +++- 5 files changed, 12 insertions(+), 4 deletions(-) diff --git a/openpgp/clearsign/clearsign.go b/openpgp/clearsign/clearsign.go index 5103859cd..33fa8e5ac 100644 --- a/openpgp/clearsign/clearsign.go +++ b/openpgp/clearsign/clearsign.go @@ -289,7 +289,9 @@ func (d *dashEscaper) Write(data []byte) (n int, err error) { } d.whitespace = d.whitespace[:0] } - d.toHash.Write(d.byteBuf) + if _, err = d.toHash.Write(d.byteBuf); err != nil { + return + } if err = d.buffered.WriteByte(b); err != nil { return } diff --git a/openpgp/keys_test.go b/openpgp/keys_test.go index 1b99c6fb3..bd0bf322d 100644 --- a/openpgp/keys_test.go +++ b/openpgp/keys_test.go @@ -1388,7 +1388,9 @@ func TestRevokeSubkey(t *testing.T) { } serializedEntity := bytes.NewBuffer(nil) - entity.SerializePrivate(serializedEntity, nil) + if err := entity.SerializePrivate(serializedEntity, nil); err != nil { + t.Fatal(err) + } // Make sure revocation reason subpackets are not lost during serialization. newEntity, err := ReadEntity(packet.NewReader(bytes.NewBuffer(serializedEntity.Bytes()))) diff --git a/openpgp/packet/symmetric_key_encrypted_test.go b/openpgp/packet/symmetric_key_encrypted_test.go index b4cec75fc..6896d68d7 100644 --- a/openpgp/packet/symmetric_key_encrypted_test.go +++ b/openpgp/packet/symmetric_key_encrypted_test.go @@ -102,6 +102,9 @@ func TestSerializeSymmetricKeyEncryptedV6RandomizeSlow(t *testing.T) { } key, err := SerializeSymmetricKeyEncrypted(&buf, passphrase, config) + if err != nil { + t.Errorf("failed to serialize %s", err) + } p, err := Read(&buf) if err != nil { t.Errorf("failed to reparse %s", err) diff --git a/openpgp/v2/keys.go b/openpgp/v2/keys.go index 4aa403c96..61bdb027d 100644 --- a/openpgp/v2/keys.go +++ b/openpgp/v2/keys.go @@ -427,7 +427,6 @@ func readToNextPublicKey(packets *packet.Reader) (err error) { return } else if err != nil { if _, ok := err.(errors.UnsupportedError); ok { - err = nil continue } return diff --git a/openpgp/v2/keys_test.go b/openpgp/v2/keys_test.go index 567a941bd..6f037f5fe 100644 --- a/openpgp/v2/keys_test.go +++ b/openpgp/v2/keys_test.go @@ -1446,7 +1446,9 @@ func TestRevokeSubkey(t *testing.T) { } serializedEntity := bytes.NewBuffer(nil) - entity.SerializePrivate(serializedEntity, nil) + if err := entity.SerializePrivate(serializedEntity, nil); err != nil { + t.Fatal(err) + } // Make sure revocation reason subpackets are not lost during serialization. newEntity, err := ReadEntity(packet.NewReader(bytes.NewBuffer(serializedEntity.Bytes()))) From d47e64875cdcfdc8b391b8863816d80e27504be0 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Fri, 24 Nov 2023 09:45:04 +0100 Subject: [PATCH 101/116] docs(v2): Improve documentation in signatures for keys --- openpgp/packet/public_key.go | 29 ++++++++--------------------- openpgp/packet/signature.go | 5 ++--- openpgp/v2/subkeys.go | 1 + openpgp/v2/user.go | 2 ++ 4 files changed, 13 insertions(+), 24 deletions(-) diff --git a/openpgp/packet/public_key.go b/openpgp/packet/public_key.go index 159108bd0..dd93c9870 100644 --- a/openpgp/packet/public_key.go +++ b/openpgp/packet/public_key.go @@ -874,13 +874,8 @@ func (pk *PublicKey) VerifyKeySignature(signed *PublicKey, sig *Signature) error return nil } -func keyRevocationHash(pk signingKey, hashFunc hash.Hash) (h hash.Hash, err error) { - h = hashFunc - - // RFC 4880, section 5.2.4 - err = pk.SerializeForHash(h) - - return +func keyRevocationHash(pk signingKey, hashFunc hash.Hash) (err error) { + return pk.SerializeForHash(hashFunc) } // VerifyRevocationSignature returns nil iff sig is a valid signature, made by this @@ -890,11 +885,10 @@ func (pk *PublicKey) VerifyRevocationSignature(sig *Signature) (err error) { if err != nil { return err } - h, err := keyRevocationHash(pk, preparedHash) - if err != nil { + if keyRevocationHash(pk, preparedHash); err != nil { return err } - return pk.VerifySignature(h, sig) + return pk.VerifySignature(preparedHash, sig) } // VerifySubkeyRevocationSignature returns nil iff sig is a valid subkey revocation signature, @@ -935,16 +929,9 @@ func userIdSignatureHash(id string, pk *PublicKey, h hash.Hash) (err error) { return nil } -// directSignatureHash returns a Hash of the message that needs to be signed +// directKeySignatureHash returns a Hash of the message that needs to be signed. func directKeySignatureHash(pk *PublicKey, h hash.Hash) (err error) { - // RFC 4880, section 5.2.4 - if err := pk.SerializeSignaturePrefix(h); err != nil { - return err - } - if err := pk.serializeWithoutHeaders(h); err != nil { - return err - } - return nil + return pk.SerializeForHash(h) } // VerifyUserIdSignature returns nil iff sig is a valid signature, made by this @@ -960,8 +947,8 @@ func (pk *PublicKey) VerifyUserIdSignature(id string, pub *PublicKey, sig *Signa return pk.VerifySignature(h, sig) } -// VerifyUserIdSignature returns nil iff sig is a valid signature, made by this -// public key +// VerifyDirectKeySignature returns nil iff sig is a valid signature, made by this +// public key. func (pk *PublicKey) VerifyDirectKeySignature(sig *Signature) (err error) { h, err := sig.PrepareVerify() if err != nil { diff --git a/openpgp/packet/signature.go b/openpgp/packet/signature.go index 41e173494..647f7ee45 100644 --- a/openpgp/packet/signature.go +++ b/openpgp/packet/signature.go @@ -1019,11 +1019,10 @@ func (sig *Signature) RevokeKey(pub *PublicKey, priv *PrivateKey, config *Config if err != nil { return err } - h, err := keyRevocationHash(pub, prepareHash) - if err != nil { + if err := keyRevocationHash(pub, prepareHash); err != nil { return err } - return sig.Sign(h, priv, config) + return sig.Sign(prepareHash, priv, config) } // RevokeSubkey computes a subkey revocation signature of pub using priv. diff --git a/openpgp/v2/subkeys.go b/openpgp/v2/subkeys.go index 7ef007f21..c3063ccb2 100644 --- a/openpgp/v2/subkeys.go +++ b/openpgp/v2/subkeys.go @@ -79,6 +79,7 @@ func (s *Subkey) Serialize(w io.Writer, includeSecrets bool) error { return nil } +// ReSign resigns the latest valid subkey binding signature with the given config. func (s *Subkey) ReSign(config *packet.Config) error { selectedSig, err := s.LatestValidBindingSignature(time.Time{}) if err != nil { diff --git a/openpgp/v2/user.go b/openpgp/v2/user.go index 2b85f2b1f..1b075eb22 100644 --- a/openpgp/v2/user.go +++ b/openpgp/v2/user.go @@ -71,6 +71,7 @@ func readUser(e *Entity, packets *packet.Reader, pkt *packet.UserId) error { return nil } +// Serialize serializes the user id to the writer. func (i *Identity) Serialize(w io.Writer) error { if err := i.UserId.Serialize(w); err != nil { return err @@ -135,6 +136,7 @@ func (i *Identity) Revoked(selfCertification *packet.Signature, date time.Time) return false } +// ReSign resigns the latest valid self-certification with the given config. func (i *Identity) ReSign(config *packet.Config) error { selectedSig, err := i.LatestValidSelfCertification(config.Now()) if err != nil { From 8b9879ab84d1cf39ca5c781aa7c96f803a9fb3e2 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Mon, 27 Nov 2023 17:01:28 +0100 Subject: [PATCH 102/116] docs(keys): Fix self-signature check comments --- openpgp/keys.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpgp/keys.go b/openpgp/keys.go index fdb062394..a071353e2 100644 --- a/openpgp/keys.go +++ b/openpgp/keys.go @@ -127,7 +127,7 @@ func (e *Entity) EncryptionKey(now time.Time) (Key, bool) { if primarySelfSignature == nil || // no self-signature found e.PrimaryKey.KeyExpired(primarySelfSignature, now) || // primary key has expired e.Revoked(now) || // primary key has been revoked - primarySelfSignature.SigExpired(now) || // user ID self-signature has expired + primarySelfSignature.SigExpired(now) || // user ID or or direct self-signature has expired (primaryIdentity != nil && primaryIdentity.Revoked(now)) { // user ID has been revoked (for v4 keys) return Key{}, false } @@ -193,7 +193,7 @@ func (e *Entity) signingKeyByIdUsage(now time.Time, id uint64, flags int) (Key, if primarySelfSignature == nil || // no self-signature found e.PrimaryKey.KeyExpired(primarySelfSignature, now) || // primary key has expired e.Revoked(now) || // primary key has been revoked - primarySelfSignature.SigExpired(now) || // user ID self-signature has expired + primarySelfSignature.SigExpired(now) || // user ID or direct self-signature has expired (primaryIdentity != nil && primaryIdentity.Revoked(now)) { // user ID has been revoked (for v4 keys) return Key{}, false } From 8781dd323f5f58150fb65d04a5ff5cfb2ab1a9e7 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Mon, 27 Nov 2023 17:02:35 +0100 Subject: [PATCH 103/116] docs(v2): Fix comment issues --- openpgp/v2/write.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpgp/v2/write.go b/openpgp/v2/write.go index 96962db55..2e62509ea 100644 --- a/openpgp/v2/write.go +++ b/openpgp/v2/write.go @@ -394,7 +394,7 @@ func Encrypt(ciphertext io.Writer, to, toHidden []*Entity, signers []*Entity, hi } // writeAndSign writes the data as a payload package and, optionally, signs -// it. hints contains optional information, that is also encrypted, +// it. Hints contains optional information, that is also encrypted, // that aids the recipients in processing the message. The resulting // WriteCloser must be closed after the contents of the file have been // written. If config is nil, sensible defaults will be used. @@ -784,7 +784,7 @@ func SignWithParams(output io.Writer, signers []*Entity, params *SignParams) (in } // Sign signs a message. The resulting WriteCloser must be closed after the -// contents of the file have been written. Hints contains optional information +// contents of the file have been written. Hints contains optional information // that aids the recipients in processing the message. // The crypto-refresh recommends to not set file hints since the data is not included in the signature hash. // See https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-12.html#section-5.9. From f5a3bd6c529f3ba7d8ceedfc3f8f8cb7427a9567 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Thu, 30 Nov 2023 15:27:20 +0100 Subject: [PATCH 104/116] feat(v2): Allow to encrypt a message to additional passwords --- openpgp/v2/write.go | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/openpgp/v2/write.go b/openpgp/v2/write.go index 2e62509ea..c53decbf0 100644 --- a/openpgp/v2/write.go +++ b/openpgp/v2/write.go @@ -216,12 +216,15 @@ type EncryptParams struct { // If nil, default is used. Hints *FileHints // SiningEntities contains the private keys to produce signatures with - // If nil, no signatures are created + // If nil, no signatures are created. Signers []*Entity - // TextSig indicates if signatures of type SigTypeText should be produced + // TextSig indicates if signatures of type SigTypeText should be produced. TextSig bool + // Passwords defines additional passwords that the message should be encrypted to. + // i.e., for each defined password an additional SKESK packet is written. + Passwords [][]byte // SessionKey provides a session key to be used for encryption. - // If nil, a one-time session key is generated + // If nil, a one-time session key is generated. SessionKey []byte // OutsideSig allows to set a signature that should be included // in the message to encrypt. @@ -282,6 +285,12 @@ func symmetricallyEncrypt(passphrase []byte, dataWriter io.Writer, params *Encry if err != nil { return } + for _, additionalPassword := range params.Passwords { + if err = packet.SerializeSymmetricKeyEncryptedReuseKey(params.KeyWriter, params.SessionKey, additionalPassword, params.Config); err != nil { + return + } + } + config := params.Config candidateCompression := []uint8{uint8(config.Compression())} cipherSuite := packet.CipherSuite{ @@ -676,6 +685,12 @@ func encrypt( } } + for _, password := range params.Passwords { + if err = packet.SerializeSymmetricKeyEncryptedReuseKey(params.KeyWriter, params.SessionKey, password, params.Config); err != nil { + return nil, err + } + } + var candidateHashesPerSignature [][]uint8 for range params.Signers { candidateHashesPerSignature = append(candidateHashesPerSignature, candidateHashes) From eafe5628a168bdd43fba8362ad3232c1c7765008 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Mon, 11 Dec 2023 14:32:42 +0100 Subject: [PATCH 105/116] docs(ed448): Fix wrong curve names --- openpgp/ed448/ed448.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpgp/ed448/ed448.go b/openpgp/ed448/ed448.go index 2db1208d0..b11fb4fb1 100644 --- a/openpgp/ed448/ed448.go +++ b/openpgp/ed448/ed448.go @@ -32,7 +32,7 @@ type PrivateKey struct { Key []byte } -// NewPublicKey creates a new empty ed25519 public key. +// NewPublicKey creates a new empty ed448 public key. func NewPublicKey() *PublicKey { return &PublicKey{} } @@ -44,7 +44,7 @@ func NewPrivateKey(key PublicKey) *PrivateKey { } } -// Seed returns the ed25519 private key secret seed. +// Seed returns the ed448 private key secret seed. // The private key representation by RFC 8032. func (pk *PrivateKey) Seed() []byte { return pk.Key[:SeedSize] From 008cc9d5fed5dd462a4978f771074ce460425dc8 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Mon, 11 Dec 2023 14:57:38 +0100 Subject: [PATCH 106/116] Apply suggestions from code review Co-authored-by: Aron Wussler --- openpgp/packet/encrypted_key.go | 8 +++----- openpgp/read.go | 2 +- openpgp/s2k/s2k_config.go | 2 +- openpgp/v2/canonical_text.go | 2 +- openpgp/v2/keys.go | 2 +- openpgp/v2/read.go | 4 ++-- openpgp/v2/subkeys.go | 2 +- openpgp/v2/user.go | 2 +- openpgp/v2/write.go | 6 +++--- 9 files changed, 14 insertions(+), 16 deletions(-) diff --git a/openpgp/packet/encrypted_key.go b/openpgp/packet/encrypted_key.go index ef93a60df..1681d5d86 100644 --- a/openpgp/packet/encrypted_key.go +++ b/openpgp/packet/encrypted_key.go @@ -36,7 +36,7 @@ type EncryptedKey struct { encryptedMPI1, encryptedMPI2 encoding.Field ephemeralPublicX25519 *x25519.PublicKey // used for x25519 ephemeralPublicX448 *x448.PublicKey // used for x448 - encryptedSession []byte // used for x25519 and Ed448 + encryptedSession []byte // used for x25519 and x448 } func (e *EncryptedKey) parse(r io.Reader) (err error) { @@ -525,8 +525,7 @@ func serializeEncryptedKeyX25519(w io.Writer, rand io.Reader, header []byte, pub if err != nil { return err } - err = x25519.EncodeFields(w, ephemeralPublicX25519, ciphertext, cipherFunc, version == 6) - return err + return x25519.EncodeFields(w, ephemeralPublicX25519, ciphertext, cipherFunc, version == 6) } func serializeEncryptedKeyX448(w io.Writer, rand io.Reader, header []byte, pub *x448.PublicKey, keyBlock []byte, cipherFunc byte, version int) error { @@ -547,8 +546,7 @@ func serializeEncryptedKeyX448(w io.Writer, rand io.Reader, header []byte, pub * if err != nil { return err } - err = x448.EncodeFields(w, ephemeralPublicX448, ciphertext, cipherFunc, version == 6) - return err + return x448.EncodeFields(w, ephemeralPublicX448, ciphertext, cipherFunc, version == 6) } func checksumKeyMaterial(key []byte) uint16 { diff --git a/openpgp/read.go b/openpgp/read.go index 8b3970a93..570244eb8 100644 --- a/openpgp/read.go +++ b/openpgp/read.go @@ -47,7 +47,7 @@ type MessageDetails struct { DecryptedWith Key // the private key used to decrypt the message, if any. IsSigned bool // true if the message is signed. SignedByKeyId uint64 // the key id of the signer, if any. - SignedByFingerprint []byte // the key fingerprint of the signer, if any. (only v6) + SignedByFingerprint []byte // the key fingerprint of the signer, if any. SignedBy *Key // the key of the signer, if available. LiteralData *packet.LiteralData // the metadata of the contents UnverifiedBody io.Reader // the contents of the message. diff --git a/openpgp/s2k/s2k_config.go b/openpgp/s2k/s2k_config.go index 1a6b7f141..b93db1ab8 100644 --- a/openpgp/s2k/s2k_config.go +++ b/openpgp/s2k/s2k_config.go @@ -50,7 +50,7 @@ type Config struct { type Argon2Config struct { NumberOfPasses uint8 DegreeOfParallelism uint8 - // The memory parameter for Argon2 specifies desired memory usage in kibibytes. + // Memory specifies the desired Argon2 memory usage in kibibytes. // For example memory=64*1024 sets the memory cost to ~64 MB. Memory uint32 } diff --git a/openpgp/v2/canonical_text.go b/openpgp/v2/canonical_text.go index 5c2f36213..d4de39ab4 100644 --- a/openpgp/v2/canonical_text.go +++ b/openpgp/v2/canonical_text.go @@ -111,7 +111,7 @@ func (tr *canonicalTextReader) Read(buf []byte) (int, error) { return n, err } if _, err = writeCanonical(tr.buffer, buf[:n], &tr.s); err != nil { - return 0, err + return n, err } return tr.buffer.Read(buf) } diff --git a/openpgp/v2/keys.go b/openpgp/v2/keys.go index 61bdb027d..cfede4409 100644 --- a/openpgp/v2/keys.go +++ b/openpgp/v2/keys.go @@ -324,7 +324,7 @@ func (e *Entity) DecryptPrivateKeys(passphrase []byte) error { return packet.DecryptPrivateKeys(keysToDecrypt, passphrase) } -// An EntityList contains one or more Entities. +// EntityList contains one or more Entities. type EntityList []*Entity // KeysById returns the set of keys that have the given key id. diff --git a/openpgp/v2/read.go b/openpgp/v2/read.go index 351b88e80..a1a916857 100644 --- a/openpgp/v2/read.go +++ b/openpgp/v2/read.go @@ -473,7 +473,7 @@ func (cr checkReader) Read(buf []byte) (int, error) { break } if err != nil { - return 0, err + return n, err } } if cr.md.decrypted != nil { @@ -519,7 +519,7 @@ func (scr *signatureCheckReader) Read(buf []byte) (int, error) { break } if err != nil { - return 0, errors.StructuralError("parsing error") + return n, errors.StructuralError("parsing error") } if sig, ok := p.(*packet.Signature); ok { if sig.Version == 5 && scr.md.LiteralData != nil && (sig.SigType == 0x00 || sig.SigType == 0x01) { diff --git a/openpgp/v2/subkeys.go b/openpgp/v2/subkeys.go index c3063ccb2..a1435e14e 100644 --- a/openpgp/v2/subkeys.go +++ b/openpgp/v2/subkeys.go @@ -8,7 +8,7 @@ import ( "github.com/ProtonMail/go-crypto/openpgp/packet" ) -// A Subkey is an additional public key in an Entity. Subkeys can be used for +// Subkey is an additional public key in an Entity. Subkeys can be used for // encryption. type Subkey struct { Primary *Entity diff --git a/openpgp/v2/user.go b/openpgp/v2/user.go index 1b075eb22..10f5199a6 100644 --- a/openpgp/v2/user.go +++ b/openpgp/v2/user.go @@ -8,7 +8,7 @@ import ( "github.com/ProtonMail/go-crypto/openpgp/packet" ) -// An Identity represents an identity claimed by an Entity and zero or more +// Identity represents an identity claimed by an Entity and zero or more // assertions by other entities about that claim. type Identity struct { Primary *Entity diff --git a/openpgp/v2/write.go b/openpgp/v2/write.go index c53decbf0..b1626b0c1 100644 --- a/openpgp/v2/write.go +++ b/openpgp/v2/write.go @@ -224,7 +224,7 @@ type EncryptParams struct { // i.e., for each defined password an additional SKESK packet is written. Passwords [][]byte // SessionKey provides a session key to be used for encryption. - // If nil, a one-time session key is generated. + // If nil, a random one-time session key is generated. SessionKey []byte // OutsideSig allows to set a signature that should be included // in the message to encrypt. @@ -253,7 +253,7 @@ func SymmetricallyEncrypt(ciphertext io.Writer, passphrase []byte, hints *FileHi }) } -// SymmetricallyEncryptWithParams acts like SymmetricallyEncrypt: but provides more configuration options +// SymmetricallyEncryptWithParams acts like SymmetricallyEncrypt but provides more configuration options. // EncryptParams provides the optional parameters. // The resulting WriteCloser must be closed after the contents of the file have been written. func SymmetricallyEncryptWithParams(passphrase []byte, dataWriter io.Writer, params *EncryptParams) (plaintext io.WriteCloser, err error) { @@ -345,7 +345,7 @@ func intersectPreferences(a []uint8, b []uint8) (intersection []uint8) { return a[:j] } -// intersectPreferences mutates and returns a prefix of a that contains only +// intersectCipherSuites mutates and returns a prefix of a that contains only // the values in the intersection of a and b. The order of a is preserved. func intersectCipherSuites(a [][2]uint8, b [][2]uint8) (intersection [][2]uint8) { var j int From f2990fa8ed1a3dbf978e0e40228e7900a1bfea8f Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Mon, 11 Dec 2023 15:13:36 +0100 Subject: [PATCH 107/116] fix(v2): Address review comments in the write api --- openpgp/v2/write.go | 6 +++--- openpgp/write_test.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openpgp/v2/write.go b/openpgp/v2/write.go index b1626b0c1..4c02e04a9 100644 --- a/openpgp/v2/write.go +++ b/openpgp/v2/write.go @@ -111,8 +111,8 @@ type detachSignContext struct { func (s detachSignWriter) Write(data []byte) (int, error) { for _, signature := range s.signatures { - if _, err := signature.wrappedHash.Write(data); err != nil { - return 0, err + if n, err := signature.wrappedHash.Write(data); err != nil { + return n, err } } return len(data), nil @@ -1024,7 +1024,7 @@ func acceptableHashesToWrite(singingKey *packet.PublicKey) []uint8 { hashToHashId(crypto.SHA512), hashToHashId(crypto.SHA3_512), } - case packet.PubKeyAlgoECDSA: + case packet.PubKeyAlgoECDSA, packet.PubKeyAlgoEdDSA: if curve, err := singingKey.Curve(); err == nil { if curve == packet.Curve448 || curve == packet.CurveNistP521 || diff --git a/openpgp/write_test.go b/openpgp/write_test.go index 2ebeff955..9e3d36b4e 100644 --- a/openpgp/write_test.go +++ b/openpgp/write_test.go @@ -352,7 +352,7 @@ func TestSymmetricEncryption(t *testing.T) { } } -func TestSymmetricEncryptionV5RandomizeSlow(t *testing.T) { +func TestSymmetricEncryptionSEIPDv2RandomizeSlow(t *testing.T) { modesS2K := map[int]s2k.Mode{ 0: s2k.IteratedSaltedS2K, 1: s2k.Argon2S2K, From ae457d10707840747bb0bea0c4bd81f35359cd17 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Mon, 11 Dec 2023 15:23:40 +0100 Subject: [PATCH 108/116] docs(v2): Improve comments in the read api --- openpgp/v2/read.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openpgp/v2/read.go b/openpgp/v2/read.go index a1a916857..f8361233c 100644 --- a/openpgp/v2/read.go +++ b/openpgp/v2/read.go @@ -3,7 +3,7 @@ // license that can be found in the LICENSE file. // Package openpgp implements high level operations on OpenPGP messages. -package v2 // import "github.com/ProtonMail/go-crypto/openpgp" +package v2 import ( "bytes" @@ -280,7 +280,7 @@ type SignatureCandidate struct { HashAlgorithm crypto.Hash PubKeyAlgo packet.PublicKeyAlgorithm IssuerKeyId uint64 - IssuerFingerprint []byte // v6 only + IssuerFingerprint []byte Salt []byte // v6 only SignedByEntity *Entity @@ -430,9 +430,9 @@ func wrapHashForSignature(hashFunc hash.Hash, sigType packet.SignatureType) (has // hashForSignature returns a pair of hashes that can be used to verify a // signature. The signature may specify that the contents of the signed message // should be preprocessed (i.e. to normalize line endings). Thus this function -// returns two hashes. The second should be used to hash the message itself and -// performs any needed preprocessing. -func hashForSignature(hashFunc crypto.Hash, sigType packet.SignatureType, sigSalt []byte) (hash.Hash, hash.Hash, error) { +// returns two hashes. The first, directHash, will feed directly into the signature algorithm. +// The second, wrappedHash, should be used to hash the message itself and performs any needed preprocessing. +func hashForSignature(hashFunc crypto.Hash, sigType packet.SignatureType, sigSalt []byte) (directHash hash.Hash, wrappedHash hash.Hash, err error) { if _, ok := algorithm.HashToHashIdWithSha1(hashFunc); !ok { return nil, nil, errors.UnsupportedError("unsupported hash function") } @@ -443,7 +443,7 @@ func hashForSignature(hashFunc crypto.Hash, sigType packet.SignatureType, sigSal if sigSalt != nil { h.Write(sigSalt) } - wrappedHash, err := wrapHashForSignature(h, sigType) + wrappedHash, err = wrapHashForSignature(h, sigType) if err != nil { return nil, nil, err } From 0a030a354616ce1bec73a25ce8e2088acd9727ed Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Mon, 11 Dec 2023 15:26:56 +0100 Subject: [PATCH 109/116] refactor: Remove depricated tests --- openpgp/keys_v5_test.go | 105 ------------------------------------ openpgp/v2/keys_test.go | 80 --------------------------- openpgp/v2/keys_v5_test.go | 107 ------------------------------------- 3 files changed, 292 deletions(-) diff --git a/openpgp/keys_v5_test.go b/openpgp/keys_v5_test.go index d285fed31..36b83a4a7 100644 --- a/openpgp/keys_v5_test.go +++ b/openpgp/keys_v5_test.go @@ -20,89 +20,6 @@ func TestReadPrivateForeignV5Key(t *testing.T) { } } -// Deprecated -/* -// TODO: Replace message with a correctly generated one. -func testV5ForeignSignedMessage(t *testing.T) { - kring, err := ReadArmoredKeyRing(strings.NewReader(v5PrivKey)) - if err != nil { - t.Fatal(err) - } - msg := strings.NewReader(v5PrivKeyMsg) - // Unarmor - block, err := armor.Decode(msg) - if err != nil { - return - } - md, err := ReadMessage(block.Body, kring, nil, nil) - if md.SignedBy == nil { - t.Fatal("incorrect signer") - } - if err != nil { - t.Fatal(err) - } - // Consume UnverifiedBody - body, err := ioutil.ReadAll(md.UnverifiedBody) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(body, []byte("test")) { - t.Fatal("bad body") - } - if md.SignatureError != nil { - t.Fatal(md.SignatureError) - } - if err != nil { - t.Fatal(err) - } -} - -func TestReadPrivateEncryptedV5Key(t *testing.T) { - c := &packet.Config{V5Keys: true} - e, err := NewEntity("V5 Key Owner", "V5 Key", "v5@pm.me", c) - if err != nil { - t.Fatal(err) - } - password := []byte("test v5 key # password") - // Encrypt private key - if err = e.PrivateKey.Encrypt(password); err != nil { - t.Fatal(err) - } - // Encrypt subkeys - for _, sub := range e.Subkeys { - if err = sub.PrivateKey.Encrypt(password); err != nil { - t.Fatal(err) - } - } - // Serialize, Read - serializedEntity := bytes.NewBuffer(nil) - err = e.SerializePrivateWithoutSigning(serializedEntity, nil) - if err != nil { - t.Fatal(err) - } - el, err := ReadKeyRing(serializedEntity) - if err != nil { - t.Fatal(err) - } - - // Decrypt - if el[0].PrivateKey == nil { - t.Fatal("No private key found") - } - if err = el[0].PrivateKey.Decrypt(password); err != nil { - t.Error(err) - } - - // Decrypt subkeys - for _, sub := range e.Subkeys { - if err = sub.PrivateKey.Decrypt(password); err != nil { - t.Error(err) - } - } - - checkV5Key(t, el[0]) -}*/ - func TestReadPrivateSerializeForeignV5Key(t *testing.T) { for _, str := range foreignKeys { el, err := ReadArmoredKeyRing(strings.NewReader(str)) @@ -113,28 +30,6 @@ func TestReadPrivateSerializeForeignV5Key(t *testing.T) { } } -// Deprecated -/* -func TestNewEntitySerializeV5Key(t *testing.T) { - c := &packet.Config{V5Keys: true} - e, err := NewEntity("V5 Key Owner", "V5 Key", "v5@pm.me", c) - if err != nil { - t.Fatal(err) - } - checkSerializeRead(t, e) -} - -func TestNewEntityV5Key(t *testing.T) { - c := &packet.Config{ - V5Keys: true, - } - e, err := NewEntity("V5 Key Owner", "V5 Key", "v5@pm.me", c) - if err != nil { - t.Fatal(err) - } - checkV5Key(t, e) -}*/ - func checkV5Key(t *testing.T, ent *Entity) { key := ent.PrimaryKey if key.Version != 5 { diff --git a/openpgp/v2/keys_test.go b/openpgp/v2/keys_test.go index 6f037f5fe..da6b234fd 100644 --- a/openpgp/v2/keys_test.go +++ b/openpgp/v2/keys_test.go @@ -685,67 +685,6 @@ func TestKeyWithSubKeyAndBadSelfSigOrder(t *testing.T) { } -/*func TestKeyUsage(t *testing.T) { - kring, err := ReadKeyRing(readerFromHex(subkeyUsageHex)) - if err != nil { - t.Fatal(err) - } - - // subkeyUsageHex contains these keys: - // pub 1024R/2866382A created: 2014-04-01 expires: never usage: SC - // sub 1024R/936C9153 created: 2014-04-01 expires: never usage: E - // sub 1024R/64D5F5BB created: 2014-04-02 expires: never usage: E - // sub 1024D/BC0BA992 created: 2014-04-02 expires: never usage: S - certifiers := []uint64{0xA42704B92866382A} - signers := []uint64{0xA42704B92866382A, 0x42CE2C64BC0BA992} - encrypters := []uint64{0x09C0C7D9936C9153, 0xC104E98664D5F5BB} - - for _, id := range certifiers { - keys := kring.KeysByIdUsage(id, packet.KeyFlagCertify) - if len(keys) == 1 { - if keys[0].PublicKey.KeyId != id { - t.Errorf("Expected to find certifier key id %X, but got %X", id, keys[0].PublicKey.KeyId) - } - } else { - t.Errorf("Expected one match for certifier key id %X, but got %d matches", id, len(keys)) - } - } - - for _, id := range signers { - keys := kring.KeysByIdUsage(id, packet.KeyFlagSign) - if len(keys) == 1 { - if keys[0].PublicKey.KeyId != id { - t.Errorf("Expected to find signing key id %X, but got %X", id, keys[0].PublicKey.KeyId) - } - } else { - t.Errorf("Expected one match for signing key id %X, but got %d matches", id, len(keys)) - } - - // This keyring contains no encryption keys that are also good for signing. - keys = kring.KeysByIdUsage(id, packet.KeyFlagEncryptStorage|packet.KeyFlagEncryptCommunications) - if len(keys) != 0 { - t.Errorf("Unexpected match for encryption key id %X", id) - } - } - - for _, id := range encrypters { - keys := kring.KeysByIdUsage(id, packet.KeyFlagEncryptStorage|packet.KeyFlagEncryptCommunications) - if len(keys) == 1 { - if keys[0].PublicKey.KeyId != id { - t.Errorf("Expected to find encryption key id %X, but got %X", id, keys[0].PublicKey.KeyId) - } - } else { - t.Errorf("Expected one match for encryption key id %X, but got %d matches", id, len(keys)) - } - - // This keyring contains no encryption keys that are also good for signing. - keys = kring.KeysByIdUsage(id, packet.KeyFlagSign) - if len(keys) != 0 { - t.Errorf("Unexpected match for signing key id %X", id) - } - } -}*/ - func TestIdVerification(t *testing.T) { kring, err := ReadKeyRing(readerFromHex(testKeys1And2PrivateHex)) if err != nil { @@ -1464,25 +1403,6 @@ func TestRevokeSubkey(t *testing.T) { } } -/*func TestRevokeSubkeyWithAnotherEntity(t *testing.T) { - entity, err := NewEntity("Golang Gopher", "Test Key", "no-reply@golang.com", nil) - if err != nil { - t.Fatal(err) - } - - sk := entity.Subkeys[0] - - newEntity, err := NewEntity("Golang Gopher", "Test Key", "no-reply@golang.com", nil) - if err != nil { - t.Fatal(err) - } - - err = newEntity.RevokeSubkey(&sk, packet.NoReason, "Key revocation", nil) - if err == nil { - t.Fatal("Entity was able to revoke a subkey owned by a different entity") - } -}*/ - func TestRevokeSubkeyWithInvalidSignature(t *testing.T) { entity, err := NewEntity("Golang Gopher", "Test Key", "no-reply@golang.com", nil) if err != nil { diff --git a/openpgp/v2/keys_v5_test.go b/openpgp/v2/keys_v5_test.go index cf72a387f..e90e482df 100644 --- a/openpgp/v2/keys_v5_test.go +++ b/openpgp/v2/keys_v5_test.go @@ -20,91 +20,6 @@ func TestReadPrivateForeignV5Key(t *testing.T) { } } -// Deprecated -/* -func testV5ForeignSignedMessage(t *testing.T) { - kring, err := ReadArmoredKeyRing(strings.NewReader(v5PrivKey)) - if err != nil { - t.Fatal(err) - } - msg := strings.NewReader(v5PrivKeyMsg) - // Unarmor - block, err := armor.Decode(msg) - if err != nil { - return - } - md, err := ReadMessage(block.Body, kring, nil, nil) - if len(md.SignatureCandidates) < 1 { - t.Fatal("no signature found") - } - if md.SignatureCandidates[0].SignedBy == nil { - t.Fatal("incorrect signer") - } - if err != nil { - t.Fatal(err) - } - // Consume UnverifiedBody - body, err := ioutil.ReadAll(md.UnverifiedBody) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(body, []byte("test")) { - t.Fatal("bad body") - } - if md.SignatureError != nil { - t.Fatal(md.SignatureError) - } - if err != nil { - t.Fatal(err) - } -} - -func TestReadPrivateEncryptedV5Key(t *testing.T) { - c := &packet.Config{V5Keys: true} - e, err := NewEntity("V5 Key Owner", "V5 Key", "v5@pm.me", c) - if err != nil { - t.Fatal(err) - } - password := []byte("test v5 key # password") - // Encrypt private key - if err = e.PrivateKey.Encrypt(password); err != nil { - t.Fatal(err) - } - // Encrypt subkeys - for _, sub := range e.Subkeys { - if err = sub.PrivateKey.Encrypt(password); err != nil { - t.Fatal(err) - } - } - // Serialize, Read - serializedEntity := bytes.NewBuffer(nil) - err = e.SerializePrivateWithoutSigning(serializedEntity, nil) - if err != nil { - t.Fatal(err) - } - el, err := ReadKeyRing(serializedEntity) - if err != nil { - t.Fatal(err) - } - - // Decrypt - if el[0].PrivateKey == nil { - t.Fatal("No private key found") - } - if err = el[0].PrivateKey.Decrypt(password); err != nil { - t.Error(err) - } - - // Decrypt subkeys - for _, sub := range e.Subkeys { - if err = sub.PrivateKey.Decrypt(password); err != nil { - t.Error(err) - } - } - - checkV5Key(t, el[0]) -}*/ - func TestReadPrivateSerializeForeignV5Key(t *testing.T) { for _, str := range foreignKeys { el, err := ReadArmoredKeyRing(strings.NewReader(str)) @@ -115,28 +30,6 @@ func TestReadPrivateSerializeForeignV5Key(t *testing.T) { } } -// Deprecated -/* -func TestNewEntitySerializeV5Key(t *testing.T) { - c := &packet.Config{V5Keys: true} - e, err := NewEntity("V5 Key Owner", "V5 Key", "v5@pm.me", c) - if err != nil { - t.Fatal(err) - } - checkSerializeRead(t, e) -} - -func TestNewEntityV5Key(t *testing.T) { - c := &packet.Config{ - V5Keys: true, - } - e, err := NewEntity("V5 Key Owner", "V5 Key", "v5@pm.me", c) - if err != nil { - t.Fatal(err) - } - checkV5Key(t, e) -}*/ - func checkV5Key(t *testing.T, ent *Entity) { key := ent.PrimaryKey if key.Version != 5 { From 2cc531dbe4cc3bc2a3ce756a7ec7e457d98708d7 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Mon, 11 Dec 2023 15:34:55 +0100 Subject: [PATCH 110/116] docs(v2): Remove old v1 comment in keys --- openpgp/v2/keys.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpgp/v2/keys.go b/openpgp/v2/keys.go index cfede4409..77e00375c 100644 --- a/openpgp/v2/keys.go +++ b/openpgp/v2/keys.go @@ -201,7 +201,6 @@ func (e *Entity) SigningKeyById(now time.Time, id uint64, config *packet.Config) } func (e *Entity) signingKeyByIdUsage(now time.Time, id uint64, flags int, config *packet.Config) (Key, error) { - // Fail to find any signing key if the... primarySelfSignature, err := e.VerifyPrimaryKey(now) if err != nil { return Key{}, err @@ -212,7 +211,7 @@ func (e *Entity) signingKeyByIdUsage(now time.Time, id uint64, flags int, config return Key{}, err } - // Iterate the keys to find the newest, unexpired one + // Iterate the keys to find the newest, unexpired one. candidateSubkey := -1 var maxTime time.Time var selectedSubkeySelfSig *packet.Signature From 9cf78f4463f989d474f95545821e629e4a40b20e Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Mon, 11 Dec 2023 15:38:16 +0100 Subject: [PATCH 111/116] fix(packet): Use constant time copy with secrets --- openpgp/packet/private_key.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpgp/packet/private_key.go b/openpgp/packet/private_key.go index eef631200..852e484c9 100644 --- a/openpgp/packet/private_key.go +++ b/openpgp/packet/private_key.go @@ -12,6 +12,7 @@ import ( "crypto/rsa" "crypto/sha1" "crypto/sha256" + "crypto/subtle" "fmt" "io" "io/ioutil" @@ -975,7 +976,7 @@ func (pk *PrivateKey) parseX25519PrivateKey(data []byte) (err error) { err = errors.StructuralError("wrong X25519 key size") return err } - copy(privateKey.Secret, data) + subtle.ConstantTimeCopy(1, privateKey.Secret, data) if err = x25519.Validate(privateKey); err != nil { return err } @@ -994,7 +995,7 @@ func (pk *PrivateKey) parseX448PrivateKey(data []byte) (err error) { err = errors.StructuralError("wrong x448 key size") return err } - copy(privateKey.Secret, data) + subtle.ConstantTimeCopy(1, privateKey.Secret, data) if err = x448.Validate(privateKey); err != nil { return err } From 0b527c66ab238c771f59ea93a167acc93217e374 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Mon, 11 Dec 2023 15:55:37 +0100 Subject: [PATCH 112/116] docs: Unify x448 and x25119 naming in comments and strings --- openpgp/internal/ecc/curve_info.go | 3 ++- openpgp/key_generation.go | 4 ++-- openpgp/packet/encrypted_key.go | 4 ++-- openpgp/packet/private_key.go | 2 +- openpgp/v2/key_generation.go | 2 +- openpgp/x25519/x25519.go | 10 +++++----- openpgp/x448/x448.go | 2 +- 7 files changed, 14 insertions(+), 13 deletions(-) diff --git a/openpgp/internal/ecc/curve_info.go b/openpgp/internal/ecc/curve_info.go index 35751034d..97f891ffc 100644 --- a/openpgp/internal/ecc/curve_info.go +++ b/openpgp/internal/ecc/curve_info.go @@ -4,6 +4,7 @@ package ecc import ( "bytes" "crypto/elliptic" + "github.com/ProtonMail/go-crypto/bitcurves" "github.com/ProtonMail/go-crypto/brainpool" "github.com/ProtonMail/go-crypto/openpgp/internal/encoding" @@ -47,7 +48,7 @@ var Curves = []CurveInfo{ Curve: NewCurve25519(), }, { - // X448 + // x448 GenName: "Curve448", Oid: encoding.NewOID([]byte{0x2B, 0x65, 0x6F}), Curve: NewX448(), diff --git a/openpgp/key_generation.go b/openpgp/key_generation.go index 127d72a4f..a40e45bee 100644 --- a/openpgp/key_generation.go +++ b/openpgp/key_generation.go @@ -346,9 +346,9 @@ func newDecrypter(config *packet.Config) (decrypter interface{}, err error) { return nil, errors.InvalidArgumentError("unsupported curve") } return ecdh.GenerateKey(config.Random(), curve, kdf) - case packet.PubKeyAlgoEd25519, packet.PubKeyAlgoX25519: // When passing Ed25519, we generate an X25519 subkey + case packet.PubKeyAlgoEd25519, packet.PubKeyAlgoX25519: // When passing Ed25519, we generate an x25519 subkey return x25519.GenerateKey(config.Random()) - case packet.PubKeyAlgoEd448, packet.PubKeyAlgoX448: // When passing Ed448, we generate an X448 subkey + case packet.PubKeyAlgoEd448, packet.PubKeyAlgoX448: // When passing Ed448, we generate an x448 subkey return x448.GenerateKey(config.Random()) default: return nil, errors.InvalidArgumentError("unsupported public key algorithm") diff --git a/openpgp/packet/encrypted_key.go b/openpgp/packet/encrypted_key.go index 1681d5d86..e70f9d941 100644 --- a/openpgp/packet/encrypted_key.go +++ b/openpgp/packet/encrypted_key.go @@ -339,7 +339,7 @@ func SerializeEncryptedKeyAEADwithHiddenOption(w io.Writer, pub *PublicKey, ciph if version == 6 && pub.PubKeyAlgo == PubKeyAlgoElGamal { return errors.InvalidArgumentError("ElGamal v6 PKESK are not allowed") } - // In v3 PKESKs, for X25519 and X448, mandate using AES + // In v3 PKESKs, for x25519 and x448, mandate using AES if version == 3 && (pub.PubKeyAlgo == PubKeyAlgoX25519 || pub.PubKeyAlgo == PubKeyAlgoX448) { switch cipherFunc { case CipherAES128, CipherAES192, CipherAES256: @@ -510,7 +510,7 @@ func serializeEncryptedKeyECDH(w io.Writer, rand io.Reader, header []byte, pub * func serializeEncryptedKeyX25519(w io.Writer, rand io.Reader, header []byte, pub *x25519.PublicKey, keyBlock []byte, cipherFunc byte, version int) error { ephemeralPublicX25519, ciphertext, err := x25519.Encrypt(rand, pub, keyBlock) if err != nil { - return errors.InvalidArgumentError("X25519 encryption failed: " + err.Error()) + return errors.InvalidArgumentError("x25519 encryption failed: " + err.Error()) } packetLen := len(header) /* header length */ diff --git a/openpgp/packet/private_key.go b/openpgp/packet/private_key.go index 852e484c9..cff3d5dac 100644 --- a/openpgp/packet/private_key.go +++ b/openpgp/packet/private_key.go @@ -973,7 +973,7 @@ func (pk *PrivateKey) parseX25519PrivateKey(data []byte) (err error) { privateKey.Secret = make([]byte, x25519.KeySize) if len(data) != x25519.KeySize { - err = errors.StructuralError("wrong X25519 key size") + err = errors.StructuralError("wrong x25519 key size") return err } subtle.ConstantTimeCopy(1, privateKey.Secret, data) diff --git a/openpgp/v2/key_generation.go b/openpgp/v2/key_generation.go index 95f46db95..5537d4f84 100644 --- a/openpgp/v2/key_generation.go +++ b/openpgp/v2/key_generation.go @@ -427,7 +427,7 @@ func newDecrypter(config *packet.Config) (decrypter interface{}, err error) { return ecdh.GenerateKey(config.Random(), curve, kdf) case packet.PubKeyAlgoEd25519, packet.PubKeyAlgoX25519: // When passing Ed25519, we generate an X25519 subkey return x25519.GenerateKey(config.Random()) - case packet.PubKeyAlgoEd448, packet.PubKeyAlgoX448: // When passing Ed448, we generate an X448 subkey + case packet.PubKeyAlgoEd448, packet.PubKeyAlgoX448: // When passing Ed448, we generate an x448 subkey return x448.GenerateKey(config.Random()) default: return nil, errors.InvalidArgumentError("unsupported public key algorithm") diff --git a/openpgp/x25519/x25519.go b/openpgp/x25519/x25519.go index c83437c84..38afcc74f 100644 --- a/openpgp/x25519/x25519.go +++ b/openpgp/x25519/x25519.go @@ -165,8 +165,8 @@ func EncodedFieldsLength(encryptedSessionKey []byte, v6 bool) int { return KeySize + 1 + len(encryptedSessionKey) + lenCipherFunction } -// EncodeField encodes X25519 session key encryption fields as -// ephemeral X25519 public key | follow byte length | cipherFunction (v3 only) | encryptedSessionKey +// EncodeField encodes x25519 session key encryption fields as +// ephemeral x25519 public key | follow byte length | cipherFunction (v3 only) | encryptedSessionKey // and writes it to writer. func EncodeFields(writer io.Writer, ephemeralPublicKey *PublicKey, encryptedSessionKey []byte, cipherFunction byte, v6 bool) (err error) { lenAlgorithm := 0 @@ -188,14 +188,14 @@ func EncodeFields(writer io.Writer, ephemeralPublicKey *PublicKey, encryptedSess return err } -// DecodeField decodes a X25519 session key encryption as -// ephemeral X25519 public key | follow byte length | cipherFunction (v3 only) | encryptedSessionKey. +// DecodeField decodes a x25519 session key encryption as +// ephemeral x25519 public key | follow byte length | cipherFunction (v3 only) | encryptedSessionKey. func DecodeFields(reader io.Reader, v6 bool) (ephemeralPublicKey *PublicKey, encryptedSessionKey []byte, cipherFunction byte, err error) { var buf [1]byte ephemeralPublicKey = &PublicKey{ Point: make([]byte, KeySize), } - // 32 octets representing an ephemeral X25519 public key. + // 32 octets representing an ephemeral x25519 public key. if _, err = io.ReadFull(reader, ephemeralPublicKey.Point); err != nil { return nil, nil, 0, err } diff --git a/openpgp/x448/x448.go b/openpgp/x448/x448.go index c8cbeca74..65a082dab 100644 --- a/openpgp/x448/x448.go +++ b/openpgp/x448/x448.go @@ -203,7 +203,7 @@ func DecodeFields(reader io.Reader, v6 bool) (ephemeralPublicKey *PublicKey, enc ephemeralPublicKey = &PublicKey{ Point: make([]byte, KeySize), } - // 56 octets representing an ephemeral X448 public key. + // 56 octets representing an ephemeral x448 public key. if _, err = io.ReadFull(reader, ephemeralPublicKey.Point); err != nil { return nil, nil, 0, err } From a09d905914a13054ebc1ddd5dbe4c65c82d26d11 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Fri, 5 Jan 2024 14:16:18 +0100 Subject: [PATCH 113/116] fix: Fix metadata in hash suffix for v5 signatures --- openpgp/v2/read.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/openpgp/v2/read.go b/openpgp/v2/read.go index f8361233c..a8ed93493 100644 --- a/openpgp/v2/read.go +++ b/openpgp/v2/read.go @@ -411,7 +411,7 @@ FindLiteralData: if md.IsSigned { md.UnverifiedBody = &signatureCheckReader{packets, md, config, md.LiteralData.Body} } else { - md.UnverifiedBody = checkReader{md, packets} + md.UnverifiedBody = &checkReader{md, packets, false} } return md, nil @@ -462,11 +462,15 @@ func hashForSignature(hashFunc crypto.Hash, sigType packet.SignatureType, sigSal type checkReader struct { md *MessageDetails packets packet.PacketReader + checked bool } -func (cr checkReader) Read(buf []byte) (int, error) { +func (cr *checkReader) Read(buf []byte) (int, error) { n, sensitiveParsingError := cr.md.LiteralData.Body.Read(buf) if sensitiveParsingError == io.EOF { + if cr.checked { + return n, io.EOF + } for { _, err := cr.packets.Next() if err == io.EOF { @@ -481,6 +485,7 @@ func (cr checkReader) Read(buf []byte) (int, error) { return n, mdcErr } } + cr.checked = true return n, io.EOF } if sensitiveParsingError != nil { From 38191b964781acf25f48c58d6fea47ca5227ddb2 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Fri, 5 Jan 2024 14:17:42 +0100 Subject: [PATCH 114/116] fix: Fix literal metadata in hash suffix for v5 signatures --- openpgp/packet/signature.go | 18 ++--- openpgp/read.go | 12 +++- openpgp/read_test.go | 35 +++++++++ openpgp/read_write_test_data.go | 121 ++++++++++++++++++++++++++++++++ openpgp/write.go | 2 +- 5 files changed, 173 insertions(+), 15 deletions(-) diff --git a/openpgp/packet/signature.go b/openpgp/packet/signature.go index 647f7ee45..ff14da318 100644 --- a/openpgp/packet/signature.go +++ b/openpgp/packet/signature.go @@ -754,7 +754,7 @@ func (sig *Signature) buildHashSuffix(hashedSubpackets []byte) (err error) { }) hashedSubpacketsLength := len(hashedSubpackets) if sig.Version == 6 { - // v6 keys store the length in 4 octets + // v6 signatures store the length in 4 octets hashedFields.Write([]byte{ uint8(hashedSubpacketsLength >> 24), uint8(hashedSubpacketsLength >> 16), @@ -1064,18 +1064,16 @@ func (sig *Signature) Serialize(w io.Writer) (err error) { panic("impossible") } + hashedSubpacketsLen := subpacketsLength(sig.outSubpackets, true) unhashedSubpacketsLen := subpacketsLength(sig.outSubpackets, false) - length := len(sig.HashSuffix) - 6 /* trailer not included */ + + length := 4 + /* length of version|signature type|public-key algorithm|hash algorithm */ + 2 /* length of hashed subpackets */ + hashedSubpacketsLen + 2 /* length of unhashed subpackets */ + unhashedSubpacketsLen + 2 /* hash tag */ + sigLength - if sig.Version == 5 { - length -= 4 // eight-octet instead of four-octet big endian - } if sig.Version == 6 { - // unhashed length is four-octet instead - // salt len 1 octet - // len(salt) octets - length += 3 + len(sig.salt) + length += 4 + /* the two length fields are four-octet instead of two */ + 1 + /* salt length */ + len(sig.salt) /* length salt */ } err = serializeHeader(w, packetTypeSignature, length) if err != nil { @@ -1370,8 +1368,6 @@ func (sig *Signature) AddMetadataToHashSuffix() { binary.BigEndian.PutUint32(buf[:], lit.Time) suffix.Write(buf[:]) - // Update the counter and restore trailing bytes - l = uint64(suffix.Len()) suffix.Write([]byte{0x05, 0xff}) suffix.Write([]byte{ uint8(l >> 56), uint8(l >> 48), uint8(l >> 40), uint8(l >> 32), diff --git a/openpgp/read.go b/openpgp/read.go index 570244eb8..ac897d709 100644 --- a/openpgp/read.go +++ b/openpgp/read.go @@ -298,7 +298,7 @@ FindLiteralData: if md.IsSigned && md.SignatureError == nil { md.UnverifiedBody = &signatureCheckReader{packets, h, wrappedHash, md, config} } else if md.decrypted != nil { - md.UnverifiedBody = checkReader{md} + md.UnverifiedBody = &checkReader{md, false} } else { md.UnverifiedBody = md.LiteralData.Body } @@ -349,16 +349,22 @@ func hashForSignature(hashFunc crypto.Hash, sigType packet.SignatureType, sigSal // it closes the ReadCloser from any SymmetricallyEncrypted packet to trigger // MDC checks. type checkReader struct { - md *MessageDetails + md *MessageDetails + checked bool } -func (cr checkReader) Read(buf []byte) (int, error) { +func (cr *checkReader) Read(buf []byte) (int, error) { n, sensitiveParsingError := cr.md.LiteralData.Body.Read(buf) if sensitiveParsingError == io.EOF { + if cr.checked { + // Only check once + return n, io.EOF + } mdcErr := cr.md.decrypted.Close() if mdcErr != nil { return n, mdcErr } + cr.checked = true return n, io.EOF } diff --git a/openpgp/read_test.go b/openpgp/read_test.go index 5a3ef63e3..7652f5829 100644 --- a/openpgp/read_test.go +++ b/openpgp/read_test.go @@ -888,3 +888,38 @@ func TestMessageWithoutMdc(t *testing.T) { } }) } + +func TestReadV5Messages(t *testing.T) { + key, err := ReadArmoredKeyRing(strings.NewReader(keyv5Test)) + if err != nil { + t.Error(err) + return + } + keyVer, err := ReadArmoredKeyRing(strings.NewReader(certv5Test)) + if err != nil { + t.Error(err) + return + } + keys := append(key, keyVer...) + msgReader, err := armor.Decode(strings.NewReader(msgv5Test)) + if err != nil { + t.Error(err) + return + } + md, err := ReadMessage(msgReader.Body, keys, nil, nil) + if err != nil { + t.Error(err) + return + } + contents, err := ioutil.ReadAll(md.UnverifiedBody) + if err != nil { + t.Error(err) + return + } + if string(contents) != "Hello World :)" { + t.Errorf("decrypted message is wrong: %s", contents) + } + if md.SignatureError != nil { + t.Error("expected no signature error, got:", md.SignatureError) + } +} diff --git a/openpgp/read_write_test_data.go b/openpgp/read_write_test_data.go index a7f82bd23..670d60226 100644 --- a/openpgp/read_write_test_data.go +++ b/openpgp/read_write_test_data.go @@ -334,3 +334,124 @@ AtNTq6ihLMD5v1d82ZC7tNatdlDMGWnIdvEMCv2GZcuIqDQ9rXWs49e7tq1NncLY hz3tYjKhoFTKEIq3y3Pp =h/aX -----END PGP PUBLIC KEY BLOCK-----` + +const keyv5Test = `-----BEGIN PGP PRIVATE KEY BLOCK----- +Comment: Bob's OpenPGP Transferable Secret Key + +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 +=miES +-----END PGP PRIVATE KEY BLOCK----- +` + +const certv5Test = `-----BEGIN PGP PRIVATE KEY BLOCK----- + +lGEFXJH05BYAAAAtCSsGAQQB2kcPAQEHQFhZlVcVVtwf+21xNQPX+ecMJJBL0MPd +fj75iux+my8QAAAAAAAiAQCHZ1SnSUmWqxEsoI6facIVZQu6mph3cBFzzTvcm5lA +Ng5ctBhlbW1hLmdvbGRtYW5AZXhhbXBsZS5uZXSIlgUTFggASCIhBRk0e8mHJGQC +X5nfPsLgAA7ZiEiS4fez6kyUAJFZVptUBQJckfTkAhsDBQsJCAcCAyICAQYVCgkI +CwIEFgIDAQIeBwIXgAAA9cAA/jiR3yMsZMeEQ40u6uzEoXa6UXeV/S3wwJAXRJy9 +M8s0AP9vuL/7AyTfFXwwzSjDnYmzS0qAhbLDQ643N+MXGBJ2BZxmBVyR9OQSAAAA +MgorBgEEAZdVAQUBAQdA+nysrzml2UCweAqtpDuncSPlvrcBWKU0yfU0YvYWWAoD +AQgHAAAAAAAiAP9OdAPppjU1WwpqjIItkxr+VPQRT8Zm/Riw7U3F6v3OiBFHiHoF +GBYIACwiIQUZNHvJhyRkAl+Z3z7C4AAO2YhIkuH3s+pMlACRWVabVAUCXJH05AIb +DAAAOSQBAP4BOOIR/sGLNMOfeb5fPs/02QMieoiSjIBnijhob2U5AQC+RtOHCHx7 +TcIYl5/Uyoi+FOvPLcNw4hOv2nwUzSSVAw== +=IiS2 +-----END PGP PRIVATE KEY BLOCK----- +` + +const msgv5Test = `-----BEGIN PGP MESSAGE----- + +wcDMA3wvqk35PDeyAQv+PcQiLsoYTH30nJYQh3j3cJaO2+jErtVCrIQRIU0+ +rmgMddERYST4A9mA0DQIiTI4FQ0Lp440D3BWCgpq3LlNWewGzduaWwym5rN6 +cwHz5ccDqOcqbd9X0GXXGy/ZH/ljSgzuVMIytMAXKdF/vrRrVgH/+I7cxvm9 +HwnhjMN5dF0j4aEt996H2T7cbtzSr2GN9SWGW8Gyu7I8Zx73hgrGUI7gDiJB +Afaff+P6hfkkHSGOItr94dde8J/7AUF4VEwwxdVVPvsNEFyvv6gRIbYtOCa2 +6RE6h1V/QTxW2O7zZgzWALrE2ui0oaYr9QuqQSssd9CdgExLfdPbI+3/ZAnE +v31Idzpk3/6ILiakYHtXkElPXvf46mCNpobty8ysT34irF+fy3C1p3oGwAsx +5VDV9OSFU6z5U+UPbSPYAy9rkc5ZssuIKxCER2oTvZ2L8Q5cfUvEUiJtRGGn +CJlHrVDdp3FssKv2tlKgLkvxJLyoOjuEkj44H1qRk+D02FzmmUT/0sAHAYYx +lTir6mjHeLpcGjn4waUuWIAJyph8SxUexP60bic0L0NBa6Qp5SxxijKsPIDb +FPHxWwfJSDZRrgUyYT7089YFB/ZM4FHyH9TZcnxn0f0xIB7NS6YNDsxzN2zT +EVEYf+De4qT/dQTsdww78Chtcv9JY9r2kDm77dk2MUGHL2j7n8jasbLtgA7h +pn2DMIWLrGamMLWRmlwslolKr1sMV5x8w+5Ias6C33iBMl9phkg42an0gYmc +byVJHvLO/XErtC+GNIJeMg== +=liRq +-----END PGP MESSAGE----- +` diff --git a/openpgp/write.go b/openpgp/write.go index d0b3887fb..0db5526ce 100644 --- a/openpgp/write.go +++ b/openpgp/write.go @@ -333,7 +333,7 @@ func writeAndSign(payload io.WriteCloser, candidateHashes []uint8, signed *Entit return nil, err } metadata := &packet.LiteralData{ - Format: 't', + Format: 'u', FileName: hints.FileName, Time: epochSeconds, } From 75630f21fbad4ada1bdd9f7702e71fd4a08c3033 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Wed, 17 Jan 2024 11:32:59 +0100 Subject: [PATCH 115/116] chore: Update min go version and dependencies --- .github/workflows/go.yml | 4 ++-- go.mod | 8 +++++--- go.sum | 20 +++++++++----------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 48ced7ab8..1a05c9196 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -32,10 +32,10 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Set up Go 1.15 + - name: Set up Go 1.17 uses: actions/setup-go@v3 with: - go-version: 1.15 + go-version: 1.17 - name: Short test run: go test -short -v ./... diff --git a/go.mod b/go.mod index bfce768fd..d417da35c 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,10 @@ module github.com/ProtonMail/go-crypto -go 1.13 +go 1.17 require ( - github.com/cloudflare/circl v1.3.3 - golang.org/x/crypto v0.9.0 + github.com/cloudflare/circl v1.3.7 + golang.org/x/crypto v0.17.0 ) + +require golang.org/x/sys v0.16.0 // indirect diff --git a/go.sum b/go.sum index 8085d85e1..712b2d44b 100644 --- a/go.sum +++ b/go.sum @@ -1,18 +1,16 @@ github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= -github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= -github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= +github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= +github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= -golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -23,22 +21,22 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= From 93256dea4f70afa8cb97b12bae102265e11043cc Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Wed, 17 Jan 2024 11:37:54 +0100 Subject: [PATCH 116/116] refactor: Replacer ioutil.ReadAll with io.ReadAll --- openpgp/armor/armor_test.go | 4 +- openpgp/integration_tests/end_to_end_test.go | 7 ++- .../integration_tests/v2/end_to_end_test.go | 7 ++- openpgp/packet/compressed_test.go | 5 +- openpgp/packet/opaque.go | 3 +- openpgp/packet/packet_test.go | 5 +- openpgp/packet/private_key.go | 3 +- .../packet/symmetric_key_encrypted_test.go | 3 +- .../packet/symmetrically_encrypted_test.go | 7 ++- openpgp/packet/userattribute.go | 3 +- openpgp/packet/userid.go | 3 +- openpgp/read_test.go | 44 +++++++++--------- openpgp/v2/read_test.go | 46 +++++++++---------- openpgp/v2/write_test.go | 17 ++++--- openpgp/write_test.go | 5 +- 15 files changed, 75 insertions(+), 87 deletions(-) diff --git a/openpgp/armor/armor_test.go b/openpgp/armor/armor_test.go index 595612493..861b35473 100644 --- a/openpgp/armor/armor_test.go +++ b/openpgp/armor/armor_test.go @@ -7,7 +7,7 @@ package armor import ( "bytes" "hash/adler32" - "io/ioutil" + "io" "testing" ) @@ -29,7 +29,7 @@ func TestDecodeEncode(t *testing.T) { t.Errorf("result.Header: got:%#v", result.Header) } - contents, err := ioutil.ReadAll(result.Body) + contents, err := io.ReadAll(result.Body) if err != nil { t.Error(err) } diff --git a/openpgp/integration_tests/end_to_end_test.go b/openpgp/integration_tests/end_to_end_test.go index 09b2221bb..303cdd612 100644 --- a/openpgp/integration_tests/end_to_end_test.go +++ b/openpgp/integration_tests/end_to_end_test.go @@ -6,7 +6,6 @@ import ( "bytes" "encoding/json" "io" - "io/ioutil" "os" "strings" "testing" @@ -42,7 +41,7 @@ func TestEndToEnd(t *testing.T) { if err != nil { panic(err) } - raw, err := ioutil.ReadAll(file) + raw, err := io.ReadAll(file) if err != nil { panic(err) } @@ -118,7 +117,7 @@ func decryptionTest(t *testing.T, vector testVector, sk openpgp.EntityList) { t.Fatal(err) } - body, err := ioutil.ReadAll(md.UnverifiedBody) + body, err := io.ReadAll(md.UnverifiedBody) if err != nil { t.Fatal(err) } @@ -218,7 +217,7 @@ func encDecTest(t *testing.T, from testVector, testVectors []testVector) { t.Fatalf("Failed to find the signing Entity") } - plaintext, err := ioutil.ReadAll(md.UnverifiedBody) + plaintext, err := io.ReadAll(md.UnverifiedBody) if err != nil { t.Fatalf("Error reading encrypted contents: %s", err) } diff --git a/openpgp/integration_tests/v2/end_to_end_test.go b/openpgp/integration_tests/v2/end_to_end_test.go index 2ed9e4b2a..d44792375 100644 --- a/openpgp/integration_tests/v2/end_to_end_test.go +++ b/openpgp/integration_tests/v2/end_to_end_test.go @@ -7,7 +7,6 @@ import ( "crypto" "encoding/json" "io" - "io/ioutil" "os" "strings" "testing" @@ -50,7 +49,7 @@ func TestEndToEnd(t *testing.T) { if err != nil { panic(err) } - raw, err := ioutil.ReadAll(file) + raw, err := io.ReadAll(file) if err != nil { panic(err) } @@ -128,7 +127,7 @@ func decryptionTest(t *testing.T, vector testVector, sk openpgp.EntityList) { t.Fatal(err) } - body, err := ioutil.ReadAll(md.UnverifiedBody) + body, err := io.ReadAll(md.UnverifiedBody) if err != nil { t.Fatal(err) } @@ -235,7 +234,7 @@ func encDecTest(t *testing.T, from testVector, testVectors []testVector) { t.Fatalf("Failed to find the signing Entity") } - plaintext, err := ioutil.ReadAll(md.UnverifiedBody) + plaintext, err := io.ReadAll(md.UnverifiedBody) if err != nil { t.Fatalf("Error reading encrypted contents: %s", err) } diff --git a/openpgp/packet/compressed_test.go b/openpgp/packet/compressed_test.go index 5cf3dba90..776d20813 100644 --- a/openpgp/packet/compressed_test.go +++ b/openpgp/packet/compressed_test.go @@ -9,7 +9,6 @@ import ( "crypto/rand" "encoding/hex" "io" - "io/ioutil" mathrand "math/rand" "testing" ) @@ -31,7 +30,7 @@ func TestCompressed(t *testing.T) { return } - contents, err := ioutil.ReadAll(c.Body) + contents, err := io.ReadAll(c.Body) if err != nil && err != io.EOF { t.Error(err) return @@ -75,7 +74,7 @@ func TestCompressDecompressRandomizeFast(t *testing.T) { if !ok { t.Error("didn't find Compressed packet") } - contents, err := ioutil.ReadAll(c.Body) + contents, err := io.ReadAll(c.Body) if err != nil && err != io.EOF { t.Error(err) } diff --git a/openpgp/packet/opaque.go b/openpgp/packet/opaque.go index 4f8204079..cef7c661d 100644 --- a/openpgp/packet/opaque.go +++ b/openpgp/packet/opaque.go @@ -7,7 +7,6 @@ package packet import ( "bytes" "io" - "io/ioutil" "github.com/ProtonMail/go-crypto/openpgp/errors" ) @@ -26,7 +25,7 @@ type OpaquePacket struct { } func (op *OpaquePacket) parse(r io.Reader) (err error) { - op.Contents, err = ioutil.ReadAll(r) + op.Contents, err = io.ReadAll(r) return } diff --git a/openpgp/packet/packet_test.go b/openpgp/packet/packet_test.go index 770b8b5ba..526871df0 100644 --- a/openpgp/packet/packet_test.go +++ b/openpgp/packet/packet_test.go @@ -9,7 +9,6 @@ import ( "encoding/hex" "fmt" "io" - "io/ioutil" "testing" "github.com/ProtonMail/go-crypto/openpgp/errors" @@ -101,7 +100,7 @@ var partialLengthReaderTests = []struct { func TestPartialLengthReader(t *testing.T) { for i, test := range partialLengthReaderTests { r := &partialLengthReader{readerFromHex(test.hexInput), 0, true} - out, err := ioutil.ReadAll(r) + out, err := io.ReadAll(r) if test.err != nil { if err != test.err { t.Errorf("%d: expected different error got:%s want:%s", i, err, test.err) @@ -173,7 +172,7 @@ func TestReadHeader(t *testing.T) { continue } - body, err := ioutil.ReadAll(contents) + body, err := io.ReadAll(contents) if err != nil { if !test.unexpectedEOF || err != io.ErrUnexpectedEOF { t.Errorf("%d: unexpected error from contents: %s", i, err) diff --git a/openpgp/packet/private_key.go b/openpgp/packet/private_key.go index cff3d5dac..099b4d9ba 100644 --- a/openpgp/packet/private_key.go +++ b/openpgp/packet/private_key.go @@ -15,7 +15,6 @@ import ( "crypto/subtle" "fmt" "io" - "io/ioutil" "math/big" "strconv" "time" @@ -312,7 +311,7 @@ func (pk *PrivateKey) parse(r io.Reader) (err error) { return } } else { - privateKeyData, err = ioutil.ReadAll(r) + privateKeyData, err = io.ReadAll(r) if err != nil { return } diff --git a/openpgp/packet/symmetric_key_encrypted_test.go b/openpgp/packet/symmetric_key_encrypted_test.go index 6896d68d7..bf7463bba 100644 --- a/openpgp/packet/symmetric_key_encrypted_test.go +++ b/openpgp/packet/symmetric_key_encrypted_test.go @@ -9,7 +9,6 @@ import ( "crypto/rand" "encoding/hex" "io" - "io/ioutil" mathrand "math/rand" "testing" @@ -55,7 +54,7 @@ func TestDecryptSymmetricKeyAndEncryptedDataPacket(t *testing.T) { t.Fatal(err) } - contents, err := ioutil.ReadAll(r) + contents, err := io.ReadAll(r) if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF { t.Fatal(err) } diff --git a/openpgp/packet/symmetrically_encrypted_test.go b/openpgp/packet/symmetrically_encrypted_test.go index f4dc8a161..21204b39d 100644 --- a/openpgp/packet/symmetrically_encrypted_test.go +++ b/openpgp/packet/symmetrically_encrypted_test.go @@ -11,7 +11,6 @@ import ( "encoding/hex" goerrors "errors" "io" - "io/ioutil" "testing" "github.com/ProtonMail/go-crypto/openpgp/errors" @@ -49,7 +48,7 @@ func TestMDCReader(t *testing.T) { for stride := 1; stride < len(mdcPlaintext)/2; stride++ { r := &testReader{data: mdcPlaintext, stride: stride} mdcReader := &seMDCReader{in: r, h: sha1.New()} - body, err := ioutil.ReadAll(mdcReader) + body, err := io.ReadAll(mdcReader) if err != nil { t.Errorf("stride: %d, error: %s", stride, err) continue @@ -69,7 +68,7 @@ func TestMDCReader(t *testing.T) { r := &testReader{data: mdcPlaintext, stride: 2} mdcReader := &seMDCReader{in: r, h: sha1.New()} - _, err := ioutil.ReadAll(mdcReader) + _, err := io.ReadAll(mdcReader) if err != nil { t.Errorf("corruption test, error: %s", err) return @@ -200,7 +199,7 @@ func TestAeadRfcVector(t *testing.T) { return } - decrypted, err := ioutil.ReadAll(aeadReader) + decrypted, err := io.ReadAll(aeadReader) if err != nil { t.Errorf("error when reading: %s", err) return diff --git a/openpgp/packet/userattribute.go b/openpgp/packet/userattribute.go index 88ec72c6c..63814ed13 100644 --- a/openpgp/packet/userattribute.go +++ b/openpgp/packet/userattribute.go @@ -9,7 +9,6 @@ import ( "image" "image/jpeg" "io" - "io/ioutil" ) const UserAttrImageSubpacket = 1 @@ -63,7 +62,7 @@ func NewUserAttribute(contents ...*OpaqueSubpacket) *UserAttribute { func (uat *UserAttribute) parse(r io.Reader) (err error) { // RFC 4880, section 5.13 - b, err := ioutil.ReadAll(r) + b, err := io.ReadAll(r) if err != nil { return } diff --git a/openpgp/packet/userid.go b/openpgp/packet/userid.go index 614fbafd5..3c7451a3c 100644 --- a/openpgp/packet/userid.go +++ b/openpgp/packet/userid.go @@ -6,7 +6,6 @@ package packet import ( "io" - "io/ioutil" "strings" ) @@ -66,7 +65,7 @@ func NewUserId(name, comment, email string) *UserId { func (uid *UserId) parse(r io.Reader) (err error) { // RFC 4880, section 5.11 - b, err := ioutil.ReadAll(r) + b, err := io.ReadAll(r) if err != nil { return } diff --git a/openpgp/read_test.go b/openpgp/read_test.go index 7652f5829..da3184012 100644 --- a/openpgp/read_test.go +++ b/openpgp/read_test.go @@ -131,7 +131,7 @@ func checkSignedMessage(t *testing.T, signedHex, expected string) { t.Errorf("bad MessageDetails: %#v", md) } - contents, err := ioutil.ReadAll(md.UnverifiedBody) + contents, err := io.ReadAll(md.UnverifiedBody) if err != nil { t.Errorf("error reading UnverifiedBody: %s", err) } @@ -229,7 +229,7 @@ func TestSignedEncryptedMessage(t *testing.T) { t.Errorf("#%d: bad MessageDetails: %#v", i, md) } - contents, err := ioutil.ReadAll(md.UnverifiedBody) + contents, err := io.ReadAll(md.UnverifiedBody) if err != nil { t.Errorf("#%d: error reading UnverifiedBody: %s", i, err) } @@ -248,7 +248,7 @@ func TestSignedEncryptedMessage(t *testing.T) { t.Errorf("#%d: error serializing verified signature: %s", i, err) } - sigData, err := ioutil.ReadAll(&sig) + sigData, err := io.ReadAll(&sig) if err != nil { t.Errorf("#%d: error reading verified signature: %s", i, err) } @@ -267,7 +267,7 @@ func TestSignedEncryptedMessage(t *testing.T) { } } - sigData, err := ioutil.ReadAll(&sig) + sigData, err := io.ReadAll(&sig) if err != nil { t.Errorf("#%d: error reading unverified signature: %s", i, err) } @@ -289,7 +289,7 @@ func TestUnspecifiedRecipient(t *testing.T) { return } - contents, err := ioutil.ReadAll(md.UnverifiedBody) + contents, err := io.ReadAll(md.UnverifiedBody) if err != nil { t.Errorf("error reading UnverifiedBody: %s", err) } @@ -324,7 +324,7 @@ func TestSymmetricallyEncrypted(t *testing.T) { return } - contents, err := ioutil.ReadAll(md.UnverifiedBody) + contents, err := io.ReadAll(md.UnverifiedBody) if err != nil { t.Errorf("ReadAll: %s", err) } @@ -450,7 +450,7 @@ func TestSignatureUnknownNotation(t *testing.T) { t.Error(err) return } - _, err = ioutil.ReadAll(md.UnverifiedBody) + _, err = io.ReadAll(md.UnverifiedBody) if err != nil { t.Error(err) return @@ -481,7 +481,7 @@ func TestSignatureKnownNotation(t *testing.T) { t.Error(err) return } - _, err = ioutil.ReadAll(md.UnverifiedBody) + _, err = io.ReadAll(md.UnverifiedBody) if err != nil { t.Error(err) return @@ -568,7 +568,7 @@ func TestSignatureV3Message(t *testing.T) { return } - _, err = ioutil.ReadAll(md.UnverifiedBody) + _, err = io.ReadAll(md.UnverifiedBody) if err != nil { t.Error(err) return @@ -603,7 +603,7 @@ func TestReadV6Messages(t *testing.T) { t.Error(err) return } - contents, err := ioutil.ReadAll(md.UnverifiedBody) + contents, err := io.ReadAll(md.UnverifiedBody) if err != nil { t.Error(err) return @@ -622,7 +622,7 @@ func TestReadV6Messages(t *testing.T) { t.Error(err) return } - contents, err = ioutil.ReadAll(md.UnverifiedBody) + contents, err = io.ReadAll(md.UnverifiedBody) if err != nil { t.Error(err) return @@ -642,7 +642,7 @@ func TestSymmetricDecryptionArgon2(t *testing.T) { if err != nil { t.Fatal(err) } - armoredEncryptedMessage, err := ioutil.ReadAll(file) + armoredEncryptedMessage, err := io.ReadAll(file) if err != nil { t.Fatal(err) } @@ -662,7 +662,7 @@ func TestSymmetricDecryptionArgon2(t *testing.T) { t.Error(err) return } - contents, err := ioutil.ReadAll(md.UnverifiedBody) + contents, err := io.ReadAll(md.UnverifiedBody) if err != nil { t.Errorf("error reading UnverifiedBody: %s", err) } @@ -687,7 +687,7 @@ func TestAsymmestricAeadOcbOpenPGPjsCompressedMessage(t *testing.T) { if err != nil { t.Fatal(err) } - armoredEncryptedMessage, err := ioutil.ReadAll(ciphertext) + armoredEncryptedMessage, err := io.ReadAll(ciphertext) if err != nil { t.Fatal(err) } @@ -704,7 +704,7 @@ func TestAsymmestricAeadOcbOpenPGPjsCompressedMessage(t *testing.T) { return } // Read contents - contents, err := ioutil.ReadAll(md.UnverifiedBody) + contents, err := io.ReadAll(md.UnverifiedBody) if err != nil && err != io.ErrUnexpectedEOF { t.Errorf("error reading UnverifiedBody: %s", err) } @@ -727,7 +727,7 @@ func TestSymmetricAeadEaxOpenPGPJsMessage(t *testing.T) { if err != nil { t.Fatal(err) } - fileBytes, err := ioutil.ReadAll(file) + fileBytes, err := io.ReadAll(file) if err != nil { t.Fatal(err) } @@ -757,7 +757,7 @@ func TestSymmetricAeadEaxOpenPGPJsMessage(t *testing.T) { ld := p.(*packet.LiteralData) // Read contents - contents, err := ioutil.ReadAll(ld.Body) + contents, err := io.ReadAll(ld.Body) if err != nil && err != io.ErrUnexpectedEOF { t.Errorf("error reading UnverifiedBody: %s", err) } @@ -779,7 +779,7 @@ func TestCorruptedMessageInvalidSigHeader(t *testing.T) { if err != nil { t.Fatal(err) } - armoredEncryptedMessage, err := ioutil.ReadAll(file) + armoredEncryptedMessage, err := io.ReadAll(file) if err != nil { t.Fatal(err) } @@ -813,7 +813,7 @@ func TestCorruptedMessageWrongLength(t *testing.T) { if err != nil { t.Fatal(err) } - armoredEncryptedMessage, err := ioutil.ReadAll(file) + armoredEncryptedMessage, err := io.ReadAll(file) if err != nil { t.Fatal(err) } @@ -827,7 +827,7 @@ func TestCorruptedMessageWrongLength(t *testing.T) { t.Error(err) return } - _, err = ioutil.ReadAll(md.UnverifiedBody) + _, err = io.ReadAll(md.UnverifiedBody) if err == nil { t.Fatal("Parsing error expected") } @@ -878,7 +878,7 @@ func TestMessageWithoutMdc(t *testing.T) { t.Fatal("reading the message should have worked") } - b, err := ioutil.ReadAll(md.UnverifiedBody) + b, err := io.ReadAll(md.UnverifiedBody) if err != nil { t.Fatal("reading the message should have worked") } @@ -911,7 +911,7 @@ func TestReadV5Messages(t *testing.T) { t.Error(err) return } - contents, err := ioutil.ReadAll(md.UnverifiedBody) + contents, err := io.ReadAll(md.UnverifiedBody) if err != nil { t.Error(err) return diff --git a/openpgp/v2/read_test.go b/openpgp/v2/read_test.go index 8f274e215..024ae608c 100644 --- a/openpgp/v2/read_test.go +++ b/openpgp/v2/read_test.go @@ -135,7 +135,7 @@ func checkSignedMessage(t *testing.T, signedHex, expected string) { t.Errorf("bad MessageDetails: %#v", md) } - contents, err := ioutil.ReadAll(md.UnverifiedBody) + contents, err := io.ReadAll(md.UnverifiedBody) if err != nil { t.Errorf("error reading UnverifiedBody: %s", err) } @@ -240,7 +240,7 @@ func TestSignedEncryptedMessage(t *testing.T) { t.Errorf("#%d: bad MessageDetails: %#v", i, md) } - contents, err := ioutil.ReadAll(md.UnverifiedBody) + contents, err := io.ReadAll(md.UnverifiedBody) if err != nil { t.Errorf("#%d: error reading UnverifiedBody: %s", i, err) } @@ -262,7 +262,7 @@ func TestSignedEncryptedMessage(t *testing.T) { t.Errorf("#%d: error serializing verified signature: %s", i, err) } - sigData, err := ioutil.ReadAll(&sig) + sigData, err := io.ReadAll(&sig) if err != nil { t.Errorf("#%d: error reading verified signature: %s", i, err) } @@ -284,7 +284,7 @@ func TestUnspecifiedRecipient(t *testing.T) { return } - contents, err := ioutil.ReadAll(md.UnverifiedBody) + contents, err := io.ReadAll(md.UnverifiedBody) if err != nil { t.Errorf("error reading UnverifiedBody: %s", err) } @@ -319,7 +319,7 @@ func TestSymmetricallyEncrypted(t *testing.T) { return } - contents, err := ioutil.ReadAll(md.UnverifiedBody) + contents, err := io.ReadAll(md.UnverifiedBody) if err != nil { t.Errorf("ReadAll: %s", err) } @@ -441,7 +441,7 @@ func TestSignatureUnknownNotation(t *testing.T) { t.Error(err) return } - _, err = ioutil.ReadAll(md.UnverifiedBody) + _, err = io.ReadAll(md.UnverifiedBody) if err != nil { t.Error(err) return @@ -471,7 +471,7 @@ func TestSignatureKnownNotation(t *testing.T) { t.Error(err) return } - _, err = ioutil.ReadAll(md.UnverifiedBody) + _, err = io.ReadAll(md.UnverifiedBody) if err != nil { t.Error(err) return @@ -561,7 +561,7 @@ func TestSignatureV3Message(t *testing.T) { return } - _, err = ioutil.ReadAll(md.UnverifiedBody) + _, err = io.ReadAll(md.UnverifiedBody) if err != nil { t.Error(err) return @@ -599,7 +599,7 @@ func TestSignatureOldStyleMessage(t *testing.T) { return } - _, err = ioutil.ReadAll(md.UnverifiedBody) + _, err = io.ReadAll(md.UnverifiedBody) if err != nil { t.Error(err) return @@ -631,7 +631,7 @@ func TestReadV6Messages(t *testing.T) { t.Error(err) return } - contents, err := ioutil.ReadAll(md.UnverifiedBody) + contents, err := io.ReadAll(md.UnverifiedBody) if err != nil { t.Error(err) return @@ -650,7 +650,7 @@ func TestReadV6Messages(t *testing.T) { t.Error(err) return } - contents, err = ioutil.ReadAll(md.UnverifiedBody) + contents, err = io.ReadAll(md.UnverifiedBody) if err != nil { t.Error(err) return @@ -673,7 +673,7 @@ func TestSymmetricDecryptionArgon2(t *testing.T) { if err != nil { t.Fatal(err) } - armoredEncryptedMessage, err := ioutil.ReadAll(file) + armoredEncryptedMessage, err := io.ReadAll(file) if err != nil { t.Fatal(err) } @@ -693,7 +693,7 @@ func TestSymmetricDecryptionArgon2(t *testing.T) { t.Error(err) return } - contents, err := ioutil.ReadAll(md.UnverifiedBody) + contents, err := io.ReadAll(md.UnverifiedBody) if err != nil { t.Errorf("error reading UnverifiedBody: %s", err) } @@ -718,7 +718,7 @@ func TestAsymmestricAeadOcbOpenPGPjsCompressedMessage(t *testing.T) { if err != nil { t.Fatal(err) } - armoredEncryptedMessage, err := ioutil.ReadAll(ciphertext) + armoredEncryptedMessage, err := io.ReadAll(ciphertext) if err != nil { t.Fatal(err) } @@ -735,7 +735,7 @@ func TestAsymmestricAeadOcbOpenPGPjsCompressedMessage(t *testing.T) { return } // Read contents - contents, err := ioutil.ReadAll(md.UnverifiedBody) + contents, err := io.ReadAll(md.UnverifiedBody) if err != nil && err != io.ErrUnexpectedEOF { t.Errorf("error reading UnverifiedBody: %s", err) } @@ -758,7 +758,7 @@ func TestSymmetricAeadEaxOpenPGPJsMessage(t *testing.T) { if err != nil { t.Fatal(err) } - fileBytes, err := ioutil.ReadAll(file) + fileBytes, err := io.ReadAll(file) if err != nil { t.Fatal(err) } @@ -792,7 +792,7 @@ func TestSymmetricAeadEaxOpenPGPJsMessage(t *testing.T) { ld := p.(*packet.LiteralData) // Read contents - contents, err := ioutil.ReadAll(ld.Body) + contents, err := io.ReadAll(ld.Body) if err != nil && err != io.ErrUnexpectedEOF { t.Errorf("error reading UnverifiedBody: %s", err) } @@ -814,7 +814,7 @@ func TestCorruptedMessageInvalidSigHeader(t *testing.T) { if err != nil { t.Fatal(err) } - armoredEncryptedMessage, err := ioutil.ReadAll(file) + armoredEncryptedMessage, err := io.ReadAll(file) if err != nil { t.Fatal(err) } @@ -848,7 +848,7 @@ func TestCorruptedMessageWrongLength(t *testing.T) { if err != nil { t.Fatal(err) } - armoredEncryptedMessage, err := ioutil.ReadAll(file) + armoredEncryptedMessage, err := io.ReadAll(file) if err != nil { t.Fatal(err) } @@ -862,7 +862,7 @@ func TestCorruptedMessageWrongLength(t *testing.T) { t.Error(err) return } - _, err = ioutil.ReadAll(md.UnverifiedBody) + _, err = io.ReadAll(md.UnverifiedBody) if err == nil { t.Fatal("Parsing error expected") } @@ -913,7 +913,7 @@ func TestMessageWithoutMdc(t *testing.T) { t.Fatal("reading the message should have worked") } - b, err := ioutil.ReadAll(md.UnverifiedBody) + b, err := io.ReadAll(md.UnverifiedBody) if err != nil { t.Fatal("reading the message should have worked") } @@ -944,7 +944,7 @@ func TestMultiSignedMessage(t *testing.T) { t.Errorf("expected 2 signature candidates, got: %d", len(md.SignatureCandidates)) } - _, err = ioutil.ReadAll(md.UnverifiedBody) + _, err = io.ReadAll(md.UnverifiedBody) if err != nil { t.Fatal(err) } @@ -994,7 +994,7 @@ func testMalformedMessage(t *testing.T, keyring EntityList, message string) { if err != nil { return } - _, err = ioutil.ReadAll(md.UnverifiedBody) + _, err = io.ReadAll(md.UnverifiedBody) if err == nil { t.Error("Expected malformed message error") return diff --git a/openpgp/v2/write_test.go b/openpgp/v2/write_test.go index f121ef7cd..0b61578b4 100644 --- a/openpgp/v2/write_test.go +++ b/openpgp/v2/write_test.go @@ -8,7 +8,6 @@ import ( "bytes" "crypto/rand" "io" - "io/ioutil" mathrand "math/rand" "testing" "time" @@ -510,7 +509,7 @@ func TestIntendedRecipientsEncryption(t *testing.T) { if !md.CheckRecipients { t.Error("should check for intended recipient") } - _, err = ioutil.ReadAll(md.UnverifiedBody) + _, err = io.ReadAll(md.UnverifiedBody) if err != nil { t.Errorf("error reading encrypted contents: %s", err) } @@ -534,7 +533,7 @@ func TestIntendedRecipientsEncryption(t *testing.T) { if !md.CheckRecipients { t.Error("should check for intended recipient") } - _, err = ioutil.ReadAll(md.UnverifiedBody) + _, err = io.ReadAll(md.UnverifiedBody) if err != nil { t.Errorf("error reading encrypted contents: %s", err) } @@ -557,7 +556,7 @@ func TestIntendedRecipientsEncryption(t *testing.T) { if md.CheckRecipients { t.Error("should not check for intended recipient") } - _, err = ioutil.ReadAll(md.UnverifiedBody) + _, err = io.ReadAll(md.UnverifiedBody) if err != nil { t.Errorf("error reading encrypted contents: %s", err) } @@ -619,7 +618,7 @@ func TestMultiSignEncryption(t *testing.T) { } // Check reading with v4 key - _, err = ioutil.ReadAll(md.UnverifiedBody) + _, err = io.ReadAll(md.UnverifiedBody) if err != nil { t.Errorf("error reading encrypted contents: %s", err) } @@ -637,7 +636,7 @@ func TestMultiSignEncryption(t *testing.T) { if err != nil { t.Errorf("error reading message: %s", err) } - _, err = ioutil.ReadAll(md.UnverifiedBody) + _, err = io.ReadAll(md.UnverifiedBody) if err != nil { t.Errorf("error reading encrypted contents: %s", err) } @@ -655,7 +654,7 @@ func TestMultiSignEncryption(t *testing.T) { if err != nil { t.Errorf("error reading message: %s", err) } - _, err = ioutil.ReadAll(md.UnverifiedBody) + _, err = io.ReadAll(md.UnverifiedBody) if err != nil { t.Errorf("error reading encrypted contents: %s", err) } @@ -766,7 +765,7 @@ func TestEncryption(t *testing.T) { } } - plaintext, err := ioutil.ReadAll(md.UnverifiedBody) + plaintext, err := io.ReadAll(md.UnverifiedBody) if err != nil { t.Errorf("#%d: error reading encrypted contents: %s", i, err) continue @@ -872,7 +871,7 @@ func TestSigning(t *testing.T) { t.Errorf("#%d: failed to find the signing Entity", i) } - plaintext, err := ioutil.ReadAll(md.UnverifiedBody) + plaintext, err := io.ReadAll(md.UnverifiedBody) if err != nil { t.Errorf("#%d: error reading contents: %v", i, err) continue diff --git a/openpgp/write_test.go b/openpgp/write_test.go index 9e3d36b4e..5f9995529 100644 --- a/openpgp/write_test.go +++ b/openpgp/write_test.go @@ -8,7 +8,6 @@ import ( "bytes" "crypto/rand" "io" - "io/ioutil" mathrand "math/rand" "testing" "time" @@ -551,7 +550,7 @@ func TestEncryption(t *testing.T) { } } - plaintext, err := ioutil.ReadAll(md.UnverifiedBody) + plaintext, err := io.ReadAll(md.UnverifiedBody) if err != nil { t.Errorf("#%d: error reading encrypted contents: %s", i, err) continue @@ -651,7 +650,7 @@ func TestSigning(t *testing.T) { t.Errorf("#%d: failed to find the signing Entity", i) } - plaintext, err := ioutil.ReadAll(md.UnverifiedBody) + plaintext, err := io.ReadAll(md.UnverifiedBody) if err != nil { t.Errorf("#%d: error reading contents: %v", i, err) continue