diff --git a/Cargo.lock b/Cargo.lock index 620dc8e..1d97c15 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -245,6 +245,38 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "camino" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "cast" version = "0.3.0" @@ -1139,6 +1171,7 @@ dependencies = [ "serde", "stacker", "static_assertions", + "test-binary", "test_bin", "thiserror", "toml", @@ -1290,6 +1323,9 @@ name = "semver" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" +dependencies = [ + "serde", +] [[package]] name = "serde" @@ -1403,6 +1439,19 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "test-binary" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c7cb854285c40b61c0fade358bf63a2bb1226688a1ea11432ea65349209e6e3" +dependencies = [ + "camino", + "cargo_metadata", + "once_cell", + "paste", + "thiserror", +] + [[package]] name = "test_bin" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index a625694..8e2323c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,3 +60,4 @@ chacha20poly1305 = { version = "0.10.1", default-features = false, features = [ libc = "0.2" zerocopy = { version = "0.7.32", features = ["derive"] } home = "0.5.9" +test-binary = "3.0.2" diff --git a/rosenpass/Cargo.toml b/rosenpass/Cargo.toml index d13f7a6..6ff106a 100644 --- a/rosenpass/Cargo.toml +++ b/rosenpass/Cargo.toml @@ -9,6 +9,15 @@ homepage = "https://rosenpass.eu/" repository = "https://github.com/rosenpass/rosenpass" readme = "readme.md" +[[bin]] +name = "rosenpass" +path = "src/main.rs" + +[[bin]] +name = "rp-it-dos" +required-features = ["integration_test_dos_exchange"] +path = "src/main.rs" + [[bench]] name = "handshake" harness = false @@ -42,7 +51,9 @@ anyhow = { workspace = true } [dev-dependencies] criterion = { workspace = true } test_bin = { workspace = true } +test-binary = { workspace = true } stacker = { workspace = true } +libc = { workspace = true} [features] -integration_test = ["dep:libc"] +integration_test_dos_exchange = ["dep:libc"] diff --git a/rosenpass/src/app_server.rs b/rosenpass/src/app_server.rs index e8f98d0..fd12a3a 100644 --- a/rosenpass/src/app_server.rs +++ b/rosenpass/src/app_server.rs @@ -34,13 +34,13 @@ use rosenpass_util::b64::{b64_writer, fmt_b64}; const IPV4_ANY_ADDR: Ipv4Addr = Ipv4Addr::new(0, 0, 0, 0); const IPV6_ANY_ADDR: Ipv6Addr = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0); -#[cfg(feature = "integration_test")] +#[cfg(feature = "integration_test_dos_exchange")] const UNDER_LOAD_RATIO: f64 = 0.001; -#[cfg(feature = "integration_test")] +#[cfg(feature = "integration_test_dos_exchange")] const DURATION_UPDATE_UNDER_LOAD_STATUS: Duration = Duration::from_millis(10); -#[cfg(not(feature = "integration_test"))] +#[cfg(not(feature = "integration_test_dos_exchange"))] const UNDER_LOAD_RATIO: f64 = 0.5; -#[cfg(not(feature = "integration_test"))] +#[cfg(not(feature = "integration_test_dos_exchange"))] const DURATION_UPDATE_UNDER_LOAD_STATUS: Duration = Duration::from_millis(100); fn ipv4_any_binding() -> SocketAddr { @@ -830,12 +830,12 @@ impl AppServer { 0.0 }; - #[cfg(feature = "integration_test")] + #[cfg(feature = "integration_test_dos_exchange")] let prev_under_load = self.under_load; if load_ratio > UNDER_LOAD_RATIO { self.under_load = DoSOperation::UnderLoad; //Test feature- if under load goes to normal operation, write to file - #[cfg(feature = "integration_test")] + #[cfg(feature = "integration_test_dos_exchange")] { if prev_under_load == DoSOperation::Normal { let sem_name = b"/rp_integration_test_under_dos\0"; @@ -853,7 +853,10 @@ impl AppServer { } } } else { - self.under_load = DoSOperation::Normal; + // Don't switch to normal operation if executing integration test for DoS exchange + if cfg!(not(feature = "integration_test_dos_exchange")) { + self.under_load = DoSOperation::Normal; + } } self.blocking_polls_count = 0; diff --git a/rosenpass/src/protocol.rs b/rosenpass/src/protocol.rs index c776ceb..1f15092 100644 --- a/rosenpass/src/protocol.rs +++ b/rosenpass/src/protocol.rs @@ -1498,12 +1498,13 @@ impl Envelope where M: AsBytes + FromBytes, { - /// Calculate the message authentication code (`mac`) + /// Calculate the message authentication code (`mac`) and also append cookie value pub fn seal(&mut self, peer: PeerPtr, srv: &CryptoServer) -> Result<()> { let mac = hash_domains::mac()? .mix(peer.get(srv).spkt.secret())? .mix(&self.as_bytes()[span_of!(Self, msg_type..mac)])?; self.mac.copy_from_slice(mac.into_value()[..16].as_ref()); + self.seal_cookie(peer, srv)?; Ok(()) } diff --git a/rosenpass/tests/integration_test.rs b/rosenpass/tests/integration_test.rs index 15b1d74..4bb47c8 100644 --- a/rosenpass/tests/integration_test.rs +++ b/rosenpass/tests/integration_test.rs @@ -123,10 +123,23 @@ fn check_exchange_under_normal() { fs::remove_dir_all(&tmpdir).unwrap(); } -// check that we can exchange keys -#[cfg(feature = "integration_test")] +// check that we can trigger a DoS condition and we can exchange keys under DoS +// This test creates a responder (server) with the feature flag "integration_test_dos_exchange". The feature flag posts a semaphore +// (linux) to indicate that the server is under load condition. It also modifies the responders behaviour to be permanently under DoS condition +// once triggered, and makes the DoS mechanism more sensitive to be easily triggered. +// The test also creates a thread to send UDP packets to the server to trigger the DoS condition. The test waits for the server to +// be under load condition and then stops the DoS attack. The test then starts the client (initiator) to exchange keys. The test checks that the keys are exchanged successfully under load condition. #[test] fn check_exchange_under_dos() { + //Generate binary with responder with feature integration_test + let server_test_bin = test_binary::TestBinary::relative_to_parent( + "rp-it-dos", + &PathBuf::from_iter(["Cargo.toml"]), + ) + .with_feature("integration_test_dos_exchange") + .build() + .unwrap(); + let tmpdir = PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join("exchange-dos"); fs::create_dir_all(&tmpdir).unwrap(); @@ -164,7 +177,8 @@ fn check_exchange_under_dos() { } }; let listen_addr = format!("localhost:{port}"); - let mut server = test_bin::get_test_bin(BIN) + let mut server = std::process::Command::new(server_test_bin) + .args(["--log-level", "debug"]) .args(["exchange", "secret-key"]) .arg(&secret_key_paths[0]) .arg("public-key") @@ -226,8 +240,12 @@ fn check_exchange_under_dos() { panic!("Failed to wait for semaphore- load condition not reached"); } + //Stop DoS attack + stop_dos_handle.store(true, std::sync::atomic::Ordering::Relaxed); + // start second process, the client let mut client = test_bin::get_test_bin(BIN) + .args(["--log-level", "debug"]) .args(["exchange", "secret-key"]) .arg(&secret_key_paths[1]) .arg("public-key") @@ -248,7 +266,6 @@ fn check_exchange_under_dos() { // time's up, kill the childs server.kill().unwrap(); client.kill().unwrap(); - stop_dos_handle.store(true, std::sync::atomic::Ordering::Relaxed); dos_attack.join().unwrap(); // read the shared keys they created