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