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 }