use std::path::Path;
use anyhow::Context;
use crate::commands::print_json;
pub fn install(pem_path: &Path, json: bool) -> anyhow::Result<()> {
let pem = std::fs::read_to_string(pem_path)
.with_context(|| format!("reading certificate file {}", pem_path.display()))?;
let cert = os_truststore::Cert::from_pem(&pem)
.map_err(|e| anyhow::anyhow!("invalid CA certificate: {e}"))?;
let name = derive_name(pem_path)?;
let fingerprint = install_cert(&cert, &name, &pem_path.display().to_string())?;
if json {
print_json(&serde_json::json!({
"installed": { "name": name, "fingerprint": fingerprint }
}));
} else {
println!("Installed CA \"{name}\" (sha256: {fingerprint})");
eprintln!("The OS trust store now trusts certificates signed by this root.");
}
Ok(())
}
fn install_cert(cert: &os_truststore::Cert, name: &str, source: &str) -> anyhow::Result<String> {
let fingerprint = koi_crypto::pinning::fingerprint_sha256(cert.der());
os_truststore::Install::new(cert)
.label(name)
.run()
.map_err(|e| anyhow::anyhow!("failed to install into OS trust store: {e}"))?;
let mut state = koi_config::state::load_trust_state().unwrap_or_default();
state.roots.retain(|r| r.name != name);
state.roots.push(koi_config::state::TrustEntry {
name: name.to_string(),
installed_at: chrono::Utc::now().to_rfc3339(),
fingerprint: fingerprint.clone(),
source: source.to_string(),
});
koi_config::state::save_trust_state(&state).context("saving trust state")?;
Ok(fingerprint)
}
pub async fn diagnose(fix: bool, json: bool) -> anyhow::Result<()> {
let core = tokio::task::spawn_blocking(|| koi_compose::cores::init_certmesh_core(None))
.await
.map_err(|e| anyhow::anyhow!("certmesh init task: {e}"))?
.ok_or_else(|| anyhow::anyhow!("certmesh is unavailable on this node"))?;
if fix {
match core.local_identity().await {
Some(id) => match os_truststore::Cert::from_pem(&id.ca_cert_pem) {
Ok(cert) => match install_cert(&cert, "koi-certmesh-ca", "certmesh") {
Ok(fp) => eprintln!(
"Fixed: installed the mesh CA (sha256: {fp}) into the OS trust store."
),
Err(e) => eprintln!("--fix: could not install the mesh CA: {e}"),
},
Err(e) => eprintln!("--fix: the mesh CA certificate is invalid: {e}"),
},
None => eprintln!("--fix: no local identity — nothing to install (this node is Open)."),
}
}
let diagnosis = core.diagnose().await;
if json {
print_json(&diagnosis);
} else {
print!("{}", crate::format::trust_diagnosis(&diagnosis));
}
if diagnosis.is_red() {
std::process::exit(diagnosis.exit_code());
}
Ok(())
}
pub fn list(json: bool) -> anyhow::Result<()> {
let state = koi_config::state::load_trust_state().unwrap_or_default();
if json {
print_json(&serde_json::json!({ "roots": state.roots }));
return Ok(());
}
if state.roots.is_empty() {
println!("No Koi-installed CA roots.");
return Ok(());
}
let (h_name, h_installed) = ("NAME", "INSTALLED");
println!("{h_name:<28} {h_installed:<20} FINGERPRINT (sha256)");
for r in &state.roots {
let fp_short: String = r.fingerprint.chars().take(16).collect();
let fp = format!("{fp_short}...");
let name = &r.name;
let installed = &r.installed_at;
println!("{name:<28} {installed:<20} {fp}");
}
Ok(())
}
pub fn remove(name: &str, json: bool) -> anyhow::Result<()> {
let mut state = koi_config::state::load_trust_state().unwrap_or_default();
let Some(entry) = state.roots.iter().find(|r| r.name == name).cloned() else {
anyhow::bail!(
"no Koi-installed CA root named \"{name}\" (run `koi trust list` to see them)"
);
};
let pem = std::fs::read_to_string(&entry.source).with_context(|| {
format!(
"re-reading the certificate from {} to remove it (the source file must still exist)",
entry.source
)
})?;
let cert = os_truststore::Cert::from_pem(&pem)
.map_err(|e| anyhow::anyhow!("the certificate at {} is invalid: {e}", entry.source))?;
os_truststore::uninstall(&cert)
.map_err(|e| anyhow::anyhow!("failed to remove from OS trust store: {e}"))?;
state.roots.retain(|r| r.name != name);
koi_config::state::save_trust_state(&state).context("saving trust state")?;
if json {
print_json(&serde_json::json!({ "removed": name }));
} else {
println!("Removed CA \"{name}\" from the OS trust store.");
}
Ok(())
}
pub fn export(ca: bool, _json: bool) -> anyhow::Result<()> {
if !ca {
anyhow::bail!(
"specify what to export: `koi trust export --ca` prints the certmesh root CA"
);
}
#[allow(clippy::disallowed_methods)]
let paths = koi_certmesh::CertmeshPaths::with_data_dir(koi_common::paths::koi_data_dir());
let ca_cert_path = paths.ca_cert_path();
let pem = std::fs::read_to_string(&ca_cert_path).with_context(|| {
format!(
"reading certmesh CA certificate at {} (run `koi certmesh create` first)",
ca_cert_path.display()
)
})?;
print!("{pem}");
Ok(())
}
fn derive_name(pem_path: &Path) -> anyhow::Result<String> {
let stem = pem_path
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("koi-root");
let sanitized: String = stem
.chars()
.map(|c| {
if c.is_ascii_alphanumeric() || c == '-' || c == '_' || c == '.' {
c
} else {
'-'
}
})
.collect();
let cleaned = sanitized.replace("..", "-");
let name = format!("koi-{}", cleaned.trim_matches('-'));
if name.len() <= 4 {
return Ok("koi-root".to_string());
}
Ok(name)
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
#[test]
fn derive_name_sanitizes_path() {
assert_eq!(
derive_name(&PathBuf::from("/etc/ssl/step-ca-root.pem")).unwrap(),
"koi-step-ca-root"
);
}
#[test]
fn derive_name_strips_dangerous_chars() {
let name = derive_name(&PathBuf::from("we:ird*name?.pem")).unwrap();
assert!(!name.contains(':'));
assert!(!name.contains('*'));
assert!(!name.contains('?'));
assert!(!name.contains(".."));
}
#[test]
fn derive_name_falls_back_for_empty_stem() {
assert_eq!(
derive_name(&PathBuf::from("/----.pem")).unwrap(),
"koi-root"
);
}
#[test]
fn derive_name_is_accepted_by_truststore_validation() {
let name = derive_name(&PathBuf::from("/etc/ssl/step-ca-root.pem")).unwrap();
assert!(!name.contains('/') && !name.contains('\\'));
assert!(!name.contains(':') && !name.contains(".."));
}
}