diff options
-rw-r--r-- | openpgp/src/cert.rs | 34 | ||||
-rw-r--r-- | openpgp/src/serialize/cert.rs | 98 |
2 files changed, 132 insertions, 0 deletions
diff --git a/openpgp/src/cert.rs b/openpgp/src/cert.rs index ea6c1d24..f9ffe76b 100644 --- a/openpgp/src/cert.rs +++ b/openpgp/src/cert.rs @@ -674,6 +674,40 @@ pub trait Preferences<'a>: seal::Sealed { /// # } /// ``` /// +/// # A note on equality +/// +/// We define equality on `Cert` as the equality of the serialized +/// form as defined by RFC 4880. That is, two certs are considered +/// equal if and only if their serialized forms are equal, modulo the +/// OpenPGP packet framing (see [`Packet`#a-note-on-equality]). +/// +/// Because secret key material is not emitted when a `Cert` is +/// serialized, two certs are considered equal even if only one of +/// them has secret key material. To take secret key material into +/// account, compare the [`TSK`s](crate::serialize::TSK) instead: +/// +/// ```rust +/// # fn main() -> sequoia_openpgp::Result<()> { +/// # use sequoia_openpgp as openpgp; +/// use openpgp::cert::prelude::*; +/// +/// // Generate a cert with secrets. +/// let (cert_with_secrets, _) = +/// CertBuilder::general_purpose(None, Some("alice@example.org")) +/// .generate()?; +/// +/// // Derive a cert without secrets. +/// let cert_without_secrets = +/// cert_with_secrets.clone().strip_secret_key_material(); +/// +/// // Both are considered equal. +/// assert!(cert_with_secrets == cert_without_secrets); +/// +/// // But not if we compare their TSKs: +/// assert!(cert_with_secrets.as_tsk() != cert_without_secrets.as_tsk()); +/// # Ok(()) } +/// ``` +/// /// # Examples /// /// Parse a certificate: diff --git a/openpgp/src/serialize/cert.rs b/openpgp/src/serialize/cert.rs index 8e748b77..a9193a3b 100644 --- a/openpgp/src/serialize/cert.rs +++ b/openpgp/src/serialize/cert.rs @@ -235,6 +235,41 @@ pub struct TSK<'a> { emit_stubs: bool, } +impl PartialEq for TSK<'_> { + fn eq(&self, other: &Self) -> bool { + // First, compare the certs. If they are not equal, then the + // TSK's cannot possibly be equal. + if self.cert != other.cert { + return false; + } + + // Second, consider all the keys. + for (a, b) in self.cert.keys() + .zip(other.cert.keys()) + { + match (a.has_secret() + && (self.filter)(a.key().parts_as_secret().expect("has secret")), + b.has_secret() + && (other.filter)(b.key().parts_as_secret().expect("has_secret"))) + { + // Both have secrets. Compare secrets. + (true, true) => if a.optional_secret() != b.optional_secret() { + return false; + }, + // No secrets. Equal iff both or neither emit stubs. + (false, false) => if self.emit_stubs != other.emit_stubs { + return false; + }, + // Otherwise, they differ. + _ => return false, + } + } + + // Everything matched. + true + } +} + impl<'a> TSK<'a> { /// Creates a new view for the given `Cert`. fn new(cert: &'a Cert) -> Self { @@ -869,4 +904,67 @@ mod test { assert_eq!(buf, buf_); Ok(()) } + + /// Checks partial equality of certificates and TSKs. + #[test] + fn issue_701() -> Result<()> { + let cert_0 = Cert::from_bytes(crate::tests::key("testy.pgp"))?; + let cert_1 = Cert::from_bytes(crate::tests::key("testy.pgp"))?; + let tsk_0 = Cert::from_bytes(crate::tests::key("testy-private.pgp"))?; + let tsk_1 = Cert::from_bytes(crate::tests::key("testy-private.pgp"))?; + + // We define equality by equality of the serialized form. + // This macro checks that. + macro_rules! check { + ($a: expr, $b: expr, $expectation: expr) => {{ + let a = $a; + let b = $b; + let serialized_eq = a.to_vec()? == b.to_vec()?; + let eq = a == b; + assert_eq!(serialized_eq, eq); + assert_eq!(eq, $expectation); + }}; + } + + // Equal as certs, because equality is defined by equality of + // the serialized form, and serializing a cert never writes + // out any secrets. + check!(&cert_0, &cert_1, true); + check!(&cert_0, &tsk_0, true); + + // Filters out secrets. + let no_secrets = |_| false; + + // TSK's equality. + check!(tsk_0.as_tsk(), tsk_1.as_tsk(), true); + + // Without secrets. + check!(tsk_0.as_tsk().set_filter(no_secrets), + tsk_1.as_tsk().set_filter(no_secrets), + true); + + // Still equal if one uses stubs and the other one does not if + // every key has a secret, i.e. stubs are not in fact used. + check!( + tsk_0.as_tsk().emit_secret_key_stubs(true), + tsk_1.as_tsk(), + true); + + // No longer equal if one actually emits stubs. + check!( + tsk_0.as_tsk().emit_secret_key_stubs(true).set_filter(no_secrets), + tsk_1.as_tsk(), + false); + + // Certs are not equal to TSKs, because here the secrets are + // written out when serialized. + check!(cert_0.as_tsk(), tsk_0.as_tsk(), false); + + // Equal, if we filter out the secrets. + check!(cert_0.as_tsk(), + tsk_0.as_tsk().set_filter(no_secrets), + true); + + Ok(()) + } } |