diff --git a/README.md b/README.md
index ade3a63d4e80bbb83ab3d6c9914b732aa8608cf8..dfca6996a0572edc8a4a4991fbe222e6d9cc6b6f 100644
--- a/README.md
+++ b/README.md
@@ -49,23 +49,22 @@ For a more detailed guide, please refer to our
     ./setup.sh
     ```
 
-2.  If you have a Nordic development board that doesn't already have Tock OS
-    installed, you can install both TockOS and the OpenSK application by running
-    the following command, depending on the board you have:
+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:
 
     ```shell
     # Nordic nRF52840-DK board
-    board=nrf52840dk ./deploy.sh os app
+    ./deploy.py os --board=nrf52840dk
     # Nordic nRF52840-Dongle
-    board=nrf52840_dongle ./deploy.sh os app
+    ./deploy.py os --board=nrf52840_dongle
     ```
 
-3.  If Tock OS is already installed and you want to install/update the OpenSK
-    application on your board (**Warning**: it will erase the locally stored
-    credentials), run:
+3.  Next step is to install/update the OpenSK application on your board
+    (**Warning**: it will erase the locally stored credentials). Run:
 
     ```shell
-    ./deploy.sh app
+    ./deploy.py app --opensk
     ```
 
 4.  On Linux, you may want to avoid the need for `root` privileges to interact
@@ -83,7 +82,7 @@ If you build your own security key, depending on the hardware you use, there are
 a few things you can personalize:
 
 1.  If you have multiple buttons, choose the buttons responsible for user
-    presence in main.rs.
+    presence in `main.rs`.
 2.  Decide whether you want to use batch attestation. There is a boolean flag in
     `ctap/mod.rs`. It is mandatory for U2F, and you can create your own
     self-signed certificate. The flag is used for FIDO2 and has some privacy
diff --git a/deploy.py b/deploy.py
new file mode 100755
index 0000000000000000000000000000000000000000..d1488d95df927240c1a244c42363ef3e812b83f1
--- /dev/null
+++ b/deploy.py
@@ -0,0 +1,425 @@
+#!/usr/bin/env python3
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# Lint as: python3
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import argparse
+import colorama
+import copy
+import os
+import shutil
+import subprocess
+import sys
+from tockloader import tab, tbfh, tockloader
+
+
+# This structure allows us in the future to also support out-of-tree boards.
+SUPPORTED_BOARDS = {
+    "nrf52840_dk": "third_party/tock/boards/nordic/nrf52840dk",
+    "nrf52840_dongle": "third_party/tock/boards/nordic/nrf52840_dongle"
+}
+
+# The STACK_SIZE value below must match the one used in the linker script
+# used by the board.
+# e.g. for Nordic nRF52840 boards the file is `nrf52840dk_layout.ld`.
+STACK_SIZE = 0x4000
+
+# The following value must match the one used in the file
+# `src/entry_point.rs`
+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")))):
+            boards.append(name)
+    return tuple(set(boards))
+
+
+def fatal(msg):
+    print("{style_begin}fatal:{style_end} {message}".format(
+        style_begin=colorama.Fore.RED + colorama.Style.BRIGHT,
+        style_end=colorama.Style.RESET_ALL,
+        message=msg))
+    sys.exit(1)
+
+
+def error(msg):
+    print("{style_begin}error:{style_end} {message}".format(
+        style_begin=colorama.Fore.RED,
+        style_end=colorama.Style.RESET_ALL,
+        message=msg))
+
+
+def info(msg):
+    print("{style_begin}info:{style_end} {message}".format(
+        style_begin=colorama.Fore.GREEN + colorama.Style.BRIGHT,
+        style_end=colorama.Style.RESET_ALL,
+        message=msg))
+
+
+class RemoveConstAction(argparse.Action):
+    def __init__(self,
+                 option_strings,
+                 dest,
+                 const,
+                 default=None,
+                 required=False,
+                 help=None,
+                 metavar=None):
+        super(RemoveConstAction, self).__init__(
+            option_strings=option_strings,
+            dest=dest,
+            nargs=0,
+            const=const,
+            default=default,
+            required=required,
+            help=help,
+            metavar=metavar)
+
+    def __call__(self, parser, namespace, values, option_string=None):
+        # Code is simply a modified version of the AppendConstAction from argparse
+        # https://github.com/python/cpython/blob/master/Lib/argparse.py#L138-L147
+        # https://github.com/python/cpython/blob/master/Lib/argparse.py#L1028-L1052
+        items = getattr(namespace, self.dest, [])
+        if type(items) is list:
+            items = items[:]
+        else:
+            items = copy.copy(items)
+        if self.const in items:
+            self.remove(self.const)
+        setattr(namespace, self.dest, items)
+
+
+class OpenSKInstaller(object):
+    def __init__(self, args):
+        colorama.init()
+        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")
+        self.tockloader_default_args = argparse.Namespace(
+            arch="cortex-m4",
+            board=getattr(self.args, "board", "nrf52840"),
+            debug=False,
+            force=False,
+            jlink=True,
+            jlink_device="nrf52840_xxaa",
+            jlink_if="swd",
+            jlink_speed=1200,
+            jtag=False,
+            no_bootloader_entry=False,
+            page_size=4096,
+            port=None,
+        )
+
+    def checked_command_output(self, cmd):
+        cmd_output = ""
+        try:
+            cmd_output = subprocess.check_output(cmd)
+        except subprocess.CalledProcessError as e:
+            fatal("Failed to execute {}: {}".format(cmd[0], str(e)))
+            # Unreachable because fatal() will exit
+        return cmd_output.decode()
+
+    def update_rustc_if_needed(self):
+        target_toolchain_fullstring = "stable"
+        with open("rust-toolchain", "r") as f:
+            target_toolchain_fullstring = f.readline().strip()
+        target_toolchain = target_toolchain_fullstring.split("-", maxsplit=1)
+        if len(target_toolchain) == 1:
+            # If we target the stable version of rust, we won't have a date
+            # associated to the version and split will only return 1 item.
+            # To avoid failing later when accessing the date, we insert an
+            # empty value.
+            target_toolchain.append('')
+        current_version = self.checked_command_output(["rustc", "--version"])
+        if not all(
+            (target_toolchain[0] in current_version,
+             target_toolchain[1] in current_version)):
+            info("Updating rust toolchain to {}".format(
+                "-".join(target_toolchain)))
+            # Need to update
+            self.checked_command_output(
+                ["rustup", "install", target_toolchain_fullstring])
+            self.checked_command_output(
+                ["rustup", "target", "add", "thumbv7em-none-eabi"])
+        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_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_and_install_opensk(self):
+        assert(self.args.application)
+        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))
+
+    def generate_crypto_materials(self, force_regenerate):
+        has_error = subprocess.call([
+            os.path.join("tools", "gen_key_materials.sh"),
+            "Y" if force_regenerate else "N",
+        ])
+        if has_error:
+            error((
+                "Something went wrong while trying to generate ECC "
+                "key and/or certificate for OpenSK"))
+
+    def install_elf_file(self, elf_path):
+        assert(self.args.application)
+        package_parameter = "-n"
+        elf2tab_ver = self.checked_command_output(
+            ["elf2tab", "--version"]).split(' ', maxsplit=1)[1]
+        # Starting from v0.5.0-dev the parameter changed.
+        # Current pyblished crate is 0.4.0 but we don't want developers
+        # running the HEAD from github to be stuck
+        if "0.5.0-dev" in elf2tab_ver:
+            package_parameter = "--package-name"
+        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"
+        ])
+        self.install_padding()
+        info("Installing Tock application {}".format(self.args.application))
+        args = copy.copy(self.tockloader_default_args)
+        setattr(args, "app_address", 0x40000)
+        setattr(args, "erase", self.args.clear_apps)
+        setattr(args, "make", False)
+        setattr(args, "no_replace", False)
+        setattr(args, "sticky", False)
+        tock = tockloader.TockLoader(args)
+        tock.open(args)
+        tabs = [tab.TAB(tab_filename)]
+        try:
+            tock.install(tabs, replace="yes",
+                         erase=args.erase, sticky=args.sticky)
+        except tockloader.exceptions.TockLoaderException as e:
+            fatal("Couldn't install Tock application {}: {}".format(
+                  self.args.application, str(e)))
+
+    def install_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["flags"] = 0
+        padding = fake_header.get_binary()
+        info("Flashing padding application")
+        args = copy.copy(self.tockloader_default_args)
+        setattr(args, "address", 0x30000)
+        tock = tockloader.TockLoader(args)
+        tock.open(args)
+        try:
+            tock.flash_binary(padding, args.address)
+        except tockloader.exceptions.TockLoaderException as e:
+            fatal("Couldn't install padding: {}".format(str(e)))
+
+    def clear_apps(self):
+        args = copy.copy(self.tockloader_default_args)
+        setattr(args, "app_address", 0x40000)
+        info("Erasing all installed applications")
+        tock = tockloader.TockLoader(args)
+        tock.open(args)
+        try:
+            tock.erase_apps(False)
+        except tockloader.exceptions.TockLoaderException as e:
+            # Erasing apps is not critical
+            info(("A non-critical error occured while erasing "
+                  "apps: {}".format(str(e))))
+
+    def run(self):
+        if self.args.action is None:
+            # Nothing to do
+            return
+
+        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 self.args.action == "app":
+            if self.args.application is None:
+                fatal("Unspecified application")
+            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()
+
+
+def main(args):
+    # Make sure the current working directory is the right one before running
+    os.chdir(os.path.realpath(os.path.dirname(__file__)))
+    OpenSKInstaller(args).run()
+
+
+if __name__ == '__main__':
+    shared_parser = argparse.ArgumentParser(add_help=False)
+    shared_parser.add_argument(
+        "--dont-clear-apps",
+        action="store_false",
+        default=True,
+        dest="clear_apps",
+        help=(
+            "When installing an application, previously installed "
+            "applications won't be erased from the board."
+        ),
+    )
+
+    parser = argparse.ArgumentParser()
+    commands = 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."
+        ),
+    )
+    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(
+        "--panic-console",
+        action="append_const",
+        const="panic_console",
+        dest="features",
+        help=(
+            "In case of application panic, the console will be used to "
+            "output messages before starting blinking the LEDs on the "
+            "board."
+        ),
+    )
+    app_commands.add_argument(
+        "--no-u2f",
+        action=RemoveConstAction,
+        const="with_ctap1",
+        dest="features",
+        help=(
+            "Compiles the OpenSK application without backward compatible "
+            "support for U2F/CTAP1 protocol."
+        ),
+    )
+    app_commands.add_argument(
+        "--regen-keys",
+        action="store_true",
+        default=False,
+        dest="regenerate_keys",
+        help=(
+            "Forces the generation of files (certificates and private keys) "
+            "under the crypto_data/ directory. "
+            "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)."
+        ),
+    )
+    apps = app_commands.add_mutually_exclusive_group()
+    apps.add_argument(
+        "--opensk",
+        dest="application",
+        action="store_const",
+        const="ctap2",
+        help="Compiles and installs the OpenSK application."
+    )
+    apps.add_argument(
+        "--crypto_bench",
+        dest="application",
+        action="store_const",
+        const="crypto_bench",
+        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.parse_args())
diff --git a/deploy.sh b/deploy.sh
deleted file mode 100755
index e86fcd303936d6fd08adef515bcce82a0ac0a9b7..0000000000000000000000000000000000000000
--- a/deploy.sh
+++ /dev/null
@@ -1,327 +0,0 @@
-#!/usr/bin/env bash
-# Copyright 2019 Google LLC
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-set -e
-
-if [ "x$VERBOSE" != "x" ]
-then
-  set -x
-fi
-
-info_text="$(tput bold)info:$(tput sgr0)"
-error_text="$(tput bold)error:$(tput sgr0)"
-
-tab_folder="target/tab"
-# elf2tab requires a file named "cortex-m4", so this path is used for all
-# target applications.
-elf_file_name="${tab_folder}/cortex-m4.elf"
-
-# elf2tab 0.4.0 and below uses "-n" flag but 0.5.0-dev changes that "-p" or
-# "--package-name"
-# We try to be compatible with both versions.
-elf2tab_package_param="-n"
-if which elf2tab > /dev/null 2>&1
-then
-  if [ "$(elf2tab --version | cut -d' ' -f2)" = "0.5.0-dev" ]
-  then
-    # Short parameter is "-p" but long parameter names should be prefered
-    # when they are used in scripts.
-    elf2tab_package_param="--package-name"
-  fi
-else
-  echo ""
-  echo "Command elf2tab not found. Have you run the setup.sh script?"
-  exit 2
-fi
-
-# We need to specify the board explicitly to be able to flash after 0x80000.
-tockloader_flags=(
-  --jlink
-  --board="${board:-nrf52840}"
-  --arch=cortex-m4
-  --jlink-device=nrf52840_xxaa
-  --page-size=4096
-)
-
-declare -A supported_boards
-supported_boards["nrf52840dk"]="Y"
-supported_boards["nrf52840_dongle"]="Y"
-
-declare -A enabled_features=( [with_ctap1]=Y )
-
-print_usage () {
-  cat <<EOH
-Usage: $0 [options...] <actions...>
-
-Example:
-  In order to install TockOS and a debug version of OpenSK on a Nordic nRF52840-DK
-  board, you need to run the following command:
-    board=nrf52840dk $0 --panic-console os app_debug
-
-Actions:
-  os
-    Compiles and installs Tock OS on the selected board.
-    The target board must be indicated by setting the environment
-    variable \$board
-
-  app
-    Compiles and installs OpenSK application.
-
-  app_debug
-    Compiles and installs OpenSK application in debug mode (i.e. more debug messages
-    will be sent over the console port such as hexdumps of packets)
-
-  crypto_bench
-    Compiles and installs the crypto_bench example that tests the performance
-    of the cryptographic algorithms on the board.
-
-Options:
-  --dont-clear-apps
-      When installing an application, previously installed applications won't
-      be erased from the board.
-
-  --no-u2f
-      Compiles OpenSK application without backward compatible support for
-      U2F/CTAP1 protocol.
-
-
-  --regen-keys
-      Forces the generation of src/ctap/key_materials.rs file.
-      This won't force re-generate OpenSSL files under crypto_data/ directory.
-      If the OpenSSL files needs to be re-generated, simply delete them (or
-      the whole directory).
-
-  --panic-console
-      In case of application panic, the console will be used to output messages
-      before starting blinking the LEDs on the board.
-
-EOH
-}
-
-display_supported_boards () {
-  echo "$info_text Currently supported boards are:"
-  for b in ${!supported_boards[@]}
-  do
-    if [ -d "third_party/tock/boards/nordic/$b" -a \
-         -e "third_party/tock/boards/nordic/$b/Cargo.toml" ]
-    then
-      echo "  - $b"
-    fi
-  done
-}
-
-# Import generate_crypto_materials function
-source tools/gen_key_materials.sh
-
-build_app_padding () {
-  # On nRF52840, the MMU can have 8 sub-regions and the flash size is 0x1000000.
-  # By default, applications are flashed at 0x30000 which means the maximum size
-  # for an application is 0x40000 (an application of size 0x80000 would need 16
-  # sub-regions of size 0x10000; sub-regions need to be aligned on their size).
-  # This padding permits to have the application start at 0x40000 and increase
-  # the maximum application size to 0x80000 (with 4 sub-regions of size
-  # 0x40000).
-  (
-    # Version: 2
-    echo -n "0200"
-    # Header size: 0x10
-    echo -n "1000"
-    # Total size: 0x10000
-    echo -n "00000100"
-    # Flags: 0
-    echo -n "00000000"
-    # Checksum
-    echo -n "02001100"
-  ) | xxd -p -r > "${tab_folder}/padding.bin"
-}
-
-comma_separated () {
-  # Flatten the array
-  # This is equivalent to the following python snippet: ' '.join(arr).replace(' ', ',')
-  local list=$(IFS=$'\n'; echo "$@")
-  if [ "X${list}" != "X" ]
-  then
-    feature_list="${list// /,}"
-  fi
-  echo ${list}
-}
-
-build_app () {
-  local feature_list="$(comma_separated "$@")"
-  cargo build \
-    --release \
-    --target=thumbv7em-none-eabi \
-    --features="${feature_list}"
-
-  mkdir -p "target/tab"
-  cp "target/thumbv7em-none-eabi/release/ctap2" "$elf_file_name"
-
-  elf2tab \
-    "${elf2tab_package_param}" "ctap2" \
-    -o "${tab_folder}/ctap2.tab" \
-    "$elf_file_name" \
-    --stack 16384 \
-    --app-heap 90000 \
-    --kernel-heap 1024 \
-    --protected-region-size=64
-}
-
-build_crypto_bench () {
-  local feature_list="$(comma_separated "$@")"
-  cargo build \
-    --release \
-    --target=thumbv7em-none-eabi \
-    --features="${feature_list}" \
-    --example crypto_bench
-
-  mkdir -p "target/tab"
-  cp "target/thumbv7em-none-eabi/release/examples/crypto_bench" "$elf_file_name"
-
-  elf2tab \
-    "${elf2tab_package_param}" "crypto_bench" \
-    -o "${tab_folder}/crypto_bench.tab" \
-    "$elf_file_name" \
-    --stack 16384 \
-    --app-heap 90000 \
-    --kernel-heap 1024 \
-    --protected-region-size=64
-}
-
-deploy_tock () {
-  if [ "x$board" = "x" ];
-  then
-    echo "$error_text You must set the board in order to install Tock OS (example: \`board=nrf52840dk $0 os\`)"
-    display_supported_boards
-    return 1
-  fi
-  if [ "${supported_boards[$board]+x}" = "x" -a -d "third_party/tock/boards/nordic/$board" ]
-  then
-    make -C third_party/tock/boards/nordic/$board flash
-  else
-    echo "$error_text The board '$board' doesn't seem to be supported"
-    display_supported_boards
-    return 1
-  fi
-}
-
-clear_apps=Y
-install_os=N
-install_app=none
-
-force_generate=N
-has_errors=N
-
-if [ "$#" -eq "0" ]
-then
-  print_usage
-  exit 1
-fi
-
-while [ "$#" -ge "1" ]
-do
-  case "$1" in
-    --dont-clear-apps)
-      clear_apps=N
-    ;;
-
-    --no-u2f)
-      unset enabled_features["with_ctap1"]
-    ;;
-
-    --regen-keys)
-      force_generate=Y
-    ;;
-
-    --panic-console)
-      enabled_features["panic_console"]="Y"
-    ;;
-
-    os)
-      install_os=Y
-    ;;
-
-    app)
-      install_app=ctap2
-    ;;
-
-    app_debug)
-      install_app=ctap2
-      enabled_features["debug_ctap"]="Y"
-    ;;
-
-    crypto_bench)
-      install_app=crypto_bench
-    ;;
-
-    *)
-      echo "$error_text Unsupported option: '"$1"'"
-      has_errors=Y
-    ;;
-  esac
-  shift 1
-done
-
-if [ "$has_errors" = "Y" ]
-then
-  echo ""
-  print_usage
-  exit 1
-fi
-
-# Test if we need to update Rust toolchain
-# rustc --version outputs a version line such as:
-# rustc 1.40.0-nightly (0e8a4b441 2019-10-16)
-# The sed regexp turns it into:
-# nightly-2019-10-16
-current_toolchain=$(rustc --version | sed -e 's/^rustc [0-9]*\.[0-9]*\.[0-9]*-\(nightly\) ([0-9a-f]* \([0-9]*-[0-9]*-[0-9]*\))$/\1-\2/')
-target_toolchain=$(head -n 1 rust-toolchain)
-
-if [ "x${current_toolchain}" != "x${target_toolchain}" ]
-then
-  rustup install "${target_toolchain}"
-  rustup target add thumbv7em-none-eabi
-fi
-
-if [ "$install_os" = "Y" ]
-then
-  deploy_tock
-fi
-
-# Don't try to uninstall app if we don't plan to install after.
-if [ "$install_app" != "none" -a "$clear_apps" = "Y" ]
-then
-  # Uninstall can fail if there's no app already installed.
-  # This is fine and we don't want that to stop the script
-  tockloader uninstall "${tockloader_flags[@]}" -a 0x40000 || true
-fi
-
-if [ "$install_app" = "ctap2" ]
-then
-  generate_crypto_materials "${force_generate}"
-  build_app "${!enabled_features[@]}"
-fi
-
-if [ "$install_app" = "crypto_bench" ]
-then
-  build_crypto_bench "${!enabled_features[@]}"
-fi
-
-if [ "$install_app" != "none" ]
-then
-  build_app_padding
-  tockloader flash "${tockloader_flags[@]}" -a 0x30000 "${tab_folder}/padding.bin"
-  tockloader install "${tockloader_flags[@]}" -a 0x40000 "${tab_folder}/${install_app}.tab"
-fi
diff --git a/docs/install.md b/docs/install.md
index 8b1b6fe4b169f9601843aef2459ee76920e3b127..745709d8ebc97e74b2343484257b3b6fa8ee54e5 100644
--- a/docs/install.md
+++ b/docs/install.md
@@ -63,19 +63,21 @@ $ ./setup.sh
 [-] Applying patch "02-usb.patch"... DONE.
 [-] Applying patch "03-app-memory.patch"... DONE.
 [-] Applying patch "04-rtt.patch"... DONE.
-[-] Applying patch "01-panic_console.patch"... DONE.
-[-] Applying patch "02-timer.patch"... DONE.
-[-] Applying patch "03-public_syscalls.patch"... DONE.
-[-] Applying patch "04-bigger_heap.patch"... DONE.
+[-] Applying patch "01-linked_list_allocator.patch"... DONE.
+[-] Applying patch "02-panic_console.patch"... DONE.
+[-] Applying patch "03-timer.patch"... DONE.
+[-] Applying patch "04-public_syscalls.patch"... DONE.
+[-] Applying patch "05-bigger_heap.patch"... DONE.
+[-] Applying patch "06-no_spin_allocator.patch"... DONE.
 Signature ok
 subject=CN = Google OpenSK CA
 Getting Private key
 Signature ok
 subject=CN = Google OpenSK Hacker Edition
 Getting CA Private Key
-info: syncing channel updates for 'nightly-2020-01-16-x86_64-unknown-linux-gnu'
+info: syncing channel updates for 'nightly-2020-02-03-x86_64-unknown-linux-gnu'
 
-  nightly-2020-01-16-x86_64-unknown-linux-gnu unchanged - rustc 1.42.0-nightly (3291ae339 2020-01-15)
+  nightly-2020-02-03-x86_64-unknown-linux-gnu unchanged - rustc 1.42.0-nightly (f43c34a13 2020-02-02)
 
 Requirement already up-to-date: tockloader in /usr/lib/python3/dist-packages/tockloader-1.4.0.dev0-py3.7.egg (1.4.0.dev0)
 Requirement already satisfied, skipping upgrade: argcomplete>=1.8.2 in /usr/lib/python3/dist-packages (from tockloader) (1.10.0)
@@ -127,8 +129,9 @@ File              | Purpose
 If you want to use your own attestation certificate and private key, simply
 replace `opensk_cert.pem` and `opensk.key` files.
 
-Our build script is responsible for converting `opensk_cert.pem` and
-`opensk.key` files into the following Rust file: `src/ctap/key_material.rs`.
+Our build script `build.rs` is responsible for converting `opensk_cert.pem` and
+`opensk.key` files into raw data that is then used by the Rust file:
+`src/ctap/key_material.rs`.
 
 ### Flashing a firmware
 
@@ -138,45 +141,72 @@ Our build script 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 your device (_output may differ_):
+1.  Run our script for compiling/flashing Tock OS on your device (_output may
+    differ_):
 
     ```shell
