rpki/tests/test_cert_path_key_usage.rs

494 lines
14 KiB
Rust

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<u8>,
ee_der: Vec<u8>,
issuer_crl_der: Vec<u8>,
}
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}");
}
#[test]
fn validate_ee_cert_path_with_prevalidated_issuer_covers_success_and_error_paths() {
use rpki::data_model::common::BigUnsigned;
use rpki::data_model::crl::RpkixCrl;
use rpki::data_model::rc::ResourceCertificate;
use rpki::validation::cert_path::validate_ee_cert_path_with_prevalidated_issuer;
use std::collections::HashSet;
use x509_parser::prelude::FromDer;
use x509_parser::x509::SubjectPublicKeyInfo;
let g = generate_issuer_ca_ee_and_crl(
"keyUsage = critical, digitalSignature
",
);
let issuer = ResourceCertificate::decode_der(&g.issuer_ca_der).expect("decode issuer");
let ee = ResourceCertificate::decode_der(&g.ee_der).expect("decode ee");
let issuer_crl = RpkixCrl::decode_der(&g.issuer_crl_der).expect("decode crl");
let (rem, issuer_spki) = SubjectPublicKeyInfo::from_der(&issuer.tbs.subject_public_key_info)
.expect("parse issuer spki");
assert!(rem.is_empty());
let now = time::OffsetDateTime::now_utc();
validate_ee_cert_path_with_prevalidated_issuer(
&g.ee_der,
&issuer,
&issuer_spki,
&issuer_crl,
&HashSet::new(),
Some("rsync://example.test/repo/issuer/issuer.cer"),
Some("rsync://example.test/repo/issuer/issuer.crl"),
now,
)
.expect("prevalidated ee path ok");
let mut revoked = HashSet::new();
revoked.insert(BigUnsigned::from_biguint(&ee.tbs.serial_number).bytes_be);
let err = validate_ee_cert_path_with_prevalidated_issuer(
&g.ee_der,
&issuer,
&issuer_spki,
&issuer_crl,
&revoked,
Some("rsync://example.test/repo/issuer/issuer.cer"),
Some("rsync://example.test/repo/issuer/issuer.crl"),
now,
)
.unwrap_err();
assert!(matches!(err, CertPathError::EeRevoked), "{err}");
}
#[test]
fn validate_ee_cert_path_with_prevalidated_issuer_rejects_non_ee_and_non_ca_issuer() {
use rpki::data_model::crl::RpkixCrl;
use rpki::data_model::rc::ResourceCertificate;
use rpki::validation::cert_path::validate_ee_cert_path_with_prevalidated_issuer;
use std::collections::HashSet;
use x509_parser::prelude::FromDer;
use x509_parser::x509::SubjectPublicKeyInfo;
let g = generate_issuer_ca_ee_and_crl(
"keyUsage = critical, digitalSignature
",
);
let issuer = ResourceCertificate::decode_der(&g.issuer_ca_der).expect("decode issuer");
let ee = ResourceCertificate::decode_der(&g.ee_der).expect("decode ee");
let issuer_crl = RpkixCrl::decode_der(&g.issuer_crl_der).expect("decode crl");
let (rem, issuer_spki) = SubjectPublicKeyInfo::from_der(&issuer.tbs.subject_public_key_info)
.expect("parse issuer spki");
assert!(rem.is_empty());
let now = time::OffsetDateTime::now_utc();
let err = validate_ee_cert_path_with_prevalidated_issuer(
&g.issuer_ca_der,
&issuer,
&issuer_spki,
&issuer_crl,
&HashSet::new(),
None,
None,
now,
)
.unwrap_err();
assert!(matches!(err, CertPathError::EeNotEe), "{err}");
let (rem, bad_spki) =
SubjectPublicKeyInfo::from_der(&ee.tbs.subject_public_key_info).expect("parse ee spki");
assert!(rem.is_empty());
let err = validate_ee_cert_path_with_prevalidated_issuer(
&g.ee_der,
&ee,
&bad_spki,
&issuer_crl,
&HashSet::new(),
None,
None,
now,
)
.unwrap_err();
assert!(matches!(err, CertPathError::IssuerNotCa), "{err}");
}
#[test]
fn validate_ee_cert_path_with_prevalidated_issuer_rejects_mismatched_issuer_subject() {
use rpki::data_model::crl::RpkixCrl;
use rpki::data_model::rc::ResourceCertificate;
use rpki::validation::cert_path::validate_ee_cert_path_with_prevalidated_issuer;
use std::collections::HashSet;
use x509_parser::prelude::FromDer;
use x509_parser::x509::SubjectPublicKeyInfo;
let g = generate_issuer_ca_ee_and_crl(
"keyUsage = critical, digitalSignature
",
);
let wrong_issuer_der = std::fs::read(
"tests/fixtures/repository/ca.rg.net/rpki/RGnet-OU/R-lVU1XGsAeqzV1Fv0HjOD6ZFkE.cer",
)
.expect("read wrong issuer fixture");
let wrong_issuer =
ResourceCertificate::decode_der(&wrong_issuer_der).expect("decode wrong issuer");
let issuer_crl = RpkixCrl::decode_der(&g.issuer_crl_der).expect("decode crl");
let (rem, wrong_spki) =
SubjectPublicKeyInfo::from_der(&wrong_issuer.tbs.subject_public_key_info)
.expect("parse wrong issuer spki");
assert!(rem.is_empty());
let err = validate_ee_cert_path_with_prevalidated_issuer(
&g.ee_der,
&wrong_issuer,
&wrong_spki,
&issuer_crl,
&HashSet::new(),
None,
None,
time::OffsetDateTime::now_utc(),
)
.unwrap_err();
assert!(
matches!(err, CertPathError::IssuerSubjectMismatch { .. }),
"{err}"
);
}
#[test]
fn validate_ee_cert_path_rejects_non_ee_and_non_ca_issuer() {
use rpki::data_model::roa::RoaObject;
let g = generate_issuer_ca_ee_and_crl(
"keyUsage = critical, digitalSignature
",
);
let roa_der =
std::fs::read("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa")
.expect("read roa fixture");
let roa = RoaObject::decode_der(&roa_der).expect("decode roa");
let ee_from_roa = roa.signed_object.signed_data.certificates[0]
.raw_der
.clone();
let now = time::OffsetDateTime::now_utc();
let err = validate_ee_cert_path(
&g.issuer_ca_der,
&g.issuer_ca_der,
&g.issuer_crl_der,
None,
None,
now,
)
.unwrap_err();
assert!(matches!(err, CertPathError::EeNotEe), "{err}");
let err = validate_ee_cert_path(&g.ee_der, &ee_from_roa, &g.issuer_crl_der, None, None, now)
.unwrap_err();
assert!(matches!(err, CertPathError::IssuerNotCa), "{err}");
}
#[test]
fn validate_ee_cert_path_rejects_stale_crl() {
use rpki::data_model::crl::RpkixCrl;
let g = generate_issuer_ca_ee_and_crl(
"keyUsage = critical, digitalSignature
",
);
let crl = RpkixCrl::decode_der(&g.issuer_crl_der).expect("decode crl");
let err = validate_ee_cert_path(
&g.ee_der,
&g.issuer_ca_der,
&g.issuer_crl_der,
None,
None,
crl.next_update.utc + time::Duration::seconds(1),
)
.unwrap_err();
assert!(matches!(err, CertPathError::CrlNotValidAtTime), "{err}");
}
#[test]
fn validate_ee_cert_path_with_prevalidated_issuer_rejects_invalid_times() {
use rpki::data_model::crl::RpkixCrl;
use rpki::data_model::rc::ResourceCertificate;
use rpki::validation::cert_path::validate_ee_cert_path_with_prevalidated_issuer;
use std::collections::HashSet;
use x509_parser::prelude::FromDer;
use x509_parser::x509::SubjectPublicKeyInfo;
let g = generate_issuer_ca_ee_and_crl(
"keyUsage = critical, digitalSignature
",
);
let issuer = ResourceCertificate::decode_der(&g.issuer_ca_der).expect("decode issuer");
let ee = ResourceCertificate::decode_der(&g.ee_der).expect("decode ee");
let issuer_crl = RpkixCrl::decode_der(&g.issuer_crl_der).expect("decode crl");
let (rem, issuer_spki) = SubjectPublicKeyInfo::from_der(&issuer.tbs.subject_public_key_info)
.expect("parse issuer spki");
assert!(rem.is_empty());
let err = validate_ee_cert_path_with_prevalidated_issuer(
&g.ee_der,
&issuer,
&issuer_spki,
&issuer_crl,
&HashSet::new(),
Some("rsync://example.test/repo/issuer/issuer.cer"),
Some("rsync://example.test/repo/issuer/issuer.crl"),
ee.tbs.validity_not_before - time::Duration::seconds(1),
)
.unwrap_err();
assert!(
matches!(err, CertPathError::CertificateNotValidAtTime),
"{err}"
);
let err = validate_ee_cert_path_with_prevalidated_issuer(
&g.ee_der,
&issuer,
&issuer_spki,
&issuer_crl,
&HashSet::new(),
Some("rsync://example.test/repo/issuer/issuer.cer"),
Some("rsync://example.test/repo/issuer/issuer.crl"),
issuer_crl.next_update.utc + time::Duration::seconds(1),
)
.unwrap_err();
assert!(matches!(err, CertPathError::CrlNotValidAtTime), "{err}");
}