367 lines
12 KiB
Rust
367 lines
12 KiB
Rust
use std::process::Command;
|
|
|
|
use rpki::data_model::rc::{ResourceCertificate, ResourceCertKind};
|
|
use rpki::data_model::router_cert::{
|
|
BgpsecRouterCertificate, BgpsecRouterCertificateDecodeError,
|
|
BgpsecRouterCertificatePathError, BgpsecRouterCertificateProfileError,
|
|
};
|
|
|
|
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>,
|
|
router_der: Vec<u8>,
|
|
issuer_crl_der: Vec<u8>,
|
|
wrong_issuer_der: Vec<u8>,
|
|
}
|
|
|
|
fn generate_router_cert_with_variant(
|
|
key_spec: &str,
|
|
include_eku: bool,
|
|
extra_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
|
|
certificatePolicies = critical, 1.3.6.1.5.5.7.14.2
|
|
subjectInfoAccess = caRepository;URI:rsync://example.test/repo/issuer/, rpkiManifest;URI:rsync://example.test/repo/issuer/issuer.mft, rpkiNotify;URI:https://example.test/notification.xml
|
|
sbgp-ipAddrBlock = critical, IPv4:10.0.0.0/8
|
|
sbgp-autonomousSysNum = critical, AS:64496-64511
|
|
|
|
[ v3_router ]
|
|
keyUsage = critical, digitalSignature
|
|
{eku_line}
|
|
authorityKeyIdentifier = keyid:always
|
|
crlDistributionPoints = URI:rsync://example.test/repo/issuer/issuer.crl
|
|
authorityInfoAccess = caIssuers;URI:rsync://example.test/repo/issuer/issuer.cer
|
|
certificatePolicies = critical, 1.3.6.1.5.5.7.14.2
|
|
sbgp-autonomousSysNum = critical, AS:64496
|
|
{eku_line}
|
|
{extra_ext}
|
|
|
|
[ crl_ext ]
|
|
authorityKeyIdentifier = keyid:always
|
|
"#,
|
|
dir = dir.display(),
|
|
eku_line = if include_eku { "extendedKeyUsage = 1.3.6.1.5.5.7.3.30" } else { "" },
|
|
extra_ext = extra_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")));
|
|
|
|
match key_spec {
|
|
"ec-p256" => {
|
|
run(Command::new("openssl")
|
|
.arg("ecparam")
|
|
.arg("-name")
|
|
.arg("prime256v1")
|
|
.arg("-genkey")
|
|
.arg("-noout")
|
|
.arg("-out")
|
|
.arg(dir.join("router.key")));
|
|
}
|
|
"ec-p384" => {
|
|
run(Command::new("openssl")
|
|
.arg("ecparam")
|
|
.arg("-name")
|
|
.arg("secp384r1")
|
|
.arg("-genkey")
|
|
.arg("-noout")
|
|
.arg("-out")
|
|
.arg(dir.join("router.key")));
|
|
}
|
|
"rsa" => {
|
|
run(Command::new("openssl")
|
|
.arg("genrsa")
|
|
.arg("-out")
|
|
.arg(dir.join("router.key"))
|
|
.arg("2048"));
|
|
}
|
|
other => panic!("unsupported key_spec {other}"),
|
|
}
|
|
|
|
run(Command::new("openssl")
|
|
.arg("req")
|
|
.arg("-new")
|
|
.arg("-key")
|
|
.arg(dir.join("router.key"))
|
|
.arg("-subj")
|
|
.arg("/CN=ROUTER-0000FC10/serialNumber=01020304")
|
|
.arg("-out")
|
|
.arg(dir.join("router.csr")));
|
|
|
|
run(Command::new("openssl")
|
|
.arg("ca")
|
|
.arg("-batch")
|
|
.arg("-config")
|
|
.arg(dir.join("openssl.cnf"))
|
|
.arg("-in")
|
|
.arg(dir.join("router.csr"))
|
|
.arg("-extensions")
|
|
.arg("v3_router")
|
|
.arg("-out")
|
|
.arg(dir.join("router.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("router.pem"))
|
|
.arg("-outform")
|
|
.arg("DER")
|
|
.arg("-out")
|
|
.arg(dir.join("router.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")));
|
|
|
|
// Wrong issuer for path failure.
|
|
run(Command::new("openssl")
|
|
.arg("genrsa")
|
|
.arg("-out")
|
|
.arg(dir.join("other.key"))
|
|
.arg("2048"));
|
|
run(Command::new("openssl")
|
|
.arg("req")
|
|
.arg("-new")
|
|
.arg("-x509")
|
|
.arg("-sha256")
|
|
.arg("-days")
|
|
.arg("365")
|
|
.arg("-key")
|
|
.arg(dir.join("other.key"))
|
|
.arg("-subj")
|
|
.arg("/CN=Other Issuer")
|
|
.arg("-out")
|
|
.arg(dir.join("other.pem")));
|
|
run(Command::new("openssl")
|
|
.arg("x509")
|
|
.arg("-in")
|
|
.arg(dir.join("other.pem"))
|
|
.arg("-outform")
|
|
.arg("DER")
|
|
.arg("-out")
|
|
.arg(dir.join("other.cer")));
|
|
|
|
Generated {
|
|
issuer_ca_der: std::fs::read(dir.join("issuer.cer")).expect("read issuer der"),
|
|
router_der: std::fs::read(dir.join("router.cer")).expect("read router der"),
|
|
issuer_crl_der: std::fs::read(dir.join("issuer.crl")).expect("read crl der"),
|
|
wrong_issuer_der: std::fs::read(dir.join("other.cer")).expect("read other issuer der"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn decode_bgpsec_router_certificate_fixture_smoke() {
|
|
let g = generate_router_cert_with_variant("ec-p256", true, "");
|
|
let cert = BgpsecRouterCertificate::decode_der(&g.router_der).expect("decode router cert");
|
|
assert_eq!(cert.resource_cert.kind, ResourceCertKind::Ee);
|
|
assert_eq!(cert.asns, vec![64496]);
|
|
assert_eq!(cert.subject_key_identifier.len(), 20);
|
|
assert_eq!(cert.spki_der[0], 0x30);
|
|
}
|
|
|
|
#[test]
|
|
fn router_certificate_profile_rejects_missing_eku() {
|
|
let g = generate_router_cert_with_variant("ec-p256", false, "");
|
|
let err = BgpsecRouterCertificate::decode_der(&g.router_der).unwrap_err();
|
|
assert!(matches!(err, BgpsecRouterCertificateDecodeError::Validate(BgpsecRouterCertificateProfileError::MissingBgpsecRouterEku | BgpsecRouterCertificateProfileError::MissingExtendedKeyUsage)), "{err}");
|
|
}
|
|
|
|
#[test]
|
|
fn router_certificate_profile_rejects_sia_and_ip_resources_and_ranges() {
|
|
let g = generate_router_cert_with_variant(
|
|
"ec-p256",
|
|
true,
|
|
"subjectInfoAccess = caRepository;URI:rsync://example.test/repo/router/\n",
|
|
);
|
|
let err = BgpsecRouterCertificate::decode_der(&g.router_der).unwrap_err();
|
|
assert!(matches!(err, BgpsecRouterCertificateDecodeError::Validate(BgpsecRouterCertificateProfileError::SubjectInfoAccessPresent)), "{err}");
|
|
|
|
let g = generate_router_cert_with_variant(
|
|
"ec-p256",
|
|
true,
|
|
"sbgp-ipAddrBlock = critical, IPv4:10.0.0.0/8\n",
|
|
);
|
|
let err = BgpsecRouterCertificate::decode_der(&g.router_der).unwrap_err();
|
|
assert!(matches!(err, BgpsecRouterCertificateDecodeError::Validate(BgpsecRouterCertificateProfileError::IpResourcesPresent)), "{err}");
|
|
|
|
let g = generate_router_cert_with_variant(
|
|
"ec-p256",
|
|
true,
|
|
"sbgp-autonomousSysNum = critical, AS:64496-64500\n",
|
|
);
|
|
let err = BgpsecRouterCertificate::decode_der(&g.router_der).unwrap_err();
|
|
assert!(matches!(err, BgpsecRouterCertificateDecodeError::Validate(BgpsecRouterCertificateProfileError::AsResourcesRangeNotAllowed)), "{err}");
|
|
}
|
|
|
|
#[test]
|
|
fn router_certificate_profile_rejects_wrong_spki_algorithm_or_curve() {
|
|
let g = generate_router_cert_with_variant("rsa", true, "");
|
|
let err = BgpsecRouterCertificate::decode_der(&g.router_der).unwrap_err();
|
|
assert!(matches!(err, BgpsecRouterCertificateDecodeError::Validate(BgpsecRouterCertificateProfileError::SpkiAlgorithmNotEcPublicKey)), "{err}");
|
|
|
|
let g = generate_router_cert_with_variant("ec-p384", true, "");
|
|
let err = BgpsecRouterCertificate::decode_der(&g.router_der).unwrap_err();
|
|
assert!(matches!(err, BgpsecRouterCertificateDecodeError::Validate(BgpsecRouterCertificateProfileError::SpkiWrongCurve | BgpsecRouterCertificateProfileError::SpkiEcPointNotUncompressedP256)), "{err}");
|
|
}
|
|
|
|
#[test]
|
|
fn router_certificate_path_validation_accepts_valid_and_rejects_wrong_issuer() {
|
|
use rpki::data_model::common::BigUnsigned;
|
|
use rpki::data_model::crl::RpkixCrl;
|
|
use std::collections::HashSet;
|
|
use x509_parser::prelude::FromDer;
|
|
use x509_parser::x509::SubjectPublicKeyInfo;
|
|
|
|
let g = generate_router_cert_with_variant("ec-p256", true, "");
|
|
let issuer = ResourceCertificate::decode_der(&g.issuer_ca_der).expect("decode issuer");
|
|
let wrong_issuer = ResourceCertificate::decode_der(&g.wrong_issuer_der).expect("decode wrong issuer");
|
|
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("issuer spki");
|
|
assert!(rem.is_empty());
|
|
let (rem, wrong_spki) = SubjectPublicKeyInfo::from_der(&wrong_issuer.tbs.subject_public_key_info).expect("wrong issuer spki");
|
|
assert!(rem.is_empty());
|
|
let now = time::OffsetDateTime::now_utc();
|
|
|
|
let cert = BgpsecRouterCertificate::validate_path_with_prevalidated_issuer(
|
|
&g.router_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("router path valid");
|
|
assert_eq!(cert.asns, vec![64496]);
|
|
|
|
let err = BgpsecRouterCertificate::validate_path_with_prevalidated_issuer(
|
|
&g.router_der,
|
|
&wrong_issuer,
|
|
&wrong_spki,
|
|
&issuer_crl,
|
|
&HashSet::new(),
|
|
None,
|
|
None,
|
|
now,
|
|
).unwrap_err();
|
|
assert!(matches!(err, BgpsecRouterCertificatePathError::CertPath(_)), "{err}");
|
|
|
|
let rc = ResourceCertificate::decode_der(&g.router_der).expect("decode router rc");
|
|
let mut revoked = HashSet::new();
|
|
revoked.insert(BigUnsigned::from_biguint(&rc.tbs.serial_number).bytes_be);
|
|
let err = BgpsecRouterCertificate::validate_path_with_prevalidated_issuer(
|
|
&g.router_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, BgpsecRouterCertificatePathError::CertPath(_)), "{err}");
|
|
}
|