diff --git a/src/api/customization.rs b/src/api/customization.rs index 2ef6f2a..8f3319f 100644 --- a/src/api/customization.rs +++ b/src/api/customization.rs @@ -18,6 +18,8 @@ //! Our deploy script enforces the invariants. use crate::ctap::data_formats::CredentialProtectionPolicy; +use alloc::string::String; +use alloc::vec::Vec; pub trait Customization { // ########################################################################### @@ -42,6 +44,42 @@ pub trait Customization { /// This can improve privacy, but can make usage less comfortable. fn default_cred_protect(&self) -> Option; + /// Sets the initial minimum PIN length in code points. + /// + /// # Invariant + /// + /// - The minimum PIN length must be at least 4. + /// - The minimum PIN length must be at most 63. + /// - default_min_pin_length_rp_ids() must be non-empty if max_rp_ids_length() is 0. + /// + /// Requiring longer PINs can help establish trust between users and relying + /// parties. It makes user verification harder to break, but less convenient. + /// NIST recommends at least 6-digit PINs in section 5.1.9.1: + /// https://pages.nist.gov/800-63-3/sp800-63b.html + /// + /// Reset reverts the minimum PIN length to this DEFAULT_MIN_PIN_LENGTH. + fn default_min_pin_length(&self) -> u8; + + /// Lists relying parties that can read the minimum PIN length. + /// + /// # Invariant + /// + /// - default_min_pin_length_rp_ids() must be non-empty if max_rp_ids_length() is 0 + /// + /// Only the RP IDs listed in default_min_pin_length_rp_ids are allowed to read + /// the minimum PIN length with the minPinLength extension. + fn default_min_pin_length_rp_ids(&self) -> Vec; + + /// Enforces the alwaysUv option. + /// + /// When setting to true, commands require a PIN. + /// Also, alwaysUv can not be disabled by commands. + /// + /// A certification (additional to FIDO Alliance's) might require enforcing + /// alwaysUv. Otherwise, users should have the choice to configure alwaysUv. + /// Calling toggleAlwaysUv is preferred over enforcing alwaysUv here. + fn enforce_always_uv(&self) -> bool; + /// Maximum message size send for CTAP commands. /// /// The maximum value is 7609, as HID packets can not encode longer messages. @@ -50,17 +88,73 @@ pub trait Customization { /// If long commands are too unreliable on your hardware, consider decreasing /// this value. fn max_msg_size(&self) -> usize; + + /// Sets the number of consecutive failed PINs before blocking interaction. + /// + /// # Invariant + /// + /// - CTAP2.0: Maximum PIN retries must be 8. + /// - CTAP2.1: Maximum PIN retries must be 8 at most. + /// + /// The fail retry counter is reset after entering the correct PIN. + fn max_pin_retries(&self) -> u8; + + /// Enables or disables signature counters. + /// + /// The signature counter is currently implemented as a global counter. + /// The specification strongly suggests to have per-credential counters. + /// Implementing those means you can't have an infinite amount of server-side + /// credentials anymore. Also, since counters need frequent writes on the + /// persistent storage, we might need a flash friendly implementation. This + /// solution is a compromise to be compatible with U2F and not wasting storage. + /// + /// https://www.w3.org/TR/webauthn/#signature-counter + fn use_signature_counter(&self) -> bool; + + // ########################################################################### + // Constants for performance optimization or adapting to different hardware. + // + // Those constants may be modified before compilation to tune the behavior of + // the key. + // ########################################################################### + + /// Limits the number of RP IDs that can change the minimum PIN length. + /// + /// # Invariant + /// + /// - If this value is 0, default_min_pin_length_rp_ids() must be non-empty. + /// + /// You can use this constant to have an upper limit in storage requirements. + /// This might be useful if you want to more reliably predict the remaining + /// storage. Stored string can still be of arbitrary length though, until RP ID + /// truncation is implemented. + /// Outside of memory considerations, you can set this value to 0 if only RP IDs + /// in default_min_pin_length_rp_ids() should be allowed to change the minimum + /// PIN length. + fn max_rp_ids_length(&self) -> usize; } #[derive(Clone)] pub struct CustomizationImpl { pub default_cred_protect: Option, + pub default_min_pin_length: u8, + pub default_min_pin_length_rp_ids: &'static [&'static str], + pub enforce_always_uv: bool, pub max_msg_size: usize, + pub max_pin_retries: u8, + pub use_signature_counter: bool, + pub max_rp_ids_length: usize, } pub const DEFAULT_CUSTOMIZATION: CustomizationImpl = CustomizationImpl { + default_min_pin_length: 4, + default_min_pin_length_rp_ids: &[], + enforce_always_uv: false, default_cred_protect: None, max_msg_size: 7609, + max_pin_retries: 8, + use_signature_counter: true, + max_rp_ids_length: 8, }; impl Customization for CustomizationImpl { @@ -68,9 +162,36 @@ impl Customization for CustomizationImpl { self.default_cred_protect } + fn default_min_pin_length(&self) -> u8 { + self.default_min_pin_length + } + + fn default_min_pin_length_rp_ids(&self) -> Vec { + self.default_min_pin_length_rp_ids + .iter() + .map(|s| String::from(*s)) + .collect() + } + + fn enforce_always_uv(&self) -> bool { + self.enforce_always_uv + } + fn max_msg_size(&self) -> usize { self.max_msg_size } + + fn max_pin_retries(&self) -> u8 { + self.max_pin_retries + } + + fn use_signature_counter(&self) -> bool { + self.use_signature_counter + } + + fn max_rp_ids_length(&self) -> usize { + self.max_rp_ids_length + } } #[cfg(feature = "std")] @@ -79,6 +200,24 @@ pub fn is_valid(customization: &impl Customization) -> bool { if customization.max_msg_size() < 1024 || customization.max_msg_size() > 7609 { return false; } + + // Default min pin length must be between 4 and 63. + if customization.default_min_pin_length() < 4 || customization.default_min_pin_length() > 63 { + return false; + } + + // Max pin retries must be less or equal than 8. + if customization.max_pin_retries() > 8 { + return false; + } + + // Default min pin length rp ids must be non-empty if max rp ids length is 0. + if customization.max_rp_ids_length() == 0 + && customization.default_min_pin_length_rp_ids().is_empty() + { + return false; + } + true } @@ -87,7 +226,6 @@ mod test { use super::*; #[test] - #[allow(clippy::assertions_on_constants)] fn test_invariants() { assert!(is_valid(&DEFAULT_CUSTOMIZATION)); } diff --git a/src/ctap/config_command.rs b/src/ctap/config_command.rs index 6cb1948..c895faa 100644 --- a/src/ctap/config_command.rs +++ b/src/ctap/config_command.rs @@ -123,7 +123,7 @@ pub fn process_config( #[cfg(test)] mod test { use super::*; - use crate::ctap::customization::ENFORCE_ALWAYS_UV; + use crate::api::customization::Customization; use crate::ctap::data_formats::PinUvAuthProtocol; use crate::ctap::pin_protocol::authenticate_pin_uv_auth_token; use crate::env::test::TestEnv; @@ -180,7 +180,7 @@ mod test { pin_uv_auth_protocol: None, }; let config_response = process_config(&mut env, &mut client_pin, config_params); - if ENFORCE_ALWAYS_UV { + if env.customization().enforce_always_uv() { assert_eq!( config_response, Err(Ctap2StatusCode::CTAP2_ERR_OPERATION_DENIED) @@ -210,7 +210,7 @@ mod test { pin_uv_auth_protocol: Some(pin_uv_auth_protocol), }; let config_response = process_config(&mut env, &mut client_pin, config_params); - if ENFORCE_ALWAYS_UV { + if env.customization().enforce_always_uv() { assert_eq!( config_response, Err(Ctap2StatusCode::CTAP2_ERR_OPERATION_DENIED) diff --git a/src/ctap/ctap1.rs b/src/ctap/ctap1.rs index c1e08d6..42c0e23 100644 --- a/src/ctap/ctap1.rs +++ b/src/ctap/ctap1.rs @@ -344,8 +344,9 @@ impl Ctap1Command { #[cfg(test)] mod test { - use super::super::{key_material, CREDENTIAL_ID_SIZE, USE_SIGNATURE_COUNTER}; + use super::super::{key_material, CREDENTIAL_ID_SIZE}; use super::*; + use crate::api::customization::Customization; use crate::clock::TEST_CLOCK_FREQUENCY_HZ; use crate::env::test::TestEnv; use crypto::Hash256; @@ -643,8 +644,8 @@ mod test { assert_eq!(response, Err(Ctap1StatusCode::SW_WRONG_DATA)); } - fn check_signature_counter(response: &[u8; 4], signature_counter: u32) { - if USE_SIGNATURE_COUNTER { + fn check_signature_counter(env: &mut impl Env, response: &[u8; 4], signature_counter: u32) { + if env.customization().use_signature_counter() { assert_eq!(u32::from_be_bytes(*response), signature_counter); } else { assert_eq!(response, &[0x00, 0x00, 0x00, 0x00]); @@ -673,9 +674,11 @@ mod test { Ctap1Command::process_command(&mut env, &message, &mut ctap_state, CtapInstant::new(0)) .unwrap(); assert_eq!(response[0], 0x01); + let global_signature_counter = storage::global_signature_counter(&mut env).unwrap(); check_signature_counter( + &mut env, array_ref!(response, 1, 4), - storage::global_signature_counter(&mut env).unwrap(), + global_signature_counter, ); } @@ -706,9 +709,11 @@ mod test { ) .unwrap(); assert_eq!(response[0], 0x01); + let global_signature_counter = storage::global_signature_counter(&mut env).unwrap(); check_signature_counter( + &mut env, array_ref!(response, 1, 4), - storage::global_signature_counter(&mut env).unwrap(), + global_signature_counter, ); } diff --git a/src/ctap/customization.rs b/src/ctap/customization.rs index 3bb24a5..d5976c9 100644 --- a/src/ctap/customization.rs +++ b/src/ctap/customization.rs @@ -19,46 +19,6 @@ use crate::ctap::data_formats::EnterpriseAttestationMode; -// ########################################################################### -// Constants for adjusting privacy and protection levels. -// ########################################################################### - -/// Sets the initial minimum PIN length in code points. -/// -/// # Invariant -/// -/// - The minimum PIN length must be at least 4. -/// - The minimum PIN length must be at most 63. -/// - DEFAULT_MIN_PIN_LENGTH_RP_IDS must be non-empty if MAX_RP_IDS_LENGTH is 0. -/// -/// Requiring longer PINs can help establish trust between users and relying -/// parties. It makes user verification harder to break, but less convenient. -/// NIST recommends at least 6-digit PINs in section 5.1.9.1: -/// https://pages.nist.gov/800-63-3/sp800-63b.html -/// -/// Reset reverts the minimum PIN length to this DEFAULT_MIN_PIN_LENGTH. -pub const DEFAULT_MIN_PIN_LENGTH: u8 = 4; - -/// Lists relying parties that can read the minimum PIN length. -/// -/// # Invariant -/// -/// - DEFAULT_MIN_PIN_LENGTH_RP_IDS must be non-empty if MAX_RP_IDS_LENGTH is 0 -/// -/// Only the RP IDs listed in DEFAULT_MIN_PIN_LENGTH_RP_IDS are allowed to read -/// the minimum PIN length with the minPinLength extension. -pub const DEFAULT_MIN_PIN_LENGTH_RP_IDS: &[&str] = &[]; - -/// Enforces the alwaysUv option. -/// -/// When setting to true, commands require a PIN. -/// Also, alwaysUv can not be disabled by commands. -/// -/// A certification (additional to FIDO Alliance's) might require enforcing -/// alwaysUv. Otherwise, users should have the choice to configure alwaysUv. -/// Calling toggleAlwaysUv is preferred over enforcing alwaysUv here. -pub const ENFORCE_ALWAYS_UV: bool = false; - /// Allows usage of enterprise attestation. /// /// # Invariant @@ -101,16 +61,6 @@ pub const ENTERPRISE_ATTESTATION_MODE: Option = None; /// VendorFacilitated. pub const ENTERPRISE_RP_ID_LIST: &[&str] = &[]; -/// Sets the number of consecutive failed PINs before blocking interaction. -/// -/// # Invariant -/// -/// - CTAP2.0: Maximum PIN retries must be 8. -/// - CTAP2.1: Maximum PIN retries must be 8 at most. -/// -/// The fail retry counter is reset after entering the correct PIN. -pub const MAX_PIN_RETRIES: u8 = 8; - /// Enables or disables basic attestation for FIDO2. /// /// # Invariant @@ -127,18 +77,6 @@ pub const MAX_PIN_RETRIES: u8 = 8; /// https://www.w3.org/TR/webauthn/#attestation pub const USE_BATCH_ATTESTATION: bool = false; -/// Enables or disables signature counters. -/// -/// The signature counter is currently implemented as a global counter. -/// The specification strongly suggests to have per-credential counters. -/// Implementing those means you can't have an infinite amount of server-side -/// credentials anymore. Also, since counters need frequent writes on the -/// persistent storage, we might need a flash friendly implementation. This -/// solution is a compromise to be compatible with U2F and not wasting storage. -/// -/// https://www.w3.org/TR/webauthn/#signature-counter -pub const USE_SIGNATURE_COUNTER: bool = true; - // ########################################################################### // Constants for performance optimization or adapting to different hardware. // @@ -171,21 +109,6 @@ pub const MAX_CREDENTIAL_COUNT_IN_LIST: Option = None; /// - The array must fit into the shards reserved in storage/key.rs. pub const MAX_LARGE_BLOB_ARRAY_SIZE: usize = 2048; -/// Limits the number of RP IDs that can change the minimum PIN length. -/// -/// # Invariant -/// -/// - If this value is 0, DEFAULT_MIN_PIN_LENGTH_RP_IDS must be non-empty. -/// -/// You can use this constant to have an upper limit in storage requirements. -/// This might be useful if you want to more reliably predict the remaining -/// storage. Stored string can still be of arbitrary length though, until RP ID -/// truncation is implemented. -/// Outside of memory considerations, you can set this value to 0 if only RP IDs -/// in DEFAULT_MIN_PIN_LENGTH_RP_IDS should be allowed to change the minimum PIN -/// length. -pub const MAX_RP_IDS_LENGTH: usize = 8; - /// Sets the number of resident keys you can store. /// /// # Invariant @@ -217,22 +140,16 @@ mod test { // Two invariants are currently tested in different files: // - storage.rs: if MAX_LARGE_BLOB_ARRAY_SIZE fits the shards // - storage/key.rs: if MAX_SUPPORTED_RESIDENT_KEYS fits CREDENTIALS - assert!(DEFAULT_MIN_PIN_LENGTH >= 4); - assert!(DEFAULT_MIN_PIN_LENGTH <= 63); assert!(!USE_BATCH_ATTESTATION || ENTERPRISE_ATTESTATION_MODE.is_none()); if let Some(EnterpriseAttestationMode::VendorFacilitated) = ENTERPRISE_ATTESTATION_MODE { assert!(!ENTERPRISE_RP_ID_LIST.is_empty()); } else { assert!(ENTERPRISE_RP_ID_LIST.is_empty()); } - assert!(MAX_PIN_RETRIES <= 8); assert!(MAX_CRED_BLOB_LENGTH >= 32); if let Some(count) = MAX_CREDENTIAL_COUNT_IN_LIST { assert!(count >= 1); } assert!(MAX_LARGE_BLOB_ARRAY_SIZE >= 1024); - if MAX_RP_IDS_LENGTH == 0 { - assert!(!DEFAULT_MIN_PIN_LENGTH_RP_IDS.is_empty()); - } } } diff --git a/src/ctap/mod.rs b/src/ctap/mod.rs index 7fd13e4..5a4fd87 100644 --- a/src/ctap/mod.rs +++ b/src/ctap/mod.rs @@ -45,8 +45,7 @@ use self::credential_management::process_credential_management; use self::crypto_wrapper::{aes256_cbc_decrypt, aes256_cbc_encrypt}; use self::customization::{ ENTERPRISE_ATTESTATION_MODE, ENTERPRISE_RP_ID_LIST, MAX_CREDENTIAL_COUNT_IN_LIST, - MAX_CRED_BLOB_LENGTH, MAX_LARGE_BLOB_ARRAY_SIZE, MAX_RP_IDS_LENGTH, USE_BATCH_ATTESTATION, - USE_SIGNATURE_COUNTER, + MAX_CRED_BLOB_LENGTH, MAX_LARGE_BLOB_ARRAY_SIZE, USE_BATCH_ATTESTATION, }; use self::data_formats::{ AuthenticatorTransport, CoseKey, CoseSignature, CredentialProtectionPolicy, @@ -422,7 +421,7 @@ impl CtapState { &mut self, env: &mut impl Env, ) -> Result<(), Ctap2StatusCode> { - if USE_SIGNATURE_COUNTER { + if env.customization().use_signature_counter() { let increment = env.rng().gen_uniform_u32x8()[0] % 8 + 1; storage::incr_global_signature_counter(env, increment)?; } @@ -1224,7 +1223,9 @@ impl CtapState { min_pin_length: storage::min_pin_length(env)?, firmware_version: None, max_cred_blob_length: Some(MAX_CRED_BLOB_LENGTH as u64), - max_rp_ids_for_set_min_pin_length: Some(MAX_RP_IDS_LENGTH as u64), + max_rp_ids_for_set_min_pin_length: Some( + env.customization().max_rp_ids_length() as u64 + ), certifications: None, remaining_discoverable_credentials: Some( storage::remaining_credentials(env)? as u64 @@ -1397,7 +1398,7 @@ impl CtapState { let mut auth_data = vec![]; auth_data.extend(rp_id_hash); auth_data.push(flag_byte); - // The global counter is only increased if USE_SIGNATURE_COUNTER is true. + // The global counter is only increased if use_signature_counter() is true. // It uses a big-endian representation. let mut signature_counter = [0u8; 4]; BigEndian::write_u32( @@ -1531,7 +1532,7 @@ mod test { 0x0C => false, 0x0D => storage::min_pin_length(&mut env).unwrap() as u64, 0x0F => MAX_CRED_BLOB_LENGTH as u64, - 0x10 => MAX_RP_IDS_LENGTH as u64, + 0x10 => env.customization().max_rp_ids_length() as u64, 0x14 => storage::remaining_credentials(&mut env).unwrap() as u64, }; diff --git a/src/ctap/storage.rs b/src/ctap/storage.rs index 67fbda0..02d2a52 100644 --- a/src/ctap/storage.rs +++ b/src/ctap/storage.rs @@ -14,11 +14,9 @@ mod key; +use crate::api::customization::Customization; use crate::ctap::client_pin::PIN_AUTH_LENGTH; -use crate::ctap::customization::{ - DEFAULT_MIN_PIN_LENGTH, DEFAULT_MIN_PIN_LENGTH_RP_IDS, ENFORCE_ALWAYS_UV, - MAX_LARGE_BLOB_ARRAY_SIZE, MAX_PIN_RETRIES, MAX_RP_IDS_LENGTH, MAX_SUPPORTED_RESIDENT_KEYS, -}; +use crate::ctap::customization::{MAX_LARGE_BLOB_ARRAY_SIZE, MAX_SUPPORTED_RESIDENT_KEYS}; use crate::ctap::data_formats::{ extract_array, extract_text_string, CredentialProtectionPolicy, PublicKeyCredentialSource, PublicKeyCredentialUserEntity, @@ -360,7 +358,7 @@ pub fn set_pin( /// Returns the number of remaining PIN retries. pub fn pin_retries(env: &mut impl Env) -> Result { match env.store().find(key::PIN_RETRIES)? { - None => Ok(MAX_PIN_RETRIES), + None => Ok(env.customization().max_pin_retries()), Some(value) if value.len() == 1 => Ok(value[0]), _ => Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR), } @@ -384,7 +382,7 @@ pub fn reset_pin_retries(env: &mut impl Env) -> Result<(), Ctap2StatusCode> { /// Returns the minimum PIN length. pub fn min_pin_length(env: &mut impl Env) -> Result { match env.store().find(key::MIN_PIN_LENGTH)? { - None => Ok(DEFAULT_MIN_PIN_LENGTH), + None => Ok(env.customization().default_min_pin_length()), Some(value) if value.len() == 1 => Ok(value[0]), _ => Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR), } @@ -399,14 +397,7 @@ pub fn set_min_pin_length(env: &mut impl Env, min_pin_length: u8) -> Result<(), /// allowed. pub fn min_pin_length_rp_ids(env: &mut impl Env) -> Result, Ctap2StatusCode> { let rp_ids = env.store().find(key::MIN_PIN_LENGTH_RP_IDS)?.map_or_else( - || { - Some( - DEFAULT_MIN_PIN_LENGTH_RP_IDS - .iter() - .map(|&s| String::from(s)) - .collect(), - ) - }, + || Some(env.customization().default_min_pin_length_rp_ids()), |value| deserialize_min_pin_length_rp_ids(&value), ); debug_assert!(rp_ids.is_some()); @@ -419,13 +410,12 @@ pub fn set_min_pin_length_rp_ids( min_pin_length_rp_ids: Vec, ) -> Result<(), Ctap2StatusCode> { let mut min_pin_length_rp_ids = min_pin_length_rp_ids; - for &rp_id in DEFAULT_MIN_PIN_LENGTH_RP_IDS.iter() { - let rp_id = String::from(rp_id); + for rp_id in env.customization().default_min_pin_length_rp_ids() { if !min_pin_length_rp_ids.contains(&rp_id) { min_pin_length_rp_ids.push(rp_id); } } - if min_pin_length_rp_ids.len() > MAX_RP_IDS_LENGTH { + if min_pin_length_rp_ids.len() > env.customization().max_rp_ids_length() { return Err(Ctap2StatusCode::CTAP2_ERR_KEY_STORE_FULL); } Ok(env.store().insert( @@ -595,7 +585,7 @@ pub fn enable_enterprise_attestation(env: &mut impl Env) -> Result<(), Ctap2Stat /// Returns whether alwaysUv is enabled. pub fn has_always_uv(env: &mut impl Env) -> Result { - if ENFORCE_ALWAYS_UV { + if env.customization().enforce_always_uv() { return Ok(true); } match env.store().find(key::ALWAYS_UV)? { @@ -607,7 +597,7 @@ pub fn has_always_uv(env: &mut impl Env) -> Result { /// Enables alwaysUv, when disabled, and vice versa. pub fn toggle_always_uv(env: &mut impl Env) -> Result<(), Ctap2StatusCode> { - if ENFORCE_ALWAYS_UV { + if env.customization().enforce_always_uv() { return Err(Ctap2StatusCode::CTAP2_ERR_OPERATION_DENIED); } if has_always_uv(env)? { @@ -1060,10 +1050,13 @@ mod test { let mut env = TestEnv::new(); // The pin retries is initially at the maximum. - assert_eq!(pin_retries(&mut env), Ok(MAX_PIN_RETRIES)); + assert_eq!( + pin_retries(&mut env), + Ok(env.customization().max_pin_retries()) + ); // Decrementing the pin retries decrements the pin retries. - for retries in (0..MAX_PIN_RETRIES).rev() { + for retries in (0..env.customization().max_pin_retries()).rev() { decr_pin_retries(&mut env).unwrap(); assert_eq!(pin_retries(&mut env), Ok(retries)); } @@ -1074,7 +1067,10 @@ mod test { // Resetting the pin retries resets the pin retries. reset_pin_retries(&mut env).unwrap(); - assert_eq!(pin_retries(&mut env), Ok(MAX_PIN_RETRIES)); + assert_eq!( + pin_retries(&mut env), + Ok(env.customization().max_pin_retries()) + ); } #[test] @@ -1111,7 +1107,10 @@ mod test { let mut env = TestEnv::new(); // The minimum PIN length is initially at the default. - assert_eq!(min_pin_length(&mut env).unwrap(), DEFAULT_MIN_PIN_LENGTH); + assert_eq!( + min_pin_length(&mut env).unwrap(), + env.customization().default_min_pin_length() + ); // Changes by the setter are reflected by the getter.. let new_min_pin_length = 8; @@ -1126,14 +1125,13 @@ mod test { // The minimum PIN length RP IDs are initially at the default. assert_eq!( min_pin_length_rp_ids(&mut env).unwrap(), - DEFAULT_MIN_PIN_LENGTH_RP_IDS + env.customization().default_min_pin_length_rp_ids() ); // Changes by the setter are reflected by the getter. let mut rp_ids = vec![String::from("example.com")]; assert_eq!(set_min_pin_length_rp_ids(&mut env, rp_ids.clone()), Ok(())); - for &rp_id in DEFAULT_MIN_PIN_LENGTH_RP_IDS.iter() { - let rp_id = String::from(rp_id); + for rp_id in env.customization().default_min_pin_length_rp_ids() { if !rp_ids.contains(&rp_id) { rp_ids.push(rp_id); } @@ -1246,7 +1244,7 @@ mod test { fn test_always_uv() { let mut env = TestEnv::new(); - if ENFORCE_ALWAYS_UV { + if env.customization().enforce_always_uv() { assert!(has_always_uv(&mut env).unwrap()); assert_eq!( toggle_always_uv(&mut env), diff --git a/src/ctap/storage/key.rs b/src/ctap/storage/key.rs index 38a2a8b..7ee0b53 100644 --- a/src/ctap/storage/key.rs +++ b/src/ctap/storage/key.rs @@ -110,12 +110,12 @@ make_partition! { /// The minimum PIN length. /// - /// If the entry is absent, the minimum PIN length is `DEFAULT_MIN_PIN_LENGTH`. + /// If the entry is absent, the minimum PIN length is `Customization::default_min_pin_length()`. MIN_PIN_LENGTH = 2043; /// The number of PIN retries. /// - /// If the entry is absent, the number of PIN retries is `MAX_PIN_RETRIES`. + /// If the entry is absent, the number of PIN retries is `Customization::max_pin_retries()`. PIN_RETRIES = 2044; /// The PIN hash and length. diff --git a/src/env/test/customization.rs b/src/env/test/customization.rs new file mode 100644 index 0000000..0b7ee23 --- /dev/null +++ b/src/env/test/customization.rs @@ -0,0 +1,92 @@ +use crate::api::customization::{Customization, CustomizationImpl}; +use crate::ctap::data_formats::CredentialProtectionPolicy; +use alloc::string::String; +use alloc::vec::Vec; + +pub struct TestCustomization { + pub default_cred_protect: Option, + pub default_min_pin_length: u8, + pub default_min_pin_length_rp_ids: Vec, + pub enforce_always_uv: bool, + pub max_msg_size: usize, + pub max_pin_retries: u8, + pub use_signature_counter: bool, + pub max_rp_ids_length: usize, +} + +impl Customization for TestCustomization { + fn default_cred_protect(&self) -> Option { + self.default_cred_protect + } + + fn default_min_pin_length(&self) -> u8 { + self.default_min_pin_length + } + + fn default_min_pin_length_rp_ids(&self) -> Vec { + self.default_min_pin_length_rp_ids.clone() + } + + fn enforce_always_uv(&self) -> bool { + self.enforce_always_uv + } + + fn max_msg_size(&self) -> usize { + self.max_msg_size + } + + fn max_pin_retries(&self) -> u8 { + self.max_pin_retries + } + + fn use_signature_counter(&self) -> bool { + self.use_signature_counter + } + + fn max_rp_ids_length(&self) -> usize { + self.max_rp_ids_length + } +} + +impl From for TestCustomization { + fn from(c: CustomizationImpl) -> Self { + let CustomizationImpl { + default_cred_protect, + default_min_pin_length, + default_min_pin_length_rp_ids, + enforce_always_uv, + max_msg_size, + max_pin_retries, + use_signature_counter, + max_rp_ids_length, + } = c; + + let default_min_pin_length_rp_ids = default_min_pin_length_rp_ids + .iter() + .map(|s| String::from(*s)) + .collect::>(); + + Self { + default_cred_protect, + default_min_pin_length, + default_min_pin_length_rp_ids, + enforce_always_uv, + max_msg_size, + max_pin_retries, + use_signature_counter, + max_rp_ids_length, + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::api::customization::{is_valid, DEFAULT_CUSTOMIZATION}; + + #[test] + fn test_invariants() { + let customization = TestCustomization::from(DEFAULT_CUSTOMIZATION.clone()); + assert!(is_valid(&customization)); + } +} diff --git a/src/env/test/mod.rs b/src/env/test/mod.rs index b5f1459..0580b65 100644 --- a/src/env/test/mod.rs +++ b/src/env/test/mod.rs @@ -1,12 +1,14 @@ use self::upgrade_storage::BufferUpgradeStorage; -use crate::api::customization::{CustomizationImpl, DEFAULT_CUSTOMIZATION}; +use crate::api::customization::DEFAULT_CUSTOMIZATION; use crate::api::firmware_protection::FirmwareProtection; use crate::ctap::status_code::Ctap2StatusCode; use crate::ctap::Channel; use crate::env::{Env, UserPresence}; use crypto::rng256::ThreadRng256; +use customization::TestCustomization; use persistent_store::{BufferOptions, BufferStorage, Store}; +mod customization; mod upgrade_storage; pub struct TestEnv { @@ -14,7 +16,7 @@ pub struct TestEnv { user_presence: TestUserPresence, store: Store, upgrade_storage: Option, - customization: CustomizationImpl, + customization: TestCustomization, } pub struct TestUserPresence { @@ -53,7 +55,7 @@ impl TestEnv { let storage = new_storage(); let store = Store::new(storage).ok().unwrap(); let upgrade_storage = Some(BufferUpgradeStorage::new().unwrap()); - let customization = DEFAULT_CUSTOMIZATION.clone(); + let customization = DEFAULT_CUSTOMIZATION.into(); TestEnv { rng, user_presence, @@ -67,7 +69,7 @@ impl TestEnv { self.upgrade_storage = None; } - pub fn customization_mut(&mut self) -> &mut CustomizationImpl { + pub fn customization_mut(&mut self) -> &mut TestCustomization { &mut self.customization } } @@ -97,7 +99,7 @@ impl Env for TestEnv { type UpgradeStorage = BufferUpgradeStorage; type FirmwareProtection = Self; type Write = TestWrite; - type Customization = CustomizationImpl; + type Customization = TestCustomization; fn rng(&mut self) -> &mut Self::Rng { &mut self.rng