mod issue;
use super::openssl::{eku, run, san, tool};
use super::*;
use crate::wire::material::{self, Material};
use tempfile::TempDir;
fn provisioned(dir: &Path) -> Vec<Material> {
material::LEAVES
.iter()
.map(|role| {
material::read_dir(dir, *role)
.expect("readable")
.expect("provisioned")
})
.collect()
}
#[test]
fn an_unprovisioned_box_founds_its_own_loopback_trust_root() {
let tmp = TempDir::new().expect("tmp");
let dir = tmp.path().join("wire");
ensure(&dir).expect("mint");
let ends = provisioned(&dir);
assert_eq!(ends.len(), 3, "server, client and window");
for end in &ends {
assert_eq!(
end.address,
format!("{LOOPBACK}:0"),
"loopback only, and a kernel-chosen port: the request, not a fixed number (bl-dc14)"
);
}
let der = std::fs::read(dir.join("window.pem")).expect("leaf");
let pem = String::from_utf8_lossy(&der);
assert!(pem.starts_with("-----BEGIN CERTIFICATE-----"), "{pem}");
assert_eq!(
crate::wire::material::Role::Window.common_name(),
crate::registry::WINDOW
);
}
#[test]
fn a_second_ensure_mints_nothing() {
let tmp = TempDir::new().expect("tmp");
ensure(tmp.path()).expect("mint");
let before = std::fs::read(tmp.path().join(ANCHORS)).expect("ca");
ensure(tmp.path()).expect("again");
assert_eq!(
std::fs::read(tmp.path().join(ANCHORS)).expect("ca"),
before,
"the trust root is untouched"
);
}
#[test]
fn a_box_with_an_anchor_and_no_ca_key_is_left_alone() {
let tmp = TempDir::new().expect("tmp");
let dir = tmp.path().join("wire");
std::fs::create_dir_all(&dir).expect("dir");
std::fs::write(dir.join(ANCHORS), b"operator's own").expect("anchor");
ensure(&dir).expect("no mint to run");
assert_eq!(
std::fs::read(dir.join(ANCHORS)).expect("anchor"),
b"operator's own",
"the operator's anchor survives"
);
assert!(!dir.join("server.pem").is_file(), "nothing to sign with");
assert_eq!(
std::fs::read_to_string(dir.join(ADDRESS))
.expect("address")
.trim(),
format!("{LOOPBACK}:0"),
"the address is still written: it is not certificate material"
);
}
#[test]
fn an_existing_address_is_kept_and_the_server_leaf_names_it() {
let tmp = TempDir::new().expect("tmp");
std::fs::write(tmp.path().join(ADDRESS), "engine.example.com:7737\n").expect("address");
ensure(tmp.path()).expect("mint");
let m = material::read_dir(tmp.path(), Role::Server)
.expect("readable")
.expect("provisioned");
assert_eq!(m.address, "engine.example.com:7737");
assert_eq!(
san(Role::Server, "engine.example.com"),
format!("DNS:engine.example.com,IP:{LOOPBACK}")
);
}
#[test]
fn a_rotation_replaces_the_trust_root() {
let tmp = TempDir::new().expect("tmp");
mint(tmp.path(), "127.0.0.1:0", false).expect("mint");
let before = std::fs::read(tmp.path().join(ANCHORS)).expect("ca");
mint(tmp.path(), "127.0.0.1:9", true).expect("rotate");
assert_ne!(
std::fs::read(tmp.path().join(ANCHORS)).expect("ca"),
before,
"a new trust root"
);
assert_eq!(
std::fs::read_to_string(tmp.path().join(ADDRESS))
.expect("address")
.trim(),
"127.0.0.1:9",
"and the address it was rotated onto"
);
}
#[test]
fn half_a_leaf_is_re_minted() {
let tmp = TempDir::new().expect("tmp");
ensure(tmp.path()).expect("mint");
assert!(leaf_present(tmp.path(), Role::Window));
std::fs::remove_file(tmp.path().join("window.key")).expect("rm");
assert!(!leaf_present(tmp.path(), Role::Window));
ensure(tmp.path()).expect("again");
assert!(leaf_present(tmp.path(), Role::Window), "re-minted");
}
#[test]
fn a_blank_address_names_nothing() {
let tmp = TempDir::new().expect("tmp");
assert_eq!(address_at(tmp.path()), None, "absent");
std::fs::write(tmp.path().join(ADDRESS), " \n").expect("blank");
assert_eq!(address_at(tmp.path()), None, "blank");
std::fs::write(tmp.path().join(ADDRESS), " host:1 \n").expect("named");
assert_eq!(address_at(tmp.path()), Some("host:1".to_owned()));
}
#[test]
fn a_host_is_the_address_without_its_port_or_brackets() {
assert_eq!(host_of("127.0.0.1:7737"), "127.0.0.1");
assert_eq!(host_of("[::1]:7737"), "::1");
assert_eq!(host_of("engine.example.com"), "engine.example.com");
}
#[test]
fn a_server_leaf_always_names_loopback_and_never_twice() {
assert_eq!(san(Role::Server, LOOPBACK), format!("IP:{LOOPBACK}"));
assert_eq!(
san(Role::Server, "192.0.2.7"),
format!("IP:192.0.2.7,IP:{LOOPBACK}")
);
assert_eq!(
san(Role::Client, "ignored"),
format!("DNS:{}", Role::Client.common_name())
);
assert_eq!(eku(Role::Server), "serverAuth");
assert_eq!(eku(Role::Window), "clientAuth");
}
#[test]
fn the_artifact_list_is_the_whole_of_what_is_written() {
let tmp = TempDir::new().expect("tmp");
ensure(tmp.path()).expect("mint");
for name in artifacts() {
assert!(tmp.path().join(&name).is_file(), "{name} was not written");
}
assert_eq!(artifacts().len(), 9, "a CA pair, an address, three leaves");
let mut left: Vec<String> = std::fs::read_dir(tmp.path())
.expect("read the mint's directory")
.filter_map(|entry| Some(entry.ok()?.file_name().to_string_lossy().into_owned()))
.collect();
left.sort();
let mut named = artifacts();
named.sort();
assert_eq!(left, named, "scratch was left behind");
}
#[test]
fn an_unwritable_target_refuses() {
let tmp = TempDir::new().expect("tmp");
let blocked = tmp.path().join("file");
std::fs::write(&blocked, b"not a directory").expect("file");
assert!(mint(&blocked, "127.0.0.1:0", false).is_err(), "no dir");
let extless = tmp.path().join("extless");
let ext = extless.join(format!("{}.ext", Role::Server.leaf()));
std::fs::create_dir_all(&ext).expect("a directory where the file goes");
let refusal = mint(&extless, "127.0.0.1:0", false).expect_err("cannot write the extensions");
assert!(refusal.contains("server.ext"), "{refusal}");
let dir = tmp.path().join("wire");
std::fs::create_dir_all(dir.join(ADDRESS)).expect("a directory where the file goes");
let refusal = mint(&dir, "127.0.0.1:0", false).expect_err("cannot write the address");
assert!(refusal.contains(ADDRESS), "{refusal}");
}
#[test]
fn the_tool_speaks_for_itself() {
let missing = run(Path::new("yog-no-such-tool"), &[]).expect_err("cannot start");
assert!(missing.contains("yog-no-such-tool"), "{missing}");
let refused =
tool(&["x509", "-in", "/nonexistent/yog-not-a-certificate"]).expect_err("openssl refuses");
assert!(refused.starts_with("openssl x509:"), "{refused}");
}