Move remaining customizations to new file (#473)

This commit is contained in:
hcyang
2022-04-28 19:33:34 +08:00
committed by GitHub
parent 4782d7e186
commit a0e11bd5aa
8 changed files with 165 additions and 130 deletions

View File

@@ -179,6 +179,31 @@ pub trait Customization {
// 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<usize>;
/// 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
@@ -193,6 +218,27 @@ pub trait Customization {
/// 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)]
@@ -207,7 +253,11 @@ pub struct CustomizationImpl {
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<usize>,
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 {
@@ -221,7 +271,11 @@ pub const DEFAULT_CUSTOMIZATION: CustomizationImpl = CustomizationImpl {
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 {
@@ -276,13 +330,33 @@ impl Customization for CustomizationImpl {
self.use_signature_counter
}
fn max_cred_blob_length(&self) -> usize {
self.max_cred_blob_length
}
fn max_credential_count_in_list(&self) -> Option<usize> {
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;
@@ -323,6 +397,23 @@ pub fn is_valid(customization: &impl Customization) -> bool {
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()

View File

@@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use super::customization::{MAX_CREDENTIAL_COUNT_IN_LIST, MAX_LARGE_BLOB_ARRAY_SIZE};
use super::data_formats::{
extract_array, extract_bool, extract_byte_string, extract_map, extract_text_string,
extract_unsigned, ok_or_missing, ClientPinSubCommand, ConfigSubCommand, ConfigSubCommandParams,
@@ -205,10 +204,8 @@ impl TryFrom<cbor::Value> for AuthenticatorMakeCredentialParameters {
let exclude_list = match exclude_list {
Some(entry) => {
let exclude_list_vec = extract_array(entry)?;
let list_len = MAX_CREDENTIAL_COUNT_IN_LIST.unwrap_or(exclude_list_vec.len());
let exclude_list = exclude_list_vec
.into_iter()
.take(list_len)
.map(PublicKeyCredentialDescriptor::try_from)
.collect::<Result<Vec<PublicKeyCredentialDescriptor>, Ctap2StatusCode>>()?;
Some(exclude_list)
@@ -283,10 +280,8 @@ impl TryFrom<cbor::Value> for AuthenticatorGetAssertionParameters {
let allow_list = match allow_list {
Some(entry) => {
let allow_list_vec = extract_array(entry)?;
let list_len = MAX_CREDENTIAL_COUNT_IN_LIST.unwrap_or(allow_list_vec.len());
let allow_list = allow_list_vec
.into_iter()
.take(list_len)
.map(PublicKeyCredentialDescriptor::try_from)
.collect::<Result<Vec<PublicKeyCredentialDescriptor>, Ctap2StatusCode>>()?;
Some(allow_list)
@@ -431,9 +426,6 @@ impl TryFrom<cbor::Value> for AuthenticatorLargeBlobsParameters {
if set.is_some() && offset == 0 {
match length {
None => return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER),
Some(len) if len > MAX_LARGE_BLOB_ARRAY_SIZE => {
return Err(Ctap2StatusCode::CTAP2_ERR_LARGE_BLOB_STORAGE_FULL)
}
Some(len) if len < MIN_LARGE_BLOB_LEN => {
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
}

View File

@@ -1,89 +0,0 @@
// Copyright 2021 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.
// ###########################################################################
// 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.
pub const MAX_CRED_BLOB_LENGTH: usize = 32;
/// 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.
pub const MAX_CREDENTIAL_COUNT_IN_LIST: Option<usize> = None;
/// 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.
pub const MAX_LARGE_BLOB_ARRAY_SIZE: usize = 2048;
/// 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.
pub const MAX_SUPPORTED_RESIDENT_KEYS: usize = 150;
#[cfg(test)]
mod test {
use super::*;
#[test]
#[allow(clippy::assertions_on_constants)]
fn test_invariants() {
// 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!(MAX_CRED_BLOB_LENGTH >= 32);
if let Some(count) = MAX_CREDENTIAL_COUNT_IN_LIST {
assert!(count >= 1);
}
assert!(MAX_LARGE_BLOB_ARRAY_SIZE >= 1024);
}
}

View File

@@ -77,9 +77,11 @@ impl LargeBlobs {
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_LENGTH);
}
if offset == 0 {
// Checks for offset and length are already done in command.
self.expected_length =
length.ok_or(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)?;
if self.expected_length > env.customization().max_large_blob_array_size() {
return Err(Ctap2StatusCode::CTAP2_ERR_LARGE_BLOB_STORAGE_FULL);
}
self.expected_next_offset = 0;
}
if offset != self.expected_next_offset {

View File

@@ -20,7 +20,6 @@ mod credential_management;
mod crypto_wrapper;
#[cfg(feature = "with_ctap1")]
mod ctap1;
pub mod customization;
pub mod data_formats;
pub mod hid;
mod key_material;
@@ -43,9 +42,6 @@ use self::command::{
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::{
MAX_CREDENTIAL_COUNT_IN_LIST, MAX_CRED_BLOB_LENGTH, MAX_LARGE_BLOB_ARRAY_SIZE,
};
use self::data_formats::{
AuthenticatorTransport, CoseKey, CoseSignature, CredentialProtectionPolicy,
EnterpriseAttestationMode, GetAssertionExtensions, PackedAttestationStatement,
@@ -776,7 +772,7 @@ impl CtapState {
let has_cred_blob_output = extensions.cred_blob.is_some();
let cred_blob = extensions
.cred_blob
.filter(|c| options.rk && c.len() <= MAX_CRED_BLOB_LENGTH);
.filter(|c| options.rk && c.len() <= env.customization().max_cred_blob_length());
let cred_blob_output = if has_cred_blob_output {
Some(cred_blob.is_some())
} else {
@@ -1215,15 +1211,20 @@ impl CtapState {
PinUvAuthProtocol::V2 as u64,
PinUvAuthProtocol::V1 as u64,
]),
max_credential_count_in_list: MAX_CREDENTIAL_COUNT_IN_LIST.map(|c| c as u64),
max_credential_count_in_list: env
.customization()
.max_credential_count_in_list()
.map(|c| c as u64),
max_credential_id_length: Some(CREDENTIAL_ID_SIZE as u64),
transports: Some(vec![AuthenticatorTransport::Usb]),
algorithms: Some(vec![ES256_CRED_PARAM]),
max_serialized_large_blob_array: Some(MAX_LARGE_BLOB_ARRAY_SIZE as u64),
max_serialized_large_blob_array: Some(
env.customization().max_large_blob_array_size() as u64,
),
force_pin_change: Some(storage::has_force_pin_change(env)?),
min_pin_length: storage::min_pin_length(env)?,
firmware_version: None,
max_cred_blob_length: Some(MAX_CRED_BLOB_LENGTH as u64),
max_cred_blob_length: Some(env.customization().max_cred_blob_length() as u64),
max_rp_ids_for_set_min_pin_length: Some(
env.customization().max_rp_ids_length() as u64
),
@@ -1525,14 +1526,14 @@ mod test {
},
0x05 => env.customization().max_msg_size() as u64,
0x06 => cbor_array![2, 1],
0x07 => MAX_CREDENTIAL_COUNT_IN_LIST.map(|c| c as u64),
0x07 => env.customization().max_credential_count_in_list().map(|c| c as u64),
0x08 => CREDENTIAL_ID_SIZE as u64,
0x09 => cbor_array!["usb"],
0x0A => cbor_array![ES256_CRED_PARAM],
0x0B => MAX_LARGE_BLOB_ARRAY_SIZE as u64,
0x0B => env.customization().max_large_blob_array_size() as u64,
0x0C => false,
0x0D => storage::min_pin_length(&mut env).unwrap() as u64,
0x0F => MAX_CRED_BLOB_LENGTH as u64,
0x0F => env.customization().max_cred_blob_length() as u64,
0x10 => env.customization().max_rp_ids_length() as u64,
0x14 => storage::remaining_credentials(&mut env).unwrap() as u64,
};
@@ -1877,7 +1878,7 @@ mod test {
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
let extensions = MakeCredentialExtensions {
cred_blob: Some(vec![0xCB; MAX_CRED_BLOB_LENGTH + 1]),
cred_blob: Some(vec![0xCB; env.customization().max_cred_blob_length() + 1]),
..Default::default()
};
let mut make_credential_params = create_minimal_make_credential_parameters();

View File

@@ -16,7 +16,6 @@ mod key;
use crate::api::customization::Customization;
use crate::ctap::client_pin::PIN_AUTH_LENGTH;
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,
@@ -90,7 +89,7 @@ pub fn get_credential(
key: usize,
) -> Result<PublicKeyCredentialSource, Ctap2StatusCode> {
let min_key = key::CREDENTIALS.start;
if key < min_key || key >= min_key + MAX_SUPPORTED_RESIDENT_KEYS {
if key < min_key || key >= min_key + env.customization().max_supported_resident_keys() {
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
}
let credential_entry = env
@@ -154,15 +153,16 @@ pub fn store_credential(
env: &mut impl Env,
new_credential: PublicKeyCredentialSource,
) -> Result<(), Ctap2StatusCode> {
let max_supported_resident_keys = env.customization().max_supported_resident_keys();
// Holds the key of the existing credential if this is an update.
let mut old_key = None;
let min_key = key::CREDENTIALS.start;
// Holds whether a key is used (indices are shifted by min_key).
let mut keys = vec![false; MAX_SUPPORTED_RESIDENT_KEYS];
let mut keys = vec![false; max_supported_resident_keys];
let mut iter_result = Ok(());
let iter = iter_credentials(env, &mut iter_result)?;
for (key, credential) in iter {
if key < min_key || key - min_key >= MAX_SUPPORTED_RESIDENT_KEYS || keys[key - min_key] {
if key < min_key || key - min_key >= max_supported_resident_keys || keys[key - min_key] {
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
}
keys[key - min_key] = true;
@@ -176,14 +176,14 @@ pub fn store_credential(
}
}
iter_result?;
if old_key.is_none() && keys.iter().filter(|&&x| x).count() >= MAX_SUPPORTED_RESIDENT_KEYS {
if old_key.is_none() && keys.iter().filter(|&&x| x).count() >= max_supported_resident_keys {
return Err(Ctap2StatusCode::CTAP2_ERR_KEY_STORE_FULL);
}
let key = match old_key {
// This is a new credential being added, we need to allocate a free key. We choose the
// first available key.
None => key::CREDENTIALS
.take(MAX_SUPPORTED_RESIDENT_KEYS)
.take(max_supported_resident_keys)
.find(|key| !keys[key - min_key])
.ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?,
// This is an existing credential being updated, we reuse its key.
@@ -233,7 +233,8 @@ pub fn count_credentials(env: &mut impl Env) -> Result<usize, Ctap2StatusCode> {
/// Returns the estimated number of credentials that can still be stored.
pub fn remaining_credentials(env: &mut impl Env) -> Result<usize, Ctap2StatusCode> {
MAX_SUPPORTED_RESIDENT_KEYS
env.customization()
.max_supported_resident_keys()
.checked_sub(count_credentials(env)?)
.ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)
}
@@ -459,7 +460,7 @@ pub fn commit_large_blob_array(
large_blob_array: &[u8],
) -> Result<(), Ctap2StatusCode> {
// This input should have been caught at caller level.
if large_blob_array.len() > MAX_LARGE_BLOB_ARRAY_SIZE {
if large_blob_array.len() > env.customization().max_large_blob_array_size() {
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
}
Ok(fragment::write(
@@ -768,7 +769,7 @@ mod test {
assert_eq!(count_credentials(&mut env).unwrap(), 0);
let mut credential_ids = vec![];
for i in 0..MAX_SUPPORTED_RESIDENT_KEYS {
for i in 0..env.customization().max_supported_resident_keys() {
let user_handle = (i as u32).to_ne_bytes().to_vec();
let credential_source = create_credential_source(env.rng(), "example.com", user_handle);
credential_ids.push(credential_source.credential_id.clone());
@@ -835,7 +836,8 @@ mod test {
let mut env = TestEnv::new();
assert_eq!(count_credentials(&mut env).unwrap(), 0);
for i in 0..MAX_SUPPORTED_RESIDENT_KEYS {
let max_supported_resident_keys = env.customization().max_supported_resident_keys();
for i in 0..max_supported_resident_keys {
let user_handle = (i as u32).to_ne_bytes().to_vec();
let credential_source = create_credential_source(env.rng(), "example.com", user_handle);
assert!(store_credential(&mut env, credential_source).is_ok());
@@ -844,7 +846,7 @@ mod test {
let credential_source = create_credential_source(
env.rng(),
"example.com",
vec![MAX_SUPPORTED_RESIDENT_KEYS as u8],
vec![max_supported_resident_keys as u8],
);
assert_eq!(
store_credential(&mut env, credential_source),
@@ -852,7 +854,7 @@ mod test {
);
assert_eq!(
count_credentials(&mut env).unwrap(),
MAX_SUPPORTED_RESIDENT_KEYS
max_supported_resident_keys
);
}
@@ -883,7 +885,8 @@ mod test {
);
reset(&mut env).unwrap();
for i in 0..MAX_SUPPORTED_RESIDENT_KEYS {
let max_supported_resident_keys = env.customization().max_supported_resident_keys();
for i in 0..max_supported_resident_keys {
let user_handle = (i as u32).to_ne_bytes().to_vec();
let credential_source = create_credential_source(env.rng(), "example.com", user_handle);
assert!(store_credential(&mut env, credential_source).is_ok());
@@ -892,7 +895,7 @@ mod test {
let credential_source = create_credential_source(
env.rng(),
"example.com",
vec![MAX_SUPPORTED_RESIDENT_KEYS as u8],
vec![max_supported_resident_keys as u8],
);
assert_eq!(
store_credential(&mut env, credential_source),
@@ -900,7 +903,7 @@ mod test {
);
assert_eq!(
count_credentials(&mut env).unwrap(),
MAX_SUPPORTED_RESIDENT_KEYS
max_supported_resident_keys
);
}
@@ -1147,7 +1150,7 @@ mod test {
let mut env = TestEnv::new();
assert!(
MAX_LARGE_BLOB_ARRAY_SIZE
env.customization().max_large_blob_array_size()
<= env.store().max_value_length()
* (key::LARGE_BLOB_SHARDS.end - key::LARGE_BLOB_SHARDS.start)
);

View File

@@ -84,8 +84,9 @@ make_partition! {
/// The credentials.
///
/// Depending on `MAX_SUPPORTED_RESIDENT_KEYS`, only a prefix of those keys is used. Each
/// board may configure `MAX_SUPPORTED_RESIDENT_KEYS` depending on the storage size.
/// Depending on `Customization::max_supported_resident_keys()`, only a prefix of those keys is used.
/// Each board may configure `Customization::max_supported_resident_keys()` depending on the
/// storage size.
CREDENTIALS = 1700..2000;
/// Storage for the serialized large blob array.
@@ -138,11 +139,17 @@ make_partition! {
#[cfg(test)]
mod test {
use super::*;
use crate::api::customization::Customization;
use crate::env::test::TestEnv;
use crate::env::Env;
#[test]
fn enough_credentials() {
use crate::ctap::customization::MAX_SUPPORTED_RESIDENT_KEYS;
assert!(MAX_SUPPORTED_RESIDENT_KEYS <= CREDENTIALS.end - CREDENTIALS.start);
let env = TestEnv::new();
assert!(
env.customization().max_supported_resident_keys()
<= CREDENTIALS.end - CREDENTIALS.start
);
}
#[test]

View File

@@ -14,7 +14,11 @@ pub struct TestCustomization {
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<usize>,
pub max_large_blob_array_size: usize,
pub max_rp_ids_length: usize,
pub max_supported_resident_keys: usize,
}
impl Customization for TestCustomization {
@@ -62,9 +66,25 @@ impl Customization for TestCustomization {
self.use_signature_counter
}
fn max_cred_blob_length(&self) -> usize {
self.max_cred_blob_length
}
fn max_credential_count_in_list(&self) -> Option<usize> {
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
}
}
impl From<CustomizationImpl> for TestCustomization {
@@ -80,7 +100,11 @@ impl From<CustomizationImpl> for TestCustomization {
max_pin_retries,
use_batch_attestation,
use_signature_counter,
max_cred_blob_length,
max_credential_count_in_list,
max_large_blob_array_size,
max_rp_ids_length,
max_supported_resident_keys,
} = c;
let default_min_pin_length_rp_ids = default_min_pin_length_rp_ids
@@ -104,7 +128,11 @@ impl From<CustomizationImpl> for TestCustomization {
max_pin_retries,
use_batch_attestation,
use_signature_counter,
max_cred_blob_length,
max_credential_count_in_list,
max_large_blob_array_size,
max_rp_ids_length,
max_supported_resident_keys,
}
}
}