重构error code

This commit is contained in:
2026-02-04 17:02:17 +08:00
parent cc9f3f21de
commit a58e507f92
44 changed files with 6095 additions and 1165 deletions

View File

@@ -1,4 +1,5 @@
use rpki::data_model::aspa::{AspaDecodeError, AspaObject};
use rpki::data_model::aspa::{AspaDecodeError, AspaObject, AspaProfileError};
use rpki::data_model::signed_object::RpkiSignedObject;
#[test]
fn decode_aspa_fixture_smoke() {
@@ -7,6 +8,7 @@ fn decode_aspa_fixture_smoke() {
)
.expect("read ASPA fixture");
let aspa = AspaObject::decode_der(&der).expect("decode aspa");
aspa.validate_profile().expect("validate ASPA profile");
assert_eq!(aspa.econtent_type, rpki::data_model::oid::OID_CT_ASPA);
assert_eq!(aspa.aspa.version, 1);
assert_ne!(aspa.aspa.customer_as_id, 0);
@@ -15,11 +17,23 @@ fn decode_aspa_fixture_smoke() {
}
#[test]
fn decode_rejects_non_aspa_econtent_type() {
fn from_signed_object_accepts_aspa_fixture() {
let der = std::fs::read(
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa",
"tests/fixtures/repository/chloe.sobornost.net/rpki/RIPE-nljobsnijders/5m80fwYws_3FiFD7JiQjAqZ1RYQ.asa",
)
.expect("read ROA fixture");
let err = AspaObject::decode_der(&der).unwrap_err();
assert!(matches!(err, AspaDecodeError::InvalidEContentType(_)));
.expect("read ASPA fixture");
let so = RpkiSignedObject::decode_der(&der).expect("decode signed object");
let aspa = AspaObject::from_signed_object(so).expect("from_signed_object");
aspa.validate_profile().expect("validate ASPA profile");
}
#[test]
fn decode_rejects_non_aspa_econtent_type() {
let der = std::fs::read("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa")
.expect("read ROA fixture");
let err = AspaObject::decode_der(&der).unwrap_err();
assert!(matches!(
err,
AspaDecodeError::Validate(AspaProfileError::InvalidEContentType(_))
));
}

View File

@@ -1,4 +1,4 @@
use rpki::data_model::aspa::{AspaDecodeError, AspaEContent};
use rpki::data_model::aspa::{AspaDecodeError, AspaEContent, AspaParseError, AspaProfileError};
fn len_bytes(len: usize) -> Vec<u8> {
if len < 128 {
@@ -68,22 +68,87 @@ fn aspa_attestation_missing_version(customer: u64, providers: Vec<u64>) -> Vec<u
der_sequence(vec![der_integer_u64(customer), providers_der])
}
#[test]
fn trailing_bytes_are_rejected_in_parse_step() {
let der = aspa_attestation_explicit_version(1, 64496, vec![64497]);
let mut bad = der.clone();
bad.push(0);
let err = AspaEContent::decode_der(&bad).unwrap_err();
assert!(matches!(
err,
AspaDecodeError::Parse(AspaParseError::TrailingBytes(1))
));
}
#[test]
fn version_tag_must_be_context_specific_0() {
// Build a 3-element SEQUENCE but make the first element an INTEGER, not [0] EXPLICIT.
let providers_der = der_sequence(vec![der_integer_u64(64497)]);
let der = der_sequence(vec![
der_integer_u64(1), // wrong tag/class
der_integer_u64(64496), // customerASID
providers_der, // providers
]);
let err = AspaEContent::decode_der(&der).unwrap_err();
assert!(matches!(
err,
AspaDecodeError::Validate(AspaProfileError::VersionMustBeExplicitOne)
));
}
#[test]
fn version_explicit_tag_rejects_trailing_bytes_inside_inner_der() {
let mut version_inner = der_integer_u64(1);
version_inner.extend(tlv(0x05, &[])); // NULL after INTEGER
let providers_der = der_sequence(vec![der_integer_u64(64497)]);
let der = der_sequence(vec![
cs_explicit(0, version_inner),
der_integer_u64(64496),
providers_der,
]);
let err = AspaEContent::decode_der(&der).unwrap_err();
assert!(matches!(
err,
AspaDecodeError::Validate(AspaProfileError::ProfileDecode(_))
));
}
#[test]
fn provider_asid_out_of_range_is_rejected() {
let der = aspa_attestation_explicit_version(1, 64496, vec![(u32::MAX as u64) + 1]);
let err = AspaEContent::decode_der(&der).unwrap_err();
assert!(matches!(
err,
AspaDecodeError::Validate(AspaProfileError::ProviderAsIdOutOfRange(_))
));
}
#[test]
fn version_must_be_explicit_and_equal_to_one() {
let der = aspa_attestation_missing_version(64496, vec![64497]);
let err = AspaEContent::decode_der(&der).unwrap_err();
assert!(matches!(err, AspaDecodeError::InvalidAttestationSequence));
assert!(matches!(
err,
AspaDecodeError::Validate(AspaProfileError::InvalidAttestationSequence)
));
let der = aspa_attestation_explicit_version(0, 64496, vec![64497]);
let err = AspaEContent::decode_der(&der).unwrap_err();
assert!(matches!(err, AspaDecodeError::VersionMustBeExplicitOne));
assert!(matches!(
err,
AspaDecodeError::Validate(AspaProfileError::VersionMustBeExplicitOne)
));
}
#[test]
fn customer_asid_out_of_range_is_rejected() {
let der = aspa_attestation_explicit_version(1, (u32::MAX as u64) + 1, vec![64497]);
let err = AspaEContent::decode_der(&der).unwrap_err();
assert!(matches!(err, AspaDecodeError::CustomerAsIdOutOfRange(_)));
assert!(matches!(
err,
AspaDecodeError::Validate(AspaProfileError::CustomerAsIdOutOfRange(_))
));
}
#[test]
@@ -91,21 +156,32 @@ fn providers_constraints_are_enforced() {
// empty providers
let der = aspa_attestation_explicit_version(1, 64496, vec![]);
let err = AspaEContent::decode_der(&der).unwrap_err();
assert!(matches!(err, AspaDecodeError::EmptyProviders));
assert!(matches!(
err,
AspaDecodeError::Validate(AspaProfileError::EmptyProviders)
));
// not strictly increasing (duplicate)
let der = aspa_attestation_explicit_version(1, 64496, vec![64497, 64497]);
let err = AspaEContent::decode_der(&der).unwrap_err();
assert!(matches!(err, AspaDecodeError::ProvidersNotStrictlyIncreasing));
assert!(matches!(
err,
AspaDecodeError::Validate(AspaProfileError::ProvidersNotStrictlyIncreasing)
));
// not strictly increasing (descending)
let der = aspa_attestation_explicit_version(1, 64496, vec![64500, 64497]);
let err = AspaEContent::decode_der(&der).unwrap_err();
assert!(matches!(err, AspaDecodeError::ProvidersNotStrictlyIncreasing));
assert!(matches!(
err,
AspaDecodeError::Validate(AspaProfileError::ProvidersNotStrictlyIncreasing)
));
// contains customer
let der = aspa_attestation_explicit_version(1, 64496, vec![64496, 64497]);
let err = AspaEContent::decode_der(&der).unwrap_err();
assert!(matches!(err, AspaDecodeError::ProvidersContainCustomer(64496)));
assert!(matches!(
err,
AspaDecodeError::Validate(AspaProfileError::ProvidersContainCustomer(64496))
));
}

View File

@@ -11,4 +11,3 @@ fn aspa_embedded_ee_cert_resources_validate() {
aspa.validate_embedded_ee_cert()
.expect("aspa EE cert resources must validate");
}

View File

@@ -3,11 +3,14 @@ use time::OffsetDateTime;
use rpki::data_model::aspa::{AspaEContent, AspaValidateError};
use rpki::data_model::rc::{
AsIdentifierChoice, AsIdOrRange, AsResourceSet, IpResourceSet, RcExtensions, ResourceCertKind,
AsIdOrRange, AsIdentifierChoice, AsResourceSet, IpResourceSet, RcExtensions, ResourceCertKind,
ResourceCertificate, RpkixTbsCertificate, SubjectInfoAccess,
};
fn dummy_ee(ip_resources: Option<IpResourceSet>, as_resources: Option<AsResourceSet>) -> ResourceCertificate {
fn dummy_ee(
ip_resources: Option<IpResourceSet>,
as_resources: Option<AsResourceSet>,
) -> ResourceCertificate {
ResourceCertificate {
raw_der: vec![],
tbs: RpkixTbsCertificate {
@@ -50,7 +53,9 @@ fn validate_accepts_when_customer_matches_ee_asid() {
let ee = dummy_ee(
None,
Some(AsResourceSet {
asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![AsIdOrRange::Id(64496)])),
asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![AsIdOrRange::Id(
64496,
)])),
rdi: None,
}),
);
@@ -82,10 +87,12 @@ fn validate_rejects_as_resources_inherit_or_ranges() {
let ee = dummy_ee(
None,
Some(AsResourceSet {
asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![AsIdOrRange::Range {
min: 64496,
max: 64497,
}])),
asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![
AsIdOrRange::Range {
min: 64496,
max: 64497,
},
])),
rdi: None,
}),
);
@@ -99,12 +106,17 @@ fn validate_rejects_customer_mismatch() {
let ee = dummy_ee(
None,
Some(AsResourceSet {
asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![AsIdOrRange::Id(64511)])),
asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![AsIdOrRange::Id(
64511,
)])),
rdi: None,
}),
);
let err = aspa.validate_against_ee_cert(&ee).unwrap_err();
assert!(matches!(err, AspaValidateError::CustomerAsIdMismatch { .. }));
assert!(matches!(
err,
AspaValidateError::CustomerAsIdMismatch { .. }
));
}
#[test]
@@ -113,7 +125,9 @@ fn validate_rejects_ip_resources_present() {
let ee = dummy_ee(
Some(IpResourceSet { families: vec![] }),
Some(AsResourceSet {
asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![AsIdOrRange::Id(64496)])),
asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![AsIdOrRange::Id(
64496,
)])),
rdi: None,
}),
);
@@ -127,8 +141,12 @@ fn validate_rejects_rdi_present_or_not_single_id() {
let ee = dummy_ee(
None,
Some(AsResourceSet {
asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![AsIdOrRange::Id(64496)])),
rdi: Some(AsIdentifierChoice::AsIdsOrRanges(vec![AsIdOrRange::Id(64496)])),
asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![AsIdOrRange::Id(
64496,
)])),
rdi: Some(AsIdentifierChoice::AsIdsOrRanges(vec![AsIdOrRange::Id(
64496,
)])),
}),
);
let err = aspa.validate_against_ee_cert(&ee).unwrap_err();
@@ -147,4 +165,3 @@ fn validate_rejects_rdi_present_or_not_single_id() {
let err = aspa.validate_against_ee_cert(&ee).unwrap_err();
assert!(matches!(err, AspaValidateError::EeAsResourcesNotSingleId));
}

View File

@@ -11,4 +11,3 @@ fn verify_aspa_cms_signature_with_embedded_ee_cert() {
.verify_signature()
.expect("ASPA CMS signature should verify with embedded EE cert");
}

View File

