diff --git a/.gitignore b/.gitignore index 611b278..d85036c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +fuzz/corpus target/ Cargo.lock diff --git a/bootloader/Cargo.toml b/bootloader/Cargo.toml index bebe244..b93d4c1 100644 --- a/bootloader/Cargo.toml +++ b/bootloader/Cargo.toml @@ -15,6 +15,7 @@ cortex-m-rt = "*" cortex-m-rt-macros = "*" panic-abort = "0.3.2" rtt-target = { version = "*", features = ["cortex-m"] } +tock-registers = { version = "0.6.0", features = ["no_std_unit_tests"] } [profile.dev] panic = "abort" diff --git a/bootloader/src/bitfields.rs b/bootloader/src/bitfields.rs new file mode 100644 index 0000000..5a6dc92 --- /dev/null +++ b/bootloader/src/bitfields.rs @@ -0,0 +1,118 @@ +// Copyright 2020-2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use tock_registers::register_bitfields; + +register_bitfields! [u32, + // Generic or shared bitfields + pub Task [ + ENABLE OFFSET(0) NUMBITS(1) + ], + + pub Byte [ + VALUE OFFSET(0) NUMBITS(8) + ], + + pub Busy [ + /// Asserted when AES_BUSY or DES_BUSY or HASH_BUSY are asserted or when the DIN FIFO is not empty + BUSY OFFSET(0) NUMBITS(1) [ + Ready = 0, + Busy = 1 + ] + ], + + // CC_CTL register bitfields + pub CryptoMode [ + /// Determines the active cryptographic engine + MODE OFFSET(0) NUMBITS(5) [ + Bypass = 0, + Aes = 1, + AesToHash = 2, + AesAndHash = 3, + Des = 4, + DesToHash = 5, + DesAndHash = 6, + Hash = 7, + AesMacAndBypass = 9, + AesToHashAndDout = 10 + ] + ], + + // HOST_RGF register bitfields + pub Interrupts [ + /// This interrupt is asserted when all data was delivered to DIN buffer from SRAM + SRAM_TO_DIN OFFSET(4) NUMBITS(1), + /// This interrupt is asserted when all data was delivered to SRAM buffer from DOUT + DOUT_TO_SRAM OFFSET(5) NUMBITS(1), + /// This interrupt is asserted when all data was delivered to DIN buffer from memory + MEM_TO_DIN OFFSET(6) NUMBITS(1), + /// This interrupt is asserted when all data was delivered to memory buffer from DOUT + DOUT_TO_MEM OFFSET(7) NUMBITS(1), + AXI_ERROR OFFSET(8) NUMBITS(1), + /// The PKA end of operation interrupt status + PKA_EXP OFFSET(9) NUMBITS(1), + /// The RNG interrupt status + RNG OFFSET(10) NUMBITS(1), + /// The GPR interrupt status + SYM_DMA_COMPLETED OFFSET(11) NUMBITS(1) + ], + + pub RgfEndianness [ + /// DOUT write endianness + DOUT_WR_BG OFFSET(3) NUMBITS(1) [ + LittleEndian = 0, + BigEndian = 1 + ], + /// DIN write endianness + DIN_RD_BG OFFSET(7) NUMBITS(1) [ + LittleEndian = 0, + BigEndian = 1 + ], + /// DOUT write word endianness + DOUT_WR_WBG OFFSET(11) NUMBITS(1) [ + LittleEndian = 0, + BigEndian = 1 + ], + /// DIN write word endianness + DIN_RD_WBG OFFSET(15) NUMBITS(1) [ + LittleEndian = 0, + BigEndian = 1 + ] + ], + + // DIN and DOUT register bitfields + pub LliWord1 [ + /// Total number of bytes to read using DMA in this entry + BYTES_NUM OFFSET(0) NUMBITS(30), + /// Indicates the first LLI entry + FIRST OFFSET(30) NUMBITS(1), + /// Indicates the last LLI entry + LAST OFFSET(31) NUMBITS(1) + ], + + pub HashControl [ + // bit 2 is reserved but to simplify the logic we include it in the bitfield. + MODE OFFSET(0) NUMBITS(4) [ + MD5 = 0, + SHA1 = 1, + SHA256 = 2, + SHA224 = 10 + ] + ], + + pub PaddingConfig [ + /// Enable Padding generation. must be reset upon completion of padding. + DO_PAD OFFSET(2) NUMBITS(1) + ] +]; diff --git a/bootloader/src/crypto_cell.rs b/bootloader/src/crypto_cell.rs new file mode 100644 index 0000000..7cbd01e --- /dev/null +++ b/bootloader/src/crypto_cell.rs @@ -0,0 +1,283 @@ +// Copyright 2019-2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! CryptoCell 310 +//! +//! Author +//! ------------------- +//! +//! * Author: Jean-Michel Picod +//! * Date: October 1 2019 + +use super::bitfields; +use super::registers::{CryptoCellRegisters, NordicCC310Registers}; +use super::static_ref::StaticRef; +use core::cell::Cell; +#[cfg(debug_assertions)] +use rtt_target::rprintln; + +const SHA256_INIT_VALUE: [u32; 8] = [ + 0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19, +]; + +#[derive(Copy, Clone)] +enum DigestAlgorithm { + Sha256 = 2, +} + +#[derive(Copy, Clone)] +enum OperationMode { + Idle, + Hash, +} + +pub struct CryptoCell310 { + registers: StaticRef, + power: StaticRef, + current_op: Cell, + + hash_ctx: Cell<[u32; 8]>, + hash_total_size: Cell, +} + +const CC310_BASE: StaticRef = + unsafe { StaticRef::new(0x5002B000 as *const CryptoCellRegisters) }; +const CC310_POWER: StaticRef = + unsafe { StaticRef::new(0x5002A500 as *const NordicCC310Registers) }; + +// Identification "signature" for CryptoCell. According to the documentation, the value +// held by this register is a fixed value, used by Host driver to verify CryptoCell presence +// at this address. +// This value was read from a CryptoCell-310 on a nRF52840-dongle kit. +const CC310_SIGNATURE: u32 = 0x20E00000; + +impl CryptoCell310 { + /// Creates a new instance of cryptocell state. + pub const fn new() -> Self { + CryptoCell310 { + registers: CC310_BASE, + power: CC310_POWER, + current_op: Cell::new(OperationMode::Idle), + + hash_ctx: Cell::new(SHA256_INIT_VALUE), + hash_total_size: Cell::new(0), + } + } + + fn enable(&self) { + self.power.enable.write(bitfields::Task::ENABLE::SET); + for _i in 1..10 { + let read_signature = self.registers.host_rgf.signature.get(); + if read_signature != CC310_SIGNATURE { + #[cfg(debug_assertions)] + rprintln!( + "[loop {}] Invalid CC310 signature. Expected {}, got {}\n", + _i, + CC310_SIGNATURE, + read_signature + ); + } else { + break; + } + } + if self.registers.host_rgf.signature.get() != CC310_SIGNATURE { + panic!("Failed to initialize CC310"); + } + // Make sure everything is set to little endian + self.registers.host_rgf.endian.write( + bitfields::RgfEndianness::DOUT_WR_BG::LittleEndian + + bitfields::RgfEndianness::DIN_RD_BG::LittleEndian + + bitfields::RgfEndianness::DOUT_WR_WBG::LittleEndian + + bitfields::RgfEndianness::DIN_RD_WBG::LittleEndian, + ); + // Always start the clock for DMA engine. It's too hard to keep + // track of which submodule needs DMA otherwise. + self.registers + .misc + .dma_clk_enable + .write(bitfields::Task::ENABLE::SET); + self.registers.host_rgf.interrupt_mask.write( + bitfields::Interrupts::SRAM_TO_DIN::CLEAR + + bitfields::Interrupts::DOUT_TO_SRAM::CLEAR + + bitfields::Interrupts::MEM_TO_DIN::CLEAR + + bitfields::Interrupts::DOUT_TO_MEM::CLEAR + + bitfields::Interrupts::AXI_ERROR::SET + + bitfields::Interrupts::PKA_EXP::SET + + bitfields::Interrupts::RNG::SET + + bitfields::Interrupts::SYM_DMA_COMPLETED::CLEAR, + ); + } + + fn disable(&self) { + self.registers.host_rgf.interrupt_mask.set(0); + self.power.enable.write(bitfields::Task::ENABLE::CLEAR); + self.registers + .misc + .dma_clk_enable + .write(bitfields::Task::ENABLE::CLEAR); + } + + fn clear_data(&self) { + let mut ctx = self.hash_ctx.get(); + ctx.iter_mut().for_each(|b| *b = 0); + self.hash_ctx.set(ctx); + self.hash_total_size.set(0); + } + + /// Adds data to the current hash computation. + /// + /// You have to know in advance if is this is going to be the last block, and indicate that + /// correctly. Sizes of chunks before the last need to be multiples of 64. + pub fn update(&self, data: &[u8], is_last_block: bool) { + // Start CryptoCell + self.enable(); + + while self.registers.ctrl.hash_busy.is_set(bitfields::Busy::BUSY) {} + while self + .registers + .ctrl + .crypto_busy + .is_set(bitfields::Busy::BUSY) + {} + while self + .registers + .din + .mem_dma_busy + .is_set(bitfields::Busy::BUSY) + {} + + // Start HASH module and configure it + self.current_op.set(OperationMode::Hash); + self.registers + .misc + .hash_clk_enable + .write(bitfields::Task::ENABLE::SET); + self.registers + .ctrl + .crypto_ctl + .write(bitfields::CryptoMode::MODE::Hash); + self.registers + .hash + .padding + .write(bitfields::Task::ENABLE::SET); + let size = self.hash_total_size.get(); + self.registers.hash.hash_len_lsb.set(size as u32); + self.registers + .hash + .hash_len_msb + .set(size.wrapping_shr(32) as u32); + self.registers + .hash + .control + .set(DigestAlgorithm::Sha256 as u32); + + // Digest must be set backwards because writing to HASH[0] + // starts computation + let mut digest = self.hash_ctx.get(); + for i in (0..digest.len()).rev() { + self.registers.hash.hash[i].set(digest[i]); + } + while self.registers.ctrl.hash_busy.is_set(bitfields::Busy::BUSY) {} + + // Process data + if !data.is_empty() { + if is_last_block { + self.registers + .hash + .auto_hw_padding + .write(bitfields::Task::ENABLE::SET); + } + self.registers.din.src_lli_word0.set(data.as_ptr() as u32); + self.registers + .din + .src_lli_word1 + .write(bitfields::LliWord1::BYTES_NUM.val(data.len() as u32)); + while !self + .registers + .host_rgf + .interrupts + .is_set(bitfields::Interrupts::MEM_TO_DIN) + {} + self.registers + .host_rgf + .interrupt_clear + .write(bitfields::Interrupts::MEM_TO_DIN::SET); + } else { + // use DO_PAD to complete padding of previous operation + self.registers + .hash + .pad_config + .write(bitfields::PaddingConfig::DO_PAD::SET); + } + while self + .registers + .ctrl + .crypto_busy + .is_set(bitfields::Busy::BUSY) + {} + while self + .registers + .din + .mem_dma_busy + .is_set(bitfields::Busy::BUSY) + {} + + // Update context and total size + for i in (0..digest.len()).rev() { + digest[i] = self.registers.hash.hash[i].get(); + } + self.hash_ctx.set(digest); + let new_size: u64 = ((self.registers.hash.hash_len_msb.get() as u64) << 32) + + (self.registers.hash.hash_len_lsb.get() as u64); + self.hash_total_size.set(new_size); + + // Disable HASH module + self.registers + .hash + .padding + .write(bitfields::Task::ENABLE::SET); + self.registers + .hash + .auto_hw_padding + .write(bitfields::Task::ENABLE::CLEAR); + self.registers + .hash + .pad_config + .write(bitfields::PaddingConfig::DO_PAD::CLEAR); + while self + .registers + .ctrl + .crypto_busy + .is_set(bitfields::Busy::BUSY) + {} + self.registers + .misc + .hash_clk_enable + .write(bitfields::Task::ENABLE::CLEAR); + + self.disable(); + } + + /// Clears the data for potential reuse, and returns the result. + pub fn finalize_and_clear(&self) -> [u8; 32] { + use byteorder::{BigEndian, ByteOrder}; + let words = self.hash_ctx.get(); + let mut bytes = [0u8; 32]; + for (i, word) in words.iter().enumerate() { + BigEndian::write_u32(&mut bytes[4 * i..4 * i + 4], *word); + } + self.clear_data(); + bytes + } +} diff --git a/bootloader/src/main.rs b/bootloader/src/main.rs index 51bd03d..b14e976 100644 --- a/bootloader/src/main.rs +++ b/bootloader/src/main.rs @@ -1,4 +1,4 @@ -// Copyright 2021 Google LLC +// Copyright 2021-2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,6 +15,11 @@ #![no_main] #![no_std] +mod bitfields; +mod crypto_cell; +mod registers; +mod static_ref; + extern crate cortex_m; extern crate cortex_m_rt as rt; @@ -69,7 +74,7 @@ struct BootPartition { } impl BootPartition { - const _FIRMWARE_LENGTH: usize = 0x00040000; + const FIRMWARE_LENGTH: usize = 0x00040000; /// Reads the metadata, returns the timestamp if all checks pass. pub fn read_timestamp(&self) -> Result { @@ -93,18 +98,40 @@ impl BootPartition { Ok(metadata.timestamp) } - /// Placeholder for the SHA256 implementation. + /// Computes the SHA256 of metadata information and partition data. /// - /// TODO implemented in next PR - /// Without it, the bootloader will never boot anything. - fn compute_upgrade_hash(&self, _metadata_page: &[u8]) -> [u8; 32] { - [0; 32] + /// Assumes that firmware address and length are divisible by the page size. + /// This is the hardware implementation on the cryptocell. + #[allow(clippy::assertions_on_constants)] + fn compute_upgrade_hash(&self, metadata_page: &[u8]) -> [u8; 32] { + debug_assert!(self.firmware_address % PAGE_SIZE == 0); + debug_assert!(BootPartition::FIRMWARE_LENGTH % PAGE_SIZE == 0); + let cc310 = crypto_cell::CryptoCell310::new(); + for page_offset in (0..BootPartition::FIRMWARE_LENGTH).step_by(PAGE_SIZE) { + let page = unsafe { read_page(self.firmware_address + page_offset) }; + cc310.update(&page, false); + } + cc310.update(&metadata_page[32..Metadata::DATA_LEN], true); + cc310.finalize_and_clear() } /// Jump to the firmware. pub fn boot(&self) -> ! { let address = self.firmware_address; + // Clear any pending Cryptocell interrupt in NVIC + let peripherals = cortex_m::Peripherals::take().unwrap(); + unsafe { + // We could only clear cryptocell interrupts, but let's clean up before booting. + // Example code to clear more specifically: + // const CC310_IRQ: u16 = 42; + // peripherals.NVIC.icpr[usize::from(CC310_IRQ / 32)].write(1 << (CC310_IRQ % 32)); + peripherals.NVIC.icer[0].write(0xffff_ffff); + peripherals.NVIC.icpr[0].write(0xffff_ffff); + peripherals.NVIC.icer[1].write(0xffff_ffff); + peripherals.NVIC.icpr[1].write(0xffff_ffff); + } + #[cfg(debug_assertions)] rprintln!("Boot jump to {:08X}", address); let address_pointer = address as *const u32; diff --git a/bootloader/src/registers.rs b/bootloader/src/registers.rs new file mode 100644 index 0000000..17725dc --- /dev/null +++ b/bootloader/src/registers.rs @@ -0,0 +1,139 @@ +// Copyright 2020-2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::bitfields::{ + Busy, CryptoMode, HashControl, Interrupts, LliWord1, PaddingConfig, RgfEndianness, Task, +}; +use tock_registers::{ + register_structs, + registers::{ReadOnly, ReadWrite, WriteOnly}, +}; + +register_structs! { + pub CryptoCellControlRegisters { + /// Defines the cryptographic flow + (0x0000 => pub crypto_ctl: WriteOnly), + (0x0004 => _reserved0), + /// This register is set whent the cryptographic core is busy + (0x0010 => pub crypto_busy: ReadOnly), + (0x0014 => _reserved1), + /// This register is set when the Hash engine is busy + (0x001C => pub hash_busy: ReadOnly), + (0x0020 => @END), + } +} + +register_structs! { + pub CryptoCellDinRegisters { + (0x0000 => _reserved0), + /// Indicates whether memoty (AXI) source DMA (DIN) is busy + (0x0020 => pub mem_dma_busy: ReadOnly), + (0x0024 => _reserved1), + /// This register is used in direct LLI mode - holds the location of the data source + /// in the memory (AXI) + (0x0028 => pub src_lli_word0: WriteOnly), + /// This register is used in direct LLI mode - holds the number of bytes to be read + /// from the memory (AXI). + /// Writing to this register triggers the DMA. + (0x002C => pub src_lli_word1: WriteOnly), + (0x0030 => @END), + } +} + +register_structs! { + pub CryptoCellHashRegisters { + /// Write initial hash value or read final hash value + (0x0000 => pub hash: [ReadWrite; 9]), + (0x0024 => _reserved0), + /// HW padding automatically activated by engine. + /// For the special case of ZERO bytes data vector this register should not be used! instead use HASH_PAD_CFG + (0x0044 => pub auto_hw_padding: WriteOnly), + (0x0048 => _reserved1), + /// Selects which HASH mode to run + (0x0180 => pub control: ReadWrite), + /// This register enables the hash hw padding. + (0x0184 => pub padding: ReadWrite), + /// HASH_PAD_CFG Register. + (0x0188 => pub pad_config: ReadWrite), + /// This register hold the length of current hash operation + (0x018C => pub hash_len_lsb: ReadWrite), + /// This register hold the length of current hash operation + (0x0190 => pub hash_len_msb: ReadWrite), + (0x0194 => @END), + } +} + +register_structs! { + pub CryptoCellHostRgfRegisters { + /// The Interrupt Request register. + /// Each bit of this register holds the interrupt status of a single interrupt source. + (0x0000 => pub interrupts: ReadOnly), + /// The Interrupt Mask register. Each bit of this register holds the mask of a single + /// interrupt source. + (0x0004 => pub interrupt_mask: ReadWrite), + /// Interrupt Clear Register + (0x0008 => pub interrupt_clear: WriteOnly), + /// This register defines the endianness of the Host-accessible registers. + (0x000C => pub endian: ReadWrite), + (0x0010 => _reserved0), + /// This register holds the CryptoCell product signature. + (0x0024 => pub signature: ReadOnly), + (0x0028 => @END), + } +} + +register_structs! { + pub CryptoCellMiscRegisters { + (0x0000 => _reserved0), + /// The HASH clock enable register + (0x0018 => pub hash_clk_enable: ReadWrite), + /// The PKA clock enable register + (0x001C => _reserved1), + /// The DMA clock enable register + (0x0020 => pub dma_clk_enable: ReadWrite), + /// the CryptoCell clocks' status register + (0x0024 => @END), + } +} + +register_structs! { + pub NordicCC310Registers { + (0x0000 => pub enable: ReadWrite), + (0x0004 => @END), + }, + + pub CryptoCellRegisters { + (0x0000 => _reserved0), + /// HASH registers + /// - Base address: 0x0640 + (0x0640 => pub hash: CryptoCellHashRegisters), + (0x07D4 => _reserved1), + /// Misc registers + /// - Base address: 0x0800 + (0x0800 => pub misc: CryptoCellMiscRegisters), + (0x0824 => _reserved2), + /// CryptoCell control registers + /// - Base address: 0x0900 + (0x0900 => pub ctrl: CryptoCellControlRegisters), + (0x0920 => _reserved3), + /// HOST_RGF registers + /// - Base address: 0x0A00 + (0x0A00 => pub host_rgf: CryptoCellHostRgfRegisters), + (0x0A28 => _reserved4), + /// DIN registers + /// - Base address: 0x0C00 + (0x0C00 => pub din: CryptoCellDinRegisters), + (0x0C30 => @END), + } +} diff --git a/bootloader/src/static_ref.rs b/bootloader/src/static_ref.rs new file mode 100644 index 0000000..4fc27f4 --- /dev/null +++ b/bootloader/src/static_ref.rs @@ -0,0 +1,46 @@ +//! Wrapper type for safe pointers to static memory. +//! +//! Imported from: +//! https://github.com/tock/tock/blob/master/kernel/src/utilities/static_ref.rs + +use core::ops::Deref; + +/// A pointer to statically allocated mutable data such as memory mapped I/O +/// registers. +/// +/// This is a simple wrapper around a raw pointer that encapsulates an unsafe +/// dereference in a safe manner. It serve the role of creating a `&'static T` +/// given a raw address and acts similarly to `extern` definitions, except +/// `StaticRef` is subject to module and crate boundaries, while `extern` +/// definitions can be imported anywhere. +#[derive(Debug)] +pub struct StaticRef { + ptr: *const T, +} + +impl StaticRef { + /// Create a new `StaticRef` from a raw pointer + /// + /// ## Safety + /// + /// Callers must pass in a reference to statically allocated memory which + /// does not overlap with other values. + pub const unsafe fn new(ptr: *const T) -> StaticRef { + StaticRef { ptr } + } +} + +impl Clone for StaticRef { + fn clone(&self) -> Self { + StaticRef { ptr: self.ptr } + } +} + +impl Copy for StaticRef {} + +impl Deref for StaticRef { + type Target = T; + fn deref(&self) -> &'static T { + unsafe { &*self.ptr } + } +} diff --git a/fuzz/fuzz_helper/src/lib.rs b/fuzz/fuzz_helper/src/lib.rs index 647749e..7a0d300 100644 --- a/fuzz/fuzz_helper/src/lib.rs +++ b/fuzz/fuzz_helper/src/lib.rs @@ -25,14 +25,12 @@ use ctap2::ctap::command::{ }; use ctap2::ctap::hid::receive::MessageAssembler; use ctap2::ctap::hid::send::HidPacketIterator; -use ctap2::ctap::hid::{ChannelID, HidPacket, Message}; +use ctap2::ctap::hid::{ChannelID, CtapHidCommand, HidPacket, Message}; use ctap2::env::test::TestEnv; use ctap2::Ctap; use libtock_drivers::timer::{ClockValue, Timestamp}; -const COMMAND_INIT: u8 = 0x06; const CHANNEL_BROADCAST: ChannelID = [0xFF, 0xFF, 0xFF, 0xFF]; -const PACKET_TYPE_MASK: u8 = 0x80; const CLOCK_FREQUENCY_HZ: usize = 32768; const DUMMY_TIMESTAMP: Timestamp = Timestamp::from_ms(0); @@ -53,13 +51,14 @@ fn raw_to_message(data: &[u8]) -> Message { cid[..data.len()].copy_from_slice(data); Message { cid, - cmd: 0, + // Arbitrary command. + cmd: CtapHidCommand::Cbor, payload: vec![], } } else { Message { cid: array_ref!(data, 0, 4).clone(), - cmd: data[4], + cmd: CtapHidCommand::from(data[4]), payload: data[5..].to_vec(), } } @@ -71,7 +70,7 @@ fn initialize(ctap: &mut Ctap) -> ChannelID { let nonce = vec![0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0]; let message = Message { cid: CHANNEL_BROADCAST, - cmd: COMMAND_INIT, + cmd: CtapHidCommand::Init, payload: nonce, }; let mut assembler_reply = MessageAssembler::new(); @@ -168,7 +167,7 @@ pub fn process_ctap_specific_type(data: &[u8], input_type: InputType) { // Splits the given data as HID packets and reassembles it, verifying that the original input message is reconstructed. pub fn split_assemble_hid_packets(data: &[u8]) { - let mut message = raw_to_message(data); + let message = raw_to_message(data); if let Some(hid_packet_iterator) = HidPacketIterator::new(message.clone()) { let mut assembler = MessageAssembler::new(); let packets: Vec = hid_packet_iterator.collect(); @@ -176,7 +175,6 @@ pub fn split_assemble_hid_packets(data: &[u8]) { for packet in first_packets { assert_eq!(assembler.parse_packet(packet, DUMMY_TIMESTAMP), Ok(None)); } - message.cmd &= !PACKET_TYPE_MASK; assert_eq!( assembler.parse_packet(last_packet, DUMMY_TIMESTAMP), Ok(Some(message)) diff --git a/fuzzing_setup.sh b/fuzzing_setup.sh index 99e0532..5877608 100755 --- a/fuzzing_setup.sh +++ b/fuzzing_setup.sh @@ -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 diff --git a/src/ctap/hid/mod.rs b/src/ctap/hid/mod.rs index c79462b..f3fa288 100644 --- a/src/ctap/hid/mod.rs +++ b/src/ctap/hid/mod.rs @@ -32,13 +32,50 @@ use core::fmt::Write; use libtock_drivers::console::Console; use libtock_drivers::timer::{ClockValue, Duration, Timestamp}; -// CTAP specification (version 20190130) section 8.1 -// TODO: Channel allocation, section 8.1.3? -// TODO: Transaction timeout, section 8.1.5.2 - pub type HidPacket = [u8; 64]; pub type ChannelID = [u8; 4]; +/// CTAPHID commands +/// +/// See section 11.2.9. of FIDO 2.1 (2021-06-15). +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum CtapHidCommand { + Ping = 0x01, + Msg = 0x03, + // Lock is optional and may be used in the future. + Lock = 0x04, + Init = 0x06, + Wink = 0x08, + Cbor = 0x10, + Cancel = 0x11, + Keepalive = 0x3B, + Error = 0x3F, + // VendorFirst and VendorLast describe a range, and are not commands themselves. + _VendorFirst = 0x40, + _VendorLast = 0x7F, +} + +impl From for CtapHidCommand { + fn from(cmd: u8) -> Self { + match cmd { + x if x == CtapHidCommand::Ping as u8 => CtapHidCommand::Ping, + x if x == CtapHidCommand::Msg as u8 => CtapHidCommand::Msg, + x if x == CtapHidCommand::Lock as u8 => CtapHidCommand::Lock, + x if x == CtapHidCommand::Init as u8 => CtapHidCommand::Init, + x if x == CtapHidCommand::Wink as u8 => CtapHidCommand::Wink, + x if x == CtapHidCommand::Cbor as u8 => CtapHidCommand::Cbor, + x if x == CtapHidCommand::Cancel as u8 => CtapHidCommand::Cancel, + x if x == CtapHidCommand::Keepalive as u8 => CtapHidCommand::Keepalive, + // This includes the actual error code 0x3F. Error is not used for incoming packets in + // the specification, so we can safely reuse it for unknown bytes. + _ => CtapHidCommand::Error, + } + } +} + +/// Describes the structure of a parsed HID packet. +/// +/// A packet is either an Init or a Continuation packet. pub enum ProcessedPacket<'a> { InitPacket { cmd: u8, @@ -51,72 +88,79 @@ pub enum ProcessedPacket<'a> { }, } -// An assembled CTAPHID command. +/// An assembled CTAPHID command. #[derive(Clone, Debug, PartialEq, Eq)] pub struct Message { // Channel ID. pub cid: ChannelID, // Command. - pub cmd: u8, + pub cmd: CtapHidCommand, // Bytes of the message. pub payload: Vec, } +/// A keepalive packet reports the reason why a command does not finish. +#[allow(dead_code)] +pub enum KeepaliveStatus { + Processing = 0x01, + UpNeeded = 0x02, +} + +/// Holds all state for receiving and sending HID packets. +/// +/// This includes +/// - state from not fully processed messages, +/// - all allocated channels, +/// - information about requested winks. +/// +/// The wink information can be polled to decide to i.e. blink LEDs. +/// +/// To process a packet and receive the response, you can call `process_hid_packet`. +/// If you want more control, you can also do the processing in steps: +/// +/// 1. `HidPacket` -> `Option` +/// 2. `Option` -> `Message` +/// 3. `Message` -> `Message` +/// 4. `Message` -> `HidPacketIterator` +/// +/// These steps correspond to: +/// +/// 1. `parse_packet` assembles the message and preprocesses all pure HID commands and errors. +/// 2. If you didn't receive any message or preprocessing discarded it, stop. +/// 3. `process_message` handles all protocol interactions. +/// 4. `split_message` creates packets out of the response message. pub struct CtapHid { assembler: MessageAssembler, - // The specification (version 20190130) only requires unique CIDs ; the allocation algorithm is - // vendor specific. + // The specification only requires unique CIDs, the allocation algorithm is vendor specific. // We allocate them incrementally, that is all `cid` such that 1 <= cid <= allocated_cids are // allocated. // In packets, the ID encoding is Big Endian to match what is used throughout CTAP (with the // u32::to/from_be_bytes methods). + // TODO(kaczmarczyck) We might want to limit or timeout open channels. allocated_cids: usize, pub(crate) wink_permission: TimedPermission, } -#[allow(dead_code)] -pub enum KeepaliveStatus { - Processing, - UpNeeded, -} - -#[allow(dead_code)] -// TODO(kaczmarczyck) disable the warning in the end impl CtapHid { - // CTAP specification (version 20190130) section 8.1.3 + // We implement CTAP 2.1 from 2021-06-15. Please see section + // 11.2. USB Human Interface Device (USB HID) const CHANNEL_RESERVED: ChannelID = [0, 0, 0, 0]; const CHANNEL_BROADCAST: ChannelID = [0xFF, 0xFF, 0xFF, 0xFF]; const TYPE_INIT_BIT: u8 = 0x80; const PACKET_TYPE_MASK: u8 = 0x80; - // CTAP specification (version 20190130) section 8.1.9 - const COMMAND_PING: u8 = 0x01; - const COMMAND_MSG: u8 = 0x03; - const COMMAND_INIT: u8 = 0x06; - const COMMAND_CBOR: u8 = 0x10; - pub const COMMAND_CANCEL: u8 = 0x11; - const COMMAND_KEEPALIVE: u8 = 0x3B; - const COMMAND_ERROR: u8 = 0x3F; - // TODO: optional lock command - const COMMAND_LOCK: u8 = 0x04; - const COMMAND_WINK: u8 = 0x08; - const COMMAND_VENDOR_FIRST: u8 = 0x40; - const COMMAND_VENDOR_LAST: u8 = 0x7F; - - // CTAP specification (version 20190130) section 8.1.9.1.6 const ERR_INVALID_CMD: u8 = 0x01; - const ERR_INVALID_PAR: u8 = 0x02; + const _ERR_INVALID_PAR: u8 = 0x02; const ERR_INVALID_LEN: u8 = 0x03; const ERR_INVALID_SEQ: u8 = 0x04; const ERR_MSG_TIMEOUT: u8 = 0x05; const ERR_CHANNEL_BUSY: u8 = 0x06; - const ERR_LOCK_REQUIRED: u8 = 0x0A; + const _ERR_LOCK_REQUIRED: u8 = 0x0A; const ERR_INVALID_CHANNEL: u8 = 0x0B; - const ERR_OTHER: u8 = 0x7F; + const _ERR_OTHER: u8 = 0x7F; - // CTAP specification (version 20190130) section 8.1.9.1.3 + // See section 11.2.9.1.3. CTAPHID_INIT (0x06). const PROTOCOL_VERSION: u8 = 2; - // The device version number is vendor-defined. const DEVICE_VERSION_MAJOR: u8 = 1; const DEVICE_VERSION_MINOR: u8 = 0; @@ -124,6 +168,7 @@ impl CtapHid { const CAPABILITY_WINK: u8 = 0x01; const CAPABILITY_CBOR: u8 = 0x04; + #[cfg(not(feature = "with_ctap1"))] const CAPABILITY_NMSG: u8 = 0x08; // Capabilitites currently supported by this device. #[cfg(feature = "with_ctap1")] @@ -136,6 +181,7 @@ impl CtapHid { const TIMEOUT_DURATION: Duration = Duration::from_ms(100); const WINK_TIMEOUT_DURATION: Duration = Duration::from_ms(5000); + /// Creates a new idle HID state. pub fn new() -> CtapHid { CtapHid { assembler: MessageAssembler::new(), @@ -144,16 +190,26 @@ impl CtapHid { } } - // Process an incoming USB HID packet, and optionally returns a list of outgoing packets to - // send as a reply. - pub fn process_hid_packet( - &mut self, - env: &mut 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 { match self .assembler .parse_packet(packet, Timestamp::::from_clock_value(clock_value)) @@ -161,170 +217,42 @@ impl CtapHid { Ok(Some(message)) => { #[cfg(feature = "debug_ctap")] writeln!(&mut Console::new(), "Received message: {:02x?}", message).unwrap(); - - let cid = message.cid; - if !self.has_valid_channel(&message) { - #[cfg(feature = "debug_ctap")] - writeln!(&mut Console::new(), "Invalid channel: {:02x?}", cid).unwrap(); - return CtapHid::error_message(cid, CtapHid::ERR_INVALID_CHANNEL); - } - // If another command arrives, stop winking to prevent accidential button touches. - self.wink_permission = TimedPermission::waiting(); - - match message.cmd { - // CTAP specification (version 20190130) section 8.1.9.1.1 - CtapHid::COMMAND_MSG => { - // If we don't have CTAP1 backward compatibilty, this command is invalid. - #[cfg(not(feature = "with_ctap1"))] - return CtapHid::error_message(cid, CtapHid::ERR_INVALID_CMD); - - #[cfg(feature = "with_ctap1")] - match ctap1::Ctap1Command::process_command( - env, - &message.payload, - ctap_state, - clock_value, - ) { - Ok(payload) => CtapHid::ctap1_success_message(cid, &payload), - Err(ctap1_status_code) => { - CtapHid::ctap1_error_message(cid, ctap1_status_code) - } - } - } - // CTAP specification (version 20190130) section 8.1.9.1.2 - CtapHid::COMMAND_CBOR => { - // CTAP specification (version 20190130) section 8.1.5.1 - // Each transaction is atomic, so we process the command directly here and - // don't handle any other packet in the meantime. - // TODO: Send keep-alive packets in the meantime. - let response = - ctap_state.process_command(env, &message.payload, cid, clock_value); - if let Some(iterator) = CtapHid::split_message(Message { - cid, - cmd: CtapHid::COMMAND_CBOR, - payload: response, - }) { - iterator - } else { - // Handle the case of a payload > 7609 bytes. - // Although this shouldn't happen if the FIDO2 commands are implemented - // correctly, we reply with a vendor specific code instead of silently - // ignoring the error. - // - // The error payload that we send instead is 1 <= 7609 bytes, so it is - // safe to unwrap() the result. - CtapHid::split_message(Message { - cid, - cmd: CtapHid::COMMAND_CBOR, - payload: vec![ - Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR as u8, - ], - }) - .unwrap() - } - } - // CTAP specification (version 20190130) section 8.1.9.1.3 - CtapHid::COMMAND_INIT => { - if message.payload.len() != 8 { - return CtapHid::error_message(cid, CtapHid::ERR_INVALID_LEN); - } - - let new_cid = if cid == CtapHid::CHANNEL_BROADCAST { - // TODO: Prevent allocating 2^32 channels. - self.allocated_cids += 1; - (self.allocated_cids as u32).to_be_bytes() - } else { - // Sync the channel and discard the current transaction. - cid - }; - - let mut payload = vec![0; 17]; - payload[..8].copy_from_slice(&message.payload); - payload[8..12].copy_from_slice(&new_cid); - payload[12] = CtapHid::PROTOCOL_VERSION; - payload[13] = CtapHid::DEVICE_VERSION_MAJOR; - payload[14] = CtapHid::DEVICE_VERSION_MINOR; - payload[15] = CtapHid::DEVICE_VERSION_BUILD; - payload[16] = CtapHid::CAPABILITIES; - - // This unwrap is safe because the payload length is 17 <= 7609 bytes. - CtapHid::split_message(Message { - cid, - cmd: CtapHid::COMMAND_INIT, - payload, - }) - .unwrap() - } - // CTAP specification (version 20190130) section 8.1.9.1.4 - CtapHid::COMMAND_PING => { - // Pong the same message. - // This unwrap is safe because if we could parse the incoming message, it's - // payload length must be <= 7609 bytes. - CtapHid::split_message(message).unwrap() - } - // CTAP specification (version 20190130) section 8.1.9.1.5 - CtapHid::COMMAND_CANCEL => { - // Authenticators MUST NOT reply to this message. - // CANCEL is handled during user presence checks in main. - HidPacketIterator::none() - } - // Optional commands - // CTAP specification (version 20190130) section 8.1.9.2.1 - CtapHid::COMMAND_WINK => { - if !message.payload.is_empty() { - return CtapHid::error_message(cid, CtapHid::ERR_INVALID_LEN); - } - self.wink_permission = - TimedPermission::granted(clock_value, CtapHid::WINK_TIMEOUT_DURATION); - CtapHid::split_message(Message { - cid, - cmd: CtapHid::COMMAND_WINK, - payload: vec![], - }) - .unwrap() - } - // CTAP specification (version 20190130) section 8.1.9.2.2 - // TODO: implement LOCK - _ => { - // Unknown or unsupported command. - CtapHid::error_message(cid, CtapHid::ERR_INVALID_CMD) - } - } + self.preprocess_message(message) } Ok(None) => { // Waiting for more packets to assemble the message, nothing to send for now. - HidPacketIterator::none() + None } Err((cid, error)) => { if !self.is_allocated_channel(cid) && error != receive::Error::UnexpectedContinuation { - CtapHid::error_message(cid, CtapHid::ERR_INVALID_CHANNEL) + Some(CtapHid::error_message(cid, CtapHid::ERR_INVALID_CHANNEL)) } else { match error { receive::Error::UnexpectedChannel => { - CtapHid::error_message(cid, CtapHid::ERR_CHANNEL_BUSY) + Some(CtapHid::error_message(cid, CtapHid::ERR_CHANNEL_BUSY)) } receive::Error::UnexpectedInit => { // TODO: Should we send another error code in this case? // Technically, we were expecting a sequence number and got another // byte, although the command/seqnum bit has higher-level semantics // than sequence numbers. - CtapHid::error_message(cid, CtapHid::ERR_INVALID_SEQ) + Some(CtapHid::error_message(cid, CtapHid::ERR_INVALID_SEQ)) } receive::Error::UnexpectedContinuation => { // CTAP specification (version 20190130) section 8.1.5.4 // Spurious continuation packets will be ignored. - HidPacketIterator::none() + None } receive::Error::UnexpectedSeq => { - CtapHid::error_message(cid, CtapHid::ERR_INVALID_SEQ) + Some(CtapHid::error_message(cid, CtapHid::ERR_INVALID_SEQ)) } receive::Error::UnexpectedLen => { - CtapHid::error_message(cid, CtapHid::ERR_INVALID_LEN) + Some(CtapHid::error_message(cid, CtapHid::ERR_INVALID_LEN)) } receive::Error::Timeout => { - CtapHid::error_message(cid, CtapHid::ERR_MSG_TIMEOUT) + Some(CtapHid::error_message(cid, CtapHid::ERR_MSG_TIMEOUT)) } } } @@ -332,10 +260,157 @@ impl CtapHid { } } + /// Processes HID-only commands of a message and returns an outgoing message if necessary. + /// + /// The preprocessed commands are: + /// - INIT + /// - CANCEL + /// - ERROR + /// - Unknown and unexpected commands like KEEPALIVE + /// - LOCK is not implemented and currently treated like an unknown command + fn preprocess_message(&mut self, message: Message) -> Option { + let cid = message.cid; + if !self.has_valid_channel(&message) { + return Some(CtapHid::error_message(cid, CtapHid::ERR_INVALID_CHANNEL)); + } + + match message.cmd { + CtapHidCommand::Msg => Some(message), + CtapHidCommand::Cbor => Some(message), + // CTAP 2.1 from 2021-06-15, section 11.2.9.1.3. + CtapHidCommand::Init => { + if message.payload.len() != 8 { + return Some(CtapHid::error_message(cid, CtapHid::ERR_INVALID_LEN)); + } + + let new_cid = if cid == CtapHid::CHANNEL_BROADCAST { + // TODO: Prevent allocating 2^32 channels. + self.allocated_cids += 1; + (self.allocated_cids as u32).to_be_bytes() + } else { + // Sync the channel and discard the current transaction. + cid + }; + + let mut payload = vec![0; 17]; + payload[..8].copy_from_slice(&message.payload); + payload[8..12].copy_from_slice(&new_cid); + payload[12] = CtapHid::PROTOCOL_VERSION; + payload[13] = CtapHid::DEVICE_VERSION_MAJOR; + payload[14] = CtapHid::DEVICE_VERSION_MINOR; + payload[15] = CtapHid::DEVICE_VERSION_BUILD; + payload[16] = CtapHid::CAPABILITIES; + + Some(Message { + cid, + cmd: CtapHidCommand::Init, + payload, + }) + } + // CTAP 2.1 from 2021-06-15, section 11.2.9.1.4. + CtapHidCommand::Ping => { + // Pong the same message. + Some(message) + } + // CTAP 2.1 from 2021-06-15, section 11.2.9.1.5. + CtapHidCommand::Cancel => { + // Authenticators MUST NOT reply to this message. + // CANCEL is handled during user presence checks in main. + None + } + CtapHidCommand::Wink => Some(message), + _ => { + // Unknown or unsupported command. + Some(CtapHid::error_message(cid, CtapHid::ERR_INVALID_CMD)) + } + } + } + + /// Processes a message's commands that affect the protocol outside HID. + pub fn process_message( + &mut self, + env: &mut impl Env, + message: Message, + clock_value: ClockValue, + ctap_state: &mut CtapState, + ) -> Message { + // If another command arrives, stop winking to prevent accidential button touches. + self.wink_permission = TimedPermission::waiting(); + + let cid = message.cid; + match message.cmd { + // CTAP 2.1 from 2021-06-15, section 11.2.9.1.1. + CtapHidCommand::Msg => { + // If we don't have CTAP1 backward compatibilty, this command is invalid. + #[cfg(not(feature = "with_ctap1"))] + return CtapHid::error_message(cid, CtapHid::ERR_INVALID_CMD); + + #[cfg(feature = "with_ctap1")] + match ctap1::Ctap1Command::process_command( + env, + &message.payload, + ctap_state, + clock_value, + ) { + Ok(payload) => CtapHid::ctap1_success_message(cid, &payload), + Err(ctap1_status_code) => CtapHid::ctap1_error_message(cid, ctap1_status_code), + } + } + // CTAP 2.1 from 2021-06-15, section 11.2.9.1.2. + CtapHidCommand::Cbor => { + // Each transaction is atomic, so we process the command directly here and + // don't handle any other packet in the meantime. + // TODO: Send "Processing" type keep-alive packets in the meantime. + let response = ctap_state.process_command(env, &message.payload, cid, clock_value); + Message { + cid, + cmd: CtapHidCommand::Cbor, + payload: response, + } + } + // CTAP 2.1 from 2021-06-15, section 11.2.9.2.1. + CtapHidCommand::Wink => { + if message.payload.is_empty() { + self.wink_permission = + TimedPermission::granted(clock_value, CtapHid::WINK_TIMEOUT_DURATION); + // The response is empty like the request. + message + } else { + CtapHid::error_message(cid, CtapHid::ERR_INVALID_LEN) + } + } + // All other commands have already been processed, keep them as is. + _ => message, + } + } + + /// Processes an incoming USB HID packet, and returns an iterator for all outgoing packets. + pub fn process_hid_packet( + &mut self, + env: &mut impl Env, + packet: &HidPacket, + clock_value: ClockValue, + ctap_state: &mut CtapState, + ) -> HidPacketIterator { + if let Some(message) = self.parse_packet(packet, clock_value) { + let processed_message = self.process_message(env, message, clock_value, ctap_state); + #[cfg(feature = "debug_ctap")] + writeln!( + &mut Console::new(), + "Sending message: {:02x?}", + processed_message + ) + .unwrap(); + CtapHid::split_message(processed_message) + } else { + HidPacketIterator::none() + } + } + fn has_valid_channel(&self, message: &Message) -> bool { match message.cid { // Only INIT commands use the broadcast channel. - CtapHid::CHANNEL_BROADCAST => message.cmd == CtapHid::COMMAND_INIT, + CtapHid::CHANNEL_BROADCAST => message.cmd == CtapHidCommand::Init, // Check that the channel is allocated. _ => self.is_allocated_channel(message.cid), } @@ -345,16 +420,15 @@ impl CtapHid { cid != CtapHid::CHANNEL_RESERVED && u32::from_be_bytes(cid) as usize <= self.allocated_cids } - fn error_message(cid: ChannelID, error_code: u8) -> HidPacketIterator { - // This unwrap is safe because the payload length is 1 <= 7609 bytes. - CtapHid::split_message(Message { + fn error_message(cid: ChannelID, error_code: u8) -> Message { + Message { cid, - cmd: CtapHid::COMMAND_ERROR, + cmd: CtapHidCommand::Error, payload: vec![error_code], - }) - .unwrap() + } } + /// Helper function to parse a raw packet. pub fn process_single_packet(packet: &HidPacket) -> (&ChannelID, ProcessedPacket) { let (cid, rest) = array_refs![packet, 4, 60]; if rest[0] & CtapHid::PACKET_TYPE_MASK != 0 { @@ -379,56 +453,65 @@ impl CtapHid { } } - fn split_message(message: Message) -> Option { - #[cfg(feature = "debug_ctap")] - writeln!(&mut Console::new(), "Sending message: {:02x?}", message).unwrap(); - HidPacketIterator::new(message) + /// Splits the message and unwraps the result. + /// + /// Unwrapping handles the case of payload lengths > 7609 bytes. All responses are fixed + /// length, with the exception of: + /// - PING, but here output equals the (validated) input, + /// - CBOR, where long responses are conceivable. + /// + /// Long CBOR responses should not happen, but we might not catch all edge cases, like for + /// example long user names that are part of the output of an assertion. These cases should be + /// correctly handled by the CTAP implementation. It is therefore an internal error from the + /// HID perspective. + fn split_message(message: Message) -> HidPacketIterator { + let cid = message.cid; + HidPacketIterator::new(message).unwrap_or_else(|| { + // The error payload is 1 <= 7609 bytes, so unwrap() is safe. + HidPacketIterator::new(Message { + cid, + cmd: CtapHidCommand::Cbor, + payload: vec![Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR as u8], + }) + .unwrap() + }) } + /// Generates the HID response packets for a keepalive status. pub fn keepalive(cid: ChannelID, status: KeepaliveStatus) -> HidPacketIterator { - let status_code = match status { - KeepaliveStatus::Processing => 1, - KeepaliveStatus::UpNeeded => 2, - }; // This unwrap is safe because the payload length is 1 <= 7609 bytes. CtapHid::split_message(Message { cid, - cmd: CtapHid::COMMAND_KEEPALIVE, - payload: vec![status_code], + cmd: CtapHidCommand::Keepalive, + payload: vec![status as u8], }) - .unwrap() } + /// Returns whether a wink permission is currently granted. pub fn should_wink(&self, now: ClockValue) -> bool { self.wink_permission.is_granted(now) } #[cfg(feature = "with_ctap1")] - fn ctap1_error_message( - cid: ChannelID, - error_code: ctap1::Ctap1StatusCode, - ) -> HidPacketIterator { - // This unwrap is safe because the payload length is 2 <= 7609 bytes + fn ctap1_error_message(cid: ChannelID, error_code: ctap1::Ctap1StatusCode) -> Message { let code: u16 = error_code.into(); - CtapHid::split_message(Message { + Message { cid, - cmd: CtapHid::COMMAND_MSG, + cmd: CtapHidCommand::Msg, payload: code.to_be_bytes().to_vec(), - }) - .unwrap() + } } #[cfg(feature = "with_ctap1")] - fn ctap1_success_message(cid: ChannelID, payload: &[u8]) -> HidPacketIterator { + fn ctap1_success_message(cid: ChannelID, payload: &[u8]) -> Message { let mut response = payload.to_vec(); let code: u16 = ctap1::Ctap1StatusCode::SW_SUCCESS.into(); response.extend_from_slice(&code.to_be_bytes()); - CtapHid::split_message(Message { + Message { cid, - cmd: CtapHid::COMMAND_MSG, + cmd: CtapHidCommand::Msg, payload: response, - }) - .unwrap() + } } } @@ -478,7 +561,7 @@ mod test { ctap_state, vec![Message { cid: CtapHid::CHANNEL_BROADCAST, - cmd: CtapHid::COMMAND_INIT, + cmd: CtapHidCommand::Init, payload: nonce.clone(), }], ); @@ -500,7 +583,7 @@ mod test { for payload_len in 0..7609 { let message = Message { cid: [0x12, 0x34, 0x56, 0x78], - cmd: 0x00, + cmd: CtapHidCommand::Cbor, payload: vec![0xFF; payload_len], }; @@ -552,7 +635,7 @@ mod test { &mut ctap_state, vec![Message { cid: CtapHid::CHANNEL_BROADCAST, - cmd: CtapHid::COMMAND_INIT, + cmd: CtapHidCommand::Init, payload: vec![0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0], }], ); @@ -561,7 +644,7 @@ mod test { reply, Some(vec![Message { cid: CtapHid::CHANNEL_BROADCAST, - cmd: CtapHid::COMMAND_INIT, + cmd: CtapHidCommand::Init, payload: vec![ 0x12, // Nonce 0x34, @@ -623,7 +706,7 @@ mod test { result, vec![Message { cid, - cmd: CtapHid::COMMAND_INIT, + cmd: CtapHidCommand::Init, payload: vec![ 0x12, // Nonce 0x34, @@ -660,7 +743,7 @@ mod test { &mut ctap_state, vec![Message { cid, - cmd: CtapHid::COMMAND_PING, + cmd: CtapHidCommand::Ping, payload: vec![0x99, 0x99], }], ); @@ -669,7 +752,7 @@ mod test { reply, Some(vec![Message { cid, - cmd: CtapHid::COMMAND_PING, + cmd: CtapHidCommand::Ping, payload: vec![0x99, 0x99] }]) ); diff --git a/src/ctap/hid/receive.rs b/src/ctap/hid/receive.rs index 08929b0..c6885a3 100644 --- a/src/ctap/hid/receive.rs +++ b/src/ctap/hid/receive.rs @@ -13,7 +13,7 @@ // limitations under the License. use super::super::customization::MAX_MSG_SIZE; -use super::{ChannelID, CtapHid, HidPacket, Message, ProcessedPacket}; +use super::{ChannelID, CtapHid, CtapHidCommand, HidPacket, Message, ProcessedPacket}; use alloc::vec::Vec; use core::mem::swap; use libtock_drivers::timer::Timestamp; @@ -131,7 +131,7 @@ impl MessageAssembler { // Unexpected initialization packet. ProcessedPacket::InitPacket { cmd, len, data } => { self.reset(); - if cmd == CtapHid::COMMAND_INIT { + if cmd == CtapHidCommand::Init as u8 { self.parse_init_packet(*cid, cmd, len, data, timestamp) } else { Err((*cid, Error::UnexpectedInit)) @@ -189,7 +189,7 @@ impl MessageAssembler { swap(&mut self.payload, &mut payload); Some(Message { cid: self.cid, - cmd: self.cmd, + cmd: CtapHidCommand::from(self.cmd), payload, }) } @@ -225,12 +225,12 @@ mod test { let mut assembler = MessageAssembler::new(); assert_eq!( assembler.parse_packet( - &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x80]), + &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x90]), DUMMY_TIMESTAMP ), Ok(Some(Message { cid: [0x12, 0x34, 0x56, 0x78], - cmd: 0x00, + cmd: CtapHidCommand::Cbor, payload: vec![] })) ); @@ -241,12 +241,12 @@ mod test { let mut assembler = MessageAssembler::new(); assert_eq!( assembler.parse_packet( - &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x80, 0x00, 0x10]), + &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x90, 0x00, 0x10]), DUMMY_TIMESTAMP ), Ok(Some(Message { cid: [0x12, 0x34, 0x56, 0x78], - cmd: 0x00, + cmd: CtapHidCommand::Cbor, payload: vec![0x00; 0x10] })) ); @@ -260,12 +260,12 @@ mod test { let mut assembler = MessageAssembler::new(); assert_eq!( assembler.parse_packet( - &byte_extend(&[0x12, 0x34, 0x56, 0x78, 0x80, 0x00, 0x10], 0xFF), + &byte_extend(&[0x12, 0x34, 0x56, 0x78, 0x90, 0x00, 0x10], 0xFF), DUMMY_TIMESTAMP ), Ok(Some(Message { cid: [0x12, 0x34, 0x56, 0x78], - cmd: 0x00, + cmd: CtapHidCommand::Cbor, payload: vec![0xFF; 0x10] })) ); @@ -288,7 +288,7 @@ mod test { ), Ok(Some(Message { cid: [0x12, 0x34, 0x56, 0x78], - cmd: 0x01, + cmd: CtapHidCommand::Ping, payload: vec![0x00; 0x40] })) ); @@ -318,7 +318,7 @@ mod test { ), Ok(Some(Message { cid: [0x12, 0x34, 0x56, 0x78], - cmd: 0x01, + cmd: CtapHidCommand::Ping, payload: vec![0x00; 0x80] })) ); @@ -350,7 +350,7 @@ mod test { ), Ok(Some(Message { cid: [0x12, 0x34, 0x56, 0x78], - cmd: 0x01, + cmd: CtapHidCommand::Ping, payload: vec![0x00; 0x1DB9] })) ); @@ -362,12 +362,15 @@ mod test { let mut assembler = MessageAssembler::new(); for i in 0..10 { // Introduce some variability in the messages. - let cmd = 2 * i; + let cmd = CtapHidCommand::from(i + 1); let byte = 3 * i; assert_eq!( assembler.parse_packet( - &byte_extend(&[0x12, 0x34, 0x56, 0x78, 0x80 | cmd, 0x00, 0x80], byte), + &byte_extend( + &[0x12, 0x34, 0x56, 0x78, 0x80 | cmd as u8, 0x00, 0x80], + byte + ), DUMMY_TIMESTAMP ), Ok(None) @@ -400,12 +403,12 @@ mod test { for i in 0..10 { // Introduce some variability in the messages. let cid = 0x78 + i; - let cmd = 2 * i; + let cmd = CtapHidCommand::from(i + 1); let byte = 3 * i; assert_eq!( assembler.parse_packet( - &byte_extend(&[0x12, 0x34, 0x56, cid, 0x80 | cmd, 0x00, 0x80], byte), + &byte_extend(&[0x12, 0x34, 0x56, cid, 0x80 | cmd as u8, 0x00, 0x80], byte), DUMMY_TIMESTAMP ), Ok(None) @@ -443,11 +446,12 @@ mod test { ); // Check that many sorts of packets on another channel are ignored. - for cmd in 0..=0xFF { + for i in 0..=0xFF { + let cmd = CtapHidCommand::from(i); for byte in 0..=0xFF { assert_eq!( assembler.parse_packet( - &byte_extend(&[0x12, 0x34, 0x56, 0x9A, cmd, 0x00], byte), + &byte_extend(&[0x12, 0x34, 0x56, 0x9A, cmd as u8, 0x00], byte), DUMMY_TIMESTAMP ), Err(([0x12, 0x34, 0x56, 0x9A], Error::UnexpectedChannel)) @@ -462,7 +466,7 @@ mod test { ), Ok(Some(Message { cid: [0x12, 0x34, 0x56, 0x78], - cmd: 0x01, + cmd: CtapHidCommand::Ping, payload: vec![0x00; 0x40] })) ); @@ -479,12 +483,12 @@ mod test { let byte = 2 * i; assert_eq!( assembler.parse_packet( - &byte_extend(&[0x12, 0x34, 0x56, 0x78, 0x80, 0x00, 0x10], byte), + &byte_extend(&[0x12, 0x34, 0x56, 0x78, 0x81, 0x00, 0x10], byte), DUMMY_TIMESTAMP ), Ok(Some(Message { cid: [0x12, 0x34, 0x56, 0x78], - cmd: 0x00, + cmd: CtapHidCommand::Ping, payload: vec![byte; 0x10] })) ); @@ -584,7 +588,7 @@ mod test { assembler.parse_packet(&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x7F]), timestamp), Ok(Some(Message { cid: [0x12, 0x34, 0x56, 0x78], - cmd: 0x01, + cmd: CtapHidCommand::Ping, payload: vec![0x00; 0x1DB9] })) ); @@ -612,7 +616,7 @@ mod test { ), Ok(Some(Message { cid: [0x12, 0x34, 0x56, 0x78], - cmd: 0x06, + cmd: CtapHidCommand::Init, payload: vec![0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0] })) ); diff --git a/src/ctap/hid/send.rs b/src/ctap/hid/send.rs index 5ea2e1c..d919e68 100644 --- a/src/ctap/hid/send.rs +++ b/src/ctap/hid/send.rs @@ -14,6 +14,9 @@ use super::{CtapHid, HidPacket, Message}; +/// Iterator for HID packets. +/// +/// The `new` constructor splits the CTAP `Message` into `HidPacket`s for sending over USB. pub struct HidPacketIterator(Option); impl HidPacketIterator { @@ -99,7 +102,7 @@ impl Iterator for MessageSplitter { match self.seq { None => { // First, send an initialization packet. - self.packet[4] = self.message.cmd | CtapHid::TYPE_INIT_BIT; + self.packet[4] = self.message.cmd as u8 | CtapHid::TYPE_INIT_BIT; self.packet[5] = (payload_len >> 8) as u8; self.packet[6] = payload_len as u8; @@ -128,6 +131,7 @@ impl Iterator for MessageSplitter { #[cfg(test)] mod test { + use super::super::CtapHidCommand; use super::*; fn assert_packet_output_equality(message: Message, expected_packets: Vec) { @@ -142,11 +146,11 @@ mod test { fn test_hid_packet_iterator_single_packet() { let message = Message { cid: [0x12, 0x34, 0x56, 0x78], - cmd: 0x4C, + cmd: CtapHidCommand::Cbor, payload: vec![0xAA, 0xBB], }; let expected_packets: Vec = vec![[ - 0x12, 0x34, 0x56, 0x78, 0xCC, 0x00, 0x02, 0xAA, 0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x12, 0x34, 0x56, 0x78, 0x90, 0x00, 0x02, 0xAA, 0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -159,11 +163,11 @@ mod test { fn test_hid_packet_iterator_big_single_packet() { let message = Message { cid: [0x12, 0x34, 0x56, 0x78], - cmd: 0x4C, + cmd: CtapHidCommand::Cbor, payload: vec![0xAA; 64 - 7], }; let expected_packets: Vec = vec![[ - 0x12, 0x34, 0x56, 0x78, 0xCC, 0x00, 0x39, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0x12, 0x34, 0x56, 0x78, 0x90, 0x00, 0x39, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, @@ -176,12 +180,12 @@ mod test { fn test_hid_packet_iterator_two_packets() { let message = Message { cid: [0x12, 0x34, 0x56, 0x78], - cmd: 0x4C, + cmd: CtapHidCommand::Cbor, payload: vec![0xAA; 64 - 7 + 1], }; let expected_packets: Vec = vec![ [ - 0x12, 0x34, 0x56, 0x78, 0xCC, 0x00, 0x3A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0x12, 0x34, 0x56, 0x78, 0x90, 0x00, 0x3A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, @@ -204,12 +208,12 @@ mod test { payload.extend(vec![0xBB; 64 - 5]); let message = Message { cid: [0x12, 0x34, 0x56, 0x78], - cmd: 0x4C, + cmd: CtapHidCommand::Cbor, payload, }; let expected_packets: Vec = vec![ [ - 0x12, 0x34, 0x56, 0x78, 0xCC, 0x00, 0x74, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0x12, 0x34, 0x56, 0x78, 0x90, 0x00, 0x74, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, @@ -238,12 +242,12 @@ mod test { let message = Message { cid: [0x12, 0x34, 0x56, 0x78], - cmd: 0xAB, + cmd: CtapHidCommand::Msg, payload, }; let mut expected_packets: Vec = vec![[ - 0x12, 0x34, 0x56, 0x78, 0xAB, 0x1D, 0xB9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x12, 0x34, 0x56, 0x78, 0x83, 0x1D, 0xB9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, @@ -271,7 +275,7 @@ mod test { assert_eq!(payload.len(), 0x1dba); let message = Message { cid: [0x12, 0x34, 0x56, 0x78], - cmd: 0xAB, + cmd: CtapHidCommand::Msg, payload, }; assert!(HidPacketIterator::new(message).is_none()); @@ -283,7 +287,7 @@ mod test { let payload = vec![0xFF; 0x10000]; let message = Message { cid: [0x12, 0x34, 0x56, 0x78], - cmd: 0xAB, + cmd: CtapHidCommand::Msg, payload, }; assert!(HidPacketIterator::new(message).is_none()); diff --git a/src/env/tock/mod.rs b/src/env/tock/mod.rs index 5c32822..ef2468b 100644 --- a/src/env/tock/mod.rs +++ b/src/env/tock/mod.rs @@ -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();