//! PKI roundtrip: drive the `aura pki` handlers to init a CA, issue server + client certs, then //! verify each against [`aura_pki::AuraCertVerifier`]. A cert from a *different* CA must fail. //! //! Runs without root or network: everything is file I/O into a unique temp directory. use std::path::PathBuf; use aura_cli::pki; use aura_pki::{AuraCa, AuraCertVerifier}; use rustls_pki_types::CertificateDer; /// A unique temp directory for this test process (no `tempfile` dependency in the workspace). fn temp_dir(tag: &str) -> PathBuf { let mut dir = std::env::temp_dir(); dir.push(format!( "aura-cli-test-{tag}-{}-{}", std::process::id(), std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() .as_nanos() )); std::fs::create_dir_all(&dir).expect("create temp dir"); dir } /// Decode a single-certificate PEM string into a DER chain for the verifier. fn pem_chain(pem: &str) -> Vec> { let (_, parsed) = x509_parser::pem::parse_x509_pem(pem.as_bytes()).expect("parse PEM"); vec![CertificateDer::from(parsed.contents)] } #[test] fn ca_init_issue_and_verify_roundtrip() { let dir = temp_dir("pki"); // init the CA. let (ca_cert_path, ca_key_path) = pki::init("Aura Roundtrip CA", &dir).expect("pki init"); assert!(ca_cert_path.exists() && ca_key_path.exists()); assert_eq!(ca_cert_path.file_name().unwrap(), "ca.crt"); // issue server + client certs (CA dir defaults to the same dir). let (server_crt, server_key) = pki::issue_server("vpn.example.com", &dir, &dir).expect("issue server"); let (client_crt, client_key) = pki::issue_client("client-42", &dir, &dir).expect("issue client"); assert!(server_crt.exists() && server_key.exists()); assert!(client_crt.exists() && client_key.exists()); // Load the CA back and build a verifier from its PEM. let ca = AuraCa::load(&ca_cert_path, &ca_key_path).expect("load CA"); let verifier = AuraCertVerifier::new(&ca.ca_cert_pem()).expect("verifier"); // Verify the server cert for its SAN. let server_pem = std::fs::read_to_string(&server_crt).unwrap(); verifier .verify_server_cert(&pem_chain(&server_pem), "vpn.example.com") .expect("server cert verifies for its SAN"); // Wrong name must fail. assert!(verifier .verify_server_cert(&pem_chain(&server_pem), "wrong.example.com") .is_err()); // Verify the client cert; the returned CN must be the issued id. let client_pem = std::fs::read_to_string(&client_crt).unwrap(); let cn = verifier .verify_client_cert(&pem_chain(&client_pem)) .expect("client cert verifies"); assert_eq!(cn, "client-42"); // A certificate from a *different* CA must NOT verify against this CA. let other_dir = temp_dir("pki-other"); pki::init("Other CA", &other_dir).expect("other CA"); let (other_client, _k) = pki::issue_client("intruder", &other_dir, &other_dir).expect("other client"); let other_pem = std::fs::read_to_string(&other_client).unwrap(); assert!( verifier.verify_client_cert(&pem_chain(&other_pem)).is_err(), "a cert from a different CA must fail verification" ); // Cleanup (best effort). let _ = std::fs::remove_dir_all(&dir); let _ = std::fs::remove_dir_all(&other_dir); } #[test] fn revoke_then_list() { let dir = temp_dir("crl"); let crl = dir.join("revoked.crl"); // Empty / absent CRL lists nothing. assert!(pki::list(&crl).unwrap().is_empty()); pki::revoke("client-42", &crl).expect("revoke 1"); pki::revoke("deadbeef", &crl).expect("revoke 2"); // Re-revoking is idempotent (set semantics). pki::revoke("client-42", &crl).expect("revoke dup"); let mut listed = pki::list(&crl).expect("list"); listed.sort(); assert_eq!( listed, vec!["client-42".to_string(), "deadbeef".to_string()] ); let _ = std::fs::remove_dir_all(&dir); }