9 Commits
2.1 ... develop

Author SHA1 Message Date
kaczmarczyck
4339600730 Fixes the Markdown lint config and affected files (#676)
I also ran our documentation against a few other linters, to see what
they would find, and fixed accordingly.
2024-01-12 16:00:37 +01:00
kaczmarczyck
298db9ea99 Public method to check if sleeping is allowed (#674) 2024-01-09 21:04:47 +01:00
kaczmarczyck
ba0d717d88 Allows initialization without Reset permission (#673)
* Allows initialization without Reset permission

This PR is useful for all implementations that can trigger a reboot
without user intervention. In these cases, we don't want to allow the
Reset command. It should only be allowed after a user initiated power
cycle.

Adds tests to the new functionality and a few other coverage holes.

* Moves soft reset parameters into Env
2024-01-09 18:30:35 +01:00
kaczmarczyck
0185d1e443 Upgrade setup-python workflows (#670)
* Upgrade setup-python in GitHub workflows

* Updates all other GitHub actions

* Fixes failing workflows

* reverts coveralls workflow version
2023-12-18 12:20:34 +01:00
kaczmarczyck
191d043cc4 Removes heapviz (#671)
Unused, and triggers security audit problems through its dependencies.
2023-12-18 11:44:32 +01:00
Jean-Michel Picod
6b8aa3aaf3 Change PKI so that attestation certs are fully compliant. (#668)
* Change PKI so that attestation certs are fully compliant.

Initially we generated the smallest certificate possible.
Unfortunately sometimes attestation certificates are
thoroughly checked and the FIDO x509v3 extensions must be present.
This PR now creates a PKI (root CA and signing CA) with corresponding
CRLs and also allows to create multiple batch certificates for the keys
instead of a single one.
The latest generated batch cert/key is automatically symlinked so that
the previous documentation still holds.

* Change openssl options to support older versions

* OSX doesn't support long options

---------

Co-authored-by: kaczmarczyck <43844792+kaczmarczyck@users.noreply.github.com>
2023-12-18 10:47:46 +01:00
kaczmarczyck
5fdc6e0739 Removes the nordicsemi pip dependency (#669)
Tested on Linux, should work on Mac.
We leave the responsibility to install `nrfutil` in version 6 to the
user.
2023-12-15 16:26:31 +01:00
kaczmarczyck
af763450a9 Always return credProtect in credential management (#666)
Fixes P-1 in this test:
https://github.com/fido-alliance/ctap2.1-conformance-module/blob/main/tests/CTAP2/Protocol/CredentialManagement/21/CredentialManagement-21-EnumerateCredentials.js
2023-11-24 16:00:06 +01:00
kaczmarczyck
eaeb927d92 Corrects the GetInfo output without Config command (#665) 2023-11-23 14:52:02 +01:00
34 changed files with 752 additions and 671 deletions

View File

@@ -8,10 +8,10 @@ jobs:
runs-on: ubuntu-latest
if: github.repository == 'google/OpenSK'
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
with:
submodules: "true"
- uses: actions/setup-python@v1
- uses: actions/setup-python@v5
with:
python-version: "3.10"
- name: Set up OpenSK

View File

@@ -6,7 +6,7 @@ jobs:
runs-on: ubuntu-latest
steps:
# Setup
- uses: actions/setup-python@v1
- uses: actions/setup-python@v5
with:
python-version: "3.10"
- uses: actions-rs/cargo@v1
@@ -15,7 +15,7 @@ jobs:
args: cargo-bloat
# First run: PR
- uses: actions/checkout@v2
- uses: actions/checkout@v4
with:
submodules: true
- name: Set up OpenSK
@@ -24,7 +24,7 @@ jobs:
run: RUSTFLAGS="-C link-arg=-icf=all -C force-frame-pointers=no -C link-arg=-Tnrf52840_layout.ld" cargo bloat --release --target=thumbv7em-none-eabi --features=config_command,with_ctap1 --crates >> .github/workflows/bloat_output_new.txt
# Second run: PR
- uses: actions/checkout@v2
- uses: actions/checkout@v4
with:
submodules: true
ref: ${{ github.base_ref }}

View File

@@ -23,8 +23,8 @@ jobs:
permissions:
contents: read
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v1
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.10"
- run: ./setup.sh

View File

@@ -21,7 +21,7 @@ jobs:
fuzz-seconds: 600
dry-run: false
- name: Upload Crash
uses: actions/upload-artifact@v1
uses: actions/upload-artifact@v4
if: failure() && steps.build.outcome == 'success'
with:
name: artifacts

View File

@@ -13,12 +13,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
with:
submodules: "true"
- name: Install Rust toolchain
run: rustup show
- uses: actions/setup-python@v1
- uses: actions/setup-python@v5
with:
python-version: "3.10"
- name: Set up OpenSK

View File

@@ -12,10 +12,10 @@ jobs:
mdlint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: markdownlint-cli
uses: nosborn/github-action-markdown-cli@v1.1.1
uses: nosborn/github-action-markdown-cli@v3
with:
files: '**/*.md'
ignore_files: "third_party/*"
config_file: ".markdownlint.json"
config_file: '.markdownlint.json'
ignore_files: 'third_party/*'

View File

@@ -13,12 +13,12 @@ jobs:
fail-fast: false
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
with:
submodules: "true"
- name: Install Rust toolchain
run: rustup show
- uses: actions/setup-python@v1
- uses: actions/setup-python@v5
with:
python-version: "3.10"
- name: Set up OpenSK
@@ -30,7 +30,7 @@ jobs:
run: ./maintainers/reproduce_hashes.sh
- name: Upload reproduced binaries
uses: actions/upload-artifact@v1
uses: actions/upload-artifact@v4
with:
name: reproduced-${{ matrix.os }}
path: reproducible/reproduced.tar

View File

@@ -1,35 +1,22 @@
{
"default": true,
"heading-style": {
"MD003": {
"style": "atx"
},
"no-trailing-spaces": {
"MD007": {
"indent": 4
},
"MD009": {
"br_spaces": 0,
"strict": true
},
"ul-indent": {
"indent": 4
},
"line-length": {
"MD013": {
"line_length": 80,
"code_blocks": false
},
"list-marker-space": {
"ol_single": 2,
"ol_multi": 2,
"ul_single": 3,
"ul_multi": 3
},
"no-inline-html": {
"MD033": {
"allowed_elements": [
"img"
]
},
"fenced-code-language": true,
"code-block-style": {
"style": "fenced"
},
"code-fence-style": {
"style": "backtick"
"img"
]
}
}
}

View File

@@ -2,8 +2,8 @@
This bootloader supports upgradability for OpenSK. Its functionality is to
- check images on A/B partitions,
- boot the most recent valid partition.
- check images on A/B partitions,
- boot the most recent valid partition.
## How to use

View File

@@ -23,6 +23,7 @@ import argparse
import collections
import copy
import os
from serial.tools import list_ports
import shutil
import subprocess
import sys
@@ -202,6 +203,12 @@ def assert_python_library(module: str):
f"Try to run: pip3 install {module}"))
def list_serials(vid: int, pid: int) -> List[str]:
ports = list_ports.comports()
ports = filter(lambda p: p.vid == vid and p.pid == pid, ports)
return list(map(lambda p: p.serial_number, ports))
class RemoveConstAction(argparse.Action):
# pylint: disable=redefined-builtin
@@ -703,10 +710,10 @@ class OpenSKInstaller:
fatal("This board doesn't seem to support flashing through pyocd.")
if self.args.programmer == "nordicdfu":
assert_mandatory_binary("nrfutil")
assert_python_library("intelhex")
assert_python_library("nordicsemi.lister")
nrfutil_version = __import__("nordicsemi.version").version.NRFUTIL_VERSION
assert_mandatory_binary("nrfutil")
nrfutil_version = self.checked_command_output(["nrfutil", "version"])
nrfutil_version = nrfutil_version.removeprefix("nrfutil version ")
if not nrfutil_version.startswith("6."):
fatal(("You need to install nrfutil python3 package v6.0 or above. "
f"Found: v{nrfutil_version}. If you use Python >= 3.11, please "
@@ -812,22 +819,17 @@ class OpenSKInstaller:
info("Press [ENTER] when ready.")
_ = input()
# Search for the DFU devices
serial_number = []
# pylint: disable=g-import-not-at-top,import-outside-toplevel
from nordicsemi.lister import device_lister
for device in device_lister.DeviceLister().enumerate():
if device.vendor_id == "1915" and device.product_id == "521F":
serial_number.append(device.serial_number)
if not serial_number:
serial_numbers = list_serials(0x1915, 0x521F)
if not serial_numbers:
fatal("Couldn't find any DFU device on your system.")
if len(serial_number) > 1:
if len(serial_numbers) > 1:
fatal("Multiple DFU devices are detected. Please only connect one.")
# Run the command without capturing stdout so that we show progress
info("Flashing device using DFU...")
dfu_return_code = subprocess.run(
[
"nrfutil", "dfu", "usb-serial", f"--package={dfu_pkg_file}",
f"--serial-number={serial_number[0]}"
f"--serial-number={serial_numbers[0]}"
],
check=False,
timeout=None,

View File

@@ -19,6 +19,9 @@ customize it.
### Flashing using DFU (preferred method)
You need `nrfutil` version 6. The [install manual](../install.md) has
setup instructions.
To flash the firmware, run:
```shell

View File

@@ -7,18 +7,18 @@
All the generated certificates and private keys are stored in the directory
`crypto_data/`. The expected content after running our `setup.sh` script is:
File | Purpose
------------------------ | --------------------------------------------------------
`aaguid.txt` | Text file containaing the AAGUID value
`opensk_ca.csr` | Certificate sign request for the Root CA
`opensk_ca.key` | ECC secp256r1 private key used for the Root CA
`opensk_ca.pem` | PEM encoded certificate of the Root CA
`opensk_ca.srl` | File generated by OpenSSL
`opensk_cert.csr` | Certificate sign request for the attestation certificate
`opensk_cert.pem` | PEM encoded certificate used for the authenticator
`opensk.key` | ECC secp256r1 private key used for the autenticator
`opensk_upgrade.key` | Private key for signing upgrades through CTAP
`opensk_upgrade_pub.pem` | Public key added to the firmware for verifying upgrades
| File | Purpose |
| ------------------------ | ----------------------------------------------- |
| `aaguid.txt` | Text file containaing the AAGUID value |
| `opensk_ca.csr` | Certificate sign request for the Root CA |
| `opensk_ca.key` | ECC secp256r1 private key used for the Root CA |
| `opensk_ca.pem` | PEM encoded certificate of the Root CA |
| `opensk_ca.srl` | File generated by OpenSSL |
| `opensk_cert.csr` | CSR for attestation certificate |
| `opensk_cert.pem` | PEM encoded certificate for the authenticator |
| `opensk.key` | ECC secp256r1 private key for the autenticator |
| `opensk_upgrade.key` | Private key for signing upgrades through CTAP |
| `opensk_upgrade_pub.pem` | Public key for verifying upgrades |
If you want to use your own attestation certificate and private key,
replace the `opensk_cert.pem` and `opensk.key` files. The script at
@@ -50,20 +50,20 @@ carefully if you want to take this privacy risk.
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 `src/main.rs`.
1. If you have colored LEDs, like different blinking patterns and want to play
around with the code in `src/main.rs` more, take a look at e.g. `wink_leds`.
1. You find more options and documentation in `src/ctap/customization.rs`,
including:
* The default level for the credProtect extension.
* The default minimum PIN length, and what relying parties can set it.
* Whether you want to enforce alwaysUv.
* Settings for enterprise attestation.
* The maximum PIN retries.
* Whether you want to use batch attestation.
* Whether you want to use signature counters.
* Various constants to adapt to different hardware.
1. If you have multiple buttons, choose the buttons responsible for user
presence in `src/main.rs`.
1. If you have colored LEDs, like different blinking patterns and want to play
around with the code in `src/main.rs` more, take a look at e.g. `wink_leds`.
1. You find more options and documentation in `src/ctap/customization.rs`,
including:
* The default level for the credProtect extension.
* The default minimum PIN length, and what relying parties can set it.
* Whether you want to enforce alwaysUv.
* Settings for enterprise attestation.
* The maximum PIN retries.
* Whether you want to use batch attestation.
* Whether you want to use signature counters.
* Various constants to adapt to different hardware.
### Testing and Fuzzing

View File

@@ -59,15 +59,15 @@ JLinkRTTClient
You can enhance the debug output by adding flags to the deploy command (see
below for details):
* `--debug`: more debug messages
* `--panic-console`: add panic messages
* `--debug-allocations`: print information about the used heap
* `--debug`: more debug messages
* `--panic-console`: add panic messages
* `--debug-allocations`: print information about the used heap
Adding debugging to your firmware increases resource usage, including
* USB communication speed
* RAM usage
* binary size
* USB communication speed
* RAM usage
* binary size
Depending on your choice of board, you may have to increase the available stack
for kernel or app, or disable features so that the binary fits the flash. Also
@@ -107,31 +107,3 @@ alloc[256, 1] = 0x2002401c (2 ptrs, 384 bytes)
# After this operation, 1 pointers are allocated, totalling 512 bytes.
dealloc[64, 1] = 0x2002410c (1 ptrs, 512 bytes)
```
A tool is provided to analyze such reports, in `tools/heapviz`. This tool
parses the console output, identifies the lines corresponding to (de)allocation
operations, and first computes some statistics:
* Address range used by the heap over this run of the program,
* Peak heap usage (how many useful bytes are allocated),
* Peak heap consumption (how many bytes are used by the heap, including
unavailable bytes between allocated blocks, due to alignment constraints and
memory fragmentation),
* Fragmentation overhead (difference between heap consumption and usage).
Then, the `heapviz` tool displays an animated "movie" of the allocated bytes in
heap memory. Each frame in this "movie" shows bytes that are currently
allocated, that were allocated but are now freed, and that have never been
allocated. A new frame is generated for each (de)allocation operation. This tool
uses the `ncurses` library, that you may have to install beforehand.
You can control the tool with the following parameters:
* `--logfile` (required) to provide the file which contains the console output
to parse,
* `--fps` (optional) to customize the number of frames per second in the movie
animation.
```shell
cargo run --manifest-path tools/heapviz/Cargo.toml -- --logfile console.log --fps 50
```

View File

@@ -8,27 +8,27 @@ This document lists required steps to start build your own OpenSK.
OpenSK supports different ways to flash your board:
* [Segger J-Link](https://www.segger.com/products/debug-probes/j-link/)
(default method).
* [OpenOCD](http://openocd.org/).
* [pyOCD](https://pypi.org/project/pyocd/).
* [nrfutil](https://pypi.org/project/nrfutil/) for the USB dongle boards that
support it, which allows you to directly flash a working board over USB
without additional hardware.
* [Segger J-Link](https://www.segger.com/products/debug-probes/j-link/)
(default method).
* [OpenOCD](http://openocd.org/).
* [pyOCD](https://pypi.org/project/pyocd/).
* [nrfutil](https://pypi.org/project/nrfutil/) for the USB dongle boards that
support it, which allows you to directly flash a working board over USB
without additional hardware.
### Software requirements
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 (can be installed with the `python3-pip` package on Debian)
* the OpenSSL command line tool (can be installed and configured with the
`libssl-dev` and `pkg-config` packages on Debian)
* `nrfutil` (can be installed using `pip3 install nrfutil`) if you want to flash
a device with DFU
* `uuid-runtime` if you are missing the `uuidgen` command.
* `llvm` and `gcc-arm-none-eabi` if you want to use the upgradability feature.
* rustup (can be installed with [Rustup](https://rustup.rs/))
* python3 and pip (can be installed with the `python3-pip` package on Debian)
* the OpenSSL command line tool (can be installed and configured with the
`libssl-dev` and `pkg-config` packages on Debian)
* `nrfutil` (pip package of the same name), if you want to flash
a device with DFU. Read the disclaimer below.
* `uuid-runtime` if you are missing the `uuidgen` command.
* `llvm` and `gcc-arm-none-eabi` if you want to use the upgradability feature.
The proprietary software to use the default programmer can be found on the
[Segger website](https://www.segger.com/downloads/jlink). Please follow their
@@ -37,47 +37,48 @@ instructions to appropriate binaries for your system.
The scripts provided in this project have been tested under Linux and OS X. We
haven't tested them on Windows and other platforms.
If you use Python newer than 3.10, then nrfutil for flashing over DFU is
currently not supported. Please use Python 3.10, or play around with [Nordic's
new tool](https://www.nordicsemi.com/Products/Development-tools/nrf-util)
instead.
You need `nrfutil` version 6, if you want to flash over DFU.
The tool doesn't support Python newer than 3.10. Therefore, we don't officially
support DFU for other versions. If you want to try regardless,
[Nordic's new tool](https://www.nordicsemi.com/Products/Development-tools/nrf-util)
might work for you.
### Compiling the firmware
If this is your first time installing OpenSK, please skip directly to
[Initial setup](#Initial-setup). Else, see
[Updating your setup](#Updating-your-setup) below.
[Initial setup](#initial-setup). Else, see
[Updating your setup](#updating-your-setup) below.
#### Updating your setup
Depending on the difference to your last state, you may need some of the
following steps:
* If you are not just testing minor changes, reset and redo the setup. This
will delete all uncommited changes.
* If you are not just testing minor changes, reset and redo the setup. This
will delete all uncommited changes.
```shell
./reset.sh
./setup.sh
```
```shell
./reset.sh
./setup.sh
```
* Flash your board according to the
[flashing instructions below](#Flashing-a-firmware]. If you come from an
OpenSK version before the 2.0 certified one, your credential storage is not
backwards compatible and you have to reset it. :warning: You will lose
logins to all websites that you registered with OpenSK. To erase your
persistent storage, run the deploy script twice: Once with the application
parameter `--erase_storage`, and once with `--opensk` as usual.
* Flash your board according to the [instructions below](#flashing-a-firmware).
This reset also clears the certificate. For a privacy discussion, see the
[certificate section in Customization](customization.md#Certificate-considerations).
If you want to reinstall it, you also need to rerun:
If you come from an OpenSK version before the 2.0 certified one, your credential
storage is not backwards compatible and you have to reset it. :warning: You will
lose logins to all websites that you registered with OpenSK. To erase your
persistent storage, run the deploy script twice: Once with the application
parameter `--erase_storage`, and once with `--opensk` as usual.
```shell
./tools/configure.py \
--certificate=crypto_data/opensk_cert.pem \
--private-key=crypto_data/opensk.key
```
This reset also clears the certificate. For a privacy discussion, see the
[certificate section in Customization](customization.md#Certificate-considerations).
If you want to reinstall it, you also need to rerun:
```shell
./tools/configure.py \
--certificate=crypto_data/opensk_cert.pem \
--private-key=crypto_data/opensk.key
```
#### Initial setup
@@ -92,17 +93,17 @@ cd OpenSK
The setup script performs the following steps:
1. Make sure that the git submodules are checked out.
1. Make sure that the git submodules are checked out.
1. Apply our patches that haven't yet been merged upstream to both
[Tock](https://github.com/tock/tock) and
[libtock-rs](https://github.com/tock/libtock-rs).
1. Apply our patches that haven't yet been merged upstream to both
[Tock](https://github.com/tock/tock) and
[libtock-rs](https://github.com/tock/libtock-rs).
1. Generate crypto material, see [Customization](customization.md) for details.
1. Generate crypto material, see [Customization](customization.md) for details.
1. Install the correct Rust toolchain for ARM devices.
1. Install the correct Rust toolchain for ARM devices.
1. Install [tockloader](https://github.com/tock/tockloader).
1. Install [tockloader](https://github.com/tock/tockloader).
Additionally on Linux, you need to install a `udev` rule file to allow non-root
users to interact with OpenSK devices. To install it, execute:
@@ -131,24 +132,24 @@ for understand privacy tradeoffs.
From here on, please follow the instructions for your hardware:
* [Nordic nRF52840-DK](boards/nrf52840dk.md)
* [Nordic nRF52840 Dongle](boards/nrf52840_dongle.md)
* [Makerdiary nRF52840-MDK USB dongle](boards/nrf52840_mdk.md)
* [Feitian OpenSK dongle](boards/nrf52840_feitian.md)
* [Nordic nRF52840-DK](boards/nrf52840dk.md)
* [Nordic nRF52840 Dongle](boards/nrf52840_dongle.md)
* [Makerdiary nRF52840-MDK USB dongle](boards/nrf52840_mdk.md)
* [Feitian OpenSK dongle](boards/nrf52840_feitian.md)
### Advanced installation
We recommend that you flash your development board with JTAG and dongles with
DFU, as described in the [board documentation](#Flashing-a-firmware) linked
DFU, as described in the [board documentation](#flashing-a-firmware) linked
above. However, we support other programmers:
* OpenOCD: `./deploy.py --board=nrf52840_dongle_opensk --opensk
--programmer=openocd`
* pyOCD: `./deploy.py --board=nrf52840_dongle_opensk --opensk
--programmer=pyocd`
* Custom: `./deploy.py --board=nrf52840_dongle_opensk --opensk
--programmer=none`. In this case, an IntelHex file will be created and how
to program a board is left to the user.
* OpenOCD: `./deploy.py --board=nrf52840_dongle_opensk --opensk
--programmer=openocd`
* pyOCD: `./deploy.py --board=nrf52840_dongle_opensk --opensk
--programmer=pyocd`
* Custom: `./deploy.py --board=nrf52840_dongle_opensk --opensk
--programmer=none`. In this case, an IntelHex file will be created and how
to program a board is left to the user.
If your board is already flashed with Tock OS, you may skip installing it:
`./deploy.py --board=nrf52840dk_opensk --opensk --no-tockos`
@@ -162,10 +163,10 @@ We experiment with a new CTAP command to allow upgrading your device without
access to its debugging port. For that purpose, the flash storage is split into
4 parts:
* the bootloader to decide with partition to boot
* firmware partition A
* firmware partition B
* the persistent storage for credentials
* the bootloader to decide with partition to boot
* firmware partition A
* firmware partition B
* the persistent storage for credentials
The storage is backward compatible to non-upgradable boards. Deploying an
upgradable board automatically installs the bootloader. Please keep in mind that

View File

@@ -113,3 +113,14 @@ impl From<StoreError> for Error {
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_store_error() {
assert_eq!(Error::from(StoreError::StorageError), Error::Storage);
assert_eq!(Error::from(StoreError::InvalidStorage), Error::Internal);
}
}

View File

@@ -14,7 +14,7 @@
use core::convert::TryFrom;
#[derive(Clone, Copy, PartialEq, Eq)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum UsbEndpoint {
MainHid = 1,
#[cfg(feature = "vendor_hid")]
@@ -40,6 +40,7 @@ pub enum SendOrRecvStatus {
Received(UsbEndpoint),
}
#[derive(Debug, PartialEq, Eq)]
pub struct SendOrRecvError;
pub type SendOrRecvResult = Result<SendOrRecvStatus, SendOrRecvError>;
@@ -47,3 +48,16 @@ pub type SendOrRecvResult = Result<SendOrRecvStatus, SendOrRecvError>;
pub trait HidConnection {
fn send_and_maybe_recv(&mut self, buf: &mut [u8; 64], timeout_ms: usize) -> SendOrRecvResult;
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_endpoint_num() {
assert_eq!(UsbEndpoint::try_from(1), Ok(UsbEndpoint::MainHid));
#[cfg(feature = "vendor_hid")]
assert_eq!(UsbEndpoint::try_from(2), Ok(UsbEndpoint::VendorHid));
assert_eq!(UsbEndpoint::try_from(3), Err(SendOrRecvError));
}
}

View File

@@ -457,4 +457,47 @@ mod test {
fn test_invariants() {
assert!(is_valid(&DEFAULT_CUSTOMIZATION));
}
#[test]
fn test_accessors() {
let customization = CustomizationImpl {
aaguid: &[0; AAGUID_LENGTH],
allows_pin_protocol_v1: true,
default_cred_protect: None,
default_min_pin_length: 4,
default_min_pin_length_rp_ids: &["example.com"],
enforce_always_uv: false,
enterprise_attestation_mode: None,
enterprise_rp_id_list: &[],
max_msg_size: 7609,
max_pin_retries: 8,
use_batch_attestation: true,
use_signature_counter: true,
max_cred_blob_length: 32,
max_credential_count_in_list: Some(3),
max_large_blob_array_size: 2048,
max_rp_ids_length: 8,
max_supported_resident_keys: 150,
};
assert_eq!(customization.aaguid(), &[0; AAGUID_LENGTH]);
assert!(customization.allows_pin_protocol_v1());
assert!(customization.default_cred_protect().is_none());
assert_eq!(customization.default_min_pin_length(), 4);
assert_eq!(
customization.default_min_pin_length_rp_ids(),
vec![String::from("example.com")]
);
assert!(!customization.enforce_always_uv());
assert!(customization.enterprise_attestation_mode().is_none());
assert!(customization.enterprise_rp_id_list().is_empty());
assert_eq!(customization.max_msg_size(), 7609);
assert_eq!(customization.max_pin_retries(), 8);
assert!(customization.use_batch_attestation());
assert!(customization.use_signature_counter());
assert_eq!(customization.max_cred_blob_length(), 32);
assert_eq!(customization.max_credential_count_in_list(), Some(3));
assert_eq!(customization.max_large_blob_array_size(), 2048);
assert_eq!(customization.max_rp_ids_length(), 8);
assert_eq!(customization.max_supported_resident_keys(), 150);
}
}

View File

@@ -140,6 +140,12 @@ impl<E: Env> ClientPin<E> {
}
}
/// Checks if a PIN UV token is in use.
pub fn has_token(&mut self, env: &mut E) -> bool {
self.update_timeouts(env);
self.pin_uv_auth_token_state.is_in_use()
}
/// Gets a reference to the PIN protocol of the given version.
fn get_pin_protocol(&self, pin_uv_auth_protocol: PinUvAuthProtocol) -> &PinProtocol<E> {
match pin_uv_auth_protocol {
@@ -1507,9 +1513,11 @@ mod test {
let mut env = TestEnv::default();
let mut client_pin = ClientPin::<TestEnv>::new(&mut env);
let message = [0xAA];
assert!(!client_pin.has_token(&mut env));
client_pin
.pin_uv_auth_token_state
.begin_using_pin_uv_auth_token(&mut env);
assert!(client_pin.has_token(&mut env));
let pin_uv_auth_token_v1 = client_pin
.get_pin_protocol(PinUvAuthProtocol::V1)
@@ -1655,6 +1663,7 @@ mod test {
.has_permissions_rp_id("example.com"),
Ok(())
);
assert!(client_pin.has_token(&mut env));
env.clock().advance(30001);
client_pin.update_timeouts(&mut env);
@@ -1672,6 +1681,7 @@ mod test {
.has_permissions_rp_id("example.com"),
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
);
assert!(!client_pin.has_token(&mut env));
}
#[test]

View File

@@ -23,6 +23,8 @@ use super::response::{AuthenticatorCredentialManagementResponse, ResponseData};
use super::status_code::Ctap2StatusCode;
use super::{Channel, StatefulCommand, StatefulPermission};
use crate::api::crypto::sha256::Sha256;
use crate::api::customization::Customization;
use crate::ctap::data_formats::CredentialProtectionPolicy;
use crate::ctap::storage;
use crate::env::{Env, Sha};
use alloc::collections::BTreeSet;
@@ -62,6 +64,7 @@ fn enumerate_rps_response<E: Env>(
/// Generates the response for subcommands enumerating credentials.
fn enumerate_credentials_response<E: Env>(
env: &mut E,
credential: PublicKeyCredentialSource,
total_credentials: Option<u64>,
) -> Result<AuthenticatorCredentialManagementResponse, Ctap2StatusCode> {
@@ -91,12 +94,15 @@ fn enumerate_credentials_response<E: Env>(
transports: None, // You can set USB as a hint here.
};
let public_key = private_key.get_pub_key::<E>()?;
let cred_protect = cred_protect_policy
.or(env.customization().default_cred_protect())
.or(Some(CredentialProtectionPolicy::UserVerificationOptional));
Ok(AuthenticatorCredentialManagementResponse {
user: Some(user),
credential_id: Some(credential_id),
public_key: Some(public_key),
total_credentials,
cred_protect: cred_protect_policy,
cred_protect,
large_blob_key,
..Default::default()
})
@@ -201,7 +207,7 @@ fn process_enumerate_credentials_begin<E: Env>(
channel,
);
}
enumerate_credentials_response::<E>(credential, Some(total_credentials as u64))
enumerate_credentials_response(env, credential, Some(total_credentials as u64))
}
/// Processes the subcommand enumerateCredentialsGetNextCredential for CredentialManagement.
@@ -211,7 +217,7 @@ fn process_enumerate_credentials_get_next_credential<E: Env>(
) -> Result<AuthenticatorCredentialManagementResponse, Ctap2StatusCode> {
let credential_key = stateful_command_permission.next_enumerate_credential(env)?;
let credential = storage::get_credential(env, credential_key)?;
enumerate_credentials_response::<E>(credential, None)
enumerate_credentials_response(env, credential, None)
}
/// Processes the subcommand deleteCredential for CredentialManagement.

View File

@@ -410,11 +410,26 @@ pub struct StatefulPermission<E: Env> {
channel: Option<Channel>,
}
impl<E: Env> Default for StatefulPermission<E> {
/// Creates the command state at device startup without user action.
///
/// Reset is not granted after a forced reboot. The user replugging the device is a required
/// to avoid accidental data loss.
fn default() -> StatefulPermission<E> {
StatefulPermission {
permission: <E::Clock as Clock>::Timer::default(),
command_type: None,
channel: None,
}
}
}
impl<E: Env> StatefulPermission<E> {
/// Creates the command state at device startup.
///
/// Resets are only possible after a power cycle. Therefore, initialization
/// means allowing Reset, and Reset cannot be granted later.
/// Resets are only possible after a power cycle. Therefore, there is no way to grant the Reset
/// permission outside of this function. If you initialize the app without a power cycle
/// (potentially after waking up from sleep), call `default` instead.
pub fn new_reset(env: &mut E) -> StatefulPermission<E> {
StatefulPermission {
permission: env.clock().make_timer(RESET_TIMEOUT_DURATION_MS),
@@ -543,11 +558,16 @@ impl<E: Env> CtapState<E> {
pub fn new(env: &mut E) -> Self {
storage::init(env).ok().unwrap();
let client_pin = ClientPin::new(env);
let stateful_command_permission = if env.boots_after_soft_reset() {
StatefulPermission::default()
} else {
StatefulPermission::new_reset(env)
};
CtapState {
client_pin,
#[cfg(feature = "with_ctap1")]
u2f_up_state: U2fUserPresenceState::new(),
stateful_command_permission: StatefulPermission::new_reset(env),
stateful_command_permission,
large_blobs: LargeBlobs::new(),
}
}
@@ -582,6 +602,12 @@ impl<E: Env> CtapState<E> {
self.stateful_command_permission.clear_old_channels(channel);
}
/// Checks if the application has any timers running.
pub fn can_sleep(&mut self, env: &mut E) -> bool {
!self.client_pin.has_token(env)
&& self.stateful_command_permission.get_command(env).is_err()
}
pub fn process_command(
&mut self,
env: &mut E,
@@ -1282,15 +1308,19 @@ impl<E: Env> CtapState<E> {
options.append(&mut vec![
(String::from("rk"), true),
(String::from("up"), true),
(String::from("alwaysUv"), has_always_uv),
(String::from("credMgmt"), true),
#[cfg(feature = "config_command")]
(String::from("authnrCfg"), true),
(String::from("clientPin"), storage::pin_hash(env)?.is_some()),
(String::from("largeBlobs"), true),
(String::from("pinUvAuthToken"), true),
#[cfg(feature = "config_command")]
(String::from("setMinPINLength"), true),
(String::from("makeCredUvNotRqd"), !has_always_uv),
]);
if cfg!(feature = "config_command") || env.customization().enforce_always_uv() {
options.push((String::from("alwaysUv"), has_always_uv));
}
let mut pin_protocols = vec![PinUvAuthProtocol::V2 as u64];
if env.customization().allows_pin_protocol_v1() {
pin_protocols.push(PinUvAuthProtocol::V1 as u64);
@@ -1499,12 +1529,15 @@ mod test {
"ep" => env.customization().enterprise_attestation_mode().map(|_| false),
"rk" => true,
"up" => true,
#[cfg(feature = "config_command")]
"alwaysUv" => false,
"credMgmt" => true,
#[cfg(feature = "config_command")]
"authnrCfg" => true,
"clientPin" => false,
"largeBlobs" => true,
"pinUvAuthToken" => true,
#[cfg(feature = "config_command")]
"setMinPINLength" => true,
"makeCredUvNotRqd" => true,
},

View File

@@ -76,6 +76,9 @@ pub trait Env {
#[cfg(feature = "vendor_hid")]
fn vendor_hid_connection(&mut self) -> &mut Self::HidConnection;
/// Indicates that the last power cycle was not caused by user action.
fn boots_after_soft_reset(&self) -> bool;
/// Option to return a firmware version that is shown as device info.
fn firmware_version(&self) -> Option<u64> {
None

View File

@@ -34,6 +34,7 @@ pub struct TestEnv {
store: Store<BufferStorage>,
customization: TestCustomization,
clock: TestClock,
soft_reset: bool,
}
pub type TestRng = StdRng;
@@ -127,6 +128,7 @@ impl Default for TestEnv {
store,
customization,
clock,
soft_reset: false,
}
}
}
@@ -139,6 +141,10 @@ impl TestEnv {
pub fn seed_rng_from_u64(&mut self, seed: u64) {
self.rng = StdRng::seed_from_u64(seed);
}
pub fn set_boots_after_soft_reset(&mut self, value: bool) {
self.soft_reset = value;
}
}
impl TestUserPresence {
@@ -227,6 +233,10 @@ impl Env for TestEnv {
self
}
fn boots_after_soft_reset(&self) -> bool {
self.soft_reset
}
fn firmware_version(&self) -> Option<u64> {
Some(0)
}
@@ -247,4 +257,14 @@ mod test {
clock.advance(1);
assert!(clock.is_elapsed(&timer));
}
#[test]
fn test_soft_reset() {
let mut env = TestEnv::default();
assert!(!env.boots_after_soft_reset());
env.set_boots_after_soft_reset(true);
assert!(env.boots_after_soft_reset());
env.set_boots_after_soft_reset(false);
assert!(!env.boots_after_soft_reset());
}
}

View File

@@ -81,10 +81,6 @@ impl<E: Env> Ctap<E> {
&mut self.state
}
pub fn hid(&mut self) -> &mut MainHid<E> {
&mut self.hid
}
pub fn env(&mut self) -> &mut E {
&mut self.env
}
@@ -120,6 +116,10 @@ impl<E: Env> Ctap<E> {
self.hid.should_wink(&mut self.env)
}
pub fn can_sleep(&mut self) -> bool {
!self.should_wink() && self.state.can_sleep(&mut self.env)
}
#[cfg(feature = "with_ctap1")]
pub fn u2f_grant_user_presence(&mut self) {
self.state.u2f_grant_user_presence(&mut self.env)
@@ -134,6 +134,7 @@ impl<E: Env> Ctap<E> {
#[cfg(test)]
mod test {
use super::*;
use crate::ctap::status_code::Ctap2StatusCode;
use crate::env::test::TestEnv;
/// Assembles a packet for a payload that fits into one packet.
@@ -200,6 +201,58 @@ mod test {
assert_eq!(response_packet[4], 0xBF);
}
#[test]
fn test_hard_reset() {
let env = TestEnv::default();
let mut ctap = Ctap::<TestEnv>::new(env);
assert!(!ctap.can_sleep());
// Send Init, receive Init response.
let mut init_response = ctap.process_hid_packet(&init_packet(), Transport::MainHid);
let response_packet = init_response.next().unwrap();
assert_eq!(response_packet[4], 0x86);
let cid = *array_ref!(response_packet, 15, 4);
// Send Reset, get Ok.
let reset_packet = assemble_packet(&cid, 0x10, &[0x07]);
let mut reset_response = ctap.process_hid_packet(&reset_packet, Transport::MainHid);
let response_packet = reset_response.next().unwrap();
let status_byte = Ctap2StatusCode::CTAP2_OK as u8;
let expected_data = [0x90, 0x00, 0x01, status_byte];
assert_eq!(response_packet[..4], cid);
assert_eq!(response_packet[4..8], expected_data);
}
#[test]
fn test_soft_reset() {
let mut env = TestEnv::default();
env.set_boots_after_soft_reset(true);
let mut ctap = Ctap::<TestEnv>::new(env);
assert!(ctap.can_sleep());
// Send Init, receive Init response.
let mut init_response = ctap.process_hid_packet(&init_packet(), Transport::MainHid);
let response_packet = init_response.next().unwrap();
assert_eq!(response_packet[4], 0x86);
let cid = *array_ref!(response_packet, 15, 4);
// Send Reset, get error.
let reset_packet = assemble_packet(&cid, 0x10, &[0x07]);
let mut reset_response = ctap.process_hid_packet(&reset_packet, Transport::MainHid);
let response_packet = reset_response.next().unwrap();
let status_byte = Ctap2StatusCode::CTAP2_ERR_NOT_ALLOWED as u8;
let expected_data = [0x90, 0x00, 0x01, status_byte];
assert_eq!(response_packet[..4], cid);
assert_eq!(response_packet[4..8], expected_data);
}
#[test]
fn test_env_api() {
let env = TestEnv::default();
let mut ctap = Ctap::<TestEnv>::new(env);
assert_eq!(ctap.env().firmware_version(), Some(0));
}
#[test]
#[cfg(feature = "vendor_hid")]
fn test_locked_transport() {
@@ -222,4 +275,14 @@ mod test {
let response_packet = init_response.next().unwrap();
assert_eq!(response_packet[4], 0xBF);
}
#[test]
#[cfg(feature = "with_ctap1")]
fn test_ctap1_initial_state() {
let env = TestEnv::default();
let mut ctap = Ctap::<TestEnv>::new(env);
// Granting doesn't work until a CTAP1 request was processed.
ctap.u2f_grant_user_presence();
assert!(!ctap.u2f_needs_user_presence());
}
}

View File

@@ -35,7 +35,6 @@ cargo check --release --target=thumbv7em-none-eabi --features "$MOST_FEATURES"
cargo check --release --target=thumbv7em-none-eabi --examples
cargo check --release --target=thumbv7em-none-eabi --examples --features with_nfc
cargo check --release --target=thumbv7em-none-eabi --manifest-path bootloader/Cargo.toml
cargo check --release --manifest-path tools/heapviz/Cargo.toml
echo "Checking Rust formatting..."
cargo fmt -- --check
@@ -46,7 +45,6 @@ cargo fmt --manifest-path libraries/cbor/fuzz/Cargo.toml -- --check
cargo fmt --manifest-path libraries/persistent_store/Cargo.toml -- --check
cargo fmt --manifest-path libraries/persistent_store/fuzz/Cargo.toml -- --check
cargo fmt --manifest-path libraries/crypto/Cargo.toml -- --check
cargo fmt --manifest-path tools/heapviz/Cargo.toml -- --check
cargo fmt --manifest-path bootloader/Cargo.toml -- --check
echo "Checking Python formatting..."
@@ -90,7 +88,6 @@ cargo test --manifest-path libraries/cbor/Cargo.toml
cargo test --manifest-path libraries/persistent_store/Cargo.toml --features std
# Running release mode to speed up. This library is legacy anyway.
cargo test --manifest-path libraries/crypto/Cargo.toml --features std --release
cargo test --manifest-path tools/heapviz/Cargo.toml
echo "Checking that boards build properly..."
make -C third_party/tock/boards/nordic/nrf52840dk_opensk

View File

@@ -1,5 +1,5 @@
#!/usr/bin/env bash
# Copyright 2019 Google LLC
# Copyright 2019-2023 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -40,7 +40,11 @@ check_command "$PIP"
# Ensure we have certificates, keys, etc. so that the tests can run
source tools/gen_key_materials.sh
generate_crypto_materials N
generate_pki N
if [ ! -f "crypto_data/opensk.key" -o ! -f "crypto_data/opensk_cert.pem" ]
then
generate_new_batch
fi
rustup show
"$PIP" install --upgrade -r requirements.txt

4
src/env/tock/mod.rs vendored
View File

@@ -452,6 +452,10 @@ impl<S: Syscalls, C: platform::subscribe::Config + platform::allow_ro::Config> E
commands::process_vendor_command(self, bytes, channel)
}
fn boots_after_soft_reset(&self) -> bool {
false
}
fn firmware_version(&self) -> Option<u64> {
self.upgrade_storage
.as_ref()

View File

@@ -1,5 +1,5 @@
#!/bin/bash
# Copyright 2019 Google LLC
# Copyright 2019-2023 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -13,20 +13,19 @@
# See the License for the specific language governing permissions and
# limitations under the License.
generate_crypto_materials () {
# OpenSSL ext file location
local openssl_ext_file=tools/openssl.ext
generate_pki () {
# Curve parameters
local ecparams_file=crypto_data/ecparams.pem
# OpenSK AAGUID
local aaguid_file=crypto_data/aaguid.txt
# Root CA key pair and certificate
local ca_priv_key=crypto_data/opensk_ca.key
local ca_cert_name=crypto_data/opensk_ca
local ca_priv_key=crypto_data/ca/root-ca/private/root-ca.key
local ca_cert_name=crypto_data/ca/root-ca
# Attestation key pair and certificate that will be embedded into the
# firmware. The certificate will be signed by the Root CA.
local opensk_key=crypto_data/opensk.key
local opensk_cert_name=crypto_data/opensk_cert
# Signing CA key pair and certificate
local signing_ca_priv_key=crypto_data/ca/signing-ca/private/signing-ca.key
local signing_ca_cert_name=crypto_data/ca/signing-ca
# The upgrade private key is used for signing, the corresponding public key
# will be COSE encoded and embedded into the firmware.
@@ -36,6 +35,9 @@ generate_crypto_materials () {
# Allow invoker to override the command with a full path.
local openssl=${OPENSSL:-$(which openssl)}
# Print version for debug purposes
${openssl} version
# We need openssl command to continue
if [ ! -x "${openssl}" ]
then
@@ -47,55 +49,109 @@ generate_crypto_materials () {
set -e
force_generate="$1"
mkdir -p crypto_data
if [ ! -f "${ca_priv_key}" ]
ask_for_password="$2"
if [ "${force_generate}" = "Y" ]
then
"${openssl}" ecparam -genkey -name prime256v1 -out "${ca_priv_key}"
# Remove old OpenSK certs and CRL
rm -rf crypto_data/crl crypto_data/certs
fi
if [ ! -f "${ca_cert_name}.pem" ]
openssl_keypwd="-nodes"
openssl_batch="-batch"
if [ "${ask_for_password}" = "Y" ]
then
openssl_keypwd=""
openssl_batch=""
fi
# Create PKI directories
mkdir -p crypto_data/ca/root-ca/private crypto_data/ca/root-ca/db
mkdir -p crypto_data/ca/signing-ca/private crypto_data/ca/signing-ca/db
mkdir -p crypto_data/crl crypto_data/certs
chmod 700 crypto_data/ca/root-ca/private crypto_data/ca/signing-ca/private
# Prepare PKI databases
for fname in \
crypto_data/ca/root-ca/db/root-ca.db \
crypto_data/ca/root-ca/db/root-ca.db.attr \
crypto_data/ca/signing-ca/db/signing-ca.db \
crypto_data/ca/signing-ca/db/signing-ca.db.attr
do
if [ "${force_generate}" = "Y" -o ! -f "${fname}" ]
then
cp /dev/null "${fname}"
fi
done
# Initialize PKI serial numbers
for fname in \
crypto_data/ca/root-ca/db/root-ca.pem.srl \
crypto_data/ca/root-ca/db/root-ca.pem.srl \
crypto_data/ca/signing-ca/db/signing-ca.pem.srl \
crypto_data/ca/signing-ca/db/signing-ca.pem.srl
do
if [ "${force_generate}" = "Y" -o ! -f "${fname}" ]
then
echo 01 > "${fname}"
fi
done
# Generate AAGUID
if [ "${force_generate}" = "Y" -o ! -f "${aaguid_file}" ]
then
uuidgen > "${aaguid_file}"
fi
if [ ! -f "${ecparams_file}" ]
then
"${openssl}" ecparam -param_enc "named_curve" -name "prime256v1" -out "${ecparams_file}"
fi
if [ "${force_generate}" = "Y" -o ! -f "${ca_cert_name}.pem" ]
then
# Create root CA request and key pair
"${openssl}" req \
-new \
-key "${ca_priv_key}" \
-config tools/openssl/root-ca.conf \
-out "${ca_cert_name}.csr" \
-subj "/CN=OpenSK CA"
"${openssl}" x509 \
-trustout \
-req \
-days 7305 \
-keyout "${ca_priv_key}" \
"${openssl_keypwd}" \
"${openssl_batch}" \
-newkey "ec:${ecparams_file}"
# Make root CA certificate
"${openssl}" ca \
-selfsign \
-config tools/openssl/root-ca.conf \
"${openssl_batch}" \
-in "${ca_cert_name}.csr" \
-signkey "${ca_priv_key}" \
-outform pem \
-out "${ca_cert_name}.pem" \
-sha256
-extensions root_ca_ext
fi
if [ "${force_generate}" = "Y" -o ! -f "${opensk_key}" ]
then
"${openssl}" ecparam -genkey -name prime256v1 -out "${opensk_key}"
fi
if [ "${force_generate}" = "Y" -o ! -f "${opensk_cert_name}.pem" ]
if [ "${force_generate}" = "Y" -o ! -f "${signing_ca_cert_name}.pem" ]
then
# Create signing CA request
"${openssl}" req \
-new \
-key "${opensk_key}" \
-out "${opensk_cert_name}.csr" \
-subj "/C=US/O=OpenSK/OU=Authenticator Attestation/CN=OpenSK Hacker Edition"
"${openssl}" x509 \
-req \
-days 3652 \
-in "${opensk_cert_name}.csr" \
-CA "${ca_cert_name}.pem" \
-extfile "${openssl_ext_file}" \
-CAkey "${ca_priv_key}" \
-CAcreateserial \
-outform pem \
-out "${opensk_cert_name}.pem" \
-sha256
-config tools/openssl/signing-ca.conf \
-out "${signing_ca_cert_name}.csr" \
-keyout "${signing_ca_priv_key}" \
"${openssl_keypwd}" \
"${openssl_batch}" \
-newkey "ec:${ecparams_file}"
# Make signing CA certificate
"${openssl}" ca \
-config tools/openssl/root-ca.conf \
"${openssl_batch}" \
-in "${signing_ca_cert_name}.csr" \
-out "${signing_ca_cert_name}.pem" \
-extensions signing_ca_ext
fi
# Create firmware update key pair
if [ "${force_generate}" = "Y" -o ! -f "${opensk_upgrade}" ]
then
"${openssl}" ecparam -genkey -name prime256v1 -out "${opensk_upgrade}"
@@ -106,11 +162,88 @@ generate_crypto_materials () {
then
"${openssl}" ec -in "${opensk_upgrade}" -pubout -out "${opensk_upgrade_pub}"
fi
if [ "${force_generate}" = "Y" -o ! -f "${aaguid_file}" ]
then
uuidgen > "${aaguid_file}"
fi
}
generate_crypto_materials "$1"
generate_new_batch () {
local openssl=${OPENSSL:-$(which openssl)}
# Curve parameters
local ecparams_file=crypto_data/ecparams.pem
# OpenSK AAGUID
local aaguid_file=crypto_data/aaguid.txt
set -e
# We need openssl command to continue
if [ ! -x "${openssl}" ]
then
echo "Missing openssl command. Try to specify its full path using OPENSSL environment variable."
exit 1
fi
if [ ! -f "${ecparams_file}" ]
then
echo "Missing curve parameters. Has the PKI been generated?"
exit 1
fi
if [ ! -f "${aaguid_file}" ]
then
echo "Missing AAGUID file."
exit 1
fi
batch_id=$(uuidgen | tr -d '-')
aaguid=$(tr -d '-' < "${aaguid_file}")
# Attestation key pair and certificate that will be embedded into the
# firmware. The certificate will be signed by the Root CA.
local opensk_key=certs/${batch_id}.key
local opensk_cert_name=certs/${batch_id}
# x509v3 extension values are passed through environment variables.
export OPENSK_AAGUID="${aaguid}"
# Comma separated values of supported transport for the batch attestation certificate.
# 0=BTC, 1=BLE, 2=USB, 3=NFC
# Default to USB only
export OPENSK_TRANSPORT="${OPENSK_TRANSPORT:-2}" # comma separated values. 1=BLE, 2=USB, 3=NFC
ask_for_password="$1"
openssl_keypwd="-nodes"
openssl_batch="-batch"
if [ "${ask_for_password}" = "Y" ]
then
openssl_keypwd=""
openssl_batch=""
fi
# Generate certificate request for the current batch
"${openssl}" req \
-new \
-config "tools/openssl/opensk.conf" \
-keyout "crypto_data/${opensk_key}" \
-out "crypto_data/${opensk_cert_name}.csr" \
"${openssl_keypwd}" \
"${openssl_batch}" \
-newkey "ec:${ecparams_file}"
# Sign it using signing-CA and injecting the AAGUID as an extension
"${openssl}" ca \
-config "tools/openssl/signing-ca.conf" \
"${openssl_batch}" \
-in "crypto_data/${opensk_cert_name}.csr" \
-out "crypto_data/${opensk_cert_name}.pem" \
-extensions "fido_key_ext"
# Force symlink to the latest batch
ln -s -f "${opensk_cert_name}.pem" crypto_data/opensk_cert.pem
ln -s -f "${opensk_key}" crypto_data/opensk.key
}
if [ "X${1}" != "X" ]
then
ask_for_password=${2:-N}
generate_pki $1 $ask_for_password
if [ "$1" = "Y" -o ! -f "crypto_data/opensk.key" -o ! -f "crypto_data/opensk_cert.pem" ]
then
generate_new_batch $ask_for_password
fi
fi

194
tools/heapviz/Cargo.lock generated
View File

@@ -1,194 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
dependencies = [
"memchr",
]
[[package]]
name = "ansi_term"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
dependencies = [
"winapi",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "cc"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
dependencies = [
"libc",
]
[[package]]
name = "clap"
version = "2.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
dependencies = [
"ansi_term",
"atty",
"bitflags",
"strsim",
"textwrap",
"unicode-width",
"vec_map",
]
[[package]]
name = "heapviz"
version = "0.1.0"
dependencies = [
"clap",
"lazy_static",
"ncurses",
"regex",
]
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.150"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
[[package]]
name = "memchr"
version = "2.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
[[package]]
name = "ncurses"
version = "5.101.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e2c5d34d72657dc4b638a1c25d40aae81e4f1c699062f72f467237920752032"
dependencies = [
"cc",
"libc",
"pkg-config",
]
[[package]]
name = "pkg-config"
version = "0.3.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
[[package]]
name = "regex"
version = "1.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]]
name = "strsim"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
dependencies = [
"unicode-width",
]
[[package]]
name = "unicode-width"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

View File

@@ -1,14 +0,0 @@
[package]
name = "heapviz"
version = "0.1.0"
authors = [
"Guillaume Endignoux <guillaumee@google.com>",
]
license = "Apache-2.0"
edition = "2018"
[dependencies]
clap = "2.33.1"
lazy_static = "1.4.0"
ncurses = "5.99.0"
regex = "1"

View File

@@ -1,217 +0,0 @@
// Copyright 2020 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.
use clap::{App, Arg};
use lazy_static::lazy_static;
use regex::Regex;
use std::fs::File;
use std::io::{BufRead, BufReader, Read, Write};
use std::thread::sleep;
use std::time::Duration;
/// Configuration, built from CLI parameters.
struct Config {
/// Handle to the log file containing allocation operations.
logfile: File,
/// Number of allocation operations to show per second.
fps: u64,
}
fn parse_cli() -> Config {
let matches = App::new("Heap visualizer")
.version("0.1")
.author("Guillaume Endignoux <guillaumee@google.com>")
.about("Tool to visualize heap usage of libtock-rs applications")
.arg(Arg::with_name("logfile")
.short("f")
.long("logfile")
.value_name("FILE")
.help("Log file containing allocation info (deploy OpenSK with --debug-allocations to obtain it)")
.takes_value(true)
.required(true))
.arg(Arg::with_name("fps")
.long("fps")
.value_name("FPS")
.help("Number of allocation operations to show per second")
.takes_value(true)
.default_value("20"))
.get_matches();
let logpath = matches.value_of("logfile").unwrap();
let fps = matches
.value_of("fps")
.unwrap()
.parse::<u64>()
.expect("The --fps parameter must be an integer");
let logfile = File::open(logpath).expect("Couldn't open --logfile for reading");
Config { logfile, fps }
}
/// An allocation or deallocation event.
#[cfg_attr(test, derive(Debug, PartialEq))]
struct Event {
/// Whether this even is an allocation (true) or a deallocation (false).
is_alloc: bool,
/// The start address of the (de)allocated block, in bytes.
start: usize,
/// The length of the (de)allocated block, in bytes.
len: usize,
}
fn parse_event(line: &str) -> Option<Event> {
// The following regex matches lines looking like the following from OpenSK's output. Such lines
// are printed to the console when the `--debug-allocations` feature is enabled in the deploy
// script.
//
// ```
// alloc[256, 1] = 0x2002401c (2 ptrs, 384 bytes)
// dealloc[64, 1] = 0x2002410c (1 ptrs, 512 bytes)
// ```
//
// The two integers between square brackets after the (de)alloc keywords represent the length
// and alignement of the allocated block, respectively. The integer serialized in hexadecimal
// after the equal sign represents the starting address of the allocated block. The two
// integers within parentheses represent statistics about the total number of allocated blocks
// and the total number of allocated bytes after the (de)allocation operation, respectively.
//
// This regex captures three elements, in this order.
// - The keyword to know whether this operation is an allocation or a deallocation.
// - The length of the allocated block.
// - The starting address of the allocated block.
lazy_static! {
static ref RE: Regex =
Regex::new(r"^(alloc|dealloc)\[(\d+), \d+\] = 0x([0-9a-f]+) \(\d+ ptrs, \d+ bytes\)$")
.unwrap();
}
RE.captures(line).map(|caps| {
let typ = caps.get(1).unwrap().as_str();
let len = caps.get(2).unwrap().as_str().parse::<usize>().unwrap();
let start = usize::from_str_radix(&caps.get(3).unwrap().as_str(), 16).unwrap();
Event {
is_alloc: typ == "alloc",
start,
len,
}
})
}
fn main() {
let config = parse_cli();
let mut events = Vec::new();
for line in BufReader::new(config.logfile).lines() {
if let Some(event) = parse_event(&line.unwrap()) {
events.push(event);
}
}
let count_alloc = events.iter().filter(|e| e.is_alloc).count();
let count_dealloc = events.len() - count_alloc;
let start = events.iter().map(|e| e.start).min().unwrap_or(0);
let end = events.iter().map(|e| e.start + e.len).max().unwrap_or(0);
let mut usage = 0;
let peak = events
.iter()
.map(|e| {
if e.is_alloc {
usage += e.len;
} else {
usage -= e.len;
}
usage
})
.max()
.unwrap_or(0);
let len = end - start;
println!(
"Observed {} allocations and {} deallocations",
count_alloc, count_dealloc
);
println!("Start address: {:08x}", start);
println!("End address: {:08x}", end);
println!("Peak usage: {0} = {0:08x} bytes", peak);
println!("Peak consumption: {0} = {0:08x} bytes", len);
println!("Fragmentation overhead: {0} = {0:08x} bytes", len - peak);
print!("\nPress ENTER to start the visualization...");
std::io::stdout().flush().unwrap();
// Wait for ENTER, by reading a single byte and discarding it.
let _ = std::io::stdin().lock().read(&mut [0u8]).unwrap();
let window = ncurses::initscr();
ncurses::cbreak();
ncurses::noecho();
ncurses::intrflush(window, false);
ncurses::curs_set(ncurses::CURSOR_VISIBILITY::CURSOR_INVISIBLE);
let width = ncurses::getmaxx(window) as usize;
for e in events.iter() {
let position = e.start - start;
ncurses::wmove(window, (position / width) as i32, (position % width) as i32);
let mut s = Vec::with_capacity(e.len);
if e.is_alloc {
s.resize(e.len, b'#');
} else {
s.resize(e.len, b'.');
}
ncurses::addstr(std::str::from_utf8(s.as_slice()).unwrap());
ncurses::refresh();
sleep(Duration::from_nanos(1_000_000_000 / config.fps));
}
ncurses::endwin();
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_parse_event_alloc() {
assert_eq!(
parse_event("alloc[256, 1] = 0x2002401c (2 ptrs, 384 bytes)"),
Some(Event {
is_alloc: true,
start: 0x2002401c,
len: 256,
})
);
}
#[test]
fn test_parse_event_dealloc() {
assert_eq!(
parse_event("dealloc[64, 1] = 0x2002410c (1 ptrs, 512 bytes)"),
Some(Event {
is_alloc: false,
start: 0x2002410c,
len: 64,
})
);
}
#[test]
fn test_parse_event_none() {
assert_eq!(
parse_event(
"NRF52 HW INFO: Variant: AAD0, Part: N52840, Package: QI, Ram: K256, Flash: K1024"
),
None
);
}
}

View File

@@ -1 +0,0 @@
basicConstraints=CA:FALSE

26
tools/openssl/opensk.conf Normal file
View File

@@ -0,0 +1,26 @@
oid_section = OIDS
[ OIDS ]
fido_attestation = 1.3.6.1.4.1.45724.2.1.1
fido_aaguid = 1.3.6.1.4.1.45724.1.1.4
[ req ]
encrypt_key = no
default_md = sha256
utf8 = yes
string_mask = utf8only
prompt = no
distinguished_name = fido_dn
req_extensions = fido_reqext
[ fido_dn ]
countryName = "US"
organizationName = "OpenSK"
organizationalUnitName = "Authenticator Attestation"
commonName = "OpenSK Hacker Edition"
[ fido_reqext ]
keyUsage = critical,digitalSignature
subjectKeyIdentifier = hash
fido_attestation = ASN1:FORMAT:BITLIST,BITSTRING:${ENV::OPENSK_TRANSPORT}
fido_aaguid = ASN1:FORMAT:HEX,OCTETSTRING:${ENV::OPENSK_AAGUID}

View File

@@ -0,0 +1,84 @@
oid_section = OIDS
[ default ]
ca = root-ca
dir = ./crypto_data
[ req ]
encrypt_key = yes
default_md = sha256
utf8 = yes
string_mask = utf8only
prompt = no
distinguished_name = ca_dn
req_extensions = ca_reqext
[ OIDS ]
fido_attestation = 1.3.6.1.4.1.45724.2.1.1
fido_aaguid = 1.3.6.1.4.1.45724.1.1.4
[ ca_dn ]
countryName = "US"
organizationName = "OpenSK"
organizationalUnitName = "Authenticator Attestation"
commonName = "OpenSK CA"
[ ca_reqext ]
keyUsage = critical,keyCertSign,cRLSign
basicConstraints = critical,CA:true
subjectKeyIdentifier = hash
[ ca ]
default_ca = root_ca
[ root_ca ]
certificate = $dir/ca/$ca.pem
private_key = $dir/ca/$ca/private/$ca.key
new_certs_dir = $dir/ca/$ca
serial = $dir/ca/$ca/db/$ca.pem.srl
crlnumber = $dir/ca/$ca/db/$ca.pem.srl
database = $dir/ca/$ca/db/$ca.db
unique_subject = no
default_days = 36525
default_md = sha256
policy = match_pol
email_in_dn = no
preserve = no
name_opt = ca_default
cert_opt = ca_default
copy_extensions = none
x509_extensions = signing_ca_ext
default_crl_days = 365
crl_extensions = crl_ext
[ match_pol ]
countryName = match
organizationName = match
organizationalUnitName = match
commonName = supplied
[ any_pol ]
countryName = optional
stateOrProvinceName = optional
localityName = optional
organizationName = optional
organizationalUnitName = optional
commonName = optional
emailAddress = optional
[ root_ca_ext ]
keyUsage = critical,keyCertSign,cRLSign
basicConstraints = critical,CA:true
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always
fido_attestation = ASN1:FORMAT:HEX,BITSTRING:00
[ signing_ca_ext ]
keyUsage = critical,keyCertSign,cRLSign
basicConstraints = critical,CA:true,pathlen:0
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always
[ crl_ext ]
authorityKeyIdentifier = keyid:always

View File

@@ -0,0 +1,91 @@
oid_section = OIDS
[ default ]
ca = signing-ca
dir = ./crypto_data
[ req ]
default_bits = 4096
encrypt_key = yes
default_md = sha256
utf8 = yes
string_mask = utf8only
prompt = no
distinguished_name = ca_dn
req_extensions = ca_reqext
[ OIDS ]
fido_attestation = 1.3.6.1.4.1.45724.2.1.1
fido_aaguid = 1.3.6.1.4.1.45724.1.1.4
[ ca_dn ]
countryName = "US"
organizationName = "OpenSK"
organizationalUnitName = "Authenticator Attestation"
commonName = "OpenSK Signing"
[ ca_reqext ]
keyUsage = critical,keyCertSign,cRLSign
basicConstraints = critical,CA:true,pathlen:0
subjectKeyIdentifier = hash
[ ca ]
default_ca = signing_ca
[ signing_ca ]
certificate = $dir/ca/$ca.pem
private_key = $dir/ca/$ca/private/$ca.key
new_certs_dir = $dir/ca/$ca
serial = $dir/ca/$ca/db/$ca.pem.srl
crlnumber = $dir/ca/$ca/db/$ca.pem.srl
database = $dir/ca/$ca/db/$ca.db
unique_subject = no
default_days = 35064
default_md = sha256
policy = match_pol
email_in_dn = no
preserve = no
name_opt = ca_default
cert_opt = ca_default
copy_extensions = copy
x509_extensions = fido_key_ext
default_crl_days = 7
crl_extensions = crl_ext
[ match_pol ]
countryName = match
organizationName = match
organizationalUnitName = match
commonName = supplied
[ any_pol ]
countryName = optional
stateOrProvinceName = optional
localityName = optional
organizationName = optional
organizationalUnitName = optional
commonName = optional
emailAddress = optional
[ root_ca_ext ]
keyUsage = critical,keyCertSign,cRLSign
basicConstraints = critical,CA:true
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always
[ signing_ca_ext ]
keyUsage = critical,keyCertSign,cRLSign
basicConstraints = critical,CA:true,pathlen:0
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always
[ fido_key_ext ]
keyUsage = critical,digitalSignature
basicConstraints = CA:false
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always
[ crl_ext ]
authorityKeyIdentifier = keyid:always