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 runs-on: ubuntu-latest
if: github.repository == 'google/OpenSK' if: github.repository == 'google/OpenSK'
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v4
with: with:
submodules: "true" submodules: "true"
- uses: actions/setup-python@v1 - uses: actions/setup-python@v5
with: with:
python-version: "3.10" python-version: "3.10"
- name: Set up OpenSK - name: Set up OpenSK

View File

@@ -6,7 +6,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
# Setup # Setup
- uses: actions/setup-python@v1 - uses: actions/setup-python@v5
with: with:
python-version: "3.10" python-version: "3.10"
- uses: actions-rs/cargo@v1 - uses: actions-rs/cargo@v1
@@ -15,7 +15,7 @@ jobs:
args: cargo-bloat args: cargo-bloat
# First run: PR # First run: PR
- uses: actions/checkout@v2 - uses: actions/checkout@v4
with: with:
submodules: true submodules: true
- name: Set up OpenSK - 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 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 # Second run: PR
- uses: actions/checkout@v2 - uses: actions/checkout@v4
with: with:
submodules: true submodules: true
ref: ${{ github.base_ref }} ref: ${{ github.base_ref }}

View File

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

View File

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

View File

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

View File

@@ -12,10 +12,10 @@ jobs:
mdlint: mdlint:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v4
- name: markdownlint-cli - name: markdownlint-cli
uses: nosborn/github-action-markdown-cli@v1.1.1 uses: nosborn/github-action-markdown-cli@v3
with: with:
files: '**/*.md' 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 fail-fast: false
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v4
with: with:
submodules: "true" submodules: "true"
- name: Install Rust toolchain - name: Install Rust toolchain
run: rustup show run: rustup show
- uses: actions/setup-python@v1 - uses: actions/setup-python@v5
with: with:
python-version: "3.10" python-version: "3.10"
- name: Set up OpenSK - name: Set up OpenSK
@@ -30,7 +30,7 @@ jobs:
run: ./maintainers/reproduce_hashes.sh run: ./maintainers/reproduce_hashes.sh
- name: Upload reproduced binaries - name: Upload reproduced binaries
uses: actions/upload-artifact@v1 uses: actions/upload-artifact@v4
with: with:
name: reproduced-${{ matrix.os }} name: reproduced-${{ matrix.os }}
path: reproducible/reproduced.tar path: reproducible/reproduced.tar

View File

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

View File

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

View File

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

View File

