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
- name: Set up OpenSK
run: ./setup.sh
- name: Building board nrf52840_dongle_dfu
run: ./deploy.py --board=nrf52840_dongle_dfu --no-app --programmer=none
- name: Building board nrf52840_mdk_dfu
run: ./deploy.py --board=nrf52840_mdk_dfu --no-app --programmer=none
- name: Create a long build directory
run: mkdir this-is-a-long-build-directory-0123456789abcdefghijklmnopqrstuvwxyz && mv third_party this-is-a-long-build-directory-0123456789abcdefghijklmnopqrstuvwxyz/

View File

@@ -64,17 +64,23 @@ jobs:
command: check
args: --target thumbv7em-none-eabi --release --features ram_storage
- name: Check OpenSK verbose
uses: actions-rs/cargo@v1
with:
command: check
args: --target thumbv7em-none-eabi --release --features verbose
- name: Check OpenSK debug_ctap,with_ctap1
uses: actions-rs/cargo@v1
with:
command: check
args: --target thumbv7em-none-eabi --release --features debug_ctap,with_ctap1
- name: Check OpenSK debug_ctap,with_ctap1,panic_console,debug_allocations
- name: Check OpenSK debug_ctap,with_ctap1,panic_console,debug_allocations,verbose
uses: actions-rs/cargo@v1
with:
command: check
args: --target thumbv7em-none-eabi --release --features debug_ctap,with_ctap1,panic_console,debug_allocations
args: --target thumbv7em-none-eabi --release --features debug_ctap,with_ctap1,panic_console,debug_allocations,verbose
- name: Check examples
uses: actions-rs/cargo@v1

View File

@@ -24,8 +24,16 @@ jobs:
- name: Set up OpenSK
run: ./setup.sh
- name: Building sha256sum tool
uses: actions-rs/cargo@v1
with:
command: build
args: --manifest-path third_party/tock/tools/sha256sum/Cargo.toml
- name: Building OpenSK
uses: actions-rs/cargo@v1
with:
command: build
args: --release --target=thumbv7em-none-eabi --features with_ctap1
- name: Compute SHA-256 sum
run: ./third_party/tock/tools/sha256sum/target/debug/sha256sum target/thumbv7em-none-eabi/release/ctap2

View File

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

View File

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

