summaryrefslogtreecommitdiffstats
path: root/openpgp
diff options
context:
space:
mode:
authorJustus Winter <justus@sequoia-pgp.org>2021-08-18 18:05:53 +0200
committerJustus Winter <justus@sequoia-pgp.org>2021-08-19 16:34:10 +0200
commitd12c9930da0e88ee3f8cff18842223c40ac85a83 (patch)
tree7d7c5d3ee547abe67f02aff1af7320ffef092910 /openpgp
parentc2a9394d7ef78d2097386ace0bc19d51710507cb (diff)
openpgp: Implement PartialEq for TSK.
- Comparing Certs ignores any secret key material, in accordance with our definition of equality based on the serialized form. To take secret key material into account, define equality of TSKs. - Fixes #701.
Diffstat (limited to 'openpgp')
-rw-r--r--openpgp/src/cert.rs34
-rw-r--r--openpgp/src/serialize/cert.rs98
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(())
+ }
}