fetch cache pp imporved
This commit is contained in:
@@ -16,13 +16,13 @@ impl Default for SyncPreference {
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum CaFailedFetchPolicy {
|
||||
UseVerifiedCache,
|
||||
UseFetchCachePp,
|
||||
StopAllOutput,
|
||||
}
|
||||
|
||||
impl Default for CaFailedFetchPolicy {
|
||||
fn default() -> Self {
|
||||
Self::UseVerifiedCache
|
||||
Self::UseFetchCachePp
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,15 +9,15 @@ use sha2::Digest;
|
||||
use std::collections::HashSet;
|
||||
|
||||
const CF_RAW_OBJECTS: &str = "raw_objects";
|
||||
const CF_VERIFIED_PUBLICATION_POINTS: &str = "verified_publication_points";
|
||||
const CF_FETCH_CACHE_PP: &str = "fetch_cache_pp";
|
||||
const CF_RRDP_STATE: &str = "rrdp_state";
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct VerifiedKey(String);
|
||||
pub struct FetchCachePpKey(String);
|
||||
|
||||
impl VerifiedKey {
|
||||
impl FetchCachePpKey {
|
||||
pub fn from_manifest_rsync_uri(manifest_rsync_uri: &str) -> Self {
|
||||
Self(format!("verified:{manifest_rsync_uri}"))
|
||||
Self(format!("fetch_cache_pp:{manifest_rsync_uri}"))
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
@@ -33,7 +33,7 @@ pub enum StorageError {
|
||||
#[error("missing column family: {0}")]
|
||||
MissingColumnFamily(&'static str),
|
||||
|
||||
#[error("verified publication point pack error: {0}")]
|
||||
#[error("fetch_cache_pp pack error: {0}")]
|
||||
Pack(#[from] PackDecodeError),
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ pub struct RocksStore {
|
||||
}
|
||||
|
||||
pub mod pack {
|
||||
pub use super::{PackDecodeError, PackFile, PackTime, VerifiedPublicationPointPack};
|
||||
pub use super::{FetchCachePpPack, PackDecodeError, PackFile, PackTime};
|
||||
}
|
||||
|
||||
impl RocksStore {
|
||||
@@ -61,7 +61,7 @@ impl RocksStore {
|
||||
|
||||
let cfs = vec![
|
||||
ColumnFamilyDescriptor::new(CF_RAW_OBJECTS, Options::default()),
|
||||
ColumnFamilyDescriptor::new(CF_VERIFIED_PUBLICATION_POINTS, Options::default()),
|
||||
ColumnFamilyDescriptor::new(CF_FETCH_CACHE_PP, Options::default()),
|
||||
ColumnFamilyDescriptor::new(CF_RRDP_STATE, Options::default()),
|
||||
];
|
||||
|
||||
@@ -102,16 +102,19 @@ impl RocksStore {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn put_verified(&self, key: &VerifiedKey, bytes: &[u8]) -> StorageResult<()> {
|
||||
let cf = self.cf(CF_VERIFIED_PUBLICATION_POINTS)?;
|
||||
pub fn put_fetch_cache_pp(&self, key: &FetchCachePpKey, bytes: &[u8]) -> StorageResult<()> {
|
||||
let cf = self.cf(CF_FETCH_CACHE_PP)?;
|
||||
self.db
|
||||
.put_cf(cf, key.as_str().as_bytes(), bytes)
|
||||
.map_err(|e| StorageError::RocksDb(e.to_string()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_verified(&self, key: &VerifiedKey) -> StorageResult<Option<Vec<u8>>> {
|
||||
let cf = self.cf(CF_VERIFIED_PUBLICATION_POINTS)?;
|
||||
pub fn get_fetch_cache_pp(
|
||||
&self,
|
||||
key: &FetchCachePpKey,
|
||||
) -> StorageResult<Option<Vec<u8>>> {
|
||||
let cf = self.cf(CF_FETCH_CACHE_PP)?;
|
||||
let v = self
|
||||
.db
|
||||
.get_cf(cf, key.as_str().as_bytes())
|
||||
@@ -172,10 +175,10 @@ impl RocksStore {
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn verified_iter_all<'a>(
|
||||
pub fn fetch_cache_pp_iter_all<'a>(
|
||||
&'a self,
|
||||
) -> StorageResult<impl Iterator<Item = (Box<[u8]>, Box<[u8]>)> + 'a> {
|
||||
let cf = self.cf(CF_VERIFIED_PUBLICATION_POINTS)?;
|
||||
let cf = self.cf(CF_FETCH_CACHE_PP)?;
|
||||
let mode = IteratorMode::Start;
|
||||
Ok(self.db.iterator_cf(cf, mode).filter_map(|res| res.ok()))
|
||||
}
|
||||
@@ -213,12 +216,17 @@ fn enable_blobdb_if_supported(opts: &mut Options) {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct VerifiedPublicationPointPack {
|
||||
pub struct FetchCachePpPack {
|
||||
pub format_version: u32,
|
||||
|
||||
pub manifest_rsync_uri: String,
|
||||
pub publication_point_rsync_uri: String,
|
||||
|
||||
/// Manifest manifestNumber value (RFC 9286 §4.2.1).
|
||||
///
|
||||
/// Minimal big-endian bytes. For zero, this is `[0]`.
|
||||
pub manifest_number_be: Vec<u8>,
|
||||
|
||||
pub this_update: PackTime,
|
||||
pub next_update: PackTime,
|
||||
|
||||
@@ -233,7 +241,7 @@ pub struct VerifiedPublicationPointPack {
|
||||
pub files: Vec<PackFile>,
|
||||
}
|
||||
|
||||
impl VerifiedPublicationPointPack {
|
||||
impl FetchCachePpPack {
|
||||
pub const FORMAT_VERSION_V1: u32 = 1;
|
||||
|
||||
pub fn encode(&self) -> StorageResult<Vec<u8>> {
|
||||
@@ -258,6 +266,23 @@ impl VerifiedPublicationPointPack {
|
||||
if self.publication_point_rsync_uri.is_empty() {
|
||||
return Err(PackDecodeError::MissingField("publication_point_rsync_uri").into());
|
||||
}
|
||||
if self.manifest_number_be.is_empty() {
|
||||
return Err(PackDecodeError::MissingField("manifest_number_be").into());
|
||||
}
|
||||
if self.manifest_number_be.len() > 20 {
|
||||
return Err(PackDecodeError::InvalidField {
|
||||
field: "manifest_number_be",
|
||||
detail: "must be at most 20 octets (RFC 9286 §4.2.1)",
|
||||
}
|
||||
.into());
|
||||
}
|
||||
if self.manifest_number_be.len() > 1 && self.manifest_number_be[0] == 0 {
|
||||
return Err(PackDecodeError::InvalidField {
|
||||
field: "manifest_number_be",
|
||||
detail: "must be minimal big-endian (no leading zeros)",
|
||||
}
|
||||
.into());
|
||||
}
|
||||
|
||||
self.this_update
|
||||
.parse()
|
||||
@@ -354,10 +379,10 @@ impl PackTime {
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum PackDecodeError {
|
||||
#[error("encode verified publication point pack failed: {0}")]
|
||||
#[error("encode fetch_cache_pp pack failed: {0}")]
|
||||
Encode(String),
|
||||
|
||||
#[error("decode verified publication point pack failed: {0}")]
|
||||
#[error("decode fetch_cache_pp pack failed: {0}")]
|
||||
Decode(String),
|
||||
|
||||
#[error("unsupported pack format_version: {0}")]
|
||||
@@ -366,16 +391,22 @@ pub enum PackDecodeError {
|
||||
#[error("missing required field: {0}")]
|
||||
MissingField(&'static str),
|
||||
|
||||
#[error("missing manifest_bytes in verified pack")]
|
||||
#[error("invalid field {field}: {detail}")]
|
||||
InvalidField {
|
||||
field: &'static str,
|
||||
detail: &'static str,
|
||||
},
|
||||
|
||||
#[error("missing manifest_bytes in fetch_cache_pp pack")]
|
||||
MissingManifestBytes,
|
||||
|
||||
#[error("duplicate file rsync uri in verified pack: {0}")]
|
||||
#[error("duplicate file rsync uri in fetch_cache_pp pack: {0}")]
|
||||
DuplicateFileRsyncUri(String),
|
||||
|
||||
#[error("empty file bytes in verified pack: {0}")]
|
||||
#[error("empty file bytes in fetch_cache_pp pack: {0}")]
|
||||
EmptyFileBytes(String),
|
||||
|
||||
#[error("file hash mismatch in verified pack: {rsync_uri}")]
|
||||
#[error("file hash mismatch in fetch_cache_pp pack: {rsync_uri}")]
|
||||
FileHashMismatch { rsync_uri: String },
|
||||
|
||||
#[error("invalid time field {field}: {detail}")]
|
||||
|
||||
@@ -2,19 +2,22 @@ use crate::data_model::manifest::{ManifestDecodeError, ManifestObject, ManifestV
|
||||
use crate::data_model::signed_object::SignedObjectVerifyError;
|
||||
use crate::policy::{CaFailedFetchPolicy, Policy};
|
||||
use crate::report::{RfcRef, Warning};
|
||||
use crate::storage::{RocksStore, StorageError, VerifiedKey, VerifiedPublicationPointPack};
|
||||
use crate::storage::{FetchCachePpKey, FetchCachePpPack, RocksStore, StorageError};
|
||||
use crate::validation::cert_path::{CertPathError, validate_ee_cert_path};
|
||||
use sha2::Digest;
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum PublicationPointSource {
|
||||
Fresh,
|
||||
VerifiedCache,
|
||||
FetchCachePp,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct PublicationPointResult {
|
||||
pub source: PublicationPointSource,
|
||||
pub pack: VerifiedPublicationPointPack,
|
||||
pub pack: FetchCachePpPack,
|
||||
pub warnings: Vec<Warning>,
|
||||
}
|
||||
|
||||
@@ -38,6 +41,26 @@ pub enum ManifestFreshError {
|
||||
)]
|
||||
Signature(#[from] SignedObjectVerifyError),
|
||||
|
||||
#[error(
|
||||
"manifest embedded EE certificate path validation failed: {0} (RFC 6488 §3; RFC 9286 §6.2; RFC 9286 §6.6)"
|
||||
)]
|
||||
EeCertPath(#[from] CertPathError),
|
||||
|
||||
#[error(
|
||||
"manifest embedded EE certificate CRLDistributionPoints missing (cannot validate EE certificate) (RFC 6487 §4.8.6; RFC 6488 §3; RFC 9286 §6.2; RFC 9286 §6.6)"
|
||||
)]
|
||||
EeCrlDpMissing,
|
||||
|
||||
#[error(
|
||||
"publication point contains no CRL files (cannot validate manifest EE certificate) (RFC 9286 §7; RFC 6487 §4.8.6; RFC 6488 §3; RFC 9286 §6.2; RFC 9286 §6.6)"
|
||||
)]
|
||||
NoCrlFiles,
|
||||
|
||||
#[error(
|
||||
"CRL referenced by manifest embedded EE certificate CRLDistributionPoints not found at publication point: {0} (RFC 6487 §4.8.6; RFC 9286 §4.2.1; RFC 9286 §6.2; RFC 9286 §6.6)"
|
||||
)]
|
||||
EeCrlNotFound(String),
|
||||
|
||||
#[error(
|
||||
"manifest is not valid at validation_time: this_update={this_update_rfc3339_utc} next_update={next_update_rfc3339_utc} validation_time={validation_time_rfc3339_utc} (RFC 9286 §6.3; RFC 9286 §6.6)"
|
||||
)]
|
||||
@@ -47,6 +70,27 @@ pub enum ManifestFreshError {
|
||||
validation_time_rfc3339_utc: String,
|
||||
},
|
||||
|
||||
#[error(
|
||||
"manifest must reside at the same publication point as id-ad-caRepository: manifest={manifest_rsync_uri} publication_point={publication_point_rsync_uri} (RFC 9286 §6.1; RFC 9286 §6.6)"
|
||||
)]
|
||||
ManifestOutsidePublicationPoint {
|
||||
manifest_rsync_uri: String,
|
||||
publication_point_rsync_uri: String,
|
||||
},
|
||||
|
||||
#[error(
|
||||
"manifestNumber not higher than previously validated manifest: old={old_hex} new={new_hex} (RFC 9286 §4.2.1; RFC 9286 §6.6)"
|
||||
)]
|
||||
ManifestNumberNotIncreasing { old_hex: String, new_hex: String },
|
||||
|
||||
#[error(
|
||||
"thisUpdate not more recent than previously validated manifest: old={old_rfc3339_utc} new={new_rfc3339_utc} (RFC 9286 §4.2.1; RFC 9286 §6.6)"
|
||||
)]
|
||||
ThisUpdateNotIncreasing {
|
||||
old_rfc3339_utc: String,
|
||||
new_rfc3339_utc: String,
|
||||
},
|
||||
|
||||
#[error(
|
||||
"manifest referenced file missing in raw_objects: {rsync_uri} (RFC 9286 §6.4; RFC 9286 §6.6)"
|
||||
)]
|
||||
@@ -58,14 +102,24 @@ pub enum ManifestFreshError {
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ManifestCachedError {
|
||||
#[error("verified cache entry missing: {0} (RFC 9286 §6.6)")]
|
||||
MissingVerifiedCache(String),
|
||||
#[error("fetch_cache_pp entry missing: {0} (RFC 9286 §6.6)")]
|
||||
MissingFetchCachePp(String),
|
||||
|
||||
#[error("verified cache pack invalid: {0}")]
|
||||
#[error("fetch_cache_pp pack invalid: {0}")]
|
||||
InvalidPack(#[from] StorageError),
|
||||
|
||||
#[error("cached manifest revalidation failed: {0}")]
|
||||
CachedManifestFresh(#[from] ManifestFreshError),
|
||||
|
||||
#[error(
|
||||
"cached fetch_cache_pp missing file referenced by manifest: {rsync_uri} (RFC 9286 §6.4; RFC 9286 §6.6)"
|
||||
)]
|
||||
CachedMissingFile { rsync_uri: String },
|
||||
|
||||
#[error(
|
||||
"cached fetch_cache_pp file hash mismatch: {rsync_uri} (RFC 9286 §6.5; RFC 9286 §6.6)"
|
||||
)]
|
||||
CachedHashMismatch { rsync_uri: String },
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
@@ -74,7 +128,7 @@ pub enum ManifestProcessError {
|
||||
StopAllOutput(#[from] ManifestFreshError),
|
||||
|
||||
#[error(
|
||||
"manifest processing failed and no usable verified cache is available: fresh={fresh}; cached={cached}"
|
||||
"manifest processing failed and no usable fetch_cache_pp is available: fresh={fresh}; cached={cached}"
|
||||
)]
|
||||
NoUsableCache {
|
||||
fresh: ManifestFreshError,
|
||||
@@ -90,20 +144,24 @@ pub fn process_manifest_publication_point(
|
||||
policy: &Policy,
|
||||
manifest_rsync_uri: &str,
|
||||
publication_point_rsync_uri: &str,
|
||||
issuer_ca_der: &[u8],
|
||||
issuer_ca_rsync_uri: Option<&str>,
|
||||
validation_time: time::OffsetDateTime,
|
||||
) -> Result<PublicationPointResult, ManifestProcessError> {
|
||||
let fresh = try_build_fresh_pack(
|
||||
store,
|
||||
manifest_rsync_uri,
|
||||
publication_point_rsync_uri,
|
||||
issuer_ca_der,
|
||||
issuer_ca_rsync_uri,
|
||||
validation_time,
|
||||
);
|
||||
|
||||
match fresh {
|
||||
Ok(pack) => {
|
||||
let key = VerifiedKey::from_manifest_rsync_uri(manifest_rsync_uri);
|
||||
let key = FetchCachePpKey::from_manifest_rsync_uri(manifest_rsync_uri);
|
||||
let bytes = pack.encode()?;
|
||||
store.put_verified(&key, &bytes)?;
|
||||
store.put_fetch_cache_pp(&key, &bytes)?;
|
||||
Ok(PublicationPointResult {
|
||||
source: PublicationPointSource::Fresh,
|
||||
pack,
|
||||
@@ -114,7 +172,7 @@ pub fn process_manifest_publication_point(
|
||||
CaFailedFetchPolicy::StopAllOutput => {
|
||||
Err(ManifestProcessError::StopAllOutput(fresh_err))
|
||||
}
|
||||
CaFailedFetchPolicy::UseVerifiedCache => {
|
||||
CaFailedFetchPolicy::UseFetchCachePp => {
|
||||
let mut warnings = vec![
|
||||
Warning::new(format!("manifest failed fetch: {fresh_err}"))
|
||||
.with_rfc_refs(&[RfcRef("RFC 9286 §6.6")])
|
||||
@@ -125,16 +183,18 @@ pub fn process_manifest_publication_point(
|
||||
store,
|
||||
manifest_rsync_uri,
|
||||
publication_point_rsync_uri,
|
||||
issuer_ca_der,
|
||||
issuer_ca_rsync_uri,
|
||||
validation_time,
|
||||
) {
|
||||
Ok(pack) => {
|
||||
warnings.push(
|
||||
Warning::new("using verified cache for publication point")
|
||||
Warning::new("using fetch_cache_pp for publication point")
|
||||
.with_rfc_refs(&[RfcRef("RFC 9286 §6.6")])
|
||||
.with_context(manifest_rsync_uri),
|
||||
);
|
||||
Ok(PublicationPointResult {
|
||||
source: PublicationPointSource::VerifiedCache,
|
||||
source: PublicationPointSource::FetchCachePp,
|
||||
pack,
|
||||
warnings,
|
||||
})
|
||||
@@ -153,13 +213,15 @@ fn load_and_revalidate_cached_pack(
|
||||
store: &RocksStore,
|
||||
manifest_rsync_uri: &str,
|
||||
publication_point_rsync_uri: &str,
|
||||
issuer_ca_der: &[u8],
|
||||
issuer_ca_rsync_uri: Option<&str>,
|
||||
validation_time: time::OffsetDateTime,
|
||||
) -> Result<VerifiedPublicationPointPack, ManifestCachedError> {
|
||||
let key = VerifiedKey::from_manifest_rsync_uri(manifest_rsync_uri);
|
||||
) -> Result<FetchCachePpPack, ManifestCachedError> {
|
||||
let key = FetchCachePpKey::from_manifest_rsync_uri(manifest_rsync_uri);
|
||||
let bytes = store
|
||||
.get_verified(&key)?
|
||||
.ok_or_else(|| ManifestCachedError::MissingVerifiedCache(key.as_str().to_string()))?;
|
||||
let pack = VerifiedPublicationPointPack::decode(&bytes)?;
|
||||
.get_fetch_cache_pp(&key)?
|
||||
.ok_or_else(|| ManifestCachedError::MissingFetchCachePp(key.as_str().to_string()))?;
|
||||
let pack = FetchCachePpPack::decode(&bytes)?;
|
||||
|
||||
if pack.manifest_rsync_uri != manifest_rsync_uri {
|
||||
return Err(ManifestCachedError::InvalidPack(StorageError::RocksDb(
|
||||
@@ -172,15 +234,61 @@ fn load_and_revalidate_cached_pack(
|
||||
)));
|
||||
}
|
||||
|
||||
revalidate_pack_with_current_time(&pack, validation_time).map_err(ManifestCachedError::from)?;
|
||||
revalidate_cached_pack_with_current_time(
|
||||
&pack,
|
||||
issuer_ca_der,
|
||||
issuer_ca_rsync_uri,
|
||||
validation_time,
|
||||
)?;
|
||||
Ok(pack)
|
||||
}
|
||||
|
||||
fn revalidate_pack_with_current_time(
|
||||
pack: &VerifiedPublicationPointPack,
|
||||
fn revalidate_cached_pack_with_current_time(
|
||||
pack: &FetchCachePpPack,
|
||||
issuer_ca_der: &[u8],
|
||||
issuer_ca_rsync_uri: Option<&str>,
|
||||
validation_time: time::OffsetDateTime,
|
||||
) -> Result<(), ManifestFreshError> {
|
||||
let manifest = ManifestObject::decode_der(&pack.manifest_bytes)?;
|
||||
) -> Result<(), ManifestCachedError> {
|
||||
// First, re-validate the cached manifest itself with the current time.
|
||||
let manifest = decode_and_validate_manifest_with_current_time(&pack.manifest_bytes, validation_time)
|
||||
.map_err(ManifestCachedError::from)?;
|
||||
|
||||
// Then, re-bind the manifest fileList to the cached pack contents, as per RFC 9286 §6.4-§6.5.
|
||||
let by_uri: HashMap<&str, &crate::storage::PackFile> = pack
|
||||
.files
|
||||
.iter()
|
||||
.map(|f| (f.rsync_uri.as_str(), f))
|
||||
.collect();
|
||||
|
||||
for entry in &manifest.manifest.files {
|
||||
let rsync_uri =
|
||||
join_rsync_dir_and_file(&pack.publication_point_rsync_uri, &entry.file_name);
|
||||
let Some(file) = by_uri.get(rsync_uri.as_str()) else {
|
||||
return Err(ManifestCachedError::CachedMissingFile { rsync_uri });
|
||||
};
|
||||
if file.sha256.as_slice() != entry.hash_bytes.as_slice() {
|
||||
return Err(ManifestCachedError::CachedHashMismatch { rsync_uri });
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, validate the manifest's embedded EE certificate path against the issuer CA + CRL.
|
||||
// This enforces cert validity + CRL validity at `validation_time` for cached packs.
|
||||
validate_manifest_embedded_ee_cert_path(
|
||||
&manifest,
|
||||
&pack.files,
|
||||
issuer_ca_der,
|
||||
issuer_ca_rsync_uri,
|
||||
validation_time,
|
||||
)
|
||||
.map_err(ManifestCachedError::from)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn decode_and_validate_manifest_with_current_time(
|
||||
manifest_bytes: &[u8],
|
||||
validation_time: time::OffsetDateTime,
|
||||
) -> Result<ManifestObject, ManifestFreshError> {
|
||||
let manifest = ManifestObject::decode_der(manifest_bytes)?;
|
||||
manifest.validate_embedded_ee_cert()?;
|
||||
manifest.signed_object.verify()?;
|
||||
|
||||
@@ -207,15 +315,24 @@ fn revalidate_pack_with_current_time(
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(manifest)
|
||||
}
|
||||
|
||||
fn try_build_fresh_pack(
|
||||
store: &RocksStore,
|
||||
manifest_rsync_uri: &str,
|
||||
publication_point_rsync_uri: &str,
|
||||
issuer_ca_der: &[u8],
|
||||
issuer_ca_rsync_uri: Option<&str>,
|
||||
validation_time: time::OffsetDateTime,
|
||||
) -> Result<VerifiedPublicationPointPack, ManifestFreshError> {
|
||||
) -> Result<FetchCachePpPack, ManifestFreshError> {
|
||||
if !rsync_uri_is_under_publication_point(manifest_rsync_uri, publication_point_rsync_uri) {
|
||||
return Err(ManifestFreshError::ManifestOutsidePublicationPoint {
|
||||
manifest_rsync_uri: manifest_rsync_uri.to_string(),
|
||||
publication_point_rsync_uri: publication_point_rsync_uri.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
let manifest_bytes = store
|
||||
.get_raw(manifest_rsync_uri)
|
||||
.map_err(|e| ManifestFreshError::MissingManifest {
|
||||
@@ -225,31 +342,50 @@ fn try_build_fresh_pack(
|
||||
manifest_rsync_uri: manifest_rsync_uri.to_string(),
|
||||
})?;
|
||||
|
||||
let manifest = ManifestObject::decode_der(&manifest_bytes)?;
|
||||
manifest.validate_embedded_ee_cert()?;
|
||||
manifest.signed_object.verify()?;
|
||||
let manifest = decode_and_validate_manifest_with_current_time(&manifest_bytes, validation_time)?;
|
||||
|
||||
let this_update = manifest
|
||||
.manifest
|
||||
.this_update
|
||||
.to_offset(time::UtcOffset::UTC);
|
||||
let next_update = manifest
|
||||
.manifest
|
||||
.next_update
|
||||
.to_offset(time::UtcOffset::UTC);
|
||||
let this_update = manifest.manifest.this_update.to_offset(time::UtcOffset::UTC);
|
||||
let next_update = manifest.manifest.next_update.to_offset(time::UtcOffset::UTC);
|
||||
let now = validation_time.to_offset(time::UtcOffset::UTC);
|
||||
if now < this_update || now > next_update {
|
||||
return Err(ManifestFreshError::StaleOrEarly {
|
||||
this_update_rfc3339_utc: this_update
|
||||
.format(&time::format_description::well_known::Rfc3339)
|
||||
.expect("format thisUpdate"),
|
||||
next_update_rfc3339_utc: next_update
|
||||
.format(&time::format_description::well_known::Rfc3339)
|
||||
.expect("format nextUpdate"),
|
||||
validation_time_rfc3339_utc: now
|
||||
.format(&time::format_description::well_known::Rfc3339)
|
||||
.expect("format validation_time"),
|
||||
});
|
||||
|
||||
// RFC 9286 §4.2.1: replay/rollback detection for manifestNumber and thisUpdate.
|
||||
//
|
||||
// If a purported "new" manifest contains a manifestNumber equal to or lower than previously
|
||||
// validated manifests, or a thisUpdate less recent than previously validated manifests,
|
||||
// this is treated as a failed fetch and processing continues via the cached objects path (§6.6).
|
||||
let key = FetchCachePpKey::from_manifest_rsync_uri(manifest_rsync_uri);
|
||||
if let Some(old_bytes) = store.get_fetch_cache_pp(&key).ok().flatten() {
|
||||
if let Ok(old_pack) = FetchCachePpPack::decode(&old_bytes) {
|
||||
if old_pack.manifest_rsync_uri == manifest_rsync_uri
|
||||
&& old_pack.publication_point_rsync_uri == publication_point_rsync_uri
|
||||
{
|
||||
let new_num = manifest.manifest.manifest_number.bytes_be.as_slice();
|
||||
let old_num = old_pack.manifest_number_be.as_slice();
|
||||
if cmp_minimal_be_unsigned(new_num, old_num) != Ordering::Greater {
|
||||
return Err(ManifestFreshError::ManifestNumberNotIncreasing {
|
||||
old_hex: hex::encode_upper(old_num),
|
||||
new_hex: hex::encode_upper(new_num),
|
||||
});
|
||||
}
|
||||
|
||||
let old_this_update = old_pack
|
||||
.this_update
|
||||
.parse()
|
||||
.expect("pack internal validation ensures this_update parses");
|
||||
if this_update <= old_this_update {
|
||||
use time::format_description::well_known::Rfc3339;
|
||||
return Err(ManifestFreshError::ThisUpdateNotIncreasing {
|
||||
old_rfc3339_utc: old_this_update
|
||||
.to_offset(time::UtcOffset::UTC)
|
||||
.format(&Rfc3339)
|
||||
.expect("format old thisUpdate"),
|
||||
new_rfc3339_utc: this_update
|
||||
.format(&Rfc3339)
|
||||
.expect("format new thisUpdate"),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut files = Vec::with_capacity(manifest.manifest.files.len());
|
||||
@@ -274,10 +410,21 @@ fn try_build_fresh_pack(
|
||||
));
|
||||
}
|
||||
|
||||
Ok(VerifiedPublicationPointPack {
|
||||
format_version: VerifiedPublicationPointPack::FORMAT_VERSION_V1,
|
||||
// RFC 6488 §3: manifest (signed object) validity includes a valid EE cert path.
|
||||
// We validate this after §6.4/§6.5 so the issuer CRL can be selected from the publication point.
|
||||
validate_manifest_embedded_ee_cert_path(
|
||||
&manifest,
|
||||
&files,
|
||||
issuer_ca_der,
|
||||
issuer_ca_rsync_uri,
|
||||
validation_time,
|
||||
)?;
|
||||
|
||||
Ok(FetchCachePpPack {
|
||||
format_version: FetchCachePpPack::FORMAT_VERSION_V1,
|
||||
manifest_rsync_uri: manifest_rsync_uri.to_string(),
|
||||
publication_point_rsync_uri: publication_point_rsync_uri.to_string(),
|
||||
manifest_number_be: manifest.manifest.manifest_number.bytes_be.clone(),
|
||||
this_update: crate::storage::PackTime::from_utc_offset_datetime(this_update),
|
||||
next_update: crate::storage::PackTime::from_utc_offset_datetime(next_update),
|
||||
verified_at: crate::storage::PackTime::from_utc_offset_datetime(now),
|
||||
@@ -286,6 +433,14 @@ fn try_build_fresh_pack(
|
||||
})
|
||||
}
|
||||
|
||||
fn cmp_minimal_be_unsigned(a: &[u8], b: &[u8]) -> Ordering {
|
||||
// Compare two minimal big-endian byte strings as unsigned integers.
|
||||
// (Leading zeros are not expected; callers store minimal big-endian.)
|
||||
a.len()
|
||||
.cmp(&b.len())
|
||||
.then_with(|| a.cmp(b))
|
||||
}
|
||||
|
||||
fn join_rsync_dir_and_file(base: &str, file_name: &str) -> String {
|
||||
if base.ends_with('/') {
|
||||
format!("{base}{file_name}")
|
||||
@@ -293,3 +448,64 @@ fn join_rsync_dir_and_file(base: &str, file_name: &str) -> String {
|
||||
format!("{base}/{file_name}")
|
||||
}
|
||||
}
|
||||
|
||||
fn rsync_uri_is_under_publication_point(uri: &str, publication_point_rsync_uri: &str) -> bool {
|
||||
let pp = if publication_point_rsync_uri.ends_with('/') {
|
||||
publication_point_rsync_uri.to_string()
|
||||
} else {
|
||||
format!("{publication_point_rsync_uri}/")
|
||||
};
|
||||
uri.starts_with(&pp)
|
||||
}
|
||||
|
||||
fn validate_manifest_embedded_ee_cert_path(
|
||||
manifest: &ManifestObject,
|
||||
files: &[crate::storage::PackFile],
|
||||
issuer_ca_der: &[u8],
|
||||
issuer_ca_rsync_uri: Option<&str>,
|
||||
validation_time: time::OffsetDateTime,
|
||||
) -> Result<(), ManifestFreshError> {
|
||||
let ee = &manifest.signed_object.signed_data.certificates[0];
|
||||
let ee_der = ee.raw_der.as_slice();
|
||||
|
||||
let crl_files = files
|
||||
.iter()
|
||||
.filter(|f| f.rsync_uri.ends_with(".crl"))
|
||||
.collect::<Vec<_>>();
|
||||
if crl_files.is_empty() {
|
||||
return Err(ManifestFreshError::NoCrlFiles);
|
||||
}
|
||||
|
||||
let Some(crldp_uris) = ee
|
||||
.resource_cert
|
||||
.tbs
|
||||
.extensions
|
||||
.crl_distribution_points_uris
|
||||
.as_ref()
|
||||
else {
|
||||
return Err(ManifestFreshError::EeCrlDpMissing);
|
||||
};
|
||||
|
||||
for u in crldp_uris {
|
||||
let s = u.as_str();
|
||||
if let Some(f) = crl_files.iter().find(|f| f.rsync_uri == s) {
|
||||
let _validated = validate_ee_cert_path(
|
||||
ee_der,
|
||||
issuer_ca_der,
|
||||
f.bytes.as_slice(),
|
||||
issuer_ca_rsync_uri,
|
||||
Some(f.rsync_uri.as_str()),
|
||||
validation_time,
|
||||
)?;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
Err(ManifestFreshError::EeCrlNotFound(
|
||||
crldp_uris
|
||||
.iter()
|
||||
.map(|u| u.as_str())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", "),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::data_model::roa::{IpPrefix, RoaAfi, RoaDecodeError, RoaObject, RoaVal
|
||||
use crate::data_model::signed_object::SignedObjectVerifyError;
|
||||
use crate::policy::{Policy, SignedObjectFailurePolicy};
|
||||
use crate::report::{RfcRef, Warning};
|
||||
use crate::storage::{PackFile, VerifiedPublicationPointPack};
|
||||
use crate::storage::{FetchCachePpPack, PackFile};
|
||||
use crate::validation::cert_path::{CertPathError, validate_ee_cert_path};
|
||||
|
||||
const RFC_NONE: &[RfcRef] = &[];
|
||||
@@ -58,10 +58,10 @@ pub struct ObjectsStats {
|
||||
pub publication_point_dropped: bool,
|
||||
}
|
||||
|
||||
/// Process objects from a verified publication point pack using a known issuer CA certificate
|
||||
/// Process objects from a fetch_cache_pp publication point pack using a known issuer CA certificate
|
||||
/// and its effective resources (resolved via the resource-path, RFC 6487 §7.2).
|
||||
pub fn process_verified_publication_point_pack_for_issuer(
|
||||
pack: &VerifiedPublicationPointPack,
|
||||
pub fn process_fetch_cache_pp_pack_for_issuer(
|
||||
pack: &FetchCachePpPack,
|
||||
policy: &Policy,
|
||||
issuer_ca_der: &[u8],
|
||||
issuer_ca_rsync_uri: Option<&str>,
|
||||
@@ -85,7 +85,7 @@ pub fn process_verified_publication_point_pack_for_issuer(
|
||||
|
||||
// Enforce that `manifest_bytes` is actually a manifest object.
|
||||
let _manifest =
|
||||
ManifestObject::decode_der(&pack.manifest_bytes).expect("verified pack manifest decodes");
|
||||
ManifestObject::decode_der(&pack.manifest_bytes).expect("fetch_cache_pp manifest decodes");
|
||||
|
||||
let crl_files = pack
|
||||
.files
|
||||
@@ -99,7 +99,7 @@ pub fn process_verified_publication_point_pack_for_issuer(
|
||||
if crl_files.is_empty() && (stats.roa_total > 0 || stats.aspa_total > 0) {
|
||||
stats.publication_point_dropped = true;
|
||||
warnings.push(
|
||||
Warning::new("dropping publication point: no CRL files in verified pack")
|
||||
Warning::new("dropping publication point: no CRL files in fetch_cache_pp")
|
||||
.with_rfc_refs(&[RfcRef("RFC 6487 §4.8.6"), RfcRef("RFC 9286 §7")])
|
||||
.with_context(&pack.manifest_rsync_uri),
|
||||
);
|
||||
@@ -110,7 +110,9 @@ pub fn process_verified_publication_point_pack_for_issuer(
|
||||
sha256_hex: sha256_hex_from_32(&f.sha256),
|
||||
kind: AuditObjectKind::Roa,
|
||||
result: AuditObjectResult::Skipped,
|
||||
detail: Some("skipped due to missing CRL files in verified pack".to_string()),
|
||||
detail: Some(
|
||||
"skipped due to missing CRL files in fetch_cache_pp".to_string(),
|
||||
),
|
||||
});
|
||||
} else if f.rsync_uri.ends_with(".asa") {
|
||||
audit.push(ObjectAuditEntry {
|
||||
@@ -118,7 +120,9 @@ pub fn process_verified_publication_point_pack_for_issuer(
|
||||
sha256_hex: sha256_hex_from_32(&f.sha256),
|
||||
kind: AuditObjectKind::Aspa,
|
||||
result: AuditObjectResult::Skipped,
|
||||
detail: Some("skipped due to missing CRL files in verified pack".to_string()),
|
||||
detail: Some(
|
||||
"skipped due to missing CRL files in fetch_cache_pp".to_string(),
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -357,12 +361,12 @@ enum ObjectValidateError {
|
||||
MissingCrlDpUris,
|
||||
|
||||
#[error(
|
||||
"no CRL available in verified pack (cannot validate certificates) (RFC 9286 §7; RFC 6487 §4.8.6)"
|
||||
"no CRL available in fetch_cache_pp (cannot validate certificates) (RFC 9286 §7; RFC 6487 §4.8.6)"
|
||||
)]
|
||||
MissingCrlInPack,
|
||||
|
||||
#[error(
|
||||
"CRL referenced by CRLDistributionPoints not found in verified pack: {0} (RFC 6487 §4.8.6; RFC 9286 §4.2.1)"
|
||||
"CRL referenced by CRLDistributionPoints not found in fetch_cache_pp: {0} (RFC 6487 §4.8.6; RFC 9286 §4.2.1)"
|
||||
)]
|
||||
CrlNotFound(String),
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use crate::data_model::rc::{AsResourceSet, IpResourceSet};
|
||||
use crate::fetch::rsync::RsyncFetcher;
|
||||
use crate::policy::Policy;
|
||||
use crate::storage::{RocksStore, VerifiedKey};
|
||||
use crate::storage::{FetchCachePpKey, RocksStore};
|
||||
use crate::sync::repo::{RepoSyncResult, sync_publication_point};
|
||||
use crate::sync::rrdp::Fetcher as HttpFetcher;
|
||||
use crate::validation::manifest::{PublicationPointResult, process_manifest_publication_point};
|
||||
use crate::validation::objects::{
|
||||
ObjectsOutput, process_verified_publication_point_pack_for_issuer,
|
||||
ObjectsOutput, process_fetch_cache_pp_pack_for_issuer,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
@@ -29,8 +29,8 @@ pub enum RunError {
|
||||
///
|
||||
/// This orchestrates:
|
||||
/// 1) repo sync (RRDP or rsync fallback) into `raw_objects`
|
||||
/// 2) manifest RP processing into a verified pack (`verified:<manifest-rsync-uri>`)
|
||||
/// 3) signed object processing (ROA/ASPA) from the verified pack
|
||||
/// 2) manifest RP processing into a fetch_cache_pp pack (`fetch_cache_pp:<manifest-rsync-uri>`)
|
||||
/// 3) signed object processing (ROA/ASPA) from the fetch_cache_pp pack
|
||||
pub fn run_publication_point_once(
|
||||
store: &RocksStore,
|
||||
policy: &Policy,
|
||||
@@ -60,10 +60,12 @@ pub fn run_publication_point_once(
|
||||
policy,
|
||||
manifest_rsync_uri,
|
||||
publication_point_rsync_uri,
|
||||
issuer_ca_der,
|
||||
issuer_ca_rsync_uri,
|
||||
validation_time,
|
||||
)?;
|
||||
|
||||
let objects = process_verified_publication_point_pack_for_issuer(
|
||||
let objects = process_fetch_cache_pp_pack_for_issuer(
|
||||
&publication_point.pack,
|
||||
policy,
|
||||
issuer_ca_der,
|
||||
@@ -80,10 +82,10 @@ pub fn run_publication_point_once(
|
||||
})
|
||||
}
|
||||
|
||||
pub fn verified_pack_exists(store: &RocksStore, manifest_rsync_uri: &str) -> Result<bool, String> {
|
||||
let key = VerifiedKey::from_manifest_rsync_uri(manifest_rsync_uri);
|
||||
pub fn fetch_cache_pp_exists(store: &RocksStore, manifest_rsync_uri: &str) -> Result<bool, String> {
|
||||
let key = FetchCachePpKey::from_manifest_rsync_uri(manifest_rsync_uri);
|
||||
store
|
||||
.get_verified(&key)
|
||||
.get_fetch_cache_pp(&key)
|
||||
.map(|v| v.is_some())
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::audit::DiscoveredFrom;
|
||||
use crate::audit::PublicationPointAudit;
|
||||
use crate::data_model::rc::{AsResourceSet, IpResourceSet};
|
||||
use crate::report::{RfcRef, Warning};
|
||||
use crate::storage::VerifiedPublicationPointPack;
|
||||
use crate::storage::FetchCachePpPack;
|
||||
use crate::validation::manifest::PublicationPointSource;
|
||||
use crate::validation::objects::{AspaAttestation, ObjectsOutput, Vrp};
|
||||
|
||||
@@ -54,14 +54,14 @@ impl CaInstanceHandle {
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct PublicationPointRunResult {
|
||||
pub source: PublicationPointSource,
|
||||
pub pack: VerifiedPublicationPointPack,
|
||||
pub pack: FetchCachePpPack,
|
||||
pub warnings: Vec<Warning>,
|
||||
pub objects: ObjectsOutput,
|
||||
pub audit: PublicationPointAudit,
|
||||
/// Candidate child CA instances discovered from this publication point.
|
||||
///
|
||||
/// RFC 9286 §6.6 restriction is enforced by the tree engine: if this
|
||||
/// publication point used verified cache due to failed fetch, children MUST NOT
|
||||
/// publication point used fetch_cache_pp due to failed fetch, children MUST NOT
|
||||
/// be enqueued/processed in this run.
|
||||
pub discovered_children: Vec<DiscoveredChildCaInstance>,
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ use crate::sync::rrdp::Fetcher;
|
||||
use crate::validation::ca_instance::ca_instance_uris_from_ca_certificate;
|
||||
use crate::validation::ca_path::{CaPathError, validate_subordinate_ca_cert};
|
||||
use crate::validation::manifest::{PublicationPointSource, process_manifest_publication_point};
|
||||
use crate::validation::objects::process_verified_publication_point_pack_for_issuer;
|
||||
use crate::validation::objects::process_fetch_cache_pp_pack_for_issuer;
|
||||
use crate::validation::tree::{
|
||||
CaInstanceHandle, DiscoveredChildCaInstance, PublicationPointRunResult, PublicationPointRunner,
|
||||
};
|
||||
@@ -53,6 +53,8 @@ impl<'a> PublicationPointRunner for Rpkiv1PublicationPointRunner<'a> {
|
||||
self.policy,
|
||||
&ca.manifest_rsync_uri,
|
||||
&ca.publication_point_rsync_uri,
|
||||
&ca.ca_certificate_der,
|
||||
ca.ca_certificate_rsync_uri.as_deref(),
|
||||
self.validation_time,
|
||||
) {
|
||||
Ok(v) => v,
|
||||
@@ -61,7 +63,7 @@ impl<'a> PublicationPointRunner for Rpkiv1PublicationPointRunner<'a> {
|
||||
|
||||
warnings.extend(pp.warnings.clone());
|
||||
|
||||
let objects = process_verified_publication_point_pack_for_issuer(
|
||||
let objects = process_fetch_cache_pp_pack_for_issuer(
|
||||
&pp.pack,
|
||||
self.policy,
|
||||
&ca.ca_certificate_der,
|
||||
@@ -107,7 +109,7 @@ struct ChildDiscoveryOutput {
|
||||
|
||||
fn discover_children_from_fresh_pack_with_audit(
|
||||
issuer: &CaInstanceHandle,
|
||||
pack: &crate::storage::VerifiedPublicationPointPack,
|
||||
pack: &crate::storage::FetchCachePpPack,
|
||||
validation_time: time::OffsetDateTime,
|
||||
) -> Result<ChildDiscoveryOutput, String> {
|
||||
let issuer_ca_der = issuer.ca_certificate_der.as_slice();
|
||||
@@ -219,7 +221,7 @@ fn discover_children_from_fresh_pack_with_audit(
|
||||
|
||||
fn select_issuer_crl_from_pack<'a>(
|
||||
child_cert_der: &[u8],
|
||||
pack: &'a crate::storage::VerifiedPublicationPointPack,
|
||||
pack: &'a crate::storage::FetchCachePpPack,
|
||||
) -> Result<(&'a str, &'a [u8]), String> {
|
||||
let child = crate::data_model::rc::ResourceCertificate::decode_der(child_cert_der)
|
||||
.map_err(|e| format!("child certificate decode failed: {e}"))?;
|
||||
@@ -237,7 +239,7 @@ fn select_issuer_crl_from_pack<'a>(
|
||||
}
|
||||
|
||||
Err(format!(
|
||||
"CRL referenced by child certificate CRLDistributionPoints not found in verified pack: {} (RFC 6487 §4.8.6; RFC 9286 §4.2.1)",
|
||||
"CRL referenced by child certificate CRLDistributionPoints not found in fetch_cache_pp: {} (RFC 6487 §4.8.6; RFC 9286 §4.2.1)",
|
||||
crldp_uris
|
||||
.iter()
|
||||
.map(|u| u.as_str())
|
||||
@@ -359,7 +361,7 @@ fn build_publication_point_audit(
|
||||
rrdp_notification_uri: ca.rrdp_notification_uri.clone(),
|
||||
source: match pp.source {
|
||||
PublicationPointSource::Fresh => "fresh".to_string(),
|
||||
PublicationPointSource::VerifiedCache => "verified_cache".to_string(),
|
||||
PublicationPointSource::FetchCachePp => "fetch_cache_pp".to_string(),
|
||||
},
|
||||
this_update_rfc3339_utc: pp.pack.this_update.rfc3339_utc.clone(),
|
||||
next_update_rfc3339_utc: pp.pack.next_update.rfc3339_utc.clone(),
|
||||
@@ -374,7 +376,7 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::data_model::rc::ResourceCertificate;
|
||||
use crate::fetch::rsync::LocalDirRsyncFetcher;
|
||||
use crate::storage::{PackFile, PackTime, VerifiedPublicationPointPack};
|
||||
use crate::storage::{FetchCachePpPack, PackFile, PackTime};
|
||||
use crate::sync::rrdp::Fetcher;
|
||||
use crate::validation::tree::PublicationPointRunner;
|
||||
|
||||
@@ -573,12 +575,13 @@ authorityKeyIdentifier = keyid:always
|
||||
}
|
||||
}
|
||||
|
||||
fn dummy_pack_with_files(files: Vec<PackFile>) -> VerifiedPublicationPointPack {
|
||||
fn dummy_pack_with_files(files: Vec<PackFile>) -> FetchCachePpPack {
|
||||
let now = time::OffsetDateTime::now_utc();
|
||||
VerifiedPublicationPointPack {
|
||||
format_version: VerifiedPublicationPointPack::FORMAT_VERSION_V1,
|
||||
FetchCachePpPack {
|
||||
format_version: FetchCachePpPack::FORMAT_VERSION_V1,
|
||||
manifest_rsync_uri: "rsync://example.test/repo/issuer/issuer.mft".to_string(),
|
||||
publication_point_rsync_uri: "rsync://example.test/repo/issuer/".to_string(),
|
||||
manifest_number_be: vec![1],
|
||||
this_update: PackTime::from_utc_offset_datetime(now),
|
||||
next_update: PackTime::from_utc_offset_datetime(now + time::Duration::hours(1)),
|
||||
verified_at: PackTime::from_utc_offset_datetime(now),
|
||||
|
||||
Reference in New Issue
Block a user