-    $ board=nrf52840dk ./deploy.sh app os
-    make: Entering directory './third_party/tock/boards/nordic/nrf52840dk'
-       Compiling kernel v0.1.0 (./third_party/tock/kernel)
-       Compiling cortexm v0.1.0 (./third_party/tock/arch/cortex-m)
-       Compiling nrf5x v0.1.0 (./third_party/tock/chips/nrf5x)
-       Compiling capsules v0.1.0 (./third_party/tock/capsules)
-       Compiling cortexm4 v0.1.0 (./third_party/tock/arch/cortex-m4)
-       Compiling nrf52 v0.1.0 (./third_party/tock/chips/nrf52)
-       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)
-       Compiling nrf52840dk v0.1.0 (./third_party/tock/boards/nordic/nrf52840dk)
-        Finished release [optimized + debuginfo] target(s) in 11.28s
-       text    data     bss     dec     hex filename
-     114688    1760  260384  376832   5c000 target/thumbv7em-none-eabi/release/nrf52840dk
-     tockloader  flash --address 0x00000 --jlink --board nrf52dk target/thumbv7em-none-eabi/release/nrf52840dk.bin
-     [STATUS ] Flashing binar(y|ies) to board...
-     [INFO   ] Using known arch and jtag-device for known board nrf52dk
-     [INFO   ] Finished in 0.324 seconds
-
-     make: Leaving directory './third_party/tock/boards/nordic/nrf52840dk'
-     [STATUS ] Preparing to uninstall apps...
-     [INFO   ] Using known arch and jtag-device for known board nrf52dk
-     [ERROR  ] No apps are installed on the board
-
-       Compiling libtock v0.1.0 (./third_party/libtock-rs)
-       Compiling crypto v0.1.0 (./libraries/crypto)
-       Compiling ctap2 v0.1.0 (.)
-         Finished release [optimized] target(s) in 7.60s
-     [STATUS ] Flashing binar(y|ies) to board...
-     [INFO   ] Using known arch and jtag-device for known board nrf52dk
-     [INFO   ] Finished in 0.305 seconds
-
-     [STATUS ] Installing app on the board...
-     [INFO   ] Using known arch and jtag-device for known board nrf52dk
-     [INFO   ] Finished in 0.975 seconds
+    $ ./deploy.py os --board=nrf52840_dongle
+        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
+            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)
+            Compiling tock_rt0 v0.1.0 (./third_party/tock/libraries/tock-rt0)
+            Compiling nrf52840dk v0.1.0 (./third_party/tock/boards/nordic/nrf52840dk)
+            Compiling kernel v0.1.0 (./third_party/tock/kernel)
+            Compiling cortexm v0.1.0 (./third_party/tock/arch/cortex-m)
+            Compiling nrf5x v0.1.0 (./third_party/tock/chips/nrf5x)
+            Compiling capsules v0.1.0 (./third_party/tock/capsules)
+            Compiling cortexm4 v0.1.0 (./third_party/tock/arch/cortex-m4)
+            Compiling nrf52 v0.1.0 (./third_party/tock/chips/nrf52)
+            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
     ```
 
 1.  Connect a micro USB cable to the device USB port.
@@ -202,45 +232,72 @@ 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 your device (_output may differ_):
+1.  Run our script for compiling/flashing Tock OS on your device (_output may
+    differ_):
 
     ```shell
