use super::{ANCHORS, CA_KEY, CURVE, DAYS, LOOPBACK, private};
use crate::git_env;
use crate::wire::material::Role;
use std::net::IpAddr;
use std::path::Path;
const EXT: &str = "ext";
const SECTION: &str = "leaf";
const SERIAL: &str = "ca.srl";
pub(super) fn ca(dir: &Path) -> Result<(), String> {
let key = dir.join(CA_KEY);
tool(&[
"req",
"-x509",
"-newkey",
"ec",
"-pkeyopt",
CURVE,
"-nodes",
"-sha256",
"-days",
DAYS,
"-subj",
"/CN=yog-ca",
"-keyout",
&key.to_string_lossy(),
"-out",
&dir.join(ANCHORS).to_string_lossy(),
])?;
private(&key, 0o600);
Ok(())
}
pub(super) fn leaf(dir: &Path, role: Role, host: &str) -> Result<(), String> {
issue(
dir,
&role.leaf(),
&role.common_name(),
&san(role, host),
eku(role),
)
}
pub(super) fn stated_leaf(dir: &Path, cn: &str) -> Result<(), String> {
issue(dir, cn, cn, &format!("DNS:{cn}"), eku(Role::Client))
}
fn issue(dir: &Path, name: &str, cn: &str, san: &str, eku: &str) -> Result<(), String> {
let key = dir.join(format!("{name}.key"));
let csr = dir.join(format!("{name}.csr"));
let ext = dir.join(format!("{name}.{EXT}"));
let body = format!("[{SECTION}]\nsubjectAltName={san}\nextendedKeyUsage={eku}\n");
std::fs::write(&ext, body).map_err(|e| format!("{}: {e}", ext.display()))?;
tool(&[
"req",
"-new",
"-newkey",
"ec",
"-pkeyopt",
CURVE,
"-nodes",
"-sha256",
"-subj",
&format!("/CN={cn}"),
"-keyout",
&key.to_string_lossy(),
"-out",
&csr.to_string_lossy(),
])?;
tool(&[
"x509",
"-req",
"-sha256",
"-days",
DAYS,
"-extfile",
&ext.to_string_lossy(),
"-extensions",
SECTION,
"-in",
&csr.to_string_lossy(),
"-CA",
&dir.join(ANCHORS).to_string_lossy(),
"-CAkey",
&dir.join(CA_KEY).to_string_lossy(),
"-CAcreateserial",
"-out",
&dir.join(format!("{name}.pem")).to_string_lossy(),
])?;
for scratch in [&csr, &ext, &dir.join(SERIAL)] {
let _ = std::fs::remove_file(scratch);
}
private(&key, 0o600);
Ok(())
}
pub(super) fn san(role: Role, host: &str) -> String {
let loopback = format!("IP:{LOOPBACK}");
match role {
Role::Server if host.parse::<IpAddr>().is_ok() => {
let named = format!("IP:{host}");
if named == loopback {
named
} else {
format!("{named},{loopback}")
}
}
Role::Server => format!("DNS:{host},{loopback}"),
_ => format!("DNS:{}", role.common_name()),
}
}
pub(super) fn eku(role: Role) -> &'static str {
match role {
Role::Server => "serverAuth",
_ => "clientAuth",
}
}
pub(super) fn tool(args: &[&str]) -> Result<(), String> {
run(Path::new("openssl"), args)
}
pub(super) fn run(program: &Path, args: &[&str]) -> Result<(), String> {
let out = git_env::output(git_env::command(program).args(args)).map_err(|e| {
format!(
"{}: {e} — the wire's certificates need it",
program.display()
)
})?;
if out.status.success() {
return Ok(());
}
let said = String::from_utf8_lossy(&out.stderr);
Err(format!(
"openssl {}: {}",
args.first().copied().unwrap_or_default(),
said.trim()
))
}