mod trust;
use std::path::PathBuf;
use anyhow::{Context as _, Result, bail};
use openmoat_core::Policy;
use openmoat_proxy::{CA_VALID_DAYS, CaFiles, Roots, Tls};
use zeroize::Zeroizing;
pub use self::trust::Trust;
use crate::home::Home;
use crate::integrity::Lock;
const EXPIRY_NOTICE_DAYS: i64 = 30;
const MS_PER_DAY: i64 = 86_400_000;
pub fn cert_path(home: &Home) -> PathBuf {
home.root().join("proxy-ca").join("ca.pem")
}
pub fn key_path(home: &Home) -> PathBuf {
home.root().join("proxy-ca").join("key.pem")
}
pub fn files(home: &Home) -> Vec<PathBuf> {
vec![cert_path(home), key_path(home)]
}
pub fn terminated_hosts(policy: &Policy) -> Vec<String> {
let mut hosts: Vec<String> = policy
.secrets
.iter()
.filter(|s| s.terminates())
.map(|s| s.host.clone())
.collect();
hosts.sort();
hosts.dedup();
hosts
}
fn date(ts_ms: i64) -> String {
crate::time::rfc3339(ts_ms)[..10].to_owned()
}
pub fn cert_text(ca: &CaFiles, hosts: &[String], now_ms: i64) -> String {
format!(
"# OpenMoat proxy CA, made by `openmoat proxy ca init` on {}\n\
# valid until {}, and only for: {}\n\
# trusted only by the commands `openmoat run` starts (ADR-043)\n{}",
date(now_ms),
date(now_ms + CA_VALID_DAYS * MS_PER_DAY),
hosts.join(", "),
ca.cert_pem
)
}
pub fn valid_until(text: &str) -> Option<&str> {
text.lines()
.find_map(|l| l.strip_prefix("# valid until "))
.and_then(|rest| rest.get(..10))
}
pub struct Loaded {
pub tls: Tls,
pub cert_pem: String,
pub notice: Option<String>,
}
pub fn load(home: &Home, policy: &Policy) -> Result<Option<Loaded>> {
let hosts = terminated_hosts(policy);
if hosts.is_empty() {
return Ok(None);
}
let again = "run `openmoat proxy ca init` in a terminal";
let lock = Lock::load(&home.lock_path())?;
if !files(home).iter().all(|f| lock.pins(f) && f.is_file()) {
bail!(
"{} {} `tls: terminate`, but there is no pinned proxy CA yet: {again}",
hosts.join(", "),
if hosts.len() == 1 { "says" } else { "say" }
);
}
let cert = std::fs::read_to_string(cert_path(home))
.with_context(|| format!("reading {}", cert_path(home).display()))?;
let key = Zeroizing::new(
std::fs::read_to_string(key_path(home))
.with_context(|| format!("reading {}", key_path(home).display()))?,
);
let tls = Tls::new(&cert, &key, &Roots::System, &hosts)
.with_context(|| format!("the proxy CA cannot be used; {again}"))?;
let now = crate::time::now_ms();
let notice = valid_until(&cert)
.filter(|until| *until <= date(now + EXPIRY_NOTICE_DAYS * MS_PER_DAY).as_str())
.map(|until| {
format!("moat run: the proxy CA expires on {until}; {again} to make a new one")
});
Ok(Some(Loaded {
tls,
cert_pem: cert,
notice,
}))
}
#[cfg(test)]
mod tests;