diff options
author | Neal H. Walfield <neal@pep.foundation> | 2020-07-22 11:46:08 +0200 |
---|---|---|
committer | Neal H. Walfield <neal@pep.foundation> | 2020-07-22 11:46:08 +0200 |
commit | 67d35136fb70226a962232dfe483d83d23c143b5 (patch) | |
tree | e12e69dce38678927c09fc85cfc4e9dad9b2ba4c | |
parent | 8cc0d7b85f979276157d5b51c6ee54d404c57d7b (diff) |
openpgp: Rework the Features implementation and interface.
- Change Features to only store the raw value, a vector of bytes.
Extract features on demand.
- Add methods to query and manipulate unknown features.
- Change `PartialEq` to implement serialized equality (like most
other data structures). Add `normalized_eq` to implement semantic
equality.
-rw-r--r-- | openpgp/src/cert/mod.rs | 6 | ||||
-rw-r--r-- | openpgp/src/packet/signature/subpacket.rs | 8 | ||||
-rw-r--r-- | openpgp/src/serialize.rs | 4 | ||||
-rw-r--r-- | openpgp/src/types/features.rs | 517 | ||||
-rw-r--r-- | openpgp/src/types/mod.rs | 2 |
5 files changed, 420 insertions, 117 deletions
diff --git a/openpgp/src/cert/mod.rs b/openpgp/src/cert/mod.rs index ae42dc24..8d7c0349 100644 --- a/openpgp/src/cert/mod.rs +++ b/openpgp/src/cert/mod.rs @@ -5130,7 +5130,7 @@ Pu1xwz57O4zo1VYf6TqHJzVC3OMvMUM2hhdecMUe5x6GorNaj6g= // Some(KeyServerPreferences::new(&[]))); assert_eq!(userid.features(), - Some(Features::new(&[]).set_mdc(true))); + Some(Features::new(&[]).set_mdc())); } else { panic!("two user ids"); } @@ -5159,7 +5159,7 @@ Pu1xwz57O4zo1VYf6TqHJzVC3OMvMUM2hhdecMUe5x6GorNaj6g= Some(KeyServerPreferences::new(&[0x80]))); assert_eq!(userid.features(), - Some(Features::new(&[]).set_mdc(true))); + Some(Features::new(&[]).set_mdc())); // Using the certificate should choose the primary user // id, which is this one (because it is lexicographically @@ -5205,7 +5205,7 @@ Pu1xwz57O4zo1VYf6TqHJzVC3OMvMUM2hhdecMUe5x6GorNaj6g= Some(KeyServerPreferences::new(&[0x80]))); assert_eq!(userid.features(), - Some(Features::new(&[]).set_mdc(true))); + Some(Features::new(&[]).set_mdc())); } else { panic!("two user ids"); } diff --git a/openpgp/src/packet/signature/subpacket.rs b/openpgp/src/packet/signature/subpacket.rs index 169a2c14..4049b15c 100644 --- a/openpgp/src/packet/signature/subpacket.rs +++ b/openpgp/src/packet/signature/subpacket.rs @@ -2699,13 +2699,13 @@ fn accessors() { assert_eq!(sig_.reason_for_revocation(), Some((ReasonForRevocation::KeyRetired, &b"foobar"[..]))); - let feats = Features::default().set_mdc(true); + let feats = Features::default().set_mdc(); sig = sig.set_features(&feats).unwrap(); let sig_ = sig.clone().sign_hash(&mut keypair, hash.clone()).unwrap(); assert_eq!(sig_.features().unwrap(), feats); - let feats = Features::default().set_aead(true); + let feats = Features::default().set_aead(); sig = sig.set_features(&feats).unwrap(); let sig_ = sig.clone().sign_hash(&mut keypair, hash.clone()).unwrap(); @@ -2981,13 +2981,13 @@ fn subpacket_test_2() { KeyFlags::default().set_certification(true).set_signing(true)) })); - assert_eq!(sig.features().unwrap(), Features::default().set_mdc(true)); + assert_eq!(sig.features().unwrap(), Features::default().set_mdc()); assert_eq!(sig.subpacket(SubpacketTag::Features), Some(&Subpacket { length: 2.into(), critical: false, value: SubpacketValue::Features( - Features::default().set_mdc(true)) + Features::default().set_mdc()) })); let keyid = "F004 B9A4 5C58 6126".parse().unwrap(); diff --git a/openpgp/src/serialize.rs b/openpgp/src/serialize.rs index 0640f3cc..f75658da 100644 --- a/openpgp/src/serialize.rs +++ b/openpgp/src/serialize.rs @@ -1401,7 +1401,7 @@ impl Marshal for SubpacketValue { o.write_all(reason)?; }, Features(ref f) => - o.write_all(&f.to_vec())?, + o.write_all(&f.as_slice())?, SignatureTarget { pk_algo, hash_algo, ref digest } => { o.write_all(&[(*pk_algo).into(), (*hash_algo).into()])?; o.write_all(digest)?; @@ -1459,7 +1459,7 @@ impl MarshalInto for SubpacketValue { KeyFlags(ref f) => f.to_vec().len(), SignersUserID(ref uid) => uid.len(), ReasonForRevocation { ref reason, .. } => 1 + reason.len(), - Features(ref f) => f.to_vec().len(), + Features(ref f) => f.as_slice().len(), SignatureTarget { ref digest, .. } => 2 + digest.len(), EmbeddedSignature(sig) => sig.serialized_len(), IssuerFingerprint(ref fp) => match fp { diff --git a/openpgp/src/types/features.rs b/openpgp/src/types/features.rs index 9756ca70..22514d09 100644 --- a/openpgp/src/types/features.rs +++ b/openpgp/src/types/features.rs @@ -4,18 +4,26 @@ use std::hash::{Hash, Hasher}; #[cfg(any(test, feature = "quickcheck"))] use quickcheck::{Arbitrary, Gen}; -/// Describes features supported by an OpenPGP implementation. +/// Describes the features supported by an OpenPGP implementation. /// -/// The features are defined in [Section 5.2.3.24 of RFC 4880], and -/// [Section 5.2.3.25 of RFC 4880bis]. +/// The feature flags are defined in [Section 5.2.3.24 of RFC 4880], +/// and [Section 5.2.3.25 of RFC 4880bis]. /// /// [Section 5.2.3.24 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.24 /// [Section 5.2.3.25 of RFC 4880bis]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-09#section-5.2.3.25 /// +/// The feature flags are set by the user's OpenPGP implementation to +/// signal to any senders what features the implementation supports. +/// /// # A note on equality /// -/// `PartialEq` is implements semantic equality, i.e. it ignores -/// padding. +/// `PartialEq` compares the serialized form of the two feature sets. +/// If you prefer to compare two feature sets for semantic equality, +/// you should use [`Features::normalized_eq`]. The difference +/// between semantic equality and serialized equality is that semantic +/// equality ignores differences in the amount of padding. +/// +/// [`Features::normalized_eq`]: #method.normalized_eq /// /// # Examples /// @@ -44,59 +52,70 @@ use quickcheck::{Arbitrary, Gen}; /// } /// # Ok(()) } /// ``` -#[derive(Clone)] +#[derive(Clone, PartialEq)] pub struct Features{ - mdc: bool, - aead: bool, - unknown: Box<[u8]>, - /// Original length, including trailing zeros. - pad_to: usize, + raw: Vec<u8>, } impl Default for Features { fn default() -> Self { - Features{ - mdc: false, - aead: false, - unknown: Default::default(), - pad_to: 0, + Features { + raw: Vec::new(), } } } impl fmt::Debug for Features { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let mut dirty = false; + // Print known features first. + let mut need_comma = false; if self.supports_mdc() { f.write_str("MDC")?; - dirty = true; + need_comma = true; } if self.supports_aead() { - if dirty { f.write_str(", ")?; } + if need_comma { f.write_str(", ")?; } f.write_str("AEAD")?; - dirty = true; + need_comma = true; } - if ! self.unknown.is_empty() { - if dirty { f.write_str(", ")?; } - f.write_str("+0x")?; - f.write_str( - &crate::fmt::hex::encode_pretty(&self.unknown))?; - dirty = true; + + // Now print any unknown features. + for i in self.raw.iter() + .flat_map(|b| { + (0..8).into_iter().map(move |i| { + b & (1 << i) != 0 + }) + }) + .enumerate() + .filter_map(|(i, v)| if v { Some(i) } else { None }) + { + match i { + FEATURE_FLAG_MDC => (), + FEATURE_FLAG_AEAD => (), + i => { + if need_comma { f.write_str(", ")?; } + write!(f, "#{}", i)?; + need_comma = true; + } + } } - if self.pad_to > FEATURE_FLAGS_N_KNOWN_BYTES + self.unknown.len() { - if dirty { f.write_str(", ")?; } - write!(f, "+padding({} bytes)", self.pad_to - self.unknown.len())?; + + // Mention any padding, as equality is sensitive to this. + let mut padding = 0; + for i in (0..self.raw.len()).rev() { + if self.raw[i] == 0 { + padding += 1; + } else { + break; + } } - Ok(()) - } -} + if padding > 0 { + if need_comma { f.write_str(", ")?; } + write!(f, "+padding({} bytes)", padding)?; + } -impl PartialEq for Features { - fn eq(&self, other: &Self) -> bool { - self.mdc == other.mdc - && self.aead == other.aead - && self.unknown == other.unknown + Ok(()) } } @@ -104,110 +123,317 @@ impl Eq for Features {} impl Hash for Features { fn hash<H: Hasher>(&self, state: &mut H) { - self.mdc.hash(state); - self.aead.hash(state); - self.unknown.hash(state); + self.raw.hash(state); } } impl Features { - /// Creates a new instance from `bits`. - pub fn new<B: AsRef<[u8]>>(bits: B) -> Self { - let bits = bits.as_ref(); - let mut pad_to = 0; - - let mdc = bits.get(0) - .map(|x| x & FEATURE_FLAG_MDC != 0).unwrap_or(false); - let aead = bits.get(0) - .map(|x| x & FEATURE_FLAG_AEAD != 0).unwrap_or(false); - let unk = if bits.is_empty() { - Box::default() - } else { - let mut cpy = Vec::from(bits); + /// Creates a new instance from `bytes`. + /// + /// This does not remove any trailing padding from `bytes`. + pub fn new<B>(bytes: B) -> Self + where B: AsRef<[u8]> + { + Features{ + raw: bytes.as_ref().to_vec(), + } + } + + /// Returns an empty feature set. + pub fn empty() -> Self { + Self::new(&[][..]) + } + + /// Returns a feature set describing Sequoia's capabilities. + pub fn sequoia() -> Self { + let v : [u8; 1] = [ 0 ]; - cpy[0] &= (FEATURE_FLAG_MDC | FEATURE_FLAG_AEAD) ^ 0xff; + Self::new(&v[..]).set_mdc() + } - pad_to = crate::types::bitfield_remove_padding(&mut cpy); - cpy.into_boxed_slice() + /// Compares two feature sets for semantic equality. + /// + /// `Features`' implementation of `PartialEq` compares two feature + /// sets for serialized equality. That is, the `PartialEq` + /// implementation considers two feature sets to *not* be equal if + /// they have different amounts of padding. This comparison + /// function ignores padding. + /// + /// # Examples + /// + /// ``` + /// use sequoia_openpgp as openpgp; + /// # use openpgp::Result; + /// use openpgp::types::Features; + /// + /// # fn main() -> Result<()> { + /// let a = Features::new(&[ 0x1 ]); + /// let b = Features::new(&[ 0x1, 0x0 ]); + /// + /// assert!(a != b); + /// assert!(a.normalized_eq(&b)); + /// # Ok(()) } + /// ``` + pub fn normalized_eq(&self, other: &Self) -> bool { + let (small, big) = if self.raw.len() < other.raw.len() { + (self, other) + } else { + (other, self) }; - Features{ - mdc, aead, unknown: unk, pad_to, + for (s, b) in small.raw.iter().zip(big.raw.iter()) { + if s != b { + return false; + } } - } - /// Returns an feature set describing Sequoia. - pub fn sequoia() -> Self { - Features{ - mdc: true, - aead: false, - unknown: Default::default(), - pad_to: 0, + for &b in &big.raw[small.raw.len()..] { + if b != 0 { + return false; + } } + + true } - /// Returns a slice referencing the raw values. - pub(crate) fn to_vec(&self) -> Vec<u8> { - let mut ret = if self.unknown.is_empty() { - vec![0] + /// Returns a slice containing the raw values. + pub(crate) fn as_slice(&self) -> &[u8] { + &self.raw + } + + /// Returns whether the specified feature flag is set. + /// + /// # Examples + /// + /// ``` + /// use sequoia_openpgp as openpgp; + /// # use openpgp::Result; + /// use openpgp::types::Features; + /// + /// # fn main() -> Result<()> { + /// // Feature flags 0 and 2. + /// let f = Features::new(&[ 0x5 ]); + /// + /// assert!(f.check(0)); + /// assert!(! f.check(1)); + /// assert!(f.check(2)); + /// assert!(! f.check(3)); + /// assert!(! f.check(8)); + /// assert!(! f.check(80)); + /// # assert!(f.supports_mdc()); + /// # assert!(! f.supports_aead()); + /// # Ok(()) } + /// ``` + pub fn check(&self, bit: usize) -> bool { + let byte = bit / 8; + + if byte >= self.raw.len() { + // Unset bits are false. + false } else { - self.unknown.clone().into() - }; + (self.raw[byte] & (1 << (bit % 8))) != 0 + } + } - if self.mdc { ret[0] |= FEATURE_FLAG_MDC; } - if self.aead { ret[0] |= FEATURE_FLAG_AEAD; } + /// Remove any trailing padding. + fn clear_padding(mut self) -> Self { + while self.raw.len() > 0 && self.raw[self.raw.len() - 1] == 0 { + self.raw.truncate(self.raw.len() - 1); + } + + self + } - // Corner case: empty flag field. We initialized ret to - // vec![0] for easy setting of flags. See if any of the above - // was set. - if ret.len() == 1 && ret[0] == 0 { - // Nope. Trim this byte. - ret.pop(); + /// Sets the specified feature flag. + /// + /// This also clears any padding (trailing NUL bytes). + /// + /// # Examples + /// + /// ``` + /// use sequoia_openpgp as openpgp; + /// # use openpgp::Result; + /// use openpgp::types::Features; + /// + /// # fn main() -> Result<()> { + /// let f = Features::empty().set(0).set(2); + /// + /// assert!(f.check(0)); + /// assert!(! f.check(1)); + /// assert!(f.check(2)); + /// assert!(! f.check(3)); + /// # assert!(f.supports_mdc()); + /// # assert!(! f.supports_aead()); + /// # Ok(()) } + /// ``` + pub fn set(mut self, bit: usize) -> Self { + let byte = bit / 8; + while self.raw.len() <= byte { + self.raw.push(0); } + self.raw[byte] |= 1 << (bit % 8); - for _ in ret.len()..self.pad_to { - ret.push(0); + self.clear_padding() + } + + /// Clears the specified feature flag. + /// + /// This also clears any padding (trailing NUL bytes). + /// + /// # Examples + /// + /// ``` + /// use sequoia_openpgp as openpgp; + /// # use openpgp::Result; + /// use openpgp::types::Features; + /// + /// # fn main() -> Result<()> { + /// let f = Features::empty().set(0).set(2).clear(2); + /// + /// assert!(f.check(0)); + /// assert!(! f.check(1)); + /// assert!(! f.check(2)); + /// assert!(! f.check(3)); + /// # assert!(f.supports_mdc()); + /// # assert!(! f.supports_aead()); + /// # Ok(()) } + /// ``` + pub fn clear(mut self, bit: usize) -> Self { + let byte = bit / 8; + if byte < self.raw.len() { + self.raw[byte] &= !(1 << (bit % 8)); } - ret + self.clear_padding() } - /// Whether or not MDC is supported. + /// Returns whether the MDC feature flag is set. + /// + /// # Examples + /// + /// ``` + /// use sequoia_openpgp as openpgp; + /// # use openpgp::Result; + /// use openpgp::types::Features; + /// + /// # fn main() -> Result<()> { + /// let f = Features::empty(); + /// + /// assert!(! f.supports_mdc()); + /// # Ok(()) } + /// ``` pub fn supports_mdc(&self) -> bool { - self.mdc + self.check(FEATURE_FLAG_MDC) } - /// Sets whether or not MDC is supported. - pub fn set_mdc(mut self, v: bool) -> Self { - self.mdc = v; - self + /// Sets the MDC feature flag. + /// + /// # Examples + /// + /// ``` + /// use sequoia_openpgp as openpgp; + /// # use openpgp::Result; + /// use openpgp::types::Features; + /// + /// # fn main() -> Result<()> { + /// let f = Features::empty().set_mdc(); + /// + /// assert!(f.supports_mdc()); + /// # assert!(f.check(0)); + /// # Ok(()) } + /// ``` + pub fn set_mdc(self) -> Self { + self.set(FEATURE_FLAG_MDC) + } + + /// Clears the MDC feature flag. + /// + /// # Examples + /// + /// ``` + /// use sequoia_openpgp as openpgp; + /// # use openpgp::Result; + /// use openpgp::types::Features; + /// + /// # fn main() -> Result<()> { + /// let f = Features::new(&[ 0x1 ]); + /// assert!(f.supports_mdc()); + /// + /// let f = f.clear_mdc(); + /// assert!(! f.supports_mdc()); + /// # Ok(()) } + /// ``` + pub fn clear_mdc(self) -> Self { + self.clear(FEATURE_FLAG_MDC) } - /// Whether or not AEAD is supported. + /// Returns whether the AEAD feature flag is set. + /// + /// # Examples + /// + /// ``` + /// use sequoia_openpgp as openpgp; + /// # use openpgp::Result; + /// use openpgp::types::Features; + /// + /// # fn main() -> Result<()> { + /// let f = Features::empty(); /// - /// This feature is [experimental](../index.html#experimental-features). + /// assert!(! f.supports_aead()); + /// # Ok(()) } + /// ``` pub fn supports_aead(&self) -> bool { - self.aead + self.check(FEATURE_FLAG_AEAD) } - /// Sets whether or not AEAD is supported. + /// Sets the AEAD feature flag. /// - /// This feature is [experimental](../index.html#experimental-features). - pub fn set_aead(mut self, v: bool) -> Self { - self.aead = v; - self + /// # Examples + /// + /// ``` + /// use sequoia_openpgp as openpgp; + /// # use openpgp::Result; + /// use openpgp::types::Features; + /// + /// # fn main() -> Result<()> { + /// let f = Features::empty().set_aead(); + /// + /// assert!(f.supports_aead()); + /// # assert!(f.check(1)); + /// # Ok(()) } + /// ``` + pub fn set_aead(self) -> Self { + self.set(FEATURE_FLAG_AEAD) + } + + /// Clears the AEAD feature flag. + /// + /// # Examples + /// + /// ``` + /// use sequoia_openpgp as openpgp; + /// # use openpgp::Result; + /// use openpgp::types::Features; + /// + /// # fn main() -> Result<()> { + /// let f = Features::new(&[ 0x2 ]); + /// assert!(f.supports_aead()); + /// + /// let f = f.clear_aead(); + /// assert!(! f.supports_aead()); + /// # Ok(()) } + /// ``` + pub fn clear_aead(self) -> Self { + self.clear(FEATURE_FLAG_AEAD) } } /// Modification Detection (packets 18 and 19). -const FEATURE_FLAG_MDC: u8 = 0x01; +const FEATURE_FLAG_MDC: usize = 0; /// AEAD Encrypted Data Packet (packet 20) and version 5 Symmetric-Key /// Encrypted Session Key Packets (packet 3). -const FEATURE_FLAG_AEAD: u8 = 0x02; - -/// Number of bytes with known flags. -const FEATURE_FLAGS_N_KNOWN_BYTES: usize = 1; +const FEATURE_FLAG_AEAD: usize = 1; #[cfg(any(test, feature = "quickcheck"))] impl Arbitrary for Features { @@ -222,15 +448,92 @@ mod tests { quickcheck! { fn roundtrip(val: Features) -> bool { - let q = Features::new(&val.to_vec()); + let mut q = Features::new(val.as_slice()); assert_eq!(val, q); + assert!(val.normalized_eq(&q)); + + // Add some padding to q. Make sure they are still equal. + q.raw.push(0); + assert!(val != q); + assert!(val.normalized_eq(&q)); - // Check that equality ignores padding. - let mut val_without_padding = val.clone(); - val_without_padding.pad_to = val.unknown.len(); - assert_eq!(val, val_without_padding); + q.raw.push(0); + assert!(val != q); + assert!(val.normalized_eq(&q)); true } } + + #[test] + fn set_clear() { + let a = Features::new(&[ 0x5, 0x1, 0x0, 0xff ]); + let b = Features::new(&[]) + .set(0).set(2) + .set(8) + .set(24).set(25).set(26).set(27).set(28).set(29).set(30).set(31); + assert_eq!(a, b); + + // Clear a bit and make sure they are not equal. + let b = b.clear(0); + assert!(a != b); + assert!(! a.normalized_eq(&b)); + let b = b.set(0); + assert_eq!(a, b); + assert!(a.normalized_eq(&b)); + + let b = b.clear(8); + assert!(a != b); + assert!(! a.normalized_eq(&b)); + let b = b.set(8); + assert_eq!(a, b); + assert!(a.normalized_eq(&b)); + + let b = b.clear(31); + assert!(a != b); + assert!(! a.normalized_eq(&b)); + let b = b.set(31); + assert_eq!(a, b); + assert!(a.normalized_eq(&b)); + + // Add a bit. + let a = a.set(10); + assert!(a != b); + assert!(! a.normalized_eq(&b)); + let b = b.set(10); + assert_eq!(a, b); + assert!(a.normalized_eq(&b)); + + let a = a.set(32); + assert!(a != b); + assert!(! a.normalized_eq(&b)); + let b = b.set(32); + assert_eq!(a, b); + assert!(a.normalized_eq(&b)); + + let a = a.set(1000); + assert!(a != b); + assert!(! a.normalized_eq(&b)); + let b = b.set(1000); + assert_eq!(a, b); + assert!(a.normalized_eq(&b)); + } + + #[test] + fn known() { + let a = Features::empty().set_mdc(); + let b = Features::new(&[ 0x1 ]); + assert_eq!(a, b); + assert!(a.normalized_eq(&b)); + + let a = Features::empty().set_aead(); + let b = Features::new(&[ 0x2 ]); + assert_eq!(a, b); + assert!(a.normalized_eq(&b)); + + let a = Features::empty().set_mdc().set_aead(); + let b = Features::new(&[ 0x1 | 0x2 ]); + assert_eq!(a, b); + assert!(a.normalized_eq(&b)); + } } diff --git a/openpgp/src/types/mod.rs b/openpgp/src/types/mod.rs index 51f8acca..d98e2464 100644 --- a/openpgp/src/types/mod.rs +++ b/openpgp/src/types/mod.rs @@ -737,7 +737,7 @@ impl Arbitrary for SymmetricAlgorithm { /// use openpgp::types::{Features, HashAlgorithm, AEADAlgorithm, SignatureType}; /// /// # fn main() -> openpgp::Result<()> { -/// let features = Features::default().set_aead(true); +/// let features = Features::default().set_aead(); /// let mut builder = SignatureBuilder::new(SignatureType::DirectKey) /// .set_features(&features)? /// .set_preferred_aead_algorithms(vec![ |