c2pa-rs

A fork of https://github.com/contentauth/c2pa-rs/
git clone git://archive.git.mtrnord.blog/mtrnords-photography-manager/c2pa-rs.git
Log | Files | Refs | README

labels.rs (9038B)


      1 // Copyright 2022 Adobe. All rights reserved.
      2 // This file is licensed to you under the Apache License,
      3 // Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
      4 // or the MIT license (http://opensource.org/licenses/MIT),
      5 // at your option.
      6 
      7 // Unless required by applicable law or agreed to in writing,
      8 // this software is distributed on an "AS IS" BASIS, WITHOUT
      9 // WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or
     10 // implied. See the LICENSE-MIT and LICENSE-APACHE files for the
     11 // specific language governing permissions and limitations under
     12 // each license.
     13 
     14 #![deny(missing_docs)]
     15 
     16 //! Labels for JUMBF boxes as defined in C2PA 1.0 Specification.
     17 //!
     18 //! See <https://c2pa.org/specifications/specifications/1.0/specs/C2PA_Specification.html#_c2pa_box_details>.
     19 
     20 /// Label for the C2PA manifest store.
     21 ///
     22 /// This value should be used when possible, since it may contain a version suffix
     23 /// when needed to support a future version of the spec.
     24 ///
     25 /// See <https://c2pa.org/specifications/specifications/1.0/specs/C2PA_Specification.html#_c2pa_box_details>.
     26 pub const MANIFEST_STORE: &str = "c2pa";
     27 
     28 /// Label for the C2PA assertion store box.
     29 ///
     30 /// See <https://c2pa.org/specifications/specifications/1.0/specs/C2PA_Specification.html#_c2pa_box_details>.
     31 pub const ASSERTIONS: &str = "c2pa.assertions";
     32 
     33 /// Label for the C2PA claim box.
     34 ///
     35 /// See <https://c2pa.org/specifications/specifications/1.0/specs/C2PA_Specification.html#_c2pa_box_details>.
     36 pub const CLAIM: &str = "c2pa.claim";
     37 
     38 /// Label for the C2PA claim signature box.
     39 ///
     40 /// See <https://c2pa.org/specifications/specifications/1.0/specs/C2PA_Specification.html#_c2pa_box_details>.
     41 pub const SIGNATURE: &str = "c2pa.signature";
     42 
     43 /// Label for the credentials store box.
     44 ///
     45 /// See <https://c2pa.org/specifications/specifications/1.0/specs/C2PA_Specification.html#_credential_storage>.
     46 pub const CREDENTIALS: &str = "c2pa.credentials";
     47 
     48 /// Label for the DataBox box.
     49 ///
     50 /// See <https://c2pa.org/specifications/specifications/1.3/specs/C2PA_Specification.html#_data_boxes>.
     51 pub const DATABOX: &str = "c2pa.data";
     52 
     53 /// Label for the DataBox store box.
     54 ///
     55 /// See <https://c2pa.org/specifications/specifications/1.3/specs/C2PA_Specification.html#_data_storage>.
     56 pub const DATABOXES: &str = "c2pa.databoxes";
     57 
     58 const JUMBF_PREFIX: &str = "self#jumbf";
     59 
     60 // Converts a manifest label to a JUMBF URI.
     61 pub(crate) fn to_manifest_uri(manifest_label: &str) -> String {
     62     format!("{JUMBF_PREFIX}=/{MANIFEST_STORE}/{manifest_label}")
     63 }
     64 
     65 // Converts a manifest label and an assertion label into a JUMBF URI.
     66 pub(crate) fn to_assertion_uri(manifest_label: &str, assertion_label: &str) -> String {
     67     format!(
     68         "{}/{}/{}",
     69         to_manifest_uri(manifest_label),
     70         ASSERTIONS,
     71         assertion_label
     72     )
     73 }
     74 
     75 // Converts a manifest label to a JUMBF URI for its signature.
     76 pub(crate) fn to_signature_uri(manifest_label: &str) -> String {
     77     format!("{}/{}", to_manifest_uri(manifest_label), SIGNATURE)
     78 }
     79 
     80 // Converts a manifest label and an assertion label to a JUMBF
     81 // verifiable credential URL.
     82 pub(crate) fn to_verifiable_credential_uri(manifest_label: &str, vc_id: &str) -> String {
     83     // TO CONSIDER: Does this now belong in jumbf::labels?
     84     format!(
     85         "{}/{}/{}",
     86         to_manifest_uri(manifest_label),
     87         CREDENTIALS,
     88         vc_id
     89     )
     90 }
     91 
     92 // Converts a manifest label and a DataBox label to a JUMBF
     93 // HashedURI.
     94 pub(crate) fn to_databox_uri(manifest_label: &str, databox_id: &str) -> String {
     95     // TO CONSIDER: Does this now belong in jumbf::labels?
     96     format!(
     97         "{}/{}/{}",
     98         to_manifest_uri(manifest_label),
     99         DATABOXES,
    100         databox_id
    101     )
    102 }
    103 
    104 // Split off JUMBF prefix.
    105 pub(crate) fn to_normalized_uri(uri: &str) -> String {
    106     let uri_parts: Vec<&str> = uri.split('=').collect();
    107 
    108     let output = if uri_parts.len() == 1 {
    109         uri_parts[0].to_string()
    110     } else {
    111         uri_parts[1].to_string()
    112     };
    113 
    114     // Add leading "/" if needed.
    115     let mut manifest_store_part = MANIFEST_STORE.to_string();
    116     manifest_store_part.push('/');
    117 
    118     if !output.is_empty() && output.starts_with(&manifest_store_part) {
    119         format!("{}{}", "/", output)
    120     } else {
    121         output
    122     }
    123 }
    124 
    125 // Converts a possibly relative JUMBF URI to an absolute URI to the manifest store.
    126 pub(crate) fn to_absolute_uri(manifest_label: &str, uri: &str) -> String {
    127     let raw_uri = to_normalized_uri(uri);
    128     let parts: Vec<&str> = raw_uri.split('/').collect();
    129     if parts.len() > 2 && parts[1] == MANIFEST_STORE {
    130         uri.to_string()
    131     } else {
    132         format!("{}/{}", to_manifest_uri(manifest_label), raw_uri)
    133     }
    134 }
    135 
    136 // Converts an absolute JUMBF URI to a URI relative to the manifest store.
    137 pub(crate) fn to_relative_uri(uri: &str) -> String {
    138     let raw_uri = to_normalized_uri(uri);
    139     let parts: Vec<&str> = raw_uri.split('/').collect();
    140 
    141     if parts.len() > 4 && parts[1] == MANIFEST_STORE {
    142         format!("{}={}", JUMBF_PREFIX, parts[3..].join("/"))
    143     } else {
    144         // Doesn't look like an absolute URI, so we'll return it as-is.
    145         uri.to_string()
    146     }
    147 }
    148 
    149 // Given a JUMBF URI, return the manifest label contained within it.
    150 pub(crate) fn manifest_label_from_uri(uri: &str) -> Option<String> {
    151     let raw_uri = to_normalized_uri(uri);
    152     let parts: Vec<&str> = raw_uri.split('/').collect();
    153     if parts.len() > 2 && parts[1] == MANIFEST_STORE {
    154         Some(parts[2].to_string())
    155     } else {
    156         None
    157     }
    158 }
    159 
    160 // Extract an assertion label from a JUMBF URI.
    161 pub(crate) fn assertion_label_from_uri(uri: &str) -> Option<String> {
    162     let raw_uri = to_normalized_uri(uri);
    163     let parts: Vec<&str> = raw_uri.split('/').collect();
    164     if parts.len() > 4 && parts[1] == MANIFEST_STORE && parts[3] == ASSERTIONS {
    165         Some(parts[4].to_string())
    166     } else if parts[0] == ASSERTIONS {
    167         Some(parts[1].to_string())
    168     } else {
    169         None
    170     }
    171 }
    172 
    173 // Extract the box the label points to.
    174 pub(crate) fn box_name_from_uri(uri: &str) -> Option<String> {
    175     let raw_uri = to_normalized_uri(uri);
    176     let parts: Vec<&str> = raw_uri.split('/').collect();
    177 
    178     parts.last().map(|b| b.to_string())
    179 }
    180 
    181 #[cfg(test)]
    182 pub mod tests {
    183     #![allow(clippy::unwrap_used)]
    184 
    185     use super::*;
    186 
    187     #[test]
    188     fn test_manifest_uri() {
    189         assert_eq!(
    190             to_manifest_uri("acme::urn:uuid::123:456:789"),
    191             "self#jumbf=/c2pa/acme::urn:uuid::123:456:789"
    192         );
    193     }
    194 
    195     #[test]
    196     fn test_assertion_uri() {
    197         assert_eq!(
    198             to_assertion_uri("acme::urn:uuid::123:456:789", "c2pa.thumbnail.claim.jpeg"),
    199             "self#jumbf=/c2pa/acme::urn:uuid::123:456:789/c2pa.assertions/c2pa.thumbnail.claim.jpeg"
    200         );
    201     }
    202 
    203     #[test]
    204     fn test_signature_uri() {
    205         assert_eq!(
    206             to_signature_uri("acme::urn:uuid::123:456:789"),
    207             "self#jumbf=/c2pa/acme::urn:uuid::123:456:789/c2pa.signature"
    208         );
    209     }
    210 
    211     #[test]
    212     fn test_verifiable_credential_uri() {
    213         assert_eq!(
    214             to_verifiable_credential_uri("acme::urn:uuid::123:456:789", "12315142234@acme.com"),
    215             "self#jumbf=/c2pa/acme::urn:uuid::123:456:789/c2pa.credentials/12315142234@acme.com"
    216         );
    217     }
    218 
    219     #[test]
    220     fn test_relative_uri() {
    221         assert_eq!(
    222             to_relative_uri(
    223                 "self#jumbf=/c2pa/acme::urn:uuid::123:456:789/c2pa.assertions/c2pa.thumbnail.claim.jpeg"
    224             ),
    225             "self#jumbf=c2pa.assertions/c2pa.thumbnail.claim.jpeg"
    226         );
    227     }
    228 
    229     #[test]
    230     fn test_paths() {
    231         let manifest = "acme::urn:uuid::123:456:789";
    232         let assertion = "c2pa.thumbnail.claim.jpeg";
    233         let empty_uri = "";
    234         let absolute_uri = to_manifest_uri(manifest);
    235 
    236         let raw_uri = to_normalized_uri(&absolute_uri);
    237 
    238         let raw_uri_no_slash =
    239             to_normalized_uri(&format!("{JUMBF_PREFIX}={MANIFEST_STORE}/{manifest}"));
    240 
    241         let raw_empty_uri = to_normalized_uri(empty_uri);
    242 
    243         assert_eq!(raw_uri, raw_uri_no_slash);
    244         assert_eq!(raw_empty_uri, "");
    245 
    246         let manifest_label_from_absolute = manifest_label_from_uri(&absolute_uri);
    247         let manifest_label_from_nomalized = manifest_label_from_uri(&raw_uri);
    248 
    249         assert_eq!(manifest_label_from_absolute, manifest_label_from_nomalized);
    250 
    251         let assertion_uri = to_assertion_uri(manifest, assertion);
    252 
    253         assert_eq!(
    254             Some(manifest.to_string()),
    255             manifest_label_from_uri(&assertion_uri)
    256         );
    257         assert_eq!(
    258             Some(assertion.to_string()),
    259             assertion_label_from_uri(&assertion_uri)
    260         );
    261         assert_eq!(None, assertion_label_from_uri(&absolute_uri));
    262 
    263         let assertion_relative = to_relative_uri(&assertion_uri);
    264 
    265         assert_eq!(
    266             assertion_relative,
    267             format!("{JUMBF_PREFIX}={ASSERTIONS}/{assertion}")
    268         );
    269         assert_eq!(
    270             Some(assertion.to_string()),
    271             assertion_label_from_uri(&assertion_relative)
    272         );
    273     }
    274 }