From 2b6424360cf03675709c011746c90feaacc66eb4 Mon Sep 17 00:00:00 2001 From: hcyang <100930165+hcyang-google@users.noreply.github.com> Date: Mon, 25 Apr 2022 09:45:59 +0800 Subject: [PATCH] Move enterprise mode related customizations to new file (#463) * Move enterprise mode related customizations to new file * Fix cargo clippy error * Add is_enterpris_rp_id API to avoid cloning * Only expose enterprise_rp_id_list API in std --- src/api/customization.rs | 110 +++++++++++++++++++++++++++++++++- src/ctap/config_command.rs | 6 +- src/ctap/customization.rs | 66 -------------------- src/ctap/data_formats.rs | 2 +- src/ctap/mod.rs | 19 +++--- src/env/test/customization.rs | 32 +++++++++- 6 files changed, 153 insertions(+), 82 deletions(-) diff --git a/src/api/customization.rs b/src/api/customization.rs index 8f3319f..01de7b8 100644 --- a/src/api/customization.rs +++ b/src/api/customization.rs @@ -17,7 +17,7 @@ //! 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; +use crate::ctap::data_formats::{CredentialProtectionPolicy, EnterpriseAttestationMode}; use alloc::string::String; use alloc::vec::Vec; @@ -80,6 +80,52 @@ pub trait Customization { /// 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 the enterprise attestation mode is + /// VendorFacilitated. + #[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. @@ -99,6 +145,22 @@ pub trait Customization { /// 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. @@ -140,19 +202,25 @@ pub struct CustomizationImpl { 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_rp_ids_length: 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, - default_cred_protect: None, + 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_rp_ids_length: 8, }; @@ -177,6 +245,22 @@ impl Customization for CustomizationImpl { 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 } @@ -185,6 +269,10 @@ impl Customization for CustomizationImpl { self.max_pin_retries } + fn use_batch_attestation(&self) -> bool { + self.use_batch_attestation + } + fn use_signature_counter(&self) -> bool { self.use_signature_counter } @@ -206,6 +294,24 @@ pub fn is_valid(customization: &impl Customization) -> bool { 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, and empty otherwise. + if matches!( + customization.enterprise_attestation_mode(), + Some(EnterpriseAttestationMode::VendorFacilitated) + ) == 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; diff --git a/src/ctap/config_command.rs b/src/ctap/config_command.rs index c895faa..3103b21 100644 --- a/src/ctap/config_command.rs +++ b/src/ctap/config_command.rs @@ -14,10 +14,10 @@ use super::client_pin::{ClientPin, PinPermission}; use super::command::AuthenticatorConfigParameters; -use super::customization::ENTERPRISE_ATTESTATION_MODE; use super::data_formats::{ConfigSubCommand, ConfigSubCommandParams, SetMinPinLengthParams}; use super::response::ResponseData; use super::status_code::Ctap2StatusCode; +use crate::api::customization::Customization; use crate::ctap::storage; use crate::env::Env; use alloc::vec; @@ -26,7 +26,7 @@ use alloc::vec; fn process_enable_enterprise_attestation( env: &mut impl Env, ) -> Result { - if ENTERPRISE_ATTESTATION_MODE.is_some() { + if env.customization().enterprise_attestation_mode().is_some() { storage::enable_enterprise_attestation(env)?; Ok(ResponseData::AuthenticatorConfig) } else { @@ -144,7 +144,7 @@ mod test { }; let config_response = process_config(&mut env, &mut client_pin, config_params); - if ENTERPRISE_ATTESTATION_MODE.is_some() { + if env.customization().enterprise_attestation_mode().is_some() { assert_eq!(config_response, Ok(ResponseData::AuthenticatorConfig)); assert_eq!(storage::enterprise_attestation(&mut env), Ok(true)); } else { diff --git a/src/ctap/customization.rs b/src/ctap/customization.rs index d5976c9..4525521 100644 --- a/src/ctap/customization.rs +++ b/src/ctap/customization.rs @@ -17,66 +17,6 @@ //! 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::EnterpriseAttestationMode; - -/// Allows usage of enterprise attestation. -/// -/// # Invariant -/// -/// - Enterprise and batch attestation can not both be active. -/// - If the mode is VendorFacilitated, ENTERPRISE_RP_ID_LIST 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_RP_ID_LIST. 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. -pub const ENTERPRISE_ATTESTATION_MODE: Option = None; - -/// Lists relying party IDs that can perform enterprise attestation. -/// -/// # Invariant -/// -/// - If the mode is VendorFacilitated, ENTERPRISE_RP_ID_LIST must be non-empty. -/// -/// This list is only considered if the enterprise attestation mode is -/// VendorFacilitated. -pub const ENTERPRISE_RP_ID_LIST: &[&str] = &[]; - -/// 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 -pub const USE_BATCH_ATTESTATION: bool = false; - // ########################################################################### // Constants for performance optimization or adapting to different hardware. // @@ -140,12 +80,6 @@ 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!(!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_CRED_BLOB_LENGTH >= 32); if let Some(count) = MAX_CREDENTIAL_COUNT_IN_LIST { assert!(count >= 1); diff --git a/src/ctap/data_formats.rs b/src/ctap/data_formats.rs index ba7ef0c..9952e0a 100644 --- a/src/ctap/data_formats.rs +++ b/src/ctap/data_formats.rs @@ -1052,7 +1052,7 @@ impl From for cbor::Value { } /// The level of enterprise attestation allowed in MakeCredential. -#[derive(Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq)] pub enum EnterpriseAttestationMode { /// Enterprise attestation is restricted to a list of RP IDs. Add your /// enterprises domain, e.g. "example.com", to the list below. diff --git a/src/ctap/mod.rs b/src/ctap/mod.rs index 5a4fd87..69649b8 100644 --- a/src/ctap/mod.rs +++ b/src/ctap/mod.rs @@ -44,8 +44,7 @@ use self::config_command::process_config; 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, USE_BATCH_ATTESTATION, + MAX_CREDENTIAL_COUNT_IN_LIST, MAX_CRED_BLOB_LENGTH, MAX_LARGE_BLOB_ARRAY_SIZE, }; use self::data_formats::{ AuthenticatorTransport, CoseKey, CoseSignature, CredentialProtectionPolicy, @@ -684,8 +683,10 @@ impl CtapState { let rp_id = rp.rp_id; let ep_att = if let Some(enterprise_attestation) = enterprise_attestation { - let authenticator_mode = - ENTERPRISE_ATTESTATION_MODE.ok_or(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)?; + let authenticator_mode = env + .customization() + .enterprise_attestation_mode() + .ok_or(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)?; if !storage::enterprise_attestation(env)? { return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER); } @@ -696,7 +697,7 @@ impl CtapState { ( EnterpriseAttestationMode::PlatformManaged, EnterpriseAttestationMode::PlatformManaged, - ) => ENTERPRISE_RP_ID_LIST.contains(&rp_id.as_str()), + ) => env.customization().is_enterprise_rp_id(&rp_id), _ => true, } } else { @@ -859,7 +860,7 @@ impl CtapState { let mut signature_data = auth_data.clone(); signature_data.extend(client_data_hash); - let (signature, x5c) = if USE_BATCH_ATTESTATION || ep_att { + let (signature, x5c) = if env.customization().use_batch_attestation() || ep_att { let attestation_private_key = storage::attestation_private_key(env)? .ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?; let attestation_key = @@ -1180,7 +1181,7 @@ impl CtapState { versions.insert(0, String::from(U2F_VERSION_STRING)) } let mut options = vec![]; - if ENTERPRISE_ATTESTATION_MODE.is_some() { + if env.customization().enterprise_attestation_mode().is_some() { options.push((String::from("ep"), storage::enterprise_attestation(env)?)); } options.append(&mut vec![ @@ -1323,7 +1324,7 @@ impl CtapState { #[cfg(feature = "with_ctap1")] let need_certificate = true; #[cfg(not(feature = "with_ctap1"))] - let need_certificate = USE_BATCH_ATTESTATION; + let need_certificate = env.customization().use_batch_attestation(); if (need_certificate && !(response.pkey_programmed && response.cert_programmed)) || !env.firmware_protection().lock() @@ -1510,7 +1511,7 @@ mod test { ], 0x03 => storage::aaguid(&mut env).unwrap(), 0x04 => cbor_map_options! { - "ep" => ENTERPRISE_ATTESTATION_MODE.map(|_| false), + "ep" => env.customization().enterprise_attestation_mode().map(|_| false), "rk" => true, "up" => true, "alwaysUv" => false, diff --git a/src/env/test/customization.rs b/src/env/test/customization.rs index 0b7ee23..d63b107 100644 --- a/src/env/test/customization.rs +++ b/src/env/test/customization.rs @@ -1,5 +1,5 @@ use crate::api::customization::{Customization, CustomizationImpl}; -use crate::ctap::data_formats::CredentialProtectionPolicy; +use crate::ctap::data_formats::{CredentialProtectionPolicy, EnterpriseAttestationMode}; use alloc::string::String; use alloc::vec::Vec; @@ -8,8 +8,11 @@ pub struct TestCustomization { pub default_min_pin_length: u8, pub default_min_pin_length_rp_ids: Vec, pub enforce_always_uv: bool, + pub enterprise_attestation_mode: Option, + pub enterprise_rp_id_list: Vec, pub max_msg_size: usize, pub max_pin_retries: u8, + pub use_batch_attestation: bool, pub use_signature_counter: bool, pub max_rp_ids_length: usize, } @@ -31,6 +34,18 @@ impl Customization for TestCustomization { self.enforce_always_uv } + fn enterprise_attestation_mode(&self) -> Option { + self.enterprise_attestation_mode + } + + fn enterprise_rp_id_list(&self) -> Vec { + self.enterprise_rp_id_list.clone() + } + + fn is_enterprise_rp_id(&self, rp_id: &str) -> bool { + self.enterprise_rp_id_list.iter().any(|id| id == rp_id) + } + fn max_msg_size(&self) -> usize { self.max_msg_size } @@ -39,6 +54,10 @@ impl Customization for TestCustomization { self.max_pin_retries } + fn use_batch_attestation(&self) -> bool { + self.use_batch_attestation + } + fn use_signature_counter(&self) -> bool { self.use_signature_counter } @@ -55,8 +74,11 @@ impl From for TestCustomization { default_min_pin_length, default_min_pin_length_rp_ids, enforce_always_uv, + enterprise_attestation_mode, + enterprise_rp_id_list, max_msg_size, max_pin_retries, + use_batch_attestation, use_signature_counter, max_rp_ids_length, } = c; @@ -66,13 +88,21 @@ impl From for TestCustomization { .map(|s| String::from(*s)) .collect::>(); + let enterprise_rp_id_list = enterprise_rp_id_list + .iter() + .map(|s| String::from(*s)) + .collect::>(); + Self { default_cred_protect, default_min_pin_length, default_min_pin_length_rp_ids, enforce_always_uv, + enterprise_attestation_mode, + enterprise_rp_id_list, max_msg_size, max_pin_retries, + use_batch_attestation, use_signature_counter, max_rp_ids_length, }