A library for writing host-specific, single-binary configuration management and deployment tools
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

282 lines
5.6 KiB

use crate::command_runner::CommandRunner;
use crate::symbols::Symbol;
use async_trait::async_trait;
use nonzero_ext::nonzero;
use std::error::Error;
use std::num::NonZeroU32;
use std::path::Path;
#[derive(Debug)]
pub struct Key<C, P> {
file_path: P,
command_runner: C,
bits: NonZeroU32,
}
impl<C, P> Key<C, P> {
pub const fn new(command_runner: C, file_path: P) -> Self {
Self {
file_path,
command_runner,
bits: nonzero!(4096u32), // FIXME: Policy
}
}
}
#[async_trait(?Send)]
impl<C: CommandRunner, P: AsRef<Path>> Symbol for Key<C, P> {
async fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
if !self.file_path.as_ref().exists() {
return Ok(false);
}
let mut stdout = self
.command_runner
.get_output(
"openssl",
args![
"rsa",
"-in",
self.file_path.as_ref(),
"-noout",
"-check",
"-text",
],
)
.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!("Private-Key: ({} bit, 2 primes)\n", self.bits).as_ref()),
)
}
async fn execute(&self) -> Result<(), Box<dyn Error>> {
self
.command_runner
.run_successfully(
"openssl",
args![
"genrsa",
"-out",
self.file_path.as_ref(),
self.bits.to_string(),
],
)
.await
}
}
#[cfg(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);
});
}
}