Merge branch 'develop' into env_store
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
fuzz/corpus
|
||||
target/
|
||||
Cargo.lock
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ cortex-m-rt = "*"
|
||||
cortex-m-rt-macros = "*"
|
||||
panic-abort = "0.3.2"
|
||||
rtt-target = { version = "*", features = ["cortex-m"] }
|
||||
tock-registers = { version = "0.6.0", features = ["no_std_unit_tests"] }
|
||||
|
||||
[profile.dev]
|
||||
panic = "abort"
|
||||
|
||||
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");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -15,6 +15,11 @@
|
||||
#![no_main]
|
||||
#![no_std]
|
||||
|
||||
mod bitfields;
|
||||
mod crypto_cell;
|
||||
mod registers;
|
||||
mod static_ref;
|
||||
|
||||
extern crate cortex_m;
|
||||
extern crate cortex_m_rt as rt;
|
||||
|
||||
@@ -69,7 +74,7 @@ struct BootPartition {
|
||||
}
|
||||
|
||||
impl BootPartition {
|
||||
const _FIRMWARE_LENGTH: usize = 0x00040000;
|
||||
const FIRMWARE_LENGTH: usize = 0x00040000;
|
||||
|
||||
/// Reads the metadata, returns the timestamp if all checks pass.
|
||||
pub fn read_timestamp(&self) -> Result<u32, ()> {
|
||||
@@ -93,18 +98,40 @@ impl BootPartition {
|
||||
Ok(metadata.timestamp)
|
||||
}
|
||||
|
||||
/// Placeholder for the SHA256 implementation.
|
||||
/// Computes the SHA256 of metadata information and partition data.
|
||||
///
|
||||
/// TODO implemented in next PR
|
||||
/// Without it, the bootloader will never boot anything.
|
||||
fn compute_upgrade_hash(&self, _metadata_page: &[u8]) -> [u8; 32] {
|
||||
[0; 32]
|
||||
/// Assumes that firmware address and length are divisible by the page size.
|
||||
/// This is the hardware implementation on the cryptocell.
|
||||
#[allow(clippy::assertions_on_constants)]
|
||||
fn compute_upgrade_hash(&self, metadata_page: &[u8]) -> [u8; 32] {
|
||||
debug_assert!(self.firmware_address % PAGE_SIZE == 0);
|
||||
debug_assert!(BootPartition::FIRMWARE_LENGTH % PAGE_SIZE == 0);
|
||||
let cc310 = crypto_cell::CryptoCell310::new();
|
||||
for page_offset in (0..BootPartition::FIRMWARE_LENGTH).step_by(PAGE_SIZE) {
|
||||
let page = unsafe { read_page(self.firmware_address + page_offset) };
|
||||
cc310.update(&page, false);
|
||||
}
|
||||
cc310.update(&metadata_page[32..Metadata::DATA_LEN], true);
|
||||
cc310.finalize_and_clear()
|
||||
}
|
||||
|
||||
/// Jump to the firmware.
|
||||
pub fn boot(&self) -> ! {
|
||||
let address = self.firmware_address;
|
||||
|
||||
// Clear any pending Cryptocell interrupt in NVIC
|
||||
let peripherals = cortex_m::Peripherals::take().unwrap();
|
||||
unsafe {
|
||||
// We could only clear cryptocell interrupts, but let's clean up before booting.
|
||||
// Example code to clear more specifically:
|
||||
// const CC310_IRQ: u16 = 42;
|
||||
// peripherals.NVIC.icpr[usize::from(CC310_IRQ / 32)].write(1 << (CC310_IRQ % 32));
|
||||
peripherals.NVIC.icer[0].write(0xffff_ffff);
|
||||
peripherals.NVIC.icpr[0].write(0xffff_ffff);
|
||||
peripherals.NVIC.icer[1].write(0xffff_ffff);
|
||||
peripherals.NVIC.icpr[1].write(0xffff_ffff);
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
rprintln!("Boot jump to {:08X}", address);
|
||||
let address_pointer = address as *const u32;
|
||||
|
||||
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 }
|
||||
}
|
||||
}
|
||||
@@ -25,14 +25,12 @@ use ctap2::ctap::command::{
|
||||
};
|
||||
use ctap2::ctap::hid::receive::MessageAssembler;
|
||||
use ctap2::ctap::hid::send::HidPacketIterator;
|
||||
use ctap2::ctap::hid::{ChannelID, HidPacket, Message};
|
||||
use ctap2::ctap::hid::{ChannelID, CtapHidCommand, HidPacket, Message};
|
||||
use ctap2::env::test::TestEnv;
|
||||
use ctap2::Ctap;
|
||||
use libtock_drivers::timer::{ClockValue, Timestamp};
|
||||
|
||||
const COMMAND_INIT: u8 = 0x06;
|
||||
const CHANNEL_BROADCAST: ChannelID = [0xFF, 0xFF, 0xFF, 0xFF];
|
||||
const PACKET_TYPE_MASK: u8 = 0x80;
|
||||
|
||||
const CLOCK_FREQUENCY_HZ: usize = 32768;
|
||||
const DUMMY_TIMESTAMP: Timestamp<isize> = Timestamp::from_ms(0);
|
||||
@@ -53,13 +51,14 @@ fn raw_to_message(data: &[u8]) -> Message {
|
||||
cid[..data.len()].copy_from_slice(data);
|
||||
Message {
|
||||
cid,
|
||||
cmd: 0,
|
||||
// Arbitrary command.
|
||||
cmd: CtapHidCommand::Cbor,
|
||||
payload: vec![],
|
||||
}
|
||||
} else {
|
||||
Message {
|
||||
cid: array_ref!(data, 0, 4).clone(),
|
||||
cmd: data[4],
|
||||
cmd: CtapHidCommand::from(data[4]),
|
||||
payload: data[5..].to_vec(),
|
||||
}
|
||||
}
|
||||
@@ -71,7 +70,7 @@ fn initialize(ctap: &mut Ctap<TestEnv>) -> ChannelID {
|
||||
let nonce = vec![0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0];
|
||||
let message = Message {
|
||||
cid: CHANNEL_BROADCAST,
|
||||
cmd: COMMAND_INIT,
|
||||
cmd: CtapHidCommand::Init,
|
||||
payload: nonce,
|
||||
};
|
||||
let mut assembler_reply = MessageAssembler::new();
|
||||
@@ -168,7 +167,7 @@ pub fn process_ctap_specific_type(data: &[u8], input_type: InputType) {
|
||||
|
||||
// Splits the given data as HID packets and reassembles it, verifying that the original input message is reconstructed.
|
||||
pub fn split_assemble_hid_packets(data: &[u8]) {
|
||||
let mut message = raw_to_message(data);
|
||||
let message = raw_to_message(data);
|
||||
if let Some(hid_packet_iterator) = HidPacketIterator::new(message.clone()) {
|
||||
let mut assembler = MessageAssembler::new();
|
||||
let packets: Vec<HidPacket> = hid_packet_iterator.collect();
|
||||
@@ -176,7 +175,6 @@ pub fn split_assemble_hid_packets(data: &[u8]) {
|
||||
for packet in first_packets {
|
||||
assert_eq!(assembler.parse_packet(packet, DUMMY_TIMESTAMP), Ok(None));
|
||||
}
|
||||
message.cmd &= !PACKET_TYPE_MASK;
|
||||
assert_eq!(
|
||||
assembler.parse_packet(last_packet, DUMMY_TIMESTAMP),
|
||||
Ok(Some(message))
|
||||
|
||||
@@ -20,4 +20,4 @@ done_text="$(tput bold)DONE.$(tput sgr0)"
|
||||
set -e
|
||||
|
||||
# Install cargo-fuzz library.
|
||||
cargo +stable install cargo-fuzz
|
||||
cargo +stable install cargo-fuzz --version 0.10.2
|
||||
|
||||
@@ -32,13 +32,50 @@ use core::fmt::Write;
|
||||
use libtock_drivers::console::Console;
|
||||
use libtock_drivers::timer::{ClockValue, Duration, Timestamp};
|
||||
|
||||
// CTAP specification (version 20190130) section 8.1
|
||||
// TODO: Channel allocation, section 8.1.3?
|
||||
// TODO: Transaction timeout, section 8.1.5.2
|
||||
|
||||
pub type HidPacket = [u8; 64];
|
||||
pub type ChannelID = [u8; 4];
|
||||
|
||||
/// CTAPHID commands
|
||||
///
|
||||
/// See section 11.2.9. of FIDO 2.1 (2021-06-15).
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum CtapHidCommand {
|
||||
Ping = 0x01,
|
||||
Msg = 0x03,
|
||||
// Lock is optional and may be used in the future.
|
||||
Lock = 0x04,
|
||||
Init = 0x06,
|
||||
Wink = 0x08,
|
||||
Cbor = 0x10,
|
||||
Cancel = 0x11,
|
||||
Keepalive = 0x3B,
|
||||
Error = 0x3F,
|
||||
// VendorFirst and VendorLast describe a range, and are not commands themselves.
|
||||
_VendorFirst = 0x40,
|
||||
_VendorLast = 0x7F,
|
||||
}
|
||||
|
||||
impl From<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> {
|
||||
InitPacket {
|
||||
cmd: u8,
|
||||
@@ -51,72 +88,79 @@ pub enum ProcessedPacket<'a> {
|
||||
},
|
||||
}
|
||||
|
||||
// An assembled CTAPHID command.
|
||||
/// An assembled CTAPHID command.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Message {
|
||||
// Channel ID.
|
||||
pub cid: ChannelID,
|
||||
// Command.
|
||||
pub cmd: u8,
|
||||
pub cmd: CtapHidCommand,
|
||||
// Bytes of the message.
|
||||
pub payload: Vec<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 {
|
||||
assembler: MessageAssembler,
|
||||
// The specification (version 20190130) only requires unique CIDs ; the allocation algorithm is
|
||||
// vendor specific.
|
||||
// The specification only requires unique CIDs, the allocation algorithm is vendor specific.
|
||||
// We allocate them incrementally, that is all `cid` such that 1 <= cid <= allocated_cids are
|
||||
// allocated.
|
||||
// In packets, the ID encoding is Big Endian to match what is used throughout CTAP (with the
|
||||
// u32::to/from_be_bytes methods).
|
||||
// TODO(kaczmarczyck) We might want to limit or timeout open channels.
|
||||
allocated_cids: usize,
|
||||
pub(crate) wink_permission: TimedPermission,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub enum KeepaliveStatus {
|
||||
Processing,
|
||||
UpNeeded,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
// TODO(kaczmarczyck) disable the warning in the end
|
||||
impl CtapHid {
|
||||
// CTAP specification (version 20190130) section 8.1.3
|
||||
// We implement CTAP 2.1 from 2021-06-15. Please see section
|
||||
// 11.2. USB Human Interface Device (USB HID)
|
||||
const CHANNEL_RESERVED: ChannelID = [0, 0, 0, 0];
|
||||
const CHANNEL_BROADCAST: ChannelID = [0xFF, 0xFF, 0xFF, 0xFF];
|
||||
const TYPE_INIT_BIT: u8 = 0x80;
|
||||
const PACKET_TYPE_MASK: u8 = 0x80;
|
||||
|
||||
// CTAP specification (version 20190130) section 8.1.9
|
||||
const COMMAND_PING: u8 = 0x01;
|
||||
const COMMAND_MSG: u8 = 0x03;
|
||||
const COMMAND_INIT: u8 = 0x06;
|
||||
const COMMAND_CBOR: u8 = 0x10;
|
||||
pub const COMMAND_CANCEL: u8 = 0x11;
|
||||
const COMMAND_KEEPALIVE: u8 = 0x3B;
|
||||
const COMMAND_ERROR: u8 = 0x3F;
|
||||
// TODO: optional lock command
|
||||
const COMMAND_LOCK: u8 = 0x04;
|
||||
const COMMAND_WINK: u8 = 0x08;
|
||||
const COMMAND_VENDOR_FIRST: u8 = 0x40;
|
||||
const COMMAND_VENDOR_LAST: u8 = 0x7F;
|
||||
|
||||
// CTAP specification (version 20190130) section 8.1.9.1.6
|
||||
const ERR_INVALID_CMD: u8 = 0x01;
|
||||
const ERR_INVALID_PAR: u8 = 0x02;
|
||||
const _ERR_INVALID_PAR: u8 = 0x02;
|
||||
const ERR_INVALID_LEN: u8 = 0x03;
|
||||
const ERR_INVALID_SEQ: u8 = 0x04;
|
||||
const ERR_MSG_TIMEOUT: u8 = 0x05;
|
||||
const ERR_CHANNEL_BUSY: u8 = 0x06;
|
||||
const ERR_LOCK_REQUIRED: u8 = 0x0A;
|
||||
const _ERR_LOCK_REQUIRED: u8 = 0x0A;
|
||||
const ERR_INVALID_CHANNEL: u8 = 0x0B;
|
||||
const ERR_OTHER: u8 = 0x7F;
|
||||
const _ERR_OTHER: u8 = 0x7F;
|
||||
|
||||
// CTAP specification (version 20190130) section 8.1.9.1.3
|
||||
// See section 11.2.9.1.3. CTAPHID_INIT (0x06).
|
||||
const PROTOCOL_VERSION: u8 = 2;
|
||||
|
||||
// The device version number is vendor-defined.
|
||||
const DEVICE_VERSION_MAJOR: u8 = 1;
|
||||
const DEVICE_VERSION_MINOR: u8 = 0;
|
||||
@@ -124,6 +168,7 @@ impl CtapHid {
|
||||
|
||||
const CAPABILITY_WINK: u8 = 0x01;
|
||||
const CAPABILITY_CBOR: u8 = 0x04;
|
||||
#[cfg(not(feature = "with_ctap1"))]
|
||||
const CAPABILITY_NMSG: u8 = 0x08;
|
||||
// Capabilitites currently supported by this device.
|
||||
#[cfg(feature = "with_ctap1")]
|
||||
@@ -136,6 +181,7 @@ impl CtapHid {
|
||||
const TIMEOUT_DURATION: Duration<isize> = Duration::from_ms(100);
|
||||
const WINK_TIMEOUT_DURATION: Duration<isize> = Duration::from_ms(5000);
|
||||
|
||||
/// Creates a new idle HID state.
|
||||
pub fn new() -> CtapHid {
|
||||
CtapHid {
|
||||
assembler: MessageAssembler::new(),
|
||||
@@ -144,16 +190,26 @@ impl CtapHid {
|
||||
}
|
||||
}
|
||||
|
||||
// Process an incoming USB HID packet, and optionally returns a list of outgoing packets to
|
||||
// send as a reply.
|
||||
pub fn process_hid_packet(
|
||||
&mut self,
|
||||
env: &mut impl Env,
|
||||
packet: &HidPacket,
|
||||
clock_value: ClockValue,
|
||||
ctap_state: &mut CtapState,
|
||||
) -> HidPacketIterator {
|
||||
// TODO: Send COMMAND_KEEPALIVE every 100ms?
|
||||
/// Parses a packet, and preprocesses some messages and errors.
|
||||
///
|
||||
/// The preprocessed commands are:
|
||||
/// - INIT
|
||||
/// - CANCEL
|
||||
/// - ERROR
|
||||
/// - Unknown and unexpected commands like KEEPALIVE
|
||||
/// - LOCK is not implemented and currently treated like an unknown command
|
||||
///
|
||||
/// Commands that may still be processed:
|
||||
/// - PING
|
||||
/// - MSG
|
||||
/// - WINK
|
||||
/// - CBOR
|
||||
///
|
||||
/// You may ignore PING, it's behaving correctly by default (input == output).
|
||||
/// Ignoring the others is incorrect behavior. You have to at least replace them with an error
|
||||
/// message:
|
||||
/// `CtapHid::error_message(message.cid, CtapHid::ERR_INVALID_CMD)`
|
||||
pub fn parse_packet(&mut self, packet: &HidPacket, clock_value: ClockValue) -> Option<Message> {
|
||||
match self
|
||||
.assembler
|
||||
.parse_packet(packet, Timestamp::<isize>::from_clock_value(clock_value))
|
||||
@@ -161,170 +217,42 @@ impl CtapHid {
|
||||
Ok(Some(message)) => {
|
||||
#[cfg(feature = "debug_ctap")]
|
||||
writeln!(&mut Console::new(), "Received message: {:02x?}", message).unwrap();
|
||||
|
||||
let cid = message.cid;
|
||||
if !self.has_valid_channel(&message) {
|
||||
#[cfg(feature = "debug_ctap")]
|
||||
writeln!(&mut Console::new(), "Invalid channel: {:02x?}", cid).unwrap();
|
||||
return CtapHid::error_message(cid, CtapHid::ERR_INVALID_CHANNEL);
|
||||
}
|
||||
// If another command arrives, stop winking to prevent accidential button touches.
|
||||
self.wink_permission = TimedPermission::waiting();
|
||||
|
||||
match message.cmd {
|
||||
// CTAP specification (version 20190130) section 8.1.9.1.1
|
||||
CtapHid::COMMAND_MSG => {
|
||||
// If we don't have CTAP1 backward compatibilty, this command is invalid.
|
||||
#[cfg(not(feature = "with_ctap1"))]
|
||||
return CtapHid::error_message(cid, CtapHid::ERR_INVALID_CMD);
|
||||
|
||||
#[cfg(feature = "with_ctap1")]
|
||||
match ctap1::Ctap1Command::process_command(
|
||||
env,
|
||||
&message.payload,
|
||||
ctap_state,
|
||||
clock_value,
|
||||
) {
|
||||
Ok(payload) => CtapHid::ctap1_success_message(cid, &payload),
|
||||
Err(ctap1_status_code) => {
|
||||
CtapHid::ctap1_error_message(cid, ctap1_status_code)
|
||||
}
|
||||
}
|
||||
}
|
||||
// CTAP specification (version 20190130) section 8.1.9.1.2
|
||||
CtapHid::COMMAND_CBOR => {
|
||||
// CTAP specification (version 20190130) section 8.1.5.1
|
||||
// Each transaction is atomic, so we process the command directly here and
|
||||
// don't handle any other packet in the meantime.
|
||||
// TODO: Send keep-alive packets in the meantime.
|
||||
let response =
|
||||
ctap_state.process_command(env, &message.payload, cid, clock_value);
|
||||
if let Some(iterator) = CtapHid::split_message(Message {
|
||||
cid,
|
||||
cmd: CtapHid::COMMAND_CBOR,
|
||||
payload: response,
|
||||
}) {
|
||||
iterator
|
||||
} else {
|
||||
// Handle the case of a payload > 7609 bytes.
|
||||
// Although this shouldn't happen if the FIDO2 commands are implemented
|
||||
// correctly, we reply with a vendor specific code instead of silently
|
||||
// ignoring the error.
|
||||
//
|
||||
// The error payload that we send instead is 1 <= 7609 bytes, so it is
|
||||
// safe to unwrap() the result.
|
||||
CtapHid::split_message(Message {
|
||||
cid,
|
||||
cmd: CtapHid::COMMAND_CBOR,
|
||||
payload: vec![
|
||||
Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR as u8,
|
||||
],
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
// CTAP specification (version 20190130) section 8.1.9.1.3
|
||||
CtapHid::COMMAND_INIT => {
|
||||
if message.payload.len() != 8 {
|
||||
return CtapHid::error_message(cid, CtapHid::ERR_INVALID_LEN);
|
||||
}
|
||||
|
||||
let new_cid = if cid == CtapHid::CHANNEL_BROADCAST {
|
||||
// TODO: Prevent allocating 2^32 channels.
|
||||
self.allocated_cids += 1;
|
||||
(self.allocated_cids as u32).to_be_bytes()
|
||||
} else {
|
||||
// Sync the channel and discard the current transaction.
|
||||
cid
|
||||
};
|
||||
|
||||
let mut payload = vec![0; 17];
|
||||
payload[..8].copy_from_slice(&message.payload);
|
||||
payload[8..12].copy_from_slice(&new_cid);
|
||||
payload[12] = CtapHid::PROTOCOL_VERSION;
|
||||
payload[13] = CtapHid::DEVICE_VERSION_MAJOR;
|
||||
payload[14] = CtapHid::DEVICE_VERSION_MINOR;
|
||||
payload[15] = CtapHid::DEVICE_VERSION_BUILD;
|
||||
payload[16] = CtapHid::CAPABILITIES;
|
||||
|
||||
// This unwrap is safe because the payload length is 17 <= 7609 bytes.
|
||||
CtapHid::split_message(Message {
|
||||
cid,
|
||||
cmd: CtapHid::COMMAND_INIT,
|
||||
payload,
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
// CTAP specification (version 20190130) section 8.1.9.1.4
|
||||
CtapHid::COMMAND_PING => {
|
||||
// Pong the same message.
|
||||
// This unwrap is safe because if we could parse the incoming message, it's
|
||||
// payload length must be <= 7609 bytes.
|
||||
CtapHid::split_message(message).unwrap()
|
||||
}
|
||||
// CTAP specification (version 20190130) section 8.1.9.1.5
|
||||
CtapHid::COMMAND_CANCEL => {
|
||||
// Authenticators MUST NOT reply to this message.
|
||||
// CANCEL is handled during user presence checks in main.
|
||||
HidPacketIterator::none()
|
||||
}
|
||||
// Optional commands
|
||||
// CTAP specification (version 20190130) section 8.1.9.2.1
|
||||
CtapHid::COMMAND_WINK => {
|
||||
if !message.payload.is_empty() {
|
||||
return CtapHid::error_message(cid, CtapHid::ERR_INVALID_LEN);
|
||||
}
|
||||
self.wink_permission =
|
||||
TimedPermission::granted(clock_value, CtapHid::WINK_TIMEOUT_DURATION);
|
||||
CtapHid::split_message(Message {
|
||||
cid,
|
||||
cmd: CtapHid::COMMAND_WINK,
|
||||
payload: vec![],
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
// CTAP specification (version 20190130) section 8.1.9.2.2
|
||||
// TODO: implement LOCK
|
||||
_ => {
|
||||
// Unknown or unsupported command.
|
||||
CtapHid::error_message(cid, CtapHid::ERR_INVALID_CMD)
|
||||
}
|
||||
}
|
||||
self.preprocess_message(message)
|
||||
}
|
||||
Ok(None) => {
|
||||
// Waiting for more packets to assemble the message, nothing to send for now.
|
||||
HidPacketIterator::none()
|
||||
None
|
||||
}
|
||||
Err((cid, error)) => {
|
||||
if !self.is_allocated_channel(cid)
|
||||
&& error != receive::Error::UnexpectedContinuation
|
||||
{
|
||||
CtapHid::error_message(cid, CtapHid::ERR_INVALID_CHANNEL)
|
||||
Some(CtapHid::error_message(cid, CtapHid::ERR_INVALID_CHANNEL))
|
||||
} else {
|
||||
match error {
|
||||
receive::Error::UnexpectedChannel => {
|
||||
CtapHid::error_message(cid, CtapHid::ERR_CHANNEL_BUSY)
|
||||
Some(CtapHid::error_message(cid, CtapHid::ERR_CHANNEL_BUSY))
|
||||
}
|
||||
receive::Error::UnexpectedInit => {
|
||||
// TODO: Should we send another error code in this case?
|
||||
// Technically, we were expecting a sequence number and got another
|
||||
// byte, although the command/seqnum bit has higher-level semantics
|
||||
// than sequence numbers.
|
||||
CtapHid::error_message(cid, CtapHid::ERR_INVALID_SEQ)
|
||||
Some(CtapHid::error_message(cid, CtapHid::ERR_INVALID_SEQ))
|
||||
}
|
||||
receive::Error::UnexpectedContinuation => {
|
||||
// CTAP specification (version 20190130) section 8.1.5.4
|
||||
// Spurious continuation packets will be ignored.
|
||||
HidPacketIterator::none()
|
||||
None
|
||||
}
|
||||
receive::Error::UnexpectedSeq => {
|
||||
CtapHid::error_message(cid, CtapHid::ERR_INVALID_SEQ)
|
||||
Some(CtapHid::error_message(cid, CtapHid::ERR_INVALID_SEQ))
|
||||
}
|
||||
receive::Error::UnexpectedLen => {
|
||||
CtapHid::error_message(cid, CtapHid::ERR_INVALID_LEN)
|
||||
Some(CtapHid::error_message(cid, CtapHid::ERR_INVALID_LEN))
|
||||
}
|
||||
receive::Error::Timeout => {
|
||||
CtapHid::error_message(cid, CtapHid::ERR_MSG_TIMEOUT)
|
||||
Some(CtapHid::error_message(cid, CtapHid::ERR_MSG_TIMEOUT))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -332,10 +260,157 @@ impl CtapHid {
|
||||
}
|
||||
}
|
||||
|
||||
/// Processes HID-only commands of a message and returns an outgoing message if necessary.
|
||||
///
|
||||
/// The preprocessed commands are:
|
||||
/// - INIT
|
||||
/// - CANCEL
|
||||
/// - ERROR
|
||||
/// - Unknown and unexpected commands like KEEPALIVE
|
||||
/// - LOCK is not implemented and currently treated like an unknown command
|
||||
fn preprocess_message(&mut self, message: Message) -> Option<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 {
|
||||
match message.cid {
|
||||
// Only INIT commands use the broadcast channel.
|
||||
CtapHid::CHANNEL_BROADCAST => message.cmd == CtapHid::COMMAND_INIT,
|
||||
CtapHid::CHANNEL_BROADCAST => message.cmd == CtapHidCommand::Init,
|
||||
// Check that the channel is allocated.
|
||||
_ => self.is_allocated_channel(message.cid),
|
||||
}
|
||||
@@ -345,16 +420,15 @@ impl CtapHid {
|
||||
cid != CtapHid::CHANNEL_RESERVED && u32::from_be_bytes(cid) as usize <= self.allocated_cids
|
||||
}
|
||||
|
||||
fn error_message(cid: ChannelID, error_code: u8) -> HidPacketIterator {
|
||||
// This unwrap is safe because the payload length is 1 <= 7609 bytes.
|
||||
CtapHid::split_message(Message {
|
||||
fn error_message(cid: ChannelID, error_code: u8) -> Message {
|
||||
Message {
|
||||
cid,
|
||||
cmd: CtapHid::COMMAND_ERROR,
|
||||
cmd: CtapHidCommand::Error,
|
||||
payload: vec![error_code],
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function to parse a raw packet.
|
||||
pub fn process_single_packet(packet: &HidPacket) -> (&ChannelID, ProcessedPacket) {
|
||||
let (cid, rest) = array_refs![packet, 4, 60];
|
||||
if rest[0] & CtapHid::PACKET_TYPE_MASK != 0 {
|
||||
@@ -379,56 +453,65 @@ impl CtapHid {
|
||||
}
|
||||
}
|
||||
|
||||
fn split_message(message: Message) -> Option<HidPacketIterator> {
|
||||
#[cfg(feature = "debug_ctap")]
|
||||
writeln!(&mut Console::new(), "Sending message: {:02x?}", message).unwrap();
|
||||
HidPacketIterator::new(message)
|
||||
/// Splits the message and unwraps the result.
|
||||
///
|
||||
/// Unwrapping handles the case of payload lengths > 7609 bytes. All responses are fixed
|
||||
/// length, with the exception of:
|
||||
/// - PING, but here output equals the (validated) input,
|
||||
/// - CBOR, where long responses are conceivable.
|
||||
///
|
||||
/// Long CBOR responses should not happen, but we might not catch all edge cases, like for
|
||||
/// example long user names that are part of the output of an assertion. These cases should be
|
||||
/// correctly handled by the CTAP implementation. It is therefore an internal error from the
|
||||
/// HID perspective.
|
||||
fn split_message(message: Message) -> HidPacketIterator {
|
||||
let cid = message.cid;
|
||||
HidPacketIterator::new(message).unwrap_or_else(|| {
|
||||
// The error payload is 1 <= 7609 bytes, so unwrap() is safe.
|
||||
HidPacketIterator::new(Message {
|
||||
cid,
|
||||
cmd: CtapHidCommand::Cbor,
|
||||
payload: vec![Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR as u8],
|
||||
})
|
||||
.unwrap()
|
||||
})
|
||||
}
|
||||
|
||||
/// Generates the HID response packets for a keepalive status.
|
||||
pub fn keepalive(cid: ChannelID, status: KeepaliveStatus) -> HidPacketIterator {
|
||||
let status_code = match status {
|
||||
KeepaliveStatus::Processing => 1,
|
||||
KeepaliveStatus::UpNeeded => 2,
|
||||
};
|
||||
// This unwrap is safe because the payload length is 1 <= 7609 bytes.
|
||||
CtapHid::split_message(Message {
|
||||
cid,
|
||||
cmd: CtapHid::COMMAND_KEEPALIVE,
|
||||
payload: vec![status_code],
|
||||
cmd: CtapHidCommand::Keepalive,
|
||||
payload: vec![status as u8],
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Returns whether a wink permission is currently granted.
|
||||
pub fn should_wink(&self, now: ClockValue) -> bool {
|
||||
self.wink_permission.is_granted(now)
|
||||
}
|
||||
|
||||
#[cfg(feature = "with_ctap1")]
|
||||
fn ctap1_error_message(
|
||||
cid: ChannelID,
|
||||
error_code: ctap1::Ctap1StatusCode,
|
||||
) -> HidPacketIterator {
|
||||
// This unwrap is safe because the payload length is 2 <= 7609 bytes
|
||||
fn ctap1_error_message(cid: ChannelID, error_code: ctap1::Ctap1StatusCode) -> Message {
|
||||
let code: u16 = error_code.into();
|
||||
CtapHid::split_message(Message {
|
||||
Message {
|
||||
cid,
|
||||
cmd: CtapHid::COMMAND_MSG,
|
||||
cmd: CtapHidCommand::Msg,
|
||||
payload: code.to_be_bytes().to_vec(),
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "with_ctap1")]
|
||||
fn ctap1_success_message(cid: ChannelID, payload: &[u8]) -> HidPacketIterator {
|
||||
fn ctap1_success_message(cid: ChannelID, payload: &[u8]) -> Message {
|
||||
let mut response = payload.to_vec();
|
||||
let code: u16 = ctap1::Ctap1StatusCode::SW_SUCCESS.into();
|
||||
response.extend_from_slice(&code.to_be_bytes());
|
||||
CtapHid::split_message(Message {
|
||||
Message {
|
||||
cid,
|
||||
cmd: CtapHid::COMMAND_MSG,
|
||||
cmd: CtapHidCommand::Msg,
|
||||
payload: response,
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -478,7 +561,7 @@ mod test {
|
||||
ctap_state,
|
||||
vec![Message {
|
||||
cid: CtapHid::CHANNEL_BROADCAST,
|
||||
cmd: CtapHid::COMMAND_INIT,
|
||||
cmd: CtapHidCommand::Init,
|
||||
payload: nonce.clone(),
|
||||
}],
|
||||
);
|
||||
@@ -500,7 +583,7 @@ mod test {
|
||||
for payload_len in 0..7609 {
|
||||
let message = Message {
|
||||
cid: [0x12, 0x34, 0x56, 0x78],
|
||||
cmd: 0x00,
|
||||
cmd: CtapHidCommand::Cbor,
|
||||
payload: vec![0xFF; payload_len],
|
||||
};
|
||||
|
||||
@@ -552,7 +635,7 @@ mod test {
|
||||
&mut ctap_state,
|
||||
vec![Message {
|
||||
cid: CtapHid::CHANNEL_BROADCAST,
|
||||
cmd: CtapHid::COMMAND_INIT,
|
||||
cmd: CtapHidCommand::Init,
|
||||
payload: vec![0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0],
|
||||
}],
|
||||
);
|
||||
@@ -561,7 +644,7 @@ mod test {
|
||||
reply,
|
||||
Some(vec![Message {
|
||||
cid: CtapHid::CHANNEL_BROADCAST,
|
||||
cmd: CtapHid::COMMAND_INIT,
|
||||
cmd: CtapHidCommand::Init,
|
||||
payload: vec![
|
||||
0x12, // Nonce
|
||||
0x34,
|
||||
@@ -623,7 +706,7 @@ mod test {
|
||||
result,
|
||||
vec![Message {
|
||||
cid,
|
||||
cmd: CtapHid::COMMAND_INIT,
|
||||
cmd: CtapHidCommand::Init,
|
||||
payload: vec![
|
||||
0x12, // Nonce
|
||||
0x34,
|
||||
@@ -660,7 +743,7 @@ mod test {
|
||||
&mut ctap_state,
|
||||
vec![Message {
|
||||
cid,
|
||||
cmd: CtapHid::COMMAND_PING,
|
||||
cmd: CtapHidCommand::Ping,
|
||||
payload: vec![0x99, 0x99],
|
||||
}],
|
||||
);
|
||||
@@ -669,7 +752,7 @@ mod test {
|
||||
reply,
|
||||
Some(vec![Message {
|
||||
cid,
|
||||
cmd: CtapHid::COMMAND_PING,
|
||||
cmd: CtapHidCommand::Ping,
|
||||
payload: vec![0x99, 0x99]
|
||||
}])
|
||||
);
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
use super::super::customization::MAX_MSG_SIZE;
|
||||
use super::{ChannelID, CtapHid, HidPacket, Message, ProcessedPacket};
|
||||
use super::{ChannelID, CtapHid, CtapHidCommand, HidPacket, Message, ProcessedPacket};
|
||||
use alloc::vec::Vec;
|
||||
use core::mem::swap;
|
||||
use libtock_drivers::timer::Timestamp;
|
||||
@@ -131,7 +131,7 @@ impl MessageAssembler {
|
||||
// Unexpected initialization packet.
|
||||
ProcessedPacket::InitPacket { cmd, len, data } => {
|
||||
self.reset();
|
||||
if cmd == CtapHid::COMMAND_INIT {
|
||||
if cmd == CtapHidCommand::Init as u8 {
|
||||
self.parse_init_packet(*cid, cmd, len, data, timestamp)
|
||||
} else {
|
||||
Err((*cid, Error::UnexpectedInit))
|
||||
@@ -189,7 +189,7 @@ impl MessageAssembler {
|
||||
swap(&mut self.payload, &mut payload);
|
||||
Some(Message {
|
||||
cid: self.cid,
|
||||
cmd: self.cmd,
|
||||
cmd: CtapHidCommand::from(self.cmd),
|
||||
payload,
|
||||
})
|
||||
}
|
||||
@@ -225,12 +225,12 @@ mod test {
|
||||
let mut assembler = MessageAssembler::new();
|
||||
assert_eq!(
|
||||
assembler.parse_packet(
|
||||
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x80]),
|
||||
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x90]),
|
||||
DUMMY_TIMESTAMP
|
||||
),
|
||||
Ok(Some(Message {
|
||||
cid: [0x12, 0x34, 0x56, 0x78],
|
||||
cmd: 0x00,
|
||||
cmd: CtapHidCommand::Cbor,
|
||||
payload: vec![]
|
||||
}))
|
||||
);
|
||||
@@ -241,12 +241,12 @@ mod test {
|
||||
let mut assembler = MessageAssembler::new();
|
||||
assert_eq!(
|
||||
assembler.parse_packet(
|
||||
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x80, 0x00, 0x10]),
|
||||
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x90, 0x00, 0x10]),
|
||||
DUMMY_TIMESTAMP
|
||||
),
|
||||
Ok(Some(Message {
|
||||
cid: [0x12, 0x34, 0x56, 0x78],
|
||||
cmd: 0x00,
|
||||
cmd: CtapHidCommand::Cbor,
|
||||
payload: vec![0x00; 0x10]
|
||||
}))
|
||||
);
|
||||
@@ -260,12 +260,12 @@ mod test {
|
||||
let mut assembler = MessageAssembler::new();
|
||||
assert_eq!(
|
||||
assembler.parse_packet(
|
||||
&byte_extend(&[0x12, 0x34, 0x56, 0x78, 0x80, 0x00, 0x10], 0xFF),
|
||||
&byte_extend(&[0x12, 0x34, 0x56, 0x78, 0x90, 0x00, 0x10], 0xFF),
|
||||
DUMMY_TIMESTAMP
|
||||
),
|
||||
Ok(Some(Message {
|
||||
cid: [0x12, 0x34, 0x56, 0x78],
|
||||
cmd: 0x00,
|
||||
cmd: CtapHidCommand::Cbor,
|
||||
payload: vec![0xFF; 0x10]
|
||||
}))
|
||||
);
|
||||
@@ -288,7 +288,7 @@ mod test {
|
||||
),
|
||||
Ok(Some(Message {
|
||||
cid: [0x12, 0x34, 0x56, 0x78],
|
||||
cmd: 0x01,
|
||||
cmd: CtapHidCommand::Ping,
|
||||
payload: vec![0x00; 0x40]
|
||||
}))
|
||||
);
|
||||
@@ -318,7 +318,7 @@ mod test {
|
||||
),
|
||||
Ok(Some(Message {
|
||||
cid: [0x12, 0x34, 0x56, 0x78],
|
||||
cmd: 0x01,
|
||||
cmd: CtapHidCommand::Ping,
|
||||
payload: vec![0x00; 0x80]
|
||||
}))
|
||||
);
|
||||
@@ -350,7 +350,7 @@ mod test {
|
||||
),
|
||||
Ok(Some(Message {
|
||||
cid: [0x12, 0x34, 0x56, 0x78],
|
||||
cmd: 0x01,
|
||||
cmd: CtapHidCommand::Ping,
|
||||
payload: vec![0x00; 0x1DB9]
|
||||
}))
|
||||
);
|
||||
@@ -362,12 +362,15 @@ mod test {
|
||||
let mut assembler = MessageAssembler::new();
|
||||
for i in 0..10 {
|
||||
// Introduce some variability in the messages.
|
||||
let cmd = 2 * i;
|
||||
let cmd = CtapHidCommand::from(i + 1);
|
||||
let byte = 3 * i;
|
||||
|
||||
assert_eq!(
|
||||
assembler.parse_packet(
|
||||
&byte_extend(&[0x12, 0x34, 0x56, 0x78, 0x80 | cmd, 0x00, 0x80], byte),
|
||||
&byte_extend(
|
||||
&[0x12, 0x34, 0x56, 0x78, 0x80 | cmd as u8, 0x00, 0x80],
|
||||
byte
|
||||
),
|
||||
DUMMY_TIMESTAMP
|
||||
),
|
||||
Ok(None)
|
||||
@@ -400,12 +403,12 @@ mod test {
|
||||
for i in 0..10 {
|
||||
// Introduce some variability in the messages.
|
||||
let cid = 0x78 + i;
|
||||
let cmd = 2 * i;
|
||||
let cmd = CtapHidCommand::from(i + 1);
|
||||
let byte = 3 * i;
|
||||
|
||||
assert_eq!(
|
||||
assembler.parse_packet(
|
||||
&byte_extend(&[0x12, 0x34, 0x56, cid, 0x80 | cmd, 0x00, 0x80], byte),
|
||||
&byte_extend(&[0x12, 0x34, 0x56, cid, 0x80 | cmd as u8, 0x00, 0x80], byte),
|
||||
DUMMY_TIMESTAMP
|
||||
),
|
||||
Ok(None)
|
||||
@@ -443,11 +446,12 @@ mod test {
|
||||
);
|
||||
|
||||
// Check that many sorts of packets on another channel are ignored.
|
||||
for cmd in 0..=0xFF {
|
||||
for i in 0..=0xFF {
|
||||
let cmd = CtapHidCommand::from(i);
|
||||
for byte in 0..=0xFF {
|
||||
assert_eq!(
|
||||
assembler.parse_packet(
|
||||
&byte_extend(&[0x12, 0x34, 0x56, 0x9A, cmd, 0x00], byte),
|
||||
&byte_extend(&[0x12, 0x34, 0x56, 0x9A, cmd as u8, 0x00], byte),
|
||||
DUMMY_TIMESTAMP
|
||||
),
|
||||
Err(([0x12, 0x34, 0x56, 0x9A], Error::UnexpectedChannel))
|
||||
@@ -462,7 +466,7 @@ mod test {
|
||||
),
|
||||
Ok(Some(Message {
|
||||
cid: [0x12, 0x34, 0x56, 0x78],
|
||||
cmd: 0x01,
|
||||
cmd: CtapHidCommand::Ping,
|
||||
payload: vec![0x00; 0x40]
|
||||
}))
|
||||
);
|
||||
@@ -479,12 +483,12 @@ mod test {
|
||||
let byte = 2 * i;
|
||||
assert_eq!(
|
||||
assembler.parse_packet(
|
||||
&byte_extend(&[0x12, 0x34, 0x56, 0x78, 0x80, 0x00, 0x10], byte),
|
||||
&byte_extend(&[0x12, 0x34, 0x56, 0x78, 0x81, 0x00, 0x10], byte),
|
||||
DUMMY_TIMESTAMP
|
||||
),
|
||||
Ok(Some(Message {
|
||||
cid: [0x12, 0x34, 0x56, 0x78],
|
||||
cmd: 0x00,
|
||||
cmd: CtapHidCommand::Ping,
|
||||
payload: vec![byte; 0x10]
|
||||
}))
|
||||
);
|
||||
@@ -584,7 +588,7 @@ mod test {
|
||||
assembler.parse_packet(&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x7F]), timestamp),
|
||||
Ok(Some(Message {
|
||||
cid: [0x12, 0x34, 0x56, 0x78],
|
||||
cmd: 0x01,
|
||||
cmd: CtapHidCommand::Ping,
|
||||
payload: vec![0x00; 0x1DB9]
|
||||
}))
|
||||
);
|
||||
@@ -612,7 +616,7 @@ mod test {
|
||||
),
|
||||
Ok(Some(Message {
|
||||
cid: [0x12, 0x34, 0x56, 0x78],
|
||||
cmd: 0x06,
|
||||
cmd: CtapHidCommand::Init,
|
||||
payload: vec![0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0]
|
||||
}))
|
||||
);
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
|
||||
use super::{CtapHid, HidPacket, Message};
|
||||
|
||||
/// Iterator for HID packets.
|
||||
///
|
||||
/// The `new` constructor splits the CTAP `Message` into `HidPacket`s for sending over USB.
|
||||
pub struct HidPacketIterator(Option<MessageSplitter>);
|
||||
|
||||
impl HidPacketIterator {
|
||||
@@ -99,7 +102,7 @@ impl Iterator for MessageSplitter {
|
||||
match self.seq {
|
||||
None => {
|
||||
// First, send an initialization packet.
|
||||
self.packet[4] = self.message.cmd | CtapHid::TYPE_INIT_BIT;
|
||||
self.packet[4] = self.message.cmd as u8 | CtapHid::TYPE_INIT_BIT;
|
||||
self.packet[5] = (payload_len >> 8) as u8;
|
||||
self.packet[6] = payload_len as u8;
|
||||
|
||||
@@ -128,6 +131,7 @@ impl Iterator for MessageSplitter {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::CtapHidCommand;
|
||||
use super::*;
|
||||
|
||||
fn assert_packet_output_equality(message: Message, expected_packets: Vec<HidPacket>) {
|
||||
@@ -142,11 +146,11 @@ mod test {
|
||||
fn test_hid_packet_iterator_single_packet() {
|
||||
let message = Message {
|
||||
cid: [0x12, 0x34, 0x56, 0x78],
|
||||
cmd: 0x4C,
|
||||
cmd: CtapHidCommand::Cbor,
|
||||
payload: vec![0xAA, 0xBB],
|
||||
};
|
||||
let expected_packets: Vec<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,
|
||||
@@ -159,11 +163,11 @@ mod test {
|
||||
fn test_hid_packet_iterator_big_single_packet() {
|
||||
let message = Message {
|
||||
cid: [0x12, 0x34, 0x56, 0x78],
|
||||
cmd: 0x4C,
|
||||
cmd: CtapHidCommand::Cbor,
|
||||
payload: vec![0xAA; 64 - 7],
|
||||
};
|
||||
let expected_packets: Vec<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,
|
||||
@@ -176,12 +180,12 @@ mod test {
|
||||
fn test_hid_packet_iterator_two_packets() {
|
||||
let message = Message {
|
||||
cid: [0x12, 0x34, 0x56, 0x78],
|
||||
cmd: 0x4C,
|
||||
cmd: CtapHidCommand::Cbor,
|
||||
payload: vec![0xAA; 64 - 7 + 1],
|
||||
};
|
||||
let expected_packets: Vec<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,
|
||||
@@ -204,12 +208,12 @@ mod test {
|
||||
payload.extend(vec![0xBB; 64 - 5]);
|
||||
let message = Message {
|
||||
cid: [0x12, 0x34, 0x56, 0x78],
|
||||
cmd: 0x4C,
|
||||
cmd: CtapHidCommand::Cbor,
|
||||
payload,
|
||||
};
|
||||
let expected_packets: Vec<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,
|
||||
@@ -238,12 +242,12 @@ mod test {
|
||||
|
||||
let message = Message {
|
||||
cid: [0x12, 0x34, 0x56, 0x78],
|
||||
cmd: 0xAB,
|
||||
cmd: CtapHidCommand::Msg,
|
||||
payload,
|
||||
};
|
||||
|
||||
let mut expected_packets: Vec<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,
|
||||
@@ -271,7 +275,7 @@ mod test {
|
||||
assert_eq!(payload.len(), 0x1dba);
|
||||
let message = Message {
|
||||
cid: [0x12, 0x34, 0x56, 0x78],
|
||||
cmd: 0xAB,
|
||||
cmd: CtapHidCommand::Msg,
|
||||
payload,
|
||||
};
|
||||
assert!(HidPacketIterator::new(message).is_none());
|
||||
@@ -283,7 +287,7 @@ mod test {
|
||||
let payload = vec![0xFF; 0x10000];
|
||||
let message = Message {
|
||||
cid: [0x12, 0x34, 0x56, 0x78],
|
||||
cmd: 0xAB,
|
||||
cmd: CtapHidCommand::Msg,
|
||||
payload,
|
||||
};
|
||||
assert!(HidPacketIterator::new(message).is_none());
|
||||
|
||||
4
src/env/tock/mod.rs
vendored
4
src/env/tock/mod.rs
vendored
@@ -1,5 +1,5 @@
|
||||
use self::storage::{SyscallStorage, SyscallUpgradeStorage};
|
||||
use crate::ctap::hid::{ChannelID, CtapHid, KeepaliveStatus, ProcessedPacket};
|
||||
use crate::ctap::hid::{ChannelID, CtapHid, CtapHidCommand, KeepaliveStatus, ProcessedPacket};
|
||||
use crate::ctap::status_code::Ctap2StatusCode;
|
||||
use crate::env::{Env, UserPresence};
|
||||
use core::cell::Cell;
|
||||
@@ -121,7 +121,7 @@ fn send_keepalive_up_needed(
|
||||
}
|
||||
match processed_packet {
|
||||
ProcessedPacket::InitPacket { cmd, .. } => {
|
||||
if cmd == CtapHid::COMMAND_CANCEL {
|
||||
if cmd == CtapHidCommand::Cancel as u8 {
|
||||
// We ignore the payload, we can't answer with an error code anyway.
|
||||
#[cfg(feature = "debug_ctap")]
|
||||
writeln!(Console::new(), "User presence check cancelled").unwrap();
|
||||
|
||||
Reference in New Issue
Block a user