@@ -7,18 +7,18 @@
All the generated certificates and private keys are stored in the directory All the generated certificates and private keys are stored in the directory
`crypto_data/`. The expected content after running our `setup.sh` script is: `crypto_data/`. The expected content after running our `setup.sh` script is:
File | Purpose | File | Purpose |
------------------------ | -------------------------------------------------------- | ------------------------ | ----------------------------------------------- |
`aaguid.txt` | Text file containaing the AAGUID value | `aaguid.txt` | Text file containaing the AAGUID value |
`opensk_ca.csr` | Certificate sign request for the Root CA | `opensk_ca.csr` | Certificate sign request for the Root CA |
`opensk_ca.key` | ECC secp256r1 private key used 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.pem` | PEM encoded certificate of the Root CA |
`opensk_ca.srl` | File generated by OpenSSL | `opensk_ca.srl` | File generated by OpenSSL |
`opensk_cert.csr` | Certificate sign request for the attestation certificate | `opensk_cert.csr` | CSR for attestation certificate |
`opensk_cert.pem` | PEM encoded certificate used for the authenticator | `opensk_cert.pem` | PEM encoded certificate for the authenticator |
`opensk.key` | ECC secp256r1 private key used for the autenticator | `opensk.key` | ECC secp256r1 private key for the autenticator |
`opensk_upgrade.key` | Private key for signing upgrades through CTAP | `opensk_upgrade.key` | Private key for signing upgrades through CTAP |
`opensk_upgrade_pub.pem` | Public key added to the firmware for verifying upgrades | `opensk_upgrade_pub.pem` | Public key for verifying upgrades |
If you want to use your own attestation certificate and private key, If you want to use your own attestation certificate and private key,
replace the `opensk_cert.pem` and `opensk.key` files. The script at replace the `opensk_cert.pem` and `opensk.key` files. The script at

View File

@@ -107,31 +107,3 @@ alloc[256, 1] = 0x2002401c (2 ptrs, 384 bytes)
# After this operation, 1 pointers are allocated, totalling 512 bytes. # After this operation, 1 pointers are allocated, totalling 512 bytes.
dealloc[64, 1] = 0x2002410c (1 ptrs, 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

@@ -25,8 +25,8 @@ following:
* python3 and pip (can be installed with the `python3-pip` package on Debian) * 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 * the OpenSSL command line tool (can be installed and configured with the
`libssl-dev` and `pkg-config` packages on Debian) `libssl-dev` and `pkg-config` packages on Debian)
* `nrfutil` (can be installed using `pip3 install nrfutil`) if you want to flash * `nrfutil` (pip package of the same name), if you want to flash
a device with DFU a device with DFU. Read the disclaimer below.
* `uuid-runtime` if you are missing the `uuidgen` command. * `uuid-runtime` if you are missing the `uuidgen` command.
* `llvm` and `gcc-arm-none-eabi` if you want to use the upgradability feature. * `llvm` and `gcc-arm-none-eabi` if you want to use the upgradability feature.
@@ -37,16 +37,17 @@ instructions to appropriate binaries for your system.
The scripts provided in this project have been tested under Linux and OS X. We The scripts provided in this project have been tested under Linux and OS X. We
haven't tested them on Windows and other platforms. haven't tested them on Windows and other platforms.
If you use Python newer than 3.10, then nrfutil for flashing over DFU is You need `nrfutil` version 6, if you want to flash over DFU.
currently not supported. Please use Python 3.10, or play around with [Nordic's The tool doesn't support Python newer than 3.10. Therefore, we don't officially
new tool](https://www.nordicsemi.com/Products/Development-tools/nrf-util) support DFU for other versions. If you want to try regardless,
instead. [Nordic's new tool](https://www.nordicsemi.com/Products/Development-tools/nrf-util)
might work for you.
### Compiling the firmware ### Compiling the firmware
If this is your first time installing OpenSK, please skip directly to If this is your first time installing OpenSK, please skip directly to
[Initial setup](#Initial-setup). Else, see [Initial setup](#initial-setup). Else, see
[Updating your setup](#Updating-your-setup) below. [Updating your setup](#updating-your-setup) below.
#### Updating your setup #### Updating your setup
@@ -61,11 +62,11 @@ following steps:
./setup.sh ./setup.sh
``` ```
* Flash your board according to the * Flash your board according to the [instructions below](#flashing-a-firmware).
[flashing instructions below](#Flashing-a-firmware]. If you come from an
OpenSK version before the 2.0 certified one, your credential storage is not If you come from an OpenSK version before the 2.0 certified one, your credential
backwards compatible and you have to reset it. :warning: You will lose storage is not backwards compatible and you have to reset it. :warning: You will
logins to all websites that you registered with OpenSK. To erase your lose logins to all websites that you registered with OpenSK. To erase your
persistent storage, run the deploy script twice: Once with the application persistent storage, run the deploy script twice: Once with the application
parameter `--erase_storage`, and once with `--opensk` as usual. parameter `--erase_storage`, and once with `--opensk` as usual.
@@ -139,7 +140,7 @@ From here on, please follow the instructions for your hardware:
### Advanced installation ### Advanced installation
We recommend that you flash your development board with JTAG and dongles with 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: above. However, we support other programmers:
* OpenOCD: `./deploy.py --board=nrf52840_dongle_opensk --opensk * OpenOCD: `./deploy.py --board=nrf52840_dongle_opensk --opensk

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; use core::convert::TryFrom;
#[derive(Clone, Copy, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum UsbEndpoint { pub enum UsbEndpoint {
MainHid = 1, MainHid = 1,
#[cfg(feature = "vendor_hid")] #[cfg(feature = "vendor_hid")]
@@ -40,6 +40,7 @@ pub enum SendOrRecvStatus {
Received(UsbEndpoint), Received(UsbEndpoint),
} }
#[derive(Debug, PartialEq, Eq)]
pub struct SendOrRecvError; pub struct SendOrRecvError;
pub type SendOrRecvResult = Result<SendOrRecvStatus, SendOrRecvError>; pub type SendOrRecvResult = Result<SendOrRecvStatus, SendOrRecvError>;
@@ -47,3 +48,16 @@ pub type SendOrRecvResult = Result<SendOrRecvStatus, SendOrRecvError>;
pub trait HidConnection { pub trait HidConnection {
fn send_and_maybe_recv(&mut self, buf: &mut [u8; 64], timeout_ms: usize) -> SendOrRecvResult; 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() { fn test_invariants() {
assert!(is_valid(&DEFAULT_CUSTOMIZATION)); 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. /// Gets a reference to the PIN protocol of the given version.
fn get_pin_protocol(&self, pin_uv_auth_protocol: PinUvAuthProtocol) -> &PinProtocol<E> { fn get_pin_protocol(&self, pin_uv_auth_protocol: PinUvAuthProtocol) -> &PinProtocol<E> {
match pin_uv_auth_protocol { match pin_uv_auth_protocol {
@@ -1507,9 +1513,11 @@ mod test {
let mut env = TestEnv::default(); let mut env = TestEnv::default();
let mut client_pin = ClientPin::<TestEnv>::new(&mut env); let mut client_pin = ClientPin::<TestEnv>::new(&mut env);
let message = [0xAA]; let message = [0xAA];
assert!(!client_pin.has_token(&mut env));
client_pin client_pin
.pin_uv_auth_token_state .pin_uv_auth_token_state
.begin_using_pin_uv_auth_token(&mut env); .begin_using_pin_uv_auth_token(&mut env);
assert!(client_pin.has_token(&mut env));
let pin_uv_auth_token_v1 = client_pin let pin_uv_auth_token_v1 = client_pin
.get_pin_protocol(PinUvAuthProtocol::V1) .get_pin_protocol(PinUvAuthProtocol::V1)
@@ -1655,6 +1663,7 @@ mod test {
.has_permissions_rp_id("example.com"), .has_permissions_rp_id("example.com"),
Ok(()) Ok(())
); );
assert!(client_pin.has_token(&mut env));
env.clock().advance(30001); env.clock().advance(30001);
client_pin.update_timeouts(&mut env); client_pin.update_timeouts(&mut env);
@@ -1672,6 +1681,7 @@ mod test {
.has_permissions_rp_id("example.com"), .has_permissions_rp_id("example.com"),
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID) Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
); );
assert!(!client_pin.has_token(&mut env));
} }
#[test] #[test]

View File

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

View File

@@ -410,11 +410,26 @@ pub struct StatefulPermission<E: Env> {
channel: Option<Channel>, 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> { impl<E: Env> StatefulPermission<E> {
/// Creates the command state at device startup. /// Creates the command state at device startup.
/// ///
/// Resets are only possible after a power cycle. Therefore, initialization /// Resets are only possible after a power cycle. Therefore, there is no way to grant the Reset
/// means allowing Reset, and Reset cannot be granted later. /// 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> { pub fn new_reset(env: &mut E) -> StatefulPermission<E> {
StatefulPermission { StatefulPermission {
permission: env.clock().make_timer(RESET_TIMEOUT_DURATION_MS), 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 { pub fn new(env: &mut E) -> Self {
storage::init(env).ok().unwrap(); storage::init(env).ok().unwrap();
let client_pin = ClientPin::new(env); let client_pin = ClientPin::new(env);
let stateful_command_permission = if env.boots_after_soft_reset() {
StatefulPermission::default()
} else {
StatefulPermission::new_reset(env)
};
CtapState { CtapState {
client_pin, client_pin,
#[cfg(feature = "with_ctap1")] #[cfg(feature = "with_ctap1")]
u2f_up_state: U2fUserPresenceState::new(), u2f_up_state: U2fUserPresenceState::new(),
stateful_command_permission: StatefulPermission::new_reset(env), stateful_command_permission,
large_blobs: LargeBlobs::new(), large_blobs: LargeBlobs::new(),
} }
} }
@@ -582,6 +602,12 @@ impl<E: Env> CtapState<E> {
self.stateful_command_permission.clear_old_channels(channel); 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( pub fn process_command(
&mut self, &mut self,
env: &mut E, env: &mut E,
@@ -1282,15 +1308,19 @@ impl<E: Env> CtapState<E> {
options.append(&mut vec![ options.append(&mut vec![
(String::from("rk"), true), (String::from("rk"), true),
(String::from("up"), true), (String::from("up"), true),
(String::from("alwaysUv"), has_always_uv),
(String::from("credMgmt"), true), (String::from("credMgmt"), true),
#[cfg(feature = "config_command")]
(String::from("authnrCfg"), true), (String::from("authnrCfg"), true),
(String::from("clientPin"), storage::pin_hash(env)?.is_some()), (String::from("clientPin"), storage::pin_hash(env)?.is_some()),
(String::from("largeBlobs"), true), (String::from("largeBlobs"), true),
(String::from("pinUvAuthToken"), true), (String::from("pinUvAuthToken"), true),
#[cfg(feature = "config_command")]
(String::from("setMinPINLength"), true), (String::from("setMinPINLength"), true),
(String::from("makeCredUvNotRqd"), !has_always_uv), (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]; let mut pin_protocols = vec![PinUvAuthProtocol::V2 as u64];
if env.customization().allows_pin_protocol_v1() { if env.customization().allows_pin_protocol_v1() {
pin_protocols.push(PinUvAuthProtocol::V1 as u64); pin_protocols.push(PinUvAuthProtocol::V1 as u64);
@@ -1499,12 +1529,15 @@ mod test {
"ep" => env.customization().enterprise_attestation_mode().map(|_| false), "ep" => env.customization().enterprise_attestation_mode().map(|_| false),
"rk" => true, "rk" => true,
"up" => true, "up" => true,
#[cfg(feature = "config_command")]
"alwaysUv" => false, "alwaysUv" => false,
"credMgmt" => true, "credMgmt" => true,
#[cfg(feature = "config_command")]
"authnrCfg" => true, "authnrCfg" => true,
"clientPin" => false, "clientPin" => false,
"largeBlobs" => true, "largeBlobs" => true,
"pinUvAuthToken" => true, "pinUvAuthToken" => true,
#[cfg(feature = "config_command")]
"setMinPINLength" => true, "setMinPINLength" => true,
"makeCredUvNotRqd" => true, "makeCredUvNotRqd" => true,
}, },

View File

@@ -76,6 +76,9 @@ pub trait Env {
#[cfg(feature = "vendor_hid")] #[cfg(feature = "vendor_hid")]
fn vendor_hid_connection(&mut self) -> &mut Self::HidConnection; 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. /// Option to return a firmware version that is shown as device info.
fn firmware_version(&self) -> Option<u64> { fn firmware_version(&self) -> Option<u64> {
None None

View File

@@ -34,6 +34,7 @@ pub struct TestEnv {
store: Store<BufferStorage>, store: Store<BufferStorage>,
customization: TestCustomization, customization: TestCustomization,
clock: TestClock, clock: TestClock,
soft_reset: bool,
} }
pub type TestRng = StdRng; pub type TestRng = StdRng;
@@ -127,6 +128,7 @@ impl Default for TestEnv {
store, store,
customization, customization,
clock, clock,
soft_reset: false,
} }
} }
} }
@@ -139,6 +141,10 @@ impl TestEnv {
pub fn seed_rng_from_u64(&mut self, seed: u64) { pub fn seed_rng_from_u64(&mut self, seed: u64) {
self.rng = StdRng::seed_from_u64(seed); self.rng = StdRng::seed_from_u64(seed);
} }
pub fn set_boots_after_soft_reset(&mut self, value: bool) {
self.soft_reset = value;
}
} }
impl TestUserPresence { impl TestUserPresence {
@@ -227,6 +233,10 @@ impl Env for TestEnv {
self self
} }
fn boots_after_soft_reset(&self) -> bool {
self.soft_reset
}
fn firmware_version(&self) -> Option<u64> { fn firmware_version(&self) -> Option<u64> {
Some(0) Some(0)
} }
@@ -247,4 +257,14 @@ mod test {
clock.advance(1); clock.advance(1);
assert!(clock.is_elapsed(&timer)); 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 &mut self.state
} }
pub fn hid(&mut self) -> &mut MainHid<E> {
&mut self.hid
}
pub fn env(&mut self) -> &mut E { pub fn env(&mut self) -> &mut E {
&mut self.env &mut self.env
} }
@@ -120,6 +116,10 @@ impl<E: Env> Ctap<E> {
self.hid.should_wink(&mut self.env) 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")] #[cfg(feature = "with_ctap1")]
pub fn u2f_grant_user_presence(&mut self) { pub fn u2f_grant_user_presence(&mut self) {
self.state.u2f_grant_user_presence(&mut self.env) self.state.u2f_grant_user_presence(&mut self.env)
@@ -134,6 +134,7 @@ impl<E: Env> Ctap<E> {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use crate::ctap::status_code::Ctap2StatusCode;
use crate::env::test::TestEnv; use crate::env::test::TestEnv;
/// Assembles a packet for a payload that fits into one packet. /// Assembles a packet for a payload that fits into one packet.
@@ -200,6 +201,58 @@ mod test {
assert_eq!(response_packet[4], 0xBF); 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] #[test]
#[cfg(feature = "vendor_hid")] #[cfg(feature = "vendor_hid")]
fn test_locked_transport() { fn test_locked_transport() {
@@ -222,4 +275,14 @@ mod test {
let response_packet = init_response.next().unwrap(); let response_packet = init_response.next().unwrap();
assert_eq!(response_packet[4], 0xBF); 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
cargo check --release --target=thumbv7em-none-eabi --examples --features with_nfc 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 --target=thumbv7em-none-eabi --manifest-path bootloader/Cargo.toml
cargo check --release --manifest-path tools/heapviz/Cargo.toml
echo "Checking Rust formatting..." echo "Checking Rust formatting..."
cargo fmt -- --check 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/Cargo.toml -- --check
cargo fmt --manifest-path libraries/persistent_store/fuzz/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 libraries/crypto/Cargo.toml -- --check
cargo fmt --manifest-path tools/heapviz/Cargo.toml -- --check
cargo fmt --manifest-path bootloader/Cargo.toml -- --check cargo fmt --manifest-path bootloader/Cargo.toml -- --check
echo "Checking Python formatting..." 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 cargo test --manifest-path libraries/persistent_store/Cargo.toml --features std
# Running release mode to speed up. This library is legacy anyway. # 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 libraries/crypto/Cargo.toml --features std --release
cargo test --manifest-path tools/heapviz/Cargo.toml
echo "Checking that boards build properly..." echo "Checking that boards build properly..."
make -C third_party/tock/boards/nordic/nrf52840dk_opensk make -C third_party/tock/boards/nordic/nrf52840dk_opensk

View File

@@ -1,5 +1,5 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Copyright 2019 Google LLC # Copyright 2019-2023 Google LLC
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with 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 # Ensure we have certificates, keys, etc. so that the tests can run
source tools/gen_key_materials.sh 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 rustup show
"$PIP" install --upgrade -r requirements.txt "$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) commands::process_vendor_command(self, bytes, channel)
} }
fn boots_after_soft_reset(&self) -> bool {
false
}
fn firmware_version(&self) -> Option<u64> { fn firmware_version(&self) -> Option<u64> {
self.upgrade_storage self.upgrade_storage
.as_ref() .as_ref()

View File

@@ -1,5 +1,5 @@
#!/bin/bash #!/bin/bash
# Copyright 2019 Google LLC # Copyright 2019-2023 Google LLC
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with 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 # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
generate_crypto_materials () { generate_pki () {
# OpenSSL ext file location # Curve parameters
local openssl_ext_file=tools/openssl.ext local ecparams_file=crypto_data/ecparams.pem
# OpenSK AAGUID # OpenSK AAGUID
local aaguid_file=crypto_data/aaguid.txt local aaguid_file=crypto_data/aaguid.txt
# Root CA key pair and certificate # Root CA key pair and certificate
local ca_priv_key=crypto_data/opensk_ca.key local ca_priv_key=crypto_data/ca/root-ca/private/root-ca.key
local ca_cert_name=crypto_data/opensk_ca local ca_cert_name=crypto_data/ca/root-ca
# Attestation key pair and certificate that will be embedded into the # Signing CA key pair and certificate
# firmware. The certificate will be signed by the Root CA. local signing_ca_priv_key=crypto_data/ca/signing-ca/private/signing-ca.key
local opensk_key=crypto_data/opensk.key local signing_ca_cert_name=crypto_data/ca/signing-ca
local opensk_cert_name=crypto_data/opensk_cert
# The upgrade private key is used for signing, the corresponding public key # The upgrade private key is used for signing, the corresponding public key
# will be COSE encoded and embedded into the firmware. # 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. # Allow invoker to override the command with a full path.
local openssl=${OPENSSL:-$(which openssl)} local openssl=${OPENSSL:-$(which openssl)}
# Print version for debug purposes
${openssl} version
# We need openssl command to continue # We need openssl command to continue
if [ ! -x "${openssl}" ] if [ ! -x "${openssl}" ]
then then
@@ -47,55 +49,109 @@ generate_crypto_materials () {
set -e set -e
force_generate="$1" force_generate="$1"
mkdir -p crypto_data ask_for_password="$2"
if [ ! -f "${ca_priv_key}" ]
if [ "${force_generate}" = "Y" ]
then 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 fi
if [ ! -f "${ca_cert_name}.pem" ] openssl_keypwd="-nodes"
openssl_batch="-batch"
if [ "${ask_for_password}" = "Y" ]
then 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 \ "${openssl}" req \
-new \ -new \
-key "${ca_priv_key}" \ -config tools/openssl/root-ca.conf \
-out "${ca_cert_name}.csr" \ -out "${ca_cert_name}.csr" \
-subj "/CN=OpenSK CA" -keyout "${ca_priv_key}" \
"${openssl}" x509 \ "${openssl_keypwd}" \
-trustout \ "${openssl_batch}" \
-req \ -newkey "ec:${ecparams_file}"
-days 7305 \
# Make root CA certificate
"${openssl}" ca \
-selfsign \
-config tools/openssl/root-ca.conf \
"${openssl_batch}" \
-in "${ca_cert_name}.csr" \ -in "${ca_cert_name}.csr" \
-signkey "${ca_priv_key}" \
-outform pem \
-out "${ca_cert_name}.pem" \ -out "${ca_cert_name}.pem" \
-sha256 -extensions root_ca_ext
fi fi
if [ "${force_generate}" = "Y" -o ! -f "${opensk_key}" ] if [ "${force_generate}" = "Y" -o ! -f "${signing_ca_cert_name}.pem" ]
then
"${openssl}" ecparam -genkey -name prime256v1 -out "${opensk_key}"
fi
if [ "${force_generate}" = "Y" -o ! -f "${opensk_cert_name}.pem" ]
then then
# Create signing CA request
"${openssl}" req \ "${openssl}" req \
-new \ -new \
-key "${opensk_key}" \ -config tools/openssl/signing-ca.conf \
-out "${opensk_cert_name}.csr" \ -out "${signing_ca_cert_name}.csr" \
-subj "/C=US/O=OpenSK/OU=Authenticator Attestation/CN=OpenSK Hacker Edition" -keyout "${signing_ca_priv_key}" \
"${openssl}" x509 \ "${openssl_keypwd}" \
-req \ "${openssl_batch}" \
-days 3652 \ -newkey "ec:${ecparams_file}"
-in "${opensk_cert_name}.csr" \
-CA "${ca_cert_name}.pem" \ # Make signing CA certificate
-extfile "${openssl_ext_file}" \ "${openssl}" ca \
-CAkey "${ca_priv_key}" \ -config tools/openssl/root-ca.conf \
-CAcreateserial \ "${openssl_batch}" \
-outform pem \ -in "${signing_ca_cert_name}.csr" \
-out "${opensk_cert_name}.pem" \ -out "${signing_ca_cert_name}.pem" \
-sha256 -extensions signing_ca_ext
fi fi
# Create firmware update key pair
if [ "${force_generate}" = "Y" -o ! -f "${opensk_upgrade}" ] if [ "${force_generate}" = "Y" -o ! -f "${opensk_upgrade}" ]
then then
"${openssl}" ecparam -genkey -name prime256v1 -out "${opensk_upgrade}" "${openssl}" ecparam -genkey -name prime256v1 -out "${opensk_upgrade}"
@@ -106,11 +162,88 @@ generate_crypto_materials () {
then then
"${openssl}" ec -in "${opensk_upgrade}" -pubout -out "${opensk_upgrade_pub}" "${openssl}" ec -in "${opensk_upgrade}" -pubout -out "${opensk_upgrade_pub}"
fi 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