diff options
Diffstat (limited to 'src/grease.rs')
-rw-r--r-- | src/grease.rs | 143 |
1 files changed, 138 insertions, 5 deletions
diff --git a/src/grease.rs b/src/grease.rs index d7f80a2..b89f828 100644 --- a/src/grease.rs +++ b/src/grease.rs @@ -13,20 +13,35 @@ // limitations under the License. //! -//! Adds deliberate errors to client responses. +//! Adds deliberate errors to client responses as part of the +//! [Roughtime Ecosystem](https://roughtime.googlesource.com/roughtime/+/HEAD/ECOSYSTEM.md#maintaining-a-healthy-software-ecosystem). +//! +//! See the documentation for [ServerConfig](../config/trait.ServerConfig.html#tymethod.fault_percentage) +//! on how to enable. //! use rand::{FromEntropy, Rng}; +use rand::distributions::Bernoulli; use rand::rngs::SmallRng; -use rand::distributions::{Bernoulli, Distribution}; use rand::seq::SliceRandom; +use rand::seq::index::sample as index_sample; use crate::RtMessage; +use crate::tag::Tag; use crate::grease::Pathologies::*; +use crate::SIGNATURE_LENGTH; -enum Pathologies { +/// +/// Ways that a message can be made invalid. +/// +pub enum Pathologies { + /// Randomly re-order the (tag, value) pairs in the message. This violates the protocol's + /// requirement that tags must be in strictly increasing order. RandomlyOrderTags, + + /// Replace the server's signature (value of the SIG tag) with random garbage. CorruptResponseSignature, + // TODO(stuart) semantic pathologies } @@ -35,6 +50,10 @@ static ALL_PATHOLOGIES: &[Pathologies] = &[ CorruptResponseSignature ]; +/// +/// Adds deliberate errors to client responses as part of the +/// [Roughtime Ecosystem](https://roughtime.googlesource.com/roughtime/+/HEAD/ECOSYSTEM.md#maintaining-a-healthy-software-ecosystem). +/// pub struct Grease { enabled: bool, dist: Bernoulli, @@ -42,6 +61,9 @@ pub struct Grease { } impl Grease { + /// + /// Creates a new instance `fault_percentage` likely to corrupt a source message. + /// pub fn new(fault_percentage: u8) -> Self { Grease { enabled: fault_percentage > 0, @@ -50,24 +72,77 @@ impl Grease { } } + /// + /// Returns true `fault_percentage` percent of the time. + /// #[inline] pub fn should_add_error(&mut self) -> bool { if self.enabled { self.prng.sample(self.dist) } else { false } } + /// + /// Returns a *new* `RtMessage` that has been altered to be deliberately invalid. + /// + /// The type of alteration made to `src_msg` is randomly chosen from from + /// [Pathologies](enum.Pathologies.html) + /// pub fn add_errors(&mut self, src_msg: &RtMessage) -> RtMessage { match ALL_PATHOLOGIES.choose(&mut self.prng) { - Some(CorruptResponseSignature) => src_msg.to_owned(), - Some(RandomlyOrderTags) => src_msg.to_owned(), + Some(CorruptResponseSignature) => self.corrupt_response_signature(src_msg), + Some(RandomlyOrderTags) => self.randomly_order_tags(src_msg), None => unreachable!() } } + + /// + /// Randomly shuffle ordering of the (tag, value) pairs in the source message. + /// + fn randomly_order_tags(&mut self, src_msg: &RtMessage) -> RtMessage { + let src_tags = src_msg.tags(); + let src_values = src_msg.values(); + let num_fields = src_msg.num_fields() as usize; + + let mut new_tags: Vec<Tag> = Vec::with_capacity(num_fields); + let mut new_values: Vec<Vec<u8>> = Vec::with_capacity(num_fields); + + // TODO(stuart) use replacement instead of copying + for idx in index_sample(&mut self.prng, num_fields, num_fields).iter() { + new_tags.push(*src_tags.get(idx).unwrap()); + new_values.push(src_values.get(idx).unwrap().to_vec()); + } + + RtMessage::new_deliberately_invalid(new_tags, new_values) + } + + /// + /// Replace valid SIG signature with random garbage + /// + fn corrupt_response_signature(&self, src_msg: &RtMessage) -> RtMessage { + if src_msg.get_field(Tag::SIG).is_none() { + return src_msg.to_owned(); + } + + let mut prng = SmallRng::from_entropy(); + let mut random_sig: [u8; SIGNATURE_LENGTH as usize] = [0u8; SIGNATURE_LENGTH as usize]; + + prng.fill(&mut random_sig); + + let mut new_msg = RtMessage::new(src_msg.num_fields()); + new_msg.add_field(Tag::SIG, &random_sig).unwrap(); + new_msg.add_field(Tag::PATH, src_msg.get_field(Tag::PATH).unwrap()).unwrap(); + new_msg.add_field(Tag::SREP, src_msg.get_field(Tag::SREP).unwrap()).unwrap(); + new_msg.add_field(Tag::CERT, src_msg.get_field(Tag::CERT).unwrap()).unwrap(); + new_msg.add_field(Tag::INDX, src_msg.get_field(Tag::INDX).unwrap()).unwrap(); + + new_msg + } } #[cfg(test)] mod test { use crate::grease::Grease; use crate::RtMessage; + use crate::tag::Tag; #[test] fn verify_error_probability() { @@ -91,4 +166,62 @@ mod test { ); } } + + #[test] + fn check_tag_reordering() { + let pairs = [ + (Tag::SIG, [b'0']), + (Tag::NONC, [b'1']), + (Tag::DELE, [b'2']), + (Tag::PATH, [b'3']), + (Tag::RADI, [b'4']), + (Tag::PUBK, [b'5']), + (Tag::MIDP, [b'6']), + (Tag::SREP, [b'7']), + (Tag::MINT, [b'8']), + (Tag::ROOT, [b'9']), + (Tag::CERT, [b'a']), + (Tag::MAXT, [b'b']), + (Tag::INDX, [b'c']), + (Tag::PAD, [b'd']) + ]; + + let mut msg = RtMessage::new(14); + for pair in &pairs { + msg.add_field(pair.0, &pair.1).unwrap(); + } + + let mut grease = Grease::new(1); + let reordered = grease.randomly_order_tags(&msg); + println!("orig: {:?}\nnew: {:?}", msg.tags(), reordered.tags()); + + // original and reordered are same length + assert_eq!(msg.num_fields(), reordered.num_fields()); + + // the shuffle took place + assert_ne!(msg.tags(), reordered.tags()); + assert_ne!(msg.values(), reordered.values()); + + // tag still points to same value + for (tag, _) in pairs.iter() { + assert_eq!(msg.get_field(*tag).unwrap(), reordered.get_field(*tag).unwrap()); + } + } + + #[test] + fn check_signature_corruption() { + let mut msg = RtMessage::new(5); + msg.add_field(Tag::SIG, &[b'a']).unwrap(); + msg.add_field(Tag::PATH, &[b'0']).unwrap(); + msg.add_field(Tag::SREP, &[b'1']).unwrap(); + msg.add_field(Tag::CERT, &[b'2']).unwrap(); + msg.add_field(Tag::INDX, &[b'3']).unwrap(); + + let grease = Grease::new(1); + let changed = grease.corrupt_response_signature(&msg); + + println!("orig: {:?}\nnew: {:?}", msg.get_field(Tag::SIG), changed.get_field(Tag::SIG)); + + assert_ne!(msg.get_field(Tag::SIG).unwrap(), changed.get_field(Tag::SIG).unwrap()); + } } |