encypher-c2pa 1.0.2

Offline, verification-only C2PA SDK
Documentation
use std::sync::LazyLock;

use crate::c2pa_trust::TrustList;

pub const SNAPSHOT_DATE: &str = "2026-08-11";

const C2PA_TRUST_PEM: &str = include_str!("default_trust/c2pa-trust.pem");
const C2PA_TSA_TRUST_PEM: &str = include_str!("default_trust/c2pa-tsa-trust.pem");
const IPTC_VNPL_END_ENTITY_PEM: &str = include_str!("default_trust/iptc-vnpl-end-entity.pem");
const IPTC_VNPL_ANCHORS_PEM: &str = include_str!("default_trust/iptc-vnpl-anchors.pem");
const CAWG_MOZILLA_EMAIL_ROOTS_PEM: &str =
    include_str!("default_trust/cawg-mozilla-email-roots.pem");
const ENCYPHER_C2PA_ROOT_PEM: &str = include_str!("default_trust/encypher-c2pa-root.pem");
const ENCYPHER_TSA_ISSUING_CA_PEM: &str = include_str!("default_trust/encypher-tsa-issuing-ca.pem");
const ENCYPHER_IDENTITY_ROOT_PEM: &str = include_str!("default_trust/encypher-identity-root.pem");

static CLAIM_SIGNING: LazyLock<TrustList> =
    LazyLock::new(|| merge("claim-signing", &[C2PA_TRUST_PEM, ENCYPHER_C2PA_ROOT_PEM]));
static TIMESTAMP_AUTHORITIES: LazyLock<TrustList> = LazyLock::new(|| {
    merge(
        "timestamp-authority",
        &[C2PA_TSA_TRUST_PEM, ENCYPHER_TSA_ISSUING_CA_PEM],
    )
});
static ALLOWED_CLAIM_SIGNERS: LazyLock<TrustList> =
    LazyLock::new(|| parse("IPTC VNPL end-entity", IPTC_VNPL_END_ENTITY_PEM));
static CAWG_IDENTITY: LazyLock<TrustList> = LazyLock::new(|| {
    merge(
        "CAWG identity",
        &[
            CAWG_MOZILLA_EMAIL_ROOTS_PEM,
            IPTC_VNPL_ANCHORS_PEM,
            ENCYPHER_IDENTITY_ROOT_PEM,
        ],
    )
});
static CAWG_ALLOWED_IDENTITIES: LazyLock<TrustList> =
    LazyLock::new(|| parse("IPTC VNPL end-entity", IPTC_VNPL_END_ENTITY_PEM));

fn parse(label: &str, pem: &str) -> TrustList {
    TrustList::from_pem(pem)
        .unwrap_or_else(|error| panic!("invalid bundled {label} trust list: {error}"))
}

fn merge(label: &str, bundles: &[&str]) -> TrustList {
    let mut anchors = Vec::new();
    for pem in bundles {
        if !pem.contains("-----BEGIN CERTIFICATE-----") {
            continue;
        }
        anchors.extend(parse(label, pem).anchors);
    }
    assert!(!anchors.is_empty(), "bundled {label} trust list is empty");
    TrustList { anchors }
}

pub(crate) fn claim_signing() -> &'static TrustList {
    &CLAIM_SIGNING
}

pub(crate) fn timestamp_authorities() -> &'static TrustList {
    &TIMESTAMP_AUTHORITIES
}

pub(crate) fn allowed_claim_signers() -> &'static TrustList {
    &ALLOWED_CLAIM_SIGNERS
}

pub(crate) fn cawg_identity() -> &'static TrustList {
    &CAWG_IDENTITY
}

pub(crate) fn cawg_allowed_identities() -> &'static TrustList {
    &CAWG_ALLOWED_IDENTITIES
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn packaged_snapshot_contains_each_default_trust_source() {
        assert_eq!(SNAPSHOT_DATE, "2026-08-11");
        assert_eq!(claim_signing().anchors.len(), 30);
        assert_eq!(timestamp_authorities().anchors.len(), 22);
        assert_eq!(allowed_claim_signers().anchors.len(), 20);
        assert_eq!(cawg_identity().anchors.len(), 92);
        assert_eq!(cawg_allowed_identities().anchors.len(), 20);
    }

    #[test]
    fn caller_trust_extends_the_packaged_snapshot() {
        let resolved = crate::resolve_trust(Some(ENCYPHER_C2PA_ROOT_PEM), Some(claim_signing()))
            .unwrap()
            .unwrap();
        assert_eq!(resolved.get().anchors.len(), 31);
    }
}