From 44b7c3cdc1e4926531f19332be4b5512ce6895ba Mon Sep 17 00:00:00 2001 From: Fabian Kaczmarczyck Date: Thu, 4 Feb 2021 21:26:00 +0100 Subject: [PATCH 1/6] dummy implementation for enterprise attestation --- src/ctap/command.rs | 8 +++--- src/ctap/config_command.rs | 45 ++++++++++++++++++++++++++++- src/ctap/data_formats.rs | 34 ++++++++++++++++++++++ src/ctap/mod.rs | 58 +++++++++++++++++++++++++++++++++----- src/ctap/response.rs | 5 ++++ src/ctap/storage.rs | 29 +++++++++++++++++++ src/ctap/storage/key.rs | 5 +++- 7 files changed, 171 insertions(+), 13 deletions(-) diff --git a/src/ctap/command.rs b/src/ctap/command.rs index a76254a..eb16a1f 100644 --- a/src/ctap/command.rs +++ b/src/ctap/command.rs @@ -161,7 +161,7 @@ pub struct AuthenticatorMakeCredentialParameters { pub options: MakeCredentialOptions, pub pin_uv_auth_param: Option>, pub pin_uv_auth_protocol: Option, - pub enterprise_attestation: Option, + pub enterprise_attestation: Option, } impl TryFrom for AuthenticatorMakeCredentialParameters { @@ -219,7 +219,7 @@ impl TryFrom for AuthenticatorMakeCredentialParameters { let pin_uv_auth_param = pin_uv_auth_param.map(extract_byte_string).transpose()?; let pin_uv_auth_protocol = pin_uv_auth_protocol.map(extract_unsigned).transpose()?; - let enterprise_attestation = enterprise_attestation.map(extract_bool).transpose()?; + let enterprise_attestation = enterprise_attestation.map(extract_unsigned).transpose()?; Ok(AuthenticatorMakeCredentialParameters { client_data_hash, @@ -601,7 +601,7 @@ mod test { 0x05 => cbor_array![], 0x08 => vec![0x12, 0x34], 0x09 => 1, - 0x0A => true, + 0x0A => 2, }; let returned_make_credential_parameters = AuthenticatorMakeCredentialParameters::try_from(cbor_value).unwrap(); @@ -635,7 +635,7 @@ mod test { options, pin_uv_auth_param: Some(vec![0x12, 0x34]), pin_uv_auth_protocol: Some(1), - enterprise_attestation: Some(true), + enterprise_attestation: Some(2), }; assert_eq!( diff --git a/src/ctap/config_command.rs b/src/ctap/config_command.rs index 5e4daf3..351ac1e 100644 --- a/src/ctap/config_command.rs +++ b/src/ctap/config_command.rs @@ -12,15 +12,27 @@ // See the License for the specific language governing permissions and // limitations under the License. -use super::check_pin_uv_auth_protocol; use super::command::AuthenticatorConfigParameters; use super::data_formats::{ConfigSubCommand, ConfigSubCommandParams, SetMinPinLengthParams}; use super::pin_protocol_v1::PinProtocolV1; use super::response::ResponseData; use super::status_code::Ctap2StatusCode; use super::storage::PersistentStore; +use super::{check_pin_uv_auth_protocol, ENTERPRISE_ATTESTATION_MODE}; use alloc::vec; +/// Processes the subcommand enableEnterpriseAttestation for AuthenticatorConfig. +fn process_enable_enterprise_attestation( + persistent_store: &mut PersistentStore, +) -> Result { + if ENTERPRISE_ATTESTATION_MODE.is_some() { + persistent_store.enable_enterprise_attestation()?; + Ok(ResponseData::AuthenticatorConfig) + } else { + Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER) + } +} + /// Processes the subcommand setMinPINLength for AuthenticatorConfig. fn process_set_min_pin_length( persistent_store: &mut PersistentStore, @@ -85,6 +97,9 @@ pub fn process_config( } match sub_command { + ConfigSubCommand::EnableEnterpriseAttestation => { + process_enable_enterprise_attestation(persistent_store) + } ConfigSubCommand::SetMinPinLength => { if let Some(ConfigSubCommandParams::SetMinPinLength(params)) = sub_command_params { process_set_min_pin_length(persistent_store, params) @@ -101,6 +116,34 @@ mod test { use super::*; use crypto::rng256::ThreadRng256; + #[test] + fn test_process_enable_enterprise_attestation() { + let mut rng = ThreadRng256 {}; + let mut persistent_store = PersistentStore::new(&mut rng); + let key_agreement_key = crypto::ecdh::SecKey::gensk(&mut rng); + let pin_uv_auth_token = [0x55; 32]; + let mut pin_protocol_v1 = PinProtocolV1::new_test(key_agreement_key, pin_uv_auth_token); + + let config_params = AuthenticatorConfigParameters { + sub_command: ConfigSubCommand::EnableEnterpriseAttestation, + sub_command_params: None, + pin_uv_auth_param: None, + pin_uv_auth_protocol: None, + }; + let config_response = + process_config(&mut persistent_store, &mut pin_protocol_v1, config_params); + + if ENTERPRISE_ATTESTATION_MODE.is_some() { + assert_eq!(config_response, Ok(ResponseData::AuthenticatorConfig)); + assert_eq!(persistent_store.enterprise_attestation(), Ok(true)); + } else { + assert_eq!( + config_response, + Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER) + ); + } + } + fn create_min_pin_config_params( min_pin_length: u8, min_pin_length_rp_ids: Option>, diff --git a/src/ctap/data_formats.rs b/src/ctap/data_formats.rs index 1992469..9f4b68c 100644 --- a/src/ctap/data_formats.rs +++ b/src/ctap/data_formats.rs @@ -939,6 +939,24 @@ impl From for cbor::Value { } } +#[derive(Debug, PartialEq)] +pub enum EnterpriseAttestationMode { + VendorFacilitated = 0x01, + PlatformManaged = 0x02, +} + +impl TryFrom for EnterpriseAttestationMode { + type Error = Ctap2StatusCode; + + fn try_from(value: u64) -> Result { + match value { + 1 => Ok(EnterpriseAttestationMode::VendorFacilitated), + 2 => Ok(EnterpriseAttestationMode::PlatformManaged), + _ => Err(Ctap2StatusCode::CTAP2_ERR_INVALID_OPTION), + } + } +} + #[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(test, derive(IntoEnumIterator))] pub enum CredentialManagementSubCommand { @@ -1795,6 +1813,22 @@ mod test { assert_eq!(cbor::Value::from(config_sub_command_params), cbor_params); } + #[test] + fn test_from_enterprise_attestation_mode() { + assert_eq!( + EnterpriseAttestationMode::try_from(1), + Ok(EnterpriseAttestationMode::VendorFacilitated), + ); + assert_eq!( + EnterpriseAttestationMode::try_from(2), + Ok(EnterpriseAttestationMode::PlatformManaged), + ); + assert_eq!( + EnterpriseAttestationMode::try_from(3), + Err(Ctap2StatusCode::CTAP2_ERR_INVALID_OPTION), + ); + } + #[test] fn test_from_into_cred_management_sub_command() { let cbor_sub_command: cbor::Value = cbor_int!(0x01); diff --git a/src/ctap/mod.rs b/src/ctap/mod.rs index ab66177..eeb43bc 100644 --- a/src/ctap/mod.rs +++ b/src/ctap/mod.rs @@ -36,10 +36,10 @@ use self::command::{ use self::config_command::process_config; use self::credential_management::process_credential_management; use self::data_formats::{ - AuthenticatorTransport, CoseKey, CredentialProtectionPolicy, GetAssertionExtensions, - PackedAttestationStatement, PublicKeyCredentialDescriptor, PublicKeyCredentialParameter, - PublicKeyCredentialSource, PublicKeyCredentialType, PublicKeyCredentialUserEntity, - SignatureAlgorithm, + AuthenticatorTransport, CoseKey, CredentialProtectionPolicy, EnterpriseAttestationMode, + GetAssertionExtensions, PackedAttestationStatement, PublicKeyCredentialDescriptor, + PublicKeyCredentialParameter, PublicKeyCredentialSource, PublicKeyCredentialType, + PublicKeyCredentialUserEntity, SignatureAlgorithm, }; use self::hid::ChannelID; use self::large_blobs::{LargeBlobs, MAX_MSG_SIZE}; @@ -61,6 +61,7 @@ use alloc::vec::Vec; use arrayref::array_ref; use byteorder::{BigEndian, ByteOrder}; use cbor::cbor_map_options; +use core::convert::TryFrom; #[cfg(feature = "debug_ctap")] use core::fmt::Write; use crypto::cbc::{cbc_decrypt, cbc_encrypt}; @@ -86,6 +87,18 @@ const USE_BATCH_ATTESTATION: bool = false; // solution is a compromise to be compatible with U2F and not wasting storage. const USE_SIGNATURE_COUNTER: bool = true; pub const INITIAL_SIGNATURE_COUNTER: u32 = 1; +// This flag allows usage of enterprise attestation. For privacy reasons, it is +// disabled by default. You can choose between +// - EnterpriseAttestationMode::VendorFacilitated, +// - EnterpriseAttestationMode::PlatformManaged. +// For VendorFacilitated, choose an appriopriate ENTERPRISE_RP_ID_LIST. +// 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. Do NOT set +// USE_BATCH_ATTESTATION to true at the same time in this case! +pub const ENTERPRISE_ATTESTATION_MODE: Option = None; +const ENTERPRISE_RP_ID_LIST: Vec = Vec::new(); // Our credential ID consists of // - 16 byte initialization vector for AES-256, // - 32 byte ECDSA private key for the credential, @@ -562,7 +575,7 @@ where options, pin_uv_auth_param, pin_uv_auth_protocol, - enterprise_attestation: _, + enterprise_attestation, } = make_credential_params; self.pin_uv_auth_precheck(&pin_uv_auth_param, pin_uv_auth_protocol, cid)?; @@ -572,6 +585,26 @@ where } 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)?; + if !self.persistent_store.enterprise_attestation()? { + return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER); + } + match ( + EnterpriseAttestationMode::try_from(enterprise_attestation)?, + authenticator_mode, + ) { + ( + EnterpriseAttestationMode::PlatformManaged, + EnterpriseAttestationMode::PlatformManaged, + ) => ENTERPRISE_RP_ID_LIST.contains(&rp_id), + _ => true, + } + } else { + false + }; + let mut cred_protect_policy = extensions.cred_protect; if cred_protect_policy.unwrap_or(CredentialProtectionPolicy::UserVerificationOptional) < DEFAULT_CRED_PROTECT.unwrap_or(CredentialProtectionPolicy::UserVerificationOptional) @@ -723,7 +756,7 @@ where let mut signature_data = auth_data.clone(); signature_data.extend(client_data_hash); - let (signature, x5c) = if USE_BATCH_ATTESTATION { + let (signature, x5c) = if USE_BATCH_ATTESTATION || ep_att { let attestation_private_key = self .persistent_store .attestation_private_key()? @@ -750,11 +783,13 @@ where x5c, ecdaa_key_id: None, }; + let ep_att = if ep_att { Some(true) } else { None }; Ok(ResponseData::AuthenticatorMakeCredential( AuthenticatorMakeCredentialResponse { fmt: String::from("packed"), auth_data, att_stmt: attestation_statement, + ep_att, large_blob_key, }, )) @@ -1026,6 +1061,12 @@ where options_map.insert(String::from("up"), true); options_map.insert(String::from("pinUvAuthToken"), true); options_map.insert(String::from("largeBlobs"), true); + if ENTERPRISE_ATTESTATION_MODE.is_some() { + options_map.insert( + String::from("ep"), + self.persistent_store.enterprise_attestation()?, + ); + } options_map.insert(String::from("authnrCfg"), true); options_map.insert(String::from("credMgmt"), true); options_map.insert(String::from("setMinPINLength"), true); @@ -1227,6 +1268,7 @@ mod test { fmt, auth_data, att_stmt, + ep_att, large_blob_key, } = make_credential_response; // The expected response is split to only assert the non-random parts. @@ -1247,6 +1289,7 @@ mod test { &auth_data[auth_data.len() - expected_extension_cbor.len()..auth_data.len()], expected_extension_cbor ); + assert!(ep_att.is_none()); assert_eq!(att_stmt.alg, SignatureAlgorithm::ES256 as i64); assert_eq!(large_blob_key, None); } @@ -1276,12 +1319,13 @@ mod test { String::from("largeBlobKey"), ]], 0x03 => ctap_state.persistent_store.aaguid().unwrap(), - 0x04 => cbor_map! { + 0x04 => cbor_map_options! { "rk" => true, "clientPin" => false, "up" => true, "pinUvAuthToken" => true, "largeBlobs" => true, + "ep" => ENTERPRISE_ATTESTATION_MODE.map(|_| false), "authnrCfg" => true, "credMgmt" => true, "setMinPINLength" => true, diff --git a/src/ctap/response.rs b/src/ctap/response.rs index 093d4c9..b6b3d25 100644 --- a/src/ctap/response.rs +++ b/src/ctap/response.rs @@ -61,6 +61,7 @@ pub struct AuthenticatorMakeCredentialResponse { pub fmt: String, pub auth_data: Vec, pub att_stmt: PackedAttestationStatement, + pub ep_att: Option, pub large_blob_key: Option>, } @@ -70,6 +71,7 @@ impl From for cbor::Value { fmt, auth_data, att_stmt, + ep_att, large_blob_key, } = make_credential_response; @@ -77,6 +79,7 @@ impl From for cbor::Value { 0x01 => fmt, 0x02 => auth_data, 0x03 => att_stmt, + 0x04 => ep_att, 0x05 => large_blob_key, } } @@ -320,6 +323,7 @@ mod test { fmt: "packed".to_string(), auth_data: vec![0xAD], att_stmt, + ep_att: Some(true), large_blob_key: Some(vec![0x1B]), }; let response_cbor: Option = @@ -328,6 +332,7 @@ mod test { 0x01 => "packed", 0x02 => vec![0xAD], 0x03 => cbor_packed_attestation_statement, + 0x04 => true, 0x05 => vec![0x1B], }; assert_eq!(response_cbor, Some(expected_cbor)); diff --git a/src/ctap/storage.rs b/src/ctap/storage.rs index b982922..c38146a 100644 --- a/src/ctap/storage.rs +++ b/src/ctap/storage.rs @@ -610,6 +610,23 @@ impl PersistentStore { pub fn force_pin_change(&mut self) -> Result<(), Ctap2StatusCode> { Ok(self.store.insert(key::FORCE_PIN_CHANGE, &[])?) } + + /// Returns whether enterprise attestation is enabled. + pub fn enterprise_attestation(&self) -> Result { + match self.store.find(key::ENTERPRISE_ATTESTATION)? { + None => Ok(false), + Some(value) if value.is_empty() => Ok(true), + _ => Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR), + } + } + + /// Marks enterprise attestation as enabled. + pub fn enable_enterprise_attestation(&mut self) -> Result<(), Ctap2StatusCode> { + if !self.enterprise_attestation()? { + self.store.insert(key::ENTERPRISE_ATTESTATION, &[])?; + } + Ok(()) + } } impl From for Ctap2StatusCode { @@ -1308,6 +1325,18 @@ mod test { assert!(!persistent_store.has_force_pin_change().unwrap()); } + #[test] + fn test_enterprise_attestation() { + let mut rng = ThreadRng256 {}; + let mut persistent_store = PersistentStore::new(&mut rng); + + assert!(!persistent_store.enterprise_attestation().unwrap()); + assert_eq!(persistent_store.enable_enterprise_attestation(), Ok(())); + assert!(persistent_store.enterprise_attestation().unwrap()); + persistent_store.reset(&mut rng).unwrap(); + assert!(!persistent_store.enterprise_attestation().unwrap()); + } + #[test] fn test_serialize_deserialize_credential() { let mut rng = ThreadRng256 {}; diff --git a/src/ctap/storage/key.rs b/src/ctap/storage/key.rs index 2093685..dd9f67e 100644 --- a/src/ctap/storage/key.rs +++ b/src/ctap/storage/key.rs @@ -93,7 +93,10 @@ make_partition! { /// The stored large blob can be too big for one key, so it has to be sharded. LARGE_BLOB_SHARDS = 2000..2004; - /// If this entry exists and equals 1, the PIN needs to be changed. + /// If this entry exists and is empty, enterprise attestation is enabled. + ENTERPRISE_ATTESTATION = 2039; + + /// If this entry exists and is empty, the PIN needs to be changed. FORCE_PIN_CHANGE = 2040; /// The secret of the CredRandom feature. From 53e05913634fe93bfb9887b2eb354fa56baeba86 Mon Sep 17 00:00:00 2001 From: Fabian Kaczmarczyck Date: Thu, 4 Feb 2021 21:33:01 +0100 Subject: [PATCH 2/6] adds some documenation for enterprise attestation --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index da46d7f..92ddac0 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,9 @@ a few things you can personalize: length. 1. Increase the `MAX_CRED_BLOB_LENGTH` in `ctap/mod.rs`, if you expect blobs bigger than the default value. +1. Implement enterprise attestation. This can be as easy as setting + ENTERPRISE_ATTESTATION_MODE in `ctap/mod.rs`. If you want to use a different + attestation type than batch attestation, you have to implement it first. ### 3D printed enclosure From 49cccfd270aa7fe0bfdca13ac9959d580e1ec8db Mon Sep 17 00:00:00 2001 From: Fabian Kaczmarczyck Date: Fri, 5 Feb 2021 11:23:12 +0100 Subject: [PATCH 3/6] correct const arrays of strings --- src/ctap/data_formats.rs | 4 ++++ src/ctap/mod.rs | 4 ++-- src/ctap/storage.rs | 25 ++++++++++++++++--------- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/ctap/data_formats.rs b/src/ctap/data_formats.rs index 9f4b68c..04e9a36 100644 --- a/src/ctap/data_formats.rs +++ b/src/ctap/data_formats.rs @@ -1815,6 +1815,10 @@ mod test { #[test] fn test_from_enterprise_attestation_mode() { + assert_eq!( + EnterpriseAttestationMode::try_from(0), + Err(Ctap2StatusCode::CTAP2_ERR_INVALID_OPTION), + ); assert_eq!( EnterpriseAttestationMode::try_from(1), Ok(EnterpriseAttestationMode::VendorFacilitated), diff --git a/src/ctap/mod.rs b/src/ctap/mod.rs index eeb43bc..b9a88b4 100644 --- a/src/ctap/mod.rs +++ b/src/ctap/mod.rs @@ -98,7 +98,7 @@ pub const INITIAL_SIGNATURE_COUNTER: u32 = 1; // individual certificates then makes authenticators identifiable. Do NOT set // USE_BATCH_ATTESTATION to true at the same time in this case! pub const ENTERPRISE_ATTESTATION_MODE: Option = None; -const ENTERPRISE_RP_ID_LIST: Vec = Vec::new(); +const ENTERPRISE_RP_ID_LIST: &[&str] = &[]; // Our credential ID consists of // - 16 byte initialization vector for AES-256, // - 32 byte ECDSA private key for the credential, @@ -598,7 +598,7 @@ where ( EnterpriseAttestationMode::PlatformManaged, EnterpriseAttestationMode::PlatformManaged, - ) => ENTERPRISE_RP_ID_LIST.contains(&rp_id), + ) => ENTERPRISE_RP_ID_LIST.contains(&rp_id.as_str()), _ => true, } } else { diff --git a/src/ctap/storage.rs b/src/ctap/storage.rs index c38146a..777f71f 100644 --- a/src/ctap/storage.rs +++ b/src/ctap/storage.rs @@ -56,7 +56,7 @@ const MAX_SUPPORTED_RESIDENT_KEYS: usize = 150; const MAX_PIN_RETRIES: u8 = 8; const DEFAULT_MIN_PIN_LENGTH: u8 = 4; -const DEFAULT_MIN_PIN_LENGTH_RP_IDS: Vec = Vec::new(); +const DEFAULT_MIN_PIN_LENGTH_RP_IDS: &[&str] = &[]; // This constant is an attempt to limit storage requirements. If you don't set it to 0, // the stored strings can still be unbounded, but that is true for all RP IDs. pub const MAX_RP_IDS_LENGTH: usize = 8; @@ -439,12 +439,17 @@ impl PersistentStore { /// Returns the list of RP IDs that are used to check if reading the minimum PIN length is /// allowed. pub fn min_pin_length_rp_ids(&self) -> Result, Ctap2StatusCode> { - let rp_ids = self - .store - .find(key::MIN_PIN_LENGTH_RP_IDS)? - .map_or(Some(DEFAULT_MIN_PIN_LENGTH_RP_IDS), |value| { - deserialize_min_pin_length_rp_ids(&value) - }); + let rp_ids = self.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(), + ) + }, + |value| deserialize_min_pin_length_rp_ids(&value), + ); debug_assert!(rp_ids.is_some()); Ok(rp_ids.unwrap_or_default()) } @@ -455,7 +460,8 @@ impl PersistentStore { 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 { + for rp_id in DEFAULT_MIN_PIN_LENGTH_RP_IDS.iter() { + let rp_id = String::from(*rp_id); if !min_pin_length_rp_ids.contains(&rp_id) { min_pin_length_rp_ids.push(rp_id); } @@ -1203,7 +1209,8 @@ mod test { persistent_store.set_min_pin_length_rp_ids(rp_ids.clone()), Ok(()) ); - for rp_id in DEFAULT_MIN_PIN_LENGTH_RP_IDS { + for rp_id in DEFAULT_MIN_PIN_LENGTH_RP_IDS.iter() { + let rp_id = rp_id.to_string().to_string(); if !rp_ids.contains(&rp_id) { rp_ids.push(rp_id); } From 502006e29ead0e39425e4707b3b323a950c38c7f Mon Sep 17 00:00:00 2001 From: Fabian Kaczmarczyck Date: Fri, 5 Feb 2021 11:57:47 +0100 Subject: [PATCH 4/6] fix string conversion style --- src/ctap/storage.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ctap/storage.rs b/src/ctap/storage.rs index 777f71f..f2f8220 100644 --- a/src/ctap/storage.rs +++ b/src/ctap/storage.rs @@ -460,8 +460,8 @@ impl PersistentStore { 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 DEFAULT_MIN_PIN_LENGTH_RP_IDS.iter() { + let rp_id = String::from(rp_id); if !min_pin_length_rp_ids.contains(&rp_id) { min_pin_length_rp_ids.push(rp_id); } @@ -1209,8 +1209,8 @@ mod test { persistent_store.set_min_pin_length_rp_ids(rp_ids.clone()), Ok(()) ); - for rp_id in DEFAULT_MIN_PIN_LENGTH_RP_IDS.iter() { - let rp_id = rp_id.to_string().to_string(); + for &rp_id in DEFAULT_MIN_PIN_LENGTH_RP_IDS.iter() { + let rp_id = String::from(rp_id); if !rp_ids.contains(&rp_id) { rp_ids.push(rp_id); } From e941073a3157520dcf653679a824f45c08d17519 Mon Sep 17 00:00:00 2001 From: Fabian Kaczmarczyck Date: Mon, 8 Feb 2021 13:10:18 +0100 Subject: [PATCH 5/6] new test for attestation configuration --- src/ctap/mod.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/ctap/mod.rs b/src/ctap/mod.rs index e89b15b..da6ba72 100644 --- a/src/ctap/mod.rs +++ b/src/ctap/mod.rs @@ -2764,4 +2764,19 @@ mod test { )) ); } + + #[test] + #[allow(clippy::assertions_on_constants)] + /// Make sure that privacy guarantees are uphold. + /// + /// The current enterprise attestation implementation reuses batch + /// attestation. Enterprise attestation would imply a batch size of 1, but + /// batch attestation needs a batch size of at least 100k. To prevent + /// accidential misconfiguration, this test allows only one of the constants + /// to be set. If you implement your own enterprise attestation mechanism, + /// and you want batch attestation at the same time, feel free to proceed + /// carefully and remove this test. + fn check_attestation_privacy() { + assert!(!USE_BATCH_ATTESTATION || ENTERPRISE_ATTESTATION_MODE.is_none()); + } } From 88a3c0fc803ce2444226c62eb43d7f309a3800f4 Mon Sep 17 00:00:00 2001 From: Fabian Kaczmarczyck Date: Mon, 8 Feb 2021 16:30:14 +0100 Subject: [PATCH 6/6] assert correct const usage in code --- src/ctap/mod.rs | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/src/ctap/mod.rs b/src/ctap/mod.rs index da6ba72..f2c3ea6 100644 --- a/src/ctap/mod.rs +++ b/src/ctap/mod.rs @@ -75,9 +75,11 @@ use libtock_drivers::crp; use libtock_drivers::timer::{ClockValue, Duration}; // This flag enables or disables basic attestation for FIDO2. U2F is unaffected by -// this setting. The basic attestation uses the signing key from key_material.rs -// as a batch key. Turn it on if you want attestation. In this case, be aware that -// it is your responsibility to generate your own key material and keep it secret. +// this setting. 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. const USE_BATCH_ATTESTATION: bool = false; // The signature counter is currently implemented as a global counter, if you set // this flag to true. The spec strongly suggests to have per-credential-counters, @@ -96,7 +98,10 @@ pub const INITIAL_SIGNATURE_COUNTER: u32 = 1; // 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. Do NOT set -// USE_BATCH_ATTESTATION to true at the same time in this case! +// USE_BATCH_ATTESTATION to true at the same time in this case! The code asserts +// that you don't use the same key material for batch and enterprise attestation. +// 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; const ENTERPRISE_RP_ID_LIST: &[&str] = &[]; // Our credential ID consists of @@ -321,6 +326,11 @@ where check_user_presence: CheckUserPresence, now: ClockValue, ) -> CtapState<'a, R, CheckUserPresence> { + #[allow(clippy::assertions_on_constants)] + { + assert!(!USE_BATCH_ATTESTATION || ENTERPRISE_ATTESTATION_MODE.is_none()); + } + let persistent_store = PersistentStore::new(rng); let pin_protocol_v1 = PinProtocolV1::new(rng); CtapState { @@ -2764,19 +2774,4 @@ mod test { )) ); } - - #[test] - #[allow(clippy::assertions_on_constants)] - /// Make sure that privacy guarantees are uphold. - /// - /// The current enterprise attestation implementation reuses batch - /// attestation. Enterprise attestation would imply a batch size of 1, but - /// batch attestation needs a batch size of at least 100k. To prevent - /// accidential misconfiguration, this test allows only one of the constants - /// to be set. If you implement your own enterprise attestation mechanism, - /// and you want batch attestation at the same time, feel free to proceed - /// carefully and remove this test. - fn check_attestation_privacy() { - assert!(!USE_BATCH_ATTESTATION || ENTERPRISE_ATTESTATION_MODE.is_none()); - } }