@@ -1,9 +1,9 @@
use rpki::data_model::common::{
algorithm_params_absent_or_null, Asn1TimeEncoding, Asn1TimeUtc, BigUnsigned,
Asn1TimeEncoding, Asn1TimeUtc, BigUnsigned, algorithm_params_absent_or_null,
};
use x509_parser::prelude::FromDer;
use x509_parser::x509::AlgorithmIdentifier;
use x509_parser::time::ASN1Time;
use x509_parser::x509::AlgorithmIdentifier;
#[test]
fn big_unsigned_helpers() {
@@ -32,8 +32,11 @@ fn time_encoding_validation() {
t.validate_encoding_rfc5280("t").expect("utc ok");
let t = Asn1TimeUtc {
utc: time::OffsetDateTime::parse("2050-01-01T00:00:00Z", &time::format_description::well_known::Rfc3339)
.unwrap(),
utc: time::OffsetDateTime::parse(
"2050-01-01T00:00:00Z",
&time::format_description::well_known::Rfc3339,
)
.unwrap(),
encoding: Asn1TimeEncoding::UtcTime,
};
assert!(t.validate_encoding_rfc5280("t").is_err());
@@ -43,18 +46,12 @@ fn time_encoding_validation() {
fn algorithm_params_absent_or_null_helper() {
// AlgorithmIdentifier ::= SEQUENCE { algorithm OID, parameters ANY OPTIONAL }
// Using sha256WithRSAEncryption with NULL parameters.
let alg_null = hex::decode(
"300D06092A864886F70D01010B0500",
)
.unwrap();
let alg_null = hex::decode("300D06092A864886F70D01010B0500").unwrap();
let (_rem, id) = AlgorithmIdentifier::from_der(&alg_null).expect("parse AlgorithmIdentifier");
assert!(algorithm_params_absent_or_null(&id));
// Same OID, but parameters = INTEGER 1 (invalid for our helper).
let alg_int = hex::decode(
"300E06092A864886F70D01010B020101",
)
.unwrap();
let alg_int = hex::decode("300E06092A864886F70D01010B020101").unwrap();
let (_rem, id) = AlgorithmIdentifier::from_der(&alg_int).expect("parse AlgorithmIdentifier");
assert!(!algorithm_params_absent_or_null(&id));

View File

@@ -1,7 +1,7 @@
use std::path::PathBuf;
use rpki::data_model::crl::RpkixCrl;
use rpki::data_model::crl::Asn1TimeEncoding;
use rpki::data_model::crl::RpkixCrl;
#[test]
fn decode_and_validate_crl_fixture() {
@@ -42,9 +42,8 @@ fn crl_signature_verification_succeeds_with_issuer_cert() {
#[test]
fn decode_crl_with_revoked_entries() {
let der =
std::fs::read("tests/fixtures/0099DEAB073EFD74C250C0A382B25012B5082AEE.crl")
.expect("read CRL fixture with revoked entries");
let der = std::fs::read("tests/fixtures/0099DEAB073EFD74C250C0A382B25012B5082AEE.crl")
.expect("read CRL fixture with revoked entries");
let crl = RpkixCrl::decode_der(&der).expect("decode CRL");

View File

@@ -1,11 +1,11 @@
use rpki::data_model::crl::{CrlDecodeError, CrlVerifyError, RpkixCrl};
use rpki::data_model::crl::{CrlDecodeError, CrlParseError, CrlVerifyError, RpkixCrl};
use x509_parser::prelude::FromDer;
use x509_parser::prelude::X509Certificate;
const TEST_NO_CRLSIGN_CERT_DER_B64: &str = "MIIDATCCAemgAwIBAgIUCyQLQJn92+gyAzvIz22q1F/97OMwDQYJKoZIhvcNAQELBQAwGjEYMBYGA1UEAwwPVGVzdCBObyBDUkxTaWduMB4XDTI2MDEyNzAzNTk1OVoXDTM2MDEyNTAzNTk1OVowGjEYMBYGA1UEAwwPVGVzdCBObyBDUkxTaWduMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr/aoMU8J6cddkM2r6F2snd1rCdQPepgo2T2lrqWFcxnQJdcxBL1OYg3wFi95TJmZSeIHIOGauDaJ2abmjgyOUHOC4U68x66JRg4hLkmLxo1cf3uYHWl9Obph6g2qPRvN80ORq70JPuL6mAfUkNiO9hnwK6oQiTzc/rjCQGIFH8kTESBMXLfNCyUpGi+MNztYH6Ha6bKAQuXgd29OFwIkOlGQnYgGC2qBMvnp86eITvV1gTiuI8Ho9m9nZHCmaD7TylvkMDq8Hk5nkIpRcG0uO60SkR2BiMOYe/TNn5dTmHd6bsdbU2GOvgnq1SnqGq3FOWhKIe3ycUJde0uNfZOqRwIDAQABoz8wPTAOBgNVHQ8BAf8EBAMCBaAwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUFjyzfJCDNhFfKxVr06kjUkE23dMwDQYJKoZIhvcNAQELBQADggEBAK98n2gVlwKA3Ob1YeAm9f+8hm7pbvrt0tA8GW180CILjf09k7fKgiRlxqGdZ9ySXjU52+zCqu3MpBXVbI87ZC+zA6uK05n4y1F0n85MJ9hGR2UEiPcqou85X73LvioynnSOy/OV1PjKJXReUsqF3GgDtgcMyFssPJ9s/5DWuUCScUJY6pu0kuIGOLQ/oXUw4TvxUeyz73gOTiAJshVTQoLpHUhj0595S7lArjwi7oLI1b8m8guTknvhk0Sc3tJZmUqOcIvYIs0guHpaeC+sMoF4K+6UTrxxOBdX+fUEWNpUyYXWHjdZq25PbJdHwA/VAW2zYVojaVREligf0Qfo6F4=";
fn test_no_crlsign_cert_der() -> Vec<u8> {
use base64::{engine::general_purpose, Engine as _};
use base64::{Engine as _, engine::general_purpose};
general_purpose::STANDARD
.decode(TEST_NO_CRLSIGN_CERT_DER_B64)
.expect("decode base64 cert")
@@ -67,7 +67,10 @@ fn verify_errors_are_reported() {
let err = crl
.verify_signature_with_issuer_certificate_der(&bad)
.unwrap_err();
assert!(matches!(err, CrlVerifyError::IssuerCertificateTrailingBytes(1)));
assert!(matches!(
err,
CrlVerifyError::IssuerCertificateTrailingBytes(1)
));
}
#[test]
@@ -103,7 +106,10 @@ fn decode_rejects_trailing_bytes() {
let mut bad = crl_der.clone();
bad.push(0);
let err = RpkixCrl::decode_der(&bad).unwrap_err();
assert!(matches!(err, CrlDecodeError::TrailingBytes(1)));
assert!(matches!(
err,
CrlDecodeError::Parse(CrlParseError::TrailingBytes(1))
));
}
#[test]
@@ -122,6 +128,8 @@ fn verify_rejects_crl_with_trailing_bytes_in_raw_der() {
let (_rem, issuer_cert) = X509Certificate::from_der(&issuer_cert_der).unwrap();
let spki_der = issuer_cert.public_key().raw.to_vec();
let err = crl.verify_signature_with_issuer_spki_der(&spki_der).unwrap_err();
let err = crl
.verify_signature_with_issuer_spki_der(&spki_der)
.unwrap_err();
assert!(matches!(err, CrlVerifyError::CrlTrailingBytes(1)));
}

View File

@@ -0,0 +1,161 @@
use std::path::PathBuf;
use rpki::data_model::aspa::AspaEContent;
use rpki::data_model::aspa::AspaObject;
use rpki::data_model::crl::RpkixCrl;
use rpki::data_model::manifest::ManifestEContent;
use rpki::data_model::manifest::ManifestObject;
use rpki::data_model::rc::ResourceCertificate;
use rpki::data_model::roa::RoaEContent;
use rpki::data_model::roa::RoaObject;
use rpki::data_model::signed_object::RpkiSignedObject;
use rpki::data_model::ta::{TaCertificate, TrustAnchor};
use rpki::data_model::tal::Tal;
#[test]
fn scheme_a_layered_api_smoke() {
// TAL / TA / TrustAnchor
let tal_path = PathBuf::from("tests/fixtures/tal/ripe-ncc.tal");
let tal_bytes = std::fs::read(&tal_path).expect("read TAL fixture");
let tal = Tal::parse_bytes(&tal_bytes)
.expect("parse TAL")
.validate_profile()
.expect("validate TAL profile");
let ta_path = PathBuf::from("tests/fixtures/ta/ripe-ncc-ta.cer");
let ta_der = std::fs::read(&ta_path).expect("read TA cert fixture");
let ta = TaCertificate::parse_der(&ta_der)
.expect("parse TA cert")
.validate_profile()
.expect("validate TA constraints");
ta.verify_self_signature()
.expect("verify TA self-signature");
let resolved = tal
.ta_uris
.first()
.cloned()
.expect("TAL must include at least one TA URI");
let _ta = TrustAnchor::bind(tal, ta, Some(&resolved)).expect("bind trust anchor");
// A CA resource certificate fixture (used as issuer in other tests).
let ca_path = PathBuf::from(
"tests/fixtures/repository/rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer",
);
let ca_der = std::fs::read(&ca_path).expect("read CA cert fixture");
let ca_rc = ResourceCertificate::parse_der(&ca_der)
.expect("parse CA resource certificate")
.validate_profile()
.expect("validate CA resource certificate profile");
ca_rc
.validate_profile()
.expect("validate CA resource certificate profile");
// Signed object wrapper.
let mft_path = PathBuf::from(
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
);
let mft_der = std::fs::read(&mft_path).expect("read MFT fixture");
let so = RpkiSignedObject::parse_der(&mft_der)
.expect("parse signed object")
.validate_profile()
.expect("validate signed object profile");
so.verify().expect("verify CMS signature");
// Manifest object.
let mft_obj = ManifestObject::parse_der(&mft_der)
.expect("parse manifest")
.validate_profile()
.expect("validate manifest profile");
mft_obj
.validate_profile()
.expect("validate manifest profile");
mft_obj
.validate_embedded_ee_cert()
.expect("validate manifest EE resources");
mft_obj
.signed_object
.verify()
.expect("verify manifest CMS signature");
let mft_ec = ManifestEContent::parse_der(
&mft_obj
.signed_object
.signed_data
.encap_content_info
.econtent,
)
.expect("parse MFT eContent")
.validate_profile()
.expect("validate MFT eContent profile");
mft_ec
.validate_profile()
.expect("validate MFT eContent profile");
// ROA object.
let roa_path =
PathBuf::from("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa");
let roa_der = std::fs::read(&roa_path).expect("read ROA fixture");
let roa_obj = RoaObject::parse_der(&roa_der)
.expect("parse ROA")
.validate_profile()
.expect("validate ROA profile");
roa_obj
.validate_embedded_ee_cert()
.expect("validate ROA EE resources");
roa_obj
.signed_object
.verify()
.expect("verify ROA CMS signature");
let roa_ec = RoaEContent::parse_der(
&roa_obj
.signed_object
.signed_data
.encap_content_info
.econtent,
)
.expect("parse ROA eContent")
.validate_profile()
.expect("validate ROA eContent profile");
roa_ec
.validate_profile()
.expect("validate ROA eContent profile");
// ASPA object.
let aspa_path = PathBuf::from(
"tests/fixtures/repository/chloe.sobornost.net/rpki/RIPE-nljobsnijders/5m80fwYws_3FiFD7JiQjAqZ1RYQ.asa",
);
let aspa_der = std::fs::read(&aspa_path).expect("read ASPA fixture");
let aspa_obj = AspaObject::parse_der(&aspa_der)
.expect("parse ASPA")
.validate_profile()
.expect("validate ASPA profile");
aspa_obj
.validate_embedded_ee_cert()
.expect("validate ASPA EE resources");
aspa_obj
.signed_object
.verify()
.expect("verify ASPA CMS signature");
let aspa_ec = AspaEContent::parse_der(
&aspa_obj
.signed_object
.signed_data
.encap_content_info
.econtent,
)
.expect("parse ASPA eContent")
.validate_profile()
.expect("validate ASPA eContent profile");
aspa_ec
.validate_profile()
.expect("validate ASPA eContent profile");
// CRL object.
let crl_path = PathBuf::from("tests/fixtures/0099DEAB073EFD74C250C0A382B25012B5082AEE.crl");
let crl_der = std::fs::read(&crl_path).expect("read CRL fixture with revoked entries");
let crl = RpkixCrl::parse_der(&crl_der)
.expect("parse CRL")
.validate_profile()
.expect("validate CRL profile");
crl.validate_profile().expect("validate CRL profile");
}

View File

@@ -1,4 +1,7 @@
use rpki::data_model::manifest::{ManifestEContent, ManifestObject};
use rpki::data_model::manifest::{
ManifestDecodeError, ManifestEContent, ManifestObject, ManifestProfileError,
};
use rpki::data_model::signed_object::RpkiSignedObject;
#[test]
fn decode_manifest_fixture_smoke() {
@@ -7,17 +10,44 @@ fn decode_manifest_fixture_smoke() {
)
.expect("read MFT fixture");
let mft = ManifestObject::decode_der(&der).expect("decode manifest object");
mft.validate_profile().expect("validate manifest profile");
assert_eq!(mft.manifest.version, 0);
assert_eq!(mft.manifest.file_hash_alg, rpki::data_model::oid::OID_SHA256);
assert_eq!(
mft.manifest.file_hash_alg,
rpki::data_model::oid::OID_SHA256
);
assert!(mft.manifest.next_update > mft.manifest.this_update);
assert!(!mft.manifest.files.is_empty());
// The manifest file MUST NOT be listed in its own fileList.
assert!(mft
.manifest
.files
.iter()
.all(|f| !f.file_name.to_ascii_lowercase().ends_with(".mft")));
assert!(
mft.manifest
.files
.iter()
.all(|f| !f.file_name.to_ascii_lowercase().ends_with(".mft"))
);
}
#[test]
fn decode_rejects_non_manifest_econtent_type() {
let der = std::fs::read("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa")
.expect("read ROA fixture");
let err = ManifestObject::decode_der(&der).unwrap_err();
assert!(matches!(
err,
ManifestDecodeError::Validate(ManifestProfileError::InvalidEContentType(_))
));
}
#[test]
fn from_signed_object_accepts_manifest_fixture() {
let so_der = std::fs::read(
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
)
.expect("read MFT fixture");
let so = RpkiSignedObject::decode_der(&so_der).expect("decode signed object");
let mft = ManifestObject::from_signed_object(so).expect("from_signed_object");
mft.validate_profile().expect("validate manifest profile");
}
#[test]
@@ -32,4 +62,3 @@ fn decode_manifest_econtent_from_fixture_signed_object() {
.expect("decode manifest eContent");
assert_eq!(e.version, 0);
}

View File

@@ -1,5 +1,7 @@
use rpki::data_model::common::BigUnsigned;
use rpki::data_model::manifest::{ManifestDecodeError, ManifestEContent, ManifestObject};
use rpki::data_model::manifest::{
ManifestDecodeError, ManifestEContent, ManifestObject, ManifestParseError, ManifestProfileError,
};
use rpki::data_model::signed_object::RpkiSignedObject;
fn len_bytes(len: usize) -> Vec<u8> {
@@ -49,8 +51,8 @@ fn der_integer_u64(v: u64) -> Vec<u8> {
}
fn der_oid(oid: &str) -> Vec<u8> {
use std::str::FromStr;
use der_parser::asn1_rs::ToDer;
use std::str::FromStr;
let oid = der_parser::Oid::from_str(oid).unwrap();
oid.to_der_vec().unwrap()
}
@@ -116,7 +118,10 @@ fn manifest_econtent_version_must_be_zero_when_present() {
vec![],
);
let err = ManifestEContent::decode_der(&der).unwrap_err();
assert!(matches!(err, ManifestDecodeError::InvalidManifestVersion(1)));
assert!(matches!(
err,
ManifestDecodeError::Validate(ManifestProfileError::InvalidManifestVersion(1))
));
}
#[test]
@@ -131,7 +136,10 @@ fn manifest_number_too_long_rejected() {
vec![],
);
let err = ManifestEContent::decode_der(&der).unwrap_err();
assert!(matches!(err, ManifestDecodeError::ManifestNumberTooLong));
assert!(matches!(
err,
ManifestDecodeError::Validate(ManifestProfileError::ManifestNumberTooLong)
));
}
#[test]
@@ -146,7 +154,10 @@ fn manifest_number_negative_rejected() {
vec![],
);
let err = ManifestEContent::decode_der(&der).unwrap_err();
assert!(matches!(err, ManifestDecodeError::InvalidManifestNumber));
assert!(matches!(
err,
ManifestDecodeError::Validate(ManifestProfileError::InvalidManifestNumber)
));
}
#[test]
@@ -160,7 +171,10 @@ fn this_update_and_next_update_must_be_generalized_time() {
vec![],
);
let err = ManifestEContent::decode_der(&der).unwrap_err();
assert!(matches!(err, ManifestDecodeError::InvalidThisUpdate));
assert!(matches!(
err,
ManifestDecodeError::Validate(ManifestProfileError::InvalidThisUpdate)
));
let der = manifest_der(
Some(0),
@@ -171,7 +185,10 @@ fn this_update_and_next_update_must_be_generalized_time() {
vec![],
);
let err = ManifestEContent::decode_der(&der).unwrap_err();
assert!(matches!(err, ManifestDecodeError::InvalidNextUpdate));
assert!(matches!(
err,
ManifestDecodeError::Validate(ManifestProfileError::InvalidNextUpdate)
));
}
#[test]
@@ -185,7 +202,10 @@ fn next_update_must_be_later() {
vec![],
);
let err = ManifestEContent::decode_der(&der).unwrap_err();
assert!(matches!(err, ManifestDecodeError::NextUpdateNotLater));
assert!(matches!(
err,
ManifestDecodeError::Validate(ManifestProfileError::NextUpdateNotLater)
));
}
#[test]
@@ -199,7 +219,10 @@ fn file_hash_alg_must_be_sha256() {
vec![],
);
let err = ManifestEContent::decode_der(&der).unwrap_err();
assert!(matches!(err, ManifestDecodeError::InvalidFileHashAlg(_)));
assert!(matches!(
err,
ManifestDecodeError::Validate(ManifestProfileError::InvalidFileHashAlg(_))
));
}
#[test]
@@ -215,7 +238,10 @@ fn file_list_entry_validation() {
vec![file_and_hash("bad!.roa", 0, &hash)],
);
let err = ManifestEContent::decode_der(&der).unwrap_err();
assert!(matches!(err, ManifestDecodeError::InvalidFileName(_)));
assert!(matches!(
err,
ManifestDecodeError::Validate(ManifestProfileError::InvalidFileName(_))
));
// Non-octet-aligned BIT STRING
let der = manifest_der(
@@ -227,7 +253,10 @@ fn file_list_entry_validation() {
vec![file_and_hash("ok.roa", 1, &hash)],
);
let err = ManifestEContent::decode_der(&der).unwrap_err();
assert!(matches!(err, ManifestDecodeError::HashNotOctetAligned));
assert!(matches!(
err,
ManifestDecodeError::Validate(ManifestProfileError::HashNotOctetAligned)
));
// Wrong hash length
let der = manifest_der(
@@ -239,7 +268,10 @@ fn file_list_entry_validation() {
vec![file_and_hash("ok.roa", 0, &[0u8; 31])],
);
let err = ManifestEContent::decode_der(&der).unwrap_err();
assert!(matches!(err, ManifestDecodeError::InvalidHashLength(31)));
assert!(matches!(
err,
ManifestDecodeError::Validate(ManifestProfileError::InvalidHashLength(31))
));
}
#[test]
@@ -251,7 +283,10 @@ fn manifest_object_requires_correct_econtent_type() {
let mut so = RpkiSignedObject::decode_der(&so_der).expect("decode signed object");
so.signed_data.encap_content_info.econtent_type = "1.2.3.4".to_string();
let err = ManifestObject::from_signed_object(so).unwrap_err();
assert!(matches!(err, ManifestDecodeError::InvalidEContentType(_)));
assert!(matches!(
err,
ManifestDecodeError::Validate(ManifestProfileError::InvalidEContentType(_))
));
}
#[test]
@@ -260,7 +295,7 @@ fn manifest_sequence_length_is_validated() {
let err = ManifestEContent::decode_der(&der).unwrap_err();
assert!(matches!(
err,
ManifestDecodeError::InvalidManifestSequenceLen(_)
ManifestDecodeError::Validate(ManifestProfileError::InvalidManifestSequenceLen(_))
));
}
@@ -279,7 +314,10 @@ fn file_list_must_be_sequence_and_entry_shape_validated() {
der_sequence(fields)
};
let err = ManifestEContent::decode_der(&der).unwrap_err();
assert!(matches!(err, ManifestDecodeError::InvalidFileList));
assert!(matches!(
err,
ManifestDecodeError::Validate(ManifestProfileError::InvalidFileList)
));
// FileAndHash not SEQUENCE of 2
let der = manifest_der(
@@ -291,7 +329,10 @@ fn file_list_must_be_sequence_and_entry_shape_validated() {
vec![der_sequence(vec![der_ia5("ok.roa")])],
);
let err = ManifestEContent::decode_der(&der).unwrap_err();
assert!(matches!(err, ManifestDecodeError::InvalidFileAndHash));
assert!(matches!(
err,
ManifestDecodeError::Validate(ManifestProfileError::InvalidFileAndHash)
));
}
#[test]
@@ -308,7 +349,10 @@ fn version_tag_must_be_context_specific_0() {
der_sequence(fields)
};
let err = ManifestEContent::decode_der(&der).unwrap_err();
assert!(matches!(err, ManifestDecodeError::Parse(_)));
assert!(matches!(
err,
ManifestDecodeError::Validate(ManifestProfileError::ProfileDecode(_))
));
}
#[test]
@@ -331,7 +375,10 @@ fn manifest_econtent_trailing_bytes_are_rejected() {
let mut bad = der.clone();
bad.push(0);
let err = ManifestEContent::decode_der(&bad).unwrap_err();
assert!(matches!(err, ManifestDecodeError::TrailingBytes(1)));
assert!(matches!(
err,
ManifestDecodeError::Parse(ManifestParseError::TrailingBytes(1))
));
}
#[test]
@@ -350,7 +397,10 @@ fn manifest_version_rejects_trailing_bytes_inside_explicit_tag() {
der_sequence(fields)
};
let err = ManifestEContent::decode_der(&der).unwrap_err();
assert!(matches!(err, ManifestDecodeError::Parse(_)));
assert!(matches!(
err,
ManifestDecodeError::Validate(ManifestProfileError::ProfileDecode(_))
));
}
#[test]
@@ -366,18 +416,21 @@ fn manifest_rejects_hash_with_wrong_type() {
vec![entry],
);
let err = ManifestEContent::decode_der(&der).unwrap_err();
assert!(matches!(err, ManifestDecodeError::InvalidHashType));
assert!(matches!(
err,
ManifestDecodeError::Validate(ManifestProfileError::InvalidHashType)
));
}
#[test]
fn file_name_validation_branches_are_exercised() {
let hash = vec![0u8; 32];
let cases = [
"noext", // missing '.'
".roa", // empty base
"a.roaa", // ext len != 3
"a.r0a", // ext not alphabetic
"a.txt", // ext not allowlisted
"noext", // missing '.'
".roa", // empty base
"a.roaa", // ext len != 3
"a.r0a", // ext not alphabetic
"a.txt", // ext not allowlisted
];
for name in cases {
let der = manifest_der(
@@ -389,7 +442,10 @@ fn file_name_validation_branches_are_exercised() {
vec![file_and_hash(name, 0, &hash)],
);
let err = ManifestEContent::decode_der(&der).unwrap_err();
assert!(matches!(err, ManifestDecodeError::InvalidFileName(_)));
assert!(matches!(
err,
ManifestDecodeError::Validate(ManifestProfileError::InvalidFileName(_))
));
}
}

View File

@@ -1,14 +1,89 @@
use rpki::data_model::manifest::ManifestObject;
use rpki::data_model::manifest::{ManifestObject, ManifestValidateError};
use rpki::data_model::rc::{
Afi, AsIdOrRange, AsIdentifierChoice, AsResourceSet, IpAddressChoice, IpAddressFamily,
IpResourceSet,
};
#[test]
fn manifest_embedded_ee_cert_resources_validate() {
fn load_manifest_fixture() -> ManifestObject {
let der = std::fs::read(
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
)
.expect("read MFT fixture");
ManifestObject::decode_der(&der).expect("decode manifest")
}
let mft = ManifestObject::decode_der(&der).expect("decode manifest");
#[test]
fn manifest_embedded_ee_cert_resources_validate() {
let mft = load_manifest_fixture();
mft.validate_embedded_ee_cert()
.expect("manifest EE cert resources must validate");
}
#[test]
fn validate_rejects_when_ip_and_as_resources_missing() {
let mft = load_manifest_fixture();
let mut ee = mft.signed_object.signed_data.certificates[0]
.resource_cert
.clone();
ee.tbs.extensions.ip_resources = None;
ee.tbs.extensions.as_resources = None;
let err = mft.validate_against_ee_cert(&ee).unwrap_err();
assert!(matches!(err, ManifestValidateError::EeResourcesMissing));
}
#[test]
fn validate_rejects_when_ip_resources_not_inherit() {
let mft = load_manifest_fixture();
let mut ee = mft.signed_object.signed_data.certificates[0]
.resource_cert
.clone();
ee.tbs.extensions.ip_resources = Some(IpResourceSet {
families: vec![IpAddressFamily {
afi: Afi::Ipv4,
choice: IpAddressChoice::AddressesOrRanges(vec![]),
}],
});
ee.tbs.extensions.as_resources = None;
let err = mft.validate_against_ee_cert(&ee).unwrap_err();
assert!(matches!(
err,
ManifestValidateError::EeIpResourcesNotInherit
));
}
#[test]
fn validate_rejects_when_as_rdi_present_or_asnum_not_inherit() {
let mft = load_manifest_fixture();
// rdi present is rejected.
let mut ee = mft.signed_object.signed_data.certificates[0]
.resource_cert
.clone();
ee.tbs.extensions.ip_resources = None;
ee.tbs.extensions.as_resources = Some(AsResourceSet {
asnum: Some(AsIdentifierChoice::Inherit),
rdi: Some(AsIdentifierChoice::Inherit),
});
let err = mft.validate_against_ee_cert(&ee).unwrap_err();
assert!(matches!(
err,
ManifestValidateError::EeAsResourcesRdiPresent
));
// asnum not inherit is rejected.
let mut ee = mft.signed_object.signed_data.certificates[0]
.resource_cert
.clone();
ee.tbs.extensions.ip_resources = None;
ee.tbs.extensions.as_resources = Some(AsResourceSet {
asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![AsIdOrRange::Id(
64496,
)])),
rdi: None,
});
let err = mft.validate_against_ee_cert(&ee).unwrap_err();
assert!(matches!(
err,
ManifestValidateError::EeAsResourcesNotInherit
));
}

View File

@@ -2,13 +2,13 @@ use rpki::data_model::aspa::{AspaEContent, AspaObject};
use rpki::data_model::crl::{CrlExtensions, RevokedCert, RpkixCrl};
use rpki::data_model::manifest::{FileAndHash, ManifestEContent, ManifestObject};
use rpki::data_model::rc::{
AsResourceSet, IpResourceSet, RcExtensions, ResourceCertKind, ResourceCertificate, RpkixTbsCertificate,
SubjectInfoAccess,
AsResourceSet, IpResourceSet, RcExtensions, ResourceCertKind, ResourceCertificate,
RpkixTbsCertificate, SubjectInfoAccess,
};
use rpki::data_model::roa::{RoaEContent, RoaIpAddressFamily, RoaObject};
use rpki::data_model::signed_object::{
EncapsulatedContentInfo, ResourceEeCertificate, RpkiSignedObject, SignedAttrsProfiled, SignedDataProfiled,
SignerInfoProfiled,
EncapsulatedContentInfo, ResourceEeCertificate, RpkiSignedObject, SignedAttrsProfiled,
SignedDataProfiled, SignerInfoProfiled,
};
use rpki::data_model::ta::{TaCertificate, TrustAnchor};
use rpki::data_model::tal::Tal;
@@ -199,7 +199,11 @@ impl From<&SignedDataProfiled> for SignedDataProfiledPretty {
.map(ResourceEeCertificatePretty::from)
.collect(),
crls_present: v.crls_present,
signer_infos: v.signer_infos.iter().map(SignerInfoProfiledPretty::from).collect(),
signer_infos: v
.signer_infos
.iter()
.map(SignerInfoProfiledPretty::from)
.collect(),
}
}
}
@@ -434,6 +438,7 @@ fn print_all_models_from_real_fixtures() {
println!("Fixture (TA): {ta_path}");
let ta_der = std::fs::read(ta_path).expect("read TA fixture");
let ta = TaCertificate::from_der(&ta_der).expect("parse TA cert");
println!("TA.verify_self_signature={:?}", ta.verify_self_signature());
let resolved = tal
.ta_uris
@@ -442,15 +447,15 @@ fn print_all_models_from_real_fixtures() {
.or_else(|| tal.ta_uris.first())
.cloned()
.expect("tal has at least one uri");
let trust_anchor = TrustAnchor::bind(tal, &ta_der, Some(&resolved)).expect("bind trust anchor");
let trust_anchor =
TrustAnchor::bind(tal, ta.clone(), Some(&resolved)).expect("bind trust anchor");
println!("{:#?}", TalPretty::from(&trust_anchor.tal));
println!("{:#?}", TaCertificatePretty::from(&ta));
println!("{:#?}", TrustAnchorPretty::from(&trust_anchor));
println!();
println!("== ResourceCertificate (example non-TA CA cert) ==");
let ca_path =
"tests/fixtures/repository/rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer";
let ca_path = "tests/fixtures/repository/rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer";
println!("Fixture (CA cert): {ca_path}");
let ca_der = std::fs::read(ca_path).expect("read CA cert fixture");
let ca_rc = ResourceCertificate::from_der(&ca_der).expect("parse CA resource certificate");
@@ -458,14 +463,19 @@ fn print_all_models_from_real_fixtures() {
println!();
println!("== Signed Object / Manifest ==");
let mft_path =
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft";
let mft_path = "tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft";
println!("Fixture (MFT): {mft_path}");
let mft_der = std::fs::read(mft_path).expect("read MFT fixture");
let mft_obj = ManifestObject::decode_der(&mft_der).expect("decode manifest object");
println!("{:#?}", ManifestObjectPretty::from(&mft_obj));
println!("Manifest.validate_embedded_ee_cert={:?}", mft_obj.validate_embedded_ee_cert());
println!("Manifest.verify_signature={:?}", mft_obj.signed_object.verify_signature());
println!(
"Manifest.validate_embedded_ee_cert={:?}",
mft_obj.validate_embedded_ee_cert()
);
println!(
"Manifest.verify_signature={:?}",
mft_obj.signed_object.verify_signature()
);
println!();
println!("== Signed Object / ROA ==");
@@ -474,19 +484,30 @@ fn print_all_models_from_real_fixtures() {
let roa_der = std::fs::read(roa_path).expect("read ROA fixture");
let roa_obj = RoaObject::decode_der(&roa_der).expect("decode ROA object");
println!("{:#?}", RoaObjectPretty::from(&roa_obj));
println!("ROA.validate_embedded_ee_cert={:?}", roa_obj.validate_embedded_ee_cert());
println!("ROA.verify_signature={:?}", roa_obj.signed_object.verify_signature());
println!(
"ROA.validate_embedded_ee_cert={:?}",
roa_obj.validate_embedded_ee_cert()
);
println!(
"ROA.verify_signature={:?}",
roa_obj.signed_object.verify_signature()
);
println!();
println!("== Signed Object / ASPA ==");
let aspa_path =
"tests/fixtures/repository/chloe.sobornost.net/rpki/RIPE-nljobsnijders/5m80fwYws_3FiFD7JiQjAqZ1RYQ.asa";
let aspa_path = "tests/fixtures/repository/chloe.sobornost.net/rpki/RIPE-nljobsnijders/5m80fwYws_3FiFD7JiQjAqZ1RYQ.asa";
println!("Fixture (ASPA): {aspa_path}");
let aspa_der = std::fs::read(aspa_path).expect("read ASPA fixture");
let aspa_obj = AspaObject::decode_der(&aspa_der).expect("decode ASPA object");
println!("{:#?}", AspaObjectPretty::from(&aspa_obj));
println!("ASPA.validate_embedded_ee_cert={:?}", aspa_obj.validate_embedded_ee_cert());
println!("ASPA.verify_signature={:?}", aspa_obj.signed_object.verify_signature());
println!(
"ASPA.validate_embedded_ee_cert={:?}",
aspa_obj.validate_embedded_ee_cert()
);
println!(
"ASPA.verify_signature={:?}",
aspa_obj.signed_object.verify_signature()
);
println!();
println!("== CRL ==");

View File

@@ -1,10 +1,13 @@
use rpki::data_model::rc::{ResourceCertificate, ResourceCertificateError};
use rpki::data_model::rc::{
ResourceCertificate, ResourceCertificateError, ResourceCertificateParseError,
ResourceCertificateProfileError,
};
const TEST_NO_SIA_CERT_DER_B64: &str = "MIIDATCCAemgAwIBAgIUCyQLQJn92+gyAzvIz22q1F/97OMwDQYJKoZIhvcNAQELBQAwGjEYMBYGA1UEAwwPVGVzdCBObyBDUkxTaWduMB4XDTI2MDEyNzAzNTk1OVoXDTM2MDEyNTAzNTk1OVowGjEYMBYGA1UEAwwPVGVzdCBObyBDUkxTaWduMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr/aoMU8J6cddkM2r6F2snd1rCdQPepgo2T2lrqWFcxnQJdcxBL1OYg3wFi95TJmZSeIHIOGauDaJ2abmjgyOUHOC4U68x66JRg4hLkmLxo1cf3uYHWl9Obph6g2qPRvN80ORq70JPuL6mAfUkNiO9hnwK6oQiTzc/rjCQGIFH8kTESBMXLfNCyUpGi+MNztYH6Ha6bKAQuXgd29OFwIkOlGQnYgGC2qBMvnp86eITvV1gTiuI8Ho9m9nZHCmaD7TylvkMDq8Hk5nkIpRcG0uO60SkR2BiMOYe/TNn5dTmHd6bsdbU2GOvgnq1SnqGq3FOWhKIe3ycUJde0uNfZOqRwIDAQABoz8wPTAOBgNVHQ8BAf8EBAMCBaAwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUFjyzfJCDNhFfKxVr06kjUkE23dMwDQYJKoZIhvcNAQELBQADggEBAK98n2gVlwKA3Ob1YeAm9f+8hm7pbvrt0tA8GW180CILjf09k7fKgiRlxqGdZ9ySXjU52+zCqu3MpBXVbI87ZC+zA6uK05n4y1F0n85MJ9hGR2UEiPcqou85X73LvioynnSOy/OV1PjKJXReUsqF3GgDtgcMyFssPJ9s/5DWuUCScUJY6pu0kuIGOLQ/oXUw4TvxUeyz73gOTiAJshVTQoLpHUhj0595S7lArjwi7oLI1b8m8guTknvhk0Sc3tJZmUqOcIvYIs0guHpaeC+sMoF4K+6UTrxxOBdX+fUEWNpUyYXWHjdZq25PbJdHwA/VAW2zYVojaVREligf0Qfo6F4=";
fn decode_b64(b64: &str) -> Vec<u8> {
use base64::engine::general_purpose::STANDARD;
use base64::Engine as _;
use base64::engine::general_purpose::STANDARD;
STANDARD.decode(b64).unwrap()
}
@@ -44,7 +47,10 @@ fn trailing_bytes_after_cert_are_rejected() {
let mut der = decode_b64(TEST_NO_SIA_CERT_DER_B64);
der.push(0);
let err = ResourceCertificate::from_der(&der).unwrap_err();
assert!(matches!(err, ResourceCertificateError::TrailingBytes(1)));
assert!(matches!(
err,
ResourceCertificateError::Parse(ResourceCertificateParseError::TrailingBytes(1))
));
}
#[test]
@@ -52,24 +58,41 @@ fn signature_algorithm_mismatch_is_detected() {
let mut der = decode_b64(TEST_NO_SIA_CERT_DER_B64);
// DER encoding of sha256WithRSAEncryption OID:
// 06 09 2A 86 48 86 F7 0D 01 01 0B
let oid = [0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B];
let oid = [
0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B,
];
let mut patched = oid;
patched[10] = 0x01; // rsaEncryption, same length encoding
assert!(replace_first(&mut der, &oid, &patched));
let err = ResourceCertificate::from_der(&der).unwrap_err();
assert!(matches!(err, ResourceCertificateError::SignatureAlgorithmMismatch));
assert!(matches!(
err,
ResourceCertificateError::Validate(
ResourceCertificateProfileError::SignatureAlgorithmMismatch
)
));
}
#[test]
fn unsupported_signature_algorithm_is_detected() {
let mut der = decode_b64(TEST_NO_SIA_CERT_DER_B64);
let oid = [0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B];
let oid = [
0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B,
];
let mut patched = oid;
patched[10] = 0x01;
let n = replace_all(&mut der, &oid, &patched);
assert!(n >= 2, "expected to patch at least inner+outer AlgorithmIdentifier");
assert!(
n >= 2,
"expected to patch at least inner+outer AlgorithmIdentifier"
);
let err = ResourceCertificate::from_der(&der).unwrap_err();
assert!(matches!(err, ResourceCertificateError::UnsupportedSignatureAlgorithm));
assert!(matches!(
err,
ResourceCertificateError::Validate(
ResourceCertificateProfileError::UnsupportedSignatureAlgorithm
)
));
}
#[test]
@@ -83,11 +106,15 @@ fn invalid_signature_algorithm_parameters_are_detected() {
let mut patched = alg;
patched[11] = 0x04;
let n = replace_all(&mut der, &alg, &patched);
assert!(n >= 2, "expected to patch at least inner+outer AlgorithmIdentifier parameters");
assert!(
n >= 2,
"expected to patch at least inner+outer AlgorithmIdentifier parameters"
);
let err = ResourceCertificate::from_der(&der).unwrap_err();
assert!(matches!(
err,
ResourceCertificateError::InvalidSignatureAlgorithmParameters
ResourceCertificateError::Validate(
ResourceCertificateProfileError::InvalidSignatureAlgorithmParameters
)
));
}

View File

@@ -13,7 +13,11 @@ fn resource_certificate_from_der_parses_ca_fixtures() {
let der = std::fs::read(path).expect("read CA cert fixture");
let rc = ResourceCertificate::from_der(&der).expect("parse CA cert fixture");
assert_eq!(rc.kind, ResourceCertKind::Ca, "fixture should be CA: {path}");
assert_eq!(
rc.kind,
ResourceCertKind::Ca,
"fixture should be CA: {path}"
);
assert_eq!(rc.tbs.version, 2, "X.509 v3 encoded as 2: {path}");
assert_eq!(
@@ -23,21 +27,28 @@ fn resource_certificate_from_der_parses_ca_fixtures() {
);
assert!(
matches!(rc.tbs.extensions.subject_info_access, Some(SubjectInfoAccess::Ca(_))),
matches!(
rc.tbs.extensions.subject_info_access,
Some(SubjectInfoAccess::Ca(_))
),
"CA SIA should not contain signedObject accessMethod: {path}"
);
assert!(rc.tbs.extensions.ip_resources.is_some(), "CA should have IP resources: {path}");
assert!(
rc.tbs.extensions.ip_resources.is_some(),
"CA should have IP resources: {path}"
);
}
}
#[test]
fn resource_certificate_from_der_parses_as_resources_in_apnic_fixture() {
let path =
"tests/fixtures/repository/rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer";
let path = "tests/fixtures/repository/rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer";
let der = std::fs::read(path).expect("read APNIC CA cert fixture");
let rc = ResourceCertificate::from_der(&der).expect("parse APNIC CA cert fixture");
assert!(rc.tbs.extensions.as_resources.is_some(), "fixture should carry AS resources");
assert!(
rc.tbs.extensions.as_resources.is_some(),
"fixture should carry AS resources"
);
}

View File

@@ -1,6 +1,6 @@
use rpki::data_model::rc::{
Afi, AsIdOrRange, AsIdentifierChoice, AsResourceSet, IpAddressChoice, IpAddressFamily, IpAddressOrRange,
IpPrefix,
Afi, AsIdOrRange, AsIdentifierChoice, AsResourceSet, IpAddressChoice, IpAddressFamily,
IpAddressOrRange, IpPrefix,
};
#[test]
@@ -40,17 +40,21 @@ fn as_resource_set_asnum_single_id_returns_expected_values() {
assert_eq!(inherit.asnum_single_id(), None);
let single_id = AsResourceSet {
asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![AsIdOrRange::Id(64512)])),
asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![AsIdOrRange::Id(
64512,
)])),
rdi: None,
};
assert_eq!(single_id.asnum_single_id(), Some(64512));
let single_range = AsResourceSet {
asnum: None,
rdi: Some(AsIdentifierChoice::AsIdsOrRanges(vec![AsIdOrRange::Range {
min: 64512,
max: 64520,
}])),
rdi: Some(AsIdentifierChoice::AsIdsOrRanges(vec![
AsIdOrRange::Range {
min: 64512,
max: 64520,
},
])),
};
assert_eq!(single_range.asnum_single_id(), None);
@@ -68,7 +72,9 @@ fn as_resource_set_asnum_single_id_returns_expected_values() {
fn as_identifier_choice_has_range_detects_ranges() {
assert!(!AsIdentifierChoice::Inherit.has_range());
assert!(!AsIdentifierChoice::AsIdsOrRanges(vec![AsIdOrRange::Id(64512)]).has_range());
assert!(AsIdentifierChoice::AsIdsOrRanges(vec![AsIdOrRange::Range { min: 1, max: 2 }]).has_range());
assert!(
AsIdentifierChoice::AsIdsOrRanges(vec![AsIdOrRange::Range { min: 1, max: 2 }]).has_range()
);
}
#[test]
@@ -81,13 +87,16 @@ fn ip_resource_contains_prefix_finds_matching_family() {
addr: vec![0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
})]),
};
let set = rpki::data_model::rc::IpResourceSet { families: vec![fam_v6] };
let set = rpki::data_model::rc::IpResourceSet {
families: vec![fam_v6],
};
let p = IpPrefix {
afi: Afi::Ipv6,
prefix_len: 48,
addr: vec![0x20, 0x01, 0x0d, 0xb8, 0x12, 0x34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
addr: vec![
0x20, 0x01, 0x0d, 0xb8, 0x12, 0x34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
],
};
assert!(set.contains_prefix(&p));
}

