Merge branch 'master' into wipe

This commit is contained in:
Julien Cretin
2020-03-18 10:59:24 +01:00
31 changed files with 1378 additions and 1199 deletions

View File

@@ -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",
]

View File

@@ -26,6 +26,12 @@ jobs:
run: python -m pip install --upgrade pip setuptools wheel run: python -m pip install --upgrade pip setuptools wheel
- name: Set up OpenSK - name: Set up OpenSK
run: ./setup.sh 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 - 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/ run: mkdir this-is-a-long-build-directory-0123456789abcdefghijklmnopqrstuvwxyz && mv third_party this-is-a-long-build-directory-0123456789abcdefghijklmnopqrstuvwxyz/

View File

@@ -64,17 +64,23 @@ jobs:
command: check command: check
args: --target thumbv7em-none-eabi --release --features ram_storage 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 - name: Check OpenSK debug_ctap,with_ctap1
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
command: check command: check
args: --target thumbv7em-none-eabi --release --features debug_ctap,with_ctap1 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 uses: actions-rs/cargo@v1
with: with:
command: check 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 - name: Check examples
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1

View File

@@ -24,8 +24,16 @@ jobs:
- name: Set up OpenSK - name: Set up OpenSK
run: ./setup.sh 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 - name: Building OpenSK
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
command: build command: build
args: --release --target=thumbv7em-none-eabi --features with_ctap1 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

View File

@@ -20,6 +20,7 @@
"python.linting.enabled": true, "python.linting.enabled": true,
"python.linting.lintOnSave": true, "python.linting.lintOnSave": true,
"python.linting.pylintEnabled": true, "python.linting.pylintEnabled": true,
"python.linting.pylintPath": "pylint",
"[python]": { "[python]": {
"editor.tabSize": 2 "editor.tabSize": 2
}, },

View File

@@ -18,12 +18,13 @@ arrayref = "0.3.6"
subtle = { version = "2.2", default-features = false, features = ["nightly"] } subtle = { version = "2.2", default-features = false, features = ["nightly"] }
[features] [features]
std = ["cbor/std", "crypto/std", "crypto/derive_debug"]
debug_ctap = ["crypto/derive_debug"]
with_ctap1 = ["crypto/with_ctap1"]
panic_console = ["libtock/panic_console"]
debug_allocations = ["libtock/debug_allocations"] debug_allocations = ["libtock/debug_allocations"]
debug_ctap = ["crypto/derive_debug"]
panic_console = ["libtock/panic_console"]
std = ["cbor/std", "crypto/std", "crypto/derive_debug"]
ram_storage = [] ram_storage = []
verbose = ["debug_ctap"]
with_ctap1 = ["crypto/with_ctap1"]
[dev-dependencies] [dev-dependencies]
elf2tab = "0.4.0" elf2tab = "0.4.0"

View File

@@ -19,8 +19,9 @@ successfully tested on the following boards:
## Disclaimer ## Disclaimer
This project is proof-of-concept and a research platform. It's still under This project is **proof-of-concept and a research platform**. It is **NOT**
development and as such comes with a few limitations: meant for a daily usage. It's still under development and as such comes with a
few limitations:
### FIDO2 ### FIDO2
@@ -53,25 +54,17 @@ For a more detailed guide, please refer to our
./setup.sh ./setup.sh
``` ```
2. If Tock OS is already installed on your board, move to the next step. 2. Next step is to install Tock OS as well as the OpenSK application on your
Otherwise, just run one of the following commands, depending on the board board (**Warning**: it will erase the locally stored credentials). Run:
you have:
```shell ```shell
# Nordic nRF52840-DK board # Nordic nRF52840-DK board
./deploy.py os --board=nrf52840_dk ./deploy.py --board=nrf52840dk --opensk
# Nordic nRF52840-Dongle # 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 3. On Linux, you may want to avoid the need for `root` privileges to interact
(**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
with the key. For that purpose we provide a udev rule file that can be with the key. For that purpose we provide a udev rule file that can be
installed with the following command: installed with the following command:

View File

@@ -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" }

View File

@@ -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)

View File

@@ -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");
}

View File

@@ -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

View File

@@ -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" }

View File

@@ -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)

View File

@@ -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");
}

View File

@@ -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

View File

@@ -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,
)
}

View File

@@ -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,
);
}

601
deploy.py
View File

