diff --git a/.cargo/config b/.cargo/config deleted file mode 100644 index 92823f72145afc5aa99dda78574337db7c8ffafc..0000000000000000000000000000000000000000 --- a/.cargo/config +++ /dev/null @@ -1,7 +0,0 @@ -# Target configuration for the CTAP2 app on the nRF52840 chip -[target.thumbv7em-none-eabi] -rustflags = [ - "-C", "link-arg=-Tnrf52840dk_layout.ld", - "-C", "relocation-model=static", - "-D", "warnings", -] diff --git a/.github/workflows/boards_build.yml b/.github/workflows/boards_build.yml index ef71f425d2bf929f569e2932838d7f6ea2be30c0..0a8fbebb7e1886443fc9a6e67ef36d4f53adca0d 100644 --- a/.github/workflows/boards_build.yml +++ b/.github/workflows/boards_build.yml @@ -26,6 +26,12 @@ jobs: run: python -m pip install --upgrade pip setuptools wheel - name: Set up OpenSK run: ./setup.sh + + - name: Building board nrf52840_dongle_dfu + run: ./deploy.py --board=nrf52840_dongle_dfu --no-app --programmer=none + - name: Building board nrf52840_mdk_dfu + run: ./deploy.py --board=nrf52840_mdk_dfu --no-app --programmer=none + - name: Create a long build directory run: mkdir this-is-a-long-build-directory-0123456789abcdefghijklmnopqrstuvwxyz && mv third_party this-is-a-long-build-directory-0123456789abcdefghijklmnopqrstuvwxyz/ diff --git a/.github/workflows/cargo_check.yml b/.github/workflows/cargo_check.yml index a6c4c4696cca73994152d3ee1422399374e7f768..c54fc9e34ab5309e19d28dc4f9894bbcba72637f 100644 --- a/.github/workflows/cargo_check.yml +++ b/.github/workflows/cargo_check.yml @@ -64,17 +64,23 @@ jobs: command: check args: --target thumbv7em-none-eabi --release --features ram_storage + - name: Check OpenSK verbose + uses: actions-rs/cargo@v1 + with: + command: check + args: --target thumbv7em-none-eabi --release --features verbose + - name: Check OpenSK debug_ctap,with_ctap1 uses: actions-rs/cargo@v1 with: command: check args: --target thumbv7em-none-eabi --release --features debug_ctap,with_ctap1 - - name: Check OpenSK debug_ctap,with_ctap1,panic_console,debug_allocations + - name: Check OpenSK debug_ctap,with_ctap1,panic_console,debug_allocations,verbose uses: actions-rs/cargo@v1 with: command: check - args: --target thumbv7em-none-eabi --release --features debug_ctap,with_ctap1,panic_console,debug_allocations + args: --target thumbv7em-none-eabi --release --features debug_ctap,with_ctap1,panic_console,debug_allocations,verbose - name: Check examples uses: actions-rs/cargo@v1 diff --git a/.github/workflows/opensk_build.yml b/.github/workflows/opensk_build.yml index a99438aca47b37a95669dd144c4e9af290758319..f51637df20fa9dbec8c120d5b6a7ac3272290f82 100644 --- a/.github/workflows/opensk_build.yml +++ b/.github/workflows/opensk_build.yml @@ -24,8 +24,16 @@ jobs: - name: Set up OpenSK run: ./setup.sh + - name: Building sha256sum tool + uses: actions-rs/cargo@v1 + with: + command: build + args: --manifest-path third_party/tock/tools/sha256sum/Cargo.toml + - name: Building OpenSK uses: actions-rs/cargo@v1 with: command: build args: --release --target=thumbv7em-none-eabi --features with_ctap1 + - name: Compute SHA-256 sum + run: ./third_party/tock/tools/sha256sum/target/debug/sha256sum target/thumbv7em-none-eabi/release/ctap2 diff --git a/.vscode/settings.json b/.vscode/settings.json index ff197f1420b30bf157cba0ad594d91a36344a81a..16666833b87006eea09cefb03bbb9a602a18b45e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -20,6 +20,7 @@ "python.linting.enabled": true, "python.linting.lintOnSave": true, "python.linting.pylintEnabled": true, + "python.linting.pylintPath": "pylint", "[python]": { "editor.tabSize": 2 }, diff --git a/Cargo.toml b/Cargo.toml index 3155106e9d79fc478ea7c9669c79fefd6124b884..1d1ad6e18f6a1651f9c5a59a5d4d4f70640e6c5c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,12 +18,13 @@ arrayref = "0.3.6" subtle = { version = "2.2", default-features = false, features = ["nightly"] } [features] -std = ["cbor/std", "crypto/std", "crypto/derive_debug"] +debug_allocations = ["libtock/debug_allocations"] debug_ctap = ["crypto/derive_debug"] -with_ctap1 = ["crypto/with_ctap1"] panic_console = ["libtock/panic_console"] -debug_allocations = ["libtock/debug_allocations"] +std = ["cbor/std", "crypto/std", "crypto/derive_debug"] ram_storage = [] +verbose = ["debug_ctap"] +with_ctap1 = ["crypto/with_ctap1"] [dev-dependencies] elf2tab = "0.4.0" diff --git a/README.md b/README.md index 34d7a9bedb7dde5b5c493fd783e20e4f0d78eec9..3f71d717201e0bfde0b4de2060305951cd2f0587 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,9 @@ successfully tested on the following boards: ## Disclaimer -This project is proof-of-concept and a research platform. It's still under -development and as such comes with a few limitations: +This project is **proof-of-concept and a research platform**. It is **NOT** +meant for a daily usage. It's still under development and as such comes with a +few limitations: ### FIDO2 @@ -53,25 +54,17 @@ For a more detailed guide, please refer to our ./setup.sh ``` -2. If Tock OS is already installed on your board, move to the next step. - Otherwise, just run one of the following commands, depending on the board - you have: +2. Next step is to install Tock OS as well as the OpenSK application on your + board (**Warning**: it will erase the locally stored credentials). Run: ```shell # Nordic nRF52840-DK board - ./deploy.py os --board=nrf52840_dk + ./deploy.py --board=nrf52840dk --opensk # Nordic nRF52840-Dongle - ./deploy.py os --board=nrf52840_dongle + ./deploy.py --board=nrf52840_dongle --opensk ``` -3. Next step is to install/update the OpenSK application on your board - (**Warning**: it will erase the locally stored credentials). Run: - - ```shell - ./deploy.py app --opensk - ``` - -4. On Linux, you may want to avoid the need for `root` privileges to interact +3. On Linux, you may want to avoid the need for `root` privileges to interact with the key. For that purpose we provide a udev rule file that can be installed with the following command: diff --git a/boards/nrf52840_dongle_dfu/Cargo.toml b/boards/nrf52840_dongle_dfu/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..6944eb33fc308caba08ec539d5d6af5783d4e3be --- /dev/null +++ b/boards/nrf52840_dongle_dfu/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "nrf52840_dongle_dfu" +version = "0.1.0" +authors = ["Tock Project Developers <tock-dev@googlegroups.com>"] +build = "build.rs" +edition = "2018" + +[profile.dev] +panic = "abort" +lto = false +opt-level = "z" +debug = true + +[profile.release] +panic = "abort" +lto = true +opt-level = "z" +debug = true + +[[bin]] +path = "../../third_party/tock/boards/nordic/nrf52840_dongle/src/main.rs" +name = "nrf52840_dongle_dfu" + +[dependencies] +components = { path = "../../third_party/tock/boards/components" } +cortexm4 = { path = "../../third_party/tock/arch/cortex-m4" } +capsules = { path = "../../third_party/tock/capsules" } +kernel = { path = "../../third_party/tock/kernel" } +nrf52840 = { path = "../../third_party/tock/chips/nrf52840" } +nrf52dk_base = { path = "../../third_party/tock/boards/nordic/nrf52dk_base" } diff --git a/boards/nrf52840_dongle_dfu/Makefile b/boards/nrf52840_dongle_dfu/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..723ba0af1570d50a7e1257bce4aa3d39108e4bf5 --- /dev/null +++ b/boards/nrf52840_dongle_dfu/Makefile @@ -0,0 +1,29 @@ +# Makefile for building the tock kernel for the nRF development kit + +TOCK_ARCH=cortex-m4 +TARGET=thumbv7em-none-eabi +PLATFORM=nrf52840_dongle_dfu + +include ../../third_party/tock/boards/Makefile.common + +TOCKLOADER=tockloader + +# Where in the nrf52 flash to load the kernel with `tockloader` +KERNEL_ADDRESS=0x01000 + +# Upload programs over uart with tockloader +ifdef PORT + TOCKLOADER_GENERAL_FLAGS += --port $(PORT) +endif + +TOCKLOADER_JTAG_FLAGS = --jlink --arch $(TOCK_ARCH) --board $(PLATFORM) --page-size 4096 --jlink-device nrf52840_xxaa + +# Upload the kernel over JTAG +.PHONY: flash +flash: target/$(TARGET)/release/$(PLATFORM).bin + $(TOCKLOADER) $(TOCKLOADER_GENERAL_FLAGS) flash --address $(KERNEL_ADDRESS) $(TOCKLOADER_JTAG_FLAGS) $< + +# Upload the kernel over serial/bootloader +.PHONY: program +program: target/$(TARGET)/release/$(PLATFORM).hex + $(error Cannot program nRF52 Dongle over USB. Use \`make flash\` and JTAG) diff --git a/boards/nrf52840_dongle_dfu/build.rs b/boards/nrf52840_dongle_dfu/build.rs new file mode 100644 index 0000000000000000000000000000000000000000..2631dccb75f21f432f5186b17215367490e2d3f2 --- /dev/null +++ b/boards/nrf52840_dongle_dfu/build.rs @@ -0,0 +1,4 @@ +fn main() { + println!("cargo:rerun-if-changed=layout.ld"); + println!("cargo:rerun-if-changed=../../third_party/tock/boards/kernel_layout.ld"); +} diff --git a/boards/nrf52840_dongle_dfu/layout.ld b/boards/nrf52840_dongle_dfu/layout.ld new file mode 100644 index 0000000000000000000000000000000000000000..834133c00b8d0adb87c9e8eb355ed0b2e91c0582 --- /dev/null +++ b/boards/nrf52840_dongle_dfu/layout.ld @@ -0,0 +1,10 @@ +MEMORY +{ + rom (rx) : ORIGIN = 0x00001000, LENGTH = 188K + prog (rx) : ORIGIN = 0x00030000, LENGTH = 832K + ram (rwx) : ORIGIN = 0x20000000, LENGTH = 256K +} + +MPU_MIN_ALIGN = 8K; + +INCLUDE ../../third_party/tock/boards/kernel_layout.ld diff --git a/boards/nrf52840_mdk_dfu/Cargo.toml b/boards/nrf52840_mdk_dfu/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..1047869ba67c2d46b0af09d0ec7dd214a051fa43 --- /dev/null +++ b/boards/nrf52840_mdk_dfu/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "nrf52840_mdk_dfu" +version = "0.1.0" +authors = ["Yihui Xiong <yihui.xiong@hotmail.com>"] +build = "build.rs" +edition = "2018" + +[profile.dev] +panic = "abort" +lto = false +opt-level = "z" +debug = true + +[profile.release] +panic = "abort" +lto = true +opt-level = "z" +debug = true + +[dependencies] +components = { path = "../../third_party/tock/boards/components" } +cortexm4 = { path = "../../third_party/tock/arch/cortex-m4" } +capsules = { path = "../../third_party/tock/capsules" } +kernel = { path = "../../third_party/tock/kernel" } +nrf52840 = { path = "../../third_party/tock/chips/nrf52840" } +nrf52dk_base = { path = "../../third_party/tock/boards/nordic/nrf52dk_base" } diff --git a/boards/nrf52840_mdk_dfu/Makefile b/boards/nrf52840_mdk_dfu/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..e915141d9174e55cb67deccc607d9dc1ae49300a --- /dev/null +++ b/boards/nrf52840_mdk_dfu/Makefile @@ -0,0 +1,29 @@ +# Makefile for building the tock kernel for the nRF development kit + +TOCK_ARCH=cortex-m4 +TARGET=thumbv7em-none-eabi +PLATFORM=nrf52840_mdk_dfu + +include ../../third_party/tock/boards/Makefile.common + +TOCKLOADER=tockloader + +# Where in the nrf52 flash to load the kernel with `tockloader` +KERNEL_ADDRESS=0x01000 + +# Upload programs over uart with tockloader +ifdef PORT + TOCKLOADER_GENERAL_FLAGS += --port $(PORT) +endif + +TOCKLOADER_JTAG_FLAGS = --jlink --arch $(TOCK_ARCH) --board $(PLATFORM) --page-size 4096 --jlink-device nrf52840_xxaa + +# Upload the kernel over JTAG +.PHONY: flash +flash: target/$(TARGET)/release/$(PLATFORM).bin + $(TOCKLOADER) $(TOCKLOADER_GENERAL_FLAGS) flash --address $(KERNEL_ADDRESS) $(TOCKLOADER_JTAG_FLAGS) $< + +# Upload the kernel over serial/bootloader +.PHONY: program +program: target/$(TARGET)/release/$(PLATFORM).hex + $(error Cannot program nRF52 Dongle over USB. Use \`make flash\` and JTAG) diff --git a/boards/nrf52840_mdk_dfu/build.rs b/boards/nrf52840_mdk_dfu/build.rs new file mode 100644 index 0000000000000000000000000000000000000000..2631dccb75f21f432f5186b17215367490e2d3f2 --- /dev/null +++ b/boards/nrf52840_mdk_dfu/build.rs @@ -0,0 +1,4 @@ +fn main() { + println!("cargo:rerun-if-changed=layout.ld"); + println!("cargo:rerun-if-changed=../../third_party/tock/boards/kernel_layout.ld"); +} diff --git a/boards/nrf52840_mdk_dfu/layout.ld b/boards/nrf52840_mdk_dfu/layout.ld new file mode 100644 index 0000000000000000000000000000000000000000..834133c00b8d0adb87c9e8eb355ed0b2e91c0582 --- /dev/null +++ b/boards/nrf52840_mdk_dfu/layout.ld @@ -0,0 +1,10 @@ +MEMORY +{ + rom (rx) : ORIGIN = 0x00001000, LENGTH = 188K + prog (rx) : ORIGIN = 0x00030000, LENGTH = 832K + ram (rwx) : ORIGIN = 0x20000000, LENGTH = 256K +} + +MPU_MIN_ALIGN = 8K; + +INCLUDE ../../third_party/tock/boards/kernel_layout.ld diff --git a/boards/nrf52840_mdk_dfu/src/io.rs b/boards/nrf52840_mdk_dfu/src/io.rs new file mode 100644 index 0000000000000000000000000000000000000000..1f5f5a6f98501aa52cf69a394025471111a6a4f7 --- /dev/null +++ b/boards/nrf52840_mdk_dfu/src/io.rs @@ -0,0 +1,65 @@ +use core::fmt::Write; +use core::panic::PanicInfo; +use cortexm4; +use kernel::debug; +use kernel::debug::IoWrite; +use kernel::hil::led; +use kernel::hil::uart::{self, Configure}; +use nrf52840::gpio::Pin; + +use crate::CHIP; +use crate::PROCESSES; + +struct Writer { + initialized: bool, +} + +static mut WRITER: Writer = Writer { initialized: false }; + +impl Write for Writer { + fn write_str(&mut self, s: &str) -> ::core::fmt::Result { + self.write(s.as_bytes()); + Ok(()) + } +} + +impl IoWrite for Writer { + fn write(&mut self, buf: &[u8]) { + let uart = unsafe { &mut nrf52840::uart::UARTE0 }; + if !self.initialized { + self.initialized = true; + uart.configure(uart::Parameters { + baud_rate: 115200, + stop_bits: uart::StopBits::One, + parity: uart::Parity::None, + hw_flow_control: false, + width: uart::Width::Eight, + }); + } + for &c in buf { + unsafe { + uart.send_byte(c); + } + while !uart.tx_ready() {} + } + } +} + +#[cfg(not(test))] +#[no_mangle] +#[panic_handler] +/// Panic handler +pub unsafe extern "C" fn panic_fmt(pi: &PanicInfo) -> ! { + // The nRF52840 Dongle LEDs (see back of board) + const LED1_PIN: Pin = Pin::P0_23; + let led = &mut led::LedLow::new(&mut nrf52840::gpio::PORT[LED1_PIN]); + let writer = &mut WRITER; + debug::panic( + &mut [led], + writer, + pi, + &cortexm4::support::nop, + &PROCESSES, + &CHIP, + ) +} diff --git a/boards/nrf52840_mdk_dfu/src/main.rs b/boards/nrf52840_mdk_dfu/src/main.rs new file mode 100644 index 0000000000000000000000000000000000000000..1777bc3c9e258c0e3b19dce61880a652815b72c6 --- /dev/null +++ b/boards/nrf52840_mdk_dfu/src/main.rs @@ -0,0 +1,123 @@ +//! Tock kernel for the Makerdiary nRF52840 MDK USB dongle. +//! +//! It is based on nRF52840 SoC (Cortex M4 core with a BLE transceiver) with +//! many exported I/O and peripherals. + +#![no_std] +#![no_main] +#![deny(missing_docs)] + +use kernel::component::Component; +#[allow(unused_imports)] +use kernel::{debug, debug_gpio, debug_verbose, static_init}; +use nrf52840::gpio::Pin; +use nrf52dk_base::{SpiPins, UartChannel, UartPins}; + +// The nRF52840 MDK USB Dongle LEDs +const LED1_R_PIN: Pin = Pin::P0_23; +const LED1_G_PIN: Pin = Pin::P0_22; +const LED1_B_PIN: Pin = Pin::P0_24; + +// The nRF52840 Dongle button +const BUTTON_PIN: Pin = Pin::P0_18; +const BUTTON_RST_PIN: Pin = Pin::P0_02; + +const UART_RTS: Option<Pin> = Some(Pin::P0_21); +const UART_TXD: Pin = Pin::P0_20; +const UART_CTS: Option<Pin> = Some(Pin::P0_03); +const UART_RXD: Pin = Pin::P0_19; + +const SPI_MOSI: Pin = Pin::P0_05; +const SPI_MISO: Pin = Pin::P0_06; +const SPI_CLK: Pin = Pin::P0_07; + +/// UART Writer +pub mod io; + +// State for loading and holding applications. +// How should the kernel respond when a process faults. +const FAULT_RESPONSE: kernel::procs::FaultResponse = kernel::procs::FaultResponse::Panic; + +// Number of concurrent processes this platform supports. +const NUM_PROCS: usize = 8; + +// RAM to be shared by all application processes. +#[link_section = ".app_memory"] +static mut APP_MEMORY: [u8; 0x3C000] = [0; 0x3C000]; + +static mut PROCESSES: [Option<&'static dyn kernel::procs::ProcessType>; NUM_PROCS] = + [None, None, None, None, None, None, None, None]; + +// Static reference to chip for panic dumps +static mut CHIP: Option<&'static nrf52840::chip::Chip> = None; + +/// Dummy buffer that causes the linker to reserve enough space for the stack. +#[no_mangle] +#[link_section = ".stack_buffer"] +pub static mut STACK_MEMORY: [u8; 0x1000] = [0; 0x1000]; + +/// Entry point in the vector table called on hard reset. +#[no_mangle] +pub unsafe fn reset_handler() { + // Loads relocations and clears BSS + nrf52840::init(); + + let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&PROCESSES)); + // GPIOs + let gpio = components::gpio::GpioComponent::new(board_kernel).finalize( + components::gpio_component_helper!( + &nrf52840::gpio::PORT[Pin::P0_04], + &nrf52840::gpio::PORT[Pin::P0_05], + &nrf52840::gpio::PORT[Pin::P0_06], + &nrf52840::gpio::PORT[Pin::P0_07], + &nrf52840::gpio::PORT[Pin::P0_08] + ), + ); + let button = components::button::ButtonComponent::new(board_kernel).finalize( + components::button_component_helper!(( + &nrf52840::gpio::PORT[BUTTON_PIN], + kernel::hil::gpio::ActivationMode::ActiveLow, + kernel::hil::gpio::FloatingState::PullUp + )), + ); + + let led = components::led::LedsComponent::new().finalize(components::led_component_helper!( + ( + &nrf52840::gpio::PORT[LED1_R_PIN], + kernel::hil::gpio::ActivationMode::ActiveLow + ), + ( + &nrf52840::gpio::PORT[LED1_G_PIN], + kernel::hil::gpio::ActivationMode::ActiveLow + ), + ( + &nrf52840::gpio::PORT[LED1_B_PIN], + kernel::hil::gpio::ActivationMode::ActiveLow + ) + )); + let chip = static_init!(nrf52840::chip::Chip, nrf52840::chip::new()); + CHIP = Some(chip); + + nrf52dk_base::setup_board( + board_kernel, + BUTTON_RST_PIN, + &nrf52840::gpio::PORT, + gpio, + LED1_R_PIN, + LED1_G_PIN, + LED1_B_PIN, + led, + UartChannel::Pins(UartPins::new(UART_RTS, UART_TXD, UART_CTS, UART_RXD)), + &SpiPins::new(SPI_MOSI, SPI_MISO, SPI_CLK), + &None, + button, + true, + &mut APP_MEMORY, + &mut PROCESSES, + FAULT_RESPONSE, + nrf52840::uicr::Regulator0Output::V3_0, + false, + &Some(&nrf52840::usbd::USBD), + chip, + ); +} diff --git a/deploy.py b/deploy.py index dc69c24ea4921d73e03577649edbcbdf995d677b..ddbd2e55cd0999324d591a7fcdb522e62169b966 100755 --- a/deploy.py +++ b/deploy.py @@ -20,6 +20,7 @@ from __future__ import division from __future__ import print_function import argparse +import collections import copy import os import shutil @@ -27,15 +28,125 @@ import subprocess import sys import colorama +from six.moves import input from tockloader import tab from tockloader import tbfh from tockloader import tockloader as loader from tockloader.exceptions import TockLoaderException -# This structure allows us in the future to also support out-of-tree boards. +PROGRAMMERS = frozenset(("jlink", "openocd", "pyocd", "nordicdfu", "none")) + +# This structure allows us to support out-of-tree boards as well as (in the +# future) more achitectures. +OpenSKBoard = collections.namedtuple( + "OpenSKBoard", + [ + # Location of the Tock board (where the Makefile file is) + "path", + # Target architecture (e.g. thumbv7em-none-eabi) + "arch", + # Size of 1 page of flash memory + "page_size", + # Flash address at which the kernel will be written + "kernel_address", + # Set to None is padding is not required for the board. + # This creates a fake Tock OS application that starts at the + # address specified by this parameter (must match the `prog` value + # specified on the board's `layout.ld` file) and will end at + # `app_address`. + "padding_address", + # Linker script to produce a working app for this board + "app_ldscript", + # Flash address at which the app should be written + "app_address", + # Target name for flashing the board using pyOCD + "pyocd_target", + # The cfg file in OpenOCD board folder + "openocd_board", + # Options to tell Tockloader how to work with OpenOCD + # Default: [] + "openocd_options", + # Dictionnary specifying custom commands for OpenOCD + # Default is an empty dict + # Valid keys are: program, read, erase + "openocd_commands", + # Interface to use with JLink (e.g. swd, jtag, etc.) + "jlink_if", + # Device name as supported by JLinkExe + "jlink_device", + # Whether Nordic DFU flashing method is supported + "nordic_dfu", + ]) + SUPPORTED_BOARDS = { - "nrf52840_dk": "third_party/tock/boards/nordic/nrf52840dk", - "nrf52840_dongle": "third_party/tock/boards/nordic/nrf52840_dongle" + "nrf52840dk": + OpenSKBoard( + path="third_party/tock/boards/nordic/nrf52840dk", + arch="thumbv7em-none-eabi", + page_size=4096, + kernel_address=0, + padding_address=0x30000, + app_ldscript="nrf52840dk_layout.ld", + app_address=0x40000, + pyocd_target="nrf52840", + openocd_board="nordic_nrf52840_dongle.cfg", + openocd_options=[], + openocd_commands={}, + jlink_if="swd", + jlink_device="nrf52840_xxaa", + nordic_dfu=False, + ), + "nrf52840_dongle": + OpenSKBoard( + path="third_party/tock/boards/nordic/nrf52840_dongle", + arch="thumbv7em-none-eabi", + page_size=4096, + kernel_address=0, + padding_address=0x30000, + app_ldscript="nrf52840dk_layout.ld", + app_address=0x40000, + pyocd_target="nrf52840", + openocd_board="nordic_nrf52840_dongle.cfg", + openocd_options=[], + openocd_commands={}, + jlink_if="swd", + jlink_device="nrf52840_xxaa", + nordic_dfu=False, + ), + "nrf52840_dongle_dfu": + OpenSKBoard( + path="boards/nrf52840_dongle_dfu", + arch="thumbv7em-none-eabi", + page_size=4096, + kernel_address=0x1000, + padding_address=0x30000, + app_ldscript="nrf52840dk_layout.ld", + app_address=0x40000, + pyocd_target="nrf52840", + openocd_board="nordic_nrf52840_dongle.cfg", + openocd_options=[], + openocd_commands={}, + jlink_if="swd", + jlink_device="nrf52840_xxaa", + nordic_dfu=True, + ), + "nrf52840_mdk_dfu": + OpenSKBoard( + path="boards/nrf52840_mdk_dfu", + arch="thumbv7em-none-eabi", + page_size=4096, + kernel_address=0x1000, + padding_address=0x30000, + app_ldscript="nrf52840dk_layout.ld", + app_address=0x40000, + pyocd_target="nrf52840", + openocd_board="nordic_nrf52840_dongle.cfg", + openocd_options=[], + openocd_commands={}, + jlink_if="swd", + jlink_device="nrf52840_xxaa", + nordic_dfu=True, + ), } # The STACK_SIZE value below must match the one used in the linker script @@ -50,9 +161,9 @@ APP_HEAP_SIZE = 90000 def get_supported_boards(): boards = [] - for name, root in SUPPORTED_BOARDS.items(): - if all((os.path.exists(os.path.join(root, "Cargo.toml")), - os.path.exists(os.path.join(root, "Makefile")))): + for name, props in SUPPORTED_BOARDS.items(): + if all((os.path.exists(os.path.join(props.path, "Cargo.toml")), + (props.app_ldscript and os.path.exists(props.app_ldscript)))): boards.append(name) return tuple(set(boards)) @@ -79,9 +190,23 @@ def info(msg): message=msg)) +def assert_mandatory_binary(binary): + if not shutil.which(binary): + fatal(("Couldn't find {} binary. Make sure it is installed and " + "that your PATH is set correctly.").format(binary)) + + +def assert_python_library(module): + try: + __import__(module) + except ModuleNotFoundError: + fatal(("Couldn't load python3 module {name}. " + "Try to run: pip3 install {name}").format(name=module)) + + class RemoveConstAction(argparse.Action): - # pylint: disable=W0622 + # pylint: disable=redefined-builtin def __init__(self, option_strings, dest, @@ -121,28 +246,34 @@ class OpenSKInstaller: self.args = args # Where all the TAB files should go self.tab_folder = os.path.join("target", "tab") - # This is the filename that elf2tab command expects in order - # to create a working TAB file. - self.target_elf_filename = os.path.join(self.tab_folder, "cortex-m4.elf") + board = SUPPORTED_BOARDS[self.args.board] self.tockloader_default_args = argparse.Namespace( - arch="cortex-m4", - board=getattr(self.args, "board", "nrf52840"), + arch=board.arch, + board=self.args.board, debug=False, force=False, - jlink=True, - jlink_device="nrf52840_xxaa", - jlink_if="swd", + jlink=self.args.programmer == "jlink", + jlink_device=board.jlink_device, + jlink_if=board.jlink_if, jlink_speed=1200, + openocd=self.args.programmer == "openocd", + openocd_board=board.openocd_board, jtag=False, no_bootloader_entry=False, - page_size=4096, + page_size=board.page_size, port=None, ) - def checked_command_output(self, cmd): + def checked_command_output(self, cmd, env=None, cwd=None): cmd_output = "" try: - cmd_output = subprocess.check_output(cmd) + cmd_output = subprocess.run( + cmd, + stdout=subprocess.PIPE, + timeout=None, + check=True, + env=env, + cwd=cwd).stdout except subprocess.CalledProcessError as e: fatal("Failed to execute {}: {}".format(cmd[0], str(e))) # Unreachable because fatal() will exit @@ -166,38 +297,57 @@ class OpenSKInstaller: # Need to update self.checked_command_output( ["rustup", "install", target_toolchain_fullstring]) - self.checked_command_output( - ["rustup", "target", "add", "thumbv7em-none-eabi"]) + self.checked_command_output( + ["rustup", "target", "add", SUPPORTED_BOARDS[self.args.board].arch]) info("Rust toolchain up-to-date") - def build_and_install_tockos(self): - self.checked_command_output( - ["make", "-C", SUPPORTED_BOARDS[self.args.board], "flash"]) + def build_tockos(self): + info("Building Tock OS for board {}".format(self.args.board)) + props = SUPPORTED_BOARDS[self.args.board] + out_directory = os.path.join(props.path, "target", props.arch, "release") + os.makedirs(out_directory, exist_ok=True) + self.checked_command_output(["make"], cwd=props.path) - def build_and_install_example(self): - assert self.args.application - self.checked_command_output([ - "cargo", "build", "--release", "--target=thumbv7em-none-eabi", - "--features={}".format(",".join(self.args.features)), "--example", - self.args.application - ]) - self.install_elf_file( - os.path.join("target/thumbv7em-none-eabi/release/examples", - self.args.application)) + def build_example(self): + info("Building example {}".format(self.args.application)) + self._build_app_or_example(is_example=True) - def build_and_install_opensk(self): - assert self.args.application + def build_opensk(self): info("Building OpenSK application") - self.checked_command_output([ - "cargo", - "build", - "--release", - "--target=thumbv7em-none-eabi", - "--features={}".format(",".join(self.args.features)), - ]) - self.install_elf_file( - os.path.join("target/thumbv7em-none-eabi/release", - self.args.application)) + self._build_app_or_example(is_example=False) + + def _build_app_or_example(self, is_example): + assert self.args.application + # Ideally we would build a TAB file for all boards at once but depending on + # the chip on the board, the link script could be totally different. + # And elf2tab doesn't seem to let us set the boards a TAB file has been + # created for. So at the moment we only build for the selected board. + props = SUPPORTED_BOARDS[self.args.board] + rust_flags = [ + "-C", + "link-arg=-T{}".format(props.app_ldscript), + "-C", + "relocation-model=static", + "-D", + "warnings", + "--remap-path-prefix={}=".format(os.getcwd()), + ] + env = os.environ.copy() + env["RUSTFLAGS"] = " ".join(rust_flags) + + command = [ + "cargo", "build", "--release", "--target={}".format(props.arch), + "--features={}".format(",".join(self.args.features)) + ] + if is_example: + command.extend(["--example", self.args.application]) + self.checked_command_output(command, env=env) + app_path = os.path.join("target", props.arch, "release") + if is_example: + app_path = os.path.join(app_path, "examples") + app_path = os.path.join(app_path, self.args.application) + # Create a TAB file + self.create_tab_file({props.arch: app_path}) def generate_crypto_materials(self, force_regenerate): has_error = subprocess.call([ @@ -208,8 +358,11 @@ class OpenSKInstaller: error(("Something went wrong while trying to generate ECC " "key and/or certificate for OpenSK")) - def install_elf_file(self, elf_path): + def create_tab_file(self, binaries): + assert binaries assert self.args.application + info("Generating Tock TAB file for application/example {}".format( + self.args.application)) package_parameter = "-n" elf2tab_ver = self.checked_command_output(["elf2tab", "--version"]).split( " ", maxsplit=1)[1] @@ -221,17 +374,26 @@ class OpenSKInstaller: os.makedirs(self.tab_folder, exist_ok=True) tab_filename = os.path.join(self.tab_folder, "{}.tab".format(self.args.application)) - shutil.copyfile(elf_path, self.target_elf_filename) - self.checked_command_output([ - "elf2tab", package_parameter, self.args.application, "-o", tab_filename, - self.target_elf_filename, "--stack={}".format(STACK_SIZE), - "--app-heap={}".format(APP_HEAP_SIZE), "--kernel-heap=1024", - "--protected-region-size=64" + elf2tab_args = [ + "elf2tab", package_parameter, self.args.application, "-o", tab_filename + ] + for arch, app_file in binaries.items(): + dest_file = os.path.join(self.tab_folder, "{}.elf".format(arch)) + shutil.copyfile(app_file, dest_file) + elf2tab_args.append(dest_file) + + elf2tab_args.extend([ + "--stack={}".format(STACK_SIZE), "--app-heap={}".format(APP_HEAP_SIZE), + "--kernel-heap=1024", "--protected-region-size=64" ]) - self.install_padding() + self.checked_command_output(elf2tab_args) + + def install_tab_file(self, tab_filename): + assert self.args.application info("Installing Tock application {}".format(self.args.application)) + board_props = SUPPORTED_BOARDS[self.args.board] args = copy.copy(self.tockloader_default_args) - setattr(args, "app_address", 0x40000) + setattr(args, "app_address", board_props.app_address) setattr(args, "erase", self.args.clear_apps) setattr(args, "make", False) setattr(args, "no_replace", False) @@ -244,16 +406,38 @@ class OpenSKInstaller: fatal("Couldn't install Tock application {}: {}".format( self.args.application, str(e))) - def install_padding(self): + def get_padding(self): fake_header = tbfh.TBFHeader("") fake_header.version = 2 fake_header.fields["header_size"] = 0x10 - fake_header.fields["total_size"] = 0x10000 + fake_header.fields["total_size"] = ( + SUPPORTED_BOARDS[self.args.board].app_address - + SUPPORTED_BOARDS[self.args.board].padding_address) fake_header.fields["flags"] = 0 - padding = fake_header.get_binary() + return fake_header.get_binary() + + def install_tock_os(self): + board_props = SUPPORTED_BOARDS[self.args.board] + kernel_file = os.path.join(board_props.path, "target", board_props.arch, + "release", "{}.bin".format(self.args.board)) + info("Flashing file {}.".format(kernel_file)) + with open(kernel_file, "rb") as f: + kernel = f.read() + args = copy.copy(self.tockloader_default_args) + setattr(args, "address", board_props.app_address) + tock = loader.TockLoader(args) + tock.open(args) + try: + tock.flash_binary(kernel, board_props.kernel_address) + except TockLoaderException as e: + fatal("Couldn't install Tock OS: {}".format(str(e))) + + def install_padding(self): + padding = self.get_padding() + board_props = SUPPORTED_BOARDS[self.args.board] info("Flashing padding application") args = copy.copy(self.tockloader_default_args) - setattr(args, "address", 0x30000) + setattr(args, "address", board_props.padding_address) tock = loader.TockLoader(args) tock.open(args) try: @@ -263,7 +447,8 @@ class OpenSKInstaller: def clear_apps(self): args = copy.copy(self.tockloader_default_args) - setattr(args, "app_address", 0x40000) + board_props = SUPPORTED_BOARDS[self.args.board] + setattr(args, "app_address", board_props.app_address) info("Erasing all installed applications") tock = loader.TockLoader(args) tock.open(args) @@ -271,11 +456,13 @@ class OpenSKInstaller: tock.erase_apps(False) except TockLoaderException as e: # Erasing apps is not critical - info(("A non-critical error occured while erasing " + info(("A non-critical error occurred while erasing " "apps: {}".format(str(e)))) - # pylint: disable=W0212 + # pylint: disable=protected-access def verify_flashed_app(self, expected_app): + if self.args.programmer not in ("jlink", "openocd"): + return False args = copy.copy(self.tockloader_default_args) tock = loader.TockLoader(args) app_found = False @@ -284,51 +471,205 @@ class OpenSKInstaller: app_found = expected_app in apps return app_found + def create_hex_file(self, dest_file): + # We produce an intelhex file with everything in it + # https://en.wikipedia.org/wiki/Intel_HEX + # pylint: disable=g-import-not-at-top,import-outside-toplevel + import intelhex + board_props = SUPPORTED_BOARDS[self.args.board] + final_hex = intelhex.IntelHex() + + if self.args.tockos: + # Process kernel + kernel_path = os.path.join(board_props.path, "target", board_props.arch, + "release", "{}.bin".format(self.args.board)) + with open(kernel_path, "rb") as kernel: + kern_hex = intelhex.IntelHex() + kern_hex.frombytes(kernel.read(), offset=board_props.kernel_address) + final_hex.merge(kern_hex, overlap="error") + + if self.args.application: + # Add padding + if board_props.padding_address: + padding_hex = intelhex.IntelHex() + padding_hex.frombytes( + self.get_padding(), offset=board_props.padding_address) + final_hex.merge(padding_hex, overlap="error") + + # Now we can add the application from the TAB file + app_tab_path = "target/tab/{}.tab".format(self.args.application) + assert os.path.exists(app_tab_path) + app_tab = tab.TAB(app_tab_path) + if board_props.arch not in app_tab.get_supported_architectures(): + fatal(("It seems that the TAB file was not produced for the " + "architecture {}".format(board_props.arch))) + app_hex = intelhex.IntelHex() + app_hex.frombytes( + app_tab.extract_app(board_props.arch).get_binary(), + offset=board_props.app_address) + final_hex.merge(app_hex) + info("Generating all-merged HEX file: {}".format(dest_file)) + final_hex.tofile(dest_file, format="hex") + + def check_prerequisites(self): + if self.args.programmer == "jlink": + assert_mandatory_binary("JLinkExe") + + if self.args.programmer == "openocd": + assert_mandatory_binary("openocd") + + if self.args.programmer == "pyocd": + assert_mandatory_binary("pyocd") + assert_python_library("intelhex") + if not SUPPORTED_BOARDS[self.args.board].pyocd_target: + fatal("This board doesn't seem to support flashing through pyocd.") + + if self.args.programmer == "nordicdfu": + assert_mandatory_binary("nrfutil") + assert_python_library("intelhex") + assert_python_library("nordicsemi.lister") + nrfutil_version = __import__("nordicsemi.version").version.NRFUTIL_VERSION + if not nrfutil_version.startswith("6."): + fatal(("You need to install nrfutil python3 package v6.0 or above. " + "Found: {}".format(nrfutil_version))) + if not SUPPORTED_BOARDS[self.args.board].nordic_dfu: + fatal("This board doesn't support flashing over DFU.") + + if self.args.programmer == "none": + assert_python_library("intelhex") + def run(self): - if self.args.action is None: - # Nothing to do + if self.args.listing == "boards": + print(os.linesep.join(get_supported_boards())) + return 0 + + if self.args.listing == "programmers": + print(os.linesep.join(PROGRAMMERS)) return 0 + if self.args.listing: + # Missing check? + fatal("Listing {} is not implemented.".format(self.args.listing)) + + self.check_prerequisites() self.update_rustc_if_needed() - if self.args.action == "os": - info("Installing Tock on board {}".format(self.args.board)) - self.build_and_install_tockos() + if not self.args.tockos and not self.args.application: + info("Nothing to do.") return 0 - if self.args.action == "app": - if self.args.application is None: - fatal("Unspecified application") + # Compile what needs to be compiled + if self.args.tockos: + self.build_tockos() + + if self.args.application == "ctap2": + self.generate_crypto_materials(self.args.regenerate_keys) + self.build_opensk() + elif self.args.application is None: + info("No application selected.") + else: + self.build_example() + + # Flashing + board_props = SUPPORTED_BOARDS[self.args.board] + if self.args.programmer in ("jlink", "openocd"): + # We rely on Tockloader to do the job if self.args.clear_apps: self.clear_apps() - if self.args.application == "ctap2": - self.generate_crypto_materials(self.args.regenerate_keys) - self.build_and_install_opensk() - else: - self.build_and_install_example() - if self.verify_flashed_app(self.args.application): - info("You're all set!") - return 0 - error(("It seems that something went wrong. " - "App/example not found on your board.")) - return 1 + if self.args.tockos: + # Install Tock OS + self.install_tock_os() + # Install padding and application if needed + if self.args.application: + self.install_padding() + self.install_tab_file("target/tab/{}.tab".format(self.args.application)) + if self.verify_flashed_app(self.args.application): + info("You're all set!") + return 0 + error( + ("It seems that something went wrong. App/example not found " + "on your board. Ensure the connections between the programmer and " + "the board are correct.")) + return 1 + return 0 + + if self.args.programmer in ("pyocd", "nordicdfu", "none"): + dest_file = "target/{}_merged.hex".format(self.args.board) + os.makedirs("target", exist_ok=True) + self.create_hex_file(dest_file) + + if self.args.programmer == "pyocd": + info("Flashing HEX file") + self.checked_command_output([ + "pyocd", "flash", "--target={}".format(board_props.pyocd_target), + "--format=hex", "--erase=auto", dest_file + ]) + if self.args.programmer == "nordicdfu": + info("Creating DFU package") + dfu_pkg_file = "target/{}_dfu.zip".format(self.args.board) + self.checked_command_output([ + "nrfutil", "pkg", "generate", "--hw-version=52", "--sd-req=0", + "--application-version=1", "--application={}".format(dest_file), + dfu_pkg_file + ]) + info( + "Please insert the dongle and switch it to DFU mode by keeping the " + "button pressed while inserting...") + info("Press [ENTER] when ready.") + _ = input() + # Search for the DFU devices + serial_number = [] + # pylint: disable=g-import-not-at-top,import-outside-toplevel + from nordicsemi.lister import device_lister + for device in device_lister.DeviceLister().enumerate(): + if device.vendor_id == "1915" and device.product_id == "521F": + serial_number.append(device.serial_number) + if not serial_number: + fatal("Couldn't find any DFU device on your system.") + if len(serial_number) > 1: + fatal("Multiple DFU devices are detected. Please only connect one.") + # Run the command without capturing stdout so that we show progress + info("Flashing device using DFU...") + return subprocess.run( + [ + "nrfutil", "dfu", "usb-serial", + "--package={}".format(dfu_pkg_file), + "--serial-number={}".format(serial_number[0]) + ], + check=False, + timeout=None, + ).returncode return 0 def main(args): # Make sure the current working directory is the right one before running os.chdir(os.path.realpath(os.path.dirname(__file__))) - # Check for pre-requisite executable files. - if not shutil.which("JLinkExe"): - fatal(("Couldn't find JLinkExe binary. Make sure Segger JLink tools " - "are installed and correctly set up.")) OpenSKInstaller(args).run() if __name__ == "__main__": - shared_parser = argparse.ArgumentParser(add_help=False) - shared_parser.add_argument( + main_parser = argparse.ArgumentParser() + action_group = main_parser.add_mutually_exclusive_group(required=True) + action_group.add_argument( + "--list", + metavar="WHAT", + choices=("boards", "programmers"), + default=None, + dest="listing", + help=("List supported boards or programmers, 1 per line and then exit."), + ) + action_group.add_argument( + "--board", + metavar="BOARD_NAME", + dest="board", + default=None, + choices=get_supported_boards(), + help="Indicates which board Tock OS will be compiled for.", + ) + + main_parser.add_argument( "--dont-clear-apps", action="store_false", default=True, @@ -336,32 +677,26 @@ if __name__ == "__main__": help=("When installing an application, previously installed " "applications won't be erased from the board."), ) + main_parser.add_argument( + "--programmer", + metavar="METHOD", + dest="programmer", + choices=PROGRAMMERS, + default="jlink", + help=("Sets the method to be used to flash Tock OS or the application " + "on the target board."), + ) - main_parser = argparse.ArgumentParser() - commands = main_parser.add_subparsers( - dest="action", - help=("Indicates which part of the firmware should be compiled and " - "flashed to the connected board.")) - - os_commands = commands.add_parser( - "os", - parents=[shared_parser], - help=("Compiles and installs Tock OS. The target board must be " - "specified by setting the --board argument."), + main_parser.add_argument( + "--no-tockos", + action="store_false", + default=True, + dest="tockos", + help=("Only compiles and flash the application/example. " + "Otherwise TockOS will also be bundled and flashed."), ) - os_commands.add_argument( - "--board", - metavar="BOARD_NAME", - dest="board", - choices=get_supported_boards(), - help="Indicates which board Tock OS will be compiled for.", - required=True) - app_commands = commands.add_parser( - "app", - parents=[shared_parser], - help="compiles and installs an application.") - app_commands.add_argument( + main_parser.add_argument( "--panic-console", action="append_const", const="panic_console", @@ -370,7 +705,16 @@ if __name__ == "__main__": "output messages before starting blinking the LEDs on the " "board."), ) - app_commands.add_argument( + main_parser.add_argument( + "--debug", + action="append_const", + const="debug_ctap", + dest="features", + help=("Compiles and installs the OpenSK application in debug mode " + "(i.e. more debug messages will be sent over the console port " + "such as hexdumps of packets)."), + ) + main_parser.add_argument( "--debug-allocations", action="append_const", const="debug_allocations", @@ -378,7 +722,15 @@ if __name__ == "__main__": help=("The console will be used to output allocator statistics every " "time an allocation/deallocation happens."), ) - app_commands.add_argument( + main_parser.add_argument( + "--verbose", + action="append_const", + const="verbose", + dest="features", + help=("The console will be used to output verbose information about the " + "OpenSK application. This also automatically activates --debug."), + ) + main_parser.add_argument( "--no-u2f", action=RemoveConstAction, const="with_ctap1", @@ -386,7 +738,7 @@ if __name__ == "__main__": help=("Compiles the OpenSK application without backward compatible " "support for U2F/CTAP1 protocol."), ) - app_commands.add_argument( + main_parser.add_argument( "--regen-keys", action="store_true", default=False, @@ -396,16 +748,7 @@ if __name__ == "__main__": "This is useful to allow flashing multiple OpenSK authenticators " "in a row without them being considered clones."), ) - app_commands.add_argument( - "--debug", - action="append_const", - const="debug_ctap", - dest="features", - help=("Compiles and installs the OpenSK application in debug mode " - "(i.e. more debug messages will be sent over the console port " - "such as hexdumps of packets)."), - ) - app_commands.add_argument( + main_parser.add_argument( "--no-persistent-storage", action="append_const", const="ram_storage", @@ -413,7 +756,15 @@ if __name__ == "__main__": help=("Compiles and installs the OpenSK application without persistent " "storage (i.e. unplugging the key will reset the key)."), ) - apps_group = app_commands.add_mutually_exclusive_group() + + apps_group = main_parser.add_mutually_exclusive_group(required=True) + apps_group.add_argument( + "--no-app", + dest="application", + action="store_const", + const=None, + help=("Doesn't compile nor install any application. Useful when you only " + "want to update Tock OS kernel.")) apps_group.add_argument( "--opensk", dest="application", @@ -428,6 +779,6 @@ if __name__ == "__main__": help=("Compiles and installs the crypto_bench example that tests " "the performance of the cryptographic algorithms on the board.")) - app_commands.set_defaults(features=["with_ctap1"]) + main_parser.set_defaults(features=["with_ctap1"]) main(main_parser.parse_args()) diff --git a/docs/install.md b/docs/install.md index 11a9147bd1fb0f34a60279846ede1abee0c73f12..5b5763b1b83ee2c58da2c1006a1d581e6c1daca7 100644 --- a/docs/install.md +++ b/docs/install.md @@ -16,8 +16,9 @@ You will need one the following supported boards: scenarios as the JTAG probe is already on the board. * [Nordic nRF52840 Dongle](https://www.nordicsemi.com/Software-and-tools/Development-Kits/nRF52840-Dongle) to have a more practical form factor. +* [Makerdiary nRF52840-MDK USB dongle](https://wiki.makerdiary.com/nrf52840-mdk/). -In the case of the Nordic USB dongle, you will also need the following extra +In the case of the Nordic USB dongle, you may also need the following extra hardware: * a [Segger J-Link](https://www.segger.com/products/debug-probes/j-link/) JTAG @@ -30,13 +31,18 @@ hardware: [Tag-Connect TC2050 retainer clip](http://www.tag-connect.com/TC2050-CLIP) to keep the spring loaded connector pressed to the PCB. -Although [OpenOCD](http://openocd.org/) should be supported we encountered some -issues while trying to flash a firmware with it. Therefore we suggest at the -moment to use a -[Segger J-Link](https://www.segger.com/products/debug-probes/j-link/) probe -instead. +Additionnaly, OpenSK supports other ways to flash your board: -This guide **does not** cover how to setup the JTAG probe on your system. +* [OpenOCD](http://openocd.org/). +* [Segger J-Link](https://www.segger.com/products/debug-probes/j-link/) + (default method). +* [pyOCD](https://pypi.org/project/pyocd/). +* [nrfutil](https://pypi.org/project/nrfutil/) for the USB dongle boards that + supports it, which allows you to directly flash a working board over USB + without additional hardware. + +This guide **does not** cover how to setup the JTAG probe and their related +tools on your system. ### Software @@ -141,17 +147,17 @@ Our build script `build.rs` is responsible for converting `opensk_cert.pem` and 1. Connect a micro USB cable to the JTAG USB port. -1. Run our script for compiling/flashing Tock OS on your device (_output may - differ_): +1. Run our script for compiling/flashing Tock OS and OpenSK on your device + (_output may differ_): ```shell - $ ./deploy.py os --board=nrf52840_dk + $ ./deploy.py --board=nrf52840dk --opensk info: Updating rust toolchain to nightly-2020-02-03 info: syncing channel updates for 'nightly-2020-02-03-x86_64-unknown-linux-gnu' info: checking for self-updates info: component 'rust-std' for target 'thumbv7em-none-eabi' is up to date info: Rust toolchain up-to-date - info: Installing Tock on board nrf52840_dk + info: Building Tock OS for board nrf52840dk Compiling tock-registers v0.5.0 (./third_party/tock/libraries/tock-register-interface) Compiling tock-cells v0.1.0 (./third_party/tock/libraries/tock-cells) Compiling enum_primitive v0.1.0 (./third_party/tock/libraries/enum_primitive) @@ -166,47 +172,17 @@ Our build script `build.rs` is responsible for converting `opensk_cert.pem` and Compiling nrf52840 v0.1.0 (./third_party/tock/chips/nrf52840) Compiling components v0.1.0 (./third_party/tock/boards/components) Compiling nrf52dk_base v0.1.0 (./third_party/tock/boards/nordic/nrf52dk_base) - Finished release [optimized + debuginfo] target(s) in 11.97s - [STATUS ] Flashing binar(y|ies) to board... - [INFO ] Using known arch and jtag-device for known board nrf52dk - [INFO ] Finished in 0.284 seconds - ``` - -1. Run our script for compiling/flashing the OpenSK application on your device - (_output may differ_): - - ```shell - $ ./deploy.py app --opensk - info: Updating rust toolchain to nightly-2020-02-03 - info: syncing channel updates for 'nightly-2020-02-03-x86_64-unknown-linux-gnu' - info: checking for self-updates - info: component 'rust-std' for target 'thumbv7em-none-eabi' is up to date - info: Rust toolchain up-to-date - info: Erasing all installed applications - All apps have been erased. - info: Building OpenSK application - Compiling autocfg v1.0.0 - Compiling pkg-config v0.3.17 - Compiling cc v1.0.50 - Compiling libc v0.2.66 - Compiling bitflags v1.2.1 - Compiling foreign-types-shared v0.1.1 - Compiling openssl v0.10.28 - Compiling cfg-if v0.1.10 - Compiling lazy_static v1.4.0 - Compiling byteorder v1.3.2 - Compiling linked_list_allocator v0.6.6 - Compiling arrayref v0.3.6 - Compiling cbor v0.1.0 (./libraries/cbor) - Compiling subtle v2.2.2 - Compiling foreign-types v0.3.2 - Compiling libtock v0.1.0 (./third_party/libtock-rs) - Compiling crypto v0.1.0 (./libraries/crypto) - Compiling openssl-sys v0.9.54 - Compiling ctap2 v0.1.0 (.) - Finished release [optimized] target(s) in 15.34s - info: Flashing padding application - info: Installing Tock application ctap2 + Finished release [optimized + debuginfo] target(s) in 13.15s + info: Converting Tock OS file into a binary + info: Building OpenSK application + Finished release [optimized] target(s) in 0.02s + info: Generating Tock TAB file for application/example ctap2 + info: Erasing all installed applications + All apps have been erased. + info: Flashing file third_party/tock/boards/nordic/nrf52840dk/target/thumbv7em-none-eabi/release/nrf52840dk.bin. + info: Flashing padding application + info: Installing Tock application ctap2 + info: You're all set! ``` 1. Connect a micro USB cable to the device USB port. @@ -217,6 +193,8 @@ the board in order to see your OpenSK device on your system. #### Nordic nRF52840 Dongle +##### Using external programmer (JLink, OpenOCD, etc.) +  1. The JTAG probe used for programming won't provide power to the board. @@ -232,17 +210,18 @@ the board in order to see your OpenSK device on your system.  -1. Run our script for compiling/flashing Tock OS on your device (_output may - differ_): +1. Depending on the programmer you're using, you may have to adapt the next + command line. Run our script for compiling/flashing Tock OS on your device + (_output may differ_): ```shell - $ ./deploy.py os --board=nrf52840_dongle + $ ./deploy.py os --board=nrf52840_dongle --programmer=jlink info: Updating rust toolchain to nightly-2020-02-03 info: syncing channel updates for 'nightly-2020-02-03-x86_64-unknown-linux-gnu' info: checking for self-updates info: component 'rust-std' for target 'thumbv7em-none-eabi' is up to date info: Rust toolchain up-to-date - info: Installing Tock on board nrf52840_dongle + info: Building Tock OS for board nrf52840_dongle Compiling tock-cells v0.1.0 (./third_party/tock/libraries/tock-cells) Compiling tock-registers v0.5.0 (./third_party/tock/libraries/tock-register-interface) Compiling enum_primitive v0.1.0 (./third_party/tock/libraries/enum_primitive) @@ -258,50 +237,39 @@ the board in order to see your OpenSK device on your system. Compiling components v0.1.0 (./third_party/tock/boards/components) Compiling nrf52dk_base v0.1.0 (./third_party/tock/boards/nordic/nrf52dk_base) Finished release [optimized + debuginfo] target(s) in 11.72s - [STATUS ] Flashing binar(y|ies) to board... - [INFO ] Using known arch and jtag-device for known board nrf52dk - [INFO ] Finished in 0.280 seconds - ``` - -1. Run our script for compiling/flashing the OpenSK application on your device - (_output may differ_): - - ```shell - $ ./deploy.py app --opensk - info: Updating rust toolchain to nightly-2020-02-03 - info: syncing channel updates for 'nightly-2020-02-03-x86_64-unknown-linux-gnu' - info: checking for self-updates - info: component 'rust-std' for target 'thumbv7em-none-eabi' is up to date - info: Rust toolchain up-to-date + info: Converting Tock OS file into a binary + info: Building OpenSK application + Finished release [optimized] target(s) in 0.02s + info: Generating Tock TAB file for application/example ctap2 info: Erasing all installed applications All apps have been erased. - info: Building OpenSK application - Compiling autocfg v1.0.0 - Compiling pkg-config v0.3.17 - Compiling cc v1.0.50 - Compiling libc v0.2.66 - Compiling bitflags v1.2.1 - Compiling foreign-types-shared v0.1.1 - Compiling openssl v0.10.28 - Compiling cfg-if v0.1.10 - Compiling lazy_static v1.4.0 - Compiling byteorder v1.3.2 - Compiling linked_list_allocator v0.6.6 - Compiling arrayref v0.3.6 - Compiling cbor v0.1.0 (./libraries/cbor) - Compiling subtle v2.2.2 - Compiling foreign-types v0.3.2 - Compiling libtock v0.1.0 (./third_party/libtock-rs) - Compiling crypto v0.1.0 (./libraries/crypto) - Compiling openssl-sys v0.9.54 - Compiling ctap2 v0.1.0 (.) - Finished release [optimized] target(s) in 15.34s + info: Flashing file third_party/tock/boards/nordic/nrf52840_dongle/target/thumbv7em-none-eabi/release/nrf52840_dongle.bin. info: Flashing padding application info: Installing Tock application ctap2 + info: You're all set! ``` 1. Remove the programming cable and the USB-A extension cable. +### Advanced installation + +Although flashing using a Segger JLink probe is the officially supported way, +our tool, `deploy.py` also supports other methods: + +* OpenOCD: `./deploy.py --board=nrf52840_dongle --opensk --programmer=openocd` +* pyOCD: `./deploy.py --board=nrf52840_dongle --opensk --programmer=pyocd` +* Nordic DFU: `./deploy.py --board=nrf52840_dongle --opensk + --programmer=nordicdfu` +* Custom: `./deploy.py --board=nrf52840_dongle --opensk --programmer=none`. In + this case, an IntelHex file will be created and how to program a board is + left to the user. + +If your board is already flashed with Tock OS, you may skip installing it: +`./deploy.py --board=nrf52840dk --opensk --no-tockos` + +For more options, we invite you to read the help of our `deploy.py` script by +running `./deploy.py --help`. + ### Installing the udev rule (Linux only) By default on Linux, a USB device will require root privilege in order interact diff --git a/patches/tock/01-persistent-storage.patch b/patches/tock/01-persistent-storage.patch index ecfb0b0b0f01847f7735d9b3a0d015d137d81242..57880c60be52d38ef8f9b4ecbc5cb07105850aac 100644 --- a/patches/tock/01-persistent-storage.patch +++ b/patches/tock/01-persistent-storage.patch @@ -286,10 +286,10 @@ index 5abd2d84..5a726fdb 100644 + } +} diff --git a/kernel/src/callback.rs b/kernel/src/callback.rs -index ece4a443..9a1afc84 100644 +index c812e0bf..bd1613b3 100644 --- a/kernel/src/callback.rs +++ b/kernel/src/callback.rs -@@ -52,6 +52,31 @@ impl AppId { +@@ -130,6 +130,31 @@ impl AppId { (start, end) }) } diff --git a/patches/tock/02-usb.patch b/patches/tock/02-usb.patch index a75a5b353492d9d67dca5ec2d606f5c64ea420a1..55f807bbe3afab867bb1b8fea03293d05bec97be 100644 --- a/patches/tock/02-usb.patch +++ b/patches/tock/02-usb.patch @@ -124,11 +124,11 @@ index 105f7120..535e5cd8 100644 ipc: kernel::ipc::IPC::new(board_kernel, &memory_allocation_capability), nvmc: nvmc, diff --git a/capsules/src/driver.rs b/capsules/src/driver.rs -index 9305e6a7..40466f44 100644 +index bfc06429..5858d352 100644 --- a/capsules/src/driver.rs +++ b/capsules/src/driver.rs -@@ -24,6 +24,7 @@ pub enum NUM { - Spi = 0x20001, +@@ -25,6 +25,7 @@ pub enum NUM { + I2cMaster = 0x20003, UsbUser = 0x20005, I2cMasterSlave = 0x20006, + UsbCtap = 0x20009, @@ -509,10 +509,10 @@ index 00000000..da3d16d8 +} diff --git a/capsules/src/usb/usbc_ctap_hid.rs b/capsules/src/usb/usbc_ctap_hid.rs new file mode 100644 -index 00000000..fdf7263a +index 00000000..4b1916cf --- /dev/null +++ b/capsules/src/usb/usbc_ctap_hid.rs -@@ -0,0 +1,352 @@ +@@ -0,0 +1,359 @@ +//! A USB HID client of the USB hardware interface + +use super::descriptors::Buffer64; @@ -603,8 +603,9 @@ index 00000000..fdf7263a +pub struct ClientCtapHID<'a, 'b, C: 'a> { + client_ctrl: ClientCtrl<'a, 'static, C>, + -+ // A 64-byte buffer for the endpoint -+ buffer: Buffer64, ++ // 64-byte buffers for the endpoint ++ in_buffer: Buffer64, ++ out_buffer: Buffer64, + + // Interaction with the client + client: OptionalCell<&'b dyn CtapUsbClient>, @@ -648,7 +649,8 @@ index 00000000..fdf7263a + LANGUAGES, + STRINGS, + ), -+ buffer: Default::default(), ++ in_buffer: Default::default(), ++ out_buffer: Default::default(), + client: OptionalCell::empty(), + tx_packet: OptionalCell::empty(), + pending_in: Cell::new(false), @@ -702,7 +704,7 @@ index 00000000..fdf7263a + fn send_packet_to_client(&'a self) -> bool { + // Copy the packet into a buffer to send to the client. + let mut buf: [u8; 64] = [0; 64]; -+ for (i, x) in self.buffer.buf.iter().enumerate() { ++ for (i, x) in self.out_buffer.buf.iter().enumerate() { + buf[i] = x.get(); + } + @@ -735,11 +737,7 @@ index 00000000..fdf7263a + + fn cancel_in_transaction(&'a self) -> bool { + self.tx_packet.take(); -+ let result = self.pending_in.take(); -+ if result { -+ self.controller().endpoint_cancel_in(1); -+ } -+ result ++ self.pending_in.take() + } + + fn cancel_out_transaction(&'a self) -> bool { @@ -758,7 +756,10 @@ index 00000000..fdf7263a + self.client_ctrl.enable(); + + // Set up the interrupt in-out endpoint -+ self.controller().endpoint_set_buffer(1, &self.buffer.buf); ++ self.controller() ++ .endpoint_set_in_buffer(1, &self.in_buffer.buf); ++ self.controller() ++ .endpoint_set_out_buffer(1, &self.out_buffer.buf); + self.controller() + .endpoint_in_out_enable(TransferType::Interrupt, 1); + } @@ -808,7 +809,7 @@ index 00000000..fdf7263a + } + + if let Some(packet) = self.tx_packet.take() { -+ let buf = &self.buffer.buf; ++ let buf = &self.in_buffer.buf; + for i in 0..64 { + buf[i].set(packet[i]); + } @@ -861,149 +862,13 @@ index 00000000..fdf7263a + panic!("Unexpected tx_packet while a packet was being transmitted."); + } + self.pending_in.set(false); ++ ++ // Clear any pending packet on the receiving side. ++ // It's up to the client to handle the transmitted packet and decide if they want to ++ // receive another packet. ++ self.cancel_out_transaction(); ++ + // Notify the client + self.client.map(|client| client.packet_transmitted()); + } +} -diff --git a/chips/nrf52/src/usbd.rs b/chips/nrf52/src/usbd.rs -index 8ddb5895..8c1992cc 100644 ---- a/chips/nrf52/src/usbd.rs -+++ b/chips/nrf52/src/usbd.rs -@@ -1499,7 +1499,23 @@ impl<'a> Usbd<'a> { - if epdatastatus.is_set(status_epin(endpoint)) { - let (transfer_type, direction, state) = - self.descriptors[endpoint].state.get().bulk_state(); -- assert_eq!(state, BulkState::InData); -+ match state { -+ BulkState::InData => { -+ // Totally expected state. Nothing to do. -+ } -+ BulkState::Init => { -+ internal_warn!( -+ "Received a stale epdata IN in an unexpected state: {:?}", -+ state -+ ); -+ } -+ BulkState::OutDelay -+ | BulkState::OutData -+ | BulkState::OutDma -+ | BulkState::InDma => { -+ internal_err!("Unexpected state: {:?}", state); -+ } -+ } - self.descriptors[endpoint].state.set(EndpointState::Bulk( - transfer_type, - direction, -@@ -1677,7 +1693,7 @@ impl<'a> Usbd<'a> { - } - - fn transmit_in(&self, endpoint: usize) { -- debug_info!("transmit_in({})", endpoint); -+ debug_events!("transmit_in({})", endpoint); - let regs = &*self.registers; - - self.client.map(|client| { -@@ -1717,7 +1733,7 @@ impl<'a> Usbd<'a> { - } - - fn transmit_out(&self, endpoint: usize) { -- debug_info!("transmit_out({})", endpoint); -+ debug_events!("transmit_out({})", endpoint); - - let (transfer_type, direction, state) = self.descriptors[endpoint].state.get().bulk_state(); - // Starting the DMA can only happen in the OutData state, i.e. after an EPDATA event. -@@ -1882,11 +1898,13 @@ impl<'a> hil::usb::UsbController<'a> for Usbd<'a> { - } - - fn endpoint_resume_in(&self, endpoint: usize) { -+ debug_events!("endpoint_resume_in({})", endpoint); -+ - let (_, direction, _) = self.descriptors[endpoint].state.get().bulk_state(); - assert!(direction.has_in()); - - if self.dma_pending.get() { -- debug_info!("requesting resume_in[{}]", endpoint); -+ debug_events!("requesting resume_in[{}]", endpoint); - // A DMA is already pending. Schedule the resume for later. - self.descriptors[endpoint].request_transmit_in.set(true); - } else { -@@ -1896,6 +1914,8 @@ impl<'a> hil::usb::UsbController<'a> for Usbd<'a> { - } - - fn endpoint_resume_out(&self, endpoint: usize) { -+ debug_events!("endpoint_resume_out({})", endpoint); -+ - let (transfer_type, direction, state) = self.descriptors[endpoint].state.get().bulk_state(); - assert!(direction.has_out()); - -@@ -1914,7 +1934,7 @@ impl<'a> hil::usb::UsbController<'a> for Usbd<'a> { - // happened in the meantime. This pending transaction will now - // continue in transmit_out(). - if self.dma_pending.get() { -- debug_info!("requesting resume_out[{}]", endpoint); -+ debug_events!("requesting resume_out[{}]", endpoint); - // A DMA is already pending. Schedule the resume for later. - self.descriptors[endpoint].request_transmit_out.set(true); - } else { -@@ -1927,6 +1947,20 @@ impl<'a> hil::usb::UsbController<'a> for Usbd<'a> { - } - } - } -+ -+ fn endpoint_cancel_in(&self, endpoint: usize) { -+ debug_events!("endpoint_cancel_in({})", endpoint); -+ -+ let (transfer_type, direction, state) = self.descriptors[endpoint].state.get().bulk_state(); -+ assert!(direction.has_in()); -+ assert_eq!(state, BulkState::InData); -+ -+ self.descriptors[endpoint].state.set(EndpointState::Bulk( -+ transfer_type, -+ direction, -+ BulkState::Init, -+ )); -+ } - } - - fn status_epin(ep: usize) -> Field<u32, EndpointStatus::Register> { -diff --git a/chips/nrf52840/src/lib.rs b/chips/nrf52840/src/lib.rs -index 9d58a705..942d0288 100644 ---- a/chips/nrf52840/src/lib.rs -+++ b/chips/nrf52840/src/lib.rs -@@ -2,7 +2,7 @@ - - pub use nrf52::{ - acomp, adc, aes, ble_radio, clock, constants, crt1, ficr, i2c, ieee802154_radio, init, nvmc, -- pinmux, ppi, pwm, rtc, spi, temperature, timer, trng, uart, uicr, -+ pinmux, ppi, pwm, rtc, spi, temperature, timer, trng, uart, uicr, usbd, - }; - pub mod chip; - pub mod gpio; -diff --git a/chips/sam4l/src/usbc/mod.rs b/chips/sam4l/src/usbc/mod.rs -index 35f3bb7c..28a0b9f9 100644 ---- a/chips/sam4l/src/usbc/mod.rs -+++ b/chips/sam4l/src/usbc/mod.rs -@@ -1547,6 +1547,10 @@ impl hil::usb::UsbController<'a> for Usbc<'a> { - requests.resume_out = true; - self.requests[endpoint].set(requests); - } -+ -+ fn endpoint_cancel_in(&self, _endpoint: usize) { -+ unimplemented!() -+ } - } - - /// Static state to manage the USBC -diff --git a/kernel/src/hil/usb.rs b/kernel/src/hil/usb.rs -index 846f5e93..64610fa5 100644 ---- a/kernel/src/hil/usb.rs -+++ b/kernel/src/hil/usb.rs -@@ -27,6 +27,8 @@ pub trait UsbController<'a> { - fn endpoint_resume_in(&self, endpoint: usize); - - fn endpoint_resume_out(&self, endpoint: usize); -+ -+ fn endpoint_cancel_in(&self, endpoint: usize); - } - - #[derive(Clone, Copy, Debug)] diff --git a/patches/tock/04-increase-rom-nordic.patch b/patches/tock/04-increase-rom-nordic.patch deleted file mode 100644 index 83948f80007be9fd12cd351ff27cfe5c50182c26..0000000000000000000000000000000000000000 --- a/patches/tock/04-increase-rom-nordic.patch +++ /dev/null @@ -1,24 +0,0 @@ -diff --git a/boards/nordic/nrf52840_dongle/layout.ld b/boards/nordic/nrf52840_dongle/layout.ld -index 657b0d26..f86b2321 100644 ---- a/boards/nordic/nrf52840_dongle/layout.ld -+++ b/boards/nordic/nrf52840_dongle/layout.ld -@@ -1,6 +1,6 @@ - MEMORY - { -- rom (rx) : ORIGIN = 0x00000000, LENGTH = 128K -+ rom (rx) : ORIGIN = 0x00000000, LENGTH = 192K - prog (rx) : ORIGIN = 0x00030000, LENGTH = 832K - ram (rwx) : ORIGIN = 0x20000000, LENGTH = 256K - } -diff --git a/boards/nordic/nrf52840dk/layout.ld b/boards/nordic/nrf52840dk/layout.ld -index 657b0d26..f86b2321 100644 ---- a/boards/nordic/nrf52840dk/layout.ld -+++ b/boards/nordic/nrf52840dk/layout.ld -@@ -1,6 +1,6 @@ - MEMORY - { -- rom (rx) : ORIGIN = 0x00000000, LENGTH = 128K -+ rom (rx) : ORIGIN = 0x00000000, LENGTH = 192K - prog (rx) : ORIGIN = 0x00030000, LENGTH = 832K - ram (rwx) : ORIGIN = 0x20000000, LENGTH = 256K - } diff --git a/patches/tock/04-nrf52-bootloader.patch b/patches/tock/04-nrf52-bootloader.patch new file mode 100644 index 0000000000000000000000000000000000000000..02f57be6552ce9ca2b09c1c98475015db6c08494 --- /dev/null +++ b/patches/tock/04-nrf52-bootloader.patch @@ -0,0 +1,21 @@ +diff --git a/chips/nrf52/src/crt1.rs b/chips/nrf52/src/crt1.rs +index 9703aac..281ceeb 100644 +--- a/chips/nrf52/src/crt1.rs ++++ b/chips/nrf52/src/crt1.rs +@@ -1,4 +1,4 @@ +-use cortexm4::{generic_isr, hard_fault_handler, nvic, svc_handler, systick_handler}; ++use cortexm4::{generic_isr, hard_fault_handler, nvic, scb, svc_handler, systick_handler}; + use tock_rt0; + + /* +@@ -168,5 +168,9 @@ pub unsafe extern "C" fn init() { + tock_rt0::init_data(&mut _etext, &mut _srelocate, &mut _erelocate); + tock_rt0::zero_bss(&mut _szero, &mut _ezero); + ++ // Ensure that we are compatible with a bootloader. ++ // For this we need to offset our vector table ++ scb::set_vector_table_offset(BASE_VECTORS.as_ptr() as *const ()); ++ + nvic::enable_all(); + } + diff --git a/patches/tock/05-usb-cancel.patch b/patches/tock/05-usb-cancel.patch deleted file mode 100644 index f853971866090e083c97dd80b9bdfbc1e420e711..0000000000000000000000000000000000000000 --- a/patches/tock/05-usb-cancel.patch +++ /dev/null @@ -1,747 +0,0 @@ -diff --git a/capsules/src/usb/usbc_client.rs b/capsules/src/usb/usbc_client.rs -index b0678c23..9fb43781 100644 ---- a/capsules/src/usb/usbc_client.rs -+++ b/capsules/src/usb/usbc_client.rs -@@ -115,11 +115,11 @@ impl<'a, C: hil::usb::UsbController<'a>> hil::usb::Client<'a> for Client<'a, C> - self.client_ctrl.enable(); - - // Set up a bulk-in endpoint for debugging -- self.controller().endpoint_set_buffer(1, self.buffer(1)); -+ self.controller().endpoint_set_in_buffer(1, self.buffer(1)); - self.controller().endpoint_in_enable(TransferType::Bulk, 1); - - // Set up a bulk-out endpoint for debugging -- self.controller().endpoint_set_buffer(2, self.buffer(2)); -+ self.controller().endpoint_set_out_buffer(2, self.buffer(2)); - self.controller().endpoint_out_enable(TransferType::Bulk, 2); - } - -diff --git a/capsules/src/usb/usbc_client_ctrl.rs b/capsules/src/usb/usbc_client_ctrl.rs -index 2aaca0cc..5f9b253c 100644 ---- a/capsules/src/usb/usbc_client_ctrl.rs -+++ b/capsules/src/usb/usbc_client_ctrl.rs -@@ -201,7 +201,7 @@ impl<'a, 'b, C: hil::usb::UsbController<'a>> ClientCtrl<'a, 'b, C> { - pub fn enable(&'a self) { - // Set up the default control endpoint - self.controller -- .endpoint_set_buffer(0, &self.ctrl_buffer.buf); -+ .endpoint_set_ctrl_buffer(&self.ctrl_buffer.buf); - self.controller - .enable_as_device(hil::usb::DeviceSpeed::Full); // must be Full for Bulk transfers - self.controller -diff --git a/capsules/src/usb/usbc_ctap_hid.rs b/capsules/src/usb/usbc_ctap_hid.rs -index fdf7263a..4b1916cf 100644 ---- a/capsules/src/usb/usbc_ctap_hid.rs -+++ b/capsules/src/usb/usbc_ctap_hid.rs -@@ -88,8 +88,9 @@ static HID: HIDDescriptor<'static> = HIDDescriptor { - pub struct ClientCtapHID<'a, 'b, C: 'a> { - client_ctrl: ClientCtrl<'a, 'static, C>, - -- // A 64-byte buffer for the endpoint -- buffer: Buffer64, -+ // 64-byte buffers for the endpoint -+ in_buffer: Buffer64, -+ out_buffer: Buffer64, - - // Interaction with the client - client: OptionalCell<&'b dyn CtapUsbClient>, -@@ -133,7 +134,8 @@ impl<'a, 'b, C: hil::usb::UsbController<'a>> ClientCtapHID<'a, 'b, C> { - LANGUAGES, - STRINGS, - ), -- buffer: Default::default(), -+ in_buffer: Default::default(), -+ out_buffer: Default::default(), - client: OptionalCell::empty(), - tx_packet: OptionalCell::empty(), - pending_in: Cell::new(false), -@@ -187,7 +189,7 @@ impl<'a, 'b, C: hil::usb::UsbController<'a>> ClientCtapHID<'a, 'b, C> { - fn send_packet_to_client(&'a self) -> bool { - // Copy the packet into a buffer to send to the client. - let mut buf: [u8; 64] = [0; 64]; -- for (i, x) in self.buffer.buf.iter().enumerate() { -+ for (i, x) in self.out_buffer.buf.iter().enumerate() { - buf[i] = x.get(); - } - -@@ -220,11 +222,7 @@ impl<'a, 'b, C: hil::usb::UsbController<'a>> ClientCtapHID<'a, 'b, C> { - - fn cancel_in_transaction(&'a self) -> bool { - self.tx_packet.take(); -- let result = self.pending_in.take(); -- if result { -- self.controller().endpoint_cancel_in(1); -- } -- result -+ self.pending_in.take() - } - - fn cancel_out_transaction(&'a self) -> bool { -@@ -243,7 +241,10 @@ impl<'a, 'b, C: hil::usb::UsbController<'a>> hil::usb::Client<'a> for ClientCtap - self.client_ctrl.enable(); - - // Set up the interrupt in-out endpoint -- self.controller().endpoint_set_buffer(1, &self.buffer.buf); -+ self.controller() -+ .endpoint_set_in_buffer(1, &self.in_buffer.buf); -+ self.controller() -+ .endpoint_set_out_buffer(1, &self.out_buffer.buf); - self.controller() - .endpoint_in_out_enable(TransferType::Interrupt, 1); - } -@@ -293,7 +294,7 @@ impl<'a, 'b, C: hil::usb::UsbController<'a>> hil::usb::Client<'a> for ClientCtap - } - - if let Some(packet) = self.tx_packet.take() { -- let buf = &self.buffer.buf; -+ let buf = &self.in_buffer.buf; - for i in 0..64 { - buf[i].set(packet[i]); - } -@@ -346,6 +347,12 @@ impl<'a, 'b, C: hil::usb::UsbController<'a>> hil::usb::Client<'a> for ClientCtap - panic!("Unexpected tx_packet while a packet was being transmitted."); - } - self.pending_in.set(false); -+ -+ // Clear any pending packet on the receiving side. -+ // It's up to the client to handle the transmitted packet and decide if they want to -+ // receive another packet. -+ self.cancel_out_transaction(); -+ - // Notify the client - self.client.map(|client| client.packet_transmitted()); - } -diff --git a/chips/nrf52/src/usbd.rs b/chips/nrf52/src/usbd.rs -index 8c1992cc..972871d0 100644 ---- a/chips/nrf52/src/usbd.rs -+++ b/chips/nrf52/src/usbd.rs -@@ -623,7 +623,7 @@ pub enum UsbState { - pub enum EndpointState { - Disabled, - Ctrl(CtrlState), -- Bulk(TransferType, EndpointDirection, BulkState), -+ Bulk(TransferType, Option<BulkInState>, Option<BulkOutState>), - } - - impl EndpointState { -@@ -634,10 +634,10 @@ impl EndpointState { - } - } - -- fn bulk_state(self) -> (TransferType, EndpointDirection, BulkState) { -+ fn bulk_state(self) -> (TransferType, Option<BulkInState>, Option<BulkOutState>) { - match self { -- EndpointState::Bulk(transfer_type, direction, state) => { -- (transfer_type, direction, state) -+ EndpointState::Bulk(transfer_type, in_state, out_state) => { -+ (transfer_type, in_state, out_state) - } - _ => panic!("Expected EndpointState::Bulk"), - } -@@ -651,31 +651,18 @@ pub enum CtrlState { - ReadStatus, - } - --#[derive(Copy, Clone, Debug)] --pub enum EndpointDirection { -- In, -- Out, -- InOut, --} -- --impl EndpointDirection { -- fn has_in(&self) -> bool { -- match self { -- EndpointDirection::In | EndpointDirection::InOut => true, -- EndpointDirection::Out => false, -- } -- } -- -- fn has_out(&self) -> bool { -- match self { -- EndpointDirection::Out | EndpointDirection::InOut => true, -- EndpointDirection::In => false, -- } -- } -+#[derive(Copy, Clone, PartialEq, Debug)] -+pub enum BulkInState { -+ // The endpoint is ready to perform transactions. -+ Init, -+ // There is a pending DMA transfer on this IN endpoint. -+ InDma, -+ // There is a pending IN packet transfer on this endpoint. -+ InData, - } - - #[derive(Copy, Clone, PartialEq, Debug)] --pub enum BulkState { -+pub enum BulkOutState { - // The endpoint is ready to perform transactions. - Init, - // There is a pending OUT packet in this endpoint's buffer, to be read by -@@ -685,14 +672,11 @@ pub enum BulkState { - OutData, - // There is a pending DMA transfer on this OUT endpoint. - OutDma, -- // There is a pending DMA transfer on this IN endpoint. -- InDma, -- // There is a pending IN packet transfer on this endpoint. -- InData, - } - - pub struct Endpoint<'a> { -- slice: OptionalCell<&'a [VolatileCell<u8>]>, -+ slice_in: OptionalCell<&'a [VolatileCell<u8>]>, -+ slice_out: OptionalCell<&'a [VolatileCell<u8>]>, - state: Cell<EndpointState>, - // The USB controller can only process one DMA transfer at a time (over all endpoints). The - // request_transmit_* bits allow to queue transfers until the DMA becomes available again. -@@ -705,7 +689,8 @@ pub struct Endpoint<'a> { - impl Endpoint<'_> { - const fn new() -> Self { - Endpoint { -- slice: OptionalCell::empty(), -+ slice_in: OptionalCell::empty(), -+ slice_out: OptionalCell::empty(), - state: Cell::new(EndpointState::Disabled), - request_transmit_in: Cell::new(false), - request_transmit_out: Cell::new(false), -@@ -914,18 +899,12 @@ impl<'a> Usbd<'a> { - chip_revision.get() - ); - } -- Some(ChipRevision::REV::Value::REVC) => { -+ Some(ChipRevision::REV::Value::REVC) | Some(ChipRevision::REV::Value::REVD) => { - debug_info!( - "Your chip is NRF52840 revision {}. The USB stack was tested on your chip :)", - chip_revision.get() - ); - } -- Some(ChipRevision::REV::Value::REVD) => { -- internal_warn!( -- "Your chip is NRF52840 revision {}. Although this USB implementation should be compatible, your chip hasn't been tested.", -- chip_revision.get() -- ); -- } - None => { - internal_warn!( - "Your chip is NRF52840 revision {} (unknown revision). Although this USB implementation should be compatible, your chip hasn't been tested.", -@@ -1026,7 +1005,7 @@ impl<'a> Usbd<'a> { - }); - self.descriptors[endpoint].state.set(match endpoint { - 0 => EndpointState::Ctrl(CtrlState::Init), -- 1..=7 => EndpointState::Bulk(transfer_type, EndpointDirection::In, BulkState::Init), -+ 1..=7 => EndpointState::Bulk(transfer_type, Some(BulkInState::Init), None), - 8 => unimplemented!("isochronous endpoint"), - _ => unreachable!("unexisting endpoint"), - }); -@@ -1064,7 +1043,7 @@ impl<'a> Usbd<'a> { - }); - self.descriptors[endpoint].state.set(match endpoint { - 0 => EndpointState::Ctrl(CtrlState::Init), -- 1..=7 => EndpointState::Bulk(transfer_type, EndpointDirection::Out, BulkState::Init), -+ 1..=7 => EndpointState::Bulk(transfer_type, None, Some(BulkOutState::Init)), - 8 => unimplemented!("isochronous endpoint"), - _ => unreachable!("unexisting endpoint"), - }); -@@ -1114,7 +1093,11 @@ impl<'a> Usbd<'a> { - }); - self.descriptors[endpoint].state.set(match endpoint { - 0 => EndpointState::Ctrl(CtrlState::Init), -- 1..=7 => EndpointState::Bulk(transfer_type, EndpointDirection::InOut, BulkState::Init), -+ 1..=7 => EndpointState::Bulk( -+ transfer_type, -+ Some(BulkInState::Init), -+ Some(BulkOutState::Init), -+ ), - 8 => unimplemented!("isochronous endpoint"), - _ => unreachable!("unexisting endpoint"), - }); -@@ -1304,13 +1287,13 @@ impl<'a> Usbd<'a> { - match desc.state.get() { - EndpointState::Disabled => {} - EndpointState::Ctrl(_) => desc.state.set(EndpointState::Ctrl(CtrlState::Init)), -- EndpointState::Bulk(transfer_type, direction, _) => { -+ EndpointState::Bulk(transfer_type, in_state, out_state) => { - desc.state.set(EndpointState::Bulk( - transfer_type, -- direction, -- BulkState::Init, -+ in_state.map(|_| BulkInState::Init), -+ out_state.map(|_| BulkOutState::Init), - )); -- if direction.has_out() { -+ if out_state.is_some() { - // Accept incoming OUT packets. - regs.size_epout[ep].set(0); - } -@@ -1347,13 +1330,13 @@ impl<'a> Usbd<'a> { - match endpoint { - 0 => {} - 1..=7 => { -- let (transfer_type, direction, state) = -+ let (transfer_type, in_state, out_state) = - self.descriptors[endpoint].state.get().bulk_state(); -- assert_eq!(state, BulkState::InDma); -+ assert_eq!(in_state, Some(BulkInState::InDma)); - self.descriptors[endpoint].state.set(EndpointState::Bulk( - transfer_type, -- direction, -- BulkState::InData, -+ Some(BulkInState::InData), -+ out_state, - )); - } - 8 => unimplemented!("isochronous endpoint"), -@@ -1405,25 +1388,25 @@ impl<'a> Usbd<'a> { - 1..=7 => { - // Notify the client about the new packet. - let packet_bytes = regs.size_epout[endpoint].get(); -- let (transfer_type, direction, state) = -+ let (transfer_type, in_state, out_state) = - self.descriptors[endpoint].state.get().bulk_state(); -- assert_eq!(state, BulkState::OutDma); -+ assert_eq!(out_state, Some(BulkOutState::OutDma)); - -- self.debug_packet("out", packet_bytes as usize, endpoint); -+ self.debug_out_packet(packet_bytes as usize, endpoint); - - self.client.map(|client| { - let result = client.packet_out(transfer_type, endpoint, packet_bytes); - debug_packets!("packet_out => {:?}", result); -- let newstate = match result { -+ let new_out_state = match result { - hil::usb::OutResult::Ok => { - // Indicate that the endpoint is ready to receive data again. - regs.size_epout[endpoint].set(0); -- BulkState::Init -+ BulkOutState::Init - } - - hil::usb::OutResult::Delay => { - // We can't send the packet now. Wait for a resume_out call from the client. -- BulkState::OutDelay -+ BulkOutState::OutDelay - } - - hil::usb::OutResult::Error => { -@@ -1432,13 +1415,13 @@ impl<'a> Usbd<'a> { - + EndpointStall::IO::Out - + EndpointStall::STALL::Stall, - ); -- BulkState::Init -+ BulkOutState::Init - } - }; - self.descriptors[endpoint].state.set(EndpointState::Bulk( - transfer_type, -- direction, -- newstate, -+ in_state, -+ Some(new_out_state), - )); - }); - } -@@ -1497,29 +1480,27 @@ impl<'a> Usbd<'a> { - // Endpoint 8 (isochronous) doesn't receive any EPDATA event. - for endpoint in 1..NUM_ENDPOINTS { - if epdatastatus.is_set(status_epin(endpoint)) { -- let (transfer_type, direction, state) = -+ let (transfer_type, in_state, out_state) = - self.descriptors[endpoint].state.get().bulk_state(); -- match state { -- BulkState::InData => { -+ assert!(in_state.is_some()); -+ match in_state.unwrap() { -+ BulkInState::InData => { - // Totally expected state. Nothing to do. - } -- BulkState::Init => { -+ BulkInState::Init => { - internal_warn!( - "Received a stale epdata IN in an unexpected state: {:?}", -- state -+ in_state - ); - } -- BulkState::OutDelay -- | BulkState::OutData -- | BulkState::OutDma -- | BulkState::InDma => { -- internal_err!("Unexpected state: {:?}", state); -+ BulkInState::InDma => { -+ internal_err!("Unexpected state: {:?}", in_state); - } - } - self.descriptors[endpoint].state.set(EndpointState::Bulk( - transfer_type, -- direction, -- BulkState::Init, -+ Some(BulkInState::Init), -+ out_state, - )); - self.client - .map(|client| client.packet_transmitted(endpoint)); -@@ -1530,28 +1511,26 @@ impl<'a> Usbd<'a> { - // Endpoint 8 (isochronous) doesn't receive any EPDATA event. - for ep in 1..NUM_ENDPOINTS { - if epdatastatus.is_set(status_epout(ep)) { -- let (transfer_type, direction, state) = -+ let (transfer_type, in_state, out_state) = - self.descriptors[ep].state.get().bulk_state(); -- match state { -- BulkState::Init => { -+ assert!(out_state.is_some()); -+ match out_state.unwrap() { -+ BulkOutState::Init => { - // The endpoint is ready to receive data. Request a transmit_out. - self.descriptors[ep].request_transmit_out.set(true); - } -- BulkState::OutDelay => { -+ BulkOutState::OutDelay => { - // The endpoint will be resumed later by the client application with transmit_out(). - } -- BulkState::OutData -- | BulkState::OutDma -- | BulkState::InDma -- | BulkState::InData => { -- internal_err!("Unexpected state: {:?}", state); -+ BulkOutState::OutData | BulkOutState::OutDma => { -+ internal_err!("Unexpected state: {:?}", out_state); - } - } - // Indicate that the endpoint now has data available. - self.descriptors[ep].state.set(EndpointState::Bulk( - transfer_type, -- direction, -- BulkState::OutData, -+ in_state, -+ Some(BulkOutState::OutData), - )); - } - } -@@ -1564,8 +1543,8 @@ impl<'a> Usbd<'a> { - let state = self.descriptors[endpoint].state.get().ctrl_state(); - match state { - CtrlState::Init => { -- let ep_buf = &self.descriptors[endpoint].slice; -- let ep_buf = ep_buf.expect("No slice set for this descriptor"); -+ let ep_buf = &self.descriptors[endpoint].slice_out; -+ let ep_buf = ep_buf.expect("No OUT slice set for this descriptor"); - if ep_buf.len() < 8 { - panic!("EP0 DMA buffer length < 8"); - } -@@ -1697,21 +1676,21 @@ impl<'a> Usbd<'a> { - let regs = &*self.registers; - - self.client.map(|client| { -- let (transfer_type, direction, state) = -+ let (transfer_type, in_state, out_state) = - self.descriptors[endpoint].state.get().bulk_state(); -- assert_eq!(state, BulkState::Init); -+ assert_eq!(in_state, Some(BulkInState::Init)); - - let result = client.packet_in(transfer_type, endpoint); - debug_packets!("packet_in => {:?}", result); -- let newstate = match result { -+ let new_in_state = match result { - hil::usb::InResult::Packet(size) => { - self.start_dma_in(endpoint, size); -- BulkState::InDma -+ BulkInState::InDma - } - - hil::usb::InResult::Delay => { - // No packet to send now. Wait for a resume call from the client. -- BulkState::Init -+ BulkInState::Init - } - - hil::usb::InResult::Error => { -@@ -1720,14 +1699,14 @@ impl<'a> Usbd<'a> { - + EndpointStall::IO::In - + EndpointStall::STALL::Stall, - ); -- BulkState::Init -+ BulkInState::Init - } - }; - - self.descriptors[endpoint].state.set(EndpointState::Bulk( - transfer_type, -- direction, -- newstate, -+ Some(new_in_state), -+ out_state, - )); - }); - } -@@ -1735,15 +1714,16 @@ impl<'a> Usbd<'a> { - fn transmit_out(&self, endpoint: usize) { - debug_events!("transmit_out({})", endpoint); - -- let (transfer_type, direction, state) = self.descriptors[endpoint].state.get().bulk_state(); -+ let (transfer_type, in_state, out_state) = -+ self.descriptors[endpoint].state.get().bulk_state(); - // Starting the DMA can only happen in the OutData state, i.e. after an EPDATA event. -- assert_eq!(state, BulkState::OutData); -+ assert_eq!(out_state, Some(BulkOutState::OutData)); - self.start_dma_out(endpoint); - - self.descriptors[endpoint].state.set(EndpointState::Bulk( - transfer_type, -- direction, -- BulkState::OutDma, -+ in_state, -+ Some(BulkOutState::OutDma), - )); - } - -@@ -1751,9 +1731,9 @@ impl<'a> Usbd<'a> { - let regs = &*self.registers; - - let slice = self.descriptors[endpoint] -- .slice -- .expect("No slice set for this descriptor"); -- self.debug_packet("in", size, endpoint); -+ .slice_in -+ .expect("No IN slice set for this descriptor"); -+ self.debug_in_packet(size, endpoint); - - // Start DMA transfer - self.set_pending_dma(); -@@ -1766,8 +1746,8 @@ impl<'a> Usbd<'a> { - let regs = &*self.registers; - - let slice = self.descriptors[endpoint] -- .slice -- .expect("No slice set for this descriptor"); -+ .slice_out -+ .expect("No OUT slice set for this descriptor"); - - // Start DMA transfer - self.set_pending_dma(); -@@ -1777,10 +1757,27 @@ impl<'a> Usbd<'a> { - } - - // Debug-only function -- fn debug_packet(&self, _title: &str, size: usize, endpoint: usize) { -+ fn debug_in_packet(&self, size: usize, endpoint: usize) { -+ let slice = self.descriptors[endpoint] -+ .slice_in -+ .expect("No IN slice set for this descriptor"); -+ if size > slice.len() { -+ panic!("Packet is too large: {}", size); -+ } -+ -+ let mut packet_hex = [0; 128]; -+ packet_to_hex(slice, &mut packet_hex); -+ debug_packets!( -+ "in={}", -+ core::str::from_utf8(&packet_hex[..(2 * size)]).unwrap() -+ ); -+ } -+ -+ // Debug-only function -+ fn debug_out_packet(&self, size: usize, endpoint: usize) { - let slice = self.descriptors[endpoint] -- .slice -- .expect("No slice set for this descriptor"); -+ .slice_out -+ .expect("No OUT slice set for this descriptor"); - if size > slice.len() { - panic!("Packet is too large: {}", size); - } -@@ -1788,8 +1785,7 @@ impl<'a> Usbd<'a> { - let mut packet_hex = [0; 128]; - packet_to_hex(slice, &mut packet_hex); - debug_packets!( -- "{}={}", -- _title, -+ "out={}", - core::str::from_utf8(&packet_hex[..(2 * size)]).unwrap() - ); - } -@@ -1807,17 +1803,41 @@ impl<'a> power::PowerClient for Usbd<'a> { - } - - impl<'a> hil::usb::UsbController<'a> for Usbd<'a> { -- fn endpoint_set_buffer(&self, endpoint: usize, buf: &'a [VolatileCell<u8>]) { -+ fn endpoint_set_ctrl_buffer(&self, buf: &'a [VolatileCell<u8>]) { -+ if buf.len() < 8 { -+ panic!("Endpoint buffer must be at least 8 bytes"); -+ } -+ if !buf.len().is_power_of_two() { -+ panic!("Buffer size must be a power of 2"); -+ } -+ self.descriptors[0].slice_in.set(buf); -+ self.descriptors[0].slice_out.set(buf); -+ } -+ -+ fn endpoint_set_in_buffer(&self, endpoint: usize, buf: &'a [VolatileCell<u8>]) { -+ if buf.len() < 8 { -+ panic!("Endpoint buffer must be at least 8 bytes"); -+ } -+ if !buf.len().is_power_of_two() { -+ panic!("Buffer size must be a power of 2"); -+ } -+ if endpoint == 0 || endpoint >= NUM_ENDPOINTS { -+ panic!("Endpoint number is invalid"); -+ } -+ self.descriptors[endpoint].slice_in.set(buf); -+ } -+ -+ fn endpoint_set_out_buffer(&self, endpoint: usize, buf: &'a [VolatileCell<u8>]) { - if buf.len() < 8 { - panic!("Endpoint buffer must be at least 8 bytes"); - } - if !buf.len().is_power_of_two() { - panic!("Buffer size must be a power of 2"); - } -- if endpoint >= NUM_ENDPOINTS { -- panic!("Endpoint number is too high"); -+ if endpoint == 0 || endpoint >= NUM_ENDPOINTS { -+ panic!("Endpoint number is invalid"); - } -- self.descriptors[endpoint].slice.set(buf); -+ self.descriptors[endpoint].slice_out.set(buf); - } - - fn enable_as_device(&self, speed: hil::usb::DeviceSpeed) { -@@ -1900,8 +1920,8 @@ impl<'a> hil::usb::UsbController<'a> for Usbd<'a> { - fn endpoint_resume_in(&self, endpoint: usize) { - debug_events!("endpoint_resume_in({})", endpoint); - -- let (_, direction, _) = self.descriptors[endpoint].state.get().bulk_state(); -- assert!(direction.has_in()); -+ let (_, in_state, _) = self.descriptors[endpoint].state.get().bulk_state(); -+ assert!(in_state.is_some()); - - if self.dma_pending.get() { - debug_events!("requesting resume_in[{}]", endpoint); -@@ -1916,20 +1936,21 @@ impl<'a> hil::usb::UsbController<'a> for Usbd<'a> { - fn endpoint_resume_out(&self, endpoint: usize) { - debug_events!("endpoint_resume_out({})", endpoint); - -- let (transfer_type, direction, state) = self.descriptors[endpoint].state.get().bulk_state(); -- assert!(direction.has_out()); -+ let (transfer_type, in_state, out_state) = -+ self.descriptors[endpoint].state.get().bulk_state(); -+ assert!(out_state.is_some()); - -- match state { -- BulkState::OutDelay => { -+ match out_state.unwrap() { -+ BulkOutState::OutDelay => { - // The endpoint has now finished processing the last ENDEPOUT. No EPDATA event - // happened in the meantime, so the state is now back to Init. - self.descriptors[endpoint].state.set(EndpointState::Bulk( - transfer_type, -- direction, -- BulkState::Init, -+ in_state, -+ Some(BulkOutState::Init), - )); - } -- BulkState::OutData => { -+ BulkOutState::OutData => { - // Although the client reported a delay before, an EPDATA event has - // happened in the meantime. This pending transaction will now - // continue in transmit_out(). -@@ -1942,25 +1963,11 @@ impl<'a> hil::usb::UsbController<'a> for Usbd<'a> { - self.transmit_out(endpoint); - } - } -- BulkState::Init | BulkState::OutDma | BulkState::InDma | BulkState::InData => { -- internal_err!("Unexpected state: {:?}", state); -+ BulkOutState::Init | BulkOutState::OutDma => { -+ internal_err!("Unexpected state: {:?}", out_state); - } - } - } -- -- fn endpoint_cancel_in(&self, endpoint: usize) { -- debug_events!("endpoint_cancel_in({})", endpoint); -- -- let (transfer_type, direction, state) = self.descriptors[endpoint].state.get().bulk_state(); -- assert!(direction.has_in()); -- assert_eq!(state, BulkState::InData); -- -- self.descriptors[endpoint].state.set(EndpointState::Bulk( -- transfer_type, -- direction, -- BulkState::Init, -- )); -- } - } - - fn status_epin(ep: usize) -> Field<u32, EndpointStatus::Register> { -diff --git a/chips/sam4l/src/usbc/mod.rs b/chips/sam4l/src/usbc/mod.rs -index 28a0b9f9..ab5b636f 100644 ---- a/chips/sam4l/src/usbc/mod.rs -+++ b/chips/sam4l/src/usbc/mod.rs -@@ -1438,11 +1438,28 @@ fn endpoint_enable_interrupts(endpoint: usize, mask: FieldValue<u32, EndpointCon - } - - impl hil::usb::UsbController<'a> for Usbc<'a> { -- fn endpoint_set_buffer(&self, endpoint: usize, buf: &'a [VolatileCell<u8>]) { -+ fn endpoint_set_ctrl_buffer(&self, buf: &'a [VolatileCell<u8>]) { - if buf.len() != 8 { - client_err!("Bad endpoint buffer size"); - } - -+ self._endpoint_bank_set_buffer(EndpointIndex::new(0), BankIndex::Bank0, buf); -+ } -+ -+ fn endpoint_set_in_buffer(&self, endpoint: usize, buf: &'a [VolatileCell<u8>]) { -+ if buf.len() != 8 { -+ client_err!("Bad endpoint buffer size"); -+ } -+ -+ self._endpoint_bank_set_buffer(EndpointIndex::new(endpoint), BankIndex::Bank0, buf); -+ } -+ -+ fn endpoint_set_out_buffer(&self, endpoint: usize, buf: &'a [VolatileCell<u8>]) { -+ if buf.len() != 8 { -+ client_err!("Bad endpoint buffer size"); -+ } -+ -+ // XXX: when implementing in_out endpoints, this should probably set a different slice than endpoint_set_in_buffer. - self._endpoint_bank_set_buffer(EndpointIndex::new(endpoint), BankIndex::Bank0, buf); - } - -@@ -1547,10 +1564,6 @@ impl hil::usb::UsbController<'a> for Usbc<'a> { - requests.resume_out = true; - self.requests[endpoint].set(requests); - } -- -- fn endpoint_cancel_in(&self, _endpoint: usize) { -- unimplemented!() -- } - } - - /// Static state to manage the USBC -diff --git a/kernel/src/hil/usb.rs b/kernel/src/hil/usb.rs -index 64610fa5..a114b30d 100644 ---- a/kernel/src/hil/usb.rs -+++ b/kernel/src/hil/usb.rs -@@ -5,7 +5,9 @@ use crate::common::cells::VolatileCell; - /// USB controller interface - pub trait UsbController<'a> { - // Should be called before `enable_as_device()` -- fn endpoint_set_buffer(&self, endpoint: usize, buf: &'a [VolatileCell<u8>]); -+ fn endpoint_set_ctrl_buffer(&self, buf: &'a [VolatileCell<u8>]); -+ fn endpoint_set_in_buffer(&self, endpoint: usize, buf: &'a [VolatileCell<u8>]); -+ fn endpoint_set_out_buffer(&self, endpoint: usize, buf: &'a [VolatileCell<u8>]); - - // Must be called before `attach()` - fn enable_as_device(&self, speed: DeviceSpeed); -@@ -27,8 +29,6 @@ pub trait UsbController<'a> { - fn endpoint_resume_in(&self, endpoint: usize); - - fn endpoint_resume_out(&self, endpoint: usize); -- -- fn endpoint_cancel_in(&self, endpoint: usize); - } - - #[derive(Clone, Copy, Debug)] diff --git a/run_desktop_tests.sh b/run_desktop_tests.sh index 33b209014900702be46cf0026d99d9d7b929e41c..e38735315eb892bf2b5e74b225afffa3400b055d 100755 --- a/run_desktop_tests.sh +++ b/run_desktop_tests.sh @@ -24,6 +24,9 @@ cd libraries/crypto cargo fmt --all -- --check cd ../.. +echo "Building sha256sum tool..." +cargo build --manifest-path third_party/tock/tools/sha256sum/Cargo.toml + echo "Checking that CTAP2 builds properly..." cargo check --release --target=thumbv7em-none-eabi cargo check --release --target=thumbv7em-none-eabi --features with_ctap1 @@ -31,19 +34,25 @@ cargo check --release --target=thumbv7em-none-eabi --features debug_ctap cargo check --release --target=thumbv7em-none-eabi --features panic_console cargo check --release --target=thumbv7em-none-eabi --features debug_allocations cargo check --release --target=thumbv7em-none-eabi --features ram_storage +cargo check --release --target=thumbv7em-none-eabi --features verbose cargo check --release --target=thumbv7em-none-eabi --features debug_ctap,with_ctap1 -cargo check --release --target=thumbv7em-none-eabi --features debug_ctap,with_ctap1,panic_console,debug_allocations +cargo check --release --target=thumbv7em-none-eabi --features debug_ctap,with_ctap1,panic_console,debug_allocations,verbose echo "Checking that examples build properly..." cargo check --release --target=thumbv7em-none-eabi --examples echo "Checking that CTAP2 builds and links properly (1 set of features)..." cargo build --release --target=thumbv7em-none-eabi --features with_ctap1 +./third_party/tock/tools/sha256sum/target/debug/sha256sum target/thumbv7em-none-eabi/release/ctap2 echo "Checking that supported boards build properly..." make -C third_party/tock/boards/nordic/nrf52840dk make -C third_party/tock/boards/nordic/nrf52840_dongle +echo "Checking that other boards build properly..." +make -C boards/nrf52840_dongle_dfu +make -C boards/nrf52840_mdk_dfu + if [ -z "${TRAVIS_OS_NAME}" -o "${TRAVIS_OS_NAME}" = "linux" ] then echo "Running unit tests on the desktop (release mode)..." diff --git a/setup.sh b/setup.sh index 5f9e63812082f8ea18c877c01be7a2c660871b84..1c6fe37cfc1fb467cf04d7e759e08784b97949a7 100755 --- a/setup.sh +++ b/setup.sh @@ -81,7 +81,7 @@ source tools/gen_key_materials.sh generate_crypto_materials N rustup install $(head -n 1 rust-toolchain) -pip3 install --user --upgrade tockloader +pip3 install --user --upgrade tockloader six intelhex rustup target add thumbv7em-none-eabi # Install dependency to create applications. diff --git a/src/ctap/data_formats.rs b/src/ctap/data_formats.rs index eae29485b7fa1ee76db13d7171f4da5573daf92f..5049d1cb5528d6e0606bc08eebce931d9c66648e 100644 --- a/src/ctap/data_formats.rs +++ b/src/ctap/data_formats.rs @@ -220,6 +220,78 @@ impl TryFrom<&cbor::Value> for Extensions { } } +impl From<Extensions> for cbor::Value { + fn from(extensions: Extensions) -> Self { + cbor_map_btree!(extensions + .0 + .into_iter() + .map(|(key, value)| (cbor_text!(key), value)) + .collect()) + } +} + +impl Extensions { + #[cfg(test)] + pub fn new(extension_map: BTreeMap<String, cbor::Value>) -> Self { + Extensions(extension_map) + } + + pub fn has_make_credential_hmac_secret(&self) -> Result<bool, Ctap2StatusCode> { + self.0 + .get("hmac-secret") + .map(read_bool) + .unwrap_or(Ok(false)) + } + + pub fn get_assertion_hmac_secret( + &self, + ) -> Option<Result<GetAssertionHmacSecretInput, Ctap2StatusCode>> { + self.0 + .get("hmac-secret") + .map(GetAssertionHmacSecretInput::try_from) + } +} + +#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))] +pub struct GetAssertionHmacSecretInput { + pub key_agreement: CoseKey, + pub salt_enc: Vec<u8>, + pub salt_auth: Vec<u8>, +} + +impl TryFrom<&cbor::Value> for GetAssertionHmacSecretInput { + type Error = Ctap2StatusCode; + + fn try_from(cbor_value: &cbor::Value) -> Result<Self, Ctap2StatusCode> { + let input_map = read_map(cbor_value)?; + let cose_key = read_map(ok_or_missing(input_map.get(&cbor_unsigned!(1)))?)?; + let salt_enc = read_byte_string(ok_or_missing(input_map.get(&cbor_unsigned!(2)))?)?; + let salt_auth = read_byte_string(ok_or_missing(input_map.get(&cbor_unsigned!(3)))?)?; + Ok(Self { + key_agreement: CoseKey(cose_key.clone()), + salt_enc, + salt_auth, + }) + } +} + +#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))] +pub struct GetAssertionHmacSecretOutput(Vec<u8>); + +impl From<GetAssertionHmacSecretOutput> for cbor::Value { + fn from(message: GetAssertionHmacSecretOutput) -> cbor::Value { + cbor_bytes!(message.0) + } +} + +impl TryFrom<&cbor::Value> for GetAssertionHmacSecretOutput { + type Error = Ctap2StatusCode; + + fn try_from(cbor_value: &cbor::Value) -> Result<Self, Ctap2StatusCode> { + Ok(GetAssertionHmacSecretOutput(read_byte_string(cbor_value)?)) + } +} + // Even though options are optional, we can use the default if not present. #[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))] pub struct MakeCredentialOptions { @@ -314,6 +386,7 @@ pub struct PublicKeyCredentialSource { pub rp_id: String, pub user_handle: Vec<u8>, // not optional, but nullable pub other_ui: Option<String>, + pub cred_random: Option<Vec<u8>>, } impl From<PublicKeyCredentialSource> for cbor::Value { @@ -324,12 +397,17 @@ impl From<PublicKeyCredentialSource> for cbor::Value { None => cbor_null!(), Some(other_ui) => cbor_text!(other_ui), }; + let cred_random = match credential.cred_random { + None => cbor_null!(), + Some(cred_random) => cbor_bytes!(cred_random), + }; cbor_array! { credential.credential_id, private_key, credential.rp_id, credential.user_handle, other_ui, + cred_random, } } } @@ -341,7 +419,7 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialSource { use cbor::{SimpleValue, Value}; let fields = read_array(&cbor_value)?; - if fields.len() != 5 { + if fields.len() != 6 { return Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR); } let credential_id = read_byte_string(&fields[0])?; @@ -357,6 +435,10 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialSource { Value::Simple(SimpleValue::NullValue) => None, cbor_value => Some(read_text_string(cbor_value)?), }; + let cred_random = match &fields[5] { + Value::Simple(SimpleValue::NullValue) => None, + cbor_value => Some(read_byte_string(cbor_value)?), + }; Ok(PublicKeyCredentialSource { key_type: PublicKeyCredentialType::PublicKey, credential_id, @@ -364,6 +446,7 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialSource { rp_id, user_handle, other_ui, + cred_random, }) } } @@ -993,6 +1076,7 @@ mod test { rp_id: "example.com".to_string(), user_handle: b"foo".to_vec(), other_ui: None, + cred_random: None, }; assert_eq!( @@ -1005,6 +1089,16 @@ mod test { ..credential }; + assert_eq!( + PublicKeyCredentialSource::try_from(cbor::Value::from(credential.clone())), + Ok(credential.clone()) + ); + + let credential = PublicKeyCredentialSource { + cred_random: Some(vec![0x00; 32]), + ..credential + }; + assert_eq!( PublicKeyCredentialSource::try_from(cbor::Value::from(credential.clone())), Ok(credential) diff --git a/src/ctap/mod.rs b/src/ctap/mod.rs index e444df8a642f2d6f772c377a33f7bfa8768e779f..e4a11516265f2ed23771179c726284dfba5d4628 100644 --- a/src/ctap/mod.rs +++ b/src/ctap/mod.rs @@ -28,9 +28,9 @@ use self::command::{ AuthenticatorMakeCredentialParameters, Command, }; use self::data_formats::{ - ClientPinSubCommand, CoseKey, PackedAttestationStatement, PublicKeyCredentialDescriptor, - PublicKeyCredentialSource, PublicKeyCredentialType, PublicKeyCredentialUserEntity, - SignatureAlgorithm, + ClientPinSubCommand, CoseKey, GetAssertionHmacSecretInput, PackedAttestationStatement, + PublicKeyCredentialDescriptor, PublicKeyCredentialSource, PublicKeyCredentialType, + PublicKeyCredentialUserEntity, SignatureAlgorithm, }; use self::hid::ChannelID; use self::key_material::{AAGUID, ATTESTATION_CERTIFICATE, ATTESTATION_PRIVATE_KEY}; @@ -81,9 +81,14 @@ const PIN_PADDED_LENGTH: usize = 64; // - 32 byte relying party ID hashed with SHA256, // - 32 byte HMAC-SHA256 over everything else. pub const ENCRYPTED_CREDENTIAL_ID_SIZE: usize = 112; +// Set this bit when checking user presence. const UP_FLAG: u8 = 0x01; +// Set this bit when checking user verification. const UV_FLAG: u8 = 0x04; +// Set this bit when performing attestation. const AT_FLAG: u8 = 0x40; +// Set this bit when an extension is used. +const ED_FLAG: u8 = 0x80; pub const TOUCH_TIMEOUT_MS: isize = 30000; #[cfg(feature = "with_ctap1")] @@ -105,6 +110,63 @@ fn check_pin_auth(hmac_key: &[u8], hmac_contents: &[u8], pin_auth: &[u8]) -> boo ) } +// Decrypts the HMAC secret salt(s) that were encrypted with the shared secret. +// The credRandom is used as a secret to HMAC those salts. +// The last step is to re-encrypt the outputs. +pub fn encrypt_hmac_secret_output( + shared_secret: &[u8; 32], + salt_enc: &[u8], + cred_random: &[u8], +) -> Result<Vec<u8>, Ctap2StatusCode> { + if salt_enc.len() != 32 && salt_enc.len() != 64 { + return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION); + } + if cred_random.len() != 32 { + // We are strict here. We need at least 32 byte, but expect exactly 32. + return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION); + } + let aes_enc_key = crypto::aes256::EncryptionKey::new(shared_secret); + let aes_dec_key = crypto::aes256::DecryptionKey::new(&aes_enc_key); + // The specification specifically asks for a zero IV. + let iv = [0; 16]; + + let mut cred_random_secret = [0; 32]; + cred_random_secret.clone_from_slice(cred_random); + + // Initialization of 4 blocks in any case makes this function more readable. + let mut blocks = [[0u8; 16]; 4]; + let block_len = salt_enc.len() / 16; + for i in 0..block_len { + blocks[i].copy_from_slice(&salt_enc[16 * i..16 * (i + 1)]); + } + cbc_decrypt(&aes_dec_key, iv, &mut blocks[..block_len]); + + let mut decrypted_salt1 = [0; 32]; + decrypted_salt1[..16].clone_from_slice(&blocks[0]); + let output1 = hmac_256::<Sha256>(&cred_random_secret, &decrypted_salt1[..]); + decrypted_salt1[16..].clone_from_slice(&blocks[1]); + for i in 0..2 { + blocks[i].copy_from_slice(&output1[16 * i..16 * (i + 1)]); + } + + if block_len == 4 { + let mut decrypted_salt2 = [0; 32]; + decrypted_salt2[..16].clone_from_slice(&blocks[2]); + decrypted_salt2[16..].clone_from_slice(&blocks[3]); + let output2 = hmac_256::<Sha256>(&cred_random_secret, &decrypted_salt2[..]); + for i in 0..2 { + blocks[i + 2].copy_from_slice(&output2[16 * i..16 * (i + 1)]); + } + } + + cbc_encrypt(&aes_enc_key, iv, &mut blocks[..block_len]); + let mut encrypted_output = Vec::with_capacity(salt_enc.len()); + for b in &blocks[..block_len] { + encrypted_output.extend(b); + } + Ok(encrypted_output) +} + // This function is adapted from https://doc.rust-lang.org/nightly/src/core/str/mod.rs.html#2110 // (as of 2020-01-20) and truncates to "max" bytes, not breaking the encoding. // We change the return value, since we don't need the bool. @@ -261,6 +323,7 @@ where rp_id: String::from(""), user_handle: vec![], other_ui: None, + cred_random: None, }) } @@ -324,10 +387,10 @@ where user, pub_key_cred_params, exclude_list, + extensions, options, pin_uv_auth_param, pin_uv_auth_protocol, - .. } = make_credential_params; if let Some(auth_param) = &pin_uv_auth_param { @@ -362,6 +425,19 @@ where return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM); } + let use_hmac_extension = + extensions.map_or(Ok(false), |e| e.has_make_credential_hmac_secret())?; + if use_hmac_extension && !options.rk { + // The extension is actually supported, but we need resident keys. + return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION); + } + let cred_random = if use_hmac_extension { + Some(self.rng.gen_uniform_u8x32().to_vec()) + } else { + None + }; + let ed_flag = if use_hmac_extension { ED_FLAG } else { 0 }; + let rp_id = rp.rp_id; if let Some(exclude_list) = exclude_list { for cred_desc in exclude_list { @@ -389,7 +465,7 @@ where if !check_pin_auth(&self.pin_uv_auth_token, &client_data_hash, &pin_auth) { return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID); } - UP_FLAG | UV_FLAG | AT_FLAG + UP_FLAG | UV_FLAG | AT_FLAG | ed_flag } None => { if self.persistent_store.pin_hash().is_some() { @@ -398,7 +474,7 @@ where if options.uv { return Err(Ctap2StatusCode::CTAP2_ERR_INVALID_OPTION); } - UP_FLAG | AT_FLAG + UP_FLAG | AT_FLAG | ed_flag } }; @@ -421,6 +497,7 @@ where other_ui: user .user_display_name .map(|s| truncate_to_char_boundary(&s, 64).to_string()), + cred_random, }; self.persistent_store.store_credential(credential_source)?; random_id @@ -441,6 +518,14 @@ where None => return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_RESPONSE_CANNOT_WRITE_CBOR), }; auth_data.extend(cose_key); + if use_hmac_extension { + let extensions = cbor_map! { + "hmac-secret" => true, + }; + if !cbor::write(extensions, &mut auth_data) { + return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_RESPONSE_CANNOT_WRITE_CBOR); + } + } let mut signature_data = auth_data.clone(); signature_data.extend(client_data_hash); @@ -481,10 +566,10 @@ where rp_id, client_data_hash, allow_list, + extensions, options, pin_uv_auth_param, pin_uv_auth_protocol, - .. } = get_assertion_params; if let Some(auth_param) = &pin_uv_auth_param { @@ -527,6 +612,15 @@ where } } + let get_assertion_hmac_secret_input = match extensions { + Some(extensions) => extensions.get_assertion_hmac_secret().transpose()?, + None => None, + }; + if get_assertion_hmac_secret_input.is_some() && !options.up { + // The extension is actually supported, but we need user presence. + return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION); + } + // The user verification bit depends on the existance of PIN auth, whereas // user presence is requested as an option. let mut flags = match pin_uv_auth_param { @@ -551,6 +645,9 @@ where if options.up { flags |= UP_FLAG; } + if get_assertion_hmac_secret_input.is_some() { + flags |= ED_FLAG; + } let rp_id_hash = Sha256::hash(rp_id.as_bytes()); let mut decrypted_credential = None; @@ -590,7 +687,36 @@ where self.increment_global_signature_counter(); - let auth_data = self.generate_auth_data(&rp_id_hash, flags); + let mut auth_data = self.generate_auth_data(&rp_id_hash, flags); + // Process extensions. + if let Some(get_assertion_hmac_secret_input) = get_assertion_hmac_secret_input { + let GetAssertionHmacSecretInput { + key_agreement, + salt_enc, + salt_auth, + } = get_assertion_hmac_secret_input; + let pk: crypto::ecdh::PubKey = CoseKey::try_into(key_agreement)?; + let shared_secret = self.key_agreement_key.exchange_x_sha256(&pk); + // HMAC-secret does the same 16 byte truncated check. + if !check_pin_auth(&shared_secret, &salt_enc, &salt_auth) { + // Again, hard to tell what the correct error code here is. + return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION); + } + + let encrypted_output = match &credential.cred_random { + Some(cr) => encrypt_hmac_secret_output(&shared_secret, &salt_enc[..], cr)?, + // This is the case if the credential was not created with HMAC-secret. + None => return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION), + }; + + let extensions = cbor_map! { + "hmac-secret" => encrypted_output, + }; + if !cbor::write(extensions, &mut auth_data) { + return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_RESPONSE_CANNOT_WRITE_CBOR); + } + } + let mut signature_data = auth_data.clone(); signature_data.extend(client_data_hash); let signature = credential @@ -639,7 +765,7 @@ where String::from(U2F_VERSION_STRING), String::from(FIDO2_VERSION_STRING), ], - extensions: Some(vec![]), + extensions: Some(vec![String::from("hmac-secret")]), aaguid: *AAGUID, options: Some(options_map), max_msg_size: Some(1024), @@ -948,7 +1074,7 @@ where #[cfg(test)] mod test { use super::data_formats::{ - GetAssertionOptions, MakeCredentialOptions, PublicKeyCredentialRpEntity, + Extensions, GetAssertionOptions, MakeCredentialOptions, PublicKeyCredentialRpEntity, PublicKeyCredentialUserEntity, }; use super::*; @@ -970,13 +1096,15 @@ mod test { let mut expected_response = vec![0x00, 0xA6, 0x01]; // The difference here is a longer array of supported versions. #[cfg(not(feature = "with_ctap1"))] - expected_response.extend(&[ - 0x81, 0x68, 0x46, 0x49, 0x44, 0x4F, 0x5F, 0x32, 0x5F, 0x30, 0x02, 0x80, 0x03, 0x50, - ]); + expected_response.extend(&[0x81, 0x68, 0x46, 0x49, 0x44, 0x4F, 0x5F, 0x32, 0x5F, 0x30]); #[cfg(feature = "with_ctap1")] expected_response.extend(&[ 0x82, 0x66, 0x55, 0x32, 0x46, 0x5F, 0x56, 0x32, 0x68, 0x46, 0x49, 0x44, 0x4F, 0x5F, - 0x32, 0x5F, 0x30, 0x02, 0x80, 0x03, 0x50, + 0x32, 0x5F, 0x30, + ]); + expected_response.extend(&[ + 0x02, 0x81, 0x6B, 0x68, 0x6D, 0x61, 0x63, 0x2D, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, + 0x03, 0x50, ]); expected_response.extend(AAGUID); expected_response.extend(&[ @@ -1130,6 +1258,7 @@ mod test { rp_id: String::from("example.com"), user_handle: vec![], other_ui: None, + cred_random: None, }; assert!(ctap_state .persistent_store @@ -1153,6 +1282,54 @@ mod test { ); } + #[test] + fn test_process_make_credential_hmac_secret() { + let mut rng = ThreadRng256 {}; + let user_immediately_present = |_| Ok(()); + let mut ctap_state = CtapState::new(&mut rng, user_immediately_present); + + let mut extension_map = BTreeMap::new(); + extension_map.insert("hmac-secret".to_string(), cbor_bool!(true)); + let extensions = Some(Extensions::new(extension_map)); + let mut make_credential_params = create_minimal_make_credential_parameters(); + make_credential_params.extensions = extensions; + let make_credential_response = + ctap_state.process_make_credential(make_credential_params, DUMMY_CHANNEL_ID); + + match make_credential_response.unwrap() { + ResponseData::AuthenticatorMakeCredential(make_credential_response) => { + let AuthenticatorMakeCredentialResponse { + fmt, + auth_data, + att_stmt, + } = make_credential_response; + // The expected response is split to only assert the non-random parts. + assert_eq!(fmt, "packed"); + let mut expected_auth_data = vec![ + 0xA3, 0x79, 0xA6, 0xF6, 0xEE, 0xAF, 0xB9, 0xA5, 0x5E, 0x37, 0x8C, 0x11, 0x80, + 0x34, 0xE2, 0x75, 0x1E, 0x68, 0x2F, 0xAB, 0x9F, 0x2D, 0x30, 0xAB, 0x13, 0xD2, + 0x12, 0x55, 0x86, 0xCE, 0x19, 0x47, 0xC1, 0x00, 0x00, 0x00, 0x00, + ]; + expected_auth_data.extend(AAGUID); + expected_auth_data.extend(&[0x00, 0x20]); + assert_eq!( + auth_data[0..expected_auth_data.len()], + expected_auth_data[..] + ); + let expected_extension_cbor = vec![ + 0xA1, 0x6B, 0x68, 0x6D, 0x61, 0x63, 0x2D, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, + 0xF5, + ]; + assert_eq!( + auth_data[auth_data.len() - expected_extension_cbor.len()..auth_data.len()], + expected_extension_cbor[..] + ); + assert_eq!(att_stmt.alg, SignatureAlgorithm::ES256 as i64); + } + _ => panic!("Invalid response type"), + } + } + #[test] fn test_process_make_credential_cancelled() { let mut rng = ThreadRng256 {}; @@ -1216,6 +1393,53 @@ mod test { } } + #[test] + fn test_residential_process_get_assertion_hmac_secret() { + let mut rng = ThreadRng256 {}; + let sk = crypto::ecdh::SecKey::gensk(&mut rng); + let user_immediately_present = |_| Ok(()); + let mut ctap_state = CtapState::new(&mut rng, user_immediately_present); + + let mut extension_map = BTreeMap::new(); + extension_map.insert("hmac-secret".to_string(), cbor_bool!(true)); + let make_extensions = Some(Extensions::new(extension_map)); + let mut make_credential_params = create_minimal_make_credential_parameters(); + make_credential_params.extensions = make_extensions; + assert!(ctap_state + .process_make_credential(make_credential_params, DUMMY_CHANNEL_ID) + .is_ok()); + + let pk = sk.genpk(); + let hmac_secret_parameters = cbor_map! { + 1 => cbor::Value::Map(CoseKey::from(pk).0), + 2 => vec![0; 32], + 3 => vec![0; 16], + }; + let mut extension_map = BTreeMap::new(); + extension_map.insert("hmac-secret".to_string(), hmac_secret_parameters); + + let get_extensions = Some(Extensions::new(extension_map)); + let get_assertion_params = AuthenticatorGetAssertionParameters { + rp_id: String::from("example.com"), + client_data_hash: vec![0xCD], + allow_list: None, + extensions: get_extensions, + options: GetAssertionOptions { + up: false, + uv: false, + }, + pin_uv_auth_param: None, + pin_uv_auth_protocol: None, + }; + let get_assertion_response = + ctap_state.process_get_assertion(get_assertion_params, DUMMY_CHANNEL_ID); + + assert_eq!( + get_assertion_response, + Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION) + ); + } + #[test] fn test_process_reset() { let mut rng = ThreadRng256 {}; @@ -1231,6 +1455,7 @@ mod test { rp_id: String::from("example.com"), user_handle: vec![], other_ui: None, + cred_random: None, }; assert!(ctap_state .persistent_store @@ -1294,4 +1519,32 @@ mod test { .is_none()); } } + + #[test] + fn test_encrypt_hmac_secret_output() { + let shared_secret = [0x55; 32]; + let salt_enc = [0x5E; 32]; + let cred_random = [0xC9; 32]; + let output = encrypt_hmac_secret_output(&shared_secret, &salt_enc, &cred_random); + assert_eq!(output.unwrap().len(), 32); + + let salt_enc = [0x5E; 48]; + let output = encrypt_hmac_secret_output(&shared_secret, &salt_enc, &cred_random); + assert_eq!( + output, + Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION) + ); + + let salt_enc = [0x5E; 64]; + let output = encrypt_hmac_secret_output(&shared_secret, &salt_enc, &cred_random); + assert_eq!(output.unwrap().len(), 64); + + let salt_enc = [0x5E; 32]; + let cred_random = [0xC9; 33]; + let output = encrypt_hmac_secret_output(&shared_secret, &salt_enc, &cred_random); + assert_eq!( + output, + Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION) + ); + } } diff --git a/src/ctap/storage.rs b/src/ctap/storage.rs index 565b0b99bb4f3b39fbc532c02226e93ee5e1392e..f0a2ca4ff0bfb3ec85e1859d44009af8c0a6ee29 100644 --- a/src/ctap/storage.rs +++ b/src/ctap/storage.rs @@ -453,6 +453,7 @@ mod test { rp_id: String::from(rp_id), user_handle, other_ui: None, + cred_random: None, } } @@ -621,6 +622,7 @@ mod test { rp_id: String::from("example.com"), user_handle: vec![0x00], other_ui: None, + cred_random: None, }; assert_eq!(found_credential, Some(expected_credential)); } diff --git a/src/usb_ctap_hid.rs b/src/usb_ctap_hid.rs index 421e70b08e13a5a7d8e61dbf4dbac535fbec6e25..7e63754e24df7be59b20a2c73f0ad623186f25d6 100644 --- a/src/usb_ctap_hid.rs +++ b/src/usb_ctap_hid.rs @@ -165,6 +165,57 @@ pub fn send_or_recv(buf: &mut [u8; 64]) -> SendOrRecvStatus { pub fn recv_with_timeout( buf: &mut [u8; 64], timeout_delay: Duration<isize>, +) -> Option<SendOrRecvStatus> { + #[cfg(feature = "verbose")] + writeln!( + Console::new(), + "Receiving packet with timeout of {}ms", + timeout_delay.ms(), + ) + .unwrap(); + + let result = recv_with_timeout_detail(buf, timeout_delay); + + #[cfg(feature = "verbose")] + { + if let Some(SendOrRecvStatus::Received) = result { + writeln!(Console::new(), "Received packet = {:02x?}", buf as &[u8]).unwrap(); + } + } + + result +} + +// Same as send_or_recv, but with a timeout. +// If the timeout elapses, return None. +pub fn send_or_recv_with_timeout( + buf: &mut [u8; 64], + timeout_delay: Duration<isize>, +) -> Option<SendOrRecvStatus> { + #[cfg(feature = "verbose")] + writeln!( + Console::new(), + "Sending packet with timeout of {}ms = {:02x?}", + timeout_delay.ms(), + buf as &[u8] + ) + .unwrap(); + + let result = send_or_recv_with_timeout_detail(buf, timeout_delay); + + #[cfg(feature = "verbose")] + { + if let Some(SendOrRecvStatus::Received) = result { + writeln!(Console::new(), "Received packet = {:02x?}", buf as &[u8]).unwrap(); + } + } + + result +} + +fn recv_with_timeout_detail( + buf: &mut [u8; 64], + timeout_delay: Duration<isize>, ) -> Option<SendOrRecvStatus> { let result = syscalls::allow(DRIVER_NUMBER, allow_nr::RECEIVE, buf); if result.is_err() { @@ -225,7 +276,7 @@ pub fn recv_with_timeout( // Cancel USB transaction if necessary. if status.get().is_none() { - #[cfg(feature = "debug_ctap")] + #[cfg(feature = "verbose")] writeln!(Console::new(), "Cancelling USB receive due to timeout").unwrap(); let result_code = unsafe { syscalls::command(DRIVER_NUMBER, command_nr::CANCEL, 0, 0) }; match result_code { @@ -249,9 +300,7 @@ pub fn recv_with_timeout( status.get() } -// Same as send_or_recv, but with a timeout. -// If the timeout elapses, return None. -pub fn send_or_recv_with_timeout( +fn send_or_recv_with_timeout_detail( buf: &mut [u8; 64], timeout_delay: Duration<isize>, ) -> Option<SendOrRecvStatus> { @@ -317,7 +366,7 @@ pub fn send_or_recv_with_timeout( // Cancel USB transaction if necessary. if status.get().is_none() { - #[cfg(feature = "debug_ctap")] + #[cfg(feature = "verbose")] writeln!(Console::new(), "Cancelling USB transaction due to timeout").unwrap(); let result_code = unsafe { syscalls::command(DRIVER_NUMBER, command_nr::CANCEL, 0, 0) }; match result_code { diff --git a/third_party/tock b/third_party/tock index fbc863faf0c9615537ee52dcdccdfcb9204d2467..3139864d391ab654bfb9c27ca8dcd3e4e9a2d58e 160000 --- a/third_party/tock +++ b/third_party/tock @@ -1 +1 @@ -Subproject commit fbc863faf0c9615537ee52dcdccdfcb9204d2467 +Subproject commit 3139864d391ab654bfb9c27ca8dcd3e4e9a2d58e