diff --git a/.github/workflows/boards_build.yml b/.github/workflows/boards_build.yml
index 0a8fbebb7e1886443fc9a6e67ef36d4f53adca0d..50a29d5a3b4faf98802b82445e9b61f965aad08e 100644
--- a/.github/workflows/boards_build.yml
+++ b/.github/workflows/boards_build.yml
@@ -27,15 +27,11 @@ jobs:
       - name: Set up OpenSK
         run: ./setup.sh
 
+      - name: Building board nrf52840dk
+        run: ./deploy.py --board=nrf52840dk --no-app --programmer=none
+      - name: Building board nrf52840_dongle
+        run: ./deploy.py --board=nrf52840_dongle --no-app --programmer=none
       - 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/
-
-      - name: Building board nrf52840dk
-        run: make -C this-is-a-long-build-directory-0123456789abcdefghijklmnopqrstuvwxyz/third_party/tock/boards/nordic/nrf52840dk
-      - name: Building board nrf52840_dongle
-        run: make -C this-is-a-long-build-directory-0123456789abcdefghijklmnopqrstuvwxyz/third_party/tock/boards/nordic/nrf52840_dongle
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/cargo_fmt.yml b/.github/workflows/cargo_fmt.yml
index 00589ca0dcc175c0533eb8d6a7d1dd5538838b05..df466b384e7adfc9296a4eb4a669ecde2c7f9833 100644
--- a/.github/workflows/cargo_fmt.yml
+++ b/.github/workflows/cargo_fmt.yml
@@ -20,6 +20,7 @@ jobs:
       - uses: actions-rs/toolchain@v1
         with:
           target: thumbv7em-none-eabi
+          components: rustfmt
       - uses: actions/setup-python@v1
         with:
           python-version: 3.7
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/boards/nordic/nrf52840_dongle_dfu/Cargo.toml b/boards/nordic/nrf52840_dongle_dfu/Cargo.toml
new file mode 100644
index 0000000000000000000000000000000000000000..35eab8a848640da82ba78e50fafe0acd46264304
--- /dev/null
+++ b/boards/nordic/nrf52840_dongle_dfu/Cargo.toml
@@ -0,0 +1,18 @@
+[package]
+name = "nrf52840_dongle_dfu"
+version = "0.1.0"
+authors = ["Tock Project Developers <tock-dev@googlegroups.com>"]
+build = "build.rs"
+edition = "2018"
+
+[[bin]]
+path = "../nrf52840_dongle/src/main.rs"
+name = "nrf52840_dongle_dfu"
+
+[dependencies]
+components = { path = "../../components" }
+cortexm4 = { path = "../../../arch/cortex-m4" }
+capsules = { path = "../../../capsules" }
+kernel = { path = "../../../kernel" }
+nrf52840 = { path = "../../../chips/nrf52840" }
+nrf52dk_base = { path = "../nrf52dk_base" }
diff --git a/boards/nrf52840_dongle_dfu/Makefile b/boards/nordic/nrf52840_dongle_dfu/Makefile
similarity index 81%
rename from boards/nrf52840_dongle_dfu/Makefile
rename to boards/nordic/nrf52840_dongle_dfu/Makefile
index 723ba0af1570d50a7e1257bce4aa3d39108e4bf5..58771b3a640ae3cc9edc1f6e0764aeb619da57f7 100644
--- a/boards/nrf52840_dongle_dfu/Makefile
+++ b/boards/nordic/nrf52840_dongle_dfu/Makefile
@@ -4,7 +4,7 @@ TOCK_ARCH=cortex-m4
 TARGET=thumbv7em-none-eabi
 PLATFORM=nrf52840_dongle_dfu
 
-include ../../third_party/tock/boards/Makefile.common
+include ../../Makefile.common
 
 TOCKLOADER=tockloader
 
@@ -20,10 +20,10 @@ TOCKLOADER_JTAG_FLAGS = --jlink --arch $(TOCK_ARCH) --board $(PLATFORM) --page-s
 
 # Upload the kernel over JTAG
 .PHONY: flash
