diff --git a/Cargo.toml b/Cargo.toml index 86de97b..658f0a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,3 +19,4 @@ nonzero_ext = "0.3.0" [dev-dependencies] tempfile = "3" +mockall = "0.11.4" diff --git a/src/command_runner.rs b/src/command_runner.rs index ec10437..75a1d3b 100644 --- a/src/command_runner.rs +++ b/src/command_runner.rs @@ -1,4 +1,6 @@ use async_trait::async_trait; +#[cfg(test)] +use mockall::mock; use std::error::Error; use std::ffi::OsStr; use std::io::Result as IoResult; @@ -33,30 +35,57 @@ pub fn get_output(output: Output) -> Result, Box> { #[async_trait(?Send)] pub trait CommandRunner { - async fn run(&self, program: &str, args: &[&OsStr], stdin: &[u8]) -> IoResult; + async fn run<'a>(&self, program: &str, args: &'a [&'a OsStr], input: &[u8]) -> IoResult; - async fn run_with_args(&self, program: &str, args: &[&OsStr]) -> IoResult { + async fn run_with_args<'a>(&self, program: &str, args: &'a [&'a OsStr]) -> IoResult { self.run(program, args, b"").await } - async fn get_output(&self, program: &str, args: &[&OsStr]) -> Result, Box> { + async fn get_output<'a>( + &self, + program: &str, + args: &'a [&'a OsStr], + ) -> Result, Box> { let output = self.run_with_args(program, args).await?; get_output(output) } - async fn run_successfully(&self, program: &str, args: &[&OsStr]) -> Result<(), Box> { + async fn run_successfully<'a>( + &self, + program: &str, + args: &'a [&'a OsStr], + ) -> Result<(), Box> { is_success(self.run(program, args, b"").await)?; Ok(()) } - async fn get_stderr(&self, program: &str, args: &[&OsStr]) -> Result, Box> { + async fn get_stderr<'a>( + &self, + program: &str, + args: &'a [&'a OsStr], + ) -> Result, Box> { Ok(is_success(self.run_with_args(program, args).await)?.stderr) } } +#[cfg(test)] +mock! { + pub CommandRunner { + } + #[async_trait(?Send)] + impl CommandRunner for CommandRunner { + async fn run<'a>(&self, program: &str, args: &'a [&'a OsStr], input: &[u8]) -> IoResult; + + async fn run_with_args<'a>(&self, program: &str, args: &'a [&'a OsStr]) -> IoResult; + async fn get_output<'a>(&self, program: &str, args: &'a [&'a OsStr]) -> Result, Box>; + async fn run_successfully<'a>(&self, program: &str, args: &'a [&'a OsStr]) -> Result<(), Box>; + async fn get_stderr<'a>(&self, program: &str, args: &'a [&'a OsStr]) -> Result, Box>; + } +} + #[derive(Debug)] pub struct StdCommandRunner; #[async_trait(?Send)] impl CommandRunner for StdCommandRunner { - async fn run(&self, program: &str, args: &[&OsStr], input: &[u8]) -> IoResult { + async fn run<'a>(&self, program: &str, args: &'a [&'a OsStr], input: &[u8]) -> IoResult { //println!("{} {:?}", program, args); let mut child = Command::new(program) .args(args) @@ -114,7 +143,7 @@ impl Drop for TempSetEnv<'_> { #[async_trait(?Send)] impl> CommandRunner for SetuidCommandRunner { - async fn run(&self, program: &str, args: &[&OsStr], input: &[u8]) -> IoResult { + async fn run<'a>(&self, program: &str, args: &'a [&'a OsStr], input: &[u8]) -> IoResult { let uid = get_user_by_name(self.user_name.as_ref()) .expect("User does not exist") .uid(); diff --git a/src/symbols/git/checkout.rs b/src/symbols/git/checkout.rs index 43be14b..b859425 100644 --- a/src/symbols/git/checkout.rs +++ b/src/symbols/git/checkout.rs @@ -92,63 +92,30 @@ impl<_C: CommandRunner, C: Borrow<_C>, P: AsRef, S: AsRef, B: AsRef>>, - } - #[async_trait(?Send)] - impl CommandRunner for DummyCommandRunner { - async fn run(&self, program: &str, args: &[&OsStr], stdin: &[u8]) -> IoResult { - assert_eq!(program, "git"); - assert_eq!(stdin, b""); - sleep(Duration::from_millis(50)).await; - self - .args - .borrow_mut() - .push(args.iter().map(|a| a.to_os_string()).collect()); - Ok(Output { - status: ExitStatus::from_raw(0), - stdout: vec![], - stderr: vec![], - }) - } - } + use crate::command_runner::MockCommandRunner; + use mockall::Sequence; #[test] fn test() { - let c = DummyCommandRunner { - args: RefCell::new(vec![]), - }; - let checkout: Checkout = - Checkout::new(".", "source", "branch", &c); - let start = Instant::now(); + let mut seq = Sequence::new(); + let mut c = MockCommandRunner::new(); + c.expect_get_output() + .times(1) + .withf(|cmd, args| cmd == "git" && args == ["-C", ".", "rev-list", "-1", "HEAD"]) + .returning(|_, _| Ok("".into())); + c.expect_get_output() + .times(1) + .in_sequence(&mut seq) + .withf(|cmd, args| cmd == "git" && args == ["-C", ".", "fetch", "source", "branch"]) + .returning(|_, _| Ok("".into())); + c.expect_get_output() + .times(1) + .in_sequence(&mut seq) + .withf(|cmd, args| cmd == "git" && args == ["-C", ".", "rev-list", "-1", "FETCH_HEAD"]) + .returning(|_, _| Ok("".into())); + let checkout = Checkout::new(".", "source", "branch", c); assert!(run(checkout.target_reached()).unwrap()); - let end = Instant::now(); - let mut args = c.args.into_inner(); - let first_two_args = &mut args[0..2]; - first_two_args.sort_unstable(); - assert_eq!( - first_two_args, - [ - ["-C", ".", "fetch", "source", "branch"], - ["-C", ".", "rev-list", "-1", "HEAD"], - ] - ); - assert_eq!(args[2], ["-C", ".", "rev-list", "-1", "FETCH_HEAD"]); - - assert!((end - start).as_millis() >= 100); - assert!((end - start).as_millis() < 150); } } diff --git a/src/symbols/tls/key.rs b/src/symbols/tls/key.rs index acbb8cc..811d773 100644 --- a/src/symbols/tls/key.rs +++ b/src/symbols/tls/key.rs @@ -30,7 +30,7 @@ impl> Symbol for Key { return Ok(false); } - let stdout = self + let mut stdout = self .command_runner .get_output( "openssl", @@ -44,9 +44,12 @@ impl> Symbol for Key { ], ) .await?; + if stdout.starts_with("RSA ".as_ref()) { + stdout = stdout.split_off(4); + } Ok( stdout.ends_with(b"RSA key ok\n") - && stdout.starts_with(format!("RSA Private-Key: ({} bit, 2 primes)\n", self.bits).as_ref()), + && stdout.starts_with(format!("Private-Key: ({} bit, 2 primes)\n", self.bits).as_ref()), ) } @@ -67,4 +70,213 @@ impl> Symbol for Key { } #[cfg(test)] -mod test {} +mod test { + use super::{Key, Symbol}; + use crate::async_utils::run; + use crate::command_runner::MockCommandRunner; + + #[test] + fn test_bookworm_success() { + let mut command_runner = MockCommandRunner::new(); + command_runner + .expect_get_output() + .times(1) + .returning(|_, _| { + Ok( + "Private-Key: (4096 bit, 2 primes) +modulus: + 00:... +publicExponent: 65537 (0x10001) +privateExponent: + 57:... +prime1: + 00:... +prime2: + 00:... +exponent1: + 2b:... +exponent2: + 0e:... +coefficient: + 43:... +RSA key ok +" + .into(), + ) + }); + let symbol = Key::new(command_runner, "/"); // FIXME: Has to be an existing file + run(async { + assert_eq!(symbol.target_reached().await.unwrap(), true); + }); + } + + #[test] + fn test_bookworm_short_key() { + let mut command_runner = MockCommandRunner::new(); + command_runner + .expect_get_output() + .times(1) + .returning(|_, _| { + Ok( + "Private-Key: (2048 bit, 2 primes) +modulus: + 00:... +publicExponent: 65537 (0x10001) +privateExponent: + 57:... +prime1: + 00:... +prime2: + 00:... +exponent1: + 2b:... +exponent2: + 0e:... +coefficient: + 43:... +RSA key ok +" + .into(), + ) + }); + let symbol = Key::new(command_runner, "/"); // FIXME: Has to be an existing file + run(async { + assert_eq!(symbol.target_reached().await.unwrap(), false); + }); + } + + #[test] + fn test_bookworm_broken_key() { + let mut command_runner = MockCommandRunner::new(); + command_runner.expect_get_output() + .times(1) + .returning(|_, _| Ok("Private-Key: (4096 bit, 2 primes) +modulus: + 00:... +publicExponent: 65537 (0x10001) +privateExponent: + 57:... +prime1: + 00:... +prime2: + 00:... +exponent1: + 2b:... +exponent2: + 0e:... +coefficient: + 43:... +RSA key not ok +40C782E5E77F0000:error:0200007E:rsa routines:rsa_validate_keypair_multiprime:iqmp not inverse of q:../crypto/rsa/rsa_chk.c:196: +".into())); + let symbol = Key::new(command_runner, "/"); // FIXME: Has to be an existing file + run(async { + assert_eq!(symbol.target_reached().await.unwrap(), false); + }); + } + + #[test] + fn test_bullseye_success() { + let mut command_runner = MockCommandRunner::new(); + command_runner + .expect_get_output() + .times(1) + .returning(|_, _| { + Ok( + "RSA Private-Key: (4096 bit, 2 primes) +modulus: + 00:... +publicExponent: 65537 (0x10001) +privateExponent: + 57:... +prime1: + 00:... +prime2: + 00:... +exponent1: + 2b:... +exponent2: + 0e:... +coefficient: + 43:... +RSA key ok +" + .into(), + ) + }); + let symbol = Key::new(command_runner, "/"); // FIXME: Has to be an existing file + run(async { + assert_eq!(symbol.target_reached().await.unwrap(), true); + }); + } + + #[test] + fn test_bullseye_short_key() { + let mut command_runner = MockCommandRunner::new(); + command_runner + .expect_get_output() + .times(1) + .returning(|_, _| { + Ok( + "RSA Private-Key: (2048 bit, 2 primes) +modulus: + 00:... +publicExponent: 65537 (0x10001) +privateExponent: + 57:... +prime1: + 00:... +prime2: + 00:... +exponent1: + 2b:... +exponent2: + 0e:... +coefficient: + 43:... +RSA key ok +" + .into(), + ) + }); + let symbol = Key::new(command_runner, "/"); // FIXME: Has to be an existing file + run(async { + assert_eq!(symbol.target_reached().await.unwrap(), false); + }); + } + + #[test] + fn test_bullseye_broken_key() { + let mut command_runner = MockCommandRunner::new(); + command_runner + .expect_get_output() + .times(1) + .returning(|_, _| { + Ok( + "RSA Private-Key: (4096 bit, 2 primes) +modulus: + 00:... +publicExponent: 65537 (0x10001) +privateExponent: + 57:... +prime1: + 00:... +prime2: + 00:... +exponent1: + 2b:... +exponent2: + 0e:... +coefficient: + 43:... +RSA key error: iqmp not inverse of q +" + .into(), + ) + }); + let symbol = Key::new(command_runner, "/"); // FIXME: Has to be an existing file + run(async { + assert_eq!(symbol.target_reached().await.unwrap(), false); + }); + } +}