View File

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

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
import argparse
import collections
import copy
import os
import shutil
@@ -27,15 +28,125 @@ import subprocess
import sys
import colorama
from six.moves import input
from tockloader import tab
from tockloader import tbfh
from tockloader import tockloader as loader
from tockloader.exceptions import TockLoaderException
# This structure allows us in the future to also support out-of-tree boards.
PROGRAMMERS = frozenset(("jlink", "openocd", "pyocd", "nordicdfu", "none"))
# This structure allows us to support out-of-tree boards as well as (in the
# future) more achitectures.
OpenSKBoard = collections.namedtuple(
"OpenSKBoard",
[
# Location of the Tock board (where the Makefile file is)
"path",
# Target architecture (e.g. thumbv7em-none-eabi)
"arch",
# Size of 1 page of flash memory
"page_size",
# Flash address at which the kernel will be written
"kernel_address",
# Set to None is padding is not required for the board.
# This creates a fake Tock OS application that starts at the
# address specified by this parameter (must match the `prog` value
# specified on the board's `layout.ld` file) and will end at
# `app_address`.
"padding_address",
# Linker script to produce a working app for this board
"app_ldscript",
# Flash address at which the app should be written
"app_address",
# Target name for flashing the board using pyOCD
"pyocd_target",
# The cfg file in OpenOCD board folder
"openocd_board",
# Options to tell Tockloader how to work with OpenOCD
# Default: []
"openocd_options",
# Dictionnary specifying custom commands for OpenOCD
# Default is an empty dict
# Valid keys are: program, read, erase
"openocd_commands",
# Interface to use with JLink (e.g. swd, jtag, etc.)
"jlink_if",
# Device name as supported by JLinkExe
"jlink_device",
# Whether Nordic DFU flashing method is supported
"nordic_dfu",
])
SUPPORTED_BOARDS = {
"nrf52840_dk": "third_party/tock/boards/nordic/nrf52840dk",
"nrf52840_dongle": "third_party/tock/boards/nordic/nrf52840_dongle"
"nrf52840dk":
OpenSKBoard(
path="third_party/tock/boards/nordic/nrf52840dk",
arch="thumbv7em-none-eabi",
page_size=4096,
kernel_address=0,
padding_address=0x30000,
app_ldscript="nrf52840dk_layout.ld",
app_address=0x40000,
pyocd_target="nrf52840",
openocd_board="nordic_nrf52840_dongle.cfg",
openocd_options=[],
openocd_commands={},
jlink_if="swd",
jlink_device="nrf52840_xxaa",
nordic_dfu=False,
),
"nrf52840_dongle":
OpenSKBoard(
path="third_party/tock/boards/nordic/nrf52840_dongle",
arch="thumbv7em-none-eabi",
page_size=4096,
kernel_address=0,
padding_address=0x30000,
app_ldscript="nrf52840dk_layout.ld",
app_address=0x40000,
pyocd_target="nrf52840",
openocd_board="nordic_nrf52840_dongle.cfg",
openocd_options=[],
openocd_commands={},
jlink_if="swd",
jlink_device="nrf52840_xxaa",
nordic_dfu=False,
),
"nrf52840_dongle_dfu":
OpenSKBoard(
path="boards/nrf52840_dongle_dfu",
arch="thumbv7em-none-eabi",
page_size=4096,
kernel_address=0x1000,
padding_address=0x30000,
app_ldscript="nrf52840dk_layout.ld",
app_address=0x40000,
pyocd_target="nrf52840",
openocd_board="nordic_nrf52840_dongle.cfg",
openocd_options=[],
openocd_commands={},
jlink_if="swd",
jlink_device="nrf52840_xxaa",
nordic_dfu=True,
),
"nrf52840_mdk_dfu":
OpenSKBoard(
path="boards/nrf52840_mdk_dfu",
arch="thumbv7em-none-eabi",
page_size=4096,
kernel_address=0x1000,
padding_address=0x30000,
app_ldscript="nrf52840dk_layout.ld",
app_address=0x40000,
pyocd_target="nrf52840",
openocd_board="nordic_nrf52840_dongle.cfg",
openocd_options=[],
openocd_commands={},
jlink_if="swd",
jlink_device="nrf52840_xxaa",
nordic_dfu=True,
),
}
# The STACK_SIZE value below must match the one used in the linker script
@@ -50,9 +161,9 @@ APP_HEAP_SIZE = 90000
def get_supported_boards():
boards = []
for name, root in SUPPORTED_BOARDS.items():
if all((os.path.exists(os.path.join(root, "Cargo.toml")),
os.path.exists(os.path.join(root, "Makefile")))):
for name, props in SUPPORTED_BOARDS.items():
if all((os.path.exists(os.path.join(props.path, "Cargo.toml")),
(props.app_ldscript and os.path.exists(props.app_ldscript)))):
boards.append(name)
return tuple(set(boards))
@@ -79,9 +190,23 @@ def info(msg):
message=msg))
def assert_mandatory_binary(binary):
if not shutil.which(binary):
fatal(("Couldn't find {} binary. Make sure it is installed and "
"that your PATH is set correctly.").format(binary))
def assert_python_library(module):
try:
__import__(module)
except ModuleNotFoundError:
fatal(("Couldn't load python3 module {name}. "
"Try to run: pip3 install {name}").format(name=module))
class RemoveConstAction(argparse.Action):
# pylint: disable=W0622
# pylint: disable=redefined-builtin
def __init__(self,
option_strings,
dest,
@@ -121,28 +246,34 @@ class OpenSKInstaller:
self.args = args
# Where all the TAB files should go
self.tab_folder = os.path.join("target", "tab")
# This is the filename that elf2tab command expects in order
# to create a working TAB file.
self.target_elf_filename = os.path.join(self.tab_folder, "cortex-m4.elf")
board = SUPPORTED_BOARDS[self.args.board]
self.tockloader_default_args = argparse.Namespace(
arch="cortex-m4",
board=getattr(self.args, "board", "nrf52840"),
arch=board.arch,
board=self.args.board,
debug=False,
force=False,
jlink=True,
jlink_device="nrf52840_xxaa",
jlink_if="swd",
jlink=self.args.programmer == "jlink",
jlink_device=board.jlink_device,
jlink_if=board.jlink_if,
jlink_speed=1200,
openocd=self.args.programmer == "openocd",
openocd_board=board.openocd_board,
jtag=False,
no_bootloader_entry=False,
page_size=4096,
page_size=board.page_size,
port=None,
)
def checked_command_output(self, cmd):
def checked_command_output(self, cmd, env=None, cwd=None):
cmd_output = ""
try:
cmd_output = subprocess.check_output(cmd)
cmd_output = subprocess.run(
cmd,
stdout=subprocess.PIPE,
timeout=None,
check=True,
env=env,
cwd=cwd).stdout
except subprocess.CalledProcessError as e:
fatal("Failed to execute {}: {}".format(cmd[0], str(e)))
# Unreachable because fatal() will exit
@@ -166,38 +297,57 @@ class OpenSKInstaller:
# Need to update
self.checked_command_output(
["rustup", "install", target_toolchain_fullstring])
self.checked_command_output(
["rustup", "target", "add", "thumbv7em-none-eabi"])
self.checked_command_output(
["rustup", "target", "add", SUPPORTED_BOARDS[self.args.board].arch])
info("Rust toolchain up-to-date")
def build_and_install_tockos(self):
self.checked_command_output(
["make", "-C", SUPPORTED_BOARDS[self.args.board], "flash"])
def build_tockos(self):
info("Building Tock OS for board {}".format(self.args.board))
props = SUPPORTED_BOARDS[self.args.board]
out_directory = os.path.join(props.path, "target", props.arch, "release")
os.makedirs(out_directory, exist_ok=True)
self.checked_command_output(["make"], cwd=props.path)
def build_and_install_example(self):
assert self.args.application
self.checked_command_output([
"cargo", "build", "--release", "--target=thumbv7em-none-eabi",
"--features={}".format(",".join(self.args.features)), "--example",
self.args.application
])
self.install_elf_file(
os.path.join("target/thumbv7em-none-eabi/release/examples",
self.args.application))
def build_example(self):
info("Building example {}".format(self.args.application))
self._build_app_or_example(is_example=True)
def build_and_install_opensk(self):
assert self.args.application
def build_opensk(self):
info("Building OpenSK application")
self.checked_command_output([
"cargo",
"build",
"--release",
"--target=thumbv7em-none-eabi",
"--features={}".format(",".join(self.args.features)),
])
self.install_elf_file(
os.path.join("target/thumbv7em-none-eabi/release",
self.args.application))
self._build_app_or_example(is_example=False)
def _build_app_or_example(self, is_example):
assert self.args.application
# Ideally we would build a TAB file for all boards at once but depending on
# the chip on the board, the link script could be totally different.
# And elf2tab doesn't seem to let us set the boards a TAB file has been
# created for. So at the moment we only build for the selected board.
props = SUPPORTED_BOARDS[self.args.board]
rust_flags = [
"-C",
"link-arg=-T{}".format(props.app_ldscript),
"-C",
"relocation-model=static",
"-D",
"warnings",
"--remap-path-prefix={}=".format(os.getcwd()),
]
env = os.environ.copy()
env["RUSTFLAGS"] = " ".join(rust_flags)
command = [
"cargo", "build", "--release", "--target={}".format(props.arch),
"--features={}".format(",".join(self.args.features))
]
if is_example:
command.extend(["--example", self.args.application])
self.checked_command_output(command, env=env)
app_path = os.path.join("target", props.arch, "release")
if is_example:
app_path = os.path.join(app_path, "examples")
app_path = os.path.join(app_path, self.args.application)
# Create a TAB file
self.create_tab_file({props.arch: app_path})
def generate_crypto_materials(self, force_regenerate):
has_error = subprocess.call([
@@ -208,8 +358,11 @@ class OpenSKInstaller:
error(("Something went wrong while trying to generate ECC "
"key and/or certificate for OpenSK"))
def install_elf_file(self, elf_path):
def create_tab_file(self, binaries):
assert binaries
assert self.args.application
info("Generating Tock TAB file for application/example {}".format(
self.args.application))
package_parameter = "-n"
elf2tab_ver = self.checked_command_output(["elf2tab", "--version"]).split(
" ", maxsplit=1)[1]
@@ -221,17 +374,26 @@ class OpenSKInstaller:
os.makedirs(self.tab_folder, exist_ok=True)
tab_filename = os.path.join(self.tab_folder,
"{}.tab".format(self.args.application))
shutil.copyfile(elf_path, self.target_elf_filename)
self.checked_command_output([
"elf2tab", package_parameter, self.args.application, "-o", tab_filename,
self.target_elf_filename, "--stack={}".format(STACK_SIZE),
"--app-heap={}".format(APP_HEAP_SIZE), "--kernel-heap=1024",
"--protected-region-size=64"
elf2tab_args = [
"elf2tab", package_parameter, self.args.application, "-o", tab_filename
]
for arch, app_file in binaries.items():
dest_file = os.path.join(self.tab_folder, "{}.elf".format(arch))
shutil.copyfile(app_file, dest_file)
elf2tab_args.append(dest_file)
elf2tab_args.extend([
"--stack={}".format(STACK_SIZE), "--app-heap={}".format(APP_HEAP_SIZE),
"--kernel-heap=1024", "--protected-region-size=64"
])
self.install_padding()
self.checked_command_output(elf2tab_args)
def install_tab_file(self, tab_filename):
assert self.args.application
info("Installing Tock application {}".format(self.args.application))
board_props = SUPPORTED_BOARDS[self.args.board]
args = copy.copy(self.tockloader_default_args)
setattr(args, "app_address", 0x40000)
setattr(args, "app_address", board_props.app_address)
setattr(args, "erase", self.args.clear_apps)
setattr(args, "make", False)
setattr(args, "no_replace", False)
@@ -244,16 +406,38 @@ class OpenSKInstaller:
fatal("Couldn't install Tock application {}: {}".format(
self.args.application, str(e)))
def install_padding(self):
def get_padding(self):
fake_header = tbfh.TBFHeader("")
fake_header.version = 2
fake_header.fields["header_size"] = 0x10
fake_header.fields["total_size"] = 0x10000
fake_header.fields["total_size"] = (
SUPPORTED_BOARDS[self.args.board].app_address -
SUPPORTED_BOARDS[self.args.board].padding_address)
fake_header.fields["flags"] = 0
padding = fake_header.get_binary()
return fake_header.get_binary()
def install_tock_os(self):
board_props = SUPPORTED_BOARDS[self.args.board]
kernel_file = os.path.join(board_props.path, "target", board_props.arch,
"release", "{}.bin".format(self.args.board))
info("Flashing file {}.".format(kernel_file))
with open(kernel_file, "rb") as f:
kernel = f.read()
args = copy.copy(self.tockloader_default_args)
setattr(args, "address", board_props.app_address)
tock = loader.TockLoader(args)
tock.open(args)
try:
tock.flash_binary(kernel, board_props.kernel_address)
except TockLoaderException as e:
fatal("Couldn't install Tock OS: {}".format(str(e)))
def install_padding(self):
padding = self.get_padding()
board_props = SUPPORTED_BOARDS[self.args.board]
info("Flashing padding application")
args = copy.copy(self.tockloader_default_args)
setattr(args, "address", 0x30000)
setattr(args, "address", board_props.padding_address)
tock = loader.TockLoader(args)
tock.open(args)
try:
@@ -263,7 +447,8 @@ class OpenSKInstaller:
def clear_apps(self):
args = copy.copy(self.tockloader_default_args)
setattr(args, "app_address", 0x40000)
board_props = SUPPORTED_BOARDS[self.args.board]
setattr(args, "app_address", board_props.app_address)
info("Erasing all installed applications")
tock = loader.TockLoader(args)
tock.open(args)
@@ -271,11 +456,13 @@ class OpenSKInstaller:
tock.erase_apps(False)
except TockLoaderException as e:
# Erasing apps is not critical
info(("A non-critical error occured while erasing "
info(("A non-critical error occurred while erasing "
"apps: {}".format(str(e))))
# pylint: disable=W0212
# pylint: disable=protected-access
def verify_flashed_app(self, expected_app):
if self.args.programmer not in ("jlink", "openocd"):
return False
args = copy.copy(self.tockloader_default_args)
tock = loader.TockLoader(args)
app_found = False
@@ -284,51 +471,205 @@ class OpenSKInstaller:
app_found = expected_app in apps
return app_found
def create_hex_file(self, dest_file):
# We produce an intelhex file with everything in it
# https://en.wikipedia.org/wiki/Intel_HEX
# pylint: disable=g-import-not-at-top,import-outside-toplevel
import intelhex
board_props = SUPPORTED_BOARDS[self.args.board]
final_hex = intelhex.IntelHex()
if self.args.tockos:
# Process kernel
kernel_path = os.path.join(board_props.path, "target", board_props.arch,
"release", "{}.bin".format(self.args.board))
with open(kernel_path, "rb") as kernel:
kern_hex = intelhex.IntelHex()
kern_hex.frombytes(kernel.read(), offset=board_props.kernel_address)
final_hex.merge(kern_hex, overlap="error")
if self.args.application:
# Add padding
if board_props.padding_address:
padding_hex = intelhex.IntelHex()
padding_hex.frombytes(
self.get_padding(), offset=board_props.padding_address)
final_hex.merge(padding_hex, overlap="error")
# Now we can add the application from the TAB file
app_tab_path = "target/tab/{}.tab".format(self.args.application)
assert os.path.exists(app_tab_path)
app_tab = tab.TAB(app_tab_path)
if board_props.arch not in app_tab.get_supported_architectures():
fatal(("It seems that the TAB file was not produced for the "
"architecture {}".format(board_props.arch)))
app_hex = intelhex.IntelHex()
app_hex.frombytes(
app_tab.extract_app(board_props.arch).get_binary(),
offset=board_props.app_address)
final_hex.merge(app_hex)
info("Generating all-merged HEX file: {}".format(dest_file))
final_hex.tofile(dest_file, format="hex")
def check_prerequisites(self):
if self.args.programmer == "jlink":
assert_mandatory_binary("JLinkExe")
if self.args.programmer == "openocd":
assert_mandatory_binary("openocd")
if self.args.programmer == "pyocd":
assert_mandatory_binary("pyocd")
assert_python_library("intelhex")
if not SUPPORTED_BOARDS[self.args.board].pyocd_target:
fatal("This board doesn't seem to support flashing through pyocd.")
if self.args.programmer == "nordicdfu":
assert_mandatory_binary("nrfutil")
assert_python_library("intelhex")
assert_python_library("nordicsemi.lister")
nrfutil_version = __import__("nordicsemi.version").version.NRFUTIL_VERSION
if not nrfutil_version.startswith("6."):
fatal(("You need to install nrfutil python3 package v6.0 or above. "
"Found: {}".format(nrfutil_version)))
if not SUPPORTED_BOARDS[self.args.board].nordic_dfu:
fatal("This board doesn't support flashing over DFU.")
if self.args.programmer == "none":
assert_python_library("intelhex")
def run(self):
if self.args.action is None:
# Nothing to do
if self.args.listing == "boards":
print(os.linesep.join(get_supported_boards()))
return 0
if self.args.listing == "programmers":
print(os.linesep.join(PROGRAMMERS))
return 0
if self.args.listing:
# Missing check?
fatal("Listing {} is not implemented.".format(self.args.listing))
self.check_prerequisites()
self.update_rustc_if_needed()
if self.args.action == "os":
info("Installing Tock on board {}".format(self.args.board))
self.build_and_install_tockos()
if not self.args.tockos and not self.args.application:
info("Nothing to do.")
return 0
if self.args.action == "app":
if self.args.application is None:
fatal("Unspecified application")
# Compile what needs to be compiled
if self.args.tockos:
self.build_tockos()
if self.args.application == "ctap2":
self.generate_crypto_materials(self.args.regenerate_keys)
self.build_opensk()
elif self.args.application is None:
info("No application selected.")
else:
self.build_example()
# Flashing
board_props = SUPPORTED_BOARDS[self.args.board]
if self.args.programmer in ("jlink", "openocd"):
# We rely on Tockloader to do the job
if self.args.clear_apps:
self.clear_apps()
if self.args.application == "ctap2":
self.generate_crypto_materials(self.args.regenerate_keys)
self.build_and_install_opensk()
else:
self.build_and_install_example()
if self.verify_flashed_app(self.args.application):
info("You're all set!")
return 0
error(("It seems that something went wrong. "
"App/example not found on your board."))
return 1
if self.args.tockos:
# Install Tock OS
self.install_tock_os()
# Install padding and application if needed
if self.args.application:
self.install_padding()
self.install_tab_file("target/tab/{}.tab".format(self.args.application))
if self.verify_flashed_app(self.args.application):
info("You're all set!")
return 0
error(
("It seems that something went wrong. App/example not found "
"on your board. Ensure the connections between the programmer and "
"the board are correct."))
return 1
return 0
if self.args.programmer in ("pyocd", "nordicdfu", "none"):
dest_file = "target/{}_merged.hex".format(self.args.board)
os.makedirs("target", exist_ok=True)
self.create_hex_file(dest_file)
if self.args.programmer == "pyocd":
info("Flashing HEX file")
self.checked_command_output([
"pyocd", "flash", "--target={}".format(board_props.pyocd_target),
"--format=hex", "--erase=auto", dest_file
])
if self.args.programmer == "nordicdfu":
info("Creating DFU package")
dfu_pkg_file = "target/{}_dfu.zip".format(self.args.board)
self.checked_command_output([
"nrfutil", "pkg", "generate", "--hw-version=52", "--sd-req=0",
"--application-version=1", "--application={}".format(dest_file),
dfu_pkg_file
])
info(
"Please insert the dongle and switch it to DFU mode by keeping the "
"button pressed while inserting...")
info("Press [ENTER] when ready.")
_ = input()
# Search for the DFU devices
serial_number = []
# pylint: disable=g-import-not-at-top,import-outside-toplevel
from nordicsemi.lister import device_lister
for device in device_lister.DeviceLister().enumerate():
if device.vendor_id == "1915" and device.product_id == "521F":
serial_number.append(device.serial_number)
if not serial_number:
fatal("Couldn't find any DFU device on your system.")
if len(serial_number) > 1:
fatal("Multiple DFU devices are detected. Please only connect one.")
# Run the command without capturing stdout so that we show progress
info("Flashing device using DFU...")
return subprocess.run(
[
"nrfutil", "dfu", "usb-serial",
"--package={}".format(dfu_pkg_file),
"--serial-number={}".format(serial_number[0])
],
check=False,
timeout=None,
).returncode
return 0
def main(args):
# Make sure the current working directory is the right one before running
os.chdir(os.path.realpath(os.path.dirname(__file__)))
# Check for pre-requisite executable files.
if not shutil.which("JLinkExe"):
fatal(("Couldn't find JLinkExe binary. Make sure Segger JLink tools "
"are installed and correctly set up."))
OpenSKInstaller(args).run()
if __name__ == "__main__":
shared_parser = argparse.ArgumentParser(add_help=False)
shared_parser.add_argument(
main_parser = argparse.ArgumentParser()
action_group = main_parser.add_mutually_exclusive_group(required=True)
action_group.add_argument(
"--list",
metavar="WHAT",
choices=("boards", "programmers"),
default=None,
dest="listing",
help=("List supported boards or programmers, 1 per line and then exit."),
)
action_group.add_argument(
"--board",
metavar="BOARD_NAME",
dest="board",
default=None,
choices=get_supported_boards(),
help="Indicates which board Tock OS will be compiled for.",
)
main_parser.add_argument(
"--dont-clear-apps",
action="store_false",
default=True,
@@ -336,32 +677,26 @@ if __name__ == "__main__":
help=("When installing an application, previously installed "
"applications won't be erased from the board."),
)
main_parser = argparse.ArgumentParser()
commands = main_parser.add_subparsers(
dest="action",
help=("Indicates which part of the firmware should be compiled and "
"flashed to the connected board."))
os_commands = commands.add_parser(
"os",
parents=[shared_parser],
help=("Compiles and installs Tock OS. The target board must be "
"specified by setting the --board argument."),
main_parser.add_argument(
"--programmer",
metavar="METHOD",
dest="programmer",
choices=PROGRAMMERS,
default="jlink",
help=("Sets the method to be used to flash Tock OS or the application "
"on the target board."),
)
os_commands.add_argument(
"--board",
metavar="BOARD_NAME",
dest="board",
choices=get_supported_boards(),
help="Indicates which board Tock OS will be compiled for.",
required=True)
app_commands = commands.add_parser(
"app",
parents=[shared_parser],
help="compiles and installs an application.")
app_commands.add_argument(
main_parser.add_argument(
"--no-tockos",
action="store_false",
default=True,
dest="tockos",
help=("Only compiles and flash the application/example. "
"Otherwise TockOS will also be bundled and flashed."),
)
main_parser.add_argument(
"--panic-console",
action="append_const",
const="panic_console",
@@ -370,7 +705,16 @@ if __name__ == "__main__":
"output messages before starting blinking the LEDs on the "
"board."),
)
app_commands.add_argument(
main_parser.add_argument(
"--debug",
action="append_const",
const="debug_ctap",
dest="features",
help=("Compiles and installs the OpenSK application in debug mode "
"(i.e. more debug messages will be sent over the console port "
"such as hexdumps of packets)."),
)
main_parser.add_argument(
"--debug-allocations",
action="append_const",
const="debug_allocations",
@@ -378,7 +722,15 @@ if __name__ == "__main__":
help=("The console will be used to output allocator statistics every "
"time an allocation/deallocation happens."),
)
app_commands.add_argument(
main_parser.add_argument(
"--verbose",
action="append_const",
const="verbose",
dest="features",
help=("The console will be used to output verbose information about the "
"OpenSK application. This also automatically activates --debug."),
)
main_parser.add_argument(
"--no-u2f",
action=RemoveConstAction,
const="with_ctap1",
@@ -386,7 +738,7 @@ if __name__ == "__main__":
help=("Compiles the OpenSK application without backward compatible "
"support for U2F/CTAP1 protocol."),
)
app_commands.add_argument(
main_parser.add_argument(
"--regen-keys",
action="store_true",
default=False,
@@ -396,16 +748,7 @@ if __name__ == "__main__":
"This is useful to allow flashing multiple OpenSK authenticators "
"in a row without them being considered clones."),
)
app_commands.add_argument(
"--debug",
action="append_const",
const="debug_ctap",
dest="features",
help=("Compiles and installs the OpenSK application in debug mode "
"(i.e. more debug messages will be sent over the console port "
"such as hexdumps of packets)."),
)
app_commands.add_argument(
main_parser.add_argument(
"--no-persistent-storage",
action="append_const",
const="ram_storage",
@@ -413,7 +756,15 @@ if __name__ == "__main__":
help=("Compiles and installs the OpenSK application without persistent "
"storage (i.e. unplugging the key will reset the key)."),
)
apps_group = app_commands.add_mutually_exclusive_group()
apps_group = main_parser.add_mutually_exclusive_group(required=True)
apps_group.add_argument(
"--no-app",
dest="application",
action="store_const",
const=None,
help=("Doesn't compile nor install any application. Useful when you only "
"want to update Tock OS kernel."))
apps_group.add_argument(
"--opensk",
dest="application",
@@ -428,6 +779,6 @@ if __name__ == "__main__":
help=("Compiles and installs the crypto_bench example that tests "
"the performance of the cryptographic algorithms on the board."))
app_commands.set_defaults(features=["with_ctap1"])
main_parser.set_defaults(features=["with_ctap1"])
main(main_parser.parse_args())

View File

@@ -16,8 +16,9 @@ You will need one the following supported boards:
scenarios as the JTAG probe is already on the board.
* [Nordic nRF52840 Dongle](https://www.nordicsemi.com/Software-and-tools/Development-Kits/nRF52840-Dongle)
to have a more practical form factor.
* [Makerdiary nRF52840-MDK USB dongle](https://wiki.makerdiary.com/nrf52840-mdk/).
In the case of the Nordic USB dongle, you will also need the following extra
In the case of the Nordic USB dongle, you may also need the following extra
hardware:
* a [Segger J-Link](https://www.segger.com/products/debug-probes/j-link/) JTAG
@@ -30,13 +31,18 @@ hardware:
[Tag-Connect TC2050 retainer clip](http://www.tag-connect.com/TC2050-CLIP)
to keep the spring loaded connector pressed to the PCB.
Although [OpenOCD](http://openocd.org/) should be supported we encountered some
issues while trying to flash a firmware with it. Therefore we suggest at the
moment to use a
[Segger J-Link](https://www.segger.com/products/debug-probes/j-link/) probe
instead.
Additionnaly, OpenSK supports other ways to flash your board:
This guide **does not** cover how to setup the JTAG probe on your system.
* [OpenOCD](http://openocd.org/).
* [Segger J-Link](https://www.segger.com/products/debug-probes/j-link/)
(default method).
* [pyOCD](https://pypi.org/project/pyocd/).
* [nrfutil](https://pypi.org/project/nrfutil/) for the USB dongle boards that
supports it, which allows you to directly flash a working board over USB
without additional hardware.
This guide **does not** cover how to setup the JTAG probe and their related
tools on your system.
### Software
@@ -141,17 +147,17 @@ Our build script `build.rs` is responsible for converting `opensk_cert.pem` and
1. Connect a micro USB cable to the JTAG USB port.
1. Run our script for compiling/flashing Tock OS on your device (_output may
differ_):
1. Run our script for compiling/flashing Tock OS and OpenSK on your device
(_output may differ_):
```shell
$ ./deploy.py os --board=nrf52840_dk
$ ./deploy.py --board=nrf52840dk --opensk
info: Updating rust toolchain to nightly-2020-02-03
info: syncing channel updates for 'nightly-2020-02-03-x86_64-unknown-linux-gnu'
info: checking for self-updates
info: component 'rust-std' for target 'thumbv7em-none-eabi' is up to date
info: Rust toolchain up-to-date
info: Installing Tock on board nrf52840_dk
info: Building Tock OS for board nrf52840dk
Compiling tock-registers v0.5.0 (./third_party/tock/libraries/tock-register-interface)
Compiling tock-cells v0.1.0 (./third_party/tock/libraries/tock-cells)
Compiling enum_primitive v0.1.0 (./third_party/tock/libraries/enum_primitive)
@@ -166,47 +172,17 @@ Our build script `build.rs` is responsible for converting `opensk_cert.pem` and
Compiling nrf52840 v0.1.0 (./third_party/tock/chips/nrf52840)
Compiling components v0.1.0 (./third_party/tock/boards/components)
Compiling nrf52dk_base v0.1.0 (./third_party/tock/boards/nordic/nrf52dk_base)
Finished release [optimized + debuginfo] target(s) in 11.97s
[STATUS ] Flashing binar(y|ies) to board...
[INFO ] Using known arch and jtag-device for known board nrf52dk
[INFO ] Finished in 0.284 seconds
```
1. Run our script for compiling/flashing the OpenSK application on your device
(_output may differ_):
```shell
$ ./deploy.py app --opensk
info: Updating rust toolchain to nightly-2020-02-03
info: syncing channel updates for 'nightly-2020-02-03-x86_64-unknown-linux-gnu'
info: checking for self-updates
info: component 'rust-std' for target 'thumbv7em-none-eabi' is up to date
info: Rust toolchain up-to-date
info: Erasing all installed applications
All apps have been erased.
info: Building OpenSK application
Compiling autocfg v1.0.0
Compiling pkg-config v0.3.17
Compiling cc v1.0.50
Compiling libc v0.2.66
Compiling bitflags v1.2.1
Compiling foreign-types-shared v0.1.1
Compiling openssl v0.10.28
Compiling cfg-if v0.1.10
Compiling lazy_static v1.4.0
Compiling byteorder v1.3.2
Compiling linked_list_allocator v0.6.6
Compiling arrayref v0.3.6
Compiling cbor v0.1.0 (./libraries/cbor)
Compiling subtle v2.2.2
Compiling foreign-types v0.3.2
Compiling libtock v0.1.0 (./third_party/libtock-rs)
Compiling crypto v0.1.0 (./libraries/crypto)
Compiling openssl-sys v0.9.54
Compiling ctap2 v0.1.0 (.)
Finished release [optimized] target(s) in 15.34s
info: Flashing padding application
info: Installing Tock application ctap2
Finished release [optimized + debuginfo] target(s) in 13.15s
info: Converting Tock OS file into a binary
info: Building OpenSK application
Finished release [optimized] target(s) in 0.02s
info: Generating Tock TAB file for application/example ctap2
info: Erasing all installed applications
All apps have been erased.
info: Flashing file third_party/tock/boards/nordic/nrf52840dk/target/thumbv7em-none-eabi/release/nrf52840dk.bin.
info: Flashing padding application
info: Installing Tock application ctap2
info: You're all set!
```
1. Connect a micro USB cable to the device USB port.
@@ -217,6 +193,8 @@ the board in order to see your OpenSK device on your system.
#### Nordic nRF52840 Dongle
##### Using external programmer (JLink, OpenOCD, etc.)
![Nordic dongle](img/dongle_front.jpg)
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)
1. Run our script for compiling/flashing Tock OS on your device (_output may
differ_):
1. Depending on the programmer you're using, you may have to adapt the next
command line. Run our script for compiling/flashing Tock OS on your device
(_output may differ_):
```shell
$ ./deploy.py os --board=nrf52840_dongle
$ ./deploy.py os --board=nrf52840_dongle --programmer=jlink
info: Updating rust toolchain to nightly-2020-02-03
info: syncing channel updates for 'nightly-2020-02-03-x86_64-unknown-linux-gnu'
info: checking for self-updates
info: component 'rust-std' for target 'thumbv7em-none-eabi' is up to date
info: Rust toolchain up-to-date
info: Installing Tock on board nrf52840_dongle
info: Building Tock OS for board nrf52840_dongle
Compiling tock-cells v0.1.0 (./third_party/tock/libraries/tock-cells)
Compiling tock-registers v0.5.0 (./third_party/tock/libraries/tock-register-interface)
Compiling enum_primitive v0.1.0 (./third_party/tock/libraries/enum_primitive)
@@ -258,50 +237,39 @@ the board in order to see your OpenSK device on your system.
Compiling components v0.1.0 (./third_party/tock/boards/components)
Compiling nrf52dk_base v0.1.0 (./third_party/tock/boards/nordic/nrf52dk_base)
Finished release [optimized + debuginfo] target(s) in 11.72s
[STATUS ] Flashing binar(y|ies) to board...
[INFO ] Using known arch and jtag-device for known board nrf52dk
[INFO ] Finished in 0.280 seconds
```
1. Run our script for compiling/flashing the OpenSK application on your device
(_output may differ_):
```shell
$ ./deploy.py app --opensk
info: Updating rust toolchain to nightly-2020-02-03
info: syncing channel updates for 'nightly-2020-02-03-x86_64-unknown-linux-gnu'
info: checking for self-updates
info: component 'rust-std' for target 'thumbv7em-none-eabi' is up to date
info: Rust toolchain up-to-date
info: Converting Tock OS file into a binary
info: Building OpenSK application
Finished release [optimized] target(s) in 0.02s
info: Generating Tock TAB file for application/example ctap2
info: Erasing all installed applications
All apps have been erased.
info: Building OpenSK application
Compiling autocfg v1.0.0
Compiling pkg-config v0.3.17
Compiling cc v1.0.50
Compiling libc v0.2.66
Compiling bitflags v1.2.1
Compiling foreign-types-shared v0.1.1
Compiling openssl v0.10.28
Compiling cfg-if v0.1.10
Compiling lazy_static v1.4.0
Compiling byteorder v1.3.2
Compiling linked_list_allocator v0.6.6
Compiling arrayref v0.3.6
Compiling cbor v0.1.0 (./libraries/cbor)
Compiling subtle v2.2.2
Compiling foreign-types v0.3.2
Compiling libtock v0.1.0 (./third_party/libtock-rs)
Compiling crypto v0.1.0 (./libraries/crypto)
Compiling openssl-sys v0.9.54
Compiling ctap2 v0.1.0 (.)
Finished release [optimized] target(s) in 15.34s
info: Flashing file third_party/tock/boards/nordic/nrf52840_dongle/target/thumbv7em-none-eabi/release/nrf52840_dongle.bin.
info: Flashing padding application
info: Installing Tock application ctap2
info: You're all set!
```
1. Remove the programming cable and the USB-A extension cable.
### Advanced installation
Although flashing using a Segger JLink probe is the officially supported way,
our tool, `deploy.py` also supports other methods:
* OpenOCD: `./deploy.py --board=nrf52840_dongle --opensk --programmer=openocd`
* pyOCD: `./deploy.py --board=nrf52840_dongle --opensk --programmer=pyocd`
* Nordic DFU: `./deploy.py --board=nrf52840_dongle --opensk
--programmer=nordicdfu`
* Custom: `./deploy.py --board=nrf52840_dongle --opensk --programmer=none`. In
this case, an IntelHex file will be created and how to program a board is
left to the user.
If your board is already flashed with Tock OS, you may skip installing it:
`./deploy.py --board=nrf52840dk --opensk --no-tockos`
For more options, we invite you to read the help of our `deploy.py` script by
running `./deploy.py --help`.
### Installing the udev rule (Linux only)
By default on Linux, a USB device will require root privilege in order interact

View File

@@ -286,10 +286,10 @@ index 5abd2d84..5a726fdb 100644
+ }
+}
diff --git a/kernel/src/callback.rs b/kernel/src/callback.rs
index ece4a443..9a1afc84 100644
index c812e0bf..bd1613b3 100644
--- a/kernel/src/callback.rs
+++ b/kernel/src/callback.rs
@@ -52,6 +52,31 @@ impl AppId {
@@ -130,6 +130,31 @@ impl AppId {
(start, end)
})
}

View File

@@ -124,11 +124,11 @@ index 105f7120..535e5cd8 100644
ipc: kernel::ipc::IPC::new(board_kernel, &memory_allocation_capability),
nvmc: nvmc,
diff --git a/capsules/src/driver.rs b/capsules/src/driver.rs
index 9305e6a7..40466f44 100644
index bfc06429..5858d352 100644
--- a/capsules/src/driver.rs
+++ b/capsules/src/driver.rs
@@ -24,6 +24,7 @@ pub enum NUM {
Spi = 0x20001,
@@ -25,6 +25,7 @@ pub enum NUM {
I2cMaster = 0x20003,
UsbUser = 0x20005,
I2cMasterSlave = 0x20006,
+ UsbCtap = 0x20009,
@@ -509,10 +509,10 @@ index 00000000..da3d16d8
+}
diff --git a/capsules/src/usb/usbc_ctap_hid.rs b/capsules/src/usb/usbc_ctap_hid.rs
new file mode 100644
index 00000000..fdf7263a
index 00000000..4b1916cf
--- /dev/null
+++ b/capsules/src/usb/usbc_ctap_hid.rs
@@ -0,0 +1,352 @@
@@ -0,0 +1,359 @@
+//! A USB HID client of the USB hardware interface
+
+use super::descriptors::Buffer64;
@@ -603,8 +603,9 @@ index 00000000..fdf7263a
+pub struct ClientCtapHID<'a, 'b, C: 'a> {
+ client_ctrl: ClientCtrl<'a, 'static, C>,
+
+ // A 64-byte buffer for the endpoint
+ buffer: Buffer64,
+ // 64-byte buffers for the endpoint
+ in_buffer: Buffer64,
+ out_buffer: Buffer64,
+
+ // Interaction with the client
+ client: OptionalCell<&'b dyn CtapUsbClient>,
@@ -648,7 +649,8 @@ index 00000000..fdf7263a
+ LANGUAGES,
+ STRINGS,
+ ),
+ buffer: Default::default(),
+ in_buffer: Default::default(),
+ out_buffer: Default::default(),
+ client: OptionalCell::empty(),
+ tx_packet: OptionalCell::empty(),
+ pending_in: Cell::new(false),
@@ -702,7 +704,7 @@ index 00000000..fdf7263a
+ fn send_packet_to_client(&'a self) -> bool {
+ // Copy the packet into a buffer to send to the client.
+ let mut buf: [u8; 64] = [0; 64];
+ for (i, x) in self.buffer.buf.iter().enumerate() {
+ for (i, x) in self.out_buffer.buf.iter().enumerate() {
+ buf[i] = x.get();
+ }
+
@@ -735,11 +737,7 @@ index 00000000..fdf7263a
+
+ fn cancel_in_transaction(&'a self) -> bool {
+ self.tx_packet.take();
+ let result = self.pending_in.take();
+ if result {
+ self.controller().endpoint_cancel_in(1);
+ }
+ result
+ self.pending_in.take()
+ }
+
+ fn cancel_out_transaction(&'a self) -> bool {
@@ -758,7 +756,10 @@ index 00000000..fdf7263a
+ self.client_ctrl.enable();
+
+ // Set up the interrupt in-out endpoint
+ self.controller().endpoint_set_buffer(1, &self.buffer.buf);
+ self.controller()
+ .endpoint_set_in_buffer(1, &self.in_buffer.buf);
+ self.controller()
+ .endpoint_set_out_buffer(1, &self.out_buffer.buf);
+ self.controller()
+ .endpoint_in_out_enable(TransferType::Interrupt, 1);
+ }
@@ -808,7 +809,7 @@ index 00000000..fdf7263a
+ }
+
+ if let Some(packet) = self.tx_packet.take() {
+ let buf = &self.buffer.buf;
+ let buf = &self.in_buffer.buf;
+ for i in 0..64 {
+ buf[i].set(packet[i]);
+ }
@@ -861,149 +862,13 @@ index 00000000..fdf7263a
+ panic!("Unexpected tx_packet while a packet was being transmitted.");
+ }
+ self.pending_in.set(false);
+
+ // Clear any pending packet on the receiving side.
+ // It's up to the client to handle the transmitted packet and decide if they want to
+ // receive another packet.
+ self.cancel_out_transaction();
+
+ // Notify the client
+ self.client.map(|client| client.packet_transmitted());
+ }
+}
diff --git a/chips/nrf52/src/usbd.rs b/chips/nrf52/src/usbd.rs
index 8ddb5895..8c1992cc 100644
--- a/chips/nrf52/src/usbd.rs
+++ b/chips/nrf52/src/usbd.rs
@@ -1499,7 +1499,23 @@ impl<'a> Usbd<'a> {
if epdatastatus.is_set(status_epin(endpoint)) {
let (transfer_type, direction, state) =
self.descriptors[endpoint].state.get().bulk_state();
- assert_eq!(state, BulkState::InData);
+ match state {
+ BulkState::InData => {
+ // Totally expected state. Nothing to do.
+ }
+ BulkState::Init => {
+ internal_warn!(
+ "Received a stale epdata IN in an unexpected state: {:?}",
+ state
+ );
+ }
+ BulkState::OutDelay
+ | BulkState::OutData
+ | BulkState::OutDma
+ | BulkState::InDma => {
+ internal_err!("Unexpected state: {:?}", state);
+ }
+ }
self.descriptors[endpoint].state.set(EndpointState::Bulk(
transfer_type,
direction,
@@ -1677,7 +1693,7 @@ impl<'a> Usbd<'a> {
}
fn transmit_in(&self, endpoint: usize) {
- debug_info!("transmit_in({})", endpoint);
+ debug_events!("transmit_in({})", endpoint);
let regs = &*self.registers;
self.client.map(|client| {
@@ -1717,7 +1733,7 @@ impl<'a> Usbd<'a> {
}
fn transmit_out(&self, endpoint: usize) {
- debug_info!("transmit_out({})", endpoint);
+ debug_events!("transmit_out({})", endpoint);
let (transfer_type, direction, state) = self.descriptors[endpoint].state.get().bulk_state();
// Starting the DMA can only happen in the OutData state, i.e. after an EPDATA event.
@@ -1882,11 +1898,13 @@ impl<'a> hil::usb::UsbController<'a> for Usbd<'a> {
}
fn endpoint_resume_in(&self, endpoint: usize) {
+ debug_events!("endpoint_resume_in({})", endpoint);
+
let (_, direction, _) = self.descriptors[endpoint].state.get().bulk_state();
assert!(direction.has_in());
if self.dma_pending.get() {
- debug_info!("requesting resume_in[{}]", endpoint);
+ debug_events!("requesting resume_in[{}]", endpoint);
// A DMA is already pending. Schedule the resume for later.
self.descriptors[endpoint].request_transmit_in.set(true);
} else {
@@ -1896,6 +1914,8 @@ impl<'a> hil::usb::UsbController<'a> for Usbd<'a> {
}
fn endpoint_resume_out(&self, endpoint: usize) {
+ debug_events!("endpoint_resume_out({})", endpoint);
+
let (transfer_type, direction, state) = self.descriptors[endpoint].state.get().bulk_state();
assert!(direction.has_out());
@@ -1914,7 +1934,7 @@ impl<'a> hil::usb::UsbController<'a> for Usbd<'a> {
// happened in the meantime. This pending transaction will now
// continue in transmit_out().
if self.dma_pending.get() {
- debug_info!("requesting resume_out[{}]", endpoint);
+ debug_events!("requesting resume_out[{}]", endpoint);
// A DMA is already pending. Schedule the resume for later.
self.descriptors[endpoint].request_transmit_out.set(true);
} else {
@@ -1927,6 +1947,20 @@ impl<'a> hil::usb::UsbController<'a> for Usbd<'a> {
}
}
}
+
+ fn endpoint_cancel_in(&self, endpoint: usize) {
+ debug_events!("endpoint_cancel_in({})", endpoint);
+
+ let (transfer_type, direction, state) = self.descriptors[endpoint].state.get().bulk_state();
+ assert!(direction.has_in());
+ assert_eq!(state, BulkState::InData);
+
+ self.descriptors[endpoint].state.set(EndpointState::Bulk(
+ transfer_type,
+ direction,
+ BulkState::Init,
+ ));
+ }
}
fn status_epin(ep: usize) -> Field<u32, EndpointStatus::Register> {
diff --git a/chips/nrf52840/src/lib.rs b/chips/nrf52840/src/lib.rs
index 9d58a705..942d0288 100644
--- a/chips/nrf52840/src/lib.rs
+++ b/chips/nrf52840/src/lib.rs
@@ -2,7 +2,7 @@
pub use nrf52::{
acomp, adc, aes, ble_radio, clock, constants, crt1, ficr, i2c, ieee802154_radio, init, nvmc,
- pinmux, ppi, pwm, rtc, spi, temperature, timer, trng, uart, uicr,
+ pinmux, ppi, pwm, rtc, spi, temperature, timer, trng, uart, uicr, usbd,
};
pub mod chip;
pub mod gpio;
diff --git a/chips/sam4l/src/usbc/mod.rs b/chips/sam4l/src/usbc/mod.rs
index 35f3bb7c..28a0b9f9 100644
--- a/chips/sam4l/src/usbc/mod.rs
+++ b/chips/sam4l/src/usbc/mod.rs
@@ -1547,6 +1547,10 @@ impl hil::usb::UsbController<'a> for Usbc<'a> {
requests.resume_out = true;
self.requests[endpoint].set(requests);
}
+
+ fn endpoint_cancel_in(&self, _endpoint: usize) {
+ unimplemented!()
+ }
}
/// Static state to manage the USBC
diff --git a/kernel/src/hil/usb.rs b/kernel/src/hil/usb.rs
index 846f5e93..64610fa5 100644
--- a/kernel/src/hil/usb.rs
+++ b/kernel/src/hil/usb.rs
@@ -27,6 +27,8 @@ pub trait UsbController<'a> {
fn endpoint_resume_in(&self, endpoint: usize);
fn endpoint_resume_out(&self, endpoint: usize);
+
+ fn endpoint_cancel_in(&self, endpoint: usize);
}
#[derive(Clone, Copy, Debug)]

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

View File

@@ -81,7 +81,7 @@ source tools/gen_key_materials.sh
generate_crypto_materials N
rustup install $(head -n 1 rust-toolchain)
pip3 install --user --upgrade tockloader
pip3 install --user --upgrade tockloader six intelhex
rustup target add thumbv7em-none-eabi
# Install dependency to create applications.

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

View File

@@ -28,9 +28,9 @@ use self::command::{
AuthenticatorMakeCredentialParameters, Command,
};
use self::data_formats::{
ClientPinSubCommand, CoseKey, PackedAttestationStatement, PublicKeyCredentialDescriptor,
PublicKeyCredentialSource, PublicKeyCredentialType, PublicKeyCredentialUserEntity,
SignatureAlgorithm,
ClientPinSubCommand, CoseKey, GetAssertionHmacSecretInput, PackedAttestationStatement,
PublicKeyCredentialDescriptor, PublicKeyCredentialSource, PublicKeyCredentialType,
PublicKeyCredentialUserEntity, SignatureAlgorithm,
};
use self::hid::ChannelID;
use self::key_material::{AAGUID, ATTESTATION_CERTIFICATE, ATTESTATION_PRIVATE_KEY};
@@ -81,9 +81,14 @@ const PIN_PADDED_LENGTH: usize = 64;
// - 32 byte relying party ID hashed with SHA256,
// - 32 byte HMAC-SHA256 over everything else.
pub const ENCRYPTED_CREDENTIAL_ID_SIZE: usize = 112;
// Set this bit when checking user presence.
const UP_FLAG: u8 = 0x01;
// Set this bit when checking user verification.
const UV_FLAG: u8 = 0x04;
// Set this bit when performing attestation.
const AT_FLAG: u8 = 0x40;
// Set this bit when an extension is used.
const ED_FLAG: u8 = 0x80;
pub const TOUCH_TIMEOUT_MS: isize = 30000;
#[cfg(feature = "with_ctap1")]
@@ -105,6 +110,63 @@ fn check_pin_auth(hmac_key: &[u8], hmac_contents: &[u8], pin_auth: &[u8]) -> boo
)
}
// Decrypts the HMAC secret salt(s) that were encrypted with the shared secret.
// The credRandom is used as a secret to HMAC those salts.
// The last step is to re-encrypt the outputs.
pub fn encrypt_hmac_secret_output(
shared_secret: &[u8; 32],
salt_enc: &[u8],
cred_random: &[u8],
) -> Result<Vec<u8>, Ctap2StatusCode> {
if salt_enc.len() != 32 && salt_enc.len() != 64 {
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION);
}
if cred_random.len() != 32 {
// We are strict here. We need at least 32 byte, but expect exactly 32.
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION);
}
let aes_enc_key = crypto::aes256::EncryptionKey::new(shared_secret);
let aes_dec_key = crypto::aes256::DecryptionKey::new(&aes_enc_key);
// The specification specifically asks for a zero IV.
let iv = [0; 16];
let mut cred_random_secret = [0; 32];
cred_random_secret.clone_from_slice(cred_random);
// Initialization of 4 blocks in any case makes this function more readable.
let mut blocks = [[0u8; 16]; 4];
let block_len = salt_enc.len() / 16;
for i in 0..block_len {
blocks[i].copy_from_slice(&salt_enc[16 * i..16 * (i + 1)]);
}
cbc_decrypt(&aes_dec_key, iv, &mut blocks[..block_len]);
let mut decrypted_salt1 = [0; 32];
decrypted_salt1[..16].clone_from_slice(&blocks[0]);
let output1 = hmac_256::<Sha256>(&cred_random_secret, &decrypted_salt1[..]);
decrypted_salt1[16..].clone_from_slice(&blocks[1]);
for i in 0..2 {
blocks[i].copy_from_slice(&output1[16 * i..16 * (i + 1)]);
}
if block_len == 4 {
let mut decrypted_salt2 = [0; 32];
decrypted_salt2[..16].clone_from_slice(&blocks[2]);
decrypted_salt2[16..].clone_from_slice(&blocks[3]);
let output2 = hmac_256::<Sha256>(&cred_random_secret, &decrypted_salt2[..]);
for i in 0..2 {
blocks[i + 2].copy_from_slice(&output2[16 * i..16 * (i + 1)]);
}
}
cbc_encrypt(&aes_enc_key, iv, &mut blocks[..block_len]);
let mut encrypted_output = Vec::with_capacity(salt_enc.len());
for b in &blocks[..block_len] {
encrypted_output.extend(b);
}
Ok(encrypted_output)
}
// This function is adapted from https://doc.rust-lang.org/nightly/src/core/str/mod.rs.html#2110
// (as of 2020-01-20) and truncates to "max" bytes, not breaking the encoding.
// We change the return value, since we don't need the bool.
@@ -261,6 +323,7 @@ where
rp_id: String::from(""),
user_handle: vec![],
other_ui: None,
cred_random: None,
})
}
@@ -324,10 +387,10 @@ where
user,
pub_key_cred_params,
exclude_list,
extensions,
options,
pin_uv_auth_param,
pin_uv_auth_protocol,
..
} = make_credential_params;
if let Some(auth_param) = &pin_uv_auth_param {
@@ -362,6 +425,19 @@ where
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM);
}
let use_hmac_extension =
extensions.map_or(Ok(false), |e| e.has_make_credential_hmac_secret())?;
if use_hmac_extension && !options.rk {
// The extension is actually supported, but we need resident keys.
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION);
}
let cred_random = if use_hmac_extension {
Some(self.rng.gen_uniform_u8x32().to_vec())
} else {
None
};
let ed_flag = if use_hmac_extension { ED_FLAG } else { 0 };
let rp_id = rp.rp_id;
if let Some(exclude_list) = exclude_list {
for cred_desc in exclude_list {
@@ -389,7 +465,7 @@ where
if !check_pin_auth(&self.pin_uv_auth_token, &client_data_hash, &pin_auth) {
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID);
}
UP_FLAG | UV_FLAG | AT_FLAG
UP_FLAG | UV_FLAG | AT_FLAG | ed_flag
}
None => {
if self.persistent_store.pin_hash().is_some() {
@@ -398,7 +474,7 @@ where
if options.uv {
return Err(Ctap2StatusCode::CTAP2_ERR_INVALID_OPTION);
}
UP_FLAG | AT_FLAG
UP_FLAG | AT_FLAG | ed_flag
}
};
@@ -421,6 +497,7 @@ where
other_ui: user
.user_display_name
.map(|s| truncate_to_char_boundary(&s, 64).to_string()),
cred_random,
};
self.persistent_store.store_credential(credential_source)?;
random_id
@@ -441,6 +518,14 @@ where
None => return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_RESPONSE_CANNOT_WRITE_CBOR),
};
auth_data.extend(cose_key);
if use_hmac_extension {
let extensions = cbor_map! {
"hmac-secret" => true,
};
if !cbor::write(extensions, &mut auth_data) {
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_RESPONSE_CANNOT_WRITE_CBOR);
}
}
let mut signature_data = auth_data.clone();
signature_data.extend(client_data_hash);
@@ -481,10 +566,10 @@ where
rp_id,
client_data_hash,
allow_list,
extensions,
options,
pin_uv_auth_param,
pin_uv_auth_protocol,
..
} = get_assertion_params;
if let Some(auth_param) = &pin_uv_auth_param {
@@ -527,6 +612,15 @@ where
}
}
let get_assertion_hmac_secret_input = match extensions {
Some(extensions) => extensions.get_assertion_hmac_secret().transpose()?,
None => None,
};
if get_assertion_hmac_secret_input.is_some() && !options.up {
// The extension is actually supported, but we need user presence.
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION);
}
// The user verification bit depends on the existance of PIN auth, whereas
// user presence is requested as an option.
let mut flags = match pin_uv_auth_param {
@@ -551,6 +645,9 @@ where
if options.up {
flags |= UP_FLAG;
}
if get_assertion_hmac_secret_input.is_some() {
flags |= ED_FLAG;
}
let rp_id_hash = Sha256::hash(rp_id.as_bytes());
let mut decrypted_credential = None;
@@ -590,7 +687,36 @@ where
self.increment_global_signature_counter();
let auth_data = self.generate_auth_data(&rp_id_hash, flags);
let mut auth_data = self.generate_auth_data(&rp_id_hash, flags);
// Process extensions.
if let Some(get_assertion_hmac_secret_input) = get_assertion_hmac_secret_input {
let GetAssertionHmacSecretInput {
key_agreement,
salt_enc,
salt_auth,
} = get_assertion_hmac_secret_input;
let pk: crypto::ecdh::PubKey = CoseKey::try_into(key_agreement)?;
let shared_secret = self.key_agreement_key.exchange_x_sha256(&pk);
// HMAC-secret does the same 16 byte truncated check.
if !check_pin_auth(&shared_secret, &salt_enc, &salt_auth) {
// Again, hard to tell what the correct error code here is.
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION);
}
let encrypted_output = match &credential.cred_random {
Some(cr) => encrypt_hmac_secret_output(&shared_secret, &salt_enc[..], cr)?,
// This is the case if the credential was not created with HMAC-secret.
None => return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION),
};
let extensions = cbor_map! {
"hmac-secret" => encrypted_output,
};
if !cbor::write(extensions, &mut auth_data) {
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_RESPONSE_CANNOT_WRITE_CBOR);
}
}
let mut signature_data = auth_data.clone();
signature_data.extend(client_data_hash);
let signature = credential
@@ -639,7 +765,7 @@ where
String::from(U2F_VERSION_STRING),
String::from(FIDO2_VERSION_STRING),
],
extensions: Some(vec![]),
extensions: Some(vec![String::from("hmac-secret")]),
aaguid: *AAGUID,
options: Some(options_map),
max_msg_size: Some(1024),
@@ -948,7 +1074,7 @@ where
#[cfg(test)]
mod test {
use super::data_formats::{
GetAssertionOptions, MakeCredentialOptions, PublicKeyCredentialRpEntity,
Extensions, GetAssertionOptions, MakeCredentialOptions, PublicKeyCredentialRpEntity,
PublicKeyCredentialUserEntity,
};
use super::*;
@@ -970,13 +1096,15 @@ mod test {
let mut expected_response = vec![0x00, 0xA6, 0x01];
// The difference here is a longer array of supported versions.
#[cfg(not(feature = "with_ctap1"))]
expected_response.extend(&[
0x81, 0x68, 0x46, 0x49, 0x44, 0x4F, 0x5F, 0x32, 0x5F, 0x30, 0x02, 0x80, 0x03, 0x50,
]);
expected_response.extend(&[0x81, 0x68, 0x46, 0x49, 0x44, 0x4F, 0x5F, 0x32, 0x5F, 0x30]);
#[cfg(feature = "with_ctap1")]
expected_response.extend(&[
0x82, 0x66, 0x55, 0x32, 0x46, 0x5F, 0x56, 0x32, 0x68, 0x46, 0x49, 0x44, 0x4F, 0x5F,
0x32, 0x5F, 0x30, 0x02, 0x80, 0x03, 0x50,
0x32, 0x5F, 0x30,
]);
expected_response.extend(&[
0x02, 0x81, 0x6B, 0x68, 0x6D, 0x61, 0x63, 0x2D, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74,
0x03, 0x50,
]);
expected_response.extend(AAGUID);
expected_response.extend(&[
@@ -1130,6 +1258,7 @@ mod test {
rp_id: String::from("example.com"),
user_handle: vec![],
other_ui: None,
cred_random: None,
};
assert!(ctap_state
.persistent_store
@@ -1153,6 +1282,54 @@ mod test {
);
}
#[test]
fn test_process_make_credential_hmac_secret() {
let mut rng = ThreadRng256 {};
let user_immediately_present = |_| Ok(());
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
let mut extension_map = BTreeMap::new();
extension_map.insert("hmac-secret".to_string(), cbor_bool!(true));
let extensions = Some(Extensions::new(extension_map));
let mut make_credential_params = create_minimal_make_credential_parameters();
make_credential_params.extensions = extensions;
let make_credential_response =
ctap_state.process_make_credential(make_credential_params, DUMMY_CHANNEL_ID);
match make_credential_response.unwrap() {
ResponseData::AuthenticatorMakeCredential(make_credential_response) => {
let AuthenticatorMakeCredentialResponse {
fmt,
auth_data,
att_stmt,
} = make_credential_response;
// The expected response is split to only assert the non-random parts.
assert_eq!(fmt, "packed");
let mut expected_auth_data = vec![
0xA3, 0x79, 0xA6, 0xF6, 0xEE, 0xAF, 0xB9, 0xA5, 0x5E, 0x37, 0x8C, 0x11, 0x80,
0x34, 0xE2, 0x75, 0x1E, 0x68, 0x2F, 0xAB, 0x9F, 0x2D, 0x30, 0xAB, 0x13, 0xD2,
0x12, 0x55, 0x86, 0xCE, 0x19, 0x47, 0xC1, 0x00, 0x00, 0x00, 0x00,
];
expected_auth_data.extend(AAGUID);
expected_auth_data.extend(&[0x00, 0x20]);
assert_eq!(
auth_data[0..expected_auth_data.len()],
expected_auth_data[..]
);
let expected_extension_cbor = vec![
0xA1, 0x6B, 0x68, 0x6D, 0x61, 0x63, 0x2D, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74,
0xF5,
];
assert_eq!(
auth_data[auth_data.len() - expected_extension_cbor.len()..auth_data.len()],
expected_extension_cbor[..]
);
assert_eq!(att_stmt.alg, SignatureAlgorithm::ES256 as i64);
}
_ => panic!("Invalid response type"),
}
}
#[test]
fn test_process_make_credential_cancelled() {
let mut rng = ThreadRng256 {};
@@ -1216,6 +1393,53 @@ mod test {
}
}
#[test]
fn test_residential_process_get_assertion_hmac_secret() {
let mut rng = ThreadRng256 {};
let sk = crypto::ecdh::SecKey::gensk(&mut rng);
let user_immediately_present = |_| Ok(());
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
let mut extension_map = BTreeMap::new();
extension_map.insert("hmac-secret".to_string(), cbor_bool!(true));
let make_extensions = Some(Extensions::new(extension_map));
let mut make_credential_params = create_minimal_make_credential_parameters();
make_credential_params.extensions = make_extensions;
assert!(ctap_state
.process_make_credential(make_credential_params, DUMMY_CHANNEL_ID)
.is_ok());
let pk = sk.genpk();
let hmac_secret_parameters = cbor_map! {
1 => cbor::Value::Map(CoseKey::from(pk).0),
2 => vec![0; 32],
3 => vec![0; 16],
};
let mut extension_map = BTreeMap::new();
extension_map.insert("hmac-secret".to_string(), hmac_secret_parameters);
let get_extensions = Some(Extensions::new(extension_map));
let get_assertion_params = AuthenticatorGetAssertionParameters {
rp_id: String::from("example.com"),
client_data_hash: vec![0xCD],
allow_list: None,
extensions: get_extensions,
options: GetAssertionOptions {
up: false,
uv: false,
},
pin_uv_auth_param: None,
pin_uv_auth_protocol: None,
};
let get_assertion_response =
ctap_state.process_get_assertion(get_assertion_params, DUMMY_CHANNEL_ID);
assert_eq!(
get_assertion_response,
Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION)
);
}
#[test]
fn test_process_reset() {
let mut rng = ThreadRng256 {};
@@ -1231,6 +1455,7 @@ mod test {
rp_id: String::from("example.com"),
user_handle: vec![],
other_ui: None,
cred_random: None,
};
assert!(ctap_state
.persistent_store
@@ -1294,4 +1519,32 @@ mod test {
.is_none());
}
}
#[test]
fn test_encrypt_hmac_secret_output() {
let shared_secret = [0x55; 32];
let salt_enc = [0x5E; 32];
let cred_random = [0xC9; 32];
let output = encrypt_hmac_secret_output(&shared_secret, &salt_enc, &cred_random);
assert_eq!(output.unwrap().len(), 32);
let salt_enc = [0x5E; 48];
let output = encrypt_hmac_secret_output(&shared_secret, &salt_enc, &cred_random);
assert_eq!(
output,
Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION)
);
let salt_enc = [0x5E; 64];
let output = encrypt_hmac_secret_output(&shared_secret, &salt_enc, &cred_random);
assert_eq!(output.unwrap().len(), 64);
let salt_enc = [0x5E; 32];
let cred_random = [0xC9; 33];
let output = encrypt_hmac_secret_output(&shared_secret, &salt_enc, &cred_random);
assert_eq!(
output,
Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION)
);
}
}

View File

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

View File

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