use std::error::Error;
use std::os::unix::fs::PermissionsExt;
use std::process::Command;
use base64::Engine;
use base64::engine::general_purpose::STANDARD;
use lys_core::ca::verify_certificate_request;
type TestResult = Result<(), Box<dyn Error>>;
#[test]
fn identity_creates_an_agent_key_and_a_signed_request_without_disclosing_the_seed() -> TestResult {
let root = tempfile::tempdir()?;
let key = root.path().join("agent.key");
let config = root.path().join("config.toml");
std::fs::write(
&config,
format!(
"[auth.lys.machine]\nagent_id = 'liminal-seats'\naudience = 'liminal'\nidentity_path = '{}'\n",
key.display()
),
)?;
let first = Command::new(env!("CARGO_BIN_EXE_liminal"))
.arg("identity")
.arg("--config")
.arg(&config)
.output()?;
assert!(
first.status.success(),
"identity refused: {}",
String::from_utf8_lossy(&first.stderr)
);
let seed = std::fs::read_to_string(&key)?;
assert_eq!(hex::decode(seed.trim())?.len(), 32);
assert_eq!(std::fs::metadata(&key)?.permissions().mode() & 0o777, 0o600);
let stdout = String::from_utf8(first.stdout)?;
assert!(stdout.starts_with("agent_id: liminal-seats\npublic_key: "));
assert!(!stdout.contains(seed.trim()), "private seed was printed");
let public = stdout
.lines()
.nth(1)
.ok_or("public key absent")?
.strip_prefix("public_key: ")
.ok_or("public key label absent")?;
assert_eq!(hex::decode(public)?.len(), 32);
let csr = stdout
.split_once("-----BEGIN CERTIFICATE REQUEST-----")
.ok_or("CSR absent")?
.1;
let encoded = csr
.split_once("-----END CERTIFICATE REQUEST-----")
.ok_or("CSR end absent")?
.0;
let mut der = STANDARD.decode(encoded.split_whitespace().collect::<String>())?;
let verified = verify_certificate_request(&der)?;
assert_eq!(verified.common_name(), "liminal-seats");
assert_eq!(
verified.subject_public_key().as_slice(),
hex::decode(public)?.as_slice()
);
*der.last_mut().ok_or("CSR signature absent")? ^= 1;
assert!(verify_certificate_request(&der).is_err());
let second = Command::new(env!("CARGO_BIN_EXE_liminal"))
.arg("identity")
.arg("--config")
.arg(&config)
.output()?;
assert!(second.status.success());
assert_eq!(String::from_utf8(second.stdout)?, stdout);
assert_eq!(std::fs::read_to_string(&key)?, seed);
Ok(())
}
#[test]
fn identity_refuses_an_existing_malformed_seed_without_replacing_it() -> TestResult {
let root = tempfile::tempdir()?;
let key = root.path().join("agent.key");
std::fs::write(&key, "malformed")?;
std::fs::set_permissions(&key, std::fs::Permissions::from_mode(0o600))?;
let config = root.path().join("config.toml");
std::fs::write(
&config,
format!(
"[auth.lys.machine]\nagent_id = 'liminal-seats'\naudience = 'liminal'\nidentity_path = '{}'\n",
key.display()
),
)?;
let answer = Command::new(env!("CARGO_BIN_EXE_liminal"))
.arg("identity")
.arg("--config")
.arg(&config)
.output()?;
assert!(!answer.status.success());
assert!(String::from_utf8(answer.stderr)?.contains("agent seed must be 64 hexadecimal digits"));
assert_eq!(std::fs::read_to_string(key)?, "malformed");
Ok(())
}
#[test]
fn identity_refuses_a_publicly_readable_seed() -> TestResult {
let root = tempfile::tempdir()?;
let key = root.path().join("agent.key");
std::fs::write(&key, hex::encode([7; 32]))?;
std::fs::set_permissions(&key, std::fs::Permissions::from_mode(0o644))?;
let config = root.path().join("config.toml");
std::fs::write(
&config,
format!(
"[auth.lys.machine]\nagent_id = 'liminal-seats'\naudience = 'liminal'\nidentity_path = '{}'\n",
key.display()
),
)?;
let answer = Command::new(env!("CARGO_BIN_EXE_liminal"))
.arg("identity")
.arg("--config")
.arg(&config)
.output()?;
assert!(!answer.status.success());
assert!(String::from_utf8(answer.stderr)?.contains("agent seed permissions must be 0600"));
assert_eq!(std::fs::metadata(key)?.permissions().mode() & 0o777, 0o644);
Ok(())
}