diff options
author | Justus Winter <justus@sequoia-pgp.org> | 2020-03-27 13:59:06 +0100 |
---|---|---|
committer | Justus Winter <justus@sequoia-pgp.org> | 2020-03-27 14:08:22 +0100 |
commit | a3f64c655c9e212f79fbbb425fac58f1ee6ce5a9 (patch) | |
tree | 8d8e0d72390cd2beee010cb799e8297d246d85fb | |
parent | 20a8c5a9b191112af4d9db974d51922c57217882 (diff) |
openpgp: Define equality using the serialized OpenPGP form.
- All packets should be considered equal if the serialized OpenPGP
form is equal, modulo framing (i.e. CTB type, packet length
encoding, partial body chunking).
- In cases this may lead to surprising outcomes, discuss this in the
documentation, and provide additional equality predicates.
- Fixes #92.
-rw-r--r-- | openpgp/src/lib.rs | 8 | ||||
-rw-r--r-- | openpgp/src/packet/aed.rs | 26 | ||||
-rw-r--r-- | openpgp/src/packet/compressed_data.rs | 20 | ||||
-rw-r--r-- | openpgp/src/packet/key/mod.rs | 16 | ||||
-rw-r--r-- | openpgp/src/packet/literal.rs | 24 | ||||
-rw-r--r-- | openpgp/src/packet/marker.rs | 17 | ||||
-rw-r--r-- | openpgp/src/packet/mdc.rs | 9 | ||||
-rw-r--r-- | openpgp/src/packet/mod.rs | 78 | ||||
-rw-r--r-- | openpgp/src/packet/one_pass_sig.rs | 27 | ||||
-rw-r--r-- | openpgp/src/packet/pkesk.rs | 22 | ||||
-rw-r--r-- | openpgp/src/packet/seip.rs | 18 | ||||
-rw-r--r-- | openpgp/src/packet/signature/mod.rs | 60 | ||||
-rw-r--r-- | openpgp/src/packet/skesk.rs | 26 | ||||
-rw-r--r-- | openpgp/src/packet/trust.rs | 18 | ||||
-rw-r--r-- | openpgp/src/packet/unknown.rs | 5 | ||||
-rw-r--r-- | openpgp/src/packet/user_attribute.rs | 32 | ||||
-rw-r--r-- | openpgp/src/packet/userid/mod.rs | 13 |
17 files changed, 193 insertions, 226 deletions
diff --git a/openpgp/src/lib.rs b/openpgp/src/lib.rs index 5ac45b36..44480a45 100644 --- a/openpgp/src/lib.rs +++ b/openpgp/src/lib.rs @@ -332,6 +332,14 @@ pub enum Error { /// /// Note: This enum cannot be exhaustively matched to allow future /// extensions. +/// +/// # A note on equality +/// +/// We define equality on `Packet` like equality of the serialized +/// form of the packet bodies defined by RFC4880, i.e. two packets are +/// considered equal if and only if their serialized form is equal, +/// modulo the OpenPGP framing (`CTB` and length style, potential +/// partial body encoding). #[derive(Debug)] #[derive(PartialEq, Eq, Hash, Clone)] pub enum Packet { diff --git a/openpgp/src/packet/aed.rs b/openpgp/src/packet/aed.rs index 16a2ee61..a84fcd01 100644 --- a/openpgp/src/packet/aed.rs +++ b/openpgp/src/packet/aed.rs @@ -17,7 +17,9 @@ use crate::Result; /// [Section 5.16 of RFC 4880bis]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-05#section-5.16 /// /// This feature is [experimental](../../index.html#experimental-features). -#[derive(Clone, Debug)] +// IMPORTANT: If you add fields to this struct, you need to explicitly +// IMPORTANT: implement PartialEq, Eq, and Hash. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct AED1 { /// CTB packet header fields. pub(crate) common: packet::Common, @@ -47,28 +49,6 @@ impl std::ops::DerefMut for AED1 { } } -impl PartialEq for AED1 { - fn eq(&self, other: &AED1) -> bool { - self.sym_algo == other.sym_algo - && self.aead == other.aead - && self.chunk_size == other.chunk_size - && self.iv == other.iv - && self.container == other.container - } -} - -impl Eq for AED1 {} - -impl std::hash::Hash for AED1 { - fn hash<H: std::hash::Hasher>(&self, state: &mut H) { - std::hash::Hash::hash(&self.sym_algo, state); - std::hash::Hash::hash(&self.aead, state); - std::hash::Hash::hash(&self.chunk_size, state); - std::hash::Hash::hash(&self.iv, state); - std::hash::Hash::hash(&self.container, state); - } -} - impl AED1 { /// Creates a new AED1 object. pub fn new(sym_algo: SymmetricAlgorithm, diff --git a/openpgp/src/packet/compressed_data.rs b/openpgp/src/packet/compressed_data.rs index c9bd74b6..edb858bb 100644 --- a/openpgp/src/packet/compressed_data.rs +++ b/openpgp/src/packet/compressed_data.rs @@ -14,7 +14,9 @@ use crate::types::CompressionAlgorithm; /// of a `CompressedData` packet. /// /// [Section 5.6 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.6 -#[derive(Clone)] +// IMPORTANT: If you add fields to this struct, you need to explicitly +// IMPORTANT: implement PartialEq, Eq, and Hash. +#[derive(Clone, PartialEq, Eq, Hash)] pub struct CompressedData { /// CTB packet header fields. pub(crate) common: packet::Common, @@ -38,22 +40,6 @@ impl std::ops::DerefMut for CompressedData { } } -impl PartialEq for CompressedData { - fn eq(&self, other: &CompressedData) -> bool { - self.algo == other.algo - && self.container == other.container - } -} - -impl Eq for CompressedData {} - -impl std::hash::Hash for CompressedData { - fn hash<H: std::hash::Hasher>(&self, state: &mut H) { - std::hash::Hash::hash(&self.algo, state); - std::hash::Hash::hash(&self.container, state); - } -} - impl fmt::Debug for CompressedData { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("CompressedData") diff --git a/openpgp/src/packet/key/mod.rs b/openpgp/src/packet/key/mod.rs index 74ce8445..722bead4 100644 --- a/openpgp/src/packet/key/mod.rs +++ b/openpgp/src/packet/key/mod.rs @@ -895,6 +895,20 @@ impl<P, R> Key4<P, R> self.pk_algo.cmp(&b.pk_algo) } + + /// This method tests for self and other values to be equal modulo + /// the secret bits. + /// + /// This returns true if the public MPIs, creation time and + /// algorithm of the two `Key4`s match. This does not consider + /// the packet's encoding, packet's tag or the secret key + /// material. + pub fn public_eq<PB, RB>(&self, b: &Key4<PB, RB>) -> bool + where PB: key::KeyParts, + RB: key::KeyRole, + { + self.public_cmp(b) == Ordering::Equal + } } impl<R> Key4<key::PublicParts, R> @@ -1522,6 +1536,8 @@ impl SecretKeyMaterial { /// demand. See [`crypto::mem::Encrypted`] for details. /// /// [`crypto::mem::Encrypted`]: ../../crypto/mem/struct.Encrypted.html +// Note: PartialEq, Eq, and Hash on mem::Encrypted does the right +// thing. #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct Unencrypted { /// MPIs of the secret key. diff --git a/openpgp/src/packet/literal.rs b/openpgp/src/packet/literal.rs index 2e66e337..b0c03bb5 100644 --- a/openpgp/src/packet/literal.rs +++ b/openpgp/src/packet/literal.rs @@ -21,7 +21,9 @@ use crate::Result; /// See [Section 5.9 of RFC 4880] for details. /// /// [Section 5.9 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.9 -#[derive(Clone)] +// IMPORTANT: If you add fields to this struct, you need to explicitly +// IMPORTANT: implement PartialEq, Eq, and Hash. +#[derive(Clone, PartialEq, Eq, Hash)] pub struct Literal { /// CTB packet header fields. pub(crate) common: packet::Common, @@ -44,26 +46,6 @@ pub struct Literal { container: packet::Container, } -impl PartialEq for Literal { - fn eq(&self, other: &Literal) -> bool { - self.format == other.format - && self.filename == other.filename - && self.date == other.date - && self.container == other.container - } -} - -impl Eq for Literal {} - -impl std::hash::Hash for Literal { - fn hash<H: std::hash::Hasher>(&self, state: &mut H) { - std::hash::Hash::hash(&self.format, state); - std::hash::Hash::hash(&self.filename, state); - std::hash::Hash::hash(&self.date, state); - std::hash::Hash::hash(&self.container, state); - } -} - impl fmt::Debug for Literal { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let filename = if let Some(ref filename) = self.filename { diff --git a/openpgp/src/packet/marker.rs b/openpgp/src/packet/marker.rs index 9e1e6718..8217901c 100644 --- a/openpgp/src/packet/marker.rs +++ b/openpgp/src/packet/marker.rs @@ -8,25 +8,14 @@ use crate::Packet; /// See [Section 5.8 of RFC 4880] for details. /// /// [Section 5.8 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.8 -#[derive(Clone, Debug)] +// IMPORTANT: If you add fields to this struct, you need to explicitly +// IMPORTANT: implement PartialEq, Eq, and Hash. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct Marker { /// CTB packet header fields. pub(crate) common: packet::Common, } -impl PartialEq for Marker { - fn eq(&self, _other: &Marker) -> bool { - true - } -} - -impl Eq for Marker {} - -impl std::hash::Hash for Marker { - fn hash<H: std::hash::Hasher>(&self, _state: &mut H) { - } -} - impl Marker { pub(crate) const BODY: &'static [u8] = &[0x50, 0x47, 0x50]; } diff --git a/openpgp/src/packet/mdc.rs b/openpgp/src/packet/mdc.rs index 9ca824a7..704e1c0a 100644 --- a/openpgp/src/packet/mdc.rs +++ b/openpgp/src/packet/mdc.rs @@ -8,6 +8,11 @@ use crate::Packet; /// SEIP packet. See [Section 5.14 of RFC 4880] for details. /// /// [Section 5.14 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.14 +/// +/// # A note on equality +/// +/// Two `Signature` packets are considered equal if their serialized +/// form is equal. This excludes the computed digest. #[derive(Clone, Debug)] pub struct MDC { /// CTB packet header fields. @@ -20,7 +25,8 @@ pub struct MDC { impl PartialEq for MDC { fn eq(&self, other: &MDC) -> bool { - self.digest == other.digest + self.common == other.common + && self.digest == other.digest } } @@ -28,6 +34,7 @@ impl Eq for MDC {} impl std::hash::Hash for MDC { fn hash<H: std::hash::Hasher>(&self, state: &mut H) { + std::hash::Hash::hash(&self.common, state); std::hash::Hash::hash(&self.digest, state); } } diff --git a/openpgp/src/packet/mod.rs b/openpgp/src/packet/mod.rs index 621a11fb..976d6ecc 100644 --- a/openpgp/src/packet/mod.rs +++ b/openpgp/src/packet/mod.rs @@ -112,6 +112,16 @@ impl<'a> DerefMut for Packet { /// Fields used by multiple packet types. #[derive(Debug, Clone)] pub struct Common { + // In the future, this structure will hold the parsed CTB, packet + // length, and lengths of chunks of partial body encoded packets. + // This will allow for bit-perfect roundtripping of parsed + // packets. Since we consider Packets to be equal if their + // serialized form is equal modulo CTB, packet length encoding, + // and chunk lengths, this structure has trivial implementations + // for PartialEq, Eq, PartialOrd, Ord, and Hash, so that we can + // derive PartialEq, Eq, PartialOrd, Ord, and Hash for most + // packets. + /// XXX: Prevents trivial matching on this structure. Remove once /// this structure actually gains some fields. dummy: std::marker::PhantomData<()>, @@ -124,6 +134,34 @@ impl Default for Common { } } } + +impl PartialEq for Common { + fn eq(&self, _: &Common) -> bool { + // Don't compare anything. + true + } +} + +impl Eq for Common {} + +impl PartialOrd for Common { + fn partial_cmp(&self, _: &Self) -> Option<std::cmp::Ordering> { + Some(std::cmp::Ordering::Equal) + } +} + +impl Ord for Common { + fn cmp(&self, _: &Self) -> std::cmp::Ordering { + std::cmp::Ordering::Equal + } +} + +impl std::hash::Hash for Common { + fn hash<H: std::hash::Hasher>(&self, _: &mut H) { + // Don't hash anything. + } +} + /// A `Iter` iterates over the *contents* of a packet in /// depth-first order. It starts by returning the current packet. @@ -313,6 +351,21 @@ fn packet_path_iter() { /// /// Note: This enum cannot be exhaustively matched to allow future /// extensions. +/// +/// # A note on equality +/// +/// Two `Signature` packets are considered equal if their serialized +/// form is equal. Notably this includes the unhashed subpacket area, +/// but excludes the computed digest and signature level. +/// +/// A consequence of considering packets in the unhashed subpacket +/// area is that an adversary can take a valid signature and create +/// many distinct but valid signatures by changing the unhashed +/// subpacket area. This has the potential of creating a denial of +/// service vector. To protect against this, consider using +/// [`Signature::normalized_eq`]. +/// +/// [`Signature::normalized_eq`]: #method.normalized_eq #[derive(PartialEq, Eq, Hash, Clone, Debug)] pub enum Signature { /// Signature packet version 4. @@ -523,6 +576,17 @@ impl From<SKESK> for Packet { /// /// Note: This enum cannot be exhaustively matched to allow future /// extensions. +/// +/// # A note on equality +/// +/// Two `Key` packets are considered equal if their serialized form is +/// equal. Notably this includes the secret key material, but +/// excludes the `KeyParts` and `KeyRole` marker traits. +/// +/// To compare only the public bits of `Key` packets, use +/// [`Key::public_eq`]. +/// +/// [`Key::public_eq`]: #method.public_eq #[derive(PartialEq, Eq, Hash, Clone, Debug)] pub enum Key<P: key::KeyParts, R: key::KeyRole> { /// Key packet version 4. @@ -567,6 +631,20 @@ impl<P: key::KeyParts, R: key::KeyRole> Key<P, R> { (_, Key::__Nonexhaustive) => unreachable!(), } } + + /// This method tests for self and other values to be equal modulo + /// the secret bits. + /// + /// This returns true if the public MPIs, creation time and + /// algorithm of the two `Key`s match. This does not consider + /// the packet's encoding, packet's tag or the secret key + /// material. + pub fn public_eq<PB, RB>(&self, b: &Key<PB, RB>) -> bool + where PB: key::KeyParts, + RB: key::KeyRole, + { + self.public_cmp(b) == std::cmp::Ordering::Equal + } } impl From<Key<key::PublicParts, key::PrimaryRole>> for Packet { diff --git a/openpgp/src/packet/one_pass_sig.rs b/openpgp/src/packet/one_pass_sig.rs index 62ef88e6..0fdad6e2 100644 --- a/openpgp/src/packet/one_pass_sig.rs +++ b/openpgp/src/packet/one_pass_sig.rs @@ -5,7 +5,6 @@ //! [Section 5.4 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.4 use std::fmt; -use std::hash::{Hash, Hasher}; use quickcheck::{Arbitrary, Gen}; use crate::Error; @@ -23,7 +22,9 @@ use crate::SignatureType; /// See [Section 5.4 of RFC 4880] for details. /// /// [Section 5.4 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.4 -#[derive(Clone)] +// IMPORTANT: If you add fields to this struct, you need to explicitly +// IMPORTANT: implement PartialEq, Eq, and Hash. +#[derive(Clone, PartialEq, Eq, Hash)] pub struct OnePassSig3 { /// CTB packet header fields. pub(crate) common: packet::Common, @@ -52,28 +53,6 @@ impl fmt::Debug for OnePassSig3 { } } -impl PartialEq for OnePassSig3 { - fn eq(&self, other: &OnePassSig3) -> bool { - self.typ == other.typ - && self.hash_algo == other.hash_algo - && self.pk_algo == other.pk_algo - && self.issuer == other.issuer - && self.last == other.last - } -} - -impl Eq for OnePassSig3 {} - -impl Hash for OnePassSig3 { - fn hash<H: Hasher>(&self, state: &mut H) { - self.typ.hash(state); - self.hash_algo.hash(state); - self.pk_algo.hash(state); - self.issuer.hash(state); - self.last.hash(state); - } -} - impl OnePassSig3 { /// Returns a new `Signature` packet. pub fn new(typ: SignatureType) -> Self { diff --git a/openpgp/src/packet/pkesk.rs b/openpgp/src/packet/pkesk.rs index e048cff8..996b5a7a 100644 --- a/openpgp/src/packet/pkesk.rs +++ b/openpgp/src/packet/pkesk.rs @@ -26,7 +26,9 @@ use crate::packet; /// [Section 5.1 of RFC 4880] for details. /// /// [Section 5.1 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.1 -#[derive(Clone, Debug)] +// IMPORTANT: If you add fields to this struct, you need to explicitly +// IMPORTANT: implement PartialEq, Eq, and Hash. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct PKESK3 { /// CTB header fields. pub(crate) common: packet::Common, @@ -38,24 +40,6 @@ pub struct PKESK3 { esk: Ciphertext, } -impl PartialEq for PKESK3 { - fn eq(&self, other: &PKESK3) -> bool { - self.recipient == other.recipient - && self.pk_algo == other.pk_algo - && self.esk == other.esk - } -} - -impl Eq for PKESK3 {} - -impl std::hash::Hash for PKESK3 { - fn hash<H: std::hash::Hasher>(&self, state: &mut H) { - std::hash::Hash::hash(&self.recipient, state); - std::hash::Hash::hash(&self.pk_algo, state); - std::hash::Hash::hash(&self.esk, state); - } -} - impl PKESK3 { /// Creates a new PKESK3 packet. pub fn new(recipient: KeyID, pk_algo: PublicKeyAlgorithm, diff --git a/openpgp/src/packet/seip.rs b/openpgp/src/packet/seip.rs index 91785666..76487bef 100644 --- a/openpgp/src/packet/seip.rs +++ b/openpgp/src/packet/seip.rs @@ -14,7 +14,9 @@ use crate::Packet; /// 4880] for details. /// /// [Section 5.13 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.13 -#[derive(Clone, Debug)] +// IMPORTANT: If you add fields to this struct, you need to explicitly +// IMPORTANT: implement PartialEq, Eq, and Hash. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct SEIP1 { /// CTB packet header fields. pub(crate) common: packet::Common, @@ -36,20 +38,6 @@ impl std::ops::DerefMut for SEIP1 { } } -impl PartialEq for SEIP1 { - fn eq(&self, other: &SEIP1) -> bool { - self.container == other.container - } -} - -impl Eq for SEIP1 {} - -impl std::hash::Hash for SEIP1 { - fn hash<H: std::hash::Hasher>(&self, state: &mut H) { - std::hash::Hash::hash(&self.container, state); - } -} - impl SEIP1 { /// Creates a new SEIP1 packet. pub fn new() -> Self { diff --git a/openpgp/src/packet/signature/mod.rs b/openpgp/src/packet/signature/mod.rs index ad59d927..6800050b 100644 --- a/openpgp/src/packet/signature/mod.rs +++ b/openpgp/src/packet/signature/mod.rs @@ -62,6 +62,8 @@ pub mod subpacket; /// [`set_signature_creation_time`]. /// /// [`set_signature_creation_time`]: #method.set_signature_creation_time +// IMPORTANT: If you add fields to this struct, you need to explicitly +// IMPORTANT: implement PartialEq, Eq, and Hash. #[derive(Clone, Hash, PartialEq, Eq)] pub struct Builder { /// Version of the signature packet. Must be 4. @@ -455,11 +457,8 @@ impl PartialEq for Signature4 { /// signatures using this predicate. fn eq(&self, other: &Signature4) -> bool { self.mpis == other.mpis - && self.fields.version == other.fields.version - && self.fields.typ == other.fields.typ - && self.fields.pk_algo == other.fields.pk_algo - && self.fields.hash_algo == other.fields.hash_algo - && self.fields.hashed_area() == other.fields.hashed_area() + && self.fields == other.fields + && self.digest_prefix == other.digest_prefix } } @@ -468,12 +467,9 @@ impl Eq for Signature4 {} impl std::hash::Hash for Signature4 { fn hash<H: std::hash::Hasher>(&self, state: &mut H) { use std::hash::Hash as StdHash; - self.fields.version.hash(state); - self.fields.typ.hash(state); - self.fields.pk_algo.hash(state); - self.fields.hash_algo.hash(state); - self.fields.hashed_area().hash(state); StdHash::hash(&self.mpis, state); + StdHash::hash(&self.fields, state); + self.digest_prefix.hash(state); } } @@ -609,6 +605,26 @@ impl crate::packet::Signature { issuers } + /// Compares Signatures ignoring the unhashed subpacket area. + /// + /// We ignore the unhashed subpacket area when comparing + /// signatures. This prevents a malicious party to take valid + /// signatures, add subpackets to the unhashed area, yielding + /// valid but distinct signatures. + /// + /// The problem we are trying to avoid here is signature spamming. + /// Ignoring the unhashed subpackets means that we can deduplicate + /// signatures using this predicate. + pub fn normalized_eq(&self, other: &Signature) -> bool { + self.mpis() == other.mpis() + && self.version() == other.version() + && self.typ() == other.typ() + && self.pk_algo() == other.pk_algo() + && self.hash_algo() == other.hash_algo() + && self.hashed_area() == other.hashed_area() + && self.digest_prefix() == other.digest_prefix() + } + /// Normalizes the signature. /// /// This function normalizes the *unhashed* signature subpackets. @@ -1461,16 +1477,20 @@ mod test { let keyid = KeyID::from(&fp); // First, make sure any superfluous subpackets are removed, - // yet the Issuer and EmbeddedSignature ones are kept. + // yet the Issuer, IssuerFingerprint and EmbeddedSignature + // ones are kept. let mut builder = Builder::new(SignatureType::Text); - // This subpacket does not belong there, and should be - // removed. builder.unhashed_area_mut().add(Subpacket::new( SubpacketValue::IssuerFingerprint(fp.clone()), false).unwrap()) .unwrap(); builder.unhashed_area_mut().add(Subpacket::new( SubpacketValue::Issuer(keyid.clone()), false).unwrap()) .unwrap(); + // This subpacket does not belong there, and should be + // removed. + builder.unhashed_area_mut().add(Subpacket::new( + SubpacketValue::PreferredSymmetricAlgorithms(Vec::new()), + false).unwrap()).unwrap(); // Build and add an embedded sig. let embedded_sig = Builder::new(SignatureType::PrimaryKeyBinding) @@ -1480,22 +1500,14 @@ mod test { .unwrap()).unwrap(); let sig = builder.sign_hash(&mut pair, hash.clone()).unwrap().normalize(); - assert_eq!(sig.unhashed_area().iter().count(), 2); + assert_eq!(sig.unhashed_area().iter().count(), 3); assert_eq!(*sig.unhashed_area().iter().nth(0).unwrap(), Subpacket::new(SubpacketValue::Issuer(keyid.clone()), false).unwrap()); assert_eq!(sig.unhashed_area().iter().nth(1).unwrap().tag(), SubpacketTag::EmbeddedSignature); - - // Now, make sure that an Issuer subpacket is synthesized from - // the hashed area for compatibility. - let sig = Builder::new(SignatureType::Text) - .set_issuer_fingerprint(fp).unwrap() - .sign_hash(&mut pair, - hash.clone()).unwrap().normalize(); - assert_eq!(sig.unhashed_area().iter().count(), 1); - assert_eq!(*sig.unhashed_area().iter().nth(0).unwrap(), - Subpacket::new(SubpacketValue::Issuer(keyid.clone()), + assert_eq!(*sig.unhashed_area().iter().nth(2).unwrap(), + Subpacket::new(SubpacketValue::IssuerFingerprint(fp.clone()), false).unwrap()); } diff --git a/openpgp/src/packet/skesk.rs b/openpgp/src/packet/skesk.rs index c9b032c7..13fc2334 100644 --- a/openpgp/src/packet/skesk.rs +++ b/openpgp/src/packet/skesk.rs @@ -54,7 +54,9 @@ impl Arbitrary for SKESK { /// 4880] for details. /// /// [Section 5.3 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.3 -#[derive(Clone, Debug)] +// IMPORTANT: If you add fields to this struct, you need to explicitly +// IMPORTANT: implement PartialEq, Eq, and Hash. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct SKESK4 { /// CTB header fields. pub(crate) common: packet::Common, @@ -71,26 +73,6 @@ pub struct SKESK4 { esk: Option<Vec<u8>>, } -impl PartialEq for SKESK4 { - fn eq(&self, other: &SKESK4) -> bool { - self.version == other.version - && self.sym_algo == other.sym_algo - && self.s2k == other.s2k - && self.esk == other.esk - } -} - -impl Eq for SKESK4 {} - -impl std::hash::Hash for SKESK4 { - fn hash<H: std::hash::Hasher>(&self, state: &mut H) { - std::hash::Hash::hash(&self.version, state); - std::hash::Hash::hash(&self.sym_algo, state); |