diff --git a/examples/erase_storage.rs b/examples/erase_storage.rs index da0e445..29ba5ea 100644 --- a/examples/erase_storage.rs +++ b/examples/erase_storage.rs @@ -17,7 +17,7 @@ extern crate lang_items; use core::fmt::Write; -use ctap2::embedded_flash::new_storage; +use ctap2::env::tock::steal_storage; use libtock_drivers::console::Console; use libtock_drivers::led; use libtock_drivers::result::FlexUnwrap; @@ -37,7 +37,7 @@ fn is_page_erased(storage: &dyn Storage, page: usize) -> bool { fn main() { led::get(1).flex_unwrap().on().flex_unwrap(); // red on dongle - let mut storage = new_storage().unwrap(); + let mut storage = unsafe { steal_storage() }.unwrap(); let num_pages = storage.num_pages(); writeln!(Console::new(), "Erase {} pages of storage:", num_pages).unwrap(); for page in 0..num_pages { diff --git a/examples/store_latency.rs b/examples/store_latency.rs index 2ea3a91..27f2af7 100644 --- a/examples/store_latency.rs +++ b/examples/store_latency.rs @@ -21,7 +21,8 @@ use alloc::string::{String, ToString}; use alloc::vec::Vec; use alloc::{format, vec}; use core::fmt::Write; -use ctap2::embedded_flash::{new_storage, Storage}; +use ctap2::env::tock::{steal_storage, TockEnv}; +use ctap2::env::Env; use libtock_drivers::console::Console; use libtock_drivers::timer::{self, Duration, Timer, Timestamp}; use persistent_store::Store; @@ -40,9 +41,9 @@ fn measure(timer: &Timer, operation: impl FnOnce() -> T) -> (T, Duration } // Only use one store at a time. -unsafe fn boot_store(erase: bool) -> Store { +unsafe fn boot_store(erase: bool) -> Store<::Storage> { use persistent_store::Storage; - let mut storage = new_storage().unwrap(); + let mut storage = steal_storage().unwrap(); let num_pages = storage.num_pages(); if erase { for page in 0..num_pages { @@ -59,7 +60,7 @@ struct StorageConfig { fn storage_config() -> StorageConfig { use persistent_store::Storage; - let storage = new_storage().unwrap(); + let storage = unsafe { steal_storage() }.unwrap(); StorageConfig { num_pages: storage.num_pages(), } diff --git a/src/api/mod.rs b/src/api/mod.rs new file mode 100644 index 0000000..b8c5bcb --- /dev/null +++ b/src/api/mod.rs @@ -0,0 +1,6 @@ +//! APIs for the environment. +//! +//! The [environment](crate::env::Env) is split into components. Each component has an API described +//! by a trait. This module gathers the API of those components. + +pub mod upgrade_storage; diff --git a/src/embedded_flash/helper.rs b/src/api/upgrade_storage/helper.rs similarity index 100% rename from src/embedded_flash/helper.rs rename to src/api/upgrade_storage/helper.rs diff --git a/src/embedded_flash/upgrade_storage.rs b/src/api/upgrade_storage/mod.rs similarity index 98% rename from src/embedded_flash/upgrade_storage.rs rename to src/api/upgrade_storage/mod.rs index 44e8583..408fcf1 100644 --- a/src/embedded_flash/upgrade_storage.rs +++ b/src/api/upgrade_storage/mod.rs @@ -14,6 +14,8 @@ use persistent_store::StorageResult; +pub(crate) mod helper; + /// Accessors to storage locations used for upgrading from a CTAP command. pub trait UpgradeStorage { /// Reads a slice of the partition, if within bounds. diff --git a/src/ctap/client_pin.rs b/src/ctap/client_pin.rs index d8ab06e..ce3c24f 100644 --- a/src/ctap/client_pin.rs +++ b/src/ctap/client_pin.rs @@ -19,8 +19,9 @@ use super::data_formats::{ use super::pin_protocol::{verify_pin_uv_auth_token, PinProtocol, SharedSecret}; use super::response::{AuthenticatorClientPinResponse, ResponseData}; use super::status_code::Ctap2StatusCode; -use super::storage::PersistentStore; use super::token_state::PinUvAuthTokenState; +use crate::ctap::storage; +use crate::env::Env; use alloc::boxed::Box; use alloc::str; use alloc::string::String; @@ -76,12 +77,12 @@ fn decrypt_pin( /// padding. Next, it is checked against the PIN policy. Last, it is hashed and /// truncated for persistent storage. fn check_and_store_new_pin( - persistent_store: &mut PersistentStore, + env: &mut impl Env, shared_secret: &dyn SharedSecret, new_pin_enc: Vec, ) -> Result<(), Ctap2StatusCode> { let pin = decrypt_pin(shared_secret, new_pin_enc)?; - let min_pin_length = persistent_store.min_pin_length()? as usize; + let min_pin_length = storage::min_pin_length(env)? as usize; let pin_length = str::from_utf8(&pin).unwrap_or("").chars().count(); if pin_length < min_pin_length || pin.len() == PIN_PADDED_LENGTH { return Err(Ctap2StatusCode::CTAP2_ERR_PIN_POLICY_VIOLATION); @@ -89,7 +90,7 @@ fn check_and_store_new_pin( let mut pin_hash = [0u8; PIN_AUTH_LENGTH]; pin_hash.copy_from_slice(&Sha256::hash(&pin[..])[..PIN_AUTH_LENGTH]); // The PIN length is always < PIN_PADDED_LENGTH < 256. - persistent_store.set_pin(&pin_hash, pin_length as u8)?; + storage::set_pin(env, &pin_hash, pin_length as u8)?; Ok(()) } @@ -157,26 +158,25 @@ impl ClientPin { /// Also, in case of failure, the key agreement key is randomly reset. fn verify_pin_hash_enc( &mut self, - rng: &mut impl Rng256, - persistent_store: &mut PersistentStore, + env: &mut impl Env, pin_uv_auth_protocol: PinUvAuthProtocol, shared_secret: &dyn SharedSecret, pin_hash_enc: Vec, ) -> Result<(), Ctap2StatusCode> { - match persistent_store.pin_hash()? { + match storage::pin_hash(env)? { Some(pin_hash) => { if self.consecutive_pin_mismatches >= 3 { return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_BLOCKED); } - persistent_store.decr_pin_retries()?; + storage::decr_pin_retries(env)?; let pin_hash_dec = shared_secret .decrypt(&pin_hash_enc) .map_err(|_| Ctap2StatusCode::CTAP2_ERR_PIN_INVALID)?; if !bool::from(pin_hash.ct_eq(&pin_hash_dec)) { self.get_mut_pin_protocol(pin_uv_auth_protocol) - .regenerate(rng); - if persistent_store.pin_retries()? == 0 { + .regenerate(env.rng()); + if storage::pin_retries(env)? == 0 { return Err(Ctap2StatusCode::CTAP2_ERR_PIN_BLOCKED); } self.consecutive_pin_mismatches += 1; @@ -189,19 +189,19 @@ impl ClientPin { // This status code is not explicitly mentioned in the specification. None => return Err(Ctap2StatusCode::CTAP2_ERR_PUAT_REQUIRED), } - persistent_store.reset_pin_retries()?; + storage::reset_pin_retries(env)?; self.consecutive_pin_mismatches = 0; Ok(()) } fn process_get_pin_retries( &self, - persistent_store: &PersistentStore, + env: &mut impl Env, ) -> Result { Ok(AuthenticatorClientPinResponse { key_agreement: None, pin_uv_auth_token: None, - retries: Some(persistent_store.pin_retries()? as u64), + retries: Some(storage::pin_retries(env)? as u64), power_cycle_state: Some(self.consecutive_pin_mismatches >= 3), }) } @@ -224,7 +224,7 @@ impl ClientPin { fn process_set_pin( &mut self, - persistent_store: &mut PersistentStore, + env: &mut impl Env, client_pin_params: AuthenticatorClientPinParameters, ) -> Result<(), Ctap2StatusCode> { let AuthenticatorClientPinParameters { @@ -238,21 +238,20 @@ impl ClientPin { let pin_uv_auth_param = ok_or_missing(pin_uv_auth_param)?; let new_pin_enc = ok_or_missing(new_pin_enc)?; - if persistent_store.pin_hash()?.is_some() { + if storage::pin_hash(env)?.is_some() { return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID); } let shared_secret = self.get_shared_secret(pin_uv_auth_protocol, key_agreement)?; shared_secret.verify(&new_pin_enc, &pin_uv_auth_param)?; - check_and_store_new_pin(persistent_store, shared_secret.as_ref(), new_pin_enc)?; - persistent_store.reset_pin_retries()?; + check_and_store_new_pin(env, shared_secret.as_ref(), new_pin_enc)?; + storage::reset_pin_retries(env)?; Ok(()) } fn process_change_pin( &mut self, - rng: &mut impl Rng256, - persistent_store: &mut PersistentStore, + env: &mut impl Env, client_pin_params: AuthenticatorClientPinParameters, ) -> Result<(), Ctap2StatusCode> { let AuthenticatorClientPinParameters { @@ -268,7 +267,7 @@ impl ClientPin { let new_pin_enc = ok_or_missing(new_pin_enc)?; let pin_hash_enc = ok_or_missing(pin_hash_enc)?; - if persistent_store.pin_retries()? == 0 { + if storage::pin_retries(env)? == 0 { return Err(Ctap2StatusCode::CTAP2_ERR_PIN_BLOCKED); } let shared_secret = self.get_shared_secret(pin_uv_auth_protocol, key_agreement)?; @@ -276,23 +275,21 @@ impl ClientPin { auth_param_data.extend(&pin_hash_enc); shared_secret.verify(&auth_param_data, &pin_uv_auth_param)?; self.verify_pin_hash_enc( - rng, - persistent_store, + env, pin_uv_auth_protocol, shared_secret.as_ref(), pin_hash_enc, )?; - check_and_store_new_pin(persistent_store, shared_secret.as_ref(), new_pin_enc)?; - self.pin_protocol_v1.reset_pin_uv_auth_token(rng); - self.pin_protocol_v2.reset_pin_uv_auth_token(rng); + check_and_store_new_pin(env, shared_secret.as_ref(), new_pin_enc)?; + self.pin_protocol_v1.reset_pin_uv_auth_token(env.rng()); + self.pin_protocol_v2.reset_pin_uv_auth_token(env.rng()); Ok(()) } fn process_get_pin_token( &mut self, - rng: &mut impl Rng256, - persistent_store: &mut PersistentStore, + env: &mut impl Env, client_pin_params: AuthenticatorClientPinParameters, now: ClockValue, ) -> Result { @@ -310,28 +307,27 @@ impl ClientPin { return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER); } - if persistent_store.pin_retries()? == 0 { + if storage::pin_retries(env)? == 0 { return Err(Ctap2StatusCode::CTAP2_ERR_PIN_BLOCKED); } let shared_secret = self.get_shared_secret(pin_uv_auth_protocol, key_agreement)?; self.verify_pin_hash_enc( - rng, - persistent_store, + env, pin_uv_auth_protocol, shared_secret.as_ref(), pin_hash_enc, )?; - if persistent_store.has_force_pin_change()? { + if storage::has_force_pin_change(env)? { return Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID); } - self.pin_protocol_v1.reset_pin_uv_auth_token(rng); - self.pin_protocol_v2.reset_pin_uv_auth_token(rng); + self.pin_protocol_v1.reset_pin_uv_auth_token(env.rng()); + self.pin_protocol_v2.reset_pin_uv_auth_token(env.rng()); self.pin_uv_auth_token_state .begin_using_pin_uv_auth_token(now); self.pin_uv_auth_token_state.set_default_permissions(); let pin_uv_auth_token = shared_secret.encrypt( - rng, + env.rng(), self.get_pin_protocol(pin_uv_auth_protocol) .get_pin_uv_auth_token(), )?; @@ -361,8 +357,7 @@ impl ClientPin { fn process_get_pin_uv_auth_token_using_pin_with_permissions( &mut self, - rng: &mut impl Rng256, - persistent_store: &mut PersistentStore, + env: &mut impl Env, mut client_pin_params: AuthenticatorClientPinParameters, now: ClockValue, ) -> Result { @@ -380,7 +375,7 @@ impl ClientPin { return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER); } - let response = self.process_get_pin_token(rng, persistent_store, client_pin_params, now)?; + let response = self.process_get_pin_token(env, client_pin_params, now)?; self.pin_uv_auth_token_state.set_permissions(permissions); self.pin_uv_auth_token_state .set_permissions_rp_id(permissions_rp_id); @@ -391,28 +386,25 @@ impl ClientPin { /// Processes the authenticatorClientPin command. pub fn process_command( &mut self, - rng: &mut impl Rng256, - persistent_store: &mut PersistentStore, + env: &mut impl Env, client_pin_params: AuthenticatorClientPinParameters, now: ClockValue, ) -> Result { let response = match client_pin_params.sub_command { - ClientPinSubCommand::GetPinRetries => { - Some(self.process_get_pin_retries(persistent_store)?) - } + ClientPinSubCommand::GetPinRetries => Some(self.process_get_pin_retries(env)?), ClientPinSubCommand::GetKeyAgreement => { Some(self.process_get_key_agreement(client_pin_params)?) } ClientPinSubCommand::SetPin => { - self.process_set_pin(persistent_store, client_pin_params)?; + self.process_set_pin(env, client_pin_params)?; None } ClientPinSubCommand::ChangePin => { - self.process_change_pin(rng, persistent_store, client_pin_params)?; + self.process_change_pin(env, client_pin_params)?; None } ClientPinSubCommand::GetPinToken => { - Some(self.process_get_pin_token(rng, persistent_store, client_pin_params, now)?) + Some(self.process_get_pin_token(env, client_pin_params, now)?) } ClientPinSubCommand::GetPinUvAuthTokenUsingUvWithPermissions => Some( self.process_get_pin_uv_auth_token_using_uv_with_permissions(client_pin_params)?, @@ -420,8 +412,7 @@ impl ClientPin { ClientPinSubCommand::GetUvRetries => Some(self.process_get_uv_retries()?), ClientPinSubCommand::GetPinUvAuthTokenUsingPinWithPermissions => Some( self.process_get_pin_uv_auth_token_using_pin_with_permissions( - rng, - persistent_store, + env, client_pin_params, now, )?, @@ -570,11 +561,10 @@ impl ClientPin { pin_uv_auth_token: [u8; PIN_TOKEN_LENGTH], pin_uv_auth_protocol: PinUvAuthProtocol, ) -> ClientPin { - use crypto::rng256::ThreadRng256; - let mut rng = ThreadRng256 {}; + let mut env = crate::env::test::TestEnv::new(); let (key_agreement_key_v1, key_agreement_key_v2) = match pin_uv_auth_protocol { - PinUvAuthProtocol::V1 => (key_agreement_key, crypto::ecdh::SecKey::gensk(&mut rng)), - PinUvAuthProtocol::V2 => (crypto::ecdh::SecKey::gensk(&mut rng), key_agreement_key), + PinUvAuthProtocol::V1 => (key_agreement_key, crypto::ecdh::SecKey::gensk(env.rng())), + PinUvAuthProtocol::V2 => (crypto::ecdh::SecKey::gensk(env.rng()), key_agreement_key), }; let mut pin_uv_auth_token_state = PinUvAuthTokenState::new(); pin_uv_auth_token_state.set_permissions(0xFF); @@ -594,29 +584,29 @@ impl ClientPin { mod test { use super::super::pin_protocol::authenticate_pin_uv_auth_token; use super::*; + use crate::env::test::TestEnv; use alloc::vec; - use crypto::rng256::ThreadRng256; use libtock_drivers::timer::Duration; const CLOCK_FREQUENCY_HZ: usize = 32768; const DUMMY_CLOCK_VALUE: ClockValue = ClockValue::new(0, CLOCK_FREQUENCY_HZ); /// Stores a PIN hash corresponding to the dummy PIN "1234". - fn set_standard_pin(persistent_store: &mut PersistentStore) { + fn set_standard_pin(env: &mut TestEnv) { let mut pin = [0u8; 64]; pin[..4].copy_from_slice(b"1234"); let mut pin_hash = [0u8; 16]; pin_hash.copy_from_slice(&Sha256::hash(&pin[..])[..16]); - persistent_store.set_pin(&pin_hash, 4).unwrap(); + storage::set_pin(env, &pin_hash, 4).unwrap(); } /// Fails on PINs bigger than 64 bytes. fn encrypt_pin(shared_secret: &dyn SharedSecret, pin: Vec) -> Vec { assert!(pin.len() <= 64); - let mut rng = ThreadRng256 {}; + let mut env = TestEnv::new(); let mut padded_pin = [0u8; 64]; padded_pin[..pin.len()].copy_from_slice(&pin[..]); - shared_secret.encrypt(&mut rng, &padded_pin).unwrap() + shared_secret.encrypt(env.rng(), &padded_pin).unwrap() } /// Generates a ClientPin instance and a shared secret for testing. @@ -628,8 +618,8 @@ mod test { fn create_client_pin_and_shared_secret( pin_uv_auth_protocol: PinUvAuthProtocol, ) -> (ClientPin, Box) { - let mut rng = ThreadRng256 {}; - let key_agreement_key = crypto::ecdh::SecKey::gensk(&mut rng); + let mut env = TestEnv::new(); + let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); let pk = key_agreement_key.genpk(); let key_agreement = CoseKey::from(pk); let pin_uv_auth_token = [0x91; PIN_TOKEN_LENGTH]; @@ -649,7 +639,7 @@ mod test { pin_uv_auth_protocol: PinUvAuthProtocol, sub_command: ClientPinSubCommand, ) -> (ClientPin, AuthenticatorClientPinParameters) { - let mut rng = ThreadRng256 {}; + let mut env = TestEnv::new(); let (client_pin, shared_secret) = create_client_pin_and_shared_secret(pin_uv_auth_protocol); let pin = b"1234"; @@ -658,12 +648,12 @@ mod test { let pin_hash = Sha256::hash(&padded_pin); let new_pin_enc = shared_secret .as_ref() - .encrypt(&mut rng, &padded_pin) + .encrypt(env.rng(), &padded_pin) .unwrap(); let pin_uv_auth_param = shared_secret.as_ref().authenticate(&new_pin_enc); let pin_hash_enc = shared_secret .as_ref() - .encrypt(&mut rng, &pin_hash[..16]) + .encrypt(env.rng(), &pin_hash[..16]) .unwrap(); let (permissions, permissions_rp_id) = match sub_command { ClientPinSubCommand::GetPinUvAuthTokenUsingUvWithPermissions @@ -691,8 +681,8 @@ mod test { #[test] fn test_mix_pin_protocols() { - let mut rng = ThreadRng256 {}; - let client_pin = ClientPin::new(&mut rng); + let mut env = TestEnv::new(); + let client_pin = ClientPin::new(env.rng()); let pin_protocol_v1 = client_pin.get_pin_protocol(PinUvAuthProtocol::V1); let pin_protocol_v2 = client_pin.get_pin_protocol(PinUvAuthProtocol::V2); let message = vec![0xAA; 16]; @@ -703,38 +693,37 @@ mod test { let shared_secret_v2 = pin_protocol_v2 .decapsulate(pin_protocol_v2.get_public_key(), PinUvAuthProtocol::V2) .unwrap(); - let ciphertext = shared_secret_v1.encrypt(&mut rng, &message).unwrap(); + let ciphertext = shared_secret_v1.encrypt(env.rng(), &message).unwrap(); let plaintext = shared_secret_v2.decrypt(&ciphertext).unwrap(); assert_ne!(&message, &plaintext); - let ciphertext = shared_secret_v2.encrypt(&mut rng, &message).unwrap(); + let ciphertext = shared_secret_v2.encrypt(env.rng(), &message).unwrap(); let plaintext = shared_secret_v1.decrypt(&ciphertext).unwrap(); assert_ne!(&message, &plaintext); let fake_secret_v1 = pin_protocol_v1 .decapsulate(pin_protocol_v2.get_public_key(), PinUvAuthProtocol::V1) .unwrap(); - let ciphertext = shared_secret_v1.encrypt(&mut rng, &message).unwrap(); + let ciphertext = shared_secret_v1.encrypt(env.rng(), &message).unwrap(); let plaintext = fake_secret_v1.decrypt(&ciphertext).unwrap(); assert_ne!(&message, &plaintext); - let ciphertext = fake_secret_v1.encrypt(&mut rng, &message).unwrap(); + let ciphertext = fake_secret_v1.encrypt(env.rng(), &message).unwrap(); let plaintext = shared_secret_v1.decrypt(&ciphertext).unwrap(); assert_ne!(&message, &plaintext); let fake_secret_v2 = pin_protocol_v2 .decapsulate(pin_protocol_v1.get_public_key(), PinUvAuthProtocol::V2) .unwrap(); - let ciphertext = shared_secret_v2.encrypt(&mut rng, &message).unwrap(); + let ciphertext = shared_secret_v2.encrypt(env.rng(), &message).unwrap(); let plaintext = fake_secret_v2.decrypt(&ciphertext).unwrap(); assert_ne!(&message, &plaintext); - let ciphertext = fake_secret_v2.encrypt(&mut rng, &message).unwrap(); + let ciphertext = fake_secret_v2.encrypt(env.rng(), &message).unwrap(); let plaintext = shared_secret_v2.decrypt(&ciphertext).unwrap(); assert_ne!(&message, &plaintext); } fn test_helper_verify_pin_hash_enc(pin_uv_auth_protocol: PinUvAuthProtocol) { - let mut rng = ThreadRng256 {}; - let mut persistent_store = PersistentStore::new(&mut rng); - let mut client_pin = ClientPin::new(&mut rng); + let mut env = TestEnv::new(); + let mut client_pin = ClientPin::new(env.rng()); let pin_protocol = client_pin.get_pin_protocol(pin_uv_auth_protocol); let shared_secret = pin_protocol .decapsulate(pin_protocol.get_public_key(), pin_uv_auth_protocol) @@ -744,13 +733,15 @@ mod test { 0x01, 0xD9, 0x88, 0x40, 0x50, 0xBB, 0xD0, 0x7A, 0x23, 0x1A, 0xEB, 0x69, 0xD8, 0x36, 0xC4, 0x12, ]; - persistent_store.set_pin(&pin_hash, 4).unwrap(); + storage::set_pin(&mut env, &pin_hash, 4).unwrap(); - let pin_hash_enc = shared_secret.as_ref().encrypt(&mut rng, &pin_hash).unwrap(); + let pin_hash_enc = shared_secret + .as_ref() + .encrypt(env.rng(), &pin_hash) + .unwrap(); assert_eq!( client_pin.verify_pin_hash_enc( - &mut rng, - &mut persistent_store, + &mut env, pin_uv_auth_protocol, shared_secret.as_ref(), pin_hash_enc @@ -761,8 +752,7 @@ mod test { let pin_hash_enc = vec![0xEE; 16]; assert_eq!( client_pin.verify_pin_hash_enc( - &mut rng, - &mut persistent_store, + &mut env, pin_uv_auth_protocol, shared_secret.as_ref(), pin_hash_enc @@ -770,12 +760,14 @@ mod test { Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID) ); - let pin_hash_enc = shared_secret.as_ref().encrypt(&mut rng, &pin_hash).unwrap(); + let pin_hash_enc = shared_secret + .as_ref() + .encrypt(env.rng(), &pin_hash) + .unwrap(); client_pin.consecutive_pin_mismatches = 3; assert_eq!( client_pin.verify_pin_hash_enc( - &mut rng, - &mut persistent_store, + &mut env, pin_uv_auth_protocol, shared_secret.as_ref(), pin_hash_enc @@ -787,8 +779,7 @@ mod test { let pin_hash_enc = vec![0x77; PIN_AUTH_LENGTH - 1]; assert_eq!( client_pin.verify_pin_hash_enc( - &mut rng, - &mut persistent_store, + &mut env, pin_uv_auth_protocol, shared_secret.as_ref(), pin_hash_enc @@ -799,8 +790,7 @@ mod test { let pin_hash_enc = vec![0x77; PIN_AUTH_LENGTH + 1]; assert_eq!( client_pin.verify_pin_hash_enc( - &mut rng, - &mut persistent_store, + &mut env, pin_uv_auth_protocol, shared_secret.as_ref(), pin_hash_enc @@ -824,21 +814,15 @@ mod test { pin_uv_auth_protocol, ClientPinSubCommand::GetPinRetries, ); - let mut rng = ThreadRng256 {}; - let mut persistent_store = PersistentStore::new(&mut rng); + let mut env = TestEnv::new(); let expected_response = Some(AuthenticatorClientPinResponse { key_agreement: None, pin_uv_auth_token: None, - retries: Some(persistent_store.pin_retries().unwrap() as u64), + retries: Some(storage::pin_retries(&mut env).unwrap() as u64), power_cycle_state: Some(false), }); assert_eq!( - client_pin.process_command( - &mut rng, - &mut persistent_store, - params.clone(), - DUMMY_CLOCK_VALUE - ), + client_pin.process_command(&mut env, params.clone(), DUMMY_CLOCK_VALUE), Ok(ResponseData::AuthenticatorClientPin(expected_response)) ); @@ -846,11 +830,11 @@ mod test { let expected_response = Some(AuthenticatorClientPinResponse { key_agreement: None, pin_uv_auth_token: None, - retries: Some(persistent_store.pin_retries().unwrap() as u64), + retries: Some(storage::pin_retries(&mut env).unwrap() as u64), power_cycle_state: Some(true), }); assert_eq!( - client_pin.process_command(&mut rng, &mut persistent_store, params, DUMMY_CLOCK_VALUE), + client_pin.process_command(&mut env, params, DUMMY_CLOCK_VALUE), Ok(ResponseData::AuthenticatorClientPin(expected_response)) ); } @@ -870,8 +854,7 @@ mod test { pin_uv_auth_protocol, ClientPinSubCommand::GetKeyAgreement, ); - let mut rng = ThreadRng256 {}; - let mut persistent_store = PersistentStore::new(&mut rng); + let mut env = TestEnv::new(); let expected_response = Some(AuthenticatorClientPinResponse { key_agreement: params.key_agreement.clone(), pin_uv_auth_token: None, @@ -879,7 +862,7 @@ mod test { power_cycle_state: None, }); assert_eq!( - client_pin.process_command(&mut rng, &mut persistent_store, params, DUMMY_CLOCK_VALUE), + client_pin.process_command(&mut env, params, DUMMY_CLOCK_VALUE), Ok(ResponseData::AuthenticatorClientPin(expected_response)) ); } @@ -897,10 +880,9 @@ mod test { fn test_helper_process_set_pin(pin_uv_auth_protocol: PinUvAuthProtocol) { let (mut client_pin, params) = create_client_pin_and_parameters(pin_uv_auth_protocol, ClientPinSubCommand::SetPin); - let mut rng = ThreadRng256 {}; - let mut persistent_store = PersistentStore::new(&mut rng); + let mut env = TestEnv::new(); assert_eq!( - client_pin.process_command(&mut rng, &mut persistent_store, params, DUMMY_CLOCK_VALUE), + client_pin.process_command(&mut env, params, DUMMY_CLOCK_VALUE), Ok(ResponseData::AuthenticatorClientPin(None)) ); } @@ -925,41 +907,30 @@ mod test { params.pin_uv_auth_protocol, ) .unwrap(); - let mut rng = ThreadRng256 {}; - let mut persistent_store = PersistentStore::new(&mut rng); - set_standard_pin(&mut persistent_store); + let mut env = TestEnv::new(); + set_standard_pin(&mut env); let mut auth_param_data = params.new_pin_enc.clone().unwrap(); auth_param_data.extend(params.pin_hash_enc.as_ref().unwrap()); let pin_uv_auth_param = shared_secret.authenticate(&auth_param_data); params.pin_uv_auth_param = Some(pin_uv_auth_param); assert_eq!( - client_pin.process_command( - &mut rng, - &mut persistent_store, - params.clone(), - DUMMY_CLOCK_VALUE - ), + client_pin.process_command(&mut env, params.clone(), DUMMY_CLOCK_VALUE), Ok(ResponseData::AuthenticatorClientPin(None)) ); let mut bad_params = params.clone(); bad_params.pin_hash_enc = Some(vec![0xEE; 16]); assert_eq!( - client_pin.process_command( - &mut rng, - &mut persistent_store, - bad_params, - DUMMY_CLOCK_VALUE - ), + client_pin.process_command(&mut env, bad_params, DUMMY_CLOCK_VALUE), Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID) ); - while persistent_store.pin_retries().unwrap() > 0 { - persistent_store.decr_pin_retries().unwrap(); + while storage::pin_retries(&mut env).unwrap() > 0 { + storage::decr_pin_retries(&mut env).unwrap(); } assert_eq!( - client_pin.process_command(&mut rng, &mut persistent_store, params, DUMMY_CLOCK_VALUE), + client_pin.process_command(&mut env, params, DUMMY_CLOCK_VALUE), Err(Ctap2StatusCode::CTAP2_ERR_PIN_BLOCKED) ); } @@ -986,17 +957,11 @@ mod test { params.pin_uv_auth_protocol, ) .unwrap(); - let mut rng = ThreadRng256 {}; - let mut persistent_store = PersistentStore::new(&mut rng); - set_standard_pin(&mut persistent_store); + let mut env = TestEnv::new(); + set_standard_pin(&mut env); let response = client_pin - .process_command( - &mut rng, - &mut persistent_store, - params.clone(), - DUMMY_CLOCK_VALUE, - ) + .process_command(&mut env, params.clone(), DUMMY_CLOCK_VALUE) .unwrap(); let encrypted_token = match response { ResponseData::AuthenticatorClientPin(Some(response)) => { @@ -1032,12 +997,7 @@ mod test { let mut bad_params = params; bad_params.pin_hash_enc = Some(vec![0xEE; 16]); assert_eq!( - client_pin.process_command( - &mut rng, - &mut persistent_store, - bad_params, - DUMMY_CLOCK_VALUE - ), + client_pin.process_command(&mut env, bad_params, DUMMY_CLOCK_VALUE), Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID) ); } @@ -1057,13 +1017,12 @@ mod test { pin_uv_auth_protocol, ClientPinSubCommand::GetPinToken, ); - let mut rng = ThreadRng256 {}; - let mut persistent_store = PersistentStore::new(&mut rng); - set_standard_pin(&mut persistent_store); + let mut env = TestEnv::new(); + set_standard_pin(&mut env); - assert_eq!(persistent_store.force_pin_change(), Ok(())); + assert_eq!(storage::force_pin_change(&mut env), Ok(())); assert_eq!( - client_pin.process_command(&mut rng, &mut persistent_store, params, DUMMY_CLOCK_VALUE), + client_pin.process_command(&mut env, params, DUMMY_CLOCK_VALUE), Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID), ); } @@ -1092,17 +1051,11 @@ mod test { params.pin_uv_auth_protocol, ) .unwrap(); - let mut rng = ThreadRng256 {}; - let mut persistent_store = PersistentStore::new(&mut rng); - set_standard_pin(&mut persistent_store); + let mut env = TestEnv::new(); + set_standard_pin(&mut env); let response = client_pin - .process_command( - &mut rng, - &mut persistent_store, - params.clone(), - DUMMY_CLOCK_VALUE, - ) + .process_command(&mut env, params.clone(), DUMMY_CLOCK_VALUE) .unwrap(); let encrypted_token = match response { ResponseData::AuthenticatorClientPin(Some(response)) => { @@ -1138,36 +1091,21 @@ mod test { let mut bad_params = params.clone(); bad_params.permissions = Some(0x00); assert_eq!( - client_pin.process_command( - &mut rng, - &mut persistent_store, - bad_params, - DUMMY_CLOCK_VALUE - ), + client_pin.process_command(&mut env, bad_params, DUMMY_CLOCK_VALUE), Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER) ); let mut bad_params = params.clone(); bad_params.permissions_rp_id = None; assert_eq!( - client_pin.process_command( - &mut rng, - &mut persistent_store, - bad_params, - DUMMY_CLOCK_VALUE - ), + client_pin.process_command(&mut env, bad_params, DUMMY_CLOCK_VALUE), Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER) ); let mut bad_params = params; bad_params.pin_hash_enc = Some(vec![0xEE; 16]); assert_eq!( - client_pin.process_command( - &mut rng, - &mut persistent_store, - bad_params, - DUMMY_CLOCK_VALUE - ), + client_pin.process_command(&mut env, bad_params, DUMMY_CLOCK_VALUE), Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID) ); } @@ -1189,13 +1127,12 @@ mod test { pin_uv_auth_protocol, ClientPinSubCommand::GetPinUvAuthTokenUsingPinWithPermissions, ); - let mut rng = ThreadRng256 {}; - let mut persistent_store = PersistentStore::new(&mut rng); - set_standard_pin(&mut persistent_store); + let mut env = TestEnv::new(); + set_standard_pin(&mut env); - assert_eq!(persistent_store.force_pin_change(), Ok(())); + assert_eq!(storage::force_pin_change(&mut env), Ok(())); assert_eq!( - client_pin.process_command(&mut rng, &mut persistent_store, params, DUMMY_CLOCK_VALUE), + client_pin.process_command(&mut env, params, DUMMY_CLOCK_VALUE), Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID) ); } @@ -1215,8 +1152,8 @@ mod test { } fn test_helper_decrypt_pin(pin_uv_auth_protocol: PinUvAuthProtocol) { - let mut rng = ThreadRng256 {}; - let pin_protocol = PinProtocol::new(&mut rng); + let mut env = TestEnv::new(); + let pin_protocol = PinProtocol::new(env.rng()); let shared_secret = pin_protocol .decapsulate(pin_protocol.get_public_key(), pin_uv_auth_protocol) .unwrap(); @@ -1259,9 +1196,8 @@ mod test { } fn test_helper_check_and_store_new_pin(pin_uv_auth_protocol: PinUvAuthProtocol) { - let mut rng = ThreadRng256 {}; - let mut persistent_store = PersistentStore::new(&mut rng); - let pin_protocol = PinProtocol::new(&mut rng); + let mut env = TestEnv::new(); + let pin_protocol = PinProtocol::new(env.rng()); let shared_secret = pin_protocol .decapsulate(pin_protocol.get_public_key(), pin_uv_auth_protocol) .unwrap(); @@ -1286,17 +1222,17 @@ mod test { ), ]; for (pin, result) in test_cases { - let old_pin_hash = persistent_store.pin_hash().unwrap(); + let old_pin_hash = storage::pin_hash(&mut env).unwrap(); let new_pin_enc = encrypt_pin(shared_secret.as_ref(), pin); assert_eq!( - check_and_store_new_pin(&mut persistent_store, shared_secret.as_ref(), new_pin_enc), + check_and_store_new_pin(&mut env, shared_secret.as_ref(), new_pin_enc), result ); if result.is_ok() { - assert_ne!(old_pin_hash, persistent_store.pin_hash().unwrap()); + assert_ne!(old_pin_hash, storage::pin_hash(&mut env).unwrap()); } else { - assert_eq!(old_pin_hash, persistent_store.pin_hash().unwrap()); + assert_eq!(old_pin_hash, storage::pin_hash(&mut env).unwrap()); } } } @@ -1317,10 +1253,10 @@ mod test { cred_random: &[u8; 32], salt: Vec, ) -> Result, Ctap2StatusCode> { - let mut rng = ThreadRng256 {}; + let mut env = TestEnv::new(); let (client_pin, shared_secret) = create_client_pin_and_shared_secret(pin_uv_auth_protocol); - let salt_enc = shared_secret.as_ref().encrypt(&mut rng, &salt).unwrap(); + let salt_enc = shared_secret.as_ref().encrypt(env.rng(), &salt).unwrap(); let salt_auth = shared_secret.authenticate(&salt_enc); let hmac_secret_input = GetAssertionHmacSecretInput { key_agreement: client_pin @@ -1330,12 +1266,12 @@ mod test { salt_auth, pin_uv_auth_protocol, }; - let output = client_pin.process_hmac_secret(&mut rng, hmac_secret_input, cred_random); + let output = client_pin.process_hmac_secret(env.rng(), hmac_secret_input, cred_random); output.map(|v| shared_secret.as_ref().decrypt(&v).unwrap()) } fn test_helper_process_hmac_secret_bad_salt_auth(pin_uv_auth_protocol: PinUvAuthProtocol) { - let mut rng = ThreadRng256 {}; + let mut env = TestEnv::new(); let (client_pin, shared_secret) = create_client_pin_and_shared_secret(pin_uv_auth_protocol); let cred_random = [0xC9; 32]; @@ -1350,7 +1286,7 @@ mod test { salt_auth, pin_uv_auth_protocol, }; - let output = client_pin.process_hmac_secret(&mut rng, hmac_secret_input, &cred_random); + let output = client_pin.process_hmac_secret(env.rng(), hmac_secret_input, &cred_random); assert_eq!(output, Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)); } @@ -1451,8 +1387,8 @@ mod test { #[test] fn test_has_permission() { - let mut rng = ThreadRng256 {}; - let mut client_pin = ClientPin::new(&mut rng); + let mut env = TestEnv::new(); + let mut client_pin = ClientPin::new(env.rng()); client_pin.pin_uv_auth_token_state.set_permissions(0x7F); for permission in PinPermission::into_enum_iter() { assert_eq!( @@ -1475,8 +1411,8 @@ mod test { #[test] fn test_has_no_rp_id_permission() { - let mut rng = ThreadRng256 {}; - let mut client_pin = ClientPin::new(&mut rng); + let mut env = TestEnv::new(); + let mut client_pin = ClientPin::new(env.rng()); assert_eq!(client_pin.has_no_rp_id_permission(), Ok(())); client_pin .pin_uv_auth_token_state @@ -1489,8 +1425,8 @@ mod test { #[test] fn test_has_no_or_rp_id_permission() { - let mut rng = ThreadRng256 {}; - let mut client_pin = ClientPin::new(&mut rng); + let mut env = TestEnv::new(); + let mut client_pin = ClientPin::new(env.rng()); assert_eq!(client_pin.has_no_or_rp_id_permission("example.com"), Ok(())); client_pin .pin_uv_auth_token_state @@ -1504,8 +1440,8 @@ mod test { #[test] fn test_has_no_or_rp_id_hash_permission() { - let mut rng = ThreadRng256 {}; - let mut client_pin = ClientPin::new(&mut rng); + let mut env = TestEnv::new(); + let mut client_pin = ClientPin::new(env.rng()); let rp_id_hash = Sha256::hash(b"example.com"); assert_eq!( client_pin.has_no_or_rp_id_hash_permission(&rp_id_hash), @@ -1526,8 +1462,8 @@ mod test { #[test] fn test_ensure_rp_id_permission() { - let mut rng = ThreadRng256 {}; - let mut client_pin = ClientPin::new(&mut rng); + let mut env = TestEnv::new(); + let mut client_pin = ClientPin::new(env.rng()); assert_eq!(client_pin.ensure_rp_id_permission("example.com"), Ok(())); assert_eq!( client_pin @@ -1544,8 +1480,8 @@ mod test { #[test] fn test_verify_pin_uv_auth_token() { - let mut rng = ThreadRng256 {}; - let mut client_pin = ClientPin::new(&mut rng); + let mut env = TestEnv::new(); + let mut client_pin = ClientPin::new(env.rng()); let message = [0xAA]; client_pin .pin_uv_auth_token_state @@ -1618,8 +1554,8 @@ mod test { #[test] fn test_verify_pin_uv_auth_token_not_in_use() { - let mut rng = ThreadRng256 {}; - let client_pin = ClientPin::new(&mut rng); + let mut env = TestEnv::new(); + let client_pin = ClientPin::new(env.rng()); let message = [0xAA]; let pin_uv_auth_token_v1 = client_pin @@ -1640,8 +1576,8 @@ mod test { #[test] fn test_reset() { - let mut rng = ThreadRng256 {}; - let mut client_pin = ClientPin::new(&mut rng); + let mut env = TestEnv::new(); + let mut client_pin = ClientPin::new(env.rng()); let public_key_v1 = client_pin.pin_protocol_v1.get_public_key(); let public_key_v2 = client_pin.pin_protocol_v2.get_public_key(); let token_v1 = *client_pin.pin_protocol_v1.get_pin_uv_auth_token(); @@ -1650,7 +1586,7 @@ mod test { client_pin .pin_uv_auth_token_state .set_permissions_rp_id(Some(String::from("example.com"))); - client_pin.reset(&mut rng); + client_pin.reset(env.rng()); assert_ne!(public_key_v1, client_pin.pin_protocol_v1.get_public_key()); assert_ne!(public_key_v2, client_pin.pin_protocol_v2.get_public_key()); assert_ne!( @@ -1676,13 +1612,12 @@ mod test { PinUvAuthProtocol::V2, ClientPinSubCommand::GetPinUvAuthTokenUsingPinWithPermissions, ); - let mut rng = ThreadRng256 {}; - let mut persistent_store = PersistentStore::new(&mut rng); - set_standard_pin(&mut persistent_store); + let mut env = TestEnv::new(); + set_standard_pin(&mut env); params.permissions = Some(0xFF); assert!(client_pin - .process_command(&mut rng, &mut persistent_store, params, DUMMY_CLOCK_VALUE) + .process_command(&mut env, params, DUMMY_CLOCK_VALUE) .is_ok()); for permission in PinPermission::into_enum_iter() { assert_eq!( @@ -1723,13 +1658,12 @@ mod test { PinUvAuthProtocol::V2, ClientPinSubCommand::GetPinUvAuthTokenUsingPinWithPermissions, ); - let mut rng = ThreadRng256 {}; - let mut persistent_store = PersistentStore::new(&mut rng); - set_standard_pin(&mut persistent_store); + let mut env = TestEnv::new(); + set_standard_pin(&mut env); params.permissions = Some(0xFF); assert!(client_pin - .process_command(&mut rng, &mut persistent_store, params, DUMMY_CLOCK_VALUE) + .process_command(&mut env, params, DUMMY_CLOCK_VALUE) .is_ok()); for permission in PinPermission::into_enum_iter() { assert_eq!( diff --git a/src/ctap/config_command.rs b/src/ctap/config_command.rs index 6599220..6cb1948 100644 --- a/src/ctap/config_command.rs +++ b/src/ctap/config_command.rs @@ -18,15 +18,16 @@ use super::customization::ENTERPRISE_ATTESTATION_MODE; use super::data_formats::{ConfigSubCommand, ConfigSubCommandParams, SetMinPinLengthParams}; use super::response::ResponseData; use super::status_code::Ctap2StatusCode; -use super::storage::PersistentStore; +use crate::ctap::storage; +use crate::env::Env; use alloc::vec; /// Processes the subcommand enableEnterpriseAttestation for AuthenticatorConfig. fn process_enable_enterprise_attestation( - persistent_store: &mut PersistentStore, + env: &mut impl Env, ) -> Result { if ENTERPRISE_ATTESTATION_MODE.is_some() { - persistent_store.enable_enterprise_attestation()?; + storage::enable_enterprise_attestation(env)?; Ok(ResponseData::AuthenticatorConfig) } else { Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER) @@ -34,16 +35,14 @@ fn process_enable_enterprise_attestation( } /// Processes the subcommand toggleAlwaysUv for AuthenticatorConfig. -fn process_toggle_always_uv( - persistent_store: &mut PersistentStore, -) -> Result { - persistent_store.toggle_always_uv()?; +fn process_toggle_always_uv(env: &mut impl Env) -> Result { + storage::toggle_always_uv(env)?; Ok(ResponseData::AuthenticatorConfig) } /// Processes the subcommand setMinPINLength for AuthenticatorConfig. fn process_set_min_pin_length( - persistent_store: &mut PersistentStore, + env: &mut impl Env, params: SetMinPinLengthParams, ) -> Result { let SetMinPinLengthParams { @@ -51,31 +50,31 @@ fn process_set_min_pin_length( min_pin_length_rp_ids, force_change_pin, } = params; - let store_min_pin_length = persistent_store.min_pin_length()?; + let store_min_pin_length = storage::min_pin_length(env)?; let new_min_pin_length = new_min_pin_length.unwrap_or(store_min_pin_length); if new_min_pin_length < store_min_pin_length { return Err(Ctap2StatusCode::CTAP2_ERR_PIN_POLICY_VIOLATION); } let mut force_change_pin = force_change_pin.unwrap_or(false); - if force_change_pin && persistent_store.pin_hash()?.is_none() { + if force_change_pin && storage::pin_hash(env)?.is_none() { return Err(Ctap2StatusCode::CTAP2_ERR_PIN_NOT_SET); } - if let Some(old_length) = persistent_store.pin_code_point_length()? { + if let Some(old_length) = storage::pin_code_point_length(env)? { force_change_pin |= new_min_pin_length > old_length; } if force_change_pin { - persistent_store.force_pin_change()?; + storage::force_pin_change(env)?; } - persistent_store.set_min_pin_length(new_min_pin_length)?; + storage::set_min_pin_length(env, new_min_pin_length)?; if let Some(min_pin_length_rp_ids) = min_pin_length_rp_ids { - persistent_store.set_min_pin_length_rp_ids(min_pin_length_rp_ids)?; + storage::set_min_pin_length_rp_ids(env, min_pin_length_rp_ids)?; } Ok(ResponseData::AuthenticatorConfig) } /// Processes the AuthenticatorConfig command. pub fn process_config( - persistent_store: &mut PersistentStore, + env: &mut impl Env, client_pin: &mut ClientPin, params: AuthenticatorConfigParameters, ) -> Result { @@ -86,9 +85,9 @@ pub fn process_config( pin_uv_auth_protocol, } = params; - let enforce_uv = !matches!(sub_command, ConfigSubCommand::ToggleAlwaysUv) - && persistent_store.has_always_uv()?; - if persistent_store.pin_hash()?.is_some() || enforce_uv { + let enforce_uv = + !matches!(sub_command, ConfigSubCommand::ToggleAlwaysUv) && storage::has_always_uv(env)?; + if storage::pin_hash(env)?.is_some() || enforce_uv { let pin_uv_auth_param = pin_uv_auth_param.ok_or(Ctap2StatusCode::CTAP2_ERR_PUAT_REQUIRED)?; let pin_uv_auth_protocol = @@ -108,13 +107,11 @@ pub fn process_config( } match sub_command { - ConfigSubCommand::EnableEnterpriseAttestation => { - process_enable_enterprise_attestation(persistent_store) - } - ConfigSubCommand::ToggleAlwaysUv => process_toggle_always_uv(persistent_store), + ConfigSubCommand::EnableEnterpriseAttestation => process_enable_enterprise_attestation(env), + ConfigSubCommand::ToggleAlwaysUv => process_toggle_always_uv(env), ConfigSubCommand::SetMinPinLength => { if let Some(ConfigSubCommandParams::SetMinPinLength(params)) = sub_command_params { - process_set_min_pin_length(persistent_store, params) + process_set_min_pin_length(env, params) } else { Err(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER) } @@ -129,13 +126,12 @@ mod test { use crate::ctap::customization::ENFORCE_ALWAYS_UV; use crate::ctap::data_formats::PinUvAuthProtocol; use crate::ctap::pin_protocol::authenticate_pin_uv_auth_token; - use crypto::rng256::ThreadRng256; + use crate::env::test::TestEnv; #[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 mut env = TestEnv::new(); + let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); let pin_uv_auth_token = [0x55; 32]; let mut client_pin = ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1); @@ -146,11 +142,11 @@ mod test { pin_uv_auth_param: None, pin_uv_auth_protocol: None, }; - let config_response = process_config(&mut persistent_store, &mut client_pin, config_params); + let config_response = process_config(&mut env, &mut client_pin, config_params); if ENTERPRISE_ATTESTATION_MODE.is_some() { assert_eq!(config_response, Ok(ResponseData::AuthenticatorConfig)); - assert_eq!(persistent_store.enterprise_attestation(), Ok(true)); + assert_eq!(storage::enterprise_attestation(&mut env), Ok(true)); } else { assert_eq!( config_response, @@ -161,9 +157,8 @@ mod test { #[test] fn test_process_toggle_always_uv() { - let mut rng = ThreadRng256 {}; - let mut persistent_store = PersistentStore::new(&mut rng); - let key_agreement_key = crypto::ecdh::SecKey::gensk(&mut rng); + let mut env = TestEnv::new(); + let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); let pin_uv_auth_token = [0x55; 32]; let mut client_pin = ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1); @@ -174,9 +169,9 @@ mod test { pin_uv_auth_param: None, pin_uv_auth_protocol: None, }; - let config_response = process_config(&mut persistent_store, &mut client_pin, config_params); + let config_response = process_config(&mut env, &mut client_pin, config_params); assert_eq!(config_response, Ok(ResponseData::AuthenticatorConfig)); - assert!(persistent_store.has_always_uv().unwrap()); + assert!(storage::has_always_uv(&mut env).unwrap()); let config_params = AuthenticatorConfigParameters { sub_command: ConfigSubCommand::ToggleAlwaysUv, @@ -184,7 +179,7 @@ mod test { pin_uv_auth_param: None, pin_uv_auth_protocol: None, }; - let config_response = process_config(&mut persistent_store, &mut client_pin, config_params); + let config_response = process_config(&mut env, &mut client_pin, config_params); if ENFORCE_ALWAYS_UV { assert_eq!( config_response, @@ -192,18 +187,17 @@ mod test { ); } else { assert_eq!(config_response, Ok(ResponseData::AuthenticatorConfig)); - assert!(!persistent_store.has_always_uv().unwrap()); + assert!(!storage::has_always_uv(&mut env).unwrap()); } } fn test_helper_process_toggle_always_uv_with_pin(pin_uv_auth_protocol: PinUvAuthProtocol) { - let mut rng = ThreadRng256 {}; - let mut persistent_store = PersistentStore::new(&mut rng); - let key_agreement_key = crypto::ecdh::SecKey::gensk(&mut rng); + let mut env = TestEnv::new(); + let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); let pin_uv_auth_token = [0x55; 32]; let mut client_pin = ClientPin::new_test(key_agreement_key, pin_uv_auth_token, pin_uv_auth_protocol); - persistent_store.set_pin(&[0x88; 16], 4).unwrap(); + storage::set_pin(&mut env, &[0x88; 16], 4).unwrap(); let mut config_data = vec![0xFF; 32]; config_data.extend(&[0x0D, ConfigSubCommand::ToggleAlwaysUv as u8]); @@ -215,7 +209,7 @@ mod test { pin_uv_auth_param: Some(pin_uv_auth_param.clone()), pin_uv_auth_protocol: Some(pin_uv_auth_protocol), }; - let config_response = process_config(&mut persistent_store, &mut client_pin, config_params); + let config_response = process_config(&mut env, &mut client_pin, config_params); if ENFORCE_ALWAYS_UV { assert_eq!( config_response, @@ -224,7 +218,7 @@ mod test { return; } assert_eq!(config_response, Ok(ResponseData::AuthenticatorConfig)); - assert!(persistent_store.has_always_uv().unwrap()); + assert!(storage::has_always_uv(&mut env).unwrap()); let config_params = AuthenticatorConfigParameters { sub_command: ConfigSubCommand::ToggleAlwaysUv, @@ -232,9 +226,9 @@ mod test { pin_uv_auth_param: Some(pin_uv_auth_param), pin_uv_auth_protocol: Some(pin_uv_auth_protocol), }; - let config_response = process_config(&mut persistent_store, &mut client_pin, config_params); + let config_response = process_config(&mut env, &mut client_pin, config_params); assert_eq!(config_response, Ok(ResponseData::AuthenticatorConfig)); - assert!(!persistent_store.has_always_uv().unwrap()); + assert!(!storage::has_always_uv(&mut env).unwrap()); } #[test] @@ -268,9 +262,8 @@ mod test { #[test] fn test_process_set_min_pin_length() { - let mut rng = ThreadRng256 {}; - let mut persistent_store = PersistentStore::new(&mut rng); - let key_agreement_key = crypto::ecdh::SecKey::gensk(&mut rng); + let mut env = TestEnv::new(); + let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); let pin_uv_auth_token = [0x55; 32]; let mut client_pin = ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1); @@ -278,13 +271,13 @@ mod test { // First, increase minimum PIN length from 4 to 6 without PIN auth. let min_pin_length = 6; let config_params = create_min_pin_config_params(min_pin_length, None); - let config_response = process_config(&mut persistent_store, &mut client_pin, config_params); + let config_response = process_config(&mut env, &mut client_pin, config_params); assert_eq!(config_response, Ok(ResponseData::AuthenticatorConfig)); - assert_eq!(persistent_store.min_pin_length(), Ok(min_pin_length)); + assert_eq!(storage::min_pin_length(&mut env), Ok(min_pin_length)); // Second, increase minimum PIN length from 6 to 8 with PIN auth. // The stored PIN or its length don't matter since we control the token. - persistent_store.set_pin(&[0x88; 16], 8).unwrap(); + storage::set_pin(&mut env, &[0x88; 16], 8).unwrap(); let min_pin_length = 8; let mut config_params = create_min_pin_config_params(min_pin_length, None); let pin_uv_auth_param = vec![ @@ -292,9 +285,9 @@ mod test { 0xB2, 0xDE, ]; config_params.pin_uv_auth_param = Some(pin_uv_auth_param); - let config_response = process_config(&mut persistent_store, &mut client_pin, config_params); + let config_response = process_config(&mut env, &mut client_pin, config_params); assert_eq!(config_response, Ok(ResponseData::AuthenticatorConfig)); - assert_eq!(persistent_store.min_pin_length(), Ok(min_pin_length)); + assert_eq!(storage::min_pin_length(&mut env), Ok(min_pin_length)); // Third, decreasing the minimum PIN length from 8 to 7 fails. let mut config_params = create_min_pin_config_params(7, None); @@ -303,19 +296,18 @@ mod test { 0xA7, 0x71, ]; config_params.pin_uv_auth_param = Some(pin_uv_auth_param); - let config_response = process_config(&mut persistent_store, &mut client_pin, config_params); + let config_response = process_config(&mut env, &mut client_pin, config_params); assert_eq!( config_response, Err(Ctap2StatusCode::CTAP2_ERR_PIN_POLICY_VIOLATION) ); - assert_eq!(persistent_store.min_pin_length(), Ok(min_pin_length)); + assert_eq!(storage::min_pin_length(&mut env), Ok(min_pin_length)); } #[test] fn test_process_set_min_pin_length_rp_ids() { - let mut rng = ThreadRng256 {}; - let mut persistent_store = PersistentStore::new(&mut rng); - let key_agreement_key = crypto::ecdh::SecKey::gensk(&mut rng); + let mut env = TestEnv::new(); + let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); let pin_uv_auth_token = [0x55; 32]; let mut client_pin = ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1); @@ -325,11 +317,11 @@ mod test { let min_pin_length_rp_ids = vec!["example.com".to_string()]; let config_params = create_min_pin_config_params(min_pin_length, Some(min_pin_length_rp_ids.clone())); - let config_response = process_config(&mut persistent_store, &mut client_pin, config_params); + let config_response = process_config(&mut env, &mut client_pin, config_params); assert_eq!(config_response, Ok(ResponseData::AuthenticatorConfig)); - assert_eq!(persistent_store.min_pin_length(), Ok(min_pin_length)); + assert_eq!(storage::min_pin_length(&mut env), Ok(min_pin_length)); assert_eq!( - persistent_store.min_pin_length_rp_ids(), + storage::min_pin_length_rp_ids(&mut env), Ok(min_pin_length_rp_ids) ); @@ -337,7 +329,7 @@ mod test { let min_pin_length = 8; let min_pin_length_rp_ids = vec!["another.example.com".to_string()]; // The stored PIN or its length don't matter since we control the token. - persistent_store.set_pin(&[0x88; 16], 8).unwrap(); + storage::set_pin(&mut env, &[0x88; 16], 8).unwrap(); let mut config_params = create_min_pin_config_params(min_pin_length, Some(min_pin_length_rp_ids.clone())); let pin_uv_auth_param = vec![ @@ -345,11 +337,11 @@ mod test { 0xD6, 0xDA, ]; config_params.pin_uv_auth_param = Some(pin_uv_auth_param.clone()); - let config_response = process_config(&mut persistent_store, &mut client_pin, config_params); + let config_response = process_config(&mut env, &mut client_pin, config_params); assert_eq!(config_response, Ok(ResponseData::AuthenticatorConfig)); - assert_eq!(persistent_store.min_pin_length(), Ok(min_pin_length)); + assert_eq!(storage::min_pin_length(&mut env), Ok(min_pin_length)); assert_eq!( - persistent_store.min_pin_length_rp_ids(), + storage::min_pin_length_rp_ids(&mut env), Ok(min_pin_length_rp_ids.clone()) ); @@ -358,14 +350,14 @@ mod test { let mut config_params = create_min_pin_config_params(9, Some(min_pin_length_rp_ids.clone())); config_params.pin_uv_auth_param = Some(pin_uv_auth_param.clone()); - let config_response = process_config(&mut persistent_store, &mut client_pin, config_params); + let config_response = process_config(&mut env, &mut client_pin, config_params); assert_eq!( config_response, Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID) ); - assert_eq!(persistent_store.min_pin_length(), Ok(min_pin_length)); + assert_eq!(storage::min_pin_length(&mut env), Ok(min_pin_length)); assert_eq!( - persistent_store.min_pin_length_rp_ids(), + storage::min_pin_length_rp_ids(&mut env), Ok(min_pin_length_rp_ids.clone()) ); @@ -376,28 +368,27 @@ mod test { Some(vec!["counter.example.com".to_string()]), ); config_params.pin_uv_auth_param = Some(pin_uv_auth_param); - let config_response = process_config(&mut persistent_store, &mut client_pin, config_params); + let config_response = process_config(&mut env, &mut client_pin, config_params); assert_eq!( config_response, Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID) ); - assert_eq!(persistent_store.min_pin_length(), Ok(min_pin_length)); + assert_eq!(storage::min_pin_length(&mut env), Ok(min_pin_length)); assert_eq!( - persistent_store.min_pin_length_rp_ids(), + storage::min_pin_length_rp_ids(&mut env), Ok(min_pin_length_rp_ids) ); } #[test] fn test_process_set_min_pin_length_force_pin_change_implicit() { - let mut rng = ThreadRng256 {}; - let mut persistent_store = PersistentStore::new(&mut rng); - let key_agreement_key = crypto::ecdh::SecKey::gensk(&mut rng); + let mut env = TestEnv::new(); + let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); let pin_uv_auth_token = [0x55; 32]; let mut client_pin = ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1); - persistent_store.set_pin(&[0x88; 16], 4).unwrap(); + storage::set_pin(&mut env, &[0x88; 16], 4).unwrap(); // Increase min PIN, force PIN change. let min_pin_length = 6; let mut config_params = create_min_pin_config_params(min_pin_length, None); @@ -406,28 +397,27 @@ mod test { 0xA8, 0xC8, ]); config_params.pin_uv_auth_param = pin_uv_auth_param; - let config_response = process_config(&mut persistent_store, &mut client_pin, config_params); + let config_response = process_config(&mut env, &mut client_pin, config_params); assert_eq!(config_response, Ok(ResponseData::AuthenticatorConfig)); - assert_eq!(persistent_store.min_pin_length(), Ok(min_pin_length)); - assert_eq!(persistent_store.has_force_pin_change(), Ok(true)); + assert_eq!(storage::min_pin_length(&mut env), Ok(min_pin_length)); + assert_eq!(storage::has_force_pin_change(&mut env), Ok(true)); } #[test] fn test_process_set_min_pin_length_force_pin_change_explicit() { - let mut rng = ThreadRng256 {}; - let mut persistent_store = PersistentStore::new(&mut rng); - let key_agreement_key = crypto::ecdh::SecKey::gensk(&mut rng); + let mut env = TestEnv::new(); + let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); let pin_uv_auth_token = [0x55; 32]; let mut client_pin = ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1); - persistent_store.set_pin(&[0x88; 16], 4).unwrap(); + storage::set_pin(&mut env, &[0x88; 16], 4).unwrap(); let pin_uv_auth_param = Some(vec![ 0xE3, 0x74, 0xF4, 0x27, 0xBE, 0x7D, 0x40, 0xB5, 0x71, 0xB6, 0xB4, 0x1A, 0xD2, 0xC1, 0x53, 0xD7, ]); let set_min_pin_length_params = SetMinPinLengthParams { - new_min_pin_length: Some(persistent_store.min_pin_length().unwrap()), + new_min_pin_length: Some(storage::min_pin_length(&mut env).unwrap()), min_pin_length_rp_ids: None, force_change_pin: Some(true), }; @@ -439,16 +429,15 @@ mod test { pin_uv_auth_param, pin_uv_auth_protocol: Some(PinUvAuthProtocol::V1), }; - let config_response = process_config(&mut persistent_store, &mut client_pin, config_params); + let config_response = process_config(&mut env, &mut client_pin, config_params); assert_eq!(config_response, Ok(ResponseData::AuthenticatorConfig)); - assert_eq!(persistent_store.has_force_pin_change(), Ok(true)); + assert_eq!(storage::has_force_pin_change(&mut env), Ok(true)); } #[test] fn test_process_config_vendor_prototype() { - let mut rng = ThreadRng256 {}; - let mut persistent_store = PersistentStore::new(&mut rng); - let key_agreement_key = crypto::ecdh::SecKey::gensk(&mut rng); + let mut env = TestEnv::new(); + let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); let pin_uv_auth_token = [0x55; 32]; let mut client_pin = ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1); @@ -459,7 +448,7 @@ mod test { pin_uv_auth_param: None, pin_uv_auth_protocol: None, }; - let config_response = process_config(&mut persistent_store, &mut client_pin, config_params); + let config_response = process_config(&mut env, &mut client_pin, config_params); assert_eq!( config_response, Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER) diff --git a/src/ctap/credential_management.rs b/src/ctap/credential_management.rs index 52ad6e5..c5952b2 100644 --- a/src/ctap/credential_management.rs +++ b/src/ctap/credential_management.rs @@ -21,8 +21,9 @@ use super::data_formats::{ }; use super::response::{AuthenticatorCredentialManagementResponse, ResponseData}; use super::status_code::Ctap2StatusCode; -use super::storage::PersistentStore; use super::{StatefulCommand, StatefulPermission}; +use crate::ctap::storage; +use crate::env::Env; use alloc::collections::BTreeSet; use alloc::string::String; use alloc::vec; @@ -32,12 +33,10 @@ use crypto::Hash256; use libtock_drivers::timer::ClockValue; /// Generates a set with all existing RP IDs. -fn get_stored_rp_ids( - persistent_store: &PersistentStore, -) -> Result, Ctap2StatusCode> { +fn get_stored_rp_ids(env: &mut impl Env) -> Result, Ctap2StatusCode> { let mut rp_set = BTreeSet::new(); let mut iter_result = Ok(()); - for (_, credential) in persistent_store.iter_credentials(&mut iter_result)? { + for (_, credential) in storage::iter_credentials(env, &mut iter_result)? { rp_set.insert(credential.rp_id); } iter_result?; @@ -109,7 +108,7 @@ fn enumerate_credentials_response( /// /// Either no RP ID is associated, or the RP ID matches the stored credential. fn check_rp_id_permissions( - persistent_store: &mut PersistentStore, + env: &mut impl Env, client_pin: &mut ClientPin, credential_id: &[u8], ) -> Result<(), Ctap2StatusCode> { @@ -117,18 +116,18 @@ fn check_rp_id_permissions( if client_pin.has_no_rp_id_permission().is_ok() { return Ok(()); } - let (_, credential) = persistent_store.find_credential_item(credential_id)?; + let (_, credential) = storage::find_credential_item(env, credential_id)?; client_pin.has_no_or_rp_id_permission(&credential.rp_id) } /// Processes the subcommand getCredsMetadata for CredentialManagement. fn process_get_creds_metadata( - persistent_store: &PersistentStore, + env: &mut impl Env, ) -> Result { Ok(AuthenticatorCredentialManagementResponse { - existing_resident_credentials_count: Some(persistent_store.count_credentials()? as u64), + existing_resident_credentials_count: Some(storage::count_credentials(env)? as u64), max_possible_remaining_resident_credentials_count: Some( - persistent_store.remaining_credentials()? as u64, + storage::remaining_credentials(env)? as u64, ), ..Default::default() }) @@ -136,11 +135,11 @@ fn process_get_creds_metadata( /// Processes the subcommand enumerateRPsBegin for CredentialManagement. fn process_enumerate_rps_begin( - persistent_store: &PersistentStore, + env: &mut impl Env, stateful_command_permission: &mut StatefulPermission, now: ClockValue, ) -> Result { - let rp_set = get_stored_rp_ids(persistent_store)?; + let rp_set = get_stored_rp_ids(env)?; let total_rps = rp_set.len(); if total_rps > 1 { @@ -156,11 +155,11 @@ fn process_enumerate_rps_begin( /// Processes the subcommand enumerateRPsGetNextRP for CredentialManagement. fn process_enumerate_rps_get_next_rp( - persistent_store: &PersistentStore, + env: &mut impl Env, stateful_command_permission: &mut StatefulPermission, ) -> Result { let rp_id_index = stateful_command_permission.next_enumerate_rp()?; - let rp_set = get_stored_rp_ids(persistent_store)?; + let rp_set = get_stored_rp_ids(env)?; // A BTreeSet is already sorted. let rp_id = rp_set .into_iter() @@ -171,7 +170,7 @@ fn process_enumerate_rps_get_next_rp( /// Processes the subcommand enumerateCredentialsBegin for CredentialManagement. fn process_enumerate_credentials_begin( - persistent_store: &PersistentStore, + env: &mut impl Env, stateful_command_permission: &mut StatefulPermission, client_pin: &mut ClientPin, sub_command_params: CredentialManagementSubCommandParameters, @@ -182,7 +181,7 @@ fn process_enumerate_credentials_begin( .ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?; client_pin.has_no_or_rp_id_hash_permission(&rp_id_hash[..])?; let mut iter_result = Ok(()); - let iter = persistent_store.iter_credentials(&mut iter_result)?; + let iter = storage::iter_credentials(env, &mut iter_result)?; let mut rp_credentials: Vec = iter .filter_map(|(key, credential)| { let cred_rp_id_hash = Sha256::hash(credential.rp_id.as_bytes()); @@ -198,7 +197,7 @@ fn process_enumerate_credentials_begin( let current_key = rp_credentials .pop() .ok_or(Ctap2StatusCode::CTAP2_ERR_NO_CREDENTIALS)?; - let credential = persistent_store.get_credential(current_key)?; + let credential = storage::get_credential(env, current_key)?; if total_credentials > 1 { stateful_command_permission .set_command(now, StatefulCommand::EnumerateCredentials(rp_credentials)); @@ -208,17 +207,17 @@ fn process_enumerate_credentials_begin( /// Processes the subcommand enumerateCredentialsGetNextCredential for CredentialManagement. fn process_enumerate_credentials_get_next_credential( - persistent_store: &PersistentStore, + env: &mut impl Env, stateful_command_permission: &mut StatefulPermission, ) -> Result { let credential_key = stateful_command_permission.next_enumerate_credential()?; - let credential = persistent_store.get_credential(credential_key)?; + let credential = storage::get_credential(env, credential_key)?; enumerate_credentials_response(credential, None) } /// Processes the subcommand deleteCredential for CredentialManagement. fn process_delete_credential( - persistent_store: &mut PersistentStore, + env: &mut impl Env, client_pin: &mut ClientPin, sub_command_params: CredentialManagementSubCommandParameters, ) -> Result<(), Ctap2StatusCode> { @@ -226,13 +225,13 @@ fn process_delete_credential( .credential_id .ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)? .key_id; - check_rp_id_permissions(persistent_store, client_pin, &credential_id)?; - persistent_store.delete_credential(&credential_id) + check_rp_id_permissions(env, client_pin, &credential_id)?; + storage::delete_credential(env, &credential_id) } /// Processes the subcommand updateUserInformation for CredentialManagement. fn process_update_user_information( - persistent_store: &mut PersistentStore, + env: &mut impl Env, client_pin: &mut ClientPin, sub_command_params: CredentialManagementSubCommandParameters, ) -> Result<(), Ctap2StatusCode> { @@ -243,13 +242,13 @@ fn process_update_user_information( let user = sub_command_params .user .ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?; - check_rp_id_permissions(persistent_store, client_pin, &credential_id)?; - persistent_store.update_credential(&credential_id, user) + check_rp_id_permissions(env, client_pin, &credential_id)?; + storage::update_credential(env, &credential_id, user) } /// Processes the CredentialManagement command and all its subcommands. pub fn process_credential_management( - persistent_store: &mut PersistentStore, + env: &mut impl Env, stateful_command_permission: &mut StatefulPermission, client_pin: &mut ClientPin, cred_management_params: AuthenticatorCredentialManagementParameters, @@ -305,37 +304,34 @@ pub fn process_credential_management( let response = match sub_command { CredentialManagementSubCommand::GetCredsMetadata => { client_pin.has_no_rp_id_permission()?; - Some(process_get_creds_metadata(persistent_store)?) + Some(process_get_creds_metadata(env)?) } CredentialManagementSubCommand::EnumerateRpsBegin => { client_pin.has_no_rp_id_permission()?; Some(process_enumerate_rps_begin( - persistent_store, + env, stateful_command_permission, now, )?) } CredentialManagementSubCommand::EnumerateRpsGetNextRp => Some( - process_enumerate_rps_get_next_rp(persistent_store, stateful_command_permission)?, + process_enumerate_rps_get_next_rp(env, stateful_command_permission)?, ), CredentialManagementSubCommand::EnumerateCredentialsBegin => { Some(process_enumerate_credentials_begin( - persistent_store, + env, stateful_command_permission, client_pin, sub_command_params.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?, now, )?) } - CredentialManagementSubCommand::EnumerateCredentialsGetNextCredential => { - Some(process_enumerate_credentials_get_next_credential( - persistent_store, - stateful_command_permission, - )?) - } + CredentialManagementSubCommand::EnumerateCredentialsGetNextCredential => Some( + process_enumerate_credentials_get_next_credential(env, stateful_command_permission)?, + ), CredentialManagementSubCommand::DeleteCredential => { process_delete_credential( - persistent_store, + env, client_pin, sub_command_params.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?, )?; @@ -343,7 +339,7 @@ pub fn process_credential_management( } CredentialManagementSubCommand::UpdateUserInformation => { process_update_user_information( - persistent_store, + env, client_pin, sub_command_params.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?, )?; @@ -395,7 +391,7 @@ mod test { let mut ctap_state = CtapState::new(&mut env, DUMMY_CLOCK_VALUE); ctap_state.client_pin = client_pin; - ctap_state.persistent_store.set_pin(&[0u8; 16], 4).unwrap(); + storage::set_pin(&mut env, &[0u8; 16], 4).unwrap(); let management_data = vec![CredentialManagementSubCommand::GetCredsMetadata as u8]; let pin_uv_auth_param = authenticate_pin_uv_auth_token( &pin_uv_auth_token, @@ -410,7 +406,7 @@ mod test { pin_uv_auth_param: Some(pin_uv_auth_param.clone()), }; let cred_management_response = process_credential_management( - &mut ctap_state.persistent_store, + &mut env, &mut ctap_state.stateful_command_permission, &mut ctap_state.client_pin, cred_management_params, @@ -426,10 +422,7 @@ mod test { _ => panic!("Invalid response type"), }; - ctap_state - .persistent_store - .store_credential(credential_source) - .unwrap(); + storage::store_credential(&mut env, credential_source).unwrap(); let cred_management_params = AuthenticatorCredentialManagementParameters { sub_command: CredentialManagementSubCommand::GetCredsMetadata, @@ -438,7 +431,7 @@ mod test { pin_uv_auth_param: Some(pin_uv_auth_param), }; let cred_management_response = process_credential_management( - &mut ctap_state.persistent_store, + &mut env, &mut ctap_state.stateful_command_permission, &mut ctap_state.client_pin, cred_management_params, @@ -480,16 +473,10 @@ mod test { let mut ctap_state = CtapState::new(&mut env, DUMMY_CLOCK_VALUE); ctap_state.client_pin = client_pin; - ctap_state - .persistent_store - .store_credential(credential_source1) - .unwrap(); - ctap_state - .persistent_store - .store_credential(credential_source2) - .unwrap(); + storage::store_credential(&mut env, credential_source1).unwrap(); + storage::store_credential(&mut env, credential_source2).unwrap(); - ctap_state.persistent_store.set_pin(&[0u8; 16], 4).unwrap(); + storage::set_pin(&mut env, &[0u8; 16], 4).unwrap(); let pin_uv_auth_param = Some(vec![ 0x1A, 0xA4, 0x96, 0xDA, 0x62, 0x80, 0x28, 0x13, 0xEB, 0x32, 0xB9, 0xF1, 0xD2, 0xA9, 0xD0, 0xD1, @@ -502,7 +489,7 @@ mod test { pin_uv_auth_param, }; let cred_management_response = process_credential_management( - &mut ctap_state.persistent_store, + &mut env, &mut ctap_state.stateful_command_permission, &mut ctap_state.client_pin, cred_management_params, @@ -526,7 +513,7 @@ mod test { pin_uv_auth_param: None, }; let cred_management_response = process_credential_management( - &mut ctap_state.persistent_store, + &mut env, &mut ctap_state.stateful_command_permission, &mut ctap_state.client_pin, cred_management_params, @@ -551,7 +538,7 @@ mod test { pin_uv_auth_param: None, }; let cred_management_response = process_credential_management( - &mut ctap_state.persistent_store, + &mut env, &mut ctap_state.stateful_command_permission, &mut ctap_state.client_pin, cred_management_params, @@ -579,13 +566,10 @@ mod test { for i in 0..NUM_CREDENTIALS { let mut credential = credential_source.clone(); credential.rp_id = i.to_string(); - ctap_state - .persistent_store - .store_credential(credential) - .unwrap(); + storage::store_credential(&mut env, credential).unwrap(); } - ctap_state.persistent_store.set_pin(&[0u8; 16], 4).unwrap(); + storage::set_pin(&mut env, &[0u8; 16], 4).unwrap(); let pin_uv_auth_param = Some(vec![ 0x1A, 0xA4, 0x96, 0xDA, 0x62, 0x80, 0x28, 0x13, 0xEB, 0x32, 0xB9, 0xF1, 0xD2, 0xA9, 0xD0, 0xD1, @@ -603,7 +587,7 @@ mod test { for _ in 0..NUM_CREDENTIALS { let cred_management_response = process_credential_management( - &mut ctap_state.persistent_store, + &mut env, &mut ctap_state.stateful_command_permission, &mut ctap_state.client_pin, cred_management_params, @@ -633,7 +617,7 @@ mod test { } let cred_management_response = process_credential_management( - &mut ctap_state.persistent_store, + &mut env, &mut ctap_state.stateful_command_permission, &mut ctap_state.client_pin, cred_management_params, @@ -662,16 +646,10 @@ mod test { let mut ctap_state = CtapState::new(&mut env, DUMMY_CLOCK_VALUE); ctap_state.client_pin = client_pin; - ctap_state - .persistent_store - .store_credential(credential_source1) - .unwrap(); - ctap_state - .persistent_store - .store_credential(credential_source2) - .unwrap(); + storage::store_credential(&mut env, credential_source1).unwrap(); + storage::store_credential(&mut env, credential_source2).unwrap(); - ctap_state.persistent_store.set_pin(&[0u8; 16], 4).unwrap(); + storage::set_pin(&mut env, &[0u8; 16], 4).unwrap(); let pin_uv_auth_param = Some(vec![ 0xF8, 0xB0, 0x3C, 0xC1, 0xD5, 0x58, 0x9C, 0xB7, 0x4D, 0x42, 0xA1, 0x64, 0x14, 0x28, 0x2B, 0x68, @@ -691,7 +669,7 @@ mod test { pin_uv_auth_param, }; let cred_management_response = process_credential_management( - &mut ctap_state.persistent_store, + &mut env, &mut ctap_state.stateful_command_permission, &mut ctap_state.client_pin, cred_management_params, @@ -714,7 +692,7 @@ mod test { pin_uv_auth_param: None, }; let cred_management_response = process_credential_management( - &mut ctap_state.persistent_store, + &mut env, &mut ctap_state.stateful_command_permission, &mut ctap_state.client_pin, cred_management_params, @@ -738,7 +716,7 @@ mod test { pin_uv_auth_param: None, }; let cred_management_response = process_credential_management( - &mut ctap_state.persistent_store, + &mut env, &mut ctap_state.stateful_command_permission, &mut ctap_state.client_pin, cred_management_params, @@ -763,12 +741,9 @@ mod test { let mut ctap_state = CtapState::new(&mut env, DUMMY_CLOCK_VALUE); ctap_state.client_pin = client_pin; - ctap_state - .persistent_store - .store_credential(credential_source) - .unwrap(); + storage::store_credential(&mut env, credential_source).unwrap(); - ctap_state.persistent_store.set_pin(&[0u8; 16], 4).unwrap(); + storage::set_pin(&mut env, &[0u8; 16], 4).unwrap(); let pin_uv_auth_param = Some(vec![ 0xBD, 0xE3, 0xEF, 0x8A, 0x77, 0x01, 0xB1, 0x69, 0x19, 0xE6, 0x62, 0xB9, 0x9B, 0x89, 0x9C, 0x64, @@ -791,7 +766,7 @@ mod test { pin_uv_auth_param: pin_uv_auth_param.clone(), }; let cred_management_response = process_credential_management( - &mut ctap_state.persistent_store, + &mut env, &mut ctap_state.stateful_command_permission, &mut ctap_state.client_pin, cred_management_params, @@ -809,7 +784,7 @@ mod test { pin_uv_auth_param, }; let cred_management_response = process_credential_management( - &mut ctap_state.persistent_store, + &mut env, &mut ctap_state.stateful_command_permission, &mut ctap_state.client_pin, cred_management_params, @@ -834,12 +809,9 @@ mod test { let mut ctap_state = CtapState::new(&mut env, DUMMY_CLOCK_VALUE); ctap_state.client_pin = client_pin; - ctap_state - .persistent_store - .store_credential(credential_source) - .unwrap(); + storage::store_credential(&mut env, credential_source).unwrap(); - ctap_state.persistent_store.set_pin(&[0u8; 16], 4).unwrap(); + storage::set_pin(&mut env, &[0u8; 16], 4).unwrap(); let pin_uv_auth_param = Some(vec![ 0xA5, 0x55, 0x8F, 0x03, 0xC3, 0xD3, 0x73, 0x1C, 0x07, 0xDA, 0x1F, 0x8C, 0xC7, 0xBD, 0x9D, 0xB7, @@ -868,7 +840,7 @@ mod test { pin_uv_auth_param, }; let cred_management_response = process_credential_management( - &mut ctap_state.persistent_store, + &mut env, &mut ctap_state.stateful_command_permission, &mut ctap_state.client_pin, cred_management_params, @@ -879,11 +851,10 @@ mod test { Ok(ResponseData::AuthenticatorCredentialManagement(None)) ); - let updated_credential = ctap_state - .persistent_store - .find_credential("example.com", &[0x1D; 32], false) - .unwrap() - .unwrap(); + let updated_credential = + storage::find_credential(&mut env, "example.com", &[0x1D; 32], false) + .unwrap() + .unwrap(); assert_eq!(updated_credential.user_handle, vec![0x01]); assert_eq!(&updated_credential.user_name.unwrap(), "new_name"); assert_eq!( @@ -898,7 +869,7 @@ mod test { let mut env = TestEnv::new(); let mut ctap_state = CtapState::new(&mut env, DUMMY_CLOCK_VALUE); - ctap_state.persistent_store.set_pin(&[0u8; 16], 4).unwrap(); + storage::set_pin(&mut env, &[0u8; 16], 4).unwrap(); let cred_management_params = AuthenticatorCredentialManagementParameters { sub_command: CredentialManagementSubCommand::GetCredsMetadata, @@ -907,7 +878,7 @@ mod test { pin_uv_auth_param: Some(vec![0u8; 16]), }; let cred_management_response = process_credential_management( - &mut ctap_state.persistent_store, + &mut env, &mut ctap_state.stateful_command_permission, &mut ctap_state.client_pin, cred_management_params, diff --git a/src/ctap/ctap1.rs b/src/ctap/ctap1.rs index 857c12b..5b72e69 100644 --- a/src/ctap/ctap1.rs +++ b/src/ctap/ctap1.rs @@ -14,6 +14,7 @@ use super::apdu::{Apdu, ApduStatusCode}; use super::CtapState; +use crate::ctap::storage; use crate::env::Env; use alloc::vec::Vec; use arrayref::array_ref; @@ -185,7 +186,7 @@ impl Ctap1Command { clock_value: ClockValue, ) -> Result, Ctap1StatusCode> { if !ctap_state - .allows_ctap1() + .allows_ctap1(env) .map_err(|_| Ctap1StatusCode::SW_INTERNAL_EXCEPTION)? { return Err(Ctap1StatusCode::SW_COMMAND_NOT_ALLOWED); @@ -259,14 +260,10 @@ impl Ctap1Command { return Err(Ctap1StatusCode::SW_INTERNAL_EXCEPTION); } - let certificate = ctap_state - .persistent_store - .attestation_certificate() + let certificate = storage::attestation_certificate(env) .map_err(|_| Ctap1StatusCode::SW_MEMERR)? .ok_or(Ctap1StatusCode::SW_INTERNAL_EXCEPTION)?; - let private_key = ctap_state - .persistent_store - .attestation_private_key() + let private_key = storage::attestation_private_key(env) .map_err(|_| Ctap1StatusCode::SW_INTERNAL_EXCEPTION)? .ok_or(Ctap1StatusCode::SW_INTERNAL_EXCEPTION)?; @@ -316,7 +313,7 @@ impl Ctap1Command { ctap_state: &mut CtapState, ) -> Result, Ctap1StatusCode> { let credential_source = ctap_state - .decrypt_credential_source(key_handle, &application) + .decrypt_credential_source(env, key_handle, &application) .map_err(|_| Ctap1StatusCode::SW_WRONG_DATA)?; if let Some(credential_source) = credential_source { if flags == Ctap1Flags::CheckOnly { @@ -326,7 +323,11 @@ impl Ctap1Command { .increment_global_signature_counter(env) .map_err(|_| Ctap1StatusCode::SW_WRONG_DATA)?; let mut signature_data = ctap_state - .generate_auth_data(&application, Ctap1Command::USER_PRESENCE_INDICATOR_BYTE) + .generate_auth_data( + env, + &application, + Ctap1Command::USER_PRESENCE_INDICATOR_BYTE, + ) .map_err(|_| Ctap1StatusCode::SW_WRONG_DATA)?; signature_data.extend(&challenge); let signature = credential_source @@ -400,7 +401,7 @@ mod test { env.user_presence() .set(|_| panic!("Unexpected user presence check in CTAP1")); let mut ctap_state = CtapState::new(&mut env, START_CLOCK_VALUE); - ctap_state.persistent_store.toggle_always_uv().unwrap(); + storage::toggle_always_uv(&mut env).unwrap(); let application = [0x0A; 32]; let message = create_register_message(&application); @@ -428,10 +429,7 @@ mod test { assert_eq!(response, Err(Ctap1StatusCode::SW_INTERNAL_EXCEPTION)); let fake_key = [0x41u8; key_material::ATTESTATION_PRIVATE_KEY_LENGTH]; - assert!(ctap_state - .persistent_store - .set_attestation_private_key(&fake_key) - .is_ok()); + assert!(storage::set_attestation_private_key(&mut env, &fake_key).is_ok()); ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE); ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE); let response = @@ -440,10 +438,7 @@ mod test { assert_eq!(response, Err(Ctap1StatusCode::SW_INTERNAL_EXCEPTION)); let fake_cert = [0x99u8; 100]; // Arbitrary length - assert!(ctap_state - .persistent_store - .set_attestation_certificate(&fake_cert[..]) - .is_ok()); + assert!(storage::set_attestation_certificate(&mut env, &fake_cert[..]).is_ok()); ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE); ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE); let response = @@ -452,7 +447,11 @@ mod test { assert_eq!(response[0], Ctap1Command::LEGACY_BYTE); assert_eq!(response[66], CREDENTIAL_ID_SIZE as u8); assert!(ctap_state - .decrypt_credential_source(response[67..67 + CREDENTIAL_ID_SIZE].to_vec(), &application) + .decrypt_credential_source( + &mut env, + response[67..67 + CREDENTIAL_ID_SIZE].to_vec(), + &application + ) .unwrap() .is_some()); const CERT_START: usize = 67 + CREDENTIAL_ID_SIZE; @@ -677,10 +676,7 @@ mod test { assert_eq!(response[0], 0x01); check_signature_counter( array_ref!(response, 1, 4), - ctap_state - .persistent_store - .global_signature_counter() - .unwrap(), + storage::global_signature_counter(&mut env).unwrap(), ); } @@ -709,10 +705,7 @@ mod test { assert_eq!(response[0], 0x01); check_signature_counter( array_ref!(response, 1, 4), - ctap_state - .persistent_store - .global_signature_counter() - .unwrap(), + storage::global_signature_counter(&mut env).unwrap(), ); } diff --git a/src/ctap/hid/mod.rs b/src/ctap/hid/mod.rs index 652a07c..f3fa288 100644 --- a/src/ctap/hid/mod.rs +++ b/src/ctap/hid/mod.rs @@ -526,7 +526,7 @@ mod test { const DUMMY_TIMESTAMP: Timestamp = Timestamp::from_ms(0); fn process_messages( - env: &mut impl Env, + env: &mut TestEnv, ctap_hid: &mut CtapHid, ctap_state: &mut CtapState, request: Vec, @@ -550,7 +550,7 @@ mod test { } fn cid_from_init( - env: &mut impl Env, + env: &mut TestEnv, ctap_hid: &mut CtapHid, ctap_state: &mut CtapState, ) -> ChannelID { diff --git a/src/ctap/large_blobs.rs b/src/ctap/large_blobs.rs index 9a01b8f..3a0eb62 100644 --- a/src/ctap/large_blobs.rs +++ b/src/ctap/large_blobs.rs @@ -17,7 +17,8 @@ use super::command::AuthenticatorLargeBlobsParameters; use super::customization::MAX_MSG_SIZE; use super::response::{AuthenticatorLargeBlobsResponse, ResponseData}; use super::status_code::Ctap2StatusCode; -use super::storage::PersistentStore; +use crate::ctap::storage; +use crate::env::Env; use alloc::vec; use alloc::vec::Vec; use byteorder::{ByteOrder, LittleEndian}; @@ -46,7 +47,7 @@ impl LargeBlobs { /// Process the large blob command. pub fn process_command( &mut self, - persistent_store: &mut PersistentStore, + env: &mut impl Env, client_pin: &mut ClientPin, large_blobs_params: AuthenticatorLargeBlobsParameters, ) -> Result { @@ -65,7 +66,7 @@ impl LargeBlobs { if get > MAX_FRAGMENT_LENGTH || offset.checked_add(get).is_none() { return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_LENGTH); } - let config = persistent_store.get_large_blob_array(offset, get)?; + let config = storage::get_large_blob_array(env, offset, get)?; return Ok(ResponseData::AuthenticatorLargeBlobs(Some( AuthenticatorLargeBlobsResponse { config }, ))); @@ -84,7 +85,7 @@ impl LargeBlobs { if offset != self.expected_next_offset { return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_SEQ); } - if persistent_store.pin_hash()?.is_some() || persistent_store.has_always_uv()? { + if storage::pin_hash(env)?.is_some() || storage::has_always_uv(env)? { let pin_uv_auth_param = pin_uv_auth_param.ok_or(Ctap2StatusCode::CTAP2_ERR_PUAT_REQUIRED)?; let pin_uv_auth_protocol = @@ -121,7 +122,7 @@ impl LargeBlobs { self.buffer = Vec::new(); return Err(Ctap2StatusCode::CTAP2_ERR_INTEGRITY_FAILURE); } - persistent_store.commit_large_blob_array(&self.buffer)?; + storage::commit_large_blob_array(env, &self.buffer)?; self.buffer = Vec::new(); } return Ok(ResponseData::AuthenticatorLargeBlobs(None)); @@ -137,13 +138,12 @@ mod test { use super::super::data_formats::PinUvAuthProtocol; use super::super::pin_protocol::authenticate_pin_uv_auth_token; use super::*; - use crypto::rng256::ThreadRng256; + use crate::env::test::TestEnv; #[test] fn test_process_command_get_empty() { - let mut rng = ThreadRng256 {}; - let mut persistent_store = PersistentStore::new(&mut rng); - let key_agreement_key = crypto::ecdh::SecKey::gensk(&mut rng); + let mut env = TestEnv::new(); + let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); let pin_uv_auth_token = [0x55; 32]; let mut client_pin = ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1); @@ -162,7 +162,7 @@ mod test { pin_uv_auth_protocol: None, }; let large_blobs_response = - large_blobs.process_command(&mut persistent_store, &mut client_pin, large_blobs_params); + large_blobs.process_command(&mut env, &mut client_pin, large_blobs_params); match large_blobs_response.unwrap() { ResponseData::AuthenticatorLargeBlobs(Some(response)) => { assert_eq!(response.config, large_blob); @@ -173,9 +173,8 @@ mod test { #[test] fn test_process_command_commit_and_get() { - let mut rng = ThreadRng256 {}; - let mut persistent_store = PersistentStore::new(&mut rng); - let key_agreement_key = crypto::ecdh::SecKey::gensk(&mut rng); + let mut env = TestEnv::new(); + let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); let pin_uv_auth_token = [0x55; 32]; let mut client_pin = ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1); @@ -195,7 +194,7 @@ mod test { pin_uv_auth_protocol: None, }; let large_blobs_response = - large_blobs.process_command(&mut persistent_store, &mut client_pin, large_blobs_params); + large_blobs.process_command(&mut env, &mut client_pin, large_blobs_params); assert_eq!( large_blobs_response, Ok(ResponseData::AuthenticatorLargeBlobs(None)) @@ -210,7 +209,7 @@ mod test { pin_uv_auth_protocol: None, }; let large_blobs_response = - large_blobs.process_command(&mut persistent_store, &mut client_pin, large_blobs_params); + large_blobs.process_command(&mut env, &mut client_pin, large_blobs_params); assert_eq!( large_blobs_response, Ok(ResponseData::AuthenticatorLargeBlobs(None)) @@ -225,7 +224,7 @@ mod test { pin_uv_auth_protocol: None, }; let large_blobs_response = - large_blobs.process_command(&mut persistent_store, &mut client_pin, large_blobs_params); + large_blobs.process_command(&mut env, &mut client_pin, large_blobs_params); match large_blobs_response.unwrap() { ResponseData::AuthenticatorLargeBlobs(Some(response)) => { assert_eq!(response.config, large_blob); @@ -236,9 +235,8 @@ mod test { #[test] fn test_process_command_commit_unexpected_offset() { - let mut rng = ThreadRng256 {}; - let mut persistent_store = PersistentStore::new(&mut rng); - let key_agreement_key = crypto::ecdh::SecKey::gensk(&mut rng); + let mut env = TestEnv::new(); + let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); let pin_uv_auth_token = [0x55; 32]; let mut client_pin = ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1); @@ -258,7 +256,7 @@ mod test { pin_uv_auth_protocol: None, }; let large_blobs_response = - large_blobs.process_command(&mut persistent_store, &mut client_pin, large_blobs_params); + large_blobs.process_command(&mut env, &mut client_pin, large_blobs_params); assert_eq!( large_blobs_response, Ok(ResponseData::AuthenticatorLargeBlobs(None)) @@ -274,7 +272,7 @@ mod test { pin_uv_auth_protocol: None, }; let large_blobs_response = - large_blobs.process_command(&mut persistent_store, &mut client_pin, large_blobs_params); + large_blobs.process_command(&mut env, &mut client_pin, large_blobs_params); assert_eq!( large_blobs_response, Err(Ctap2StatusCode::CTAP1_ERR_INVALID_SEQ), @@ -283,9 +281,8 @@ mod test { #[test] fn test_process_command_commit_unexpected_length() { - let mut rng = ThreadRng256 {}; - let mut persistent_store = PersistentStore::new(&mut rng); - let key_agreement_key = crypto::ecdh::SecKey::gensk(&mut rng); + let mut env = TestEnv::new(); + let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); let pin_uv_auth_token = [0x55; 32]; let mut client_pin = ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1); @@ -306,7 +303,7 @@ mod test { pin_uv_auth_protocol: None, }; let large_blobs_response = - large_blobs.process_command(&mut persistent_store, &mut client_pin, large_blobs_params); + large_blobs.process_command(&mut env, &mut client_pin, large_blobs_params); assert_eq!( large_blobs_response, Ok(ResponseData::AuthenticatorLargeBlobs(None)) @@ -321,7 +318,7 @@ mod test { pin_uv_auth_protocol: None, }; let large_blobs_response = - large_blobs.process_command(&mut persistent_store, &mut client_pin, large_blobs_params); + large_blobs.process_command(&mut env, &mut client_pin, large_blobs_params); assert_eq!( large_blobs_response, Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER), @@ -330,9 +327,8 @@ mod test { #[test] fn test_process_command_commit_end_offset_overflow() { - let mut rng = ThreadRng256 {}; - let mut persistent_store = PersistentStore::new(&mut rng); - let key_agreement_key = crypto::ecdh::SecKey::gensk(&mut rng); + let mut env = TestEnv::new(); + let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); let pin_uv_auth_token = [0x55; 32]; let mut client_pin = ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1); @@ -347,16 +343,15 @@ mod test { pin_uv_auth_protocol: None, }; assert_eq!( - large_blobs.process_command(&mut persistent_store, &mut client_pin, large_blobs_params), + large_blobs.process_command(&mut env, &mut client_pin, large_blobs_params), Err(Ctap2StatusCode::CTAP1_ERR_INVALID_LENGTH), ); } #[test] fn test_process_command_commit_unexpected_hash() { - let mut rng = ThreadRng256 {}; - let mut persistent_store = PersistentStore::new(&mut rng); - let key_agreement_key = crypto::ecdh::SecKey::gensk(&mut rng); + let mut env = TestEnv::new(); + let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); let pin_uv_auth_token = [0x55; 32]; let mut client_pin = ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1); @@ -375,7 +370,7 @@ mod test { pin_uv_auth_protocol: None, }; let large_blobs_response = - large_blobs.process_command(&mut persistent_store, &mut client_pin, large_blobs_params); + large_blobs.process_command(&mut env, &mut client_pin, large_blobs_params); assert_eq!( large_blobs_response, Err(Ctap2StatusCode::CTAP2_ERR_INTEGRITY_FAILURE), @@ -383,9 +378,8 @@ mod test { } fn test_helper_process_command_commit_with_pin(pin_uv_auth_protocol: PinUvAuthProtocol) { - let mut rng = ThreadRng256 {}; - let mut persistent_store = PersistentStore::new(&mut rng); - let key_agreement_key = crypto::ecdh::SecKey::gensk(&mut rng); + let mut env = TestEnv::new(); + let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); let pin_uv_auth_token = [0x55; 32]; let mut client_pin = ClientPin::new_test(key_agreement_key, pin_uv_auth_token, pin_uv_auth_protocol); @@ -396,7 +390,7 @@ mod test { let mut large_blob = vec![0x1B; DATA_LEN]; large_blob.extend_from_slice(&Sha256::hash(&large_blob[..])[..TRUNCATED_HASH_LEN]); - persistent_store.set_pin(&[0u8; 16], 4).unwrap(); + storage::set_pin(&mut env, &[0u8; 16], 4).unwrap(); let mut large_blob_data = vec![0xFF; 32]; // Command constant and offset bytes. large_blob_data.extend(&[0x0C, 0x00, 0x00, 0x00, 0x00, 0x00]); @@ -416,7 +410,7 @@ mod test { pin_uv_auth_protocol: Some(pin_uv_auth_protocol), }; let large_blobs_response = - large_blobs.process_command(&mut persistent_store, &mut client_pin, large_blobs_params); + large_blobs.process_command(&mut env, &mut client_pin, large_blobs_params); assert_eq!( large_blobs_response, Ok(ResponseData::AuthenticatorLargeBlobs(None)) diff --git a/src/ctap/mod.rs b/src/ctap/mod.rs index dba8ed9..eed20f9 100644 --- a/src/ctap/mod.rs +++ b/src/ctap/mod.rs @@ -60,11 +60,10 @@ use self::response::{ AuthenticatorVendorUpgradeInfoResponse, ResponseData, }; use self::status_code::Ctap2StatusCode; -use self::storage::PersistentStore; use self::timed_permission::TimedPermission; #[cfg(feature = "with_ctap1")] use self::timed_permission::U2fUserPresenceState; -use crate::embedded_flash::{UpgradeLocations, UpgradeStorage}; +use crate::api::upgrade_storage::UpgradeStorage; use crate::env::{Env, UserPresence}; use alloc::boxed::Box; use alloc::string::{String, ToString}; @@ -161,7 +160,7 @@ fn truncate_to_char_boundary(s: &str, mut max: usize) -> &str { /// The upgrade hash is computed over the firmware image and all metadata, /// except the hash itself. fn parse_metadata( - upgrade_locations: &UpgradeLocations, + upgrade_locations: &impl UpgradeStorage, metadata: &[u8], ) -> Result<[u8; 32], Ctap2StatusCode> { const METADATA_LEN: usize = 40; @@ -331,22 +330,19 @@ impl StatefulPermission { // This struct currently holds all state, not only the persistent memory. The persistent members are // in the persistent store field. pub struct CtapState { - persistent_store: PersistentStore, client_pin: ClientPin, #[cfg(feature = "with_ctap1")] pub(crate) u2f_up_state: U2fUserPresenceState, // The state initializes to Reset and its timeout, and never goes back to Reset. stateful_command_permission: StatefulPermission, large_blobs: LargeBlobs, - upgrade_locations: Option, } impl CtapState { - pub fn new(env: &mut impl Env, now: ClockValue) -> CtapState { - let persistent_store = PersistentStore::new(env.rng()); + pub fn new(env: &mut impl Env, now: ClockValue) -> Self { + storage::init(env).ok().unwrap(); let client_pin = ClientPin::new(env.rng()); CtapState { - persistent_store, client_pin, #[cfg(feature = "with_ctap1")] u2f_up_state: U2fUserPresenceState::new( @@ -355,7 +351,6 @@ impl CtapState { ), stateful_command_permission: StatefulPermission::new_reset(now), large_blobs: LargeBlobs::new(), - upgrade_locations: UpgradeLocations::new().ok(), } } @@ -373,8 +368,7 @@ impl CtapState { ) -> Result<(), Ctap2StatusCode> { if USE_SIGNATURE_COUNTER { let increment = env.rng().gen_uniform_u32x8()[0] % 8 + 1; - self.persistent_store - .incr_global_signature_counter(increment)?; + storage::incr_global_signature_counter(env, increment)?; } Ok(()) } @@ -383,8 +377,8 @@ impl CtapState { // If alwaysUv is enabled and the authenticator does not support internal UV, // CTAP1 needs to be disabled. #[cfg(feature = "with_ctap1")] - pub fn allows_ctap1(&self) -> Result { - Ok(!self.persistent_store.has_always_uv()?) + pub fn allows_ctap1(&self, env: &mut impl Env) -> Result { + Ok(!storage::has_always_uv(env)?) } // Encrypts the private key and relying party ID hash into a credential ID. Other @@ -397,7 +391,7 @@ impl CtapState { private_key: crypto::ecdsa::SecKey, application: &[u8; 32], ) -> Result, Ctap2StatusCode> { - let master_keys = self.persistent_store.master_keys()?; + let master_keys = storage::master_keys(env)?; let aes_enc_key = crypto::aes256::EncryptionKey::new(&master_keys.encryption); let mut plaintext = [0; 64]; private_key.to_bytes(array_mut_ref!(plaintext, 0, 32)); @@ -414,13 +408,14 @@ impl CtapState { // decrypted relying party ID hash. pub fn decrypt_credential_source( &self, + env: &mut impl Env, credential_id: Vec, rp_id_hash: &[u8], ) -> Result, Ctap2StatusCode> { if credential_id.len() != CREDENTIAL_ID_SIZE { return Ok(None); } - let master_keys = self.persistent_store.master_keys()?; + let master_keys = storage::master_keys(env)?; let payload_size = credential_id.len() - 32; if !verify_hmac_256::( &master_keys.hmac, @@ -500,17 +495,14 @@ impl CtapState { Command::AuthenticatorGetNextAssertion => { self.process_get_next_assertion(env, now) } - Command::AuthenticatorGetInfo => self.process_get_info(), - Command::AuthenticatorClientPin(params) => self.client_pin.process_command( - env.rng(), - &mut self.persistent_store, - params, - now, - ), + Command::AuthenticatorGetInfo => self.process_get_info(env), + Command::AuthenticatorClientPin(params) => { + self.client_pin.process_command(env, params, now) + } Command::AuthenticatorReset => self.process_reset(env, cid, now), Command::AuthenticatorCredentialManagement(params) => { process_credential_management( - &mut self.persistent_store, + env, &mut self.stateful_command_permission, &mut self.client_pin, params, @@ -518,22 +510,23 @@ impl CtapState { ) } Command::AuthenticatorSelection => self.process_selection(env, cid), - Command::AuthenticatorLargeBlobs(params) => self.large_blobs.process_command( - &mut self.persistent_store, - &mut self.client_pin, - params, - ), + Command::AuthenticatorLargeBlobs(params) => { + self.large_blobs + .process_command(env, &mut self.client_pin, params) + } Command::AuthenticatorConfig(params) => { - process_config(&mut self.persistent_store, &mut self.client_pin, params) + process_config(env, &mut self.client_pin, params) } // Vendor specific commands Command::AuthenticatorVendorConfigure(params) => { self.process_vendor_configure(env, params, cid) } Command::AuthenticatorVendorUpgrade(params) => { - self.process_vendor_upgrade(params) + self.process_vendor_upgrade(env, params) + } + Command::AuthenticatorVendorUpgradeInfo => { + self.process_vendor_upgrade_info(env) } - Command::AuthenticatorVendorUpgradeInfo => self.process_vendor_upgrade_info(), }; #[cfg(feature = "debug_ctap")] writeln!(&mut Console::new(), "Sending response: {:#?}", response).unwrap(); @@ -566,7 +559,7 @@ impl CtapState { // This case was added in FIDO 2.1. if auth_param.is_empty() { env.user_presence().check(cid)?; - if self.persistent_store.pin_hash()?.is_none() { + if storage::pin_hash(env)?.is_none() { return Err(Ctap2StatusCode::CTAP2_ERR_PIN_NOT_SET); } else { return Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID); @@ -606,7 +599,7 @@ impl CtapState { 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()? { + if !storage::enterprise_attestation(env)? { return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER); } match ( @@ -630,7 +623,7 @@ impl CtapState { let mut flags = match pin_uv_auth_param { Some(pin_uv_auth_param) => { // This case is not mentioned in CTAP2.1, so we keep 2.0 logic. - if self.persistent_store.pin_hash()?.is_none() { + if storage::pin_hash(env)?.is_none() { return Err(Ctap2StatusCode::CTAP2_ERR_PIN_NOT_SET); } self.client_pin.verify_pin_uv_auth_token( @@ -651,11 +644,11 @@ impl CtapState { if options.uv { return Err(Ctap2StatusCode::CTAP2_ERR_INVALID_OPTION); } - if self.persistent_store.has_always_uv()? { + if storage::has_always_uv(env)? { return Err(Ctap2StatusCode::CTAP2_ERR_PUAT_REQUIRED); } // Corresponds to makeCredUvNotRqd set to true. - if options.rk && self.persistent_store.pin_hash()?.is_some() { + if options.rk && storage::pin_hash(env)?.is_some() { return Err(Ctap2StatusCode::CTAP2_ERR_PUAT_REQUIRED); } 0x00 @@ -666,12 +659,9 @@ impl CtapState { let rp_id_hash = Sha256::hash(rp_id.as_bytes()); if let Some(exclude_list) = exclude_list { for cred_desc in exclude_list { - if self - .persistent_store - .find_credential(&rp_id, &cred_desc.key_id, !has_uv)? - .is_some() + if storage::find_credential(env, &rp_id, &cred_desc.key_id, !has_uv)?.is_some() || self - .decrypt_credential_source(cred_desc.key_id, &rp_id_hash)? + .decrypt_credential_source(env, cred_desc.key_id, &rp_id_hash)? .is_some() { // Perform this check, so bad actors can't brute force exclude_list @@ -691,11 +681,8 @@ impl CtapState { { cred_protect_policy = DEFAULT_CRED_PROTECT; } - let min_pin_length = extensions.min_pin_length - && self - .persistent_store - .min_pin_length_rp_ids()? - .contains(&rp_id); + let min_pin_length = + extensions.min_pin_length && storage::min_pin_length_rp_ids(env)?.contains(&rp_id); // None for no input, false for invalid input, true for valid input. let has_cred_blob_output = extensions.cred_blob.is_some(); let cred_blob = extensions @@ -735,7 +722,7 @@ impl CtapState { .user_display_name .map(|s| truncate_to_char_boundary(&s, 64).to_string()), cred_protect_policy, - creation_order: self.persistent_store.new_creation_order()?, + creation_order: storage::new_creation_order(env)?, user_name: user .user_name .map(|s| truncate_to_char_boundary(&s, 64).to_string()), @@ -745,14 +732,14 @@ impl CtapState { cred_blob, large_blob_key: large_blob_key.clone(), }; - self.persistent_store.store_credential(credential_source)?; + storage::store_credential(env, credential_source)?; random_id } else { self.encrypt_key_handle(env, sk.clone(), &rp_id_hash)? }; - let mut auth_data = self.generate_auth_data(&rp_id_hash, flags)?; - auth_data.extend(&self.persistent_store.aaguid()?); + let mut auth_data = self.generate_auth_data(env, &rp_id_hash, flags)?; + auth_data.extend(&storage::aaguid(env)?); // The length is fixed to 0x20 or 0x70 and fits one byte. if credential_id.len() > 0xFF { return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR); @@ -767,7 +754,7 @@ impl CtapState { None }; let min_pin_length_output = if min_pin_length { - Some(self.persistent_store.min_pin_length()? as u64) + Some(storage::min_pin_length(env)? as u64) } else { None }; @@ -785,15 +772,11 @@ impl CtapState { signature_data.extend(client_data_hash); let (signature, x5c) = if USE_BATCH_ATTESTATION || ep_att { - let attestation_private_key = self - .persistent_store - .attestation_private_key()? + let attestation_private_key = storage::attestation_private_key(env)? .ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?; let attestation_key = crypto::ecdsa::SecKey::from_bytes(&attestation_private_key).unwrap(); - let attestation_certificate = self - .persistent_store - .attestation_certificate()? + let attestation_certificate = storage::attestation_certificate(env)? .ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?; ( attestation_key.sign_rfc6979::(&signature_data), @@ -824,12 +807,13 @@ impl CtapState { // The computation is deterministic, and private_key expected to be unique. fn generate_cred_random( &mut self, + env: &mut impl Env, private_key: &crypto::ecdsa::SecKey, has_uv: bool, ) -> Result<[u8; 32], Ctap2StatusCode> { let mut private_key_bytes = [0u8; 32]; private_key.to_bytes(&mut private_key_bytes); - let key = self.persistent_store.cred_random_secret(has_uv)?; + let key = storage::cred_random_secret(env, has_uv)?; Ok(hmac_256::(&key, &private_key_bytes)) } @@ -852,7 +836,8 @@ impl CtapState { // Process extensions. if extensions.hmac_secret.is_some() || extensions.cred_blob { let encrypted_output = if let Some(hmac_secret_input) = extensions.hmac_secret { - let cred_random = self.generate_cred_random(&credential.private_key, has_uv)?; + let cred_random = + self.generate_cred_random(env, &credential.private_key, has_uv)?; Some(self.client_pin.process_hmac_secret( env.rng(), hmac_secret_input, @@ -920,22 +905,20 @@ impl CtapState { // Returns the first applicable credential from the allow list. fn get_any_credential_from_allow_list( &mut self, + env: &mut impl Env, allow_list: Vec, rp_id: &str, rp_id_hash: &[u8], has_uv: bool, ) -> Result, Ctap2StatusCode> { for allowed_credential in allow_list { - let credential = self.persistent_store.find_credential( - rp_id, - &allowed_credential.key_id, - !has_uv, - )?; + let credential = + storage::find_credential(env, rp_id, &allowed_credential.key_id, !has_uv)?; if credential.is_some() { return Ok(credential); } let credential = - self.decrypt_credential_source(allowed_credential.key_id, rp_id_hash)?; + self.decrypt_credential_source(env, allowed_credential.key_id, rp_id_hash)?; if credential.is_some() { return Ok(credential); } @@ -973,7 +956,7 @@ impl CtapState { let mut flags = match pin_uv_auth_param { Some(pin_uv_auth_param) => { // This case is not mentioned in CTAP2.1, so we keep 2.0 logic. - if self.persistent_store.pin_hash()?.is_none() { + if storage::pin_hash(env)?.is_none() { return Err(Ctap2StatusCode::CTAP2_ERR_PIN_NOT_SET); } self.client_pin.verify_pin_uv_auth_token( @@ -994,7 +977,7 @@ impl CtapState { if options.uv { return Err(Ctap2StatusCode::CTAP2_ERR_INVALID_OPTION); } - if options.up && self.persistent_store.has_always_uv()? { + if options.up && storage::has_always_uv(env)? { return Err(Ctap2StatusCode::CTAP2_ERR_PUAT_REQUIRED); } 0x00 @@ -1010,12 +993,18 @@ impl CtapState { let rp_id_hash = Sha256::hash(rp_id.as_bytes()); let (credential, next_credential_keys) = if let Some(allow_list) = allow_list { ( - self.get_any_credential_from_allow_list(allow_list, &rp_id, &rp_id_hash, has_uv)?, + self.get_any_credential_from_allow_list( + env, + allow_list, + &rp_id, + &rp_id_hash, + has_uv, + )?, vec![], ) } else { let mut iter_result = Ok(()); - let iter = self.persistent_store.iter_credentials(&mut iter_result)?; + let iter = storage::iter_credentials(env, &mut iter_result)?; let mut stored_credentials: Vec<(usize, u64)> = iter .filter_map(|(key, credential)| { if credential.rp_id == rp_id && (has_uv || credential.is_discoverable()) { @@ -1033,7 +1022,7 @@ impl CtapState { .collect(); let credential = stored_credentials .pop() - .map(|key| self.persistent_store.get_credential(key)) + .map(|key| storage::get_credential(env, key)) .transpose()?; (credential, stored_credentials) }; @@ -1050,7 +1039,7 @@ impl CtapState { let assertion_input = AssertionInput { client_data_hash, - auth_data: self.generate_auth_data(&rp_id_hash, flags)?, + auth_data: self.generate_auth_data(env, &rp_id_hash, flags)?, extensions, has_uv, }; @@ -1079,12 +1068,12 @@ impl CtapState { let (assertion_input, credential_key) = self .stateful_command_permission .next_assertion_credential()?; - let credential = self.persistent_store.get_credential(credential_key)?; + let credential = storage::get_credential(env, credential_key)?; self.assertion_response(env, credential, assertion_input, None) } - fn process_get_info(&self) -> Result { - let has_always_uv = self.persistent_store.has_always_uv()?; + fn process_get_info(&self, env: &mut impl Env) -> Result { + let has_always_uv = storage::has_always_uv(env)?; #[cfg_attr(not(feature = "with_ctap1"), allow(unused_mut))] let mut versions = vec![ String::from(FIDO2_VERSION_STRING), @@ -1098,10 +1087,7 @@ impl CtapState { } let mut options = vec![]; if ENTERPRISE_ATTESTATION_MODE.is_some() { - options.push(( - String::from("ep"), - self.persistent_store.enterprise_attestation()?, - )); + options.push((String::from("ep"), storage::enterprise_attestation(env)?)); } options.append(&mut vec![ (String::from("rk"), true), @@ -1109,10 +1095,7 @@ impl CtapState { (String::from("alwaysUv"), has_always_uv), (String::from("credMgmt"), true), (String::from("authnrCfg"), true), - ( - String::from("clientPin"), - self.persistent_store.pin_hash()?.is_some(), - ), + (String::from("clientPin"), storage::pin_hash(env)?.is_some()), (String::from("largeBlobs"), true), (String::from("pinUvAuthToken"), true), (String::from("setMinPINLength"), true), @@ -1129,7 +1112,7 @@ impl CtapState { String::from("credBlob"), String::from("largeBlobKey"), ]), - aaguid: self.persistent_store.aaguid()?, + aaguid: storage::aaguid(env)?, options: Some(options), max_msg_size: Some(MAX_MSG_SIZE as u64), // The order implies preference. We favor the new V2. @@ -1142,14 +1125,14 @@ impl CtapState { transports: Some(vec![AuthenticatorTransport::Usb]), algorithms: Some(vec![ES256_CRED_PARAM]), max_serialized_large_blob_array: Some(MAX_LARGE_BLOB_ARRAY_SIZE as u64), - force_pin_change: Some(self.persistent_store.has_force_pin_change()?), - min_pin_length: self.persistent_store.min_pin_length()?, + 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_rp_ids_for_set_min_pin_length: Some(MAX_RP_IDS_LENGTH as u64), certifications: None, remaining_discoverable_credentials: Some( - self.persistent_store.remaining_credentials()? as u64, + storage::remaining_credentials(env)? as u64 ), }, )) @@ -1169,7 +1152,7 @@ impl CtapState { } env.user_presence().check(cid)?; - self.persistent_store.reset(env.rng())?; + storage::reset(env)?; self.client_pin.reset(env.rng()); #[cfg(feature = "with_ctap1")] { @@ -1201,8 +1184,8 @@ impl CtapState { } // Sanity checks - let current_priv_key = self.persistent_store.attestation_private_key()?; - let current_cert = self.persistent_store.attestation_certificate()?; + let current_priv_key = storage::attestation_private_key(env)?; + let current_cert = storage::attestation_certificate(env)?; let response = match params.attestation_material { // Only reading values. @@ -1230,12 +1213,10 @@ impl CtapState { } } if current_cert.is_none() { - self.persistent_store - .set_attestation_certificate(&data.certificate)?; + storage::set_attestation_certificate(env, &data.certificate)?; } if current_priv_key.is_none() { - self.persistent_store - .set_attestation_private_key(&data.private_key)?; + storage::set_attestation_private_key(env, &data.private_key)?; } AuthenticatorVendorConfigureResponse { cert_programmed: true, @@ -1263,6 +1244,7 @@ impl CtapState { fn process_vendor_upgrade( &mut self, + env: &mut impl Env, params: AuthenticatorVendorUpgradeParameters, ) -> Result { let AuthenticatorVendorUpgradeParameters { @@ -1271,9 +1253,8 @@ impl CtapState { hash, signature, } = params; - let upgrade_locations = self - .upgrade_locations - .as_mut() + let upgrade_locations = env + .upgrade_storage() .ok_or(Ctap2StatusCode::CTAP1_ERR_INVALID_COMMAND)?; let written_slice = if let Some(address) = address { upgrade_locations @@ -1302,10 +1283,12 @@ impl CtapState { Ok(ResponseData::AuthenticatorVendorUpgrade) } - fn process_vendor_upgrade_info(&self) -> Result { - let upgrade_locations = self - .upgrade_locations - .as_ref() + fn process_vendor_upgrade_info( + &self, + env: &mut impl Env, + ) -> Result { + let upgrade_locations = env + .upgrade_storage() .ok_or(Ctap2StatusCode::CTAP1_ERR_INVALID_COMMAND)?; Ok(ResponseData::AuthenticatorVendorUpgradeInfo( AuthenticatorVendorUpgradeInfoResponse { @@ -1316,6 +1299,7 @@ impl CtapState { pub fn generate_auth_data( &self, + env: &mut impl Env, rp_id_hash: &[u8], flag_byte: u8, ) -> Result, Ctap2StatusCode> { @@ -1327,7 +1311,7 @@ impl CtapState { let mut signature_counter = [0u8; 4]; BigEndian::write_u32( &mut signature_counter, - self.persistent_store.global_signature_counter()?, + storage::global_signature_counter(env)?, ); auth_data.extend(&signature_counter); Ok(auth_data) @@ -1429,7 +1413,7 @@ mod test { String::from("credBlob"), String::from("largeBlobKey"), ], - 0x03 => ctap_state.persistent_store.aaguid().unwrap(), + 0x03 => storage::aaguid(&mut env).unwrap(), 0x04 => cbor_map_options! { "ep" => ENTERPRISE_ATTESTATION_MODE.map(|_| false), "rk" => true, @@ -1451,10 +1435,10 @@ mod test { 0x0A => cbor_array![ES256_CRED_PARAM], 0x0B => MAX_LARGE_BLOB_ARRAY_SIZE as u64, 0x0C => false, - 0x0D => ctap_state.persistent_store.min_pin_length().unwrap() as u64, + 0x0D => storage::min_pin_length(&mut env).unwrap() as u64, 0x0F => MAX_CRED_BLOB_LENGTH as u64, 0x10 => MAX_RP_IDS_LENGTH as u64, - 0x14 => ctap_state.persistent_store.remaining_credentials().unwrap() as u64, + 0x14 => storage::remaining_credentials(&mut env).unwrap() as u64, }; let mut response_cbor = vec![0x00]; @@ -1532,7 +1516,7 @@ mod test { check_make_response( make_credential_response, 0x41, - &ctap_state.persistent_store.aaguid().unwrap(), + &storage::aaguid(&mut env).unwrap(), 0x20, &[], ); @@ -1551,7 +1535,7 @@ mod test { check_make_response( make_credential_response, 0x41, - &ctap_state.persistent_store.aaguid().unwrap(), + &storage::aaguid(&mut env).unwrap(), CREDENTIAL_ID_SIZE as u8, &[], ); @@ -1596,10 +1580,7 @@ mod test { cred_blob: None, large_blob_key: None, }; - assert!(ctap_state - .persistent_store - .store_credential(excluded_credential_source) - .is_ok()); + assert!(storage::store_credential(&mut env, excluded_credential_source).is_ok()); let make_credential_response = ctap_state.process_make_credential(&mut env, make_credential_params, DUMMY_CHANNEL_ID); @@ -1622,10 +1603,7 @@ mod test { assert!(make_credential_response.is_ok()); let mut iter_result = Ok(()); - let iter = ctap_state - .persistent_store - .iter_credentials(&mut iter_result) - .unwrap(); + let iter = storage::iter_credentials(&mut env, &mut iter_result).unwrap(); // There is only 1 credential, so last is good enough. let (_, stored_credential) = iter.last().unwrap(); iter_result.unwrap(); @@ -1649,10 +1627,7 @@ mod test { assert!(make_credential_response.is_ok()); let mut iter_result = Ok(()); - let iter = ctap_state - .persistent_store - .iter_credentials(&mut iter_result) - .unwrap(); + let iter = storage::iter_credentials(&mut env, &mut iter_result).unwrap(); // There is only 1 credential, so last is good enough. let (_, stored_credential) = iter.last().unwrap(); iter_result.unwrap(); @@ -1687,7 +1662,7 @@ mod test { check_make_response( make_credential_response, 0xC1, - &ctap_state.persistent_store.aaguid().unwrap(), + &storage::aaguid(&mut env).unwrap(), CREDENTIAL_ID_SIZE as u8, &expected_extension_cbor, ); @@ -1713,7 +1688,7 @@ mod test { check_make_response( make_credential_response, 0xC1, - &ctap_state.persistent_store.aaguid().unwrap(), + &storage::aaguid(&mut env).unwrap(), 0x20, &expected_extension_cbor, ); @@ -1736,16 +1711,14 @@ mod test { check_make_response( make_credential_response, 0x41, - &ctap_state.persistent_store.aaguid().unwrap(), + &storage::aaguid(&mut env).unwrap(), 0x20, &[], ); // Second part: The extension is used. assert_eq!( - ctap_state - .persistent_store - .set_min_pin_length_rp_ids(vec!["example.com".to_string()]), + storage::set_min_pin_length_rp_ids(&mut env, vec!["example.com".to_string()]), Ok(()) ); @@ -1764,7 +1737,7 @@ mod test { check_make_response( make_credential_response, 0xC1, - &ctap_state.persistent_store.aaguid().unwrap(), + &storage::aaguid(&mut env).unwrap(), 0x20, &expected_extension_cbor, ); @@ -1789,16 +1762,13 @@ mod test { check_make_response( make_credential_response, 0xC1, - &ctap_state.persistent_store.aaguid().unwrap(), + &storage::aaguid(&mut env).unwrap(), 0x20, &expected_extension_cbor, ); let mut iter_result = Ok(()); - let iter = ctap_state - .persistent_store - .iter_credentials(&mut iter_result) - .unwrap(); + let iter = storage::iter_credentials(&mut env, &mut iter_result).unwrap(); // There is only 1 credential, so last is good enough. let (_, stored_credential) = iter.last().unwrap(); iter_result.unwrap(); @@ -1824,16 +1794,13 @@ mod test { check_make_response( make_credential_response, 0xC1, - &ctap_state.persistent_store.aaguid().unwrap(), + &storage::aaguid(&mut env).unwrap(), 0x20, &expected_extension_cbor, ); let mut iter_result = Ok(()); - let iter = ctap_state - .persistent_store - .iter_credentials(&mut iter_result) - .unwrap(); + let iter = storage::iter_credentials(&mut env, &mut iter_result).unwrap(); // There is only 1 credential, so last is good enough. let (_, stored_credential) = iter.last().unwrap(); iter_result.unwrap(); @@ -1862,10 +1829,7 @@ mod test { assert_eq!(large_blob_key.len(), 32); let mut iter_result = Ok(()); - let iter = ctap_state - .persistent_store - .iter_credentials(&mut iter_result) - .unwrap(); + let iter = storage::iter_credentials(&mut env, &mut iter_result).unwrap(); // There is only 1 credential, so last is good enough. let (_, stored_credential) = iter.last().unwrap(); iter_result.unwrap(); @@ -1883,7 +1847,7 @@ mod test { let mut ctap_state = CtapState::new(&mut env, DUMMY_CLOCK_VALUE); ctap_state.client_pin = client_pin; - ctap_state.persistent_store.set_pin(&[0x88; 16], 4).unwrap(); + storage::set_pin(&mut env, &[0x88; 16], 4).unwrap(); let client_data_hash = [0xCD]; let pin_uv_auth_param = authenticate_pin_uv_auth_token( @@ -1904,7 +1868,7 @@ mod test { check_make_response( make_credential_response, 0x45, - &ctap_state.persistent_store.aaguid().unwrap(), + &storage::aaguid(&mut env).unwrap(), 0x20, &[], ); @@ -1931,7 +1895,7 @@ mod test { fn test_non_resident_process_make_credential_with_pin() { let mut env = TestEnv::new(); let mut ctap_state = CtapState::new(&mut env, DUMMY_CLOCK_VALUE); - ctap_state.persistent_store.set_pin(&[0x88; 16], 4).unwrap(); + storage::set_pin(&mut env, &[0x88; 16], 4).unwrap(); let mut make_credential_params = create_minimal_make_credential_parameters(); make_credential_params.options.rk = false; @@ -1941,7 +1905,7 @@ mod test { check_make_response( make_credential_response, 0x41, - &ctap_state.persistent_store.aaguid().unwrap(), + &storage::aaguid(&mut env).unwrap(), 0x70, &[], ); @@ -1951,7 +1915,7 @@ mod test { fn test_resident_process_make_credential_with_pin() { let mut env = TestEnv::new(); let mut ctap_state = CtapState::new(&mut env, DUMMY_CLOCK_VALUE); - ctap_state.persistent_store.set_pin(&[0x88; 16], 4).unwrap(); + storage::set_pin(&mut env, &[0x88; 16], 4).unwrap(); let make_credential_params = create_minimal_make_credential_parameters(); let make_credential_response = @@ -1967,7 +1931,7 @@ mod test { let mut env = TestEnv::new(); let mut ctap_state = CtapState::new(&mut env, DUMMY_CLOCK_VALUE); - ctap_state.persistent_store.toggle_always_uv().unwrap(); + storage::toggle_always_uv(&mut env).unwrap(); let make_credential_params = create_minimal_make_credential_parameters(); let make_credential_response = ctap_state.process_make_credential(&mut env, make_credential_params, DUMMY_CHANNEL_ID); @@ -1976,7 +1940,7 @@ mod test { Err(Ctap2StatusCode::CTAP2_ERR_PUAT_REQUIRED) ); - ctap_state.persistent_store.set_pin(&[0x88; 16], 4).unwrap(); + storage::set_pin(&mut env, &[0x88; 16], 4).unwrap(); let mut make_credential_params = create_minimal_make_credential_parameters(); make_credential_params.pin_uv_auth_param = Some(vec![0xA4; 16]); make_credential_params.pin_uv_auth_protocol = Some(PinUvAuthProtocol::V1); @@ -2113,10 +2077,7 @@ mod test { DUMMY_CHANNEL_ID, DUMMY_CLOCK_VALUE, ); - let signature_counter = ctap_state - .persistent_store - .global_signature_counter() - .unwrap(); + let signature_counter = storage::global_signature_counter(&mut env).unwrap(); check_assertion_response(get_assertion_response, vec![0x1D], signature_counter, None); } @@ -2191,7 +2152,7 @@ mod test { let credential_id = match make_credential_response.unwrap() { ResponseData::AuthenticatorMakeCredential(make_credential_response) => { let auth_data = make_credential_response.auth_data; - let offset = 37 + ctap_state.persistent_store.aaguid().unwrap().len(); + let offset = 37 + storage::aaguid(&mut env).unwrap().len(); assert_eq!(auth_data[offset], 0x00); assert_eq!(auth_data[offset + 1] as usize, CREDENTIAL_ID_SIZE); auth_data[offset + 2..offset + 2 + CREDENTIAL_ID_SIZE].to_vec() @@ -2209,12 +2170,10 @@ mod test { permissions: None, permissions_rp_id: None, }; - let key_agreement_response = ctap_state.client_pin.process_command( - env.rng(), - &mut ctap_state.persistent_store, - client_pin_params, - DUMMY_CLOCK_VALUE, - ); + let key_agreement_response = + ctap_state + .client_pin + .process_command(&mut env, client_pin_params, DUMMY_CLOCK_VALUE); let get_assertion_params = get_assertion_hmac_secret_params( key_agreement_key, key_agreement_response.unwrap(), @@ -2267,12 +2226,10 @@ mod test { permissions: None, permissions_rp_id: None, }; - let key_agreement_response = ctap_state.client_pin.process_command( - env.rng(), - &mut ctap_state.persistent_store, - client_pin_params, - DUMMY_CLOCK_VALUE, - ); + let key_agreement_response = + ctap_state + .client_pin + .process_command(&mut env, client_pin_params, DUMMY_CLOCK_VALUE); let get_assertion_params = get_assertion_hmac_secret_params( key_agreement_key, key_agreement_response.unwrap(), @@ -2326,10 +2283,7 @@ mod test { cred_blob: None, large_blob_key: None, }; - assert!(ctap_state - .persistent_store - .store_credential(credential) - .is_ok()); + assert!(storage::store_credential(&mut env, credential).is_ok()); let get_assertion_params = AuthenticatorGetAssertionParameters { rp_id: String::from("example.com"), @@ -2372,10 +2326,7 @@ mod test { DUMMY_CHANNEL_ID, DUMMY_CLOCK_VALUE, ); - let signature_counter = ctap_state - .persistent_store - .global_signature_counter() - .unwrap(); + let signature_counter = storage::global_signature_counter(&mut env).unwrap(); check_assertion_response(get_assertion_response, vec![0x1D], signature_counter, None); let credential = PublicKeyCredentialSource { @@ -2392,10 +2343,7 @@ mod test { cred_blob: None, large_blob_key: None, }; - assert!(ctap_state - .persistent_store - .store_credential(credential) - .is_ok()); + assert!(storage::store_credential(&mut env, credential).is_ok()); let get_assertion_params = AuthenticatorGetAssertionParameters { rp_id: String::from("example.com"), @@ -2442,10 +2390,7 @@ mod test { cred_blob: Some(vec![0xCB]), large_blob_key: None, }; - assert!(ctap_state - .persistent_store - .store_credential(credential) - .is_ok()); + assert!(storage::store_credential(&mut env, credential).is_ok()); let extensions = GetAssertionExtensions { cred_blob: true, @@ -2469,10 +2414,7 @@ mod test { DUMMY_CHANNEL_ID, DUMMY_CLOCK_VALUE, ); - let signature_counter = ctap_state - .persistent_store - .global_signature_counter() - .unwrap(); + let signature_counter = storage::global_signature_counter(&mut env).unwrap(); let expected_extension_cbor = [ 0xA1, 0x68, 0x63, 0x72, 0x65, 0x64, 0x42, 0x6C, 0x6F, 0x62, 0x41, 0xCB, ]; @@ -2506,10 +2448,7 @@ mod test { cred_blob: None, large_blob_key: Some(vec![0x1C; 32]), }; - assert!(ctap_state - .persistent_store - .store_credential(credential) - .is_ok()); + assert!(storage::store_credential(&mut env, credential).is_ok()); let extensions = GetAssertionExtensions { large_blob_key: Some(true), @@ -2578,7 +2517,7 @@ mod test { ctap_state.client_pin = client_pin; // The PIN length is outside of the test scope and most likely incorrect. - ctap_state.persistent_store.set_pin(&[0u8; 16], 4).unwrap(); + storage::set_pin(&mut env, &[0u8; 16], 4).unwrap(); let client_data_hash = vec![0xCD]; let pin_uv_auth_param = authenticate_pin_uv_auth_token( &pin_uv_auth_token, @@ -2604,10 +2543,7 @@ mod test { DUMMY_CHANNEL_ID, DUMMY_CLOCK_VALUE, ); - let signature_counter = ctap_state - .persistent_store - .global_signature_counter() - .unwrap(); + let signature_counter = storage::global_signature_counter(&mut env).unwrap(); check_assertion_response_with_user( get_assertion_response, user2, @@ -2694,10 +2630,7 @@ mod test { DUMMY_CHANNEL_ID, DUMMY_CLOCK_VALUE, ); - let signature_counter = ctap_state - .persistent_store - .global_signature_counter() - .unwrap(); + let signature_counter = storage::global_signature_counter(&mut env).unwrap(); check_assertion_response( get_assertion_response, vec![0x03], @@ -2808,17 +2741,14 @@ mod test { cred_blob: None, large_blob_key: None, }; - assert!(ctap_state - .persistent_store - .store_credential(credential_source) - .is_ok()); - assert!(ctap_state.persistent_store.count_credentials().unwrap() > 0); + assert!(storage::store_credential(&mut env, credential_source).is_ok()); + assert!(storage::count_credentials(&mut env).unwrap() > 0); let reset_reponse = ctap_state.process_command(&mut env, &[0x07], DUMMY_CHANNEL_ID, DUMMY_CLOCK_VALUE); let expected_response = vec![0x00]; assert_eq!(reset_reponse, expected_response); - assert!(ctap_state.persistent_store.count_credentials().unwrap() == 0); + assert!(storage::count_credentials(&mut env).unwrap() == 0); } #[test] @@ -2889,7 +2819,7 @@ mod test { .encrypt_key_handle(&mut env, private_key.clone(), &rp_id_hash) .unwrap(); let decrypted_source = ctap_state - .decrypt_credential_source(encrypted_id, &rp_id_hash) + .decrypt_credential_source(&mut env, encrypted_id, &rp_id_hash) .unwrap() .unwrap(); @@ -2911,7 +2841,7 @@ mod test { let mut modified_id = encrypted_id.clone(); modified_id[i] ^= 0x01; assert!(ctap_state - .decrypt_credential_source(modified_id, &rp_id_hash) + .decrypt_credential_source(&mut env, modified_id, &rp_id_hash) .unwrap() .is_none()); } @@ -2922,19 +2852,13 @@ mod test { let mut env = TestEnv::new(); let mut ctap_state = CtapState::new(&mut env, DUMMY_CLOCK_VALUE); - let mut last_counter = ctap_state - .persistent_store - .global_signature_counter() - .unwrap(); + let mut last_counter = storage::global_signature_counter(&mut env).unwrap(); assert!(last_counter > 0); for _ in 0..100 { assert!(ctap_state .increment_global_signature_counter(&mut env) .is_ok()); - let next_counter = ctap_state - .persistent_store - .global_signature_counter() - .unwrap(); + let next_counter = storage::global_signature_counter(&mut env).unwrap(); assert!(next_counter > last_counter); last_counter = next_counter; } @@ -2988,19 +2912,11 @@ mod test { )) ); assert_eq!( - ctap_state - .persistent_store - .attestation_certificate() - .unwrap() - .unwrap(), + storage::attestation_certificate(&mut env).unwrap().unwrap(), dummy_cert ); assert_eq!( - ctap_state - .persistent_store - .attestation_private_key() - .unwrap() - .unwrap(), + storage::attestation_private_key(&mut env).unwrap().unwrap(), dummy_key ); @@ -3027,19 +2943,11 @@ mod test { )) ); assert_eq!( - ctap_state - .persistent_store - .attestation_certificate() - .unwrap() - .unwrap(), + storage::attestation_certificate(&mut env).unwrap().unwrap(), dummy_cert ); assert_eq!( - ctap_state - .persistent_store - .attestation_private_key() - .unwrap() - .unwrap(), + storage::attestation_private_key(&mut env).unwrap().unwrap(), dummy_key ); @@ -3066,11 +2974,10 @@ mod test { #[test] fn test_parse_metadata() { let mut env = TestEnv::new(); - let mut ctap_state = CtapState::new(&mut env, DUMMY_CLOCK_VALUE); // The test buffer starts fully erased with 0xFF bytes. // The compiler issues an incorrect warning. #[allow(unused_mut)] - let mut upgrade_locations = ctap_state.upgrade_locations.as_mut().unwrap(); + let mut upgrade_locations = env.upgrade_storage().unwrap(); // Partition of 0x40000 bytes and 8 bytes metadata are hashed. let hashed_data = vec![0xFF; 0x40000 + 8]; @@ -3170,7 +3077,7 @@ mod test { let data = vec![0xFF; 0x1000]; let hash = Sha256::hash(&data).to_vec(); - let upgrade_locations = ctap_state.upgrade_locations.as_ref().unwrap(); + let upgrade_locations = env.upgrade_storage().unwrap(); let partition_length = upgrade_locations.partition_length(); let mut signed_over_data = upgrade_locations .read_partition(0, partition_length) @@ -3191,66 +3098,84 @@ mod test { }; // Write to partition and metadata. - let response = ctap_state.process_vendor_upgrade(AuthenticatorVendorUpgradeParameters { - address: Some(0x20000), - data: data.clone(), - hash: hash.clone(), - signature: None, - }); + let response = ctap_state.process_vendor_upgrade( + &mut env, + AuthenticatorVendorUpgradeParameters { + address: Some(0x20000), + data: data.clone(), + hash: hash.clone(), + signature: None, + }, + ); assert_eq!(response, Ok(ResponseData::AuthenticatorVendorUpgrade)); // We can't inject a public key for our known private key, so the last upgrade step fails. // verify_signature is separately tested for that reason. - let response = ctap_state.process_vendor_upgrade(AuthenticatorVendorUpgradeParameters { - address: None, - data: metadata.clone(), - hash: metadata_hash.clone(), - signature: Some(cose_signature.clone()), - }); + let response = ctap_state.process_vendor_upgrade( + &mut env, + AuthenticatorVendorUpgradeParameters { + address: None, + data: metadata.clone(), + hash: metadata_hash.clone(), + signature: Some(cose_signature.clone()), + }, + ); assert_eq!(response, Err(Ctap2StatusCode::CTAP2_ERR_INTEGRITY_FAILURE)); // Write metadata of a wrong size. - let response = ctap_state.process_vendor_upgrade(AuthenticatorVendorUpgradeParameters { - address: None, - data: metadata[..METADATA_LEN - 1].to_vec(), - hash: metadata_hash, - signature: Some(cose_signature), - }); + let response = ctap_state.process_vendor_upgrade( + &mut env, + AuthenticatorVendorUpgradeParameters { + address: None, + data: metadata[..METADATA_LEN - 1].to_vec(), + hash: metadata_hash, + signature: Some(cose_signature), + }, + ); assert_eq!(response, Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)); // Write outside of the partition. - let response = ctap_state.process_vendor_upgrade(AuthenticatorVendorUpgradeParameters { - address: Some(0x40000), - data: data.clone(), - hash, - signature: None, - }); + let response = ctap_state.process_vendor_upgrade( + &mut env, + AuthenticatorVendorUpgradeParameters { + address: Some(0x40000), + data: data.clone(), + hash, + signature: None, + }, + ); assert_eq!(response, Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)); // Write a bad hash. - let response = ctap_state.process_vendor_upgrade(AuthenticatorVendorUpgradeParameters { - address: Some(0x20000), - data, - hash: [0xEE; 32].to_vec(), - signature: None, - }); + let response = ctap_state.process_vendor_upgrade( + &mut env, + AuthenticatorVendorUpgradeParameters { + address: Some(0x20000), + data, + hash: [0xEE; 32].to_vec(), + signature: None, + }, + ); assert_eq!(response, Err(Ctap2StatusCode::CTAP2_ERR_INTEGRITY_FAILURE)); } #[test] fn test_vendor_upgrade_no_second_partition() { let mut env = TestEnv::new(); + env.disable_upgrade_storage(); let mut ctap_state = CtapState::new(&mut env, DUMMY_CLOCK_VALUE); - ctap_state.upgrade_locations = None; let data = vec![0xFF; 0x1000]; let hash = Sha256::hash(&data).to_vec(); - let response = ctap_state.process_vendor_upgrade(AuthenticatorVendorUpgradeParameters { - address: Some(0), - data, - hash, - signature: None, - }); + let response = ctap_state.process_vendor_upgrade( + &mut env, + AuthenticatorVendorUpgradeParameters { + address: Some(0), + data, + hash, + signature: None, + }, + ); assert_eq!(response, Err(Ctap2StatusCode::CTAP1_ERR_INVALID_COMMAND)); } @@ -3258,13 +3183,9 @@ mod test { fn test_vendor_upgrade_info() { let mut env = TestEnv::new(); let ctap_state = CtapState::new(&mut env, DUMMY_CLOCK_VALUE); - let partition_address = ctap_state - .upgrade_locations - .as_ref() - .unwrap() - .partition_address(); + let partition_address = env.upgrade_storage().unwrap().partition_address(); - let upgrade_info_reponse = ctap_state.process_vendor_upgrade_info(); + let upgrade_info_reponse = ctap_state.process_vendor_upgrade_info(&mut env); assert_eq!( upgrade_info_reponse, Ok(ResponseData::AuthenticatorVendorUpgradeInfo( diff --git a/src/ctap/storage.rs b/src/ctap/storage.rs index b84856f..371e38f 100644 --- a/src/ctap/storage.rs +++ b/src/ctap/storage.rs @@ -26,7 +26,7 @@ use crate::ctap::data_formats::{ use crate::ctap::key_material; use crate::ctap::status_code::Ctap2StatusCode; use crate::ctap::INITIAL_SIGNATURE_COUNTER; -use crate::embedded_flash::{new_storage, Storage}; +use crate::env::Env; use alloc::string::String; use alloc::vec; use alloc::vec::Vec; @@ -55,582 +55,566 @@ struct PinProperties { code_point_length: u8, } -/// CTAP persistent storage. -pub struct PersistentStore { - store: persistent_store::Store, +/// Initializes the store by creating missing objects. +pub fn init(env: &mut impl Env) -> Result<(), Ctap2StatusCode> { + // Generate and store the master keys if they are missing. + if env.store().find_handle(key::MASTER_KEYS)?.is_none() { + let master_encryption_key = env.rng().gen_uniform_u8x32(); + let master_hmac_key = env.rng().gen_uniform_u8x32(); + let mut master_keys = Vec::with_capacity(64); + master_keys.extend_from_slice(&master_encryption_key); + master_keys.extend_from_slice(&master_hmac_key); + env.store().insert(key::MASTER_KEYS, &master_keys)?; + } + + // Generate and store the CredRandom secrets if they are missing. + if env.store().find_handle(key::CRED_RANDOM_SECRET)?.is_none() { + let cred_random_with_uv = env.rng().gen_uniform_u8x32(); + let cred_random_without_uv = env.rng().gen_uniform_u8x32(); + let mut cred_random = Vec::with_capacity(64); + cred_random.extend_from_slice(&cred_random_without_uv); + cred_random.extend_from_slice(&cred_random_with_uv); + env.store().insert(key::CRED_RANDOM_SECRET, &cred_random)?; + } + + if env.store().find_handle(key::AAGUID)?.is_none() { + set_aaguid(env, key_material::AAGUID)?; + } + Ok(()) } -impl PersistentStore { - /// Gives access to the persistent store. - /// - /// # Safety - /// - /// This should be at most one instance of persistent store per program lifetime. - pub fn new(rng: &mut impl Rng256) -> PersistentStore { - let storage = new_storage().ok().unwrap(); - let mut store = PersistentStore { - store: persistent_store::Store::new(storage).ok().unwrap(), - }; - store.init(rng).ok().unwrap(); - store +/// Returns the credential at the given key. +/// +/// # Errors +/// +/// Returns `CTAP2_ERR_VENDOR_INTERNAL_ERROR` if the key does not hold a valid credential. +pub fn get_credential( + env: &mut impl Env, + key: usize, +) -> Result { + let min_key = key::CREDENTIALS.start; + if key < min_key || key >= min_key + MAX_SUPPORTED_RESIDENT_KEYS { + return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR); } + let credential_entry = env + .store() + .find(key)? + .ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?; + deserialize_credential(&credential_entry) + .ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR) +} - /// Initializes the store by creating missing objects. - fn init(&mut self, rng: &mut impl Rng256) -> Result<(), Ctap2StatusCode> { - // Generate and store the master keys if they are missing. - if self.store.find_handle(key::MASTER_KEYS)?.is_none() { - let master_encryption_key = rng.gen_uniform_u8x32(); - let master_hmac_key = rng.gen_uniform_u8x32(); - let mut master_keys = Vec::with_capacity(64); - master_keys.extend_from_slice(&master_encryption_key); - master_keys.extend_from_slice(&master_hmac_key); - self.store.insert(key::MASTER_KEYS, &master_keys)?; - } - - // Generate and store the CredRandom secrets if they are missing. - if self.store.find_handle(key::CRED_RANDOM_SECRET)?.is_none() { - let cred_random_with_uv = rng.gen_uniform_u8x32(); - let cred_random_without_uv = rng.gen_uniform_u8x32(); - let mut cred_random = Vec::with_capacity(64); - cred_random.extend_from_slice(&cred_random_without_uv); - cred_random.extend_from_slice(&cred_random_with_uv); - self.store.insert(key::CRED_RANDOM_SECRET, &cred_random)?; - } - - if self.store.find_handle(key::AAGUID)?.is_none() { - self.set_aaguid(key_material::AAGUID)?; - } - Ok(()) +/// Finds the key and value for a given credential ID. +/// +/// # Errors +/// +/// Returns `CTAP2_ERR_NO_CREDENTIALS` if the credential is not found. +pub fn find_credential_item( + env: &mut impl Env, + credential_id: &[u8], +) -> Result<(usize, PublicKeyCredentialSource), Ctap2StatusCode> { + let mut iter_result = Ok(()); + let iter = iter_credentials(env, &mut iter_result)?; + let mut credentials: Vec<(usize, PublicKeyCredentialSource)> = iter + .filter(|(_, credential)| credential.credential_id == credential_id) + .collect(); + iter_result?; + if credentials.len() > 1 { + return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR); } + credentials + .pop() + .ok_or(Ctap2StatusCode::CTAP2_ERR_NO_CREDENTIALS) +} - /// Returns the credential at the given key. - /// - /// # Errors - /// - /// Returns `CTAP2_ERR_VENDOR_INTERNAL_ERROR` if the key does not hold a valid credential. - pub fn get_credential(&self, key: usize) -> Result { - let min_key = key::CREDENTIALS.start; - if key < min_key || key >= min_key + MAX_SUPPORTED_RESIDENT_KEYS { +/// Returns the first matching credential. +/// +/// Returns `None` if no credentials are matched or if `check_cred_protect` is set and the first +/// matched credential requires user verification. +pub fn find_credential( + env: &mut impl Env, + rp_id: &str, + credential_id: &[u8], + check_cred_protect: bool, +) -> Result, Ctap2StatusCode> { + let credential = match find_credential_item(env, credential_id) { + Err(Ctap2StatusCode::CTAP2_ERR_NO_CREDENTIALS) => return Ok(None), + Err(e) => return Err(e), + Ok((_key, credential)) => credential, + }; + let is_protected = credential.cred_protect_policy + == Some(CredentialProtectionPolicy::UserVerificationRequired); + if credential.rp_id != rp_id || (check_cred_protect && is_protected) { + return Ok(None); + } + Ok(Some(credential)) +} + +/// Stores or updates a credential. +/// +/// If a credential with the same RP id and user handle already exists, it is replaced. +pub fn store_credential( + env: &mut impl Env, + new_credential: PublicKeyCredentialSource, +) -> Result<(), Ctap2StatusCode> { + // 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 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] { return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR); } - let credential_entry = self - .store - .find(key)? - .ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?; - deserialize_credential(&credential_entry) - .ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR) - } - - /// Finds the key and value for a given credential ID. - /// - /// # Errors - /// - /// Returns `CTAP2_ERR_NO_CREDENTIALS` if the credential is not found. - pub fn find_credential_item( - &self, - credential_id: &[u8], - ) -> Result<(usize, PublicKeyCredentialSource), Ctap2StatusCode> { - let mut iter_result = Ok(()); - let iter = self.iter_credentials(&mut iter_result)?; - let mut credentials: Vec<(usize, PublicKeyCredentialSource)> = iter - .filter(|(_, credential)| credential.credential_id == credential_id) - .collect(); - iter_result?; - if credentials.len() > 1 { - return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR); - } - credentials - .pop() - .ok_or(Ctap2StatusCode::CTAP2_ERR_NO_CREDENTIALS) - } - - /// Returns the first matching credential. - /// - /// Returns `None` if no credentials are matched or if `check_cred_protect` is set and the first - /// matched credential requires user verification. - pub fn find_credential( - &self, - rp_id: &str, - credential_id: &[u8], - check_cred_protect: bool, - ) -> Result, Ctap2StatusCode> { - let credential = match self.find_credential_item(credential_id) { - Err(Ctap2StatusCode::CTAP2_ERR_NO_CREDENTIALS) => return Ok(None), - Err(e) => return Err(e), - Ok((_key, credential)) => credential, - }; - let is_protected = credential.cred_protect_policy - == Some(CredentialProtectionPolicy::UserVerificationRequired); - if credential.rp_id != rp_id || (check_cred_protect && is_protected) { - return Ok(None); - } - Ok(Some(credential)) - } - - /// Stores or updates a credential. - /// - /// If a credential with the same RP id and user handle already exists, it is replaced. - pub fn store_credential( - &mut self, - new_credential: PublicKeyCredentialSource, - ) -> Result<(), Ctap2StatusCode> { - // 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 iter_result = Ok(()); - let iter = self.iter_credentials(&mut iter_result)?; - for (key, credential) in iter { - if key < min_key || key - min_key >= MAX_SUPPORTED_RESIDENT_KEYS || keys[key - min_key] - { + keys[key - min_key] = true; + if credential.rp_id == new_credential.rp_id + && credential.user_handle == new_credential.user_handle + { + if old_key.is_some() { return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR); } - keys[key - min_key] = true; - if credential.rp_id == new_credential.rp_id - && credential.user_handle == new_credential.user_handle - { - if old_key.is_some() { - return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR); - } - old_key = Some(key); - } - } - iter_result?; - 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) - .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. - Some(x) => x, - }; - let value = serialize_credential(new_credential)?; - self.store.insert(key, &value)?; - Ok(()) - } - - /// Deletes a credential. - /// - /// # Errors - /// - /// Returns `CTAP2_ERR_NO_CREDENTIALS` if the credential is not found. - pub fn delete_credential(&mut self, credential_id: &[u8]) -> Result<(), Ctap2StatusCode> { - let (key, _) = self.find_credential_item(credential_id)?; - Ok(self.store.remove(key)?) - } - - /// Updates a credential's user information. - /// - /// # Errors - /// - /// Returns `CTAP2_ERR_NO_CREDENTIALS` if the credential is not found. - pub fn update_credential( - &mut self, - credential_id: &[u8], - user: PublicKeyCredentialUserEntity, - ) -> Result<(), Ctap2StatusCode> { - let (key, mut credential) = self.find_credential_item(credential_id)?; - credential.user_name = user.user_name; - credential.user_display_name = user.user_display_name; - credential.user_icon = user.user_icon; - let value = serialize_credential(credential)?; - Ok(self.store.insert(key, &value)?) - } - - /// Returns the number of credentials. - pub fn count_credentials(&self) -> Result { - let mut count = 0; - for handle in self.store.iter()? { - count += key::CREDENTIALS.contains(&handle?.get_key()) as usize; - } - Ok(count) - } - - /// Returns the estimated number of credentials that can still be stored. - pub fn remaining_credentials(&self) -> Result { - MAX_SUPPORTED_RESIDENT_KEYS - .checked_sub(self.count_credentials()?) - .ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR) - } - - /// Iterates through the credentials. - /// - /// If an error is encountered during iteration, it is written to `result`. - pub fn iter_credentials<'a>( - &'a self, - result: &'a mut Result<(), Ctap2StatusCode>, - ) -> Result, Ctap2StatusCode> { - IterCredentials::new(&self.store, result) - } - - /// Returns the next creation order. - pub fn new_creation_order(&self) -> Result { - let mut iter_result = Ok(()); - let iter = self.iter_credentials(&mut iter_result)?; - let max = iter.map(|(_, credential)| credential.creation_order).max(); - iter_result?; - Ok(max.unwrap_or(0).wrapping_add(1)) - } - - /// Returns the global signature counter. - pub fn global_signature_counter(&self) -> Result { - match self.store.find(key::GLOBAL_SIGNATURE_COUNTER)? { - None => Ok(INITIAL_SIGNATURE_COUNTER), - Some(value) if value.len() == 4 => Ok(u32::from_ne_bytes(*array_ref!(&value, 0, 4))), - Some(_) => Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR), + old_key = Some(key); } } - - /// Increments the global signature counter. - pub fn incr_global_signature_counter(&mut self, increment: u32) -> Result<(), Ctap2StatusCode> { - let old_value = self.global_signature_counter()?; - // In hopes that servers handle the wrapping gracefully. - let new_value = old_value.wrapping_add(increment); - self.store - .insert(key::GLOBAL_SIGNATURE_COUNTER, &new_value.to_ne_bytes())?; - Ok(()) + iter_result?; + 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) + .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. + Some(x) => x, + }; + let value = serialize_credential(new_credential)?; + env.store().insert(key, &value)?; + Ok(()) +} - /// Returns the master keys. - pub fn master_keys(&self) -> Result { - let master_keys = self - .store - .find(key::MASTER_KEYS)? - .ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?; - if master_keys.len() != 64 { - return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR); - } - Ok(MasterKeys { - encryption: *array_ref![master_keys, 0, 32], - hmac: *array_ref![master_keys, 32, 32], - }) +/// Deletes a credential. +/// +/// # Errors +/// +/// Returns `CTAP2_ERR_NO_CREDENTIALS` if the credential is not found. +pub fn delete_credential(env: &mut impl Env, credential_id: &[u8]) -> Result<(), Ctap2StatusCode> { + let (key, _) = find_credential_item(env, credential_id)?; + Ok(env.store().remove(key)?) +} + +/// Updates a credential's user information. +/// +/// # Errors +/// +/// Returns `CTAP2_ERR_NO_CREDENTIALS` if the credential is not found. +pub fn update_credential( + env: &mut impl Env, + credential_id: &[u8], + user: PublicKeyCredentialUserEntity, +) -> Result<(), Ctap2StatusCode> { + let (key, mut credential) = find_credential_item(env, credential_id)?; + credential.user_name = user.user_name; + credential.user_display_name = user.user_display_name; + credential.user_icon = user.user_icon; + let value = serialize_credential(credential)?; + Ok(env.store().insert(key, &value)?) +} + +/// Returns the number of credentials. +pub fn count_credentials(env: &mut impl Env) -> Result { + let mut count = 0; + for handle in env.store().iter()? { + count += key::CREDENTIALS.contains(&handle?.get_key()) as usize; } + Ok(count) +} - /// Returns the CredRandom secret. - pub fn cred_random_secret(&self, has_uv: bool) -> Result<[u8; 32], Ctap2StatusCode> { - let cred_random_secret = self - .store - .find(key::CRED_RANDOM_SECRET)? - .ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?; - if cred_random_secret.len() != 64 { - return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR); - } - let offset = if has_uv { 32 } else { 0 }; - Ok(*array_ref![cred_random_secret, offset, 32]) +/// Returns the estimated number of credentials that can still be stored. +pub fn remaining_credentials(env: &mut impl Env) -> Result { + MAX_SUPPORTED_RESIDENT_KEYS + .checked_sub(count_credentials(env)?) + .ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR) +} + +/// Iterates through the credentials. +/// +/// If an error is encountered during iteration, it is written to `result`. +pub fn iter_credentials<'a, E: Env>( + env: &'a mut E, + result: &'a mut Result<(), Ctap2StatusCode>, +) -> Result, Ctap2StatusCode> { + IterCredentials::new(env.store(), result) +} + +/// Returns the next creation order. +pub fn new_creation_order(env: &mut impl Env) -> Result { + let mut iter_result = Ok(()); + let iter = iter_credentials(env, &mut iter_result)?; + let max = iter.map(|(_, credential)| credential.creation_order).max(); + iter_result?; + Ok(max.unwrap_or(0).wrapping_add(1)) +} + +/// Returns the global signature counter. +pub fn global_signature_counter(env: &mut impl Env) -> Result { + match env.store().find(key::GLOBAL_SIGNATURE_COUNTER)? { + None => Ok(INITIAL_SIGNATURE_COUNTER), + Some(value) if value.len() == 4 => Ok(u32::from_ne_bytes(*array_ref!(&value, 0, 4))), + Some(_) => Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR), } +} - /// Reads the PIN properties and wraps them into PinProperties. - fn pin_properties(&self) -> Result, Ctap2StatusCode> { - let pin_properties = match self.store.find(key::PIN_PROPERTIES)? { - None => return Ok(None), - Some(pin_properties) => pin_properties, - }; - const PROPERTIES_LENGTH: usize = PIN_AUTH_LENGTH + 1; - match pin_properties.len() { - PROPERTIES_LENGTH => Ok(Some(PinProperties { - hash: *array_ref![pin_properties, 1, PIN_AUTH_LENGTH], - code_point_length: pin_properties[0], - })), - _ => Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR), +/// Increments the global signature counter. +pub fn incr_global_signature_counter( + env: &mut impl Env, + increment: u32, +) -> Result<(), Ctap2StatusCode> { + let old_value = global_signature_counter(env)?; + // In hopes that servers handle the wrapping gracefully. + let new_value = old_value.wrapping_add(increment); + env.store() + .insert(key::GLOBAL_SIGNATURE_COUNTER, &new_value.to_ne_bytes())?; + Ok(()) +} + +/// Returns the master keys. +pub fn master_keys(env: &mut impl Env) -> Result { + let master_keys = env + .store() + .find(key::MASTER_KEYS)? + .ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?; + if master_keys.len() != 64 { + return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR); + } + Ok(MasterKeys { + encryption: *array_ref![master_keys, 0, 32], + hmac: *array_ref![master_keys, 32, 32], + }) +} + +/// Returns the CredRandom secret. +pub fn cred_random_secret(env: &mut impl Env, has_uv: bool) -> Result<[u8; 32], Ctap2StatusCode> { + let cred_random_secret = env + .store() + .find(key::CRED_RANDOM_SECRET)? + .ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?; + if cred_random_secret.len() != 64 { + return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR); + } + let offset = if has_uv { 32 } else { 0 }; + Ok(*array_ref![cred_random_secret, offset, 32]) +} + +/// Reads the PIN properties and wraps them into PinProperties. +fn pin_properties(env: &mut impl Env) -> Result, Ctap2StatusCode> { + let pin_properties = match env.store().find(key::PIN_PROPERTIES)? { + None => return Ok(None), + Some(pin_properties) => pin_properties, + }; + const PROPERTIES_LENGTH: usize = PIN_AUTH_LENGTH + 1; + match pin_properties.len() { + PROPERTIES_LENGTH => Ok(Some(PinProperties { + hash: *array_ref![pin_properties, 1, PIN_AUTH_LENGTH], + code_point_length: pin_properties[0], + })), + _ => Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR), + } +} + +/// Returns the PIN hash if defined. +pub fn pin_hash(env: &mut impl Env) -> Result, Ctap2StatusCode> { + Ok(pin_properties(env)?.map(|p| p.hash)) +} + +/// Returns the length of the currently set PIN if defined. +pub fn pin_code_point_length(env: &mut impl Env) -> Result, Ctap2StatusCode> { + Ok(pin_properties(env)?.map(|p| p.code_point_length)) +} + +/// Sets the PIN hash and length. +/// +/// If it was already defined, it is updated. +pub fn set_pin( + env: &mut impl Env, + pin_hash: &[u8; PIN_AUTH_LENGTH], + pin_code_point_length: u8, +) -> Result<(), Ctap2StatusCode> { + let mut pin_properties = [0; 1 + PIN_AUTH_LENGTH]; + pin_properties[0] = pin_code_point_length; + pin_properties[1..].clone_from_slice(pin_hash); + Ok(env.store().transaction(&[ + StoreUpdate::Insert { + key: key::PIN_PROPERTIES, + value: &pin_properties[..], + }, + StoreUpdate::Remove { + key: key::FORCE_PIN_CHANGE, + }, + ])?) +} + +/// Returns the number of remaining PIN retries. +pub fn pin_retries(env: &mut impl Env) -> Result { + match env.store().find(key::PIN_RETRIES)? { + None => Ok(MAX_PIN_RETRIES), + Some(value) if value.len() == 1 => Ok(value[0]), + _ => Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR), + } +} + +/// Decrements the number of remaining PIN retries. +pub fn decr_pin_retries(env: &mut impl Env) -> Result<(), Ctap2StatusCode> { + let old_value = pin_retries(env)?; + let new_value = old_value.saturating_sub(1); + if new_value != old_value { + env.store().insert(key::PIN_RETRIES, &[new_value])?; + } + Ok(()) +} + +/// Resets the number of remaining PIN retries. +pub fn reset_pin_retries(env: &mut impl Env) -> Result<(), Ctap2StatusCode> { + Ok(env.store().remove(key::PIN_RETRIES)?) +} + +/// Returns the minimum PIN length. +pub fn min_pin_length(env: &mut impl Env) -> Result { + match env.store().find(key::MIN_PIN_LENGTH)? { + None => Ok(DEFAULT_MIN_PIN_LENGTH), + Some(value) if value.len() == 1 => Ok(value[0]), + _ => Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR), + } +} + +/// Sets the minimum PIN length. +pub fn set_min_pin_length(env: &mut impl Env, min_pin_length: u8) -> Result<(), Ctap2StatusCode> { + Ok(env.store().insert(key::MIN_PIN_LENGTH, &[min_pin_length])?) +} + +/// 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(env: &mut impl Env) -> Result, Ctap2StatusCode> { + let rp_ids = env.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()) +} + +/// Sets the list of RP IDs that are used to check if reading the minimum PIN length is allowed. +pub fn set_min_pin_length_rp_ids( + env: &mut impl Env, + 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); + if !min_pin_length_rp_ids.contains(&rp_id) { + min_pin_length_rp_ids.push(rp_id); } } - - /// Returns the PIN hash if defined. - pub fn pin_hash(&self) -> Result, Ctap2StatusCode> { - Ok(self.pin_properties()?.map(|p| p.hash)) + if min_pin_length_rp_ids.len() > MAX_RP_IDS_LENGTH { + return Err(Ctap2StatusCode::CTAP2_ERR_KEY_STORE_FULL); } + Ok(env.store().insert( + key::MIN_PIN_LENGTH_RP_IDS, + &serialize_min_pin_length_rp_ids(min_pin_length_rp_ids)?, + )?) +} - /// Returns the length of the currently set PIN if defined. - pub fn pin_code_point_length(&self) -> Result, Ctap2StatusCode> { - Ok(self.pin_properties()?.map(|p| p.code_point_length)) +/// Reads the byte vector stored as the serialized large blobs array. +/// +/// If too few bytes exist at that offset, return the maximum number +/// available. This includes cases of offset being beyond the stored array. +/// +/// If no large blob is committed to the store, get responds as if an empty +/// CBOR array (0x80) was written, together with the 16 byte prefix of its +/// SHA256, to a total length of 17 byte (which is the shortest legitimate +/// large blob entry possible). +pub fn get_large_blob_array( + env: &mut impl Env, + offset: usize, + byte_count: usize, +) -> Result, Ctap2StatusCode> { + let byte_range = offset..offset + byte_count; + let output = fragment::read_range(env.store(), &key::LARGE_BLOB_SHARDS, byte_range)?; + Ok(output.unwrap_or_else(|| { + const EMPTY_LARGE_BLOB: [u8; 17] = [ + 0x80, 0x76, 0xBE, 0x8B, 0x52, 0x8D, 0x00, 0x75, 0xF7, 0xAA, 0xE9, 0x8D, 0x6F, 0xA5, + 0x7A, 0x6D, 0x3C, + ]; + let last_index = cmp::min(EMPTY_LARGE_BLOB.len(), offset + byte_count); + EMPTY_LARGE_BLOB + .get(offset..last_index) + .unwrap_or_default() + .to_vec() + })) +} + +/// Sets a byte vector as the serialized large blobs array. +pub fn commit_large_blob_array( + env: &mut impl Env, + 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 { + return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR); } + Ok(fragment::write( + env.store(), + &key::LARGE_BLOB_SHARDS, + large_blob_array, + )?) +} - /// Sets the PIN hash and length. - /// - /// If it was already defined, it is updated. - pub fn set_pin( - &mut self, - pin_hash: &[u8; PIN_AUTH_LENGTH], - pin_code_point_length: u8, - ) -> Result<(), Ctap2StatusCode> { - let mut pin_properties = [0; 1 + PIN_AUTH_LENGTH]; - pin_properties[0] = pin_code_point_length; - pin_properties[1..].clone_from_slice(pin_hash); - Ok(self.store.transaction(&[ - StoreUpdate::Insert { - key: key::PIN_PROPERTIES, - value: &pin_properties[..], - }, - StoreUpdate::Remove { - key: key::FORCE_PIN_CHANGE, - }, - ])?) - } - - /// Returns the number of remaining PIN retries. - pub fn pin_retries(&self) -> Result { - match self.store.find(key::PIN_RETRIES)? { - None => Ok(MAX_PIN_RETRIES), - Some(value) if value.len() == 1 => Ok(value[0]), - _ => Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR), +/// Returns the attestation private key if defined. +pub fn attestation_private_key( + env: &mut impl Env, +) -> Result, Ctap2StatusCode> { + match env.store().find(key::ATTESTATION_PRIVATE_KEY)? { + None => Ok(None), + Some(key) if key.len() == key_material::ATTESTATION_PRIVATE_KEY_LENGTH => { + Ok(Some(*array_ref![ + key, + 0, + key_material::ATTESTATION_PRIVATE_KEY_LENGTH + ])) } + Some(_) => Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR), } +} - /// Decrements the number of remaining PIN retries. - pub fn decr_pin_retries(&mut self) -> Result<(), Ctap2StatusCode> { - let old_value = self.pin_retries()?; - let new_value = old_value.saturating_sub(1); - if new_value != old_value { - self.store.insert(key::PIN_RETRIES, &[new_value])?; - } - Ok(()) +/// Sets the attestation private key. +/// +/// If it is already defined, it is overwritten. +pub fn set_attestation_private_key( + env: &mut impl Env, + attestation_private_key: &[u8; key_material::ATTESTATION_PRIVATE_KEY_LENGTH], +) -> Result<(), Ctap2StatusCode> { + match env.store().find(key::ATTESTATION_PRIVATE_KEY)? { + None => Ok(env + .store() + .insert(key::ATTESTATION_PRIVATE_KEY, attestation_private_key)?), + Some(_) => Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR), } +} - /// Resets the number of remaining PIN retries. - pub fn reset_pin_retries(&mut self) -> Result<(), Ctap2StatusCode> { - Ok(self.store.remove(key::PIN_RETRIES)?) +/// Returns the attestation certificate if defined. +pub fn attestation_certificate(env: &mut impl Env) -> Result>, Ctap2StatusCode> { + Ok(env.store().find(key::ATTESTATION_CERTIFICATE)?) +} + +/// Sets the attestation certificate. +/// +/// If it is already defined, it is overwritten. +pub fn set_attestation_certificate( + env: &mut impl Env, + attestation_certificate: &[u8], +) -> Result<(), Ctap2StatusCode> { + match env.store().find(key::ATTESTATION_CERTIFICATE)? { + None => Ok(env + .store() + .insert(key::ATTESTATION_CERTIFICATE, attestation_certificate)?), + Some(_) => Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR), } +} - /// Returns the minimum PIN length. - pub fn min_pin_length(&self) -> Result { - match self.store.find(key::MIN_PIN_LENGTH)? { - None => Ok(DEFAULT_MIN_PIN_LENGTH), - Some(value) if value.len() == 1 => Ok(value[0]), - _ => Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR), - } +/// Returns the AAGUID. +pub fn aaguid(env: &mut impl Env) -> Result<[u8; key_material::AAGUID_LENGTH], Ctap2StatusCode> { + let aaguid = env + .store() + .find(key::AAGUID)? + .ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?; + if aaguid.len() != key_material::AAGUID_LENGTH { + return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR); } + Ok(*array_ref![aaguid, 0, key_material::AAGUID_LENGTH]) +} - /// Sets the minimum PIN length. - pub fn set_min_pin_length(&mut self, min_pin_length: u8) -> Result<(), Ctap2StatusCode> { - Ok(self.store.insert(key::MIN_PIN_LENGTH, &[min_pin_length])?) +/// Sets the AAGUID. +/// +/// If it is already defined, it is overwritten. +pub fn set_aaguid( + env: &mut impl Env, + aaguid: &[u8; key_material::AAGUID_LENGTH], +) -> Result<(), Ctap2StatusCode> { + Ok(env.store().insert(key::AAGUID, aaguid)?) +} + +/// Resets the store as for a CTAP reset. +/// +/// In particular persistent entries are not reset. +pub fn reset(env: &mut impl Env) -> Result<(), Ctap2StatusCode> { + env.store().clear(key::NUM_PERSISTENT_KEYS)?; + init(env)?; + Ok(()) +} + +/// Returns whether the PIN needs to be changed before its next usage. +pub fn has_force_pin_change(env: &mut impl Env) -> Result { + match env.store().find(key::FORCE_PIN_CHANGE)? { + None => Ok(false), + Some(value) if value.is_empty() => Ok(true), + _ => Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR), } +} - /// 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_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()) +/// Marks the PIN as outdated with respect to the new PIN policy. +pub fn force_pin_change(env: &mut impl Env) -> Result<(), Ctap2StatusCode> { + Ok(env.store().insert(key::FORCE_PIN_CHANGE, &[])?) +} + +/// Returns whether enterprise attestation is enabled. +pub fn enterprise_attestation(env: &mut impl Env) -> Result { + match env.store().find(key::ENTERPRISE_ATTESTATION)? { + None => Ok(false), + Some(value) if value.is_empty() => Ok(true), + _ => Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR), } +} - /// Sets the list of RP IDs that are used to check if reading the minimum PIN length is allowed. - pub fn set_min_pin_length_rp_ids( - &mut self, - 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); - if !min_pin_length_rp_ids.contains(&rp_id) { - min_pin_length_rp_ids.push(rp_id); - } - } - if min_pin_length_rp_ids.len() > MAX_RP_IDS_LENGTH { - return Err(Ctap2StatusCode::CTAP2_ERR_KEY_STORE_FULL); - } - Ok(self.store.insert( - key::MIN_PIN_LENGTH_RP_IDS, - &serialize_min_pin_length_rp_ids(min_pin_length_rp_ids)?, - )?) +/// Marks enterprise attestation as enabled. +pub fn enable_enterprise_attestation(env: &mut impl Env) -> Result<(), Ctap2StatusCode> { + if !enterprise_attestation(env)? { + env.store().insert(key::ENTERPRISE_ATTESTATION, &[])?; } + Ok(()) +} - /// Reads the byte vector stored as the serialized large blobs array. - /// - /// If too few bytes exist at that offset, return the maximum number - /// available. This includes cases of offset being beyond the stored array. - /// - /// If no large blob is committed to the store, get responds as if an empty - /// CBOR array (0x80) was written, together with the 16 byte prefix of its - /// SHA256, to a total length of 17 byte (which is the shortest legitimate - /// large blob entry possible). - pub fn get_large_blob_array( - &self, - offset: usize, - byte_count: usize, - ) -> Result, Ctap2StatusCode> { - let byte_range = offset..offset + byte_count; - let output = fragment::read_range(&self.store, &key::LARGE_BLOB_SHARDS, byte_range)?; - Ok(output.unwrap_or_else(|| { - const EMPTY_LARGE_BLOB: [u8; 17] = [ - 0x80, 0x76, 0xBE, 0x8B, 0x52, 0x8D, 0x00, 0x75, 0xF7, 0xAA, 0xE9, 0x8D, 0x6F, 0xA5, - 0x7A, 0x6D, 0x3C, - ]; - let last_index = cmp::min(EMPTY_LARGE_BLOB.len(), offset + byte_count); - EMPTY_LARGE_BLOB - .get(offset..last_index) - .unwrap_or_default() - .to_vec() - })) +/// Returns whether alwaysUv is enabled. +pub fn has_always_uv(env: &mut impl Env) -> Result { + if ENFORCE_ALWAYS_UV { + return Ok(true); } - - /// Sets a byte vector as the serialized large blobs array. - pub fn commit_large_blob_array( - &mut self, - 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 { - return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR); - } - Ok(fragment::write( - &mut self.store, - &key::LARGE_BLOB_SHARDS, - large_blob_array, - )?) + match env.store().find(key::ALWAYS_UV)? { + None => Ok(false), + Some(value) if value.is_empty() => Ok(true), + _ => Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR), } +} - /// Returns the attestation private key if defined. - pub fn attestation_private_key( - &self, - ) -> Result, Ctap2StatusCode> { - match self.store.find(key::ATTESTATION_PRIVATE_KEY)? { - None => Ok(None), - Some(key) if key.len() == key_material::ATTESTATION_PRIVATE_KEY_LENGTH => { - Ok(Some(*array_ref![ - key, - 0, - key_material::ATTESTATION_PRIVATE_KEY_LENGTH - ])) - } - Some(_) => Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR), - } +/// Enables alwaysUv, when disabled, and vice versa. +pub fn toggle_always_uv(env: &mut impl Env) -> Result<(), Ctap2StatusCode> { + if ENFORCE_ALWAYS_UV { + return Err(Ctap2StatusCode::CTAP2_ERR_OPERATION_DENIED); } - - /// Sets the attestation private key. - /// - /// If it is already defined, it is overwritten. - pub fn set_attestation_private_key( - &mut self, - attestation_private_key: &[u8; key_material::ATTESTATION_PRIVATE_KEY_LENGTH], - ) -> Result<(), Ctap2StatusCode> { - match self.store.find(key::ATTESTATION_PRIVATE_KEY)? { - None => Ok(self - .store - .insert(key::ATTESTATION_PRIVATE_KEY, attestation_private_key)?), - Some(_) => Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR), - } - } - - /// Returns the attestation certificate if defined. - pub fn attestation_certificate(&self) -> Result>, Ctap2StatusCode> { - Ok(self.store.find(key::ATTESTATION_CERTIFICATE)?) - } - - /// Sets the attestation certificate. - /// - /// If it is already defined, it is overwritten. - pub fn set_attestation_certificate( - &mut self, - attestation_certificate: &[u8], - ) -> Result<(), Ctap2StatusCode> { - match self.store.find(key::ATTESTATION_CERTIFICATE)? { - None => Ok(self - .store - .insert(key::ATTESTATION_CERTIFICATE, attestation_certificate)?), - Some(_) => Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR), - } - } - - /// Returns the AAGUID. - pub fn aaguid(&self) -> Result<[u8; key_material::AAGUID_LENGTH], Ctap2StatusCode> { - let aaguid = self - .store - .find(key::AAGUID)? - .ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?; - if aaguid.len() != key_material::AAGUID_LENGTH { - return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR); - } - Ok(*array_ref![aaguid, 0, key_material::AAGUID_LENGTH]) - } - - /// Sets the AAGUID. - /// - /// If it is already defined, it is overwritten. - pub fn set_aaguid( - &mut self, - aaguid: &[u8; key_material::AAGUID_LENGTH], - ) -> Result<(), Ctap2StatusCode> { - Ok(self.store.insert(key::AAGUID, aaguid)?) - } - - /// Resets the store as for a CTAP reset. - /// - /// In particular persistent entries are not reset. - pub fn reset(&mut self, rng: &mut impl Rng256) -> Result<(), Ctap2StatusCode> { - self.store.clear(key::NUM_PERSISTENT_KEYS)?; - self.init(rng)?; - Ok(()) - } - - /// Returns whether the PIN needs to be changed before its next usage. - pub fn has_force_pin_change(&self) -> Result { - match self.store.find(key::FORCE_PIN_CHANGE)? { - None => Ok(false), - Some(value) if value.is_empty() => Ok(true), - _ => Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR), - } - } - - /// Marks the PIN as outdated with respect to the new PIN policy. - 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(()) - } - - /// Returns whether alwaysUv is enabled. - pub fn has_always_uv(&self) -> Result { - if ENFORCE_ALWAYS_UV { - return Ok(true); - } - match self.store.find(key::ALWAYS_UV)? { - None => Ok(false), - Some(value) if value.is_empty() => Ok(true), - _ => Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR), - } - } - - /// Enables alwaysUv, when disabled, and vice versa. - pub fn toggle_always_uv(&mut self) -> Result<(), Ctap2StatusCode> { - if ENFORCE_ALWAYS_UV { - return Err(Ctap2StatusCode::CTAP2_ERR_OPERATION_DENIED); - } - if self.has_always_uv()? { - Ok(self.store.remove(key::ALWAYS_UV)?) - } else { - Ok(self.store.insert(key::ALWAYS_UV, &[])?) - } + if has_always_uv(env)? { + Ok(env.store().remove(key::ALWAYS_UV)?) + } else { + Ok(env.store().insert(key::ALWAYS_UV, &[])?) } } @@ -655,9 +639,9 @@ impl From for Ctap2StatusCode { } /// Iterator for credentials. -pub struct IterCredentials<'a> { +pub struct IterCredentials<'a, E: Env> { /// The store being iterated. - store: &'a persistent_store::Store, + store: &'a persistent_store::Store, /// The store iterator. iter: persistent_store::StoreIter<'a>, @@ -669,12 +653,12 @@ pub struct IterCredentials<'a> { result: &'a mut Result<(), Ctap2StatusCode>, } -impl<'a> IterCredentials<'a> { +impl<'a, E: Env> IterCredentials<'a, E> { /// Creates a credential iterator. fn new( - store: &'a persistent_store::Store, + store: &'a persistent_store::Store, result: &'a mut Result<(), Ctap2StatusCode>, - ) -> Result, Ctap2StatusCode> { + ) -> Result { let iter = store.iter()?; Ok(IterCredentials { store, @@ -696,7 +680,7 @@ impl<'a> IterCredentials<'a> { } } -impl<'a> Iterator for IterCredentials<'a> { +impl<'a, E: Env> Iterator for IterCredentials<'a, E> { type Item = (usize, PublicKeyCredentialSource); fn next(&mut self) -> Option<(usize, PublicKeyCredentialSource)> { @@ -752,6 +736,7 @@ fn serialize_min_pin_length_rp_ids(rp_ids: Vec) -> Result, Ctap2 mod test { use super::*; use crate::ctap::data_formats::{PublicKeyCredentialSource, PublicKeyCredentialType}; + use crate::env::test::TestEnv; use crypto::rng256::{Rng256, ThreadRng256}; fn create_credential_source( @@ -778,40 +763,37 @@ mod test { #[test] fn test_store() { - let mut rng = ThreadRng256 {}; - let mut persistent_store = PersistentStore::new(&mut rng); - assert_eq!(persistent_store.count_credentials().unwrap(), 0); - let credential_source = create_credential_source(&mut rng, "example.com", vec![]); - assert!(persistent_store.store_credential(credential_source).is_ok()); - assert!(persistent_store.count_credentials().unwrap() > 0); + let mut env = TestEnv::new(); + assert_eq!(count_credentials(&mut env).unwrap(), 0); + let credential_source = create_credential_source(env.rng(), "example.com", vec![]); + assert!(store_credential(&mut env, credential_source).is_ok()); + assert!(count_credentials(&mut env).unwrap() > 0); } #[test] fn test_delete_credential() { - let mut rng = ThreadRng256 {}; - let mut persistent_store = PersistentStore::new(&mut rng); - assert_eq!(persistent_store.count_credentials().unwrap(), 0); + let mut env = TestEnv::new(); + assert_eq!(count_credentials(&mut env).unwrap(), 0); let mut credential_ids = vec![]; 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(&mut 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()); - assert!(persistent_store.store_credential(credential_source).is_ok()); - assert_eq!(persistent_store.count_credentials().unwrap(), i + 1); + assert!(store_credential(&mut env, credential_source).is_ok()); + assert_eq!(count_credentials(&mut env).unwrap(), i + 1); } - let mut count = persistent_store.count_credentials().unwrap(); + let mut count = count_credentials(&mut env).unwrap(); for credential_id in credential_ids { - assert!(persistent_store.delete_credential(&credential_id).is_ok()); + assert!(delete_credential(&mut env, &credential_id).is_ok()); count -= 1; - assert_eq!(persistent_store.count_credentials().unwrap(), count); + assert_eq!(count_credentials(&mut env).unwrap(), count); } } #[test] fn test_update_credential() { - let mut rng = ThreadRng256 {}; - let mut persistent_store = PersistentStore::new(&mut rng); + let mut env = TestEnv::new(); let user = PublicKeyCredentialUserEntity { // User ID is ignored. user_id: vec![0x00], @@ -820,25 +802,21 @@ mod test { user_icon: Some("icon".to_string()), }; assert_eq!( - persistent_store.update_credential(&[0x1D], user.clone()), + update_credential(&mut env, &[0x1D], user.clone()), Err(Ctap2StatusCode::CTAP2_ERR_NO_CREDENTIALS) ); - let credential_source = create_credential_source(&mut rng, "example.com", vec![0x1D]); + let credential_source = create_credential_source(env.rng(), "example.com", vec![0x1D]); let credential_id = credential_source.credential_id.clone(); - assert!(persistent_store.store_credential(credential_source).is_ok()); - let stored_credential = persistent_store - .find_credential("example.com", &credential_id, false) + assert!(store_credential(&mut env, credential_source).is_ok()); + let stored_credential = find_credential(&mut env, "example.com", &credential_id, false) .unwrap() .unwrap(); assert_eq!(stored_credential.user_name, None); assert_eq!(stored_credential.user_display_name, None); assert_eq!(stored_credential.user_icon, None); - assert!(persistent_store - .update_credential(&credential_id, user.clone()) - .is_ok()); - let stored_credential = persistent_store - .find_credential("example.com", &credential_id, false) + assert!(update_credential(&mut env, &credential_id, user.clone()).is_ok()); + let stored_credential = find_credential(&mut env, "example.com", &credential_id, false) .unwrap() .unwrap(); assert_eq!(stored_credential.user_name, user.user_name); @@ -848,136 +826,123 @@ mod test { #[test] fn test_credential_order() { - let mut rng = ThreadRng256 {}; - let mut persistent_store = PersistentStore::new(&mut rng); - let credential_source = create_credential_source(&mut rng, "example.com", vec![]); + let mut env = TestEnv::new(); + let credential_source = create_credential_source(env.rng(), "example.com", vec![]); let current_latest_creation = credential_source.creation_order; - assert!(persistent_store.store_credential(credential_source).is_ok()); - let mut credential_source = create_credential_source(&mut rng, "example.com", vec![]); - credential_source.creation_order = persistent_store.new_creation_order().unwrap(); + assert!(store_credential(&mut env, credential_source).is_ok()); + let mut credential_source = create_credential_source(env.rng(), "example.com", vec![]); + credential_source.creation_order = new_creation_order(&mut env).unwrap(); assert!(credential_source.creation_order > current_latest_creation); let current_latest_creation = credential_source.creation_order; - assert!(persistent_store.store_credential(credential_source).is_ok()); - assert!(persistent_store.new_creation_order().unwrap() > current_latest_creation); + assert!(store_credential(&mut env, credential_source).is_ok()); + assert!(new_creation_order(&mut env).unwrap() > current_latest_creation); } #[test] fn test_fill_store() { - let mut rng = ThreadRng256 {}; - let mut persistent_store = PersistentStore::new(&mut rng); - assert_eq!(persistent_store.count_credentials().unwrap(), 0); + let mut env = TestEnv::new(); + assert_eq!(count_credentials(&mut env).unwrap(), 0); 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(&mut rng, "example.com", user_handle); - assert!(persistent_store.store_credential(credential_source).is_ok()); - assert_eq!(persistent_store.count_credentials().unwrap(), i + 1); + let credential_source = create_credential_source(env.rng(), "example.com", user_handle); + assert!(store_credential(&mut env, credential_source).is_ok()); + assert_eq!(count_credentials(&mut env).unwrap(), i + 1); } let credential_source = create_credential_source( - &mut rng, + env.rng(), "example.com", vec![MAX_SUPPORTED_RESIDENT_KEYS as u8], ); assert_eq!( - persistent_store.store_credential(credential_source), + store_credential(&mut env, credential_source), Err(Ctap2StatusCode::CTAP2_ERR_KEY_STORE_FULL) ); assert_eq!( - persistent_store.count_credentials().unwrap(), + count_credentials(&mut env).unwrap(), MAX_SUPPORTED_RESIDENT_KEYS ); } #[test] fn test_overwrite() { - let mut rng = ThreadRng256 {}; - let mut persistent_store = PersistentStore::new(&mut rng); - assert_eq!(persistent_store.count_credentials().unwrap(), 0); + let mut env = TestEnv::new(); + init(&mut env).unwrap(); + + assert_eq!(count_credentials(&mut env).unwrap(), 0); // These should have different IDs. - let credential_source0 = create_credential_source(&mut rng, "example.com", vec![0x00]); - let credential_source1 = create_credential_source(&mut rng, "example.com", vec![0x00]); + let credential_source0 = create_credential_source(env.rng(), "example.com", vec![0x00]); + let credential_source1 = create_credential_source(env.rng(), "example.com", vec![0x00]); let credential_id0 = credential_source0.credential_id.clone(); let credential_id1 = credential_source1.credential_id.clone(); - assert!(persistent_store - .store_credential(credential_source0) - .is_ok()); - assert!(persistent_store - .store_credential(credential_source1) - .is_ok()); - assert_eq!(persistent_store.count_credentials().unwrap(), 1); - assert!(persistent_store - .find_credential("example.com", &credential_id0, false) - .unwrap() - .is_none()); - assert!(persistent_store - .find_credential("example.com", &credential_id1, false) - .unwrap() - .is_some()); + assert!(store_credential(&mut env, credential_source0).is_ok()); + assert!(store_credential(&mut env, credential_source1).is_ok()); + assert_eq!(count_credentials(&mut env).unwrap(), 1); + assert!( + find_credential(&mut env, "example.com", &credential_id0, false) + .unwrap() + .is_none() + ); + assert!( + find_credential(&mut env, "example.com", &credential_id1, false) + .unwrap() + .is_some() + ); - let mut persistent_store = PersistentStore::new(&mut rng); + reset(&mut env).unwrap(); 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(&mut rng, "example.com", user_handle); - assert!(persistent_store.store_credential(credential_source).is_ok()); - assert_eq!(persistent_store.count_credentials().unwrap(), i + 1); + let credential_source = create_credential_source(env.rng(), "example.com", user_handle); + assert!(store_credential(&mut env, credential_source).is_ok()); + assert_eq!(count_credentials(&mut env).unwrap(), i + 1); } let credential_source = create_credential_source( - &mut rng, + env.rng(), "example.com", vec![MAX_SUPPORTED_RESIDENT_KEYS as u8], ); assert_eq!( - persistent_store.store_credential(credential_source), + store_credential(&mut env, credential_source), Err(Ctap2StatusCode::CTAP2_ERR_KEY_STORE_FULL) ); assert_eq!( - persistent_store.count_credentials().unwrap(), + count_credentials(&mut env).unwrap(), MAX_SUPPORTED_RESIDENT_KEYS ); } #[test] fn test_get_credential() { - let mut rng = ThreadRng256 {}; - let mut persistent_store = PersistentStore::new(&mut rng); - let credential_source0 = create_credential_source(&mut rng, "example.com", vec![0x00]); - let credential_source1 = create_credential_source(&mut rng, "example.com", vec![0x01]); + let mut env = TestEnv::new(); + let credential_source0 = create_credential_source(env.rng(), "example.com", vec![0x00]); + let credential_source1 = create_credential_source(env.rng(), "example.com", vec![0x01]); let credential_source2 = - create_credential_source(&mut rng, "another.example.com", vec![0x02]); + create_credential_source(env.rng(), "another.example.com", vec![0x02]); let credential_sources = vec![credential_source0, credential_source1, credential_source2]; for credential_source in credential_sources.into_iter() { let cred_id = credential_source.credential_id.clone(); - assert!(persistent_store.store_credential(credential_source).is_ok()); - let (key, _) = persistent_store.find_credential_item(&cred_id).unwrap(); - let cred = persistent_store.get_credential(key).unwrap(); + assert!(store_credential(&mut env, credential_source).is_ok()); + let (key, _) = find_credential_item(&mut env, &cred_id).unwrap(); + let cred = get_credential(&mut env, key).unwrap(); assert_eq!(&cred_id, &cred.credential_id); } } #[test] fn test_find() { - let mut rng = ThreadRng256 {}; - let mut persistent_store = PersistentStore::new(&mut rng); - assert_eq!(persistent_store.count_credentials().unwrap(), 0); - let credential_source0 = create_credential_source(&mut rng, "example.com", vec![0x00]); - let credential_source1 = create_credential_source(&mut rng, "example.com", vec![0x01]); + let mut env = TestEnv::new(); + assert_eq!(count_credentials(&mut env).unwrap(), 0); + let credential_source0 = create_credential_source(env.rng(), "example.com", vec![0x00]); + let credential_source1 = create_credential_source(env.rng(), "example.com", vec![0x01]); let id0 = credential_source0.credential_id.clone(); let key0 = credential_source0.private_key.clone(); - assert!(persistent_store - .store_credential(credential_source0) - .is_ok()); - assert!(persistent_store - .store_credential(credential_source1) - .is_ok()); + assert!(store_credential(&mut env, credential_source0).is_ok()); + assert!(store_credential(&mut env, credential_source1).is_ok()); - let no_credential = persistent_store - .find_credential("another.example.com", &id0, false) - .unwrap(); + let no_credential = find_credential(&mut env, "another.example.com", &id0, false).unwrap(); assert_eq!(no_credential, None); - let found_credential = persistent_store - .find_credential("example.com", &id0, false) - .unwrap(); + let found_credential = find_credential(&mut env, "example.com", &id0, false).unwrap(); let expected_credential = PublicKeyCredentialSource { key_type: PublicKeyCredentialType::PublicKey, credential_id: id0, @@ -997,13 +962,12 @@ mod test { #[test] fn test_find_with_cred_protect() { - let mut rng = ThreadRng256 {}; - let mut persistent_store = PersistentStore::new(&mut rng); - assert_eq!(persistent_store.count_credentials().unwrap(), 0); - let private_key = crypto::ecdsa::SecKey::gensk(&mut rng); + let mut env = TestEnv::new(); + assert_eq!(count_credentials(&mut env).unwrap(), 0); + let private_key = crypto::ecdsa::SecKey::gensk(env.rng()); let credential = PublicKeyCredentialSource { key_type: PublicKeyCredentialType::PublicKey, - credential_id: rng.gen_uniform_u8x32().to_vec(), + credential_id: env.rng().gen_uniform_u8x32().to_vec(), private_key, rp_id: String::from("example.com"), user_handle: vec![0x00], @@ -1015,22 +979,20 @@ mod test { cred_blob: None, large_blob_key: None, }; - assert!(persistent_store.store_credential(credential).is_ok()); + assert!(store_credential(&mut env, credential).is_ok()); - let no_credential = persistent_store - .find_credential("example.com", &[0x00], true) - .unwrap(); + let no_credential = find_credential(&mut env, "example.com", &[0x00], true).unwrap(); assert_eq!(no_credential, None); } #[test] fn test_master_keys() { - let mut rng = ThreadRng256 {}; - let mut persistent_store = PersistentStore::new(&mut rng); + let mut env = TestEnv::new(); + init(&mut env).unwrap(); // Master keys stay the same within the same CTAP reset cycle. - let master_keys_1 = persistent_store.master_keys().unwrap(); - let master_keys_2 = persistent_store.master_keys().unwrap(); + let master_keys_1 = master_keys(&mut env).unwrap(); + let master_keys_2 = master_keys(&mut env).unwrap(); assert_eq!(master_keys_2.encryption, master_keys_1.encryption); assert_eq!(master_keys_2.hmac, master_keys_1.hmac); @@ -1038,328 +1000,275 @@ mod test { // same keys. let master_encryption_key = master_keys_1.encryption.to_vec(); let master_hmac_key = master_keys_1.hmac.to_vec(); - persistent_store.reset(&mut rng).unwrap(); - let master_keys_3 = persistent_store.master_keys().unwrap(); + reset(&mut env).unwrap(); + let master_keys_3 = master_keys(&mut env).unwrap(); assert!(master_keys_3.encryption != master_encryption_key.as_slice()); assert!(master_keys_3.hmac != master_hmac_key.as_slice()); } #[test] fn test_cred_random_secret() { - let mut rng = ThreadRng256 {}; - let mut persistent_store = PersistentStore::new(&mut rng); + let mut env = TestEnv::new(); + init(&mut env).unwrap(); // CredRandom secrets stay the same within the same CTAP reset cycle. - let cred_random_with_uv_1 = persistent_store.cred_random_secret(true).unwrap(); - let cred_random_without_uv_1 = persistent_store.cred_random_secret(false).unwrap(); - let cred_random_with_uv_2 = persistent_store.cred_random_secret(true).unwrap(); - let cred_random_without_uv_2 = persistent_store.cred_random_secret(false).unwrap(); + let cred_random_with_uv_1 = cred_random_secret(&mut env, true).unwrap(); + let cred_random_without_uv_1 = cred_random_secret(&mut env, false).unwrap(); + let cred_random_with_uv_2 = cred_random_secret(&mut env, true).unwrap(); + let cred_random_without_uv_2 = cred_random_secret(&mut env, false).unwrap(); assert_eq!(cred_random_with_uv_1, cred_random_with_uv_2); assert_eq!(cred_random_without_uv_1, cred_random_without_uv_2); // CredRandom secrets change after reset. This test may fail if the random generator produces the // same keys. - persistent_store.reset(&mut rng).unwrap(); - let cred_random_with_uv_3 = persistent_store.cred_random_secret(true).unwrap(); - let cred_random_without_uv_3 = persistent_store.cred_random_secret(false).unwrap(); + reset(&mut env).unwrap(); + let cred_random_with_uv_3 = cred_random_secret(&mut env, true).unwrap(); + let cred_random_without_uv_3 = cred_random_secret(&mut env, false).unwrap(); assert!(cred_random_with_uv_1 != cred_random_with_uv_3); assert!(cred_random_without_uv_1 != cred_random_without_uv_3); } #[test] fn test_pin_hash_and_length() { - let mut rng = ThreadRng256 {}; - let mut persistent_store = PersistentStore::new(&mut rng); + let mut env = TestEnv::new(); // Pin hash is initially not set. - assert!(persistent_store.pin_hash().unwrap().is_none()); - assert!(persistent_store.pin_code_point_length().unwrap().is_none()); + assert!(pin_hash(&mut env).unwrap().is_none()); + assert!(pin_code_point_length(&mut env).unwrap().is_none()); // Setting the pin sets the pin hash. - let random_data = rng.gen_uniform_u8x32(); + let random_data = env.rng().gen_uniform_u8x32(); assert_eq!(random_data.len(), 2 * PIN_AUTH_LENGTH); let pin_hash_1 = *array_ref!(random_data, 0, PIN_AUTH_LENGTH); let pin_hash_2 = *array_ref!(random_data, PIN_AUTH_LENGTH, PIN_AUTH_LENGTH); let pin_length_1 = 4; let pin_length_2 = 63; - persistent_store.set_pin(&pin_hash_1, pin_length_1).unwrap(); - assert_eq!(persistent_store.pin_hash().unwrap(), Some(pin_hash_1)); - assert_eq!( - persistent_store.pin_code_point_length().unwrap(), - Some(pin_length_1) - ); - persistent_store.set_pin(&pin_hash_2, pin_length_2).unwrap(); - assert_eq!(persistent_store.pin_hash().unwrap(), Some(pin_hash_2)); - assert_eq!( - persistent_store.pin_code_point_length().unwrap(), - Some(pin_length_2) - ); + set_pin(&mut env, &pin_hash_1, pin_length_1).unwrap(); + assert_eq!(pin_hash(&mut env).unwrap(), Some(pin_hash_1)); + assert_eq!(pin_code_point_length(&mut env).unwrap(), Some(pin_length_1)); + set_pin(&mut env, &pin_hash_2, pin_length_2).unwrap(); + assert_eq!(pin_hash(&mut env).unwrap(), Some(pin_hash_2)); + assert_eq!(pin_code_point_length(&mut env).unwrap(), Some(pin_length_2)); // Resetting the storage resets the pin hash. - persistent_store.reset(&mut rng).unwrap(); - assert!(persistent_store.pin_hash().unwrap().is_none()); - assert!(persistent_store.pin_code_point_length().unwrap().is_none()); + reset(&mut env).unwrap(); + assert!(pin_hash(&mut env).unwrap().is_none()); + assert!(pin_code_point_length(&mut env).unwrap().is_none()); } #[test] fn test_pin_retries() { - let mut rng = ThreadRng256 {}; - let mut persistent_store = PersistentStore::new(&mut rng); + let mut env = TestEnv::new(); // The pin retries is initially at the maximum. - assert_eq!(persistent_store.pin_retries(), Ok(MAX_PIN_RETRIES)); + assert_eq!(pin_retries(&mut env), Ok(MAX_PIN_RETRIES)); // Decrementing the pin retries decrements the pin retries. - for pin_retries in (0..MAX_PIN_RETRIES).rev() { - persistent_store.decr_pin_retries().unwrap(); - assert_eq!(persistent_store.pin_retries(), Ok(pin_retries)); + for retries in (0..MAX_PIN_RETRIES).rev() { + decr_pin_retries(&mut env).unwrap(); + assert_eq!(pin_retries(&mut env), Ok(retries)); } // Decrementing the pin retries after zero does not modify the pin retries. - persistent_store.decr_pin_retries().unwrap(); - assert_eq!(persistent_store.pin_retries(), Ok(0)); + decr_pin_retries(&mut env).unwrap(); + assert_eq!(pin_retries(&mut env), Ok(0)); // Resetting the pin retries resets the pin retries. - persistent_store.reset_pin_retries().unwrap(); - assert_eq!(persistent_store.pin_retries(), Ok(MAX_PIN_RETRIES)); + reset_pin_retries(&mut env).unwrap(); + assert_eq!(pin_retries(&mut env), Ok(MAX_PIN_RETRIES)); } #[test] fn test_persistent_keys() { - let mut rng = ThreadRng256 {}; - let mut persistent_store = PersistentStore::new(&mut rng); + let mut env = TestEnv::new(); + init(&mut env).unwrap(); // Make sure the attestation are absent. There is no batch attestation in tests. - assert!(persistent_store - .attestation_private_key() - .unwrap() - .is_none()); - assert!(persistent_store - .attestation_certificate() - .unwrap() - .is_none()); + assert!(attestation_private_key(&mut env,).unwrap().is_none()); + assert!(attestation_certificate(&mut env,).unwrap().is_none()); // Make sure the persistent keys are initialized to dummy values. let dummy_key = [0x41u8; key_material::ATTESTATION_PRIVATE_KEY_LENGTH]; let dummy_cert = [0xddu8; 20]; - persistent_store - .set_attestation_private_key(&dummy_key) - .unwrap(); - persistent_store - .set_attestation_certificate(&dummy_cert) - .unwrap(); - assert_eq!(&persistent_store.aaguid().unwrap(), key_material::AAGUID); + set_attestation_private_key(&mut env, &dummy_key).unwrap(); + set_attestation_certificate(&mut env, &dummy_cert).unwrap(); + assert_eq!(&aaguid(&mut env).unwrap(), key_material::AAGUID); // The persistent keys stay initialized and preserve their value after a reset. - persistent_store.reset(&mut rng).unwrap(); + reset(&mut env).unwrap(); assert_eq!( - &persistent_store.attestation_private_key().unwrap().unwrap(), + &attestation_private_key(&mut env).unwrap().unwrap(), &dummy_key ); assert_eq!( - persistent_store.attestation_certificate().unwrap().unwrap(), + attestation_certificate(&mut env).unwrap().unwrap(), &dummy_cert ); - assert_eq!(&persistent_store.aaguid().unwrap(), key_material::AAGUID); + assert_eq!(&aaguid(&mut env).unwrap(), key_material::AAGUID); } #[test] fn test_min_pin_length() { - let mut rng = ThreadRng256 {}; - let mut persistent_store = PersistentStore::new(&mut rng); + let mut env = TestEnv::new(); // The minimum PIN length is initially at the default. - assert_eq!( - persistent_store.min_pin_length().unwrap(), - DEFAULT_MIN_PIN_LENGTH - ); + assert_eq!(min_pin_length(&mut env).unwrap(), DEFAULT_MIN_PIN_LENGTH); // Changes by the setter are reflected by the getter.. let new_min_pin_length = 8; - persistent_store - .set_min_pin_length(new_min_pin_length) - .unwrap(); - assert_eq!( - persistent_store.min_pin_length().unwrap(), - new_min_pin_length - ); + set_min_pin_length(&mut env, new_min_pin_length).unwrap(); + assert_eq!(min_pin_length(&mut env).unwrap(), new_min_pin_length); } #[test] fn test_min_pin_length_rp_ids() { - let mut rng = ThreadRng256 {}; - let mut persistent_store = PersistentStore::new(&mut rng); + let mut env = TestEnv::new(); // The minimum PIN length RP IDs are initially at the default. assert_eq!( - persistent_store.min_pin_length_rp_ids().unwrap(), + min_pin_length_rp_ids(&mut env).unwrap(), DEFAULT_MIN_PIN_LENGTH_RP_IDS ); // Changes by the setter are reflected by the getter. let mut rp_ids = vec![String::from("example.com")]; - assert_eq!( - persistent_store.set_min_pin_length_rp_ids(rp_ids.clone()), - Ok(()) - ); + assert_eq!(set_min_pin_length_rp_ids(&mut env, rp_ids.clone()), Ok(())); 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); } } - assert_eq!(persistent_store.min_pin_length_rp_ids().unwrap(), rp_ids); + assert_eq!(min_pin_length_rp_ids(&mut env).unwrap(), rp_ids); } #[test] fn test_max_large_blob_array_size() { - let mut rng = ThreadRng256 {}; - let persistent_store = PersistentStore::new(&mut rng); + let mut env = TestEnv::new(); assert!( MAX_LARGE_BLOB_ARRAY_SIZE - <= persistent_store.store.max_value_length() + <= env.store().max_value_length() * (key::LARGE_BLOB_SHARDS.end - key::LARGE_BLOB_SHARDS.start) ); } #[test] fn test_commit_get_large_blob_array() { - let mut rng = ThreadRng256 {}; - let mut persistent_store = PersistentStore::new(&mut rng); + let mut env = TestEnv::new(); let large_blob_array = vec![0x01, 0x02, 0x03]; - assert!(persistent_store - .commit_large_blob_array(&large_blob_array) - .is_ok()); - let restored_large_blob_array = persistent_store.get_large_blob_array(0, 1).unwrap(); + assert!(commit_large_blob_array(&mut env, &large_blob_array).is_ok()); + let restored_large_blob_array = get_large_blob_array(&mut env, 0, 1).unwrap(); assert_eq!(vec![0x01], restored_large_blob_array); - let restored_large_blob_array = persistent_store.get_large_blob_array(1, 1).unwrap(); + let restored_large_blob_array = get_large_blob_array(&mut env, 1, 1).unwrap(); assert_eq!(vec![0x02], restored_large_blob_array); - let restored_large_blob_array = persistent_store.get_large_blob_array(2, 1).unwrap(); + let restored_large_blob_array = get_large_blob_array(&mut env, 2, 1).unwrap(); assert_eq!(vec![0x03], restored_large_blob_array); - let restored_large_blob_array = persistent_store.get_large_blob_array(2, 2).unwrap(); + let restored_large_blob_array = get_large_blob_array(&mut env, 2, 2).unwrap(); assert_eq!(vec![0x03], restored_large_blob_array); - let restored_large_blob_array = persistent_store.get_large_blob_array(3, 1).unwrap(); + let restored_large_blob_array = get_large_blob_array(&mut env, 3, 1).unwrap(); assert_eq!(Vec::::new(), restored_large_blob_array); - let restored_large_blob_array = persistent_store.get_large_blob_array(4, 1).unwrap(); + let restored_large_blob_array = get_large_blob_array(&mut env, 4, 1).unwrap(); assert_eq!(Vec::::new(), restored_large_blob_array); } #[test] fn test_commit_get_large_blob_array_overwrite() { - let mut rng = ThreadRng256 {}; - let mut persistent_store = PersistentStore::new(&mut rng); + let mut env = TestEnv::new(); let large_blob_array = vec![0x11; 5]; - assert!(persistent_store - .commit_large_blob_array(&large_blob_array) - .is_ok()); + assert!(commit_large_blob_array(&mut env, &large_blob_array).is_ok()); let large_blob_array = vec![0x22; 4]; - assert!(persistent_store - .commit_large_blob_array(&large_blob_array) - .is_ok()); - let restored_large_blob_array = persistent_store.get_large_blob_array(0, 5).unwrap(); + assert!(commit_large_blob_array(&mut env, &large_blob_array).is_ok()); + let restored_large_blob_array = get_large_blob_array(&mut env, 0, 5).unwrap(); assert_eq!(large_blob_array, restored_large_blob_array); - let restored_large_blob_array = persistent_store.get_large_blob_array(4, 1).unwrap(); + let restored_large_blob_array = get_large_blob_array(&mut env, 4, 1).unwrap(); assert_eq!(Vec::::new(), restored_large_blob_array); - assert!(persistent_store.commit_large_blob_array(&[]).is_ok()); - let restored_large_blob_array = persistent_store.get_large_blob_array(0, 20).unwrap(); + assert!(commit_large_blob_array(&mut env, &[]).is_ok()); + let restored_large_blob_array = get_large_blob_array(&mut env, 0, 20).unwrap(); // Committing an empty array resets to the default blob of 17 byte. assert_eq!(restored_large_blob_array.len(), 17); } #[test] fn test_commit_get_large_blob_array_no_commit() { - let mut rng = ThreadRng256 {}; - let persistent_store = PersistentStore::new(&mut rng); + let mut env = TestEnv::new(); let empty_blob_array = vec![ 0x80, 0x76, 0xBE, 0x8B, 0x52, 0x8D, 0x00, 0x75, 0xF7, 0xAA, 0xE9, 0x8D, 0x6F, 0xA5, 0x7A, 0x6D, 0x3C, ]; - let restored_large_blob_array = persistent_store.get_large_blob_array(0, 17).unwrap(); + let restored_large_blob_array = get_large_blob_array(&mut env, 0, 17).unwrap(); assert_eq!(empty_blob_array, restored_large_blob_array); - let restored_large_blob_array = persistent_store.get_large_blob_array(0, 1).unwrap(); + let restored_large_blob_array = get_large_blob_array(&mut env, 0, 1).unwrap(); assert_eq!(vec![0x80], restored_large_blob_array); - let restored_large_blob_array = persistent_store.get_large_blob_array(16, 1).unwrap(); + let restored_large_blob_array = get_large_blob_array(&mut env, 16, 1).unwrap(); assert_eq!(vec![0x3C], restored_large_blob_array); } #[test] fn test_global_signature_counter() { - let mut rng = ThreadRng256 {}; - let mut persistent_store = PersistentStore::new(&mut rng); + let mut env = TestEnv::new(); let mut counter_value = 1; - assert_eq!( - persistent_store.global_signature_counter().unwrap(), - counter_value - ); + assert_eq!(global_signature_counter(&mut env).unwrap(), counter_value); for increment in 1..10 { - assert!(persistent_store - .incr_global_signature_counter(increment) - .is_ok()); + assert!(incr_global_signature_counter(&mut env, increment).is_ok()); counter_value += increment; - assert_eq!( - persistent_store.global_signature_counter().unwrap(), - counter_value - ); + assert_eq!(global_signature_counter(&mut env).unwrap(), counter_value); } } #[test] fn test_force_pin_change() { - let mut rng = ThreadRng256 {}; - let mut persistent_store = PersistentStore::new(&mut rng); + let mut env = TestEnv::new(); - assert!(!persistent_store.has_force_pin_change().unwrap()); - assert_eq!(persistent_store.force_pin_change(), Ok(())); - assert!(persistent_store.has_force_pin_change().unwrap()); - assert_eq!(persistent_store.set_pin(&[0x88; 16], 8), Ok(())); - assert!(!persistent_store.has_force_pin_change().unwrap()); + assert!(!has_force_pin_change(&mut env).unwrap()); + assert_eq!(force_pin_change(&mut env), Ok(())); + assert!(has_force_pin_change(&mut env).unwrap()); + assert_eq!(set_pin(&mut env, &[0x88; 16], 8), Ok(())); + assert!(!has_force_pin_change(&mut env).unwrap()); } #[test] fn test_enterprise_attestation() { - let mut rng = ThreadRng256 {}; - let mut persistent_store = PersistentStore::new(&mut rng); + let mut env = TestEnv::new(); - 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()); + assert!(!enterprise_attestation(&mut env).unwrap()); + assert_eq!(enable_enterprise_attestation(&mut env), Ok(())); + assert!(enterprise_attestation(&mut env).unwrap()); + reset(&mut env).unwrap(); + assert!(!enterprise_attestation(&mut env).unwrap()); } #[test] fn test_always_uv() { - let mut rng = ThreadRng256 {}; - let mut persistent_store = PersistentStore::new(&mut rng); + let mut env = TestEnv::new(); if ENFORCE_ALWAYS_UV { - assert!(persistent_store.has_always_uv().unwrap()); + assert!(has_always_uv(&mut env).unwrap()); assert_eq!( - persistent_store.toggle_always_uv(), + toggle_always_uv(&mut env), Err(Ctap2StatusCode::CTAP2_ERR_OPERATION_DENIED) ); } else { - assert!(!persistent_store.has_always_uv().unwrap()); - assert_eq!(persistent_store.toggle_always_uv(), Ok(())); - assert!(persistent_store.has_always_uv().unwrap()); - assert_eq!(persistent_store.toggle_always_uv(), Ok(())); - assert!(!persistent_store.has_always_uv().unwrap()); + assert!(!has_always_uv(&mut env).unwrap()); + assert_eq!(toggle_always_uv(&mut env), Ok(())); + assert!(has_always_uv(&mut env).unwrap()); + assert_eq!(toggle_always_uv(&mut env), Ok(())); + assert!(!has_always_uv(&mut env).unwrap()); } } #[test] fn test_serialize_deserialize_credential() { - let mut rng = ThreadRng256 {}; - let private_key = crypto::ecdsa::SecKey::gensk(&mut rng); + let mut env = TestEnv::new(); + let private_key = crypto::ecdsa::SecKey::gensk(env.rng()); let credential = PublicKeyCredentialSource { key_type: PublicKeyCredentialType::PublicKey, - credential_id: rng.gen_uniform_u8x32().to_vec(), + credential_id: env.rng().gen_uniform_u8x32().to_vec(), private_key, rp_id: String::from("example.com"), user_handle: vec![0x00], diff --git a/src/embedded_flash/mod.rs b/src/embedded_flash/mod.rs deleted file mode 100644 index c34049a..0000000 --- a/src/embedded_flash/mod.rs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2019-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. - -#[cfg(feature = "std")] -mod buffer_upgrade; -mod helper; -#[cfg(not(feature = "std"))] -mod syscall; -mod upgrade_storage; - -pub use upgrade_storage::UpgradeStorage; - -/// Definitions for production. -#[cfg(not(feature = "std"))] -mod prod { - use super::syscall::{SyscallStorage, SyscallUpgradeStorage}; - - pub type Storage = SyscallStorage; - - pub fn new_storage() -> persistent_store::StorageResult { - Storage::new() - } - - pub type UpgradeLocations = SyscallUpgradeStorage; -} -#[cfg(not(feature = "std"))] -pub use self::prod::{new_storage, Storage, UpgradeLocations}; - -/// Definitions for testing. -#[cfg(feature = "std")] -mod test { - use super::buffer_upgrade::BufferUpgradeStorage; - - pub type Storage = persistent_store::BufferStorage; - - pub fn new_storage() -> persistent_store::StorageResult { - // Use the Nordic configuration. - const PAGE_SIZE: usize = 0x1000; - const NUM_PAGES: usize = 20; - let store = vec![0xff; NUM_PAGES * PAGE_SIZE].into_boxed_slice(); - let options = persistent_store::BufferOptions { - word_size: 4, - page_size: PAGE_SIZE, - max_word_writes: 2, - max_page_erases: 10000, - strict_mode: true, - }; - Ok(Storage::new(store, options)) - } - - pub type UpgradeLocations = BufferUpgradeStorage; -} -#[cfg(feature = "std")] -pub use self::test::{new_storage, Storage, UpgradeLocations}; diff --git a/src/env/mod.rs b/src/env/mod.rs index 04f849c..ba8f75b 100644 --- a/src/env/mod.rs +++ b/src/env/mod.rs @@ -1,6 +1,8 @@ +use crate::api::upgrade_storage::UpgradeStorage; use crate::ctap::hid::ChannelID; use crate::ctap::status_code::Ctap2StatusCode; use crypto::rng256::Rng256; +use persistent_store::{Storage, Store}; #[cfg(feature = "std")] pub mod test; @@ -17,7 +19,16 @@ pub trait UserPresence { pub trait Env { type Rng: Rng256; type UserPresence: UserPresence; + type Storage: Storage; + type UpgradeStorage: UpgradeStorage; fn rng(&mut self) -> &mut Self::Rng; fn user_presence(&mut self) -> &mut Self::UserPresence; + fn store(&mut self) -> &mut Store; + + /// Returns the upgrade storage instance. + /// + /// Upgrade storage is optional, so implementations may return `None`. However, implementations + /// should either always return `None` or always return `Some`. + fn upgrade_storage(&mut self) -> Option<&mut Self::UpgradeStorage>; } diff --git a/src/env/test.rs b/src/env/test.rs deleted file mode 100644 index 7d81f4a..0000000 --- a/src/env/test.rs +++ /dev/null @@ -1,48 +0,0 @@ -use crate::ctap::hid::ChannelID; -use crate::ctap::status_code::Ctap2StatusCode; -use crate::env::{Env, UserPresence}; -use crypto::rng256::ThreadRng256; - -pub struct TestEnv { - rng: ThreadRng256, - user_presence: TestUserPresence, -} - -pub struct TestUserPresence { - check: Box Result<(), Ctap2StatusCode>>, -} - -impl TestEnv { - pub fn new() -> Self { - let rng = ThreadRng256 {}; - let user_presence = TestUserPresence { - check: Box::new(|_| Ok(())), - }; - TestEnv { rng, user_presence } - } -} - -impl TestUserPresence { - pub fn set(&mut self, check: impl Fn(ChannelID) -> Result<(), Ctap2StatusCode> + 'static) { - self.check = Box::new(check); - } -} - -impl UserPresence for TestUserPresence { - fn check(&self, cid: ChannelID) -> Result<(), Ctap2StatusCode> { - (self.check)(cid) - } -} - -impl Env for TestEnv { - type Rng = ThreadRng256; - type UserPresence = TestUserPresence; - - fn rng(&mut self) -> &mut Self::Rng { - &mut self.rng - } - - fn user_presence(&mut self) -> &mut Self::UserPresence { - &mut self.user_presence - } -} diff --git a/src/env/test/mod.rs b/src/env/test/mod.rs new file mode 100644 index 0000000..cf1f506 --- /dev/null +++ b/src/env/test/mod.rs @@ -0,0 +1,91 @@ +use self::upgrade_storage::BufferUpgradeStorage; +use crate::ctap::hid::ChannelID; +use crate::ctap::status_code::Ctap2StatusCode; +use crate::env::{Env, UserPresence}; +use crypto::rng256::ThreadRng256; +use persistent_store::{BufferOptions, BufferStorage, Store}; + +mod upgrade_storage; + +pub struct TestEnv { + rng: ThreadRng256, + user_presence: TestUserPresence, + store: Store, + upgrade_storage: Option, +} + +pub struct TestUserPresence { + check: Box Result<(), Ctap2StatusCode>>, +} + +fn new_storage() -> BufferStorage { + // Use the Nordic configuration. + const PAGE_SIZE: usize = 0x1000; + const NUM_PAGES: usize = 20; + let store = vec![0xff; NUM_PAGES * PAGE_SIZE].into_boxed_slice(); + let options = BufferOptions { + word_size: 4, + page_size: PAGE_SIZE, + max_word_writes: 2, + max_page_erases: 10000, + strict_mode: true, + }; + BufferStorage::new(store, options) +} + +impl TestEnv { + pub fn new() -> Self { + let rng = ThreadRng256 {}; + let user_presence = TestUserPresence { + check: Box::new(|_| Ok(())), + }; + let storage = new_storage(); + let store = Store::new(storage).ok().unwrap(); + let upgrade_storage = Some(BufferUpgradeStorage::new().unwrap()); + TestEnv { + rng, + user_presence, + store, + upgrade_storage, + } + } + + pub fn disable_upgrade_storage(&mut self) { + self.upgrade_storage = None; + } +} + +impl TestUserPresence { + pub fn set(&mut self, check: impl Fn(ChannelID) -> Result<(), Ctap2StatusCode> + 'static) { + self.check = Box::new(check); + } +} + +impl UserPresence for TestUserPresence { + fn check(&self, cid: ChannelID) -> Result<(), Ctap2StatusCode> { + (self.check)(cid) + } +} + +impl Env for TestEnv { + type Rng = ThreadRng256; + type UserPresence = TestUserPresence; + type Storage = BufferStorage; + type UpgradeStorage = BufferUpgradeStorage; + + fn rng(&mut self) -> &mut Self::Rng { + &mut self.rng + } + + fn user_presence(&mut self) -> &mut Self::UserPresence { + &mut self.user_presence + } + + fn store(&mut self) -> &mut Store { + &mut self.store + } + + fn upgrade_storage(&mut self) -> Option<&mut Self::UpgradeStorage> { + self.upgrade_storage.as_mut() + } +} diff --git a/src/embedded_flash/buffer_upgrade.rs b/src/env/test/upgrade_storage.rs similarity index 98% rename from src/embedded_flash/buffer_upgrade.rs rename to src/env/test/upgrade_storage.rs index 8f4bc6a..04e3e55 100644 --- a/src/embedded_flash/buffer_upgrade.rs +++ b/src/env/test/upgrade_storage.rs @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -use super::helper::ModRange; -use super::upgrade_storage::UpgradeStorage; +use crate::api::upgrade_storage::helper::ModRange; +use crate::api::upgrade_storage::UpgradeStorage; use alloc::boxed::Box; use persistent_store::{StorageError, StorageResult}; diff --git a/src/env/tock.rs b/src/env/tock/mod.rs similarity index 83% rename from src/env/tock.rs rename to src/env/tock/mod.rs index 6ac1f5d..273f147 100644 --- a/src/env/tock.rs +++ b/src/env/tock/mod.rs @@ -1,9 +1,11 @@ +use self::storage::{SyscallStorage, SyscallUpgradeStorage}; use crate::ctap::hid::{ChannelID, CtapHid, CtapHidCommand, KeepaliveStatus, ProcessedPacket}; use crate::ctap::status_code::Ctap2StatusCode; use crate::env::{Env, UserPresence}; use core::cell::Cell; #[cfg(feature = "debug_ctap")] use core::fmt::Write; +use core::sync::atomic::{AtomicBool, Ordering}; use crypto::rng256::TockRng256; use libtock_core::result::{CommandError, EALREADY}; use libtock_drivers::buttons::{self, ButtonState}; @@ -12,18 +14,50 @@ use libtock_drivers::console::Console; use libtock_drivers::result::{FlexUnwrap, TockError}; use libtock_drivers::timer::Duration; use libtock_drivers::{led, timer, usb_ctap_hid}; +use persistent_store::{StorageResult, Store}; + +mod storage; pub struct TockEnv { rng: TockRng256, + store: Store, + upgrade_storage: Option, } impl TockEnv { + /// Returns the unique instance of the Tock environment. + /// + /// # Panics + /// + /// - If called a second time. pub fn new() -> Self { - let rng = TockRng256 {}; - TockEnv { rng } + // Make sure the environment was not already taken. + static TAKEN: AtomicBool = AtomicBool::new(false); + assert!(!TAKEN.fetch_or(true, Ordering::SeqCst)); + let storage = unsafe { steal_storage() }.unwrap(); + let store = Store::new(storage).ok().unwrap(); + let upgrade_storage = SyscallUpgradeStorage::new().ok(); + TockEnv { + rng: TockRng256 {}, + store, + upgrade_storage, + } } } +/// Creates a new storage instance. +/// +/// # Safety +/// +/// It is probably technically memory-safe to have multiple storage instances at the same time, but +/// for extra precaution we mark the function as unsafe. To ensure correct usage, this function +/// should only be called if the previous storage instance was dropped. +// This function is exposed to example binaries testing the hardware. This could probably be cleaned +// up by having the persistent store return its storage. +pub unsafe fn steal_storage() -> StorageResult { + SyscallStorage::new() +} + impl UserPresence for TockEnv { fn check(&self, cid: ChannelID) -> Result<(), Ctap2StatusCode> { check_user_presence(cid) @@ -33,6 +67,8 @@ impl UserPresence for TockEnv { impl Env for TockEnv { type Rng = TockRng256; type UserPresence = Self; + type Storage = SyscallStorage; + type UpgradeStorage = SyscallUpgradeStorage; fn rng(&mut self) -> &mut Self::Rng { &mut self.rng @@ -41,6 +77,14 @@ impl Env for TockEnv { fn user_presence(&mut self) -> &mut Self::UserPresence { self } + + fn store(&mut self) -> &mut Store { + &mut self.store + } + + fn upgrade_storage(&mut self) -> Option<&mut Self::UpgradeStorage> { + self.upgrade_storage.as_mut() + } } // Returns whether the keepalive was sent, or false if cancelled. diff --git a/src/embedded_flash/syscall.rs b/src/env/tock/storage.rs similarity index 98% rename from src/embedded_flash/syscall.rs rename to src/env/tock/storage.rs index d5dac9c..1d4c6c4 100644 --- a/src/embedded_flash/syscall.rs +++ b/src/env/tock/storage.rs @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -use super::helper::{find_slice, is_aligned, ModRange}; -use super::upgrade_storage::UpgradeStorage; +use crate::api::upgrade_storage::helper::{find_slice, is_aligned, ModRange}; +use crate::api::upgrade_storage::UpgradeStorage; use alloc::vec::Vec; use core::cell::Cell; use libtock_core::{callback, syscalls}; diff --git a/src/lib.rs b/src/lib.rs index e068f26..26573c2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,13 +24,12 @@ use crate::ctap::CtapState; use crate::env::Env; use libtock_drivers::timer::ClockValue; +pub mod api; // Implementation details must be public for testing (in particular fuzzing). #[cfg(feature = "std")] pub mod ctap; #[cfg(not(feature = "std"))] mod ctap; -// Store example binaries use the flash directly. Eventually, they should access it from env::tock. -pub mod embedded_flash; pub mod env; /// CTAP implementation parameterized by its environment.