c2pa-rs

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

settings.rs (21027B)


      1 // Copyright 2024 Adobe. All rights reserved.
      2 // This file is licensed to you under the Apache License,
      3 // Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
      4 // or the MIT license (http://opensource.org/licenses/MIT),
      5 // at your option.
      6 
      7 // Unless required by applicable law or agreed to in writing,
      8 // this software is distributed on an "AS IS" BASIS, WITHOUT
      9 // WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or
     10 // implied. See the LICENSE-MIT and LICENSE-APACHE files for the
     11 // specific language governing permissions and limitations under
     12 // each license.
     13 
     14 #[cfg(feature = "file_io")]
     15 use std::path::Path;
     16 use std::{
     17     io::{BufRead, BufReader, Cursor},
     18     sync::RwLock,
     19 };
     20 
     21 use config::{Config, FileFormat};
     22 use lazy_static::lazy_static;
     23 use serde_derive::{Deserialize, Serialize};
     24 
     25 use crate::{utils::base64, Error, Result};
     26 
     27 lazy_static! {
     28     static ref SETTINGS: RwLock<Config> =
     29         RwLock::new(Config::try_from(&Settings::default()).unwrap_or_default());
     30 }
     31 
     32 // trait used to validate user input to make sure user supplied configurations are valid
     33 pub(crate) trait SettingsValidate {
     34     // returns error if settings are invalid
     35     fn validate(&self) -> Result<()> {
     36         Ok(())
     37     }
     38 }
     39 
     40 // Settings for trust list feature
     41 #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
     42 #[allow(unused)]
     43 pub(crate) struct Trust {
     44     private_anchors: Option<String>,
     45     trust_anchors: Option<String>,
     46     trust_config: Option<String>,
     47     allowed_list: Option<String>,
     48 }
     49 
     50 impl Trust {
     51     // load PEMs
     52     fn load_trust_from_data(&self, trust_data: &[u8]) -> Result<Vec<Vec<u8>>> {
     53         let mut certs = Vec::new();
     54 
     55         for pem_result in x509_parser::pem::Pem::iter_from_buffer(trust_data) {
     56             let pem = pem_result.map_err(|_e| Error::CoseInvalidCert)?;
     57             certs.push(pem.contents);
     58         }
     59         Ok(certs)
     60     }
     61 
     62     // sanity check to see if can parse trust settings
     63     fn test_load_trust(&self, allowed_list: &[u8]) -> Result<()> {
     64         // check pems
     65         if let Ok(cert_list) = self.load_trust_from_data(allowed_list) {
     66             if !cert_list.is_empty() {
     67                 return Ok(());
     68             }
     69         }
     70 
     71         // try to load the of base64 encoded encoding of the sha256 hash of the certificate DER encoding
     72         let reader = Cursor::new(allowed_list);
     73         let buf_reader = BufReader::new(reader);
     74         let mut found_der_hash = false;
     75 
     76         let mut inside_cert_block = false;
     77         for l in buf_reader.lines().map_while(|v| v.ok()) {
     78             if l.contains("-----BEGIN") {
     79                 inside_cert_block = true;
     80             }
     81             if l.contains("-----END") {
     82                 inside_cert_block = false;
     83             }
     84 
     85             // sanity check that that is is base64 encoded and outside of certificate block
     86             if !inside_cert_block && base64::decode(&l).is_ok() && !l.is_empty() {
     87                 found_der_hash = true;
     88             }
     89         }
     90 
     91         if found_der_hash {
     92             Ok(())
     93         } else {
     94             Err(Error::NotFound)
     95         }
     96     }
     97 }
     98 
     99 impl SettingsValidate for Trust {
    100     fn validate(&self) -> Result<()> {
    101         if let Some(ta) = &self.trust_anchors {
    102             self.test_load_trust(ta.as_bytes())?;
    103         }
    104 
    105         if let Some(pa) = &self.private_anchors {
    106             self.test_load_trust(pa.as_bytes())?;
    107         }
    108 
    109         if let Some(al) = &self.allowed_list {
    110             self.test_load_trust(al.as_bytes())?;
    111         }
    112 
    113         Ok(())
    114     }
    115 }
    116 
    117 // Settings for core C2PA-RS functionality
    118 #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
    119 #[allow(unused)]
    120 pub(crate) struct Core {
    121     debug: bool,
    122     hash_alg: String,
    123     salt_jumbf_boxes: bool,
    124     prefer_box_hash: bool,
    125     prefer_bmff_merkle_tree: bool,
    126     compress_manifests: bool,
    127     max_memory_usage: Option<u64>,
    128 }
    129 
    130 impl Default for Core {
    131     fn default() -> Self {
    132         Self {
    133             debug: false,
    134             hash_alg: "sha256".into(),
    135             salt_jumbf_boxes: true,
    136             prefer_box_hash: false,
    137             prefer_bmff_merkle_tree: false,
    138             compress_manifests: true,
    139             max_memory_usage: None,
    140         }
    141     }
    142 }
    143 
    144 impl SettingsValidate for Core {
    145     fn validate(&self) -> Result<()> {
    146         match self.hash_alg.as_str() {
    147             "sha256" | "sha384" | "sha512" => Ok(()),
    148             _ => Err(Error::UnsupportedType),
    149         }
    150     }
    151 }
    152 
    153 // Settings for verification options
    154 #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
    155 #[allow(unused)]
    156 pub(crate) struct Verify {
    157     verify_after_reading: bool,
    158     verify_after_sign: bool,
    159     verify_trust: bool,
    160     ocsp_fetch: bool,
    161     remote_manifest_fetch: bool,
    162 }
    163 
    164 impl Default for Verify {
    165     fn default() -> Self {
    166         Self {
    167             verify_after_reading: true,
    168             verify_after_sign: true,
    169             verify_trust: false,
    170             ocsp_fetch: false,
    171             remote_manifest_fetch: true,
    172         }
    173     }
    174 }
    175 
    176 impl SettingsValidate for Verify {}
    177 
    178 // Settings for Builder API options
    179 #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
    180 #[allow(unused)]
    181 pub(crate) struct Builder {
    182     auto_thumbnail: bool,
    183 }
    184 
    185 impl Default for Builder {
    186     fn default() -> Self {
    187         Self {
    188             auto_thumbnail: true,
    189         }
    190     }
    191 }
    192 
    193 impl SettingsValidate for Builder {}
    194 
    195 // Settings configuration for C2PA-RS.  Default configuration values
    196 // are lazy loaded on first use.  Values can also be loaded from a configuration
    197 // file or by setting specific value via code.  There is a single configuration
    198 // setting for the entire C2PA-RS instance.
    199 #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
    200 #[allow(unused)]
    201 pub(crate) struct Settings {
    202     trust: Trust,
    203     core: Core,
    204     verify: Verify,
    205     builder: Builder,
    206 }
    207 
    208 impl Settings {
    209     #[allow(unused)]
    210     #[cfg(feature = "file_io")]
    211     pub fn from_file<P: AsRef<Path>>(setting_path: P) -> Result<Self> {
    212         let ext = setting_path
    213             .as_ref()
    214             .extension()
    215             .ok_or(Error::UnsupportedType)?
    216             .to_string_lossy();
    217 
    218         let setting_buf = std::fs::read(&setting_path).map_err(Error::IoError)?;
    219         Settings::from_string(&String::from_utf8_lossy(&setting_buf), &ext)
    220     }
    221 
    222     #[allow(unused)]
    223     pub fn from_string(settings_str: &str, format: &str) -> Result<Self> {
    224         let f = match format.to_lowercase().as_str() {
    225             "json" => FileFormat::Json,
    226             "json5" => FileFormat::Json5,
    227             //"ini" => FileFormat::Ini,
    228             "toml" => FileFormat::Toml,
    229             //"yaml" => FileFormat::Yaml,
    230             "ron" => FileFormat::Ron,
    231             _ => return Err(Error::UnsupportedType),
    232         };
    233 
    234         let new_config = Config::builder()
    235             .add_source(config::File::from_str(settings_str, f))
    236             .build()
    237             .map_err(|_e| Error::BadParam("could not parse configuration file".into()))?;
    238 
    239         // blocking write
    240         match SETTINGS.write() {
    241             Ok(mut c) => {
    242                 let source = c.clone();
    243                 let update_config = Config::builder()
    244                     .add_source(source)
    245                     .add_source(new_config) // merge overrides, allows for partial changes
    246                     .build()
    247                     .map_err(|_e| Error::OtherError("could not update configuration".into()))?;
    248 
    249                 // sanity check the values before committing
    250                 let settings = update_config
    251                     .clone()
    252                     .try_deserialize::<Settings>()
    253                     .map_err(|_e| {
    254                         Error::BadParam("configuration file contains unrecognized param".into())
    255                     })?;
    256                 settings.validate()?;
    257 
    258                 // update if valid
    259                 *c = update_config;
    260 
    261                 Ok(settings)
    262             }
    263             Err(_) => Err(Error::OtherError("could not save settings".into())),
    264         }
    265     }
    266 }
    267 
    268 impl SettingsValidate for Settings {
    269     fn validate(&self) -> Result<()> {
    270         self.trust.validate()?;
    271         self.core.validate()?;
    272         self.trust.validate()?;
    273         self.builder.validate()
    274     }
    275 }
    276 
    277 // Get snapshot of the Settings objects, returns None if there is an error
    278 #[allow(unused)]
    279 pub(crate) fn get_settings() -> Option<Settings> {
    280     match SETTINGS.try_read() {
    281         // concurrent read
    282         Ok(c) => {
    283             let source = c.clone(); // clone required since deserialize consumes object
    284             let cloned_config = Config::builder().add_source(source).build();
    285 
    286             if let Ok(cloned_config) = cloned_config {
    287                 match cloned_config.try_deserialize::<Settings>() {
    288                     Ok(s) => Some(s),
    289                     Err(_) => None,
    290                 }
    291             } else {
    292                 None
    293             }
    294         }
    295         Err(_) => None,
    296     }
    297 }
    298 
    299 // Load settings from configuration file
    300 #[allow(unused)]
    301 #[cfg(feature = "file_io")]
    302 pub(crate) fn load_settings<P: AsRef<Path>>(settings_path: P) -> Result<()> {
    303     let ext = settings_path
    304         .as_ref()
    305         .extension()
    306         .ok_or(Error::UnsupportedType)?
    307         .to_string_lossy();
    308 
    309     let setting_buf = std::fs::read(&settings_path).map_err(Error::IoError)?;
    310 
    311     load_settings_from_str(&String::from_utf8_lossy(&setting_buf), &ext)
    312 }
    313 
    314 /// Load settings form string representation of the configuration.  Format of configuration must be supplied.
    315 #[allow(unused)]
    316 pub fn load_settings_from_str(settings_str: &str, format: &str) -> Result<()> {
    317     Settings::from_string(settings_str, format).map(|_| ())
    318 }
    319 
    320 // Save the current configuration to a json file.
    321 #[allow(unused)]
    322 #[cfg(feature = "file_io")]
    323 pub(crate) fn save_settings_as_json<P: AsRef<Path>>(settings_path: P) -> Result<()> {
    324     let settings =
    325         get_settings().ok_or(Error::OtherError("could not get current settings".into()))?;
    326 
    327     let settings_json = serde_json::to_string_pretty(&settings).map_err(Error::JsonError)?;
    328 
    329     std::fs::write(settings_path, settings_json.as_bytes()).map_err(Error::IoError)
    330 }
    331 
    332 // Set a Settings value by path reference.  The path is nested names of of the Settings objects
    333 // separated by "." notation.  For example "core.hash_alg" would set settings.core.hash_alg value.
    334 // The nesting can be arbitrarily deep based on the Settings definition.
    335 #[allow(unused)]
    336 pub(crate) fn set_settings_value<T: Into<config::Value>>(value_path: &str, value: T) -> Result<()> {
    337     match SETTINGS.write() {
    338         Ok(mut c) => {
    339             let source = c.clone();
    340             let update_config = Config::builder()
    341                 .add_source(source)
    342                 .set_override(value_path, value);
    343 
    344             if let Ok(updated) = update_config {
    345                 let update_config = updated
    346                     .build()
    347                     .map_err(|_e| Error::OtherError("could not update configuration".into()))?;
    348 
    349                 let settings = update_config
    350                     .clone()
    351                     .try_deserialize::<Settings>()
    352                     .map_err(|_e| {
    353                         Error::BadParam("configuration file contains unrecognized param".into())
    354                     })?;
    355                 settings.validate()?;
    356 
    357                 *c = update_config;
    358 
    359                 Ok(())
    360             } else {
    361                 Err(Error::OtherError("could not save settings".into()))
    362             }
    363         }
    364         Err(_) => Err(Error::OtherError("could not save settings".into())),
    365     }
    366 }
    367 
    368 // Get a Settings value by path reference.  The path is nested names of of the Settings objects
    369 // separated by "." notation.  For example "core.hash_alg" would get the settings.core.hash_alg value.
    370 // The nesting can be arbitrarily deep based on the Settings definition.
    371 #[allow(unused)]
    372 pub(crate) fn get_settings_value<'de, T: serde::de::Deserialize<'de>>(
    373     value_path: &str,
    374 ) -> Result<T> {
    375     match SETTINGS.read() {
    376         Ok(settings) => settings.get::<T>(value_path).map_err(|_| Error::NotFound),
    377         Err(_) => Err(Error::OtherError("could not read setting object".into())),
    378     }
    379 }
    380 
    381 // Set settings back to the default values.  Current use case is for testing.
    382 #[allow(unused)]
    383 pub fn reset_default_settings() -> Result<()> {
    384     if let Ok(default_settings) = Config::try_from(&Settings::default()) {
    385         match SETTINGS.write() {
    386             Ok(mut current_settings) => {
    387                 *current_settings = default_settings;
    388                 Ok(())
    389             }
    390             Err(_) => Err(Error::OtherError("could not save settings".into())),
    391         }
    392     } else {
    393         Err(Error::OtherError("could not save settings".into()))
    394     }
    395 }
    396 
    397 #[cfg(test)]
    398 pub mod tests {
    399     #![allow(clippy::panic)]
    400     #![allow(clippy::unwrap_used)]
    401 
    402     use std::sync::Mutex;
    403 
    404     use super::*;
    405 
    406     // prevent tests from polluting the results of each other because of Rust unit test concurrency
    407     static PROTECT: Mutex<u32> = Mutex::new(1); // prevent tests from polluting the results of each other
    408 
    409     #[test]
    410     fn test_get_defaults() {
    411         let _protect = PROTECT.lock().unwrap();
    412 
    413         let settings = get_settings().unwrap();
    414 
    415         assert_eq!(settings.core, Core::default());
    416         assert_eq!(settings.trust, Trust::default());
    417         assert_eq!(settings.verify, Verify::default());
    418         assert_eq!(settings.builder, Builder::default());
    419 
    420         reset_default_settings().unwrap();
    421     }
    422 
    423     #[test]
    424     fn test_get_val_by_direct_path() {
    425         let _protect = PROTECT.lock().unwrap();
    426 
    427         // you can do this for all values but if these sanity checks pass they all should if the path is correct
    428         assert_eq!(
    429             get_settings_value::<String>("core.hash_alg").unwrap(),
    430             Core::default().hash_alg
    431         );
    432         assert_eq!(
    433             get_settings_value::<bool>("builder.auto_thumbnail").unwrap(),
    434             Builder::default().auto_thumbnail
    435         );
    436         assert_eq!(
    437             get_settings_value::<Option<String>>("trust.private_anchors").unwrap(),
    438             Trust::default().private_anchors
    439         );
    440 
    441         // test getting full objects
    442         assert_eq!(get_settings_value::<Core>("core").unwrap(), Core::default());
    443         assert_eq!(
    444             get_settings_value::<Verify>("verify").unwrap(),
    445             Verify::default()
    446         );
    447         assert_eq!(
    448             get_settings_value::<Builder>("builder").unwrap(),
    449             Builder::default()
    450         );
    451         assert_eq!(
    452             get_settings_value::<Trust>("trust").unwrap(),
    453             Trust::default()
    454         );
    455 
    456         // test implicit deserialization
    457         let hash_alg: String = get_settings_value("core.hash_alg").unwrap();
    458         let remote_manifest_fetch: bool =
    459             get_settings_value("verify.remote_manifest_fetch").unwrap();
    460         let auto_thumbnail: bool = get_settings_value("builder.auto_thumbnail").unwrap();
    461         let private_anchors: Option<String> = get_settings_value("trust.private_anchors").unwrap();
    462 
    463         assert_eq!(hash_alg, Core::default().hash_alg);
    464         assert_eq!(
    465             remote_manifest_fetch,
    466             Verify::default().remote_manifest_fetch
    467         );
    468         assert_eq!(auto_thumbnail, Builder::default().auto_thumbnail);
    469         assert_eq!(private_anchors, Trust::default().private_anchors);
    470 
    471         // test implicit deserialization on objects
    472         let core: Core = get_settings_value("core").unwrap();
    473         let verify: Verify = get_settings_value("verify").unwrap();
    474         let builder: Builder = get_settings_value("builder").unwrap();
    475         let trust: Trust = get_settings_value("trust").unwrap();
    476 
    477         assert_eq!(core, Core::default());
    478         assert_eq!(verify, Verify::default());
    479         assert_eq!(builder, Builder::default());
    480         assert_eq!(trust, Trust::default());
    481 
    482         reset_default_settings().unwrap();
    483     }
    484 
    485     #[test]
    486     fn test_set_val_by_direct_path() {
    487         let _protect = PROTECT.lock().unwrap();
    488 
    489         let ts = include_bytes!("../tests/fixtures/certs/trust/test_cert_root_bundle.pem");
    490 
    491         // test updating values
    492         set_settings_value("core.hash_alg", "sha512").unwrap();
    493         set_settings_value("verify.remote_manifest_fetch", false).unwrap();
    494         set_settings_value("builder.auto_thumbnail", false).unwrap();
    495         set_settings_value(
    496             "trust.private_anchors",
    497             Some(String::from_utf8(ts.to_vec()).unwrap()),
    498         )
    499         .unwrap();
    500 
    501         assert_eq!(
    502             get_settings_value::<String>("core.hash_alg").unwrap(),
    503             "sha512"
    504         );
    505         assert!(!get_settings_value::<bool>("verify.remote_manifest_fetch").unwrap());
    506         assert!(!get_settings_value::<bool>("builder.auto_thumbnail").unwrap());
    507         assert_eq!(
    508             get_settings_value::<Option<String>>("trust.private_anchors").unwrap(),
    509             Some(String::from_utf8(ts.to_vec()).unwrap())
    510         );
    511 
    512         // the current config should be different from the defaults
    513         assert_ne!(get_settings_value::<Core>("core").unwrap(), Core::default());
    514         assert_ne!(
    515             get_settings_value::<Verify>("verify").unwrap(),
    516             Verify::default()
    517         );
    518         assert_ne!(
    519             get_settings_value::<Builder>("builder").unwrap(),
    520             Builder::default()
    521         );
    522         assert_ne!(
    523             get_settings_value::<Trust>("trust").unwrap(),
    524             Trust::default()
    525         );
    526 
    527         reset_default_settings().unwrap();
    528     }
    529 
    530     #[cfg(feature = "file_io")]
    531     #[test]
    532     fn test_save_load() {
    533         let _protect = PROTECT.lock().unwrap();
    534 
    535         let temp_dir = tempfile::tempdir().unwrap();
    536         let op = crate::utils::test::temp_dir_path(&temp_dir, "sdk_config.json");
    537 
    538         save_settings_as_json(&op).unwrap();
    539 
    540         load_settings(&op).unwrap();
    541         let settings = get_settings().unwrap();
    542 
    543         assert_eq!(settings, Settings::default());
    544 
    545         reset_default_settings().unwrap();
    546     }
    547 
    548     #[cfg(feature = "file_io")]
    549     #[test]
    550     fn test_save_load_from_string() {
    551         let _protect = PROTECT.lock().unwrap();
    552 
    553         let temp_dir = tempfile::tempdir().unwrap();
    554         let op = crate::utils::test::temp_dir_path(&temp_dir, "sdk_config.json");
    555 
    556         save_settings_as_json(&op).unwrap();
    557 
    558         let setting_buf = std::fs::read(&op).unwrap();
    559 
    560         load_settings_from_str(&String::from_utf8_lossy(&setting_buf), "json").unwrap();
    561         let settings = get_settings().unwrap();
    562 
    563         assert_eq!(settings, Settings::default());
    564 
    565         reset_default_settings().unwrap();
    566     }
    567 
    568     #[test]
    569     fn test_partial_loading() {
    570         let _protect = PROTECT.lock().unwrap();
    571 
    572         // we support just changing the fields you are interested in changing
    573         // here is an example of incomplete structures only overriding specific
    574         // fields
    575 
    576         let modified_core = r#"{
    577             "core": {
    578                 "debug": true,
    579                 "hash_alg": "sha512",
    580                 "max_memory_usage": 123456
    581             }
    582         }"#;
    583 
    584         load_settings_from_str(modified_core, "json").unwrap();
    585 
    586         // see if updated values match
    587         assert!(get_settings_value::<bool>("core.debug").unwrap());
    588         assert_eq!(
    589             get_settings_value::<String>("core.hash_alg").unwrap(),
    590             "sha512".to_string()
    591         );
    592         assert_eq!(
    593             get_settings_value::<u32>("core.max_memory_usage").unwrap(),
    594             123456u32
    595         );
    596 
    597         // check a few defaults to make sure they are still there
    598         assert_eq!(
    599             get_settings_value::<bool>("builder.auto_thumbnail").unwrap(),
    600             Builder::default().auto_thumbnail
    601         );
    602 
    603         assert_eq!(
    604             get_settings_value::<bool>("core.salt_jumbf_boxes").unwrap(),
    605             Core::default().salt_jumbf_boxes
    606         );
    607 
    608         reset_default_settings().unwrap();
    609     }
    610 
    611     #[test]
    612     fn test_bad_setting() {
    613         let _protect = PROTECT.lock().unwrap();
    614 
    615         let modified_core = r#"{
    616             "core": {
    617                 "debug": true,
    618                 "hash_alg": "sha1000000",
    619                 "max_memory_usage": 123456
    620             }
    621         }"#;
    622 
    623         assert!(load_settings_from_str(modified_core, "json").is_err());
    624 
    625         reset_default_settings().unwrap();
    626     }
    627     #[test]
    628     fn test_hidden_setting() {
    629         let _protect = PROTECT.lock().unwrap();
    630 
    631         let secret = r#"{
    632             "hidden": {
    633                 "test1": true,
    634                 "test2": "hello world",
    635                 "test3": 123456
    636             }
    637         }"#;
    638 
    639         load_settings_from_str(secret, "json").unwrap();
    640 
    641         assert!(get_settings_value::<bool>("hidden.test1").unwrap());
    642         assert_eq!(
    643             get_settings_value::<String>("hidden.test2").unwrap(),
    644             "hello world".to_string()
    645         );
    646         assert_eq!(
    647             get_settings_value::<u32>("hidden.test3").unwrap(),
    648             123456u32
    649         );
    650 
    651         reset_default_settings().unwrap();
    652     }
    653 }