diff --git a/src/data_model/ta.rs b/src/data_model/ta.rs index 197b47d..826979d 100644 --- a/src/data_model/ta.rs +++ b/src/data_model/ta.rs @@ -244,6 +244,9 @@ pub enum TrustAnchorError { #[error("TA certificate error: {0} (RFC 8630 §2.3)")] TaCertificate(#[from] TaCertificateDecodeError), + #[error("TA certificate self-signature error: {0} (RFC 8630 §2.3)")] + TaSelfSignature(#[from] TaCertificateVerifyError), + #[error("{0}")] Bind(#[from] TrustAnchorBindError), } @@ -269,6 +272,7 @@ impl TrustAnchor { resolved_uri: Option<&Url>, ) -> Result { let ta_certificate = TaCertificate::decode_der(ta_der)?; + ta_certificate.verify_self_signature()?; Ok(Self::bind(tal, ta_certificate, resolved_uri)?) } @@ -278,6 +282,7 @@ impl TrustAnchor { resolved_uri: Option<&Url>, ) -> Result { let ta_certificate = TaCertificate::decode_der_with_strict_name(ta_der)?; + ta_certificate.verify_self_signature()?; Ok(Self::bind(tal, ta_certificate, resolved_uri)?) } diff --git a/tests/test_from_tal_offline.rs b/tests/test_from_tal_offline.rs index 1cdbba4..1e58292 100644 --- a/tests/test_from_tal_offline.rs +++ b/tests/test_from_tal_offline.rs @@ -88,6 +88,18 @@ fn offline_discovery_from_apnic_tal_and_ta_der_fixture_works() { } } +#[test] +fn offline_discovery_rejects_tampered_ta_self_signature() { + let tal_bytes = apnic_tal_bytes(); + let mut ta_der = apnic_ta_der(); + let last = ta_der.last_mut().expect("TA fixture must not be empty"); + *last ^= 0x01; + + let err = discover_root_ca_instance_from_tal_and_ta_der(&tal_bytes, &ta_der, None) + .expect_err("tampered TA signature must not form a root CA instance"); + assert!(err.to_string().contains("self-signature"), "{err}"); +} + #[test] fn discover_root_from_tal_url_works_with_mock_fetcher() { let tal_bytes = apnic_tal_bytes(); diff --git a/tests/test_trust_anchor_bind.rs b/tests/test_trust_anchor_bind.rs index 7adc99b..3400725 100644 --- a/tests/test_trust_anchor_bind.rs +++ b/tests/test_trust_anchor_bind.rs @@ -1,4 +1,6 @@ -use rpki::data_model::ta::{TrustAnchor, TrustAnchorBindError, TrustAnchorError}; +use rpki::data_model::ta::{ + TaCertificateVerifyError, TrustAnchor, TrustAnchorBindError, TrustAnchorError, +}; use rpki::data_model::tal::Tal; use url::Url; @@ -76,3 +78,21 @@ fn bind_rejects_resolved_uri_not_listed_in_tal() { )) )); } + +#[test] +fn bind_rejects_tampered_ta_self_signature() { + let tal_raw = + std::fs::read("tests/fixtures/tal/apnic-rfc7730-https.tal").expect("read TAL fixture"); + let tal = Tal::decode_bytes(&tal_raw).expect("decode TAL fixture"); + let mut ta_der = std::fs::read("tests/fixtures/ta/apnic-ta.cer").expect("read TA fixture"); + + let last = ta_der.last_mut().expect("TA fixture must not be empty"); + *last ^= 0x01; + + assert!(matches!( + TrustAnchor::bind_der(tal, &ta_der, None), + Err(TrustAnchorError::TaSelfSignature( + TaCertificateVerifyError::InvalidSelfSignature(_) + )) + )); +}