View File

@@ -1,5 +1,6 @@
use rpki::data_model::rc::{
Afi, IpAddressChoice, IpAddressFamily, IpAddressOrRange, IpAddressRange, IpPrefix, IpResourceSet,
Afi, IpAddressChoice, IpAddressFamily, IpAddressOrRange, IpAddressRange, IpPrefix,
IpResourceSet,
};
#[test]
@@ -7,10 +8,12 @@ fn ip_resource_range_covers_prefix_ipv4() {
let set = IpResourceSet {
families: vec![IpAddressFamily {
afi: Afi::Ipv4,
choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Range(IpAddressRange {
min: vec![10, 0, 0, 0],
max: vec![10, 255, 255, 255],
})]),
choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Range(
IpAddressRange {
min: vec![10, 0, 0, 0],
max: vec![10, 255, 255, 255],
},
)]),
}],
};
@@ -34,10 +37,15 @@ fn ip_resource_range_covers_prefix_ipv6() {
let set = IpResourceSet {
families: vec![IpAddressFamily {
afi: Afi::Ipv6,
choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Range(IpAddressRange {
min: vec![0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
max: vec![0x20, 0x01, 0x0d, 0xb8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff],
})]),
choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Range(
IpAddressRange {
min: vec![0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
max: vec![
0x20, 0x01, 0x0d, 0xb8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff,
],
},
)]),
}],
};
@@ -55,4 +63,3 @@ fn ip_resource_range_covers_prefix_ipv6() {
};
assert!(!set.contains_prefix(&outside));
}

