use crate::types::{EvidenceChannel, EvidenceStrength, VerificationStatus};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum FieldSource {
Xmp,
Legacy,
EmbeddedPayloadV1,
EmbeddedPayloadV2,
EmbeddedPayloadV3,
DetachedManifest,
CallerSupplied,
Computed,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RightsVerification {
found: bool,
copyright_holder: Option<String>,
creator: Option<String>,
contact: Option<String>,
rights_url: Option<String>,
usage_terms: Option<String>,
ai_constraints: Option<String>,
dmi: Option<u8>,
source: FieldSource,
channels: Vec<EvidenceChannel>,
}
impl RightsVerification {
#[must_use]
pub fn found(&self) -> bool {
self.found
}
#[must_use]
pub fn copyright_holder(&self) -> Option<&str> {
self.copyright_holder.as_deref()
}
#[must_use]
pub fn creator(&self) -> Option<&str> {
self.creator.as_deref()
}
#[must_use]
pub fn contact(&self) -> Option<&str> {
self.contact.as_deref()
}
#[must_use]
pub fn rights_url(&self) -> Option<&str> {
self.rights_url.as_deref()
}
#[must_use]
pub fn usage_terms(&self) -> Option<&str> {
self.usage_terms.as_deref()
}
#[must_use]
pub fn ai_constraints(&self) -> Option<&str> {
self.ai_constraints.as_deref()
}
#[must_use]
pub fn dmi(&self) -> Option<u8> {
self.dmi
}
#[must_use]
pub fn source(&self) -> FieldSource {
self.source
}
#[must_use]
pub fn channels(&self) -> &[EvidenceChannel] {
&self.channels
}
pub fn builder() -> RightsVerificationBuilder {
RightsVerificationBuilder {
found: false,
copyright_holder: None,
creator: None,
contact: None,
rights_url: None,
usage_terms: None,
ai_constraints: None,
dmi: None,
source: FieldSource::Xmp,
channels: Vec::new(),
}
}
}
pub struct RightsVerificationBuilder {
found: bool,
copyright_holder: Option<String>,
creator: Option<String>,
contact: Option<String>,
rights_url: Option<String>,
usage_terms: Option<String>,
ai_constraints: Option<String>,
dmi: Option<u8>,
source: FieldSource,
channels: Vec<EvidenceChannel>,
}
impl RightsVerificationBuilder {
#[must_use]
pub fn found(mut self, found: bool) -> Self {
self.found = found;
self
}
#[must_use]
pub fn copyright_holder(mut self, val: impl Into<String>) -> Self {
self.copyright_holder = Some(val.into());
self
}
#[must_use]
pub fn creator(mut self, val: impl Into<String>) -> Self {
self.creator = Some(val.into());
self
}
#[must_use]
pub fn contact(mut self, val: impl Into<String>) -> Self {
self.contact = Some(val.into());
self
}
#[must_use]
pub fn rights_url(mut self, val: impl Into<String>) -> Self {
self.rights_url = Some(val.into());
self
}
#[must_use]
pub fn usage_terms(mut self, val: impl Into<String>) -> Self {
self.usage_terms = Some(val.into());
self
}
#[must_use]
pub fn ai_constraints(mut self, val: impl Into<String>) -> Self {
self.ai_constraints = Some(val.into());
self
}
#[must_use]
pub fn dmi(mut self, val: u8) -> Self {
self.dmi = Some(val);
self
}
#[must_use]
pub fn source(mut self, source: FieldSource) -> Self {
self.source = source;
self
}
#[must_use]
pub fn channels(mut self, channels: Vec<EvidenceChannel>) -> Self {
self.channels = channels;
self
}
#[must_use]
pub fn build(self) -> RightsVerification {
RightsVerification {
found: self.found,
copyright_holder: self.copyright_holder,
creator: self.creator,
contact: self.contact,
rights_url: self.rights_url,
usage_terms: self.usage_terms,
ai_constraints: self.ai_constraints,
dmi: self.dmi,
source: self.source,
channels: self.channels,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HiddenMarkerVerification {
status: VerificationStatus,
payload_version: Option<u8>,
seed: Option<u64>,
intensity: Option<f32>,
source: FieldSource,
tiled: bool,
}
impl HiddenMarkerVerification {
#[must_use]
pub fn status(&self) -> VerificationStatus {
self.status
}
#[must_use]
pub fn payload_version(&self) -> Option<u8> {
self.payload_version
}
#[must_use]
pub fn seed(&self) -> Option<u64> {
self.seed
}
#[must_use]
pub fn intensity(&self) -> Option<f32> {
self.intensity
}
#[must_use]
pub fn source(&self) -> FieldSource {
self.source
}
#[must_use]
pub fn tiled(&self) -> bool {
self.tiled
}
pub fn builder() -> HiddenMarkerVerificationBuilder {
HiddenMarkerVerificationBuilder {
status: VerificationStatus::NotFound,
payload_version: None,
seed: None,
intensity: None,
source: FieldSource::Xmp,
tiled: false,
}
}
}
pub struct HiddenMarkerVerificationBuilder {
status: VerificationStatus,
payload_version: Option<u8>,
seed: Option<u64>,
intensity: Option<f32>,
source: FieldSource,
tiled: bool,
}
impl HiddenMarkerVerificationBuilder {
#[must_use]
pub fn status(mut self, status: VerificationStatus) -> Self {
self.status = status;
self
}
#[must_use]
pub fn payload_version(mut self, version: u8) -> Self {
self.payload_version = Some(version);
self
}
#[must_use]
pub fn seed(mut self, seed: u64) -> Self {
self.seed = Some(seed);
self
}
#[must_use]
pub fn intensity(mut self, intensity: f32) -> Self {
self.intensity = Some(intensity);
self
}
#[must_use]
pub fn source(mut self, source: FieldSource) -> Self {
self.source = source;
self
}
#[must_use]
pub fn tiled(mut self, tiled: bool) -> Self {
self.tiled = tiled;
self
}
#[must_use]
pub fn build(self) -> HiddenMarkerVerification {
HiddenMarkerVerification {
status: self.status,
payload_version: self.payload_version,
seed: self.seed,
intensity: self.intensity,
source: self.source,
tiled: self.tiled,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuthenticationVerification {
attempted: bool,
hmac_status: Option<VerificationStatus>,
key_id: Option<Vec<u8>>,
algorithm: String,
key_matched: bool,
}
impl AuthenticationVerification {
#[must_use]
pub fn attempted(&self) -> bool {
self.attempted
}
#[must_use]
pub fn hmac_status(&self) -> Option<VerificationStatus> {
self.hmac_status
}
#[must_use]
pub fn key_id(&self) -> Option<&[u8]> {
self.key_id.as_deref()
}
#[must_use]
pub fn algorithm(&self) -> &str {
&self.algorithm
}
#[must_use]
pub fn key_matched(&self) -> bool {
self.key_matched
}
pub fn builder() -> AuthenticationVerificationBuilder {
AuthenticationVerificationBuilder {
attempted: false,
hmac_status: None,
key_id: None,
algorithm: String::new(),
key_matched: false,
}
}
}
pub struct AuthenticationVerificationBuilder {
attempted: bool,
hmac_status: Option<VerificationStatus>,
key_id: Option<Vec<u8>>,
algorithm: String,
key_matched: bool,
}
impl AuthenticationVerificationBuilder {
#[must_use]
pub fn attempted(mut self, attempted: bool) -> Self {
self.attempted = attempted;
self
}
#[must_use]
pub fn hmac_status(mut self, status: VerificationStatus) -> Self {
self.hmac_status = Some(status);
self
}
#[must_use]
pub fn key_id(mut self, key_id: Vec<u8>) -> Self {
self.key_id = Some(key_id);
self
}
#[must_use]
pub fn algorithm(mut self, algorithm: impl Into<String>) -> Self {
self.algorithm = algorithm.into();
self
}
#[must_use]
pub fn key_matched(mut self, matched: bool) -> Self {
self.key_matched = matched;
self
}
#[must_use]
pub fn build(self) -> AuthenticationVerification {
AuthenticationVerification {
attempted: self.attempted,
hmac_status: self.hmac_status,
key_id: self.key_id,
algorithm: self.algorithm,
key_matched: self.key_matched,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SignatureVerification {
present: bool,
structurally_valid: bool,
cryptographically_valid: bool,
public_key_id: Option<Vec<u8>>,
expected_key_id: Option<Vec<u8>>,
key_id_matched: bool,
trusted: bool,
claim: Option<Vec<u8>>,
source: FieldSource,
}
impl SignatureVerification {
#[must_use]
pub fn present(&self) -> bool {
self.present
}
#[must_use]
pub fn structurally_valid(&self) -> bool {
self.structurally_valid
}
#[must_use]
pub fn cryptographically_valid(&self) -> bool {
self.cryptographically_valid
}
#[must_use]
pub fn public_key_id(&self) -> Option<&[u8]> {
self.public_key_id.as_deref()
}
#[must_use]
pub fn expected_key_id(&self) -> Option<&[u8]> {
self.expected_key_id.as_deref()
}
#[must_use]
pub fn key_id_matched(&self) -> bool {
self.key_id_matched
}
#[must_use]
pub fn trusted(&self) -> bool {
self.trusted
}
#[must_use]
pub fn claim(&self) -> Option<&[u8]> {
self.claim.as_deref()
}
#[must_use]
pub fn source(&self) -> FieldSource {
self.source
}
pub fn builder() -> SignatureVerificationBuilder {
SignatureVerificationBuilder {
present: false,
structurally_valid: false,
cryptographically_valid: false,
public_key_id: None,
expected_key_id: None,
key_id_matched: false,
trusted: false,
claim: None,
source: FieldSource::Xmp,
}
}
}
pub struct SignatureVerificationBuilder {
present: bool,
structurally_valid: bool,
cryptographically_valid: bool,
public_key_id: Option<Vec<u8>>,
expected_key_id: Option<Vec<u8>>,
key_id_matched: bool,
trusted: bool,
claim: Option<Vec<u8>>,
source: FieldSource,
}
impl SignatureVerificationBuilder {
#[must_use]
pub fn present(mut self, present: bool) -> Self {
self.present = present;
self
}
#[must_use]
pub fn structurally_valid(mut self, valid: bool) -> Self {
self.structurally_valid = valid;
self
}
#[must_use]
pub fn cryptographically_valid(mut self, valid: bool) -> Self {
self.cryptographically_valid = valid;
self
}
#[must_use]
pub fn public_key_id(mut self, key_id: Vec<u8>) -> Self {
self.public_key_id = Some(key_id);
self
}
#[must_use]
pub fn expected_key_id(mut self, key_id: Vec<u8>) -> Self {
self.expected_key_id = Some(key_id);
self
}
#[must_use]
pub fn key_id_matched(mut self, matched: bool) -> Self {
self.key_id_matched = matched;
self
}
#[must_use]
pub fn trusted(mut self, trusted: bool) -> Self {
self.trusted = trusted;
self
}
#[must_use]
pub fn claim(mut self, claim: Vec<u8>) -> Self {
self.claim = Some(claim);
self
}
#[must_use]
pub fn source(mut self, source: FieldSource) -> Self {
self.source = source;
self
}
#[must_use]
pub fn build(self) -> SignatureVerification {
SignatureVerification {
present: self.present,
structurally_valid: self.structurally_valid,
cryptographically_valid: self.cryptographically_valid,
public_key_id: self.public_key_id,
expected_key_id: self.expected_key_id,
key_id_matched: self.key_id_matched,
trusted: self.trusted,
claim: self.claim,
source: self.source,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BindingVerification {
instance_digest_present: bool,
instance_digest_valid: bool,
content_hash_present: bool,
content_hash_valid: bool,
format_valid: bool,
dimensions_valid: bool,
file_size_valid: bool,
source: FieldSource,
}
impl BindingVerification {
#[must_use]
pub fn instance_digest_present(&self) -> bool {
self.instance_digest_present
}
#[must_use]
pub fn instance_digest_valid(&self) -> bool {
self.instance_digest_valid
}
#[must_use]
pub fn content_hash_present(&self) -> bool {
self.content_hash_present
}
#[must_use]
pub fn content_hash_valid(&self) -> bool {
self.content_hash_valid
}
#[must_use]
pub fn format_valid(&self) -> bool {
self.format_valid
}
#[must_use]
pub fn dimensions_valid(&self) -> bool {
self.dimensions_valid
}
#[must_use]
pub fn file_size_valid(&self) -> bool {
self.file_size_valid
}
#[must_use]
pub fn source(&self) -> FieldSource {
self.source
}
pub fn builder() -> BindingVerificationBuilder {
BindingVerificationBuilder {
instance_digest_present: false,
instance_digest_valid: false,
content_hash_present: false,
content_hash_valid: false,
format_valid: false,
dimensions_valid: false,
file_size_valid: false,
source: FieldSource::Xmp,
}
}
}
pub struct BindingVerificationBuilder {
instance_digest_present: bool,
instance_digest_valid: bool,
content_hash_present: bool,
content_hash_valid: bool,
format_valid: bool,
dimensions_valid: bool,
file_size_valid: bool,
source: FieldSource,
}
impl BindingVerificationBuilder {
#[must_use]
pub fn instance_digest_present(mut self, present: bool) -> Self {
self.instance_digest_present = present;
self
}
#[must_use]
pub fn instance_digest_valid(mut self, valid: bool) -> Self {
self.instance_digest_valid = valid;
self
}
#[must_use]
pub fn content_hash_present(mut self, present: bool) -> Self {
self.content_hash_present = present;
self
}
#[must_use]
pub fn content_hash_valid(mut self, valid: bool) -> Self {
self.content_hash_valid = valid;
self
}
#[must_use]
pub fn format_valid(mut self, valid: bool) -> Self {
self.format_valid = valid;
self
}
#[must_use]
pub fn dimensions_valid(mut self, valid: bool) -> Self {
self.dimensions_valid = valid;
self
}
#[must_use]
pub fn file_size_valid(mut self, valid: bool) -> Self {
self.file_size_valid = valid;
self
}
#[must_use]
pub fn source(mut self, source: FieldSource) -> Self {
self.source = source;
self
}
#[must_use]
pub fn build(self) -> BindingVerification {
BindingVerification {
instance_digest_present: self.instance_digest_present,
instance_digest_valid: self.instance_digest_valid,
content_hash_present: self.content_hash_present,
content_hash_valid: self.content_hash_valid,
format_valid: self.format_valid,
dimensions_valid: self.dimensions_valid,
file_size_valid: self.file_size_valid,
source: self.source,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TrustEvaluation {
trust_model: String,
trusted: bool,
reason: String,
}
impl TrustEvaluation {
#[must_use]
pub fn trust_model(&self) -> &str {
&self.trust_model
}
#[must_use]
pub fn trusted(&self) -> bool {
self.trusted
}
#[must_use]
pub fn reason(&self) -> &str {
&self.reason
}
pub fn builder() -> TrustEvaluationBuilder {
TrustEvaluationBuilder {
trust_model: String::new(),
trusted: false,
reason: String::new(),
}
}
}
pub struct TrustEvaluationBuilder {
trust_model: String,
trusted: bool,
reason: String,
}
impl TrustEvaluationBuilder {
#[must_use]
pub fn trust_model(mut self, model: impl Into<String>) -> Self {
self.trust_model = model.into();
self
}
#[must_use]
pub fn trusted(mut self, trusted: bool) -> Self {
self.trusted = trusted;
self
}
#[must_use]
pub fn reason(mut self, reason: impl Into<String>) -> Self {
self.reason = reason.into();
self
}
#[must_use]
pub fn build(self) -> TrustEvaluation {
TrustEvaluation {
trust_model: self.trust_model,
trusted: self.trusted,
reason: self.reason,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Diagnostic {
level: DiagnosticLevel,
message: String,
source: String,
}
impl Diagnostic {
#[must_use]
pub fn level(&self) -> DiagnosticLevel {
self.level
}
#[must_use]
pub fn message(&self) -> &str {
&self.message
}
#[must_use]
pub fn source(&self) -> &str {
&self.source
}
pub fn builder() -> DiagnosticBuilder {
DiagnosticBuilder {
level: DiagnosticLevel::Info,
message: String::new(),
source: String::new(),
}
}
}
pub struct DiagnosticBuilder {
level: DiagnosticLevel,
message: String,
source: String,
}
impl DiagnosticBuilder {
#[must_use]
pub fn level(mut self, level: DiagnosticLevel) -> Self {
self.level = level;
self
}
#[must_use]
pub fn message(mut self, message: impl Into<String>) -> Self {
self.message = message.into();
self
}
#[must_use]
pub fn source(mut self, source: impl Into<String>) -> Self {
self.source = source.into();
self
}
#[must_use]
pub fn build(self) -> Diagnostic {
Diagnostic {
level: self.level,
message: self.message,
source: self.source,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum DiagnosticLevel {
Info,
Warning,
Error,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VerificationReport {
pub(crate) rights: RightsVerification,
pub(crate) hidden_marker: HiddenMarkerVerification,
pub(crate) authentication: AuthenticationVerification,
pub(crate) signatures: Vec<SignatureVerification>,
pub(crate) bindings: BindingVerification,
pub(crate) trust: TrustEvaluation,
pub(crate) evidence_strength: EvidenceStrength,
pub(crate) diagnostics: Vec<Diagnostic>,
}
impl VerificationReport {
#[must_use]
pub fn rights(&self) -> &RightsVerification {
&self.rights
}
#[must_use]
pub fn hidden_marker(&self) -> &HiddenMarkerVerification {
&self.hidden_marker
}
#[must_use]
pub fn authentication(&self) -> &AuthenticationVerification {
&self.authentication
}
#[must_use]
pub fn signatures(&self) -> &[SignatureVerification] {
&self.signatures
}
#[must_use]
pub fn bindings(&self) -> &BindingVerification {
&self.bindings
}
#[must_use]
pub fn trust(&self) -> &TrustEvaluation {
&self.trust
}
#[must_use]
pub fn evidence_strength(&self) -> EvidenceStrength {
self.evidence_strength
}
#[must_use]
pub fn diagnostics(&self) -> &[Diagnostic] {
&self.diagnostics
}
pub fn compute_evidence_strength(&self) -> EvidenceStrength {
let has_notice = self.rights.found;
let stego_verified = self.hidden_marker.status() == VerificationStatus::Verified;
let authenticated = self.authentication.attempted()
&& self.authentication.hmac_status() == Some(VerificationStatus::Verified)
&& self.authentication.key_matched();
match (has_notice, stego_verified, authenticated) {
(true, true, true) => EvidenceStrength::MetadataNoticeAndAuthenticatedProvenance,
(true, true, false) => EvidenceStrength::MetadataNoticeAndBestEffortStego,
(true, false, _) => EvidenceStrength::MetadataNoticeOnly,
(false, _, _) => EvidenceStrength::NoNoticeFound,
}
}
pub fn summary_status(&self) -> VerificationStatus {
match self.hidden_marker.status() {
VerificationStatus::Verified => VerificationStatus::Verified,
VerificationStatus::Invalid => VerificationStatus::Invalid,
VerificationStatus::NotFound => {
if self.rights.found() {
VerificationStatus::Verified
} else {
VerificationStatus::NotFound
}
}
}
}
pub fn has_errors(&self) -> bool {
self.diagnostics
.iter()
.any(|d| d.level() == DiagnosticLevel::Error)
|| self.hidden_marker.status() == VerificationStatus::Invalid
|| (self.authentication.attempted()
&& self.authentication.hmac_status() == Some(VerificationStatus::Invalid))
}
pub fn builder() -> crate::verification::VerificationReportBuilder {
crate::verification::VerificationReportBuilder::new()
}
pub fn from_notice_verification(notice: &crate::types::NoticeVerification) -> Self {
use crate::types::VerificationStatus;
let rights = RightsVerification::builder()
.found(notice.has_notice())
.source(if notice.channels().is_empty() {
FieldSource::Xmp
} else {
FieldSource::Legacy
})
.channels(notice.channels().to_vec());
let rights = if let Some(h) = notice.copyright_holder() {
rights.copyright_holder(h)
} else {
rights
};
let rights = if let Some(c) = notice.creator() {
rights.creator(c)
} else {
rights
};
let rights = if let Some(c) = notice.contact() {
rights.contact(c)
} else {
rights
};
let rights = if let Some(u) = notice.usage_terms() {
rights.usage_terms(u)
} else {
rights
};
let rights = if let Some(a) = notice.ai_constraints() {
rights.ai_constraints(a)
} else {
rights
};
let rights = if let Some(d) = notice.dmi() {
rights.dmi(d as u8)
} else {
rights
};
let rights = rights.build();
let mut hidden_marker = HiddenMarkerVerification::builder()
.status(notice.stego_status())
.source(if notice.stego_payload().is_some() {
match notice.stego_payload().unwrap().version() {
1 => FieldSource::EmbeddedPayloadV1,
2 => FieldSource::EmbeddedPayloadV2,
3 => FieldSource::EmbeddedPayloadV3,
_ => FieldSource::Xmp,
}
} else {
FieldSource::Xmp
});
if let Some(seed) = notice.protection_seed() {
hidden_marker = hidden_marker.seed(seed);
}
let hidden_marker = hidden_marker.build();
let authentication = AuthenticationVerification::builder()
.attempted(
notice.authenticated() || notice.stego_status() == VerificationStatus::Verified,
)
.hmac_status(if notice.authenticated() {
VerificationStatus::Verified
} else if notice.stego_status() == VerificationStatus::Invalid {
VerificationStatus::Invalid
} else {
VerificationStatus::NotFound
})
.build();
let trust = TrustEvaluation::builder()
.trust_model("caller-owned")
.trusted(false)
.reason("Trust evaluation requires an explicit key/trust policy".to_string())
.build();
Self {
rights,
hidden_marker,
authentication,
signatures: Vec::new(),
bindings: BindingVerification::builder().build(),
trust,
evidence_strength: notice.evidence_strength(),
diagnostics: Vec::new(),
}
}
}