-    $ board=nrf52840_dongle ./deploy.sh app os
-     make: Entering directory './third_party/tock/boards/nordic/nrf52840_dongle'
-       Compiling kernel v0.1.0 (./third_party/tock/kernel)
-       Compiling cortexm v0.1.0 (./third_party/tock/arch/cortex-m)
-       Compiling nrf5x v0.1.0 (./third_party/tock/chips/nrf5x)
-       Compiling capsules v0.1.0 (./third_party/tock/capsules)
-       Compiling cortexm4 v0.1.0 (./third_party/tock/arch/cortex-m4)
-       Compiling nrf52 v0.1.0 (./third_party/tock/chips/nrf52)
-       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)
-       Compiling nrf52840_dongle v0.1.0 (./third_party/tock/boards/nordic/nrf52840_dongle)
-         Finished release [optimized + debuginfo] target(s) in 10.47s
-       text    data     bss     dec     hex filename
-     110592    1688  252264  364544   59000 target/thumbv7em-none-eabi/release/nrf52840_dongle
-     tockloader  flash --address 0x00000 --jlink --board nrf52dk target/thumbv7em-none-eabi/release/nrf52840_dongle.bin
-     [STATUS ] Flashing binar(y|ies) to board...
-     [INFO   ] Using known arch and jtag-device for known board nrf52dk
-     [INFO   ] Finished in 0.296 seconds
-
-     make: Leaving directory './third_party/tock/boards/nordic/nrf52840_dongle'
-     [STATUS ] Preparing to uninstall apps...
-     [INFO   ] Using known arch and jtag-device for known board nrf52dk
-     [ERROR  ] No apps are installed on the board
-
-       Compiling libtock v0.1.0 (./third_party/libtock-rs)
-       Compiling crypto v0.1.0 (./libraries/crypto)
-       Compiling ctap2 v0.1.0 (.)
-         Finished release [optimized] target(s) in 7.60s
-     [STATUS ] Flashing binar(y|ies) to board...
-     [INFO   ] Using known arch and jtag-device for known board nrf52dk
-     [INFO   ] Finished in 0.317 seconds
-
-     [STATUS ] Installing app on the board...
-     [INFO   ] Using known arch and jtag-device for known board nrf52dk
-     [INFO   ] Finished in 0.902 seconds
+    $ ./deploy.py os --board=nrf52840_dongle
+    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
+        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)
+        Compiling tock_rt0 v0.1.0 (./third_party/tock/libraries/tock-rt0)
+        Compiling nrf52840_dongle v0.1.0 (./third_party/tock/boards/nordic/nrf52840_dongle)
+        Compiling kernel v0.1.0 (./third_party/tock/kernel)
+        Compiling cortexm v0.1.0 (./third_party/tock/arch/cortex-m)
+        Compiling nrf5x v0.1.0 (./third_party/tock/chips/nrf5x)
+        Compiling capsules v0.1.0 (./third_party/tock/capsules)
+        Compiling cortexm4 v0.1.0 (./third_party/tock/arch/cortex-m4)
+        Compiling nrf52 v0.1.0 (./third_party/tock/chips/nrf52)
+        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.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: Erasing all installed applications
+    All apps have been erased.
+    info: Building OpenSK application
+        Compiling autocfg v1.0.0
+        Compiling pkg-config v0.3.17
+        Compiling cc v1.0.50
+        Compiling libc v0.2.66
+        Compiling bitflags v1.2.1
+        Compiling foreign-types-shared v0.1.1
+        Compiling openssl v0.10.28
+        Compiling cfg-if v0.1.10
+        Compiling lazy_static v1.4.0
+        Compiling byteorder v1.3.2
+        Compiling linked_list_allocator v0.6.6
+        Compiling arrayref v0.3.6
+        Compiling cbor v0.1.0 (./libraries/cbor)
+        Compiling subtle v2.2.2
+        Compiling foreign-types v0.3.2
+        Compiling libtock v0.1.0 (./third_party/libtock-rs)
+        Compiling crypto v0.1.0 (./libraries/crypto)
+        Compiling openssl-sys v0.9.54
+        Compiling ctap2 v0.1.0 (.)
+            Finished release [optimized] target(s) in 15.34s
+    info: Flashing padding application
+    info: Installing Tock application ctap2
     ```
 
 1.  Remove the programming cable and the USB-A extension cable.
@@ -261,5 +318,7 @@ sudo udevadm control --reload
 
 Then, you will need to unplug and replug the key for the rule to trigger.
 
+## Testing the key
+
 To test whether the installation was successful, visit a
 [demo website](https://webauthn.io/) and try to register and login.
diff --git a/tools/gen_key_materials.sh b/tools/gen_key_materials.sh
old mode 100644
new mode 100755
index 414626a3e5b297c645c53782c79a5feb9da649ae..f8a7bca85aacf8e16432e4d38594ed4b062bec08
--- a/tools/gen_key_materials.sh
+++ b/tools/gen_key_materials.sh
@@ -23,9 +23,6 @@ generate_crypto_materials () {
   local opensk_key=crypto_data/opensk.key
   local opensk_cert_name=crypto_data/opensk_cert
 
-  # Rust file that we will generate will all cryptographic data.
-  local rust_file=src/ctap/key_material.rs
-
   # Allow invoker to override the command with a full path.
   local openssl=${OPENSSL:-$(which openssl)}
 
@@ -36,6 +33,9 @@ generate_crypto_materials () {
     exit 1
   fi
 
+  # Exit on first error
+  set -e
+
   force_generate="$1"
   mkdir -p crypto_data
   if [ ! -f "${ca_priv_key}" ]
@@ -85,3 +85,5 @@ generate_crypto_materials () {
       -sha256
   fi
 }
+
+generate_crypto_materials "$1"