View File

@@ -1,5 +1,5 @@
use rpki::data_model::rc::{
Afi, AsIdentifierChoice, AsIdOrRange, AsResourceSet, IpAddressChoice, IpAddressOrRange,
Afi, AsIdOrRange, AsIdentifierChoice, AsResourceSet, IpAddressChoice, IpAddressOrRange,
IpResourceSet,
};
@@ -100,7 +100,8 @@ fn ip_addr_blocks_decode_handles_inherit_and_range_endpoint_fill() {
// Inherit branch.
let fam_inherit = der_sequence(vec![der_octet_string(&[0x00, 0x01]), der_null()]);
let decoded = IpResourceSet::decode_extn_value(&der_sequence(vec![fam_inherit])).expect("decode inherit");
let decoded =
IpResourceSet::decode_extn_value(&der_sequence(vec![fam_inherit])).expect("decode inherit");
assert!(decoded.is_all_inherit());
}
@@ -125,11 +126,17 @@ fn autonomous_sys_ids_decode_handles_inherit_ids_and_ranges() {
};
assert_eq!(items.len(), 2);
assert!(matches!(items[0], AsIdOrRange::Id(64496)));
assert!(matches!(items[1], AsIdOrRange::Range { min: 64500, max: 64510 }));
assert!(matches!(
items[1],
AsIdOrRange::Range {
min: 64500,
max: 64510
}
));
// asnum inherit.
let asnum_inherit = cs_cons(0, der_null());
let decoded = AsResourceSet::decode_extn_value(&der_sequence(vec![asnum_inherit])).expect("decode inherit");
let decoded = AsResourceSet::decode_extn_value(&der_sequence(vec![asnum_inherit]))
.expect("decode inherit");
assert!(decoded.is_asnum_inherit());
}

View File

@@ -93,10 +93,7 @@ fn ip_addr_blocks_decode_rejects_invalid_encodings() {
assert!(IpResourceSet::decode_extn_value(&der_sequence(vec![fam_wrong])).is_err());
// ipAddressChoice wrong type.
let fam_wrong = der_sequence(vec![
der_octet_string(&[0x00, 0x01]),
der_integer_u64(1),
]);
let fam_wrong = der_sequence(vec![der_octet_string(&[0x00, 0x01]), der_integer_u64(1)]);
assert!(IpResourceSet::decode_extn_value(&der_sequence(vec![fam_wrong])).is_err());
// BitString with invalid unused-bits value (>7).
@@ -188,8 +185,13 @@ fn ip_addr_blocks_range_upper_bound_can_fill_all_ones_when_bit_len_zero() {
der_sequence(vec![range]),
]);
let set = IpResourceSet::decode_extn_value(&der_sequence(vec![fam])).expect("decode range with 0-bit endpoints");
let fam = set.families.iter().find(|f| f.afi == rpki::data_model::rc::Afi::Ipv4).unwrap();
let set = IpResourceSet::decode_extn_value(&der_sequence(vec![fam]))
.expect("decode range with 0-bit endpoints");
let fam = set
.families
.iter()
.find(|f| f.afi == rpki::data_model::rc::Afi::Ipv4)
.unwrap();
let rpki::data_model::rc::IpAddressChoice::AddressesOrRanges(items) = &fam.choice else {
panic!("expected explicit addressesOrRanges");
};

View File

@@ -124,4 +124,3 @@ fn canonicalize_sorts_families_sorts_and_dedups_addresses() {
}
);
}

View File

