cb89312a27
aura-cli: clap command tree (pki init/issue-server/issue-client/revoke/list,
server, client, route add/list/remove, status, bench-crypto); TOML config with
~ expansion and split-tunnel rules -> RouteTable; JSON-over-Unix-socket admin
IPC; server/client data paths wiring transport + tunnel (TUN run needs root).
config/{server,client}.toml.example. 15 tests (pki roundtrip, config parse,
admin-socket roundtrip, loopback connection). Verified the real binary: --help,
bench-crypto, and a full CA->server->client cert workflow.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
113 lines
4.2 KiB
Rust
113 lines
4.2 KiB
Rust
//! `aura pki` subcommand handlers (project §10): CA init, server/client issuance, revocation list.
|
|
//!
|
|
//! Each handler is a thin, side-effecting wrapper over [`aura_pki`] that writes PEM/CRL files into
|
|
//! a directory. They are split out from the clap layer (see [`crate::cli`]) and take plain values so
|
|
//! the test suite can drive a full init -> issue -> verify roundtrip without spawning the binary.
|
|
|
|
use std::path::{Path, PathBuf};
|
|
|
|
use anyhow::Context;
|
|
use aura_pki::{AuraCa, CrlStore};
|
|
|
|
use crate::config::expand_tilde;
|
|
|
|
/// File name of the CA certificate within a CA directory.
|
|
pub const CA_CERT: &str = "ca.crt";
|
|
/// File name of the CA private key within a CA directory.
|
|
pub const CA_KEY: &str = "ca.key";
|
|
/// Default CRL file name.
|
|
pub const CRL_FILE: &str = "revoked.crl";
|
|
|
|
/// `aura pki init`: generate a new CA into `out_dir` as `ca.crt` / `ca.key`.
|
|
///
|
|
/// Creates `out_dir` (and parents) if needed. Returns the paths written.
|
|
pub fn init(ca_name: &str, out_dir: &Path) -> anyhow::Result<(PathBuf, PathBuf)> {
|
|
std::fs::create_dir_all(out_dir)
|
|
.with_context(|| format!("creating output dir {}", out_dir.display()))?;
|
|
let ca = AuraCa::generate(ca_name).context("generating CA")?;
|
|
let cert_path = out_dir.join(CA_CERT);
|
|
let key_path = out_dir.join(CA_KEY);
|
|
ca.save(&cert_path, &key_path)?;
|
|
Ok((cert_path, key_path))
|
|
}
|
|
|
|
/// `aura pki issue-server`: issue a server cert for `domain` into `out_dir`.
|
|
///
|
|
/// Loads the CA from `ca_dir` (`ca.crt`/`ca.key`) and writes `server.crt` / `server.key`.
|
|
pub fn issue_server(
|
|
domain: &str,
|
|
out_dir: &Path,
|
|
ca_dir: &Path,
|
|
) -> anyhow::Result<(PathBuf, PathBuf)> {
|
|
let ca = load_ca(ca_dir)?;
|
|
let issued = ca.issue_server_cert(domain)?;
|
|
write_leaf(out_dir, "server", &issued.cert_pem, &issued.key_pem)
|
|
}
|
|
|
|
/// `aura pki issue-client`: issue a client cert with `CN = id` into `out_dir`.
|
|
///
|
|
/// Loads the CA from `ca_dir` and writes `client.crt` / `client.key`.
|
|
pub fn issue_client(id: &str, out_dir: &Path, ca_dir: &Path) -> anyhow::Result<(PathBuf, PathBuf)> {
|
|
let ca = load_ca(ca_dir)?;
|
|
let issued = ca.issue_client_cert(id)?;
|
|
write_leaf(out_dir, "client", &issued.cert_pem, &issued.key_pem)
|
|
}
|
|
|
|
/// `aura pki revoke`: add `id` (a client id or serial) to the CRL file, creating it if absent.
|
|
pub fn revoke(id: &str, crl_path: &Path) -> anyhow::Result<()> {
|
|
let mut crl = if crl_path.exists() {
|
|
CrlStore::load(crl_path)?
|
|
} else {
|
|
if let Some(parent) = crl_path.parent() {
|
|
if !parent.as_os_str().is_empty() {
|
|
std::fs::create_dir_all(parent)
|
|
.with_context(|| format!("creating CRL dir {}", parent.display()))?;
|
|
}
|
|
}
|
|
CrlStore::new()
|
|
};
|
|
crl.revoke(id.to_string());
|
|
crl.save(crl_path)?;
|
|
Ok(())
|
|
}
|
|
|
|
/// `aura pki list`: return the revoked identifiers in the CRL file (empty if the file is absent).
|
|
pub fn list(crl_path: &Path) -> anyhow::Result<Vec<String>> {
|
|
if !crl_path.exists() {
|
|
return Ok(Vec::new());
|
|
}
|
|
let crl = CrlStore::load(crl_path)?;
|
|
Ok(crl.iter().map(str::to_string).collect())
|
|
}
|
|
|
|
/// Load a CA from a directory, expanding a leading `~` in the directory path.
|
|
fn load_ca(ca_dir: &Path) -> anyhow::Result<AuraCa> {
|
|
let dir = expand_tilde(&ca_dir.to_string_lossy());
|
|
let cert = dir.join(CA_CERT);
|
|
let key = dir.join(CA_KEY);
|
|
AuraCa::load(&cert, &key).with_context(|| {
|
|
format!(
|
|
"loading CA from {} (expected {CA_CERT} + {CA_KEY})",
|
|
dir.display()
|
|
)
|
|
})
|
|
}
|
|
|
|
/// Write a leaf cert/key pair as `<stem>.crt` / `<stem>.key` into `out_dir`.
|
|
fn write_leaf(
|
|
out_dir: &Path,
|
|
stem: &str,
|
|
cert_pem: &str,
|
|
key_pem: &str,
|
|
) -> anyhow::Result<(PathBuf, PathBuf)> {
|
|
std::fs::create_dir_all(out_dir)
|
|
.with_context(|| format!("creating output dir {}", out_dir.display()))?;
|
|
let cert_path = out_dir.join(format!("{stem}.crt"));
|
|
let key_path = out_dir.join(format!("{stem}.key"));
|
|
std::fs::write(&cert_path, cert_pem)
|
|
.with_context(|| format!("writing {}", cert_path.display()))?;
|
|
std::fs::write(&key_path, key_pem)
|
|
.with_context(|| format!("writing {}", key_path.display()))?;
|
|
Ok((cert_path, key_path))
|
|
}
|