diff --git a/cli/src/main.rs b/cli/src/main.rs index b21572c7a..bb6901085 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -870,14 +870,13 @@ fn main() -> Result<()> { let mut reader = Reader::from_file(&args.path).map_err(special_errs)?; validate_cawg(&mut reader)?; reader.to_folder(&output)?; - let report = reader.to_string(); + let _report = reader.to_string(); if args.detailed { // for a detailed report first call the above to generate the thumbnails // then call this to add the detailed report let detailed = format!("{reader:#?}"); File::create(output.join("detailed.json"))?.write_all(&detailed.into_bytes())?; } - File::create(output.join("manifest_store.json"))?.write_all(&report.into_bytes())?; println!("Manifest report written to the directory {:?}", &output); } } else if args.ingredient { diff --git a/docs/content_credentials.md b/docs/content_credentials.md index 8e07d7de0..6340aae49 100644 --- a/docs/content_credentials.md +++ b/docs/content_credentials.md @@ -52,14 +52,6 @@ For Builder archives - Sign and save a manifest, either embedded or sidecar. If - Add a captured .c2pa archived ingredient using add_ingredient_from_stream. This will use the parent ingredient in the archive as the ingredient added. - - -### Questions - -- Should we allow any Reader to be converted to a Builder, or only those with the same claim_generator? Maybe there is some other flag. - -- Can we save a .c2pa file that does not need to validate against an asset? This should probably be an option, but lets think about it. - ### Test cases 1) Validate an ingredient without a manifest, store in Builder and save. @@ -78,7 +70,7 @@ A future lower level API will wrap the Claim and Store structures providing a si ## Asset objects (not directly related to the above) -An Asset object creates a persistent layer over the asset_io traits. Currently we parse entire asset every time we need to access information about it. We have separate passes for XMP, JUMBF, Offset/box generation & etc.. +An Asset object creates a semi-persistent layer over the asset_io traits. Currently we parse entire asset every time we need to access information about it. We have separate passes for XMP, JUMBF, Offset/box generation & etc.. - The details of file i/o, in memory, streamed, or remote web access are handled here. - This will parse the asset, extract XMP, and C2PA data and allow diff --git a/sdk/src/assertion_input.rs b/sdk/src/assertion_input.rs new file mode 100644 index 000000000..3abb903ed --- /dev/null +++ b/sdk/src/assertion_input.rs @@ -0,0 +1,81 @@ +// In assertion_input.rs +use crate::Result; +//use serde::{Serialize}; +use serde_json::Value as JsonValue; +use serde_cbor::Value as CborValue; +use crate::assertion::AssertionBase; + +pub trait AssertionInput { + fn to_assertion_with_label(self, label: &str) -> Result>; +} + +// Implementation for JSON values +impl AssertionInput for JsonValue { + fn to_assertion_with_label(self, label: &str) -> Result> { + try_deserialize_known_json_assertion(label, &self) + } +} + +// Implementation for CBOR values +impl AssertionInput for CborValue { + fn to_assertion_with_label(self, label: &str) -> Result> { + try_deserialize_known_cbor_assertion(label, &self) + } +} + +// Implementation for strings +impl AssertionInput for &str { + fn to_assertion_with_label(self, label: &str) -> Result> { + let json_value: JsonValue = serde_json::from_str(self)?; + json_value.to_assertion_with_label(label) + } +} + +impl AssertionInput for String { + fn to_assertion_with_label(self, label: &str) -> Result> { + self.as_str().to_assertion_with_label(label) + } +} + + +// Helper functions - return concrete Assertion +fn try_deserialize_known_json_assertion(label: &str, value: &JsonValue) -> Result> { + match label { + crate::assertions::Actions::LABEL => { + let actions: crate::assertions::Actions = serde_json::from_value(value.clone())?; + Ok(Box::new(actions)) // Convert to concrete Assertion + }, + // crate::assertions::Ingredient::LABEL => { + // let ingredient: crate::assertions::Ingredient = serde_json::from_value(value.clone())?; + // ingredient.to_assertion() // Convert to concrete Assertion + // }, + // Add more known assertion types here as needed + _ => { + // Create a User assertion for unknown types + let json_str = serde_json::to_string(value)?; + let user_json = crate::assertions::User::new(label, &json_str); + Ok(Box::new(user_json)) + } + } +} + +fn try_deserialize_known_cbor_assertion(label: &str, value: &CborValue) -> Result> { + let cbor_bytes = serde_cbor::to_vec(value)?; + + match label { + crate::assertions::Actions::LABEL => { + let actions: crate::assertions::Actions = serde_cbor::from_slice(&cbor_bytes)?; + Ok(Box::new(actions)) // Convert to concrete Assertion + }, + // crate::assertions::Ingredient::LABEL => { + // let ingredient: crate::assertions::Ingredient = serde_cbor::from_slice(&cbor_bytes)?; + // ingredient.to_assertion() // Convert to concrete Assertion + // }, + // Add more known assertion types here as needed + _ => { + // Create a UserCbor assertion for unknown types + let user_cbor = crate::assertions::UserCbor::new(label, cbor_bytes); + Ok(Box::new(user_cbor)) + } + } +} \ No newline at end of file diff --git a/sdk/src/assertions/actions.rs b/sdk/src/assertions/actions.rs index 314b1d504..8a8e37748 100644 --- a/sdk/src/assertions/actions.rs +++ b/sdk/src/assertions/actions.rs @@ -21,7 +21,7 @@ use serde_cbor::Value; use crate::{ assertion::{Assertion, AssertionBase, AssertionCbor}, assertions::{labels, region_of_interest::RegionOfInterest, Actor, AssertionMetadata}, - error::Result, + error::{Error, Result}, resource_store::UriOrResource, utils::cbor_types::DateT, ClaimGeneratorInfo, HashedUri, @@ -660,6 +660,27 @@ impl Action { Ok(self) } + /// Adds an ingredient HashedUri to the action. + pub(crate) fn add_ingredient(mut self, ingredient: HashedUri) -> Result { + match &mut self.parameters { + Some(params) => match &mut params.ingredients { + Some(ingredients) => { + ingredients.push(ingredient); + } + None => { + params.ingredients = Some(vec![ingredient]); + } + }, + None => { + self.parameters = Some(ActionParameters { + ingredients: Some(vec![ingredient]), + ..Default::default() + }); + } + } + Ok(self) + } + /// Extracts ingredient IDs from the action /// There are many deprecated ways to specify ingredient IDs /// priority: parameters.ingredientIds, parameters.org.cai.ingredientIds, parameters.instanceId, instanceId. @@ -775,6 +796,7 @@ impl Actions { /// /// See . pub const LABEL: &'static str = labels::ACTIONS; + pub const LABEL_VERSIONED: &'static str = "c2pa.actions.v2"; pub const VERSION: Option = Some(ASSERTION_CREATION_VERSION); /// Creates a new [`Actions`] assertion struct. @@ -850,6 +872,34 @@ impl Actions { self } + /// Adds an [`Action`] to this assertion's list of actions. + pub fn add_action_checked(mut self, action: Action) -> Result { + let action_name = action.action(); + if action_name.is_empty() { + return Err(Error::AssertionSpecificError( + "Action must have a non-empty action label".to_string(), + )); + } + if V2_DEPRECATED_ACTIONS.contains(&action_name) { + return Err(Error::VersionCompatibility(format!( + "Action '{action_name}' is deprecated in C2PA v2" + ))); + } + if action_name == c2pa_action::OPENED || action_name == c2pa_action::CREATED { + let existing_action = self.actions.iter().find(|a| a.action() == action_name); + if existing_action.is_some() { + return Err(Error::AssertionSpecificError(format!( + "Only one '{action_name}' action is allowed" + ))); + } + // always insert as first action + self.actions.insert(0, action); + return Ok(self); + } + self.actions.push(action); + Ok(self) + } + /// Sets [`AssertionMetadata`] for the action. pub fn add_metadata(mut self, metadata: AssertionMetadata) -> Self { self.metadata = Some(metadata); @@ -882,7 +932,7 @@ impl AssertionBase for Actions { } fn label(&self) -> &str { - "c2pa.actions.v2" + Self::LABEL_VERSIONED } fn to_assertion(&self) -> Result { diff --git a/sdk/src/assertions/ingredient.rs b/sdk/src/assertions/ingredient.rs index 22dc42c3a..7a4df45b8 100644 --- a/sdk/src/assertions/ingredient.rs +++ b/sdk/src/assertions/ingredient.rs @@ -11,6 +11,8 @@ // specific language governing permissions and limitations under // each license. +use std::io::{Read, Seek}; + #[cfg(feature = "json_schema")] use schemars::JsonSchema; use serde::{ser::SerializeStruct, Deserialize, Serialize, Serializer}; @@ -20,8 +22,12 @@ use crate::{ assertion::{Assertion, AssertionBase, AssertionDecodeError, AssertionDecodeErrorCause}, assertions::{labels, AssertionMetadata, ReviewRating}, cbor_types::map_cbor_to_type, + context::Context, error::Result, hashed_uri::HashedUri, + jumbf::labels::{to_manifest_uri, to_signature_uri}, + status_tracker::StatusTracker, + store::Store, validation_results::ValidationResults, validation_status::ValidationStatus, Error, @@ -520,6 +526,79 @@ impl Ingredient { ingredient_map.end() } + + /// Create a new Ingredient assertion from a stream + /// You must specify the relationship and format. + /// This will return both the new Ingredient and the associated Store. + pub(crate) fn from_stream( + relationship: Relationship, + format: &str, + mut stream: impl Read + Seek + Send, + context: &Context, + ) -> Result<(Self, Store)> { + let mut validation_log = StatusTracker::default(); + + // // Try to get xmp info, if this fails all XmpInfo fields will be None. + // let xmp_info = XmpInfo::from_source(stream, &format); + + // let id = if let Some(id) = xmp_info.instance_id { + // id + // } else { + // default_instance_id() + // }; + + // let mut ingredient = Self::new(title.into(), format, id); + + // ingredient.document_id = xmp_info.document_id; // use document id if one exists + // ingredient.provenance = xmp_info.provenance; + let store: Store = Store::from_stream(format, &mut stream, &mut validation_log, context)?; + let validation_results = ValidationResults::from_store(&store, &validation_log); + let ingredient = + Self::from_store_and_validation_results(relationship, &store, &validation_results)?; + Ok((ingredient, store)) + } + + /// Create a new Ingredient assertion from a Store and ValidationResults. + /// You must specify the relationship. + pub(crate) fn from_store_and_validation_results( + relationship: Relationship, + store: &Store, + validation_results: &ValidationResults, + ) -> Result { + if let Some(claim) = store.provenance_claim() { + let mut ingredient = Self::new_v3(relationship); + + ingredient.title = claim.title().cloned(); + ingredient.format = claim.format().map(|f| f.to_string()); + ingredient.instance_id = Some(claim.instance_id().to_string()); + + let hashes = store.get_manifest_box_hashes(claim); + + ingredient.active_manifest = Some(HashedUri::new( + to_manifest_uri(claim.label()), + Some(claim.alg().to_owned()), + hashes.manifest_box_hash.as_ref(), + )); + ingredient.claim_signature = Some(HashedUri::new( + to_signature_uri(claim.label()), + Some(claim.alg().to_owned()), + hashes.signature_box_hash.as_ref(), + )); + + ingredient.validation_results = Some(validation_results.clone()); + + if ingredient + .validation_results + .as_ref() + .map(|r| r.validation_state()) + != Some(crate::ValidationState::Invalid) + { + ingredient.thumbnail = claim.thumbnail(); + } + return Ok(ingredient); + } + Ok(Self::new_v3(relationship)) + } } fn to_decoding_err(label: &str, version: usize, field: &str) -> Error { diff --git a/sdk/src/asset.rs b/sdk/src/asset.rs new file mode 100644 index 000000000..7676a41db --- /dev/null +++ b/sdk/src/asset.rs @@ -0,0 +1,89 @@ +use std::io::{BufReader, Read, Seek, Write}; + +use crate::{ + asset_io::CAIRead, + jumbf_io::{get_cailoader_handler, get_caiwriter_handler}, + Error, Result, +}; + +#[allow(dead_code)] +pub struct Asset<'a> { + name: Option, + format: String, + stream: Box, + xmp: Option, + manifest: Option>, +} + +impl<'a> Asset<'a> { + #[allow(dead_code)] + pub fn from_stream(stream: impl Read + Seek + Send + 'a, format: &str) -> Result { + let cailoader_handler = get_cailoader_handler(format).ok_or(Error::UnsupportedType)?; + let mut stream = BufReader::new(stream); + let xmp = cailoader_handler.read_xmp(&mut stream); + stream.rewind()?; + let manifest = cailoader_handler.read_cai(&mut stream)?; + Ok(Asset { + name: None, + format: format.to_string(), + stream: Box::new(stream), + xmp, + manifest: Some(manifest), + }) + } + + #[allow(dead_code)] + pub fn with_manifest(mut self, manifest: Vec) -> Self { + self.manifest = Some(manifest); + self + } + + #[allow(dead_code)] + pub fn write_stream( + &mut self, + mut output: impl Read + Write + Seek + Send, + format: &str, + ) -> Result<()> { + let caiwriter_handler = get_caiwriter_handler(format).ok_or(Error::UnsupportedType)?; + if let Some(manifest) = &self.manifest { + caiwriter_handler.write_cai(&mut self.stream, &mut output, manifest)?; + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + #![allow(clippy::unwrap_used)] + use std::{ + fs::File, + io::{BufReader, Cursor}, + }; + + use super::*; + use crate::content_credential::ContentCredential; + #[test] + fn test_asset_from_stream() { + let context = crate::context::Context::new(); + let file = File::open("tests/fixtures/C.jpg").unwrap(); + let mut reader = BufReader::new(file); + let mut asset = Asset::from_stream(&mut reader, "image/jpeg").unwrap(); + if let Some(xmp) = &asset.xmp { + println!("xmp: {xmp}"); + } + assert!(asset.manifest.is_some()); + if let Some(manifest) = &asset.manifest { + let cc = ContentCredential::new(&context) + .open_stream("application/c2pa", std::io::Cursor::new(manifest)) + .unwrap(); + println!("manifest: {:?}", cc); + } + let mut output = Cursor::new(Vec::new()); + Asset::write_stream(&mut asset, &mut output, "image/jpeg").unwrap(); + + let cc = ContentCredential::new(&context) + .open_stream("image/jpeg", &mut output) + .unwrap(); + println!("manifest: {:?}", cc); + } +} diff --git a/sdk/src/asset_handlers/c2pa_io.rs b/sdk/src/asset_handlers/c2pa_io.rs index 862a90f8e..76343ba96 100644 --- a/sdk/src/asset_handlers/c2pa_io.rs +++ b/sdk/src/asset_handlers/c2pa_io.rs @@ -176,8 +176,6 @@ pub mod tests { use super::{AssetIO, C2paIO, CAIReader, CAIWriter}; use crate::{ crypto::raw_signature::SigningAlg, - http::SyncGenericResolver, - settings::Settings, status_tracker::{ErrorBehavior, StatusTracker}, store::Store, utils::{ @@ -189,8 +187,7 @@ pub mod tests { #[test] fn c2pa_io_parse() { - let settings = Settings::default(); - let http_resolver = SyncGenericResolver::new(); + let context = crate::context::Context::new(); let path = fixture_path("C.jpg"); @@ -213,10 +210,8 @@ pub mod tests { &manifest, "image/jpeg", &stream, - true, &mut StatusTracker::with_error_behavior(ErrorBehavior::StopOnFirstError), - &http_resolver, - &settings, + &context, ) .expect("loading store"); diff --git a/sdk/src/builder.rs b/sdk/src/builder.rs index c2b0b1718..cda62bede 100644 --- a/sdk/src/builder.rs +++ b/sdk/src/builder.rs @@ -36,11 +36,10 @@ use crate::{ UserCbor, }, claim::Claim, + context::Context, error::{Error, Result}, - http::{AsyncGenericResolver, SyncGenericResolver}, jumbf_io, resource_store::{ResourceRef, ResourceResolver, ResourceStore}, - settings::{self, Settings}, store::Store, utils::{hash_utils::hash_to_b64, mime::format_to_mime}, AsyncSigner, ClaimGeneratorInfo, HashRange, HashedUri, Ingredient, ManifestAssertionKind, @@ -305,16 +304,16 @@ pub struct Builder { pub base_path: Option, /// A builder should construct a created, opened or updated manifest. - #[deprecated(note = "Use set_intent() to set or intent() to get the builder intent")] + #[deprecated(note = "Use set_intent() to set or intent()")] pub intent: Option, /// Container for binary assets (like thumbnails). #[serde(skip)] pub(crate) resources: ResourceStore, - // Contains the builder settings + // Contains the builder context #[serde(skip)] - settings: Settings, + context: Context, } impl AsRef for Builder { @@ -329,7 +328,19 @@ impl Builder { /// * A new [`Builder`]. pub fn new() -> Self { Self { - settings: settings::get_settings().unwrap_or_default(), + context: Context::new(), + ..Default::default() + } + } + + /// Creates a new [`Builder`] struct from a [`Context`]. + /// # Arguments + /// * `context` - The [`Context`] to use for this [`Builder`]. + /// # Returns + /// * A new [`Builder`]. + pub fn from_context(context: Context) -> Self { + Self { + context, ..Default::default() } } @@ -350,8 +361,8 @@ impl Builder { /// * A mutable reference to the [`Builder`]. #[allow(deprecated)] pub fn set_intent(&mut self, intent: BuilderIntent) -> &mut Self { - self.settings.builder.intent = Some(intent.clone()); - self.intent = Some(intent); + self.context.settings_mut().builder.intent = Some(intent.clone()); + self.intent = Some(intent); // self.intent is deprecated but keep in sync for now self } @@ -361,7 +372,7 @@ impl Builder { pub fn intent(&self) -> Option { let mut intent = self.intent.clone(); if intent.is_none() { - intent = self.settings.builder.intent.clone(); + intent = self.context.settings().builder.intent.clone(); } intent } @@ -377,11 +388,25 @@ impl Builder { pub fn from_json(json: &str) -> Result { Ok(Self { definition: serde_json::from_str(json).map_err(Error::JsonError)?, - settings: settings::get_settings().unwrap_or_default(), + context: Context::new(), ..Default::default() }) } + /// Sets the [`ManifestDefinition`] for this [`Builder`] from a JSON string. + /// # Arguments + /// * `json` - A JSON string representing the [`ManifestDefinition`]. + /// # Returns + /// * A mutable reference to the [`Builder`]. + /// # Errors + /// * Returns an [`Error`] if the JSON is malformed or incorrect. + /// # Notes + /// * This will overwrite any existing definition in the [`Builder`]. + pub fn with_json(&mut self, json: &str) -> Result<&mut Self> { + self.definition = serde_json::from_str(json).map_err(Error::JsonError)?; + Ok(self) + } + /// Returns a [Vec] of mime types that [c2pa-rs] is able to sign. pub fn supported_mime_types() -> Vec { jumbf_io::supported_builder_mime_types() @@ -616,8 +641,6 @@ impl Builder { T: Into, R: Read + Seek + Send, { - let settings = crate::settings::get_settings().unwrap_or_default(); - let ingredient: Ingredient = Ingredient::from_json(&ingredient_json.into())?; if format == "c2pa" || format == "application/c2pa" { @@ -632,10 +655,10 @@ impl Builder { } let ingredient = if _sync { - ingredient.with_stream(format, stream, &SyncGenericResolver::new(), &settings)? + ingredient.with_stream(format, stream, &self.context)? } else { ingredient - .with_stream_async(format, stream, &AsyncGenericResolver::new(), &settings) + .with_stream_async(format, stream, &self.context) .await? }; @@ -831,9 +854,8 @@ impl Builder { /// # Errors /// * Returns an [`Error`] if the archive cannot be written. pub fn to_archive(&mut self, mut stream: impl Write + Seek) -> Result<()> { - let settings = crate::settings::get_settings().unwrap_or_default(); - if let Some(true) = settings.builder.generate_c2pa_archive { - let c2pa_data = self.working_store_sign(&settings)?; + if let Some(true) = self.context.settings().builder.generate_c2pa_archive { + let c2pa_data = self.working_store_sign()?; stream.write_all(&c2pa_data)?; } else { return self.old_to_archive(stream); @@ -861,7 +883,8 @@ impl Builder { // but we need to disable validation since we are not signing yet // so we will read the store directly here //crate::Reader::from_stream("application/c2pa", stream).and_then(|r| r.into_builder()) - let settings = crate::settings::get_settings().unwrap_or_default(); + let mut context = Context::new(); + context.settings_mut().verify.verify_after_reading = false; let mut validation_log = crate::status_tracker::StatusTracker::default(); stream.rewind()?; // Ensure stream is at the start @@ -869,18 +892,17 @@ impl Builder { let store = Store::from_stream( "application/c2pa", &mut stream, - false, &mut validation_log, - &SyncGenericResolver::new(), - &settings, + &context, )?; - let reader = Reader::from_store(store, &mut validation_log, &settings)?; + let mut reader = Reader::new(context); + reader.with_store(store, &mut validation_log)?; reader.into_builder() }) } // Convert a Manifest into a Claim - fn to_claim(&self, settings: &Settings) -> Result { + fn to_claim(&self) -> Result { // utility function to add created or gathered assertions fn add_assertion( claim: &mut Claim, @@ -899,7 +921,8 @@ impl Builder { // add the default claim generator info for this library if claim_generator_info.is_empty() { - let claim_generator_info_settings = &settings.builder.claim_generator_info; + let claim_generator_info_settings = + &self.context.settings().builder.claim_generator_info; match claim_generator_info_settings { Some(claim_generator_info_settings) => { claim_generator_info.push(claim_generator_info_settings.clone().try_into()?); @@ -1009,7 +1032,7 @@ impl Builder { &mut claim, definition.redactions.clone(), Some(&self.resources), - settings, + &self.context, )?; if !id.is_empty() { ingredient_map.insert(id, (ingredient.relationship(), uri)); @@ -1176,10 +1199,11 @@ impl Builder { actions: &mut Actions, ) -> Result<()> { if actions.all_actions_included.is_none() { - actions.all_actions_included = self.settings.builder.actions.all_actions_included; + actions.all_actions_included = + self.context.settings().builder.actions.all_actions_included; } - let action_templates = &self.settings.builder.actions.templates; + let action_templates = &self.context.settings().builder.actions.templates; if let Some(action_templates) = action_templates { let action_templates = action_templates @@ -1194,7 +1218,7 @@ impl Builder { } } - let additional_actions = &self.settings.builder.actions.actions; + let additional_actions = &self.context.settings().builder.actions.actions; if let Some(additional_actions) = additional_actions { let additional_actions = additional_actions @@ -1224,9 +1248,10 @@ impl Builder { ingredient_map: &HashMap, actions: &mut Actions, ) -> Result<()> { + let settings = self.context.settings(); // https://spec.c2pa.org/specifications/specifications/2.2/specs/C2PA_Specification.html#_mandatory_presence_of_at_least_one_actions_assertion - let auto_created = self.settings.builder.actions.auto_created_action.enabled; - let auto_opened = self.settings.builder.actions.auto_opened_action.enabled; + let auto_created = settings.builder.actions.auto_created_action.enabled; + let auto_opened = settings.builder.actions.auto_opened_action.enabled; if (self.intent().is_some() || auto_created || auto_opened) && !actions.actions.iter().any(|action| { @@ -1269,7 +1294,7 @@ impl Builder { let mut action = Action::new(c2pa_action::OPENED) .set_parameter("ingredients", vec![parent_uri])?; if let Some(source_type) = - &self.settings.builder.actions.auto_opened_action.source_type + &settings.builder.actions.auto_opened_action.source_type { action = action.set_source_type(source_type.clone()); } @@ -1279,12 +1304,8 @@ impl Builder { } } else if auto_created { let mut action = Action::new(c2pa_action::CREATED); - if let Some(source_type) = &self - .settings - .builder - .actions - .auto_created_action - .source_type + if let Some(source_type) = + &settings.builder.actions.auto_created_action.source_type { action = action.set_source_type(source_type.clone()); } @@ -1302,7 +1323,7 @@ impl Builder { } // https://spec.c2pa.org/specifications/specifications/2.2/specs/C2PA_Specification.html#_relationship - if self.settings.builder.actions.auto_placed_action.enabled { + if settings.builder.actions.auto_placed_action.enabled { // Get a list of ingredient URIs referenced by "c2pa.placed" actions. let mut referenced_uris = HashSet::new(); for action in &actions.actions { @@ -1326,8 +1347,7 @@ impl Builder { let action = action.set_parameter("ingredients", vec![uri])?; - let action = match self.settings.builder.actions.auto_placed_action.source_type - { + let action = match settings.builder.actions.auto_placed_action.source_type { Some(ref source_type) => action.set_source_type(source_type.clone()), _ => action, }; @@ -1339,10 +1359,10 @@ impl Builder { } // Convert a Manifest into a Store - fn to_store(&self, settings: &Settings) -> Result { - let claim = self.to_claim(settings)?; + fn to_store(&self) -> Result { + let claim = self.to_claim()?; - let mut store = Store::with_settings(settings); + let mut store = Store::with_context(&self.context); // if this can be an update manifest, then set the update_manifest flag if self.intent() == Some(BuilderIntent::Update) { @@ -1355,12 +1375,7 @@ impl Builder { } #[cfg(feature = "add_thumbnails")] - fn maybe_add_thumbnail( - &mut self, - format: &str, - stream: &mut R, - settings: &Settings, - ) -> Result<&mut Self> + fn maybe_add_thumbnail(&mut self, format: &str, stream: &mut R) -> Result<&mut Self> where R: Read + Seek + ?Sized, { @@ -1370,7 +1385,7 @@ impl Builder { } // check settings to see if we should auto generate a thumbnail - let auto_thumbnail = settings.builder.thumbnail.enabled; + let auto_thumbnail = self.context.settings().builder.thumbnail.enabled; if self.definition.thumbnail.is_none() && auto_thumbnail { stream.rewind()?; @@ -1380,7 +1395,7 @@ impl Builder { crate::utils::thumbnail::make_thumbnail_bytes_from_stream( format, &mut stream, - settings, + self.context.settings(), )? { stream.rewind()?; @@ -1452,8 +1467,6 @@ impl Builder { reserve_size: usize, format: &str, ) -> Result> { - let settings = crate::settings::get_settings().unwrap_or_default(); - let dh: Result = self.find_assertion(DataHash::LABEL); if dh.is_err() { let mut ph = DataHash::new("jumbf manifest", "sha256"); @@ -1464,7 +1477,7 @@ impl Builder { } self.definition.format = format.to_string(); self.definition.instance_id = format!("xmp:iid:{}", Uuid::new_v4()); - let mut store = self.to_store(&settings)?; + let mut store = self.to_store()?; let placeholder = store.get_data_hashed_manifest_placeholder(reserve_size, format)?; Ok(placeholder) } @@ -1496,15 +1509,23 @@ impl Builder { data_hash: &DataHash, format: &str, ) -> Result> { - let settings = crate::settings::get_settings().unwrap_or_default(); - - let mut store = self.to_store(&settings)?; + let mut store = self.to_store()?; if _sync { - store.get_data_hashed_embeddable_manifest(data_hash, signer, format, None, &settings) + store.get_data_hashed_embeddable_manifest( + data_hash, + signer, + format, + None, + &self.context, + ) } else { store .get_data_hashed_embeddable_manifest_async( - data_hash, signer, format, None, &settings, + data_hash, + signer, + format, + None, + &self.context, ) .await } @@ -1530,16 +1551,14 @@ impl Builder { signer: &dyn Signer, format: &str, ) -> Result> { - let settings = crate::settings::get_settings().unwrap_or_default(); - self.definition.instance_id = format!("xmp:iid:{}", Uuid::new_v4()); - let mut store = self.to_store(&settings)?; + let mut store = self.to_store()?; let bytes = if _sync { - store.get_box_hashed_embeddable_manifest(signer, &settings) + store.get_box_hashed_embeddable_manifest(signer, &self.context) } else { store - .get_box_hashed_embeddable_manifest_async(signer, &settings) + .get_box_hashed_embeddable_manifest_async(signer, &self.context) .await }?; // get composed version for embedding to JPEG @@ -1575,13 +1594,6 @@ impl Builder { R: Read + Seek + Send, W: Write + Read + Seek + Send, { - let settings = crate::settings::get_settings().unwrap_or_default(); - let http_resolver = if _sync { - SyncGenericResolver::new() - } else { - AsyncGenericResolver::new() - }; - let format = format_to_mime(format); self.definition.format.clone_from(&format); // todo:: read instance_id from xmp from stream ? @@ -1597,17 +1609,17 @@ impl Builder { // generate thumbnail if we don't already have one #[cfg(feature = "add_thumbnails")] - self.maybe_add_thumbnail(&format, source, &settings)?; + self.maybe_add_thumbnail(&format, source)?; // convert the manifest to a store - let mut store = self.to_store(&settings)?; + let mut store = self.to_store()?; // sign and write our store to to the output image file if _sync { - store.save_to_stream(&format, source, dest, signer, &http_resolver, &settings) + store.save_to_stream(&format, source, dest, signer, &self.context) } else { store - .save_to_stream_async(&format, source, dest, signer, &http_resolver, &settings) + .save_to_stream_async(&format, source, dest, signer, &self.context) .await } } @@ -1661,8 +1673,6 @@ impl Builder { fragment_paths: &Vec, output_path: P, ) -> Result<()> { - let settings = crate::settings::get_settings().unwrap_or_default(); - if !output_path.as_ref().exists() { // ensure the path exists std::fs::create_dir_all(output_path.as_ref())?; @@ -1685,7 +1695,7 @@ impl Builder { } // convert the manifest to a store - let mut store = self.to_store(&settings)?; + let mut store = self.to_store()?; // sign and write our store to DASH content store.save_to_bmff_fragmented( @@ -1693,7 +1703,7 @@ impl Builder { fragment_paths, output_path.as_ref(), signer, - &settings, + &self.context, ) } @@ -1785,7 +1795,7 @@ impl Builder { /// The working store is signed with a BoxHash over an empty string /// And is returned as a Vec of the c2pa_manifest bytes /// This works as an archive of the store that can be read back to restore the Builder state - fn working_store_sign(&self, settings: &Settings) -> Result> { + fn working_store_sign(&self) -> Result> { // first we need to generate a BoxHash over an empty string let mut empty_asset = std::io::Cursor::new(""); let boxes = jumbf_io::get_assetio_handler("application/c2pa") @@ -1796,7 +1806,7 @@ impl Builder { let box_hash = BoxHash { boxes }; // then convert the builder to a claim and add the box hash assertion - let mut claim = self.to_claim(settings)?; + let mut claim = self.to_claim()?; claim.add_assertion(&box_hash)?; // now commit and sign it. The signing will allow us to detect tampering. @@ -2219,7 +2229,7 @@ mod tests { #[cfg(target_os = "wasi")] Settings::reset().unwrap(); - settings::Settings::from_toml( + crate::settings::Settings::from_toml( &toml::toml! { [builder.actions.auto_opened_action] enabled = true diff --git a/sdk/src/claim.rs b/sdk/src/claim.rs index 7759aeadd..d1bc80827 100644 --- a/sdk/src/claim.rs +++ b/sdk/src/claim.rs @@ -35,11 +35,12 @@ use crate::{ self, ACTIONS, ASSERTION_METADATA, ASSERTION_STORE, BMFF_HASH, CLAIM_THUMBNAIL, DATABOX_STORE, METADATA_LABEL_REGEX, }, - Actions, AssertionMetadata, AssetType, BmffHash, BoxHash, DataBox, DataHash, Ingredient, - Metadata, Relationship, V2_DEPRECATED_ACTIONS, + Action, Actions, AssertionMetadata, AssetType, BmffHash, BoxHash, DataBox, DataHash, + Ingredient, Metadata, Relationship, V2_DEPRECATED_ACTIONS, }, asset_io::CAIRead, cbor_types::map_cbor_to_type, + context::Context, cose_validator::{ get_signing_cert_serial_num, get_signing_info, get_signing_info_async, verify_cose, verify_cose_async, @@ -55,7 +56,6 @@ use crate::{ }, error::{Error, Result}, hashed_uri::HashedUri, - http::{AsyncHttpResolver, SyncHttpResolver}, jumbf::{ self, boxes::{ @@ -1155,6 +1155,28 @@ impl Claim { self.update_manifest = is_update_manifest; } + /// Adds an action to the claim. + /// + /// If an Actions assertion already exists, the action is added to it. + /// If not, a new Actions assertion is created and added to the claim. + /// If multiple exist, this will update the first one found. + pub fn add_action(&mut self, action: Action) -> Result<&mut Self> { + match self.get_assertion(Actions::LABEL_VERSIONED, 0) { + None => { + let actions = Actions::new().add_action_checked(action)?; + self.add_assertion(&actions)?; + } + Some(a) => { + let actions = Actions::from_assertion(a)?; + + let actions = actions.add_action_checked(action)?; + + self.replace_assertion(actions.to_assertion()?)?; + } + }; + Ok(self) + } + pub fn add_claim_generator_info(&mut self, info: ClaimGeneratorInfo) -> &mut Self { match self.claim_generator_info.as_mut() { Some(cgi) => cgi.push(info), @@ -1353,8 +1375,11 @@ impl Claim { if labels::HASH_LABELS.contains(&base_label) || add_as_created_assertion { ClaimAssertionType::Created } else if let Some(created_assertions) = { - let settings = crate::settings::get_settings().unwrap_or_default(); - settings.builder.created_assertion_labels + Context::new() + .settings() + .builder + .created_assertion_labels + .clone() } { if created_assertions.iter().any(|label| label == base_label) { ClaimAssertionType::Created @@ -1824,7 +1849,6 @@ impl Claim { /// Verify claim signature, assertion store and asset hashes /// claim - claim to be verified /// asset_bytes - reference to bytes of the asset - #[allow(clippy::too_many_arguments)] pub(crate) async fn verify_claim_async( claim: &Claim, asset_data: &mut ClaimAssetData<'_>, @@ -1832,9 +1856,9 @@ impl Claim { cert_check: bool, ctp: &CertificateTrustPolicy, validation_log: &mut StatusTracker, - http_resolver: &impl AsyncHttpResolver, - settings: &Settings, + context: &Context, ) -> Result<()> { + let settings = context.settings(); // Parse COSE signed data (signature) and validate it. let sig = claim.signature_val().clone(); let additional_bytes: Vec = Vec::new(); @@ -1884,8 +1908,7 @@ impl Claim { svi.certificate_statuses.get(&certificate_serial_num), svi.timestamps.get(claim.label()), validation_log, - http_resolver, - settings, + context, ) .await?; @@ -1918,14 +1941,13 @@ impl Claim { cert_check: bool, ctp: &CertificateTrustPolicy, validation_log: &mut StatusTracker, - http_resolver: &impl SyncHttpResolver, - settings: &Settings, + context: &Context, ) -> Result<()> { // Parse COSE signed data (signature) and validate it. let sig = claim.signature_val(); let additional_bytes: Vec = Vec::new(); - let mut adjusted_settings = settings.clone(); + let mut adjusted_settings = context.settings().clone(); if claim.version() == 1 { adjusted_settings.verify.verify_timestamp_trust = false; } @@ -1986,8 +2008,7 @@ impl Claim { svi.certificate_statuses.get(&certificate_serial_num), svi.timestamps.get(claim.label()), validation_log, - http_resolver, - &adjusted_settings, + context, )?; let verified = verify_cose( @@ -3857,6 +3878,19 @@ impl Claim { iter.next() } + // /// returns first instance of an assertion whose label and instance match + // pub fn get_assertion_mut(&self, assertion_label: &str, instance: usize) -> Option<&Assertion> { + // let mut iter = self.claim_assertion_store().iter().filter_map(|ca| { + // if ca.label_raw() == assertion_label && ca.instance() == instance { + // Some(ca.assertion()) + // } else { + // None + // } + // }); + + // iter.next() + // } + /// returns instance of an assertion whose label and instance match pub fn get_claim_assertion( &self, @@ -3922,6 +3956,18 @@ impl Claim { } } + // Returns a HashedUri to the claim thumbnail assertion, if it exists. + pub fn thumbnail(&self) -> Option { + self.assertions() + .iter() + .find(|hashed_uri| hashed_uri.url().contains(CLAIM_THUMBNAIL)) + .map(|t| { + // convert to absolute + let url = crate::jumbf::labels::to_absolute_uri(self.label(), &t.url()); + HashedUri::new(url, t.alg(), &t.hash()) + }) + } + /// Checks whether or not ocsp values are present in claim pub fn has_ocsp_vals(&self) -> bool { if !self.certificate_status_assertions().is_empty() { @@ -3946,16 +3992,7 @@ impl Claim { } #[allow(dead_code, clippy::too_many_arguments)] -#[async_generic(async_signature( - sign1: &coset::CoseSign1, - data: &[u8], - ctp: &CertificateTrustPolicy, - ocsp_responses: Option<&Vec>>, - tst_info: Option<&TstInfo>, - validation_log: &mut StatusTracker, - http_resolver: &impl AsyncHttpResolver, - settings: &Settings, -))] +#[async_generic] pub(crate) fn check_ocsp_status( sign1: &coset::CoseSign1, data: &[u8], @@ -3963,12 +4000,11 @@ pub(crate) fn check_ocsp_status( ocsp_responses: Option<&Vec>>, tst_info: Option<&TstInfo>, validation_log: &mut StatusTracker, - http_resolver: &impl SyncHttpResolver, - settings: &Settings, + context: &Context, ) -> Result { // Moved here instead of c2pa-crypto because of the dependency on settings. - let fetch_policy = if settings.verify.ocsp_fetch { + let fetch_policy = if context.settings().verify.ocsp_fetch { OcspFetchPolicy::FetchAllowed } else { OcspFetchPolicy::DoNotFetch @@ -3983,8 +4019,7 @@ pub(crate) fn check_ocsp_status( ocsp_responses, tst_info, validation_log, - http_resolver, - settings, + context, )?) } else { Ok(crate::crypto::cose::check_ocsp_status_async( @@ -3995,8 +4030,7 @@ pub(crate) fn check_ocsp_status( ocsp_responses, tst_info, validation_log, - http_resolver, - settings, + context, ) .await?) } diff --git a/sdk/src/content_credential.rs b/sdk/src/content_credential.rs new file mode 100644 index 000000000..9d1346bd6 --- /dev/null +++ b/sdk/src/content_credential.rs @@ -0,0 +1,323 @@ +// Copyright 2025 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 std::{ + collections::HashMap, + io::{Read, Seek, Write}, +}; + +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use serde_with::skip_serializing_none; + +use crate::{ + assertion::AssertionBase, + assertions::{c2pa_action, Action, Actions, Ingredient, Relationship}, + claim::Claim, + context::Context, + manifest::{Manifest, StoreOptions}, + manifest_store_report::ManifestStoreReport, + settings::Settings, + status_tracker::StatusTracker, + store::Store, + utils::hash_utils::hash_to_b64, + validation_status::ValidationStatus, + DigitalSourceType, Error, HashedUri, Result, ValidationResults, +}; + +/// This Generates the standard Reader output format for a Store +#[skip_serializing_none] +#[derive(Serialize, Deserialize, Debug, Default)] +pub struct StandardStoreReport { + title: Option, + format: Option, + instance_id: Option, + /// A label for the active (most recent) manifest in the store + active_manifest: Option, + + /// A HashMap of Manifests + manifests: HashMap, + + validation_results: ValidationResults, +} + +impl StandardStoreReport { + fn from_store(store: &Store, validation_results: &ValidationResults) -> Result { + let settings = Settings::default(); + let mut validation_results = validation_results.clone(); + let active_manifest = store.provenance_label(); + let mut manifests = HashMap::new(); + let mut validation_log = StatusTracker::default(); + let mut options = StoreOptions::default(); + + for claim in store.claims() { + let manifest_label = claim.label(); + let result = Manifest::from_store( + store, + manifest_label, + &mut options, + &mut validation_log, + &settings, + ); + + match result { + Ok(manifest) => { + manifests.insert(manifest_label.to_owned(), manifest); + } + Err(e) => { + validation_results.add_status(ValidationStatus::from_error(&e)); + return Err(e); + } + }; + } + Ok(Self { + active_manifest, + manifests, + validation_results, + ..Default::default() + }) + } +} + +/// Experimental optimized Content Credential Structure +pub struct ContentCredential<'a> { + claim: Claim, + store: Store, + context: &'a Context, +} + +impl<'a> ContentCredential<'a> { + pub fn new(context: &'a Context) -> Self { + let mut claim = Claim::new("", context.settings().builder.vendor.as_deref(), 2); + claim.instance_id = uuid::Uuid::new_v4().to_string(); + ContentCredential { + claim, + store: Store::new(), + context, + } + } + + /// Use this for a content credential that is being created from scratch + pub fn create(mut self, source_type: DigitalSourceType) -> Result { + let actions = Actions::new() + .add_action(Action::new(c2pa_action::CREATED).set_source_type(source_type)); + + self.add_assertion(&actions)?; + + Ok(self) + } + + // edits the content credential by setting the parent ingredient from the provided stream + pub fn open_stream( + mut self, + format: &str, + mut stream: impl Read + Seek + Send, + ) -> Result { + stream.rewind()?; + self.add_ingredient_from_stream(Relationship::ParentOf, format, &mut stream)?; + Ok(self) + } + + fn parent_ingredient(&self) -> Option { + for i in self.claim.ingredient_assertions() { + if let Ok(ingredient) = Ingredient::from_assertion(i.assertion()) { + if ingredient.relationship == Relationship::ParentOf { + return Some(ingredient); + } + } + } + None + } + + /// adds an assertion to the content credential's claim + pub fn add_assertion(&mut self, assertion: &impl AssertionBase) -> Result { + self.claim.add_assertion(assertion) + } + + /// create a manifest store report from the store and validation results + pub fn add_action(&mut self, action: Action) -> Result<()> { + self.claim.add_action(action)?; + Ok(()) + } + + /// internal implementation to add ingredient from stream + fn add_ingredient_from_stream( + &mut self, + relationship: Relationship, + format: &str, + mut stream: impl Read + Seek + Send, + ) -> Result { + let (ingredient_assertion, store) = + Ingredient::from_stream(relationship.clone(), format, &mut stream, self.context)?; + + // todo: allow passing store to load_ingredient_to_claim to avoid this conversion + let manifest_bytes = store.to_jumbf_internal(0)?; + Store::load_ingredient_to_claim(&mut self.claim, &manifest_bytes, None, self.context)?; + + if relationship == Relationship::ParentOf { + // we must replace the store for parent ingredients + self.store = store; + } + + // add the ingredient assertion and get it's uri + let ingredient_uri = self.add_assertion(&ingredient_assertion)?; + + // This part automatically adds the correct associated action for the ingredient + // I'm not sure if we want to have this behavior here or leave it to the user + let action_label = if relationship == Relationship::ParentOf { + c2pa_action::OPENED + } else { + c2pa_action::PLACED + }; + + let action = Action::new(action_label).add_ingredient(ingredient_uri.clone())?; + + self.add_action(action)?; + + Ok(ingredient_uri) + } + + /// signs and saves the content credential to the destination stream + pub fn save_to_stream( + &mut self, + format: &str, + source: &mut R, + dest: &mut W, + ) -> Result> + where + R: Read + Seek + Send, + W: Write + Read + Seek + Send, + { + let signer = self.context.signer()?; + if self.claim.claim_generator_info().is_none() { + if let Some(cgi) = &self.context.settings().builder.claim_generator_info { + self.claim.add_claim_generator_info(cgi.try_into()?); + } + } + self.store.commit_claim(self.claim.clone())?; + source.rewind()?; // always reset source to start + self.store + .save_to_stream(format, source, dest, signer, self.context) + } + + /// Generates a value similar to the C2PA Reader output + pub fn reader_value(&self) -> Result { + // get the validation results from the parent ingredient + let results = self + .parent_ingredient() + .and_then(|i| i.validation_results) + .ok_or(Error::ClaimMissing { + label: "Parent Ingredient missing".to_string(), + })?; + let report = StandardStoreReport::from_store(&self.store, &results)?; + let json = serde_json::to_value(report).map_err(Error::JsonError)?; + Ok(hash_to_b64(json)) + } + + /// generates a value similar to the C2PA Reader detailed output + pub fn detailed_value(&self) -> Result { + let results = self + .parent_ingredient() + .and_then(|i| i.validation_results) + .ok_or(Error::ClaimMissing { + label: "Parent Ingredient missing".to_string(), + })?; + let report = ManifestStoreReport::from_store_with_results(&self.store, &results)?; + let json = serde_json::to_value(report).map_err(Error::JsonError)?; + Ok(hash_to_b64(json)) + } +} + +impl std::fmt::Display for ContentCredential<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let value = self.reader_value().map_err(|_| std::fmt::Error)?; + f.write_str( + serde_json::to_string_pretty(&value) + .map_err(|_| std::fmt::Error)? + .as_str(), + ) + } +} + +impl std::fmt::Debug for ContentCredential<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let value = self.reader_value().map_err(|_| std::fmt::Error)?; + f.write_str( + serde_json::to_string_pretty(&value) + .map_err(|_| std::fmt::Error)? + .as_str(), + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::utils::test::*; + + fn test_settings_json() -> serde_json::Value { + serde_json::json!({ + "builder": { + "claim_generator_info": { + "name": "Content Credential Tests", + "version": env!("CARGO_PKG_VERSION") + } + } + }) + } + + #[test] + fn test_content_credential_create() -> Result<()> { + let (format, mut source, mut dest) = create_test_streams(CA_JPEG); + + let context = Context::new().with_settings(test_settings_json())?; + + let mut cr = ContentCredential::new(&context).create(DigitalSourceType::Empty)?; + + cr.save_to_stream(format, &mut source, &mut dest)?; + + let cr = ContentCredential::new(&context).open_stream(format, &mut dest)?; + println!("{cr}"); + Ok(()) + } + + #[test] + fn test_content_credential_open_stream() -> Result<()> { + let (format, mut source, mut dest) = create_test_streams(CA_JPEG); + let context = Context::new().with_settings(test_settings_json())?; + + let mut cr = ContentCredential::new(&context).open_stream(format, &mut source)?; + cr.add_action(Action::new(c2pa_action::PUBLISHED))?; + + cr.save_to_stream(format, &mut source, &mut dest)?; + + let cr2 = ContentCredential::new(&context).open_stream(format, &mut dest)?; + println!("{cr2}"); + Ok(()) + } + + #[test] + fn test_add_ingredient_from_stream() -> Result<()> { + let (format, mut source, mut dest) = create_test_streams(CA_JPEG); + let context = Context::new().with_settings(test_settings_json())?; + + let mut cr = ContentCredential::new(&context).create(DigitalSourceType::Empty)?; + cr.add_ingredient_from_stream(Relationship::ComponentOf, format, &mut source)?; + + cr.save_to_stream(format, &mut source, &mut dest)?; + + let cr2 = ContentCredential::new(&context).open_stream(format, &mut dest)?; + println!("{cr2}"); + Ok(()) + } +} diff --git a/sdk/src/context.rs b/sdk/src/context.rs new file mode 100644 index 000000000..abbba50be --- /dev/null +++ b/sdk/src/context.rs @@ -0,0 +1,217 @@ +use std::cell::OnceCell; + +use crate::{ + content_credential::ContentCredential, + http::{AsyncGenericResolver, AsyncHttpResolver, SyncGenericResolver, SyncHttpResolver}, + settings::Settings, + AsyncSigner, Error, Result, Signer, +}; + +/// A trait for types that can be converted into Settings +pub trait IntoSettings { + /// Convert this type into Settings + fn into_settings(self) -> Result; +} + +/// Implement for Settings (passthrough) +impl IntoSettings for Settings { + fn into_settings(self) -> Result { + Ok(self) + } +} + +/// Implement for &str (JSON/TOML string - tries both formats) +impl IntoSettings for &str { + fn into_settings(self) -> Result { + let mut settings = Settings::default(); + // Try JSON first, then TOML + settings + .update_from_str(self, "json") + .or_else(|_| settings.update_from_str(self, "toml"))?; + Ok(settings) + } +} + +/// Implement for String +impl IntoSettings for String { + fn into_settings(self) -> Result { + self.as_str().into_settings() + } +} + +/// Implement for serde_json::Value +impl IntoSettings for serde_json::Value { + fn into_settings(self) -> Result { + let json_str = serde_json::to_string(&self).map_err(Error::JsonError)?; + let mut settings = Settings::default(); + settings.update_from_str(&json_str, "json")?; + Ok(settings) + } +} + +pub struct Context { + settings: Settings, + http_resolver: OnceCell>, + http_resolver_async: OnceCell>, + signer: OnceCell>, + _signer_async: OnceCell>, +} + +impl Default for Context { + fn default() -> Self { + Self { + settings: crate::settings::get_settings().unwrap_or_default(), + http_resolver: OnceCell::new(), + http_resolver_async: OnceCell::new(), + signer: OnceCell::new(), + _signer_async: OnceCell::new(), + } + } +} + +impl std::fmt::Debug for Context { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Context") + .field("settings", &self.settings) + .finish() + } +} + +impl Context { + pub fn new() -> Self { + Self::default() + } + + /// use the provided settings in this context + pub fn with_settings(mut self, settings: S) -> Result { + self.settings = settings.into_settings()?; + Ok(self) + } + + /// Returns a reference to the settings. + pub fn settings(&self) -> &Settings { + &self.settings + } + + /// Returns a mutable reference to the settings. + pub fn settings_mut(&mut self) -> &mut Settings { + &mut self.settings + } + + /// Use the provided sync resolver in this context + pub fn with_resolver(self, resolver: T) -> Self { + let _ = self.http_resolver.set(Box::new(resolver)); + self + } + + /// Use the provided async resolver in this context + pub fn with_resolver_async(self, resolver: T) -> Self { + let _ = self.http_resolver_async.set(Box::new(resolver)); + self + } + + /// Returns a reference to the sync resolver. + pub fn resolver(&self) -> &dyn SyncHttpResolver { + self.http_resolver + .get_or_init(|| Box::new(SyncGenericResolver::new())) + .as_ref() + } + + /// Returns a reference to the async resolver. + pub fn resolver_async(&self) -> &dyn AsyncHttpResolver { + self.http_resolver_async + .get_or_init(|| Box::new(AsyncGenericResolver::new())) + .as_ref() + } + + /// Returns a reference to the signer. + #[allow(clippy::unwrap_used)] // switch to get_or_try_init when stable + pub fn signer(&self) -> Result<&dyn Signer> { + match self.signer.get() { + Some(s) => Ok(s), + None => { + if let Some(c2pa_settings) = self.settings().signer.clone() { + let mut signer = c2pa_settings.c2pa_signer()?; + if let Some(cawg_settings) = self.settings().cawg_x509_signer.clone() { + signer = cawg_settings.cawg_signer(signer)?; + } + self.signer.set(signer).ok(); + return Ok(self.signer.get().unwrap()); + } + #[cfg(test)] + { + self.signer + .set(crate::utils::test_signer::test_signer( + crate::SigningAlg::Ps256, + )) + .ok(); + Ok(self.signer.get().unwrap()) + } + #[cfg(not(test))] + { + Err(crate::Error::MissingSignerSettings) + } + } + } + } + + // pub fn signer_async(&self) -> Result<&dyn crate::signer::AsyncSigner> { + // match self.signer_async.get() { + // Some(s) => Ok(s.as_ref()), + // None => { + // self.signer_async + // .set(Settings::signer_async()?) + // .ok(); + // Ok(self.signer_async.get().unwrap().as_ref()) + // } + // } + // } + + pub fn content_credential(&self) -> ContentCredential<'_> { + ContentCredential::new(self) + } +} + +#[cfg(test)] +mod tests { + #![allow(clippy::unwrap_used)] + use super::*; + + #[test] + fn test_into_settings_from_settings() { + let mut settings = Settings::default(); + settings.verify.verify_after_sign = true; + let context = Context::new().with_settings(settings).unwrap(); + assert!(context.settings().verify.verify_after_sign); + } + + #[test] + fn test_into_settings_from_json_str() { + let json = r#"{"verify": {"verify_after_sign": true}}"#; + let context = Context::new().with_settings(json).unwrap(); + assert!(context.settings().verify.verify_after_sign); + } + #[test] + fn test_into_settings_from_toml_str() { + let toml = r#" + [verify] + verify_after_sign = true + "#; + let context = Context::new().with_settings(toml).unwrap(); + assert!(context.settings().verify.verify_after_sign); + } + + #[test] + fn test_into_settings_from_json_value() { + let value = serde_json::json!({"verify": {"verify_after_sign": true}}); + let context = Context::new().with_settings(value).unwrap(); + assert!(context.settings().verify.verify_after_sign); + } + + #[test] + fn test_into_settings_invalid_json() { + let invalid_json = r#"{"verify": {"verify_after_sign": "#; + let result = Context::new().with_settings(invalid_json); + assert!(result.is_err()); + } +} diff --git a/sdk/src/crypto/cose/ocsp.rs b/sdk/src/crypto/cose/ocsp.rs index b65809829..7681e146a 100644 --- a/sdk/src/crypto/cose/ocsp.rs +++ b/sdk/src/crypto/cose/ocsp.rs @@ -17,6 +17,7 @@ use ciborium::value::Value; use coset::{CoseSign1, Label}; use crate::{ + context::Context, crypto::{ asn1::rfc3161::TstInfo, cose::{ @@ -25,7 +26,6 @@ use crate::{ }, ocsp::OcspResponse, }, - http::{AsyncHttpResolver, SyncHttpResolver}, log_item, settings::Settings, status_tracker::StatusTracker, @@ -42,8 +42,7 @@ use crate::{ ocsp_responses: Option<&Vec>>, tst_info: Option<&TstInfo>, validation_log: &mut StatusTracker, - http_resolver: &impl AsyncHttpResolver, - settings: &Settings, + context: &Context, ))] #[allow(clippy::too_many_arguments)] pub fn check_ocsp_status( @@ -54,10 +53,10 @@ pub fn check_ocsp_status( ocsp_responses: Option<&Vec>>, tst_info: Option<&TstInfo>, validation_log: &mut StatusTracker, - http_resolver: &impl SyncHttpResolver, - settings: &Settings, + context: &Context, ) -> Result { - if settings + if context + .settings() .builder .certificate_status_should_override .unwrap_or(false) @@ -72,7 +71,7 @@ pub fn check_ocsp_status( ocsp_response_ders, tst_info, validation_log, - settings, + context.settings(), ) } else { process_ocsp_responses_async( @@ -82,7 +81,7 @@ pub fn check_ocsp_status( ocsp_response_ders, tst_info, validation_log, - settings, + context.settings(), ) .await }; @@ -100,7 +99,7 @@ pub fn check_ocsp_status( ctp, tst_info, validation_log, - settings, + context.settings(), ) } else { check_stapled_ocsp_response_async( @@ -110,7 +109,7 @@ pub fn check_ocsp_status( ctp, tst_info, validation_log, - settings, + context.settings(), ) .await } @@ -125,8 +124,7 @@ pub fn check_ocsp_status( ctp, tst_info, validation_log, - http_resolver, - settings, + context, ) } else { fetch_and_check_ocsp_response_async( @@ -135,8 +133,7 @@ pub fn check_ocsp_status( ctp, tst_info, validation_log, - http_resolver, - settings, + context, ) .await } @@ -152,7 +149,7 @@ pub fn check_ocsp_status( ocsp_response_ders, tst_info, validation_log, - settings, + context.settings(), ) } else { process_ocsp_responses_async( @@ -162,7 +159,7 @@ pub fn check_ocsp_status( ocsp_response_ders, tst_info, validation_log, - settings, + context.settings(), ) .await } @@ -333,8 +330,7 @@ fn check_stapled_ocsp_response( ctp: &CertificateTrustPolicy, tst_info: Option<&TstInfo>, validation_log: &mut StatusTracker, - http_resolver: &impl AsyncHttpResolver, - settings: &Settings, + context: &crate::context::Context, ))] pub(crate) fn fetch_and_check_ocsp_response( sign1: &CoseSign1, @@ -342,15 +338,14 @@ pub(crate) fn fetch_and_check_ocsp_response( ctp: &CertificateTrustPolicy, tst_info: Option<&TstInfo>, validation_log: &mut StatusTracker, - http_resolver: &impl SyncHttpResolver, - settings: &Settings, + context: &crate::context::Context, ) -> Result { let certs = cert_chain_from_sign1(sign1)?; let ocsp_der = if _sync { - crate::crypto::ocsp::fetch_ocsp_response(&certs, http_resolver) + crate::crypto::ocsp::fetch_ocsp_response(&certs, context) } else { - crate::crypto::ocsp::fetch_ocsp_response_async(&certs, http_resolver).await + crate::crypto::ocsp::fetch_ocsp_response_async(&certs, context).await }; let Some(ocsp_response_der) = ocsp_der else { @@ -360,7 +355,7 @@ pub(crate) fn fetch_and_check_ocsp_response( // use supplied override time if provided let signing_time: Option> = match tst_info { Some(tst_info) => Some(tst_info.gen_time.clone().into()), - None => validate_cose_tst_info(sign1, data, ctp, validation_log, settings) + None => validate_cose_tst_info(sign1, data, ctp, validation_log, context.settings()) .ok() .map(|tst_info| tst_info.gen_time.clone().into()), }; diff --git a/sdk/src/crypto/ocsp/fetch.rs b/sdk/src/crypto/ocsp/fetch.rs index e54b18bd4..243023b0f 100644 --- a/sdk/src/crypto/ocsp/fetch.rs +++ b/sdk/src/crypto/ocsp/fetch.rs @@ -23,10 +23,7 @@ use x509_parser::{ prelude::*, }; -use crate::{ - crypto::base64, - http::{AsyncHttpResolver, SyncHttpResolver}, -}; +use crate::{context::Context, crypto::base64}; const AD_OCSP_OID: Oid<'static> = oid!(1.3.6 .1 .5 .5 .7 .48 .1); const AUTHORITY_INFO_ACCESS_OID: Oid<'static> = oid!(1.3.6 .1 .5 .5 .7 .1 .1); @@ -145,14 +142,8 @@ fn process_ocsp_responders(certs: &[Vec]) -> Option> { /// /// Checks for an OCSP responder in the end-entity certificate. If found, it /// will attempt to retrieve the raw DER-encoded OCSP response. -#[async_generic(async_signature( - certs: &[Vec], - http_resolver: &impl AsyncHttpResolver, -))] -pub(crate) fn fetch_ocsp_response( - certs: &[Vec], - http_resolver: &impl SyncHttpResolver, -) -> Option> { +#[async_generic] +pub(crate) fn fetch_ocsp_response(certs: &[Vec], context: &Context) -> Option> { let requests = process_ocsp_responders(certs)?; for request_data in requests { let req_url = request_data.url.join(&request_data.request_str).ok()?; @@ -165,9 +156,13 @@ pub(crate) fn fetch_ocsp_response( let request = request.body(Vec::new()).ok()?; let response = if _sync { - http_resolver.http_resolve(request).ok()? + context.resolver().http_resolve(request).ok()? } else { - http_resolver.http_resolve_async(request).await.ok()? + context + .resolver_async() + .http_resolve_async(request) + .await + .ok()? }; if response.status() == 200 { diff --git a/sdk/src/identity/identity_assertion/assertion.rs b/sdk/src/identity/identity_assertion/assertion.rs index 424bfb285..cd059cc54 100644 --- a/sdk/src/identity/identity_assertion/assertion.rs +++ b/sdk/src/identity/identity_assertion/assertion.rs @@ -295,7 +295,7 @@ impl IdentityAssertion { partial_claim: &PartialClaim, status_tracker: &mut StatusTracker, ) -> Result> { - let settings = crate::settings::get_settings().unwrap_or_default(); + let settings = crate::Context::new().settings().clone(); self.check_padding(status_tracker)?; self.signer_payload @@ -310,19 +310,19 @@ impl IdentityAssertion { // are checked during setting generation. let cose_verifier = if settings.cawg_trust.verify_trust_list { - if let Some(ta) = settings.cawg_trust.trust_anchors { + if let Some(ta) = &settings.cawg_trust.trust_anchors { let _ = ctp.add_trust_anchors(ta.as_bytes()); } - if let Some(pa) = settings.cawg_trust.user_anchors { + if let Some(pa) = &settings.cawg_trust.user_anchors { let _ = ctp.add_user_trust_anchors(pa.as_bytes()); } - if let Some(tc) = settings.cawg_trust.trust_config { + if let Some(tc) = &settings.cawg_trust.trust_config { ctp.add_valid_ekus(tc.as_bytes()); } - if let Some(al) = settings.cawg_trust.allowed_list { + if let Some(al) = &settings.cawg_trust.allowed_list { let _ = ctp.add_end_entity_credentials(al.as_bytes()); } diff --git a/sdk/src/ingredient.rs b/sdk/src/ingredient.rs index 6f1aa71aa..f075dbafd 100644 --- a/sdk/src/ingredient.rs +++ b/sdk/src/ingredient.rs @@ -32,17 +32,16 @@ use crate::{ }, asset_io::CAIRead, claim::{Claim, ClaimAssetData}, + context::Context, crypto::base64, error::{Error, Result}, hashed_uri::HashedUri, - http::{AsyncGenericResolver, AsyncHttpResolver, SyncGenericResolver, SyncHttpResolver}, jumbf::{ self, labels::{assertion_label_from_uri, manifest_label_from_uri}, }, log_item, resource_store::{ResourceRef, ResourceStore}, - settings::Settings, status_tracker::StatusTracker, store::Store, utils::{ @@ -732,9 +731,8 @@ impl Ingredient { path: P, options: &dyn IngredientOptions, ) -> Result { - let settings = crate::settings::get_settings().unwrap_or_default(); - let http_resolver = SyncGenericResolver::new(); - Self::from_file_impl(path.as_ref(), options, &http_resolver, &settings) + let context = Context::new(); + Self::from_file_impl(path.as_ref(), options, &context) } // Internal implementation to avoid code bloat. @@ -742,8 +740,7 @@ impl Ingredient { fn from_file_impl( path: &Path, options: &dyn IngredientOptions, - http_resolver: &impl SyncHttpResolver, - settings: &Settings, + context: &Context, ) -> Result { #[cfg(feature = "diagnostics")] let _t = crate::utils::time_it::TimeIt::new("Ingredient:from_file_with_options"); @@ -774,25 +771,15 @@ impl Ingredient { let mut validation_log = StatusTracker::default(); // retrieve the manifest bytes from embedded, sidecar or remote and convert to store if found - let (result, manifest_bytes) = - match Store::load_jumbf_from_path(path, http_resolver, settings) { - Ok(manifest_bytes) => { - ( - // generate a store from the buffer and then validate from the asset path - Store::from_jumbf_with_settings( - &manifest_bytes, - &mut validation_log, - settings, - ) + let (result, manifest_bytes) = match Store::load_jumbf_from_path(path, context) { + Ok(manifest_bytes) => { + ( + // generate a store from the buffer and then validate from the asset path + Store::from_jumbf_with_context(&manifest_bytes, &mut validation_log, context) .and_then(|mut store| { // verify the store store - .verify_from_path( - path, - &mut validation_log, - http_resolver, - settings, - ) + .verify_from_path(path, &mut validation_log, context) .map(|_| store) }) .inspect_err(|e| { @@ -800,11 +787,11 @@ impl Ingredient { log_item!("asset", "error loading file", "Ingredient::from_file") .failure_no_throw(&mut validation_log, e); }), - Some(manifest_bytes), - ) - } - Err(err) => (Err(err), None), - }; + Some(manifest_bytes), + ) + } + Err(err) => (Err(err), None), + }; // set validation status from result and log ingredient.update_validation_status(result, manifest_bytes, &validation_log)?; @@ -819,7 +806,7 @@ impl Ingredient { ingredient.maybe_add_thumbnail( &format, &mut std::io::BufReader::new(std::fs::File::open(path)?), - settings, + context, )?; } } @@ -841,10 +828,10 @@ impl Ingredient { /// This does not set title or hash. /// Thumbnail will be set only if one can be retrieved from a previous valid manifest. pub fn from_stream(format: &str, stream: &mut dyn CAIRead) -> Result { - let settings = crate::settings::get_settings().unwrap_or_default(); + let context = Context::new(); let ingredient = Self::from_stream_info(stream, format, "untitled"); stream.rewind()?; - ingredient.add_stream_internal(format, stream, &SyncGenericResolver::new(), &settings) + ingredient.add_stream_internal(format, stream, &context) } /// Create an Ingredient from JSON. @@ -859,19 +846,12 @@ impl Ingredient { /// Sets thumbnail if not defined and a valid claim thumbnail is found or add_thumbnails is enabled. /// Instance_id, document_id, and provenance will be overridden if found in the stream. /// Format will be overridden only if it is the default (application/octet-stream). - #[async_generic(async_signature( - mut self, - format: S, - stream: &mut dyn CAIRead, - http_resolver: &impl AsyncHttpResolver, - settings: &Settings, - ))] + #[async_generic] pub(crate) fn with_stream>( mut self, format: S, stream: &mut dyn CAIRead, - http_resolver: &impl SyncHttpResolver, - settings: &Settings, + context: &Context, ) -> Result { let format = format.into(); @@ -903,27 +883,20 @@ impl Ingredient { stream.rewind()?; if _sync { - self.add_stream_internal(&format, stream, http_resolver, settings) + self.add_stream_internal(&format, stream, context) } else { - self.add_stream_internal_async(&format, stream, http_resolver, settings) + self.add_stream_internal_async(&format, stream, context) .await } } // Internal implementation to avoid code bloat. - #[async_generic(async_signature( - mut self, - format: &str, - stream: &mut dyn CAIRead, - http_resolver: &impl AsyncHttpResolver, - settings: &Settings, - ))] + #[async_generic] fn add_stream_internal( mut self, format: &str, stream: &mut dyn CAIRead, - http_resolver: &impl SyncHttpResolver, - settings: &Settings, + context: &Context, ) -> Result { let mut validation_log = StatusTracker::default(); @@ -931,9 +904,9 @@ impl Ingredient { let jumbf_result = match self.manifest_data() { Some(data) => Ok(data.into_owned()), None => if _sync { - Store::load_jumbf_from_stream(format, stream, http_resolver, settings) + Store::load_jumbf_from_stream(format, stream, context) } else { - Store::load_jumbf_from_stream_async(format, stream, http_resolver, settings).await + Store::load_jumbf_from_stream_async(format, stream, context).await } .map(|(manifest_bytes, _)| manifest_bytes), }; @@ -946,20 +919,16 @@ impl Ingredient { &manifest_bytes, format, &mut *stream, - true, &mut validation_log, - http_resolver, - settings, + context, ) } else { Store::from_manifest_data_and_stream_async( &manifest_bytes, format, &mut *stream, - true, &mut validation_log, - http_resolver, - settings, + context, ) .await }; @@ -970,23 +939,13 @@ impl Ingredient { // Fetch ocsp responses and store it with the ingredient if let Ok(ref mut store) = result { - let labels = store.get_manifest_labels_for_ocsp(settings); + let labels = store.get_manifest_labels_for_ocsp(context.settings()); let ocsp_response_ders = if _sync { - store.get_ocsp_response_ders( - labels, - &mut validation_log, - http_resolver, - settings, - )? + store.get_ocsp_response_ders(labels, &mut validation_log, context)? } else { store - .get_ocsp_response_ders_async( - labels, - &mut validation_log, - http_resolver, - settings, - ) + .get_ocsp_response_ders_async(labels, &mut validation_log, context) .await? }; @@ -1003,7 +962,7 @@ impl Ingredient { // create a thumbnail if we don't already have a manifest with a thumb we can use #[cfg(feature = "add_thumbnails")] - self.maybe_add_thumbnail(format, &mut std::io::BufReader::new(stream), settings)?; + self.maybe_add_thumbnail(format, &mut std::io::BufReader::new(stream), context)?; Ok(self) } @@ -1022,16 +981,14 @@ impl Ingredient { /// This does not set title or hash. /// Thumbnail will be set only if one can be retrieved from a previous valid manifest. pub async fn from_stream_async(format: &str, stream: &mut dyn CAIRead) -> Result { - let settings = crate::settings::get_settings().unwrap_or_default(); - let http_resolver = AsyncGenericResolver::new(); - Self::from_stream_async_with_settings(format, stream, &http_resolver, &settings).await + let context = Context::new(); + Self::from_stream_async_with_settings(format, stream, &context).await } pub(crate) async fn from_stream_async_with_settings( format: &str, stream: &mut dyn CAIRead, - http_resolver: &impl AsyncHttpResolver, - settings: &Settings, + context: &Context, ) -> Result { let mut ingredient = Self::from_stream_info(stream, format, "untitled"); stream.rewind()?; @@ -1039,57 +996,50 @@ impl Ingredient { let mut validation_log = StatusTracker::default(); // retrieve the manifest bytes from embedded, sidecar or remote and convert to store if found - let (result, manifest_bytes) = match Store::load_jumbf_from_stream_async( - format, - stream, - &AsyncGenericResolver::new(), - settings, - ) - .await - { - Ok((manifest_bytes, _)) => { - ( - // generate a store from the buffer and then validate from the asset path - match Store::from_jumbf_with_settings( - &manifest_bytes, - &mut validation_log, - settings, - ) { - Ok(store) => { - // verify the store - Store::verify_store_async( - &store, - &mut ClaimAssetData::Stream(stream, format), - &mut validation_log, - http_resolver, - settings, - ) - .await - .map(|_| store) - } - Err(e) => { - log_item!( - "asset", - "error loading asset", - "Ingredient::from_stream_async" - ) - .failure_no_throw(&mut validation_log, &e); + let (result, manifest_bytes) = + match Store::load_jumbf_from_stream_async(format, stream, context).await { + Ok((manifest_bytes, _)) => { + ( + // generate a store from the buffer and then validate from the asset path + match Store::from_jumbf_with_context( + &manifest_bytes, + &mut validation_log, + context, + ) { + Ok(store) => { + // verify the store + Store::verify_store_async( + &store, + &mut ClaimAssetData::Stream(stream, format), + &mut validation_log, + context, + ) + .await + .map(|_| store) + } + Err(e) => { + log_item!( + "asset", + "error loading asset", + "Ingredient::from_stream_async" + ) + .failure_no_throw(&mut validation_log, &e); - Err(e) - } - }, - Some(manifest_bytes), - ) - } - Err(err) => (Err(err), None), - }; + Err(e) + } + }, + Some(manifest_bytes), + ) + } + Err(err) => (Err(err), None), + }; // set validation status from result and log ingredient.update_validation_status(result, manifest_bytes, &validation_log)?; // create a thumbnail if we don't already have a manifest with a thumb we can use #[cfg(feature = "add_thumbnails")] - ingredient.maybe_add_thumbnail(format, &mut std::io::BufReader::new(stream), settings)?; + ingredient.maybe_add_thumbnail(format, &mut std::io::BufReader::new(stream), context)?; Ok(ingredient) } @@ -1249,7 +1199,7 @@ impl Ingredient { claim: &mut Claim, redactions: Option>, resources: Option<&ResourceStore>, // use alternate resource store (for Builder model) - settings: &Settings, + context: &Context, ) -> Result { let mut thumbnail = None; // for Builder model, ingredient resources may be in the manifest @@ -1270,7 +1220,7 @@ impl Ingredient { // have Store check and load ingredients and add them to a claim let ingredient_store = - Store::load_ingredient_to_claim(claim, &manifest_data, redactions, settings)?; + Store::load_ingredient_to_claim(claim, &manifest_data, redactions, context)?; let ingredient_active_claim = ingredient_store .provenance_claim() @@ -1488,48 +1438,43 @@ impl Ingredient { format: &str, stream: &mut dyn CAIRead, ) -> Result { - let settings = crate::settings::get_settings().unwrap_or_default(); - let http_resolver = AsyncGenericResolver::new(); + let context = Context::new(); let mut ingredient = Self::from_stream_info(stream, format, "untitled"); let mut validation_log = StatusTracker::default(); let manifest_bytes: Vec = manifest_bytes.into(); // generate a store from the buffer and then validate from the asset path - let result = match Store::from_jumbf_with_settings( - &manifest_bytes, - &mut validation_log, - &settings, - ) { - Ok(store) => { - // verify the store - stream.rewind()?; - - Store::verify_store_async( - &store, - &mut ClaimAssetData::Stream(stream, format), - &mut validation_log, - &http_resolver, - &settings, - ) - .await - .map(|_| store) - } - Err(e) => { - // add a log entry for the error so we act like verify - log_item!("asset", "error loading file", "Ingredient::from_file") - .failure_no_throw(&mut validation_log, &e); + let result = + match Store::from_jumbf_with_context(&manifest_bytes, &mut validation_log, &context) { + Ok(store) => { + // verify the store + stream.rewind()?; + + Store::verify_store_async( + &store, + &mut ClaimAssetData::Stream(stream, format), + &mut validation_log, + &context, + ) + .await + .map(|_| store) + } + Err(e) => { + // add a log entry for the error so we act like verify + log_item!("asset", "error loading file", "Ingredient::from_file") + .failure_no_throw(&mut validation_log, &e); - Err(e) - } - }; + Err(e) + } + }; // set validation status from result and log ingredient.update_validation_status(result, Some(manifest_bytes), &validation_log)?; // create a thumbnail if we don't already have a manifest with a thumb we can use #[cfg(feature = "add_thumbnails")] - ingredient.maybe_add_thumbnail(format, &mut std::io::BufReader::new(stream), &settings)?; + ingredient.maybe_add_thumbnail(format, &mut std::io::BufReader::new(stream), &context)?; Ok(ingredient) } @@ -1543,11 +1488,12 @@ impl Ingredient { &mut self, format: &str, stream: &mut R, - settings: &Settings, + context: &Context, ) -> Result<()> where R: std::io::BufRead + std::io::Seek, { + let settings = context.settings(); let auto_thumbnail = settings.builder.thumbnail.enabled; if self.thumbnail.is_none() && auto_thumbnail { @@ -1923,7 +1869,6 @@ mod tests_file_io { const NO_MANIFEST_JPEG: &str = "earth_apollo17.jpg"; const MANIFEST_JPEG: &str = "C.jpg"; const BAD_SIGNATURE_JPEG: &str = "E-sig-CA.jpg"; - const PRERELEASE_JPEG: &str = "prerelease.jpg"; fn stats(ingredient: &Ingredient) -> usize { let thumb_size = ingredient.thumbnail_bytes().map_or(0, |i| i.len()); @@ -2083,6 +2028,7 @@ mod tests_file_io { #[test] #[cfg(all(feature = "file_io", feature = "add_thumbnails"))] fn test_jpg_prerelease() { + const PRERELEASE_JPEG: &str = "prerelease.jpg"; let ap = fixture_path(PRERELEASE_JPEG); let ingredient = Ingredient::from_file(ap).expect("from_file"); stats(&ingredient); diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index ca9752126..29a7bd017 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -118,6 +118,10 @@ pub const VERSION: &str = env!("CARGO_PKG_VERSION"); /// The assertions module contains the definitions for the assertions that are part of the C2PA specification. pub mod assertions; +pub mod context; +pub use context::{Context, IntoSettings}; +pub mod content_credential; +pub use content_credential::ContentCredential; /// The cose_sign module contains the definitions for the COSE signing algorithms. pub mod cose_sign; @@ -188,6 +192,7 @@ pub use validation_results::{ValidationResults, ValidationState}; // Internal modules pub(crate) mod assertion; +pub(crate) mod asset; pub(crate) mod asset_handlers; pub(crate) mod asset_io; pub(crate) mod builder; diff --git a/sdk/src/manifest.rs b/sdk/src/manifest.rs index addc4db53..80b26516e 100644 --- a/sdk/src/manifest.rs +++ b/sdk/src/manifest.rs @@ -92,6 +92,7 @@ pub struct Manifest { /// A List of ingredients #[serde(default = "default_vec::")] + #[serde(skip_serializing_if = "Vec::is_empty")] pub(crate) ingredients: Vec, /// A List of verified credentials diff --git a/sdk/src/reader.rs b/sdk/src/reader.rs index 0eff6e704..0b86e3146 100644 --- a/sdk/src/reader.rs +++ b/sdk/src/reader.rs @@ -33,14 +33,13 @@ use serde_with::skip_serializing_none; use crate::utils::io_utils::uri_to_path; use crate::{ claim::Claim, + context::Context, dynamic_assertion::PartialClaim, error::{Error, Result}, - http::{AsyncGenericResolver, SyncGenericResolver}, jumbf::labels::{manifest_label_from_uri, to_absolute_uri, to_relative_uri}, jumbf_io, log_item, manifest::StoreOptions, manifest_store_report::ManifestStoreReport, - settings::Settings, status_tracker::StatusTracker, store::Store, utils::hash_utils::hash_to_b64, @@ -49,6 +48,18 @@ use crate::{ Ingredient, Manifest, ManifestAssertion, Relationship, }; +/// MaybeSend allows for no Send bound on wasm32 targets +/// todo: move this to a common module +#[cfg(not(target_arch = "wasm32"))] +pub trait MaybeSend: Send {} +#[cfg(target_arch = "wasm32")] +pub trait MaybeSend {} + +#[cfg(not(target_arch = "wasm32"))] +impl MaybeSend for T {} +#[cfg(target_arch = "wasm32")] +impl MaybeSend for T {} + /// A trait for post-validation of manifest assertions. pub trait PostValidator { fn validate( @@ -97,18 +108,63 @@ pub struct Reader { #[serde(skip)] /// We keep this around so we can generate a detailed report if needed - store: Store, + pub(crate) store: Store, #[serde(skip)] /// Map to hold post-validation assertion values for reports /// the key is an assertion uri and the value is the assertion value assertion_values: HashMap, + + #[serde(skip)] + context: Context, } type ValidationFn = dyn Fn(&str, &crate::ManifestAssertion, &mut StatusTracker) -> Option; impl Reader { + /// Create a new Reader with the given Context + /// # Arguments + /// * `context` - The Context to use for the Reader + /// # Returns + /// A new Reader + pub fn new(context: Context) -> Self { + Self { + context, + store: Store::new(), + assertion_values: HashMap::new(), + ..Default::default() + } + } + + /// Add manifest store from a stream to the Reader + /// # Arguments + /// * `format` - The format of the stream. MIME type or extension that maps to a MIME type. + /// * `stream` - The stream to read from. Must implement the Read and Seek traits. + /// # Returns + /// The updated Reader. + #[async_generic] + pub fn with_stream( + mut self, + format: &str, + mut stream: impl Read + Seek + MaybeSend, + ) -> Result { + let mut validation_log = StatusTracker::default(); + stream.rewind()?; // Ensure stream is at the start + let store = if _sync { + Store::from_stream(format, stream, &mut validation_log, &self.context) + } else { + Store::from_stream_async(format, stream, &mut validation_log, &self.context).await + }?; + + if _sync { + self.with_store(store, &mut validation_log) + } else { + self.with_store_async(store, &mut validation_log).await + }?; + Ok(self) + } + /// Create a manifest store [`Reader`] from a stream. A Reader is used to validate C2PA data from an asset. /// /// # Arguments @@ -137,86 +193,13 @@ impl Reader { /// /// [CAWG identity]: https://cawg.io/identity/ #[async_generic] - #[cfg(not(target_arch = "wasm32"))] - pub fn from_stream(format: &str, mut stream: impl Read + Seek + Send) -> Result { - let settings = crate::settings::get_settings().unwrap_or_default(); - let http_resolver = if _sync { - SyncGenericResolver::new() - } else { - AsyncGenericResolver::new() - }; - // TODO: passing verify is redundant with settings - let verify = settings.verify.verify_after_reading; - - let mut validation_log = StatusTracker::default(); - stream.rewind()?; // Ensure stream is at the start - let store = if _sync { - Store::from_stream( - format, - stream, - verify, - &mut validation_log, - &http_resolver, - &settings, - ) - } else { - Store::from_stream_async( - format, - stream, - verify, - &mut validation_log, - &http_resolver, - &settings, - ) - .await - }?; - - if _sync { - Self::from_store(store, &mut validation_log, &settings) - } else { - Self::from_store_async(store, &mut validation_log, &settings).await - } - } - - #[async_generic] - #[cfg(target_arch = "wasm32")] - pub fn from_stream(format: &str, mut stream: impl Read + Seek) -> Result { - let settings = crate::settings::get_settings().unwrap_or_default(); - let http_resolver = if _sync { - SyncGenericResolver::new() - } else { - AsyncGenericResolver::new() - }; - // TODO: passing verify is redundant with settings - let verify = settings.verify.verify_after_reading; - - let mut validation_log = StatusTracker::default(); - - let store = if _sync { - Store::from_stream( - format, - &mut stream, - verify, - &mut validation_log, - &http_resolver, - &settings, - ) - } else { - Store::from_stream_async( - format, - &mut stream, - verify, - &mut validation_log, - &http_resolver, - &settings, - ) - .await - }?; - + pub fn from_stream(format: &str, stream: impl Read + Seek + MaybeSend) -> Result { if _sync { - Self::from_store(store, &mut validation_log, &settings) + Reader::new(Context::new()).with_stream(format, stream) } else { - Self::from_store_async(store, &mut validation_log, &settings).await + Reader::new(Context::new()) + .with_stream_async(format, stream) + .await } } @@ -308,41 +291,35 @@ impl Reader { format: &str, stream: impl Read + Seek + Send, ) -> Result { - let settings = crate::settings::get_settings().unwrap_or_default(); - let http_resolver = if _sync { - SyncGenericResolver::new() - } else { - AsyncGenericResolver::new() - }; + let context = Context::new(); + let mut reader = Reader::new(context); let mut validation_log = StatusTracker::default(); - let verify = settings.verify.verify_after_reading; - let store = if _sync { Store::from_manifest_data_and_stream( c2pa_data, format, stream, - verify, &mut validation_log, - &http_resolver, - &settings, + &reader.context, ) } else { Store::from_manifest_data_and_stream_async( c2pa_data, format, stream, - verify, &mut validation_log, - &http_resolver, - &settings, + &reader.context, ) .await }?; - - Self::from_store(store, &mut validation_log, &settings) + if _sync { + reader.with_store(store, &mut validation_log) + } else { + reader.with_store_async(store, &mut validation_log).await + }?; + Ok(reader) } /// Create a [`Reader`] from an initial segment and a fragment stream. @@ -362,12 +339,7 @@ impl Reader { mut stream: impl Read + Seek + Send, mut fragment: impl Read + Seek + Send, ) -> Result { - let settings = crate::settings::get_settings().unwrap_or_default(); - let http_resolver = if _sync { - SyncGenericResolver::new() - } else { - AsyncGenericResolver::new() - }; + let mut reader = Reader::new(Context::new()); let mut validation_log = StatusTracker::default(); @@ -377,8 +349,7 @@ impl Reader { &mut stream, &mut fragment, &mut validation_log, - &http_resolver, - &settings, + &reader.context, ) } else { Store::load_fragment_from_stream_async( @@ -386,13 +357,17 @@ impl Reader { &mut stream, &mut fragment, &mut validation_log, - &http_resolver, - &settings, + &reader.context, ) .await }?; - Self::from_store(store, &mut validation_log, &settings) + if _sync { + reader.with_store(store, &mut validation_log) + } else { + reader.with_store_async(store, &mut validation_log).await + }?; + Ok(reader) } /// Loads a [`Reader`]` from an initial segment and fragments. This @@ -403,10 +378,8 @@ impl Reader { path: P, fragments: &Vec, ) -> Result { - let settings = crate::settings::get_settings().unwrap_or_default(); - let http_resolver = SyncGenericResolver::new(); + let mut reader = Reader::new(Context::new()); - let verify = settings.verify.verify_after_reading; let mut validation_log = StatusTracker::default(); let asset_type = jumbf_io::get_supported_file_extension(path.as_ref()) @@ -418,12 +391,13 @@ impl Reader { &asset_type, &mut init_segment, fragments, - verify, &mut validation_log, - &http_resolver, - &settings, + &reader.context, ) { - Ok(store) => Self::from_store(store, &mut validation_log, &settings), + Ok(store) => { + reader.with_store(store, &mut validation_log)?; + Ok(reader) + } Err(e) => Err(e), } } @@ -572,13 +546,13 @@ impl Reader { /// Get the [`ValidationState`] of the manifest store. pub fn validation_state(&self) -> ValidationState { - let settings = crate::settings::get_settings().unwrap_or_default(); + let context = Context::new(); if let Some(validation_results) = self.validation_results() { return validation_results.validation_state(); } - let verify_trust = settings.verify.verify_trust; + let verify_trust = context.settings().verify.verify_trust; match self.validation_status() { Some(status) => { // if there are any errors, the state is invalid unless the only error is an untrusted credential @@ -726,7 +700,9 @@ impl Reader { #[cfg(feature = "file_io")] pub fn to_folder>(&self, path: P) -> Result<()> { std::fs::create_dir_all(&path)?; - std::fs::write(path.as_ref().join("manifest.json"), self.json())?; + std::fs::write(path.as_ref().join("manifest_store.json"), self.json())?; + let c2pa_data = self.store.to_jumbf_internal(0)?; + std::fs::write(path.as_ref().join("manifest_data.c2pa"), c2pa_data)?; for manifest in self.manifests.values() { let resources = manifest.resources(); for (uri, data) in resources.resources() { @@ -743,11 +719,11 @@ impl Reader { } #[async_generic()] - pub(crate) fn from_store( + pub(crate) fn with_store( + &mut self, store: Store, validation_log: &mut StatusTracker, - settings: &Settings, - ) -> Result { + ) -> Result<&Self> { let active_manifest = store.provenance_label(); let mut manifests = HashMap::new(); let mut options = StoreOptions::default(); @@ -760,7 +736,7 @@ impl Reader { manifest_label, &mut options, validation_log, - settings, + self.context.settings(), ) } else { Manifest::from_store_async( @@ -768,7 +744,7 @@ impl Reader { manifest_label, &mut options, validation_log, - settings, + self.context.settings(), ) .await }; @@ -804,7 +780,6 @@ impl Reader { missing.retain(|item| !options.redacted_assertions.contains(item)); // Add any remaining redacted assertions to the validation results - // todo: figure out what to do here! for uri in &redacted { log_item!(uri.clone(), "assertion not redacted", "Reader::from_store") .validation_status(ASSERTION_NOT_REDACTED) @@ -818,15 +793,14 @@ impl Reader { } let validation_state = validation_results.validation_state(); - Ok(Self { - active_manifest, - manifests, - validation_status: validation_results.validation_errors(), - validation_results: Some(validation_results), - validation_state: Some(validation_state), - store, - assertion_values: HashMap::new(), - }) + + self.active_manifest = active_manifest; + self.manifests = manifests; + self.validation_status = validation_results.validation_errors(); + self.validation_results = Some(validation_results); + self.validation_state = Some(validation_state); + self.store = store; + Ok(self) } /// Populate manifest_data references for all ingredients in a manifest @@ -1214,6 +1188,23 @@ pub mod tests { Ok(()) } + #[test] + fn test_reader_new_with_stream() -> Result<()> { + const TEST_SETTINGS: &str = include_str!("../tests/fixtures/test_settings.toml"); + let context = Context::new().with_settings(TEST_SETTINGS)?; + + let mut source = Cursor::new(IMAGE_WITH_MANIFEST); + + let reader = Reader::new(context).with_stream("image/jpeg", &mut source)?; + + assert_eq!(reader.remote_url(), None); + assert!(reader.is_embedded()); + assert_eq!(reader.validation_state(), ValidationState::Trusted); + assert!(reader.active_manifest().is_some()); + + Ok(()) + } + #[test] #[cfg(feature = "fetch_remote_manifests")] fn test_reader_remote_url() -> Result<()> { @@ -1325,7 +1316,9 @@ pub mod tests { let temp_dir = tempdirectory().unwrap(); reader.to_folder(temp_dir.path())?; - let path = temp_dir_path(&temp_dir, "manifest.json"); + let path = temp_dir_path(&temp_dir, "manifest_store.json"); + assert!(path.exists()); + let path = temp_dir_path(&temp_dir, "manifest_data.c2pa"); assert!(path.exists()); Ok(()) } diff --git a/sdk/src/settings/builder.rs b/sdk/src/settings/builder.rs index 234e1de05..d762ec906 100644 --- a/sdk/src/settings/builder.rs +++ b/sdk/src/settings/builder.rs @@ -222,6 +222,38 @@ impl TryFrom for ClaimGeneratorInfo { } } +impl TryFrom<&ClaimGeneratorInfoSettings> for ClaimGeneratorInfo { + type Error = Error; + + fn try_from(value: &ClaimGeneratorInfoSettings) -> Result { + Ok(ClaimGeneratorInfo { + name: value.name.clone(), + version: value.version.clone(), + icon: value + .icon + .as_ref() + .map(|icon| UriOrResource::ResourceRef(icon.clone())), + operating_system: { + value.operating_system.as_ref().map(|os| match os { + ClaimGeneratorInfoOperatingSystem::Auto => { + format!("{}-unknown-{}", consts::ARCH, consts::OS) + } + ClaimGeneratorInfoOperatingSystem::Other(name) => name.clone(), + }) + }, + other: value + .other + .iter() + .map(|(key, value)| { + serde_json::to_value(value) + .map(|value| (key.clone(), value)) + .map_err(|err| err.into()) + }) + .collect::>>()?, + }) + } +} + /// Settings for an action template. #[cfg_attr(feature = "json_schema", derive(JsonSchema))] #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] @@ -418,6 +450,9 @@ impl SettingsValidate for ActionsSettings { #[cfg_attr(feature = "json_schema", derive(JsonSchema))] #[derive(Clone, Debug, Deserialize, PartialEq, Serialize, Default)] pub struct BuilderSettings { + /// The name of the vendor creating the content credential. + pub vendor: Option, + /// Claim generator info that is automatically added to the builder. /// /// Note that this information will prepend any claim generator info diff --git a/sdk/src/settings/mod.rs b/sdk/src/settings/mod.rs index f271a062c..a25cd7ccc 100644 --- a/sdk/src/settings/mod.rs +++ b/sdk/src/settings/mod.rs @@ -424,6 +424,78 @@ impl Settings { Settings::from_string(toml, "toml").map(|_| ()) } + /// Update this `Settings` instance from a string representation. + /// This overlays the provided configuration on top of the current settings + /// without affecting the global thread-local settings. + /// + /// # Arguments + /// * `settings_str` - The configuration string + /// * `format` - The format of the configuration ("json" or "toml") + /// + /// # Example + /// ``` + /// use c2pa::settings::Settings; + /// + /// let mut settings = Settings::default(); + /// + /// // Update with TOML + /// settings + /// .update_from_str( + /// r#" + /// [verify] + /// verify_after_sign = false + /// "#, + /// "toml", + /// ) + /// .unwrap(); + /// + /// assert!(!settings.verify.verify_after_sign); + /// + /// // Update with JSON (can set values to null) + /// settings + /// .update_from_str( + /// r#" + /// { + /// "verify": { + /// "verify_after_sign": true + /// } + /// } + /// "#, + /// "json", + /// ) + /// .unwrap(); + /// + /// assert!(settings.verify.verify_after_sign); + /// ``` + pub fn update_from_str(&mut self, settings_str: &str, format: &str) -> Result<()> { + let file_format = match format.to_lowercase().as_str() { + "json" => FileFormat::Json, + "toml" => FileFormat::Toml, + _ => return Err(Error::UnsupportedType), + }; + + // Convert current settings to Config + let current_config = Config::try_from(&*self) + .map_err(|e| Error::BadParam(format!("could not convert settings: {}", e)))?; + + // Build new config with the source + let merged_config = Config::builder() + .add_source(current_config) + .add_source(config::File::from_str(settings_str, file_format)) + .build() + .map_err(|e| Error::BadParam(format!("could not merge configuration: {}", e)))?; + + // Deserialize and validate + let updated_settings = merged_config + .try_deserialize::() + .map_err(|e| Error::BadParam(e.to_string()))?; + + updated_settings.validate()?; + + *self = updated_settings; + Ok(()) + } + /// Set a [Settings] value by path reference. The path is nested names of of the Settings objects /// separated by "." notation. /// @@ -865,4 +937,127 @@ pub mod tests { let toml = include_bytes!("../../examples/c2pa.toml"); Settings::from_toml(std::str::from_utf8(toml).unwrap()).unwrap(); } + + #[test] + fn test_update_from_str_toml() { + let mut settings = Settings::default(); + + // Check defaults + assert!(settings.verify.verify_after_sign); + assert!(settings.verify.verify_trust); + + // Set both to false + settings + .update_from_str( + r#" + [verify] + verify_after_sign = false + verify_trust = false + "#, + "toml", + ) + .unwrap(); + + assert!(!settings.verify.verify_after_sign); + assert!(!settings.verify.verify_trust); + + // Override: set one to true, keep other false + settings + .update_from_str( + r#" + [verify] + verify_after_sign = true + "#, + "toml", + ) + .unwrap(); + + assert!(settings.verify.verify_after_sign); + assert!(!settings.verify.verify_trust); + } + + #[test] + fn test_update_from_str_json() { + let mut settings = Settings::default(); + + // Check defaults + assert!(settings.verify.verify_after_sign); + assert!(settings.verify.verify_trust); + assert!(settings.builder.created_assertion_labels.is_none()); + + // Set both to false and set created_assertion_labels + settings + .update_from_str( + r#" + { + "verify": { + "verify_after_sign": false, + "verify_trust": false + }, + "builder": { + "created_assertion_labels": ["c2pa.metadata"] + } + } + "#, + "json", + ) + .unwrap(); + + assert!(!settings.verify.verify_after_sign); + assert!(!settings.verify.verify_trust); + assert_eq!( + settings.builder.created_assertion_labels, + Some(vec!["c2pa.metadata".to_string()]) + ); + + // Override: set one to true, keep other false + settings + .update_from_str( + r#" + { + "verify": { + "verify_after_sign": true + } + } + "#, + "json", + ) + .unwrap(); + + assert!(settings.verify.verify_after_sign); + assert!(!settings.verify.verify_trust); + assert_eq!( + settings.builder.created_assertion_labels, + Some(vec!["c2pa.metadata".to_string()]) + ); + + // Set created_assertion_labels back to null + settings + .update_from_str( + r#" + { + "builder": { + "created_assertion_labels": null + } + } + "#, + "json", + ) + .unwrap(); + + assert!(settings.verify.verify_after_sign); + assert!(!settings.verify.verify_trust); + assert!(settings.builder.created_assertion_labels.is_none()); + } + + #[test] + fn test_update_from_str_invalid() { + assert!(Settings::default() + .update_from_str("invalid toml { ]", "toml") + .is_err()); + assert!(Settings::default() + .update_from_str("{ invalid json }", "json") + .is_err()); + assert!(Settings::default().update_from_str("data", "yaml").is_err()); + } } diff --git a/sdk/src/settings/signer.rs b/sdk/src/settings/signer.rs index c862c068d..b3bc71ff5 100644 --- a/sdk/src/settings/signer.rs +++ b/sdk/src/settings/signer.rs @@ -70,81 +70,90 @@ impl SignerSettings { /// /// If the signer settings aren't specified, this function will return [Error::MissingSignerSettings]. pub fn signer() -> Result> { - let c2pa_signer = Self::c2pa_signer()?; + let signer_info = match Settings::get_value::>("signer") { + Ok(Some(signer_info)) => signer_info, + #[cfg(test)] + _ => { + return Ok(crate::utils::test_signer::test_signer(SigningAlg::Ps256)); + } + #[cfg(not(test))] + _ => { + return Err(Error::MissingSignerSettings); + } + }; + + let c2pa_signer = Self::c2pa_signer(signer_info)?; // TO DISCUSS: What if get_value returns an Err(...)? if let Ok(Some(cawg_x509_settings)) = Settings::get_value::>("cawg_x509_signer") { - match cawg_x509_settings { - SignerSettings::Local { - alg: cawg_alg, - sign_cert: cawg_sign_cert, - private_key: cawg_private_key, - tsa_url: cawg_tsa_url, - referenced_assertions, - } => { - let cawg_dual_signer = CawgX509IdentitySigner { - c2pa_signer, - cawg_alg, - cawg_sign_cert, - cawg_private_key, - cawg_tsa_url, - cawg_referenced_assertions: referenced_assertions.unwrap_or_default(), - }; - - Ok(Box::new(cawg_dual_signer)) - } - - SignerSettings::Remote { - url: _url, - alg: _alg, - sign_cert: _sign_cert, - tsa_url: _tsa_url, - referenced_assertions: _referenced_assertions, - } => todo!("Remote CAWG X.509 signing not yet supported"), - } + cawg_x509_settings.cawg_signer(c2pa_signer) } else { Ok(c2pa_signer) } } - /// Returns a C2PA-only signer from the [`BuilderSettings::signer`] field. - fn c2pa_signer() -> Result> { - let signer_info = Settings::get_value::>("signer"); - - match signer_info { - Ok(Some(signer_info)) => match signer_info { - SignerSettings::Local { - alg, - sign_cert, - private_key, - tsa_url, - referenced_assertions: _, - } => create_signer::from_keys( - sign_cert.as_bytes(), - private_key.as_bytes(), - alg, - tsa_url.to_owned(), - ), - SignerSettings::Remote { - url, - alg, - sign_cert, - tsa_url, - referenced_assertions: _, - } => Ok(Box::new(RemoteSigner { - url, - alg, - reserve_size: 10000 + sign_cert.len(), - certs: vec![sign_cert.into_bytes()], - tsa_url, - })), - }, - #[cfg(test)] - _ => Ok(crate::utils::test_signer::test_signer(SigningAlg::Ps256)), - #[cfg(not(test))] - _ => Err(Error::MissingSignerSettings), + /// Returns a c2pa signer using the provided signer settings. + pub fn c2pa_signer(self) -> Result> { + match self { + SignerSettings::Local { + alg, + sign_cert, + private_key, + tsa_url, + referenced_assertions: _, + } => create_signer::from_keys( + sign_cert.as_bytes(), + private_key.as_bytes(), + alg, + tsa_url.to_owned(), + ), + SignerSettings::Remote { + url, + alg, + sign_cert, + tsa_url, + referenced_assertions: _, + } => Ok(Box::new(RemoteSigner { + url, + alg, + reserve_size: 10000 + sign_cert.len(), + certs: vec![sign_cert.into_bytes()], + tsa_url, + })), + } + } + + /// Returns a CAWG X.509 identity signer that wraps the provided c2pa signer. + pub fn cawg_signer(self, c2pa_signer: Box) -> Result> { + match self { + SignerSettings::Local { + alg: cawg_alg, + sign_cert: cawg_sign_cert, + private_key: cawg_private_key, + tsa_url: cawg_tsa_url, + referenced_assertions: cawg_referenced_assertions, + } => { + let cawg_dual_signer = CawgX509IdentitySigner { + c2pa_signer, + cawg_alg, + cawg_sign_cert, + cawg_private_key, + cawg_tsa_url, + cawg_referenced_assertions: cawg_referenced_assertions.unwrap_or_default(), + }; + + Ok(Box::new(cawg_dual_signer)) + } + + SignerSettings::Remote { + url: _url, + alg: _alg, + sign_cert: _sign_cert, + tsa_url: _tsa_url, + referenced_assertions: _, + } => todo!("Remote CAWG X.509 signing not yet supported"), } } } diff --git a/sdk/src/store.rs b/sdk/src/store.rs index c1cd24a5e..9e216bb6a 100644 --- a/sdk/src/store.rs +++ b/sdk/src/store.rs @@ -41,6 +41,7 @@ use crate::{ check_ocsp_status, check_ocsp_status_async, Claim, ClaimAssertion, ClaimAssetData, RemoteManifest, }, + context::Context, cose_sign::{cose_sign, cose_sign_async}, cose_validator::{verify_cose, verify_cose_async}, crypto::{ @@ -90,6 +91,7 @@ use crate::{ }; const MANIFEST_STORE_EXT: &str = "c2pa"; // file extension for external manifests +#[cfg(feature = "fetch_remote_manifests")] const DEFAULT_MANIFEST_RESPONSE_SIZE: usize = 10 * 1024 * 1024; // 10 MB pub(crate) struct ManifestHashes { @@ -132,8 +134,7 @@ struct ManifestInfo<'a> { impl Default for Store { fn default() -> Self { - let settings = crate::settings::get_settings().unwrap_or_default(); - Self::with_settings(&settings) + Self::with_context(&Context::new()) } } @@ -153,8 +154,9 @@ impl Store { } /// Create a new, empty claims store with the specified settings. - pub fn with_settings(settings: &Settings) -> Self { + pub fn with_context(context: &Context) -> Self { let mut store = Store::new(); + let settings = context.settings(); // load the trust handler settings, don't worry about status as these are checked during setting generation if let Some(ta) = &settings.trust.trust_anchors { @@ -540,16 +542,8 @@ impl Store { // Currently only called from manifest_store behind a feature flag but this is allowable // anywhere so allow dead code here for future uses to compile #[allow(dead_code)] - #[async_generic(async_signature( - &self, - http_resolver: &impl AsyncHttpResolver, - settings: &Settings, - ))] - pub fn get_ocsp_status( - &self, - http_resolver: &impl SyncHttpResolver, - settings: &Settings, - ) -> Option { + #[async_generic] + pub fn get_ocsp_status(&self, context: &Context) -> Option { let claim = self .provenance_claim() .ok_or(Error::ProvenanceMissing) @@ -569,8 +563,7 @@ impl Store { None, None, &mut validation_log, - http_resolver, - settings, + context, ) } else { check_ocsp_status_async( @@ -580,8 +573,7 @@ impl Store { None, None, &mut validation_log, - http_resolver, - settings, + context, ) .await }; @@ -1194,12 +1186,12 @@ impl Store { } #[inline] - pub fn from_jumbf_with_settings( + pub fn from_jumbf_with_context( buffer: &[u8], validation_log: &mut StatusTracker, - settings: &Settings, + context: &Context, ) -> Result { - Self::from_jumbf_impl(Store::with_settings(settings), buffer, validation_log) + Self::from_jumbf_impl(Store::with_context(context), buffer, validation_log) } fn from_jumbf_impl( @@ -1559,9 +1551,9 @@ impl Store { svi: &StoreValidationInfo, asset_data: &mut ClaimAssetData<'_>, validation_log: &mut StatusTracker, - http_resolver: &impl SyncHttpResolver, - settings: &Settings, + context: &Context, ) -> Result<()> { + let settings = context.settings(); // walk the ingredients for i in claim.ingredient_assertions() { // allow for zero out ingredient assertions @@ -1737,8 +1729,7 @@ impl Store { check_ingredient_trust, &store.ctp, validation_log, - http_resolver, - settings, + context, )?; // recurse nested ingredients @@ -1748,8 +1739,7 @@ impl Store { svi, asset_data, validation_log, - http_resolver, - settings, + context, )?; } else { log_item!(label.clone(), "ingredient not found", "ingredient_checks") @@ -1783,9 +1773,9 @@ impl Store { svi: &StoreValidationInfo<'_>, asset_data: &mut ClaimAssetData<'_>, validation_log: &mut StatusTracker, - http_resolver: &impl AsyncHttpResolver, - settings: &Settings, + context: &Context, ) -> Result<()> { + let settings = context.settings(); // walk the ingredients for i in claim.ingredient_assertions() { // allow for zero out ingredient assertions @@ -1961,8 +1951,7 @@ impl Store { check_ingredient_trust, &store.ctp, validation_log, - http_resolver, - settings, + context, ) .await?; @@ -1973,8 +1962,7 @@ impl Store { svi, asset_data, validation_log, - http_resolver, - settings, + context, )) .await?; } else { @@ -2148,16 +2136,14 @@ impl Store { store: &Store, asset_data: &mut ClaimAssetData<'_>, validation_log: &mut StatusTracker, - http_resolver: &impl AsyncHttpResolver, - settings: &Settings, + context: &Context, ))] pub fn verify_store( store: &Store, asset_data: &mut ClaimAssetData<'_>, validation_log: &mut StatusTracker, - http_resolver: &impl SyncHttpResolver, - settings: &Settings, + context: &Context, ) -> Result<()> { let claim = match store.provenance_claim() { Some(c) => c, @@ -2171,7 +2157,12 @@ impl Store { }; // get info needed to complete validation - let svi = store.get_store_validation_info(claim, asset_data, validation_log, settings)?; + let svi = store.get_store_validation_info( + claim, + asset_data, + validation_log, + context.settings(), + )?; if _sync { // verify the provenance claim @@ -2182,19 +2173,10 @@ impl Store { true, &store.ctp, validation_log, - http_resolver, - settings, + context, )?; - Store::ingredient_checks( - store, - claim, - &svi, - asset_data, - validation_log, - http_resolver, - settings, - )?; + Store::ingredient_checks(store, claim, &svi, asset_data, validation_log, context)?; } else { Claim::verify_claim_async( claim, @@ -2203,21 +2185,12 @@ impl Store { true, &store.ctp, validation_log, - http_resolver, - settings, + context, ) .await?; - Store::ingredient_checks_async( - store, - claim, - &svi, - asset_data, - validation_log, - http_resolver, - settings, - ) - .await?; + Store::ingredient_checks_async(store, claim, &svi, asset_data, validation_log, context) + .await?; } Ok(()) @@ -2582,14 +2555,14 @@ impl Store { signer: &dyn Signer, format: &str, asset_reader: Option<&mut dyn CAIRead>, - settings: &Settings, + context: &Context, ) -> Result> { let mut jumbf_bytes = self.prep_embeddable_store(signer.reserve_size(), dh, asset_reader)?; // sign contents let pc = self.provenance_claim().ok_or(Error::ClaimEncoding)?; - let sig = self.sign_claim(pc, signer, signer.reserve_size(), settings)?; + let sig = self.sign_claim(pc, signer, signer.reserve_size(), context.settings())?; let sig_placeholder = Store::sign_claim_placeholder(pc, signer.reserve_size()); @@ -2613,7 +2586,7 @@ impl Store { signer: &dyn AsyncSigner, format: &str, asset_reader: Option<&mut dyn CAIRead>, - settings: &Settings, + context: &Context, ) -> Result> { let mut jumbf_bytes = self.prep_embeddable_store(signer.reserve_size(), dh, asset_reader)?; @@ -2621,7 +2594,7 @@ impl Store { // sign contents let pc = self.provenance_claim().ok_or(Error::ClaimEncoding)?; let sig = self - .sign_claim_async(pc, signer, signer.reserve_size(), settings) + .sign_claim_async(pc, signer, signer.reserve_size(), context.settings()) .await?; let sig_placeholder = Store::sign_claim_placeholder(pc, signer.reserve_size()); @@ -2634,7 +2607,7 @@ impl Store { pub fn get_box_hashed_embeddable_manifest( &mut self, signer: &dyn Signer, - settings: &Settings, + context: &Context, ) -> Result> { let pc = self.provenance_claim().ok_or(Error::ClaimEncoding)?; @@ -2653,7 +2626,7 @@ impl Store { let mut jumbf_bytes = self.to_jumbf_internal(signer.reserve_size())?; // sign contents - let sig = self.sign_claim(pc, signer, signer.reserve_size(), settings)?; + let sig = self.sign_claim(pc, signer, signer.reserve_size(), context.settings())?; let sig_placeholder = Store::sign_claim_placeholder(pc, signer.reserve_size()); if sig_placeholder.len() != sig.len() { @@ -2671,7 +2644,7 @@ impl Store { pub async fn get_box_hashed_embeddable_manifest_async( &mut self, signer: &dyn AsyncSigner, - settings: &Settings, + context: &Context, ) -> Result> { let pc = self.provenance_claim().ok_or(Error::ClaimEncoding)?; @@ -2691,7 +2664,7 @@ impl Store { // sign contents let sig = self - .sign_claim_async(pc, signer, signer.reserve_size(), settings) + .sign_claim_async(pc, signer, signer.reserve_size(), context.settings()) .await?; let sig_placeholder = Store::sign_claim_placeholder(pc, signer.reserve_size()); @@ -2878,7 +2851,7 @@ impl Store { fragments: &Vec, output_path: &Path, signer: &dyn Signer, - settings: &Settings, + context: &Context, ) -> Result<()> { match get_supported_file_extension(asset_path) { Some(ext) => { @@ -2903,15 +2876,14 @@ impl Store { let jumbf = self.to_jumbf(signer)?; // use temp store so mulitple calls across renditions will work (the Store is not finalized this way) - let mut temp_store = - Store::from_jumbf_with_settings(&jumbf, &mut validation_log, settings)?; + let mut temp_store = Store::from_jumbf_with_context(&jumbf, &mut validation_log, context)?; let mut jumbf_bytes = temp_store.start_save_bmff_fragmented( asset_path, fragments, output_path, signer.reserve_size(), - settings, + context.settings(), )?; let mut preliminary_claim = PartialClaim::default(); @@ -2960,7 +2932,7 @@ impl Store { // sign the claim let pc = temp_store.provenance_claim().ok_or(Error::ClaimEncoding)?; - let sig = temp_store.sign_claim(pc, signer, signer.reserve_size(), settings)?; + let sig = temp_store.sign_claim(pc, signer, signer.reserve_size(), context.settings())?; let sig_placeholder = Store::sign_claim_placeholder(pc, signer.reserve_size()); match temp_store.finish_save(jumbf_bytes, &dest_path, sig, &sig_placeholder) { @@ -2986,8 +2958,7 @@ impl Store { input_stream: &mut dyn CAIRead, output_stream: &mut dyn CAIReadWrite, signer: &dyn AsyncSigner, - http_resolver: &impl AsyncHttpResolver, - settings: &Settings, + context: &Context, ))] pub(crate) fn save_to_stream( &mut self, @@ -2995,9 +2966,9 @@ impl Store { input_stream: &mut dyn CAIRead, output_stream: &mut dyn CAIReadWrite, signer: &dyn Signer, - http_resolver: &impl SyncHttpResolver, - settings: &Settings, + context: &Context, ) -> Result> { + let settings = context.settings(); let dynamic_assertions = signer.dynamic_assertions(); let da_uris = if _sync { @@ -3105,16 +3076,14 @@ impl Store { self, &mut crate::claim::ClaimAssetData::Stream(output_stream, format), &mut validation_log, - http_resolver, - settings, + context, )?; } else { Store::verify_store_async( self, &mut crate::claim::ClaimAssetData::Stream(output_stream, format), &mut validation_log, - http_resolver, - settings, + context, ) .await?; } @@ -3396,32 +3365,27 @@ impl Store { &mut self, asset_path: &'_ Path, validation_log: &mut StatusTracker, - http_resolver: &impl SyncHttpResolver, - settings: &Settings, + context: &Context, ) -> Result<()> { Store::verify_store( self, &mut ClaimAssetData::Path(asset_path), validation_log, - http_resolver, - settings, + context, ) } // fetch remote manifest if possible #[cfg(feature = "fetch_remote_manifests")] - #[async_generic(async_signature( - url: &str, - http_resolver: &impl AsyncHttpResolver - ))] - fn fetch_remote_manifest(url: &str, http_resolver: &impl SyncHttpResolver) -> Result> { + #[async_generic] + fn fetch_remote_manifest(url: &str, context: &Context) -> Result> { //const MANIFEST_CONTENT_TYPE: &str = "application/x-c2pa-manifest-store"; // todo verify once these are served let request = http::Request::get(url).body(Vec::new())?; let response = if _sync { - http_resolver.http_resolve(request) + context.resolver().http_resolve(request) } else { - http_resolver.http_resolve_async(request).await + context.resolver_async().http_resolve_async(request).await }; match response { @@ -3460,26 +3424,18 @@ impl Store { } /// Handles remote manifests when file_io/fetch_remote_manifests feature is enabled - #[async_generic(async_signature( - ext_ref: &str, - http_resolver: &impl AsyncHttpResolver, - settings: &Settings - ))] - fn handle_remote_manifest( - ext_ref: &str, - http_resolver: &impl SyncHttpResolver, - settings: &Settings, - ) -> Result> { + #[async_generic] + fn handle_remote_manifest(ext_ref: &str, _context: &Context) -> Result> { // verify provenance path is remote url if Store::is_valid_remote_url(ext_ref) { #[cfg(feature = "fetch_remote_manifests")] { // Everything except browser wasm if fetch_remote_manifests is enabled - if settings.verify.remote_manifest_fetch { + if _context.settings().verify.remote_manifest_fetch { if _sync { - Store::fetch_remote_manifest(ext_ref, http_resolver) + Store::fetch_remote_manifest(ext_ref, _context) } else { - Store::fetch_remote_manifest_async(ext_ref, http_resolver).await + Store::fetch_remote_manifest_async(ext_ref, _context).await } } else { Err(Error::RemoteManifestUrl(ext_ref.to_owned())) @@ -3505,14 +3461,12 @@ impl Store { #[async_generic(async_signature( asset_type: &str, stream: &mut dyn CAIRead, - http_resolver: &impl AsyncHttpResolver, - settings: &Settings + context: &Context ))] pub fn load_jumbf_from_stream( asset_type: &str, stream: &mut dyn CAIRead, - http_resolver: &impl SyncHttpResolver, - settings: &Settings, + context: &Context, ) -> Result<(Vec, Option)> { match load_jumbf_from_stream(asset_type, stream) { Ok(manifest_bytes) => Ok((manifest_bytes, None)), @@ -3523,10 +3477,9 @@ impl Store { .provenance { let jumbf = if _sync { - Store::handle_remote_manifest(&ext_ref, http_resolver, settings)? + Store::handle_remote_manifest(&ext_ref, context)? } else { - Store::handle_remote_manifest_async(&ext_ref, http_resolver, settings) - .await? + Store::handle_remote_manifest_async(&ext_ref, context).await? }; Ok((jumbf, Some(ext_ref))) } else { @@ -3544,11 +3497,7 @@ impl Store { /// in_path - path to source file /// validation_log - optional vec to contain addition info about the asset #[cfg(feature = "file_io")] - pub fn load_jumbf_from_path( - in_path: &Path, - http_resolver: &impl SyncHttpResolver, - settings: &Settings, - ) -> Result> { + pub fn load_jumbf_from_path(in_path: &Path, context: &Context) -> Result> { let external_manifest = in_path.with_extension(MANIFEST_STORE_EXT); let external_exists = external_manifest.exists(); @@ -3574,7 +3523,7 @@ impl Store { ) .provenance { - Store::handle_remote_manifest(&ext_ref, http_resolver, settings) + Store::handle_remote_manifest(&ext_ref, context) } else { Err(Error::JumbfNotFound) } @@ -3612,27 +3561,18 @@ impl Store { } /// Load store from a stream - #[async_generic(async_signature( - format: &str, - mut stream: impl Read + Seek + Send, - verify: bool, - validation_log: &mut StatusTracker, - http_resolver: &impl AsyncHttpResolver, - settings: &Settings, - ))] + #[async_generic] #[cfg(not(target_arch = "wasm32"))] pub fn from_stream( format: &str, mut stream: impl Read + Seek + Send, - verify: bool, validation_log: &mut StatusTracker, - http_resolver: &impl SyncHttpResolver, - settings: &Settings, + context: &Context, ) -> Result { let (manifest_bytes, remote_url) = if _sync { - Store::load_jumbf_from_stream(format, &mut stream, http_resolver, settings) + Store::load_jumbf_from_stream(format, &mut stream, context) } else { - Store::load_jumbf_from_stream_async(format, &mut stream, http_resolver, settings).await + Store::load_jumbf_from_stream_async(format, &mut stream, context).await } .inspect_err(|e| { log_item!("asset", "error loading file", "load_from_asset") @@ -3644,20 +3584,16 @@ impl Store { &manifest_bytes, format, &mut stream, - verify, validation_log, - http_resolver, - settings, + context, ) } else { Self::from_manifest_data_and_stream_async( &manifest_bytes, format, &mut stream, - verify, validation_log, - http_resolver, - settings, + context, ) .await }; @@ -3673,27 +3609,18 @@ impl Store { } /// Load store from a stream - #[async_generic(async_signature( - format: &str, - mut stream: impl Read + Seek, - verify: bool, - validation_log: &mut StatusTracker, - http_resolver: &impl AsyncHttpResolver, - settings: &Settings, - ))] + #[async_generic] #[cfg(target_arch = "wasm32")] pub fn from_stream( format: &str, mut stream: impl Read + Seek, - verify: bool, validation_log: &mut StatusTracker, - http_resolver: &impl SyncHttpResolver, - settings: &Settings, + context: &Context, ) -> Result { let (manifest_bytes, remote_url) = if _sync { - Store::load_jumbf_from_stream(format, &mut stream, http_resolver, settings) + Store::load_jumbf_from_stream(format, &mut stream, context) } else { - Store::load_jumbf_from_stream_async(format, &mut stream, http_resolver, settings).await + Store::load_jumbf_from_stream_async(format, &mut stream, context).await } .inspect_err(|e| { log_item!("asset", "error loading file", "load_from_asset") @@ -3705,20 +3632,16 @@ impl Store { &manifest_bytes, format, &mut stream, - verify, validation_log, - http_resolver, - settings, + context, ) } else { Self::from_manifest_data_and_stream_async( &manifest_bytes, format, &mut stream, - verify, validation_log, - http_resolver, - settings, + context, ) .await }; @@ -3734,107 +3657,60 @@ impl Store { } /// Load store from a manifest data and stream - #[async_generic(async_signature( - c2pa_data: &[u8], - format: &str, - mut stream: impl Read + Seek + Send, - verify: bool, - validation_log: &mut StatusTracker, - http_resolver: &impl AsyncHttpResolver, - settings: &Settings, - ))] + #[async_generic] #[cfg(not(target_arch = "wasm32"))] pub fn from_manifest_data_and_stream( c2pa_data: &[u8], format: &str, mut stream: impl Read + Seek + Send, - verify: bool, validation_log: &mut StatusTracker, - http_resolver: &impl SyncHttpResolver, - settings: &Settings, + context: &Context, ) -> Result { stream.rewind()?; // First we convert the JUMBF into a usable store. - let store = Store::from_jumbf_with_settings(c2pa_data, validation_log, settings) + let store = Store::from_jumbf_with_context(c2pa_data, validation_log, context) .inspect_err(|e| { log_item!("asset", "error loading file", "load_from_asset") .failure_no_throw(validation_log, e); })?; - if verify { + if context.settings().verify.verify_after_reading { stream.rewind()?; let mut asset_data = ClaimAssetData::Stream(&mut stream, format); if _sync { - Store::verify_store( - &store, - &mut asset_data, - validation_log, - http_resolver, - settings, - ) + Store::verify_store(&store, &mut asset_data, validation_log, context) } else { - Store::verify_store_async( - &store, - &mut asset_data, - validation_log, - http_resolver, - settings, - ) - .await + Store::verify_store_async(&store, &mut asset_data, validation_log, context).await }?; } Ok(store) } /// Load store from a manifest data and stream - #[async_generic(async_signature( - c2pa_data: &[u8], - format: &str, - mut stream: impl Read + Seek, - verify: bool, - validation_log: &mut StatusTracker, - http_resolver: &impl AsyncHttpResolver, - settings: &Settings, - ))] + #[async_generic] #[cfg(target_arch = "wasm32")] pub fn from_manifest_data_and_stream( c2pa_data: &[u8], format: &str, mut stream: impl Read + Seek, - verify: bool, validation_log: &mut StatusTracker, - http_resolver: &impl SyncHttpResolver, - settings: &Settings, + context: &Context, ) -> Result { // first we convert the JUMBF into a usable store - let store = Store::from_jumbf_with_settings(c2pa_data, validation_log, settings) + let store = Store::from_jumbf_with_context(c2pa_data, validation_log, context) .inspect_err(|e| { log_item!("asset", "error loading file", "load_from_asset") .failure_no_throw(validation_log, e); })?; - //let verify = get_settings_value::("verify.verify_after_reading")?; // defaults to true - + let verify = context.settings().verify.verify_after_reading; if verify { let mut asset_data = ClaimAssetData::Stream(&mut stream, format); if _sync { - Store::verify_store( - &store, - &mut asset_data, - validation_log, - http_resolver, - settings, - ) + Store::verify_store(&store, &mut asset_data, validation_log, context) } else { - Store::verify_store_async( - &store, - &mut asset_data, - validation_log, - http_resolver, - settings, - ) - .await + Store::verify_store_async(&store, &mut asset_data, validation_log, context).await }?; } Ok(store) @@ -3851,19 +3727,11 @@ impl Store { asset_type: &str, init_segment: &mut dyn CAIRead, fragments: &Vec, - verify: bool, validation_log: &mut StatusTracker, - http_resolver: &impl SyncHttpResolver, - settings: &Settings, + context: &Context, ) -> Result { - let store = Self::from_stream( - asset_type, - &mut *init_segment, - verify, - validation_log, - http_resolver, - settings, - )?; + let verify = context.settings().verify.verify_after_reading; + let store = Self::from_stream(asset_type, &mut *init_segment, validation_log, context)?; // verify the store if verify { @@ -3873,8 +3741,7 @@ impl Store { &store, &mut ClaimAssetData::StreamFragments(init_segment, fragments, asset_type), validation_log, - http_resolver, - settings, + context, )?; } @@ -3892,47 +3759,32 @@ impl Store { mut stream: impl Read + Seek + Send, mut fragment: impl Read + Seek + Send, validation_log: &mut StatusTracker, - http_resolver: &impl AsyncHttpResolver, - settings: &Settings, + context: &Context, ))] pub fn load_fragment_from_stream( format: &str, mut stream: impl Read + Seek + Send, mut fragment: impl Read + Seek + Send, validation_log: &mut StatusTracker, - http_resolver: &impl SyncHttpResolver, - settings: &Settings, + context: &Context, ) -> Result { let manifest_bytes = if _sync { - Store::load_jumbf_from_stream(format, &mut stream, http_resolver, settings)?.0 + Store::load_jumbf_from_stream(format, &mut stream, context)?.0 } else { - Store::load_jumbf_from_stream_async(format, &mut stream, http_resolver, settings) + Store::load_jumbf_from_stream_async(format, &mut stream, context) .await? .0 }; - let store = Store::from_jumbf_with_settings(&manifest_bytes, validation_log, settings)?; - let verify = settings.verify.verify_after_reading; + let store = Store::from_jumbf_with_context(&manifest_bytes, validation_log, context)?; + let verify = context.settings().verify.verify_after_reading; if verify { let mut fragment = ClaimAssetData::StreamFragment(&mut stream, &mut fragment, format); if _sync { - Store::verify_store( - &store, - &mut fragment, - validation_log, - http_resolver, - settings, - ) + Store::verify_store(&store, &mut fragment, validation_log, context) } else { - Store::verify_store_async( - &store, - &mut fragment, - validation_log, - http_resolver, - settings, - ) - .await + Store::verify_store_async(&store, &mut fragment, validation_log, context).await }?; }; Ok(store) @@ -4100,7 +3952,7 @@ impl Store { claim: &mut Claim, data: &[u8], redactions: Option>, - settings: &Settings, + context: &Context, ) -> Result { // constants for ingredient conflict reasons const CONFLICTING_MANIFEST: usize = 1; // Conflicts with another C2PA Manifest @@ -4109,7 +3961,7 @@ impl Store { let mut to_remove_from_incoming = Vec::new(); let mut report = StatusTracker::with_error_behavior(ErrorBehavior::StopOnFirstError); - let i_store = Store::from_jumbf_with_settings(data, &mut report, settings)?; + let i_store = Store::from_jumbf_with_context(data, &mut report, context)?; let empty_store = Store::default(); @@ -4134,7 +3986,10 @@ impl Store { // resolve conflicts // for 2.x perform ingredients conflict handling by making new label if needed - let skip_resolution = settings.verify.skip_ingredient_conflict_resolution; + let skip_resolution = context + .settings() + .verify + .skip_ingredient_conflict_resolution; if claim.version() > 1 && !skip_resolution { // if the hashes match then the values are OK to add so remove form conflict list @@ -4256,7 +4111,7 @@ impl Store { } // make necessary changes to the incoming store - let mut i_store_mut = Store::from_jumbf_with_settings(data, &mut report, settings)?; + let mut i_store_mut = Store::from_jumbf_with_context(data, &mut report, context)?; let mut final_redactions = Vec::new(); if let Some(mut redactions) = redactions { final_redactions.append(&mut redactions); @@ -4294,19 +4149,17 @@ impl Store { &self, manifest_labels: Vec, validation_log: &mut StatusTracker, - http_resolver: &impl AsyncHttpResolver, - settings: &Settings, + context: &Context, ))] pub fn get_ocsp_response_ders( &self, manifest_labels: Vec, validation_log: &mut StatusTracker, - http_resolver: &impl SyncHttpResolver, - settings: &Settings, + context: &Context, ) -> Result)>> { let mut oscp_response_ders = Vec::new(); - let mut adjusted_settings = settings.clone(); + let mut adjusted_settings = context.settings().clone(); let original_trust_val = adjusted_settings.verify.verify_timestamp_trust; for manifest_label in manifest_labels { @@ -4327,8 +4180,7 @@ impl Store { &self.ctp, None, validation_log, - http_resolver, - settings, + context, )? .ocsp_der } else { @@ -4338,8 +4190,7 @@ impl Store { &self.ctp, None, validation_log, - http_resolver, - settings, + context, ) .await? .ocsp_der @@ -4470,8 +4321,6 @@ pub mod tests { assertions::{Action, Actions, Uuid}, claim::AssertionStoreJsonFormat, crypto::raw_signature::SigningAlg, - http::{AsyncGenericResolver, SyncGenericResolver}, - settings::Settings, status_tracker::{LogItem, StatusTracker}, utils::{ patch::patch_bytes, @@ -4512,14 +4361,13 @@ pub mod tests { #[test] fn test_jumbf_generation() { - let settings = Settings::default(); - let http_resolver = SyncGenericResolver::new(); + let context = Context::new(); let (format, mut input_stream, mut output_stream) = create_test_streams("earth_apollo17.jpg"); // Create claims store. - let mut store = Store::with_settings(&settings); + let mut store = Store::with_context(&context); // ClaimGeneratorInfo is mandatory in Claim V2 let cgi = ClaimGeneratorInfo::new("claim_v1_unit_test"); @@ -4538,8 +4386,7 @@ pub mod tests { &mut input_stream, &mut output_stream, signer.as_ref(), - &http_resolver, - &settings, + &context, ) .unwrap(); @@ -4547,15 +4394,8 @@ pub mod tests { // read from new stream output_stream.rewind().unwrap(); - let new_store = Store::from_stream( - format, - &mut output_stream, - true, - &mut report, - &http_resolver, - &settings, - ) - .unwrap(); + let new_store = + Store::from_stream(format, &mut output_stream, &mut report, &context).unwrap(); // should not have any assert!(!report.has_any_error()); @@ -4588,14 +4428,13 @@ pub mod tests { #[test] fn test_claim_v2_generation() { - let settings = Settings::default(); - let http_resolver = SyncGenericResolver::new(); + let context = Context::new(); let (format, mut input_stream, mut output_stream) = create_test_streams("earth_apollo17.jpg"); // Create claims store. - let mut store = Store::with_settings(&settings); + let mut store = Store::with_context(&context); // ClaimGeneratorInfo is mandatory in Claim V2 let cgi = ClaimGeneratorInfo::new("claim_v2_unit_test"); @@ -4614,8 +4453,7 @@ pub mod tests { &mut input_stream, &mut output_stream, signer.as_ref(), - &http_resolver, - &settings, + &context, ) .unwrap(); @@ -4623,15 +4461,8 @@ pub mod tests { // read from new stream output_stream.rewind().unwrap(); - let new_store = Store::from_stream( - format, - &mut output_stream, - true, - &mut report, - &http_resolver, - &settings, - ) - .unwrap(); + let new_store = + Store::from_stream(format, &mut output_stream, &mut report, &context).unwrap(); // should not have any assert!(!report.has_any_error()); @@ -4664,15 +4495,14 @@ pub mod tests { #[test] fn test_bad_claim_v2_generation() { - let mut settings = Settings::default(); - settings.verify.verify_after_sign = false; - let http_resolver = SyncGenericResolver::new(); + let mut context = Context::new(); + context.settings_mut().verify.verify_after_sign = false; let (format, mut input_stream, mut output_stream) = create_test_streams("earth_apollo17.jpg"); // Create claims store. - let mut store = Store::with_settings(&settings); + let mut store = Store::with_context(&context); // ClaimGeneratorInfo is mandatory in Claim V2 let cgi = ClaimGeneratorInfo::new("claim_v2_unit_test"); @@ -4695,8 +4525,7 @@ pub mod tests { &mut input_stream, &mut output_stream, signer.as_ref(), - &http_resolver, - &settings, + &context, ) .unwrap(); @@ -4704,14 +4533,7 @@ pub mod tests { // read from new stream output_stream.rewind().unwrap(); - let _new_store = Store::from_stream( - format, - &mut output_stream, - true, - &mut report, - &http_resolver, - &settings, - ); + let _new_store = Store::from_stream(format, &mut output_stream, &mut report, &context); // should have action errors assert!(report.has_any_error()); @@ -4724,15 +4546,14 @@ pub mod tests { #[cfg(feature = "file_io")] #[ignore = "we need to make this work again"] fn test_unknown_asset_type_generation() { - let settings = Settings::default(); - let http_resolver = SyncGenericResolver::new(); + let context = crate::context::Context::new(); // test adding to actual image let (_format, mut input_stream, mut output_stream) = create_test_streams("unsupported_type.txt"); let format = "text/plain"; // Create claims store. - let mut store = Store::with_settings(&settings); + let mut store = Store::with_context(&context); // Create a new claim. let claim1 = create_test_claim().unwrap(); @@ -4756,8 +4577,7 @@ pub mod tests { &mut input_stream, &mut output_stream, signer.as_ref(), - &http_resolver, - &settings, + &context, ) .unwrap(); @@ -4766,10 +4586,8 @@ pub mod tests { let new_store = Store::from_stream( format, &mut output_stream, - true, &mut StatusTracker::with_error_behavior(ErrorBehavior::StopOnFirstError), - &http_resolver, - &settings, + &context, ) .unwrap(); @@ -4804,8 +4622,7 @@ pub mod tests { #[test] #[cfg(feature = "file_io")] fn test_detects_unverifiable_signature() { - let settings = Settings::default(); - let http_resolver = SyncGenericResolver::new(); + let context = crate::context::Context::new(); struct BadSigner {} @@ -4831,7 +4648,7 @@ pub mod tests { let (format, mut input_stream, mut output_stream) = create_test_streams("earth_apollo17.jpg"); - let mut store = Store::with_settings(&settings); + let mut store = Store::with_context(&context); let claim = create_test_claim().unwrap(); @@ -4850,8 +4667,7 @@ pub mod tests { &mut input_stream, &mut output_stream, &signer, - &http_resolver, - &settings, + &context, ) .unwrap_err(); } @@ -4861,14 +4677,13 @@ pub mod tests { fn test_sign_with_expired_cert() { use crate::{create_signer, crypto::raw_signature::SigningAlg}; - let settings = Settings::default(); - let http_resolver = SyncGenericResolver::new(); + let context = Context::new(); // test adding to actual image let (format, mut input_stream, mut output_stream) = create_test_streams("earth_apollo17.jpg"); - let mut store = Store::with_settings(&settings); + let mut store = Store::with_context(&context); let claim = create_test_claim().unwrap(); @@ -4884,8 +4699,7 @@ pub mod tests { &mut input_stream, &mut output_stream, &signer, - &http_resolver, - &settings, + &context, ); assert!(r.is_err()); assert_eq!( @@ -4897,8 +4711,9 @@ pub mod tests { #[test] #[cfg(feature = "file_io")] fn test_jumbf_replacement_generation() { + let context = Context::new(); // Create claims store. - let mut store = Store::with_settings(&Settings::default()); + let mut store = Store::with_context(&context); // Create a new claim. let claim1 = create_test_claim().unwrap(); @@ -4932,8 +4747,7 @@ pub mod tests { #[c2pa_test_async] //#[ignore] // this is not generating the expected error. Needs investigation. async fn test_jumbf_generation_async() -> Result<()> { - let settings = Settings::default(); - let http_resolver = AsyncGenericResolver::new(); + let context = crate::context::Context::new(); // Verify after sign is causing UnreferencedManifest errors here, since the manifests don't reference each other. //no_verify_after_sign(); @@ -4944,7 +4758,7 @@ pub mod tests { create_test_streams("earth_apollo17.jpg"); // Create claims store. - let mut store = Store::with_settings(&settings); + let mut store = Store::with_context(&context); // Create a new claim. let claim1 = crate::utils::test::create_test_claim()?; @@ -4970,8 +4784,7 @@ pub mod tests { &mut input_stream, &mut output_stream, &signer, - &http_resolver, - &settings, + &context, ) .await?; store.commit_claim(claim_capture)?; @@ -4983,8 +4796,7 @@ pub mod tests { &mut output_stream, &mut temp_stream, &signer, - &http_resolver, - &settings, + &context, ) .await?; store.commit_claim(claim2)?; @@ -4996,8 +4808,7 @@ pub mod tests { &mut temp_stream, &mut output_stream, &signer, - &http_resolver, - &settings, + &context, ) .await .unwrap(); @@ -5021,15 +4832,8 @@ pub mod tests { let mut report = StatusTracker::default(); output_stream.rewind()?; - let _new_store = Store::from_stream_async( - format, - &mut output_stream, - true, - &mut report, - &http_resolver, - &settings, - ) - .await?; + let _new_store = + Store::from_stream_async(format, &mut output_stream, &mut report, &context).await?; assert!(!report.has_any_error()); Ok(()) @@ -5037,15 +4841,14 @@ pub mod tests { #[test] fn test_png_jumbf_generation() { - let mut settings = Settings::default(); - settings.verify.verify_after_sign = false; - let http_resolver = SyncGenericResolver::new(); + let mut context = Context::new(); + context.settings_mut().verify.verify_after_sign = false; // test adding to actual image let (format, mut input_stream, mut output_stream) = create_test_streams("libpng-test.png"); // Create claims store. - let mut store = Store::with_settings(&settings); + let mut store = Store::with_context(&context); // Create a new claim. let claim1 = create_test_claim().unwrap(); @@ -5069,8 +4872,7 @@ pub mod tests { &mut input_stream, &mut output_stream, &signer, - &http_resolver, - &settings, + &context, ) .unwrap(); @@ -5083,8 +4885,7 @@ pub mod tests { &mut output_stream, &mut temp_stream, &signer, - &http_resolver, - &settings, + &context, ) .unwrap(); @@ -5097,8 +4898,7 @@ pub mod tests { &mut temp_stream, &mut output_stream, signer.as_ref(), - &http_resolver, - &settings, + &context, ) .unwrap(); @@ -5109,15 +4909,8 @@ pub mod tests { // read from new stream output_stream.rewind().unwrap(); - let new_store = Store::from_stream( - format, - &mut output_stream, - true, - &mut report, - &http_resolver, - &settings, - ) - .unwrap(); + let new_store = + Store::from_stream(format, &mut output_stream, &mut report, &context).unwrap(); // can we get by the ingredient data back let _some_binary_data: Vec = vec![ @@ -5324,13 +5117,12 @@ pub mod tests { */ #[test] fn test_wav_jumbf_generation() { - let settings = Settings::default(); - let http_resolver = SyncGenericResolver::new(); + let context = crate::context::Context::new(); let (format, mut input_stream, mut output_stream) = create_test_streams("sample1.wav"); // Create claims store. - let mut store = Store::with_settings(&settings); + let mut store = Store::with_context(&context); // Create a new claim. let claim1 = create_test_claim().unwrap(); @@ -5354,8 +5146,7 @@ pub mod tests { &mut input_stream, &mut output_stream, signer.as_ref(), - &http_resolver, - &settings, + &context, ) .unwrap(); store.commit_claim(claim_capture).unwrap(); @@ -5367,8 +5158,7 @@ pub mod tests { &mut output_stream, &mut temp_stream, signer.as_ref(), - &http_resolver, - &settings, + &context, ) .unwrap(); store.commit_claim(claim2).unwrap(); @@ -5379,8 +5169,7 @@ pub mod tests { &mut temp_stream, &mut output_stream, signer.as_ref(), - &http_resolver, - &settings, + &context, ) .unwrap(); @@ -5397,15 +5186,8 @@ pub mod tests { // read from new stream output_stream.rewind().unwrap(); - let new_store = Store::from_stream( - format, - &mut output_stream, - true, - &mut report, - &http_resolver, - &settings, - ) - .unwrap(); + let new_store = + Store::from_stream(format, &mut output_stream, &mut report, &context).unwrap(); // dump store and compare to original for claim in new_store.claims() { @@ -5437,13 +5219,12 @@ pub mod tests { #[test] fn test_avi_jumbf_generation() { - let settings = Settings::default(); - let http_resolver = SyncGenericResolver::new(); + let context = crate::context::Context::new(); let (format, mut input_stream, mut output_stream) = create_test_streams("test.avi"); // Create claims store. - let mut store = Store::with_settings(&settings); + let mut store = Store::with_context(&context); // Create a new claim. let claim1 = create_test_claim().unwrap(); @@ -5467,8 +5248,7 @@ pub mod tests { &mut input_stream, &mut output_stream, &signer, - &http_resolver, - &settings, + &context, ) .unwrap(); store.commit_claim(claim_capture).unwrap(); @@ -5480,8 +5260,7 @@ pub mod tests { &mut output_stream, &mut temp_stream, &signer, - &http_resolver, - &settings, + &context, ) .unwrap(); store.commit_claim(claim2).unwrap(); @@ -5493,8 +5272,7 @@ pub mod tests { &mut temp_stream, &mut output_stream, signer.as_ref(), - &http_resolver, - &settings, + &context, ) .unwrap(); @@ -5511,15 +5289,8 @@ pub mod tests { // read from new stream output_stream.rewind().unwrap(); - let new_store = Store::from_stream( - format, - &mut output_stream, - true, - &mut report, - &http_resolver, - &settings, - ) - .unwrap(); + let new_store = + Store::from_stream(format, &mut output_stream, &mut report, &context).unwrap(); // dump store and compare to original for claim in new_store.claims() { @@ -5551,13 +5322,12 @@ pub mod tests { #[test] fn test_webp_jumbf_generation() { - let settings = Settings::default(); - let http_resolver = SyncGenericResolver::new(); + let context = crate::context::Context::new(); let (format, mut input_stream, mut output_stream) = create_test_streams("sample1.webp"); // Create claims store. - let mut store = Store::with_settings(&settings); + let mut store = Store::with_context(&context); // Create a new claim. let claim1 = create_test_claim().unwrap(); @@ -5581,8 +5351,7 @@ pub mod tests { &mut input_stream, &mut output_stream, &signer, - &http_resolver, - &settings, + &context, ) .unwrap(); store.commit_claim(claim_capture).unwrap(); @@ -5594,8 +5363,7 @@ pub mod tests { &mut output_stream, &mut temp_stream, &signer, - &http_resolver, - &settings, + &context, ) .unwrap(); store.commit_claim(claim2).unwrap(); @@ -5607,8 +5375,7 @@ pub mod tests { &mut temp_stream, &mut output_stream, signer.as_ref(), - &http_resolver, - &settings, + &context, ) .unwrap(); @@ -5625,15 +5392,8 @@ pub mod tests { // read from new stream output_stream.rewind().unwrap(); - let new_store = Store::from_stream( - format, - &mut output_stream, - true, - &mut report, - &http_resolver, - &settings, - ) - .unwrap(); + let new_store = + Store::from_stream(format, &mut output_stream, &mut report, &context).unwrap(); // dump store and compare to original for claim in new_store.claims() { @@ -5665,13 +5425,12 @@ pub mod tests { #[test] fn test_heic() { - let settings = Settings::default(); - let http_resolver = SyncGenericResolver::new(); + let context = crate::context::Context::new(); let (format, mut input_stream, mut output_stream) = create_test_streams("sample1.heic"); // Create claims store. - let mut store = Store::with_settings(&settings); + let mut store = Store::with_context(&context); // Create a new claim. let claim1 = create_test_claim().unwrap(); @@ -5687,8 +5446,7 @@ pub mod tests { &mut input_stream, &mut output_stream, signer.as_ref(), - &http_resolver, - &settings, + &context, ) .unwrap(); @@ -5696,15 +5454,8 @@ pub mod tests { // read from new stream output_stream.rewind().unwrap(); - let new_store = Store::from_stream( - format, - &mut output_stream, - true, - &mut report, - &http_resolver, - &settings, - ) - .unwrap(); + let new_store = + Store::from_stream(format, &mut output_stream, &mut report, &context).unwrap(); // dump store and compare to original for claim in new_store.claims() { @@ -5727,13 +5478,12 @@ pub mod tests { #[test] fn test_avif() { - let settings = Settings::default(); - let http_resolver = SyncGenericResolver::new(); + let context = crate::context::Context::new(); let (format, mut input_stream, mut output_stream) = create_test_streams("sample1.avif"); // Create claims store. - let mut store = Store::with_settings(&settings); + let mut store = Store::with_context(&context); // Create a new claim. let claim1 = create_test_claim().unwrap(); @@ -5749,8 +5499,7 @@ pub mod tests { &mut input_stream, &mut output_stream, signer.as_ref(), - &http_resolver, - &settings, + &context, ) .unwrap(); @@ -5758,15 +5507,8 @@ pub mod tests { // read from new stream output_stream.rewind().unwrap(); - let new_store = Store::from_stream( - format, - &mut output_stream, - true, - &mut report, - &http_resolver, - &settings, - ) - .unwrap(); + let new_store = + Store::from_stream(format, &mut output_stream, &mut report, &context).unwrap(); // dump store and compare to original for claim in new_store.claims() { @@ -5789,13 +5531,12 @@ pub mod tests { #[test] fn test_heif() { - let settings = Settings::default(); - let http_resolver = SyncGenericResolver::new(); + let context = crate::context::Context::new(); let (format, mut input_stream, mut output_stream) = create_test_streams("sample1.heif"); // Create claims store. - let mut store = Store::with_settings(&settings); + let mut store = Store::with_context(&context); // Create a new claim. let claim1 = create_test_claim().unwrap(); @@ -5811,8 +5552,7 @@ pub mod tests { &mut input_stream, &mut output_stream, signer.as_ref(), - &http_resolver, - &settings, + &context, ) .unwrap(); @@ -5820,15 +5560,8 @@ pub mod tests { // read from new stream output_stream.rewind().unwrap(); - let new_store = Store::from_stream( - format, - &mut output_stream, - true, - &mut report, - &http_resolver, - &settings, - ) - .unwrap(); + let new_store = + Store::from_stream(format, &mut output_stream, &mut report, &context).unwrap(); // dump store and compare to original for claim in new_store.claims() { @@ -5860,16 +5593,10 @@ pub mod tests { #[test] #[ignore = "This is not generating the expected error. Needs investigation."] fn test_manifest_bad_sig() { + let context = Context::new(); let (format, mut input_stream, _output_stream) = create_test_streams("CIE-sig-CA.jpg"); let tracker = &mut StatusTracker::default(); - let result = Store::from_stream( - format, - &mut input_stream, - true, - tracker, - &SyncGenericResolver::new(), - &Settings::default(), - ); + let result = Store::from_stream(format, &mut input_stream, tracker, &context); assert!(result.is_ok()); println!("Error report: {tracker:?}"); assert!(tracker.has_error(Error::AssertionInvalidRedaction)); @@ -5877,17 +5604,11 @@ pub mod tests { #[test] fn test_unsupported_type_without_external_manifest() { + let context = Context::new(); let (format, mut input_stream, _output_stream) = create_test_streams("unsupported_type.txt"); let mut report = StatusTracker::default(); - let result = Store::from_stream( - format, - &mut input_stream, - true, - &mut report, - &SyncGenericResolver::new(), - &Settings::default(), - ); + let result = Store::from_stream(format, &mut input_stream, &mut report, &context); assert!(matches!(result, Err(Error::UnsupportedType))); println!("Error report: {report:?}"); assert!(!report.logged_items().is_empty()); @@ -5900,14 +5621,7 @@ pub mod tests { // test bad jumbf let (format, mut input_stream, _output_stream) = create_test_streams("prerelease.jpg"); let mut report = StatusTracker::default(); - let _r = Store::from_stream( - format, - &mut input_stream, - true, - &mut report, - &SyncGenericResolver::new(), - &Settings::default(), - ); + let _r = Store::from_stream(format, &mut input_stream, &mut report, &Context::new()); // error report println!("Error report: {report:?}"); @@ -5921,15 +5635,7 @@ pub mod tests { // test bad jumbf let (format, mut input_stream, _output_stream) = create_test_streams("XCA.jpg"); let mut report = StatusTracker::default(); - Store::from_stream( - format, - &mut input_stream, - true, - &mut report, - &SyncGenericResolver::new(), - &Settings::default(), - ) - .unwrap(); + Store::from_stream(format, &mut input_stream, &mut report, &Context::new()).unwrap(); // error report println!("Error report: {report:?}"); @@ -5961,14 +5667,7 @@ pub mod tests { fn test_old_manifest() { let (format, mut input_stream, _output_stream) = create_test_streams("prerelease.jpg"); let mut report = StatusTracker::default(); - let _r = Store::from_stream( - format, - &mut input_stream, - true, - &mut report, - &SyncGenericResolver::new(), - &Settings::default(), - ); + let _r = Store::from_stream(format, &mut input_stream, &mut report, &Context::new()); println!("Error report: {report:?}"); @@ -5986,8 +5685,7 @@ pub mod tests { fn test_verifiable_credentials() { use crate::utils::test::create_test_store_v1; - let settings = Settings::default(); - let http_resolver = SyncGenericResolver::new(); + let context = crate::context::Context::new(); let (format, mut input_stream, mut output_stream) = create_test_streams("earth_apollo17.jpg"); @@ -6004,8 +5702,7 @@ pub mod tests { &mut input_stream, &mut output_stream, signer.as_ref(), - &http_resolver, - &settings, + &context, ) .unwrap(); @@ -6014,10 +5711,8 @@ pub mod tests { let restored_store = Store::from_stream( format, &mut output_stream, - true, &mut StatusTracker::with_error_behavior(ErrorBehavior::StopOnFirstError), - &http_resolver, - &settings, + &context, ) .unwrap(); @@ -6038,8 +5733,7 @@ pub mod tests { fn test_data_box_creation() { use crate::utils::test::create_test_store_v1; - let settings = Settings::default(); - let http_resolver = SyncGenericResolver::new(); + let context = crate::context::Context::new(); let (format, mut input_stream, mut output_stream) = create_test_streams("earth_apollo17.jpg"); @@ -6056,8 +5750,7 @@ pub mod tests { &mut input_stream, &mut output_stream, signer.as_ref(), - &http_resolver, - &settings, + &context, ) .unwrap(); @@ -6066,10 +5759,8 @@ pub mod tests { let restored_store = Store::from_stream( format, &mut output_stream, - true, &mut StatusTracker::with_error_behavior(ErrorBehavior::StopOnFirstError), - &http_resolver, - &settings, + &context, ) .unwrap(); @@ -6105,14 +5796,7 @@ pub mod tests { let mut patched_stream = std::io::Cursor::new(data); let mut report = StatusTracker::default(); - let _r = Store::from_stream( - format, - &mut patched_stream, - true, - &mut report, - &SyncGenericResolver::new(), - &Settings::default(), - ); // errs are in report + let _r = Store::from_stream(format, &mut patched_stream, &mut report, &Context::new()); // errs are in report println!("report: {report:?}"); report } @@ -6121,8 +5805,7 @@ pub mod tests { fn test_update_manifest_v1() { use crate::{hashed_uri::HashedUri, utils::test::create_test_store_v1}; - let settings = Settings::default(); - let http_resolver = SyncGenericResolver::new(); + let context = crate::context::Context::new(); let (format, mut input_stream, mut output_stream) = create_test_streams("earth_apollo17.jpg"); @@ -6138,23 +5821,15 @@ pub mod tests { &mut input_stream, &mut output_stream, signer.as_ref(), - &http_resolver, - &settings, + &context, ) .unwrap(); let mut report = StatusTracker::with_error_behavior(ErrorBehavior::StopOnFirstError); // read back in output_stream.rewind().unwrap(); - let restored_store = Store::from_stream( - format, - &mut output_stream, - true, - &mut report, - &http_resolver, - &settings, - ) - .unwrap(); + let restored_store = + Store::from_stream(format, &mut output_stream, &mut report, &context).unwrap(); let pc = restored_store.provenance_claim().unwrap(); // should be a regular manifest @@ -6167,7 +5842,7 @@ pub mod tests { &mut claim, &load_jumbf_from_stream(format, &mut output_stream).unwrap(), None, - &settings, + &context, ) .unwrap(); @@ -6193,22 +5868,14 @@ pub mod tests { &mut output_stream, &mut output_stream2, signer.as_ref(), - &http_resolver, - &settings, + &context, ) .unwrap(); // read back in store with update manifest output_stream2.rewind().unwrap(); - let um_store = Store::from_stream( - format, - &mut output_stream2, - true, - &mut report, - &http_resolver, - &settings, - ) - .unwrap(); + let um_store = + Store::from_stream(format, &mut output_stream2, &mut report, &context).unwrap(); let um = um_store.provenance_claim().unwrap(); @@ -6227,8 +5894,7 @@ pub mod tests { utils::test::create_test_store_v1, ClaimGeneratorInfo, ValidationResults, }; - let settings = Settings::default(); - let http_resolver = SyncGenericResolver::new(); + let context = crate::context::Context::new(); let signer = test_signer(SigningAlg::Ps256); @@ -6246,8 +5912,7 @@ pub mod tests { &mut input_stream, &mut output_stream, signer.as_ref(), - &http_resolver, - &settings, + &context, ) .unwrap(); @@ -6256,15 +5921,8 @@ pub mod tests { output_stream.rewind().unwrap(); let ingredient_vec = output_stream.get_ref().clone(); let mut ingredient_stream = Cursor::new(ingredient_vec); - let restored_store = Store::from_stream( - format, - &mut ingredient_stream, - true, - &mut report, - &http_resolver, - &settings, - ) - .unwrap(); + let restored_store = + Store::from_stream(format, &mut ingredient_stream, &mut report, &context).unwrap(); let pc = restored_store.provenance_claim().unwrap(); // should be a regular manifest @@ -6277,15 +5935,10 @@ pub mod tests { claim.add_claim_generator_info(cgi); ingredient_stream.rewind().unwrap(); - let (manifest_bytes, _) = Store::load_jumbf_from_stream( - format, - &mut ingredient_stream, - &http_resolver, - &settings, - ) - .unwrap(); + let (manifest_bytes, _) = + Store::load_jumbf_from_stream(format, &mut ingredient_stream, &context).unwrap(); let mut new_store = - Store::load_ingredient_to_claim(&mut claim, &manifest_bytes, None, &settings).unwrap(); + Store::load_ingredient_to_claim(&mut claim, &manifest_bytes, None, &context).unwrap(); let ingredient_hashes = new_store.get_manifest_box_hashes(pc); let parent_hashed_uri = HashedUri::new( @@ -6346,8 +5999,7 @@ pub mod tests { &mut output_stream, &mut output_stream2, signer.as_ref(), - &http_resolver, - &settings, + &context, ) .unwrap(); @@ -6355,15 +6007,8 @@ pub mod tests { // read back in store with update manifest output_stream2.rewind().unwrap(); - let um_store = Store::from_stream( - format, - &mut output_stream2, - true, - &mut um_report, - &http_resolver, - &settings, - ) - .unwrap(); + let um_store = + Store::from_stream(format, &mut output_stream2, &mut um_report, &context).unwrap(); let um = um_store.provenance_claim().unwrap(); @@ -6382,8 +6027,7 @@ pub mod tests { ValidationResults, }; - let settings = Settings::default(); - let http_resolver = SyncGenericResolver::new(); + let context = crate::context::Context::new(); let signer = test_signer(SigningAlg::Ps256); @@ -6393,15 +6037,8 @@ pub mod tests { let mut report = StatusTracker::with_error_behavior(ErrorBehavior::StopOnFirstError); // read in the store - let mut store = Store::from_stream( - format, - &mut input_stream, - true, - &mut report, - &http_resolver, - &settings, - ) - .unwrap(); + let mut store = + Store::from_stream(format, &mut input_stream, &mut report, &context).unwrap(); let pc = store.provenance_claim().unwrap(); // create a new update manifest @@ -6458,8 +6095,7 @@ pub mod tests { &mut input_stream, &mut output_stream, signer.as_ref(), - &http_resolver, - &settings, + &context, ) .unwrap(); @@ -6467,15 +6103,8 @@ pub mod tests { // read back in store with update manifest output_stream.rewind().unwrap(); - let mut um_store = Store::from_stream( - format, - &mut output_stream, - true, - &mut um_report, - &http_resolver, - &settings, - ) - .unwrap(); + let mut um_store = + Store::from_stream(format, &mut output_stream, &mut um_report, &context).unwrap(); let um = um_store.provenance_claim().unwrap(); @@ -6545,8 +6174,7 @@ pub mod tests { &mut output_stream, &mut output_stream2, signer.as_ref(), - &http_resolver, - &settings, + &context, ) .unwrap(); @@ -6555,15 +6183,9 @@ pub mod tests { // read back in store with update manifest output_stream2.rewind().unwrap(); - let collapsed_store = Store::from_stream( - format, - &mut output_stream2, - true, - &mut collapsed_report, - &http_resolver, - &settings, - ) - .unwrap(); + let collapsed_store = + Store::from_stream(format, &mut output_stream2, &mut collapsed_report, &context) + .unwrap(); let cm = collapsed_store.provenance_claim().unwrap(); assert!(!cm.update_manifest()); @@ -6579,15 +6201,8 @@ pub mod tests { let (format, mut input_stream, _output_stream) = create_test_streams("update_manifest.jpg"); let mut report = StatusTracker::with_error_behavior(ErrorBehavior::StopOnFirstError); - let restored_store = Store::from_stream( - format, - &mut input_stream, - true, - &mut report, - &SyncGenericResolver::new(), - &Settings::default(), - ) - .unwrap(); + let restored_store = + Store::from_stream(format, &mut input_stream, &mut report, &Context::new()).unwrap(); let pc = restored_store.provenance_claim().unwrap(); // should be an update manifest @@ -6601,8 +6216,7 @@ pub mod tests { utils::test::create_test_store_v1, ClaimGeneratorInfo, ValidationResults, }; - let settings = Settings::default(); - let http_resolver = SyncGenericResolver::new(); + let context = crate::context::Context::new(); let signer = test_signer(SigningAlg::Ps256); @@ -6620,8 +6234,7 @@ pub mod tests { &mut input_stream, &mut output_stream, signer.as_ref(), - &http_resolver, - &settings, + &context, ) .unwrap(); @@ -6630,15 +6243,8 @@ pub mod tests { output_stream.rewind().unwrap(); let ingredient_vec = output_stream.get_ref().clone(); let mut ingredient_stream = Cursor::new(ingredient_vec.clone()); - let restored_store = Store::from_stream( - format, - &mut ingredient_stream, - true, - &mut report, - &http_resolver, - &settings, - ) - .unwrap(); + let restored_store = + Store::from_stream(format, &mut ingredient_stream, &mut report, &context).unwrap(); let pc = restored_store.provenance_claim().unwrap(); // should be a regular manifest @@ -6656,15 +6262,14 @@ pub mod tests { let (manifest_bytes, _) = Store::load_jumbf_from_stream( format, &mut Cursor::new(ingredient_vec.clone()), - &http_resolver, - &settings, + &context, ) .unwrap(); let mut redacted_store = Store::load_ingredient_to_claim( &mut claim, &manifest_bytes, Some(vec![redacted_uri]), - &settings, + &context, ) .unwrap(); @@ -6718,8 +6323,7 @@ pub mod tests { &mut output_stream, &mut output_stream2, signer.as_ref(), - &http_resolver, - &settings, + &context, ) .unwrap(); @@ -6727,15 +6331,8 @@ pub mod tests { // read back in store with update manifest output_stream2.rewind().unwrap(); - let um_store = Store::from_stream( - format, - &mut output_stream2, - true, - &mut um_report, - &http_resolver, - &settings, - ) - .unwrap(); + let um_store = + Store::from_stream(format, &mut output_stream2, &mut um_report, &context).unwrap(); let um = um_store.provenance_claim().unwrap(); @@ -6754,24 +6351,19 @@ pub mod tests { // load ingredient with redaction output_stream2.rewind().unwrap(); let (redacted_manifest_bytes, _) = - Store::load_jumbf_from_stream(format, &mut output_stream2, &http_resolver, &settings) - .unwrap(); - Store::load_ingredient_to_claim(&mut new_claim, &redacted_manifest_bytes, None, &settings) + Store::load_jumbf_from_stream(format, &mut output_stream2, &context).unwrap(); + Store::load_ingredient_to_claim(&mut new_claim, &redacted_manifest_bytes, None, &context) .unwrap(); // load original ingredient without redaction - let (original_manifest_bytes, _) = Store::load_jumbf_from_stream( - format, - &mut Cursor::new(ingredient_vec), - &http_resolver, - &settings, - ) - .unwrap(); + let (original_manifest_bytes, _) = + Store::load_jumbf_from_stream(format, &mut Cursor::new(ingredient_vec), &context) + .unwrap(); let _conflict_store = Store::load_ingredient_to_claim( &mut new_claim, &original_manifest_bytes, None, - &settings, + &context, ) .unwrap(); @@ -6789,8 +6381,7 @@ pub mod tests { utils::test::create_test_store_v1, ClaimGeneratorInfo, ValidationResults, }; - let settings = Settings::default(); - let http_resolver = SyncGenericResolver::new(); + let context = Context::new(); let signer = test_signer(SigningAlg::Ps256); @@ -6808,8 +6399,7 @@ pub mod tests { &mut input_stream, &mut output_stream, signer.as_ref(), - &http_resolver, - &settings, + &context, ) .unwrap(); @@ -6818,15 +6408,8 @@ pub mod tests { output_stream.rewind().unwrap(); let ingredient_vec = output_stream.get_ref().clone(); let mut ingredient_stream = Cursor::new(ingredient_vec.clone()); - let restored_store = Store::from_stream( - format, - &mut ingredient_stream, - true, - &mut report, - &http_resolver, - &settings, - ) - .unwrap(); + let restored_store = + Store::from_stream(format, &mut ingredient_stream, &mut report, &context).unwrap(); let pc = restored_store.provenance_claim().unwrap(); @@ -6845,8 +6428,7 @@ pub mod tests { let (manifest_bytes, _) = Store::load_jumbf_from_stream( format, &mut Cursor::new(ingredient_vec.clone()), - &http_resolver, - &settings, + &context, ) .unwrap(); @@ -6854,7 +6436,7 @@ pub mod tests { &mut claim, &manifest_bytes, Some(vec![redacted_uri]), - &settings, + &context, ) .unwrap(); @@ -6908,8 +6490,7 @@ pub mod tests { &mut output_stream, &mut output_stream2, signer.as_ref(), - &http_resolver, - &settings, + &context, ) .unwrap(); @@ -6917,15 +6498,8 @@ pub mod tests { // read back in store with update manifest output_stream2.rewind().unwrap(); - let um_store = Store::from_stream( - format, - &mut output_stream2, - true, - &mut um_report, - &http_resolver, - &settings, - ) - .unwrap(); + let um_store = + Store::from_stream(format, &mut output_stream2, &mut um_report, &context).unwrap(); let um = um_store.provenance_claim().unwrap(); @@ -6942,14 +6516,10 @@ pub mod tests { new_claim.add_claim_generator_info(cgi); // load original ingredient without redaction - let (original_manifest_bytes, _) = Store::load_jumbf_from_stream( - format, - &mut Cursor::new(ingredient_vec), - &http_resolver, - &settings, - ) - .unwrap(); - Store::load_ingredient_to_claim(&mut new_claim, &original_manifest_bytes, None, &settings) + let (original_manifest_bytes, _) = + Store::load_jumbf_from_stream(format, &mut Cursor::new(ingredient_vec), &context) + .unwrap(); + Store::load_ingredient_to_claim(&mut new_claim, &original_manifest_bytes, None, &context) .unwrap(); // the confict_store is adjusted to remove the conflicting claim @@ -6962,9 +6532,8 @@ pub mod tests { output_stream2.rewind().unwrap(); let (redacted_manifest_bytes, _) = - Store::load_jumbf_from_stream(format, &mut output_stream2, &http_resolver, &settings) - .unwrap(); - Store::load_ingredient_to_claim(&mut new_claim, &redacted_manifest_bytes, None, &settings) + Store::load_jumbf_from_stream(format, &mut output_stream2, &context).unwrap(); + Store::load_ingredient_to_claim(&mut new_claim, &redacted_manifest_bytes, None, &context) .unwrap(); // the confict_store is adjusted to remove the conflicting claim @@ -6982,8 +6551,7 @@ pub mod tests { utils::test::create_test_store_v1, ClaimGeneratorInfo, ValidationResults, }; - let settings = Settings::default(); - let http_resolver = SyncGenericResolver::new(); + let context = Context::new(); let signer = test_signer(SigningAlg::Ps256); @@ -7001,23 +6569,15 @@ pub mod tests { &mut input_stream, &mut output_stream, signer.as_ref(), - &http_resolver, - &settings, + &context, ) .unwrap(); let mut report = StatusTracker::with_error_behavior(ErrorBehavior::StopOnFirstError); // read back in output_stream.rewind().unwrap(); - let restored_store = Store::from_stream( - format, - &mut output_stream, - true, - &mut report, - &http_resolver, - &settings, - ) - .unwrap(); + let restored_store = + Store::from_stream(format, &mut output_stream, &mut report, &context).unwrap(); let pc = restored_store.provenance_claim().unwrap(); // should be a regular manifest @@ -7038,7 +6598,7 @@ pub mod tests { &mut claim, &ingredient_vec, Some(vec![redacted_uri]), - &settings, + &context, ) .unwrap(); @@ -7092,8 +6652,7 @@ pub mod tests { &mut output_stream, &mut op_output, signer.as_ref(), - &http_resolver, - &settings, + &context, ) .unwrap(); @@ -7101,15 +6660,8 @@ pub mod tests { // read back in store with update manifest op_output.rewind().unwrap(); - let um_store = Store::from_stream( - format, - &mut op_output, - true, - &mut um_report, - &http_resolver, - &settings, - ) - .unwrap(); + let um_store = + Store::from_stream(format, &mut op_output, &mut um_report, &context).unwrap(); let um = um_store.provenance_claim().unwrap(); @@ -7131,7 +6683,7 @@ pub mod tests { &mut claim2, &ingredient_vec, Some(vec![redacted_uri2]), - &settings, + &context, ) .unwrap(); @@ -7185,8 +6737,7 @@ pub mod tests { &mut output_stream, &mut op2_output, signer.as_ref(), - &http_resolver, - &settings, + &context, ) .unwrap(); @@ -7202,7 +6753,7 @@ pub mod tests { &mut new_claim, &load_jumbf_from_stream(format, &mut op_output).unwrap(), None, - &settings, + &context, ) .unwrap(); @@ -7212,7 +6763,7 @@ pub mod tests { &mut new_claim, &load_jumbf_from_stream(format, &mut op2_output).unwrap(), None, - &settings, + &context, ) .unwrap(); @@ -7276,18 +6827,12 @@ pub mod tests { #[test] fn test_display() { + let context = Context::new(); let (format, mut input_stream, _output_stream) = create_test_streams("CA.jpg"); let mut report = StatusTracker::default(); - let store = Store::from_stream( - format, - &mut input_stream, - true, - &mut report, - &SyncGenericResolver::new(), - &Settings::default(), - ) - .expect("from_stream"); + let store = Store::from_stream(format, &mut input_stream, &mut report, &context) + .expect("from_stream"); assert!(!report.has_any_error()); println!("store = {store}"); @@ -7297,14 +6842,7 @@ pub mod tests { fn test_no_alg() { let (format, mut input_stream, _output_stream) = create_test_streams("no_alg.jpg"); let mut report = StatusTracker::default(); - let _store = Store::from_stream( - format, - &mut input_stream, - true, - &mut report, - &SyncGenericResolver::new(), - &Settings::default(), - ); + let _store = Store::from_stream(format, &mut input_stream, &mut report, &Context::new()); assert!(report.has_status(ALGORITHM_UNSUPPORTED)); } @@ -7328,37 +6866,26 @@ pub mod tests { #[test] fn test_legacy_ingredient_hash() { + let context = Context::new(); // test 1.0 ingredient hash - let (format, mut input_stream, _output_stream) = + let (format, input_stream, _output_stream) = create_test_streams("legacy_ingredient_hash.jpg"); let mut report = StatusTracker::default(); - let store = Store::from_stream( - format, - &mut input_stream, - true, - &mut report, - &SyncGenericResolver::new(), - &Settings::default(), - ) - .expect("from_stream"); + let store = + Store::from_stream(format, input_stream, &mut report, &context).expect("from_stream"); println!("store = {store}"); } #[test] #[ignore = "this test is not generating the expected errors - the test image cert has expired"] fn test_bmff_legacy() { - crate::settings::set_settings_value("verify.verify_trust", false).unwrap(); + let mut context = Context::new(); + context.settings_mut().verify.verify_trust = false; + let (format, mut input_stream, _output_stream) = create_test_streams("legacy.mp4"); // test 1.0 bmff hash let mut report = StatusTracker::default(); - let store = Store::from_stream( - format, - &mut input_stream, - true, - &mut report, - &SyncGenericResolver::new(), - &Settings::default(), - ); + let store = Store::from_stream(format, &mut input_stream, &mut report, &context); println!("store = {report:#?}"); // expect action error assert!(store.is_err()); @@ -7370,6 +6897,7 @@ pub mod tests { #[test] fn test_bmff_fragments() { + let context = Context::new(); let init_stream_path = fixture_path("dashinit.mp4"); let segment_stream_path = fixture_path("dash1.m4s"); @@ -7384,8 +6912,7 @@ pub mod tests { init_stream, segment_stream, &mut report, - &SyncGenericResolver::new(), - &Settings::default(), + &context, ) .expect("load_from_asset"); println!("store = {store}"); @@ -7393,14 +6920,13 @@ pub mod tests { #[test] fn test_bmff_jumbf_generation() { - let settings = Settings::default(); - let http_resolver = SyncGenericResolver::new(); + let context = crate::context::Context::new(); // test adding to actual image let (format, mut input_stream, mut output_stream) = create_test_streams("video1.mp4"); // Create claims store. - let mut store = Store::with_settings(&settings); + let mut store = Store::with_context(&context); // Create a new claim. let claim1 = create_test_claim().unwrap(); @@ -7415,8 +6941,7 @@ pub mod tests { &mut input_stream, &mut output_stream, signer.as_ref(), - &http_resolver, - &settings, + &context, ) .unwrap(); @@ -7424,15 +6949,8 @@ pub mod tests { // can we read back in output_stream.set_position(0); - let new_store = Store::from_stream( - format, - &mut output_stream, - true, - &mut report, - &http_resolver, - &settings, - ) - .unwrap(); + let new_store = + Store::from_stream(format, &mut output_stream, &mut report, &context).unwrap(); assert!(!report.has_any_error()); @@ -7441,14 +6959,13 @@ pub mod tests { #[test] fn test_bmff_jumbf_generation_claim_v1() { - let settings = Settings::default(); - let http_resolver = SyncGenericResolver::new(); + let context = crate::context::Context::new(); // test adding to actual image let (format, mut input_stream, mut output_stream) = create_test_streams("video1.mp4"); // Create claims store. - let mut store = Store::with_settings(&settings); + let mut store = Store::with_context(&context); // Create a new claim. let claim1 = crate::utils::test::create_test_claim_v1().unwrap(); @@ -7463,8 +6980,7 @@ pub mod tests { &mut input_stream, &mut output_stream, signer.as_ref(), - &http_resolver, - &settings, + &context, ) .unwrap(); @@ -7472,15 +6988,8 @@ pub mod tests { // can we read back in output_stream.set_position(0); - let new_store = Store::from_stream( - format, - &mut output_stream, - true, - &mut report, - &http_resolver, - &settings, - ) - .unwrap(); + let new_store = + Store::from_stream(format, &mut output_stream, &mut report, &context).unwrap(); assert!(!report.has_any_error()); @@ -7489,8 +6998,7 @@ pub mod tests { #[test] fn test_jumbf_generation_with_bmffv3_fixed_block_size() { - let settings = Settings::default(); - let http_resolver = SyncGenericResolver::new(); + let context = crate::context::Context::new(); // test adding to actual image let (format, mut input_stream, mut output_stream) = create_test_streams("video1.mp4"); @@ -7499,7 +7007,7 @@ pub mod tests { crate::settings::set_settings_value("core.merkle_tree_chunk_size_in_kb", 1).unwrap(); // Create claims store. - let mut store = Store::with_settings(&settings); + let mut store = Store::with_context(&context); // Create a new claim. let claim1 = create_test_claim().unwrap(); @@ -7514,8 +7022,7 @@ pub mod tests { &mut input_stream, &mut output_stream, signer.as_ref(), - &http_resolver, - &settings, + &context, ) .unwrap(); @@ -7523,15 +7030,8 @@ pub mod tests { // can we read back in output_stream.set_position(0); - let new_store = Store::from_stream( - format, - &mut output_stream, - true, - &mut report, - &http_resolver, - &settings, - ) - .unwrap(); + let new_store = + Store::from_stream(format, &mut output_stream, &mut report, &context).unwrap(); assert!(!report.has_any_error()); @@ -7540,8 +7040,7 @@ pub mod tests { #[test] fn test_jumbf_generation_with_bmffv3_fixed_block_size_no_proof() { - let settings = Settings::default(); - let http_resolver = SyncGenericResolver::new(); + let context = crate::context::Context::new(); let (format, mut input_stream, mut output_stream) = create_test_streams("video1.mp4"); @@ -7550,7 +7049,7 @@ pub mod tests { crate::settings::set_settings_value("core.merkle_tree_max_proofs", 0).unwrap(); // Create claims store. - let mut store = Store::with_settings(&settings); + let mut store = Store::with_context(&context); // Create a new claim. let claim1 = create_test_claim().unwrap(); @@ -7565,8 +7064,7 @@ pub mod tests { &mut input_stream, &mut output_stream, signer.as_ref(), - &http_resolver, - &settings, + &context, ) .unwrap(); @@ -7574,15 +7072,8 @@ pub mod tests { // can we read back in output_stream.set_position(0); - let new_store = Store::from_stream( - format, - &mut output_stream, - true, - &mut report, - &http_resolver, - &settings, - ) - .unwrap(); + let new_store = + Store::from_stream(format, &mut output_stream, &mut report, &context).unwrap(); assert!(!report.has_any_error()); @@ -7591,8 +7082,7 @@ pub mod tests { #[test] fn test_jumbf_generation_with_bmffv3_fixed_block_size_stream() { - let settings = Settings::default(); - let http_resolver = SyncGenericResolver::new(); + let context = crate::context::Context::new(); // test adding to actual image let (format, mut input_stream, mut output_stream) = create_test_streams("video1.mp4"); @@ -7601,7 +7091,7 @@ pub mod tests { crate::settings::set_settings_value("core.merkle_tree_chunk_size_in_kb", 1).unwrap(); // Create claims store. - let mut store = Store::with_settings(&settings); + let mut store = Store::with_context(&context); // Create a new claim. let claim1 = create_test_claim().unwrap(); @@ -7616,8 +7106,7 @@ pub mod tests { &mut input_stream, &mut output_stream, signer.as_ref(), - &http_resolver, - &settings, + &context, ) .unwrap(); @@ -7625,15 +7114,8 @@ pub mod tests { // can we read back in output_stream.set_position(0); - let new_store = Store::from_stream( - format, - &mut output_stream, - true, - &mut report, - &http_resolver, - &settings, - ) - .unwrap(); + let new_store = + Store::from_stream(format, &mut output_stream, &mut report, &context).unwrap(); assert!(!report.has_any_error()); @@ -7642,14 +7124,14 @@ pub mod tests { #[test] fn test_bmff_jumbf_stream_generation() { - let settings = Settings::default(); - let http_resolver = SyncGenericResolver::new(); + let mut context = Context::new(); + context.settings_mut().verify.verify_after_reading = false; // test adding to actual image let (format, mut input_stream, mut output_stream) = create_test_streams("video1.mp4"); // Create claims store. - let mut store = Store::with_settings(&settings); + let mut store = Store::with_context(&context); // Create a new claim. let claim1 = create_test_claim().unwrap(); @@ -7665,8 +7147,7 @@ pub mod tests { &mut input_stream, &mut output_stream, signer.as_ref(), - &http_resolver, - &settings, + &context, ) .unwrap(); @@ -7675,22 +7156,19 @@ pub mod tests { output_stream.set_position(0); let (manifest_bytes, _) = - Store::load_jumbf_from_stream(format, &mut input_stream, &http_resolver, &settings) - .unwrap(); + Store::load_jumbf_from_stream(format, &mut input_stream, &context).unwrap(); let _new_store = { Store::from_manifest_data_and_stream( &manifest_bytes, format, &mut output_stream, - false, &mut report, - &http_resolver, - &settings, + &context, ) .unwrap() }; - println!("report = {report:?}"); + println!("report = {report:#?}"); assert!(!report.has_any_error()); } @@ -7698,19 +7176,13 @@ pub mod tests { #[cfg(not(target_arch = "wasm32"))] // TODO: Can run on Wasm once https://github.com/contentauth/c2pa-rs/pull/1325 lands fn test_removed_jumbf() { + let context = Context::new(); let (format, mut input_stream, _output_stream) = create_test_streams("no_manifest.jpg"); let mut report = StatusTracker::default(); // can we read back in - let result = Store::from_stream( - format, - &mut input_stream, - true, - &mut report, - &SyncGenericResolver::new(), - &Settings::default(), - ); + let result = Store::from_stream(format, &mut input_stream, &mut report, &context); assert!(result.is_err()); assert!(matches!(result, Err(Error::JumbfNotFound))); @@ -7762,11 +7234,10 @@ pub mod tests { use crate::utils::test::{run_file_test, TestFileSetup}; run_file_test(file_name, |setup: &TestFileSetup| { - let settings = Settings::default(); - let http_resolver = SyncGenericResolver::new(); + let context = crate::context::Context::new(); // Create claims store. - let mut store = Store::with_settings(&settings); + let mut store = Store::with_context(&context); // Create a new claim. let mut claim = create_test_claim().unwrap(); @@ -7788,8 +7259,7 @@ pub mod tests { &mut input_stream, &mut output_stream, signer.as_ref(), - &http_resolver, - &settings, + &context, ) .unwrap(); @@ -7813,10 +7283,8 @@ pub mod tests { &saved_manifest, format, &mut validation_stream, - true, &mut validation_log, - &http_resolver, - &settings, + &context, ) .unwrap(); }); @@ -7839,14 +7307,13 @@ pub mod tests { #[test] fn test_user_guid_external_manifest_embedded() { - let settings = Settings::default(); - let http_resolver = SyncGenericResolver::new(); + let context = crate::context::Context::new(); // Create test streams from fixture let (format, mut input_stream, mut output_stream) = create_test_streams("libpng-test.png"); // Create claims store. - let mut store = Store::with_settings(&settings); + let mut store = Store::with_context(&context); // Create a new claim. let mut claim = create_test_claim().unwrap(); @@ -7871,8 +7338,7 @@ pub mod tests { &mut input_stream, &mut output_stream, signer.as_ref(), - &http_resolver, - &settings, + &context, ) .unwrap(); @@ -7896,25 +7362,22 @@ pub mod tests { &saved_manifest, format, &mut output_stream, - true, &mut validation_log, - &http_resolver, - &settings, + &context, ) .unwrap(); } #[c2pa_test_async] async fn test_jumbf_generation_stream() { - let settings = Settings::default(); - let http_resolver = AsyncGenericResolver::new(); + let context = crate::context::Context::new(); let file_buffer = include_bytes!("../tests/fixtures/earth_apollo17.jpg").to_vec(); // convert buffer to cursor with Read/Write/Seek capability let mut buf_io = Cursor::new(file_buffer); // Create claims store. - let mut store = Store::with_settings(&settings); + let mut store = Store::with_context(&context); // Create a new claim. let claim1 = create_test_claim().unwrap(); @@ -7932,8 +7395,7 @@ pub mod tests { &mut buf_io, &mut result_stream, signer.as_ref(), - &http_resolver, - &settings, + &context, ) .await .unwrap(); @@ -7943,16 +7405,10 @@ pub mod tests { // make sure we can read from new file let mut report = StatusTracker::default(); - let _new_store = Store::from_stream_async( - "image/jpeg", - &mut result_stream, - true, - &mut report, - &http_resolver, - &settings, - ) - .await - .unwrap(); + let _new_store = + Store::from_stream_async("image/jpeg", &mut result_stream, &mut report, &context) + .await + .unwrap(); assert!(!report.has_any_error()); // std::fs::write("target/test.jpg", result).unwrap(); @@ -7960,14 +7416,13 @@ pub mod tests { #[test] fn test_tiff_jumbf_generation() { - let settings = Settings::default(); - let http_resolver = SyncGenericResolver::new(); + let context = crate::context::Context::new(); // Create test streams from fixture let (format, mut input_stream, mut output_stream) = create_test_streams("TUSCANY.TIF"); // Create claims store. - let mut store = Store::with_settings(&settings); + let mut store = Store::with_context(&context); // Create a new claim. let claim1 = create_test_claim().unwrap(); @@ -7983,8 +7438,7 @@ pub mod tests { &mut input_stream, &mut output_stream, signer.as_ref(), - &http_resolver, - &settings, + &context, ) .unwrap(); @@ -7994,15 +7448,8 @@ pub mod tests { // read from new file output_stream.rewind().unwrap(); - let new_store = Store::from_stream( - format, - &mut output_stream, - true, - &mut report, - &http_resolver, - &settings, - ) - .unwrap(); + let new_store = + Store::from_stream(format, &mut output_stream, &mut report, &context).unwrap(); assert!(!report.has_any_error()); @@ -8037,15 +7484,14 @@ pub mod tests { #[c2pa_test_async] #[cfg(feature = "file_io")] async fn test_boxhash_embeddable_manifest_async() { - let settings = Settings::default(); - let http_resolver = AsyncGenericResolver::new(); + let context = crate::context::Context::new(); // test adding to actual image let ap = fixture_path("boxhash.jpg"); let box_hash_path = fixture_path("boxhash.json"); // Create claims store. - let mut store = Store::with_settings(&settings); + let mut store = Store::with_context(&context); // Create a new claim. let mut claim = create_test_claim().unwrap(); @@ -8063,7 +7509,7 @@ pub mod tests { // get the embeddable manifest let em = store - .get_box_hashed_embeddable_manifest_async(&signer, &settings) + .get_box_hashed_embeddable_manifest_async(&signer, &context) .await .unwrap(); @@ -8103,16 +7549,10 @@ pub mod tests { out_stream.rewind().unwrap(); let mut report = StatusTracker::default(); - let _new_store = Store::from_stream_async( - "image/jpeg", - &mut out_stream, - true, - &mut report, - &http_resolver, - &settings, - ) - .await - .unwrap(); + let _new_store = + Store::from_stream_async("image/jpeg", &mut out_stream, &mut report, &context) + .await + .unwrap(); assert!(!report.has_any_error()); } @@ -8120,15 +7560,14 @@ pub mod tests { #[test] #[cfg(feature = "file_io")] fn test_boxhash_embeddable_manifest() { - let settings = Settings::default(); - let http_resolver = SyncGenericResolver::new(); + let context = crate::context::Context::new(); // test adding to actual image let ap = fixture_path("boxhash.jpg"); let box_hash_path = fixture_path("boxhash.json"); // Create claims store. - let mut store = Store::with_settings(&settings); + let mut store = Store::with_context(&context); // Create a new claim. let mut claim = create_test_claim().unwrap(); @@ -8146,7 +7585,7 @@ pub mod tests { // get the embeddable manifest let em = store - .get_box_hashed_embeddable_manifest(signer.as_ref(), &settings) + .get_box_hashed_embeddable_manifest(signer.as_ref(), &context) .unwrap(); // get composed version for embedding to JPEG @@ -8185,15 +7624,8 @@ pub mod tests { out_stream.rewind().unwrap(); let mut report = StatusTracker::default(); - let _new_store = Store::from_stream( - "image/jpeg", - &mut out_stream, - true, - &mut report, - &http_resolver, - &settings, - ) - .unwrap(); + let _new_store = + Store::from_stream("image/jpeg", &mut out_stream, &mut report, &context).unwrap(); assert!(!report.has_any_error()); } @@ -8201,8 +7633,7 @@ pub mod tests { #[c2pa_test_async] #[cfg(feature = "file_io")] async fn test_datahash_embeddable_manifest_async() { - let settings = Settings::default(); - let http_resolver = AsyncGenericResolver::new(); + let context = crate::context::Context::new(); // test adding to actual image use std::io::SeekFrom; @@ -8213,7 +7644,7 @@ pub mod tests { let signer = async_test_signer(SigningAlg::Ps256); // Create claims store. - let mut store = Store::with_settings(&settings); + let mut store = Store::with_context(&context); // Create a new claim. let claim = create_test_claim().unwrap(); @@ -8256,7 +7687,7 @@ pub mod tests { &signer, "jpeg", Some(&mut output_file), - &settings, + &context, ) .await .unwrap(); @@ -8267,16 +7698,10 @@ pub mod tests { output_file.rewind().unwrap(); let mut report = StatusTracker::default(); - let _new_store = Store::from_stream_async( - "image/jpeg", - &mut output_file, - true, - &mut report, - &http_resolver, - &settings, - ) - .await - .unwrap(); + let _new_store = + Store::from_stream_async("image/jpeg", &mut output_file, &mut report, &context) + .await + .unwrap(); assert!(!report.has_any_error()); } @@ -8284,8 +7709,7 @@ pub mod tests { #[test] #[cfg(feature = "file_io")] fn test_datahash_embeddable_manifest() { - let settings = Settings::default(); - let http_resolver = SyncGenericResolver::new(); + let context = crate::context::Context::new(); // test adding to actual image @@ -8296,7 +7720,7 @@ pub mod tests { let signer = test_signer(SigningAlg::Ps256); // Create claims store. - let mut store = Store::with_settings(&settings); + let mut store = Store::with_context(&context); // Create a new claim. let claim = create_test_claim().unwrap(); @@ -8339,7 +7763,7 @@ pub mod tests { signer.as_ref(), "jpeg", Some(&mut output_file), - &settings, + &context, ) .unwrap(); @@ -8349,15 +7773,8 @@ pub mod tests { output_file.rewind().unwrap(); let mut report = StatusTracker::default(); - let _new_store = Store::from_stream( - "image/jpeg", - &mut output_file, - true, - &mut report, - &http_resolver, - &settings, - ) - .unwrap(); + let _new_store = + Store::from_stream("image/jpeg", &mut output_file, &mut report, &context).unwrap(); assert!(!report.has_any_error()); } @@ -8365,8 +7782,7 @@ pub mod tests { #[test] #[cfg(feature = "file_io")] fn test_datahash_embeddable_manifest_user_hashed() { - let settings = Settings::default(); - let http_resolver = SyncGenericResolver::new(); + let context = crate::context::Context::new(); use std::io::SeekFrom; @@ -8381,7 +7797,7 @@ pub mod tests { let signer = test_signer(SigningAlg::Ps256); // Create claims store. - let mut store = Store::with_settings(&settings); + let mut store = Store::with_context(&context); // Create a new claim. let claim = create_test_claim().unwrap(); @@ -8420,7 +7836,7 @@ pub mod tests { // get the embeddable manifest, using user hashing let cm = store - .get_data_hashed_embeddable_manifest(&dh, signer.as_ref(), "jpeg", None, &settings) + .get_data_hashed_embeddable_manifest(&dh, signer.as_ref(), "jpeg", None, &context) .unwrap(); // path in new composed manifest @@ -8429,23 +7845,15 @@ pub mod tests { output_file.rewind().unwrap(); let mut report = StatusTracker::default(); - let _new_store = Store::from_stream( - "image/jpeg", - &mut output_file, - true, - &mut report, - &http_resolver, - &settings, - ) - .unwrap(); + let _new_store = + Store::from_stream("image/jpeg", &mut output_file, &mut report, &context).unwrap(); assert!(!report.has_any_error()); } #[test] fn test_dynamic_assertions() { - let settings = Settings::default(); - let http_resolver = SyncGenericResolver::new(); + let context = crate::context::Context::new(); #[derive(Serialize)] struct TestAssertion { @@ -8538,7 +7946,7 @@ pub mod tests { let mut buf_io = Cursor::new(file_buffer); // Create claims store. - let mut store = Store::with_settings(&settings); + let mut store = Store::with_context(&context); // Create a new claim. let claim1 = create_test_claim().unwrap(); @@ -8551,14 +7959,7 @@ pub mod tests { let mut result_stream = Cursor::new(result); store - .save_to_stream( - "jpeg", - &mut buf_io, - &mut result_stream, - &signer, - &http_resolver, - &settings, - ) + .save_to_stream("jpeg", &mut buf_io, &mut result_stream, &signer, &context) .unwrap(); // rewind the result stream to read from it @@ -8566,15 +7967,8 @@ pub mod tests { // make sure we can read from new file let mut report = StatusTracker::default(); - let new_store = Store::from_stream( - "image/jpeg", - &mut result_stream, - true, - &mut report, - &http_resolver, - &settings, - ) - .unwrap(); + let new_store = + Store::from_stream("image/jpeg", &mut result_stream, &mut report, &context).unwrap(); println!("new_store: {new_store}"); @@ -8586,8 +7980,7 @@ pub mod tests { async fn test_async_dynamic_assertions() { use async_trait::async_trait; - let settings = Settings::default(); - let http_resolver = AsyncGenericResolver::new(); + let context = crate::context::Context::new(); #[derive(Serialize)] struct TestAssertion { @@ -8684,7 +8077,7 @@ pub mod tests { let mut buf_io = Cursor::new(file_buffer); // Create claims store. - let mut store = Store::with_settings(&settings); + let mut store = Store::with_context(&context); // Create a new claim. let claim1 = create_test_claim().unwrap(); @@ -8697,14 +8090,7 @@ pub mod tests { let mut result_stream = Cursor::new(result); store - .save_to_stream_async( - "jpeg", - &mut buf_io, - &mut result_stream, - &signer, - &http_resolver, - &settings, - ) + .save_to_stream_async("jpeg", &mut buf_io, &mut result_stream, &signer, &context) .await .unwrap(); @@ -8712,16 +8098,9 @@ pub mod tests { // make sure we can read from new file let mut report = StatusTracker::default(); - let new_store = Store::from_stream_async( - "jpeg", - &mut result_stream, - true, - &mut report, - &http_resolver, - &settings, - ) - .await - .unwrap(); + let new_store = Store::from_stream_async("jpeg", &mut result_stream, &mut report, &context) + .await + .unwrap(); println!("new_store: {new_store}"); @@ -8731,8 +8110,7 @@ pub mod tests { &new_store, &mut ClaimAssetData::Bytes(&result, "jpg"), &mut report, - &http_resolver, - &settings, + &context, ) .await .unwrap(); @@ -8744,8 +8122,8 @@ pub mod tests { #[test] #[cfg(feature = "file_io")] fn test_fragmented_jumbf_generation() { - let settings = Settings::default(); - let http_resolver = SyncGenericResolver::new(); + let mut context = crate::context::Context::new(); + context.settings_mut().verify.verify_after_reading = false; // test adding to actual image @@ -8772,7 +8150,7 @@ pub mod tests { } // Create claims store. - let mut store = Store::with_settings(&settings); + let mut store = Store::with_context(&context); // Create a new claim. let claim = create_test_claim().unwrap(); @@ -8792,7 +8170,7 @@ pub mod tests { &fragments, new_output_path.as_path(), signer.as_ref(), - &settings, + &context, ) .unwrap(); @@ -8811,8 +8189,7 @@ pub mod tests { &mut init_stream, &mut fragment_stream, &mut validation_log, - &http_resolver, - &settings, + &context, ) .unwrap(); init_stream.seek(std::io::SeekFrom::Start(0)).unwrap(); @@ -8825,16 +8202,13 @@ pub mod tests { output_fragments.push(new_output_path.join(entry.file_name().unwrap())); } - //let mut reader = Cursor::new(init_stream); let mut validation_log = StatusTracker::default(); let _manifest = Store::load_from_file_and_fragments( "mp4", &mut init_stream, &output_fragments, - false, &mut validation_log, - &http_resolver, - &settings, + &context, ) .unwrap(); @@ -8849,20 +8223,23 @@ pub mod tests { #[cfg(feature = "file_io")] fn test_bogus_cert() { use crate::builder::{Builder, BuilderIntent}; + let png = include_bytes!("../tests/fixtures/libpng-test.png"); // Randomly generated local Ed25519 let ed25519 = include_bytes!("../tests/fixtures/certs/ed25519.pem"); let certs = include_bytes!("../tests/fixtures/certs/es256.pub"); - let mut builder = Builder::new(); + + let mut context = Context::new(); + // bypass auto signature checks + context.settings_mut().verify.verify_after_sign = false; + context.settings_mut().verify.verify_trust = false; + + let mut builder = Builder::from_context(context); builder.set_intent(BuilderIntent::Create(DigitalSourceType::Empty)); let signer = crate::create_signer::from_keys(certs, ed25519, SigningAlg::Ed25519, None).unwrap(); let mut dst = Cursor::new(Vec::new()); - // bypass auto sig check - crate::settings::set_settings_value("verify.verify_after_sign", false).unwrap(); - crate::settings::set_settings_value("verify.verify_trust", false).unwrap(); - builder .sign(&signer, "image/png", &mut Cursor::new(png), &mut dst) .unwrap(); @@ -8875,8 +8252,7 @@ pub mod tests { #[test] /// Test that we can we load a store from JUMBF and then convert it back to the identical JUMBF. fn test_from_and_to_jumbf() { - let settings = Settings::default(); - let http_resolver = SyncGenericResolver::new(); + let context = crate::context::Context::new(); // test adding to actual image let ap = fixture_path("C.jpg"); @@ -8885,12 +8261,12 @@ pub mod tests { let format = "image/jpeg"; let (manifest_bytes, _remote_url) = - Store::load_jumbf_from_stream(format, &mut stream, &http_resolver, &settings).unwrap(); + Store::load_jumbf_from_stream(format, &mut stream, &context).unwrap(); - let store = Store::from_jumbf_with_settings( + let store = Store::from_jumbf_with_context( &manifest_bytes, &mut StatusTracker::default(), - &settings, + &context, ) .unwrap(); @@ -8903,6 +8279,7 @@ pub mod tests { #[c2pa_test_async] async fn test_store_load_fragment_from_stream_async() { + let context = crate::context::Context::new(); // Use the dash fixtures that are known to work with fragment loading // These are the same files used in test_bmff_fragments let init_segment = include_bytes!("../tests/fixtures/dashinit.mp4"); @@ -8920,8 +8297,7 @@ pub mod tests { &mut init_stream, &mut fragment_stream, &mut validation_log, - &AsyncGenericResolver::new(), - &Settings::default(), + &context, ) .await; diff --git a/sdk/src/utils/test.rs b/sdk/src/utils/test.rs index 7f11bd743..bc885aed4 100644 --- a/sdk/src/utils/test.rs +++ b/sdk/src/utils/test.rs @@ -32,11 +32,11 @@ use crate::{ }, asset_io::CAIReadWrite, claim::Claim, + context::Context, crypto::{cose::CertificateTrustPolicy, raw_signature::SigningAlg}, hash_utils::Hasher, jumbf_io::get_assetio_handler, resource_store::UriOrResource, - settings::Settings, store::Store, utils::{io_utils::tempdirectory, mime::extension_to_mime}, AsyncSigner, ClaimGeneratorInfo, Result, @@ -327,7 +327,7 @@ pub fn create_test_claim_v1() -> Result { /// Creates a store with an unsigned claim for testing pub fn create_test_store() -> Result { // Create claims store. - let mut store = Store::with_settings(&Settings::default()); + let mut store = Store::with_context(&Context::new()); let claim = create_test_claim()?; store.commit_claim(claim).unwrap(); @@ -337,7 +337,7 @@ pub fn create_test_store() -> Result { /// Creates a store with an unsigned v1 claim for testing pub fn create_test_store_v1() -> Result { // Create claims store. - let mut store = Store::with_settings(&Settings::default()); + let mut store = Store::with_context(&Context::new()); let claim = create_test_claim_v1()?; store.commit_claim(claim).unwrap();