Merge branch 'develop' into firmware_protection
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
|
fuzz/corpus
|
||||||
target/
|
target/
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ cortex-m-rt = "*"
|
|||||||
cortex-m-rt-macros = "*"
|
cortex-m-rt-macros = "*"
|
||||||
panic-abort = "0.3.2"
|
panic-abort = "0.3.2"
|
||||||
rtt-target = { version = "*", features = ["cortex-m"] }
|
rtt-target = { version = "*", features = ["cortex-m"] }
|
||||||
|
tock-registers = { version = "0.6.0", features = ["no_std_unit_tests"] }
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
panic = "abort"
|
panic = "abort"
|
||||||
|
|||||||
118
bootloader/src/bitfields.rs
Normal file
118
bootloader/src/bitfields.rs
Normal file
@@ -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)
|
||||||
|
]
|
||||||
|
];
|
||||||
283
bootloader/src/crypto_cell.rs
Normal file
283
bootloader/src/crypto_cell.rs
Normal file
@@ -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 <jmichel@google.com>
|
||||||
|
//! * 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<CryptoCellRegisters>,
|
||||||
|
power: StaticRef<NordicCC310Registers>,
|
||||||
|
current_op: Cell<OperationMode>,
|
||||||
|
|
||||||
|
hash_ctx: Cell<[u32; 8]>,
|
||||||
|
hash_total_size: Cell<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
const CC310_BASE: StaticRef<CryptoCellRegisters> =
|
||||||
|
unsafe { StaticRef::new(0x5002B000 as *const CryptoCellRegisters) };
|
||||||
|
const CC310_POWER: StaticRef<NordicCC310Registers> =
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2021 Google LLC
|
// Copyright 2021-2022 Google LLC
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@@ -15,6 +15,11 @@
|
|||||||
#![no_main]
|
#![no_main]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
|
mod bitfields;
|
||||||
|
mod crypto_cell;
|
||||||
|
mod registers;
|
||||||
|
mod static_ref;
|
||||||
|
|
||||||
extern crate cortex_m;
|
extern crate cortex_m;
|
||||||
extern crate cortex_m_rt as rt;
|
extern crate cortex_m_rt as rt;
|
||||||
|
|
||||||
@@ -69,7 +74,7 @@ struct BootPartition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl BootPartition {
|
impl BootPartition {
|
||||||
const _FIRMWARE_LENGTH: usize = 0x00040000;
|
const FIRMWARE_LENGTH: usize = 0x00040000;
|
||||||
|
|
||||||
/// Reads the metadata, returns the timestamp if all checks pass.
|
/// Reads the metadata, returns the timestamp if all checks pass.
|
||||||
pub fn read_timestamp(&self) -> Result<u32, ()> {
|
pub fn read_timestamp(&self) -> Result<u32, ()> {
|
||||||
@@ -93,18 +98,40 @@ impl BootPartition {
|
|||||||
Ok(metadata.timestamp)
|
Ok(metadata.timestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Placeholder for the SHA256 implementation.
|
/// Computes the SHA256 of metadata information and partition data.
|
||||||
///
|
///
|
||||||
/// TODO implemented in next PR
|
/// Assumes that firmware address and length are divisible by the page size.
|
||||||
/// Without it, the bootloader will never boot anything.
|
/// This is the hardware implementation on the cryptocell.
|
||||||
fn compute_upgrade_hash(&self, _metadata_page: &[u8]) -> [u8; 32] {
|
#[allow(clippy::assertions_on_constants)]
|
||||||
[0; 32]
|
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.
|
/// Jump to the firmware.
|
||||||
pub fn boot(&self) -> ! {
|
pub fn boot(&self) -> ! {
|
||||||
let address = self.firmware_address;
|
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)]
|
#[cfg(debug_assertions)]
|
||||||
rprintln!("Boot jump to {:08X}", address);
|
rprintln!("Boot jump to {:08X}", address);
|
||||||
let address_pointer = address as *const u32;
|
let address_pointer = address as *const u32;
|
||||||
|
|||||||
139
bootloader/src/registers.rs
Normal file
139
bootloader/src/registers.rs
Normal file
@@ -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<u32, CryptoMode::Register>),
|
||||||
|
(0x0004 => _reserved0),
|
||||||
|
/// This register is set whent the cryptographic core is busy
|
||||||
|
(0x0010 => pub crypto_busy: ReadOnly<u32, Busy::Register>),
|
||||||
|
(0x0014 => _reserved1),
|
||||||
|
/// This register is set when the Hash engine is busy
|
||||||
|
(0x001C => pub hash_busy: ReadOnly<u32, Busy::Register>),
|
||||||
|
(0x0020 => @END),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
register_structs! {
|
||||||
|
pub CryptoCellDinRegisters {
|
||||||
|
(0x0000 => _reserved0),
|
||||||
|
/// Indicates whether memoty (AXI) source DMA (DIN) is busy
|
||||||
|
(0x0020 => pub mem_dma_busy: ReadOnly<u32, Busy::Register>),
|
||||||
|
(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<u32>),
|
||||||
|
/// 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<u32, LliWord1::Register>),
|
||||||
|
(0x0030 => @END),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
register_structs! {
|
||||||
|
pub CryptoCellHashRegisters {
|
||||||
|
/// Write initial hash value or read final hash value
|
||||||
|
(0x0000 => pub hash: [ReadWrite<u32>; 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<u32, Task::Register>),
|
||||||
|
(0x0048 => _reserved1),
|
||||||
|
/// Selects which HASH mode to run
|
||||||
|
(0x0180 => pub control: ReadWrite<u32, HashControl::Register>),
|
||||||
|
/// This register enables the hash hw padding.
|
||||||
|
(0x0184 => pub padding: ReadWrite<u32, Task::Register>),
|
||||||
|
/// HASH_PAD_CFG Register.
|
||||||
|
(0x0188 => pub pad_config: ReadWrite<u32, PaddingConfig::Register>),
|
||||||
|
/// This register hold the length of current hash operation
|
||||||
|
(0x018C => pub hash_len_lsb: ReadWrite<u32>),
|
||||||
|
/// This register hold the length of current hash operation
|
||||||
|
(0x0190 => pub hash_len_msb: ReadWrite<u32>),
|
||||||
|
(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<u32, Interrupts::Register>),
|
||||||
|
/// The Interrupt Mask register. Each bit of this register holds the mask of a single
|
||||||
|
/// interrupt source.
|
||||||
|
(0x0004 => pub interrupt_mask: ReadWrite<u32, Interrupts::Register>),
|
||||||
|
/// Interrupt Clear Register
|
||||||
|
(0x0008 => pub interrupt_clear: WriteOnly<u32, Interrupts::Register>),
|
||||||
|
/// This register defines the endianness of the Host-accessible registers.
|
||||||
|
(0x000C => pub endian: ReadWrite<u32, RgfEndianness::Register>),
|
||||||
|
(0x0010 => _reserved0),
|
||||||
|
/// This register holds the CryptoCell product signature.
|
||||||
|
(0x0024 => pub signature: ReadOnly<u32>),
|
||||||
|
(0x0028 => @END),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
register_structs! {
|
||||||
|
pub CryptoCellMiscRegisters {
|
||||||
|
(0x0000 => _reserved0),
|
||||||
|
/// The HASH clock enable register
|
||||||
|
(0x0018 => pub hash_clk_enable: ReadWrite<u32, Task::Register>),
|
||||||
|
/// The PKA clock enable register
|
||||||
|
(0x001C => _reserved1),
|
||||||
|
/// The DMA clock enable register
|
||||||
|
(0x0020 => pub dma_clk_enable: ReadWrite<u32, Task::Register>),
|
||||||
|
/// the CryptoCell clocks' status register
|
||||||
|
(0x0024 => @END),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
register_structs! {
|
||||||
|
pub NordicCC310Registers {
|
||||||
|
(0x0000 => pub enable: ReadWrite<u32, Task::Register>),
|
||||||
|
(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),
|
||||||
|
}
|
||||||
|
}
|
||||||
46
bootloader/src/static_ref.rs
Normal file
46
bootloader/src/static_ref.rs
Normal file
@@ -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<T> {
|
||||||
|
ptr: *const T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> StaticRef<T> {
|
||||||
|
/// 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<T> {
|
||||||
|
StaticRef { ptr }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Clone for StaticRef<T> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
StaticRef { ptr: self.ptr }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Copy for StaticRef<T> {}
|
||||||
|
|
||||||
|
impl<T> Deref for StaticRef<T> {
|
||||||
|
type Target = T;
|
||||||
|
fn deref(&self) -> &'static T {
|
||||||
|
unsafe { &*self.ptr }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -64,3 +64,32 @@ a few things you can personalize:
|
|||||||
* Whether you want to use batch attestation.
|
* Whether you want to use batch attestation.
|
||||||
* Whether you want to use signature counters.
|
* Whether you want to use signature counters.
|
||||||
* Various constants to adapt to different hardware.
|
* 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
|
||||||
|
```
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
extern crate lang_items;
|
extern crate lang_items;
|
||||||
|
|
||||||
use core::fmt::Write;
|
use core::fmt::Write;
|
||||||
use ctap2::env::tock::steal_storage;
|
use ctap2::env::tock::take_storage;
|
||||||
use libtock_drivers::console::Console;
|
use libtock_drivers::console::Console;
|
||||||
use libtock_drivers::led;
|
use libtock_drivers::led;
|
||||||
use libtock_drivers::result::FlexUnwrap;
|
use libtock_drivers::result::FlexUnwrap;
|
||||||
@@ -37,7 +37,7 @@ fn is_page_erased(storage: &dyn Storage, page: usize) -> bool {
|
|||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
led::get(1).flex_unwrap().on().flex_unwrap(); // red on dongle
|
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();
|
let num_pages = storage.num_pages();
|
||||||
writeln!(Console::new(), "Erase {} pages of storage:", num_pages).unwrap();
|
writeln!(Console::new(), "Erase {} pages of storage:", num_pages).unwrap();
|
||||||
for page in 0..num_pages {
|
for page in 0..num_pages {
|
||||||
|
|||||||
@@ -21,8 +21,7 @@ use alloc::string::{String, ToString};
|
|||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use alloc::{format, vec};
|
use alloc::{format, vec};
|
||||||
use core::fmt::Write;
|
use core::fmt::Write;
|
||||||
use ctap2::env::tock::{steal_storage, TockEnv};
|
use ctap2::env::tock::{take_storage, TockStorage};
|
||||||
use ctap2::env::Env;
|
|
||||||
use libtock_drivers::console::Console;
|
use libtock_drivers::console::Console;
|
||||||
use libtock_drivers::timer::{self, Duration, Timer, Timestamp};
|
use libtock_drivers::timer::{self, Duration, Timer, Timestamp};
|
||||||
use persistent_store::Store;
|
use persistent_store::Store;
|
||||||
@@ -40,10 +39,8 @@ fn measure<T>(timer: &Timer, operation: impl FnOnce() -> T) -> (T, Duration<f64>
|
|||||||
(result, after - before)
|
(result, after - before)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only use one store at a time.
|
fn boot_store(mut storage: TockStorage, erase: bool) -> Store<TockStorage> {
|
||||||
unsafe fn boot_store(erase: bool) -> Store<<TockEnv as Env>::Storage> {
|
|
||||||
use persistent_store::Storage;
|
use persistent_store::Storage;
|
||||||
let mut storage = steal_storage().unwrap();
|
|
||||||
let num_pages = storage.num_pages();
|
let num_pages = storage.num_pages();
|
||||||
if erase {
|
if erase {
|
||||||
for page in 0..num_pages {
|
for page in 0..num_pages {
|
||||||
@@ -58,9 +55,8 @@ struct StorageConfig {
|
|||||||
num_pages: usize,
|
num_pages: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn storage_config() -> StorageConfig {
|
fn storage_config(storage: &TockStorage) -> StorageConfig {
|
||||||
use persistent_store::Storage;
|
use persistent_store::Storage;
|
||||||
let storage = unsafe { steal_storage() }.unwrap();
|
|
||||||
StorageConfig {
|
StorageConfig {
|
||||||
num_pages: storage.num_pages(),
|
num_pages: storage.num_pages(),
|
||||||
}
|
}
|
||||||
@@ -77,11 +73,12 @@ struct Stat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn compute_latency(
|
fn compute_latency(
|
||||||
|
storage: TockStorage,
|
||||||
timer: &Timer,
|
timer: &Timer,
|
||||||
num_pages: usize,
|
num_pages: usize,
|
||||||
key_increment: usize,
|
key_increment: usize,
|
||||||
word_length: usize,
|
word_length: usize,
|
||||||
) -> Stat {
|
) -> (TockStorage, Stat) {
|
||||||
let mut stat = Stat {
|
let mut stat = Stat {
|
||||||
key_increment,
|
key_increment,
|
||||||
entry_length: word_length,
|
entry_length: word_length,
|
||||||
@@ -96,7 +93,7 @@ fn compute_latency(
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut store = unsafe { boot_store(true) };
|
let mut store = boot_store(storage, true);
|
||||||
let total_capacity = store.capacity().unwrap().total();
|
let total_capacity = store.capacity().unwrap().total();
|
||||||
assert_eq!(store.capacity().unwrap().used(), 0);
|
assert_eq!(store.capacity().unwrap().used(), 0);
|
||||||
assert_eq!(store.lifetime().unwrap().used(), 0);
|
assert_eq!(store.lifetime().unwrap().used(), 0);
|
||||||
@@ -130,7 +127,8 @@ fn compute_latency(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Measure latency of boot.
|
// 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();
|
writeln!(console, "Boot: {:.1}ms.", time.ms()).unwrap();
|
||||||
stat.boot_ms = time.ms();
|
stat.boot_ms = time.ms();
|
||||||
|
|
||||||
@@ -153,20 +151,23 @@ fn compute_latency(
|
|||||||
stat.compaction_ms = time.ms();
|
stat.compaction_ms = time.ms();
|
||||||
assert!(store.lifetime().unwrap().used() > total_capacity + num_pages);
|
assert!(store.lifetime().unwrap().used() > total_capacity + num_pages);
|
||||||
|
|
||||||
stat
|
(store.extract_storage(), stat)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut with_callback = timer::with_callback(|_, _| {});
|
let mut with_callback = timer::with_callback(|_, _| {});
|
||||||
let timer = with_callback.init().ok().unwrap();
|
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();
|
let mut stats = Vec::new();
|
||||||
|
|
||||||
writeln!(Console::new(), "\nRunning 2 tests...").unwrap();
|
writeln!(Console::new(), "\nRunning 2 tests...").unwrap();
|
||||||
// Simulate a store full of credentials (of 50 words).
|
// 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.
|
// 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();
|
writeln!(Console::new(), "\nDone.\n").unwrap();
|
||||||
|
|
||||||
const HEADERS: &[&str] = &[
|
const HEADERS: &[&str] = &[
|
||||||
|
|||||||
@@ -25,14 +25,12 @@ use ctap2::ctap::command::{
|
|||||||
};
|
};
|
||||||
use ctap2::ctap::hid::receive::MessageAssembler;
|
use ctap2::ctap::hid::receive::MessageAssembler;
|
||||||
use ctap2::ctap::hid::send::HidPacketIterator;
|
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::env::test::TestEnv;
|
||||||
use ctap2::Ctap;
|
use ctap2::Ctap;
|
||||||
use libtock_drivers::timer::{ClockValue, Timestamp};
|
use libtock_drivers::timer::{ClockValue, Timestamp};
|
||||||
|
|
||||||
const COMMAND_INIT: u8 = 0x06;
|
|
||||||
const CHANNEL_BROADCAST: ChannelID = [0xFF, 0xFF, 0xFF, 0xFF];
|
const CHANNEL_BROADCAST: ChannelID = [0xFF, 0xFF, 0xFF, 0xFF];
|
||||||
const PACKET_TYPE_MASK: u8 = 0x80;
|
|
||||||
|
|
||||||
const CLOCK_FREQUENCY_HZ: usize = 32768;
|
const CLOCK_FREQUENCY_HZ: usize = 32768;
|
||||||
const DUMMY_TIMESTAMP: Timestamp<isize> = Timestamp::from_ms(0);
|
const DUMMY_TIMESTAMP: Timestamp<isize> = Timestamp::from_ms(0);
|
||||||
@@ -53,13 +51,14 @@ fn raw_to_message(data: &[u8]) -> Message {
|
|||||||
cid[..data.len()].copy_from_slice(data);
|
cid[..data.len()].copy_from_slice(data);
|
||||||
Message {
|
Message {
|
||||||
cid,
|
cid,
|
||||||
cmd: 0,
|
// Arbitrary command.
|
||||||
|
cmd: CtapHidCommand::Cbor,
|
||||||
payload: vec![],
|
payload: vec![],
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Message {
|
Message {
|
||||||
cid: array_ref!(data, 0, 4).clone(),
|
cid: array_ref!(data, 0, 4).clone(),
|
||||||
cmd: data[4],
|
cmd: CtapHidCommand::from(data[4]),
|
||||||
payload: data[5..].to_vec(),
|
payload: data[5..].to_vec(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,7 +70,7 @@ fn initialize(ctap: &mut Ctap<TestEnv>) -> ChannelID {
|
|||||||
let nonce = vec![0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0];
|
let nonce = vec![0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0];
|
||||||
let message = Message {
|
let message = Message {
|
||||||
cid: CHANNEL_BROADCAST,
|
cid: CHANNEL_BROADCAST,
|
||||||
cmd: COMMAND_INIT,
|
cmd: CtapHidCommand::Init,
|
||||||
payload: nonce,
|
payload: nonce,
|
||||||
};
|
};
|
||||||
let mut assembler_reply = MessageAssembler::new();
|
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.
|
// 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]) {
|
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()) {
|
if let Some(hid_packet_iterator) = HidPacketIterator::new(message.clone()) {
|
||||||
let mut assembler = MessageAssembler::new();
|
let mut assembler = MessageAssembler::new();
|
||||||
let packets: Vec<HidPacket> = hid_packet_iterator.collect();
|
let packets: Vec<HidPacket> = hid_packet_iterator.collect();
|
||||||
@@ -176,7 +175,6 @@ pub fn split_assemble_hid_packets(data: &[u8]) {
|
|||||||
for packet in first_packets {
|
for packet in first_packets {
|
||||||
assert_eq!(assembler.parse_packet(packet, DUMMY_TIMESTAMP), Ok(None));
|
assert_eq!(assembler.parse_packet(packet, DUMMY_TIMESTAMP), Ok(None));
|
||||||
}
|
}
|
||||||
message.cmd &= !PACKET_TYPE_MASK;
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
assembler.parse_packet(last_packet, DUMMY_TIMESTAMP),
|
assembler.parse_packet(last_packet, DUMMY_TIMESTAMP),
|
||||||
Ok(Some(message))
|
Ok(Some(message))
|
||||||
|
|||||||
@@ -238,6 +238,11 @@ impl<S: Storage> Store<S> {
|
|||||||
Ok(store)
|
Ok(store)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extracts the storage.
|
||||||
|
pub fn extract_storage(self) -> S {
|
||||||
|
self.storage
|
||||||
|
}
|
||||||
|
|
||||||
/// Iterates over the entries.
|
/// Iterates over the entries.
|
||||||
pub fn iter<'a>(&'a self) -> StoreResult<StoreIter<'a>> {
|
pub fn iter<'a>(&'a self) -> StoreResult<StoreIter<'a>> {
|
||||||
let head = or_invalid(self.head)?;
|
let head = or_invalid(self.head)?;
|
||||||
@@ -1162,11 +1167,6 @@ impl Store<BufferStorage> {
|
|||||||
&mut self.storage
|
&mut self.storage
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extracts the storage.
|
|
||||||
pub fn extract_storage(self) -> BufferStorage {
|
|
||||||
self.storage
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the value of a possibly deleted entry.
|
/// Returns the value of a possibly deleted entry.
|
||||||
///
|
///
|
||||||
/// If the value has been partially compacted, only return the non-compacted part. Returns an
|
/// If the value has been partially compacted, only return the non-compacted part. Returns an
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ use super::data_formats::{
|
|||||||
use super::pin_protocol::{verify_pin_uv_auth_token, PinProtocol, SharedSecret};
|
use super::pin_protocol::{verify_pin_uv_auth_token, PinProtocol, SharedSecret};
|
||||||
use super::response::{AuthenticatorClientPinResponse, ResponseData};
|
use super::response::{AuthenticatorClientPinResponse, ResponseData};
|
||||||
use super::status_code::Ctap2StatusCode;
|
use super::status_code::Ctap2StatusCode;
|
||||||
use super::storage::PersistentStore;
|
|
||||||
use super::token_state::PinUvAuthTokenState;
|
use super::token_state::PinUvAuthTokenState;
|
||||||
|
use crate::ctap::storage;
|
||||||
use crate::env::Env;
|
use crate::env::Env;
|
||||||
use alloc::boxed::Box;
|
use alloc::boxed::Box;
|
||||||
use alloc::str;
|
use alloc::str;
|
||||||
@@ -76,13 +76,13 @@ fn decrypt_pin(
|
|||||||
/// The new PIN is passed encrypted, so it is first decrypted and stripped from
|
/// 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
|
/// padding. Next, it is checked against the PIN policy. Last, it is hashed and
|
||||||
/// truncated for persistent storage.
|
/// truncated for persistent storage.
|
||||||
fn check_and_store_new_pin<E: Env>(
|
fn check_and_store_new_pin(
|
||||||
persistent_store: &mut PersistentStore<E>,
|
env: &mut impl Env,
|
||||||
shared_secret: &dyn SharedSecret,
|
shared_secret: &dyn SharedSecret,
|
||||||
new_pin_enc: Vec<u8>,
|
new_pin_enc: Vec<u8>,
|
||||||
) -> Result<(), Ctap2StatusCode> {
|
) -> Result<(), Ctap2StatusCode> {
|
||||||
let pin = decrypt_pin(shared_secret, new_pin_enc)?;
|
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();
|
let pin_length = str::from_utf8(&pin).unwrap_or("").chars().count();
|
||||||
if pin_length < min_pin_length || pin.len() == PIN_PADDED_LENGTH {
|
if pin_length < min_pin_length || pin.len() == PIN_PADDED_LENGTH {
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_POLICY_VIOLATION);
|
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_POLICY_VIOLATION);
|
||||||
@@ -90,7 +90,7 @@ fn check_and_store_new_pin<E: Env>(
|
|||||||
let mut pin_hash = [0u8; PIN_AUTH_LENGTH];
|
let mut pin_hash = [0u8; PIN_AUTH_LENGTH];
|
||||||
pin_hash.copy_from_slice(&Sha256::hash(&pin[..])[..PIN_AUTH_LENGTH]);
|
pin_hash.copy_from_slice(&Sha256::hash(&pin[..])[..PIN_AUTH_LENGTH]);
|
||||||
// The PIN length is always < PIN_PADDED_LENGTH < 256.
|
// 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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,28 +156,27 @@ impl ClientPin {
|
|||||||
/// Decrypts the encrypted pin_hash and compares it to the stored pin_hash.
|
/// Decrypts the encrypted pin_hash and compares it to the stored pin_hash.
|
||||||
/// Resets or decreases the PIN retries, depending on success or failure.
|
/// Resets or decreases the PIN retries, depending on success or failure.
|
||||||
/// Also, in case of failure, the key agreement key is randomly reset.
|
/// Also, in case of failure, the key agreement key is randomly reset.
|
||||||
fn verify_pin_hash_enc<E: Env>(
|
fn verify_pin_hash_enc(
|
||||||
&mut self,
|
&mut self,
|
||||||
rng: &mut impl Rng256,
|
env: &mut impl Env,
|
||||||
persistent_store: &mut PersistentStore<E>,
|
|
||||||
pin_uv_auth_protocol: PinUvAuthProtocol,
|
pin_uv_auth_protocol: PinUvAuthProtocol,
|
||||||
shared_secret: &dyn SharedSecret,
|
shared_secret: &dyn SharedSecret,
|
||||||
pin_hash_enc: Vec<u8>,
|
pin_hash_enc: Vec<u8>,
|
||||||
) -> Result<(), Ctap2StatusCode> {
|
) -> Result<(), Ctap2StatusCode> {
|
||||||
match persistent_store.pin_hash()? {
|
match storage::pin_hash(env)? {
|
||||||
Some(pin_hash) => {
|
Some(pin_hash) => {
|
||||||
if self.consecutive_pin_mismatches >= 3 {
|
if self.consecutive_pin_mismatches >= 3 {
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_BLOCKED);
|
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_BLOCKED);
|
||||||
}
|
}
|
||||||
persistent_store.decr_pin_retries()?;
|
storage::decr_pin_retries(env)?;
|
||||||
let pin_hash_dec = shared_secret
|
let pin_hash_dec = shared_secret
|
||||||
.decrypt(&pin_hash_enc)
|
.decrypt(&pin_hash_enc)
|
||||||
.map_err(|_| Ctap2StatusCode::CTAP2_ERR_PIN_INVALID)?;
|
.map_err(|_| Ctap2StatusCode::CTAP2_ERR_PIN_INVALID)?;
|
||||||
|
|
||||||
if !bool::from(pin_hash.ct_eq(&pin_hash_dec)) {
|
if !bool::from(pin_hash.ct_eq(&pin_hash_dec)) {
|
||||||
self.get_mut_pin_protocol(pin_uv_auth_protocol)
|
self.get_mut_pin_protocol(pin_uv_auth_protocol)
|
||||||
.regenerate(rng);
|
.regenerate(env.rng());
|
||||||
if persistent_store.pin_retries()? == 0 {
|
if storage::pin_retries(env)? == 0 {
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_BLOCKED);
|
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_BLOCKED);
|
||||||
}
|
}
|
||||||
self.consecutive_pin_mismatches += 1;
|
self.consecutive_pin_mismatches += 1;
|
||||||
@@ -190,19 +189,19 @@ impl ClientPin {
|
|||||||
// This status code is not explicitly mentioned in the specification.
|
// This status code is not explicitly mentioned in the specification.
|
||||||
None => return Err(Ctap2StatusCode::CTAP2_ERR_PUAT_REQUIRED),
|
None => return Err(Ctap2StatusCode::CTAP2_ERR_PUAT_REQUIRED),
|
||||||
}
|
}
|
||||||
persistent_store.reset_pin_retries()?;
|
storage::reset_pin_retries(env)?;
|
||||||
self.consecutive_pin_mismatches = 0;
|
self.consecutive_pin_mismatches = 0;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_get_pin_retries<E: Env>(
|
fn process_get_pin_retries(
|
||||||
&self,
|
&self,
|
||||||
persistent_store: &PersistentStore<E>,
|
env: &mut impl Env,
|
||||||
) -> Result<AuthenticatorClientPinResponse, Ctap2StatusCode> {
|
) -> Result<AuthenticatorClientPinResponse, Ctap2StatusCode> {
|
||||||
Ok(AuthenticatorClientPinResponse {
|
Ok(AuthenticatorClientPinResponse {
|
||||||
key_agreement: None,
|
key_agreement: None,
|
||||||
pin_uv_auth_token: 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),
|
power_cycle_state: Some(self.consecutive_pin_mismatches >= 3),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -223,9 +222,9 @@ impl ClientPin {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_set_pin<E: Env>(
|
fn process_set_pin(
|
||||||
&mut self,
|
&mut self,
|
||||||
persistent_store: &mut PersistentStore<E>,
|
env: &mut impl Env,
|
||||||
client_pin_params: AuthenticatorClientPinParameters,
|
client_pin_params: AuthenticatorClientPinParameters,
|
||||||
) -> Result<(), Ctap2StatusCode> {
|
) -> Result<(), Ctap2StatusCode> {
|
||||||
let AuthenticatorClientPinParameters {
|
let AuthenticatorClientPinParameters {
|
||||||
@@ -239,21 +238,20 @@ impl ClientPin {
|
|||||||
let pin_uv_auth_param = ok_or_missing(pin_uv_auth_param)?;
|
let pin_uv_auth_param = ok_or_missing(pin_uv_auth_param)?;
|
||||||
let new_pin_enc = ok_or_missing(new_pin_enc)?;
|
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);
|
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID);
|
||||||
}
|
}
|
||||||
let shared_secret = self.get_shared_secret(pin_uv_auth_protocol, key_agreement)?;
|
let shared_secret = self.get_shared_secret(pin_uv_auth_protocol, key_agreement)?;
|
||||||
shared_secret.verify(&new_pin_enc, &pin_uv_auth_param)?;
|
shared_secret.verify(&new_pin_enc, &pin_uv_auth_param)?;
|
||||||
|
|
||||||
check_and_store_new_pin(persistent_store, shared_secret.as_ref(), new_pin_enc)?;
|
check_and_store_new_pin(env, shared_secret.as_ref(), new_pin_enc)?;
|
||||||
persistent_store.reset_pin_retries()?;
|
storage::reset_pin_retries(env)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_change_pin<E: Env>(
|
fn process_change_pin(
|
||||||
&mut self,
|
&mut self,
|
||||||
rng: &mut impl Rng256,
|
env: &mut impl Env,
|
||||||
persistent_store: &mut PersistentStore<E>,
|
|
||||||
client_pin_params: AuthenticatorClientPinParameters,
|
client_pin_params: AuthenticatorClientPinParameters,
|
||||||
) -> Result<(), Ctap2StatusCode> {
|
) -> Result<(), Ctap2StatusCode> {
|
||||||
let AuthenticatorClientPinParameters {
|
let AuthenticatorClientPinParameters {
|
||||||
@@ -269,7 +267,7 @@ impl ClientPin {
|
|||||||
let new_pin_enc = ok_or_missing(new_pin_enc)?;
|
let new_pin_enc = ok_or_missing(new_pin_enc)?;
|
||||||
let pin_hash_enc = ok_or_missing(pin_hash_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);
|
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_BLOCKED);
|
||||||
}
|
}
|
||||||
let shared_secret = self.get_shared_secret(pin_uv_auth_protocol, key_agreement)?;
|
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);
|
auth_param_data.extend(&pin_hash_enc);
|
||||||
shared_secret.verify(&auth_param_data, &pin_uv_auth_param)?;
|
shared_secret.verify(&auth_param_data, &pin_uv_auth_param)?;
|
||||||
self.verify_pin_hash_enc(
|
self.verify_pin_hash_enc(
|
||||||
rng,
|
env,
|
||||||
persistent_store,
|
|
||||||
pin_uv_auth_protocol,
|
pin_uv_auth_protocol,
|
||||||
shared_secret.as_ref(),
|
shared_secret.as_ref(),
|
||||||
pin_hash_enc,
|
pin_hash_enc,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
check_and_store_new_pin(persistent_store, shared_secret.as_ref(), new_pin_enc)?;
|
check_and_store_new_pin(env, shared_secret.as_ref(), new_pin_enc)?;
|
||||||
self.pin_protocol_v1.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(rng);
|
self.pin_protocol_v2.reset_pin_uv_auth_token(env.rng());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_get_pin_token<E: Env>(
|
fn process_get_pin_token(
|
||||||
&mut self,
|
&mut self,
|
||||||
rng: &mut impl Rng256,
|
env: &mut impl Env,
|
||||||
persistent_store: &mut PersistentStore<E>,
|
|
||||||
client_pin_params: AuthenticatorClientPinParameters,
|
client_pin_params: AuthenticatorClientPinParameters,
|
||||||
now: ClockValue,
|
now: ClockValue,
|
||||||
) -> Result<AuthenticatorClientPinResponse, Ctap2StatusCode> {
|
) -> Result<AuthenticatorClientPinResponse, Ctap2StatusCode> {
|
||||||
@@ -311,28 +307,27 @@ impl ClientPin {
|
|||||||
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER);
|
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);
|
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_BLOCKED);
|
||||||
}
|
}
|
||||||
let shared_secret = self.get_shared_secret(pin_uv_auth_protocol, key_agreement)?;
|
let shared_secret = self.get_shared_secret(pin_uv_auth_protocol, key_agreement)?;
|
||||||
self.verify_pin_hash_enc(
|
self.verify_pin_hash_enc(
|
||||||
rng,
|
env,
|
||||||
persistent_store,
|
|
||||||
pin_uv_auth_protocol,
|
pin_uv_auth_protocol,
|
||||||
shared_secret.as_ref(),
|
shared_secret.as_ref(),
|
||||||
pin_hash_enc,
|
pin_hash_enc,
|
||||||
)?;
|
)?;
|
||||||
if persistent_store.has_force_pin_change()? {
|
if storage::has_force_pin_change(env)? {
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID);
|
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.pin_protocol_v1.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(rng);
|
self.pin_protocol_v2.reset_pin_uv_auth_token(env.rng());
|
||||||
self.pin_uv_auth_token_state
|
self.pin_uv_auth_token_state
|
||||||
.begin_using_pin_uv_auth_token(now);
|
.begin_using_pin_uv_auth_token(now);
|
||||||
self.pin_uv_auth_token_state.set_default_permissions();
|
self.pin_uv_auth_token_state.set_default_permissions();
|
||||||
let pin_uv_auth_token = shared_secret.encrypt(
|
let pin_uv_auth_token = shared_secret.encrypt(
|
||||||
rng,
|
env.rng(),
|
||||||
self.get_pin_protocol(pin_uv_auth_protocol)
|
self.get_pin_protocol(pin_uv_auth_protocol)
|
||||||
.get_pin_uv_auth_token(),
|
.get_pin_uv_auth_token(),
|
||||||
)?;
|
)?;
|
||||||
@@ -360,10 +355,9 @@ impl ClientPin {
|
|||||||
Err(Ctap2StatusCode::CTAP2_ERR_INVALID_SUBCOMMAND)
|
Err(Ctap2StatusCode::CTAP2_ERR_INVALID_SUBCOMMAND)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_get_pin_uv_auth_token_using_pin_with_permissions<E: Env>(
|
fn process_get_pin_uv_auth_token_using_pin_with_permissions(
|
||||||
&mut self,
|
&mut self,
|
||||||
rng: &mut impl Rng256,
|
env: &mut impl Env,
|
||||||
persistent_store: &mut PersistentStore<E>,
|
|
||||||
mut client_pin_params: AuthenticatorClientPinParameters,
|
mut client_pin_params: AuthenticatorClientPinParameters,
|
||||||
now: ClockValue,
|
now: ClockValue,
|
||||||
) -> Result<AuthenticatorClientPinResponse, Ctap2StatusCode> {
|
) -> Result<AuthenticatorClientPinResponse, Ctap2StatusCode> {
|
||||||
@@ -381,7 +375,7 @@ impl ClientPin {
|
|||||||
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER);
|
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(permissions);
|
||||||
self.pin_uv_auth_token_state
|
self.pin_uv_auth_token_state
|
||||||
.set_permissions_rp_id(permissions_rp_id);
|
.set_permissions_rp_id(permissions_rp_id);
|
||||||
@@ -390,30 +384,27 @@ impl ClientPin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Processes the authenticatorClientPin command.
|
/// Processes the authenticatorClientPin command.
|
||||||
pub fn process_command<E: Env>(
|
pub fn process_command(
|
||||||
&mut self,
|
&mut self,
|
||||||
rng: &mut impl Rng256,
|
env: &mut impl Env,
|
||||||
persistent_store: &mut PersistentStore<E>,
|
|
||||||
client_pin_params: AuthenticatorClientPinParameters,
|
client_pin_params: AuthenticatorClientPinParameters,
|
||||||
now: ClockValue,
|
now: ClockValue,
|
||||||
) -> Result<ResponseData, Ctap2StatusCode> {
|
) -> Result<ResponseData, Ctap2StatusCode> {
|
||||||
let response = match client_pin_params.sub_command {
|
let response = match client_pin_params.sub_command {
|
||||||
ClientPinSubCommand::GetPinRetries => {
|
ClientPinSubCommand::GetPinRetries => Some(self.process_get_pin_retries(env)?),
|
||||||
Some(self.process_get_pin_retries(persistent_store)?)
|
|
||||||
}
|
|
||||||
ClientPinSubCommand::GetKeyAgreement => {
|
ClientPinSubCommand::GetKeyAgreement => {
|
||||||
Some(self.process_get_key_agreement(client_pin_params)?)
|
Some(self.process_get_key_agreement(client_pin_params)?)
|
||||||
}
|
}
|
||||||
ClientPinSubCommand::SetPin => {
|
ClientPinSubCommand::SetPin => {
|
||||||
self.process_set_pin(persistent_store, client_pin_params)?;
|
self.process_set_pin(env, client_pin_params)?;
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
ClientPinSubCommand::ChangePin => {
|
ClientPinSubCommand::ChangePin => {
|
||||||
self.process_change_pin(rng, persistent_store, client_pin_params)?;
|
self.process_change_pin(env, client_pin_params)?;
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
ClientPinSubCommand::GetPinToken => {
|
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(
|
ClientPinSubCommand::GetPinUvAuthTokenUsingUvWithPermissions => Some(
|
||||||
self.process_get_pin_uv_auth_token_using_uv_with_permissions(client_pin_params)?,
|
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::GetUvRetries => Some(self.process_get_uv_retries()?),
|
||||||
ClientPinSubCommand::GetPinUvAuthTokenUsingPinWithPermissions => Some(
|
ClientPinSubCommand::GetPinUvAuthTokenUsingPinWithPermissions => Some(
|
||||||
self.process_get_pin_uv_auth_token_using_pin_with_permissions(
|
self.process_get_pin_uv_auth_token_using_pin_with_permissions(
|
||||||
rng,
|
env,
|
||||||
persistent_store,
|
|
||||||
client_pin_params,
|
client_pin_params,
|
||||||
now,
|
now,
|
||||||
)?,
|
)?,
|
||||||
@@ -602,12 +592,12 @@ mod test {
|
|||||||
const DUMMY_CLOCK_VALUE: ClockValue = ClockValue::new(0, CLOCK_FREQUENCY_HZ);
|
const DUMMY_CLOCK_VALUE: ClockValue = ClockValue::new(0, CLOCK_FREQUENCY_HZ);
|
||||||
|
|
||||||
/// Stores a PIN hash corresponding to the dummy PIN "1234".
|
/// Stores a PIN hash corresponding to the dummy PIN "1234".
|
||||||
fn set_standard_pin<E: Env>(persistent_store: &mut PersistentStore<E>) {
|
fn set_standard_pin(env: &mut TestEnv) {
|
||||||
let mut pin = [0u8; 64];
|
let mut pin = [0u8; 64];
|
||||||
pin[..4].copy_from_slice(b"1234");
|
pin[..4].copy_from_slice(b"1234");
|
||||||
let mut pin_hash = [0u8; 16];
|
let mut pin_hash = [0u8; 16];
|
||||||
pin_hash.copy_from_slice(&Sha256::hash(&pin[..])[..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.
|
/// 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) {
|
fn test_helper_verify_pin_hash_enc(pin_uv_auth_protocol: PinUvAuthProtocol) {
|
||||||
let mut env = TestEnv::new();
|
let mut env = TestEnv::new();
|
||||||
let mut persistent_store = PersistentStore::new(&mut env);
|
|
||||||
let mut client_pin = ClientPin::new(env.rng());
|
let mut client_pin = ClientPin::new(env.rng());
|
||||||
let pin_protocol = client_pin.get_pin_protocol(pin_uv_auth_protocol);
|
let pin_protocol = client_pin.get_pin_protocol(pin_uv_auth_protocol);
|
||||||
let shared_secret = pin_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,
|
0x01, 0xD9, 0x88, 0x40, 0x50, 0xBB, 0xD0, 0x7A, 0x23, 0x1A, 0xEB, 0x69, 0xD8, 0x36,
|
||||||
0xC4, 0x12,
|
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
|
let pin_hash_enc = shared_secret
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@@ -752,8 +741,7 @@ mod test {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
client_pin.verify_pin_hash_enc(
|
client_pin.verify_pin_hash_enc(
|
||||||
env.rng(),
|
&mut env,
|
||||||
&mut persistent_store,
|
|
||||||
pin_uv_auth_protocol,
|
pin_uv_auth_protocol,
|
||||||
shared_secret.as_ref(),
|
shared_secret.as_ref(),
|
||||||
pin_hash_enc
|
pin_hash_enc
|
||||||
@@ -764,8 +752,7 @@ mod test {
|
|||||||
let pin_hash_enc = vec![0xEE; 16];
|
let pin_hash_enc = vec![0xEE; 16];
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
client_pin.verify_pin_hash_enc(
|
client_pin.verify_pin_hash_enc(
|
||||||
env.rng(),
|
&mut env,
|
||||||
&mut persistent_store,
|
|
||||||
pin_uv_auth_protocol,
|
pin_uv_auth_protocol,
|
||||||
shared_secret.as_ref(),
|
shared_secret.as_ref(),
|
||||||
pin_hash_enc
|
pin_hash_enc
|
||||||
@@ -780,8 +767,7 @@ mod test {
|
|||||||
client_pin.consecutive_pin_mismatches = 3;
|
client_pin.consecutive_pin_mismatches = 3;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
client_pin.verify_pin_hash_enc(
|
client_pin.verify_pin_hash_enc(
|
||||||
env.rng(),
|
&mut env,
|
||||||
&mut persistent_store,
|
|
||||||
pin_uv_auth_protocol,
|
pin_uv_auth_protocol,
|
||||||
shared_secret.as_ref(),
|
shared_secret.as_ref(),
|
||||||
pin_hash_enc
|
pin_hash_enc
|
||||||
@@ -793,8 +779,7 @@ mod test {
|
|||||||
let pin_hash_enc = vec![0x77; PIN_AUTH_LENGTH - 1];
|
let pin_hash_enc = vec![0x77; PIN_AUTH_LENGTH - 1];
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
client_pin.verify_pin_hash_enc(
|
client_pin.verify_pin_hash_enc(
|
||||||
env.rng(),
|
&mut env,
|
||||||
&mut persistent_store,
|
|
||||||
pin_uv_auth_protocol,
|
pin_uv_auth_protocol,
|
||||||
shared_secret.as_ref(),
|
shared_secret.as_ref(),
|
||||||
pin_hash_enc
|
pin_hash_enc
|
||||||
@@ -805,8 +790,7 @@ mod test {
|
|||||||
let pin_hash_enc = vec![0x77; PIN_AUTH_LENGTH + 1];
|
let pin_hash_enc = vec![0x77; PIN_AUTH_LENGTH + 1];
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
client_pin.verify_pin_hash_enc(
|
client_pin.verify_pin_hash_enc(
|
||||||
env.rng(),
|
&mut env,
|
||||||
&mut persistent_store,
|
|
||||||
pin_uv_auth_protocol,
|
pin_uv_auth_protocol,
|
||||||
shared_secret.as_ref(),
|
shared_secret.as_ref(),
|
||||||
pin_hash_enc
|
pin_hash_enc
|
||||||
@@ -831,20 +815,14 @@ mod test {
|
|||||||
ClientPinSubCommand::GetPinRetries,
|
ClientPinSubCommand::GetPinRetries,
|
||||||
);
|
);
|
||||||
let mut env = TestEnv::new();
|
let mut env = TestEnv::new();
|
||||||
let mut persistent_store = PersistentStore::new(&mut env);
|
|
||||||
let expected_response = Some(AuthenticatorClientPinResponse {
|
let expected_response = Some(AuthenticatorClientPinResponse {
|
||||||
key_agreement: None,
|
key_agreement: None,
|
||||||
pin_uv_auth_token: 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),
|
power_cycle_state: Some(false),
|
||||||
});
|
});
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
client_pin.process_command(
|
client_pin.process_command(&mut env, params.clone(), DUMMY_CLOCK_VALUE),
|
||||||
env.rng(),
|
|
||||||
&mut persistent_store,
|
|
||||||
params.clone(),
|
|
||||||
DUMMY_CLOCK_VALUE
|
|
||||||
),
|
|
||||||
Ok(ResponseData::AuthenticatorClientPin(expected_response))
|
Ok(ResponseData::AuthenticatorClientPin(expected_response))
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -852,11 +830,11 @@ mod test {
|
|||||||
let expected_response = Some(AuthenticatorClientPinResponse {
|
let expected_response = Some(AuthenticatorClientPinResponse {
|
||||||
key_agreement: None,
|
key_agreement: None,
|
||||||
pin_uv_auth_token: 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),
|
power_cycle_state: Some(true),
|
||||||
});
|
});
|
||||||
assert_eq!(
|
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))
|
Ok(ResponseData::AuthenticatorClientPin(expected_response))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -877,7 +855,6 @@ mod test {
|
|||||||
ClientPinSubCommand::GetKeyAgreement,
|
ClientPinSubCommand::GetKeyAgreement,
|
||||||
);
|
);
|
||||||
let mut env = TestEnv::new();
|
let mut env = TestEnv::new();
|
||||||
let mut persistent_store = PersistentStore::new(&mut env);
|
|
||||||
let expected_response = Some(AuthenticatorClientPinResponse {
|
let expected_response = Some(AuthenticatorClientPinResponse {
|
||||||
key_agreement: params.key_agreement.clone(),
|
key_agreement: params.key_agreement.clone(),
|
||||||
pin_uv_auth_token: None,
|
pin_uv_auth_token: None,
|
||||||
@@ -885,7 +862,7 @@ mod test {
|
|||||||
power_cycle_state: None,
|
power_cycle_state: None,
|
||||||
});
|
});
|
||||||
assert_eq!(
|
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))
|
Ok(ResponseData::AuthenticatorClientPin(expected_response))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -904,9 +881,8 @@ mod test {
|
|||||||
let (mut client_pin, params) =
|
let (mut client_pin, params) =
|
||||||
create_client_pin_and_parameters(pin_uv_auth_protocol, ClientPinSubCommand::SetPin);
|
create_client_pin_and_parameters(pin_uv_auth_protocol, ClientPinSubCommand::SetPin);
|
||||||
let mut env = TestEnv::new();
|
let mut env = TestEnv::new();
|
||||||
let mut persistent_store = PersistentStore::new(&mut env);
|
|
||||||
assert_eq!(
|
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))
|
Ok(ResponseData::AuthenticatorClientPin(None))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -932,40 +908,29 @@ mod test {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut env = TestEnv::new();
|
let mut env = TestEnv::new();
|
||||||
let mut persistent_store = PersistentStore::new(&mut env);
|
set_standard_pin(&mut env);
|
||||||
set_standard_pin(&mut persistent_store);
|
|
||||||
|
|
||||||
let mut auth_param_data = params.new_pin_enc.clone().unwrap();
|
let mut auth_param_data = params.new_pin_enc.clone().unwrap();
|
||||||
auth_param_data.extend(params.pin_hash_enc.as_ref().unwrap());
|
auth_param_data.extend(params.pin_hash_enc.as_ref().unwrap());
|
||||||
let pin_uv_auth_param = shared_secret.authenticate(&auth_param_data);
|
let pin_uv_auth_param = shared_secret.authenticate(&auth_param_data);
|
||||||
params.pin_uv_auth_param = Some(pin_uv_auth_param);
|
params.pin_uv_auth_param = Some(pin_uv_auth_param);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
client_pin.process_command(
|
client_pin.process_command(&mut env, params.clone(), DUMMY_CLOCK_VALUE),
|
||||||
env.rng(),
|
|
||||||
&mut persistent_store,
|
|
||||||
params.clone(),
|
|
||||||
DUMMY_CLOCK_VALUE
|
|
||||||
),
|
|
||||||
Ok(ResponseData::AuthenticatorClientPin(None))
|
Ok(ResponseData::AuthenticatorClientPin(None))
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut bad_params = params.clone();
|
let mut bad_params = params.clone();
|
||||||
bad_params.pin_hash_enc = Some(vec![0xEE; 16]);
|
bad_params.pin_hash_enc = Some(vec![0xEE; 16]);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
client_pin.process_command(
|
client_pin.process_command(&mut env, bad_params, DUMMY_CLOCK_VALUE),
|
||||||
env.rng(),
|
|
||||||
&mut persistent_store,
|
|
||||||
bad_params,
|
|
||||||
DUMMY_CLOCK_VALUE
|
|
||||||
),
|
|
||||||
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
|
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
|
||||||
);
|
);
|
||||||
|
|
||||||
while persistent_store.pin_retries().unwrap() > 0 {
|
while storage::pin_retries(&mut env).unwrap() > 0 {
|
||||||
persistent_store.decr_pin_retries().unwrap();
|
storage::decr_pin_retries(&mut env).unwrap();
|
||||||
}
|
}
|
||||||
assert_eq!(
|
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)
|
Err(Ctap2StatusCode::CTAP2_ERR_PIN_BLOCKED)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -993,16 +958,10 @@ mod test {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut env = TestEnv::new();
|
let mut env = TestEnv::new();
|
||||||
let mut persistent_store = PersistentStore::new(&mut env);
|
set_standard_pin(&mut env);
|
||||||
set_standard_pin(&mut persistent_store);
|
|
||||||
|
|
||||||
let response = client_pin
|
let response = client_pin
|
||||||
.process_command(
|
.process_command(&mut env, params.clone(), DUMMY_CLOCK_VALUE)
|
||||||
env.rng(),
|
|
||||||
&mut persistent_store,
|
|
||||||
params.clone(),
|
|
||||||
DUMMY_CLOCK_VALUE,
|
|
||||||
)
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let encrypted_token = match response {
|
let encrypted_token = match response {
|
||||||
ResponseData::AuthenticatorClientPin(Some(response)) => {
|
ResponseData::AuthenticatorClientPin(Some(response)) => {
|
||||||
@@ -1038,12 +997,7 @@ mod test {
|
|||||||
let mut bad_params = params;
|
let mut bad_params = params;
|
||||||
bad_params.pin_hash_enc = Some(vec![0xEE; 16]);
|
bad_params.pin_hash_enc = Some(vec![0xEE; 16]);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
client_pin.process_command(
|
client_pin.process_command(&mut env, bad_params, DUMMY_CLOCK_VALUE),
|
||||||
env.rng(),
|
|
||||||
&mut persistent_store,
|
|
||||||
bad_params,
|
|
||||||
DUMMY_CLOCK_VALUE
|
|
||||||
),
|
|
||||||
Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID)
|
Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1064,12 +1018,11 @@ mod test {
|
|||||||
ClientPinSubCommand::GetPinToken,
|
ClientPinSubCommand::GetPinToken,
|
||||||
);
|
);
|
||||||
let mut env = TestEnv::new();
|
let mut env = TestEnv::new();
|
||||||
let mut persistent_store = PersistentStore::new(&mut env);
|
set_standard_pin(&mut env);
|
||||||
set_standard_pin(&mut persistent_store);
|
|
||||||
|
|
||||||
assert_eq!(persistent_store.force_pin_change(), Ok(()));
|
assert_eq!(storage::force_pin_change(&mut env), Ok(()));
|
||||||
assert_eq!(
|
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),
|
Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1099,16 +1052,10 @@ mod test {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut env = TestEnv::new();
|
let mut env = TestEnv::new();
|
||||||
let mut persistent_store = PersistentStore::new(&mut env);
|
set_standard_pin(&mut env);
|
||||||
set_standard_pin(&mut persistent_store);
|
|
||||||
|
|
||||||
let response = client_pin
|
let response = client_pin
|
||||||
.process_command(
|
.process_command(&mut env, params.clone(), DUMMY_CLOCK_VALUE)
|
||||||
env.rng(),
|
|
||||||
&mut persistent_store,
|
|
||||||
params.clone(),
|
|
||||||
DUMMY_CLOCK_VALUE,
|
|
||||||
)
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let encrypted_token = match response {
|
let encrypted_token = match response {
|
||||||
ResponseData::AuthenticatorClientPin(Some(response)) => {
|
ResponseData::AuthenticatorClientPin(Some(response)) => {
|
||||||
@@ -1144,36 +1091,21 @@ mod test {
|
|||||||
let mut bad_params = params.clone();
|
let mut bad_params = params.clone();
|
||||||
bad_params.permissions = Some(0x00);
|
bad_params.permissions = Some(0x00);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
client_pin.process_command(
|
client_pin.process_command(&mut env, bad_params, DUMMY_CLOCK_VALUE),
|
||||||
env.rng(),
|
|
||||||
&mut persistent_store,
|
|
||||||
bad_params,
|
|
||||||
DUMMY_CLOCK_VALUE
|
|
||||||
),
|
|
||||||
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
|
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut bad_params = params.clone();
|
let mut bad_params = params.clone();
|
||||||
bad_params.permissions_rp_id = None;
|
bad_params.permissions_rp_id = None;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
client_pin.process_command(
|
client_pin.process_command(&mut env, bad_params, DUMMY_CLOCK_VALUE),
|
||||||
env.rng(),
|
|
||||||
&mut persistent_store,
|
|
||||||
bad_params,
|
|
||||||
DUMMY_CLOCK_VALUE
|
|
||||||
),
|
|
||||||
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
|
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut bad_params = params;
|
let mut bad_params = params;
|
||||||
bad_params.pin_hash_enc = Some(vec![0xEE; 16]);
|
bad_params.pin_hash_enc = Some(vec![0xEE; 16]);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
client_pin.process_command(
|
client_pin.process_command(&mut env, bad_params, DUMMY_CLOCK_VALUE),
|
||||||
env.rng(),
|
|
||||||
&mut persistent_store,
|
|
||||||
bad_params,
|
|
||||||
DUMMY_CLOCK_VALUE
|
|
||||||
),
|
|
||||||
Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID)
|
Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1196,12 +1128,11 @@ mod test {
|
|||||||
ClientPinSubCommand::GetPinUvAuthTokenUsingPinWithPermissions,
|
ClientPinSubCommand::GetPinUvAuthTokenUsingPinWithPermissions,
|
||||||
);
|
);
|
||||||
let mut env = TestEnv::new();
|
let mut env = TestEnv::new();
|
||||||
let mut persistent_store = PersistentStore::new(&mut env);
|
set_standard_pin(&mut env);
|
||||||
set_standard_pin(&mut persistent_store);
|
|
||||||
|
|
||||||
assert_eq!(persistent_store.force_pin_change(), Ok(()));
|
assert_eq!(storage::force_pin_change(&mut env), Ok(()));
|
||||||
assert_eq!(
|
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)
|
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) {
|
fn test_helper_check_and_store_new_pin(pin_uv_auth_protocol: PinUvAuthProtocol) {
|
||||||
let mut env = TestEnv::new();
|
let mut env = TestEnv::new();
|
||||||
let mut persistent_store = PersistentStore::new(&mut env);
|
|
||||||
let pin_protocol = PinProtocol::new(env.rng());
|
let pin_protocol = PinProtocol::new(env.rng());
|
||||||
let shared_secret = pin_protocol
|
let shared_secret = pin_protocol
|
||||||
.decapsulate(pin_protocol.get_public_key(), pin_uv_auth_protocol)
|
.decapsulate(pin_protocol.get_public_key(), pin_uv_auth_protocol)
|
||||||
@@ -1292,17 +1222,17 @@ mod test {
|
|||||||
),
|
),
|
||||||
];
|
];
|
||||||
for (pin, result) in test_cases {
|
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);
|
let new_pin_enc = encrypt_pin(shared_secret.as_ref(), pin);
|
||||||
|
|
||||||
assert_eq!(
|
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
|
result
|
||||||
);
|
);
|
||||||
if result.is_ok() {
|
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 {
|
} 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,
|
ClientPinSubCommand::GetPinUvAuthTokenUsingPinWithPermissions,
|
||||||
);
|
);
|
||||||
let mut env = TestEnv::new();
|
let mut env = TestEnv::new();
|
||||||
let mut persistent_store = PersistentStore::new(&mut env);
|
set_standard_pin(&mut env);
|
||||||
set_standard_pin(&mut persistent_store);
|
|
||||||
params.permissions = Some(0xFF);
|
params.permissions = Some(0xFF);
|
||||||
|
|
||||||
assert!(client_pin
|
assert!(client_pin
|
||||||
.process_command(env.rng(), &mut persistent_store, params, DUMMY_CLOCK_VALUE)
|
.process_command(&mut env, params, DUMMY_CLOCK_VALUE)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
for permission in PinPermission::into_enum_iter() {
|
for permission in PinPermission::into_enum_iter() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -1730,12 +1659,11 @@ mod test {
|
|||||||
ClientPinSubCommand::GetPinUvAuthTokenUsingPinWithPermissions,
|
ClientPinSubCommand::GetPinUvAuthTokenUsingPinWithPermissions,
|
||||||
);
|
);
|
||||||
let mut env = TestEnv::new();
|
let mut env = TestEnv::new();
|
||||||
let mut persistent_store = PersistentStore::new(&mut env);
|
set_standard_pin(&mut env);
|
||||||
set_standard_pin(&mut persistent_store);
|
|
||||||
params.permissions = Some(0xFF);
|
params.permissions = Some(0xFF);
|
||||||
|
|
||||||
assert!(client_pin
|
assert!(client_pin
|
||||||
.process_command(env.rng(), &mut persistent_store, params, DUMMY_CLOCK_VALUE)
|
.process_command(&mut env, params, DUMMY_CLOCK_VALUE)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
for permission in PinPermission::into_enum_iter() {
|
for permission in PinPermission::into_enum_iter() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|||||||
@@ -18,16 +18,16 @@ use super::customization::ENTERPRISE_ATTESTATION_MODE;
|
|||||||
use super::data_formats::{ConfigSubCommand, ConfigSubCommandParams, SetMinPinLengthParams};
|
use super::data_formats::{ConfigSubCommand, ConfigSubCommandParams, SetMinPinLengthParams};
|
||||||
use super::response::ResponseData;
|
use super::response::ResponseData;
|
||||||
use super::status_code::Ctap2StatusCode;
|
use super::status_code::Ctap2StatusCode;
|
||||||
use super::storage::PersistentStore;
|
use crate::ctap::storage;
|
||||||
use crate::env::Env;
|
use crate::env::Env;
|
||||||
use alloc::vec;
|
use alloc::vec;
|
||||||
|
|
||||||
/// Processes the subcommand enableEnterpriseAttestation for AuthenticatorConfig.
|
/// Processes the subcommand enableEnterpriseAttestation for AuthenticatorConfig.
|
||||||
fn process_enable_enterprise_attestation<E: Env>(
|
fn process_enable_enterprise_attestation(
|
||||||
persistent_store: &mut PersistentStore<E>,
|
env: &mut impl Env,
|
||||||
) -> Result<ResponseData, Ctap2StatusCode> {
|
) -> Result<ResponseData, Ctap2StatusCode> {
|
||||||
if ENTERPRISE_ATTESTATION_MODE.is_some() {
|
if ENTERPRISE_ATTESTATION_MODE.is_some() {
|
||||||
persistent_store.enable_enterprise_attestation()?;
|
storage::enable_enterprise_attestation(env)?;
|
||||||
Ok(ResponseData::AuthenticatorConfig)
|
Ok(ResponseData::AuthenticatorConfig)
|
||||||
} else {
|
} else {
|
||||||
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
|
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
|
||||||
@@ -35,16 +35,14 @@ fn process_enable_enterprise_attestation<E: Env>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Processes the subcommand toggleAlwaysUv for AuthenticatorConfig.
|
/// Processes the subcommand toggleAlwaysUv for AuthenticatorConfig.
|
||||||
fn process_toggle_always_uv<E: Env>(
|
fn process_toggle_always_uv(env: &mut impl Env) -> Result<ResponseData, Ctap2StatusCode> {
|
||||||
persistent_store: &mut PersistentStore<E>,
|
storage::toggle_always_uv(env)?;
|
||||||
) -> Result<ResponseData, Ctap2StatusCode> {
|
|
||||||
persistent_store.toggle_always_uv()?;
|
|
||||||
Ok(ResponseData::AuthenticatorConfig)
|
Ok(ResponseData::AuthenticatorConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Processes the subcommand setMinPINLength for AuthenticatorConfig.
|
/// Processes the subcommand setMinPINLength for AuthenticatorConfig.
|
||||||
fn process_set_min_pin_length<E: Env>(
|
fn process_set_min_pin_length(
|
||||||
persistent_store: &mut PersistentStore<E>,
|
env: &mut impl Env,
|
||||||
params: SetMinPinLengthParams,
|
params: SetMinPinLengthParams,
|
||||||
) -> Result<ResponseData, Ctap2StatusCode> {
|
) -> Result<ResponseData, Ctap2StatusCode> {
|
||||||
let SetMinPinLengthParams {
|
let SetMinPinLengthParams {
|
||||||
@@ -52,31 +50,31 @@ fn process_set_min_pin_length<E: Env>(
|
|||||||
min_pin_length_rp_ids,
|
min_pin_length_rp_ids,
|
||||||
force_change_pin,
|
force_change_pin,
|
||||||
} = params;
|
} = 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);
|
let new_min_pin_length = new_min_pin_length.unwrap_or(store_min_pin_length);
|
||||||
if new_min_pin_length < store_min_pin_length {
|
if new_min_pin_length < store_min_pin_length {
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_POLICY_VIOLATION);
|
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_POLICY_VIOLATION);
|
||||||
}
|
}
|
||||||
let mut force_change_pin = force_change_pin.unwrap_or(false);
|
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);
|
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;
|
force_change_pin |= new_min_pin_length > old_length;
|
||||||
}
|
}
|
||||||
if force_change_pin {
|
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 {
|
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)
|
Ok(ResponseData::AuthenticatorConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Processes the AuthenticatorConfig command.
|
/// Processes the AuthenticatorConfig command.
|
||||||
pub fn process_config<E: Env>(
|
pub fn process_config(
|
||||||
persistent_store: &mut PersistentStore<E>,
|
env: &mut impl Env,
|
||||||
client_pin: &mut ClientPin,
|
client_pin: &mut ClientPin,
|
||||||
params: AuthenticatorConfigParameters,
|
params: AuthenticatorConfigParameters,
|
||||||
) -> Result<ResponseData, Ctap2StatusCode> {
|
) -> Result<ResponseData, Ctap2StatusCode> {
|
||||||
@@ -87,9 +85,9 @@ pub fn process_config<E: Env>(
|
|||||||
pin_uv_auth_protocol,
|
pin_uv_auth_protocol,
|
||||||
} = params;
|
} = params;
|
||||||
|
|
||||||
let enforce_uv = !matches!(sub_command, ConfigSubCommand::ToggleAlwaysUv)
|
let enforce_uv =
|
||||||
&& persistent_store.has_always_uv()?;
|
!matches!(sub_command, ConfigSubCommand::ToggleAlwaysUv) && storage::has_always_uv(env)?;
|
||||||
if persistent_store.pin_hash()?.is_some() || enforce_uv {
|
if storage::pin_hash(env)?.is_some() || enforce_uv {
|
||||||
let pin_uv_auth_param =
|
let pin_uv_auth_param =
|
||||||
pin_uv_auth_param.ok_or(Ctap2StatusCode::CTAP2_ERR_PUAT_REQUIRED)?;
|
pin_uv_auth_param.ok_or(Ctap2StatusCode::CTAP2_ERR_PUAT_REQUIRED)?;
|
||||||
let pin_uv_auth_protocol =
|
let pin_uv_auth_protocol =
|
||||||
@@ -109,13 +107,11 @@ pub fn process_config<E: Env>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
match sub_command {
|
match sub_command {
|
||||||
ConfigSubCommand::EnableEnterpriseAttestation => {
|
ConfigSubCommand::EnableEnterpriseAttestation => process_enable_enterprise_attestation(env),
|
||||||
process_enable_enterprise_attestation(persistent_store)
|
ConfigSubCommand::ToggleAlwaysUv => process_toggle_always_uv(env),
|
||||||
}
|
|
||||||
ConfigSubCommand::ToggleAlwaysUv => process_toggle_always_uv(persistent_store),
|
|
||||||
ConfigSubCommand::SetMinPinLength => {
|
ConfigSubCommand::SetMinPinLength => {
|
||||||
if let Some(ConfigSubCommandParams::SetMinPinLength(params)) = sub_command_params {
|
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 {
|
} else {
|
||||||
Err(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)
|
Err(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)
|
||||||
}
|
}
|
||||||
@@ -135,7 +131,6 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_process_enable_enterprise_attestation() {
|
fn test_process_enable_enterprise_attestation() {
|
||||||
let mut env = TestEnv::new();
|
let mut env = TestEnv::new();
|
||||||
let mut persistent_store = PersistentStore::new(&mut env);
|
|
||||||
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
|
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
|
||||||
let pin_uv_auth_token = [0x55; 32];
|
let pin_uv_auth_token = [0x55; 32];
|
||||||
let mut client_pin =
|
let mut client_pin =
|
||||||
@@ -147,11 +142,11 @@ mod test {
|
|||||||
pin_uv_auth_param: None,
|
pin_uv_auth_param: None,
|
||||||
pin_uv_auth_protocol: 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() {
|
if ENTERPRISE_ATTESTATION_MODE.is_some() {
|
||||||
assert_eq!(config_response, Ok(ResponseData::AuthenticatorConfig));
|
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 {
|
} else {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
config_response,
|
config_response,
|
||||||
@@ -163,7 +158,6 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_process_toggle_always_uv() {
|
fn test_process_toggle_always_uv() {
|
||||||
let mut env = TestEnv::new();
|
let mut env = TestEnv::new();
|
||||||
let mut persistent_store = PersistentStore::new(&mut env);
|
|
||||||
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
|
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
|
||||||
let pin_uv_auth_token = [0x55; 32];
|
let pin_uv_auth_token = [0x55; 32];
|
||||||
let mut client_pin =
|
let mut client_pin =
|
||||||
@@ -175,9 +169,9 @@ mod test {
|
|||||||
pin_uv_auth_param: None,
|
pin_uv_auth_param: None,
|
||||||
pin_uv_auth_protocol: 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_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 {
|
let config_params = AuthenticatorConfigParameters {
|
||||||
sub_command: ConfigSubCommand::ToggleAlwaysUv,
|
sub_command: ConfigSubCommand::ToggleAlwaysUv,
|
||||||
@@ -185,7 +179,7 @@ mod test {
|
|||||||
pin_uv_auth_param: None,
|
pin_uv_auth_param: None,
|
||||||
pin_uv_auth_protocol: 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 {
|
if ENFORCE_ALWAYS_UV {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
config_response,
|
config_response,
|
||||||
@@ -193,18 +187,17 @@ mod test {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
assert_eq!(config_response, Ok(ResponseData::AuthenticatorConfig));
|
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) {
|
fn test_helper_process_toggle_always_uv_with_pin(pin_uv_auth_protocol: PinUvAuthProtocol) {
|
||||||
let mut env = TestEnv::new();
|
let mut env = TestEnv::new();
|
||||||
let mut persistent_store = PersistentStore::new(&mut env);
|
|
||||||
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
|
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
|
||||||
let pin_uv_auth_token = [0x55; 32];
|
let pin_uv_auth_token = [0x55; 32];
|
||||||
let mut client_pin =
|
let mut client_pin =
|
||||||
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, pin_uv_auth_protocol);
|
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];
|
let mut config_data = vec![0xFF; 32];
|
||||||
config_data.extend(&[0x0D, ConfigSubCommand::ToggleAlwaysUv as u8]);
|
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_param: Some(pin_uv_auth_param.clone()),
|
||||||
pin_uv_auth_protocol: Some(pin_uv_auth_protocol),
|
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 {
|
if ENFORCE_ALWAYS_UV {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
config_response,
|
config_response,
|
||||||
@@ -225,7 +218,7 @@ mod test {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
assert_eq!(config_response, Ok(ResponseData::AuthenticatorConfig));
|
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 {
|
let config_params = AuthenticatorConfigParameters {
|
||||||
sub_command: ConfigSubCommand::ToggleAlwaysUv,
|
sub_command: ConfigSubCommand::ToggleAlwaysUv,
|
||||||
@@ -233,9 +226,9 @@ mod test {
|
|||||||
pin_uv_auth_param: Some(pin_uv_auth_param),
|
pin_uv_auth_param: Some(pin_uv_auth_param),
|
||||||
pin_uv_auth_protocol: Some(pin_uv_auth_protocol),
|
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_eq!(config_response, Ok(ResponseData::AuthenticatorConfig));
|
||||||
assert!(!persistent_store.has_always_uv().unwrap());
|
assert!(!storage::has_always_uv(&mut env).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -270,7 +263,6 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_process_set_min_pin_length() {
|
fn test_process_set_min_pin_length() {
|
||||||
let mut env = TestEnv::new();
|
let mut env = TestEnv::new();
|
||||||
let mut persistent_store = PersistentStore::new(&mut env);
|
|
||||||
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
|
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
|
||||||
let pin_uv_auth_token = [0x55; 32];
|
let pin_uv_auth_token = [0x55; 32];
|
||||||
let mut client_pin =
|
let mut client_pin =
|
||||||
@@ -279,13 +271,13 @@ mod test {
|
|||||||
// First, increase minimum PIN length from 4 to 6 without PIN auth.
|
// First, increase minimum PIN length from 4 to 6 without PIN auth.
|
||||||
let min_pin_length = 6;
|
let min_pin_length = 6;
|
||||||
let config_params = create_min_pin_config_params(min_pin_length, None);
|
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!(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.
|
// 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.
|
// 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 min_pin_length = 8;
|
||||||
let mut config_params = create_min_pin_config_params(min_pin_length, None);
|
let mut config_params = create_min_pin_config_params(min_pin_length, None);
|
||||||
let pin_uv_auth_param = vec![
|
let pin_uv_auth_param = vec![
|
||||||
@@ -293,9 +285,9 @@ mod test {
|
|||||||
0xB2, 0xDE,
|
0xB2, 0xDE,
|
||||||
];
|
];
|
||||||
config_params.pin_uv_auth_param = Some(pin_uv_auth_param);
|
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!(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.
|
// Third, decreasing the minimum PIN length from 8 to 7 fails.
|
||||||
let mut config_params = create_min_pin_config_params(7, None);
|
let mut config_params = create_min_pin_config_params(7, None);
|
||||||
@@ -304,18 +296,17 @@ mod test {
|
|||||||
0xA7, 0x71,
|
0xA7, 0x71,
|
||||||
];
|
];
|
||||||
config_params.pin_uv_auth_param = Some(pin_uv_auth_param);
|
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!(
|
assert_eq!(
|
||||||
config_response,
|
config_response,
|
||||||
Err(Ctap2StatusCode::CTAP2_ERR_PIN_POLICY_VIOLATION)
|
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]
|
#[test]
|
||||||
fn test_process_set_min_pin_length_rp_ids() {
|
fn test_process_set_min_pin_length_rp_ids() {
|
||||||
let mut env = TestEnv::new();
|
let mut env = TestEnv::new();
|
||||||
let mut persistent_store = PersistentStore::new(&mut env);
|
|
||||||
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
|
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
|
||||||
let pin_uv_auth_token = [0x55; 32];
|
let pin_uv_auth_token = [0x55; 32];
|
||||||
let mut client_pin =
|
let mut client_pin =
|
||||||
@@ -326,11 +317,11 @@ mod test {
|
|||||||
let min_pin_length_rp_ids = vec!["example.com".to_string()];
|
let min_pin_length_rp_ids = vec!["example.com".to_string()];
|
||||||
let config_params =
|
let config_params =
|
||||||
create_min_pin_config_params(min_pin_length, Some(min_pin_length_rp_ids.clone()));
|
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!(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!(
|
assert_eq!(
|
||||||
persistent_store.min_pin_length_rp_ids(),
|
storage::min_pin_length_rp_ids(&mut env),
|
||||||
Ok(min_pin_length_rp_ids)
|
Ok(min_pin_length_rp_ids)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -338,7 +329,7 @@ mod test {
|
|||||||
let min_pin_length = 8;
|
let min_pin_length = 8;
|
||||||
let min_pin_length_rp_ids = vec!["another.example.com".to_string()];
|
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.
|
// 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 =
|
let mut config_params =
|
||||||
create_min_pin_config_params(min_pin_length, Some(min_pin_length_rp_ids.clone()));
|
create_min_pin_config_params(min_pin_length, Some(min_pin_length_rp_ids.clone()));
|
||||||
let pin_uv_auth_param = vec![
|
let pin_uv_auth_param = vec![
|
||||||
@@ -346,11 +337,11 @@ mod test {
|
|||||||
0xD6, 0xDA,
|
0xD6, 0xDA,
|
||||||
];
|
];
|
||||||
config_params.pin_uv_auth_param = Some(pin_uv_auth_param.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, Ok(ResponseData::AuthenticatorConfig));
|
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!(
|
assert_eq!(
|
||||||
persistent_store.min_pin_length_rp_ids(),
|
storage::min_pin_length_rp_ids(&mut env),
|
||||||
Ok(min_pin_length_rp_ids.clone())
|
Ok(min_pin_length_rp_ids.clone())
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -359,14 +350,14 @@ mod test {
|
|||||||
let mut config_params =
|
let mut config_params =
|
||||||
create_min_pin_config_params(9, Some(min_pin_length_rp_ids.clone()));
|
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());
|
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!(
|
assert_eq!(
|
||||||
config_response,
|
config_response,
|
||||||
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
|
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!(
|
assert_eq!(
|
||||||
persistent_store.min_pin_length_rp_ids(),
|
storage::min_pin_length_rp_ids(&mut env),
|
||||||
Ok(min_pin_length_rp_ids.clone())
|
Ok(min_pin_length_rp_ids.clone())
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -377,14 +368,14 @@ mod test {
|
|||||||
Some(vec!["counter.example.com".to_string()]),
|
Some(vec!["counter.example.com".to_string()]),
|
||||||
);
|
);
|
||||||
config_params.pin_uv_auth_param = Some(pin_uv_auth_param);
|
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!(
|
assert_eq!(
|
||||||
config_response,
|
config_response,
|
||||||
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
|
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!(
|
assert_eq!(
|
||||||
persistent_store.min_pin_length_rp_ids(),
|
storage::min_pin_length_rp_ids(&mut env),
|
||||||
Ok(min_pin_length_rp_ids)
|
Ok(min_pin_length_rp_ids)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -392,13 +383,12 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_process_set_min_pin_length_force_pin_change_implicit() {
|
fn test_process_set_min_pin_length_force_pin_change_implicit() {
|
||||||
let mut env = TestEnv::new();
|
let mut env = TestEnv::new();
|
||||||
let mut persistent_store = PersistentStore::new(&mut env);
|
|
||||||
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
|
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
|
||||||
let pin_uv_auth_token = [0x55; 32];
|
let pin_uv_auth_token = [0x55; 32];
|
||||||
let mut client_pin =
|
let mut client_pin =
|
||||||
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
|
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.
|
// Increase min PIN, force PIN change.
|
||||||
let min_pin_length = 6;
|
let min_pin_length = 6;
|
||||||
let mut config_params = create_min_pin_config_params(min_pin_length, None);
|
let mut config_params = create_min_pin_config_params(min_pin_length, None);
|
||||||
@@ -407,28 +397,27 @@ mod test {
|
|||||||
0xA8, 0xC8,
|
0xA8, 0xC8,
|
||||||
]);
|
]);
|
||||||
config_params.pin_uv_auth_param = pin_uv_auth_param;
|
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!(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.has_force_pin_change(), Ok(true));
|
assert_eq!(storage::has_force_pin_change(&mut env), Ok(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_process_set_min_pin_length_force_pin_change_explicit() {
|
fn test_process_set_min_pin_length_force_pin_change_explicit() {
|
||||||
let mut env = TestEnv::new();
|
let mut env = TestEnv::new();
|
||||||
let mut persistent_store = PersistentStore::new(&mut env);
|
|
||||||
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
|
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
|
||||||
let pin_uv_auth_token = [0x55; 32];
|
let pin_uv_auth_token = [0x55; 32];
|
||||||
let mut client_pin =
|
let mut client_pin =
|
||||||
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
|
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![
|
let pin_uv_auth_param = Some(vec![
|
||||||
0xE3, 0x74, 0xF4, 0x27, 0xBE, 0x7D, 0x40, 0xB5, 0x71, 0xB6, 0xB4, 0x1A, 0xD2, 0xC1,
|
0xE3, 0x74, 0xF4, 0x27, 0xBE, 0x7D, 0x40, 0xB5, 0x71, 0xB6, 0xB4, 0x1A, 0xD2, 0xC1,
|
||||||
0x53, 0xD7,
|
0x53, 0xD7,
|
||||||
]);
|
]);
|
||||||
let set_min_pin_length_params = SetMinPinLengthParams {
|
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,
|
min_pin_length_rp_ids: None,
|
||||||
force_change_pin: Some(true),
|
force_change_pin: Some(true),
|
||||||
};
|
};
|
||||||
@@ -440,15 +429,14 @@ mod test {
|
|||||||
pin_uv_auth_param,
|
pin_uv_auth_param,
|
||||||
pin_uv_auth_protocol: Some(PinUvAuthProtocol::V1),
|
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!(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]
|
#[test]
|
||||||
fn test_process_config_vendor_prototype() {
|
fn test_process_config_vendor_prototype() {
|
||||||
let mut env = TestEnv::new();
|
let mut env = TestEnv::new();
|
||||||
let mut persistent_store = PersistentStore::new(&mut env);
|
|
||||||
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
|
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
|
||||||
let pin_uv_auth_token = [0x55; 32];
|
let pin_uv_auth_token = [0x55; 32];
|
||||||
let mut client_pin =
|
let mut client_pin =
|
||||||
@@ -460,7 +448,7 @@ mod test {
|
|||||||
pin_uv_auth_param: None,
|
pin_uv_auth_param: None,
|
||||||
pin_uv_auth_protocol: 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!(
|
assert_eq!(
|
||||||
config_response,
|
config_response,
|
||||||
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
|
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ use super::data_formats::{
|
|||||||
};
|
};
|
||||||
use super::response::{AuthenticatorCredentialManagementResponse, ResponseData};
|
use super::response::{AuthenticatorCredentialManagementResponse, ResponseData};
|
||||||
use super::status_code::Ctap2StatusCode;
|
use super::status_code::Ctap2StatusCode;
|
||||||
use super::storage::PersistentStore;
|
|
||||||
use super::{StatefulCommand, StatefulPermission};
|
use super::{StatefulCommand, StatefulPermission};
|
||||||
|
use crate::ctap::storage;
|
||||||
use crate::env::Env;
|
use crate::env::Env;
|
||||||
use alloc::collections::BTreeSet;
|
use alloc::collections::BTreeSet;
|
||||||
use alloc::string::String;
|
use alloc::string::String;
|
||||||
@@ -33,12 +33,10 @@ use crypto::Hash256;
|
|||||||
use libtock_drivers::timer::ClockValue;
|
use libtock_drivers::timer::ClockValue;
|
||||||
|
|
||||||
/// Generates a set with all existing RP IDs.
|
/// Generates a set with all existing RP IDs.
|
||||||
fn get_stored_rp_ids<E: Env>(
|
fn get_stored_rp_ids(env: &mut impl Env) -> Result<BTreeSet<String>, Ctap2StatusCode> {
|
||||||
persistent_store: &PersistentStore<E>,
|
|
||||||
) -> Result<BTreeSet<String>, Ctap2StatusCode> {
|
|
||||||
let mut rp_set = BTreeSet::new();
|
let mut rp_set = BTreeSet::new();
|
||||||
let mut iter_result = Ok(());
|
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);
|
rp_set.insert(credential.rp_id);
|
||||||
}
|
}
|
||||||
iter_result?;
|
iter_result?;
|
||||||
@@ -109,8 +107,8 @@ fn enumerate_credentials_response(
|
|||||||
/// Check if the token permissions have the correct associated RP ID.
|
/// 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.
|
/// Either no RP ID is associated, or the RP ID matches the stored credential.
|
||||||
fn check_rp_id_permissions<E: Env>(
|
fn check_rp_id_permissions(
|
||||||
persistent_store: &mut PersistentStore<E>,
|
env: &mut impl Env,
|
||||||
client_pin: &mut ClientPin,
|
client_pin: &mut ClientPin,
|
||||||
credential_id: &[u8],
|
credential_id: &[u8],
|
||||||
) -> Result<(), Ctap2StatusCode> {
|
) -> Result<(), Ctap2StatusCode> {
|
||||||
@@ -118,30 +116,30 @@ fn check_rp_id_permissions<E: Env>(
|
|||||||
if client_pin.has_no_rp_id_permission().is_ok() {
|
if client_pin.has_no_rp_id_permission().is_ok() {
|
||||||
return 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)
|
client_pin.has_no_or_rp_id_permission(&credential.rp_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Processes the subcommand getCredsMetadata for CredentialManagement.
|
/// Processes the subcommand getCredsMetadata for CredentialManagement.
|
||||||
fn process_get_creds_metadata<E: Env>(
|
fn process_get_creds_metadata(
|
||||||
persistent_store: &PersistentStore<E>,
|
env: &mut impl Env,
|
||||||
) -> Result<AuthenticatorCredentialManagementResponse, Ctap2StatusCode> {
|
) -> Result<AuthenticatorCredentialManagementResponse, Ctap2StatusCode> {
|
||||||
Ok(AuthenticatorCredentialManagementResponse {
|
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(
|
max_possible_remaining_resident_credentials_count: Some(
|
||||||
persistent_store.remaining_credentials()? as u64,
|
storage::remaining_credentials(env)? as u64,
|
||||||
),
|
),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Processes the subcommand enumerateRPsBegin for CredentialManagement.
|
/// Processes the subcommand enumerateRPsBegin for CredentialManagement.
|
||||||
fn process_enumerate_rps_begin<E: Env>(
|
fn process_enumerate_rps_begin(
|
||||||
persistent_store: &PersistentStore<E>,
|
env: &mut impl Env,
|
||||||
stateful_command_permission: &mut StatefulPermission,
|
stateful_command_permission: &mut StatefulPermission,
|
||||||
now: ClockValue,
|
now: ClockValue,
|
||||||
) -> Result<AuthenticatorCredentialManagementResponse, Ctap2StatusCode> {
|
) -> Result<AuthenticatorCredentialManagementResponse, Ctap2StatusCode> {
|
||||||
let rp_set = get_stored_rp_ids(persistent_store)?;
|
let rp_set = get_stored_rp_ids(env)?;
|
||||||
let total_rps = rp_set.len();
|
let total_rps = rp_set.len();
|
||||||
|
|
||||||
if total_rps > 1 {
|
if total_rps > 1 {
|
||||||
@@ -156,12 +154,12 @@ fn process_enumerate_rps_begin<E: Env>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Processes the subcommand enumerateRPsGetNextRP for CredentialManagement.
|
/// Processes the subcommand enumerateRPsGetNextRP for CredentialManagement.
|
||||||
fn process_enumerate_rps_get_next_rp<E: Env>(
|
fn process_enumerate_rps_get_next_rp(
|
||||||
persistent_store: &PersistentStore<E>,
|
env: &mut impl Env,
|
||||||
stateful_command_permission: &mut StatefulPermission,
|
stateful_command_permission: &mut StatefulPermission,
|
||||||
) -> Result<AuthenticatorCredentialManagementResponse, Ctap2StatusCode> {
|
) -> Result<AuthenticatorCredentialManagementResponse, Ctap2StatusCode> {
|
||||||
let rp_id_index = stateful_command_permission.next_enumerate_rp()?;
|
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.
|
// A BTreeSet is already sorted.
|
||||||
let rp_id = rp_set
|
let rp_id = rp_set
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -171,8 +169,8 @@ fn process_enumerate_rps_get_next_rp<E: Env>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Processes the subcommand enumerateCredentialsBegin for CredentialManagement.
|
/// Processes the subcommand enumerateCredentialsBegin for CredentialManagement.
|
||||||
fn process_enumerate_credentials_begin<E: Env>(
|
fn process_enumerate_credentials_begin(
|
||||||
persistent_store: &PersistentStore<E>,
|
env: &mut impl Env,
|
||||||
stateful_command_permission: &mut StatefulPermission,
|
stateful_command_permission: &mut StatefulPermission,
|
||||||
client_pin: &mut ClientPin,
|
client_pin: &mut ClientPin,
|
||||||
sub_command_params: CredentialManagementSubCommandParameters,
|
sub_command_params: CredentialManagementSubCommandParameters,
|
||||||
@@ -183,7 +181,7 @@ fn process_enumerate_credentials_begin<E: Env>(
|
|||||||
.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?;
|
.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?;
|
||||||
client_pin.has_no_or_rp_id_hash_permission(&rp_id_hash[..])?;
|
client_pin.has_no_or_rp_id_hash_permission(&rp_id_hash[..])?;
|
||||||
let mut iter_result = Ok(());
|
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<usize> = iter
|
let mut rp_credentials: Vec<usize> = iter
|
||||||
.filter_map(|(key, credential)| {
|
.filter_map(|(key, credential)| {
|
||||||
let cred_rp_id_hash = Sha256::hash(credential.rp_id.as_bytes());
|
let cred_rp_id_hash = Sha256::hash(credential.rp_id.as_bytes());
|
||||||
@@ -199,7 +197,7 @@ fn process_enumerate_credentials_begin<E: Env>(
|
|||||||
let current_key = rp_credentials
|
let current_key = rp_credentials
|
||||||
.pop()
|
.pop()
|
||||||
.ok_or(Ctap2StatusCode::CTAP2_ERR_NO_CREDENTIALS)?;
|
.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 {
|
if total_credentials > 1 {
|
||||||
stateful_command_permission
|
stateful_command_permission
|
||||||
.set_command(now, StatefulCommand::EnumerateCredentials(rp_credentials));
|
.set_command(now, StatefulCommand::EnumerateCredentials(rp_credentials));
|
||||||
@@ -208,18 +206,18 @@ fn process_enumerate_credentials_begin<E: Env>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Processes the subcommand enumerateCredentialsGetNextCredential for CredentialManagement.
|
/// Processes the subcommand enumerateCredentialsGetNextCredential for CredentialManagement.
|
||||||
fn process_enumerate_credentials_get_next_credential<E: Env>(
|
fn process_enumerate_credentials_get_next_credential(
|
||||||
persistent_store: &PersistentStore<E>,
|
env: &mut impl Env,
|
||||||
stateful_command_permission: &mut StatefulPermission,
|
stateful_command_permission: &mut StatefulPermission,
|
||||||
) -> Result<AuthenticatorCredentialManagementResponse, Ctap2StatusCode> {
|
) -> Result<AuthenticatorCredentialManagementResponse, Ctap2StatusCode> {
|
||||||
let credential_key = stateful_command_permission.next_enumerate_credential()?;
|
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)
|
enumerate_credentials_response(credential, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Processes the subcommand deleteCredential for CredentialManagement.
|
/// Processes the subcommand deleteCredential for CredentialManagement.
|
||||||
fn process_delete_credential<E: Env>(
|
fn process_delete_credential(
|
||||||
persistent_store: &mut PersistentStore<E>,
|
env: &mut impl Env,
|
||||||
client_pin: &mut ClientPin,
|
client_pin: &mut ClientPin,
|
||||||
sub_command_params: CredentialManagementSubCommandParameters,
|
sub_command_params: CredentialManagementSubCommandParameters,
|
||||||
) -> Result<(), Ctap2StatusCode> {
|
) -> Result<(), Ctap2StatusCode> {
|
||||||
@@ -227,13 +225,13 @@ fn process_delete_credential<E: Env>(
|
|||||||
.credential_id
|
.credential_id
|
||||||
.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?
|
.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?
|
||||||
.key_id;
|
.key_id;
|
||||||
check_rp_id_permissions(persistent_store, client_pin, &credential_id)?;
|
check_rp_id_permissions(env, client_pin, &credential_id)?;
|
||||||
persistent_store.delete_credential(&credential_id)
|
storage::delete_credential(env, &credential_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Processes the subcommand updateUserInformation for CredentialManagement.
|
/// Processes the subcommand updateUserInformation for CredentialManagement.
|
||||||
fn process_update_user_information<E: Env>(
|
fn process_update_user_information(
|
||||||
persistent_store: &mut PersistentStore<E>,
|
env: &mut impl Env,
|
||||||
client_pin: &mut ClientPin,
|
client_pin: &mut ClientPin,
|
||||||
sub_command_params: CredentialManagementSubCommandParameters,
|
sub_command_params: CredentialManagementSubCommandParameters,
|
||||||
) -> Result<(), Ctap2StatusCode> {
|
) -> Result<(), Ctap2StatusCode> {
|
||||||
@@ -244,13 +242,13 @@ fn process_update_user_information<E: Env>(
|
|||||||
let user = sub_command_params
|
let user = sub_command_params
|
||||||
.user
|
.user
|
||||||
.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?;
|
.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?;
|
||||||
check_rp_id_permissions(persistent_store, client_pin, &credential_id)?;
|
check_rp_id_permissions(env, client_pin, &credential_id)?;
|
||||||
persistent_store.update_credential(&credential_id, user)
|
storage::update_credential(env, &credential_id, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Processes the CredentialManagement command and all its subcommands.
|
/// Processes the CredentialManagement command and all its subcommands.
|
||||||
pub fn process_credential_management<E: Env>(
|
pub fn process_credential_management(
|
||||||
persistent_store: &mut PersistentStore<E>,
|
env: &mut impl Env,
|
||||||
stateful_command_permission: &mut StatefulPermission,
|
stateful_command_permission: &mut StatefulPermission,
|
||||||
client_pin: &mut ClientPin,
|
client_pin: &mut ClientPin,
|
||||||
cred_management_params: AuthenticatorCredentialManagementParameters,
|
cred_management_params: AuthenticatorCredentialManagementParameters,
|
||||||
@@ -306,37 +304,34 @@ pub fn process_credential_management<E: Env>(
|
|||||||
let response = match sub_command {
|
let response = match sub_command {
|
||||||
CredentialManagementSubCommand::GetCredsMetadata => {
|
CredentialManagementSubCommand::GetCredsMetadata => {
|
||||||
client_pin.has_no_rp_id_permission()?;
|
client_pin.has_no_rp_id_permission()?;
|
||||||
Some(process_get_creds_metadata(persistent_store)?)
|
Some(process_get_creds_metadata(env)?)
|
||||||
}
|
}
|
||||||
CredentialManagementSubCommand::EnumerateRpsBegin => {
|
CredentialManagementSubCommand::EnumerateRpsBegin => {
|
||||||
client_pin.has_no_rp_id_permission()?;
|
client_pin.has_no_rp_id_permission()?;
|
||||||
Some(process_enumerate_rps_begin(
|
Some(process_enumerate_rps_begin(
|
||||||
persistent_store,
|
env,
|
||||||
stateful_command_permission,
|
stateful_command_permission,
|
||||||
now,
|
now,
|
||||||
)?)
|
)?)
|
||||||
}
|
}
|
||||||
CredentialManagementSubCommand::EnumerateRpsGetNextRp => Some(
|
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 => {
|
CredentialManagementSubCommand::EnumerateCredentialsBegin => {
|
||||||
Some(process_enumerate_credentials_begin(
|
Some(process_enumerate_credentials_begin(
|
||||||
persistent_store,
|
env,
|
||||||
stateful_command_permission,
|
stateful_command_permission,
|
||||||
client_pin,
|
client_pin,
|
||||||
sub_command_params.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?,
|
sub_command_params.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?,
|
||||||
now,
|
now,
|
||||||
)?)
|
)?)
|
||||||
}
|
}
|
||||||
CredentialManagementSubCommand::EnumerateCredentialsGetNextCredential => {
|
CredentialManagementSubCommand::EnumerateCredentialsGetNextCredential => Some(
|
||||||
Some(process_enumerate_credentials_get_next_credential(
|
process_enumerate_credentials_get_next_credential(env, stateful_command_permission)?,
|
||||||
persistent_store,
|
),
|
||||||
stateful_command_permission,
|
|
||||||
)?)
|
|
||||||
}
|
|
||||||
CredentialManagementSubCommand::DeleteCredential => {
|
CredentialManagementSubCommand::DeleteCredential => {
|
||||||
process_delete_credential(
|
process_delete_credential(
|
||||||
persistent_store,
|
env,
|
||||||
client_pin,
|
client_pin,
|
||||||
sub_command_params.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?,
|
sub_command_params.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?,
|
||||||
)?;
|
)?;
|
||||||
@@ -344,7 +339,7 @@ pub fn process_credential_management<E: Env>(
|
|||||||
}
|
}
|
||||||
CredentialManagementSubCommand::UpdateUserInformation => {
|
CredentialManagementSubCommand::UpdateUserInformation => {
|
||||||
process_update_user_information(
|
process_update_user_information(
|
||||||
persistent_store,
|
env,
|
||||||
client_pin,
|
client_pin,
|
||||||
sub_command_params.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?,
|
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);
|
let mut ctap_state = CtapState::new(&mut env, DUMMY_CLOCK_VALUE);
|
||||||
ctap_state.client_pin = client_pin;
|
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 management_data = vec![CredentialManagementSubCommand::GetCredsMetadata as u8];
|
||||||
let pin_uv_auth_param = authenticate_pin_uv_auth_token(
|
let pin_uv_auth_param = authenticate_pin_uv_auth_token(
|
||||||
&pin_uv_auth_token,
|
&pin_uv_auth_token,
|
||||||
@@ -411,7 +406,7 @@ mod test {
|
|||||||
pin_uv_auth_param: Some(pin_uv_auth_param.clone()),
|
pin_uv_auth_param: Some(pin_uv_auth_param.clone()),
|
||||||
};
|
};
|
||||||
let cred_management_response = process_credential_management(
|
let cred_management_response = process_credential_management(
|
||||||
&mut ctap_state.persistent_store,
|
&mut env,
|
||||||
&mut ctap_state.stateful_command_permission,
|
&mut ctap_state.stateful_command_permission,
|
||||||
&mut ctap_state.client_pin,
|
&mut ctap_state.client_pin,
|
||||||
cred_management_params,
|
cred_management_params,
|
||||||
@@ -427,10 +422,7 @@ mod test {
|
|||||||
_ => panic!("Invalid response type"),
|
_ => panic!("Invalid response type"),
|
||||||
};
|
};
|
||||||
|
|
||||||
ctap_state
|
storage::store_credential(&mut env, credential_source).unwrap();
|
||||||
.persistent_store
|
|
||||||
.store_credential(credential_source)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let cred_management_params = AuthenticatorCredentialManagementParameters {
|
let cred_management_params = AuthenticatorCredentialManagementParameters {
|
||||||
sub_command: CredentialManagementSubCommand::GetCredsMetadata,
|
sub_command: CredentialManagementSubCommand::GetCredsMetadata,
|
||||||
@@ -439,7 +431,7 @@ mod test {
|
|||||||
pin_uv_auth_param: Some(pin_uv_auth_param),
|
pin_uv_auth_param: Some(pin_uv_auth_param),
|
||||||
};
|
};
|
||||||
let cred_management_response = process_credential_management(
|
let cred_management_response = process_credential_management(
|
||||||
&mut ctap_state.persistent_store,
|
&mut env,
|
||||||
&mut ctap_state.stateful_command_permission,
|
&mut ctap_state.stateful_command_permission,
|
||||||
&mut ctap_state.client_pin,
|
&mut ctap_state.client_pin,
|
||||||
cred_management_params,
|
cred_management_params,
|
||||||
@@ -481,16 +473,10 @@ mod test {
|
|||||||
let mut ctap_state = CtapState::new(&mut env, DUMMY_CLOCK_VALUE);
|
let mut ctap_state = CtapState::new(&mut env, DUMMY_CLOCK_VALUE);
|
||||||
ctap_state.client_pin = client_pin;
|
ctap_state.client_pin = client_pin;
|
||||||
|
|
||||||
ctap_state
|
storage::store_credential(&mut env, credential_source1).unwrap();
|
||||||
.persistent_store
|
storage::store_credential(&mut env, credential_source2).unwrap();
|
||||||
.store_credential(credential_source1)
|
|
||||||
.unwrap();
|
|
||||||
ctap_state
|
|
||||||
.persistent_store
|
|
||||||
.store_credential(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![
|
let pin_uv_auth_param = Some(vec![
|
||||||
0x1A, 0xA4, 0x96, 0xDA, 0x62, 0x80, 0x28, 0x13, 0xEB, 0x32, 0xB9, 0xF1, 0xD2, 0xA9,
|
0x1A, 0xA4, 0x96, 0xDA, 0x62, 0x80, 0x28, 0x13, 0xEB, 0x32, 0xB9, 0xF1, 0xD2, 0xA9,
|
||||||
0xD0, 0xD1,
|
0xD0, 0xD1,
|
||||||
@@ -503,7 +489,7 @@ mod test {
|
|||||||
pin_uv_auth_param,
|
pin_uv_auth_param,
|
||||||
};
|
};
|
||||||
let cred_management_response = process_credential_management(
|
let cred_management_response = process_credential_management(
|
||||||
&mut ctap_state.persistent_store,
|
&mut env,
|
||||||
&mut ctap_state.stateful_command_permission,
|
&mut ctap_state.stateful_command_permission,
|
||||||
&mut ctap_state.client_pin,
|
&mut ctap_state.client_pin,
|
||||||
cred_management_params,
|
cred_management_params,
|
||||||
@@ -527,7 +513,7 @@ mod test {
|
|||||||
pin_uv_auth_param: None,
|
pin_uv_auth_param: None,
|
||||||
};
|
};
|
||||||
let cred_management_response = process_credential_management(
|
let cred_management_response = process_credential_management(
|
||||||
&mut ctap_state.persistent_store,
|
&mut env,
|
||||||
&mut ctap_state.stateful_command_permission,
|
&mut ctap_state.stateful_command_permission,
|
||||||
&mut ctap_state.client_pin,
|
&mut ctap_state.client_pin,
|
||||||
cred_management_params,
|
cred_management_params,
|
||||||
@@ -552,7 +538,7 @@ mod test {
|
|||||||
pin_uv_auth_param: None,
|
pin_uv_auth_param: None,
|
||||||
};
|
};
|
||||||
let cred_management_response = process_credential_management(
|
let cred_management_response = process_credential_management(
|
||||||
&mut ctap_state.persistent_store,
|
&mut env,
|
||||||
&mut ctap_state.stateful_command_permission,
|
&mut ctap_state.stateful_command_permission,
|
||||||
&mut ctap_state.client_pin,
|
&mut ctap_state.client_pin,
|
||||||
cred_management_params,
|
cred_management_params,
|
||||||
@@ -580,13 +566,10 @@ mod test {
|
|||||||
for i in 0..NUM_CREDENTIALS {
|
for i in 0..NUM_CREDENTIALS {
|
||||||
let mut credential = credential_source.clone();
|
let mut credential = credential_source.clone();
|
||||||
credential.rp_id = i.to_string();
|
credential.rp_id = i.to_string();
|
||||||
ctap_state
|
storage::store_credential(&mut env, credential).unwrap();
|
||||||
.persistent_store
|
|
||||||
.store_credential(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![
|
let pin_uv_auth_param = Some(vec![
|
||||||
0x1A, 0xA4, 0x96, 0xDA, 0x62, 0x80, 0x28, 0x13, 0xEB, 0x32, 0xB9, 0xF1, 0xD2, 0xA9,
|
0x1A, 0xA4, 0x96, 0xDA, 0x62, 0x80, 0x28, 0x13, 0xEB, 0x32, 0xB9, 0xF1, 0xD2, 0xA9,
|
||||||
0xD0, 0xD1,
|
0xD0, 0xD1,
|
||||||
@@ -604,7 +587,7 @@ mod test {
|
|||||||
|
|
||||||
for _ in 0..NUM_CREDENTIALS {
|
for _ in 0..NUM_CREDENTIALS {
|
||||||
let cred_management_response = process_credential_management(
|
let cred_management_response = process_credential_management(
|
||||||
&mut ctap_state.persistent_store,
|
&mut env,
|
||||||
&mut ctap_state.stateful_command_permission,
|
&mut ctap_state.stateful_command_permission,
|
||||||
&mut ctap_state.client_pin,
|
&mut ctap_state.client_pin,
|
||||||
cred_management_params,
|
cred_management_params,
|
||||||
@@ -634,7 +617,7 @@ mod test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let cred_management_response = process_credential_management(
|
let cred_management_response = process_credential_management(
|
||||||
&mut ctap_state.persistent_store,
|
&mut env,
|
||||||
&mut ctap_state.stateful_command_permission,
|
&mut ctap_state.stateful_command_permission,
|
||||||
&mut ctap_state.client_pin,
|
&mut ctap_state.client_pin,
|
||||||
cred_management_params,
|
cred_management_params,
|
||||||
@@ -663,16 +646,10 @@ mod test {
|
|||||||
let mut ctap_state = CtapState::new(&mut env, DUMMY_CLOCK_VALUE);
|
let mut ctap_state = CtapState::new(&mut env, DUMMY_CLOCK_VALUE);
|
||||||
ctap_state.client_pin = client_pin;
|
ctap_state.client_pin = client_pin;
|
||||||
|
|
||||||
ctap_state
|
storage::store_credential(&mut env, credential_source1).unwrap();
|
||||||
.persistent_store
|
storage::store_credential(&mut env, credential_source2).unwrap();
|
||||||
.store_credential(credential_source1)
|
|
||||||
.unwrap();
|
|
||||||
ctap_state
|
|
||||||
.persistent_store
|
|
||||||
.store_credential(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![
|
let pin_uv_auth_param = Some(vec![
|
||||||
0xF8, 0xB0, 0x3C, 0xC1, 0xD5, 0x58, 0x9C, 0xB7, 0x4D, 0x42, 0xA1, 0x64, 0x14, 0x28,
|
0xF8, 0xB0, 0x3C, 0xC1, 0xD5, 0x58, 0x9C, 0xB7, 0x4D, 0x42, 0xA1, 0x64, 0x14, 0x28,
|
||||||
0x2B, 0x68,
|
0x2B, 0x68,
|
||||||
@@ -692,7 +669,7 @@ mod test {
|
|||||||
pin_uv_auth_param,
|
pin_uv_auth_param,
|
||||||
};
|
};
|
||||||
let cred_management_response = process_credential_management(
|
let cred_management_response = process_credential_management(
|
||||||
&mut ctap_state.persistent_store,
|
&mut env,
|
||||||
&mut ctap_state.stateful_command_permission,
|
&mut ctap_state.stateful_command_permission,
|
||||||
&mut ctap_state.client_pin,
|
&mut ctap_state.client_pin,
|
||||||
cred_management_params,
|
cred_management_params,
|
||||||
@@ -715,7 +692,7 @@ mod test {
|
|||||||
pin_uv_auth_param: None,
|
pin_uv_auth_param: None,
|
||||||
};
|
};
|
||||||
let cred_management_response = process_credential_management(
|
let cred_management_response = process_credential_management(
|
||||||
&mut ctap_state.persistent_store,
|
&mut env,
|
||||||
&mut ctap_state.stateful_command_permission,
|
&mut ctap_state.stateful_command_permission,
|
||||||
&mut ctap_state.client_pin,
|
&mut ctap_state.client_pin,
|
||||||
cred_management_params,
|
cred_management_params,
|
||||||
@@ -739,7 +716,7 @@ mod test {
|
|||||||
pin_uv_auth_param: None,
|
pin_uv_auth_param: None,
|
||||||
};
|
};
|
||||||
let cred_management_response = process_credential_management(
|
let cred_management_response = process_credential_management(
|
||||||
&mut ctap_state.persistent_store,
|
&mut env,
|
||||||
&mut ctap_state.stateful_command_permission,
|
&mut ctap_state.stateful_command_permission,
|
||||||
&mut ctap_state.client_pin,
|
&mut ctap_state.client_pin,
|
||||||
cred_management_params,
|
cred_management_params,
|
||||||
@@ -764,12 +741,9 @@ mod test {
|
|||||||
let mut ctap_state = CtapState::new(&mut env, DUMMY_CLOCK_VALUE);
|
let mut ctap_state = CtapState::new(&mut env, DUMMY_CLOCK_VALUE);
|
||||||
ctap_state.client_pin = client_pin;
|
ctap_state.client_pin = client_pin;
|
||||||
|
|
||||||
ctap_state
|
storage::store_credential(&mut env, credential_source).unwrap();
|
||||||
.persistent_store
|
|
||||||
.store_credential(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![
|
let pin_uv_auth_param = Some(vec![
|
||||||
0xBD, 0xE3, 0xEF, 0x8A, 0x77, 0x01, 0xB1, 0x69, 0x19, 0xE6, 0x62, 0xB9, 0x9B, 0x89,
|
0xBD, 0xE3, 0xEF, 0x8A, 0x77, 0x01, 0xB1, 0x69, 0x19, 0xE6, 0x62, 0xB9, 0x9B, 0x89,
|
||||||
0x9C, 0x64,
|
0x9C, 0x64,
|
||||||
@@ -792,7 +766,7 @@ mod test {
|
|||||||
pin_uv_auth_param: pin_uv_auth_param.clone(),
|
pin_uv_auth_param: pin_uv_auth_param.clone(),
|
||||||
};
|
};
|
||||||
let cred_management_response = process_credential_management(
|
let cred_management_response = process_credential_management(
|
||||||
&mut ctap_state.persistent_store,
|
&mut env,
|
||||||
&mut ctap_state.stateful_command_permission,
|
&mut ctap_state.stateful_command_permission,
|
||||||
&mut ctap_state.client_pin,
|
&mut ctap_state.client_pin,
|
||||||
cred_management_params,
|
cred_management_params,
|
||||||
@@ -810,7 +784,7 @@ mod test {
|
|||||||
pin_uv_auth_param,
|
pin_uv_auth_param,
|
||||||
};
|
};
|
||||||
let cred_management_response = process_credential_management(
|
let cred_management_response = process_credential_management(
|
||||||
&mut ctap_state.persistent_store,
|
&mut env,
|
||||||
&mut ctap_state.stateful_command_permission,
|
&mut ctap_state.stateful_command_permission,
|
||||||
&mut ctap_state.client_pin,
|
&mut ctap_state.client_pin,
|
||||||
cred_management_params,
|
cred_management_params,
|
||||||
@@ -835,12 +809,9 @@ mod test {
|
|||||||
let mut ctap_state = CtapState::new(&mut env, DUMMY_CLOCK_VALUE);
|
let mut ctap_state = CtapState::new(&mut env, DUMMY_CLOCK_VALUE);
|
||||||
ctap_state.client_pin = client_pin;
|
ctap_state.client_pin = client_pin;
|
||||||
|
|
||||||
ctap_state
|
storage::store_credential(&mut env, credential_source).unwrap();
|
||||||
.persistent_store
|
|
||||||
.store_credential(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![
|
let pin_uv_auth_param = Some(vec![
|
||||||
0xA5, 0x55, 0x8F, 0x03, 0xC3, 0xD3, 0x73, 0x1C, 0x07, 0xDA, 0x1F, 0x8C, 0xC7, 0xBD,
|
0xA5, 0x55, 0x8F, 0x03, 0xC3, 0xD3, 0x73, 0x1C, 0x07, 0xDA, 0x1F, 0x8C, 0xC7, 0xBD,
|
||||||
0x9D, 0xB7,
|
0x9D, 0xB7,
|
||||||
@@ -869,7 +840,7 @@ mod test {
|
|||||||
pin_uv_auth_param,
|
pin_uv_auth_param,
|
||||||
};
|
};
|
||||||
let cred_management_response = process_credential_management(
|
let cred_management_response = process_credential_management(
|
||||||
&mut ctap_state.persistent_store,
|
&mut env,
|
||||||
&mut ctap_state.stateful_command_permission,
|
&mut ctap_state.stateful_command_permission,
|
||||||
&mut ctap_state.client_pin,
|
&mut ctap_state.client_pin,
|
||||||
cred_management_params,
|
cred_management_params,
|
||||||
@@ -880,11 +851,10 @@ mod test {
|
|||||||
Ok(ResponseData::AuthenticatorCredentialManagement(None))
|
Ok(ResponseData::AuthenticatorCredentialManagement(None))
|
||||||
);
|
);
|
||||||
|
|
||||||
let updated_credential = ctap_state
|
let updated_credential =
|
||||||
.persistent_store
|
storage::find_credential(&mut env, "example.com", &[0x1D; 32], false)
|
||||||
.find_credential("example.com", &[0x1D; 32], false)
|
.unwrap()
|
||||||
.unwrap()
|
.unwrap();
|
||||||
.unwrap();
|
|
||||||
assert_eq!(updated_credential.user_handle, vec![0x01]);
|
assert_eq!(updated_credential.user_handle, vec![0x01]);
|
||||||
assert_eq!(&updated_credential.user_name.unwrap(), "new_name");
|
assert_eq!(&updated_credential.user_name.unwrap(), "new_name");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -899,7 +869,7 @@ mod test {
|
|||||||
let mut env = TestEnv::new();
|
let mut env = TestEnv::new();
|
||||||
let mut ctap_state = CtapState::new(&mut env, DUMMY_CLOCK_VALUE);
|
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 {
|
let cred_management_params = AuthenticatorCredentialManagementParameters {
|
||||||
sub_command: CredentialManagementSubCommand::GetCredsMetadata,
|
sub_command: CredentialManagementSubCommand::GetCredsMetadata,
|
||||||
@@ -908,7 +878,7 @@ mod test {
|
|||||||
pin_uv_auth_param: Some(vec![0u8; 16]),
|
pin_uv_auth_param: Some(vec![0u8; 16]),
|
||||||
};
|
};
|
||||||
let cred_management_response = process_credential_management(
|
let cred_management_response = process_credential_management(
|
||||||
&mut ctap_state.persistent_store,
|
&mut env,
|
||||||
&mut ctap_state.stateful_command_permission,
|
&mut ctap_state.stateful_command_permission,
|
||||||
&mut ctap_state.client_pin,
|
&mut ctap_state.client_pin,
|
||||||
cred_management_params,
|
cred_management_params,
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
use super::apdu::{Apdu, ApduStatusCode};
|
use super::apdu::{Apdu, ApduStatusCode};
|
||||||
use super::CtapState;
|
use super::CtapState;
|
||||||
|
use crate::ctap::storage;
|
||||||
use crate::env::Env;
|
use crate::env::Env;
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use arrayref::array_ref;
|
use arrayref::array_ref;
|
||||||
@@ -178,14 +179,14 @@ impl Ctap1Command {
|
|||||||
const VENDOR_SPECIFIC_FIRST: u8 = 0x40;
|
const VENDOR_SPECIFIC_FIRST: u8 = 0x40;
|
||||||
const VENDOR_SPECIFIC_LAST: u8 = 0xBF;
|
const VENDOR_SPECIFIC_LAST: u8 = 0xBF;
|
||||||
|
|
||||||
pub fn process_command<E: Env>(
|
pub fn process_command(
|
||||||
env: &mut E,
|
env: &mut impl Env,
|
||||||
message: &[u8],
|
message: &[u8],
|
||||||
ctap_state: &mut CtapState<E>,
|
ctap_state: &mut CtapState,
|
||||||
clock_value: ClockValue,
|
clock_value: ClockValue,
|
||||||
) -> Result<Vec<u8>, Ctap1StatusCode> {
|
) -> Result<Vec<u8>, Ctap1StatusCode> {
|
||||||
if !ctap_state
|
if !ctap_state
|
||||||
.allows_ctap1()
|
.allows_ctap1(env)
|
||||||
.map_err(|_| Ctap1StatusCode::SW_INTERNAL_EXCEPTION)?
|
.map_err(|_| Ctap1StatusCode::SW_INTERNAL_EXCEPTION)?
|
||||||
{
|
{
|
||||||
return Err(Ctap1StatusCode::SW_COMMAND_NOT_ALLOWED);
|
return Err(Ctap1StatusCode::SW_COMMAND_NOT_ALLOWED);
|
||||||
@@ -243,11 +244,11 @@ impl Ctap1Command {
|
|||||||
// +------+-------------------+-----------------+------------+--------------------+
|
// +------+-------------------+-----------------+------------+--------------------+
|
||||||
// + 0x00 | application (32B) | challenge (32B) | key handle | User pub key (65B) |
|
// + 0x00 | application (32B) | challenge (32B) | key handle | User pub key (65B) |
|
||||||
// +------+-------------------+-----------------+------------+--------------------+
|
// +------+-------------------+-----------------+------------+--------------------+
|
||||||
fn process_register<E: Env>(
|
fn process_register(
|
||||||
env: &mut E,
|
env: &mut impl Env,
|
||||||
challenge: [u8; 32],
|
challenge: [u8; 32],
|
||||||
application: [u8; 32],
|
application: [u8; 32],
|
||||||
ctap_state: &mut CtapState<E>,
|
ctap_state: &mut CtapState,
|
||||||
) -> Result<Vec<u8>, Ctap1StatusCode> {
|
) -> Result<Vec<u8>, Ctap1StatusCode> {
|
||||||
let sk = crypto::ecdsa::SecKey::gensk(env.rng());
|
let sk = crypto::ecdsa::SecKey::gensk(env.rng());
|
||||||
let pk = sk.genpk();
|
let pk = sk.genpk();
|
||||||
@@ -259,14 +260,10 @@ impl Ctap1Command {
|
|||||||
return Err(Ctap1StatusCode::SW_INTERNAL_EXCEPTION);
|
return Err(Ctap1StatusCode::SW_INTERNAL_EXCEPTION);
|
||||||
}
|
}
|
||||||
|
|
||||||
let certificate = ctap_state
|
let certificate = storage::attestation_certificate(env)
|
||||||
.persistent_store
|
|
||||||
.attestation_certificate()
|
|
||||||
.map_err(|_| Ctap1StatusCode::SW_MEMERR)?
|
.map_err(|_| Ctap1StatusCode::SW_MEMERR)?
|
||||||
.ok_or(Ctap1StatusCode::SW_INTERNAL_EXCEPTION)?;
|
.ok_or(Ctap1StatusCode::SW_INTERNAL_EXCEPTION)?;
|
||||||
let private_key = ctap_state
|
let private_key = storage::attestation_private_key(env)
|
||||||
.persistent_store
|
|
||||||
.attestation_private_key()
|
|
||||||
.map_err(|_| Ctap1StatusCode::SW_INTERNAL_EXCEPTION)?
|
.map_err(|_| Ctap1StatusCode::SW_INTERNAL_EXCEPTION)?
|
||||||
.ok_or(Ctap1StatusCode::SW_INTERNAL_EXCEPTION)?;
|
.ok_or(Ctap1StatusCode::SW_INTERNAL_EXCEPTION)?;
|
||||||
|
|
||||||
@@ -307,16 +304,16 @@ impl Ctap1Command {
|
|||||||
// +-------------------+---------+--------------+-----------------+
|
// +-------------------+---------+--------------+-----------------+
|
||||||
// + application (32B) | UP (1B) | Counter (4B) | challenge (32B) |
|
// + application (32B) | UP (1B) | Counter (4B) | challenge (32B) |
|
||||||
// +-------------------+---------+--------------+-----------------+
|
// +-------------------+---------+--------------+-----------------+
|
||||||
fn process_authenticate<E: Env>(
|
fn process_authenticate(
|
||||||
env: &mut E,
|
env: &mut impl Env,
|
||||||
challenge: [u8; 32],
|
challenge: [u8; 32],
|
||||||
application: [u8; 32],
|
application: [u8; 32],
|
||||||
key_handle: Vec<u8>,
|
key_handle: Vec<u8>,
|
||||||
flags: Ctap1Flags,
|
flags: Ctap1Flags,
|
||||||
ctap_state: &mut CtapState<E>,
|
ctap_state: &mut CtapState,
|
||||||
) -> Result<Vec<u8>, Ctap1StatusCode> {
|
) -> Result<Vec<u8>, Ctap1StatusCode> {
|
||||||
let credential_source = ctap_state
|
let credential_source = ctap_state
|
||||||
.decrypt_credential_source(key_handle, &application)
|
.decrypt_credential_source(env, key_handle, &application)
|
||||||
.map_err(|_| Ctap1StatusCode::SW_WRONG_DATA)?;
|
.map_err(|_| Ctap1StatusCode::SW_WRONG_DATA)?;
|
||||||
if let Some(credential_source) = credential_source {
|
if let Some(credential_source) = credential_source {
|
||||||
if flags == Ctap1Flags::CheckOnly {
|
if flags == Ctap1Flags::CheckOnly {
|
||||||
@@ -326,7 +323,11 @@ impl Ctap1Command {
|
|||||||
.increment_global_signature_counter(env)
|
.increment_global_signature_counter(env)
|
||||||
.map_err(|_| Ctap1StatusCode::SW_WRONG_DATA)?;
|
.map_err(|_| Ctap1StatusCode::SW_WRONG_DATA)?;
|
||||||
let mut signature_data = ctap_state
|
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)?;
|
.map_err(|_| Ctap1StatusCode::SW_WRONG_DATA)?;
|
||||||
signature_data.extend(&challenge);
|
signature_data.extend(&challenge);
|
||||||
let signature = credential_source
|
let signature = credential_source
|
||||||
@@ -400,7 +401,7 @@ mod test {
|
|||||||
env.user_presence()
|
env.user_presence()
|
||||||
.set(|_| panic!("Unexpected user presence check in CTAP1"));
|
.set(|_| panic!("Unexpected user presence check in CTAP1"));
|
||||||
let mut ctap_state = CtapState::new(&mut env, START_CLOCK_VALUE);
|
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 application = [0x0A; 32];
|
||||||
let message = create_register_message(&application);
|
let message = create_register_message(&application);
|
||||||
@@ -428,10 +429,7 @@ mod test {
|
|||||||
assert_eq!(response, Err(Ctap1StatusCode::SW_INTERNAL_EXCEPTION));
|
assert_eq!(response, Err(Ctap1StatusCode::SW_INTERNAL_EXCEPTION));
|
||||||
|
|
||||||
let fake_key = [0x41u8; key_material::ATTESTATION_PRIVATE_KEY_LENGTH];
|
let fake_key = [0x41u8; key_material::ATTESTATION_PRIVATE_KEY_LENGTH];
|
||||||
assert!(ctap_state
|
assert!(storage::set_attestation_private_key(&mut env, &fake_key).is_ok());
|
||||||
.persistent_store
|
|
||||||
.set_attestation_private_key(&fake_key)
|
|
||||||
.is_ok());
|
|
||||||
ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE);
|
ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE);
|
||||||
ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE);
|
ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE);
|
||||||
let response =
|
let response =
|
||||||
@@ -440,10 +438,7 @@ mod test {
|
|||||||
assert_eq!(response, Err(Ctap1StatusCode::SW_INTERNAL_EXCEPTION));
|
assert_eq!(response, Err(Ctap1StatusCode::SW_INTERNAL_EXCEPTION));
|
||||||
|
|
||||||
let fake_cert = [0x99u8; 100]; // Arbitrary length
|
let fake_cert = [0x99u8; 100]; // Arbitrary length
|
||||||
assert!(ctap_state
|
assert!(storage::set_attestation_certificate(&mut env, &fake_cert[..]).is_ok());
|
||||||
.persistent_store
|
|
||||||
.set_attestation_certificate(&fake_cert[..])
|
|
||||||
.is_ok());
|
|
||||||
ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE);
|
ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE);
|
||||||
ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE);
|
ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE);
|
||||||
let response =
|
let response =
|
||||||
@@ -452,7 +447,11 @@ mod test {
|
|||||||
assert_eq!(response[0], Ctap1Command::LEGACY_BYTE);
|
assert_eq!(response[0], Ctap1Command::LEGACY_BYTE);
|
||||||
assert_eq!(response[66], CREDENTIAL_ID_SIZE as u8);
|
assert_eq!(response[66], CREDENTIAL_ID_SIZE as u8);
|
||||||
assert!(ctap_state
|
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()
|
.unwrap()
|
||||||
.is_some());
|
.is_some());
|
||||||
const CERT_START: usize = 67 + CREDENTIAL_ID_SIZE;
|
const CERT_START: usize = 67 + CREDENTIAL_ID_SIZE;
|
||||||
@@ -677,10 +676,7 @@ mod test {
|
|||||||
assert_eq!(response[0], 0x01);
|
assert_eq!(response[0], 0x01);
|
||||||
check_signature_counter(
|
check_signature_counter(
|
||||||
array_ref!(response, 1, 4),
|
array_ref!(response, 1, 4),
|
||||||
ctap_state
|
storage::global_signature_counter(&mut env).unwrap(),
|
||||||
.persistent_store
|
|
||||||
.global_signature_counter()
|
|
||||||
.unwrap(),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -709,10 +705,7 @@ mod test {
|
|||||||
assert_eq!(response[0], 0x01);
|
assert_eq!(response[0], 0x01);
|
||||||
check_signature_counter(
|
check_signature_counter(
|
||||||
array_ref!(response, 1, 4),
|
array_ref!(response, 1, 4),
|
||||||
ctap_state
|
storage::global_signature_counter(&mut env).unwrap(),
|
||||||
.persistent_store
|
|
||||||
.global_signature_counter()
|
|
||||||
.unwrap(),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,13 +32,50 @@ use core::fmt::Write;
|
|||||||
use libtock_drivers::console::Console;
|
use libtock_drivers::console::Console;
|
||||||
use libtock_drivers::timer::{ClockValue, Duration, Timestamp};
|
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 HidPacket = [u8; 64];
|
||||||
pub type ChannelID = [u8; 4];
|
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<u8> 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> {
|
pub enum ProcessedPacket<'a> {
|
||||||
InitPacket {
|
InitPacket {
|
||||||
cmd: u8,
|
cmd: u8,
|
||||||
@@ -51,72 +88,79 @@ pub enum ProcessedPacket<'a> {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// An assembled CTAPHID command.
|
/// An assembled CTAPHID command.
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct Message {
|
pub struct Message {
|
||||||
// Channel ID.
|
// Channel ID.
|
||||||
pub cid: ChannelID,
|
pub cid: ChannelID,
|
||||||
// Command.
|
// Command.
|
||||||
pub cmd: u8,
|
pub cmd: CtapHidCommand,
|
||||||
// Bytes of the message.
|
// Bytes of the message.
|
||||||
pub payload: Vec<u8>,
|
pub payload: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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<Message>`
|
||||||
|
/// 2. `Option<Message>` -> `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 {
|
pub struct CtapHid {
|
||||||
assembler: MessageAssembler,
|
assembler: MessageAssembler,
|
||||||
// The specification (version 20190130) only requires unique CIDs ; the allocation algorithm is
|
// The specification only requires unique CIDs, the allocation algorithm is vendor specific.
|
||||||
// vendor specific.
|
|
||||||
// We allocate them incrementally, that is all `cid` such that 1 <= cid <= allocated_cids are
|
// We allocate them incrementally, that is all `cid` such that 1 <= cid <= allocated_cids are
|
||||||
// allocated.
|
// allocated.
|
||||||
// In packets, the ID encoding is Big Endian to match what is used throughout CTAP (with the
|
// In packets, the ID encoding is Big Endian to match what is used throughout CTAP (with the
|
||||||
// u32::to/from_be_bytes methods).
|
// u32::to/from_be_bytes methods).
|
||||||
|
// TODO(kaczmarczyck) We might want to limit or timeout open channels.
|
||||||
allocated_cids: usize,
|
allocated_cids: usize,
|
||||||
pub(crate) wink_permission: TimedPermission,
|
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 {
|
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_RESERVED: ChannelID = [0, 0, 0, 0];
|
||||||
const CHANNEL_BROADCAST: ChannelID = [0xFF, 0xFF, 0xFF, 0xFF];
|
const CHANNEL_BROADCAST: ChannelID = [0xFF, 0xFF, 0xFF, 0xFF];
|
||||||
const TYPE_INIT_BIT: u8 = 0x80;
|
const TYPE_INIT_BIT: u8 = 0x80;
|
||||||
const PACKET_TYPE_MASK: 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_CMD: u8 = 0x01;
|
||||||
const ERR_INVALID_PAR: u8 = 0x02;
|
const _ERR_INVALID_PAR: u8 = 0x02;
|
||||||
const ERR_INVALID_LEN: u8 = 0x03;
|
const ERR_INVALID_LEN: u8 = 0x03;
|
||||||
const ERR_INVALID_SEQ: u8 = 0x04;
|
const ERR_INVALID_SEQ: u8 = 0x04;
|
||||||
const ERR_MSG_TIMEOUT: u8 = 0x05;
|
const ERR_MSG_TIMEOUT: u8 = 0x05;
|
||||||
const ERR_CHANNEL_BUSY: u8 = 0x06;
|
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_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;
|
const PROTOCOL_VERSION: u8 = 2;
|
||||||
|
|
||||||
// The device version number is vendor-defined.
|
// The device version number is vendor-defined.
|
||||||
const DEVICE_VERSION_MAJOR: u8 = 1;
|
const DEVICE_VERSION_MAJOR: u8 = 1;
|
||||||
const DEVICE_VERSION_MINOR: u8 = 0;
|
const DEVICE_VERSION_MINOR: u8 = 0;
|
||||||
@@ -124,6 +168,7 @@ impl CtapHid {
|
|||||||
|
|
||||||
const CAPABILITY_WINK: u8 = 0x01;
|
const CAPABILITY_WINK: u8 = 0x01;
|
||||||
const CAPABILITY_CBOR: u8 = 0x04;
|
const CAPABILITY_CBOR: u8 = 0x04;
|
||||||
|
#[cfg(not(feature = "with_ctap1"))]
|
||||||
const CAPABILITY_NMSG: u8 = 0x08;
|
const CAPABILITY_NMSG: u8 = 0x08;
|
||||||
// Capabilitites currently supported by this device.
|
// Capabilitites currently supported by this device.
|
||||||
#[cfg(feature = "with_ctap1")]
|
#[cfg(feature = "with_ctap1")]
|
||||||
@@ -136,6 +181,7 @@ impl CtapHid {
|
|||||||
const TIMEOUT_DURATION: Duration<isize> = Duration::from_ms(100);
|
const TIMEOUT_DURATION: Duration<isize> = Duration::from_ms(100);
|
||||||
const WINK_TIMEOUT_DURATION: Duration<isize> = Duration::from_ms(5000);
|
const WINK_TIMEOUT_DURATION: Duration<isize> = Duration::from_ms(5000);
|
||||||
|
|
||||||
|
/// Creates a new idle HID state.
|
||||||
pub fn new() -> CtapHid {
|
pub fn new() -> CtapHid {
|
||||||
CtapHid {
|
CtapHid {
|
||||||
assembler: MessageAssembler::new(),
|
assembler: MessageAssembler::new(),
|
||||||
@@ -144,16 +190,26 @@ impl CtapHid {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process an incoming USB HID packet, and optionally returns a list of outgoing packets to
|
/// Parses a packet, and preprocesses some messages and errors.
|
||||||
// send as a reply.
|
///
|
||||||
pub fn process_hid_packet<E: Env>(
|
/// The preprocessed commands are:
|
||||||
&mut self,
|
/// - INIT
|
||||||
env: &mut E,
|
/// - CANCEL
|
||||||
packet: &HidPacket,
|
/// - ERROR
|
||||||
clock_value: ClockValue,
|
/// - Unknown and unexpected commands like KEEPALIVE
|
||||||
ctap_state: &mut CtapState<E>,
|
/// - LOCK is not implemented and currently treated like an unknown command
|
||||||
) -> HidPacketIterator {
|
///
|
||||||
// TODO: Send COMMAND_KEEPALIVE every 100ms?
|
/// 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<Message> {
|
||||||
match self
|
match self
|
||||||
.assembler
|
.assembler
|
||||||
.parse_packet(packet, Timestamp::<isize>::from_clock_value(clock_value))
|
.parse_packet(packet, Timestamp::<isize>::from_clock_value(clock_value))
|
||||||
@@ -161,170 +217,42 @@ impl CtapHid {
|
|||||||
Ok(Some(message)) => {
|
Ok(Some(message)) => {
|
||||||
#[cfg(feature = "debug_ctap")]
|
#[cfg(feature = "debug_ctap")]
|
||||||
writeln!(&mut Console::new(), "Received message: {:02x?}", message).unwrap();
|
writeln!(&mut Console::new(), "Received message: {:02x?}", message).unwrap();
|
||||||
|
self.preprocess_message(message)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Ok(None) => {
|
Ok(None) => {
|
||||||
// Waiting for more packets to assemble the message, nothing to send for now.
|
// Waiting for more packets to assemble the message, nothing to send for now.
|
||||||
HidPacketIterator::none()
|
None
|
||||||
}
|
}
|
||||||
Err((cid, error)) => {
|
Err((cid, error)) => {
|
||||||
if !self.is_allocated_channel(cid)
|
if !self.is_allocated_channel(cid)
|
||||||
&& error != receive::Error::UnexpectedContinuation
|
&& error != receive::Error::UnexpectedContinuation
|
||||||
{
|
{
|
||||||
CtapHid::error_message(cid, CtapHid::ERR_INVALID_CHANNEL)
|
Some(CtapHid::error_message(cid, CtapHid::ERR_INVALID_CHANNEL))
|
||||||
} else {
|
} else {
|
||||||
match error {
|
match error {
|
||||||
receive::Error::UnexpectedChannel => {
|
receive::Error::UnexpectedChannel => {
|
||||||
CtapHid::error_message(cid, CtapHid::ERR_CHANNEL_BUSY)
|
Some(CtapHid::error_message(cid, CtapHid::ERR_CHANNEL_BUSY))
|
||||||
}
|
}
|
||||||
receive::Error::UnexpectedInit => {
|
receive::Error::UnexpectedInit => {
|
||||||
// TODO: Should we send another error code in this case?
|
// TODO: Should we send another error code in this case?
|
||||||
// Technically, we were expecting a sequence number and got another
|
// Technically, we were expecting a sequence number and got another
|
||||||
// byte, although the command/seqnum bit has higher-level semantics
|
// byte, although the command/seqnum bit has higher-level semantics
|
||||||
// than sequence numbers.
|
// than sequence numbers.
|
||||||
CtapHid::error_message(cid, CtapHid::ERR_INVALID_SEQ)
|
Some(CtapHid::error_message(cid, CtapHid::ERR_INVALID_SEQ))
|
||||||
}
|
}
|
||||||
receive::Error::UnexpectedContinuation => {
|
receive::Error::UnexpectedContinuation => {
|
||||||
// CTAP specification (version 20190130) section 8.1.5.4
|
// CTAP specification (version 20190130) section 8.1.5.4
|
||||||
// Spurious continuation packets will be ignored.
|
// Spurious continuation packets will be ignored.
|
||||||
HidPacketIterator::none()
|
None
|
||||||
}
|
}
|
||||||
receive::Error::UnexpectedSeq => {
|
receive::Error::UnexpectedSeq => {
|
||||||
CtapHid::error_message(cid, CtapHid::ERR_INVALID_SEQ)
|
Some(CtapHid::error_message(cid, CtapHid::ERR_INVALID_SEQ))
|
||||||
}
|
}
|
||||||
receive::Error::UnexpectedLen => {
|
receive::Error::UnexpectedLen => {
|
||||||
CtapHid::error_message(cid, CtapHid::ERR_INVALID_LEN)
|
Some(CtapHid::error_message(cid, CtapHid::ERR_INVALID_LEN))
|
||||||
}
|
}
|
||||||
receive::Error::Timeout => {
|
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<Message> {
|
||||||
|
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 {
|
fn has_valid_channel(&self, message: &Message) -> bool {
|
||||||
match message.cid {
|
match message.cid {
|
||||||
// Only INIT commands use the broadcast channel.
|
// 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.
|
// Check that the channel is allocated.
|
||||||
_ => self.is_allocated_channel(message.cid),
|
_ => 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
|
cid != CtapHid::CHANNEL_RESERVED && u32::from_be_bytes(cid) as usize <= self.allocated_cids
|
||||||
}
|
}
|
||||||
|
|
||||||
fn error_message(cid: ChannelID, error_code: u8) -> HidPacketIterator {
|
fn error_message(cid: ChannelID, error_code: u8) -> Message {
|
||||||
// This unwrap is safe because the payload length is 1 <= 7609 bytes.
|
Message {
|
||||||
CtapHid::split_message(Message {
|
|
||||||
cid,
|
cid,
|
||||||
cmd: CtapHid::COMMAND_ERROR,
|
cmd: CtapHidCommand::Error,
|
||||||
payload: vec![error_code],
|
payload: vec![error_code],
|
||||||
})
|
}
|
||||||
.unwrap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper function to parse a raw packet.
|
||||||
pub fn process_single_packet(packet: &HidPacket) -> (&ChannelID, ProcessedPacket) {
|
pub fn process_single_packet(packet: &HidPacket) -> (&ChannelID, ProcessedPacket) {
|
||||||
let (cid, rest) = array_refs![packet, 4, 60];
|
let (cid, rest) = array_refs![packet, 4, 60];
|
||||||
if rest[0] & CtapHid::PACKET_TYPE_MASK != 0 {
|
if rest[0] & CtapHid::PACKET_TYPE_MASK != 0 {
|
||||||
@@ -379,56 +453,65 @@ impl CtapHid {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn split_message(message: Message) -> Option<HidPacketIterator> {
|
/// Splits the message and unwraps the result.
|
||||||
#[cfg(feature = "debug_ctap")]
|
///
|
||||||
writeln!(&mut Console::new(), "Sending message: {:02x?}", message).unwrap();
|
/// Unwrapping handles the case of payload lengths > 7609 bytes. All responses are fixed
|
||||||
HidPacketIterator::new(message)
|
/// 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 {
|
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.
|
// This unwrap is safe because the payload length is 1 <= 7609 bytes.
|
||||||
CtapHid::split_message(Message {
|
CtapHid::split_message(Message {
|
||||||
cid,
|
cid,
|
||||||
cmd: CtapHid::COMMAND_KEEPALIVE,
|
cmd: CtapHidCommand::Keepalive,
|
||||||
payload: vec![status_code],
|
payload: vec![status as u8],
|
||||||
})
|
})
|
||||||
.unwrap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns whether a wink permission is currently granted.
|
||||||
pub fn should_wink(&self, now: ClockValue) -> bool {
|
pub fn should_wink(&self, now: ClockValue) -> bool {
|
||||||
self.wink_permission.is_granted(now)
|
self.wink_permission.is_granted(now)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "with_ctap1")]
|
#[cfg(feature = "with_ctap1")]
|
||||||
fn ctap1_error_message(
|
fn ctap1_error_message(cid: ChannelID, error_code: ctap1::Ctap1StatusCode) -> Message {
|
||||||
cid: ChannelID,
|
|
||||||
error_code: ctap1::Ctap1StatusCode,
|
|
||||||
) -> HidPacketIterator {
|
|
||||||
// This unwrap is safe because the payload length is 2 <= 7609 bytes
|
|
||||||
let code: u16 = error_code.into();
|
let code: u16 = error_code.into();
|
||||||
CtapHid::split_message(Message {
|
Message {
|
||||||
cid,
|
cid,
|
||||||
cmd: CtapHid::COMMAND_MSG,
|
cmd: CtapHidCommand::Msg,
|
||||||
payload: code.to_be_bytes().to_vec(),
|
payload: code.to_be_bytes().to_vec(),
|
||||||
})
|
}
|
||||||
.unwrap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "with_ctap1")]
|
#[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 mut response = payload.to_vec();
|
||||||
let code: u16 = ctap1::Ctap1StatusCode::SW_SUCCESS.into();
|
let code: u16 = ctap1::Ctap1StatusCode::SW_SUCCESS.into();
|
||||||
response.extend_from_slice(&code.to_be_bytes());
|
response.extend_from_slice(&code.to_be_bytes());
|
||||||
CtapHid::split_message(Message {
|
Message {
|
||||||
cid,
|
cid,
|
||||||
cmd: CtapHid::COMMAND_MSG,
|
cmd: CtapHidCommand::Msg,
|
||||||
payload: response,
|
payload: response,
|
||||||
})
|
}
|
||||||
.unwrap()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -442,10 +525,10 @@ mod test {
|
|||||||
const DUMMY_CLOCK_VALUE: ClockValue = ClockValue::new(0, CLOCK_FREQUENCY_HZ);
|
const DUMMY_CLOCK_VALUE: ClockValue = ClockValue::new(0, CLOCK_FREQUENCY_HZ);
|
||||||
const DUMMY_TIMESTAMP: Timestamp<isize> = Timestamp::from_ms(0);
|
const DUMMY_TIMESTAMP: Timestamp<isize> = Timestamp::from_ms(0);
|
||||||
|
|
||||||
fn process_messages<E: Env>(
|
fn process_messages(
|
||||||
env: &mut E,
|
env: &mut TestEnv,
|
||||||
ctap_hid: &mut CtapHid,
|
ctap_hid: &mut CtapHid,
|
||||||
ctap_state: &mut CtapState<E>,
|
ctap_state: &mut CtapState,
|
||||||
request: Vec<Message>,
|
request: Vec<Message>,
|
||||||
) -> Option<Vec<Message>> {
|
) -> Option<Vec<Message>> {
|
||||||
let mut result = Vec::new();
|
let mut result = Vec::new();
|
||||||
@@ -466,10 +549,10 @@ mod test {
|
|||||||
Some(result)
|
Some(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cid_from_init<E: Env>(
|
fn cid_from_init(
|
||||||
env: &mut E,
|
env: &mut TestEnv,
|
||||||
ctap_hid: &mut CtapHid,
|
ctap_hid: &mut CtapHid,
|
||||||
ctap_state: &mut CtapState<E>,
|
ctap_state: &mut CtapState,
|
||||||
) -> ChannelID {
|
) -> ChannelID {
|
||||||
let nonce = vec![0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0];
|
let nonce = vec![0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0];
|
||||||
let reply = process_messages(
|
let reply = process_messages(
|
||||||
@@ -478,7 +561,7 @@ mod test {
|
|||||||
ctap_state,
|
ctap_state,
|
||||||
vec![Message {
|
vec![Message {
|
||||||
cid: CtapHid::CHANNEL_BROADCAST,
|
cid: CtapHid::CHANNEL_BROADCAST,
|
||||||
cmd: CtapHid::COMMAND_INIT,
|
cmd: CtapHidCommand::Init,
|
||||||
payload: nonce.clone(),
|
payload: nonce.clone(),
|
||||||
}],
|
}],
|
||||||
);
|
);
|
||||||
@@ -500,7 +583,7 @@ mod test {
|
|||||||
for payload_len in 0..7609 {
|
for payload_len in 0..7609 {
|
||||||
let message = Message {
|
let message = Message {
|
||||||
cid: [0x12, 0x34, 0x56, 0x78],
|
cid: [0x12, 0x34, 0x56, 0x78],
|
||||||
cmd: 0x00,
|
cmd: CtapHidCommand::Cbor,
|
||||||
payload: vec![0xFF; payload_len],
|
payload: vec![0xFF; payload_len],
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -552,7 +635,7 @@ mod test {
|
|||||||
&mut ctap_state,
|
&mut ctap_state,
|
||||||
vec![Message {
|
vec![Message {
|
||||||
cid: CtapHid::CHANNEL_BROADCAST,
|
cid: CtapHid::CHANNEL_BROADCAST,
|
||||||
cmd: CtapHid::COMMAND_INIT,
|
cmd: CtapHidCommand::Init,
|
||||||
payload: vec![0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0],
|
payload: vec![0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0],
|
||||||
}],
|
}],
|
||||||
);
|
);
|
||||||
@@ -561,7 +644,7 @@ mod test {
|
|||||||
reply,
|
reply,
|
||||||
Some(vec![Message {
|
Some(vec![Message {
|
||||||
cid: CtapHid::CHANNEL_BROADCAST,
|
cid: CtapHid::CHANNEL_BROADCAST,
|
||||||
cmd: CtapHid::COMMAND_INIT,
|
cmd: CtapHidCommand::Init,
|
||||||
payload: vec![
|
payload: vec![
|
||||||
0x12, // Nonce
|
0x12, // Nonce
|
||||||
0x34,
|
0x34,
|
||||||
@@ -623,7 +706,7 @@ mod test {
|
|||||||
result,
|
result,
|
||||||
vec![Message {
|
vec![Message {
|
||||||
cid,
|
cid,
|
||||||
cmd: CtapHid::COMMAND_INIT,
|
cmd: CtapHidCommand::Init,
|
||||||
payload: vec![
|
payload: vec![
|
||||||
0x12, // Nonce
|
0x12, // Nonce
|
||||||
0x34,
|
0x34,
|
||||||
@@ -660,7 +743,7 @@ mod test {
|
|||||||
&mut ctap_state,
|
&mut ctap_state,
|
||||||
vec![Message {
|
vec![Message {
|
||||||
cid,
|
cid,
|
||||||
cmd: CtapHid::COMMAND_PING,
|
cmd: CtapHidCommand::Ping,
|
||||||
payload: vec![0x99, 0x99],
|
payload: vec![0x99, 0x99],
|
||||||
}],
|
}],
|
||||||
);
|
);
|
||||||
@@ -669,7 +752,7 @@ mod test {
|
|||||||
reply,
|
reply,
|
||||||
Some(vec![Message {
|
Some(vec![Message {
|
||||||
cid,
|
cid,
|
||||||
cmd: CtapHid::COMMAND_PING,
|
cmd: CtapHidCommand::Ping,
|
||||||
payload: vec![0x99, 0x99]
|
payload: vec![0x99, 0x99]
|
||||||
}])
|
}])
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use super::super::customization::MAX_MSG_SIZE;
|
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 alloc::vec::Vec;
|
||||||
use core::mem::swap;
|
use core::mem::swap;
|
||||||
use libtock_drivers::timer::Timestamp;
|
use libtock_drivers::timer::Timestamp;
|
||||||
@@ -131,7 +131,7 @@ impl MessageAssembler {
|
|||||||
// Unexpected initialization packet.
|
// Unexpected initialization packet.
|
||||||
ProcessedPacket::InitPacket { cmd, len, data } => {
|
ProcessedPacket::InitPacket { cmd, len, data } => {
|
||||||
self.reset();
|
self.reset();
|
||||||
if cmd == CtapHid::COMMAND_INIT {
|
if cmd == CtapHidCommand::Init as u8 {
|
||||||
self.parse_init_packet(*cid, cmd, len, data, timestamp)
|
self.parse_init_packet(*cid, cmd, len, data, timestamp)
|
||||||
} else {
|
} else {
|
||||||
Err((*cid, Error::UnexpectedInit))
|
Err((*cid, Error::UnexpectedInit))
|
||||||
@@ -189,7 +189,7 @@ impl MessageAssembler {
|
|||||||
swap(&mut self.payload, &mut payload);
|
swap(&mut self.payload, &mut payload);
|
||||||
Some(Message {
|
Some(Message {
|
||||||
cid: self.cid,
|
cid: self.cid,
|
||||||
cmd: self.cmd,
|
cmd: CtapHidCommand::from(self.cmd),
|
||||||
payload,
|
payload,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -225,12 +225,12 @@ mod test {
|
|||||||
let mut assembler = MessageAssembler::new();
|
let mut assembler = MessageAssembler::new();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
assembler.parse_packet(
|
assembler.parse_packet(
|
||||||
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x80]),
|
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x90]),
|
||||||
DUMMY_TIMESTAMP
|
DUMMY_TIMESTAMP
|
||||||
),
|
),
|
||||||
Ok(Some(Message {
|
Ok(Some(Message {
|
||||||
cid: [0x12, 0x34, 0x56, 0x78],
|
cid: [0x12, 0x34, 0x56, 0x78],
|
||||||
cmd: 0x00,
|
cmd: CtapHidCommand::Cbor,
|
||||||
payload: vec![]
|
payload: vec![]
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
@@ -241,12 +241,12 @@ mod test {
|
|||||||
let mut assembler = MessageAssembler::new();
|
let mut assembler = MessageAssembler::new();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
assembler.parse_packet(
|
assembler.parse_packet(
|
||||||
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x80, 0x00, 0x10]),
|
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x90, 0x00, 0x10]),
|
||||||
DUMMY_TIMESTAMP
|
DUMMY_TIMESTAMP
|
||||||
),
|
),
|
||||||
Ok(Some(Message {
|
Ok(Some(Message {
|
||||||
cid: [0x12, 0x34, 0x56, 0x78],
|
cid: [0x12, 0x34, 0x56, 0x78],
|
||||||
cmd: 0x00,
|
cmd: CtapHidCommand::Cbor,
|
||||||
payload: vec![0x00; 0x10]
|
payload: vec![0x00; 0x10]
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
@@ -260,12 +260,12 @@ mod test {
|
|||||||
let mut assembler = MessageAssembler::new();
|
let mut assembler = MessageAssembler::new();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
assembler.parse_packet(
|
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
|
DUMMY_TIMESTAMP
|
||||||
),
|
),
|
||||||
Ok(Some(Message {
|
Ok(Some(Message {
|
||||||
cid: [0x12, 0x34, 0x56, 0x78],
|
cid: [0x12, 0x34, 0x56, 0x78],
|
||||||
cmd: 0x00,
|
cmd: CtapHidCommand::Cbor,
|
||||||
payload: vec![0xFF; 0x10]
|
payload: vec![0xFF; 0x10]
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
@@ -288,7 +288,7 @@ mod test {
|
|||||||
),
|
),
|
||||||
Ok(Some(Message {
|
Ok(Some(Message {
|
||||||
cid: [0x12, 0x34, 0x56, 0x78],
|
cid: [0x12, 0x34, 0x56, 0x78],
|
||||||
cmd: 0x01,
|
cmd: CtapHidCommand::Ping,
|
||||||
payload: vec![0x00; 0x40]
|
payload: vec![0x00; 0x40]
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
@@ -318,7 +318,7 @@ mod test {
|
|||||||
),
|
),
|
||||||
Ok(Some(Message {
|
Ok(Some(Message {
|
||||||
cid: [0x12, 0x34, 0x56, 0x78],
|
cid: [0x12, 0x34, 0x56, 0x78],
|
||||||
cmd: 0x01,
|
cmd: CtapHidCommand::Ping,
|
||||||
payload: vec![0x00; 0x80]
|
payload: vec![0x00; 0x80]
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
@@ -350,7 +350,7 @@ mod test {
|
|||||||
),
|
),
|
||||||
Ok(Some(Message {
|
Ok(Some(Message {
|
||||||
cid: [0x12, 0x34, 0x56, 0x78],
|
cid: [0x12, 0x34, 0x56, 0x78],
|
||||||
cmd: 0x01,
|
cmd: CtapHidCommand::Ping,
|
||||||
payload: vec![0x00; 0x1DB9]
|
payload: vec![0x00; 0x1DB9]
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
@@ -362,12 +362,15 @@ mod test {
|
|||||||
let mut assembler = MessageAssembler::new();
|
let mut assembler = MessageAssembler::new();
|
||||||
for i in 0..10 {
|
for i in 0..10 {
|
||||||
// Introduce some variability in the messages.
|
// Introduce some variability in the messages.
|
||||||
let cmd = 2 * i;
|
let cmd = CtapHidCommand::from(i + 1);
|
||||||
let byte = 3 * i;
|
let byte = 3 * i;
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
assembler.parse_packet(
|
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
|
DUMMY_TIMESTAMP
|
||||||
),
|
),
|
||||||
Ok(None)
|
Ok(None)
|
||||||
@@ -400,12 +403,12 @@ mod test {
|
|||||||
for i in 0..10 {
|
for i in 0..10 {
|
||||||
// Introduce some variability in the messages.
|
// Introduce some variability in the messages.
|
||||||
let cid = 0x78 + i;
|
let cid = 0x78 + i;
|
||||||
let cmd = 2 * i;
|
let cmd = CtapHidCommand::from(i + 1);
|
||||||
let byte = 3 * i;
|
let byte = 3 * i;
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
assembler.parse_packet(
|
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
|
DUMMY_TIMESTAMP
|
||||||
),
|
),
|
||||||
Ok(None)
|
Ok(None)
|
||||||
@@ -443,11 +446,12 @@ mod test {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Check that many sorts of packets on another channel are ignored.
|
// 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 {
|
for byte in 0..=0xFF {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
assembler.parse_packet(
|
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
|
DUMMY_TIMESTAMP
|
||||||
),
|
),
|
||||||
Err(([0x12, 0x34, 0x56, 0x9A], Error::UnexpectedChannel))
|
Err(([0x12, 0x34, 0x56, 0x9A], Error::UnexpectedChannel))
|
||||||
@@ -462,7 +466,7 @@ mod test {
|
|||||||
),
|
),
|
||||||
Ok(Some(Message {
|
Ok(Some(Message {
|
||||||
cid: [0x12, 0x34, 0x56, 0x78],
|
cid: [0x12, 0x34, 0x56, 0x78],
|
||||||
cmd: 0x01,
|
cmd: CtapHidCommand::Ping,
|
||||||
payload: vec![0x00; 0x40]
|
payload: vec![0x00; 0x40]
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
@@ -479,12 +483,12 @@ mod test {
|
|||||||
let byte = 2 * i;
|
let byte = 2 * i;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
assembler.parse_packet(
|
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
|
DUMMY_TIMESTAMP
|
||||||
),
|
),
|
||||||
Ok(Some(Message {
|
Ok(Some(Message {
|
||||||
cid: [0x12, 0x34, 0x56, 0x78],
|
cid: [0x12, 0x34, 0x56, 0x78],
|
||||||
cmd: 0x00,
|
cmd: CtapHidCommand::Ping,
|
||||||
payload: vec![byte; 0x10]
|
payload: vec![byte; 0x10]
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
@@ -584,7 +588,7 @@ mod test {
|
|||||||
assembler.parse_packet(&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x7F]), timestamp),
|
assembler.parse_packet(&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x7F]), timestamp),
|
||||||
Ok(Some(Message {
|
Ok(Some(Message {
|
||||||
cid: [0x12, 0x34, 0x56, 0x78],
|
cid: [0x12, 0x34, 0x56, 0x78],
|
||||||
cmd: 0x01,
|
cmd: CtapHidCommand::Ping,
|
||||||
payload: vec![0x00; 0x1DB9]
|
payload: vec![0x00; 0x1DB9]
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
@@ -612,7 +616,7 @@ mod test {
|
|||||||
),
|
),
|
||||||
Ok(Some(Message {
|
Ok(Some(Message {
|
||||||
cid: [0x12, 0x34, 0x56, 0x78],
|
cid: [0x12, 0x34, 0x56, 0x78],
|
||||||
cmd: 0x06,
|
cmd: CtapHidCommand::Init,
|
||||||
payload: vec![0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0]
|
payload: vec![0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0]
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -14,6 +14,9 @@
|
|||||||
|
|
||||||
use super::{CtapHid, HidPacket, Message};
|
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<MessageSplitter>);
|
pub struct HidPacketIterator(Option<MessageSplitter>);
|
||||||
|
|
||||||
impl HidPacketIterator {
|
impl HidPacketIterator {
|
||||||
@@ -99,7 +102,7 @@ impl Iterator for MessageSplitter {
|
|||||||
match self.seq {
|
match self.seq {
|
||||||
None => {
|
None => {
|
||||||
// First, send an initialization packet.
|
// 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[5] = (payload_len >> 8) as u8;
|
||||||
self.packet[6] = payload_len as u8;
|
self.packet[6] = payload_len as u8;
|
||||||
|
|
||||||
@@ -128,6 +131,7 @@ impl Iterator for MessageSplitter {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
use super::super::CtapHidCommand;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
fn assert_packet_output_equality(message: Message, expected_packets: Vec<HidPacket>) {
|
fn assert_packet_output_equality(message: Message, expected_packets: Vec<HidPacket>) {
|
||||||
@@ -142,11 +146,11 @@ mod test {
|
|||||||
fn test_hid_packet_iterator_single_packet() {
|
fn test_hid_packet_iterator_single_packet() {
|
||||||
let message = Message {
|
let message = Message {
|
||||||
cid: [0x12, 0x34, 0x56, 0x78],
|
cid: [0x12, 0x34, 0x56, 0x78],
|
||||||
cmd: 0x4C,
|
cmd: CtapHidCommand::Cbor,
|
||||||
payload: vec![0xAA, 0xBB],
|
payload: vec![0xAA, 0xBB],
|
||||||
};
|
};
|
||||||
let expected_packets: Vec<HidPacket> = vec![[
|
let expected_packets: Vec<HidPacket> = 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,
|
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() {
|
fn test_hid_packet_iterator_big_single_packet() {
|
||||||
let message = Message {
|
let message = Message {
|
||||||
cid: [0x12, 0x34, 0x56, 0x78],
|
cid: [0x12, 0x34, 0x56, 0x78],
|
||||||
cmd: 0x4C,
|
cmd: CtapHidCommand::Cbor,
|
||||||
payload: vec![0xAA; 64 - 7],
|
payload: vec![0xAA; 64 - 7],
|
||||||
};
|
};
|
||||||
let expected_packets: Vec<HidPacket> = vec![[
|
let expected_packets: Vec<HidPacket> = 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,
|
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() {
|
fn test_hid_packet_iterator_two_packets() {
|
||||||
let message = Message {
|
let message = Message {
|
||||||
cid: [0x12, 0x34, 0x56, 0x78],
|
cid: [0x12, 0x34, 0x56, 0x78],
|
||||||
cmd: 0x4C,
|
cmd: CtapHidCommand::Cbor,
|
||||||
payload: vec![0xAA; 64 - 7 + 1],
|
payload: vec![0xAA; 64 - 7 + 1],
|
||||||
};
|
};
|
||||||
let expected_packets: Vec<HidPacket> = vec![
|
let expected_packets: Vec<HidPacket> = 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,
|
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]);
|
payload.extend(vec![0xBB; 64 - 5]);
|
||||||
let message = Message {
|
let message = Message {
|
||||||
cid: [0x12, 0x34, 0x56, 0x78],
|
cid: [0x12, 0x34, 0x56, 0x78],
|
||||||
cmd: 0x4C,
|
cmd: CtapHidCommand::Cbor,
|
||||||
payload,
|
payload,
|
||||||
};
|
};
|
||||||
let expected_packets: Vec<HidPacket> = vec![
|
let expected_packets: Vec<HidPacket> = 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,
|
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 {
|
let message = Message {
|
||||||
cid: [0x12, 0x34, 0x56, 0x78],
|
cid: [0x12, 0x34, 0x56, 0x78],
|
||||||
cmd: 0xAB,
|
cmd: CtapHidCommand::Msg,
|
||||||
payload,
|
payload,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut expected_packets: Vec<HidPacket> = vec![[
|
let mut expected_packets: Vec<HidPacket> = 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,
|
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);
|
assert_eq!(payload.len(), 0x1dba);
|
||||||
let message = Message {
|
let message = Message {
|
||||||
cid: [0x12, 0x34, 0x56, 0x78],
|
cid: [0x12, 0x34, 0x56, 0x78],
|
||||||
cmd: 0xAB,
|
cmd: CtapHidCommand::Msg,
|
||||||
payload,
|
payload,
|
||||||
};
|
};
|
||||||
assert!(HidPacketIterator::new(message).is_none());
|
assert!(HidPacketIterator::new(message).is_none());
|
||||||
@@ -283,7 +287,7 @@ mod test {
|
|||||||
let payload = vec![0xFF; 0x10000];
|
let payload = vec![0xFF; 0x10000];
|
||||||
let message = Message {
|
let message = Message {
|
||||||
cid: [0x12, 0x34, 0x56, 0x78],
|
cid: [0x12, 0x34, 0x56, 0x78],
|
||||||
cmd: 0xAB,
|
cmd: CtapHidCommand::Msg,
|
||||||
payload,
|
payload,
|
||||||
};
|
};
|
||||||
assert!(HidPacketIterator::new(message).is_none());
|
assert!(HidPacketIterator::new(message).is_none());
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ use super::command::AuthenticatorLargeBlobsParameters;
|
|||||||
use super::customization::MAX_MSG_SIZE;
|
use super::customization::MAX_MSG_SIZE;
|
||||||
use super::response::{AuthenticatorLargeBlobsResponse, ResponseData};
|
use super::response::{AuthenticatorLargeBlobsResponse, ResponseData};
|
||||||
use super::status_code::Ctap2StatusCode;
|
use super::status_code::Ctap2StatusCode;
|
||||||
use super::storage::PersistentStore;
|
use crate::ctap::storage;
|
||||||
use crate::env::Env;
|
use crate::env::Env;
|
||||||
use alloc::vec;
|
use alloc::vec;
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
@@ -45,9 +45,9 @@ impl LargeBlobs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Process the large blob command.
|
/// Process the large blob command.
|
||||||
pub fn process_command<E: Env>(
|
pub fn process_command(
|
||||||
&mut self,
|
&mut self,
|
||||||
persistent_store: &mut PersistentStore<E>,
|
env: &mut impl Env,
|
||||||
client_pin: &mut ClientPin,
|
client_pin: &mut ClientPin,
|
||||||
large_blobs_params: AuthenticatorLargeBlobsParameters,
|
large_blobs_params: AuthenticatorLargeBlobsParameters,
|
||||||
) -> Result<ResponseData, Ctap2StatusCode> {
|
) -> Result<ResponseData, Ctap2StatusCode> {
|
||||||
@@ -66,7 +66,7 @@ impl LargeBlobs {
|
|||||||
if get > MAX_FRAGMENT_LENGTH || offset.checked_add(get).is_none() {
|
if get > MAX_FRAGMENT_LENGTH || offset.checked_add(get).is_none() {
|
||||||
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_LENGTH);
|
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(
|
return Ok(ResponseData::AuthenticatorLargeBlobs(Some(
|
||||||
AuthenticatorLargeBlobsResponse { config },
|
AuthenticatorLargeBlobsResponse { config },
|
||||||
)));
|
)));
|
||||||
@@ -85,7 +85,7 @@ impl LargeBlobs {
|
|||||||
if offset != self.expected_next_offset {
|
if offset != self.expected_next_offset {
|
||||||
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_SEQ);
|
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 =
|
let pin_uv_auth_param =
|
||||||
pin_uv_auth_param.ok_or(Ctap2StatusCode::CTAP2_ERR_PUAT_REQUIRED)?;
|
pin_uv_auth_param.ok_or(Ctap2StatusCode::CTAP2_ERR_PUAT_REQUIRED)?;
|
||||||
let pin_uv_auth_protocol =
|
let pin_uv_auth_protocol =
|
||||||
@@ -122,7 +122,7 @@ impl LargeBlobs {
|
|||||||
self.buffer = Vec::new();
|
self.buffer = Vec::new();
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_INTEGRITY_FAILURE);
|
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();
|
self.buffer = Vec::new();
|
||||||
}
|
}
|
||||||
return Ok(ResponseData::AuthenticatorLargeBlobs(None));
|
return Ok(ResponseData::AuthenticatorLargeBlobs(None));
|
||||||
@@ -143,7 +143,6 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_process_command_get_empty() {
|
fn test_process_command_get_empty() {
|
||||||
let mut env = TestEnv::new();
|
let mut env = TestEnv::new();
|
||||||
let mut persistent_store = PersistentStore::new(&mut env);
|
|
||||||
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
|
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
|
||||||
let pin_uv_auth_token = [0x55; 32];
|
let pin_uv_auth_token = [0x55; 32];
|
||||||
let mut client_pin =
|
let mut client_pin =
|
||||||
@@ -163,7 +162,7 @@ mod test {
|
|||||||
pin_uv_auth_protocol: None,
|
pin_uv_auth_protocol: None,
|
||||||
};
|
};
|
||||||
let large_blobs_response =
|
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() {
|
match large_blobs_response.unwrap() {
|
||||||
ResponseData::AuthenticatorLargeBlobs(Some(response)) => {
|
ResponseData::AuthenticatorLargeBlobs(Some(response)) => {
|
||||||
assert_eq!(response.config, large_blob);
|
assert_eq!(response.config, large_blob);
|
||||||
@@ -175,7 +174,6 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_process_command_commit_and_get() {
|
fn test_process_command_commit_and_get() {
|
||||||
let mut env = TestEnv::new();
|
let mut env = TestEnv::new();
|
||||||
let mut persistent_store = PersistentStore::new(&mut env);
|
|
||||||
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
|
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
|
||||||
let pin_uv_auth_token = [0x55; 32];
|
let pin_uv_auth_token = [0x55; 32];
|
||||||
let mut client_pin =
|
let mut client_pin =
|
||||||
@@ -196,7 +194,7 @@ mod test {
|
|||||||
pin_uv_auth_protocol: None,
|
pin_uv_auth_protocol: None,
|
||||||
};
|
};
|
||||||
let large_blobs_response =
|
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!(
|
assert_eq!(
|
||||||
large_blobs_response,
|
large_blobs_response,
|
||||||
Ok(ResponseData::AuthenticatorLargeBlobs(None))
|
Ok(ResponseData::AuthenticatorLargeBlobs(None))
|
||||||
@@ -211,7 +209,7 @@ mod test {
|
|||||||
pin_uv_auth_protocol: None,
|
pin_uv_auth_protocol: None,
|
||||||
};
|
};
|
||||||
let large_blobs_response =
|
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!(
|
assert_eq!(
|
||||||
large_blobs_response,
|
large_blobs_response,
|
||||||
Ok(ResponseData::AuthenticatorLargeBlobs(None))
|
Ok(ResponseData::AuthenticatorLargeBlobs(None))
|
||||||
@@ -226,7 +224,7 @@ mod test {
|
|||||||
pin_uv_auth_protocol: None,
|
pin_uv_auth_protocol: None,
|
||||||
};
|
};
|
||||||
let large_blobs_response =
|
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() {
|
match large_blobs_response.unwrap() {
|
||||||
ResponseData::AuthenticatorLargeBlobs(Some(response)) => {
|
ResponseData::AuthenticatorLargeBlobs(Some(response)) => {
|
||||||
assert_eq!(response.config, large_blob);
|
assert_eq!(response.config, large_blob);
|
||||||
@@ -238,7 +236,6 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_process_command_commit_unexpected_offset() {
|
fn test_process_command_commit_unexpected_offset() {
|
||||||
let mut env = TestEnv::new();
|
let mut env = TestEnv::new();
|
||||||
let mut persistent_store = PersistentStore::new(&mut env);
|
|
||||||
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
|
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
|
||||||
let pin_uv_auth_token = [0x55; 32];
|
let pin_uv_auth_token = [0x55; 32];
|
||||||
let mut client_pin =
|
let mut client_pin =
|
||||||
@@ -259,7 +256,7 @@ mod test {
|
|||||||
pin_uv_auth_protocol: None,
|
pin_uv_auth_protocol: None,
|
||||||
};
|
};
|
||||||
let large_blobs_response =
|
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!(
|
assert_eq!(
|
||||||
large_blobs_response,
|
large_blobs_response,
|
||||||
Ok(ResponseData::AuthenticatorLargeBlobs(None))
|
Ok(ResponseData::AuthenticatorLargeBlobs(None))
|
||||||
@@ -275,7 +272,7 @@ mod test {
|
|||||||
pin_uv_auth_protocol: None,
|
pin_uv_auth_protocol: None,
|
||||||
};
|
};
|
||||||
let large_blobs_response =
|
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!(
|
assert_eq!(
|
||||||
large_blobs_response,
|
large_blobs_response,
|
||||||
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_SEQ),
|
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_SEQ),
|
||||||
@@ -285,7 +282,6 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_process_command_commit_unexpected_length() {
|
fn test_process_command_commit_unexpected_length() {
|
||||||
let mut env = TestEnv::new();
|
let mut env = TestEnv::new();
|
||||||
let mut persistent_store = PersistentStore::new(&mut env);
|
|
||||||
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
|
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
|
||||||
let pin_uv_auth_token = [0x55; 32];
|
let pin_uv_auth_token = [0x55; 32];
|
||||||
let mut client_pin =
|
let mut client_pin =
|
||||||
@@ -307,7 +303,7 @@ mod test {
|
|||||||
pin_uv_auth_protocol: None,
|
pin_uv_auth_protocol: None,
|
||||||
};
|
};
|
||||||
let large_blobs_response =
|
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!(
|
assert_eq!(
|
||||||
large_blobs_response,
|
large_blobs_response,
|
||||||
Ok(ResponseData::AuthenticatorLargeBlobs(None))
|
Ok(ResponseData::AuthenticatorLargeBlobs(None))
|
||||||
@@ -322,7 +318,7 @@ mod test {
|
|||||||
pin_uv_auth_protocol: None,
|
pin_uv_auth_protocol: None,
|
||||||
};
|
};
|
||||||
let large_blobs_response =
|
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!(
|
assert_eq!(
|
||||||
large_blobs_response,
|
large_blobs_response,
|
||||||
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER),
|
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER),
|
||||||
@@ -332,7 +328,6 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_process_command_commit_end_offset_overflow() {
|
fn test_process_command_commit_end_offset_overflow() {
|
||||||
let mut env = TestEnv::new();
|
let mut env = TestEnv::new();
|
||||||
let mut persistent_store = PersistentStore::new(&mut env);
|
|
||||||
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
|
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
|
||||||
let pin_uv_auth_token = [0x55; 32];
|
let pin_uv_auth_token = [0x55; 32];
|
||||||
let mut client_pin =
|
let mut client_pin =
|
||||||
@@ -348,7 +343,7 @@ mod test {
|
|||||||
pin_uv_auth_protocol: None,
|
pin_uv_auth_protocol: None,
|
||||||
};
|
};
|
||||||
assert_eq!(
|
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),
|
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_LENGTH),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -356,7 +351,6 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_process_command_commit_unexpected_hash() {
|
fn test_process_command_commit_unexpected_hash() {
|
||||||
let mut env = TestEnv::new();
|
let mut env = TestEnv::new();
|
||||||
let mut persistent_store = PersistentStore::new(&mut env);
|
|
||||||
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
|
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
|
||||||
let pin_uv_auth_token = [0x55; 32];
|
let pin_uv_auth_token = [0x55; 32];
|
||||||
let mut client_pin =
|
let mut client_pin =
|
||||||
@@ -376,7 +370,7 @@ mod test {
|
|||||||
pin_uv_auth_protocol: None,
|
pin_uv_auth_protocol: None,
|
||||||
};
|
};
|
||||||
let large_blobs_response =
|
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!(
|
assert_eq!(
|
||||||
large_blobs_response,
|
large_blobs_response,
|
||||||
Err(Ctap2StatusCode::CTAP2_ERR_INTEGRITY_FAILURE),
|
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) {
|
fn test_helper_process_command_commit_with_pin(pin_uv_auth_protocol: PinUvAuthProtocol) {
|
||||||
let mut env = TestEnv::new();
|
let mut env = TestEnv::new();
|
||||||
let mut persistent_store = PersistentStore::new(&mut env);
|
|
||||||
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
|
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
|
||||||
let pin_uv_auth_token = [0x55; 32];
|
let pin_uv_auth_token = [0x55; 32];
|
||||||
let mut client_pin =
|
let mut client_pin =
|
||||||
@@ -397,7 +390,7 @@ mod test {
|
|||||||
let mut large_blob = vec![0x1B; DATA_LEN];
|
let mut large_blob = vec![0x1B; DATA_LEN];
|
||||||
large_blob.extend_from_slice(&Sha256::hash(&large_blob[..])[..TRUNCATED_HASH_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];
|
let mut large_blob_data = vec![0xFF; 32];
|
||||||
// Command constant and offset bytes.
|
// Command constant and offset bytes.
|
||||||
large_blob_data.extend(&[0x0C, 0x00, 0x00, 0x00, 0x00, 0x00]);
|
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),
|
pin_uv_auth_protocol: Some(pin_uv_auth_protocol),
|
||||||
};
|
};
|
||||||
let large_blobs_response =
|
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!(
|
assert_eq!(
|
||||||
large_blobs_response,
|
large_blobs_response,
|
||||||
Ok(ResponseData::AuthenticatorLargeBlobs(None))
|
Ok(ResponseData::AuthenticatorLargeBlobs(None))
|
||||||
|
|||||||
500
src/ctap/mod.rs
500
src/ctap/mod.rs
File diff suppressed because it is too large
Load Diff
1406
src/ctap/storage.rs
1406
src/ctap/storage.rs
File diff suppressed because it is too large
Load Diff
15
src/env/mod.rs
vendored
15
src/env/mod.rs
vendored
@@ -3,7 +3,7 @@ use crate::api::upgrade_storage::UpgradeStorage;
|
|||||||
use crate::ctap::hid::ChannelID;
|
use crate::ctap::hid::ChannelID;
|
||||||
use crate::ctap::status_code::Ctap2StatusCode;
|
use crate::ctap::status_code::Ctap2StatusCode;
|
||||||
use crypto::rng256::Rng256;
|
use crypto::rng256::Rng256;
|
||||||
use persistent_store::{Storage, StorageResult};
|
use persistent_store::{Storage, Store};
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
pub mod test;
|
pub mod test;
|
||||||
@@ -26,16 +26,13 @@ pub trait Env {
|
|||||||
|
|
||||||
fn rng(&mut self) -> &mut Self::Rng;
|
fn rng(&mut self) -> &mut Self::Rng;
|
||||||
fn user_presence(&mut self) -> &mut Self::UserPresence;
|
fn user_presence(&mut self) -> &mut Self::UserPresence;
|
||||||
|
fn store(&mut self) -> &mut Store<Self::Storage>;
|
||||||
|
|
||||||
/// 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.
|
/// Upgrade storage is optional, so implementations may return `None`. However, implementations
|
||||||
fn storage(&mut self) -> StorageResult<Self::Storage>;
|
/// should either always return `None` or always return `Some`.
|
||||||
|
fn upgrade_storage(&mut self) -> Option<&mut Self::UpgradeStorage>;
|
||||||
/// 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<Self::UpgradeStorage>;
|
|
||||||
|
|
||||||
fn firmware_protection(&mut self) -> &mut Self::FirmwareProtection;
|
fn firmware_protection(&mut self) -> &mut Self::FirmwareProtection;
|
||||||
}
|
}
|
||||||
|
|||||||
52
src/env/test/mod.rs
vendored
52
src/env/test/mod.rs
vendored
@@ -4,26 +4,55 @@ use crate::ctap::hid::ChannelID;
|
|||||||
use crate::ctap::status_code::Ctap2StatusCode;
|
use crate::ctap::status_code::Ctap2StatusCode;
|
||||||
use crate::env::{Env, UserPresence};
|
use crate::env::{Env, UserPresence};
|
||||||
use crypto::rng256::ThreadRng256;
|
use crypto::rng256::ThreadRng256;
|
||||||
use persistent_store::{BufferOptions, BufferStorage, StorageResult};
|
use persistent_store::{BufferOptions, BufferStorage, Store};
|
||||||
|
|
||||||
mod upgrade_storage;
|
mod upgrade_storage;
|
||||||
|
|
||||||
pub struct TestEnv {
|
pub struct TestEnv {
|
||||||
rng: ThreadRng256,
|
rng: ThreadRng256,
|
||||||
user_presence: TestUserPresence,
|
user_presence: TestUserPresence,
|
||||||
|
store: Store<BufferStorage>,
|
||||||
|
upgrade_storage: Option<BufferUpgradeStorage>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TestUserPresence {
|
pub struct TestUserPresence {
|
||||||
check: Box<dyn Fn(ChannelID) -> Result<(), Ctap2StatusCode>>,
|
check: Box<dyn Fn(ChannelID) -> 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 {
|
impl TestEnv {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let rng = ThreadRng256 {};
|
let rng = ThreadRng256 {};
|
||||||
let user_presence = TestUserPresence {
|
let user_presence = TestUserPresence {
|
||||||
check: Box::new(|_| Ok(())),
|
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
|
&mut self.user_presence
|
||||||
}
|
}
|
||||||
|
|
||||||
fn storage(&mut self) -> StorageResult<Self::Storage> {
|
fn store(&mut self) -> &mut Store<Self::Storage> {
|
||||||
// Use the Nordic configuration.
|
&mut self.store
|
||||||
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 upgrade_storage(&mut self) -> StorageResult<Self::UpgradeStorage> {
|
fn upgrade_storage(&mut self) -> Option<&mut Self::UpgradeStorage> {
|
||||||
BufferUpgradeStorage::new()
|
self.upgrade_storage.as_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn firmware_protection(&mut self) -> &mut Self::FirmwareProtection {
|
fn firmware_protection(&mut self) -> &mut Self::FirmwareProtection {
|
||||||
|
|||||||
73
src/env/tock/mod.rs
vendored
73
src/env/tock/mod.rs
vendored
@@ -1,6 +1,6 @@
|
|||||||
use self::storage::{SyscallStorage, SyscallUpgradeStorage};
|
pub use self::storage::{TockStorage, TockUpgradeStorage};
|
||||||
use crate::api::firmware_protection::FirmwareProtection;
|
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::ctap::status_code::Ctap2StatusCode;
|
||||||
use crate::env::{Env, UserPresence};
|
use crate::env::{Env, UserPresence};
|
||||||
use core::cell::Cell;
|
use core::cell::Cell;
|
||||||
@@ -15,46 +15,45 @@ use libtock_drivers::console::Console;
|
|||||||
use libtock_drivers::result::{FlexUnwrap, TockError};
|
use libtock_drivers::result::{FlexUnwrap, TockError};
|
||||||
use libtock_drivers::timer::Duration;
|
use libtock_drivers::timer::Duration;
|
||||||
use libtock_drivers::{crp, led, timer, usb_ctap_hid};
|
use libtock_drivers::{crp, led, timer, usb_ctap_hid};
|
||||||
use persistent_store::StorageResult;
|
use persistent_store::{StorageResult, Store};
|
||||||
|
|
||||||
mod storage;
|
mod storage;
|
||||||
|
|
||||||
pub struct TockEnv {
|
pub struct TockEnv {
|
||||||
rng: TockRng256,
|
rng: TockRng256,
|
||||||
storage: bool,
|
store: Store<TockStorage>,
|
||||||
upgrade_storage: bool,
|
upgrade_storage: Option<TockUpgradeStorage>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TockEnv {
|
impl TockEnv {
|
||||||
/// Returns the unique instance of the Tock environment.
|
/// Returns the unique instance of the Tock environment.
|
||||||
///
|
///
|
||||||
/// This function returns `Some` the first time it is called. Afterwards, it repeatedly returns
|
/// # Panics
|
||||||
/// `None`.
|
///
|
||||||
pub fn new() -> Option<Self> {
|
/// - If called a second time.
|
||||||
// Make sure the environment was not already taken.
|
pub fn new() -> Self {
|
||||||
static TAKEN: AtomicBool = AtomicBool::new(false);
|
// We rely on `take_storage` to ensure that this function is called only once.
|
||||||
if TAKEN.fetch_or(true, Ordering::SeqCst) {
|
let storage = take_storage().unwrap();
|
||||||
return None;
|
let store = Store::new(storage).ok().unwrap();
|
||||||
}
|
let upgrade_storage = TockUpgradeStorage::new().ok();
|
||||||
Some(TockEnv {
|
TockEnv {
|
||||||
rng: TockRng256 {},
|
rng: TockRng256 {},
|
||||||
storage: false,
|
store,
|
||||||
upgrade_storage: false,
|
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
|
/// - If called a second time.
|
||||||
/// for extra precaution we mark the function as unsafe. To ensure correct usage, this function
|
pub fn take_storage() -> StorageResult<TockStorage> {
|
||||||
/// should only be called if the previous storage instance was dropped.
|
// Make sure the storage was not already taken.
|
||||||
// This function is exposed for example binaries testing the hardware. This could probably be
|
static TAKEN: AtomicBool = AtomicBool::new(false);
|
||||||
// cleaned up by having the persistent store return its storage.
|
assert!(!TAKEN.fetch_or(true, Ordering::SeqCst));
|
||||||
pub unsafe fn steal_storage() -> StorageResult<SyscallStorage> {
|
TockStorage::new()
|
||||||
SyscallStorage::new()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UserPresence for TockEnv {
|
impl UserPresence for TockEnv {
|
||||||
@@ -79,8 +78,8 @@ impl FirmwareProtection for TockEnv {
|
|||||||
impl Env for TockEnv {
|
impl Env for TockEnv {
|
||||||
type Rng = TockRng256;
|
type Rng = TockRng256;
|
||||||
type UserPresence = Self;
|
type UserPresence = Self;
|
||||||
type Storage = SyscallStorage;
|
type Storage = TockStorage;
|
||||||
type UpgradeStorage = SyscallUpgradeStorage;
|
type UpgradeStorage = TockUpgradeStorage;
|
||||||
type FirmwareProtection = Self;
|
type FirmwareProtection = Self;
|
||||||
|
|
||||||
fn rng(&mut self) -> &mut Self::Rng {
|
fn rng(&mut self) -> &mut Self::Rng {
|
||||||
@@ -91,14 +90,12 @@ impl Env for TockEnv {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn storage(&mut self) -> StorageResult<Self::Storage> {
|
fn store(&mut self) -> &mut Store<Self::Storage> {
|
||||||
assert_once(&mut self.storage);
|
&mut self.store
|
||||||
unsafe { steal_storage() }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn upgrade_storage(&mut self) -> StorageResult<Self::UpgradeStorage> {
|
fn upgrade_storage(&mut self) -> Option<&mut Self::UpgradeStorage> {
|
||||||
assert_once(&mut self.upgrade_storage);
|
self.upgrade_storage.as_mut()
|
||||||
SyscallUpgradeStorage::new()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn firmware_protection(&mut self) -> &mut Self::FirmwareProtection {
|
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.
|
// Returns whether the keepalive was sent, or false if cancelled.
|
||||||
fn send_keepalive_up_needed(
|
fn send_keepalive_up_needed(
|
||||||
cid: ChannelID,
|
cid: ChannelID,
|
||||||
@@ -146,7 +137,7 @@ fn send_keepalive_up_needed(
|
|||||||
}
|
}
|
||||||
match processed_packet {
|
match processed_packet {
|
||||||
ProcessedPacket::InitPacket { cmd, .. } => {
|
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.
|
// We ignore the payload, we can't answer with an error code anyway.
|
||||||
#[cfg(feature = "debug_ctap")]
|
#[cfg(feature = "debug_ctap")]
|
||||||
writeln!(Console::new(), "User presence check cancelled").unwrap();
|
writeln!(Console::new(), "User presence check cancelled").unwrap();
|
||||||
|
|||||||
20
src/env/tock/storage.rs
vendored
20
src/env/tock/storage.rs
vendored
@@ -115,7 +115,7 @@ fn erase_page(ptr: usize, page_length: usize) -> StorageResult<()> {
|
|||||||
block_command(DRIVER_NUMBER, command_nr::ERASE_PAGE, ptr, page_length)
|
block_command(DRIVER_NUMBER, command_nr::ERASE_PAGE, ptr, page_length)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SyscallStorage {
|
pub struct TockStorage {
|
||||||
word_size: usize,
|
word_size: usize,
|
||||||
page_size: usize,
|
page_size: usize,
|
||||||
num_pages: usize,
|
num_pages: usize,
|
||||||
@@ -124,7 +124,7 @@ pub struct SyscallStorage {
|
|||||||
storage_locations: Vec<&'static [u8]>,
|
storage_locations: Vec<&'static [u8]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SyscallStorage {
|
impl TockStorage {
|
||||||
/// Provides access to the embedded flash if available.
|
/// Provides access to the embedded flash if available.
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
@@ -134,8 +134,8 @@ impl SyscallStorage {
|
|||||||
/// - The page size is a power of two.
|
/// - The page size is a power of two.
|
||||||
/// - The page size is a multiple of the word size.
|
/// - The page size is a multiple of the word size.
|
||||||
/// - The storage is page-aligned.
|
/// - The storage is page-aligned.
|
||||||
pub fn new() -> StorageResult<SyscallStorage> {
|
pub fn new() -> StorageResult<TockStorage> {
|
||||||
let mut syscall = SyscallStorage {
|
let mut syscall = TockStorage {
|
||||||
word_size: get_info(command_nr::get_info_nr::WORD_SIZE, 0)?,
|
word_size: get_info(command_nr::get_info_nr::WORD_SIZE, 0)?,
|
||||||
page_size: get_info(command_nr::get_info_nr::PAGE_SIZE, 0)?,
|
page_size: get_info(command_nr::get_info_nr::PAGE_SIZE, 0)?,
|
||||||
num_pages: 0,
|
num_pages: 0,
|
||||||
@@ -175,7 +175,7 @@ impl SyscallStorage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Storage for SyscallStorage {
|
impl Storage for TockStorage {
|
||||||
fn word_size(&self) -> usize {
|
fn word_size(&self) -> usize {
|
||||||
self.word_size
|
self.word_size
|
||||||
}
|
}
|
||||||
@@ -217,13 +217,13 @@ impl Storage for SyscallStorage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SyscallUpgradeStorage {
|
pub struct TockUpgradeStorage {
|
||||||
page_size: usize,
|
page_size: usize,
|
||||||
partition: ModRange,
|
partition: ModRange,
|
||||||
metadata: ModRange,
|
metadata: ModRange,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SyscallUpgradeStorage {
|
impl TockUpgradeStorage {
|
||||||
/// Provides access to the other upgrade partition and metadata if available.
|
/// Provides access to the other upgrade partition and metadata if available.
|
||||||
///
|
///
|
||||||
/// The implementation assumes that storage locations returned by the kernel through
|
/// 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
|
/// Returns a `NotAligned` error if partitions or metadata ranges are
|
||||||
/// - not exclusive or,
|
/// - not exclusive or,
|
||||||
/// - not consecutive.
|
/// - not consecutive.
|
||||||
pub fn new() -> StorageResult<SyscallUpgradeStorage> {
|
pub fn new() -> StorageResult<TockUpgradeStorage> {
|
||||||
let mut locations = SyscallUpgradeStorage {
|
let mut locations = TockUpgradeStorage {
|
||||||
page_size: get_info(command_nr::get_info_nr::PAGE_SIZE, 0)?,
|
page_size: get_info(command_nr::get_info_nr::PAGE_SIZE, 0)?,
|
||||||
partition: ModRange::new_empty(),
|
partition: ModRange::new_empty(),
|
||||||
metadata: 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]> {
|
fn read_partition(&self, offset: usize, length: usize) -> StorageResult<&[u8]> {
|
||||||
if length == 0 {
|
if length == 0 {
|
||||||
return Err(StorageError::OutOfBounds);
|
return Err(StorageError::OutOfBounds);
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ pub mod env;
|
|||||||
/// CTAP implementation parameterized by its environment.
|
/// CTAP implementation parameterized by its environment.
|
||||||
pub struct Ctap<E: Env> {
|
pub struct Ctap<E: Env> {
|
||||||
env: E,
|
env: E,
|
||||||
state: CtapState<E>,
|
state: CtapState,
|
||||||
hid: CtapHid,
|
hid: CtapHid,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ impl<E: Env> Ctap<E> {
|
|||||||
Ctap { env, state, hid }
|
Ctap { env, state, hid }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn state(&mut self) -> &mut CtapState<E> {
|
pub fn state(&mut self) -> &mut CtapState {
|
||||||
&mut self.state
|
&mut self.state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let boot_time = timer.get_current_clock().flex_unwrap();
|
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 ctap = ctap2::Ctap::new(env, boot_time);
|
||||||
|
|
||||||
let mut led_counter = 0;
|
let mut led_counter = 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user