diff options
author | Stuart Stock <stuart@int08h.com> | 2019-02-10 18:41:44 -0600 |
---|---|---|
committer | Stuart Stock <stuart@int08h.com> | 2019-02-10 18:41:44 -0600 |
commit | 177372f63a33b25a075c4cba446319929950b0f8 (patch) | |
tree | 5f274bf2f4874a6dc66770c71cf1a036432f122f /src | |
parent | 62e6dd64a276dbf8e10cbe6a4111f0cfb98d163f (diff) | |
download | roughenough-177372f63a33b25a075c4cba446319929950b0f8.zip |
initial version of client response mangling
Diffstat (limited to 'src')
-rw-r--r-- | src/config/mod.rs | 13 | ||||
-rw-r--r-- | src/grease.rs | 94 | ||||
-rw-r--r-- | src/kms/envelope.rs | 6 | ||||
-rw-r--r-- | src/lib.rs | 5 | ||||
-rw-r--r-- | src/message.rs | 13 | ||||
-rw-r--r-- | src/server.rs | 13 | ||||
-rw-r--r-- | src/stats/aggregated.rs | 2 | ||||
-rw-r--r-- | src/stats/mod.rs | 4 | ||||
-rw-r--r-- | src/stats/per_client.rs | 8 |
9 files changed, 137 insertions, 21 deletions
diff --git a/src/config/mod.rs b/src/config/mod.rs index 70da38e..1d1d149 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -22,20 +22,20 @@ //! such as files or environment variables. //! +mod environment; +mod file; +mod memory; + use std::net::SocketAddr; use std::time::Duration; -mod file; -pub use self::file::FileConfig; - -mod environment; pub use self::environment::EnvironmentConfig; - -mod memory; +pub use self::file::FileConfig; pub use self::memory::MemoryConfig; use crate::key::KmsProtection; use crate::Error; +use crate::SEED_LENGTH; /// Maximum number of requests to process in one batch and include the the Merkle tree. pub const DEFAULT_BATCH_SIZE: u8 = 64; @@ -172,6 +172,7 @@ pub fn is_valid_config(cfg: &Box<dyn ServerConfig>) -> bool { ); is_valid = false; } + if cfg.fault_percentage() > 50 { error!("fault_percentage {} is invalid; valid range 0-50", cfg.fault_percentage()); is_valid = false; diff --git a/src/grease.rs b/src/grease.rs new file mode 100644 index 0000000..d7f80a2 --- /dev/null +++ b/src/grease.rs @@ -0,0 +1,94 @@ +// Copyright 2017-2019 int08h LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! +//! Adds deliberate errors to client responses. +//! + +use rand::{FromEntropy, Rng}; +use rand::rngs::SmallRng; +use rand::distributions::{Bernoulli, Distribution}; +use rand::seq::SliceRandom; + +use crate::RtMessage; +use crate::grease::Pathologies::*; + +enum Pathologies { + RandomlyOrderTags, + CorruptResponseSignature, + // TODO(stuart) semantic pathologies +} + +static ALL_PATHOLOGIES: &[Pathologies] = &[ + RandomlyOrderTags, + CorruptResponseSignature +]; + +pub struct Grease { + enabled: bool, + dist: Bernoulli, + prng: SmallRng, +} + +impl Grease { + pub fn new(fault_percentage: u8) -> Self { + Grease { + enabled: fault_percentage > 0, + dist: Bernoulli::from_ratio(u32::from(fault_percentage), 100), + prng: SmallRng::from_entropy(), + } + } + + #[inline] + pub fn should_add_error(&mut self) -> bool { + if self.enabled { self.prng.sample(self.dist) } else { false } + } + + 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(), + None => unreachable!() + } + } +} + +#[cfg(test)] +mod test { + use crate::grease::Grease; + use crate::RtMessage; + + #[test] + fn verify_error_probability() { + const TRIALS: u64 = 100_000; + const TOLERANCE: f64 = 0.75; + + for target in 1..50 { + let mut g = Grease::new(target); + let (lower, upper) = (target as f64 - TOLERANCE, target as f64 + TOLERANCE); + + let acc: u64 = (0..TRIALS) + .map(|_| if g.should_add_error() { 1 } else { 0 }) + .sum(); + + let percentage = 100.0 * (acc as f64 / TRIALS as f64); + + assert_eq!( + percentage > lower && percentage < upper, + true, + "target {}, actual {} [{}, {}]", target, percentage, lower, upper + ); + } + } +} diff --git a/src/kms/envelope.rs b/src/kms/envelope.rs index b82e61d..d83aa64 100644 --- a/src/kms/envelope.rs +++ b/src/kms/envelope.rs @@ -17,7 +17,7 @@ use std::io::{Cursor, Read, Write}; use ring::aead::{open_in_place, seal_in_place, OpeningKey, SealingKey, AES_256_GCM}; use ring::rand::{SecureRandom, SystemRandom}; -use crate::MIN_SEED_LENGTH; +use crate::SEED_LENGTH; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use crate::kms::{KmsError, KmsProvider, AD, DEK_SIZE_BYTES, NONCE_SIZE_BYTES, TAG_SIZE_BYTES}; @@ -33,7 +33,7 @@ const MIN_PAYLOAD_SIZE: usize = DEK_LEN_FIELD + NONCE_LEN_FIELD + DEK_SIZE_BYTES + NONCE_SIZE_BYTES - + MIN_SEED_LENGTH as usize + + SEED_LENGTH as usize + TAG_SIZE_BYTES; // No input prefix to skip, consume entire buffer @@ -41,7 +41,7 @@ const IN_PREFIX_LEN: usize = 0; // Convenience function to create zero-filled Vec of given size fn vec_zero_filled(len: usize) -> Vec<u8> { - (0..len).into_iter().map(|_| 0).collect() + (0..len).map(|_| 0).collect() } /// Envelope encryption of the long-term key seed value. @@ -63,6 +63,7 @@ mod message; mod tag; pub mod config; +pub mod grease; pub mod key; pub mod kms; pub mod merkle; @@ -95,8 +96,8 @@ pub fn roughenough_version() -> String { /// Minimum size (in bytes) of a client request pub const MIN_REQUEST_LENGTH: u32 = 1024; -/// Minimum size (in bytes) of seeds used to derive private keys -pub const MIN_SEED_LENGTH: u32 = 32; +/// Size (in bytes) of seeds used to derive private keys +pub const SEED_LENGTH: u32 = 32; /// Size (in bytes) of an Ed25519 public key pub const PUBKEY_LENGTH: u32 = 32; diff --git a/src/message.rs b/src/message.rs index d598dee..fea3014 100644 --- a/src/message.rs +++ b/src/message.rs @@ -69,6 +69,19 @@ impl RtMessage { } } + /// + /// Dangerous: construct a new RtMessage **without validation or error checking**. + /// + /// Intended _only_ for construction of deliberately bogus responses as part of [Roughtime's + /// ecosystem](https://roughtime.googlesource.com/roughtime/+/HEAD/ECOSYSTEM.md#maintaining-a-healthy-software-ecosystem). + /// + pub fn new_deliberately_invalid(tags: Vec<Tag>, values: Vec<Vec<u8>>) -> Self { + RtMessage { + tags, + values, + } + } + /// Internal function to create a single tag message fn single_tag_message(bytes: &[u8], msg: &mut Cursor<&[u8]>) -> Result<Self, Error> { if bytes.len() < 8 { diff --git a/src/server.rs b/src/server.rs index 85411ce..8d2d7c1 100644 --- a/src/server.rs +++ b/src/server.rs @@ -37,6 +37,7 @@ use mio_extras::timer::Timer; use crate::{Error, RtMessage, Tag, MIN_REQUEST_LENGTH}; use crate::config::ServerConfig; +use crate::grease::Grease; use crate::key::{LongTermKey, OnlineKey}; use crate::kms; use crate::merkle::MerkleTree; @@ -78,6 +79,7 @@ pub struct Server { health_listener: Option<TcpListener>, keep_running: Arc<AtomicBool>, poll_duration: Option<Duration>, + grease: Grease, timer: Timer<()>, poll: Poll, merkle: MerkleTree, @@ -154,6 +156,7 @@ impl Server { Box::new(AggregatedStats::new()) }; + let grease = Grease::new(config.fault_percentage()); let merkle = MerkleTree::new(); let requests = Vec::with_capacity(config.batch_size() as usize); @@ -167,6 +170,7 @@ impl Server { keep_running, poll_duration, + grease, timer, poll, merkle, @@ -357,7 +361,7 @@ impl Server { } } - fn send_responses(&mut self) -> () { + fn send_responses(&mut self) { let merkle_root = self.merkle.compute_root(); // The SREP tag is identical for each response @@ -365,8 +369,11 @@ impl Server { for (i, &(ref nonce, ref src_addr)) in self.requests.iter().enumerate() { let paths = self.merkle.get_paths(i); - let resp = self.make_response(&srep, &self.cert_bytes, &paths, i as u32); - let resp_bytes = resp.encode().unwrap(); + let resp_msg = { + let r = self.make_response(&srep, &self.cert_bytes, &paths, i as u32); + if self.grease.should_add_error() { self.grease.add_errors(&r) } else { r } + }; + let resp_bytes = resp_msg.encode().unwrap(); let bytes_sent = self .socket diff --git a/src/stats/aggregated.rs b/src/stats/aggregated.rs index afff109..389a99d 100644 --- a/src/stats/aggregated.rs +++ b/src/stats/aggregated.rs @@ -91,7 +91,7 @@ impl ServerStats for AggregatedStats { 0 } - fn get_client_stats(&self, _addr: &IpAddr) -> Option<&ClientStatEntry> { + fn stats_for_client(&self, _addr: &IpAddr) -> Option<&ClientStatEntry> { None } diff --git a/src/stats/mod.rs b/src/stats/mod.rs index 29e7ad8..ad90bd2 100644 --- a/src/stats/mod.rs +++ b/src/stats/mod.rs @@ -74,7 +74,7 @@ pub trait ServerStats { fn total_unique_clients(&self) -> u64; - fn get_client_stats(&self, addr: &IpAddr) -> Option<&ClientStatEntry>; + fn stats_for_client(&self, addr: &IpAddr) -> Option<&ClientStatEntry>; fn iter(&self) -> Iter<IpAddr, ClientStatEntry>; @@ -131,7 +131,7 @@ mod test { stats.add_response(&ip, 2048); stats.add_response(&ip, 1024); - let entry = stats.get_client_stats(&ip).unwrap(); + let entry = stats.stats_for_client(&ip).unwrap(); assert_eq!(entry.valid_requests, 1); assert_eq!(entry.invalid_requests, 0); assert_eq!(entry.responses_sent, 2); diff --git a/src/stats/per_client.rs b/src/stats/per_client.rs index 0799a76..8641fde 100644 --- a/src/stats/per_client.rs +++ b/src/stats/per_client.rs @@ -40,7 +40,7 @@ impl PerClientStats { pub fn new() -> Self { PerClientStats { - clients: HashMap::with_capacity(128), + clients: HashMap::with_capacity(64), num_overflows: 0, max_clients: PerClientStats::MAX_CLIENTS, } @@ -50,7 +50,7 @@ impl PerClientStats { #[cfg(test)] pub fn with_limit(limit: usize) -> Self { PerClientStats { - clients: HashMap::with_capacity(128), + clients: HashMap::with_capacity(64), num_overflows: 0, max_clients: limit, } @@ -64,7 +64,7 @@ impl PerClientStats { self.num_overflows += 1; } - return too_big; + too_big } #[allow(dead_code)] @@ -155,7 +155,7 @@ impl ServerStats for PerClientStats { self.clients.len() as u64 } - fn get_client_stats(&self, addr: &IpAddr) -> Option<&ClientStatEntry> { + fn stats_for_client(&self, addr: &IpAddr) -> Option<&ClientStatEntry> { self.clients.get(addr) } |