@@ -1,12 +1,12 @@
use rpki::data_model::roa::{RoaDecodeError, RoaObject};
use rpki::data_model::roa::{RoaDecodeError, RoaObject, RoaProfileError};
use rpki::data_model::signed_object::RpkiSignedObject;
#[test]
fn decode_roa_fixture_smoke() {
let der = std::fs::read(
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa",
)
.expect("read ROA fixture");
let der = std::fs::read("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa")
.expect("read ROA fixture");
let roa = RoaObject::decode_der(&der).expect("decode roa");
roa.validate_profile().expect("validate ROA profile");
assert_eq!(
roa.econtent_type,
rpki::data_model::oid::OID_CT_ROUTE_ORIGIN_AUTHZ
@@ -14,14 +14,24 @@ fn decode_roa_fixture_smoke() {
assert_eq!(roa.roa.version, 0);
assert_eq!(roa.roa.as_id, 4538);
assert!(!roa.roa.ip_addr_blocks.is_empty());
assert!(roa
.roa
.ip_addr_blocks
.iter()
.all(|f| !f.addresses.is_empty()));
assert!(
roa.roa
.ip_addr_blocks
.iter()
.all(|f| !f.addresses.is_empty())
);
println!("{roa:#?}");
}
#[test]
fn from_signed_object_accepts_roa_fixture() {
let der = std::fs::read("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa")
.expect("read ROA fixture");
let so = RpkiSignedObject::decode_der(&der).expect("decode signed object");
let roa = RoaObject::from_signed_object(so).expect("from_signed_object");
roa.validate_profile().expect("validate ROA profile");
}
#[test]
fn decode_rejects_non_roa_econtent_type() {
let der = std::fs::read(
@@ -29,5 +39,8 @@ fn decode_rejects_non_roa_econtent_type() {
)
.expect("read MFT fixture");
let err = RoaObject::decode_der(&der).unwrap_err();
assert!(matches!(err, RoaDecodeError::InvalidEContentType(_)));
assert!(matches!(
err,
RoaDecodeError::Validate(RoaProfileError::InvalidEContentType(_))
));
}

View File

@@ -1,4 +1,4 @@
use rpki::data_model::roa::{RoaDecodeError, RoaEContent};
use rpki::data_model::roa::{RoaDecodeError, RoaEContent, RoaParseError, RoaProfileError};
fn len_bytes(len: usize) -> Vec<u8> {
if len < 128 {
@@ -86,15 +86,184 @@ fn roa_attestation(version: Option<u64>, as_id: u64, families: Vec<Vec<u8>>) ->
der_sequence(fields)
}
#[test]
fn trailing_bytes_are_rejected_in_parse_step() {
let der = roa_attestation(
None,
64496,
vec![roa_ip_family(
[0, 1],
vec![roa_ip_address(der_bit_string(0, &[]), None)],
)],
);
let mut bad = der.clone();
bad.push(0);
let err = RoaEContent::decode_der(&bad).unwrap_err();
assert!(matches!(
err,
RoaDecodeError::Parse(RoaParseError::TrailingBytes(1))
));
}
#[test]
fn attestation_sequence_len_is_validated() {
let der = der_sequence(vec![der_integer_u64(64496)]);
let err = RoaEContent::decode_der(&der).unwrap_err();
assert!(matches!(
err,
RoaDecodeError::Validate(RoaProfileError::InvalidAttestationSequenceLen(1))
));
}
#[test]
fn version_tag_must_be_context_specific_0() {
let der = {
let mut fields = Vec::new();
fields.push(cs_explicit(1, der_integer_u64(0))); // wrong tag number [1]
fields.push(der_integer_u64(64496));
fields.push(der_sequence(vec![roa_ip_family(
[0, 1],
vec![roa_ip_address(der_bit_string(0, &[]), None)],
)]));
der_sequence(fields)
};
let err = RoaEContent::decode_der(&der).unwrap_err();
assert!(matches!(
err,
RoaDecodeError::Validate(RoaProfileError::ProfileDecode(_))
));
}
#[test]
fn version_explicit_tag_rejects_trailing_bytes_inside_inner_der() {
let mut version_inner = der_integer_u64(0);
version_inner.extend(tlv(0x05, &[])); // NULL after INTEGER
let der = {
let mut fields = Vec::new();
fields.push(cs_explicit(0, version_inner));
fields.push(der_integer_u64(64496));
fields.push(der_sequence(vec![roa_ip_family(
[0, 1],
vec![roa_ip_address(der_bit_string(0, &[]), None)],
)]));
der_sequence(fields)
};
let err = RoaEContent::decode_der(&der).unwrap_err();
assert!(matches!(
err,
RoaDecodeError::Validate(RoaProfileError::ProfileDecode(_))
));
}
#[test]
fn version_zero_is_accepted_when_explicitly_encoded() {
let der = roa_attestation(
Some(0),
64496,
vec![roa_ip_family(
[0, 1],
vec![roa_ip_address(der_bit_string(7, &[0x80]), None)], // prefix_len=1
)],
);
let roa = RoaEContent::decode_der(&der).expect("ROA must decode");
assert_eq!(roa.version, 0);
assert_eq!(roa.as_id, 64496);
}
#[test]
fn ip_address_family_shape_and_address_family_length_are_validated() {
// family SEQUENCE has wrong length (1)
let bad_family = der_sequence(vec![der_octet_string(&[0, 1])]);
let der = roa_attestation(None, 64496, vec![bad_family]);
let err = RoaEContent::decode_der(&der).unwrap_err();
assert!(matches!(
err,
RoaDecodeError::Validate(RoaProfileError::InvalidIpAddressFamily)
));
// addressFamily OCTET STRING has invalid length (3)
let family = der_sequence(vec![
der_octet_string(&[0, 1, 2]),
der_sequence(vec![roa_ip_address(der_bit_string(0, &[]), None)]),
]);
let der = roa_attestation(None, 64496, vec![family]);
let err = RoaEContent::decode_der(&der).unwrap_err();
assert!(matches!(
err,
RoaDecodeError::Validate(RoaProfileError::InvalidAddressFamily)
));
}
#[test]
fn roa_ip_address_shape_and_prefix_encoding_are_validated() {
// ROAIPAddress must have 1..2 elements.
let family = roa_ip_family([0, 1], vec![der_sequence(vec![])]);
let der = roa_attestation(None, 64496, vec![family]);
let err = RoaEContent::decode_der(&der).unwrap_err();
assert!(matches!(
err,
RoaDecodeError::Validate(RoaProfileError::InvalidRoaIpAddress)
));
// ROAIPAddress.address must be BIT STRING, not OCTET STRING.
let family = roa_ip_family([0, 1], vec![der_sequence(vec![der_octet_string(&[0])])]);
let der = roa_attestation(None, 64496, vec![family]);
let err = RoaEContent::decode_der(&der).unwrap_err();
assert!(matches!(
err,
RoaDecodeError::Validate(RoaProfileError::InvalidPrefixBitString)
));
// unusedBits > 7 is rejected.
let family = roa_ip_family([0, 1], vec![roa_ip_address(der_bit_string(8, &[0]), None)]);
let der = roa_attestation(None, 64496, vec![family]);
let err = RoaEContent::decode_der(&der).unwrap_err();
assert!(matches!(
err,
RoaDecodeError::Validate(RoaProfileError::InvalidPrefixUnusedBits)
));
// empty BIT STRING with unusedBits != 0 is rejected.
let family = roa_ip_family([0, 1], vec![roa_ip_address(der_bit_string(1, &[]), None)]);
let der = roa_attestation(None, 64496, vec![family]);
let err = RoaEContent::decode_der(&der).unwrap_err();
assert!(matches!(
err,
RoaDecodeError::Validate(RoaProfileError::InvalidPrefixUnusedBits)
));
}
#[test]
fn max_length_integer_range_is_validated() {
// maxLength too large to fit u16 triggers the try_into() error path.
let family = roa_ip_family(
[0, 1],
vec![roa_ip_address(der_bit_string(0, &[0x0A]), Some(70000))],
);
let der = roa_attestation(None, 64496, vec![family]);
let err = RoaEContent::decode_der(&der).unwrap_err();
assert!(matches!(
err,
RoaDecodeError::Validate(RoaProfileError::InvalidMaxLength { .. })
));
}
#[test]
fn version_must_be_zero_when_present() {
let der = roa_attestation(
Some(1),
64496,
vec![roa_ip_family([0, 1], vec![roa_ip_address(der_bit_string(0, &[]), None)])],
vec![roa_ip_family(
[0, 1],
vec![roa_ip_address(der_bit_string(0, &[]), None)],
)],
);
let err = RoaEContent::decode_der(&der).unwrap_err();
assert!(matches!(err, RoaDecodeError::InvalidVersion(1)));
assert!(matches!(
err,
RoaDecodeError::Validate(RoaProfileError::InvalidVersion(1))
));
}
#[test]
@@ -102,17 +271,26 @@ fn as_id_out_of_range_is_rejected() {
let der = roa_attestation(
None,
(u32::MAX as u64) + 1,
vec![roa_ip_family([0, 1], vec![roa_ip_address(der_bit_string(0, &[]), None)])],
vec![roa_ip_family(
[0, 1],
vec![roa_ip_address(der_bit_string(0, &[]), None)],
)],
);
let err = RoaEContent::decode_der(&der).unwrap_err();
assert!(matches!(err, RoaDecodeError::AsIdOutOfRange(_)));
assert!(matches!(
err,
RoaDecodeError::Validate(RoaProfileError::AsIdOutOfRange(_))
));
}
#[test]
fn ip_addr_blocks_len_is_validated() {
let der = roa_attestation(None, 64496, vec![]);
let err = RoaEContent::decode_der(&der).unwrap_err();
assert!(matches!(err, RoaDecodeError::InvalidIpAddrBlocksLen(0)));
assert!(matches!(
err,
RoaDecodeError::Validate(RoaProfileError::InvalidIpAddrBlocksLen(0))
));
let families = vec![
roa_ip_family([0, 1], vec![roa_ip_address(der_bit_string(0, &[]), None)]),
@@ -121,7 +299,10 @@ fn ip_addr_blocks_len_is_validated() {
];
let der = roa_attestation(None, 64496, families);
let err = RoaEContent::decode_der(&der).unwrap_err();
assert!(matches!(err, RoaDecodeError::InvalidIpAddrBlocksLen(3)));
assert!(matches!(
err,
RoaDecodeError::Validate(RoaProfileError::InvalidIpAddrBlocksLen(3))
));
}
#[test]
@@ -132,7 +313,10 @@ fn duplicate_afi_is_rejected() {
];
let der = roa_attestation(None, 64496, families);
let err = RoaEContent::decode_der(&der).unwrap_err();
assert!(matches!(err, RoaDecodeError::DuplicateAfi(_)));
assert!(matches!(
err,
RoaDecodeError::Validate(RoaProfileError::DuplicateAfi(_))
));
}
#[test]
@@ -140,17 +324,26 @@ fn unsupported_afi_is_rejected() {
let der = roa_attestation(
None,
64496,
vec![roa_ip_family([0x12, 0x34], vec![roa_ip_address(der_bit_string(0, &[]), None)])],
vec![roa_ip_family(
[0x12, 0x34],
vec![roa_ip_address(der_bit_string(0, &[]), None)],
)],
);
let err = RoaEContent::decode_der(&der).unwrap_err();
assert!(matches!(err, RoaDecodeError::UnsupportedAfi(_)));
assert!(matches!(
err,
RoaDecodeError::Validate(RoaProfileError::UnsupportedAfi(_))
));
}
#[test]
fn empty_address_list_is_rejected() {
let der = roa_attestation(None, 64496, vec![roa_ip_family([0, 1], vec![])]);
let err = RoaEContent::decode_der(&der).unwrap_err();
assert!(matches!(err, RoaDecodeError::EmptyAddressList));
assert!(matches!(
err,
RoaDecodeError::Validate(RoaProfileError::EmptyAddressList)
));
}
#[test]
@@ -164,7 +357,10 @@ fn prefix_unused_bits_must_be_zeroed() {
vec![roa_ip_family([0, 1], vec![roa_ip_address(bs, None)])],
);
let err = RoaEContent::decode_der(&der).unwrap_err();
assert!(matches!(err, RoaDecodeError::InvalidPrefixUnusedBits));
assert!(matches!(
err,
RoaDecodeError::Validate(RoaProfileError::InvalidPrefixUnusedBits)
));
}
#[test]
@@ -177,7 +373,10 @@ fn prefix_len_out_of_range_is_rejected() {
vec![roa_ip_family([0, 1], vec![roa_ip_address(bs, None)])],
);
let err = RoaEContent::decode_der(&der).unwrap_err();
assert!(matches!(err, RoaDecodeError::PrefixLenOutOfRange { .. }));
assert!(matches!(
err,
RoaDecodeError::Validate(RoaProfileError::PrefixLenOutOfRange { .. })
));
}
#[test]
@@ -188,10 +387,16 @@ fn max_length_range_and_relation_are_validated() {
let der = roa_attestation(
None,
64496,
vec![roa_ip_family([0, 1], vec![roa_ip_address(bs.clone(), Some(7))])],
vec![roa_ip_family(
[0, 1],
vec![roa_ip_address(bs.clone(), Some(7))],
)],
);
let err = RoaEContent::decode_der(&der).unwrap_err();
assert!(matches!(err, RoaDecodeError::InvalidMaxLength { .. }));
assert!(matches!(
err,
RoaDecodeError::Validate(RoaProfileError::InvalidMaxLength { .. })
));
// maxLength > ub
let der = roa_attestation(
@@ -200,6 +405,8 @@ fn max_length_range_and_relation_are_validated() {
vec![roa_ip_family([0, 1], vec![roa_ip_address(bs, Some(33))])],
);
let err = RoaEContent::decode_der(&der).unwrap_err();
assert!(matches!(err, RoaDecodeError::InvalidMaxLength { .. }));
assert!(matches!(
err,
RoaDecodeError::Validate(RoaProfileError::InvalidMaxLength { .. })
));
}

View File

@@ -2,10 +2,8 @@ use rpki::data_model::roa::RoaObject;
#[test]
fn roa_embedded_ee_cert_resources_validate() {
let der = std::fs::read(
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa",
)
.expect("read ROA fixture");
let der = std::fs::read("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa")
.expect("read ROA fixture");
let roa = RoaObject::decode_der(&der).expect("decode roa");
roa.validate_embedded_ee_cert()

View File

@@ -2,12 +2,18 @@ use der_parser::num_bigint::BigUint;
use time::OffsetDateTime;
use rpki::data_model::rc::{
Afi, AsResourceSet, IpAddressChoice, IpAddressFamily, IpAddressOrRange, IpPrefix, IpResourceSet,
RcExtensions, ResourceCertKind, ResourceCertificate, RpkixTbsCertificate, SubjectInfoAccess,
Afi, AsResourceSet, IpAddressChoice, IpAddressFamily, IpAddressOrRange, IpPrefix,
IpResourceSet, RcExtensions, ResourceCertKind, ResourceCertificate, RpkixTbsCertificate,
SubjectInfoAccess,
};
use rpki::data_model::roa::{
RoaAfi, RoaEContent, RoaIpAddress, RoaIpAddressFamily, RoaValidateError,
};
use rpki::data_model::roa::{RoaAfi, RoaEContent, RoaIpAddress, RoaIpAddressFamily, RoaValidateError};
fn dummy_ee(ip_resources: Option<IpResourceSet>, as_resources: Option<AsResourceSet>) -> ResourceCertificate {
fn dummy_ee(
ip_resources: Option<IpResourceSet>,
as_resources: Option<AsResourceSet>,
) -> ResourceCertificate {
ResourceCertificate {
raw_der: vec![],
tbs: RpkixTbsCertificate {
@@ -133,7 +139,10 @@ fn validate_rejects_when_prefix_not_covered() {
None,
);
let err = roa.validate_against_ee_cert(&ee).unwrap_err();
assert!(matches!(err, RoaValidateError::PrefixNotInEeResources { .. }));
assert!(matches!(
err,
RoaValidateError::PrefixNotInEeResources { .. }
));
}
#[test]
@@ -173,4 +182,3 @@ fn contains_prefix_handles_non_octet_boundary_prefix_len() {
roa.validate_against_ee_cert(&ee)
.expect("160.18.0.0/16 should be covered by 160.0.0.0/9");
}

View File

@@ -2,13 +2,10 @@ use rpki::data_model::roa::RoaObject;
#[test]
fn verify_roa_cms_signature_with_embedded_ee_cert() {
let der = std::fs::read(
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa",
)
.expect("read ROA fixture");
let der = std::fs::read("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa")
.expect("read ROA fixture");
let roa = RoaObject::decode_der(&der).expect("decode roa");
roa.signed_object
.verify_signature()
.expect("ROA CMS signature should verify with embedded EE cert");
}

View File

@@ -12,19 +12,26 @@ fn decode_manifest_signed_object_smoke() {
assert_eq!(so.content_info_content_type, OID_SIGNED_DATA);
assert_eq!(so.signed_data.version, 3);
assert_eq!(so.signed_data.digest_algorithms, vec![OID_SHA256.to_string()]);
assert_eq!(
so.signed_data.digest_algorithms,
vec![OID_SHA256.to_string()]
);
assert_eq!(
so.signed_data.encap_content_info.econtent_type,
OID_CT_RPKI_MANIFEST
);
assert_eq!(so.signed_data.certificates.len(), 1);
assert!(!so.signed_data.certificates[0]
.sia_signed_object_uris
.is_empty());
assert!(so.signed_data.certificates[0]
.sia_signed_object_uris
.iter()
.any(|u| u.starts_with("rsync://")));
assert!(
!so.signed_data.certificates[0]
.sia_signed_object_uris
.is_empty()
);
assert!(
so.signed_data.certificates[0]
.sia_signed_object_uris
.iter()
.any(|u| u.starts_with("rsync://"))
);
assert_eq!(so.signed_data.signer_infos.len(), 1);
println!("{so:#?}")
}

View File

@@ -2,7 +2,9 @@ use rpki::data_model::oid::{
OID_CMS_ATTR_CONTENT_TYPE, OID_CMS_ATTR_MESSAGE_DIGEST, OID_CMS_ATTR_SIGNING_TIME,
OID_RSA_ENCRYPTION, OID_SHA256, OID_SHA256_WITH_RSA_ENCRYPTION, OID_SIGNED_DATA,
};
use rpki::data_model::signed_object::{RpkiSignedObject, SignedObjectDecodeError};
use rpki::data_model::signed_object::{
RpkiSignedObject, SignedObjectDecodeError, SignedObjectParseError, SignedObjectValidateError,
};
use sha2::{Digest, Sha256};
use x509_parser::extensions::ParsedExtension;
use x509_parser::prelude::FromDer;
@@ -14,7 +16,7 @@ const TEST_SIA_HTTPS_CERT_DER_B64: &str = "MIIDODCCAiCgAwIBAgIUBp2fsJYhUBJk711xT
const TEST_SIA_DNS_CERT_DER_B64: &str = "MIIDHjCCAgagAwIBAgIUJlS9d2BCJaamLyjYVTjvMNzWDxMwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UEAwwMVGVzdCBTSUEgZG5zMB4XDTI2MDEyNzA2NTMwOVoXDTM2MDEyNTA2NTMwOVowFzEVMBMGA1UEAwwMVGVzdCBTSUEgZG5zMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnKIddAQzQpDLbM+ZX3qR707L7CZvZ3MDGYN+tvuPXOfhATcOLtEaxu1dK4ZhV4Ou3ZqdxwYauyC+N4An0qCJW8mVr3zhbxathVGW7w4/S9pEV/+8dGW8ypOiqNixtmV++Ww54PguD6uxMk1S3IUOVTJY+QaetMy+SV9lCbOykZys17J56tMBmHRtuOxGPnaLtzZLddWqGhGFSDthSbKX4yToUIhTUl+wIRRYjBjnbGgzH5jV6eHUgrHRk+n567jNa9fe3cuRCGNBe6ny/8NPQnJEksWpA9lGfJDlEFsDIM9cXY78izr6i4JHeErwfusJiSchTT0ePhHXRAYMQoIqywIDAQABo2IwYDAJBgNVHRMEAjAAMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQUaY5MXriJ8s5VVa+s09HoSUloKBUwJwYIKwYBBQUHAQsEGzAZMBcGCCsGAQUFBzALggtleGFtcGxlLm5ldDANBgkqhkiG9w0BAQsFAAOCAQEAZSnkWxPTFWepHaK0XvAV145idY0ztEqY9BWUql2Ythzb4rjBAU1TfDRRklnnlE9o9/I6363ltaZBvj95e3CyTu4YGflxEpHsW+4aTth8ty1ee7YSqsdJ8gN08sroIpMTfr6tvWf65cVLSTkB4yP8cnNEM3zGr37zb32ChPXgUFwS9JFf3SMsXudZ4rHougE/PM4pQZvaOl3tFEzohV5MjA2VD38n3y6bVmx3i0Xqze7UZnl06aDKozzTXmFy/DoDRGG2pd2EjoC8gNAqIOL53uRz5nJlp8WEIBMe5Hmokrzv+zkAywVZZtYo1FvonOdg5etH94oMnZEtgV/OO9joRg==";
fn decode_b64(b64: &str) -> Vec<u8> {
use base64::{engine::general_purpose, Engine as _};
use base64::{Engine as _, engine::general_purpose};
general_purpose::STANDARD
.decode(b64)
.expect("decode base64 cert")
@@ -99,8 +101,8 @@ fn der_octet_string(bytes: &[u8]) -> Vec<u8> {
}
fn der_oid(oid: &str) -> Vec<u8> {
use std::str::FromStr;
use der_parser::asn1_rs::ToDer;
use std::str::FromStr;
let oid = der_parser::Oid::from_str(oid).unwrap();
oid.to_der_vec().unwrap()
@@ -156,7 +158,10 @@ fn signed_attrs_implicit(
) -> Vec<u8> {
let mut attrs = vec![
cms_attribute(OID_CMS_ATTR_CONTENT_TYPE, der_oid(content_type_oid)),
cms_attribute(OID_CMS_ATTR_MESSAGE_DIGEST, der_octet_string(message_digest)),
cms_attribute(
OID_CMS_ATTR_MESSAGE_DIGEST,
der_octet_string(message_digest),
),
cms_attribute(OID_CMS_ATTR_SIGNING_TIME, signing_time_der),
];
attrs.extend(extra_attrs);
@@ -278,7 +283,10 @@ fn trailing_bytes_after_object() {
let mut bad = der.clone();
bad.push(0);
let err = RpkiSignedObject::decode_der(&bad).unwrap_err();
assert!(matches!(err, SignedObjectDecodeError::TrailingBytes(1)));
assert!(matches!(
err,
SignedObjectDecodeError::Parse(SignedObjectParseError::TrailingBytes(1))
));
}
#[test]
@@ -297,7 +305,14 @@ fn content_info_content_must_be_tag0_explicit() {
b"e".to_vec(),
None,
false,
vec![signer_info(vec![1], OID_SHA256, None, OID_RSA_ENCRYPTION, Some(der_null()), false)],
vec![signer_info(
vec![1],
OID_SHA256,
None,
OID_RSA_ENCRYPTION,
Some(der_null()),
false,
)],
);
let ci = der_sequence(vec![der_oid(OID_SIGNED_DATA), cs_cons(1, &sd)]);
let err = RpkiSignedObject::decode_der(&ci).unwrap_err();
@@ -313,7 +328,14 @@ fn content_info_content_must_contain_only_one_inner_object() {
b"e".to_vec(),
None,
false,
vec![signer_info(vec![1], OID_SHA256, None, OID_RSA_ENCRYPTION, Some(der_null()), false)],
vec![signer_info(
vec![1],
OID_SHA256,
None,
OID_RSA_ENCRYPTION,
Some(der_null()),
false,
)],
);
let mut inner = sd.clone();
inner.extend(der_null());
@@ -414,7 +436,14 @@ fn signed_data_unexpected_field_is_rejected() {
cs_cons(0, &der_octet_string(b"e")),
]),
der_null(),
der_set(vec![signer_info(vec![1], OID_SHA256, None, OID_RSA_ENCRYPTION, Some(der_null()), false)]),
der_set(vec![signer_info(
vec![1],
OID_SHA256,
None,
OID_RSA_ENCRYPTION,
Some(der_null()),
false,
)]),
]);
let ci = content_info_signed_data(sd);
let err = RpkiSignedObject::decode_der(&ci).unwrap_err();
@@ -433,7 +462,14 @@ fn encap_content_info_length_and_tags_are_validated() {
der_integer_u64(3),
der_set(vec![algorithm_id(OID_SHA256, Some(der_null()))]),
encap,
der_set(vec![signer_info(vec![1], OID_SHA256, None, OID_RSA_ENCRYPTION, Some(der_null()), false)]),
der_set(vec![signer_info(
vec![1],
OID_SHA256,
None,
OID_RSA_ENCRYPTION,
Some(der_null()),
false,
)]),
]);
let ci = content_info_signed_data(sd);
let err = RpkiSignedObject::decode_der(&ci).unwrap_err();
@@ -448,7 +484,14 @@ fn encap_content_info_length_and_tags_are_validated() {
der_integer_u64(3),
der_set(vec![algorithm_id(OID_SHA256, Some(der_null()))]),
encap,
der_set(vec![signer_info(vec![1], OID_SHA256, None, OID_RSA_ENCRYPTION, Some(der_null()), false)]),
der_set(vec![signer_info(
vec![1],
OID_SHA256,
None,
OID_RSA_ENCRYPTION,
Some(der_null()),
false,
)]),
]);
let ci = content_info_signed_data(sd);
let err = RpkiSignedObject::decode_der(&ci).unwrap_err();
@@ -465,7 +508,14 @@ fn encap_content_info_length_and_tags_are_validated() {
der_integer_u64(3),
der_set(vec![algorithm_id(OID_SHA256, Some(der_null()))]),
encap,
der_set(vec![signer_info(vec![1], OID_SHA256, None, OID_RSA_ENCRYPTION, Some(der_null()), false)]),
der_set(vec![signer_info(
vec![1],
OID_SHA256,
None,
OID_RSA_ENCRYPTION,
Some(der_null()),
false,
)]),
]);
let ci = content_info_signed_data(sd);
let err = RpkiSignedObject::decode_der(&ci).unwrap_err();
@@ -501,7 +551,10 @@ fn empty_econtent_is_rejected() {
vec![si],
));
let err = RpkiSignedObject::decode_der(&so).unwrap_err();
assert!(matches!(err, SignedObjectDecodeError::EContentMissing));
assert!(matches!(
err,
SignedObjectDecodeError::Validate(SignedObjectValidateError::EContentMissing)
));
}
#[test]
@@ -555,7 +608,10 @@ fn signer_info_sequence_len_and_version_are_validated() {
vec![si],
));
let err = RpkiSignedObject::decode_der(&so).unwrap_err();
assert!(matches!(err, SignedObjectDecodeError::InvalidSignerInfoVersion(1)));
assert!(matches!(
err,
SignedObjectDecodeError::Validate(SignedObjectValidateError::InvalidSignerInfoVersion(1))
));
}
#[test]
@@ -585,7 +641,10 @@ fn signed_attrs_structure_and_presence_are_validated() {
vec![si],
));
let err = RpkiSignedObject::decode_der(&so).unwrap_err();
assert!(matches!(err, SignedObjectDecodeError::Parse(_)));
assert!(matches!(
err,
SignedObjectDecodeError::Validate(SignedObjectValidateError::SignedAttrsParse(_))
));
// Missing content-type.
let signed_attrs = signed_attrs_raw(vec![
@@ -610,11 +669,17 @@ fn signed_attrs_structure_and_presence_are_validated() {
vec![si],
));
let err = RpkiSignedObject::decode_der(&so).unwrap_err();
assert!(matches!(err, SignedObjectDecodeError::Parse(_)));
assert!(matches!(
err,
SignedObjectDecodeError::Validate(SignedObjectValidateError::SignedAttrsContentTypeMissing)
));
// Missing message-digest.
let signed_attrs = signed_attrs_raw(vec![
cms_attribute(OID_CMS_ATTR_CONTENT_TYPE, der_oid(rpki::data_model::oid::OID_CT_RPKI_MANIFEST)),
cms_attribute(
OID_CMS_ATTR_CONTENT_TYPE,
der_oid(rpki::data_model::oid::OID_CT_RPKI_MANIFEST),
),
cms_attribute(OID_CMS_ATTR_SIGNING_TIME, tlv(0x17, b"240101000000Z")),
]);
let si = signer_info(
@@ -635,11 +700,19 @@ fn signed_attrs_structure_and_presence_are_validated() {
vec![si],
));
let err = RpkiSignedObject::decode_der(&so).unwrap_err();
assert!(matches!(err, SignedObjectDecodeError::Parse(_)));
assert!(matches!(
err,
SignedObjectDecodeError::Validate(
SignedObjectValidateError::SignedAttrsMessageDigestMissing
)
));
// Missing signing-time.
let signed_attrs = signed_attrs_raw(vec![
cms_attribute(OID_CMS_ATTR_CONTENT_TYPE, der_oid(rpki::data_model::oid::OID_CT_RPKI_MANIFEST)),
cms_attribute(
OID_CMS_ATTR_CONTENT_TYPE,
der_oid(rpki::data_model::oid::OID_CT_RPKI_MANIFEST),
),
cms_attribute(OID_CMS_ATTR_MESSAGE_DIGEST, der_octet_string(&digest)),
]);
let si = signer_info(
@@ -660,7 +733,10 @@ fn signed_attrs_structure_and_presence_are_validated() {
vec![si],
));
let err = RpkiSignedObject::decode_der(&so).unwrap_err();
assert!(matches!(err, SignedObjectDecodeError::Parse(_)));
assert!(matches!(
err,
SignedObjectDecodeError::Validate(SignedObjectValidateError::SignedAttrsSigningTimeMissing)
));
}
#[test]
@@ -672,7 +748,14 @@ fn algorithm_identifier_sequence_shape_is_validated() {
b"e".to_vec(),
None,
false,
vec![signer_info(vec![1], OID_SHA256, None, OID_RSA_ENCRYPTION, Some(der_null()), false)],
vec![signer_info(
vec![1],
OID_SHA256,
None,
OID_RSA_ENCRYPTION,
Some(der_null()),
false,
)],
);
let ci = content_info_signed_data(sd);
let err = RpkiSignedObject::decode_der(&ci).unwrap_err();
@@ -713,8 +796,10 @@ fn ee_certificate_missing_signed_object_sia_is_rejected() {
let err = RpkiSignedObject::decode_der(&so).unwrap_err();
assert!(matches!(
err,
SignedObjectDecodeError::EeCertificateMissingSia
| SignedObjectDecodeError::EeCertificateMissingSignedObjectSia
SignedObjectDecodeError::Validate(SignedObjectValidateError::EeCertificateMissingSia)
| SignedObjectDecodeError::Validate(
SignedObjectValidateError::EeCertificateMissingSignedObjectSia
)
));
}
@@ -751,7 +836,9 @@ fn ee_certificate_sia_without_signed_object_access_method_is_rejected() {
let err = RpkiSignedObject::decode_der(&so).unwrap_err();
assert!(matches!(
err,
SignedObjectDecodeError::EeCertificateMissingSignedObjectSia
SignedObjectDecodeError::Validate(
SignedObjectValidateError::EeCertificateMissingSignedObjectSia
)
));
}
@@ -788,7 +875,9 @@ fn ee_certificate_signed_object_sia_must_be_uri() {
let err = RpkiSignedObject::decode_der(&so).unwrap_err();
assert!(matches!(
err,
SignedObjectDecodeError::EeCertificateSignedObjectSiaNotUri
SignedObjectDecodeError::Validate(
SignedObjectValidateError::EeCertificateSignedObjectSiaNotUri
)
));
}
@@ -825,7 +914,9 @@ fn ee_certificate_signed_object_sia_requires_rsync_uri() {
let err = RpkiSignedObject::decode_der(&so).unwrap_err();
assert!(matches!(
err,
SignedObjectDecodeError::EeCertificateSignedObjectSiaNoRsync
SignedObjectDecodeError::Validate(
SignedObjectValidateError::EeCertificateSignedObjectSiaNoRsync
)
));
}
@@ -867,7 +958,7 @@ fn signed_attrs_duplicate_content_type_is_rejected() {
let err = RpkiSignedObject::decode_der(&so).unwrap_err();
assert!(matches!(
err,
SignedObjectDecodeError::DuplicateSignedAttribute(ref oid)
SignedObjectDecodeError::Validate(SignedObjectValidateError::DuplicateSignedAttribute(ref oid))
if oid == OID_CMS_ATTR_CONTENT_TYPE
));
}
@@ -907,19 +998,26 @@ fn signed_attrs_duplicate_signing_time_is_rejected() {
let err = RpkiSignedObject::decode_der(&so).unwrap_err();
assert!(matches!(
err,
SignedObjectDecodeError::DuplicateSignedAttribute(ref oid)
SignedObjectDecodeError::Validate(SignedObjectValidateError::DuplicateSignedAttribute(ref oid))
if oid == OID_CMS_ATTR_SIGNING_TIME
));
}
#[test]
fn invalid_content_info_content_type() {
let sd = der_sequence(vec![der_integer_u64(3), der_set(vec![]), der_sequence(vec![der_oid("1.2.3")]) , der_set(vec![])]);
let sd = der_sequence(vec![
der_integer_u64(3),
der_set(vec![]),
der_sequence(vec![der_oid("1.2.3")]),
der_set(vec![]),
]);
let ci = der_sequence(vec![der_oid("1.2.3.4"), cs_cons(0, &sd)]);
let err = RpkiSignedObject::decode_der(&ci).unwrap_err();
assert!(matches!(
err,
SignedObjectDecodeError::InvalidContentInfoContentType(_)
SignedObjectDecodeError::Validate(
SignedObjectValidateError::InvalidContentInfoContentType(_)
)
));
}
@@ -932,12 +1030,19 @@ fn invalid_signed_data_version() {
b"e".to_vec(),
None,
false,
vec![signer_info(vec![1], OID_SHA256, None, OID_RSA_ENCRYPTION, Some(der_null()), false)],
vec![signer_info(
vec![1],
OID_SHA256,
None,
OID_RSA_ENCRYPTION,
Some(der_null()),
false,
)],
));
let err = RpkiSignedObject::decode_der(&so).unwrap_err();
assert!(matches!(
err,
SignedObjectDecodeError::InvalidSignedDataVersion(4)
SignedObjectDecodeError::Validate(SignedObjectValidateError::InvalidSignedDataVersion(4))
));
}
@@ -953,12 +1058,21 @@ fn invalid_digest_algorithms_count() {
b"e".to_vec(),
None,
false,
vec![signer_info(vec![1], OID_SHA256, None, OID_RSA_ENCRYPTION, Some(der_null()), false)],
vec![signer_info(
vec![1],
OID_SHA256,
None,
OID_RSA_ENCRYPTION,
Some(der_null()),
false,
)],
));
let err = RpkiSignedObject::decode_der(&so).unwrap_err();
assert!(matches!(
err,
SignedObjectDecodeError::InvalidDigestAlgorithmsCount(2)
SignedObjectDecodeError::Validate(SignedObjectValidateError::InvalidDigestAlgorithmsCount(
2
))
));
}
@@ -971,12 +1085,19 @@ fn invalid_digest_algorithm_oid() {
b"e".to_vec(),
None,
false,
vec![signer_info(vec![1], OID_SHA256, None, OID_RSA_ENCRYPTION, Some(der_null()), false)],
vec![signer_info(
vec![1],
OID_SHA256,
None,
OID_RSA_ENCRYPTION,
Some(der_null()),
false,
)],
));
let err = RpkiSignedObject::decode_der(&so).unwrap_err();
assert!(matches!(
err,
SignedObjectDecodeError::InvalidDigestAlgorithm(_)
SignedObjectDecodeError::Validate(SignedObjectValidateError::InvalidDigestAlgorithm(_))
));
}
@@ -991,7 +1112,10 @@ fn econtent_missing() {
]);
let so = der_sequence(vec![der_oid(OID_SIGNED_DATA), cs_cons(0, &sd)]);
let err = RpkiSignedObject::decode_der(&so).unwrap_err();
assert!(matches!(err, SignedObjectDecodeError::EContentMissing));
assert!(matches!(
err,
SignedObjectDecodeError::Validate(SignedObjectValidateError::EContentMissing)
));
}
#[test]
@@ -1003,10 +1127,20 @@ fn crls_present_is_rejected() {
b"e".to_vec(),
None,
true,
vec![signer_info(vec![1], OID_SHA256, None, OID_RSA_ENCRYPTION, Some(der_null()), false)],
vec![signer_info(
vec![1],
OID_SHA256,
None,
OID_RSA_ENCRYPTION,
Some(der_null()),
false,
)],
));
let err = RpkiSignedObject::decode_der(&so).unwrap_err();
assert!(matches!(err, SignedObjectDecodeError::CrlsPresent));
assert!(matches!(
err,
SignedObjectDecodeError::Validate(SignedObjectValidateError::CrlsPresent)
));
}
#[test]
@@ -1018,10 +1152,20 @@ fn certificates_missing_is_rejected() {
b"e".to_vec(),
None,
false,
vec![signer_info(vec![1], OID_SHA256, None, OID_RSA_ENCRYPTION, Some(der_null()), false)],
vec![signer_info(
vec![1],
OID_SHA256,
None,
OID_RSA_ENCRYPTION,
Some(der_null()),
false,
)],
));
let err = RpkiSignedObject::decode_der(&so).unwrap_err();
assert!(matches!(err, SignedObjectDecodeError::CertificatesMissing));
assert!(matches!(
err,
SignedObjectDecodeError::Validate(SignedObjectValidateError::CertificatesMissing)
));
}
#[test]
@@ -1057,7 +1201,7 @@ fn invalid_certificates_count_rejected() {
let err = RpkiSignedObject::decode_der(&so).unwrap_err();
assert!(matches!(
err,
SignedObjectDecodeError::InvalidCertificatesCount(2)
SignedObjectDecodeError::Validate(SignedObjectValidateError::InvalidCertificatesCount(2))
));
}
@@ -1091,7 +1235,10 @@ fn ee_certificate_parse_error_is_reported() {
vec![si],
));
let err = RpkiSignedObject::decode_der(&so).unwrap_err();
assert!(matches!(err, SignedObjectDecodeError::EeCertificateParse(_)));
assert!(matches!(
err,
SignedObjectDecodeError::Validate(SignedObjectValidateError::EeCertificateParse(_))
));
}
#[test]
@@ -1133,7 +1280,7 @@ fn invalid_signer_infos_count_rejected() {
let err = RpkiSignedObject::decode_der(&so).unwrap_err();
assert!(matches!(
err,
SignedObjectDecodeError::InvalidSignerInfosCount(2)
SignedObjectDecodeError::Validate(SignedObjectValidateError::InvalidSignerInfosCount(2))
));
}
@@ -1162,7 +1309,10 @@ fn signer_info_errors_are_detected() {
vec![si_no_attrs],
));
let err = RpkiSignedObject::decode_der(&so).unwrap_err();
assert!(matches!(err, SignedObjectDecodeError::SignedAttrsMissing));
assert!(matches!(
err,
SignedObjectDecodeError::Validate(SignedObjectValidateError::SignedAttrsMissing)
));
// Invalid signer identifier.
let signed_attrs = signed_attrs_implicit(
@@ -1189,7 +1339,10 @@ fn signer_info_errors_are_detected() {
vec![si],
));
let err = RpkiSignedObject::decode_der(&so).unwrap_err();
assert!(matches!(err, SignedObjectDecodeError::InvalidSignerIdentifier));
assert!(matches!(
err,
SignedObjectDecodeError::Validate(SignedObjectValidateError::InvalidSignerIdentifier)
));
// Invalid digest algorithm.
let signed_attrs = signed_attrs_implicit(
@@ -1218,7 +1371,9 @@ fn signer_info_errors_are_detected() {
let err = RpkiSignedObject::decode_der(&so).unwrap_err();
assert!(matches!(
err,
SignedObjectDecodeError::InvalidSignerInfoDigestAlgorithm(_)
SignedObjectDecodeError::Validate(
SignedObjectValidateError::InvalidSignerInfoDigestAlgorithm(_)
)
));
// Invalid signature algorithm OID.
@@ -1248,7 +1403,7 @@ fn signer_info_errors_are_detected() {
let err = RpkiSignedObject::decode_der(&so).unwrap_err();
assert!(matches!(
err,
SignedObjectDecodeError::InvalidSignatureAlgorithm(_)
SignedObjectDecodeError::Validate(SignedObjectValidateError::InvalidSignatureAlgorithm(_))
));
// Invalid signing-time value.
@@ -1278,7 +1433,7 @@ fn signer_info_errors_are_detected() {
let err = RpkiSignedObject::decode_der(&so).unwrap_err();
assert!(matches!(
err,
SignedObjectDecodeError::InvalidSigningTimeValue
SignedObjectDecodeError::Validate(SignedObjectValidateError::InvalidSigningTimeValue)
));
// signedAttrs has duplicate attribute.
@@ -1309,7 +1464,7 @@ fn signer_info_errors_are_detected() {
let err = RpkiSignedObject::decode_der(&so).unwrap_err();
assert!(matches!(
err,
SignedObjectDecodeError::DuplicateSignedAttribute(_)
SignedObjectDecodeError::Validate(SignedObjectValidateError::DuplicateSignedAttribute(_))
));
// signedAttrs attrValues count != 1.
@@ -1346,16 +1501,14 @@ fn signer_info_errors_are_detected() {
let err = RpkiSignedObject::decode_der(&so).unwrap_err();
assert!(matches!(
err,
SignedObjectDecodeError::InvalidSignedAttributeValuesCount { .. }
SignedObjectDecodeError::Validate(
SignedObjectValidateError::InvalidSignedAttributeValuesCount { .. }
)
));
// content-type mismatch.
let signed_attrs = signed_attrs_implicit(
"1.2.3.4",
&digest,
tlv(0x17, b"240101000000Z"),
vec![],
);
let signed_attrs =
signed_attrs_implicit("1.2.3.4", &digest, tlv(0x17, b"240101000000Z"), vec![]);
let si = signer_info(
cert_ski.clone(),
OID_SHA256,
@@ -1376,7 +1529,9 @@ fn signer_info_errors_are_detected() {
let err = RpkiSignedObject::decode_der(&so).unwrap_err();
assert!(matches!(
err,
SignedObjectDecodeError::ContentTypeAttrMismatch { .. }
SignedObjectDecodeError::Validate(
SignedObjectValidateError::ContentTypeAttrMismatch { .. }
)
));
// message-digest mismatch.
@@ -1404,7 +1559,10 @@ fn signer_info_errors_are_detected() {
vec![si],
));
let err = RpkiSignedObject::decode_der(&so).unwrap_err();
assert!(matches!(err, SignedObjectDecodeError::MessageDigestMismatch));
assert!(matches!(
err,
SignedObjectDecodeError::Validate(SignedObjectValidateError::MessageDigestMismatch)
));
// sid_ski mismatch.
let signed_attrs = signed_attrs_implicit(
@@ -1431,7 +1589,10 @@ fn signer_info_errors_are_detected() {
vec![si],
));
let err = RpkiSignedObject::decode_der(&so).unwrap_err();
assert!(matches!(err, SignedObjectDecodeError::SidSkiMismatch));
assert!(matches!(
err,
SignedObjectDecodeError::Validate(SignedObjectValidateError::SidSkiMismatch)
));
// Unsupported signedAttrs attribute.
let bad_attr = cms_attribute("1.2.3.4", der_null());
@@ -1461,7 +1622,7 @@ fn signer_info_errors_are_detected() {
let err = RpkiSignedObject::decode_der(&so).unwrap_err();
assert!(matches!(
err,
SignedObjectDecodeError::UnsupportedSignedAttribute(_)
SignedObjectDecodeError::Validate(SignedObjectValidateError::UnsupportedSignedAttribute(_))
));
// signatureAlgorithm parameters invalid.
@@ -1491,7 +1652,9 @@ fn signer_info_errors_are_detected() {
let err = RpkiSignedObject::decode_der(&so).unwrap_err();
assert!(matches!(
err,
SignedObjectDecodeError::InvalidSignatureAlgorithmParameters
SignedObjectDecodeError::Validate(
SignedObjectValidateError::InvalidSignatureAlgorithmParameters
)
));
// unsignedAttrs present.
@@ -1519,5 +1682,8 @@ fn signer_info_errors_are_detected() {
vec![si],
));
let err = RpkiSignedObject::decode_der(&so).unwrap_err();
assert!(matches!(err, SignedObjectDecodeError::UnsignedAttrsPresent));
assert!(matches!(
err,
SignedObjectDecodeError::Validate(SignedObjectValidateError::UnsignedAttrsPresent)
));
}

View File

@@ -53,8 +53,8 @@ fn der_null() -> Vec<u8> {
}
fn der_oid(oid: &str) -> Vec<u8> {
use std::str::FromStr;
use der_parser::asn1_rs::ToDer;
use std::str::FromStr;
let oid = der_parser::Oid::from_str(oid).unwrap();
oid.to_der_vec().unwrap()
}
@@ -140,10 +140,11 @@ fn verify_rejects_spki_der_with_trailing_bytes() {
let mut spki_der = so.signed_data.certificates[0].spki_der.clone();
spki_der.push(0);
let err = so
.verify_signature_with_ee_spki_der(&spki_der)
.unwrap_err();
assert!(matches!(err, SignedObjectVerifyError::EeSpkiTrailingBytes(1)));
let err = so.verify_signature_with_ee_spki_der(&spki_der).unwrap_err();
assert!(matches!(
err,
SignedObjectVerifyError::EeSpkiTrailingBytes(1)
));
}
#[test]
@@ -157,8 +158,6 @@ fn verify_with_all_zero_modulus_exercises_strip_leading_zeros_fallback() {
// modulus INTEGER 0, exponent 65537 (valid exponent encoding); signature verification must fail
// but the SPKI parsing path should succeed.
let spki_der = rsa_spki_der_with_modulus_bytes(&[0x00], 65537);
let err = so
.verify_signature_with_ee_spki_der(&spki_der)
.unwrap_err();
let err = so.verify_signature_with_ee_spki_der(&spki_der).unwrap_err();
assert!(matches!(err, SignedObjectVerifyError::InvalidSignature));
}

View File

@@ -1,10 +1,11 @@
use der_parser::num_bigint::BigUint;
use rpki::data_model::oid::OID_CP_IPADDR_ASNUMBER;
use rpki::data_model::rc::{
Afi, AsIdOrRange, AsIdentifierChoice, AsResourceSet, IpAddressChoice, IpAddressFamily, IpAddressOrRange,
IpPrefix, RcExtensions, ResourceCertKind, ResourceCertificate, RpkixTbsCertificate,
Afi, AsIdOrRange, AsIdentifierChoice, AsResourceSet, IpAddressChoice, IpAddressFamily,
IpAddressOrRange, IpPrefix, RcExtensions, ResourceCertKind, ResourceCertificate,
RpkixTbsCertificate,
};
use rpki::data_model::ta::{TaCertificate, TaCertificateError};
use rpki::data_model::ta::{TaCertificate, TaCertificateDecodeError, TaCertificateProfileError};
use time::OffsetDateTime;
fn dummy_rc_ca(ext: RcExtensions) -> ResourceCertificate {
@@ -56,8 +57,9 @@ fn ta_certificate_rejects_non_self_signed_ca() {
.expect("read CA cert fixture");
assert!(matches!(
TaCertificate::from_der(&der),
Err(TaCertificateError::NotSelfSignedIssuerSubject)
| Err(TaCertificateError::InvalidSelfSignature(_))
Err(TaCertificateDecodeError::Validate(
TaCertificateProfileError::NotSelfSignedIssuerSubject
))
));
}
@@ -73,7 +75,7 @@ fn ta_constraints_require_policies_and_ski() {
});
assert!(matches!(
TaCertificate::validate_rc_constraints(&rc),
Err(TaCertificateError::MissingOrInvalidCertificatePolicies)
Err(TaCertificateProfileError::MissingOrInvalidCertificatePolicies)
));
let rc = dummy_rc_ca(RcExtensions {
@@ -82,7 +84,7 @@ fn ta_constraints_require_policies_and_ski() {
});
assert!(matches!(
TaCertificate::validate_rc_constraints(&rc),
Err(TaCertificateError::MissingSubjectKeyIdentifier)
Err(TaCertificateProfileError::MissingSubjectKeyIdentifier)
));
}
@@ -99,7 +101,7 @@ fn ta_constraints_require_non_empty_resources_and_no_inherit() {
});
assert!(matches!(
TaCertificate::validate_rc_constraints(&rc),
Err(TaCertificateError::ResourcesMissing)
Err(TaCertificateProfileError::ResourcesMissing)
));
// IP resources present but empty => resources empty.
@@ -109,7 +111,7 @@ fn ta_constraints_require_non_empty_resources_and_no_inherit() {
});
assert!(matches!(
TaCertificate::validate_rc_constraints(&rc),
Err(TaCertificateError::ResourcesEmpty)
Err(TaCertificateProfileError::ResourcesEmpty)
));
// IP resources inherit is rejected.
@@ -124,7 +126,7 @@ fn ta_constraints_require_non_empty_resources_and_no_inherit() {
});
assert!(matches!(
TaCertificate::validate_rc_constraints(&rc),
Err(TaCertificateError::IpResourcesInherit)
Err(TaCertificateProfileError::IpResourcesInherit)
));
// AS resources inherit is rejected.
@@ -138,7 +140,7 @@ fn ta_constraints_require_non_empty_resources_and_no_inherit() {
});
assert!(matches!(
TaCertificate::validate_rc_constraints(&rc),
Err(TaCertificateError::AsResourcesInherit)
Err(TaCertificateProfileError::AsResourcesInherit)
));
// Valid non-empty explicit IP resources => OK.
@@ -146,11 +148,13 @@ fn ta_constraints_require_non_empty_resources_and_no_inherit() {
ip_resources: Some(rpki::data_model::rc::IpResourceSet {
families: vec![IpAddressFamily {
afi: Afi::Ipv6,
choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Prefix(IpPrefix {
afi: Afi::Ipv6,
prefix_len: 32,
addr: vec![0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
})]),
choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Prefix(
IpPrefix {
afi: Afi::Ipv6,
prefix_len: 32,
addr: vec![0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
},
)]),
}],
}),
as_resources: None,
@@ -162,11 +166,12 @@ fn ta_constraints_require_non_empty_resources_and_no_inherit() {
let rc = dummy_rc_ca(RcExtensions {
ip_resources: None,
as_resources: Some(AsResourceSet {
asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![AsIdOrRange::Id(64512)])),
asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![AsIdOrRange::Id(
64512,
)])),
rdi: None,
}),
..rc.tbs.extensions.clone()
});
TaCertificate::validate_rc_constraints(&rc).expect("valid explicit AS resources");
}

