// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! This file contains all customizable constants. //! //! If you adapt them, make sure to run the tests before flashing the firmware. //! Our deploy script enforces the invariants. use crate::ctap::data_formats::{CredentialProtectionPolicy, EnterpriseAttestationMode}; use alloc::string::String; use alloc::vec::Vec; pub trait Customization { // ########################################################################### // Constants for adjusting privacy and protection levels. // ########################################################################### /// Changes the default level for the credProtect extension. /// /// You can change this value to one of the following for more privacy: /// - CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIdList /// - CredentialProtectionPolicy::UserVerificationRequired /// /// UserVerificationOptionalWithCredentialIdList /// Resident credentials are discoverable with /// - an allowList, /// - an excludeList, /// - user verification. /// /// UserVerificationRequired /// Resident credentials are discoverable with user verification only. /// /// 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; /// Allows usage of enterprise attestation. /// /// # Invariant /// /// - Enterprise and batch attestation can not both be active. /// - If the mode is VendorFacilitated, enterprise_attestation_mode() must be non-empty. /// /// For privacy reasons, it is disabled by default. You can choose between: /// - EnterpriseAttestationMode::VendorFacilitated /// - EnterpriseAttestationMode::PlatformManaged /// /// VendorFacilitated /// Enterprise attestation is restricted to enterprise_attestation_mode(). Add your /// enterprises domain, e.g. "example.com", to the list below. /// /// PlatformManaged /// All relying parties can request an enterprise attestation. The authenticator /// trusts the platform to filter requests. /// /// To enable the feature, send the subcommand enableEnterpriseAttestation in /// AuthenticatorConfig. An enterprise might want to customize the type of /// attestation that is used. OpenSK defaults to batch attestation. Configuring /// individual certificates then makes authenticators identifiable. /// /// OpenSK prevents activating batch and enterprise attestation together. The /// current implementation uses the same key material at the moment, and these /// two modes have conflicting privacy guarantees. /// If you implement your own enterprise attestation mechanism, and you want /// batch attestation at the same time, proceed carefully and remove the /// assertion. fn enterprise_attestation_mode(&self) -> Option; /// Lists relying party IDs that can perform enterprise attestation. /// /// # Invariant /// /// - If the mode is VendorFacilitated, enterprise_attestation_mode() must be non-empty. /// /// This list is only considered if enterprise attestation is used. #[cfg(feature = "std")] fn enterprise_rp_id_list(&self) -> Vec; /// Returns whether the rp_id is contained in enterprise_rp_id_list(). fn is_enterprise_rp_id(&self, rp_id: &str) -> bool; /// Maximum message size send for CTAP commands. /// /// The maximum value is 7609, as HID packets can not encode longer messages. /// 1024 is the default mentioned in the authenticatorLargeBlobs commands. /// Larger values are preferred, as that allows more parameters in commands. /// 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 basic attestation for FIDO2. /// /// # Invariant /// /// - Enterprise and batch attestation can not both be active (see above). /// /// The basic attestation uses the signing key configured with a vendor command /// as a batch key. If you turn batch attestation on, be aware that it is your /// responsibility to safely generate and store the key material. Also, the /// batches must have size of at least 100k authenticators before using new key /// material. /// U2F is unaffected by this setting. /// /// https://www.w3.org/TR/webauthn/#attestation fn use_batch_attestation(&self) -> bool; /// 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. // ########################################################################### /// Sets the maximum blob size stored with the credBlob extension. /// /// # Invariant /// /// - The length must be at least 32. fn max_cred_blob_length(&self) -> usize; /// Limits the number of considered entries in credential lists. /// /// # Invariant /// /// - This value, if present, must be at least 1 (more is preferred). /// /// Depending on your memory, you can use Some(n) to limit request sizes in /// MakeCredential and GetAssertion. This affects allowList and excludeList. fn max_credential_count_in_list(&self) -> Option; /// Limits the size of largeBlobs the authenticator stores. /// /// # Invariant /// /// - The allowed size must be at least 1024. /// - The array must fit into the shards reserved in storage/key.rs. fn max_large_blob_array_size(&self) -> usize; /// 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; /// Sets the number of resident keys you can store. /// /// # Invariant /// /// - The storage key CREDENTIALS must fit at least this number of credentials. /// /// Limiting the number of resident keys permits to ensure a minimum number of /// counter increments. /// Let: /// - P the number of pages (NUM_PAGES in the board definition) /// - K the maximum number of resident keys (max_supported_resident_keys()) /// - S the maximum size of a resident key (about 500) /// - C the number of erase cycles (10000) /// - I the minimum number of counter increments /// /// We have: I = (P * 4084 - 5107 - K * S) / 8 * C /// /// With P=20 and K=150, we have I=2M which is enough for 500 increments per day /// for 10 years. fn max_supported_resident_keys(&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 enterprise_attestation_mode: Option, pub enterprise_rp_id_list: &'static [&'static str], pub max_msg_size: usize, pub max_pin_retries: u8, pub use_batch_attestation: bool, pub use_signature_counter: bool, pub max_cred_blob_length: usize, pub max_credential_count_in_list: Option, pub max_large_blob_array_size: usize, pub max_rp_ids_length: usize, pub max_supported_resident_keys: usize, } pub const DEFAULT_CUSTOMIZATION: CustomizationImpl = CustomizationImpl { default_cred_protect: None, default_min_pin_length: 4, default_min_pin_length_rp_ids: &[], enforce_always_uv: false, enterprise_attestation_mode: None, enterprise_rp_id_list: &[], max_msg_size: 7609, max_pin_retries: 8, use_batch_attestation: false, use_signature_counter: true, max_cred_blob_length: 32, max_credential_count_in_list: None, max_large_blob_array_size: 2048, max_rp_ids_length: 8, max_supported_resident_keys: 150, }; impl Customization for CustomizationImpl { 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 .iter() .map(|s| String::from(*s)) .collect() } fn enforce_always_uv(&self) -> bool { self.enforce_always_uv } fn enterprise_attestation_mode(&self) -> Option { self.enterprise_attestation_mode } #[cfg(feature = "std")] fn enterprise_rp_id_list(&self) -> Vec { self.enterprise_rp_id_list .iter() .map(|s| String::from(*s)) .collect() } fn is_enterprise_rp_id(&self, rp_id: &str) -> bool { self.enterprise_rp_id_list.contains(&rp_id) } fn max_msg_size(&self) -> usize { self.max_msg_size } fn max_pin_retries(&self) -> u8 { self.max_pin_retries } fn use_batch_attestation(&self) -> bool { self.use_batch_attestation } fn use_signature_counter(&self) -> bool { self.use_signature_counter } fn max_cred_blob_length(&self) -> usize { self.max_cred_blob_length } fn max_credential_count_in_list(&self) -> Option { self.max_credential_count_in_list } fn max_large_blob_array_size(&self) -> usize { self.max_large_blob_array_size } fn max_rp_ids_length(&self) -> usize { self.max_rp_ids_length } fn max_supported_resident_keys(&self) -> usize { self.max_supported_resident_keys } } #[cfg(feature = "std")] pub fn is_valid(customization: &impl Customization) -> bool { // 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 // Max message size must be between 1024 and 7609. 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; } // OpenSK prevents activating batch and enterprise attestation together. The // current implementation uses the same key material at the moment, and these // two modes have conflicting privacy guarantees. if customization.use_batch_attestation() && customization.enterprise_attestation_mode().is_some() { return false; } // enterprise_rp_id_list() should be non-empty in vendor facilitated mode. if matches!( customization.enterprise_attestation_mode(), Some(EnterpriseAttestationMode::VendorFacilitated) ) && customization.enterprise_rp_id_list().is_empty() { return false; } // enterprise_rp_id_list() should be empty without an enterprise attestation mode. if customization.enterprise_attestation_mode().is_none() && !customization.enterprise_rp_id_list().is_empty() { return false; } // Max pin retries must be less or equal than 8. if customization.max_pin_retries() > 8 { return false; } // Max cred blob length should be at least 32. if customization.max_cred_blob_length() < 32 { return false; } // Max credential count in list should be positive if exists. if let Some(count) = customization.max_credential_count_in_list() { if count < 1 { return false; } } // Max large blob array size should not be less than 1024. if customization.max_large_blob_array_size() < 1024 { 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 } #[cfg(test)] mod test { use super::*; #[test] fn test_invariants() { assert!(is_valid(&DEFAULT_CUSTOMIZATION)); } }