-flash: target/$(TARGET)/release/$(PLATFORM).bin
+flash: $(TOCK_ROOT_DIRECTORY)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
+program: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).hex
 	$(error Cannot program nRF52 Dongle over USB. Use \`make flash\` and JTAG)
diff --git a/boards/nordic/nrf52840_dongle_dfu/build.rs b/boards/nordic/nrf52840_dongle_dfu/build.rs
new file mode 100644
index 0000000000000000000000000000000000000000..1fdd4924f0a125f565133842a274c5e2067ce21f
--- /dev/null
+++ b/boards/nordic/nrf52840_dongle_dfu/build.rs
@@ -0,0 +1,4 @@
+fn main() {
+    println!("cargo:rerun-if-changed=layout.ld");
+    println!("cargo:rerun-if-changed=../../kernel_layout.ld");
+}
diff --git a/boards/nrf52840_mdk_dfu/layout.ld b/boards/nordic/nrf52840_dongle_dfu/layout.ld
similarity index 76%
rename from boards/nrf52840_mdk_dfu/layout.ld
rename to boards/nordic/nrf52840_dongle_dfu/layout.ld
index 834133c00b8d0adb87c9e8eb355ed0b2e91c0582..41ae608ac5d17b9ff7334f769472e1b9c458f466 100644
--- a/boards/nrf52840_mdk_dfu/layout.ld
+++ b/boards/nordic/nrf52840_dongle_dfu/layout.ld
@@ -7,4 +7,4 @@ MEMORY
 
 MPU_MIN_ALIGN = 8K;
 
-INCLUDE ../../third_party/tock/boards/kernel_layout.ld
+INCLUDE ../../kernel_layout.ld
diff --git a/boards/nordic/nrf52840_mdk_dfu/Cargo.toml b/boards/nordic/nrf52840_mdk_dfu/Cargo.toml
new file mode 100644
index 0000000000000000000000000000000000000000..c0cb7af33a7349efc49cdcd6304ad56b5756f9d3
--- /dev/null
+++ b/boards/nordic/nrf52840_mdk_dfu/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "nrf52840_mdk_dfu"
+version = "0.1.0"
+authors = ["Yihui Xiong <yihui.xiong@hotmail.com>"]
+build = "build.rs"
+edition = "2018"
+
+[dependencies]
+components = { path = "../../components" }
+cortexm4 = { path = "../../../arch/cortex-m4" }
+capsules = { path = "../../../capsules" }
+kernel = { path = "../../../kernel" }
+nrf52840 = { path = "../../../chips/nrf52840" }
+nrf52dk_base = { path = "../nrf52dk_base" }
diff --git a/boards/nrf52840_mdk_dfu/Makefile b/boards/nordic/nrf52840_mdk_dfu/Makefile
similarity index 74%
rename from boards/nrf52840_mdk_dfu/Makefile
rename to boards/nordic/nrf52840_mdk_dfu/Makefile
index e915141d9174e55cb67deccc607d9dc1ae49300a..a179ffb4b2c9eb870392037c5fff971f5b8c44fb 100644
--- a/boards/nrf52840_mdk_dfu/Makefile
+++ b/boards/nordic/nrf52840_mdk_dfu/Makefile
@@ -4,7 +4,7 @@ TOCK_ARCH=cortex-m4
 TARGET=thumbv7em-none-eabi
 PLATFORM=nrf52840_mdk_dfu
 
-include ../../third_party/tock/boards/Makefile.common
+include ../../Makefile.common
 
 TOCKLOADER=tockloader
 
@@ -20,10 +20,10 @@ TOCKLOADER_JTAG_FLAGS = --jlink --arch $(TOCK_ARCH) --board $(PLATFORM) --page-s
 
 # Upload the kernel over JTAG
 .PHONY: flash
-flash: target/$(TARGET)/release/$(PLATFORM).bin
+flash: $(TOCK_ROOT_DIRECTORY)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)
+program: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).hex
+	$(error Cannot program nRF52840-MDK over USB. Use \`make flash\` and JTAG)
diff --git a/boards/nordic/nrf52840_mdk_dfu/build.rs b/boards/nordic/nrf52840_mdk_dfu/build.rs
new file mode 100644
index 0000000000000000000000000000000000000000..1fdd4924f0a125f565133842a274c5e2067ce21f
--- /dev/null
+++ b/boards/nordic/nrf52840_mdk_dfu/build.rs
@@ -0,0 +1,4 @@
+fn main() {
+    println!("cargo:rerun-if-changed=layout.ld");
+    println!("cargo:rerun-if-changed=../../kernel_layout.ld");
+}
diff --git a/boards/nrf52840_dongle_dfu/layout.ld b/boards/nordic/nrf52840_mdk_dfu/layout.ld
similarity index 76%
rename from boards/nrf52840_dongle_dfu/layout.ld
rename to boards/nordic/nrf52840_mdk_dfu/layout.ld
index 834133c00b8d0adb87c9e8eb355ed0b2e91c0582..41ae608ac5d17b9ff7334f769472e1b9c458f466 100644
--- a/boards/nrf52840_dongle_dfu/layout.ld
+++ b/boards/nordic/nrf52840_mdk_dfu/layout.ld
@@ -7,4 +7,4 @@ MEMORY
 
 MPU_MIN_ALIGN = 8K;
 
-INCLUDE ../../third_party/tock/boards/kernel_layout.ld
+INCLUDE ../../kernel_layout.ld
diff --git a/boards/nrf52840_mdk_dfu/src/io.rs b/boards/nordic/nrf52840_mdk_dfu/src/io.rs
similarity index 100%
rename from boards/nrf52840_mdk_dfu/src/io.rs
rename to boards/nordic/nrf52840_mdk_dfu/src/io.rs
diff --git a/boards/nrf52840_mdk_dfu/src/main.rs b/boards/nordic/nrf52840_mdk_dfu/src/main.rs
similarity index 100%
rename from boards/nrf52840_mdk_dfu/src/main.rs
rename to boards/nordic/nrf52840_mdk_dfu/src/main.rs
diff --git a/boards/nrf52840_dongle_dfu/Cargo.toml b/boards/nrf52840_dongle_dfu/Cargo.toml
deleted file mode 100644
index 6944eb33fc308caba08ec539d5d6af5783d4e3be..0000000000000000000000000000000000000000
--- a/boards/nrf52840_dongle_dfu/Cargo.toml
+++ /dev/null
@@ -1,30 +0,0 @@
-[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/build.rs b/boards/nrf52840_dongle_dfu/build.rs
deleted file mode 100644
index 2631dccb75f21f432f5186b17215367490e2d3f2..0000000000000000000000000000000000000000
--- a/boards/nrf52840_dongle_dfu/build.rs
+++ /dev/null
@@ -1,4 +0,0 @@
-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/Cargo.toml b/boards/nrf52840_mdk_dfu/Cargo.toml
deleted file mode 100644
index 1047869ba67c2d46b0af09d0ec7dd214a051fa43..0000000000000000000000000000000000000000
--- a/boards/nrf52840_mdk_dfu/Cargo.toml
+++ /dev/null
@@ -1,26 +0,0 @@
-[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/build.rs b/boards/nrf52840_mdk_dfu/build.rs
deleted file mode 100644
index 2631dccb75f21f432f5186b17215367490e2d3f2..0000000000000000000000000000000000000000
--- a/boards/nrf52840_mdk_dfu/build.rs
+++ /dev/null
@@ -1,4 +0,0 @@
-fn main() {
-    println!("cargo:rerun-if-changed=layout.ld");
-    println!("cargo:rerun-if-changed=../../third_party/tock/boards/kernel_layout.ld");
-}
diff --git a/deploy.py b/deploy.py
index 639b70bb45d118a92c05f15697aeb8c2f99a6cb8..6b552faad1e6c6dd474bf0b924ec1da2e2fbb786 100755
--- a/deploy.py
+++ b/deploy.py
@@ -115,7 +115,7 @@ SUPPORTED_BOARDS = {
         ),
     "nrf52840_dongle_dfu":
         OpenSKBoard(
-            path="boards/nrf52840_dongle_dfu",
+            path="third_party/tock/boards/nordic/nrf52840_dongle_dfu",
             arch="thumbv7em-none-eabi",
             page_size=4096,
             kernel_address=0x1000,
@@ -132,7 +132,7 @@ SUPPORTED_BOARDS = {
         ),
     "nrf52840_mdk_dfu":
         OpenSKBoard(
-            path="boards/nrf52840_mdk_dfu",
+            path="third_party/tock/boards/nordic/nrf52840_mdk_dfu",
             arch="thumbv7em-none-eabi",
             page_size=4096,
             kernel_address=0x1000,
@@ -304,7 +304,8 @@ class OpenSKInstaller:
   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")
+    out_directory = os.path.join("third_party", "tock", "target", props.arch,
+                                 "release")
     os.makedirs(out_directory, exist_ok=True)
     self.checked_command_output(["make"], cwd=props.path)
 
@@ -418,8 +419,9 @@ class OpenSKInstaller:
 
   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))
+    kernel_file = os.path.join("third_party", "tock", "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()
@@ -481,8 +483,9 @@ class OpenSKInstaller:
 
     if self.args.tockos:
       # Process kernel
-      kernel_path = os.path.join(board_props.path, "target", board_props.arch,
-                                 "release", "{}.bin".format(self.args.board))
+      kernel_path = os.path.join("third_party", "tock", "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)
@@ -705,6 +708,15 @@ if __name__ == "__main__":
             "output messages before starting blinking the LEDs on the "
             "board."),
   )
+  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",
@@ -713,6 +725,14 @@ if __name__ == "__main__":
       help=("The console will be used to output allocator statistics every "
             "time an allocation/deallocation happens."),
   )
+  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,
@@ -731,15 +751,6 @@ if __name__ == "__main__":
             "This is useful to allow flashing multiple OpenSK authenticators "
             "in a row without them being considered clones."),
   )
-  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(
       "--no-persistent-storage",
       action="append_const",
diff --git a/docs/install.md b/docs/install.md
index 5b5763b1b83ee2c58da2c1006a1d581e6c1daca7..cf355fac54fefdad8907618480f49b770ea873b9 100644
--- a/docs/install.md
+++ b/docs/install.md
@@ -50,8 +50,9 @@ In order to compile and flash a working OpenSK firmware, you will need the
 following:
 
 *   rustup (can be installed with [Rustup](https://rustup.rs/))
-*   python3 and pip
-*   the OpenSSL command line tool
+*   python3 and pip (can be installed with the `python3-pip` package on Debian)
+*   the OpenSSL command line tool (can be installed with the `libssl-dev`
+    package on Debian)
 
 The scripts provided in this project have been tested under Linux and OS X. We
 haven't tested them on Windows and other platforms.
diff --git a/patches/tock/04-additional-boards.patch b/patches/tock/04-additional-boards.patch
new file mode 100644
index 0000000000000000000000000000000000000000..88cd1c3db38e11462467ecfdb7216b8a5ad12871
--- /dev/null
+++ b/patches/tock/04-additional-boards.patch
@@ -0,0 +1,13 @@
+diff --git a/Cargo.toml b/Cargo.toml
+index 18f4a10d..db88dc1d 100644
+--- a/Cargo.toml
++++ b/Cargo.toml
+@@ -13,6 +13,8 @@ members = [
+     "boards/launchxl",
+     "boards/nordic/nrf52840dk",
+     "boards/nordic/nrf52840_dongle",
++    "boards/nordic/nrf52840_dongle_dfu",
++    "boards/nordic/nrf52840_mdk_dfu",
+     "boards/nordic/nrf52dk",
+     "boards/nucleo_f429zi",
+     "boards/nucleo_f446re",
diff --git a/patches/tock/04-nrf52-bootloader.patch b/patches/tock/04-nrf52-bootloader.patch
deleted file mode 100644
index 02f57be6552ce9ca2b09c1c98475015db6c08494..0000000000000000000000000000000000000000
--- a/patches/tock/04-nrf52-bootloader.patch
+++ /dev/null
@@ -1,21 +0,0 @@
-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/run_desktop_tests.sh b/run_desktop_tests.sh
index 864b8fecca526ddc56726348472de28b8876a347..36bde36da10d929257197db4316cc2aa11f96b2b 100755
--- a/run_desktop_tests.sh
+++ b/run_desktop_tests.sh
@@ -34,8 +34,9 @@ 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
@@ -49,8 +50,16 @@ 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
+make -C third_party/tock/boards/nordic/nrf52840_dongle_dfu
+make -C third_party/tock/boards/nordic/nrf52840_mdk_dfu
+
+echo "Checking deployment of supported boards..."
+./deploy.py --board=nrf52840dk --no-app --programmer=none
+./deploy.py --board=nrf52840_dongle --no-app --programmer=none
+
+echo "Checking deployment of other boards..."
+./deploy.py --board=nrf52840_dongle_dfu --no-app --programmer=none
+./deploy.py --board=nrf52840_mdk_dfu --no-app --programmer=none
 
 if [ -z "${TRAVIS_OS_NAME}" -o "${TRAVIS_OS_NAME}" = "linux" ]
 then
diff --git a/setup.sh b/setup.sh
index 1c6fe37cfc1fb467cf04d7e759e08784b97949a7..c4297adc9ccae692ed06aa5e588285e7a1046129 100755
--- a/setup.sh
+++ b/setup.sh
@@ -46,6 +46,11 @@ EOF
   exit 1
 }
 
+# Copy additional boards to the kernel.
+echo -n '[-] Copying additional boards to Tock... '
+cp -r boards/* third_party/tock/boards
+echo $done_text
+
 # Apply patches to kernel. Do that in a sub-shell
 (
   cd third_party/tock/ && \
diff --git a/src/ctap/data_formats.rs b/src/ctap/data_formats.rs
index 5049d1cb5528d6e0606bc08eebce931d9c66648e..3a2bed91e85089124c5780dd399b39bbd42902d4 100644
--- a/src/ctap/data_formats.rs
+++ b/src/ctap/data_formats.rs
@@ -462,6 +462,8 @@ pub struct CoseKey(pub BTreeMap<cbor::KeyType, cbor::Value>);
 // here: https://www.iana.org/assignments/cose/cose.xhtml#algorithms
 // In fact, this is just used for compatibility with older specification versions.
 const ECDH_ALGORITHM: i64 = -25;
+// This is the identifier used by OpenSSH. To be compatible, we accept both.
+const ES256_ALGORITHM: i64 = -7;
 const EC2_KEY_TYPE: i64 = 2;
 const P_256_CURVE: i64 = 1;
 
@@ -497,7 +499,7 @@ impl TryFrom<CoseKey> for ecdh::PubKey {
             return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM);
         }
         let algorithm = read_integer(ok_or_missing(cose_key.0.get(&cbor_int!(3)))?)?;
-        if algorithm != ECDH_ALGORITHM {
+        if algorithm != ECDH_ALGORITHM && algorithm != ES256_ALGORITHM {
             return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM);
         }
         let curve = read_integer(ok_or_missing(cose_key.0.get(&cbor_int!(-1)))?)?;
diff --git a/src/ctap/storage.rs b/src/ctap/storage.rs
index 8f2c2b57aea76e3b2180fa4234f8b4c6495fe9aa..f0a2ca4ff0bfb3ec85e1859d44009af8c0a6ee29 100644
--- a/src/ctap/storage.rs
+++ b/src/ctap/storage.rs
@@ -198,6 +198,7 @@ impl PersistentStore {
                 .insert(StoreEntry {
                     tag: MASTER_KEYS,
                     data: &master_keys,
+                    sensitive: true,
                 })
                 .unwrap();
         }
@@ -206,6 +207,7 @@ impl PersistentStore {
                 .insert(StoreEntry {
                     tag: PIN_RETRIES,
                     data: &[MAX_PIN_RETRIES],
+                    sensitive: false,
                 })
                 .unwrap();
         }
@@ -245,6 +247,7 @@ impl PersistentStore {
         let new_entry = StoreEntry {
             tag: TAG_CREDENTIAL,
             data: &credential,
+            sensitive: true,
         };
         match old_entry {
             None => self.store.insert(new_entry)?,
@@ -299,6 +302,7 @@ impl PersistentStore {
                     .insert(StoreEntry {
                         tag: GLOBAL_SIGNATURE_COUNTER,
                         data: &buffer,
+                        sensitive: false,
                     })
                     .unwrap();
             }
@@ -312,6 +316,7 @@ impl PersistentStore {
                         StoreEntry {
                             tag: GLOBAL_SIGNATURE_COUNTER,
                             data: &buffer,
+                            sensitive: false,
                         },
                     )
                     .unwrap();
@@ -339,6 +344,7 @@ impl PersistentStore {
         let entry = StoreEntry {
             tag: PIN_HASH,
             data: pin_hash,
+            sensitive: true,
         };
         match self.store.find_one(&Key::PinHash) {
             None => self.store.insert(entry).unwrap(),
@@ -368,6 +374,7 @@ impl PersistentStore {
                 StoreEntry {
                     tag: PIN_RETRIES,
                     data: &[new_value],
+                    sensitive: false,
                 },
             )
             .unwrap();
@@ -381,6 +388,7 @@ impl PersistentStore {
                 StoreEntry {
                     tag: PIN_RETRIES,
                     data: &[MAX_PIN_RETRIES],
+                    sensitive: false,
                 },
             )
             .unwrap();
@@ -466,9 +474,9 @@ mod test {
         let storage = Storage::new(store, options);
         let store = embedded_flash::Store::new(storage, Config).unwrap();
         // We can replace 3 bytes with minimal overhead.
-        assert_eq!(store.replace_len(0), 2 * WORD_SIZE);
-        assert_eq!(store.replace_len(3), 2 * WORD_SIZE);
-        assert_eq!(store.replace_len(4), 3 * WORD_SIZE);
+        assert_eq!(store.replace_len(false, 0), 2 * WORD_SIZE);
+        assert_eq!(store.replace_len(false, 3), 3 * WORD_SIZE);
+        assert_eq!(store.replace_len(false, 4), 3 * WORD_SIZE);
     }
 
     #[test]
diff --git a/src/embedded_flash/store/bitfield.rs b/src/embedded_flash/store/bitfield.rs
index 60c6f869b583e4cf2fb6d4ff35040fe579dc0efb..797c78bd2b2cd12554c2248318e98d68e49f3c33 100644
--- a/src/embedded_flash/store/bitfield.rs
+++ b/src/embedded_flash/store/bitfield.rs
@@ -55,6 +55,11 @@ impl ByteGap {
             bit + 8 * self.length
         }
     }
+
+    /// Returns the slice of `data` corresponding to the gap.
+    pub fn slice(self, data: &[u8]) -> &[u8] {
+        &data[self.start..self.start + self.length]
+    }
 }
 
 /// Returns whether a bit is set in a sequence of bits.
diff --git a/src/embedded_flash/store/format.rs b/src/embedded_flash/store/format.rs
index cbef61f5e5981cae5b257bedc919ace462ed9449..447506c513b95ed6a234731018a38c6f83275514 100644
--- a/src/embedded_flash/store/format.rs
+++ b/src/embedded_flash/store/format.rs
@@ -59,6 +59,17 @@ pub struct Format {
     /// - 1 for insert entries.
     replace_bit: usize,
 
+    /// Whether a user entry has sensitive data.
+    ///
+    /// - 0 for sensitive data.
+    /// - 1 for non-sensitive data.
+    ///
+    /// When a user entry with sensitive data is deleted, the data is overwritten with zeroes. This
+    /// feature is subject to the same guarantees as all other features of the store, in particular
+    /// deleting a sensitive entry is atomic. See the store module-level documentation for more
+    /// information.
+    sensitive_bit: usize,
+
     /// The data length of a user entry.
     length_range: bitfield::BitRange,
 
@@ -138,8 +149,9 @@ impl Format {
         let deleted_bit = present_bit + 1;
         let internal_bit = deleted_bit + 1;
         let replace_bit = internal_bit + 1;
+        let sensitive_bit = replace_bit + 1;
         let length_range = bitfield::BitRange {
-            start: replace_bit + 1,
+            start: sensitive_bit + 1,
             length: byte_bits,
         };
         let tag_range = bitfield::BitRange {
@@ -182,6 +194,7 @@ impl Format {
             deleted_bit,
             internal_bit,
             replace_bit,
+            sensitive_bit,
             length_range,
             tag_range,
             replace_page_range,
@@ -196,10 +209,11 @@ impl Format {
         // Make sure all the following conditions hold:
         // - The page header is one word.
         // - The internal entry is one word.
-        // - The entry header fits in one word.
+        // - The entry header fits in one word (which is equivalent to the entry header size being
+        //   exactly one word for sensitive entries).
         if format.page_header_size() != word_size
             || format.internal_entry_size() != word_size
-            || format.header_size() > word_size
+            || format.header_size(true) != word_size
         {
             return None;
         }
@@ -220,28 +234,46 @@ impl Format {
     /// Returns the entry header length in bytes.
     ///
     /// This is the smallest number of bytes necessary to store all fields of the entry info up to
-    /// and including `length`.
-    pub fn header_size(&self) -> usize {
-        self.bits_to_bytes(self.length_range.end())
+    /// and including `length`. For sensitive entries, the result is word-aligned.
+    pub fn header_size(&self, sensitive: bool) -> usize {
+        let mut size = self.bits_to_bytes(self.length_range.end());
+        if sensitive {
+            // We need to align to the next word boundary so that wiping the user data will not
+            // count as a write to the header.
+            size = self.align_word(size);
+        }
+        size
+    }
+
+    /// Returns the entry header length in bytes.
+    ///
+    /// This is a convenience function for `header_size` above.
+    fn header_offset(&self, entry: &[u8]) -> usize {
+        self.header_size(self.is_sensitive(entry))
     }
 
     /// Returns the entry info length in bytes.
     ///
     /// This is the number of bytes necessary to store all fields of the entry info. This also
-    /// includes the internal padding to protect the `committed` bit from the `deleted` bit.
-    fn info_size(&self, is_replace: IsReplace) -> usize {
+    /// includes the internal padding to protect the `committed` bit from the `deleted` bit and to
+    /// protect the entry info from the user data for sensitive entries.
+    fn info_size(&self, is_replace: IsReplace, sensitive: bool) -> usize {
         let suffix_bits = 2; // committed + complete
         let info_bits = match is_replace {
             IsReplace::Replace => self.replace_byte_range.end() + suffix_bits,
             IsReplace::Insert => self.tag_range.end() + suffix_bits,
         };
-        let info_size = self.bits_to_bytes(info_bits);
+        let mut info_size = self.bits_to_bytes(info_bits);
         // If the suffix bits would end up in the header, we need to add one byte for them.
-        if info_size == self.header_size() {
-            info_size + 1
-        } else {
-            info_size
+        let header_size = self.header_size(sensitive);
+        if info_size <= header_size {
+            info_size = header_size + 1;
+        }
+        // If the entry is sensitive, we need to align to the next word boundary.
+        if sensitive {
+            info_size = self.align_word(info_size);
         }
+        info_size
     }
 
     /// Returns the length in bytes of an entry.
@@ -249,8 +281,8 @@ impl Format {
     /// This depends on the length of the user data and whether the entry replaces an old entry or
     /// is an insertion. This also includes the internal padding to protect the `committed` bit from
     /// the `deleted` bit.
-    pub fn entry_size(&self, is_replace: IsReplace, length: usize) -> usize {
-        let mut entry_size = length + self.info_size(is_replace);
+    pub fn entry_size(&self, is_replace: IsReplace, sensitive: bool, length: usize) -> usize {
+        let mut entry_size = length + self.info_size(is_replace, sensitive);
         let word_size = self.word_size;
         entry_size = self.align_word(entry_size);
         // The entry must be at least 2 words such that the `committed` and `deleted` bits are on
@@ -308,6 +340,14 @@ impl Format {
         bitfield::set_zero(self.replace_bit, header, bitfield::NO_GAP)
     }
 
+    pub fn is_sensitive(&self, header: &[u8]) -> bool {
+        bitfield::is_zero(self.sensitive_bit, header, bitfield::NO_GAP)
+    }
+
+    pub fn set_sensitive(&self, header: &mut [u8]) {
+        bitfield::set_zero(self.sensitive_bit, header, bitfield::NO_GAP)
+    }
+
     pub fn get_length(&self, header: &[u8]) -> usize {
         bitfield::get_range(self.length_range, header, bitfield::NO_GAP)
     }
@@ -317,16 +357,19 @@ impl Format {
     }
 
     pub fn get_data<'a>(&self, entry: &'a [u8]) -> &'a [u8] {
-        &entry[self.header_size()..][..self.get_length(entry)]
+        &entry[self.header_offset(entry)..][..self.get_length(entry)]
     }
 
     /// Returns the span of user data in an entry.
     ///
     /// The complement of this gap in the entry is exactly the entry info. The header is before the
     /// gap and the footer is after the gap.
-    fn entry_gap(&self, entry: &[u8]) -> bitfield::ByteGap {
-        let start = self.header_size();
-        let length = self.get_length(entry);
+    pub fn entry_gap(&self, entry: &[u8]) -> bitfield::ByteGap {
+        let start = self.header_offset(entry);
+        let mut length = self.get_length(entry);
+        if self.is_sensitive(entry) {
+            length = self.align_word(length);
+        }
         bitfield::ByteGap { start, length }
     }
 
@@ -406,16 +449,23 @@ impl Format {
 
     /// Builds an entry for replace or insert operations.
     pub fn build_entry(&self, replace: Option<Index>, user_entry: StoreEntry) -> Vec<u8> {
-        let StoreEntry { tag, data } = user_entry;
+        let StoreEntry {
+            tag,
+            data,
+            sensitive,
+        } = user_entry;
         let is_replace = match replace {
             None => IsReplace::Insert,
             Some(_) => IsReplace::Replace,
         };
-        let entry_len = self.entry_size(is_replace, data.len());
+        let entry_len = self.entry_size(is_replace, sensitive, data.len());
         let mut entry = Vec::with_capacity(entry_len);
         // Build the header.
-        entry.resize(self.header_size(), 0xff);
+        entry.resize(self.header_size(sensitive), 0xff);
         self.set_present(&mut entry[..]);
+        if sensitive {
+            self.set_sensitive(&mut entry[..]);
+        }
         self.set_length(&mut entry[..], data.len());
         // Add the data.
         entry.extend_from_slice(data);
diff --git a/src/embedded_flash/store/mod.rs b/src/embedded_flash/store/mod.rs
index 0431073009286219699fcce75307c66b1a957e5d..c5cf8386aad0cbe5cfcf71272b424db9c22028ea 100644
--- a/src/embedded_flash/store/mod.rs
+++ b/src/embedded_flash/store/mod.rs
@@ -43,6 +43,28 @@
 //! The data-structure can be configured with the `StoreConfig` trait. By implementing this trait,
 //! the number of possible tags and the association between keys and entries are defined.
 //!
+//! # Properties
+//!
+//! The data-structure provides the following properties:
+//! - When an operation returns success, then the represented multi-set is updated accordingly. For
+//!   example, an inserted entry can be found without alteration until replaced or deleted.
+//! - When an operation returns an error, the resulting multi-set state is described in the error
+//!   documentation.
+//! - When power is lost before an operation returns, the operation will either succeed or be
+//!   rolled-back on the next initialization. So the multi-set would be either left unchanged or
+//!   updated accordingly.
+//!
+//! Those properties rely on the following assumptions:
+//! - Writing a word to flash is atomic. When power is lost, the word is either fully written or not
+//!   written at all.
+//! - Reading a word from flash is deterministic. When power is lost while writing or erasing a word
+//!   (erasing a page containing that word), reading that word repeatedly returns the same result
+//!   (until it is written or its page is erased).
+//! - To decide whether a page has been erased, it is enough to test if all its bits are equal to 1.
+//!
+//! The properties may still hold outside those assumptions but with weaker probabilities as the
+//! usage diverges from the assumptions.
+//!
 //! # Implementation
 //!
 //! The store is a page-aligned sequence of bits. It matches the following grammar:
@@ -57,7 +79,7 @@
 //!     new_page:page_bits
 //!     Padding(word)
 //! Entry := Header Data Footer
-//! // Let X be the byte following `length` in `Info`.
+//! // Let X be the byte (word-aligned for sensitive queries) following `length` in `Info`.
 //! Header := Info[..X]  // must fit in one word
 //! Footer := Info[X..]  // must fit in one word
 //! Info :=
@@ -65,6 +87,7 @@
 //!     deleted:1
 //!     internal=1
 //!     replace:1
+//!     sensitive:1
 //!     length:byte_bits
 //!     tag:tag_bits
 //!     [  // present if `replace` is 0
@@ -109,15 +132,16 @@
 //!    0.1   deleted
 //!    0.2   internal
 //!    0.3   replace
-//!    0.4   length (9 bits)
-//!    1.5   tag (least significant 3 bits out of 5)
+//!    0.4   sensitive
+//!    0.5   length (9 bits)
+//!    1.6   tag (least significant 2 bits out of 5)
 //! (the header ends at the first byte boundary after `length`)
 //!    2.0   <user data> (2 bytes in this example)
 //! (the footer starts immediately after the user data)
-//!    4.0   tag (most significant 2 bits out of 5)
-//!    4.2   replace_page (6 bits)
-//!    5.0   replace_byte (9 bits)
-//!    6.1   padding (make sure the 2 properties below hold)
+//!    4.0   tag (most significant 3 bits out of 5)
+//!    4.3   replace_page (6 bits)
+//!    5.1   replace_byte (9 bits)
+//!    6.2   padding (make sure the 2 properties below hold)
 //!    7.6   committed
 //!    7.7   complete (on a different word than `present`)
 //!    8.0   <end> (word-aligned)
@@ -203,6 +227,11 @@ pub struct StoreEntry<'a> {
 
     /// The data of the entry.
     pub data: &'a [u8],
+
+    /// Whether the data is sensitive.
+    ///
+    /// Sensitive data is overwritten with zeroes when the entry is deleted.
+    pub sensitive: bool,
 }
 
 /// Implements a configurable multi-set on top of any storage.
@@ -262,6 +291,7 @@ impl<S: Storage, C: StoreConfig> Store<S, C> {
                     StoreEntry {
                         tag: self.format.get_tag(entry),
                         data: self.format.get_data(entry),
+                        sensitive: self.format.is_sensitive(entry),
                     },
                 ))
             } else {
@@ -326,7 +356,7 @@ impl<S: Storage, C: StoreConfig> Store<S, C> {
         self.format.validate_entry(new)?;
         let mut old_index = old.index;
         // Find a slot.
-        let entry_len = self.replace_len(new.data.len());
+        let entry_len = self.replace_len(new.sensitive, new.data.len());
         let index = self.find_slot_for_write(entry_len, Some(&mut old_index))?;
         // Build a new entry replacing the old one.
         let entry = self.format.build_entry(Some(old_index), new);
@@ -360,17 +390,20 @@ impl<S: Storage, C: StoreConfig> Store<S, C> {
     /// Returns the byte cost of a replace operation.
     ///
     /// Computes the length in bytes that would be used in the storage if a replace operation is
-    /// executed provided the data of the new entry has `length` bytes.
-    pub fn replace_len(&self, length: usize) -> usize {
-        self.format.entry_size(IsReplace::Replace, length)
+    /// executed provided the data of the new entry has `length` bytes and whether this data is
+    /// sensitive.
+    pub fn replace_len(&self, sensitive: bool, length: usize) -> usize {
+        self.format
+            .entry_size(IsReplace::Replace, sensitive, length)
     }
 
     /// Returns the byte cost of an insert operation.
     ///
     /// Computes the length in bytes that would be used in the storage if an insert operation is
-    /// executed provided the data of the inserted entry has `length` bytes.
-    pub fn insert_len(&self, length: usize) -> usize {
-        self.format.entry_size(IsReplace::Insert, length)
+    /// executed provided the data of the inserted entry has `length` bytes and whether this data is
+    /// sensitive.
+    pub fn insert_len(&self, sensitive: bool, length: usize) -> usize {
+        self.format.entry_size(IsReplace::Insert, sensitive, length)
     }
 
     /// Returns the erase count of all pages.
@@ -410,8 +443,11 @@ impl<S: Storage, C: StoreConfig> Store<S, C> {
                 let entry_index = index;
                 let entry = self.read_entry(index);
                 index.byte += entry.len();
-                if !self.format.is_alive(entry) {
-                    // Skip deleted entries (or the page padding).
+                if !self.format.is_present(entry) {
+                    // Reached the end of the page.
+                } else if self.format.is_deleted(entry) {
+                    // Wipe sensitive data if needed.
+                    self.wipe_sensitive_data(entry_index);
                 } else if self.format.is_internal(entry) {
                     // Finish page compaction.
                     self.erase_page(entry_index);
@@ -449,6 +485,31 @@ impl<S: Storage, C: StoreConfig> Store<S, C> {
     /// The provided index must point to the beginning of an entry.
     fn delete_index(&mut self, index: Index) {
         self.update_word(index, |format, word| format.set_deleted(word));
+        self.wipe_sensitive_data(index);
+    }
+
+    /// Wipes the data of a sensitive entry.
+    ///
+    /// If the entry at the provided index is sensitive, overwrites the data with zeroes. Otherwise,
+    /// does nothing.
+    fn wipe_sensitive_data(&mut self, mut index: Index) {
+        let entry = self.read_entry(index);
+        debug_assert!(self.format.is_present(entry));
+        debug_assert!(self.format.is_deleted(entry));
+        if self.format.is_internal(entry) || !self.format.is_sensitive(entry) {
+            // No need to wipe the data.
+            return;
+        }
+        let gap = self.format.entry_gap(entry);
+        let data = gap.slice(entry);
+        if data.iter().all(|&byte| byte == 0x00) {
+            // The data is already wiped.
+            return;
+        }
+        index.byte += gap.start;
+        self.storage
+            .write_slice(index, &vec![0; gap.length])
+            .unwrap();
     }
 
     /// Finds a page with enough free space.
@@ -555,10 +616,13 @@ impl<S: Storage, C: StoreConfig> Store<S, C> {
         } else if self.format.is_internal(first_byte) {
             self.format.internal_entry_size()
         } else {
-            let header = self.read_slice(index, self.format.header_size());
+            // We don't know if the entry is sensitive or not, but it doesn't matter here. We just
+            // need to read the replace, sensitive, and length fields.
+            let header = self.read_slice(index, self.format.header_size(false));
             let replace = self.format.is_replace(header);
+            let sensitive = self.format.is_sensitive(header);
             let length = self.format.get_length(header);
-            self.format.entry_size(replace, length)
+            self.format.entry_size(replace, sensitive, length)
         };
         // Truncate the length to fit the page. This can only happen in case of corruption or
         // partial writes.
@@ -673,7 +737,7 @@ impl<S: Storage, C: StoreConfig> Store<S, C> {
         // Save the old page index and erase count to the new page.
         let erase_index = new_index;
         let erase_entry = self.format.build_erase_entry(old_page, erase_count);
-        self.storage.write_slice(new_index, &erase_entry).unwrap();
+        self.write_entry(new_index, &erase_entry);
         // Erase the page.
         self.erase_page(erase_index);
         // Increase generation.
@@ -728,6 +792,25 @@ impl<C: StoreConfig> Store<BufferStorage, C> {
     pub fn set_erase_count(&mut self, page: usize, erase_count: usize) {
         self.initialize_page(page, erase_count);
     }
+
+    /// Returns whether all deleted sensitive entries have been wiped.
+    pub fn deleted_entries_are_wiped(&self) -> bool {
+        for (_, entry) in Iter::new(self) {
+            if !self.format.is_present(entry)
+                || !self.format.is_deleted(entry)
+                || self.format.is_internal(entry)
+                || !self.format.is_sensitive(entry)
+            {
+                continue;
+            }
+            let gap = self.format.entry_gap(entry);
+            let data = gap.slice(entry);
+            if !data.iter().all(|&byte| byte == 0x00) {
+                return false;
+            }
+        }
+        true
+    }
 }
 
 /// Maps an index from an old page to a new page if needed.
@@ -843,7 +926,27 @@ mod tests {
         let tag = 0;
         let key = 1;
         let data = &[key, 2];
-        let entry = StoreEntry { tag, data };
+        let entry = StoreEntry {
+            tag,
+            data,
+            sensitive: false,
+        };
+        store.insert(entry).unwrap();
+        assert_eq!(store.iter().count(), 1);
+        assert_eq!(store.find_one(&key).unwrap().1, entry);
+    }
+
+    #[test]
+    fn insert_sensitive_ok() {
+        let mut store = new_store();
+        let tag = 0;
+        let key = 1;
+        let data = &[key, 4];
+        let entry = StoreEntry {
+            tag,
+            data,
+            sensitive: true,
+        };
         store.insert(entry).unwrap();
         assert_eq!(store.iter().count(), 1);
         assert_eq!(store.find_one(&key).unwrap().1, entry);
@@ -857,6 +960,25 @@ mod tests {
         let entry = StoreEntry {
             tag,
             data: &[key, 2],
+            sensitive: false,
+        };
+        store.insert(entry).unwrap();
+        assert_eq!(store.find_all(&key).count(), 1);
+        let (index, _) = store.find_one(&key).unwrap();
+        store.delete(index).unwrap();
+        assert_eq!(store.find_all(&key).count(), 0);
+        assert_eq!(store.iter().count(), 0);
+    }
+
+    #[test]
+    fn delete_sensitive_ok() {
+        let mut store = new_store();
+        let tag = 0;
+        let key = 1;
+        let entry = StoreEntry {
+            tag,
+            data: &[key, 2],
+            sensitive: true,
         };
         store.insert(entry).unwrap();
         assert_eq!(store.find_all(&key).count(), 1);
@@ -864,6 +986,7 @@ mod tests {
         store.delete(index).unwrap();
         assert_eq!(store.find_all(&key).count(), 0);
         assert_eq!(store.iter().count(), 0);
+        assert!(store.deleted_entries_are_wiped());
     }
 
     #[test]
@@ -875,6 +998,7 @@ mod tests {
             .insert(StoreEntry {
                 tag,
                 data: &[key, 0],
+                sensitive: false,
             })
             .is_ok()
         {
@@ -892,6 +1016,7 @@ mod tests {
             .insert(StoreEntry {
                 tag,
                 data: &[key, 0],
+                sensitive: false,
             })
             .is_ok()
         {
@@ -903,6 +1028,7 @@ mod tests {
             .insert(StoreEntry {
                 tag: 0,
                 data: &[key, 0],
+                sensitive: false,
             })
             .unwrap();
         for k in 1..=key {
@@ -916,7 +1042,11 @@ mod tests {
         let tag = 0;
         let key = 1;
         let data = &[key, 2];
-        let entry = StoreEntry { tag, data };
+        let entry = StoreEntry {
+            tag,
+            data,
+            sensitive: false,
+        };
         store.insert(entry).unwrap();
 
         // Reboot the store.
@@ -934,10 +1064,12 @@ mod tests {
         let old_entry = StoreEntry {
             tag,
             data: &[key, 2, 3, 4, 5, 6],
+            sensitive: false,
         };
         let new_entry = StoreEntry {
             tag,
             data: &[key, 7, 8, 9],
+            sensitive: false,
         };
         let mut delay = 0;
         loop {
@@ -973,6 +1105,7 @@ mod tests {
                 .insert(StoreEntry {
                     tag,
                     data: &[key, 0],
+                    sensitive: false,
                 })
                 .is_ok()
             {
@@ -983,7 +1116,14 @@ mod tests {
             let (index, _) = store.find_one(&1).unwrap();
             store.arm_snapshot(delay);
             store
-                .replace(index, StoreEntry { tag, data: &[1, 1] })
+                .replace(
+                    index,
+                    StoreEntry {
+                        tag,
+                        data: &[1, 1],
+                        sensitive: false,
+                    },
+                )
                 .unwrap();
             let (complete, store) = match store.get_snapshot() {
                 Err(_) => (true, store.get_storage()),
@@ -995,7 +1135,11 @@ mod tests {
                 assert_eq!(store.find_all(&k).count(), 1);
                 assert_eq!(
                     store.find_one(&k).unwrap().1,
-                    StoreEntry { tag, data: &[k, 0] }
+                    StoreEntry {
+                        tag,
+                        data: &[k, 0],
+                        sensitive: false,
+                    }
                 );
             }
             assert_eq!(store.find_all(&1).count(), 1);
@@ -1012,7 +1156,11 @@ mod tests {
     #[test]
     fn invalid_tag() {
         let mut store = new_store();
-        let entry = StoreEntry { tag: 1, data: &[] };
+        let entry = StoreEntry {
+            tag: 1,
+            data: &[],
+            sensitive: false,
+        };
         assert_eq!(store.insert(entry), Err(StoreError::InvalidTag));
     }
 
@@ -1022,6 +1170,7 @@ mod tests {
         let entry = StoreEntry {
             tag: 0,
             data: &[0; PAGE_SIZE],
+            sensitive: false,
         };
         assert_eq!(store.insert(entry), Err(StoreError::StoreFull));
     }
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 3139864d391ab654bfb9c27ca8dcd3e4e9a2d58e..ba44dd690f2db52ab9c928975f85c34c1c6f5bea 160000
--- a/third_party/tock
+++ b/third_party/tock
@@ -1 +1 @@
-Subproject commit 3139864d391ab654bfb9c27ca8dcd3e4e9a2d58e
+Subproject commit ba44dd690f2db52ab9c928975f85c34c1c6f5bea