View File

@@ -15,17 +15,31 @@ fn decode_tal_fixtures_smoke() {
let raw = std::fs::read(path).expect("read TAL fixture");
let tal = Tal::decode_bytes(&raw).expect("decode TAL fixture");
assert!(!tal.ta_uris.is_empty(), "TA URI list must be non-empty: {path}");
assert!(!tal.subject_public_key_info_der.is_empty(), "SPKI DER must be non-empty: {path}");
assert!(
!tal.ta_uris.is_empty(),
"TA URI list must be non-empty: {path}"
);
assert!(
!tal.subject_public_key_info_der.is_empty(),
"SPKI DER must be non-empty: {path}"
);
for u in &tal.ta_uris {
assert!(matches!(u.scheme(), "rsync" | "https"), "scheme must be allowed: {u}");
assert!(!u.path().ends_with('/'), "TA URI must not be a directory: {u}");
assert!(
matches!(u.scheme(), "rsync" | "https"),
"scheme must be allowed: {u}"
);
assert!(
!u.path().ends_with('/'),
"TA URI must not be a directory: {u}"
);
}
// SPKI DER must be parseable as a DER object (typically a SEQUENCE).
let (rem, _obj) = parse_der(&tal.subject_public_key_info_der).expect("parse spki DER");
assert!(rem.is_empty(), "SPKI DER must not have trailing bytes: {path}");
assert!(
rem.is_empty(),
"SPKI DER must not have trailing bytes: {path}"
);
}
}

