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 { /// CA repository base rsync URI (must end with `/`). pub rsync_base_uri: String, /// rsync URI for the manifest object (`.mft`). pub manifest_rsync_uri: String, /// Publication point rsync URI (RFC 9286 terminology). In v1 this equals `rsync_base_uri`. pub publication_point_rsync_uri: String, /// Optional RRDP notification URI (https). pub rrdp_notification_uri: Option, } #[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 { 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 = None; let mut manifest: Option = None; let mut notify: Option = None; for ad in access_descriptions { if ad.access_method_oid == OID_AD_CA_REPOSITORY { let u = ad.access_location.to_string(); if ad.access_location.scheme() != "rsync" { return Err(CaInstanceUrisError::CaRepositoryNotRsync(u)); } ca_repo.get_or_insert(u); } else if ad.access_method_oid == OID_AD_RPKI_MANIFEST { let u = ad.access_location.to_string(); if ad.access_location.scheme() != "rsync" { return Err(CaInstanceUrisError::RpkiManifestNotRsync(u)); } manifest.get_or_insert(u); } else if ad.access_method_oid == OID_AD_RPKI_NOTIFY { let u = ad.access_location.to_string(); if ad.access_location.scheme() != "https" { return Err(CaInstanceUrisError::RpkiNotifyNotHttps(u)); } notify.get_or_insert(u); } } 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 rsync_base_uri = publication_point_rsync_uri.clone(); 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, }); } Ok(CaInstanceUris { rsync_base_uri, manifest_rsync_uri, publication_point_rsync_uri, rrdp_notification_uri: notify, }) }