@@ -20,6 +20,7 @@ from __future__ import division
from __future__ import print_function from __future__ import print_function
import argparse import argparse
import collections
import copy import copy
import os import os
import shutil import shutil
@@ -27,15 +28,125 @@ import subprocess
import sys import sys
import colorama import colorama
from six.moves import input
from tockloader import tab from tockloader import tab
from tockloader import tbfh from tockloader import tbfh
from tockloader import tockloader as loader from tockloader import tockloader as loader
from tockloader.exceptions import TockLoaderException 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 = { SUPPORTED_BOARDS = {
"nrf52840_dk": "third_party/tock/boards/nordic/nrf52840dk", "nrf52840dk":
"nrf52840_dongle": "third_party/tock/boards/nordic/nrf52840_dongle" 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 # 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(): def get_supported_boards():
boards = [] boards = []
for name, root in SUPPORTED_BOARDS.items(): for name, props in SUPPORTED_BOARDS.items():
if all((os.path.exists(os.path.join(root, "Cargo.toml")), if all((os.path.exists(os.path.join(props.path, "Cargo.toml")),
os.path.exists(os.path.join(root, "Makefile")))): (props.app_ldscript and os.path.exists(props.app_ldscript)))):
boards.append(name) boards.append(name)
return tuple(set(boards)) return tuple(set(boards))
@@ -79,9 +190,23 @@ def info(msg):
message=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): class RemoveConstAction(argparse.Action):
# pylint: disable=W0622 # pylint: disable=redefined-builtin
def __init__(self, def __init__(self,
option_strings, option_strings,
dest, dest,
@@ -121,28 +246,34 @@ class OpenSKInstaller:
self.args = args self.args = args
# Where all the TAB files should go # Where all the TAB files should go
self.tab_folder = os.path.join("target", "tab") self.tab_folder = os.path.join("target", "tab")
# This is the filename that elf2tab command expects in order board = SUPPORTED_BOARDS[self.args.board]
# to create a working TAB file.
self.target_elf_filename = os.path.join(self.tab_folder, "cortex-m4.elf")
self.tockloader_default_args = argparse.Namespace( self.tockloader_default_args = argparse.Namespace(
arch="cortex-m4", arch=board.arch,
board=getattr(self.args, "board", "nrf52840"), board=self.args.board,
debug=False, debug=False,
force=False, force=False,
jlink=True, jlink=self.args.programmer == "jlink",
jlink_device="nrf52840_xxaa", jlink_device=board.jlink_device,
jlink_if="swd", jlink_if=board.jlink_if,
jlink_speed=1200, jlink_speed=1200,
openocd=self.args.programmer == "openocd",
openocd_board=board.openocd_board,
jtag=False, jtag=False,
no_bootloader_entry=False, no_bootloader_entry=False,
page_size=4096, page_size=board.page_size,
port=None, port=None,
) )
def checked_command_output(self, cmd): def checked_command_output(self, cmd, env=None, cwd=None):
cmd_output = "" cmd_output = ""
try: 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: except subprocess.CalledProcessError as e:
fatal("Failed to execute {}: {}".format(cmd[0], str(e))) fatal("Failed to execute {}: {}".format(cmd[0], str(e)))
# Unreachable because fatal() will exit # Unreachable because fatal() will exit
@@ -166,38 +297,57 @@ class OpenSKInstaller:
# Need to update # Need to update
self.checked_command_output( self.checked_command_output(
["rustup", "install", target_toolchain_fullstring]) ["rustup", "install", target_toolchain_fullstring])
self.checked_command_output( self.checked_command_output(
["rustup", "target", "add", "thumbv7em-none-eabi"]) ["rustup", "target", "add", SUPPORTED_BOARDS[self.args.board].arch])
info("Rust toolchain up-to-date") info("Rust toolchain up-to-date")
def build_and_install_tockos(self): def build_tockos(self):
self.checked_command_output( info("Building Tock OS for board {}".format(self.args.board))
["make", "-C", SUPPORTED_BOARDS[self.args.board], "flash"]) 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): def build_example(self):
assert self.args.application info("Building example {}".format(self.args.application))
self.checked_command_output([ self._build_app_or_example(is_example=True)
"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_and_install_opensk(self): def build_opensk(self):
assert self.args.application
info("Building OpenSK application") info("Building OpenSK application")
self.checked_command_output([ self._build_app_or_example(is_example=False)
"cargo",
"build", def _build_app_or_example(self, is_example):
"--release", assert self.args.application
"--target=thumbv7em-none-eabi", # Ideally we would build a TAB file for all boards at once but depending on
"--features={}".format(",".join(self.args.features)), # 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
self.install_elf_file( # created for. So at the moment we only build for the selected board.
os.path.join("target/thumbv7em-none-eabi/release", props = SUPPORTED_BOARDS[self.args.board]
self.args.application)) 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): def generate_crypto_materials(self, force_regenerate):
has_error = subprocess.call([ has_error = subprocess.call([
@@ -208,8 +358,11 @@ class OpenSKInstaller:
error(("Something went wrong while trying to generate ECC " error(("Something went wrong while trying to generate ECC "
"key and/or certificate for OpenSK")) "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 assert self.args.application
info("Generating Tock TAB file for application/example {}".format(
self.args.application))
package_parameter = "-n" package_parameter = "-n"
elf2tab_ver = self.checked_command_output(["elf2tab", "--version"]).split( elf2tab_ver = self.checked_command_output(["elf2tab", "--version"]).split(
" ", maxsplit=1)[1] " ", maxsplit=1)[1]
@@ -221,17 +374,26 @@ class OpenSKInstaller:
os.makedirs(self.tab_folder, exist_ok=True) os.makedirs(self.tab_folder, exist_ok=True)
tab_filename = os.path.join(self.tab_folder, tab_filename = os.path.join(self.tab_folder,
"{}.tab".format(self.args.application)) "{}.tab".format(self.args.application))
shutil.copyfile(elf_path, self.target_elf_filename) elf2tab_args = [
self.checked_command_output([ "elf2tab", package_parameter, self.args.application, "-o", tab_filename
"elf2tab", package_parameter, self.args.application, "-o", tab_filename, ]
self.target_elf_filename, "--stack={}".format(STACK_SIZE), for arch, app_file in binaries.items():
"--app-heap={}".format(APP_HEAP_SIZE), "--kernel-heap=1024", dest_file = os.path.join(self.tab_folder, "{}.elf".format(arch))
"--protected-region-size=64" 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)) info("Installing Tock application {}".format(self.args.application))
board_props = SUPPORTED_BOARDS[self.args.board]
args = copy.copy(self.tockloader_default_args) 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, "erase", self.args.clear_apps)
setattr(args, "make", False) setattr(args, "make", False)
setattr(args, "no_replace", False) setattr(args, "no_replace", False)
@@ -244,16 +406,38 @@ class OpenSKInstaller:
fatal("Couldn't install Tock application {}: {}".format( fatal("Couldn't install Tock application {}: {}".format(
self.args.application, str(e))) self.args.application, str(e)))
def install_padding(self): def get_padding(self):
fake_header = tbfh.TBFHeader("") fake_header = tbfh.TBFHeader("")
fake_header.version = 2 fake_header.version = 2
fake_header.fields["header_size"] = 0x10 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 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") info("Flashing padding application")
args = copy.copy(self.tockloader_default_args) args = copy.copy(self.tockloader_default_args)
setattr(args, "address", 0x30000) setattr(args, "address", board_props.padding_address)
tock = loader.TockLoader(args) tock = loader.TockLoader(args)
tock.open(args) tock.open(args)
try: try:
@@ -263,7 +447,8 @@ class OpenSKInstaller:
def clear_apps(self): def clear_apps(self):
args = copy.copy(self.tockloader_default_args) 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") info("Erasing all installed applications")
tock = loader.TockLoader(args) tock = loader.TockLoader(args)
tock.open(args) tock.open(args)
@@ -271,11 +456,13 @@ class OpenSKInstaller:
tock.erase_apps(False) tock.erase_apps(False)
except TockLoaderException as e: except TockLoaderException as e:
# Erasing apps is not critical # 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)))) "apps: {}".format(str(e))))
# pylint: disable=W0212 # pylint: disable=protected-access
def verify_flashed_app(self, expected_app): def verify_flashed_app(self, expected_app):
if self.args.programmer not in ("jlink", "openocd"):
return False
args = copy.copy(self.tockloader_default_args) args = copy.copy(self.tockloader_default_args)
tock = loader.TockLoader(args) tock = loader.TockLoader(args)
app_found = False app_found = False
@@ -284,51 +471,205 @@ class OpenSKInstaller:
app_found = expected_app in apps app_found = expected_app in apps
return app_found 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): def run(self):
if self.args.action is None: if self.args.listing == "boards":
# Nothing to do print(os.linesep.join(get_supported_boards()))
return 0 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() self.update_rustc_if_needed()
if self.args.action == "os": if not self.args.tockos and not self.args.application:
info("Installing Tock on board {}".format(self.args.board)) info("Nothing to do.")
self.build_and_install_tockos()
return 0 return 0
if self.args.action == "app": # Compile what needs to be compiled
if self.args.application is None: if self.args.tockos:
fatal("Unspecified application") 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: if self.args.clear_apps:
self.clear_apps() self.clear_apps()
if self.args.application == "ctap2": if self.args.tockos:
self.generate_crypto_materials(self.args.regenerate_keys) # Install Tock OS
self.build_and_install_opensk() self.install_tock_os()
else: # Install padding and application if needed
self.build_and_install_example() if self.args.application:
if self.verify_flashed_app(self.args.application): self.install_padding()
info("You're all set!") self.install_tab_file("target/tab/{}.tab".format(self.args.application))
return 0 if self.verify_flashed_app(self.args.application):
error(("It seems that something went wrong. " info("You're all set!")
"App/example not found on your board.")) return 0
return 1 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 return 0
def main(args): def main(args):
# Make sure the current working directory is the right one before running # Make sure the current working directory is the right one before running
os.chdir(os.path.realpath(os.path.dirname(__file__))) 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() OpenSKInstaller(args).run()
if __name__ == "__main__": if __name__ == "__main__":
shared_parser = argparse.ArgumentParser(add_help=False) main_parser = argparse.ArgumentParser()
shared_parser.add_argument( 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", "--dont-clear-apps",
action="store_false", action="store_false",
default=True, default=True,
@@ -336,32 +677,26 @@ if __name__ == "__main__":
help=("When installing an application, previously installed " help=("When installing an application, previously installed "
"applications won't be erased from the board."), "applications won't be erased from the board."),
) )
main_parser.add_argument(
main_parser = argparse.ArgumentParser() "--programmer",
commands = main_parser.add_subparsers( metavar="METHOD",
dest="action", dest="programmer",
help=("Indicates which part of the firmware should be compiled and " choices=PROGRAMMERS,
"flashed to the connected board.")) default="jlink",
help=("Sets the method to be used to flash Tock OS or the application "
os_commands = commands.add_parser( "on the target board."),
"os",
parents=[shared_parser],
help=("Compiles and installs Tock OS. The target board must be "
"specified by setting the --board argument."),
) )
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( main_parser.add_argument(
"app", "--no-tockos",
parents=[shared_parser], action="store_false",
help="compiles and installs an application.") default=True,
app_commands.add_argument( dest="tockos",
help=("Only compiles and flash the application/example. "
"Otherwise TockOS will also be bundled and flashed."),
)
main_parser.add_argument(
"--panic-console", "--panic-console",
action="append_const", action="append_const",
const="panic_console", const="panic_console",
@@ -370,7 +705,16 @@ if __name__ == "__main__":
"output messages before starting blinking the LEDs on the " "output messages before starting blinking the LEDs on the "
"board."), "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", "--debug-allocations",
action="append_const", action="append_const",
const="debug_allocations", const="debug_allocations",
@@ -378,7 +722,15 @@ if __name__ == "__main__":
help=("The console will be used to output allocator statistics every " help=("The console will be used to output allocator statistics every "
"time an allocation/deallocation happens."), "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", "--no-u2f",
action=RemoveConstAction, action=RemoveConstAction,
const="with_ctap1", const="with_ctap1",
@@ -386,7 +738,7 @@ if __name__ == "__main__":
help=("Compiles the OpenSK application without backward compatible " help=("Compiles the OpenSK application without backward compatible "
"support for U2F/CTAP1 protocol."), "support for U2F/CTAP1 protocol."),
) )
app_commands.add_argument( main_parser.add_argument(
"--regen-keys", "--regen-keys",
action="store_true", action="store_true",
default=False, default=False,
@@ -396,16 +748,7 @@ if __name__ == "__main__":
"This is useful to allow flashing multiple OpenSK authenticators " "This is useful to allow flashing multiple OpenSK authenticators "
"in a row without them being considered clones."), "in a row without them being considered clones."),
) )
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)."),
)
app_commands.add_argument(
"--no-persistent-storage", "--no-persistent-storage",
action="append_const", action="append_const",
const="ram_storage", const="ram_storage",
@@ -413,7 +756,15 @@ if __name__ == "__main__":
help=("Compiles and installs the OpenSK application without persistent " help=("Compiles and installs the OpenSK application without persistent "
"storage (i.e. unplugging the key will reset the key)."), "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( apps_group.add_argument(
"--opensk", "--opensk",
dest="application", dest="application",
@@ -428,6 +779,6 @@ if __name__ == "__main__":
help=("Compiles and installs the crypto_bench example that tests " help=("Compiles and installs the crypto_bench example that tests "
"the performance of the cryptographic algorithms on the board.")) "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()) main(main_parser.parse_args())

View File

@@ -16,8 +16,9 @@ You will need one the following supported boards:
scenarios as the JTAG probe is already on the board. scenarios as the JTAG probe is already on the board.
* [Nordic nRF52840 Dongle](https://www.nordicsemi.com/Software-and-tools/Development-Kits/nRF52840-Dongle) * [Nordic nRF52840 Dongle](https://www.nordicsemi.com/Software-and-tools/Development-Kits/nRF52840-Dongle)
to have a more practical form factor. 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: hardware:
* a [Segger J-Link](https://www.segger.com/products/debug-probes/j-link/) JTAG * 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) [Tag-Connect TC2050 retainer clip](http://www.tag-connect.com/TC2050-CLIP)
to keep the spring loaded connector pressed to the PCB. to keep the spring loaded connector pressed to the PCB.
Although [OpenOCD](http://openocd.org/) should be supported we encountered some Additionnaly, OpenSK supports other ways to flash your board:
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.
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 ### 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. Connect a micro USB cable to the JTAG USB port.
1. Run our script for compiling/flashing Tock OS on your device (_output may 1. Run our script for compiling/flashing Tock OS and OpenSK on your device
differ_): (_output may differ_):
```shell ```shell
$ ./deploy.py os --board=nrf52840_dk $ ./deploy.py --board=nrf52840dk --opensk
info: Updating rust toolchain to nightly-2020-02-03 info: Updating rust toolchain to nightly-2020-02-03
info: syncing channel updates for 'nightly-2020-02-03-x86_64-unknown-linux-gnu' info: syncing channel updates for 'nightly-2020-02-03-x86_64-unknown-linux-gnu'
info: checking for self-updates info: checking for self-updates
info: component 'rust-std' for target 'thumbv7em-none-eabi' is up to date info: component 'rust-std' for target 'thumbv7em-none-eabi' is up to date
info: Rust toolchain 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-registers v0.5.0 (./third_party/tock/libraries/tock-register-interface)
Compiling tock-cells v0.1.0 (./third_party/tock/libraries/tock-cells) Compiling tock-cells v0.1.0 (./third_party/tock/libraries/tock-cells)
Compiling enum_primitive v0.1.0 (./third_party/tock/libraries/enum_primitive) 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 nrf52840 v0.1.0 (./third_party/tock/chips/nrf52840)
Compiling components v0.1.0 (./third_party/tock/boards/components) Compiling components v0.1.0 (./third_party/tock/boards/components)
Compiling nrf52dk_base v0.1.0 (./third_party/tock/boards/nordic/nrf52dk_base) Compiling nrf52dk_base v0.1.0 (./third_party/tock/boards/nordic/nrf52dk_base)
Finished release [optimized + debuginfo] target(s) in 11.97s Finished release [optimized + debuginfo] target(s) in 13.15s
[STATUS ] Flashing binar(y|ies) to board... info: Converting Tock OS file into a binary
[INFO ] Using known arch and jtag-device for known board nrf52dk info: Building OpenSK application
[INFO ] Finished in 0.284 seconds Finished release [optimized] target(s) in 0.02s
``` info: Generating Tock TAB file for application/example ctap2
info: Erasing all installed applications
1. Run our script for compiling/flashing the OpenSK application on your device All apps have been erased.
(_output may differ_): info: Flashing file third_party/tock/boards/nordic/nrf52840dk/target/thumbv7em-none-eabi/release/nrf52840dk.bin.
info: Flashing padding application
```shell info: Installing Tock application ctap2
$ ./deploy.py app --opensk info: You're all set!
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
``` ```
1. Connect a micro USB cable to the device USB port. 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 #### Nordic nRF52840 Dongle
##### Using external programmer (JLink, OpenOCD, etc.)
![Nordic dongle](img/dongle_front.jpg) ![Nordic dongle](img/dongle_front.jpg)
1. The JTAG probe used for programming won't provide power to the board. 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.
![Nordic dongle retainer clip](img/dongle_clip.jpg) ![Nordic dongle retainer clip](img/dongle_clip.jpg)
1. Run our script for compiling/flashing Tock OS on your device (_output may 1. Depending on the programmer you're using, you may have to adapt the next
differ_): command line. Run our script for compiling/flashing Tock OS on your device
(_output may differ_):
```shell ```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: Updating rust toolchain to nightly-2020-02-03
info: syncing channel updates for 'nightly-2020-02-03-x86_64-unknown-linux-gnu' info: syncing channel updates for 'nightly-2020-02-03-x86_64-unknown-linux-gnu'
info: checking for self-updates info: checking for self-updates
info: component 'rust-std' for target 'thumbv7em-none-eabi' is up to date info: component 'rust-std' for target 'thumbv7em-none-eabi' is up to date
info: Rust toolchain 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-cells v0.1.0 (./third_party/tock/libraries/tock-cells)
Compiling tock-registers v0.5.0 (./third_party/tock/libraries/tock-register-interface) 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) 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 components v0.1.0 (./third_party/tock/boards/components)
Compiling nrf52dk_base v0.1.0 (./third_party/tock/boards/nordic/nrf52dk_base) Compiling nrf52dk_base v0.1.0 (./third_party/tock/boards/nordic/nrf52dk_base)
Finished release [optimized + debuginfo] target(s) in 11.72s Finished release [optimized + debuginfo] target(s) in 11.72s
[STATUS ] Flashing binar(y|ies) to board... info: Converting Tock OS file into a binary
[INFO ] Using known arch and jtag-device for known board nrf52dk info: Building OpenSK application
[INFO ] Finished in 0.280 seconds Finished release [optimized] target(s) in 0.02s
``` info: Generating Tock TAB file for application/example ctap2
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 info: Erasing all installed applications
All apps have been erased. All apps have been erased.
info: Building OpenSK application info: Flashing file third_party/tock/boards/nordic/nrf52840_dongle/target/thumbv7em-none-eabi/release/nrf52840_dongle.bin.
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: Flashing padding application
info: Installing Tock application ctap2 info: Installing Tock application ctap2
info: You're all set!
``` ```
1. Remove the programming cable and the USB-A extension cable. 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) ### Installing the udev rule (Linux only)
By default on Linux, a USB device will require root privilege in order interact By default on Linux, a USB device will require root privilege in order interact

View File

@@ -286,10 +286,10 @@ index 5abd2d84..5a726fdb 100644
+ } + }
+} +}
diff --git a/kernel/src/callback.rs b/kernel/src/callback.rs 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 --- a/kernel/src/callback.rs
+++ b/kernel/src/callback.rs +++ b/kernel/src/callback.rs
@@ -52,6 +52,31 @@ impl AppId { @@ -130,6 +130,31 @@ impl AppId {
(start, end) (start, end)
}) })
} }

View File

@@ -124,11 +124,11 @@ index 105f7120..535e5cd8 100644
ipc: kernel::ipc::IPC::new(board_kernel, &memory_allocation_capability), ipc: kernel::ipc::IPC::new(board_kernel, &memory_allocation_capability),
nvmc: nvmc, nvmc: nvmc,
diff --git a/capsules/src/driver.rs b/capsules/src/driver.rs 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 --- a/capsules/src/driver.rs
+++ b/capsules/src/driver.rs +++ b/capsules/src/driver.rs
@@ -24,6 +24,7 @@ pub enum NUM { @@ -25,6 +25,7 @@ pub enum NUM {
Spi = 0x20001, I2cMaster = 0x20003,
UsbUser = 0x20005, UsbUser = 0x20005,
I2cMasterSlave = 0x20006, I2cMasterSlave = 0x20006,
+ UsbCtap = 0x20009, + 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 diff --git a/capsules/src/usb/usbc_ctap_hid.rs b/capsules/src/usb/usbc_ctap_hid.rs
new file mode 100644 new file mode 100644
index 00000000..fdf7263a index 00000000..4b1916cf
--- /dev/null --- /dev/null
+++ b/capsules/src/usb/usbc_ctap_hid.rs +++ 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 +//! A USB HID client of the USB hardware interface
+ +
+use super::descriptors::Buffer64; +use super::descriptors::Buffer64;
@@ -603,8 +603,9 @@ index 00000000..fdf7263a
+pub struct ClientCtapHID<'a, 'b, C: 'a> { +pub struct ClientCtapHID<'a, 'b, C: 'a> {
+ client_ctrl: ClientCtrl<'a, 'static, C>, + client_ctrl: ClientCtrl<'a, 'static, C>,
+ +
+ // A 64-byte buffer for the endpoint + // 64-byte buffers for the endpoint
+ buffer: Buffer64, + in_buffer: Buffer64,
+ out_buffer: Buffer64,
+ +
+ // Interaction with the client + // Interaction with the client
+ client: OptionalCell<&'b dyn CtapUsbClient>, + client: OptionalCell<&'b dyn CtapUsbClient>,
@@ -648,7 +649,8 @@ index 00000000..fdf7263a
+ LANGUAGES, + LANGUAGES,
+ STRINGS, + STRINGS,
+ ), + ),
+ buffer: Default::default(), + in_buffer: Default::default(),
+ out_buffer: Default::default(),
+ client: OptionalCell::empty(), + client: OptionalCell::empty(),
+ tx_packet: OptionalCell::empty(), + tx_packet: OptionalCell::empty(),
+ pending_in: Cell::new(false), + pending_in: Cell::new(false),
@@ -702,7 +704,7 @@ index 00000000..fdf7263a
+ fn send_packet_to_client(&'a self) -> bool { + fn send_packet_to_client(&'a self) -> bool {
+ // Copy the packet into a buffer to send to the client. + // Copy the packet into a buffer to send to the client.
+ let mut buf: [u8; 64] = [0; 64]; + 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(); + buf[i] = x.get();
+ } + }
+ +
@@ -735,11 +737,7 @@ index 00000000..fdf7263a
+ +
+ fn cancel_in_transaction(&'a self) -> bool { + fn cancel_in_transaction(&'a self) -> bool {
+ self.tx_packet.take(); + self.tx_packet.take();
+ let result = self.pending_in.take(); + self.pending_in.take()
+ if result {
+ self.controller().endpoint_cancel_in(1);
+ }
+ result
+ } + }
+ +
+ fn cancel_out_transaction(&'a self) -> bool { + fn cancel_out_transaction(&'a self) -> bool {
@@ -758,7 +756,10 @@ index 00000000..fdf7263a
+ self.client_ctrl.enable(); + self.client_ctrl.enable();
+ +
+ // Set up the interrupt in-out endpoint + // 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() + self.controller()
+ .endpoint_in_out_enable(TransferType::Interrupt, 1); + .endpoint_in_out_enable(TransferType::Interrupt, 1);
+ } + }
@@ -808,7 +809,7 @@ index 00000000..fdf7263a
+ } + }
+ +
+ if let Some(packet) = self.tx_packet.take() { + if let Some(packet) = self.tx_packet.take() {
+ let buf = &self.buffer.buf; + let buf = &self.in_buffer.buf;
+ for i in 0..64 { + for i in 0..64 {
+ buf[i].set(packet[i]); + buf[i].set(packet[i]);
+ } + }
@@ -861,149 +862,13 @@ index 00000000..fdf7263a
+ panic!("Unexpected tx_packet while a packet was being transmitted."); + panic!("Unexpected tx_packet while a packet was being transmitted.");
+ } + }
+ self.pending_in.set(false); + 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 + // Notify the client
+ self.client.map(|client| client.packet_transmitted()); + 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)]

View File

@@ -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
}

View File

@@ -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();
}

View File

@@ -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)]

View File

@@ -24,6 +24,9 @@ cd libraries/crypto
cargo fmt --all -- --check cargo fmt --all -- --check
cd ../.. cd ../..
echo "Building sha256sum tool..."
cargo build --manifest-path third_party/tock/tools/sha256sum/Cargo.toml
echo "Checking that CTAP2 builds properly..." echo "Checking that CTAP2 builds properly..."
cargo check --release --target=thumbv7em-none-eabi cargo check --release --target=thumbv7em-none-eabi
cargo check --release --target=thumbv7em-none-eabi --features with_ctap1 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 panic_console
cargo check --release --target=thumbv7em-none-eabi --features debug_allocations 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 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
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..." echo "Checking that examples build properly..."
cargo check --release --target=thumbv7em-none-eabi --examples cargo check --release --target=thumbv7em-none-eabi --examples
echo "Checking that CTAP2 builds and links properly (1 set of features)..." echo "Checking that CTAP2 builds and links properly (1 set of features)..."
cargo build --release --target=thumbv7em-none-eabi --features with_ctap1 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..." echo "Checking that supported boards build properly..."
make -C third_party/tock/boards/nordic/nrf52840dk make -C third_party/tock/boards/nordic/nrf52840dk
make -C third_party/tock/boards/nordic/nrf52840_dongle 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" ] if [ -z "${TRAVIS_OS_NAME}" -o "${TRAVIS_OS_NAME}" = "linux" ]
then then
echo "Running unit tests on the desktop (release mode)..." echo "Running unit tests on the desktop (release mode)..."

View File

@@ -81,7 +81,7 @@ source tools/gen_key_materials.sh
generate_crypto_materials N generate_crypto_materials N
rustup install $(head -n 1 rust-toolchain) 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 rustup target add thumbv7em-none-eabi
# Install dependency to create applications. # Install dependency to create applications.

View File

@@ -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. // Even though options are optional, we can use the default if not present.
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))] #[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
pub struct MakeCredentialOptions { pub struct MakeCredentialOptions {
@@ -314,6 +386,7 @@ pub struct PublicKeyCredentialSource {
pub rp_id: String, pub rp_id: String,
pub user_handle: Vec<u8>, // not optional, but nullable pub user_handle: Vec<u8>, // not optional, but nullable
pub other_ui: Option<String>, pub other_ui: Option<String>,
pub cred_random: Option<Vec<u8>>,
} }
impl From<PublicKeyCredentialSource> for cbor::Value { impl From<PublicKeyCredentialSource> for cbor::Value {
@@ -324,12 +397,17 @@ impl From<PublicKeyCredentialSource> for cbor::Value {
None => cbor_null!(), None => cbor_null!(),
Some(other_ui) => cbor_text!(other_ui), 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! { cbor_array! {
credential.credential_id, credential.credential_id,
private_key, private_key,
credential.rp_id, credential.rp_id,
credential.user_handle, credential.user_handle,
other_ui, other_ui,
cred_random,
} }
} }
} }
@@ -341,7 +419,7 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialSource {
use cbor::{SimpleValue, Value}; use cbor::{SimpleValue, Value};
let fields = read_array(&cbor_value)?; let fields = read_array(&cbor_value)?;
if fields.len() != 5 { if fields.len() != 6 {
return Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR); return Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR);
} }
let credential_id = read_byte_string(&fields[0])?; let credential_id = read_byte_string(&fields[0])?;
@@ -357,6 +435,10 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialSource {
Value::Simple(SimpleValue::NullValue) => None, Value::Simple(SimpleValue::NullValue) => None,
cbor_value => Some(read_text_string(cbor_value)?), 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 { Ok(PublicKeyCredentialSource {
key_type: PublicKeyCredentialType::PublicKey, key_type: PublicKeyCredentialType::PublicKey,
credential_id, credential_id,
@@ -364,6 +446,7 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialSource {
rp_id, rp_id,
user_handle, user_handle,
other_ui, other_ui,
cred_random,
}) })
} }
} }
@@ -993,6 +1076,7 @@ mod test {
rp_id: "example.com".to_string(), rp_id: "example.com".to_string(),
user_handle: b"foo".to_vec(), user_handle: b"foo".to_vec(),
other_ui: None, other_ui: None,
cred_random: None,
}; };
assert_eq!( assert_eq!(
@@ -1005,6 +1089,16 @@ mod test {
..credential ..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!( assert_eq!(
PublicKeyCredentialSource::try_from(cbor::Value::from(credential.clone())), PublicKeyCredentialSource::try_from(cbor::Value::from(credential.clone())),
Ok(credential) Ok(credential)

View File

@@ -28,9 +28,9 @@ use self::command::{
AuthenticatorMakeCredentialParameters, Command, AuthenticatorMakeCredentialParameters, Command,
}; };
use self::data_formats::{ use self::data_formats::{
ClientPinSubCommand, CoseKey, PackedAttestationStatement, PublicKeyCredentialDescriptor, ClientPinSubCommand, CoseKey, GetAssertionHmacSecretInput, PackedAttestationStatement,
PublicKeyCredentialSource, PublicKeyCredentialType, PublicKeyCredentialUserEntity, PublicKeyCredentialDescriptor, PublicKeyCredentialSource, PublicKeyCredentialType,
SignatureAlgorithm, PublicKeyCredentialUserEntity, SignatureAlgorithm,
}; };
use self::hid::ChannelID; use self::hid::ChannelID;
use self::key_material::{AAGUID, ATTESTATION_CERTIFICATE, ATTESTATION_PRIVATE_KEY}; 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 relying party ID hashed with SHA256,
// - 32 byte HMAC-SHA256 over everything else. // - 32 byte HMAC-SHA256 over everything else.
pub const ENCRYPTED_CREDENTIAL_ID_SIZE: usize = 112; pub const ENCRYPTED_CREDENTIAL_ID_SIZE: usize = 112;
// Set this bit when checking user presence.
const UP_FLAG: u8 = 0x01; const UP_FLAG: u8 = 0x01;
// Set this bit when checking user verification.
const UV_FLAG: u8 = 0x04; const UV_FLAG: u8 = 0x04;
// Set this bit when performing attestation.
const AT_FLAG: u8 = 0x40; 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; pub const TOUCH_TIMEOUT_MS: isize = 30000;
#[cfg(feature = "with_ctap1")] #[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 // 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. // (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. // We change the return value, since we don't need the bool.
@@ -261,6 +323,7 @@ where
rp_id: String::from(""), rp_id: String::from(""),
user_handle: vec![], user_handle: vec![],
other_ui: None, other_ui: None,
cred_random: None,
}) })
} }
@@ -324,10 +387,10 @@ where
user, user,
pub_key_cred_params, pub_key_cred_params,
exclude_list, exclude_list,
extensions,
options, options,
pin_uv_auth_param, pin_uv_auth_param,
pin_uv_auth_protocol, pin_uv_auth_protocol,
..
} = make_credential_params; } = make_credential_params;
if let Some(auth_param) = &pin_uv_auth_param { if let Some(auth_param) = &pin_uv_auth_param {
@@ -362,6 +425,19 @@ where
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM); 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; let rp_id = rp.rp_id;
if let Some(exclude_list) = exclude_list { if let Some(exclude_list) = exclude_list {
for cred_desc in 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) { if !check_pin_auth(&self.pin_uv_auth_token, &client_data_hash, &pin_auth) {
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID); return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID);
} }
UP_FLAG | UV_FLAG | AT_FLAG UP_FLAG | UV_FLAG | AT_FLAG | ed_flag
} }
None => { None => {
if self.persistent_store.pin_hash().is_some() { if self.persistent_store.pin_hash().is_some() {
@@ -398,7 +474,7 @@ where
if options.uv { if options.uv {
return Err(Ctap2StatusCode::CTAP2_ERR_INVALID_OPTION); 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 other_ui: user
.user_display_name .user_display_name
.map(|s| truncate_to_char_boundary(&s, 64).to_string()), .map(|s| truncate_to_char_boundary(&s, 64).to_string()),
cred_random,
}; };
self.persistent_store.store_credential(credential_source)?; self.persistent_store.store_credential(credential_source)?;
random_id random_id
@@ -441,6 +518,14 @@ where
None => return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_RESPONSE_CANNOT_WRITE_CBOR), None => return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_RESPONSE_CANNOT_WRITE_CBOR),
}; };
auth_data.extend(cose_key); 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(); let mut signature_data = auth_data.clone();
signature_data.extend(client_data_hash); signature_data.extend(client_data_hash);
@@ -481,10 +566,10 @@ where
rp_id, rp_id,
client_data_hash, client_data_hash,
allow_list, allow_list,
extensions,
options, options,
pin_uv_auth_param, pin_uv_auth_param,
pin_uv_auth_protocol, pin_uv_auth_protocol,
..
} = get_assertion_params; } = get_assertion_params;
if let Some(auth_param) = &pin_uv_auth_param { 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 // The user verification bit depends on the existance of PIN auth, whereas
// user presence is requested as an option. // user presence is requested as an option.
let mut flags = match pin_uv_auth_param { let mut flags = match pin_uv_auth_param {
@@ -551,6 +645,9 @@ where
if options.up { if options.up {
flags |= UP_FLAG; 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 rp_id_hash = Sha256::hash(rp_id.as_bytes());
let mut decrypted_credential = None; let mut decrypted_credential = None;
@@ -590,7 +687,36 @@ where
self.increment_global_signature_counter(); 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(); let mut signature_data = auth_data.clone();
signature_data.extend(client_data_hash); signature_data.extend(client_data_hash);
let signature = credential let signature = credential
@@ -639,7 +765,7 @@ where
String::from(U2F_VERSION_STRING), String::from(U2F_VERSION_STRING),
String::from(FIDO2_VERSION_STRING), String::from(FIDO2_VERSION_STRING),
], ],
extensions: Some(vec![]), extensions: Some(vec![String::from("hmac-secret")]),
aaguid: *AAGUID, aaguid: *AAGUID,
options: Some(options_map), options: Some(options_map),
max_msg_size: Some(1024), max_msg_size: Some(1024),
@@ -948,7 +1074,7 @@ where
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::data_formats::{ use super::data_formats::{
GetAssertionOptions, MakeCredentialOptions, PublicKeyCredentialRpEntity, Extensions, GetAssertionOptions, MakeCredentialOptions, PublicKeyCredentialRpEntity,
PublicKeyCredentialUserEntity, PublicKeyCredentialUserEntity,
}; };
use super::*; use super::*;
@@ -970,13 +1096,15 @@ mod test {
let mut expected_response = vec![0x00, 0xA6, 0x01]; let mut expected_response = vec![0x00, 0xA6, 0x01];
// The difference here is a longer array of supported versions. // The difference here is a longer array of supported versions.
#[cfg(not(feature = "with_ctap1"))] #[cfg(not(feature = "with_ctap1"))]
expected_response.extend(&[ expected_response.extend(&[0x81, 0x68, 0x46, 0x49, 0x44, 0x4F, 0x5F, 0x32, 0x5F, 0x30]);
0x81, 0x68, 0x46, 0x49, 0x44, 0x4F, 0x5F, 0x32, 0x5F, 0x30, 0x02, 0x80, 0x03, 0x50,
]);
#[cfg(feature = "with_ctap1")] #[cfg(feature = "with_ctap1")]
expected_response.extend(&[ expected_response.extend(&[
0x82, 0x66, 0x55, 0x32, 0x46, 0x5F, 0x56, 0x32, 0x68, 0x46, 0x49, 0x44, 0x4F, 0x5F, 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(AAGUID);
expected_response.extend(&[ expected_response.extend(&[
@@ -1130,6 +1258,7 @@ mod test {
rp_id: String::from("example.com"), rp_id: String::from("example.com"),
user_handle: vec![], user_handle: vec![],
other_ui: None, other_ui: None,
cred_random: None,
}; };
assert!(ctap_state assert!(ctap_state
.persistent_store .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] #[test]
fn test_process_make_credential_cancelled() { fn test_process_make_credential_cancelled() {
let mut rng = ThreadRng256 {}; 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] #[test]
fn test_process_reset() { fn test_process_reset() {
let mut rng = ThreadRng256 {}; let mut rng = ThreadRng256 {};
@@ -1231,6 +1455,7 @@ mod test {
rp_id: String::from("example.com"), rp_id: String::from("example.com"),
user_handle: vec![], user_handle: vec![],
other_ui: None, other_ui: None,
cred_random: None,
}; };
assert!(ctap_state assert!(ctap_state
.persistent_store .persistent_store
@@ -1294,4 +1519,32 @@ mod test {
.is_none()); .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)
);
}
} }

View File

@@ -453,6 +453,7 @@ mod test {
rp_id: String::from(rp_id), rp_id: String::from(rp_id),
user_handle, user_handle,
other_ui: None, other_ui: None,
cred_random: None,
} }
} }
@@ -621,6 +622,7 @@ mod test {
rp_id: String::from("example.com"), rp_id: String::from("example.com"),
user_handle: vec![0x00], user_handle: vec![0x00],
other_ui: None, other_ui: None,
cred_random: None,
}; };
assert_eq!(found_credential, Some(expected_credential)); assert_eq!(found_credential, Some(expected_credential));
} }

View File

@@ -165,6 +165,57 @@ pub fn send_or_recv(buf: &mut [u8; 64]) -> SendOrRecvStatus {
pub fn recv_with_timeout( pub fn recv_with_timeout(
buf: &mut [u8; 64], buf: &mut [u8; 64],
timeout_delay: Duration<isize>, 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> { ) -> Option<SendOrRecvStatus> {
let result = syscalls::allow(DRIVER_NUMBER, allow_nr::RECEIVE, buf); let result = syscalls::allow(DRIVER_NUMBER, allow_nr::RECEIVE, buf);
if result.is_err() { if result.is_err() {
@@ -225,7 +276,7 @@ pub fn recv_with_timeout(
// Cancel USB transaction if necessary. // Cancel USB transaction if necessary.
if status.get().is_none() { if status.get().is_none() {
#[cfg(feature = "debug_ctap")] #[cfg(feature = "verbose")]
writeln!(Console::new(), "Cancelling USB receive due to timeout").unwrap(); writeln!(Console::new(), "Cancelling USB receive due to timeout").unwrap();
let result_code = unsafe { syscalls::command(DRIVER_NUMBER, command_nr::CANCEL, 0, 0) }; let result_code = unsafe { syscalls::command(DRIVER_NUMBER, command_nr::CANCEL, 0, 0) };
match result_code { match result_code {
@@ -249,9 +300,7 @@ pub fn recv_with_timeout(
status.get() status.get()
} }
// Same as send_or_recv, but with a timeout. fn send_or_recv_with_timeout_detail(
// If the timeout elapses, return None.
pub fn send_or_recv_with_timeout(
buf: &mut [u8; 64], buf: &mut [u8; 64],
timeout_delay: Duration<isize>, timeout_delay: Duration<isize>,
) -> Option<SendOrRecvStatus> { ) -> Option<SendOrRecvStatus> {
@@ -317,7 +366,7 @@ pub fn send_or_recv_with_timeout(
// Cancel USB transaction if necessary. // Cancel USB transaction if necessary.
if status.get().is_none() { if status.get().is_none() {
#[cfg(feature = "debug_ctap")] #[cfg(feature = "verbose")]
writeln!(Console::new(), "Cancelling USB transaction due to timeout").unwrap(); writeln!(Console::new(), "Cancelling USB transaction due to timeout").unwrap();
let result_code = unsafe { syscalls::command(DRIVER_NUMBER, command_nr::CANCEL, 0, 0) }; let result_code = unsafe { syscalls::command(DRIVER_NUMBER, command_nr::CANCEL, 0, 0) };
match result_code { match result_code {