use std::process::Command; use rpki::validation::cert_path::{CertPathError, validate_ee_cert_path}; fn openssl_available() -> bool { Command::new("openssl") .arg("version") .output() .map(|o| o.status.success()) .unwrap_or(false) } fn run(cmd: &mut Command) { let out = cmd.output().expect("run command"); if !out.status.success() { panic!( "command failed: {:?}\nstdout={}\nstderr={}", cmd, String::from_utf8_lossy(&out.stdout), String::from_utf8_lossy(&out.stderr) ); } } struct Generated { issuer_ca_der: Vec, ee_der: Vec, issuer_crl_der: Vec, } fn generate_issuer_ca_ee_and_crl(ee_ext: &str) -> Generated { assert!(openssl_available(), "openssl is required for this test"); let td = tempfile::tempdir().expect("tempdir"); let dir = td.path(); std::fs::create_dir_all(dir.join("newcerts")).expect("newcerts"); std::fs::write(dir.join("index.txt"), b"").expect("index"); std::fs::write(dir.join("serial"), b"1000\n").expect("serial"); std::fs::write(dir.join("crlnumber"), b"1000\n").expect("crlnumber"); let cnf = format!( r#" [ ca ] default_ca = CA_default [ CA_default ] dir = {dir} database = $dir/index.txt new_certs_dir = $dir/newcerts certificate = $dir/issuer.pem private_key = $dir/issuer.key serial = $dir/serial crlnumber = $dir/crlnumber default_md = sha256 default_days = 365 default_crl_days = 1 policy = policy_any x509_extensions = v3_issuer_ca crl_extensions = crl_ext unique_subject = no copy_extensions = none [ policy_any ] commonName = supplied [ req ] prompt = no distinguished_name = dn [ dn ] CN = Test Issuer CA [ v3_issuer_ca ] basicConstraints = critical,CA:true keyUsage = critical, keyCertSign, cRLSign subjectKeyIdentifier = hash authorityKeyIdentifier = keyid:always [ v3_ee ] basicConstraints = critical,CA:false subjectKeyIdentifier = hash authorityKeyIdentifier = keyid:always crlDistributionPoints = URI:rsync://example.test/repo/issuer/issuer.crl authorityInfoAccess = caIssuers;URI:rsync://example.test/repo/issuer/issuer.cer {ee_ext} [ crl_ext ] authorityKeyIdentifier = keyid:always "#, dir = dir.display(), ee_ext = ee_ext ); std::fs::write(dir.join("openssl.cnf"), cnf.as_bytes()).expect("write cnf"); run( Command::new("openssl") .arg("genrsa") .arg("-out") .arg(dir.join("issuer.key")) .arg("2048"), ); run( Command::new("openssl") .arg("req") .arg("-new") .arg("-x509") .arg("-sha256") .arg("-days") .arg("365") .arg("-key") .arg(dir.join("issuer.key")) .arg("-config") .arg(dir.join("openssl.cnf")) .arg("-extensions") .arg("v3_issuer_ca") .arg("-out") .arg(dir.join("issuer.pem")), ); run( Command::new("openssl") .arg("genrsa") .arg("-out") .arg(dir.join("ee.key")) .arg("2048"), ); run( Command::new("openssl") .arg("req") .arg("-new") .arg("-key") .arg(dir.join("ee.key")) .arg("-subj") .arg("/CN=Test EE") .arg("-out") .arg(dir.join("ee.csr")), ); run( Command::new("openssl") .arg("ca") .arg("-batch") .arg("-config") .arg(dir.join("openssl.cnf")) .arg("-in") .arg(dir.join("ee.csr")) .arg("-extensions") .arg("v3_ee") .arg("-out") .arg(dir.join("ee.pem")), ); run( Command::new("openssl") .arg("x509") .arg("-in") .arg(dir.join("issuer.pem")) .arg("-outform") .arg("DER") .arg("-out") .arg(dir.join("issuer.cer")), ); run( Command::new("openssl") .arg("x509") .arg("-in") .arg(dir.join("ee.pem")) .arg("-outform") .arg("DER") .arg("-out") .arg(dir.join("ee.cer")), ); run( Command::new("openssl") .arg("ca") .arg("-gencrl") .arg("-config") .arg(dir.join("openssl.cnf")) .arg("-out") .arg(dir.join("issuer.crl.pem")), ); run( Command::new("openssl") .arg("crl") .arg("-in") .arg(dir.join("issuer.crl.pem")) .arg("-outform") .arg("DER") .arg("-out") .arg(dir.join("issuer.crl")), ); Generated { issuer_ca_der: std::fs::read(dir.join("issuer.cer")).expect("read issuer der"), ee_der: std::fs::read(dir.join("ee.cer")).expect("read ee der"), issuer_crl_der: std::fs::read(dir.join("issuer.crl")).expect("read crl der"), } } #[test] fn ee_key_usage_digital_signature_only_is_accepted() { let g = generate_issuer_ca_ee_and_crl("keyUsage = critical, digitalSignature\n"); let now = time::OffsetDateTime::now_utc(); validate_ee_cert_path(&g.ee_der, &g.issuer_ca_der, &g.issuer_crl_der, None, None, now) .expect("valid EE path"); } #[test] fn ee_key_usage_missing_is_rejected() { let g = generate_issuer_ca_ee_and_crl(""); let now = time::OffsetDateTime::now_utc(); let err = validate_ee_cert_path(&g.ee_der, &g.issuer_ca_der, &g.issuer_crl_der, None, None, now) .unwrap_err(); assert!(matches!(err, CertPathError::KeyUsageMissing), "{err}"); } #[test] fn ee_key_usage_not_critical_is_rejected() { let g = generate_issuer_ca_ee_and_crl("keyUsage = digitalSignature\n"); let now = time::OffsetDateTime::now_utc(); let err = validate_ee_cert_path(&g.ee_der, &g.issuer_ca_der, &g.issuer_crl_der, None, None, now) .unwrap_err(); assert!(matches!(err, CertPathError::KeyUsageNotCritical), "{err}"); } #[test] fn ee_key_usage_wrong_bits_is_rejected() { let g = generate_issuer_ca_ee_and_crl("keyUsage = critical, digitalSignature, keyEncipherment\n"); let now = time::OffsetDateTime::now_utc(); let err = validate_ee_cert_path(&g.ee_der, &g.issuer_ca_der, &g.issuer_crl_der, None, None, now) .unwrap_err(); assert!(matches!(err, CertPathError::KeyUsageInvalidBits), "{err}"); }