cawg_identity/builder/
identity_assertion_builder.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
// Copyright 2024 Adobe. All rights reserved.
// This file is licensed to you under the Apache License,
// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
// or the MIT license (http://opensource.org/licenses/MIT),
// at your option.

// Unless required by applicable law or agreed to in writing,
// this software is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or
// implied. See the LICENSE-MIT and LICENSE-APACHE files for the
// specific language governing permissions and limitations under
// each license.

use async_trait::async_trait;
use c2pa::{DynamicAssertion, PreliminaryClaim};
use serde_bytes::ByteBuf;

use crate::{builder::CredentialHolder, IdentityAssertion, SignerPayload};

/// An `IdentityAssertionBuilder` gathers together the necessary components
/// for an identity assertion. When added to an [`IdentityAssertionSigner`],
/// it ensures that the proper data is added to the final C2PA Manifest.
///
/// [`IdentityAssertionSigner`]: crate::builder::IdentityAssertionSigner
pub struct IdentityAssertionBuilder {
    #[cfg(not(target_arch = "wasm32"))]
    credential_holder: Box<dyn CredentialHolder + Sync + Send>,

    #[cfg(target_arch = "wasm32")]
    credential_holder: Box<dyn CredentialHolder>,
    // referenced_assertions: Vec<MumbleSomething>,
}

impl IdentityAssertionBuilder {
    /// Create an `IdentityAssertionBuilder` for the given
    /// `CredentialHolder` instance.
    pub fn for_credential_holder<CH: CredentialHolder + 'static>(credential_holder: CH) -> Self {
        Self {
            credential_holder: Box::new(credential_holder),
        }
    }
}

#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
impl DynamicAssertion for IdentityAssertionBuilder {
    fn label(&self) -> String {
        "cawg.identity".to_string()
    }

    fn reserve_size(&self) -> usize {
        self.credential_holder.reserve_size()
        // TO DO: Credential holder will state reserve size for signature.
        // Add additional size for CBOR wrapper outside signature.
    }

    async fn content_async(
        &self,
        _label: &str,
        size: Option<usize>,
        claim: &PreliminaryClaim,
    ) -> c2pa::Result<Vec<u8>> {
        // TO DO: Better filter for referenced assertions.
        // For now, just require hard binding.

        // TO DO: Update to respond correctly when identity assertions refer to each
        // other.
        let referenced_assertions = claim
            .assertions()
            .filter(|a| a.url().contains("c2pa.assertions/c2pa.hash."))
            .cloned()
            .collect();

        let signer_payload = SignerPayload {
            referenced_assertions,
            sig_type: self.credential_holder.sig_type().to_owned(),
        };

        let signature = self
            .credential_holder
            .sign(&signer_payload)
            .await
            .map_err(|e| c2pa::Error::BadParam(e.to_string()))?;
        // TO DO: Think through how errors map into c2pa::Error.

        let mut ia = IdentityAssertion {
            signer_payload,
            signature,
            pad1: vec![],
            pad2: None,
        };

        let mut assertion_cbor: Vec<u8> = vec![];
        ciborium::into_writer(&ia, &mut assertion_cbor)
            .map_err(|e| c2pa::Error::BadParam(e.to_string()))?;
        // TO DO: Think through how errors map into c2pa::Error.

        if let Some(assertion_size) = size {
            if assertion_cbor.len() > assertion_size {
                // TO DO: Think about how to signal this in such a way that
                // the CredentialHolder implementor understands the problem.
                return Err(c2pa::Error::BadParam(format!("Serialized assertion is {len} bytes, which exceeds the planned size of {assertion_size} bytes", len = assertion_cbor.len())));
            }

            ia.pad1 = vec![0u8; assertion_size - assertion_cbor.len() - 15];

            assertion_cbor.clear();
            ciborium::into_writer(&ia, &mut assertion_cbor)
                .map_err(|e| c2pa::Error::BadParam(e.to_string()))?;
            // TO DO: Think through how errors map into c2pa::Error.

            ia.pad2 = Some(ByteBuf::from(vec![
                0u8;
                assertion_size - assertion_cbor.len() - 6
            ]));

            assertion_cbor.clear();
            ciborium::into_writer(&ia, &mut assertion_cbor)
                .map_err(|e| c2pa::Error::BadParam(e.to_string()))?;
            // TO DO: Think through how errors map into c2pa::Error.

            // TO DO: See if this approach ever fails. IMHO it "should" work for all cases.
            assert_eq!(assertion_size, assertion_cbor.len());
        }

        Ok(assertion_cbor)
    }
}