View File

@@ -1,4 +1,4 @@
use rpki::data_model::tal::{Tal, TalDecodeError};
use rpki::data_model::tal::{Tal, TalDecodeError, TalParseError, TalProfileError};
fn mk_tal(uris: &[&str], b64_lines: &[&str]) -> String {
let mut out = String::new();
@@ -20,14 +20,19 @@ fn tal_rejects_missing_separator() {
let s = "# c\nhttps://example.invalid/ta.cer\nAAAA\n";
assert!(matches!(
Tal::decode_bytes(s.as_bytes()),
Err(TalDecodeError::MissingSeparatorEmptyLine)
Err(TalDecodeError::Validate(
TalProfileError::MissingSeparatorEmptyLine
))
));
}
#[test]
fn tal_rejects_missing_uris() {
let s = "# c\n\nAAAA\n";
assert!(matches!(Tal::decode_bytes(s.as_bytes()), Err(TalDecodeError::MissingTaUris)));
assert!(matches!(
Tal::decode_bytes(s.as_bytes()),
Err(TalDecodeError::Validate(TalProfileError::MissingTaUris))
));
}
#[test]
@@ -35,7 +40,9 @@ fn tal_rejects_unsupported_scheme() {
let s = mk_tal(&["ftp://example.invalid/ta.cer"], &["AAAA"]);
assert!(matches!(
Tal::decode_bytes(s.as_bytes()),
Err(TalDecodeError::UnsupportedUriScheme(_))
Err(TalDecodeError::Validate(
TalProfileError::UnsupportedUriScheme(_)
))
));
}
@@ -44,14 +51,19 @@ fn tal_rejects_directory_uri() {
let s = mk_tal(&["https://example.invalid/dir/"], &["AAAA"]);
assert!(matches!(
Tal::decode_bytes(s.as_bytes()),
Err(TalDecodeError::UriIsDirectory(_))
Err(TalDecodeError::Validate(TalProfileError::UriIsDirectory(_)))
));
}
#[test]
fn tal_rejects_comment_after_header() {
let s = "# c\nhttps://example.invalid/ta.cer\n# late\n\nAAAA\n";
assert!(matches!(Tal::decode_bytes(s.as_bytes()), Err(TalDecodeError::CommentAfterHeader)));
assert!(matches!(
Tal::decode_bytes(s.as_bytes()),
Err(TalDecodeError::Validate(
TalProfileError::CommentAfterHeader
))
));
}
#[test]
@@ -59,13 +71,15 @@ fn tal_rejects_invalid_base64() {
let s = mk_tal(&["https://example.invalid/ta.cer"], &["not-base64!!!"]);
assert!(matches!(
Tal::decode_bytes(s.as_bytes()),
Err(TalDecodeError::SpkiBase64Decode)
Err(TalDecodeError::Validate(TalProfileError::SpkiBase64Decode))
));
}
#[test]
fn tal_rejects_invalid_utf8() {
let bytes = [0xFFu8, 0xFEu8];
assert!(matches!(Tal::decode_bytes(&bytes), Err(TalDecodeError::InvalidUtf8)));
assert!(matches!(
Tal::decode_bytes(&bytes),
Err(TalDecodeError::Parse(TalParseError::InvalidUtf8))
));
}

View File

@@ -1,18 +1,30 @@
use rpki::data_model::ta::{TrustAnchor, TrustAnchorError};
use rpki::data_model::ta::{TrustAnchor, TrustAnchorBindError, TrustAnchorError};
use rpki::data_model::tal::Tal;
use url::Url;
#[test]
fn bind_trust_anchor_with_downloaded_fixtures_succeeds() {
let cases = [
("tests/fixtures/tal/afrinic.tal", "tests/fixtures/ta/afrinic-ta.cer"),
(
"tests/fixtures/tal/afrinic.tal",
"tests/fixtures/ta/afrinic-ta.cer",
),
(
"tests/fixtures/tal/apnic-rfc7730-https.tal",
"tests/fixtures/ta/apnic-ta.cer",
),
("tests/fixtures/tal/arin.tal", "tests/fixtures/ta/arin-ta.cer"),
("tests/fixtures/tal/lacnic.tal", "tests/fixtures/ta/lacnic-ta.cer"),
("tests/fixtures/tal/ripe-ncc.tal", "tests/fixtures/ta/ripe-ncc-ta.cer"),
(
"tests/fixtures/tal/arin.tal",
"tests/fixtures/ta/arin-ta.cer",
),
(
"tests/fixtures/tal/lacnic.tal",
"tests/fixtures/ta/lacnic-ta.cer",
),
(
"tests/fixtures/tal/ripe-ncc.tal",
"tests/fixtures/ta/ripe-ncc-ta.cer",
),
];
for (tal_path, ta_path) in cases {
@@ -20,7 +32,7 @@ fn bind_trust_anchor_with_downloaded_fixtures_succeeds() {
let tal = Tal::decode_bytes(&tal_raw).expect("decode TAL fixture");
let ta_der = std::fs::read(ta_path).expect("read TA fixture");
TrustAnchor::bind(tal.clone(), &ta_der, None).expect("bind without resolved uri");
TrustAnchor::bind_der(tal.clone(), &ta_der, None).expect("bind without resolved uri");
// Also exercise the resolved-uri-in-TAL check using one URI from the TAL list.
let resolved = tal
@@ -30,7 +42,7 @@ fn bind_trust_anchor_with_downloaded_fixtures_succeeds() {
.or_else(|| tal.ta_uris.first())
.expect("tal has ta uris");
let resolved = resolved.clone();
TrustAnchor::bind(tal, &ta_der, Some(&resolved)).expect("bind with resolved uri");
TrustAnchor::bind_der(tal, &ta_der, Some(&resolved)).expect("bind with resolved uri");
}
}
@@ -43,8 +55,10 @@ fn bind_rejects_spki_mismatch() {
// Flip a byte in TAL SPKI to force mismatch.
tal.subject_public_key_info_der[0] ^= 0x01;
assert!(matches!(
TrustAnchor::bind(tal, &ta_der, None),
Err(TrustAnchorError::TalSpkiMismatch)
TrustAnchor::bind_der(tal, &ta_der, None),
Err(TrustAnchorError::Bind(
TrustAnchorBindError::TalSpkiMismatch
))
));
}
@@ -56,7 +70,9 @@ fn bind_rejects_resolved_uri_not_listed_in_tal() {
let bad = Url::parse("https://example.invalid/not-in-tal.cer").unwrap();
assert!(matches!(
TrustAnchor::bind(tal, &ta_der, Some(&bad)),
Err(TrustAnchorError::ResolvedUriNotInTal(_))
TrustAnchor::bind_der(tal, &ta_der, Some(&bad)),
Err(TrustAnchorError::Bind(
TrustAnchorBindError::ResolvedUriNotInTal(_)
))
));
}