add delta sync and fail fetch process

This commit is contained in:
2026-02-27 18:02:01 +08:00
parent 68cbd3c500
commit 13516c4f73
24 changed files with 4815 additions and 89 deletions

View File

@@ -7,6 +7,8 @@ use crate::data_model::rc::{
};
use x509_parser::prelude::{FromDer, X509Certificate};
use crate::validation::x509_name::x509_names_equivalent;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ValidatedSubordinateCa {
pub child_ca: ResourceCertificate,
@@ -135,7 +137,7 @@ pub fn validate_subordinate_ca_cert(
return Err(CaPathError::IssuerNotCa);
}
if child_ca.tbs.issuer_name != issuer_ca.tbs.subject_name {
if !x509_names_equivalent(&child_ca.tbs.issuer_name, &issuer_ca.tbs.subject_name) {
return Err(CaPathError::IssuerSubjectMismatch {
child_issuer_dn: child_ca.tbs.issuer_name.to_string(),
issuer_subject_dn: issuer_ca.tbs.subject_name.to_string(),
@@ -697,6 +699,7 @@ mod tests {
};
use crate::data_model::common::X509NameDer;
use der_parser::num_bigint::BigUint;
use std::process::Command;
fn dummy_cert(
kind: ResourceCertKind,
subject_dn: &str,
@@ -736,6 +739,158 @@ mod tests {
}
}
fn openssl_available() -> bool {
Command::new("openssl")
.arg("version")
.output()
.map(|o| o.status.success())
.unwrap_or(false)
}
fn write_cert_der_with_addext(dir: &std::path::Path, addext: Option<&str>) -> Vec<u8> {
assert!(openssl_available(), "openssl is required for this test");
let key = dir.join("k.pem");
let cert = dir.join("c.pem");
let der = dir.join("c.der");
let mut cmd = Command::new("openssl");
cmd.arg("req")
.arg("-x509")
.arg("-newkey")
.arg("rsa:2048")
.arg("-nodes")
.arg("-keyout")
.arg(&key)
.arg("-subj")
.arg("/CN=ku")
.arg("-days")
.arg("1")
.arg("-out")
.arg(&cert);
if let Some(ext) = addext {
cmd.arg("-addext").arg(ext);
}
let out = cmd.output().expect("openssl req");
assert!(
out.status.success(),
"openssl req failed: {}",
String::from_utf8_lossy(&out.stderr)
);
let out = Command::new("openssl")
.arg("x509")
.arg("-in")
.arg(&cert)
.arg("-outform")
.arg("DER")
.arg("-out")
.arg(&der)
.output()
.expect("openssl x509");
assert!(
out.status.success(),
"openssl x509 failed: {}",
String::from_utf8_lossy(&out.stderr)
);
std::fs::read(&der).expect("read der")
}
fn gen_issuer_and_child_der(dir: &std::path::Path) -> (Vec<u8>, Vec<u8>, Vec<u8>) {
assert!(openssl_available(), "openssl is required for this test");
let issuer_key = dir.join("issuer.key");
let issuer_csr = dir.join("issuer.csr");
let issuer_pem = dir.join("issuer.pem");
let issuer_der = dir.join("issuer.der");
let child_key = dir.join("child.key");
let child_csr = dir.join("child.csr");
let child_pem = dir.join("child.pem");
let child_der = dir.join("child.der");
let other_key = dir.join("other.key");
let other_csr = dir.join("other.csr");
let other_pem = dir.join("other.pem");
let other_der = dir.join("other.der");
let run = |cmd: &mut Command| {
let out = cmd.output().expect("run openssl");
assert!(
out.status.success(),
"command failed: {:?}\nstderr={}",
cmd,
String::from_utf8_lossy(&out.stderr)
);
};
// Issuer self-signed.
run(Command::new("openssl").args(["genrsa", "-out"]).arg(&issuer_key).arg("2048"));
run(Command::new("openssl")
.args(["req", "-new", "-key"])
.arg(&issuer_key)
.args(["-subj", "/CN=issuer", "-out"])
.arg(&issuer_csr));
run(Command::new("openssl")
.args(["x509", "-req", "-in"])
.arg(&issuer_csr)
.args(["-signkey"])
.arg(&issuer_key)
.args(["-days", "1", "-out"])
.arg(&issuer_pem));
run(Command::new("openssl")
.args(["x509", "-in"])
.arg(&issuer_pem)
.args(["-outform", "DER", "-out"])
.arg(&issuer_der));
// Child signed by issuer.
run(Command::new("openssl").args(["genrsa", "-out"]).arg(&child_key).arg("2048"));
run(Command::new("openssl")
.args(["req", "-new", "-key"])
.arg(&child_key)
.args(["-subj", "/CN=child", "-out"])
.arg(&child_csr));
run(Command::new("openssl")
.args(["x509", "-req", "-in"])
.arg(&child_csr)
.args(["-CA"])
.arg(&issuer_pem)
.args(["-CAkey"])
.arg(&issuer_key)
.args(["-CAcreateserial", "-days", "1", "-out"])
.arg(&child_pem));
run(Command::new("openssl")
.args(["x509", "-in"])
.arg(&child_pem)
.args(["-outform", "DER", "-out"])
.arg(&child_der));
// Other self-signed issuer.
run(Command::new("openssl").args(["genrsa", "-out"]).arg(&other_key).arg("2048"));
run(Command::new("openssl")
.args(["req", "-new", "-key"])
.arg(&other_key)
.args(["-subj", "/CN=other", "-out"])
.arg(&other_csr));
run(Command::new("openssl")
.args(["x509", "-req", "-in"])
.arg(&other_csr)
.args(["-signkey"])
.arg(&other_key)
.args(["-days", "1", "-out"])
.arg(&other_pem));
run(Command::new("openssl")
.args(["x509", "-in"])
.arg(&other_pem)
.args(["-outform", "DER", "-out"])
.arg(&other_der));
(
std::fs::read(&issuer_der).expect("read issuer der"),
std::fs::read(&child_der).expect("read child der"),
std::fs::read(&other_der).expect("read other der"),
)
}
#[test]
fn resolve_child_ip_resources_rejects_inherit_without_parent_effective_resources() {
let child = IpResourceSet {
@@ -953,6 +1108,142 @@ mod tests {
validate_child_crldp_contains_issuer_crl_uri(&child, "rsync://example.test/issuer.crl")
.expect("crldp ok");
}
#[test]
fn validate_child_ca_key_usage_accepts_only_keycertsign_and_crlsign_critical() {
let td = tempfile::tempdir().expect("tempdir");
let der = write_cert_der_with_addext(
td.path(),
Some("keyUsage = critical, keyCertSign, cRLSign"),
);
validate_child_ca_key_usage(&der).expect("key usage ok");
}
#[test]
fn validate_child_ca_key_usage_rejects_missing_noncritical_and_invalid_bits() {
let td = tempfile::tempdir().expect("tempdir");
let missing = write_cert_der_with_addext(td.path(), None);
let err = validate_child_ca_key_usage(&missing).unwrap_err();
assert!(matches!(err, CaPathError::KeyUsageMissing), "{err}");
let td = tempfile::tempdir().expect("tempdir");
let noncritical =
write_cert_der_with_addext(td.path(), Some("keyUsage = keyCertSign, cRLSign"));
let err = validate_child_ca_key_usage(&noncritical).unwrap_err();
assert!(matches!(err, CaPathError::KeyUsageNotCritical), "{err}");
let td = tempfile::tempdir().expect("tempdir");
let invalid = write_cert_der_with_addext(
td.path(),
Some("keyUsage = critical, keyCertSign, cRLSign, digitalSignature"),
);
let err = validate_child_ca_key_usage(&invalid).unwrap_err();
assert!(matches!(err, CaPathError::KeyUsageInvalidBits), "{err}");
}
#[test]
fn verify_cert_signature_with_issuer_accepts_valid_chain_and_rejects_wrong_issuer() {
let td = tempfile::tempdir().expect("tempdir");
let (issuer, child, other) = gen_issuer_and_child_der(td.path());
verify_cert_signature_with_issuer(&child, &issuer).expect("signature ok");
let err = verify_cert_signature_with_issuer(&child, &other).unwrap_err();
assert!(matches!(err, CaPathError::ChildSignatureInvalid(_)), "{err}");
}
#[test]
fn resolve_child_ip_and_as_resources_success_paths() {
use crate::data_model::rc::{AsIdOrRange, IpAddressOrRange, IpPrefix};
let parent_ip = IpResourceSet {
families: vec![IpAddressFamily {
afi: Afi::Ipv4,
choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Prefix(
IpPrefix {
afi: Afi::Ipv4,
prefix_len: 8,
addr: vec![10, 0, 0, 0],
},
)]),
}],
};
let child_ip_inherit = IpResourceSet {
families: vec![IpAddressFamily {
afi: Afi::Ipv4,
choice: IpAddressChoice::Inherit,
}],
};
let eff = resolve_child_ip_resources(Some(&child_ip_inherit), Some(&parent_ip))
.expect("inherit resolves")
.expect("some ip");
assert_eq!(eff.families.len(), 1);
assert!(matches!(
eff.families[0].choice,
IpAddressChoice::AddressesOrRanges(_)
));
let child_ip_subset = IpResourceSet {
families: vec![IpAddressFamily {
afi: Afi::Ipv4,
choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Prefix(
IpPrefix {
afi: Afi::Ipv4,
prefix_len: 16,
addr: vec![10, 1, 0, 0],
},
)]),
}],
};
resolve_child_ip_resources(Some(&child_ip_subset), Some(&parent_ip))
.expect("subset ok")
.expect("some");
let child_ip_bad = IpResourceSet {
families: vec![IpAddressFamily {
afi: Afi::Ipv4,
choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Prefix(
IpPrefix {
afi: Afi::Ipv4,
prefix_len: 16,
addr: vec![11, 0, 0, 0],
},
)]),
}],
};
let err = resolve_child_ip_resources(Some(&child_ip_bad), Some(&parent_ip)).unwrap_err();
assert!(matches!(err, CaPathError::ResourcesNotSubset), "{err}");
let parent_as = AsResourceSet {
asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![AsIdOrRange::Range {
min: 1,
max: 100,
}])),
rdi: None,
};
let child_as_inherit = AsResourceSet {
asnum: Some(AsIdentifierChoice::Inherit),
rdi: None,
};
let eff_as = resolve_child_as_resources(Some(&child_as_inherit), Some(&parent_as))
.expect("inherit as")
.expect("some");
assert_eq!(eff_as.asnum, parent_as.asnum);
let child_as_subset = AsResourceSet {
asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![AsIdOrRange::Id(50)])),
rdi: None,
};
resolve_child_as_resources(Some(&child_as_subset), Some(&parent_as))
.expect("subset as")
.expect("some");
let child_as_bad = AsResourceSet {
asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![AsIdOrRange::Id(200)])),
rdi: None,
};
let err = resolve_child_as_resources(Some(&child_as_bad), Some(&parent_as)).unwrap_err();
assert!(matches!(err, CaPathError::ResourcesNotSubset), "{err}");
}
}
fn increment_bytes(v: &[u8]) -> Vec<u8> {