Move remaining customizations to new file (#473)
This commit is contained in:
@@ -179,6 +179,31 @@ pub trait Customization {
|
|||||||
// the key.
|
// 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.
|
/// Limits the number of RP IDs that can change the minimum PIN length.
|
||||||
///
|
///
|
||||||
/// # Invariant
|
/// # Invariant
|
||||||
@@ -193,6 +218,27 @@ pub trait Customization {
|
|||||||
/// in default_min_pin_length_rp_ids() should be allowed to change the minimum
|
/// in default_min_pin_length_rp_ids() should be allowed to change the minimum
|
||||||
/// PIN length.
|
/// PIN length.
|
||||||
fn max_rp_ids_length(&self) -> usize;
|
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)]
|
#[derive(Clone)]
|
||||||
@@ -207,7 +253,11 @@ pub struct CustomizationImpl {
|
|||||||
pub max_pin_retries: u8,
|
pub max_pin_retries: u8,
|
||||||
pub use_batch_attestation: bool,
|
pub use_batch_attestation: bool,
|
||||||
pub use_signature_counter: 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_rp_ids_length: usize,
|
||||||
|
pub max_supported_resident_keys: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const DEFAULT_CUSTOMIZATION: CustomizationImpl = CustomizationImpl {
|
pub const DEFAULT_CUSTOMIZATION: CustomizationImpl = CustomizationImpl {
|
||||||
@@ -221,7 +271,11 @@ pub const DEFAULT_CUSTOMIZATION: CustomizationImpl = CustomizationImpl {
|
|||||||
max_pin_retries: 8,
|
max_pin_retries: 8,
|
||||||
use_batch_attestation: false,
|
use_batch_attestation: false,
|
||||||
use_signature_counter: true,
|
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_rp_ids_length: 8,
|
||||||
|
max_supported_resident_keys: 150,
|
||||||
};
|
};
|
||||||
|
|
||||||
impl Customization for CustomizationImpl {
|
impl Customization for CustomizationImpl {
|
||||||
@@ -276,13 +330,33 @@ impl Customization for CustomizationImpl {
|
|||||||
self.use_signature_counter
|
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 {
|
fn max_rp_ids_length(&self) -> usize {
|
||||||
self.max_rp_ids_length
|
self.max_rp_ids_length
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn max_supported_resident_keys(&self) -> usize {
|
||||||
|
self.max_supported_resident_keys
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
pub fn is_valid(customization: &impl Customization) -> bool {
|
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.
|
// Max message size must be between 1024 and 7609.
|
||||||
if customization.max_msg_size() < 1024 || customization.max_msg_size() > 7609 {
|
if customization.max_msg_size() < 1024 || customization.max_msg_size() > 7609 {
|
||||||
return false;
|
return false;
|
||||||
@@ -323,6 +397,23 @@ pub fn is_valid(customization: &impl Customization) -> bool {
|
|||||||
return false;
|
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.
|
// 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()
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use super::customization::{MAX_CREDENTIAL_COUNT_IN_LIST, MAX_LARGE_BLOB_ARRAY_SIZE};
|
|
||||||
use super::data_formats::{
|
use super::data_formats::{
|
||||||
extract_array, extract_bool, extract_byte_string, extract_map, extract_text_string,
|
extract_array, extract_bool, extract_byte_string, extract_map, extract_text_string,
|
||||||
extract_unsigned, ok_or_missing, ClientPinSubCommand, ConfigSubCommand, ConfigSubCommandParams,
|
extract_unsigned, ok_or_missing, ClientPinSubCommand, ConfigSubCommand, ConfigSubCommandParams,
|
||||||
@@ -205,10 +204,8 @@ impl TryFrom<cbor::Value> for AuthenticatorMakeCredentialParameters {
|
|||||||
let exclude_list = match exclude_list {
|
let exclude_list = match exclude_list {
|
||||||
Some(entry) => {
|
Some(entry) => {
|
||||||
let exclude_list_vec = extract_array(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
|
let exclude_list = exclude_list_vec
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.take(list_len)
|
|
||||||
.map(PublicKeyCredentialDescriptor::try_from)
|
.map(PublicKeyCredentialDescriptor::try_from)
|
||||||
.collect::<Result<Vec<PublicKeyCredentialDescriptor>, Ctap2StatusCode>>()?;
|
.collect::<Result<Vec<PublicKeyCredentialDescriptor>, Ctap2StatusCode>>()?;
|
||||||
Some(exclude_list)
|
Some(exclude_list)
|
||||||
@@ -283,10 +280,8 @@ impl TryFrom<cbor::Value> for AuthenticatorGetAssertionParameters {
|
|||||||
let allow_list = match allow_list {
|
let allow_list = match allow_list {
|
||||||
Some(entry) => {
|
Some(entry) => {
|
||||||
let allow_list_vec = extract_array(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
|
let allow_list = allow_list_vec
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.take(list_len)
|
|
||||||
.map(PublicKeyCredentialDescriptor::try_from)
|
.map(PublicKeyCredentialDescriptor::try_from)
|
||||||
.collect::<Result<Vec<PublicKeyCredentialDescriptor>, Ctap2StatusCode>>()?;
|
.collect::<Result<Vec<PublicKeyCredentialDescriptor>, Ctap2StatusCode>>()?;
|
||||||
Some(allow_list)
|
Some(allow_list)
|
||||||
@@ -431,9 +426,6 @@ impl TryFrom<cbor::Value> for AuthenticatorLargeBlobsParameters {
|
|||||||
if set.is_some() && offset == 0 {
|
if set.is_some() && offset == 0 {
|
||||||
match length {
|
match length {
|
||||||
None => return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER),
|
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 => {
|
Some(len) if len < MIN_LARGE_BLOB_LEN => {
|
||||||
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
|
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -77,9 +77,11 @@ impl LargeBlobs {
|
|||||||
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_LENGTH);
|
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_LENGTH);
|
||||||
}
|
}
|
||||||
if offset == 0 {
|
if offset == 0 {
|
||||||
// Checks for offset and length are already done in command.
|
|
||||||
self.expected_length =
|
self.expected_length =
|
||||||
length.ok_or(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)?;
|
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;
|
self.expected_next_offset = 0;
|
||||||
}
|
}
|
||||||
if offset != self.expected_next_offset {
|
if offset != self.expected_next_offset {
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ mod credential_management;
|
|||||||
mod crypto_wrapper;
|
mod crypto_wrapper;
|
||||||
#[cfg(feature = "with_ctap1")]
|
#[cfg(feature = "with_ctap1")]
|
||||||
mod ctap1;
|
mod ctap1;
|
||||||
pub mod customization;
|
|
||||||
pub mod data_formats;
|
pub mod data_formats;
|
||||||
pub mod hid;
|
pub mod hid;
|
||||||
mod key_material;
|
mod key_material;
|
||||||
@@ -43,9 +42,6 @@ use self::command::{
|
|||||||
use self::config_command::process_config;
|
use self::config_command::process_config;
|
||||||
use self::credential_management::process_credential_management;
|
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::{
|
|
||||||
MAX_CREDENTIAL_COUNT_IN_LIST, MAX_CRED_BLOB_LENGTH, MAX_LARGE_BLOB_ARRAY_SIZE,
|
|
||||||
};
|
|
||||||
use self::data_formats::{
|
use self::data_formats::{
|
||||||
AuthenticatorTransport, CoseKey, CoseSignature, CredentialProtectionPolicy,
|
AuthenticatorTransport, CoseKey, CoseSignature, CredentialProtectionPolicy,
|
||||||
EnterpriseAttestationMode, GetAssertionExtensions, PackedAttestationStatement,
|
EnterpriseAttestationMode, GetAssertionExtensions, PackedAttestationStatement,
|
||||||
@@ -776,7 +772,7 @@ impl CtapState {
|
|||||||
let has_cred_blob_output = extensions.cred_blob.is_some();
|
let has_cred_blob_output = extensions.cred_blob.is_some();
|
||||||
let cred_blob = extensions
|
let cred_blob = extensions
|
||||||
.cred_blob
|
.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 {
|
let cred_blob_output = if has_cred_blob_output {
|
||||||
Some(cred_blob.is_some())
|
Some(cred_blob.is_some())
|
||||||
} else {
|
} else {
|
||||||
@@ -1215,15 +1211,20 @@ impl CtapState {
|
|||||||
PinUvAuthProtocol::V2 as u64,
|
PinUvAuthProtocol::V2 as u64,
|
||||||
PinUvAuthProtocol::V1 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),
|
max_credential_id_length: Some(CREDENTIAL_ID_SIZE as u64),
|
||||||
transports: Some(vec![AuthenticatorTransport::Usb]),
|
transports: Some(vec![AuthenticatorTransport::Usb]),
|
||||||
algorithms: Some(vec![ES256_CRED_PARAM]),
|
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)?),
|
force_pin_change: Some(storage::has_force_pin_change(env)?),
|
||||||
min_pin_length: storage::min_pin_length(env)?,
|
min_pin_length: storage::min_pin_length(env)?,
|
||||||
firmware_version: None,
|
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(
|
max_rp_ids_for_set_min_pin_length: Some(
|
||||||
env.customization().max_rp_ids_length() as u64
|
env.customization().max_rp_ids_length() as u64
|
||||||
),
|
),
|
||||||
@@ -1525,14 +1526,14 @@ mod test {
|
|||||||
},
|
},
|
||||||
0x05 => env.customization().max_msg_size() as u64,
|
0x05 => env.customization().max_msg_size() as u64,
|
||||||
0x06 => cbor_array![2, 1],
|
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,
|
0x08 => CREDENTIAL_ID_SIZE as u64,
|
||||||
0x09 => cbor_array!["usb"],
|
0x09 => cbor_array!["usb"],
|
||||||
0x0A => cbor_array![ES256_CRED_PARAM],
|
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,
|
0x0C => false,
|
||||||
0x0D => storage::min_pin_length(&mut env).unwrap() as u64,
|
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,
|
0x10 => env.customization().max_rp_ids_length() as u64,
|
||||||
0x14 => storage::remaining_credentials(&mut env).unwrap() 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 mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
|
||||||
|
|
||||||
let extensions = MakeCredentialExtensions {
|
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()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let mut make_credential_params = create_minimal_make_credential_parameters();
|
let mut make_credential_params = create_minimal_make_credential_parameters();
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ 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::{MAX_LARGE_BLOB_ARRAY_SIZE, 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,
|
||||||
@@ -90,7 +89,7 @@ pub fn get_credential(
|
|||||||
key: usize,
|
key: usize,
|
||||||
) -> Result<PublicKeyCredentialSource, Ctap2StatusCode> {
|
) -> Result<PublicKeyCredentialSource, Ctap2StatusCode> {
|
||||||
let min_key = key::CREDENTIALS.start;
|
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);
|
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
|
||||||
}
|
}
|
||||||
let credential_entry = env
|
let credential_entry = env
|
||||||
@@ -154,15 +153,16 @@ pub fn store_credential(
|
|||||||
env: &mut impl Env,
|
env: &mut impl Env,
|
||||||
new_credential: PublicKeyCredentialSource,
|
new_credential: PublicKeyCredentialSource,
|
||||||
) -> Result<(), Ctap2StatusCode> {
|
) -> 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.
|
// Holds the key of the existing credential if this is an update.
|
||||||
let mut old_key = None;
|
let mut old_key = None;
|
||||||
let min_key = key::CREDENTIALS.start;
|
let min_key = key::CREDENTIALS.start;
|
||||||
// Holds whether a key is used (indices are shifted by min_key).
|
// 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 mut iter_result = Ok(());
|
||||||
let iter = iter_credentials(env, &mut iter_result)?;
|
let iter = iter_credentials(env, &mut iter_result)?;
|
||||||
for (key, credential) in iter {
|
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);
|
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
|
||||||
}
|
}
|
||||||
keys[key - min_key] = true;
|
keys[key - min_key] = true;
|
||||||
@@ -176,14 +176,14 @@ pub fn store_credential(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
iter_result?;
|
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);
|
return Err(Ctap2StatusCode::CTAP2_ERR_KEY_STORE_FULL);
|
||||||
}
|
}
|
||||||
let key = match old_key {
|
let key = match old_key {
|
||||||
// This is a new credential being added, we need to allocate a free key. We choose the
|
// This is a new credential being added, we need to allocate a free key. We choose the
|
||||||
// first available key.
|
// first available key.
|
||||||
None => key::CREDENTIALS
|
None => key::CREDENTIALS
|
||||||
.take(MAX_SUPPORTED_RESIDENT_KEYS)
|
.take(max_supported_resident_keys)
|
||||||
.find(|key| !keys[key - min_key])
|
.find(|key| !keys[key - min_key])
|
||||||
.ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?,
|
.ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?,
|
||||||
// This is an existing credential being updated, we reuse its key.
|
// 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.
|
/// Returns the estimated number of credentials that can still be stored.
|
||||||
pub fn remaining_credentials(env: &mut impl Env) -> Result<usize, Ctap2StatusCode> {
|
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)?)
|
.checked_sub(count_credentials(env)?)
|
||||||
.ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)
|
.ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)
|
||||||
}
|
}
|
||||||
@@ -459,7 +460,7 @@ pub fn commit_large_blob_array(
|
|||||||
large_blob_array: &[u8],
|
large_blob_array: &[u8],
|
||||||
) -> Result<(), Ctap2StatusCode> {
|
) -> Result<(), Ctap2StatusCode> {
|
||||||
// This input should have been caught at caller level.
|
// 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);
|
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
|
||||||
}
|
}
|
||||||
Ok(fragment::write(
|
Ok(fragment::write(
|
||||||
@@ -768,7 +769,7 @@ mod test {
|
|||||||
assert_eq!(count_credentials(&mut env).unwrap(), 0);
|
assert_eq!(count_credentials(&mut env).unwrap(), 0);
|
||||||
|
|
||||||
let mut credential_ids = vec![];
|
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 user_handle = (i as u32).to_ne_bytes().to_vec();
|
||||||
let credential_source = create_credential_source(env.rng(), "example.com", user_handle);
|
let credential_source = create_credential_source(env.rng(), "example.com", user_handle);
|
||||||
credential_ids.push(credential_source.credential_id.clone());
|
credential_ids.push(credential_source.credential_id.clone());
|
||||||
@@ -835,7 +836,8 @@ mod test {
|
|||||||
let mut env = TestEnv::new();
|
let mut env = TestEnv::new();
|
||||||
assert_eq!(count_credentials(&mut env).unwrap(), 0);
|
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 user_handle = (i as u32).to_ne_bytes().to_vec();
|
||||||
let credential_source = create_credential_source(env.rng(), "example.com", user_handle);
|
let credential_source = create_credential_source(env.rng(), "example.com", user_handle);
|
||||||
assert!(store_credential(&mut env, credential_source).is_ok());
|
assert!(store_credential(&mut env, credential_source).is_ok());
|
||||||
@@ -844,7 +846,7 @@ mod test {
|
|||||||
let credential_source = create_credential_source(
|
let credential_source = create_credential_source(
|
||||||
env.rng(),
|
env.rng(),
|
||||||
"example.com",
|
"example.com",
|
||||||
vec![MAX_SUPPORTED_RESIDENT_KEYS as u8],
|
vec![max_supported_resident_keys as u8],
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
store_credential(&mut env, credential_source),
|
store_credential(&mut env, credential_source),
|
||||||
@@ -852,7 +854,7 @@ mod test {
|
|||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
count_credentials(&mut env).unwrap(),
|
count_credentials(&mut env).unwrap(),
|
||||||
MAX_SUPPORTED_RESIDENT_KEYS
|
max_supported_resident_keys
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -883,7 +885,8 @@ mod test {
|
|||||||
);
|
);
|
||||||
|
|
||||||
reset(&mut env).unwrap();
|
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 user_handle = (i as u32).to_ne_bytes().to_vec();
|
||||||
let credential_source = create_credential_source(env.rng(), "example.com", user_handle);
|
let credential_source = create_credential_source(env.rng(), "example.com", user_handle);
|
||||||
assert!(store_credential(&mut env, credential_source).is_ok());
|
assert!(store_credential(&mut env, credential_source).is_ok());
|
||||||
@@ -892,7 +895,7 @@ mod test {
|
|||||||
let credential_source = create_credential_source(
|
let credential_source = create_credential_source(
|
||||||
env.rng(),
|
env.rng(),
|
||||||
"example.com",
|
"example.com",
|
||||||
vec![MAX_SUPPORTED_RESIDENT_KEYS as u8],
|
vec![max_supported_resident_keys as u8],
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
store_credential(&mut env, credential_source),
|
store_credential(&mut env, credential_source),
|
||||||
@@ -900,7 +903,7 @@ mod test {
|
|||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
count_credentials(&mut env).unwrap(),
|
count_credentials(&mut env).unwrap(),
|
||||||
MAX_SUPPORTED_RESIDENT_KEYS
|
max_supported_resident_keys
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1147,7 +1150,7 @@ mod test {
|
|||||||
let mut env = TestEnv::new();
|
let mut env = TestEnv::new();
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
MAX_LARGE_BLOB_ARRAY_SIZE
|
env.customization().max_large_blob_array_size()
|
||||||
<= env.store().max_value_length()
|
<= env.store().max_value_length()
|
||||||
* (key::LARGE_BLOB_SHARDS.end - key::LARGE_BLOB_SHARDS.start)
|
* (key::LARGE_BLOB_SHARDS.end - key::LARGE_BLOB_SHARDS.start)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -84,8 +84,9 @@ make_partition! {
|
|||||||
|
|
||||||
/// The credentials.
|
/// The credentials.
|
||||||
///
|
///
|
||||||
/// Depending on `MAX_SUPPORTED_RESIDENT_KEYS`, only a prefix of those keys is used. Each
|
/// Depending on `Customization::max_supported_resident_keys()`, only a prefix of those keys is used.
|
||||||
/// board may configure `MAX_SUPPORTED_RESIDENT_KEYS` depending on the storage size.
|
/// Each board may configure `Customization::max_supported_resident_keys()` depending on the
|
||||||
|
/// storage size.
|
||||||
CREDENTIALS = 1700..2000;
|
CREDENTIALS = 1700..2000;
|
||||||
|
|
||||||
/// Storage for the serialized large blob array.
|
/// Storage for the serialized large blob array.
|
||||||
@@ -138,11 +139,17 @@ make_partition! {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::api::customization::Customization;
|
||||||
|
use crate::env::test::TestEnv;
|
||||||
|
use crate::env::Env;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn enough_credentials() {
|
fn enough_credentials() {
|
||||||
use crate::ctap::customization::MAX_SUPPORTED_RESIDENT_KEYS;
|
let env = TestEnv::new();
|
||||||
assert!(MAX_SUPPORTED_RESIDENT_KEYS <= CREDENTIALS.end - CREDENTIALS.start);
|
assert!(
|
||||||
|
env.customization().max_supported_resident_keys()
|
||||||
|
<= CREDENTIALS.end - CREDENTIALS.start
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
28
src/env/test/customization.rs
vendored
28
src/env/test/customization.rs
vendored
@@ -14,7 +14,11 @@ pub struct TestCustomization {
|
|||||||
pub max_pin_retries: u8,
|
pub max_pin_retries: u8,
|
||||||
pub use_batch_attestation: bool,
|
pub use_batch_attestation: bool,
|
||||||
pub use_signature_counter: 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_rp_ids_length: usize,
|
||||||
|
pub max_supported_resident_keys: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Customization for TestCustomization {
|
impl Customization for TestCustomization {
|
||||||
@@ -62,9 +66,25 @@ impl Customization for TestCustomization {
|
|||||||
self.use_signature_counter
|
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 {
|
fn max_rp_ids_length(&self) -> usize {
|
||||||
self.max_rp_ids_length
|
self.max_rp_ids_length
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn max_supported_resident_keys(&self) -> usize {
|
||||||
|
self.max_supported_resident_keys
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<CustomizationImpl> for TestCustomization {
|
impl From<CustomizationImpl> for TestCustomization {
|
||||||
@@ -80,7 +100,11 @@ impl From<CustomizationImpl> for TestCustomization {
|
|||||||
max_pin_retries,
|
max_pin_retries,
|
||||||
use_batch_attestation,
|
use_batch_attestation,
|
||||||
use_signature_counter,
|
use_signature_counter,
|
||||||
|
max_cred_blob_length,
|
||||||
|
max_credential_count_in_list,
|
||||||
|
max_large_blob_array_size,
|
||||||
max_rp_ids_length,
|
max_rp_ids_length,
|
||||||
|
max_supported_resident_keys,
|
||||||
} = c;
|
} = c;
|
||||||
|
|
||||||
let default_min_pin_length_rp_ids = default_min_pin_length_rp_ids
|
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,
|
max_pin_retries,
|
||||||
use_batch_attestation,
|
use_batch_attestation,
|
||||||
use_signature_counter,
|
use_signature_counter,
|
||||||
|
max_cred_blob_length,
|
||||||
|
max_credential_count_in_list,
|
||||||
|
max_large_blob_array_size,
|
||||||
max_rp_ids_length,
|
max_rp_ids_length,
|
||||||
|
max_supported_resident_keys,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user