use crate::brew::Brew;
use crate::paths;
use anyhow::{bail, Context, Result};
use std::path::PathBuf;
use std::process::Command;
fn mkcert_bin(brew: &Brew) -> PathBuf {
brew.bin("mkcert")
}
pub fn ensure_installed(brew: &Brew) -> Result<()> {
if !brew.is_installed("mkcert") {
println!("Installing mkcert…");
brew.install("mkcert")?;
}
Ok(())
}
pub fn caroot(brew: &Brew) -> Result<PathBuf> {
let out = Command::new(mkcert_bin(brew))
.arg("-CAROOT")
.output()
.context("Failed to run `mkcert -CAROOT`")?;
if !out.status.success() {
bail!("`mkcert -CAROOT` failed");
}
Ok(PathBuf::from(
String::from_utf8_lossy(&out.stdout).trim().to_string(),
))
}
pub fn ca_cert(brew: &Brew) -> Result<PathBuf> {
Ok(caroot(brew)?.join("rootCA.pem"))
}
pub fn ensure_ca(brew: &Brew) -> Result<bool> {
ensure_installed(brew)?;
let out = Command::new(mkcert_bin(brew))
.arg("-install")
.output()
.context("Failed to run `mkcert -install`")?;
if !out.status.success() {
bail!(
"`mkcert -install` failed: {}. You may need to authorize adding the \
CA to your trust store, then re-run.",
String::from_utf8_lossy(&out.stderr).trim()
);
}
let combined = format!(
"{}{}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
);
Ok(!combined.contains("already installed"))
}
pub fn uninstall_ca(brew: &Brew) -> Result<()> {
let out = Command::new(mkcert_bin(brew))
.arg("-uninstall")
.output()
.context("Failed to run `mkcert -uninstall`")?;
if !out.status.success() {
bail!(
"`mkcert -uninstall` failed: {}",
String::from_utf8_lossy(&out.stderr).trim()
);
}
Ok(())
}
pub fn is_trusted(brew: &Brew) -> bool {
let _ = brew;
Command::new("security")
.args([
"find-certificate",
"-c",
"mkcert",
"/Library/Keychains/System.keychain",
])
.output()
.map(|o| o.status.success())
.unwrap_or(false)
}
pub fn cert_paths(host: &str) -> Result<(PathBuf, PathBuf)> {
let dir = paths::certs_dir()?;
Ok((
dir.join(format!("{host}.pem")),
dir.join(format!("{host}-key.pem")),
))
}
pub fn mint(brew: &Brew, host: &str) -> Result<()> {
ensure_installed(brew)?;
paths::ensure_dirs()?;
let (cert, key) = cert_paths(host)?;
let out = Command::new(mkcert_bin(brew))
.arg("-cert-file")
.arg(&cert)
.arg("-key-file")
.arg(&key)
.arg(host)
.output()
.with_context(|| format!("Failed to run mkcert for {host}"))?;
if !out.status.success() {
bail!(
"mkcert failed to mint a certificate for {host}: {}",
String::from_utf8_lossy(&out.stderr).trim()
);
}
Ok(())
}
pub fn exists(host: &str) -> bool {
cert_paths(host)
.map(|(c, k)| c.exists() && k.exists())
.unwrap_or(false)
}