diff --git a/libraries/opensk/src/api/attestation_store.rs b/libraries/opensk/src/api/attestation_store.rs deleted file mode 100644 index 68dcee27..00000000 --- a/libraries/opensk/src/api/attestation_store.rs +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright 2022-2023 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. - -use crate::api::crypto::EC_FIELD_SIZE; -use crate::ctap::secret::Secret; -use crate::env::Env; -use alloc::vec::Vec; -use persistent_store::{StoreError, StoreUpdate}; - -/// Identifies an attestation. -#[derive(Clone, PartialEq, Eq)] -pub enum Id { - Batch, - Enterprise, -} - -#[cfg_attr(feature = "std", derive(Debug, PartialEq, Eq))] -pub struct Attestation { - /// ECDSA private key (big-endian). - pub private_key: Secret<[u8; EC_FIELD_SIZE]>, - pub certificate: Vec, -} - -/// Stores enterprise or batch attestations. -pub trait AttestationStore { - /// Returns an attestation given its id, if it exists. - /// - /// This should always return the attestation. Checking whether it is ok to use the attestation - /// is done in the CTAP library. - fn get(&mut self, id: &Id) -> Result, Error>; - - /// Sets the attestation for a given id. - /// - /// This function may not be supported. - fn set(&mut self, id: &Id, attestation: Option<&Attestation>) -> Result<(), Error>; -} - -/// Attestation store errors. -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -#[non_exhaustive] -pub enum Error { - Storage, - Internal, - NoSupport, -} - -/// Keys of the environment store reserved for the attestation store. -pub const STORAGE_KEYS: &[usize] = &[1, 2]; - -pub fn helper_get(env: &mut impl Env) -> Result, Error> { - let private_key = env.store().find(PRIVATE_KEY_STORAGE_KEY)?; - let certificate = env.store().find(CERTIFICATE_STORAGE_KEY)?; - let (private_key, certificate) = match (private_key, certificate) { - (Some(x), Some(y)) => (x, y), - (None, None) => return Ok(None), - _ => return Err(Error::Internal), - }; - if private_key.len() != EC_FIELD_SIZE { - return Err(Error::Internal); - } - Ok(Some(Attestation { - private_key: Secret::from_exposed_secret(*array_ref![private_key, 0, EC_FIELD_SIZE]), - certificate, - })) -} - -pub fn helper_set(env: &mut impl Env, attestation: Option<&Attestation>) -> Result<(), Error> { - let updates = match attestation { - None => [ - StoreUpdate::Remove { - key: PRIVATE_KEY_STORAGE_KEY, - }, - StoreUpdate::Remove { - key: CERTIFICATE_STORAGE_KEY, - }, - ], - Some(attestation) => [ - StoreUpdate::Insert { - key: PRIVATE_KEY_STORAGE_KEY, - value: &attestation.private_key[..], - }, - StoreUpdate::Insert { - key: CERTIFICATE_STORAGE_KEY, - value: &attestation.certificate[..], - }, - ], - }; - Ok(env.store().transaction(&updates)?) -} - -const PRIVATE_KEY_STORAGE_KEY: usize = STORAGE_KEYS[0]; -const CERTIFICATE_STORAGE_KEY: usize = STORAGE_KEYS[1]; - -impl From for Error { - fn from(error: StoreError) -> Self { - match error { - StoreError::InvalidArgument - | StoreError::NoCapacity - | StoreError::NoLifetime - | StoreError::InvalidStorage => Error::Internal, - StoreError::StorageError => Error::Storage, - } - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_store_error() { - assert_eq!(Error::from(StoreError::StorageError), Error::Storage); - assert_eq!(Error::from(StoreError::InvalidStorage), Error::Internal); - } -} diff --git a/libraries/opensk/src/api/key_store.rs b/libraries/opensk/src/api/key_store.rs index c50ff36e..ca766e1c 100644 --- a/libraries/opensk/src/api/key_store.rs +++ b/libraries/opensk/src/api/key_store.rs @@ -15,16 +15,15 @@ use crate::api::crypto::aes256::Aes256; use crate::api::crypto::hmac256::Hmac256; use crate::api::crypto::HASH_SIZE; +use crate::api::persist::Persist; use crate::api::private_key::PrivateKey; use crate::ctap::crypto_wrapper::{aes256_cbc_decrypt, aes256_cbc_encrypt}; use crate::ctap::data_formats::CredentialProtectionPolicy; use crate::ctap::secret::Secret; use crate::ctap::{cbor_read, cbor_write}; use crate::env::{AesKey, Env, Hmac}; -use alloc::vec; use alloc::vec::Vec; use core::convert::{TryFrom, TryInto}; -use persistent_store::StoreError; use rand_core::RngCore; use sk_cbor as cbor; use sk_cbor::{cbor_map_options, destructure_cbor_map}; @@ -66,8 +65,6 @@ impl From for cbor::Value { } /// Provides storage for secret keys. -/// -/// Implementations may use the environment store: [`STORAGE_KEY`] is reserved for this usage. pub trait KeyStore { /// Initializes the key store (if needed). /// @@ -82,7 +79,7 @@ pub trait KeyStore { /// - doing anything that does not support [`Secret`]. fn wrap_key(&mut self) -> Result, Error>; - /// Encodes a credential as a binary strings. + /// Encodes a credential as a binary string. /// /// The output is encrypted and authenticated. Since the wrapped credentials are passed to the /// relying party, the choice for credential wrapping impacts privacy. Looking at their size and @@ -125,9 +122,6 @@ pub trait KeyStore { #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct Error; -/// Key of the environment store reserved for the key store. -pub const STORAGE_KEY: usize = 2046; - /// Implements a default key store using the environment rng and store. pub trait Helper: Env {} @@ -240,8 +234,7 @@ impl KeyStore for T { } fn reset(&mut self) -> Result<(), Error> { - // The storage also removes `STORAGE_KEY`, but this makes KeyStore more self-sufficient. - Ok(self.store().remove(STORAGE_KEY)?) + Ok(()) } } @@ -258,13 +251,13 @@ struct MasterKeys { } fn get_master_keys(env: &mut impl Env) -> Result { - let master_keys = match env.store().find(STORAGE_KEY)? { + let master_keys = match env.persist().key_store_bytes()? { Some(x) if x.len() == 128 => x, Some(_) => return Err(Error), None => { - let mut master_keys = vec![0; 128]; + let mut master_keys = Secret::new(128); env.rng().fill_bytes(&mut master_keys); - env.store().insert(STORAGE_KEY, &master_keys)?; + env.persist().write_key_store_bytes(&master_keys)?; master_keys } }; @@ -358,12 +351,6 @@ fn decrypt_cbor_credential_id( }) } -impl From for Error { - fn from(_: StoreError) -> Self { - Error - } -} - fn extract_byte_string(cbor_value: cbor::Value) -> Result, Error> { cbor_value.extract_byte_string().ok_or(Error) } @@ -384,29 +371,38 @@ mod test { #[test] fn test_key_store() { let mut env = TestEnv::default(); - let key_store = env.key_store(); // Master keys are well-defined and stable. - let cred_random_no_uv = key_store.cred_random(false).unwrap(); - let cred_random_with_uv = key_store.cred_random(true).unwrap(); - assert_eq!(&key_store.cred_random(false).unwrap(), &cred_random_no_uv); - assert_eq!(&key_store.cred_random(true).unwrap(), &cred_random_with_uv); + let cred_random_no_uv = env.key_store().cred_random(false).unwrap(); + let cred_random_with_uv = env.key_store().cred_random(true).unwrap(); + assert_eq!( + &env.key_store().cred_random(false).unwrap(), + &cred_random_no_uv + ); + assert_eq!( + &env.key_store().cred_random(true).unwrap(), + &cred_random_with_uv + ); // Same for wrap key. - let wrap_key = key_store.wrap_key::().unwrap(); + let wrap_key = env.key_store().wrap_key::().unwrap(); let mut test_block = [0x33; 16]; wrap_key.encrypt_block(&mut test_block); - let new_wrap_key = key_store.wrap_key::().unwrap(); + let new_wrap_key = env.key_store().wrap_key::().unwrap(); let mut new_test_block = [0x33; 16]; new_wrap_key.encrypt_block(&mut new_test_block); assert_eq!(&new_test_block, &test_block); - // Master keys change after reset. We don't require this for ECDSA seeds because it's not - // the case, but it might be better. - key_store.reset().unwrap(); - assert_ne!(&key_store.cred_random(false).unwrap(), &cred_random_no_uv); - assert_ne!(&key_store.cred_random(true).unwrap(), &cred_random_with_uv); - let new_wrap_key = key_store.wrap_key::().unwrap(); + assert_eq!(crate::ctap::reset(&mut env), Ok(())); + assert_ne!( + &env.key_store().cred_random(false).unwrap(), + &cred_random_no_uv + ); + assert_ne!( + &env.key_store().cred_random(true).unwrap(), + &cred_random_with_uv + ); + let new_wrap_key = env.key_store().wrap_key::().unwrap(); let mut new_test_block = [0x33; 16]; new_wrap_key.encrypt_block(&mut new_test_block); assert_ne!(&new_test_block, &test_block); @@ -416,7 +412,7 @@ mod test { fn test_pin_hash_encrypt_decrypt() { let mut env = TestEnv::default(); let key_store = env.key_store(); - assert_eq!(key_store.init(), Ok(())); + assert_eq!(KeyStore::init(key_store), Ok(())); let pin_hash = [0x55; 16]; let encrypted = key_store.encrypt_pin_hash(&pin_hash).unwrap(); diff --git a/libraries/opensk/src/api/mod.rs b/libraries/opensk/src/api/mod.rs index 65baab57..65a58718 100644 --- a/libraries/opensk/src/api/mod.rs +++ b/libraries/opensk/src/api/mod.rs @@ -17,13 +17,13 @@ //! 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 attestation_store; pub mod clock; pub mod connection; pub mod crypto; pub mod customization; pub mod firmware_protection; pub mod key_store; +pub mod persist; pub mod private_key; pub mod rng; pub mod user_presence; diff --git a/libraries/opensk/src/api/persist.rs b/libraries/opensk/src/api/persist.rs new file mode 100644 index 00000000..97f24274 --- /dev/null +++ b/libraries/opensk/src/api/persist.rs @@ -0,0 +1,550 @@ +// Copyright 2024 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. + +mod keys; + +use crate::api::crypto::EC_FIELD_SIZE; +use crate::ctap::secret::Secret; +use crate::ctap::status_code::{Ctap2StatusCode, CtapResult}; +use crate::ctap::PIN_AUTH_LENGTH; +use alloc::boxed::Box; +use alloc::vec::Vec; +use core::cmp; +use core::convert::TryFrom; +#[cfg(test)] +use enum_iterator::IntoEnumIterator; + +pub type PersistIter<'a> = Box> + 'a>; +pub type PersistCredentialIter<'a> = Box)>> + 'a>; + +/// Stores data that persists across reboots. +/// +/// This trait might get appended to with new versions of CTAP. +/// +/// The default implementations using the key-value store have assumptions on the ranges for key +/// and value, if you decide to use them: +/// - Keys within 0 and 4095 are supported. +/// - Values of at most 1023 bytes are supported. +/// +/// To implement this trait, you have 2 options: +/// - Implement all high level functions with default implementations, +/// calling `unimplemented!` in the key-value accessors. +/// When we update this trait in a new version, OpenSK will panic when calling any new functions. +/// If you need special implementation for new functions, you need to manually add them. +/// - Implement the key-value accessors, and special case as many default implemented high level +/// functions as desired. +/// When the trait gets extended, new features will silently work. +/// Credentials still need keys to be identified by. +pub trait Persist { + /// Retrieves the value for a given key. + fn find(&self, key: usize) -> CtapResult>>; + + /// Inserts the value at the given key. + fn insert(&mut self, key: usize, value: &[u8]) -> CtapResult<()>; + + /// Removes a key, if present. + fn remove(&mut self, key: usize) -> CtapResult<()>; + + /// Iterator for all present keys. + fn iter(&self) -> CtapResult>; + + /// Checks consistency on boot, and if necessary fixes problems or initializes. + /// + /// Calling this function after successful init should be a NO-OP. + fn init(&mut self) -> CtapResult<()> { + if self.find(keys::RESET_COMPLETION)?.is_some() { + self.reset()?; + } + Ok(()) + } + + /// Returns the byte array representation of a stored credential. + fn credential_bytes(&self, key: usize) -> CtapResult> { + if !keys::CREDENTIALS.contains(&key) { + return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR); + } + self.find(key)? + .ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR) + } + + /// Writes a credential at the given key. + fn write_credential_bytes(&mut self, key: usize, value: &[u8]) -> CtapResult<()> { + if !keys::CREDENTIALS.contains(&key) { + return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR); + } + self.insert(key, value) + } + + /// Removes a credential at the given key. + fn remove_credential(&mut self, key: usize) -> CtapResult<()> { + if !keys::CREDENTIALS.contains(&key) { + return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR); + } + self.remove(key) + } + + /// Iterates all stored credentials. + fn iter_credentials(&self) -> CtapResult> { + Ok(Box::new(self.iter()?.filter_map(move |key| match key { + Ok(k) => { + if keys::CREDENTIALS.contains(&k) { + match self.find(k) { + Ok(Some(v)) => Some(Ok((k, v))), + Ok(None) => None, + Err(e) => Some(Err(e)), + } + } else { + None + } + } + Err(e) => Some(Err(e)), + }))) + } + + /// Returns a key where a new credential can be inserted. + fn free_credential_key(&self) -> CtapResult { + for key in keys::CREDENTIALS { + if self.find(key)?.is_none() { + return Ok(key); + } + } + Err(Ctap2StatusCode::CTAP2_ERR_KEY_STORE_FULL) + } + + /// Returns the global signature counter. + fn global_signature_counter(&self) -> CtapResult { + const INITIAL_SIGNATURE_COUNTER: u32 = 1; + match self.find(keys::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), + } + } + + /// Increments the global signature counter. + fn incr_global_signature_counter(&mut self, increment: u32) -> CtapResult<()> { + let old_value = self.global_signature_counter()?; + // In hopes that servers handle the wrapping gracefully. + let new_value = old_value.wrapping_add(increment); + self.insert(keys::GLOBAL_SIGNATURE_COUNTER, &new_value.to_ne_bytes()) + } + + /// Returns the PIN hash if defined. + fn pin_hash(&self) -> CtapResult> { + let pin_properties = match self.find(keys::PIN_PROPERTIES)? { + None => return Ok(None), + Some(pin_properties) => pin_properties, + }; + if pin_properties.len() != 1 + PIN_AUTH_LENGTH { + return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR); + } + Ok(Some(*array_ref![pin_properties, 1, PIN_AUTH_LENGTH])) + } + + /// Returns the length of the currently set PIN if defined. + #[cfg(feature = "config_command")] + fn pin_code_point_length(&self) -> CtapResult> { + let pin_properties = match self.find(keys::PIN_PROPERTIES)? { + None => return Ok(None), + Some(pin_properties) => pin_properties, + }; + if pin_properties.is_empty() { + return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR); + } + Ok(Some(pin_properties[0])) + } + + /// Sets the PIN hash and length. + /// + /// If it was already defined, it is updated. + fn set_pin( + &mut self, + pin_hash: &[u8; PIN_AUTH_LENGTH], + pin_code_point_length: u8, + ) -> CtapResult<()> { + let mut pin_properties = [0; 1 + PIN_AUTH_LENGTH]; + pin_properties[0] = pin_code_point_length; + pin_properties[1..].clone_from_slice(pin_hash); + self.insert(keys::PIN_PROPERTIES, &pin_properties[..])?; + // If power fails between these 2 transactions, PIN has to be set again. + self.remove(keys::FORCE_PIN_CHANGE) + } + + /// Returns the number of failed PIN attempts. + fn pin_fails(&self) -> CtapResult { + match self.find(keys::PIN_RETRIES)? { + None => Ok(0), + Some(value) if value.len() == 1 => Ok(value[0]), + _ => Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR), + } + } + + /// Decrements the number of remaining PIN retries. + fn incr_pin_fails(&mut self) -> CtapResult<()> { + let old_value = self.pin_fails()?; + let new_value = old_value.saturating_add(1); + self.insert(keys::PIN_RETRIES, &[new_value]) + } + + /// Resets the number of remaining PIN retries. + fn reset_pin_retries(&mut self) -> CtapResult<()> { + self.remove(keys::PIN_RETRIES) + } + + /// Returns the minimum PIN length, if stored. + fn min_pin_length(&self) -> CtapResult> { + match self.find(keys::MIN_PIN_LENGTH)? { + None => Ok(None), + Some(value) if value.len() == 1 => Ok(Some(value[0])), + _ => Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR), + } + } + + /// Sets the minimum PIN length. + #[cfg(feature = "config_command")] + fn set_min_pin_length(&mut self, min_pin_length: u8) -> CtapResult<()> { + self.insert(keys::MIN_PIN_LENGTH, &[min_pin_length]) + } + + /// Returns the list of RP IDs that may read the minimum PIN length. + /// + /// Defaults to an empty vector if not found. + fn min_pin_length_rp_ids_bytes(&self) -> CtapResult> { + Ok(self + .find(keys::MIN_PIN_LENGTH_RP_IDS)? + .unwrap_or(Vec::new())) + } + + /// Sets the list of RP IDs that may read the minimum PIN length. + #[cfg(feature = "config_command")] + fn set_min_pin_length_rp_ids(&mut self, min_pin_length_rp_ids_bytes: &[u8]) -> CtapResult<()> { + self.insert(keys::MIN_PIN_LENGTH_RP_IDS, min_pin_length_rp_ids_bytes) + } + + // TODO rework LargeBlob + // Problem 1: Env should be allowed to choose whether to buffer in memory or persist + // Otherwise small RAM devices have limited large blog size. + // Problem 2: LargeBlob is a stateful command, but doesn't use the safeguard and infrastructure + // of Stateful command. It has to be migrated there. + // While doing that, check if PinUvAuthToken timers and StatefulCommand timeouts are working + // together correctly. + /// 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). + fn get_large_blob_array( + &self, + mut offset: usize, + byte_count: usize, + ) -> CtapResult>> { + let mut result = Vec::with_capacity(byte_count); + for key in keys::LARGE_BLOB_SHARDS { + if offset >= VALUE_LENGTH { + offset = offset.saturating_sub(VALUE_LENGTH); + continue; + } + let end = offset.saturating_add(byte_count - result.len()); + let end = cmp::min(end, VALUE_LENGTH); + let value = self.find(key)?.unwrap_or(Vec::new()); + if key == keys::LARGE_BLOB_SHARDS.start && value.is_empty() { + return Ok(None); + } + let end = cmp::min(end, value.len()); + if end < offset { + return Ok(Some(result)); + } + result.extend(&value[offset..end]); + offset = offset.saturating_sub(VALUE_LENGTH); + } + Ok(Some(result)) + } + + /// Sets a byte vector as the serialized large blobs array. + fn commit_large_blob_array(&mut self, large_blob_array: &[u8]) -> CtapResult<()> { + debug_assert!(large_blob_array.len() <= keys::LARGE_BLOB_SHARDS.len() * VALUE_LENGTH); + let mut offset = 0; + for key in keys::LARGE_BLOB_SHARDS { + let cur_len = cmp::min(large_blob_array.len().saturating_sub(offset), VALUE_LENGTH); + let slice = &large_blob_array[offset..][..cur_len]; + if slice.is_empty() { + self.remove(key)?; + } else { + self.insert(key, slice)?; + } + offset += cur_len; + } + Ok(()) + } + + /// Resets persistent data, consistent with a CTAP reset. + /// + /// In particular, entries that are persistent across factory reset are not removed. + fn reset(&mut self) -> CtapResult<()> { + self.insert(keys::RESET_COMPLETION, &[])?; + let mut removed_keys = Vec::new(); + for key in self.iter()? { + let key = key?; + if key >= keys::NUM_PERSISTENT_KEYS && key != keys::RESET_COMPLETION { + removed_keys.push(key); + } + } + for key in removed_keys { + self.remove(key)?; + } + self.remove(keys::RESET_COMPLETION) + } + + /// Returns whether the PIN needs to be changed before its next usage. + fn has_force_pin_change(&self) -> CtapResult { + match self.find(keys::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. + #[cfg(feature = "config_command")] + fn force_pin_change(&mut self) -> CtapResult<()> { + self.insert(keys::FORCE_PIN_CHANGE, &[]) + } + + /// Returns whether enterprise attestation is enabled. + #[cfg(feature = "config_command")] + fn enterprise_attestation(&self) -> CtapResult { + match self.find(keys::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. + #[cfg(feature = "config_command")] + fn enable_enterprise_attestation(&mut self) -> CtapResult<()> { + if self.get_attestation(AttestationId::Enterprise)?.is_none() { + return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR); + } + self.insert(keys::ENTERPRISE_ATTESTATION, &[]) + } + + /// Returns whether alwaysUv is enabled. + fn has_always_uv(&self) -> CtapResult { + match self.find(keys::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. + #[cfg(feature = "config_command")] + fn toggle_always_uv(&mut self) -> CtapResult<()> { + if self.has_always_uv()? { + Ok(self.remove(keys::ALWAYS_UV)?) + } else { + Ok(self.insert(keys::ALWAYS_UV, &[])?) + } + } + + fn get_attestation(&self, id: AttestationId) -> CtapResult> { + let stored_id_bytes = self.find(keys::ATTESTATION_ID)?; + if let Some(bytes) = stored_id_bytes { + if bytes.len() != 1 { + return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR); + } + if id != AttestationId::try_from(bytes[0])? { + return Ok(None); + } + } else { + // This is for backwards compatibility. No ID stored implies batch. + if id != AttestationId::Batch { + return Ok(None); + } + } + let private_key = self.find(keys::ATTESTATION_PRIVATE_KEY)?; + let certificate = self.find(keys::ATTESTATION_CERTIFICATE)?; + let (private_key, certificate) = match (private_key, certificate) { + (Some(x), Some(y)) => (x, y), + (None, None) => return Ok(None), + _ => return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR), + }; + if private_key.len() != EC_FIELD_SIZE { + return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR); + } + Ok(Some(Attestation { + private_key: Secret::from_exposed_secret(*array_ref![private_key, 0, EC_FIELD_SIZE]), + certificate, + })) + } + + fn set_attestation( + &mut self, + id: AttestationId, + attestation: Option<&Attestation>, + ) -> CtapResult<()> { + // To overwrite, first call with None, then call again, to avoid mistakes. + if self.find(keys::ATTESTATION_ID)?.is_some() { + return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR); + } + // We set attestation storage in 3 transactions. If that gets interrupted halfway, + // Register and MakeCredential will error when being called. Needs to be redone then. + // ID is set last to allow idempotent rewrites. + match attestation { + None => { + self.remove(keys::ATTESTATION_PRIVATE_KEY)?; + self.remove(keys::ATTESTATION_CERTIFICATE)?; + self.remove(keys::ATTESTATION_ID)?; + } + Some(attestation) => { + self.insert(keys::ATTESTATION_PRIVATE_KEY, &attestation.private_key[..])?; + self.insert(keys::ATTESTATION_CERTIFICATE, &attestation.certificate[..])?; + self.insert(keys::ATTESTATION_ID, &[id as u8])?; + } + } + Ok(()) + } + + fn key_store_bytes(&self) -> CtapResult>> { + let bytes = self.find(keys::KEY_STORE)?; + Ok(bytes.map(|b| { + let mut secret = Secret::new(b.len()); + secret.copy_from_slice(&b); + secret + })) + } + + fn write_key_store_bytes(&mut self, bytes: &[u8]) -> CtapResult<()> { + self.insert(keys::KEY_STORE, bytes) + } +} + +const VALUE_LENGTH: usize = 1023; + +/// Identifies an attestation. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(test, derive(IntoEnumIterator))] +pub enum AttestationId { + Batch = 0x01, + Enterprise = 0x02, +} + +impl TryFrom for AttestationId { + type Error = Ctap2StatusCode; + + fn try_from(byte: u8) -> CtapResult { + match byte { + 0x01 => Ok(Self::Batch), + 0x02 => Ok(Self::Enterprise), + _ => Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR), + } + } +} + +#[cfg_attr(feature = "std", derive(Debug, PartialEq, Eq))] +pub struct Attestation { + /// ECDSA private key (big-endian). + pub private_key: Secret<[u8; EC_FIELD_SIZE]>, + pub certificate: Vec, +} + +#[cfg(test)] +mod test { + use super::*; + use crate::api::customization::Customization; + use crate::api::rng::Rng; + use crate::env::test::TestEnv; + use crate::env::Env; + + #[test] + fn test_max_large_blob_array_size() { + let env = TestEnv::default(); + + assert!( + env.customization().max_large_blob_array_size() + <= VALUE_LENGTH * keys::LARGE_BLOB_SHARDS.len() + ); + } + + #[test] + fn test_from_into_attestation_id() { + for id in AttestationId::into_enum_iter() { + assert_eq!(id, AttestationId::try_from(id as u8).unwrap()); + } + assert_eq!( + AttestationId::try_from(0x03), + Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR) + ); + } + + #[test] + fn test_global_signature_counter() { + let mut env = TestEnv::default(); + let persist = env.persist(); + + let mut counter_value = 1; + assert_eq!(persist.global_signature_counter().unwrap(), counter_value); + for increment in 1..10 { + assert!(persist.incr_global_signature_counter(increment).is_ok()); + counter_value += increment; + assert_eq!(persist.global_signature_counter().unwrap(), counter_value); + } + } + + #[test] + fn test_force_pin_change() { + let mut env = TestEnv::default(); + let persist = env.persist(); + + assert!(!persist.has_force_pin_change().unwrap()); + assert_eq!(persist.force_pin_change(), Ok(())); + assert!(persist.has_force_pin_change().unwrap()); + assert_eq!(persist.set_pin(&[0x88; 16], 8), Ok(())); + assert!(!persist.has_force_pin_change().unwrap()); + } + + #[test] + fn test_pin_hash_and_length() { + let mut env = TestEnv::default(); + let random_data = env.rng().gen_uniform_u8x32(); + let persist = env.persist(); + + // Pin hash is initially not set. + assert!(persist.pin_hash().unwrap().is_none()); + assert!(persist.pin_code_point_length().unwrap().is_none()); + + // Setting the pin sets the pin hash. + 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; + assert_eq!(persist.set_pin(&pin_hash_1, pin_length_1), Ok(())); + assert_eq!(persist.pin_hash().unwrap(), Some(pin_hash_1)); + assert_eq!(persist.pin_code_point_length().unwrap(), Some(pin_length_1)); + assert_eq!(persist.set_pin(&pin_hash_2, pin_length_2), Ok(())); + assert_eq!(persist.pin_hash().unwrap(), Some(pin_hash_2)); + assert_eq!(persist.pin_code_point_length().unwrap(), Some(pin_length_2)); + + // Resetting the storage resets the pin hash. + assert_eq!(persist.reset(), Ok(())); + assert!(persist.pin_hash().unwrap().is_none()); + assert!(persist.pin_code_point_length().unwrap().is_none()); + } +} diff --git a/libraries/opensk/src/ctap/storage/key.rs b/libraries/opensk/src/api/persist/keys.rs similarity index 91% rename from libraries/opensk/src/ctap/storage/key.rs rename to libraries/opensk/src/api/persist/keys.rs index 78a87c3e..d734cef9 100644 --- a/libraries/opensk/src/ctap/storage/key.rs +++ b/libraries/opensk/src/api/persist/keys.rs @@ -13,6 +13,9 @@ // limitations under the License. /// Number of keys that persist the CTAP reset command. +/// +/// Note that persistent is overloaded: Here, we mean values that survive a Reset. Outside, of this +/// file, we mean values that survive reboot. pub const NUM_PERSISTENT_KEYS: usize = 20; /// Defines a key given its name and value or range of values. @@ -61,8 +64,14 @@ make_partition! { // WARNING: Keys should not be deleted but prefixed with `_` to avoid accidentally reusing them. - /// Reserved for the attestation store implementation of the environment. - _RESERVED_ATTESTATION_STORE = 1..3; + /// Private key used during attestation. + ATTESTATION_PRIVATE_KEY = 1; + + /// Certificate used during attestation. + ATTESTATION_CERTIFICATE = 2; + + /// Type of attestation used. + ATTESTATION_ID = 4; /// Used for the AAGUID before, but deprecated. _AAGUID = 3; @@ -73,6 +82,9 @@ make_partition! { // - When adding a (non-persistent) key below this message, make sure its value is bigger or // equal than NUM_PERSISTENT_KEYS. + /// Used to make sure that a Reset command completes once started. + RESET_COMPLETION = 20; + /// Reserved for future credential-related objects. /// /// In particular, additional credentials could be added there by reducing the lower bound of @@ -124,7 +136,7 @@ make_partition! { PIN_PROPERTIES = 2045; /// Reserved for the key store implementation of the environment. - _RESERVED_KEY_STORE = 2046; + KEY_STORE = 2046; /// The global signature counter. /// diff --git a/libraries/opensk/src/ctap/apdu.rs b/libraries/opensk/src/ctap/apdu.rs index 81288558..d327e1a2 100644 --- a/libraries/opensk/src/ctap/apdu.rs +++ b/libraries/opensk/src/ctap/apdu.rs @@ -16,8 +16,6 @@ use alloc::vec::Vec; use byteorder::{BigEndian, ByteOrder}; use core::convert::TryFrom; -use crate::api::attestation_store; - const APDU_HEADER_LEN: usize = 4; #[derive(Clone, Debug, PartialEq, Eq)] @@ -46,17 +44,6 @@ impl From for u16 { } } -impl From for ApduStatusCode { - fn from(error: attestation_store::Error) -> Self { - use attestation_store::Error; - match error { - Error::Storage => ApduStatusCode::SW_MEMERR, - Error::Internal => ApduStatusCode::SW_INTERNAL_EXCEPTION, - Error::NoSupport => ApduStatusCode::SW_INTERNAL_EXCEPTION, - } - } -} - #[allow(dead_code)] pub enum ApduInstructions { Select = 0xA4, diff --git a/libraries/opensk/src/ctap/client_pin.rs b/libraries/opensk/src/ctap/client_pin.rs index 1a697293..e8bd92e7 100644 --- a/libraries/opensk/src/ctap/client_pin.rs +++ b/libraries/opensk/src/ctap/client_pin.rs @@ -27,6 +27,7 @@ use crate::api::crypto::hmac256::Hmac256; use crate::api::crypto::sha256::Sha256; use crate::api::customization::Customization; use crate::api::key_store::KeyStore; +use crate::api::persist::Persist; use crate::ctap::storage; #[cfg(test)] use crate::env::EcdhSk; @@ -103,11 +104,8 @@ fn check_and_store_new_pin( .key_store() .encrypt_pin_hash(array_ref![pin_hash, 0, PIN_AUTH_LENGTH])?; // The PIN length is always < PIN_PADDED_LENGTH < 256. - storage::set_pin( - env, - array_ref!(pin_hash, 0, PIN_AUTH_LENGTH), - pin_length as u8, - )?; + env.persist() + .set_pin(array_ref!(pin_hash, 0, PIN_AUTH_LENGTH), pin_length as u8)?; Ok(()) } @@ -187,7 +185,7 @@ impl ClientPin { shared_secret: &SharedSecret, pin_hash_enc: Vec, ) -> Result<(), Ctap2StatusCode> { - match storage::pin_hash(env)? { + match env.persist().pin_hash()? { Some(pin_hash) => { if self.consecutive_pin_mismatches >= 3 { return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_BLOCKED); @@ -263,7 +261,7 @@ 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 storage::pin_hash(env)?.is_some() { + if env.persist().pin_hash()?.is_some() { return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID); } let shared_secret = self.get_shared_secret(pin_uv_auth_protocol, key_agreement)?; @@ -331,7 +329,7 @@ impl ClientPin { } let shared_secret = self.get_shared_secret(pin_uv_auth_protocol, key_agreement)?; self.verify_pin_hash_enc(env, pin_uv_auth_protocol, &shared_secret, pin_hash_enc)?; - if storage::has_force_pin_change(env)? { + if env.persist().has_force_pin_change()? { return Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID); } @@ -618,7 +616,7 @@ mod test { pin[..4].copy_from_slice(b"1234"); let mut pin_hash = [0u8; 16]; pin_hash.copy_from_slice(&Sha::::digest(&pin[..])[..16]); - storage::set_pin(env, &pin_hash, 4).unwrap(); + env.persist().set_pin(&pin_hash, 4).unwrap(); } /// Fails on PINs bigger than 64 bytes. @@ -752,7 +750,7 @@ mod test { 0x01, 0xD9, 0x88, 0x40, 0x50, 0xBB, 0xD0, 0x7A, 0x23, 0x1A, 0xEB, 0x69, 0xD8, 0x36, 0xC4, 0x12, ]; - storage::set_pin(&mut env, &pin_hash, 4).unwrap(); + env.persist().set_pin(&pin_hash, 4).unwrap(); let pin_hash_enc = shared_secret.encrypt(&mut env, &pin_hash).unwrap(); assert_eq!( @@ -1047,7 +1045,7 @@ mod test { let mut env = TestEnv::default(); set_standard_pin(&mut env); - assert_eq!(storage::force_pin_change(&mut env), Ok(())); + assert_eq!(env.persist().force_pin_change(), Ok(())); assert_eq!( client_pin.process_command(&mut env, params), Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID), @@ -1157,7 +1155,7 @@ mod test { let mut env = TestEnv::default(); set_standard_pin(&mut env); - assert_eq!(storage::force_pin_change(&mut env), Ok(())); + assert_eq!(env.persist().force_pin_change(), Ok(())); assert_eq!( client_pin.process_command(&mut env, params), Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID) @@ -1249,7 +1247,7 @@ mod test { ), ]; for (pin, result) in test_cases { - let old_pin_hash = storage::pin_hash(&mut env).unwrap(); + let old_pin_hash = env.persist().pin_hash().unwrap(); let new_pin_enc = encrypt_pin(&shared_secret, pin); assert_eq!( @@ -1257,9 +1255,9 @@ mod test { result ); if result.is_ok() { - assert_ne!(old_pin_hash, storage::pin_hash(&mut env).unwrap()); + assert_ne!(old_pin_hash, env.persist().pin_hash().unwrap()); } else { - assert_eq!(old_pin_hash, storage::pin_hash(&mut env).unwrap()); + assert_eq!(old_pin_hash, env.persist().pin_hash().unwrap()); } } } diff --git a/libraries/opensk/src/ctap/config_command.rs b/libraries/opensk/src/ctap/config_command.rs index 91371cd9..1f1447d0 100644 --- a/libraries/opensk/src/ctap/config_command.rs +++ b/libraries/opensk/src/ctap/config_command.rs @@ -18,6 +18,7 @@ use super::data_formats::{ConfigSubCommand, ConfigSubCommandParams, SetMinPinLen use super::response::ResponseData; use super::status_code::Ctap2StatusCode; use crate::api::customization::Customization; +use crate::api::persist::Persist; use crate::ctap::storage; use crate::env::Env; use alloc::vec; @@ -56,14 +57,14 @@ fn process_set_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 && storage::pin_hash(env)?.is_none() { + if force_change_pin && env.persist().pin_hash()?.is_none() { return Err(Ctap2StatusCode::CTAP2_ERR_PIN_NOT_SET); } - if let Some(old_length) = storage::pin_code_point_length(env)? { + if let Some(old_length) = env.persist().pin_code_point_length()? { force_change_pin |= new_min_pin_length > old_length; } if force_change_pin { - storage::force_pin_change(env)?; + env.persist().force_pin_change()?; } storage::set_min_pin_length(env, new_min_pin_length)?; if let Some(min_pin_length_rp_ids) = min_pin_length_rp_ids { @@ -87,7 +88,7 @@ pub fn process_config( let enforce_uv = !matches!(sub_command, ConfigSubCommand::ToggleAlwaysUv) && storage::has_always_uv(env)?; - if storage::pin_hash(env)?.is_some() || enforce_uv { + if env.persist().pin_hash()?.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 = @@ -211,7 +212,7 @@ mod test { pin_uv_auth_token, pin_uv_auth_protocol, ); - storage::set_pin(&mut env, &[0x88; 16], 4).unwrap(); + env.persist().set_pin(&[0x88; 16], 4).unwrap(); let mut config_data = vec![0xFF; 32]; config_data.extend(&[0x0D, ConfigSubCommand::ToggleAlwaysUv as u8]); @@ -295,7 +296,7 @@ mod test { // 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. - storage::set_pin(&mut env, &[0x88; 16], 8).unwrap(); + env.persist().set_pin(&[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![ @@ -351,7 +352,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. - storage::set_pin(&mut env, &[0x88; 16], 8).unwrap(); + env.persist().set_pin(&[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![ @@ -414,7 +415,7 @@ mod test { PinUvAuthProtocol::V1, ); - storage::set_pin(&mut env, &[0x88; 16], 4).unwrap(); + env.persist().set_pin(&[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); @@ -426,7 +427,7 @@ mod test { let config_response = process_config(&mut env, &mut client_pin, config_params); assert_eq!(config_response, Ok(ResponseData::AuthenticatorConfig)); assert_eq!(storage::min_pin_length(&mut env), Ok(min_pin_length)); - assert_eq!(storage::has_force_pin_change(&mut env), Ok(true)); + assert_eq!(env.persist().has_force_pin_change(), Ok(true)); } #[test] @@ -441,7 +442,7 @@ mod test { PinUvAuthProtocol::V1, ); - storage::set_pin(&mut env, &[0x88; 16], 4).unwrap(); + env.persist().set_pin(&[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, @@ -461,7 +462,7 @@ mod test { }; let config_response = process_config(&mut env, &mut client_pin, config_params); assert_eq!(config_response, Ok(ResponseData::AuthenticatorConfig)); - assert_eq!(storage::has_force_pin_change(&mut env), Ok(true)); + assert_eq!(env.persist().has_force_pin_change(), Ok(true)); } #[test] diff --git a/libraries/opensk/src/ctap/credential_management.rs b/libraries/opensk/src/ctap/credential_management.rs index 6000d39a..11c6767e 100644 --- a/libraries/opensk/src/ctap/credential_management.rs +++ b/libraries/opensk/src/ctap/credential_management.rs @@ -361,6 +361,7 @@ mod test { use super::super::CtapState; use super::*; use crate::api::crypto::ecdh::SecretKey as _; + use crate::api::persist::Persist; use crate::api::private_key::PrivateKey; use crate::api::rng::Rng; use crate::env::test::TestEnv; @@ -401,7 +402,7 @@ mod test { let mut ctap_state = CtapState::new(&mut env); ctap_state.client_pin = client_pin; - storage::set_pin(&mut env, &[0u8; 16], 4).unwrap(); + env.persist().set_pin(&[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, @@ -490,7 +491,7 @@ mod test { storage::store_credential(&mut env, credential_source1).unwrap(); storage::store_credential(&mut env, credential_source2).unwrap(); - storage::set_pin(&mut env, &[0u8; 16], 4).unwrap(); + env.persist().set_pin(&[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, @@ -587,7 +588,7 @@ mod test { storage::store_credential(&mut env, credential).unwrap(); } - storage::set_pin(&mut env, &[0u8; 16], 4).unwrap(); + env.persist().set_pin(&[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, @@ -671,7 +672,7 @@ mod test { storage::store_credential(&mut env, credential_source1).unwrap(); storage::store_credential(&mut env, credential_source2).unwrap(); - storage::set_pin(&mut env, &[0u8; 16], 4).unwrap(); + env.persist().set_pin(&[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, @@ -769,7 +770,7 @@ mod test { storage::store_credential(&mut env, credential_source).unwrap(); - storage::set_pin(&mut env, &[0u8; 16], 4).unwrap(); + env.persist().set_pin(&[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, @@ -841,7 +842,7 @@ mod test { storage::store_credential(&mut env, credential_source).unwrap(); - storage::set_pin(&mut env, &[0u8; 16], 4).unwrap(); + env.persist().set_pin(&[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, @@ -898,7 +899,7 @@ mod test { let mut env = TestEnv::default(); let mut ctap_state = CtapState::new(&mut env); - storage::set_pin(&mut env, &[0u8; 16], 4).unwrap(); + env.persist().set_pin(&[0u8; 16], 4).unwrap(); let cred_management_params = AuthenticatorCredentialManagementParameters { sub_command: CredentialManagementSubCommand::GetCredsMetadata, diff --git a/libraries/opensk/src/ctap/ctap1.rs b/libraries/opensk/src/ctap/ctap1.rs index bb9d326e..f279f1e1 100644 --- a/libraries/opensk/src/ctap/ctap1.rs +++ b/libraries/opensk/src/ctap/ctap1.rs @@ -14,10 +14,10 @@ use super::apdu::{Apdu, ApduStatusCode}; use super::{filter_listed_credential, CtapState}; -use crate::api::attestation_store::{self, Attestation, AttestationStore}; use crate::api::crypto::ecdsa::{self, SecretKey as _, Signature}; use crate::api::crypto::EC_FIELD_SIZE; use crate::api::key_store::{CredentialSource, KeyStore}; +use crate::api::persist::{Attestation, AttestationId, Persist}; use crate::api::private_key::PrivateKey; use crate::env::{EcdsaSk, Env}; use alloc::vec::Vec; @@ -278,8 +278,9 @@ impl Ctap1Command { private_key, certificate, } = env - .attestation_store() - .get(&attestation_store::Id::Batch)? + .persist() + .get_attestation(AttestationId::Batch) + .map_err(|_| Ctap1StatusCode::SW_INTERNAL_EXCEPTION)? .ok_or(Ctap1StatusCode::SW_INTERNAL_EXCEPTION)?; let mut response = Vec::with_capacity(105 + key_handle.len() + certificate.len()); @@ -457,8 +458,8 @@ mod test { private_key: Secret::from_exposed_secret([0x41; 32]), certificate: vec![0x99; 100], }; - env.attestation_store() - .set(&attestation_store::Id::Batch, Some(&attestation)) + env.persist() + .set_attestation(AttestationId::Batch, Some(&attestation)) .unwrap(); ctap_state.u2f_up_state.consume_up(&mut env); ctap_state.u2f_up_state.grant_up(&mut env); @@ -640,7 +641,7 @@ mod test { ctap_state.u2f_up_state.grant_up(&mut env); let response = Ctap1Command::process_command(&mut env, &message, &mut ctap_state).unwrap(); assert_eq!(response[0], 0x01); - let global_signature_counter = storage::global_signature_counter(&mut env).unwrap(); + let global_signature_counter = env.persist().global_signature_counter().unwrap(); check_signature_counter( &mut env, array_ref!(response, 1, 4), @@ -665,7 +666,7 @@ mod test { env.clock().advance(TOUCH_TIMEOUT_MS); let response = Ctap1Command::process_command(&mut env, &message, &mut ctap_state).unwrap(); assert_eq!(response[0], 0x01); - let global_signature_counter = storage::global_signature_counter(&mut env).unwrap(); + let global_signature_counter = env.persist().global_signature_counter().unwrap(); check_signature_counter( &mut env, array_ref!(response, 1, 4), diff --git a/libraries/opensk/src/ctap/large_blobs.rs b/libraries/opensk/src/ctap/large_blobs.rs index ff92fa4e..caa0a20a 100644 --- a/libraries/opensk/src/ctap/large_blobs.rs +++ b/libraries/opensk/src/ctap/large_blobs.rs @@ -18,6 +18,7 @@ use super::response::{AuthenticatorLargeBlobsResponse, ResponseData}; use super::status_code::Ctap2StatusCode; use crate::api::crypto::sha256::Sha256; use crate::api::customization::Customization; +use crate::api::persist::Persist; use crate::ctap::storage; use crate::env::{Env, Sha}; use alloc::vec; @@ -86,7 +87,7 @@ impl LargeBlobs { if offset != self.expected_next_offset { return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_SEQ); } - if storage::pin_hash(env)?.is_some() || storage::has_always_uv(env)? { + if env.persist().pin_hash()?.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 = @@ -425,7 +426,7 @@ mod test { large_blob .extend_from_slice(&Sha::::digest(&large_blob[..])[..TRUNCATED_HASH_LEN]); - storage::set_pin(&mut env, &[0u8; 16], 4).unwrap(); + env.persist().set_pin(&[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]); diff --git a/libraries/opensk/src/ctap/mod.rs b/libraries/opensk/src/ctap/mod.rs index 605e7d10..9d223f98 100644 --- a/libraries/opensk/src/ctap/mod.rs +++ b/libraries/opensk/src/ctap/mod.rs @@ -36,6 +36,7 @@ mod u2f_up; #[cfg(feature = "vendor_hid")] pub mod vendor_hid; +pub use self::client_pin::PIN_AUTH_LENGTH; use self::client_pin::{ClientPin, PinPermission}; use self::command::{ AuthenticatorGetAssertionParameters, AuthenticatorMakeCredentialParameters, Command, @@ -59,7 +60,6 @@ use self::secret::Secret; use self::status_code::Ctap2StatusCode; #[cfg(feature = "with_ctap1")] use self::u2f_up::U2fUserPresenceState; -use crate::api::attestation_store::{self, Attestation, AttestationStore}; use crate::api::clock::Clock; use crate::api::connection::{HidConnection, SendOrRecvStatus, UsbEndpoint}; use crate::api::crypto::ecdsa::{SecretKey as _, Signature}; @@ -68,6 +68,7 @@ use crate::api::crypto::sha256::Sha256; use crate::api::crypto::HASH_SIZE; use crate::api::customization::Customization; use crate::api::key_store::{CredentialSource, KeyStore, MAX_CREDENTIAL_ID_SIZE}; +use crate::api::persist::{Attestation, AttestationId, Persist}; use crate::api::private_key::PrivateKey; use crate::api::rng::Rng; use crate::api::user_presence::{UserPresence, UserPresenceError}; @@ -82,7 +83,6 @@ use rand_core::RngCore; use sk_cbor as cbor; use sk_cbor::cbor_map_options; -pub const INITIAL_SIGNATURE_COUNTER: u32 = 1; // Set this bit when checking user presence. const UP_FLAG: u8 = 0x01; // Set this bit when checking user verification. @@ -185,6 +185,13 @@ pub fn cbor_write(value: cbor::Value, encoded_cbor: &mut Vec) -> Result<(), .map_err(|_e| Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR) } +/// Resets the all state for a CTAP Reset command. +pub fn reset(env: &mut impl Env) -> Result<(), Ctap2StatusCode> { + env.persist().reset()?; + env.key_store().reset()?; + storage::init(env) +} + /// Filters the credential from the option if credProtect criteria are not met. pub fn filter_listed_credential( credential: Option, @@ -578,7 +585,7 @@ impl CtapState { ) -> Result<(), Ctap2StatusCode> { if env.customization().use_signature_counter() { let increment = env.rng().next_u32() % 8 + 1; - storage::incr_global_signature_counter(env, increment)?; + env.persist().incr_global_signature_counter(increment)?; } Ok(()) } @@ -737,7 +744,7 @@ impl CtapState { // This case was added in FIDO 2.1. if auth_param.is_empty() { check_user_presence(env, channel)?; - if storage::pin_hash(env)?.is_none() { + if env.persist().pin_hash()?.is_none() { return Err(Ctap2StatusCode::CTAP2_ERR_PIN_NOT_SET); } else { return Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID); @@ -804,7 +811,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 storage::pin_hash(env)?.is_none() { + if env.persist().pin_hash()?.is_none() { return Err(Ctap2StatusCode::CTAP2_ERR_PIN_NOT_SET); } self.client_pin.verify_pin_uv_auth_token( @@ -829,7 +836,7 @@ impl CtapState { return Err(Ctap2StatusCode::CTAP2_ERR_PUAT_REQUIRED); } // Corresponds to makeCredUvNotRqd set to true. - if options.rk && storage::pin_hash(env)?.is_some() { + if options.rk && env.persist().pin_hash()?.is_some() { return Err(Ctap2StatusCode::CTAP2_ERR_PUAT_REQUIRED); } 0x00 @@ -970,9 +977,9 @@ impl CtapState { signature_data.extend(client_data_hash); let attestation_id = if ep_att { - Some(attestation_store::Id::Enterprise) + Some(AttestationId::Enterprise) } else if env.customization().use_batch_attestation() { - Some(attestation_store::Id::Batch) + Some(AttestationId::Batch) } else { None }; @@ -982,8 +989,8 @@ impl CtapState { private_key, certificate, } = env - .attestation_store() - .get(&id)? + .persist() + .get_attestation(id)? .ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?; let attestation_key = EcdsaSk::::from_slice(&private_key).unwrap(); ( @@ -1174,7 +1181,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 storage::pin_hash(env)?.is_none() { + if env.persist().pin_hash()?.is_none() { return Err(Ctap2StatusCode::CTAP2_ERR_PIN_NOT_SET); } self.client_pin.verify_pin_uv_auth_token( @@ -1311,7 +1318,10 @@ impl CtapState { (String::from("credMgmt"), true), #[cfg(feature = "config_command")] (String::from("authnrCfg"), true), - (String::from("clientPin"), storage::pin_hash(env)?.is_some()), + ( + String::from("clientPin"), + env.persist().pin_hash()?.is_some(), + ), (String::from("largeBlobs"), true), (String::from("pinUvAuthToken"), true), #[cfg(feature = "config_command")] @@ -1351,7 +1361,7 @@ impl CtapState { max_serialized_large_blob_array: Some( env.customization().max_large_blob_array_size() as u64, ), - force_pin_change: Some(storage::has_force_pin_change(env)?), + force_pin_change: Some(env.persist().has_force_pin_change()?), min_pin_length: storage::min_pin_length(env)?, firmware_version: env.firmware_version(), max_cred_blob_length: Some(env.customization().max_cred_blob_length() as u64), @@ -1379,7 +1389,7 @@ impl CtapState { } check_user_presence(env, channel)?; - storage::reset(env)?; + reset(env)?; self.client_pin.reset(env); #[cfg(feature = "with_ctap1")] { @@ -1413,7 +1423,7 @@ impl CtapState { let mut signature_counter = [0u8; 4]; BigEndian::write_u32( &mut signature_counter, - storage::global_signature_counter(env)?, + env.persist().global_signature_counter()?, ); auth_data.extend(&signature_counter); Ok(auth_data) @@ -1467,6 +1477,7 @@ mod test { expected_credential_id_size: u8, expected_extension_cbor: &[u8], ) { + const INITIAL_SIGNATURE_COUNTER: u32 = 1; match make_credential_response.as_ref().unwrap() { ResponseData::AuthenticatorMakeCredential(make_credential_response) => { let AuthenticatorMakeCredentialResponse { @@ -2042,7 +2053,7 @@ mod test { let mut ctap_state = CtapState::::new(&mut env); ctap_state.client_pin = client_pin; - storage::set_pin(&mut env, &[0x88; 16], 4).unwrap(); + env.persist().set_pin(&[0x88; 16], 4).unwrap(); let client_data_hash = [0xCD]; let pin_uv_auth_param = authenticate_pin_uv_auth_token( @@ -2090,7 +2101,7 @@ mod test { fn test_non_resident_process_make_credential_with_pin() { let mut env = TestEnv::default(); let mut ctap_state = CtapState::::new(&mut env); - storage::set_pin(&mut env, &[0x88; 16], 4).unwrap(); + env.persist().set_pin(&[0x88; 16], 4).unwrap(); let mut make_credential_params = create_minimal_make_credential_parameters(); make_credential_params.options.rk = false; @@ -2110,7 +2121,7 @@ mod test { fn test_resident_process_make_credential_with_pin() { let mut env = TestEnv::default(); let mut ctap_state = CtapState::::new(&mut env); - storage::set_pin(&mut env, &[0x88; 16], 4).unwrap(); + env.persist().set_pin(&[0x88; 16], 4).unwrap(); let make_credential_params = create_minimal_make_credential_parameters(); let make_credential_response = @@ -2135,7 +2146,7 @@ mod test { Err(Ctap2StatusCode::CTAP2_ERR_PUAT_REQUIRED) ); - storage::set_pin(&mut env, &[0x88; 16], 4).unwrap(); + env.persist().set_pin(&[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); @@ -2388,7 +2399,7 @@ mod test { }; let get_assertion_response = ctap_state.process_get_assertion(&mut env, get_assertion_params, DUMMY_CHANNEL); - let signature_counter = storage::global_signature_counter(&mut env).unwrap(); + let signature_counter = env.persist().global_signature_counter().unwrap(); check_assertion_response(get_assertion_response, vec![0x1D], signature_counter, None); } @@ -2613,7 +2624,7 @@ mod test { }; let get_assertion_response = ctap_state.process_get_assertion(&mut env, get_assertion_params, DUMMY_CHANNEL); - let signature_counter = storage::global_signature_counter(&mut env).unwrap(); + let signature_counter = env.persist().global_signature_counter().unwrap(); check_assertion_response(get_assertion_response, vec![0x1D], signature_counter, None); let credential = PublicKeyCredentialSource { @@ -2766,7 +2777,7 @@ mod test { }; let get_assertion_response = ctap_state.process_get_assertion(&mut env, get_assertion_params, DUMMY_CHANNEL); - let signature_counter = storage::global_signature_counter(&mut env).unwrap(); + let signature_counter = env.persist().global_signature_counter().unwrap(); let expected_extension_cbor = [ 0xA1, 0x68, 0x63, 0x72, 0x65, 0x64, 0x42, 0x6C, 0x6F, 0x62, 0x41, 0xCB, ]; @@ -2831,7 +2842,7 @@ mod test { }; let get_assertion_response = ctap_state.process_get_assertion(&mut env, get_assertion_params, DUMMY_CHANNEL); - let signature_counter = storage::global_signature_counter(&mut env).unwrap(); + let signature_counter = env.persist().global_signature_counter().unwrap(); let expected_extension_cbor = [ 0xA1, 0x68, 0x63, 0x72, 0x65, 0x64, 0x42, 0x6C, 0x6F, 0x62, 0x41, 0xCB, ]; @@ -2934,7 +2945,7 @@ mod test { ctap_state.client_pin = client_pin; // The PIN length is outside of the test scope and most likely incorrect. - storage::set_pin(&mut env, &[0u8; 16], 4).unwrap(); + env.persist().set_pin(&[0u8; 16], 4).unwrap(); let client_data_hash = vec![0xCD]; let pin_uv_auth_param = authenticate_pin_uv_auth_token( &pin_uv_auth_token, @@ -2956,7 +2967,7 @@ mod test { }; let get_assertion_response = ctap_state.process_get_assertion(&mut env, get_assertion_params, DUMMY_CHANNEL); - let signature_counter = storage::global_signature_counter(&mut env).unwrap(); + let signature_counter = env.persist().global_signature_counter().unwrap(); check_assertion_response_with_user( get_assertion_response, Some(user2), @@ -3037,7 +3048,7 @@ mod test { }; let get_assertion_response = ctap_state.process_get_assertion(&mut env, get_assertion_params, DUMMY_CHANNEL); - let signature_counter = storage::global_signature_counter(&mut env).unwrap(); + let signature_counter = env.persist().global_signature_counter().unwrap(); check_assertion_response( get_assertion_response, vec![0x03], @@ -3202,13 +3213,13 @@ mod test { let mut env = TestEnv::default(); let mut ctap_state = CtapState::::new(&mut env); - let mut last_counter = storage::global_signature_counter(&mut env).unwrap(); + let mut last_counter = env.persist().global_signature_counter().unwrap(); assert!(last_counter > 0); for _ in 0..100 { assert!(ctap_state .increment_global_signature_counter(&mut env) .is_ok()); - let next_counter = storage::global_signature_counter(&mut env).unwrap(); + let next_counter = env.persist().global_signature_counter().unwrap(); assert!(next_counter > last_counter); last_counter = next_counter; } @@ -3314,7 +3325,7 @@ mod test { storage::store_credential(&mut env, credential).unwrap(); } - storage::set_pin(&mut env, &[0u8; 16], 4).unwrap(); + env.persist().set_pin(&[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, diff --git a/libraries/opensk/src/ctap/status_code.rs b/libraries/opensk/src/ctap/status_code.rs index 2a2f6cb9..2e47bcdd 100644 --- a/libraries/opensk/src/ctap/status_code.rs +++ b/libraries/opensk/src/ctap/status_code.rs @@ -12,8 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. +use crate::api::key_store; use crate::api::user_presence::UserPresenceError; -use crate::api::{attestation_store, key_store}; + +pub type CtapResult = Result; // CTAP specification (version 20190130) section 6.3 // For now, only the CTAP2 codes are here, the CTAP1 are not included. @@ -102,13 +104,28 @@ impl From for Ctap2StatusCode { } } -impl From for Ctap2StatusCode { - fn from(error: attestation_store::Error) -> Self { - use attestation_store::Error; +impl From for key_store::Error { + fn from(_: Ctap2StatusCode) -> Self { + Self + } +} + +impl From for Ctap2StatusCode { + fn from(error: persistent_store::StoreError) -> Ctap2StatusCode { + use persistent_store::StoreError; match error { - Error::Storage => Self::CTAP2_ERR_VENDOR_HARDWARE_FAILURE, - Error::Internal => Self::CTAP2_ERR_VENDOR_INTERNAL_ERROR, - Error::NoSupport => Self::CTAP2_ERR_VENDOR_INTERNAL_ERROR, + // This error is expected. The store is full. + StoreError::NoCapacity => Ctap2StatusCode::CTAP2_ERR_KEY_STORE_FULL, + // This error is expected. The flash is out of life. + StoreError::NoLifetime => Ctap2StatusCode::CTAP2_ERR_KEY_STORE_FULL, + // This error is expected if we don't satisfy the store preconditions. For example we + // try to store a credential which is too long. + StoreError::InvalidArgument => Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR, + // This error is not expected. The storage has been tempered with. We could erase the + // storage. + StoreError::InvalidStorage => Ctap2StatusCode::CTAP2_ERR_VENDOR_HARDWARE_FAILURE, + // This error is not expected. The kernel is failing our syscalls. + StoreError::StorageError => Ctap2StatusCode::CTAP1_ERR_OTHER, } } } diff --git a/libraries/opensk/src/ctap/storage.rs b/libraries/opensk/src/ctap/storage.rs index bcf27123..2754721b 100644 --- a/libraries/opensk/src/ctap/storage.rs +++ b/libraries/opensk/src/ctap/storage.rs @@ -12,40 +12,23 @@ // See the License for the specific language governing permissions and // limitations under the License. -mod key; - -#[cfg(feature = "config_command")] -use crate::api::attestation_store::{self, AttestationStore}; use crate::api::customization::Customization; use crate::api::key_store::KeyStore; -use crate::ctap::client_pin::PIN_AUTH_LENGTH; +use crate::api::persist::{Persist, PersistCredentialIter}; use crate::ctap::data_formats::{ extract_array, extract_text_string, PublicKeyCredentialSource, PublicKeyCredentialUserEntity, }; use crate::ctap::status_code::Ctap2StatusCode; -use crate::ctap::INITIAL_SIGNATURE_COUNTER; use crate::env::{AesKey, Env}; use alloc::string::String; -use alloc::vec; use alloc::vec::Vec; -use arrayref::array_ref; use core::cmp; -use persistent_store::{fragment, StoreUpdate}; #[cfg(feature = "config_command")] use sk_cbor::cbor_array_vec; -/// Wrapper for PIN properties. -struct PinProperties { - /// 16 byte prefix of SHA256 of the currently set PIN. - hash: [u8; PIN_AUTH_LENGTH], - - /// Length of the current PIN in code points. - #[cfg_attr(not(feature = "config_command"), allow(dead_code))] - code_point_length: u8, -} - /// Initializes the store by creating missing objects. pub fn init(env: &mut impl Env) -> Result<(), Ctap2StatusCode> { + env.persist().init()?; env.key_store().init()?; Ok(()) } @@ -59,14 +42,7 @@ pub fn get_credential( env: &mut E, key: usize, ) -> Result { - let min_key = key::CREDENTIALS.start; - if key < min_key || key >= min_key + env.customization().max_supported_resident_keys() { - return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR); - } - let credential_entry = env - .store() - .find(key)? - .ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?; + let credential_entry = env.persist().credential_bytes(key)?; let wrap_key = env.key_store().wrap_key::()?; deserialize_credential::(&wrap_key, &credential_entry) .ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR) @@ -122,45 +98,33 @@ pub fn store_credential( env: &mut E, new_credential: PublicKeyCredentialSource, ) -> Result<(), Ctap2StatusCode> { - let max_supported_resident_keys = env.customization().max_supported_resident_keys(); // Holds the key of the existing credential if this is an update. let mut old_key = None; - let min_key = key::CREDENTIALS.start; - // Holds whether a key is used (indices are shifted by min_key). - let mut keys = vec![false; max_supported_resident_keys]; let mut 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); - } - 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); + break; } } iter_result?; - if old_key.is_none() && keys.iter().filter(|&&x| x).count() >= max_supported_resident_keys { + let max_supported_resident_keys = env.customization().max_supported_resident_keys(); + if old_key.is_none() && count_credentials(env)? >= 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)?, + None => env.persist().free_credential_key()?, // This is an existing credential being updated, we reuse its key. Some(x) => x, }; let wrap_key = env.key_store().wrap_key::()?; let value = serialize_credential::(env, &wrap_key, new_credential)?; - env.store().insert(key, &value)?; + env.persist().write_credential_bytes(key, &value)?; Ok(()) } @@ -171,7 +135,7 @@ pub fn store_credential( /// 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)?) + env.persist().remove_credential(key) } /// Updates a credential's user information. @@ -190,16 +154,12 @@ pub fn update_credential( credential.user_icon = user.user_icon; let wrap_key = env.key_store().wrap_key::()?; let value = serialize_credential::(env, &wrap_key, credential)?; - Ok(env.store().insert(key, &value)?) + env.persist().write_credential_bytes(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) + Ok(env.persist().iter_credentials()?.count()) } /// Returns the estimated number of credentials that can still be stored. @@ -229,123 +189,47 @@ pub fn new_creation_order(env: &mut impl Env) -> 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), - } -} - -/// 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(()) -} - -/// 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. -#[cfg(feature = "config_command")] -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(env.customization().max_pin_retries()), - Some(value) if value.len() == 1 => Ok(value[0]), - _ => Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR), - } + Ok(env + .customization() + .max_pin_retries() + .saturating_sub(env.persist().pin_fails()?)) } /// 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(()) + env.persist().incr_pin_fails() } /// 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)?) + env.persist().reset_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(env.customization().default_min_pin_length()), - Some(value) if value.len() == 1 => Ok(value[0]), - _ => Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR), - } + Ok(env + .persist() + .min_pin_length()? + .unwrap_or(env.customization().default_min_pin_length())) } /// Sets the minimum PIN length. #[cfg(feature = "config_command")] 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])?) + env.persist().set_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(env.customization().default_min_pin_length_rp_ids()), - |value| deserialize_min_pin_length_rp_ids(&value), - ); + let rp_ids_bytes = env.persist().min_pin_length_rp_ids_bytes()?; + let rp_ids = if rp_ids_bytes.is_empty() { + Some(env.customization().default_min_pin_length_rp_ids()) + } else { + deserialize_min_pin_length_rp_ids(&rp_ids_bytes) + }; debug_assert!(rp_ids.is_some()); Ok(rp_ids.unwrap_or_default()) } @@ -354,9 +238,8 @@ pub fn min_pin_length_rp_ids(env: &mut impl Env) -> Result, Ctap2Sta #[cfg(feature = "config_command")] pub fn set_min_pin_length_rp_ids( env: &mut impl Env, - min_pin_length_rp_ids: Vec, + mut min_pin_length_rp_ids: Vec, ) -> Result<(), Ctap2StatusCode> { - let mut min_pin_length_rp_ids = min_pin_length_rp_ids; for rp_id in env.customization().default_min_pin_length_rp_ids() { if !min_pin_length_rp_ids.contains(&rp_id) { min_pin_length_rp_ids.push(rp_id); @@ -365,10 +248,8 @@ pub fn set_min_pin_length_rp_ids( if min_pin_length_rp_ids.len() > env.customization().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)?, - )?) + env.persist() + .set_min_pin_length_rp_ids(&serialize_min_pin_length_rp_ids(min_pin_length_rp_ids)?) } /// Reads the byte vector stored as the serialized large blobs array. @@ -385,8 +266,7 @@ pub fn get_large_blob_array( 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)?; + let output = env.persist().get_large_blob_array(offset, byte_count)?; 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, @@ -409,36 +289,7 @@ pub fn commit_large_blob_array( if large_blob_array.len() > env.customization().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, - )?) -} - -/// 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)?; - env.key_store().reset()?; - 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), - } -} - -/// Marks the PIN as outdated with respect to the new PIN policy. -#[cfg(feature = "config_command")] -pub fn force_pin_change(env: &mut impl Env) -> Result<(), Ctap2StatusCode> { - Ok(env.store().insert(key::FORCE_PIN_CHANGE, &[])?) + env.persist().commit_large_blob_array(large_blob_array) } /// Returns whether enterprise attestation is enabled. @@ -454,27 +305,13 @@ pub fn enterprise_attestation(env: &mut impl Env) -> Result 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), - } + env.persist().enterprise_attestation() } /// Marks enterprise attestation as enabled. #[cfg(feature = "config_command")] pub fn enable_enterprise_attestation(env: &mut impl Env) -> Result<(), Ctap2StatusCode> { - if env - .attestation_store() - .get(&attestation_store::Id::Enterprise)? - .is_none() - { - return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR); - } - if !enterprise_attestation(env)? { - env.store().insert(key::ENTERPRISE_ATTESTATION, &[])?; - } - Ok(()) + env.persist().enable_enterprise_attestation() } /// Returns whether alwaysUv is enabled. @@ -482,11 +319,7 @@ pub fn has_always_uv(env: &mut impl Env) -> Result { if env.customization().enforce_always_uv() { return Ok(true); } - 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), - } + env.persist().has_always_uv() } /// Enables alwaysUv, when disabled, and vice versa. @@ -495,43 +328,16 @@ pub fn toggle_always_uv(env: &mut impl Env) -> Result<(), Ctap2StatusCode> { if env.customization().enforce_always_uv() { return Err(Ctap2StatusCode::CTAP2_ERR_OPERATION_DENIED); } - if has_always_uv(env)? { - Ok(env.store().remove(key::ALWAYS_UV)?) - } else { - Ok(env.store().insert(key::ALWAYS_UV, &[])?) - } -} - -impl From for Ctap2StatusCode { - fn from(error: persistent_store::StoreError) -> Ctap2StatusCode { - use persistent_store::StoreError; - match error { - // This error is expected. The store is full. - StoreError::NoCapacity => Ctap2StatusCode::CTAP2_ERR_KEY_STORE_FULL, - // This error is expected. The flash is out of life. - StoreError::NoLifetime => Ctap2StatusCode::CTAP2_ERR_KEY_STORE_FULL, - // This error is expected if we don't satisfy the store preconditions. For example we - // try to store a credential which is too long. - StoreError::InvalidArgument => Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR, - // This error is not expected. The storage has been tempered with. We could erase the - // storage. - StoreError::InvalidStorage => Ctap2StatusCode::CTAP2_ERR_VENDOR_HARDWARE_FAILURE, - // This error is not expected. The kernel is failing our syscalls. - StoreError::StorageError => Ctap2StatusCode::CTAP1_ERR_OTHER, - } - } + env.persist().toggle_always_uv() } /// Iterator for credentials. pub struct IterCredentials<'a, E: Env> { - /// The store being iterated. - store: &'a persistent_store::Store, - /// The key store for credential unwrapping. wrap_key: AesKey, /// The store iterator. - iter: persistent_store::StoreIter<'a>, + iter: PersistCredentialIter<'a>, /// The iteration result. /// @@ -547,10 +353,8 @@ impl<'a, E: Env> IterCredentials<'a, E> { result: &'a mut Result<(), Ctap2StatusCode>, ) -> Result { let wrap_key = env.key_store().wrap_key::()?; - let store = env.store(); - let iter = store.iter()?; + let iter = env.persist().iter_credentials()?; Ok(IterCredentials { - store, wrap_key, iter, result, @@ -577,18 +381,11 @@ impl<'a, E: Env> Iterator for IterCredentials<'a, E> { if self.result.is_err() { return None; } - while let Some(next) = self.iter.next() { - let handle = self.unwrap(next.ok())?; - let key = handle.get_key(); - if !key::CREDENTIALS.contains(&key) { - continue; - } - let value = self.unwrap(handle.get_value(self.store).ok())?; - let deserialized = deserialize_credential::(&self.wrap_key, &value); - let credential = self.unwrap(deserialized)?; - return Some((key, credential)); - } - None + let next = self.iter.next()?; + let (key, value) = self.unwrap(next.ok())?; + let deserialized = deserialize_credential::(&self.wrap_key, &value); + let credential = self.unwrap(deserialized)?; + Some((key, credential)) } } @@ -634,12 +431,13 @@ fn serialize_min_pin_length_rp_ids(rp_ids: Vec) -> Result, Ctap2 #[cfg(test)] mod test { use super::*; - use crate::api::attestation_store::{self, Attestation, AttestationStore}; + use crate::api::persist::{Attestation, AttestationId, Persist}; use crate::api::private_key::PrivateKey; use crate::api::rng::Rng; use crate::ctap::data_formats::{ CredentialProtectionPolicy, PublicKeyCredentialSource, PublicKeyCredentialType, }; + use crate::ctap::reset; use crate::ctap::secret::Secret; use crate::env::test::TestEnv; @@ -862,34 +660,6 @@ mod test { assert_eq!(found_credential, Some(expected_credential)); } - #[test] - fn test_pin_hash_and_length() { - let mut env = TestEnv::default(); - - // Pin hash is initially not set. - 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 = 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; - 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. - 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 env = TestEnv::default(); @@ -925,7 +695,7 @@ mod test { // Make sure the attestation are absent. There is no batch attestation in tests. assert_eq!( - env.attestation_store().get(&attestation_store::Id::Batch), + env.persist().get_attestation(AttestationId::Batch), Ok(None) ); @@ -934,14 +704,14 @@ mod test { private_key: Secret::from_exposed_secret([0x41; 32]), certificate: vec![0xdd; 20], }; - env.attestation_store() - .set(&attestation_store::Id::Batch, Some(&dummy_attestation)) + env.persist() + .set_attestation(AttestationId::Batch, Some(&dummy_attestation)) .unwrap(); // The persistent keys stay initialized and preserve their value after a reset. reset(&mut env).unwrap(); assert_eq!( - env.attestation_store().get(&attestation_store::Id::Batch), + env.persist().get_attestation(AttestationId::Batch), Ok(Some(dummy_attestation)) ); } @@ -983,17 +753,6 @@ mod test { assert_eq!(min_pin_length_rp_ids(&mut env).unwrap(), rp_ids); } - #[test] - fn test_max_large_blob_array_size() { - let mut env = TestEnv::default(); - - assert!( - env.customization().max_large_blob_array_size() - <= 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 env = TestEnv::default(); @@ -1049,30 +808,6 @@ mod test { assert_eq!(vec![0x3C], restored_large_blob_array); } - #[test] - fn test_global_signature_counter() { - let mut env = TestEnv::default(); - - let mut counter_value = 1; - assert_eq!(global_signature_counter(&mut env).unwrap(), counter_value); - for increment in 1..10 { - assert!(incr_global_signature_counter(&mut env, increment).is_ok()); - counter_value += increment; - assert_eq!(global_signature_counter(&mut env).unwrap(), counter_value); - } - } - - #[test] - fn test_force_pin_change() { - let mut env = TestEnv::default(); - - 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 env = TestEnv::default(); @@ -1081,8 +816,8 @@ mod test { private_key: Secret::from_exposed_secret([0x41; 32]), certificate: vec![0xdd; 20], }; - env.attestation_store() - .set(&attestation_store::Id::Enterprise, Some(&dummy_attestation)) + env.persist() + .set_attestation(AttestationId::Enterprise, Some(&dummy_attestation)) .unwrap(); assert!(!enterprise_attestation(&mut env).unwrap()); diff --git a/libraries/opensk/src/env/mod.rs b/libraries/opensk/src/env/mod.rs index 7130a034..427450fa 100644 --- a/libraries/opensk/src/env/mod.rs +++ b/libraries/opensk/src/env/mod.rs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::api::attestation_store::AttestationStore; use crate::api::clock::Clock; use crate::api::connection::HidConnection; use crate::api::crypto::ecdh::Ecdh; @@ -20,11 +19,11 @@ use crate::api::crypto::ecdsa::Ecdsa; use crate::api::crypto::Crypto; use crate::api::customization::Customization; use crate::api::key_store::KeyStore; +use crate::api::persist::Persist; use crate::api::rng::Rng; use crate::api::user_presence::UserPresence; use crate::ctap::Channel; use alloc::vec::Vec; -use persistent_store::{Storage, Store}; #[cfg(feature = "std")] pub mod test; @@ -43,20 +42,18 @@ pub type Hkdf = <::Crypto as Crypto>::Hkdf256; pub trait Env { type Rng: Rng; type UserPresence: UserPresence; - type Storage: Storage; + type Persist: Persist; type KeyStore: KeyStore; type Write: core::fmt::Write; type Customization: Customization; type HidConnection: HidConnection; - type AttestationStore: AttestationStore; type Clock: Clock; type Crypto: Crypto; fn rng(&mut self) -> &mut Self::Rng; fn user_presence(&mut self) -> &mut Self::UserPresence; - fn store(&mut self) -> &mut Store; + fn persist(&mut self) -> &mut Self::Persist; fn key_store(&mut self) -> &mut Self::KeyStore; - fn attestation_store(&mut self) -> &mut Self::AttestationStore; fn clock(&mut self) -> &mut Self::Clock; /// Creates a write instance for debugging. diff --git a/libraries/opensk/src/env/test/mod.rs b/libraries/opensk/src/env/test/mod.rs index b8a1da6b..2d9f6119 100644 --- a/libraries/opensk/src/env/test/mod.rs +++ b/libraries/opensk/src/env/test/mod.rs @@ -12,14 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::api::attestation_store::AttestationStore; use crate::api::clock::Clock; use crate::api::connection::{HidConnection, SendOrRecvResult, SendOrRecvStatus}; use crate::api::crypto::software_crypto::SoftwareCrypto; use crate::api::customization::DEFAULT_CUSTOMIZATION; +use crate::api::key_store; +use crate::api::persist::{Persist, PersistIter}; use crate::api::rng::Rng; use crate::api::user_presence::{UserPresence, UserPresenceResult}; -use crate::api::{attestation_store, key_store}; +use crate::ctap::status_code::CtapResult; use crate::env::Env; use customization::TestCustomization; use persistent_store::{BufferOptions, BufferStorage, Store}; @@ -105,6 +106,27 @@ fn new_storage() -> BufferStorage { BufferStorage::new(store, options) } +impl Persist for TestEnv { + fn find(&self, key: usize) -> CtapResult>> { + Ok(self.store.find(key)?) + } + + fn insert(&mut self, key: usize, value: &[u8]) -> CtapResult<()> { + Ok(self.store.insert(key, value)?) + } + + fn remove(&mut self, key: usize) -> CtapResult<()> { + Ok(self.store.remove(key)?) + } + + fn iter(&self) -> CtapResult> { + Ok(Box::new(self.store.iter()?.map(|handle| match handle { + Ok(handle) => Ok(handle.get_key()), + Err(error) => Err(error.into()), + }))) + } +} + impl HidConnection for TestEnv { fn send_and_maybe_recv(&mut self, _buf: &mut [u8; 64], _timeout_ms: usize) -> SendOrRecvResult { // TODO: Implement I/O from canned requests/responses for integration testing. @@ -163,29 +185,11 @@ impl UserPresence for TestUserPresence { impl key_store::Helper for TestEnv {} -impl AttestationStore for TestEnv { - fn get( - &mut self, - _id: &attestation_store::Id, - ) -> Result, attestation_store::Error> { - attestation_store::helper_get(self) - } - - fn set( - &mut self, - _id: &attestation_store::Id, - attestation: Option<&attestation_store::Attestation>, - ) -> Result<(), attestation_store::Error> { - attestation_store::helper_set(self, attestation) - } -} - impl Env for TestEnv { type Rng = TestRng; type UserPresence = TestUserPresence; - type Storage = BufferStorage; + type Persist = Self; type KeyStore = Self; - type AttestationStore = Self; type Clock = TestClock; type Write = TestWrite; type Customization = TestCustomization; @@ -200,15 +204,11 @@ impl Env for TestEnv { &mut self.user_presence } - fn store(&mut self) -> &mut Store { - &mut self.store - } - - fn key_store(&mut self) -> &mut Self { + fn persist(&mut self) -> &mut Self { self } - fn attestation_store(&mut self) -> &mut Self { + fn key_store(&mut self) -> &mut Self { self } diff --git a/libraries/opensk/src/test_helpers/mod.rs b/libraries/opensk/src/test_helpers/mod.rs index a89bcde1..d0aaaa8b 100644 --- a/libraries/opensk/src/test_helpers/mod.rs +++ b/libraries/opensk/src/test_helpers/mod.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::api::attestation_store::{self, AttestationStore}; +use crate::api::persist::{Attestation, AttestationId, Persist}; use crate::ctap::command::{AuthenticatorConfigParameters, Command}; use crate::ctap::data_formats::ConfigSubCommand; use crate::ctap::secret::Secret; @@ -27,13 +27,13 @@ const DUMMY_CHANNEL: Channel = Channel::MainHid([0x12, 0x34, 0x56, 0x78]); pub fn enable_enterprise_attestation( state: &mut CtapState, env: &mut E, -) -> Result { - let attestation = attestation_store::Attestation { +) -> Result { + let attestation = Attestation { private_key: Secret::from_exposed_secret([0x41; 32]), certificate: vec![0xdd; 20], }; - env.attestation_store() - .set(&attestation_store::Id::Batch, Some(&attestation))?; + env.persist() + .set_attestation(AttestationId::Enterprise, Some(&attestation))?; let config_params = AuthenticatorConfigParameters { sub_command: ConfigSubCommand::EnableEnterpriseAttestation, diff --git a/src/env/tock/commands.rs b/src/env/tock/commands.rs index 818013f8..d60ddbc9 100644 --- a/src/env/tock/commands.rs +++ b/src/env/tock/commands.rs @@ -18,11 +18,11 @@ use alloc::vec::Vec; use arrayref::array_ref; use core::convert::TryFrom; use libtock_platform::Syscalls; -use opensk::api::attestation_store::{self, Attestation, AttestationStore}; use opensk::api::crypto::sha256::Sha256; use opensk::api::crypto::EC_FIELD_SIZE; #[cfg(not(feature = "with_ctap1"))] use opensk::api::customization::Customization; +use opensk::api::persist::{Attestation, AttestationId, Persist}; #[cfg(not(feature = "std"))] use opensk::ctap::check_user_presence; use opensk::ctap::data_formats::{ @@ -104,10 +104,10 @@ fn process_vendor_configure< check_user_presence(env, _channel)?; } // This command is for U2F support and we use the batch attestation there. - let attestation_id = attestation_store::Id::Batch; + let attestation_id = AttestationId::Batch; // Sanity checks - let current_attestation = env.attestation_store().get(&attestation_id)?; + let current_attestation = env.persist().get_attestation(attestation_id)?; let response = match params.attestation_material { None => VendorConfigureResponse { cert_programmed: current_attestation.is_some(), @@ -121,8 +121,8 @@ fn process_vendor_configure< private_key: Secret::from_exposed_secret(data.private_key), certificate: data.certificate, }; - env.attestation_store() - .set(&attestation_id, Some(&attestation))?; + env.persist() + .set_attestation(attestation_id, Some(&attestation))?; } VendorConfigureResponse { cert_programmed: true, @@ -517,7 +517,7 @@ mod test { }) ); assert_eq!( - env.attestation_store().get(&attestation_store::Id::Batch), + env.persist().get_attestation(AttestationId::Batch), Ok(Some(Attestation { private_key: Secret::from_exposed_secret(dummy_key), certificate: dummy_cert.to_vec(), @@ -545,7 +545,7 @@ mod test { }) ); assert_eq!( - env.attestation_store().get(&attestation_store::Id::Batch), + env.persist().get_attestation(AttestationId::Batch), Ok(Some(Attestation { private_key: Secret::from_exposed_secret(dummy_key), certificate: dummy_cert.to_vec(), diff --git a/src/env/tock/mod.rs b/src/env/tock/mod.rs index 497d07d8..6ebc5778 100644 --- a/src/env/tock/mod.rs +++ b/src/env/tock/mod.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use alloc::boxed::Box; use alloc::vec::Vec; use clock::TockClock; use core::cell::Cell; @@ -28,15 +29,16 @@ use libtock_drivers::{rng, timer, usb_ctap_hid}; use libtock_leds::Leds; use libtock_platform as platform; use libtock_platform::{ErrorCode, Syscalls}; -use opensk::api::attestation_store::AttestationStore; use opensk::api::connection::{ HidConnection, SendOrRecvError, SendOrRecvResult, SendOrRecvStatus, UsbEndpoint, }; use opensk::api::crypto::software_crypto::SoftwareCrypto; use opensk::api::customization::{CustomizationImpl, AAGUID_LENGTH, DEFAULT_CUSTOMIZATION}; +use opensk::api::key_store; +use opensk::api::persist::{Persist, PersistIter}; use opensk::api::rng::Rng; use opensk::api::user_presence::{UserPresence, UserPresenceError, UserPresenceResult}; -use opensk::api::{attestation_store, key_store}; +use opensk::ctap::status_code::CtapResult; use opensk::ctap::Channel; use opensk::env::Env; #[cfg(feature = "std")] @@ -260,6 +262,31 @@ pub fn take_storage Persist for TockEnv +where + S: Syscalls, + C: platform::subscribe::Config + platform::allow_ro::Config, +{ + fn find(&self, key: usize) -> CtapResult>> { + Ok(self.store.find(key)?) + } + + fn insert(&mut self, key: usize, value: &[u8]) -> CtapResult<()> { + Ok(self.store.insert(key, value)?) + } + + fn remove(&mut self, key: usize) -> CtapResult<()> { + Ok(self.store.remove(key)?) + } + + fn iter(&self) -> CtapResult> { + Ok(Box::new(self.store.iter()?.map(|handle| match handle { + Ok(handle) => Ok(handle.get_key()), + Err(error) => Err(error.into()), + }))) + } +} + impl UserPresence for TockEnv where S: Syscalls, @@ -366,41 +393,13 @@ where { } -impl AttestationStore for TockEnv -where - S: Syscalls, - C: platform::subscribe::Config + platform::allow_ro::Config, -{ - fn get( - &mut self, - id: &attestation_store::Id, - ) -> Result, attestation_store::Error> { - if !matches!(id, attestation_store::Id::Batch) { - return Err(attestation_store::Error::NoSupport); - } - attestation_store::helper_get(self) - } - - fn set( - &mut self, - id: &attestation_store::Id, - attestation: Option<&attestation_store::Attestation>, - ) -> Result<(), attestation_store::Error> { - if !matches!(id, attestation_store::Id::Batch) { - return Err(attestation_store::Error::NoSupport); - } - attestation_store::helper_set(self, attestation) - } -} - impl Env for TockEnv { type Rng = TockRng; type UserPresence = Self; - type Storage = Storage; + type Persist = Self; type KeyStore = Self; - type AttestationStore = Self; type Clock = TockClock; type Write = ConsoleWriter; type Customization = CustomizationImpl; @@ -415,15 +414,11 @@ impl E self } - fn store(&mut self) -> &mut Store { - &mut self.store - } - - fn key_store(&mut self) -> &mut Self { + fn persist(&mut self) -> &mut Self { self } - fn attestation_store(&mut self) -> &mut Self { + fn key_store(&mut self) -> &mut Self { self }