Move 3 pure constants to new file

This commit is contained in:
Howard Yang
2022-04-14 19:18:11 +08:00
parent eb8eccabc4
commit ab67d14e93
8 changed files with 111 additions and 59 deletions

View File

@@ -68,6 +68,16 @@ pub trait Customization {
/// the minimum PIN length with the minPinLength extension. /// the minimum PIN length with the minPinLength extension.
fn default_min_pin_length_rp_ids(&self) -> &[&str]; fn default_min_pin_length_rp_ids(&self) -> &[&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.
fn enforce_always_uv(&self) -> bool;
/// Maximum message size send for CTAP commands. /// Maximum message size send for CTAP commands.
/// ///
/// The maximum value is 7609, as HID packets can not encode longer messages. /// The maximum value is 7609, as HID packets can not encode longer messages.
@@ -77,6 +87,28 @@ pub trait Customization {
/// this value. /// this value.
fn max_msg_size(&self) -> usize; 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. // Constants for performance optimization or adapting to different hardware.
// //
@@ -102,18 +134,24 @@ pub trait Customization {
#[derive(Clone)] #[derive(Clone)]
pub struct CustomizationImpl { pub struct CustomizationImpl {
pub default_cred_protect: Option<CredentialProtectionPolicy>,
pub default_min_pin_length: u8, pub default_min_pin_length: u8,
pub default_min_pin_length_rp_ids: &'static [&'static str], pub default_min_pin_length_rp_ids: &'static [&'static str],
pub default_cred_protect: Option<CredentialProtectionPolicy>, pub enforce_always_uv: bool,
pub max_msg_size: usize, pub max_msg_size: usize,
pub max_pin_retries: u8,
pub use_signature_counter: bool,
pub max_rp_ids_length: usize, pub max_rp_ids_length: usize,
} }
pub const DEFAULT_CUSTOMIZATION: CustomizationImpl = CustomizationImpl { pub const DEFAULT_CUSTOMIZATION: CustomizationImpl = CustomizationImpl {
default_min_pin_length: 4, default_min_pin_length: 4,
default_min_pin_length_rp_ids: &[], default_min_pin_length_rp_ids: &[],
enforce_always_uv: false,
default_cred_protect: None, default_cred_protect: None,
max_msg_size: 7609, max_msg_size: 7609,
max_pin_retries: 8,
use_signature_counter: true,
max_rp_ids_length: 8, max_rp_ids_length: 8,
}; };
@@ -130,10 +168,22 @@ impl Customization for CustomizationImpl {
self.default_min_pin_length_rp_ids self.default_min_pin_length_rp_ids
} }
fn enforce_always_uv(&self) -> bool {
self.enforce_always_uv
}
fn max_msg_size(&self) -> usize { fn max_msg_size(&self) -> usize {
self.max_msg_size 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 { fn max_rp_ids_length(&self) -> usize {
self.max_rp_ids_length self.max_rp_ids_length
} }
@@ -151,6 +201,11 @@ pub fn is_valid(customization: &impl Customization) -> bool {
return false; 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. // Default min pin length rp ids must be non-empty if max rp ids length is 0.
if customization.max_rp_ids_length() == 0 if customization.max_rp_ids_length() == 0
&& customization.default_min_pin_length_rp_ids().is_empty() && customization.default_min_pin_length_rp_ids().is_empty()

View File

@@ -123,7 +123,7 @@ pub fn process_config(
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use crate::ctap::customization::ENFORCE_ALWAYS_UV; use crate::api::customization::Customization;
use crate::ctap::data_formats::PinUvAuthProtocol; use crate::ctap::data_formats::PinUvAuthProtocol;
use crate::ctap::pin_protocol::authenticate_pin_uv_auth_token; use crate::ctap::pin_protocol::authenticate_pin_uv_auth_token;
use crate::env::test::TestEnv; use crate::env::test::TestEnv;
@@ -180,7 +180,7 @@ mod test {
pin_uv_auth_protocol: None, pin_uv_auth_protocol: None,
}; };
let config_response = process_config(&mut env, &mut client_pin, config_params); let config_response = process_config(&mut env, &mut client_pin, config_params);
if ENFORCE_ALWAYS_UV { if env.customization().enforce_always_uv() {
assert_eq!( assert_eq!(
config_response, config_response,
Err(Ctap2StatusCode::CTAP2_ERR_OPERATION_DENIED) Err(Ctap2StatusCode::CTAP2_ERR_OPERATION_DENIED)
@@ -210,7 +210,7 @@ mod test {
pin_uv_auth_protocol: Some(pin_uv_auth_protocol), pin_uv_auth_protocol: Some(pin_uv_auth_protocol),
}; };
let config_response = process_config(&mut env, &mut client_pin, config_params); let config_response = process_config(&mut env, &mut client_pin, config_params);
if ENFORCE_ALWAYS_UV { if env.customization().enforce_always_uv() {
assert_eq!( assert_eq!(
config_response, config_response,
Err(Ctap2StatusCode::CTAP2_ERR_OPERATION_DENIED) Err(Ctap2StatusCode::CTAP2_ERR_OPERATION_DENIED)

View File

@@ -344,8 +344,9 @@ impl Ctap1Command {
#[cfg(test)] #[cfg(test)]
mod 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 super::*;
use crate::api::customization::Customization;
use crate::clock::TEST_CLOCK_FREQUENCY_HZ; use crate::clock::TEST_CLOCK_FREQUENCY_HZ;
use crate::env::test::TestEnv; use crate::env::test::TestEnv;
use crypto::Hash256; use crypto::Hash256;
@@ -643,8 +644,8 @@ mod test {
assert_eq!(response, Err(Ctap1StatusCode::SW_WRONG_DATA)); assert_eq!(response, Err(Ctap1StatusCode::SW_WRONG_DATA));
} }
fn check_signature_counter(response: &[u8; 4], signature_counter: u32) { fn check_signature_counter(env: &mut impl Env, response: &[u8; 4], signature_counter: u32) {
if USE_SIGNATURE_COUNTER { if env.customization().use_signature_counter() {
assert_eq!(u32::from_be_bytes(*response), signature_counter); assert_eq!(u32::from_be_bytes(*response), signature_counter);
} else { } else {
assert_eq!(response, &[0x00, 0x00, 0x00, 0x00]); 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)) Ctap1Command::process_command(&mut env, &message, &mut ctap_state, CtapInstant::new(0))
.unwrap(); .unwrap();
assert_eq!(response[0], 0x01); assert_eq!(response[0], 0x01);
let global_signature_counter = storage::global_signature_counter(&mut env).unwrap();
check_signature_counter( check_signature_counter(
&mut env,
array_ref!(response, 1, 4), array_ref!(response, 1, 4),
storage::global_signature_counter(&mut env).unwrap(), global_signature_counter,
); );
} }
@@ -706,9 +709,11 @@ mod test {
) )
.unwrap(); .unwrap();
assert_eq!(response[0], 0x01); assert_eq!(response[0], 0x01);
let global_signature_counter = storage::global_signature_counter(&mut env).unwrap();
check_signature_counter( check_signature_counter(
&mut env,
array_ref!(response, 1, 4), array_ref!(response, 1, 4),
storage::global_signature_counter(&mut env).unwrap(), global_signature_counter,
); );
} }

View File

@@ -19,16 +19,6 @@
use crate::ctap::data_formats::EnterpriseAttestationMode; use crate::ctap::data_formats::EnterpriseAttestationMode;
/// 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. /// Allows usage of enterprise attestation.
/// ///
/// # Invariant /// # Invariant
@@ -71,16 +61,6 @@ pub const ENTERPRISE_ATTESTATION_MODE: Option<EnterpriseAttestationMode> = None;
/// VendorFacilitated. /// VendorFacilitated.
pub const ENTERPRISE_RP_ID_LIST: &[&str] = &[]; 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. /// Enables or disables basic attestation for FIDO2.
/// ///
/// # Invariant /// # Invariant
@@ -97,18 +77,6 @@ pub const MAX_PIN_RETRIES: u8 = 8;
/// https://www.w3.org/TR/webauthn/#attestation /// https://www.w3.org/TR/webauthn/#attestation
pub const USE_BATCH_ATTESTATION: bool = false; 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. // Constants for performance optimization or adapting to different hardware.
// //
@@ -178,7 +146,6 @@ mod test {
} else { } else {
assert!(ENTERPRISE_RP_ID_LIST.is_empty()); assert!(ENTERPRISE_RP_ID_LIST.is_empty());
} }
assert!(MAX_PIN_RETRIES <= 8);
assert!(MAX_CRED_BLOB_LENGTH >= 32); assert!(MAX_CRED_BLOB_LENGTH >= 32);
if let Some(count) = MAX_CREDENTIAL_COUNT_IN_LIST { if let Some(count) = MAX_CREDENTIAL_COUNT_IN_LIST {
assert!(count >= 1); assert!(count >= 1);

View File

@@ -45,7 +45,7 @@ use self::credential_management::process_credential_management;
use self::crypto_wrapper::{aes256_cbc_decrypt, aes256_cbc_encrypt}; use self::crypto_wrapper::{aes256_cbc_decrypt, aes256_cbc_encrypt};
use self::customization::{ use self::customization::{
ENTERPRISE_ATTESTATION_MODE, ENTERPRISE_RP_ID_LIST, MAX_CREDENTIAL_COUNT_IN_LIST, ENTERPRISE_ATTESTATION_MODE, ENTERPRISE_RP_ID_LIST, MAX_CREDENTIAL_COUNT_IN_LIST,
MAX_CRED_BLOB_LENGTH, MAX_LARGE_BLOB_ARRAY_SIZE, USE_BATCH_ATTESTATION, USE_SIGNATURE_COUNTER, MAX_CRED_BLOB_LENGTH, MAX_LARGE_BLOB_ARRAY_SIZE, USE_BATCH_ATTESTATION,
}; };
use self::data_formats::{ use self::data_formats::{
AuthenticatorTransport, CoseKey, CoseSignature, CredentialProtectionPolicy, AuthenticatorTransport, CoseKey, CoseSignature, CredentialProtectionPolicy,
@@ -421,7 +421,7 @@ impl CtapState {
&mut self, &mut self,
env: &mut impl Env, env: &mut impl Env,
) -> Result<(), Ctap2StatusCode> { ) -> Result<(), Ctap2StatusCode> {
if USE_SIGNATURE_COUNTER { if env.customization().use_signature_counter() {
let increment = env.rng().gen_uniform_u32x8()[0] % 8 + 1; let increment = env.rng().gen_uniform_u32x8()[0] % 8 + 1;
storage::incr_global_signature_counter(env, increment)?; storage::incr_global_signature_counter(env, increment)?;
} }
@@ -1398,7 +1398,7 @@ impl CtapState {
let mut auth_data = vec![]; let mut auth_data = vec![];
auth_data.extend(rp_id_hash); auth_data.extend(rp_id_hash);
auth_data.push(flag_byte); 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. // It uses a big-endian representation.
let mut signature_counter = [0u8; 4]; let mut signature_counter = [0u8; 4];
BigEndian::write_u32( BigEndian::write_u32(

View File

@@ -16,9 +16,7 @@ mod key;
use crate::api::customization::Customization; use crate::api::customization::Customization;
use crate::ctap::client_pin::PIN_AUTH_LENGTH; use crate::ctap::client_pin::PIN_AUTH_LENGTH;
use crate::ctap::customization::{ use crate::ctap::customization::{MAX_LARGE_BLOB_ARRAY_SIZE, MAX_SUPPORTED_RESIDENT_KEYS};
ENFORCE_ALWAYS_UV, MAX_LARGE_BLOB_ARRAY_SIZE, MAX_PIN_RETRIES, MAX_SUPPORTED_RESIDENT_KEYS,
};
use crate::ctap::data_formats::{ use crate::ctap::data_formats::{
extract_array, extract_text_string, CredentialProtectionPolicy, PublicKeyCredentialSource, extract_array, extract_text_string, CredentialProtectionPolicy, PublicKeyCredentialSource,
PublicKeyCredentialUserEntity, PublicKeyCredentialUserEntity,
@@ -360,7 +358,7 @@ pub fn set_pin(
/// Returns the number of remaining PIN retries. /// Returns the number of remaining PIN retries.
pub fn pin_retries(env: &mut impl Env) -> Result<u8, Ctap2StatusCode> { pub fn pin_retries(env: &mut impl Env) -> Result<u8, Ctap2StatusCode> {
match env.store().find(key::PIN_RETRIES)? { 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]), Some(value) if value.len() == 1 => Ok(value[0]),
_ => Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR), _ => Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR),
} }
@@ -596,7 +594,7 @@ pub fn enable_enterprise_attestation(env: &mut impl Env) -> Result<(), Ctap2Stat
/// Returns whether alwaysUv is enabled. /// Returns whether alwaysUv is enabled.
pub fn has_always_uv(env: &mut impl Env) -> Result<bool, Ctap2StatusCode> { pub fn has_always_uv(env: &mut impl Env) -> Result<bool, Ctap2StatusCode> {
if ENFORCE_ALWAYS_UV { if env.customization().enforce_always_uv() {
return Ok(true); return Ok(true);
} }
match env.store().find(key::ALWAYS_UV)? { match env.store().find(key::ALWAYS_UV)? {
@@ -608,7 +606,7 @@ pub fn has_always_uv(env: &mut impl Env) -> Result<bool, Ctap2StatusCode> {
/// Enables alwaysUv, when disabled, and vice versa. /// Enables alwaysUv, when disabled, and vice versa.
pub fn toggle_always_uv(env: &mut impl Env) -> Result<(), Ctap2StatusCode> { 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); return Err(Ctap2StatusCode::CTAP2_ERR_OPERATION_DENIED);
} }
if has_always_uv(env)? { if has_always_uv(env)? {
@@ -1061,10 +1059,13 @@ mod test {
let mut env = TestEnv::new(); let mut env = TestEnv::new();
// The pin retries is initially at the maximum. // 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. // 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(); decr_pin_retries(&mut env).unwrap();
assert_eq!(pin_retries(&mut env), Ok(retries)); assert_eq!(pin_retries(&mut env), Ok(retries));
} }
@@ -1075,7 +1076,10 @@ mod test {
// Resetting the pin retries resets the pin retries. // Resetting the pin retries resets the pin retries.
reset_pin_retries(&mut env).unwrap(); 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] #[test]
@@ -1250,7 +1254,7 @@ mod test {
fn test_always_uv() { fn test_always_uv() {
let mut env = TestEnv::new(); let mut env = TestEnv::new();
if ENFORCE_ALWAYS_UV { if env.customization().enforce_always_uv() {
assert!(has_always_uv(&mut env).unwrap()); assert!(has_always_uv(&mut env).unwrap());
assert_eq!( assert_eq!(
toggle_always_uv(&mut env), toggle_always_uv(&mut env),

View File

@@ -115,7 +115,7 @@ make_partition! {
/// The number of PIN retries. /// 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; PIN_RETRIES = 2044;
/// The PIN hash and length. /// The PIN hash and length.

View File

@@ -4,11 +4,14 @@ use crate::api::customization::{Customization, CustomizationImpl};
use crate::ctap::data_formats::CredentialProtectionPolicy; use crate::ctap::data_formats::CredentialProtectionPolicy;
pub struct TestCustomization { pub struct TestCustomization {
pub default_cred_protect: Option<CredentialProtectionPolicy>,
pub default_min_pin_length: u8, pub default_min_pin_length: u8,
default_min_pin_length_rp_ids_backing_store: Vec<String>, default_min_pin_length_rp_ids_backing_store: Vec<String>,
default_min_pin_length_rp_ids: Vec<*const str>, default_min_pin_length_rp_ids: Vec<*const str>,
pub default_cred_protect: Option<CredentialProtectionPolicy>, pub enforce_always_uv: bool,
pub max_msg_size: usize, pub max_msg_size: usize,
pub max_pin_retries: u8,
pub use_signature_counter: bool,
pub max_rp_ids_length: usize, pub max_rp_ids_length: usize,
} }
@@ -38,10 +41,22 @@ impl Customization for TestCustomization {
unsafe { from_raw_parts(rp_ids, length) } unsafe { from_raw_parts(rp_ids, length) }
} }
fn enforce_always_uv(&self) -> bool {
self.enforce_always_uv
}
fn max_msg_size(&self) -> usize { fn max_msg_size(&self) -> usize {
self.max_msg_size 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 { fn max_rp_ids_length(&self) -> usize {
self.max_rp_ids_length self.max_rp_ids_length
} }
@@ -50,10 +65,13 @@ impl Customization for TestCustomization {
impl From<CustomizationImpl> for TestCustomization { impl From<CustomizationImpl> for TestCustomization {
fn from(c: CustomizationImpl) -> Self { fn from(c: CustomizationImpl) -> Self {
let CustomizationImpl { let CustomizationImpl {
default_cred_protect,
default_min_pin_length, default_min_pin_length,
default_min_pin_length_rp_ids, default_min_pin_length_rp_ids,
default_cred_protect, enforce_always_uv,
max_msg_size, max_msg_size,
max_pin_retries,
use_signature_counter,
max_rp_ids_length, max_rp_ids_length,
} = c; } = c;
@@ -63,11 +81,14 @@ impl From<CustomizationImpl> for TestCustomization {
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let mut ret = Self { let mut ret = Self {
default_cred_protect,
default_min_pin_length, default_min_pin_length,
default_min_pin_length_rp_ids_backing_store, default_min_pin_length_rp_ids_backing_store,
default_min_pin_length_rp_ids: vec![], default_min_pin_length_rp_ids: vec![],
default_cred_protect, enforce_always_uv,
max_msg_size, max_msg_size,
max_pin_retries,
use_signature_counter,
max_rp_ids_length, max_rp_ids_length,
}; };