diff --git a/.gitignore b/.gitignore index 611b278..d85036c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +fuzz/corpus target/ Cargo.lock diff --git a/bootloader/Cargo.toml b/bootloader/Cargo.toml index bebe244..b93d4c1 100644 --- a/bootloader/Cargo.toml +++ b/bootloader/Cargo.toml @@ -15,6 +15,7 @@ cortex-m-rt = "*" cortex-m-rt-macros = "*" panic-abort = "0.3.2" rtt-target = { version = "*", features = ["cortex-m"] } +tock-registers = { version = "0.6.0", features = ["no_std_unit_tests"] } [profile.dev] panic = "abort" diff --git a/bootloader/src/bitfields.rs b/bootloader/src/bitfields.rs new file mode 100644 index 0000000..5a6dc92 --- /dev/null +++ b/bootloader/src/bitfields.rs @@ -0,0 +1,118 @@ +// Copyright 2020-2022 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 tock_registers::register_bitfields; + +register_bitfields! [u32, + // Generic or shared bitfields + pub Task [ + ENABLE OFFSET(0) NUMBITS(1) + ], + + pub Byte [ + VALUE OFFSET(0) NUMBITS(8) + ], + + pub Busy [ + /// Asserted when AES_BUSY or DES_BUSY or HASH_BUSY are asserted or when the DIN FIFO is not empty + BUSY OFFSET(0) NUMBITS(1) [ + Ready = 0, + Busy = 1 + ] + ], + + // CC_CTL register bitfields + pub CryptoMode [ + /// Determines the active cryptographic engine + MODE OFFSET(0) NUMBITS(5) [ + Bypass = 0, + Aes = 1, + AesToHash = 2, + AesAndHash = 3, + Des = 4, + DesToHash = 5, + DesAndHash = 6, + Hash = 7, + AesMacAndBypass = 9, + AesToHashAndDout = 10 + ] + ], + + // HOST_RGF register bitfields + pub Interrupts [ + /// This interrupt is asserted when all data was delivered to DIN buffer from SRAM + SRAM_TO_DIN OFFSET(4) NUMBITS(1), + /// This interrupt is asserted when all data was delivered to SRAM buffer from DOUT + DOUT_TO_SRAM OFFSET(5) NUMBITS(1), + /// This interrupt is asserted when all data was delivered to DIN buffer from memory + MEM_TO_DIN OFFSET(6) NUMBITS(1), + /// This interrupt is asserted when all data was delivered to memory buffer from DOUT + DOUT_TO_MEM OFFSET(7) NUMBITS(1), + AXI_ERROR OFFSET(8) NUMBITS(1), + /// The PKA end of operation interrupt status + PKA_EXP OFFSET(9) NUMBITS(1), + /// The RNG interrupt status + RNG OFFSET(10) NUMBITS(1), + /// The GPR interrupt status + SYM_DMA_COMPLETED OFFSET(11) NUMBITS(1) + ], + + pub RgfEndianness [ + /// DOUT write endianness + DOUT_WR_BG OFFSET(3) NUMBITS(1) [ + LittleEndian = 0, + BigEndian = 1 + ], + /// DIN write endianness + DIN_RD_BG OFFSET(7) NUMBITS(1) [ + LittleEndian = 0, + BigEndian = 1 + ], + /// DOUT write word endianness + DOUT_WR_WBG OFFSET(11) NUMBITS(1) [ + LittleEndian = 0, + BigEndian = 1 + ], + /// DIN write word endianness + DIN_RD_WBG OFFSET(15) NUMBITS(1) [ + LittleEndian = 0, + BigEndian = 1 + ] + ], + + // DIN and DOUT register bitfields + pub LliWord1 [ + /// Total number of bytes to read using DMA in this entry + BYTES_NUM OFFSET(0) NUMBITS(30), + /// Indicates the first LLI entry + FIRST OFFSET(30) NUMBITS(1), + /// Indicates the last LLI entry + LAST OFFSET(31) NUMBITS(1) + ], + + pub HashControl [ + // bit 2 is reserved but to simplify the logic we include it in the bitfield. + MODE OFFSET(0) NUMBITS(4) [ + MD5 = 0, + SHA1 = 1, + SHA256 = 2, + SHA224 = 10 + ] + ], + + pub PaddingConfig [ + /// Enable Padding generation. must be reset upon completion of padding. + DO_PAD OFFSET(2) NUMBITS(1) + ] +]; diff --git a/bootloader/src/crypto_cell.rs b/bootloader/src/crypto_cell.rs new file mode 100644 index 0000000..7cbd01e --- /dev/null +++ b/bootloader/src/crypto_cell.rs @@ -0,0 +1,283 @@ +// Copyright 2019-2022 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. + +//! CryptoCell 310 +//! +//! Author +//! ------------------- +//! +//! * Author: Jean-Michel Picod +//! * Date: October 1 2019 + +use super::bitfields; +use super::registers::{CryptoCellRegisters, NordicCC310Registers}; +use super::static_ref::StaticRef; +use core::cell::Cell; +#[cfg(debug_assertions)] +use rtt_target::rprintln; + +const SHA256_INIT_VALUE: [u32; 8] = [ + 0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19, +]; + +#[derive(Copy, Clone)] +enum DigestAlgorithm { + Sha256 = 2, +} + +#[derive(Copy, Clone)] +enum OperationMode { + Idle, + Hash, +} + +pub struct CryptoCell310 { + registers: StaticRef, + power: StaticRef, + current_op: Cell, + + hash_ctx: Cell<[u32; 8]>, + hash_total_size: Cell, +} + +const CC310_BASE: StaticRef = + unsafe { StaticRef::new(0x5002B000 as *const CryptoCellRegisters) }; +const CC310_POWER: StaticRef = + unsafe { StaticRef::new(0x5002A500 as *const NordicCC310Registers) }; + +// Identification "signature" for CryptoCell. According to the documentation, the value +// held by this register is a fixed value, used by Host driver to verify CryptoCell presence +// at this address. +// This value was read from a CryptoCell-310 on a nRF52840-dongle kit. +const CC310_SIGNATURE: u32 = 0x20E00000; + +impl CryptoCell310 { + /// Creates a new instance of cryptocell state. + pub const fn new() -> Self { + CryptoCell310 { + registers: CC310_BASE, + power: CC310_POWER, + current_op: Cell::new(OperationMode::Idle), + + hash_ctx: Cell::new(SHA256_INIT_VALUE), + hash_total_size: Cell::new(0), + } + } + + fn enable(&self) { + self.power.enable.write(bitfields::Task::ENABLE::SET); + for _i in 1..10 { + let read_signature = self.registers.host_rgf.signature.get(); + if read_signature != CC310_SIGNATURE { + #[cfg(debug_assertions)] + rprintln!( + "[loop {}] Invalid CC310 signature. Expected {}, got {}\n", + _i, + CC310_SIGNATURE, + read_signature + ); + } else { + break; + } + } + if self.registers.host_rgf.signature.get() != CC310_SIGNATURE { + panic!("Failed to initialize CC310"); + } + // Make sure everything is set to little endian + self.registers.host_rgf.endian.write( + bitfields::RgfEndianness::DOUT_WR_BG::LittleEndian + + bitfields::RgfEndianness::DIN_RD_BG::LittleEndian + + bitfields::RgfEndianness::DOUT_WR_WBG::LittleEndian + + bitfields::RgfEndianness::DIN_RD_WBG::LittleEndian, + ); + // Always start the clock for DMA engine. It's too hard to keep + // track of which submodule needs DMA otherwise. + self.registers + .misc + .dma_clk_enable + .write(bitfields::Task::ENABLE::SET); + self.registers.host_rgf.interrupt_mask.write( + bitfields::Interrupts::SRAM_TO_DIN::CLEAR + + bitfields::Interrupts::DOUT_TO_SRAM::CLEAR + + bitfields::Interrupts::MEM_TO_DIN::CLEAR + + bitfields::Interrupts::DOUT_TO_MEM::CLEAR + + bitfields::Interrupts::AXI_ERROR::SET + + bitfields::Interrupts::PKA_EXP::SET + + bitfields::Interrupts::RNG::SET + + bitfields::Interrupts::SYM_DMA_COMPLETED::CLEAR, + ); + } + + fn disable(&self) { + self.registers.host_rgf.interrupt_mask.set(0); + self.power.enable.write(bitfields::Task::ENABLE::CLEAR); + self.registers + .misc + .dma_clk_enable + .write(bitfields::Task::ENABLE::CLEAR); + } + + fn clear_data(&self) { + let mut ctx = self.hash_ctx.get(); + ctx.iter_mut().for_each(|b| *b = 0); + self.hash_ctx.set(ctx); + self.hash_total_size.set(0); + } + + /// Adds data to the current hash computation. + /// + /// You have to know in advance if is this is going to be the last block, and indicate that + /// correctly. Sizes of chunks before the last need to be multiples of 64. + pub fn update(&self, data: &[u8], is_last_block: bool) { + // Start CryptoCell + self.enable(); + + while self.registers.ctrl.hash_busy.is_set(bitfields::Busy::BUSY) {} + while self + .registers + .ctrl + .crypto_busy + .is_set(bitfields::Busy::BUSY) + {} + while self + .registers + .din + .mem_dma_busy + .is_set(bitfields::Busy::BUSY) + {} + + // Start HASH module and configure it + self.current_op.set(OperationMode::Hash); + self.registers + .misc + .hash_clk_enable + .write(bitfields::Task::ENABLE::SET); + self.registers + .ctrl + .crypto_ctl + .write(bitfields::CryptoMode::MODE::Hash); + self.registers + .hash + .padding + .write(bitfields::Task::ENABLE::SET); + let size = self.hash_total_size.get(); + self.registers.hash.hash_len_lsb.set(size as u32); + self.registers + .hash + .hash_len_msb + .set(size.wrapping_shr(32) as u32); + self.registers + .hash + .control + .set(DigestAlgorithm::Sha256 as u32); + + // Digest must be set backwards because writing to HASH[0] + // starts computation + let mut digest = self.hash_ctx.get(); + for i in (0..digest.len()).rev() { + self.registers.hash.hash[i].set(digest[i]); + } + while self.registers.ctrl.hash_busy.is_set(bitfields::Busy::BUSY) {} + + // Process data + if !data.is_empty() { + if is_last_block { + self.registers + .hash + .auto_hw_padding + .write(bitfields::Task::ENABLE::SET); + } + self.registers.din.src_lli_word0.set(data.as_ptr() as u32); + self.registers + .din + .src_lli_word1 + .write(bitfields::LliWord1::BYTES_NUM.val(data.len() as u32)); + while !self + .registers + .host_rgf + .interrupts + .is_set(bitfields::Interrupts::MEM_TO_DIN) + {} + self.registers + .host_rgf + .interrupt_clear + .write(bitfields::Interrupts::MEM_TO_DIN::SET); + } else { + // use DO_PAD to complete padding of previous operation + self.registers + .hash + .pad_config + .write(bitfields::PaddingConfig::DO_PAD::SET); + } + while self + .registers + .ctrl + .crypto_busy + .is_set(bitfields::Busy::BUSY) + {} + while self + .registers + .din + .mem_dma_busy + .is_set(bitfields::Busy::BUSY) + {} + + // Update context and total size + for i in (0..digest.len()).rev() { + digest[i] = self.registers.hash.hash[i].get(); + } + self.hash_ctx.set(digest); + let new_size: u64 = ((self.registers.hash.hash_len_msb.get() as u64) << 32) + + (self.registers.hash.hash_len_lsb.get() as u64); + self.hash_total_size.set(new_size); + + // Disable HASH module + self.registers + .hash + .padding + .write(bitfields::Task::ENABLE::SET); + self.registers + .hash + .auto_hw_padding + .write(bitfields::Task::ENABLE::CLEAR); + self.registers + .hash + .pad_config + .write(bitfields::PaddingConfig::DO_PAD::CLEAR); + while self + .registers + .ctrl + .crypto_busy + .is_set(bitfields::Busy::BUSY) + {} + self.registers + .misc + .hash_clk_enable + .write(bitfields::Task::ENABLE::CLEAR); + + self.disable(); + } + + /// Clears the data for potential reuse, and returns the result. + pub fn finalize_and_clear(&self) -> [u8; 32] { + use byteorder::{BigEndian, ByteOrder}; + let words = self.hash_ctx.get(); + let mut bytes = [0u8; 32]; + for (i, word) in words.iter().enumerate() { + BigEndian::write_u32(&mut bytes[4 * i..4 * i + 4], *word); + } + self.clear_data(); + bytes + } +} diff --git a/bootloader/src/main.rs b/bootloader/src/main.rs index 51bd03d..b14e976 100644 --- a/bootloader/src/main.rs +++ b/bootloader/src/main.rs @@ -1,4 +1,4 @@ -// Copyright 2021 Google LLC +// Copyright 2021-2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,6 +15,11 @@ #![no_main] #![no_std] +mod bitfields; +mod crypto_cell; +mod registers; +mod static_ref; + extern crate cortex_m; extern crate cortex_m_rt as rt; @@ -69,7 +74,7 @@ struct BootPartition { } impl BootPartition { - const _FIRMWARE_LENGTH: usize = 0x00040000; + const FIRMWARE_LENGTH: usize = 0x00040000; /// Reads the metadata, returns the timestamp if all checks pass. pub fn read_timestamp(&self) -> Result { @@ -93,18 +98,40 @@ impl BootPartition { Ok(metadata.timestamp) } - /// Placeholder for the SHA256 implementation. + /// Computes the SHA256 of metadata information and partition data. /// - /// TODO implemented in next PR - /// Without it, the bootloader will never boot anything. - fn compute_upgrade_hash(&self, _metadata_page: &[u8]) -> [u8; 32] { - [0; 32] + /// Assumes that firmware address and length are divisible by the page size. + /// This is the hardware implementation on the cryptocell. + #[allow(clippy::assertions_on_constants)] + fn compute_upgrade_hash(&self, metadata_page: &[u8]) -> [u8; 32] { + debug_assert!(self.firmware_address % PAGE_SIZE == 0); + debug_assert!(BootPartition::FIRMWARE_LENGTH % PAGE_SIZE == 0); + let cc310 = crypto_cell::CryptoCell310::new(); + for page_offset in (0..BootPartition::FIRMWARE_LENGTH).step_by(PAGE_SIZE) { + let page = unsafe { read_page(self.firmware_address + page_offset) }; + cc310.update(&page, false); + } + cc310.update(&metadata_page[32..Metadata::DATA_LEN], true); + cc310.finalize_and_clear() } /// Jump to the firmware. pub fn boot(&self) -> ! { let address = self.firmware_address; + // Clear any pending Cryptocell interrupt in NVIC + let peripherals = cortex_m::Peripherals::take().unwrap(); + unsafe { + // We could only clear cryptocell interrupts, but let's clean up before booting. + // Example code to clear more specifically: + // const CC310_IRQ: u16 = 42; + // peripherals.NVIC.icpr[usize::from(CC310_IRQ / 32)].write(1 << (CC310_IRQ % 32)); + peripherals.NVIC.icer[0].write(0xffff_ffff); + peripherals.NVIC.icpr[0].write(0xffff_ffff); + peripherals.NVIC.icer[1].write(0xffff_ffff); + peripherals.NVIC.icpr[1].write(0xffff_ffff); + } + #[cfg(debug_assertions)] rprintln!("Boot jump to {:08X}", address); let address_pointer = address as *const u32; diff --git a/bootloader/src/registers.rs b/bootloader/src/registers.rs new file mode 100644 index 0000000..17725dc --- /dev/null +++ b/bootloader/src/registers.rs @@ -0,0 +1,139 @@ +// Copyright 2020-2022 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 super::bitfields::{ + Busy, CryptoMode, HashControl, Interrupts, LliWord1, PaddingConfig, RgfEndianness, Task, +}; +use tock_registers::{ + register_structs, + registers::{ReadOnly, ReadWrite, WriteOnly}, +}; + +register_structs! { + pub CryptoCellControlRegisters { + /// Defines the cryptographic flow + (0x0000 => pub crypto_ctl: WriteOnly), + (0x0004 => _reserved0), + /// This register is set whent the cryptographic core is busy + (0x0010 => pub crypto_busy: ReadOnly), + (0x0014 => _reserved1), + /// This register is set when the Hash engine is busy + (0x001C => pub hash_busy: ReadOnly), + (0x0020 => @END), + } +} + +register_structs! { + pub CryptoCellDinRegisters { + (0x0000 => _reserved0), + /// Indicates whether memoty (AXI) source DMA (DIN) is busy + (0x0020 => pub mem_dma_busy: ReadOnly), + (0x0024 => _reserved1), + /// This register is used in direct LLI mode - holds the location of the data source + /// in the memory (AXI) + (0x0028 => pub src_lli_word0: WriteOnly), + /// This register is used in direct LLI mode - holds the number of bytes to be read + /// from the memory (AXI). + /// Writing to this register triggers the DMA. + (0x002C => pub src_lli_word1: WriteOnly), + (0x0030 => @END), + } +} + +register_structs! { + pub CryptoCellHashRegisters { + /// Write initial hash value or read final hash value + (0x0000 => pub hash: [ReadWrite; 9]), + (0x0024 => _reserved0), + /// HW padding automatically activated by engine. + /// For the special case of ZERO bytes data vector this register should not be used! instead use HASH_PAD_CFG + (0x0044 => pub auto_hw_padding: WriteOnly), + (0x0048 => _reserved1), + /// Selects which HASH mode to run + (0x0180 => pub control: ReadWrite), + /// This register enables the hash hw padding. + (0x0184 => pub padding: ReadWrite), + /// HASH_PAD_CFG Register. + (0x0188 => pub pad_config: ReadWrite), + /// This register hold the length of current hash operation + (0x018C => pub hash_len_lsb: ReadWrite), + /// This register hold the length of current hash operation + (0x0190 => pub hash_len_msb: ReadWrite), + (0x0194 => @END), + } +} + +register_structs! { + pub CryptoCellHostRgfRegisters { + /// The Interrupt Request register. + /// Each bit of this register holds the interrupt status of a single interrupt source. + (0x0000 => pub interrupts: ReadOnly), + /// The Interrupt Mask register. Each bit of this register holds the mask of a single + /// interrupt source. + (0x0004 => pub interrupt_mask: ReadWrite), + /// Interrupt Clear Register + (0x0008 => pub interrupt_clear: WriteOnly), + /// This register defines the endianness of the Host-accessible registers. + (0x000C => pub endian: ReadWrite), + (0x0010 => _reserved0), + /// This register holds the CryptoCell product signature. + (0x0024 => pub signature: ReadOnly), + (0x0028 => @END), + } +} + +register_structs! { + pub CryptoCellMiscRegisters { + (0x0000 => _reserved0), + /// The HASH clock enable register + (0x0018 => pub hash_clk_enable: ReadWrite), + /// The PKA clock enable register + (0x001C => _reserved1), + /// The DMA clock enable register + (0x0020 => pub dma_clk_enable: ReadWrite), + /// the CryptoCell clocks' status register + (0x0024 => @END), + } +} + +register_structs! { + pub NordicCC310Registers { + (0x0000 => pub enable: ReadWrite), + (0x0004 => @END), + }, + + pub CryptoCellRegisters { + (0x0000 => _reserved0), + /// HASH registers + /// - Base address: 0x0640 + (0x0640 => pub hash: CryptoCellHashRegisters), + (0x07D4 => _reserved1), + /// Misc registers + /// - Base address: 0x0800 + (0x0800 => pub misc: CryptoCellMiscRegisters), + (0x0824 => _reserved2), + /// CryptoCell control registers + /// - Base address: 0x0900 + (0x0900 => pub ctrl: CryptoCellControlRegisters), + (0x0920 => _reserved3), + /// HOST_RGF registers + /// - Base address: 0x0A00 + (0x0A00 => pub host_rgf: CryptoCellHostRgfRegisters), + (0x0A28 => _reserved4), + /// DIN registers + /// - Base address: 0x0C00 + (0x0C00 => pub din: CryptoCellDinRegisters), + (0x0C30 => @END), + } +} diff --git a/bootloader/src/static_ref.rs b/bootloader/src/static_ref.rs new file mode 100644 index 0000000..4fc27f4 --- /dev/null +++ b/bootloader/src/static_ref.rs @@ -0,0 +1,46 @@ +//! Wrapper type for safe pointers to static memory. +//! +//! Imported from: +//! https://github.com/tock/tock/blob/master/kernel/src/utilities/static_ref.rs + +use core::ops::Deref; + +/// A pointer to statically allocated mutable data such as memory mapped I/O +/// registers. +/// +/// This is a simple wrapper around a raw pointer that encapsulates an unsafe +/// dereference in a safe manner. It serve the role of creating a `&'static T` +/// given a raw address and acts similarly to `extern` definitions, except +/// `StaticRef` is subject to module and crate boundaries, while `extern` +/// definitions can be imported anywhere. +#[derive(Debug)] +pub struct StaticRef { + ptr: *const T, +} + +impl StaticRef { + /// Create a new `StaticRef` from a raw pointer + /// + /// ## Safety + /// + /// Callers must pass in a reference to statically allocated memory which + /// does not overlap with other values. + pub const unsafe fn new(ptr: *const T) -> StaticRef { + StaticRef { ptr } + } +} + +impl Clone for StaticRef { + fn clone(&self) -> Self { + StaticRef { ptr: self.ptr } + } +} + +impl Copy for StaticRef {} + +impl Deref for StaticRef { + type Target = T; + fn deref(&self) -> &'static T { + unsafe { &*self.ptr } + } +} diff --git a/docs/customization.md b/docs/customization.md index be3a6bc..6d6b82c 100644 --- a/docs/customization.md +++ b/docs/customization.md @@ -64,3 +64,32 @@ a few things you can personalize: * Whether you want to use batch attestation. * Whether you want to use signature counters. * Various constants to adapt to different hardware. + +### Testing and Fuzzing + +You might want to test your changes before deploying them. To run unit tests, +make sure that at least the `std` feature is included, e.g.: + +```shell +cargo test --features=std,with_ctap1 +``` + +Alternatively, you can simply call our test script to also test all libraries, +run clippy, check formatting and more: + +```shell +./run_desktop_tests.sh +``` + +OpenSK is fuzzed with the [OSS-Fuzz](https://github.com/google/oss-fuzz) +project. You can also run fuzzing locally. First install: + +```shell +cargo +stable install cargo-fuzz --version 0.10.2 +``` + +Then choose a fuzz target from `fuzz/fuzz_targets/`, e.g.: + +```shell +cargo fuzz run fuzz_target_process_ctap1 +``` diff --git a/examples/erase_storage.rs b/examples/erase_storage.rs index 29ba5ea..0c8791f 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::env::tock::steal_storage; +use ctap2::env::tock::take_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 = unsafe { steal_storage() }.unwrap(); + let mut storage = take_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 27f2af7..0d84979 100644 --- a/examples/store_latency.rs +++ b/examples/store_latency.rs @@ -21,8 +21,7 @@ use alloc::string::{String, ToString}; use alloc::vec::Vec; use alloc::{format, vec}; use core::fmt::Write; -use ctap2::env::tock::{steal_storage, TockEnv}; -use ctap2::env::Env; +use ctap2::env::tock::{take_storage, TockStorage}; use libtock_drivers::console::Console; use libtock_drivers::timer::{self, Duration, Timer, Timestamp}; use persistent_store::Store; @@ -40,10 +39,8 @@ fn measure(timer: &Timer, operation: impl FnOnce() -> T) -> (T, Duration (result, after - before) } -// Only use one store at a time. -unsafe fn boot_store(erase: bool) -> Store<::Storage> { +fn boot_store(mut storage: TockStorage, erase: bool) -> Store { use persistent_store::Storage; - let mut storage = steal_storage().unwrap(); let num_pages = storage.num_pages(); if erase { for page in 0..num_pages { @@ -58,9 +55,8 @@ struct StorageConfig { num_pages: usize, } -fn storage_config() -> StorageConfig { +fn storage_config(storage: &TockStorage) -> StorageConfig { use persistent_store::Storage; - let storage = unsafe { steal_storage() }.unwrap(); StorageConfig { num_pages: storage.num_pages(), } @@ -77,11 +73,12 @@ struct Stat { } fn compute_latency( + storage: TockStorage, timer: &Timer, num_pages: usize, key_increment: usize, word_length: usize, -) -> Stat { +) -> (TockStorage, Stat) { let mut stat = Stat { key_increment, entry_length: word_length, @@ -96,7 +93,7 @@ fn compute_latency( ) .unwrap(); - let mut store = unsafe { boot_store(true) }; + let mut store = boot_store(storage, true); let total_capacity = store.capacity().unwrap().total(); assert_eq!(store.capacity().unwrap().used(), 0); assert_eq!(store.lifetime().unwrap().used(), 0); @@ -130,7 +127,8 @@ fn compute_latency( ); // Measure latency of boot. - let (mut store, time) = measure(timer, || unsafe { boot_store(false) }); + let storage = store.extract_storage(); + let (mut store, time) = measure(timer, || boot_store(storage, false)); writeln!(console, "Boot: {:.1}ms.", time.ms()).unwrap(); stat.boot_ms = time.ms(); @@ -153,20 +151,23 @@ fn compute_latency( stat.compaction_ms = time.ms(); assert!(store.lifetime().unwrap().used() > total_capacity + num_pages); - stat + (store.extract_storage(), stat) } fn main() { let mut with_callback = timer::with_callback(|_, _| {}); let timer = with_callback.init().ok().unwrap(); - let config = storage_config(); + let storage = take_storage().unwrap(); + let config = storage_config(&storage); let mut stats = Vec::new(); writeln!(Console::new(), "\nRunning 2 tests...").unwrap(); // Simulate a store full of credentials (of 50 words). - stats.push(compute_latency(&timer, config.num_pages, 1, 50)); + let (storage, stat) = compute_latency(storage, &timer, config.num_pages, 1, 50); + stats.push(stat); // Simulate a store full of increments of a single counter. - stats.push(compute_latency(&timer, config.num_pages, 0, 1)); + let (_storage, stat) = compute_latency(storage, &timer, config.num_pages, 0, 1); + stats.push(stat); writeln!(Console::new(), "\nDone.\n").unwrap(); const HEADERS: &[&str] = &[ diff --git a/fuzz/fuzz_helper/src/lib.rs b/fuzz/fuzz_helper/src/lib.rs index 647749e..7a0d300 100644 --- a/fuzz/fuzz_helper/src/lib.rs +++ b/fuzz/fuzz_helper/src/lib.rs @@ -25,14 +25,12 @@ use ctap2::ctap::command::{ }; use ctap2::ctap::hid::receive::MessageAssembler; use ctap2::ctap::hid::send::HidPacketIterator; -use ctap2::ctap::hid::{ChannelID, HidPacket, Message}; +use ctap2::ctap::hid::{ChannelID, CtapHidCommand, HidPacket, Message}; use ctap2::env::test::TestEnv; use ctap2::Ctap; use libtock_drivers::timer::{ClockValue, Timestamp}; -const COMMAND_INIT: u8 = 0x06; const CHANNEL_BROADCAST: ChannelID = [0xFF, 0xFF, 0xFF, 0xFF]; -const PACKET_TYPE_MASK: u8 = 0x80; const CLOCK_FREQUENCY_HZ: usize = 32768; const DUMMY_TIMESTAMP: Timestamp = Timestamp::from_ms(0); @@ -53,13 +51,14 @@ fn raw_to_message(data: &[u8]) -> Message { cid[..data.len()].copy_from_slice(data); Message { cid, - cmd: 0, + // Arbitrary command. + cmd: CtapHidCommand::Cbor, payload: vec![], } } else { Message { cid: array_ref!(data, 0, 4).clone(), - cmd: data[4], + cmd: CtapHidCommand::from(data[4]), payload: data[5..].to_vec(), } } @@ -71,7 +70,7 @@ fn initialize(ctap: &mut Ctap) -> ChannelID { let nonce = vec![0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0]; let message = Message { cid: CHANNEL_BROADCAST, - cmd: COMMAND_INIT, + cmd: CtapHidCommand::Init, payload: nonce, }; let mut assembler_reply = MessageAssembler::new(); @@ -168,7 +167,7 @@ pub fn process_ctap_specific_type(data: &[u8], input_type: InputType) { // Splits the given data as HID packets and reassembles it, verifying that the original input message is reconstructed. pub fn split_assemble_hid_packets(data: &[u8]) { - let mut message = raw_to_message(data); + let message = raw_to_message(data); if let Some(hid_packet_iterator) = HidPacketIterator::new(message.clone()) { let mut assembler = MessageAssembler::new(); let packets: Vec = hid_packet_iterator.collect(); @@ -176,7 +175,6 @@ pub fn split_assemble_hid_packets(data: &[u8]) { for packet in first_packets { assert_eq!(assembler.parse_packet(packet, DUMMY_TIMESTAMP), Ok(None)); } - message.cmd &= !PACKET_TYPE_MASK; assert_eq!( assembler.parse_packet(last_packet, DUMMY_TIMESTAMP), Ok(Some(message)) diff --git a/libraries/persistent_store/src/store.rs b/libraries/persistent_store/src/store.rs index d1e50bd..674bc06 100644 --- a/libraries/persistent_store/src/store.rs +++ b/libraries/persistent_store/src/store.rs @@ -238,6 +238,11 @@ impl Store { Ok(store) } + /// Extracts the storage. + pub fn extract_storage(self) -> S { + self.storage + } + /// Iterates over the entries. pub fn iter<'a>(&'a self) -> StoreResult> { let head = or_invalid(self.head)?; @@ -1162,11 +1167,6 @@ impl Store { &mut self.storage } - /// Extracts the storage. - pub fn extract_storage(self) -> BufferStorage { - self.storage - } - /// Returns the value of a possibly deleted entry. /// /// If the value has been partially compacted, only return the non-compacted part. Returns an diff --git a/src/ctap/client_pin.rs b/src/ctap/client_pin.rs index 2cac1e0..ce3c24f 100644 --- a/src/ctap/client_pin.rs +++ b/src/ctap/client_pin.rs @@ -19,8 +19,8 @@ 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; @@ -76,13 +76,13 @@ fn decrypt_pin( /// The new PIN is passed encrypted, so it is first decrypted and stripped from /// 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, +fn check_and_store_new_pin( + 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); @@ -90,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(()) } @@ -156,28 +156,27 @@ impl ClientPin { /// Decrypts the encrypted pin_hash and compares it to the stored pin_hash. /// Resets or decreases the PIN retries, depending on success or failure. /// Also, in case of failure, the key agreement key is randomly reset. - fn verify_pin_hash_enc( + 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; @@ -190,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( + 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), }) } @@ -223,9 +222,9 @@ impl ClientPin { }) } - fn process_set_pin( + fn process_set_pin( &mut self, - persistent_store: &mut PersistentStore, + env: &mut impl Env, client_pin_params: AuthenticatorClientPinParameters, ) -> Result<(), Ctap2StatusCode> { let AuthenticatorClientPinParameters { @@ -239,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( + 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 { @@ -269,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)?; @@ -277,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( + 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 { @@ -311,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(), )?; @@ -360,10 +355,9 @@ impl ClientPin { Err(Ctap2StatusCode::CTAP2_ERR_INVALID_SUBCOMMAND) } - fn process_get_pin_uv_auth_token_using_pin_with_permissions( + 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 { @@ -381,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); @@ -390,30 +384,27 @@ impl ClientPin { } /// Processes the authenticatorClientPin command. - pub fn process_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)?, @@ -421,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, )?, @@ -602,12 +592,12 @@ mod test { 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. @@ -733,7 +723,6 @@ mod test { fn test_helper_verify_pin_hash_enc(pin_uv_auth_protocol: PinUvAuthProtocol) { let mut env = TestEnv::new(); - let mut persistent_store = PersistentStore::new(&mut env); 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 @@ -744,7 +733,7 @@ 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() @@ -752,8 +741,7 @@ mod test { .unwrap(); assert_eq!( client_pin.verify_pin_hash_enc( - env.rng(), - &mut persistent_store, + &mut env, pin_uv_auth_protocol, shared_secret.as_ref(), pin_hash_enc @@ -764,8 +752,7 @@ mod test { let pin_hash_enc = vec![0xEE; 16]; assert_eq!( client_pin.verify_pin_hash_enc( - env.rng(), - &mut persistent_store, + &mut env, pin_uv_auth_protocol, shared_secret.as_ref(), pin_hash_enc @@ -780,8 +767,7 @@ mod test { client_pin.consecutive_pin_mismatches = 3; assert_eq!( client_pin.verify_pin_hash_enc( - env.rng(), - &mut persistent_store, + &mut env, pin_uv_auth_protocol, shared_secret.as_ref(), pin_hash_enc @@ -793,8 +779,7 @@ mod test { let pin_hash_enc = vec![0x77; PIN_AUTH_LENGTH - 1]; assert_eq!( client_pin.verify_pin_hash_enc( - env.rng(), - &mut persistent_store, + &mut env, pin_uv_auth_protocol, shared_secret.as_ref(), pin_hash_enc @@ -805,8 +790,7 @@ mod test { let pin_hash_enc = vec![0x77; PIN_AUTH_LENGTH + 1]; assert_eq!( client_pin.verify_pin_hash_enc( - env.rng(), - &mut persistent_store, + &mut env, pin_uv_auth_protocol, shared_secret.as_ref(), pin_hash_enc @@ -831,20 +815,14 @@ mod test { ClientPinSubCommand::GetPinRetries, ); let mut env = TestEnv::new(); - let mut persistent_store = PersistentStore::new(&mut env); 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( - env.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)) ); @@ -852,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(env.rng(), &mut persistent_store, params, DUMMY_CLOCK_VALUE), + client_pin.process_command(&mut env, params, DUMMY_CLOCK_VALUE), Ok(ResponseData::AuthenticatorClientPin(expected_response)) ); } @@ -877,7 +855,6 @@ mod test { ClientPinSubCommand::GetKeyAgreement, ); let mut env = TestEnv::new(); - let mut persistent_store = PersistentStore::new(&mut env); let expected_response = Some(AuthenticatorClientPinResponse { key_agreement: params.key_agreement.clone(), pin_uv_auth_token: None, @@ -885,7 +862,7 @@ mod test { power_cycle_state: None, }); assert_eq!( - client_pin.process_command(env.rng(), &mut persistent_store, params, DUMMY_CLOCK_VALUE), + client_pin.process_command(&mut env, params, DUMMY_CLOCK_VALUE), Ok(ResponseData::AuthenticatorClientPin(expected_response)) ); } @@ -904,9 +881,8 @@ mod test { let (mut client_pin, params) = create_client_pin_and_parameters(pin_uv_auth_protocol, ClientPinSubCommand::SetPin); let mut env = TestEnv::new(); - let mut persistent_store = PersistentStore::new(&mut env); assert_eq!( - client_pin.process_command(env.rng(), &mut persistent_store, params, DUMMY_CLOCK_VALUE), + client_pin.process_command(&mut env, params, DUMMY_CLOCK_VALUE), Ok(ResponseData::AuthenticatorClientPin(None)) ); } @@ -932,40 +908,29 @@ mod test { ) .unwrap(); let mut env = TestEnv::new(); - let mut persistent_store = PersistentStore::new(&mut env); - set_standard_pin(&mut persistent_store); + 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( - env.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( - env.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(env.rng(), &mut persistent_store, params, DUMMY_CLOCK_VALUE), + client_pin.process_command(&mut env, params, DUMMY_CLOCK_VALUE), Err(Ctap2StatusCode::CTAP2_ERR_PIN_BLOCKED) ); } @@ -993,16 +958,10 @@ mod test { ) .unwrap(); let mut env = TestEnv::new(); - let mut persistent_store = PersistentStore::new(&mut env); - set_standard_pin(&mut persistent_store); + set_standard_pin(&mut env); let response = client_pin - .process_command( - env.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)) => { @@ -1038,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( - env.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) ); } @@ -1064,12 +1018,11 @@ mod test { ClientPinSubCommand::GetPinToken, ); let mut env = TestEnv::new(); - let mut persistent_store = PersistentStore::new(&mut env); - set_standard_pin(&mut persistent_store); + 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(env.rng(), &mut persistent_store, params, DUMMY_CLOCK_VALUE), + client_pin.process_command(&mut env, params, DUMMY_CLOCK_VALUE), Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID), ); } @@ -1099,16 +1052,10 @@ mod test { ) .unwrap(); let mut env = TestEnv::new(); - let mut persistent_store = PersistentStore::new(&mut env); - set_standard_pin(&mut persistent_store); + set_standard_pin(&mut env); let response = client_pin - .process_command( - env.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)) => { @@ -1144,36 +1091,21 @@ mod test { let mut bad_params = params.clone(); bad_params.permissions = Some(0x00); assert_eq!( - client_pin.process_command( - env.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( - env.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( - env.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) ); } @@ -1196,12 +1128,11 @@ mod test { ClientPinSubCommand::GetPinUvAuthTokenUsingPinWithPermissions, ); let mut env = TestEnv::new(); - let mut persistent_store = PersistentStore::new(&mut env); - set_standard_pin(&mut persistent_store); + 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(env.rng(), &mut persistent_store, params, DUMMY_CLOCK_VALUE), + client_pin.process_command(&mut env, params, DUMMY_CLOCK_VALUE), Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID) ); } @@ -1266,7 +1197,6 @@ mod test { fn test_helper_check_and_store_new_pin(pin_uv_auth_protocol: PinUvAuthProtocol) { let mut env = TestEnv::new(); - let mut persistent_store = PersistentStore::new(&mut env); let pin_protocol = PinProtocol::new(env.rng()); let shared_secret = pin_protocol .decapsulate(pin_protocol.get_public_key(), pin_uv_auth_protocol) @@ -1292,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()); } } } @@ -1683,12 +1613,11 @@ mod test { ClientPinSubCommand::GetPinUvAuthTokenUsingPinWithPermissions, ); let mut env = TestEnv::new(); - let mut persistent_store = PersistentStore::new(&mut env); - set_standard_pin(&mut persistent_store); + set_standard_pin(&mut env); params.permissions = Some(0xFF); assert!(client_pin - .process_command(env.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!( @@ -1730,12 +1659,11 @@ mod test { ClientPinSubCommand::GetPinUvAuthTokenUsingPinWithPermissions, ); let mut env = TestEnv::new(); - let mut persistent_store = PersistentStore::new(&mut env); - set_standard_pin(&mut persistent_store); + set_standard_pin(&mut env); params.permissions = Some(0xFF); assert!(client_pin - .process_command(env.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 e02fbbb..6cb1948 100644 --- a/src/ctap/config_command.rs +++ b/src/ctap/config_command.rs @@ -18,16 +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, +fn process_enable_enterprise_attestation( + 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) @@ -35,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, +fn process_set_min_pin_length( + env: &mut impl Env, params: SetMinPinLengthParams, ) -> Result { let SetMinPinLengthParams { @@ -52,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, +pub fn process_config( + env: &mut impl Env, client_pin: &mut ClientPin, params: AuthenticatorConfigParameters, ) -> Result { @@ -87,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 = @@ -109,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) } @@ -135,7 +131,6 @@ mod test { #[test] fn test_process_enable_enterprise_attestation() { let mut env = TestEnv::new(); - let mut persistent_store = PersistentStore::new(&mut env); let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); let pin_uv_auth_token = [0x55; 32]; let mut client_pin = @@ -147,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, @@ -163,7 +158,6 @@ mod test { #[test] fn test_process_toggle_always_uv() { let mut env = TestEnv::new(); - let mut persistent_store = PersistentStore::new(&mut env); let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); let pin_uv_auth_token = [0x55; 32]; let mut client_pin = @@ -175,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, @@ -185,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, @@ -193,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 env = TestEnv::new(); - let mut persistent_store = PersistentStore::new(&mut env); 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]); @@ -216,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, @@ -225,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, @@ -233,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] @@ -270,7 +263,6 @@ mod test { #[test] fn test_process_set_min_pin_length() { let mut env = TestEnv::new(); - let mut persistent_store = PersistentStore::new(&mut env); let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); let pin_uv_auth_token = [0x55; 32]; let mut client_pin = @@ -279,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![ @@ -293,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); @@ -304,18 +296,17 @@ 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 env = TestEnv::new(); - let mut persistent_store = PersistentStore::new(&mut env); let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); let pin_uv_auth_token = [0x55; 32]; let mut client_pin = @@ -326,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) ); @@ -338,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![ @@ -346,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()) ); @@ -359,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()) ); @@ -377,14 +368,14 @@ 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) ); } @@ -392,13 +383,12 @@ mod test { #[test] fn test_process_set_min_pin_length_force_pin_change_implicit() { let mut env = TestEnv::new(); - let mut persistent_store = PersistentStore::new(&mut env); 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); @@ -407,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 env = TestEnv::new(); - let mut persistent_store = PersistentStore::new(&mut env); 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), }; @@ -440,15 +429,14 @@ 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 env = TestEnv::new(); - let mut persistent_store = PersistentStore::new(&mut env); let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); let pin_uv_auth_token = [0x55; 32]; let mut client_pin = @@ -460,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 f5e78a7..c5952b2 100644 --- a/src/ctap/credential_management.rs +++ b/src/ctap/credential_management.rs @@ -21,8 +21,8 @@ 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; @@ -33,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,8 +107,8 @@ fn enumerate_credentials_response( /// Check if the token permissions have the correct associated RP ID. /// /// Either no RP ID is associated, or the RP ID matches the stored credential. -fn check_rp_id_permissions( - persistent_store: &mut PersistentStore, +fn check_rp_id_permissions( + env: &mut impl Env, client_pin: &mut ClientPin, credential_id: &[u8], ) -> Result<(), Ctap2StatusCode> { @@ -118,30 +116,30 @@ 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, +fn process_get_creds_metadata( + 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() }) } /// Processes the subcommand enumerateRPsBegin for CredentialManagement. -fn process_enumerate_rps_begin( - persistent_store: &PersistentStore, +fn process_enumerate_rps_begin( + 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,12 +154,12 @@ fn process_enumerate_rps_begin( } /// Processes the subcommand enumerateRPsGetNextRP for CredentialManagement. -fn process_enumerate_rps_get_next_rp( - persistent_store: &PersistentStore, +fn process_enumerate_rps_get_next_rp( + 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,8 +169,8 @@ fn process_enumerate_rps_get_next_rp( } /// Processes the subcommand enumerateCredentialsBegin for CredentialManagement. -fn process_enumerate_credentials_begin( - persistent_store: &PersistentStore, +fn process_enumerate_credentials_begin( + env: &mut impl Env, stateful_command_permission: &mut StatefulPermission, client_pin: &mut ClientPin, sub_command_params: CredentialManagementSubCommandParameters, @@ -183,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()); @@ -199,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,18 +206,18 @@ fn process_enumerate_credentials_begin( } /// Processes the subcommand enumerateCredentialsGetNextCredential for CredentialManagement. -fn process_enumerate_credentials_get_next_credential( - persistent_store: &PersistentStore, +fn process_enumerate_credentials_get_next_credential( + 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, +fn process_delete_credential( + env: &mut impl Env, client_pin: &mut ClientPin, sub_command_params: CredentialManagementSubCommandParameters, ) -> Result<(), Ctap2StatusCode> { @@ -227,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, +fn process_update_user_information( + env: &mut impl Env, client_pin: &mut ClientPin, sub_command_params: CredentialManagementSubCommandParameters, ) -> Result<(), Ctap2StatusCode> { @@ -244,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, +pub fn process_credential_management( + env: &mut impl Env, stateful_command_permission: &mut StatefulPermission, client_pin: &mut ClientPin, cred_management_params: AuthenticatorCredentialManagementParameters, @@ -306,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)?, )?; @@ -344,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)?, )?; @@ -396,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, @@ -411,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, @@ -427,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, @@ -439,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, @@ -481,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, @@ -503,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, @@ -527,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, @@ -552,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, @@ -580,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, @@ -604,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, @@ -634,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, @@ -663,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, @@ -692,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, @@ -715,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, @@ -739,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, @@ -764,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, @@ -792,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, @@ -810,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, @@ -835,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, @@ -869,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, @@ -880,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!( @@ -899,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, @@ -908,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 ff39bf8..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; @@ -178,14 +179,14 @@ impl Ctap1Command { const VENDOR_SPECIFIC_FIRST: u8 = 0x40; const VENDOR_SPECIFIC_LAST: u8 = 0xBF; - pub fn process_command( - env: &mut E, + pub fn process_command( + env: &mut impl Env, message: &[u8], - ctap_state: &mut CtapState, + ctap_state: &mut CtapState, 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); @@ -243,11 +244,11 @@ impl Ctap1Command { // +------+-------------------+-----------------+------------+--------------------+ // + 0x00 | application (32B) | challenge (32B) | key handle | User pub key (65B) | // +------+-------------------+-----------------+------------+--------------------+ - fn process_register( - env: &mut E, + fn process_register( + env: &mut impl Env, challenge: [u8; 32], application: [u8; 32], - ctap_state: &mut CtapState, + ctap_state: &mut CtapState, ) -> Result, Ctap1StatusCode> { let sk = crypto::ecdsa::SecKey::gensk(env.rng()); let pk = sk.genpk(); @@ -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)?; @@ -307,16 +304,16 @@ impl Ctap1Command { // +-------------------+---------+--------------+-----------------+ // + application (32B) | UP (1B) | Counter (4B) | challenge (32B) | // +-------------------+---------+--------------+-----------------+ - fn process_authenticate( - env: &mut E, + fn process_authenticate( + env: &mut impl Env, challenge: [u8; 32], application: [u8; 32], key_handle: Vec, flags: Ctap1Flags, - ctap_state: &mut CtapState, + 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 bf6f799..f3fa288 100644 --- a/src/ctap/hid/mod.rs +++ b/src/ctap/hid/mod.rs @@ -32,13 +32,50 @@ use core::fmt::Write; use libtock_drivers::console::Console; use libtock_drivers::timer::{ClockValue, Duration, Timestamp}; -// CTAP specification (version 20190130) section 8.1 -// TODO: Channel allocation, section 8.1.3? -// TODO: Transaction timeout, section 8.1.5.2 - pub type HidPacket = [u8; 64]; pub type ChannelID = [u8; 4]; +/// CTAPHID commands +/// +/// See section 11.2.9. of FIDO 2.1 (2021-06-15). +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum CtapHidCommand { + Ping = 0x01, + Msg = 0x03, + // Lock is optional and may be used in the future. + Lock = 0x04, + Init = 0x06, + Wink = 0x08, + Cbor = 0x10, + Cancel = 0x11, + Keepalive = 0x3B, + Error = 0x3F, + // VendorFirst and VendorLast describe a range, and are not commands themselves. + _VendorFirst = 0x40, + _VendorLast = 0x7F, +} + +impl From for CtapHidCommand { + fn from(cmd: u8) -> Self { + match cmd { + x if x == CtapHidCommand::Ping as u8 => CtapHidCommand::Ping, + x if x == CtapHidCommand::Msg as u8 => CtapHidCommand::Msg, + x if x == CtapHidCommand::Lock as u8 => CtapHidCommand::Lock, + x if x == CtapHidCommand::Init as u8 => CtapHidCommand::Init, + x if x == CtapHidCommand::Wink as u8 => CtapHidCommand::Wink, + x if x == CtapHidCommand::Cbor as u8 => CtapHidCommand::Cbor, + x if x == CtapHidCommand::Cancel as u8 => CtapHidCommand::Cancel, + x if x == CtapHidCommand::Keepalive as u8 => CtapHidCommand::Keepalive, + // This includes the actual error code 0x3F. Error is not used for incoming packets in + // the specification, so we can safely reuse it for unknown bytes. + _ => CtapHidCommand::Error, + } + } +} + +/// Describes the structure of a parsed HID packet. +/// +/// A packet is either an Init or a Continuation packet. pub enum ProcessedPacket<'a> { InitPacket { cmd: u8, @@ -51,72 +88,79 @@ pub enum ProcessedPacket<'a> { }, } -// An assembled CTAPHID command. +/// An assembled CTAPHID command. #[derive(Clone, Debug, PartialEq, Eq)] pub struct Message { // Channel ID. pub cid: ChannelID, // Command. - pub cmd: u8, + pub cmd: CtapHidCommand, // Bytes of the message. pub payload: Vec, } +/// A keepalive packet reports the reason why a command does not finish. +#[allow(dead_code)] +pub enum KeepaliveStatus { + Processing = 0x01, + UpNeeded = 0x02, +} + +/// Holds all state for receiving and sending HID packets. +/// +/// This includes +/// - state from not fully processed messages, +/// - all allocated channels, +/// - information about requested winks. +/// +/// The wink information can be polled to decide to i.e. blink LEDs. +/// +/// To process a packet and receive the response, you can call `process_hid_packet`. +/// If you want more control, you can also do the processing in steps: +/// +/// 1. `HidPacket` -> `Option` +/// 2. `Option` -> `Message` +/// 3. `Message` -> `Message` +/// 4. `Message` -> `HidPacketIterator` +/// +/// These steps correspond to: +/// +/// 1. `parse_packet` assembles the message and preprocesses all pure HID commands and errors. +/// 2. If you didn't receive any message or preprocessing discarded it, stop. +/// 3. `process_message` handles all protocol interactions. +/// 4. `split_message` creates packets out of the response message. pub struct CtapHid { assembler: MessageAssembler, - // The specification (version 20190130) only requires unique CIDs ; the allocation algorithm is - // vendor specific. + // The specification only requires unique CIDs, the allocation algorithm is vendor specific. // We allocate them incrementally, that is all `cid` such that 1 <= cid <= allocated_cids are // allocated. // In packets, the ID encoding is Big Endian to match what is used throughout CTAP (with the // u32::to/from_be_bytes methods). + // TODO(kaczmarczyck) We might want to limit or timeout open channels. allocated_cids: usize, pub(crate) wink_permission: TimedPermission, } -#[allow(dead_code)] -pub enum KeepaliveStatus { - Processing, - UpNeeded, -} - -#[allow(dead_code)] -// TODO(kaczmarczyck) disable the warning in the end impl CtapHid { - // CTAP specification (version 20190130) section 8.1.3 + // We implement CTAP 2.1 from 2021-06-15. Please see section + // 11.2. USB Human Interface Device (USB HID) const CHANNEL_RESERVED: ChannelID = [0, 0, 0, 0]; const CHANNEL_BROADCAST: ChannelID = [0xFF, 0xFF, 0xFF, 0xFF]; const TYPE_INIT_BIT: u8 = 0x80; const PACKET_TYPE_MASK: u8 = 0x80; - // CTAP specification (version 20190130) section 8.1.9 - const COMMAND_PING: u8 = 0x01; - const COMMAND_MSG: u8 = 0x03; - const COMMAND_INIT: u8 = 0x06; - const COMMAND_CBOR: u8 = 0x10; - pub const COMMAND_CANCEL: u8 = 0x11; - const COMMAND_KEEPALIVE: u8 = 0x3B; - const COMMAND_ERROR: u8 = 0x3F; - // TODO: optional lock command - const COMMAND_LOCK: u8 = 0x04; - const COMMAND_WINK: u8 = 0x08; - const COMMAND_VENDOR_FIRST: u8 = 0x40; - const COMMAND_VENDOR_LAST: u8 = 0x7F; - - // CTAP specification (version 20190130) section 8.1.9.1.6 const ERR_INVALID_CMD: u8 = 0x01; - const ERR_INVALID_PAR: u8 = 0x02; + const _ERR_INVALID_PAR: u8 = 0x02; const ERR_INVALID_LEN: u8 = 0x03; const ERR_INVALID_SEQ: u8 = 0x04; const ERR_MSG_TIMEOUT: u8 = 0x05; const ERR_CHANNEL_BUSY: u8 = 0x06; - const ERR_LOCK_REQUIRED: u8 = 0x0A; + const _ERR_LOCK_REQUIRED: u8 = 0x0A; const ERR_INVALID_CHANNEL: u8 = 0x0B; - const ERR_OTHER: u8 = 0x7F; + const _ERR_OTHER: u8 = 0x7F; - // CTAP specification (version 20190130) section 8.1.9.1.3 + // See section 11.2.9.1.3. CTAPHID_INIT (0x06). const PROTOCOL_VERSION: u8 = 2; - // The device version number is vendor-defined. const DEVICE_VERSION_MAJOR: u8 = 1; const DEVICE_VERSION_MINOR: u8 = 0; @@ -124,6 +168,7 @@ impl CtapHid { const CAPABILITY_WINK: u8 = 0x01; const CAPABILITY_CBOR: u8 = 0x04; + #[cfg(not(feature = "with_ctap1"))] const CAPABILITY_NMSG: u8 = 0x08; // Capabilitites currently supported by this device. #[cfg(feature = "with_ctap1")] @@ -136,6 +181,7 @@ impl CtapHid { const TIMEOUT_DURATION: Duration = Duration::from_ms(100); const WINK_TIMEOUT_DURATION: Duration = Duration::from_ms(5000); + /// Creates a new idle HID state. pub fn new() -> CtapHid { CtapHid { assembler: MessageAssembler::new(), @@ -144,16 +190,26 @@ impl CtapHid { } } - // Process an incoming USB HID packet, and optionally returns a list of outgoing packets to - // send as a reply. - pub fn process_hid_packet( - &mut self, - env: &mut E, - packet: &HidPacket, - clock_value: ClockValue, - ctap_state: &mut CtapState, - ) -> HidPacketIterator { - // TODO: Send COMMAND_KEEPALIVE every 100ms? + /// Parses a packet, and preprocesses some messages and errors. + /// + /// The preprocessed commands are: + /// - INIT + /// - CANCEL + /// - ERROR + /// - Unknown and unexpected commands like KEEPALIVE + /// - LOCK is not implemented and currently treated like an unknown command + /// + /// Commands that may still be processed: + /// - PING + /// - MSG + /// - WINK + /// - CBOR + /// + /// You may ignore PING, it's behaving correctly by default (input == output). + /// Ignoring the others is incorrect behavior. You have to at least replace them with an error + /// message: + /// `CtapHid::error_message(message.cid, CtapHid::ERR_INVALID_CMD)` + pub fn parse_packet(&mut self, packet: &HidPacket, clock_value: ClockValue) -> Option { match self .assembler .parse_packet(packet, Timestamp::::from_clock_value(clock_value)) @@ -161,170 +217,42 @@ impl CtapHid { Ok(Some(message)) => { #[cfg(feature = "debug_ctap")] writeln!(&mut Console::new(), "Received message: {:02x?}", message).unwrap(); - - let cid = message.cid; - if !self.has_valid_channel(&message) { - #[cfg(feature = "debug_ctap")] - writeln!(&mut Console::new(), "Invalid channel: {:02x?}", cid).unwrap(); - return CtapHid::error_message(cid, CtapHid::ERR_INVALID_CHANNEL); - } - // If another command arrives, stop winking to prevent accidential button touches. - self.wink_permission = TimedPermission::waiting(); - - match message.cmd { - // CTAP specification (version 20190130) section 8.1.9.1.1 - CtapHid::COMMAND_MSG => { - // If we don't have CTAP1 backward compatibilty, this command is invalid. - #[cfg(not(feature = "with_ctap1"))] - return CtapHid::error_message(cid, CtapHid::ERR_INVALID_CMD); - - #[cfg(feature = "with_ctap1")] - match ctap1::Ctap1Command::process_command( - env, - &message.payload, - ctap_state, - clock_value, - ) { - Ok(payload) => CtapHid::ctap1_success_message(cid, &payload), - Err(ctap1_status_code) => { - CtapHid::ctap1_error_message(cid, ctap1_status_code) - } - } - } - // CTAP specification (version 20190130) section 8.1.9.1.2 - CtapHid::COMMAND_CBOR => { - // CTAP specification (version 20190130) section 8.1.5.1 - // Each transaction is atomic, so we process the command directly here and - // don't handle any other packet in the meantime. - // TODO: Send keep-alive packets in the meantime. - let response = - ctap_state.process_command(env, &message.payload, cid, clock_value); - if let Some(iterator) = CtapHid::split_message(Message { - cid, - cmd: CtapHid::COMMAND_CBOR, - payload: response, - }) { - iterator - } else { - // Handle the case of a payload > 7609 bytes. - // Although this shouldn't happen if the FIDO2 commands are implemented - // correctly, we reply with a vendor specific code instead of silently - // ignoring the error. - // - // The error payload that we send instead is 1 <= 7609 bytes, so it is - // safe to unwrap() the result. - CtapHid::split_message(Message { - cid, - cmd: CtapHid::COMMAND_CBOR, - payload: vec![ - Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR as u8, - ], - }) - .unwrap() - } - } - // CTAP specification (version 20190130) section 8.1.9.1.3 - CtapHid::COMMAND_INIT => { - if message.payload.len() != 8 { - return CtapHid::error_message(cid, CtapHid::ERR_INVALID_LEN); - } - - let new_cid = if cid == CtapHid::CHANNEL_BROADCAST { - // TODO: Prevent allocating 2^32 channels. - self.allocated_cids += 1; - (self.allocated_cids as u32).to_be_bytes() - } else { - // Sync the channel and discard the current transaction. - cid - }; - - let mut payload = vec![0; 17]; - payload[..8].copy_from_slice(&message.payload); - payload[8..12].copy_from_slice(&new_cid); - payload[12] = CtapHid::PROTOCOL_VERSION; - payload[13] = CtapHid::DEVICE_VERSION_MAJOR; - payload[14] = CtapHid::DEVICE_VERSION_MINOR; - payload[15] = CtapHid::DEVICE_VERSION_BUILD; - payload[16] = CtapHid::CAPABILITIES; - - // This unwrap is safe because the payload length is 17 <= 7609 bytes. - CtapHid::split_message(Message { - cid, - cmd: CtapHid::COMMAND_INIT, - payload, - }) - .unwrap() - } - // CTAP specification (version 20190130) section 8.1.9.1.4 - CtapHid::COMMAND_PING => { - // Pong the same message. - // This unwrap is safe because if we could parse the incoming message, it's - // payload length must be <= 7609 bytes. - CtapHid::split_message(message).unwrap() - } - // CTAP specification (version 20190130) section 8.1.9.1.5 - CtapHid::COMMAND_CANCEL => { - // Authenticators MUST NOT reply to this message. - // CANCEL is handled during user presence checks in main. - HidPacketIterator::none() - } - // Optional commands - // CTAP specification (version 20190130) section 8.1.9.2.1 - CtapHid::COMMAND_WINK => { - if !message.payload.is_empty() { - return CtapHid::error_message(cid, CtapHid::ERR_INVALID_LEN); - } - self.wink_permission = - TimedPermission::granted(clock_value, CtapHid::WINK_TIMEOUT_DURATION); - CtapHid::split_message(Message { - cid, - cmd: CtapHid::COMMAND_WINK, - payload: vec![], - }) - .unwrap() - } - // CTAP specification (version 20190130) section 8.1.9.2.2 - // TODO: implement LOCK - _ => { - // Unknown or unsupported command. - CtapHid::error_message(cid, CtapHid::ERR_INVALID_CMD) - } - } + self.preprocess_message(message) } Ok(None) => { // Waiting for more packets to assemble the message, nothing to send for now. - HidPacketIterator::none() + None } Err((cid, error)) => { if !self.is_allocated_channel(cid) && error != receive::Error::UnexpectedContinuation { - CtapHid::error_message(cid, CtapHid::ERR_INVALID_CHANNEL) + Some(CtapHid::error_message(cid, CtapHid::ERR_INVALID_CHANNEL)) } else { match error { receive::Error::UnexpectedChannel => { - CtapHid::error_message(cid, CtapHid::ERR_CHANNEL_BUSY) + Some(CtapHid::error_message(cid, CtapHid::ERR_CHANNEL_BUSY)) } receive::Error::UnexpectedInit => { // TODO: Should we send another error code in this case? // Technically, we were expecting a sequence number and got another // byte, although the command/seqnum bit has higher-level semantics // than sequence numbers. - CtapHid::error_message(cid, CtapHid::ERR_INVALID_SEQ) + Some(CtapHid::error_message(cid, CtapHid::ERR_INVALID_SEQ)) } receive::Error::UnexpectedContinuation => { // CTAP specification (version 20190130) section 8.1.5.4 // Spurious continuation packets will be ignored. - HidPacketIterator::none() + None } receive::Error::UnexpectedSeq => { - CtapHid::error_message(cid, CtapHid::ERR_INVALID_SEQ) + Some(CtapHid::error_message(cid, CtapHid::ERR_INVALID_SEQ)) } receive::Error::UnexpectedLen => { - CtapHid::error_message(cid, CtapHid::ERR_INVALID_LEN) + Some(CtapHid::error_message(cid, CtapHid::ERR_INVALID_LEN)) } receive::Error::Timeout => { - CtapHid::error_message(cid, CtapHid::ERR_MSG_TIMEOUT) + Some(CtapHid::error_message(cid, CtapHid::ERR_MSG_TIMEOUT)) } } } @@ -332,10 +260,157 @@ impl CtapHid { } } + /// Processes HID-only commands of a message and returns an outgoing message if necessary. + /// + /// The preprocessed commands are: + /// - INIT + /// - CANCEL + /// - ERROR + /// - Unknown and unexpected commands like KEEPALIVE + /// - LOCK is not implemented and currently treated like an unknown command + fn preprocess_message(&mut self, message: Message) -> Option { + let cid = message.cid; + if !self.has_valid_channel(&message) { + return Some(CtapHid::error_message(cid, CtapHid::ERR_INVALID_CHANNEL)); + } + + match message.cmd { + CtapHidCommand::Msg => Some(message), + CtapHidCommand::Cbor => Some(message), + // CTAP 2.1 from 2021-06-15, section 11.2.9.1.3. + CtapHidCommand::Init => { + if message.payload.len() != 8 { + return Some(CtapHid::error_message(cid, CtapHid::ERR_INVALID_LEN)); + } + + let new_cid = if cid == CtapHid::CHANNEL_BROADCAST { + // TODO: Prevent allocating 2^32 channels. + self.allocated_cids += 1; + (self.allocated_cids as u32).to_be_bytes() + } else { + // Sync the channel and discard the current transaction. + cid + }; + + let mut payload = vec![0; 17]; + payload[..8].copy_from_slice(&message.payload); + payload[8..12].copy_from_slice(&new_cid); + payload[12] = CtapHid::PROTOCOL_VERSION; + payload[13] = CtapHid::DEVICE_VERSION_MAJOR; + payload[14] = CtapHid::DEVICE_VERSION_MINOR; + payload[15] = CtapHid::DEVICE_VERSION_BUILD; + payload[16] = CtapHid::CAPABILITIES; + + Some(Message { + cid, + cmd: CtapHidCommand::Init, + payload, + }) + } + // CTAP 2.1 from 2021-06-15, section 11.2.9.1.4. + CtapHidCommand::Ping => { + // Pong the same message. + Some(message) + } + // CTAP 2.1 from 2021-06-15, section 11.2.9.1.5. + CtapHidCommand::Cancel => { + // Authenticators MUST NOT reply to this message. + // CANCEL is handled during user presence checks in main. + None + } + CtapHidCommand::Wink => Some(message), + _ => { + // Unknown or unsupported command. + Some(CtapHid::error_message(cid, CtapHid::ERR_INVALID_CMD)) + } + } + } + + /// Processes a message's commands that affect the protocol outside HID. + pub fn process_message( + &mut self, + env: &mut impl Env, + message: Message, + clock_value: ClockValue, + ctap_state: &mut CtapState, + ) -> Message { + // If another command arrives, stop winking to prevent accidential button touches. + self.wink_permission = TimedPermission::waiting(); + + let cid = message.cid; + match message.cmd { + // CTAP 2.1 from 2021-06-15, section 11.2.9.1.1. + CtapHidCommand::Msg => { + // If we don't have CTAP1 backward compatibilty, this command is invalid. + #[cfg(not(feature = "with_ctap1"))] + return CtapHid::error_message(cid, CtapHid::ERR_INVALID_CMD); + + #[cfg(feature = "with_ctap1")] + match ctap1::Ctap1Command::process_command( + env, + &message.payload, + ctap_state, + clock_value, + ) { + Ok(payload) => CtapHid::ctap1_success_message(cid, &payload), + Err(ctap1_status_code) => CtapHid::ctap1_error_message(cid, ctap1_status_code), + } + } + // CTAP 2.1 from 2021-06-15, section 11.2.9.1.2. + CtapHidCommand::Cbor => { + // Each transaction is atomic, so we process the command directly here and + // don't handle any other packet in the meantime. + // TODO: Send "Processing" type keep-alive packets in the meantime. + let response = ctap_state.process_command(env, &message.payload, cid, clock_value); + Message { + cid, + cmd: CtapHidCommand::Cbor, + payload: response, + } + } + // CTAP 2.1 from 2021-06-15, section 11.2.9.2.1. + CtapHidCommand::Wink => { + if message.payload.is_empty() { + self.wink_permission = + TimedPermission::granted(clock_value, CtapHid::WINK_TIMEOUT_DURATION); + // The response is empty like the request. + message + } else { + CtapHid::error_message(cid, CtapHid::ERR_INVALID_LEN) + } + } + // All other commands have already been processed, keep them as is. + _ => message, + } + } + + /// Processes an incoming USB HID packet, and returns an iterator for all outgoing packets. + pub fn process_hid_packet( + &mut self, + env: &mut impl Env, + packet: &HidPacket, + clock_value: ClockValue, + ctap_state: &mut CtapState, + ) -> HidPacketIterator { + if let Some(message) = self.parse_packet(packet, clock_value) { + let processed_message = self.process_message(env, message, clock_value, ctap_state); + #[cfg(feature = "debug_ctap")] + writeln!( + &mut Console::new(), + "Sending message: {:02x?}", + processed_message + ) + .unwrap(); + CtapHid::split_message(processed_message) + } else { + HidPacketIterator::none() + } + } + fn has_valid_channel(&self, message: &Message) -> bool { match message.cid { // Only INIT commands use the broadcast channel. - CtapHid::CHANNEL_BROADCAST => message.cmd == CtapHid::COMMAND_INIT, + CtapHid::CHANNEL_BROADCAST => message.cmd == CtapHidCommand::Init, // Check that the channel is allocated. _ => self.is_allocated_channel(message.cid), } @@ -345,16 +420,15 @@ impl CtapHid { cid != CtapHid::CHANNEL_RESERVED && u32::from_be_bytes(cid) as usize <= self.allocated_cids } - fn error_message(cid: ChannelID, error_code: u8) -> HidPacketIterator { - // This unwrap is safe because the payload length is 1 <= 7609 bytes. - CtapHid::split_message(Message { + fn error_message(cid: ChannelID, error_code: u8) -> Message { + Message { cid, - cmd: CtapHid::COMMAND_ERROR, + cmd: CtapHidCommand::Error, payload: vec![error_code], - }) - .unwrap() + } } + /// Helper function to parse a raw packet. pub fn process_single_packet(packet: &HidPacket) -> (&ChannelID, ProcessedPacket) { let (cid, rest) = array_refs![packet, 4, 60]; if rest[0] & CtapHid::PACKET_TYPE_MASK != 0 { @@ -379,56 +453,65 @@ impl CtapHid { } } - fn split_message(message: Message) -> Option { - #[cfg(feature = "debug_ctap")] - writeln!(&mut Console::new(), "Sending message: {:02x?}", message).unwrap(); - HidPacketIterator::new(message) + /// Splits the message and unwraps the result. + /// + /// Unwrapping handles the case of payload lengths > 7609 bytes. All responses are fixed + /// length, with the exception of: + /// - PING, but here output equals the (validated) input, + /// - CBOR, where long responses are conceivable. + /// + /// Long CBOR responses should not happen, but we might not catch all edge cases, like for + /// example long user names that are part of the output of an assertion. These cases should be + /// correctly handled by the CTAP implementation. It is therefore an internal error from the + /// HID perspective. + fn split_message(message: Message) -> HidPacketIterator { + let cid = message.cid; + HidPacketIterator::new(message).unwrap_or_else(|| { + // The error payload is 1 <= 7609 bytes, so unwrap() is safe. + HidPacketIterator::new(Message { + cid, + cmd: CtapHidCommand::Cbor, + payload: vec![Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR as u8], + }) + .unwrap() + }) } + /// Generates the HID response packets for a keepalive status. pub fn keepalive(cid: ChannelID, status: KeepaliveStatus) -> HidPacketIterator { - let status_code = match status { - KeepaliveStatus::Processing => 1, - KeepaliveStatus::UpNeeded => 2, - }; // This unwrap is safe because the payload length is 1 <= 7609 bytes. CtapHid::split_message(Message { cid, - cmd: CtapHid::COMMAND_KEEPALIVE, - payload: vec![status_code], + cmd: CtapHidCommand::Keepalive, + payload: vec![status as u8], }) - .unwrap() } + /// Returns whether a wink permission is currently granted. pub fn should_wink(&self, now: ClockValue) -> bool { self.wink_permission.is_granted(now) } #[cfg(feature = "with_ctap1")] - fn ctap1_error_message( - cid: ChannelID, - error_code: ctap1::Ctap1StatusCode, - ) -> HidPacketIterator { - // This unwrap is safe because the payload length is 2 <= 7609 bytes + fn ctap1_error_message(cid: ChannelID, error_code: ctap1::Ctap1StatusCode) -> Message { let code: u16 = error_code.into(); - CtapHid::split_message(Message { + Message { cid, - cmd: CtapHid::COMMAND_MSG, + cmd: CtapHidCommand::Msg, payload: code.to_be_bytes().to_vec(), - }) - .unwrap() + } } #[cfg(feature = "with_ctap1")] - fn ctap1_success_message(cid: ChannelID, payload: &[u8]) -> HidPacketIterator { + fn ctap1_success_message(cid: ChannelID, payload: &[u8]) -> Message { let mut response = payload.to_vec(); let code: u16 = ctap1::Ctap1StatusCode::SW_SUCCESS.into(); response.extend_from_slice(&code.to_be_bytes()); - CtapHid::split_message(Message { + Message { cid, - cmd: CtapHid::COMMAND_MSG, + cmd: CtapHidCommand::Msg, payload: response, - }) - .unwrap() + } } } @@ -442,10 +525,10 @@ mod test { const DUMMY_CLOCK_VALUE: ClockValue = ClockValue::new(0, CLOCK_FREQUENCY_HZ); const DUMMY_TIMESTAMP: Timestamp = Timestamp::from_ms(0); - fn process_messages( - env: &mut E, + fn process_messages( + env: &mut TestEnv, ctap_hid: &mut CtapHid, - ctap_state: &mut CtapState, + ctap_state: &mut CtapState, request: Vec, ) -> Option> { let mut result = Vec::new(); @@ -466,10 +549,10 @@ mod test { Some(result) } - fn cid_from_init( - env: &mut E, + fn cid_from_init( + env: &mut TestEnv, ctap_hid: &mut CtapHid, - ctap_state: &mut CtapState, + ctap_state: &mut CtapState, ) -> ChannelID { let nonce = vec![0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0]; let reply = process_messages( @@ -478,7 +561,7 @@ mod test { ctap_state, vec![Message { cid: CtapHid::CHANNEL_BROADCAST, - cmd: CtapHid::COMMAND_INIT, + cmd: CtapHidCommand::Init, payload: nonce.clone(), }], ); @@ -500,7 +583,7 @@ mod test { for payload_len in 0..7609 { let message = Message { cid: [0x12, 0x34, 0x56, 0x78], - cmd: 0x00, + cmd: CtapHidCommand::Cbor, payload: vec![0xFF; payload_len], }; @@ -552,7 +635,7 @@ mod test { &mut ctap_state, vec![Message { cid: CtapHid::CHANNEL_BROADCAST, - cmd: CtapHid::COMMAND_INIT, + cmd: CtapHidCommand::Init, payload: vec![0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0], }], ); @@ -561,7 +644,7 @@ mod test { reply, Some(vec![Message { cid: CtapHid::CHANNEL_BROADCAST, - cmd: CtapHid::COMMAND_INIT, + cmd: CtapHidCommand::Init, payload: vec![ 0x12, // Nonce 0x34, @@ -623,7 +706,7 @@ mod test { result, vec![Message { cid, - cmd: CtapHid::COMMAND_INIT, + cmd: CtapHidCommand::Init, payload: vec![ 0x12, // Nonce 0x34, @@ -660,7 +743,7 @@ mod test { &mut ctap_state, vec![Message { cid, - cmd: CtapHid::COMMAND_PING, + cmd: CtapHidCommand::Ping, payload: vec![0x99, 0x99], }], ); @@ -669,7 +752,7 @@ mod test { reply, Some(vec![Message { cid, - cmd: CtapHid::COMMAND_PING, + cmd: CtapHidCommand::Ping, payload: vec![0x99, 0x99] }]) ); diff --git a/src/ctap/hid/receive.rs b/src/ctap/hid/receive.rs index 08929b0..c6885a3 100644 --- a/src/ctap/hid/receive.rs +++ b/src/ctap/hid/receive.rs @@ -13,7 +13,7 @@ // limitations under the License. use super::super::customization::MAX_MSG_SIZE; -use super::{ChannelID, CtapHid, HidPacket, Message, ProcessedPacket}; +use super::{ChannelID, CtapHid, CtapHidCommand, HidPacket, Message, ProcessedPacket}; use alloc::vec::Vec; use core::mem::swap; use libtock_drivers::timer::Timestamp; @@ -131,7 +131,7 @@ impl MessageAssembler { // Unexpected initialization packet. ProcessedPacket::InitPacket { cmd, len, data } => { self.reset(); - if cmd == CtapHid::COMMAND_INIT { + if cmd == CtapHidCommand::Init as u8 { self.parse_init_packet(*cid, cmd, len, data, timestamp) } else { Err((*cid, Error::UnexpectedInit)) @@ -189,7 +189,7 @@ impl MessageAssembler { swap(&mut self.payload, &mut payload); Some(Message { cid: self.cid, - cmd: self.cmd, + cmd: CtapHidCommand::from(self.cmd), payload, }) } @@ -225,12 +225,12 @@ mod test { let mut assembler = MessageAssembler::new(); assert_eq!( assembler.parse_packet( - &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x80]), + &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x90]), DUMMY_TIMESTAMP ), Ok(Some(Message { cid: [0x12, 0x34, 0x56, 0x78], - cmd: 0x00, + cmd: CtapHidCommand::Cbor, payload: vec![] })) ); @@ -241,12 +241,12 @@ mod test { let mut assembler = MessageAssembler::new(); assert_eq!( assembler.parse_packet( - &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x80, 0x00, 0x10]), + &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x90, 0x00, 0x10]), DUMMY_TIMESTAMP ), Ok(Some(Message { cid: [0x12, 0x34, 0x56, 0x78], - cmd: 0x00, + cmd: CtapHidCommand::Cbor, payload: vec![0x00; 0x10] })) ); @@ -260,12 +260,12 @@ mod test { let mut assembler = MessageAssembler::new(); assert_eq!( assembler.parse_packet( - &byte_extend(&[0x12, 0x34, 0x56, 0x78, 0x80, 0x00, 0x10], 0xFF), + &byte_extend(&[0x12, 0x34, 0x56, 0x78, 0x90, 0x00, 0x10], 0xFF), DUMMY_TIMESTAMP ), Ok(Some(Message { cid: [0x12, 0x34, 0x56, 0x78], - cmd: 0x00, + cmd: CtapHidCommand::Cbor, payload: vec![0xFF; 0x10] })) ); @@ -288,7 +288,7 @@ mod test { ), Ok(Some(Message { cid: [0x12, 0x34, 0x56, 0x78], - cmd: 0x01, + cmd: CtapHidCommand::Ping, payload: vec![0x00; 0x40] })) ); @@ -318,7 +318,7 @@ mod test { ), Ok(Some(Message { cid: [0x12, 0x34, 0x56, 0x78], - cmd: 0x01, + cmd: CtapHidCommand::Ping, payload: vec![0x00; 0x80] })) ); @@ -350,7 +350,7 @@ mod test { ), Ok(Some(Message { cid: [0x12, 0x34, 0x56, 0x78], - cmd: 0x01, + cmd: CtapHidCommand::Ping, payload: vec![0x00; 0x1DB9] })) ); @@ -362,12 +362,15 @@ mod test { let mut assembler = MessageAssembler::new(); for i in 0..10 { // Introduce some variability in the messages. - let cmd = 2 * i; + let cmd = CtapHidCommand::from(i + 1); let byte = 3 * i; assert_eq!( assembler.parse_packet( - &byte_extend(&[0x12, 0x34, 0x56, 0x78, 0x80 | cmd, 0x00, 0x80], byte), + &byte_extend( + &[0x12, 0x34, 0x56, 0x78, 0x80 | cmd as u8, 0x00, 0x80], + byte + ), DUMMY_TIMESTAMP ), Ok(None) @@ -400,12 +403,12 @@ mod test { for i in 0..10 { // Introduce some variability in the messages. let cid = 0x78 + i; - let cmd = 2 * i; + let cmd = CtapHidCommand::from(i + 1); let byte = 3 * i; assert_eq!( assembler.parse_packet( - &byte_extend(&[0x12, 0x34, 0x56, cid, 0x80 | cmd, 0x00, 0x80], byte), + &byte_extend(&[0x12, 0x34, 0x56, cid, 0x80 | cmd as u8, 0x00, 0x80], byte), DUMMY_TIMESTAMP ), Ok(None) @@ -443,11 +446,12 @@ mod test { ); // Check that many sorts of packets on another channel are ignored. - for cmd in 0..=0xFF { + for i in 0..=0xFF { + let cmd = CtapHidCommand::from(i); for byte in 0..=0xFF { assert_eq!( assembler.parse_packet( - &byte_extend(&[0x12, 0x34, 0x56, 0x9A, cmd, 0x00], byte), + &byte_extend(&[0x12, 0x34, 0x56, 0x9A, cmd as u8, 0x00], byte), DUMMY_TIMESTAMP ), Err(([0x12, 0x34, 0x56, 0x9A], Error::UnexpectedChannel)) @@ -462,7 +466,7 @@ mod test { ), Ok(Some(Message { cid: [0x12, 0x34, 0x56, 0x78], - cmd: 0x01, + cmd: CtapHidCommand::Ping, payload: vec![0x00; 0x40] })) ); @@ -479,12 +483,12 @@ mod test { let byte = 2 * i; assert_eq!( assembler.parse_packet( - &byte_extend(&[0x12, 0x34, 0x56, 0x78, 0x80, 0x00, 0x10], byte), + &byte_extend(&[0x12, 0x34, 0x56, 0x78, 0x81, 0x00, 0x10], byte), DUMMY_TIMESTAMP ), Ok(Some(Message { cid: [0x12, 0x34, 0x56, 0x78], - cmd: 0x00, + cmd: CtapHidCommand::Ping, payload: vec![byte; 0x10] })) ); @@ -584,7 +588,7 @@ mod test { assembler.parse_packet(&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x7F]), timestamp), Ok(Some(Message { cid: [0x12, 0x34, 0x56, 0x78], - cmd: 0x01, + cmd: CtapHidCommand::Ping, payload: vec![0x00; 0x1DB9] })) ); @@ -612,7 +616,7 @@ mod test { ), Ok(Some(Message { cid: [0x12, 0x34, 0x56, 0x78], - cmd: 0x06, + cmd: CtapHidCommand::Init, payload: vec![0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0] })) ); diff --git a/src/ctap/hid/send.rs b/src/ctap/hid/send.rs index 5ea2e1c..d919e68 100644 --- a/src/ctap/hid/send.rs +++ b/src/ctap/hid/send.rs @@ -14,6 +14,9 @@ use super::{CtapHid, HidPacket, Message}; +/// Iterator for HID packets. +/// +/// The `new` constructor splits the CTAP `Message` into `HidPacket`s for sending over USB. pub struct HidPacketIterator(Option); impl HidPacketIterator { @@ -99,7 +102,7 @@ impl Iterator for MessageSplitter { match self.seq { None => { // First, send an initialization packet. - self.packet[4] = self.message.cmd | CtapHid::TYPE_INIT_BIT; + self.packet[4] = self.message.cmd as u8 | CtapHid::TYPE_INIT_BIT; self.packet[5] = (payload_len >> 8) as u8; self.packet[6] = payload_len as u8; @@ -128,6 +131,7 @@ impl Iterator for MessageSplitter { #[cfg(test)] mod test { + use super::super::CtapHidCommand; use super::*; fn assert_packet_output_equality(message: Message, expected_packets: Vec) { @@ -142,11 +146,11 @@ mod test { fn test_hid_packet_iterator_single_packet() { let message = Message { cid: [0x12, 0x34, 0x56, 0x78], - cmd: 0x4C, + cmd: CtapHidCommand::Cbor, payload: vec![0xAA, 0xBB], }; let expected_packets: Vec = vec![[ - 0x12, 0x34, 0x56, 0x78, 0xCC, 0x00, 0x02, 0xAA, 0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x12, 0x34, 0x56, 0x78, 0x90, 0x00, 0x02, 0xAA, 0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -159,11 +163,11 @@ mod test { fn test_hid_packet_iterator_big_single_packet() { let message = Message { cid: [0x12, 0x34, 0x56, 0x78], - cmd: 0x4C, + cmd: CtapHidCommand::Cbor, payload: vec![0xAA; 64 - 7], }; let expected_packets: Vec = vec![[ - 0x12, 0x34, 0x56, 0x78, 0xCC, 0x00, 0x39, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0x12, 0x34, 0x56, 0x78, 0x90, 0x00, 0x39, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, @@ -176,12 +180,12 @@ mod test { fn test_hid_packet_iterator_two_packets() { let message = Message { cid: [0x12, 0x34, 0x56, 0x78], - cmd: 0x4C, + cmd: CtapHidCommand::Cbor, payload: vec![0xAA; 64 - 7 + 1], }; let expected_packets: Vec = vec![ [ - 0x12, 0x34, 0x56, 0x78, 0xCC, 0x00, 0x3A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0x12, 0x34, 0x56, 0x78, 0x90, 0x00, 0x3A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, @@ -204,12 +208,12 @@ mod test { payload.extend(vec![0xBB; 64 - 5]); let message = Message { cid: [0x12, 0x34, 0x56, 0x78], - cmd: 0x4C, + cmd: CtapHidCommand::Cbor, payload, }; let expected_packets: Vec = vec![ [ - 0x12, 0x34, 0x56, 0x78, 0xCC, 0x00, 0x74, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0x12, 0x34, 0x56, 0x78, 0x90, 0x00, 0x74, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, @@ -238,12 +242,12 @@ mod test { let message = Message { cid: [0x12, 0x34, 0x56, 0x78], - cmd: 0xAB, + cmd: CtapHidCommand::Msg, payload, }; let mut expected_packets: Vec = vec![[ - 0x12, 0x34, 0x56, 0x78, 0xAB, 0x1D, 0xB9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x12, 0x34, 0x56, 0x78, 0x83, 0x1D, 0xB9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, @@ -271,7 +275,7 @@ mod test { assert_eq!(payload.len(), 0x1dba); let message = Message { cid: [0x12, 0x34, 0x56, 0x78], - cmd: 0xAB, + cmd: CtapHidCommand::Msg, payload, }; assert!(HidPacketIterator::new(message).is_none()); @@ -283,7 +287,7 @@ mod test { let payload = vec![0xFF; 0x10000]; let message = Message { cid: [0x12, 0x34, 0x56, 0x78], - cmd: 0xAB, + cmd: CtapHidCommand::Msg, payload, }; assert!(HidPacketIterator::new(message).is_none()); diff --git a/src/ctap/large_blobs.rs b/src/ctap/large_blobs.rs index a6cd23a..3a0eb62 100644 --- a/src/ctap/large_blobs.rs +++ b/src/ctap/large_blobs.rs @@ -17,7 +17,7 @@ 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; @@ -45,9 +45,9 @@ impl LargeBlobs { } /// Process the large blob command. - pub fn process_command( + pub fn process_command( &mut self, - persistent_store: &mut PersistentStore, + env: &mut impl Env, client_pin: &mut ClientPin, large_blobs_params: AuthenticatorLargeBlobsParameters, ) -> Result { @@ -66,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 }, ))); @@ -85,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 = @@ -122,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)); @@ -143,7 +143,6 @@ mod test { #[test] fn test_process_command_get_empty() { let mut env = TestEnv::new(); - let mut persistent_store = PersistentStore::new(&mut env); let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); let pin_uv_auth_token = [0x55; 32]; let mut client_pin = @@ -163,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); @@ -175,7 +174,6 @@ mod test { #[test] fn test_process_command_commit_and_get() { let mut env = TestEnv::new(); - let mut persistent_store = PersistentStore::new(&mut env); let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); let pin_uv_auth_token = [0x55; 32]; let mut client_pin = @@ -196,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)) @@ -211,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)) @@ -226,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); @@ -238,7 +236,6 @@ mod test { #[test] fn test_process_command_commit_unexpected_offset() { let mut env = TestEnv::new(); - let mut persistent_store = PersistentStore::new(&mut env); let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); let pin_uv_auth_token = [0x55; 32]; let mut client_pin = @@ -259,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)) @@ -275,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), @@ -285,7 +282,6 @@ mod test { #[test] fn test_process_command_commit_unexpected_length() { let mut env = TestEnv::new(); - let mut persistent_store = PersistentStore::new(&mut env); let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); let pin_uv_auth_token = [0x55; 32]; let mut client_pin = @@ -307,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)) @@ -322,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), @@ -332,7 +328,6 @@ mod test { #[test] fn test_process_command_commit_end_offset_overflow() { let mut env = TestEnv::new(); - let mut persistent_store = PersistentStore::new(&mut env); let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); let pin_uv_auth_token = [0x55; 32]; let mut client_pin = @@ -348,7 +343,7 @@ 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), ); } @@ -356,7 +351,6 @@ mod test { #[test] fn test_process_command_commit_unexpected_hash() { let mut env = TestEnv::new(); - let mut persistent_store = PersistentStore::new(&mut env); let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); let pin_uv_auth_token = [0x55; 32]; let mut client_pin = @@ -376,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), @@ -385,7 +379,6 @@ mod test { fn test_helper_process_command_commit_with_pin(pin_uv_auth_protocol: PinUvAuthProtocol) { let mut env = TestEnv::new(); - let mut persistent_store = PersistentStore::new(&mut env); let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); let pin_uv_auth_token = [0x55; 32]; let mut client_pin = @@ -397,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]); @@ -417,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 cac6cea..f78aaf1 100644 --- a/src/ctap/mod.rs +++ b/src/ctap/mod.rs @@ -60,7 +60,6 @@ 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; @@ -330,24 +329,20 @@ 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, +pub struct CtapState { 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 support is optional. - upgrade_locations: Option, } -impl CtapState { - pub fn new(env: &mut E, now: ClockValue) -> Self { - let persistent_store = PersistentStore::new(env); +impl CtapState { + 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( @@ -356,7 +351,6 @@ impl CtapState { ), stateful_command_permission: StatefulPermission::new_reset(now), large_blobs: LargeBlobs::new(), - upgrade_locations: env.upgrade_storage().ok(), } } @@ -370,12 +364,11 @@ impl CtapState { pub fn increment_global_signature_counter( &mut self, - env: &mut E, + env: &mut impl Env, ) -> 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(()) } @@ -384,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 @@ -394,11 +387,11 @@ impl CtapState { // compatible with U2F. pub fn encrypt_key_handle( &mut self, - env: &mut E, + env: &mut impl Env, 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)); @@ -415,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, @@ -455,7 +449,7 @@ impl CtapState { pub fn process_command( &mut self, - env: &mut E, + env: &mut impl Env, command_cbor: &[u8], cid: ChannelID, now: ClockValue, @@ -501,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, @@ -519,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(); @@ -558,7 +550,7 @@ impl CtapState { fn pin_uv_auth_precheck( &mut self, - env: &mut E, + env: &mut impl Env, pin_uv_auth_param: &Option>, pin_uv_auth_protocol: Option, cid: ChannelID, @@ -567,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); @@ -580,7 +572,7 @@ impl CtapState { fn process_make_credential( &mut self, - env: &mut E, + env: &mut impl Env, make_credential_params: AuthenticatorMakeCredentialParameters, cid: ChannelID, ) -> Result { @@ -607,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 ( @@ -631,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( @@ -652,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 @@ -667,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 @@ -692,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 @@ -736,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()), @@ -746,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); @@ -768,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 }; @@ -786,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), @@ -825,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)) } @@ -838,7 +821,7 @@ impl CtapState { // and returns the correct Get(Next)Assertion response. fn assertion_response( &mut self, - env: &mut E, + env: &mut impl Env, mut credential: PublicKeyCredentialSource, assertion_input: AssertionInput, number_of_credentials: Option, @@ -853,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, @@ -921,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); } @@ -946,7 +928,7 @@ impl CtapState { fn process_get_assertion( &mut self, - env: &mut E, + env: &mut impl Env, get_assertion_params: AuthenticatorGetAssertionParameters, cid: ChannelID, now: ClockValue, @@ -974,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( @@ -995,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 @@ -1011,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()) { @@ -1034,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) }; @@ -1051,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, }; @@ -1072,7 +1060,7 @@ impl CtapState { fn process_get_next_assertion( &mut self, - env: &mut E, + env: &mut impl Env, now: ClockValue, ) -> Result { self.stateful_command_permission @@ -1080,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), @@ -1099,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), @@ -1110,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), @@ -1130,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. @@ -1143,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 ), }, )) @@ -1158,7 +1140,7 @@ impl CtapState { fn process_reset( &mut self, - env: &mut E, + env: &mut impl Env, cid: ChannelID, now: ClockValue, ) -> Result { @@ -1170,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")] { @@ -1184,7 +1166,7 @@ impl CtapState { fn process_selection( &self, - env: &mut E, + env: &mut impl Env, cid: ChannelID, ) -> Result { env.user_presence().check(cid)?; @@ -1193,7 +1175,7 @@ impl CtapState { fn process_vendor_configure( &mut self, - env: &mut E, + env: &mut impl Env, params: AuthenticatorVendorConfigureParameters, cid: ChannelID, ) -> Result { @@ -1202,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. @@ -1231,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, @@ -1264,6 +1244,7 @@ impl CtapState { fn process_vendor_upgrade( &mut self, + env: &mut impl Env, params: AuthenticatorVendorUpgradeParameters, ) -> Result { let AuthenticatorVendorUpgradeParameters { @@ -1272,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 @@ -1303,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 { @@ -1317,6 +1299,7 @@ impl CtapState { pub fn generate_auth_data( &self, + env: &mut impl Env, rp_id_hash: &[u8], flag_byte: u8, ) -> Result, Ctap2StatusCode> { @@ -1328,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) @@ -1430,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, @@ -1452,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]; @@ -1533,7 +1516,7 @@ mod test { check_make_response( make_credential_response, 0x41, - &ctap_state.persistent_store.aaguid().unwrap(), + &storage::aaguid(&mut env).unwrap(), 0x20, &[], ); @@ -1552,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, &[], ); @@ -1597,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); @@ -1623,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(); @@ -1650,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(); @@ -1688,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, ); @@ -1714,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, ); @@ -1737,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(()) ); @@ -1765,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, ); @@ -1790,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(); @@ -1825,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(); @@ -1863,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(); @@ -1884,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( @@ -1905,7 +1868,7 @@ mod test { check_make_response( make_credential_response, 0x45, - &ctap_state.persistent_store.aaguid().unwrap(), + &storage::aaguid(&mut env).unwrap(), 0x20, &[], ); @@ -1932,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; @@ -1942,7 +1905,7 @@ mod test { check_make_response( make_credential_response, 0x41, - &ctap_state.persistent_store.aaguid().unwrap(), + &storage::aaguid(&mut env).unwrap(), 0x70, &[], ); @@ -1952,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 = @@ -1968,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); @@ -1977,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); @@ -2114,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); } @@ -2192,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() @@ -2210,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(), @@ -2268,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(), @@ -2327,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"), @@ -2373,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 { @@ -2393,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"), @@ -2443,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, @@ -2470,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, ]; @@ -2507,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), @@ -2579,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, @@ -2605,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, @@ -2695,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], @@ -2809,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] @@ -2890,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(); @@ -2912,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()); } @@ -2923,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; } @@ -2989,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 ); @@ -3028,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 ); @@ -3067,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]; @@ -3171,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) @@ -3192,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)); } @@ -3259,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 08e88ff..371e38f 100644 --- a/src/ctap/storage.rs +++ b/src/ctap/storage.rs @@ -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(env: &mut E) -> Self { - let storage = env.storage().ok().unwrap(); - let mut store = PersistentStore { - store: persistent_store::Store::new(storage).ok().unwrap(), - }; - store.init(env.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, &[])?) } } @@ -780,39 +764,36 @@ mod test { #[test] fn test_store() { let mut env = TestEnv::new(); - let mut persistent_store = PersistentStore::new(&mut env); - assert_eq!(persistent_store.count_credentials().unwrap(), 0); + assert_eq!(count_credentials(&mut env).unwrap(), 0); let credential_source = create_credential_source(env.rng(), "example.com", vec![]); - assert!(persistent_store.store_credential(credential_source).is_ok()); - assert!(persistent_store.count_credentials().unwrap() > 0); + assert!(store_credential(&mut env, credential_source).is_ok()); + assert!(count_credentials(&mut env).unwrap() > 0); } #[test] fn test_delete_credential() { let mut env = TestEnv::new(); - let mut persistent_store = PersistentStore::new(&mut env); - assert_eq!(persistent_store.count_credentials().unwrap(), 0); + 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(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 env = TestEnv::new(); - let mut persistent_store = PersistentStore::new(&mut env); let user = PublicKeyCredentialUserEntity { // User ID is ignored. user_id: vec![0x00], @@ -821,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(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); @@ -850,29 +827,27 @@ mod test { #[test] fn test_credential_order() { let mut env = TestEnv::new(); - let mut persistent_store = PersistentStore::new(&mut env); 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()); + 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 = persistent_store.new_creation_order().unwrap(); + 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 env = TestEnv::new(); - let mut persistent_store = PersistentStore::new(&mut env); - assert_eq!(persistent_store.count_credentials().unwrap(), 0); + 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(env.rng(), "example.com", user_handle); - 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 credential_source = create_credential_source( env.rng(), @@ -880,11 +855,11 @@ mod test { 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 ); } @@ -892,36 +867,35 @@ mod test { #[test] fn test_overwrite() { let mut env = TestEnv::new(); - let mut persistent_store = PersistentStore::new(&mut env); - assert_eq!(persistent_store.count_credentials().unwrap(), 0); + init(&mut env).unwrap(); + + assert_eq!(count_credentials(&mut env).unwrap(), 0); // These should have different IDs. 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 env); + 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(env.rng(), "example.com", user_handle); - 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 credential_source = create_credential_source( env.rng(), @@ -929,11 +903,11 @@ mod test { 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 ); } @@ -941,7 +915,6 @@ mod test { #[test] fn test_get_credential() { let mut env = TestEnv::new(); - let mut persistent_store = PersistentStore::new(&mut env); 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 = @@ -949,9 +922,9 @@ mod test { 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); } } @@ -959,26 +932,17 @@ mod test { #[test] fn test_find() { let mut env = TestEnv::new(); - let mut persistent_store = PersistentStore::new(&mut env); - assert_eq!(persistent_store.count_credentials().unwrap(), 0); + 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, @@ -999,8 +963,7 @@ mod test { #[test] fn test_find_with_cred_protect() { let mut env = TestEnv::new(); - let mut persistent_store = PersistentStore::new(&mut env); - assert_eq!(persistent_store.count_credentials().unwrap(), 0); + assert_eq!(count_credentials(&mut env).unwrap(), 0); let private_key = crypto::ecdsa::SecKey::gensk(env.rng()); let credential = PublicKeyCredentialSource { key_type: PublicKeyCredentialType::PublicKey, @@ -1016,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 env = TestEnv::new(); - let mut persistent_store = PersistentStore::new(&mut env); + 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); @@ -1039,8 +1000,8 @@ 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(env.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()); } @@ -1048,21 +1009,21 @@ mod test { #[test] fn test_cred_random_secret() { let mut env = TestEnv::new(); - let mut persistent_store = PersistentStore::new(&mut env); + 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(env.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); } @@ -1070,11 +1031,10 @@ mod test { #[test] fn test_pin_hash_and_length() { let mut env = TestEnv::new(); - let mut persistent_store = PersistentStore::new(&mut env); // 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 = env.rng().gen_uniform_u8x32(); @@ -1083,143 +1043,112 @@ mod test { 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(env.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 env = TestEnv::new(); - let mut persistent_store = PersistentStore::new(&mut env); // 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 env = TestEnv::new(); - let mut persistent_store = PersistentStore::new(&mut env); + 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(env.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 env = TestEnv::new(); - let mut persistent_store = PersistentStore::new(&mut env); // 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 env = TestEnv::new(); - let mut persistent_store = PersistentStore::new(&mut env); // 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 env = TestEnv::new(); - let persistent_store = PersistentStore::new(&mut env); 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) ); } @@ -1227,46 +1156,38 @@ mod test { #[test] fn test_commit_get_large_blob_array() { let mut env = TestEnv::new(); - let mut persistent_store = PersistentStore::new(&mut env); 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 env = TestEnv::new(); - let mut persistent_store = PersistentStore::new(&mut env); 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); } @@ -1274,83 +1195,70 @@ mod test { #[test] fn test_commit_get_large_blob_array_no_commit() { let mut env = TestEnv::new(); - let persistent_store = PersistentStore::new(&mut env); 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 env = TestEnv::new(); - let mut persistent_store = PersistentStore::new(&mut env); 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 env = TestEnv::new(); - let mut persistent_store = PersistentStore::new(&mut env); - 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 env = TestEnv::new(); - let mut persistent_store = PersistentStore::new(&mut env); - assert!(!persistent_store.enterprise_attestation().unwrap()); - assert_eq!(persistent_store.enable_enterprise_attestation(), Ok(())); - assert!(persistent_store.enterprise_attestation().unwrap()); - persistent_store.reset(env.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 env = TestEnv::new(); - let mut persistent_store = PersistentStore::new(&mut env); 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()); } } diff --git a/src/env/mod.rs b/src/env/mod.rs index e04d1d8..9db1b70 100644 --- a/src/env/mod.rs +++ b/src/env/mod.rs @@ -3,7 +3,7 @@ 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, StorageResult}; +use persistent_store::{Storage, Store}; #[cfg(feature = "std")] pub mod test; @@ -26,16 +26,13 @@ pub trait Env { fn rng(&mut self) -> &mut Self::Rng; fn user_presence(&mut self) -> &mut Self::UserPresence; + fn store(&mut self) -> &mut Store; - /// Returns the unique storage instance. + /// Returns the upgrade storage instance. /// - /// This function is called at most once. Implementation may panic if called more than once. - fn storage(&mut self) -> StorageResult; - - /// Returns the unique upgrade storage instance. - /// - /// This function is called at most once. Implementation may panic if called more than once. - fn upgrade_storage(&mut self) -> StorageResult; + /// 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>; fn firmware_protection(&mut self) -> &mut Self::FirmwareProtection; } diff --git a/src/env/test/mod.rs b/src/env/test/mod.rs index e1ace12..66eb031 100644 --- a/src/env/test/mod.rs +++ b/src/env/test/mod.rs @@ -4,26 +4,55 @@ 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, StorageResult}; +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(())), }; - TestEnv { rng, user_presence } + 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; } } @@ -60,23 +89,12 @@ impl Env for TestEnv { &mut self.user_presence } - fn storage(&mut self) -> 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 = BufferOptions { - word_size: 4, - page_size: PAGE_SIZE, - max_word_writes: 2, - max_page_erases: 10000, - strict_mode: true, - }; - Ok(BufferStorage::new(store, options)) + fn store(&mut self) -> &mut Store { + &mut self.store } - fn upgrade_storage(&mut self) -> StorageResult { - BufferUpgradeStorage::new() + fn upgrade_storage(&mut self) -> Option<&mut Self::UpgradeStorage> { + self.upgrade_storage.as_mut() } fn firmware_protection(&mut self) -> &mut Self::FirmwareProtection { diff --git a/src/env/tock/mod.rs b/src/env/tock/mod.rs index 9d27f56..8b249cb 100644 --- a/src/env/tock/mod.rs +++ b/src/env/tock/mod.rs @@ -1,6 +1,6 @@ -use self::storage::{SyscallStorage, SyscallUpgradeStorage}; +pub use self::storage::{TockStorage, TockUpgradeStorage}; use crate::api::firmware_protection::FirmwareProtection; -use crate::ctap::hid::{ChannelID, CtapHid, KeepaliveStatus, ProcessedPacket}; +use crate::ctap::hid::{ChannelID, CtapHid, CtapHidCommand, KeepaliveStatus, ProcessedPacket}; use crate::ctap::status_code::Ctap2StatusCode; use crate::env::{Env, UserPresence}; use core::cell::Cell; @@ -15,46 +15,45 @@ use libtock_drivers::console::Console; use libtock_drivers::result::{FlexUnwrap, TockError}; use libtock_drivers::timer::Duration; use libtock_drivers::{crp, led, timer, usb_ctap_hid}; -use persistent_store::StorageResult; +use persistent_store::{StorageResult, Store}; mod storage; pub struct TockEnv { rng: TockRng256, - storage: bool, - upgrade_storage: bool, + store: Store, + upgrade_storage: Option, } impl TockEnv { /// Returns the unique instance of the Tock environment. /// - /// This function returns `Some` the first time it is called. Afterwards, it repeatedly returns - /// `None`. - pub fn new() -> Option { - // Make sure the environment was not already taken. - static TAKEN: AtomicBool = AtomicBool::new(false); - if TAKEN.fetch_or(true, Ordering::SeqCst) { - return None; - } - Some(TockEnv { + /// # Panics + /// + /// - If called a second time. + pub fn new() -> Self { + // We rely on `take_storage` to ensure that this function is called only once. + let storage = take_storage().unwrap(); + let store = Store::new(storage).ok().unwrap(); + let upgrade_storage = TockUpgradeStorage::new().ok(); + TockEnv { rng: TockRng256 {}, - storage: false, - upgrade_storage: false, - }) + store, + upgrade_storage, + } } } -/// Creates a new storage instance. +/// Returns the unique storage instance. /// -/// # Safety +/// # Panics /// -/// It is probably technically memory-safe to hame 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 for 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() +/// - If called a second time. +pub fn take_storage() -> StorageResult { + // Make sure the storage was not already taken. + static TAKEN: AtomicBool = AtomicBool::new(false); + assert!(!TAKEN.fetch_or(true, Ordering::SeqCst)); + TockStorage::new() } impl UserPresence for TockEnv { @@ -79,8 +78,8 @@ impl FirmwareProtection for TockEnv { impl Env for TockEnv { type Rng = TockRng256; type UserPresence = Self; - type Storage = SyscallStorage; - type UpgradeStorage = SyscallUpgradeStorage; + type Storage = TockStorage; + type UpgradeStorage = TockUpgradeStorage; type FirmwareProtection = Self; fn rng(&mut self) -> &mut Self::Rng { @@ -91,14 +90,12 @@ impl Env for TockEnv { self } - fn storage(&mut self) -> StorageResult { - assert_once(&mut self.storage); - unsafe { steal_storage() } + fn store(&mut self) -> &mut Store { + &mut self.store } - fn upgrade_storage(&mut self) -> StorageResult { - assert_once(&mut self.upgrade_storage); - SyscallUpgradeStorage::new() + fn upgrade_storage(&mut self) -> Option<&mut Self::UpgradeStorage> { + self.upgrade_storage.as_mut() } fn firmware_protection(&mut self) -> &mut Self::FirmwareProtection { @@ -106,12 +103,6 @@ impl Env for TockEnv { } } -/// Asserts a boolean is false and sets it to true. -fn assert_once(b: &mut bool) { - assert!(!*b); - *b = true; -} - // Returns whether the keepalive was sent, or false if cancelled. fn send_keepalive_up_needed( cid: ChannelID, @@ -146,7 +137,7 @@ fn send_keepalive_up_needed( } match processed_packet { ProcessedPacket::InitPacket { cmd, .. } => { - if cmd == CtapHid::COMMAND_CANCEL { + if cmd == CtapHidCommand::Cancel as u8 { // We ignore the payload, we can't answer with an error code anyway. #[cfg(feature = "debug_ctap")] writeln!(Console::new(), "User presence check cancelled").unwrap(); diff --git a/src/env/tock/storage.rs b/src/env/tock/storage.rs index 1d4c6c4..515dab3 100644 --- a/src/env/tock/storage.rs +++ b/src/env/tock/storage.rs @@ -115,7 +115,7 @@ fn erase_page(ptr: usize, page_length: usize) -> StorageResult<()> { block_command(DRIVER_NUMBER, command_nr::ERASE_PAGE, ptr, page_length) } -pub struct SyscallStorage { +pub struct TockStorage { word_size: usize, page_size: usize, num_pages: usize, @@ -124,7 +124,7 @@ pub struct SyscallStorage { storage_locations: Vec<&'static [u8]>, } -impl SyscallStorage { +impl TockStorage { /// Provides access to the embedded flash if available. /// /// # Errors @@ -134,8 +134,8 @@ impl SyscallStorage { /// - The page size is a power of two. /// - The page size is a multiple of the word size. /// - The storage is page-aligned. - pub fn new() -> StorageResult { - let mut syscall = SyscallStorage { + pub fn new() -> StorageResult { + let mut syscall = TockStorage { word_size: get_info(command_nr::get_info_nr::WORD_SIZE, 0)?, page_size: get_info(command_nr::get_info_nr::PAGE_SIZE, 0)?, num_pages: 0, @@ -175,7 +175,7 @@ impl SyscallStorage { } } -impl Storage for SyscallStorage { +impl Storage for TockStorage { fn word_size(&self) -> usize { self.word_size } @@ -217,13 +217,13 @@ impl Storage for SyscallStorage { } } -pub struct SyscallUpgradeStorage { +pub struct TockUpgradeStorage { page_size: usize, partition: ModRange, metadata: ModRange, } -impl SyscallUpgradeStorage { +impl TockUpgradeStorage { /// Provides access to the other upgrade partition and metadata if available. /// /// The implementation assumes that storage locations returned by the kernel through @@ -238,8 +238,8 @@ impl SyscallUpgradeStorage { /// Returns a `NotAligned` error if partitions or metadata ranges are /// - not exclusive or, /// - not consecutive. - pub fn new() -> StorageResult { - let mut locations = SyscallUpgradeStorage { + pub fn new() -> StorageResult { + let mut locations = TockUpgradeStorage { page_size: get_info(command_nr::get_info_nr::PAGE_SIZE, 0)?, partition: ModRange::new_empty(), metadata: ModRange::new_empty(), @@ -287,7 +287,7 @@ impl SyscallUpgradeStorage { } } -impl UpgradeStorage for SyscallUpgradeStorage { +impl UpgradeStorage for TockUpgradeStorage { fn read_partition(&self, offset: usize, length: usize) -> StorageResult<&[u8]> { if length == 0 { return Err(StorageError::OutOfBounds); diff --git a/src/lib.rs b/src/lib.rs index c3be33d..26573c2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,7 +35,7 @@ pub mod env; /// CTAP implementation parameterized by its environment. pub struct Ctap { env: E, - state: CtapState, + state: CtapState, hid: CtapHid, } @@ -49,7 +49,7 @@ impl Ctap { Ctap { env, state, hid } } - pub fn state(&mut self) -> &mut CtapState { + pub fn state(&mut self) -> &mut CtapState { &mut self.state } diff --git a/src/main.rs b/src/main.rs index ab3be82..04f4c3b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -57,7 +57,7 @@ fn main() { } let boot_time = timer.get_current_clock().flex_unwrap(); - let env = TockEnv::new().unwrap(); + let env = TockEnv::new(); let mut ctap = ctap2::Ctap::new(env, boot_time); let mut led_counter = 0;