use super::data_ref::{fetch_and_verify_data_ref, DataRefFetcher};
use super::registry::RegistryClient;
use crate::crypto::verify::Verifier;
use crate::did::WebResolver;
use crate::error::AcdpError;
use crate::types::{body::FullContext, primitives::CtxId};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VerificationPolicy {
pub validate_body_schema: bool,
pub allow_unknown_status: bool,
pub verify_registry_receipt: bool,
}
impl Default for VerificationPolicy {
fn default() -> Self {
Self {
validate_body_schema: true,
allow_unknown_status: true,
verify_registry_receipt: false,
}
}
}
impl VerificationPolicy {
pub fn strict_v0_1_0() -> Self {
Self::default()
}
}
pub struct VerifiedContext {
pub inner: FullContext,
}
impl VerifiedContext {
pub async fn fetch(
client: &RegistryClient,
resolver: &WebResolver,
ctx_id: &CtxId,
) -> Result<Self, AcdpError> {
Self::fetch_with_policy(client, resolver, ctx_id, &VerificationPolicy::default()).await
}
pub async fn fetch_with_policy(
client: &RegistryClient,
resolver: &WebResolver,
ctx_id: &CtxId,
policy: &VerificationPolicy,
) -> Result<Self, AcdpError> {
let ctx = client.retrieve(ctx_id).await?;
if policy.validate_body_schema {
crate::validation::validate_body(&ctx.body)?;
}
let verifier = Verifier::new(resolver);
verifier.verify_body_signed(&ctx.body).await?;
if !policy.allow_unknown_status {
if let Some(other) = ctx.registry_state.status.as_other() {
return Err(AcdpError::SchemaViolation(format!(
"policy.allow_unknown_status=false; registry returned '{other}'"
)));
}
}
if policy.verify_registry_receipt && ctx.registry_receipt.is_some() {
return Err(AcdpError::NotImplemented(
"registry_receipt verification reserved for v0.1+ (RFC-ACDP-0009 §2.7)".into(),
));
}
Ok(Self { inner: ctx })
}
pub async fn fetch_report(
client: &RegistryClient,
resolver: &WebResolver,
ctx_id: &CtxId,
policy: &VerificationPolicy,
) -> Result<(Self, VerificationReport), AcdpError> {
Self::fetch_report_inner::<NoFetcher>(client, resolver, ctx_id, policy, None).await
}
pub async fn fetch_report_diagnose(
client: &RegistryClient,
resolver: &WebResolver,
ctx_id: &CtxId,
policy: &VerificationPolicy,
) -> Result<(Option<Self>, VerificationReport), AcdpError> {
let ctx = client.retrieve(ctx_id).await?;
let mut report = VerificationReport {
body_hash_ok: false,
signature_ok: false,
schema_ok: false,
data_ref_embedded: Vec::with_capacity(ctx.body.data_refs.len()),
data_ref_external: Vec::with_capacity(ctx.body.data_refs.len()),
};
if policy.validate_body_schema {
match crate::validation::validate_body_structural(&ctx.body) {
Ok(()) => report.schema_ok = true,
Err(_) => { }
}
} else {
report.schema_ok = true;
}
for dr in &ctx.body.data_refs {
if let (Some(emb), Some(_)) = (&dr.embedded, &dr.content_hash) {
let outcome = crate::validation::verify_embedded_hash(dr)
.and_then(|()| crate::validation::embedded_decoded_bytes(emb).map(|b| b.len()));
report.data_ref_embedded.push(outcome);
} else {
report.data_ref_embedded.push(Ok(0));
}
}
let verifier = Verifier::new(resolver);
report.body_hash_ok = verifier.verify_body_hash(&ctx.body).is_ok();
report.signature_ok = verifier.verify_body_signature(&ctx.body).await.is_ok();
for _ in &ctx.body.data_refs {
report.data_ref_external.push(None);
}
let all_top_level_pass = report.schema_ok && report.body_hash_ok && report.signature_ok;
let verified = if all_top_level_pass {
Some(Self { inner: ctx })
} else {
None
};
Ok((verified, report))
}
pub async fn fetch_report_with_fetcher<F: DataRefFetcher>(
client: &RegistryClient,
resolver: &WebResolver,
ctx_id: &CtxId,
policy: &VerificationPolicy,
fetcher: &F,
) -> Result<(Self, VerificationReport), AcdpError> {
Self::fetch_report_inner(client, resolver, ctx_id, policy, Some(fetcher)).await
}
async fn fetch_report_inner<F: DataRefFetcher>(
client: &RegistryClient,
resolver: &WebResolver,
ctx_id: &CtxId,
policy: &VerificationPolicy,
fetcher: Option<&F>,
) -> Result<(Self, VerificationReport), AcdpError> {
let ctx = client.retrieve(ctx_id).await?;
let mut report = VerificationReport {
body_hash_ok: false,
signature_ok: false,
schema_ok: false,
data_ref_embedded: Vec::with_capacity(ctx.body.data_refs.len()),
data_ref_external: Vec::with_capacity(ctx.body.data_refs.len()),
};
if policy.validate_body_schema {
crate::validation::validate_body_structural(&ctx.body)?;
}
report.schema_ok = true;
for dr in &ctx.body.data_refs {
if let (Some(emb), Some(_)) = (&dr.embedded, &dr.content_hash) {
let outcome = crate::validation::verify_embedded_hash(dr)
.and_then(|()| crate::validation::embedded_decoded_bytes(emb).map(|b| b.len()));
report.data_ref_embedded.push(outcome);
} else {
report.data_ref_embedded.push(Ok(0));
}
}
Verifier::new(resolver)
.verify_body_signed(&ctx.body)
.await?;
report.body_hash_ok = true;
report.signature_ok = true;
if !policy.allow_unknown_status {
if let Some(other) = ctx.registry_state.status.as_other() {
return Err(AcdpError::SchemaViolation(format!(
"policy.allow_unknown_status=false; registry returned '{other}'"
)));
}
}
for dr in &ctx.body.data_refs {
let slot: Option<Result<usize, AcdpError>> = match (fetcher, &dr.location) {
(Some(f), Some(_)) => Some(fetch_and_verify_data_ref(dr, f).await.map(|b| b.len())),
_ => None,
};
report.data_ref_external.push(slot);
}
Ok((Self { inner: ctx }, report))
}
pub fn body(&self) -> &crate::types::body::Body {
&self.inner.body
}
pub fn registry_state(&self) -> &crate::types::body::RegistryState {
&self.inner.registry_state
}
pub fn receipt(&self) -> Option<&serde_json::Value> {
self.inner.registry_receipt.as_ref()
}
pub async fn verify_receipt(&self, _resolver: &WebResolver) -> Result<(), AcdpError> {
if self.inner.registry_receipt.is_some() {
return Err(AcdpError::NotImplemented(
"registry_receipt verification is reserved for ACDP v0.1+ \
(RFC-ACDP-0009 §2.7); upgrade the acdp library to verify receipts"
.into(),
));
}
Ok(())
}
}
#[derive(Debug)]
pub struct VerificationReport {
pub body_hash_ok: bool,
pub signature_ok: bool,
pub schema_ok: bool,
pub data_ref_embedded: Vec<Result<usize, AcdpError>>,
pub data_ref_external: Vec<Option<Result<usize, AcdpError>>>,
}
struct NoFetcher;
impl DataRefFetcher for NoFetcher {
async fn fetch(
&self,
_location: &crate::types::data_ref::Location,
) -> Result<Vec<u8>, AcdpError> {
Err(AcdpError::NotImplemented(
"NoFetcher should never be called — this is a fetch_report sentinel".into(),
))
}
}
#[cfg(test)]
mod tests {
use super::VerificationPolicy;
#[test]
fn strict_v0_1_0_equals_default() {
assert_eq!(
VerificationPolicy::strict_v0_1_0(),
VerificationPolicy::default()
);
}
}