commit 40fc8df617d99f7ffb76e61333856dcbafe1f882
parent f23822c4122c6f719d4cf665e4d2d0e9af9ca3ee
Author: mauricefisher64 <92736594+mauricefisher64@users.noreply.github.com>
Date: Fri, 15 Jul 2022 14:05:38 -0400
Support for asynchronous signing of claims (#57)
* Fix bug in verify_from_buffer
* make_test fixes
adjust signature box reserved size to account for large timestamps
* change tsa to tsa_url
* Fix make images in makefile
* remove debug printlns
* Async claim signing support
* fix formatting
* allow async validation with OpenSSL validators
* simplify login in cose_pad
* clippy fixes
* cargo fmt fix
* Remove old comment to resolve PR request
* Restore `bmff` feature
Co-authored-by: Gavin Peacock <gpeacock@adobe.com>
Co-authored-by: Eric Scouten <scouten@adobe.com>
Diffstat:
7 files changed, 402 insertions(+), 179 deletions(-)
diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml
@@ -17,7 +17,7 @@ all-features = true
rustdoc-args = ["--cfg", "docsrs"]
[features]
-async_signer = ["async-trait"]
+async_signer = ["async-trait", "file_io"]
bmff = [] # Work in progress support for BMFF-based containers
file_io = ["openssl"]
serialize_thumbnails = []
@@ -96,3 +96,6 @@ anyhow = "1.0.40"
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
wasm-bindgen-test = "0.3.31"
+
+[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
+actix = "0.11.0"
diff --git a/sdk/src/cose_sign.rs b/sdk/src/cose_sign.rs
@@ -16,7 +16,9 @@
#![deny(missing_docs)]
use ciborium::value::Value;
-use coset::{iana, CoseSign1, CoseSign1Builder, HeaderBuilder, Label, TaggedCborSerializable};
+use coset::{
+ iana, CoseSign1, CoseSign1Builder, Header, HeaderBuilder, Label, TaggedCborSerializable,
+};
use crate::{
claim::Claim,
@@ -25,6 +27,77 @@ use crate::{
time_stamp::{cose_timestamp_countersign, make_cose_timestamp},
Error, Result, Signer,
};
+fn build_unprotected_header(
+ data: &[u8],
+ alg: &str,
+ certs: Vec<Vec<u8>>,
+ ta_url: Option<String>,
+ ocsp_val: Option<Vec<u8>>,
+) -> Result<(Header, Header)> {
+ let alg_id = match alg {
+ "ps256" => HeaderBuilder::new()
+ .algorithm(iana::Algorithm::PS256)
+ .build(),
+ "ps384" => HeaderBuilder::new()
+ .algorithm(iana::Algorithm::PS384)
+ .build(),
+ "ps512" => HeaderBuilder::new()
+ .algorithm(iana::Algorithm::PS512)
+ .build(),
+ "es256" => HeaderBuilder::new()
+ .algorithm(iana::Algorithm::ES256)
+ .build(),
+ "es384" => HeaderBuilder::new()
+ .algorithm(iana::Algorithm::ES384)
+ .build(),
+ "es512" => HeaderBuilder::new()
+ .algorithm(iana::Algorithm::ES512)
+ .build(),
+ "ed25519" => HeaderBuilder::new()
+ .algorithm(iana::Algorithm::EdDSA)
+ .build(),
+ _ => return Err(Error::UnsupportedType),
+ };
+
+ let sc_der_array_or_bytes = match certs.len() {
+ 1 => Value::Bytes(certs[0].clone()), // single cert
+ _ => {
+ let mut sc_der_array: Vec<Value> = Vec::new();
+ for cert in certs {
+ sc_der_array.push(Value::Bytes(cert));
+ }
+ Value::Array(sc_der_array) // provide vec of certs when required
+ }
+ };
+
+ let mut unprotected = if let Some(url) = ta_url {
+ let cts = cose_timestamp_countersign(data, alg, &url)?;
+ let sigtst_vec = serde_cbor::to_vec(&make_cose_timestamp(&cts))?;
+ let sigtst_cbor = serde_cbor::from_slice(&sigtst_vec)?;
+
+ HeaderBuilder::new()
+ .text_value("x5chain".to_string(), sc_der_array_or_bytes)
+ .text_value("sigTst".to_string(), sigtst_cbor)
+ } else {
+ HeaderBuilder::new().text_value("x5chain".to_string(), sc_der_array_or_bytes)
+ };
+
+ // set the ocsp responder response if available
+ if let Some(ocsp) = ocsp_val {
+ let mut ocsp_vec: Vec<Value> = Vec::new();
+ let mut r_vals: Vec<(Value, Value)> = vec![];
+
+ ocsp_vec.push(Value::Bytes(ocsp));
+ r_vals.push((Value::Text("ocspVals".to_string()), Value::Array(ocsp_vec)));
+
+ unprotected = unprotected.text_value("rVals".to_string(), Value::Map(r_vals));
+ }
+
+ // build complete header
+ let unprotected_header = unprotected.build();
+
+ Ok((alg_id, unprotected_header))
+}
/// Generate a COSE signature for a block of bytes which must be a valid C2PA
/// claim structure.
@@ -87,85 +160,91 @@ pub(crate) fn cose_sign(signer: &dyn Signer, data: &[u8], box_size: usize) -> Re
let alg = signer.alg().ok_or(Error::UnsupportedType)?;
- let alg_id = match alg.as_ref() {
- "ps256" => HeaderBuilder::new()
- .algorithm(iana::Algorithm::PS256)
- .build(),
- "ps384" => HeaderBuilder::new()
- .algorithm(iana::Algorithm::PS384)
- .build(),
- "ps512" => HeaderBuilder::new()
- .algorithm(iana::Algorithm::PS512)
- .build(),
- "es256" => HeaderBuilder::new()
- .algorithm(iana::Algorithm::ES256)
- .build(),
- "es384" => HeaderBuilder::new()
- .algorithm(iana::Algorithm::ES384)
- .build(),
- "es512" => HeaderBuilder::new()
- .algorithm(iana::Algorithm::ES512)
- .build(),
- "ed25519" => HeaderBuilder::new()
- .algorithm(iana::Algorithm::EdDSA)
- .build(),
- _ => return Err(Error::UnsupportedType),
- };
+ // build complete header
+ let (alg_id, unprotected_header) = build_unprotected_header(
+ data,
+ &alg,
+ signer.certs()?,
+ signer.time_authority_url(),
+ signer.ocsp_val(),
+ )?;
- // Get the public CAs for the Signer.
- let certs = signer.certs()?;
- let sc_der_array_or_bytes = match certs.len() {
- 1 => Value::Bytes(certs[0].clone()), // single cert
- _ => {
- let mut sc_der_array: Vec<Value> = Vec::new();
- for cert in certs {
- sc_der_array.push(Value::Bytes(cert));
- }
- Value::Array(sc_der_array) // provide vec of certs when required
- }
- };
+ let aad = b""; // no additional data required here
- let mut unprotected = match signer.time_authority_url() {
- Some(url) => {
- let cts = cose_timestamp_countersign(data, &alg, &url)?;
- let sigtst_vec = serde_cbor::to_vec(&make_cose_timestamp(&cts))?;
- let sigtst_cbor = serde_cbor::from_slice(&sigtst_vec)?;
+ let sign1_builder = CoseSign1Builder::new()
+ .protected(alg_id)
+ .unprotected(unprotected_header)
+ .payload(data.to_vec())
+ .try_create_signature(aad, |bytes| signer.sign(bytes))?;
- HeaderBuilder::new()
- .text_value("x5chain".to_string(), sc_der_array_or_bytes)
- .text_value("sigTst".to_string(), sigtst_cbor)
- }
- None => {
- let sign_time = chrono::Utc::now().to_rfc3339(); // todo: remove when switch to cose_timestamp
- HeaderBuilder::new()
- .text_value("x5chain".to_string(), sc_der_array_or_bytes)
- .text_value("temp_signing_time".to_string(), Value::Text(sign_time))
- }
- };
+ let mut sign1 = sign1_builder.build();
+ sign1.payload = None; // clear the payload since it is known
- // set the ocsp responder response if available
- if let Some(ocsp) = signer.ocsp_val() {
- let mut ocsp_vec: Vec<Value> = Vec::new();
- let mut r_vals: Vec<(Value, Value)> = vec![];
+ let c2pa_sig_data = pad_cose_sig(&mut sign1, box_size)?;
- ocsp_vec.push(Value::Bytes(ocsp));
- r_vals.push((Value::Text("ocspVals".to_string()), Value::Array(ocsp_vec)));
+ // println!("sig: {}", Hexlify(&c2pa_sig_data));
- unprotected = unprotected.text_value("rVals".to_string(), Value::Map(r_vals));
- }
+ Ok(c2pa_sig_data)
+}
+
+/// Returns signed Cose_Sign1 bytes for "data". The Cose_Sign1 will be signed with the algorithm from `Signer`.
+#[cfg(feature = "async_signer")]
+pub async fn cose_sign_async(
+ signer: &dyn crate::AsyncSigner,
+ data: &[u8],
+ box_size: usize,
+) -> Result<Vec<u8>> {
+ // 13.2.1. X.509 Certificates
+ //
+ // X.509 Certificates are stored in a header named x5chain draft-ietf-cose-x509.
+ // The value is a CBOR array of byte strings, each of which contains the certificate
+ // encoded as ASN.1 distinguished encoding rules (DER). This array must contain at
+ // least one element. The first element of the array must be the certificate of
+ // the signer, and the subjectPublicKeyInfo element of the certificate will be the
+ // public key used to validate the signature. The Validity member of the TBSCertificate
+ // sequence provides the time validity period of the certificate.
+
+ /*
+ This header parameter allows for a single X.509 certificate or a
+ chain of X.509 certificates to be carried in the message.
+
+ * If a single certificate is conveyed, it is placed in a CBOR
+ byte string.
+
+ * If multiple certificates are conveyed, a CBOR array of byte
+ strings is used, with each certificate being in its own byte
+ string.
+ */
+
+ let alg = signer.alg().ok_or(Error::UnsupportedType)?;
// build complete header
- let unprotected_header = unprotected.build();
+ let (alg_id, unprotected_header) = build_unprotected_header(
+ data,
+ &alg,
+ signer.certs()?,
+ signer.time_authority_url(),
+ signer.ocsp_val(),
+ )?;
let aad = b""; // no additional data required here
let sign1_builder = CoseSign1Builder::new()
.protected(alg_id)
.unprotected(unprotected_header)
- .payload(data.to_vec())
- .try_create_signature(aad, |bytes| signer.sign(bytes))?;
+ .payload(data.to_vec());
let mut sign1 = sign1_builder.build();
+
+ let tbs = coset::sig_structure_data(
+ coset::SignatureContext::CoseSign1,
+ sign1.protected.clone(),
+ None,
+ aad,
+ sign1.payload.as_ref().unwrap_or(&vec![]),
+ );
+ sign1.signature = signer.sign(tbs).await?;
+
sign1.payload = None; // clear the payload since it is known
let c2pa_sig_data = pad_cose_sig(&mut sign1, box_size)?;
@@ -190,13 +269,13 @@ fn pad_cose_sig(sign1: &mut CoseSign1, end_size: usize) -> Result<Vec<u8>> {
.map_err(|_e| Error::CoseSignature)?;
let cur_size = cur_vec.len();
- // check for box too small
- match cur_size > end_size {
- true => {
- return Err(Error::CoseSigboxTooSmall);
- }
- false if cur_size == end_size => return Ok(cur_vec),
- false => (),
+ if cur_size == end_size {
+ return Ok(cur_vec);
+ }
+
+ // check for box too small and matched size
+ if cur_size + PAD_OFFSET > end_size {
+ return Err(Error::CoseSigboxTooSmall);
}
let mut padding_found = false;
@@ -235,7 +314,7 @@ fn pad_cose_sig(sign1: &mut CoseSign1, end_size: usize) -> Result<Vec<u8>> {
match new_cbor.len() < end_size {
true => target_guess += 1,
false if new_cbor.len() == end_size => return Ok(new_cbor),
- false => break, // we couuld not match end_size in a single pad so break and add a second
+ false => break, // we could not match end_size in a single pad so break and add a second
}
}
diff --git a/sdk/src/cose_validator.rs b/sdk/src/cose_validator.rs
@@ -954,12 +954,25 @@ async fn validate_with_cert_async(
#[cfg(not(target_arch = "wasm32"))]
async fn validate_with_cert_async(
- _validator_str: &str,
- _sig: &[u8],
- _data: &[u8],
- _der_bytes: &[u8],
+ validator_str: &str,
+ sig: &[u8],
+ data: &[u8],
+ der_bytes: &[u8],
) -> Result<String> {
- Err(Error::CoseSignatureAlgorithmNotSupported)
+ // get the cert in der format
+ let (_rem, signcert) =
+ X509Certificate::from_der(der_bytes).map_err(|_err| Error::CoseInvalidCert)?;
+ let pk = signcert.public_key();
+ let pk_der = pk.raw;
+
+ let validator =
+ get_validator(validator_str).ok_or(Error::CoseSignatureAlgorithmNotSupported)?;
+
+ if validator.validate(sig, data, pk_der)? {
+ Ok(extract_subject_from_cert(&signcert)?)
+ } else {
+ Err(Error::CoseSignature)
+ }
}
#[allow(unused_imports)]
#[cfg(feature = "file_io")]
diff --git a/sdk/src/openssl/mod.rs b/sdk/src/openssl/mod.rs
@@ -32,6 +32,14 @@ pub(crate) use ed_validator::EdValidator;
#[cfg(test)]
pub(crate) mod temp_signer;
+#[cfg(test)]
+pub(crate) mod temp_signer_async;
+
+#[cfg(test)]
+#[allow(unused_imports)]
+#[cfg(feature = "async_signer")]
+pub(crate) use temp_signer_async::AsyncSignerAdapter;
+
use openssl::x509::X509;
pub(crate) fn check_chain_order(certs: &[X509]) -> bool {
@@ -56,31 +64,14 @@ pub(crate) fn check_chain_order(certs: &[X509]) -> bool {
}
pub(crate) fn check_chain_order_der(cert_ders: &[Vec<u8>]) -> bool {
- if cert_ders.len() > 1 {
- let mut certs: Vec<X509> = Vec::new();
- for cert_der in cert_ders {
- if let Ok(cert) = X509::from_der(cert_der) {
- certs.push(cert);
- } else {
- return false;
- }
- }
-
- for (i, c) in certs.iter().enumerate() {
- if let Some(next_c) = certs.get(i + 1) {
- if let Ok(pkey) = next_c.public_key() {
- if let Ok(verified) = c.verify(&pkey) {
- if !verified {
- return false;
- }
- } else {
- return false;
- }
- } else {
- return false;
- }
- }
+ let mut certs: Vec<X509> = Vec::new();
+ for cert_der in cert_ders {
+ if let Ok(cert) = X509::from_der(cert_der) {
+ certs.push(cert);
+ } else {
+ return false;
}
}
- true
+
+ check_chain_order(&certs)
}
diff --git a/sdk/src/openssl/temp_signer_async.rs b/sdk/src/openssl/temp_signer_async.rs
@@ -0,0 +1,102 @@
+// Copyright 2022 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.
+
+#![deny(missing_docs)]
+
+//! Temporary async signing instances for testing purposes.
+//!
+//! This is only a demonstration async Signer that is used to test
+//! the asynchronous signing of claims.
+//! This module should be used only for testing purposes.
+
+#[cfg(feature = "async_signer")]
+fn get_local_signer(alg: &str) -> Box<dyn crate::Signer> {
+ let cert_dir = crate::utils::test::fixture_path("certs");
+
+ match alg {
+ "ps256" | "ps384" | "ps512" => {
+ let (s, _k) = super::temp_signer::get_rsa_signer(&cert_dir, alg, None);
+ Box::new(s)
+ }
+ "es256" | "es384" | "es512" => {
+ let (s, _k) = super::temp_signer::get_ec_signer(&cert_dir, alg, None);
+ Box::new(s)
+ }
+ "ed25519" => {
+ let (s, _k) = super::temp_signer::get_ed_signer(&cert_dir, alg, None);
+ Box::new(s)
+ }
+ _ => {
+ let (s, _k) = super::temp_signer::get_rsa_signer(&cert_dir, "ps256", None);
+ Box::new(s)
+ }
+ }
+}
+
+#[cfg(feature = "async_signer")]
+pub struct AsyncSignerAdapter {
+ alg: String,
+ certs: Vec<Vec<u8>>,
+ reserve_size: usize,
+ tsa_url: Option<String>,
+ ocsp_val: Option<Vec<u8>>,
+}
+
+#[cfg(feature = "async_signer")]
+impl AsyncSignerAdapter {
+ pub fn new(alg: String) -> Self {
+ let signer = get_local_signer(&alg);
+
+ AsyncSignerAdapter {
+ alg,
+ certs: signer.certs().unwrap_or_default(),
+ reserve_size: signer.reserve_size(),
+ tsa_url: signer.time_authority_url(),
+ ocsp_val: signer.ocsp_val(),
+ }
+ }
+}
+
+#[cfg(test)]
+#[cfg(feature = "async_signer")]
+#[async_trait::async_trait]
+impl crate::AsyncSigner for AsyncSignerAdapter {
+ async fn sign(&self, data: Vec<u8>) -> crate::error::Result<Vec<u8>> {
+ let signer = get_local_signer(&self.alg);
+ signer.sign(&data)
+ }
+
+ fn alg(&self) -> Option<String> {
+ Some(self.alg.clone())
+ }
+
+ fn certs(&self) -> crate::Result<Vec<Vec<u8>>> {
+ let mut output: Vec<Vec<u8>> = Vec::new();
+ for v in &self.certs {
+ output.push(v.clone());
+ }
+ Ok(output)
+ }
+
+ fn reserve_size(&self) -> usize {
+ self.reserve_size
+ }
+
+ fn time_authority_url(&self) -> Option<String> {
+ self.tsa_url.clone()
+ }
+
+ fn ocsp_val(&self) -> Option<Vec<u8>> {
+ self.ocsp_val.clone()
+ }
+}
diff --git a/sdk/src/signer.rs b/sdk/src/signer.rs
@@ -76,10 +76,29 @@ use async_trait::async_trait;
#[async_trait]
pub trait AsyncSigner: Sync {
/// Returns a new byte array which is a signature over the original.
- async fn sign(&self, data: &[u8]) -> Result<Vec<u8>>;
+ async fn sign(&self, data: Vec<u8>) -> Result<Vec<u8>>;
+
+ /// Returns the algorithm of the Signer.
+ fn alg(&self) -> Option<String>;
+
+ /// Returns the certificates as a Vec containing a Vec of DER bytes for each certificate.
+ fn certs(&self) -> Result<Vec<Vec<u8>>>;
/// Returns the size in bytes of the largest possible expected signature.
/// Signing will fail if the result of the `sign` function is larger
/// than this value.
fn reserve_size(&self) -> usize;
+
+ /// URL for time authority to time stamp the signature
+ fn time_authority_url(&self) -> Option<String> {
+ None
+ }
+
+ /// OCSP response for the signing cert if available
+ /// This is the only C2PA supported cert revocation method.
+ /// By pre-querying the value for a your signing cert the value can
+ /// be cached taking pressure off of the CA (recommended by C2PA spec)
+ fn ocsp_val(&self) -> Option<Vec<u8>> {
+ None
+ }
}
diff --git a/sdk/src/store.rs b/sdk/src/store.rs
@@ -358,9 +358,38 @@ impl Store {
&self,
claim: &Claim,
signer: &dyn AsyncSigner,
+ box_size: usize,
) -> Result<Vec<u8>> {
+ use crate::cose_sign::cose_sign_async;
+ use crate::cose_validator::verify_cose_async;
+
let claim_bytes = claim.data()?;
- signer.sign(&claim_bytes).await
+
+ match cose_sign_async(signer, &claim_bytes, box_size).await {
+ // Sanity check: Ensure that this signature is valid.
+ Ok(sig) => {
+ let mut cose_log = OneShotStatusTracker::new();
+ match verify_cose_async(
+ sig.clone(),
+ claim_bytes,
+ b"".to_vec(),
+ false,
+ &mut cose_log,
+ )
+ .await
+ {
+ Ok(_) => Ok(sig),
+ Err(err) => {
+ error!(
+ "Signature that was just generated does not validate: {:#?}",
+ err
+ );
+ Err(err)
+ }
+ }
+ }
+ Err(e) => Err(e),
+ }
}
/// return the current provenance claim label if available
@@ -1395,7 +1424,9 @@ impl Store {
let jumbf_bytes = self.start_save(asset_path, output_path, signer.reserve_size())?;
let pc = self.provenance_claim().ok_or(Error::ClaimEncoding)?;
- let sig = self.sign_claim_async(pc, signer).await?;
+ let sig = self
+ .sign_claim_async(pc, signer, signer.reserve_size())
+ .await?;
let sig_placeholder = self.sign_claim_placeholder(pc, signer.reserve_size());
match self.finish_save(jumbf_bytes, output_path, sig, &sig_placeholder) {
@@ -2142,79 +2173,64 @@ pub mod tests {
assert!(find_bytes(&buf, &original_jumbf[0..1024]).is_none());
}
- /* async signing not supported at the moment
- NOTE: Add this to Cargo.toml if this test is restored.
-
- [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
- actix = "0.11.0"
-
- #[cfg(feature = "async_signer")]
- #[actix::test]
- async fn test_jumbf_generation_async() {
- let signer = crate::AsyncPlaceholder {};
-
- // test adding to actual image
- let ap = fixture_path("earth_apollo17.jpg");
- let temp_dir = tempdir().expect("temp dir");
- let op = temp_dir_path(&temp_dir, "test-async.jpg");
-
- // Create claims store.
- let mut store = Store::new();
-
- // Create a new claim.
- let claim1 = create_test_claim().unwrap();
-
- // Create a new claim.
- let mut claim2 = Claim::new("Photoshop", Some("Adobe"));
- create_editing_claim(&mut claim2).unwrap();
-
- // Create a 3rd party claim
- let mut claim_capture = Claim::new("capture", Some("claim_capture"));
- create_capture_claim(&mut claim_capture).unwrap();
-
- // Test generate JUMBF
- // Get labels for label test
- let claim1_label = claim1.label().to_string();
- let capture = claim_capture.label().to_string();
- let claim2_label = claim2.label().to_string();
-
- /*
- Move the claim to claims list. Note this is not real, the claims would have to be signed in between commits
- */
- store.commit_claim(claim1).unwrap();
- store.save_to_asset_async(&ap, &signer, &op).await.unwrap();
- store.commit_claim(claim_capture).unwrap();
- store.save_to_asset_async(&ap, &signer, &op).await.unwrap();
- store.commit_claim(claim2).unwrap();
- store.save_to_asset_async(&ap, &signer, &op).await.unwrap();
-
- // test finding claims by label
- let c1 = store.get_claim(&claim1_label);
- let c2 = store.get_claim(&capture);
- let c3 = store.get_claim(&claim2_label);
- assert_eq!(&claim1_label, c1.unwrap().label());
- assert_eq!(&capture, c2.unwrap().label());
- assert_eq!(claim2_label, c3.unwrap().label());
-
- // Do we generate JUMBF
- let jumbf_bytes = store.to_jumbf_async(&signer).unwrap();
- assert!(!jumbf_bytes.is_empty());
-
- // write to new file
- println!("Provenance: {}\n", store.provenance_path().unwrap());
-
- // read from new file
- let mut report: Vec<ValidationItem> = Vec::new();
- let new_store = Store::load_from_asset(&op, true, Some(&mut report)).unwrap();
- // Async placeholder signature won't verify. We need the load to complete,
- // but we ignore the validation log which we know will have errors.
-
- let claim = new_store.provenance_claim().unwrap();
- let sig = claim.signature_val();
-
- assert_eq!(&sig[0..19], b"invalid signature\0\0");
- }
- */
+ #[cfg(feature = "async_signer")]
+ #[actix::test]
+ async fn test_jumbf_generation_async() {
+ let signer =
+ crate::openssl::temp_signer_async::AsyncSignerAdapter::new("ps256".to_string());
+
+ // test adding to actual image
+ let ap = fixture_path("earth_apollo17.jpg");
+ let temp_dir = tempdir().expect("temp dir");
+ let op = temp_dir_path(&temp_dir, "test-async.jpg");
+
+ // Create claims store.
+ let mut store = Store::new();
+
+ // Create a new claim.
+ let claim1 = create_test_claim().unwrap();
+
+ // Create a new claim.
+ let mut claim2 = Claim::new("Photoshop", Some("Adobe"));
+ create_editing_claim(&mut claim2).unwrap();
+
+ // Create a 3rd party claim
+ let mut claim_capture = Claim::new("capture", Some("claim_capture"));
+ create_capture_claim(&mut claim_capture).unwrap();
+
+ // Test generate JUMBF
+ // Get labels for label test
+ let claim1_label = claim1.label().to_string();
+ let capture = claim_capture.label().to_string();
+ let claim2_label = claim2.label().to_string();
+
+ store.commit_claim(claim1).unwrap();
+ store.save_to_asset_async(&ap, &signer, &op).await.unwrap();
+ store.commit_claim(claim_capture).unwrap();
+ store.save_to_asset_async(&ap, &signer, &op).await.unwrap();
+ store.commit_claim(claim2).unwrap();
+ store.save_to_asset_async(&ap, &signer, &op).await.unwrap();
+
+ // test finding claims by label
+ let c1 = store.get_claim(&claim1_label);
+ let c2 = store.get_claim(&capture);
+ let c3 = store.get_claim(&claim2_label);
+ assert_eq!(&claim1_label, c1.unwrap().label());
+ assert_eq!(&capture, c2.unwrap().label());
+ assert_eq!(claim2_label, c3.unwrap().label());
+
+ // Do we generate JUMBF
+ let jumbf_bytes = store.to_jumbf_async(&signer).unwrap();
+ assert!(!jumbf_bytes.is_empty());
+
+ // write to new file
+ println!("Provenance: {}\n", store.provenance_path().unwrap());
+
+ // make sure we can read from new file
+ let mut report = DetailedStatusTracker::new();
+ let _new_store = Store::load_from_asset(&op, true, &mut report).unwrap();
+ }
+
#[test]
#[cfg(feature = "file_io")]
fn test_png_jumbf_generation() {