rpki/src/validation/ca_instance.rs

133 lines
4.9 KiB
Rust

use crate::data_model::oid::{OID_AD_CA_REPOSITORY, OID_AD_RPKI_MANIFEST, OID_AD_RPKI_NOTIFY};
use crate::data_model::rc::{ResourceCertKind, ResourceCertificate, SubjectInfoAccess};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CaInstanceUris {
/// rsync sync base URI used for live/replay publication point fetches (must end with `/`).
///
/// This is the parent directory of `manifest_rsync_uri`, not necessarily the full
/// `id-ad-caRepository` URI. Using the manifest parent keeps sync scope narrow while
/// `publication_point_rsync_uri` preserves the full RFC 9286 publication-point value.
pub rsync_base_uri: String,
/// rsync URI for the manifest object (`.mft`).
pub manifest_rsync_uri: String,
/// Publication point rsync URI (RFC 9286 terminology).
pub publication_point_rsync_uri: String,
/// Optional RRDP notification URI (https).
pub rrdp_notification_uri: Option<String>,
}
#[derive(Debug, thiserror::Error)]
pub enum CaInstanceUrisError {
#[error("certificate must be a CA certificate (RFC 6487 §4.8.1)")]
NotCa,
#[error(
"CA certificate must contain Subject Information Access extension (RFC 6487 §4.8.8; RFC 5280 §4.2.2.2)"
)]
MissingSia,
#[error("CA certificate SIA must contain id-ad-caRepository rsync URI (RFC 6487 §4.8.8.1)")]
MissingCaRepository,
#[error("CA certificate SIA must contain id-ad-rpkiManifest rsync URI (RFC 6487 §4.8.8.2)")]
MissingRpkiManifest,
#[error(
"SIA id-ad-caRepository accessLocation must be rsync:// URI, got {0} (RFC 6487 §4.8.8.1)"
)]
CaRepositoryNotRsync(String),
#[error(
"SIA id-ad-rpkiManifest accessLocation must be rsync:// URI, got {0} (RFC 6487 §4.8.8.2)"
)]
RpkiManifestNotRsync(String),
#[error(
"SIA id-ad-rpkiNotify accessLocation must be https:// URI, got {0} (RFC 8182 §3.4.1; RFC 6487 §4.8.8.3)"
)]
RpkiNotifyNotHttps(String),
#[error(
"manifest rsync URI must be under CA publication point: manifest={manifest_rsync_uri} publication_point={publication_point_rsync_uri} (RFC 9286 §6.1)"
)]
ManifestNotUnderPublicationPoint {
manifest_rsync_uri: String,
publication_point_rsync_uri: String,
},
}
pub fn ca_instance_uris_from_ca_certificate(
cert: &ResourceCertificate,
) -> Result<CaInstanceUris, CaInstanceUrisError> {
if cert.kind != ResourceCertKind::Ca {
return Err(CaInstanceUrisError::NotCa);
}
let sia = cert
.tbs
.extensions
.subject_info_access
.as_ref()
.ok_or(CaInstanceUrisError::MissingSia)?;
let access_descriptions = match sia {
SubjectInfoAccess::Ca(ca) => &ca.access_descriptions,
SubjectInfoAccess::Ee(_ee) => return Err(CaInstanceUrisError::MissingSia),
};
let mut ca_repo: Option<String> = None;
let mut manifest: Option<String> = None;
let mut notify: Option<String> = None;
for ad in access_descriptions {
if ad.access_method_oid == OID_AD_CA_REPOSITORY {
let u = ad.access_location.as_str();
if !u.starts_with("rsync://") {
return Err(CaInstanceUrisError::CaRepositoryNotRsync(u.to_string()));
}
ca_repo.get_or_insert(u.to_string());
} else if ad.access_method_oid == OID_AD_RPKI_MANIFEST {
let u = ad.access_location.as_str();
if !u.starts_with("rsync://") {
return Err(CaInstanceUrisError::RpkiManifestNotRsync(u.to_string()));
}
manifest.get_or_insert(u.to_string());
} else if ad.access_method_oid == OID_AD_RPKI_NOTIFY {
let u = ad.access_location.as_str();
if !u.starts_with("https://") {
return Err(CaInstanceUrisError::RpkiNotifyNotHttps(u.to_string()));
}
notify.get_or_insert(u.to_string());
}
}
let mut publication_point_rsync_uri =
ca_repo.ok_or(CaInstanceUrisError::MissingCaRepository)?;
if !publication_point_rsync_uri.ends_with('/') {
publication_point_rsync_uri.push('/');
}
let manifest_rsync_uri = manifest.ok_or(CaInstanceUrisError::MissingRpkiManifest)?;
if !manifest_rsync_uri.starts_with(&publication_point_rsync_uri) {
return Err(CaInstanceUrisError::ManifestNotUnderPublicationPoint {
manifest_rsync_uri,
publication_point_rsync_uri,
});
}
let manifest_parent = manifest_rsync_uri
.rsplit_once('/')
.map(|(parent, _)| format!("{parent}/"))
.ok_or_else(|| CaInstanceUrisError::ManifestNotUnderPublicationPoint {
manifest_rsync_uri: manifest_rsync_uri.clone(),
publication_point_rsync_uri: publication_point_rsync_uri.clone(),
})?;
Ok(CaInstanceUris {
rsync_base_uri: manifest_parent,
manifest_rsync_uri,
publication_point_rsync_uri,
rrdp_notification_uri: notify,
})
}