Merge branch 'develop' into v2_optim

This commit is contained in:
Julien Cretin
2021-01-18 16:10:08 +01:00
36 changed files with 2849 additions and 1264 deletions
+2 -14
View File
@@ -42,12 +42,6 @@ jobs:
command: check
args: --target thumbv7em-none-eabi --release --features with_ctap1
- name: Check OpenSK with_ctap2_1
uses: actions-rs/cargo@v1
with:
command: check
args: --target thumbv7em-none-eabi --release --features with_ctap2_1
- name: Check OpenSK debug_ctap
uses: actions-rs/cargo@v1
with:
@@ -78,17 +72,11 @@ jobs:
command: check
args: --target thumbv7em-none-eabi --release --features debug_ctap,with_ctap1
- name: Check OpenSK debug_ctap,with_ctap2_1
- name: Check OpenSK debug_ctap,with_ctap1,panic_console,debug_allocations,verbose
uses: actions-rs/cargo@v1
with:
command: check
args: --target thumbv7em-none-eabi --release --features debug_ctap,with_ctap2_1
- name: Check OpenSK debug_ctap,with_ctap1,with_ctap2_1,panic_console,debug_allocations,verbose
uses: actions-rs/cargo@v1
with:
command: check
args: --target thumbv7em-none-eabi --release --features debug_ctap,with_ctap1,with_ctap2_1,panic_console,debug_allocations,verbose
args: --target thumbv7em-none-eabi --release --features debug_ctap,with_ctap1,,panic_console,debug_allocations,verbose
- name: Check examples
uses: actions-rs/cargo@v1
-24
View File
@@ -51,27 +51,3 @@ jobs:
command: test
args: --features std,with_ctap1
- name: Unit testing of CTAP2 (release mode + CTAP2.1)
uses: actions-rs/cargo@v1
with:
command: test
args: --release --features std,with_ctap2_1
- name: Unit testing of CTAP2 (debug mode + CTAP2.1)
uses: actions-rs/cargo@v1
with:
command: test
args: --features std,with_ctap2_1
- name: Unit testing of CTAP2 (release mode + CTAP1 + CTAP2.1)
uses: actions-rs/cargo@v1
with:
command: test
args: --release --features std,with_ctap1,with_ctap2_1
- name: Unit testing of CTAP2 (debug mode + CTAP1 + CTAP2.1)
uses: actions-rs/cargo@v1
with:
command: test
args: --features std,with_ctap1,with_ctap2_1
+1
View File
@@ -11,3 +11,4 @@ Cargo.lock
/reproducible/binaries.sha256sum
/reproducible/elf2tab.txt
/reproducible/reproduced.tar
__pycache__
+1 -3
View File
@@ -1,6 +1,6 @@
[package]
name = "ctap2"
version = "0.1.0"
version = "1.0.0"
authors = [
"Fabian Kaczmarczyck <kaczmarczyck@google.com>",
"Guillaume Endignoux <guillaumee@google.com>",
@@ -27,7 +27,6 @@ panic_console = ["lang_items/panic_console"]
std = ["cbor/std", "crypto/std", "crypto/derive_debug", "lang_items/std", "persistent_store/std"]
verbose = ["debug_ctap", "libtock_drivers/verbose_usb"]
with_ctap1 = ["crypto/with_ctap1"]
with_ctap2_1 = []
with_nfc = ["libtock_drivers/with_nfc"]
[dev-dependencies]
@@ -35,7 +34,6 @@ elf2tab = "0.6.0"
enum-iterator = "0.6.0"
[build-dependencies]
openssl = "0.10"
uuid = { version = "0.8", features = ["v4"] }
[profile.dev]
+43 -29
View File
@@ -1,6 +1,5 @@
# <img alt="OpenSK logo" src="docs/img/OpenSK.svg" width="200px">
[![Build Status](https://travis-ci.org/google/OpenSK.svg?branch=master)](https://travis-ci.org/google/OpenSK)
![markdownlint](https://github.com/google/OpenSK/workflows/markdownlint/badge.svg?branch=master)
![pylint](https://github.com/google/OpenSK/workflows/pylint/badge.svg?branch=master)
![Cargo check](https://github.com/google/OpenSK/workflows/Cargo%20check/badge.svg?branch=master)
@@ -25,14 +24,14 @@ few limitations:
### FIDO2
Although we tested and implemented our firmware based on the published
The stable branch implements the published
[CTAP2.0 specifications](https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html),
our implementation was not reviewed nor officially tested and doesn't claim to
be FIDO Certified.
We started adding features of the upcoming next version of the
[CTAP2.1 specifications](https://fidoalliance.org/specs/fido2/fido-client-to-authenticator-protocol-v2.1-rd-20191217.html).
The development is currently between 2.0 and 2.1, with updates hidden behind a feature flag.
Please add the flag `--ctap2.1` to the deploy command to include them.
but our implementation was not reviewed nor officially tested and doesn't claim
to be FIDO Certified. It already contains some preview features of 2.1, that you
can try by adding the flag `--ctap2.1` to the deploy command.
The develop branch offers only the
[CTAP2.1 specifications](https://fidoalliance.org/specs/fido-v2.1-rd-20201208/fido-client-to-authenticator-protocol-v2.1-rd-20201208.html).
The new features of 2.1 are currently work in progress.
### Cryptography
@@ -58,8 +57,8 @@ For a more detailed guide, please refer to our
./setup.sh
```
2. Next step is to install Tock OS as well as the OpenSK application on your
board (**Warning**: it will erase the locally stored credentials). Run:
1. Next step is to install Tock OS as well as the OpenSK application on your
board. Run:
```shell
# Nordic nRF52840-DK board
@@ -68,7 +67,17 @@ For a more detailed guide, please refer to our
./deploy.py --board=nrf52840_dongle --opensk
```
3. On Linux, you may want to avoid the need for `root` privileges to interact
1. Finally you need to inject the cryptographic material if you enabled
batch attestation or CTAP1/U2F compatibility (which is the case by
default):
```shell
./tools/configure.py \
--certificate=crypto_data/opensk_cert.pem \
--private-key=crypto_data/opensk.key
```
1. On Linux, you may want to avoid the need for `root` privileges to interact
with the key. For that purpose we provide a udev rule file that can be
installed with the following command:
@@ -84,32 +93,36 @@ a few things you can personalize:
1. If you have multiple buttons, choose the buttons responsible for user
presence in `main.rs`.
2. Decide whether you want to use batch attestation. There is a boolean flag in
1. Decide whether you want to use batch attestation. There is a boolean flag in
`ctap/mod.rs`. It is mandatory for U2F, and you can create your own
self-signed certificate. The flag is used for FIDO2 and has some privacy
implications. Please check
[WebAuthn](https://www.w3.org/TR/webauthn/#attestation) for more
information.
3. Decide whether you want to use signature counters. Currently, only global
1. Decide whether you want to use signature counters. Currently, only global
signature counters are implemented, as they are the default option for U2F.
The flag in `ctap/mod.rs` only turns them off for FIDO2. The most privacy
preserving solution is individual or no signature counters. Again, please
check [WebAuthn](https://www.w3.org/TR/webauthn/#signature-counter) for
documentation.
4. Depending on your available flash storage, choose an appropriate maximum
number of supported residential keys and number of pages in
`ctap/storage.rs`.
5. Change the default level for the credProtect extension in `ctap/mod.rs`.
1. Depending on your available flash storage, choose an appropriate maximum
number of supported resident keys and number of pages in `ctap/storage.rs`.
1. Change the default level for the credProtect extension in `ctap/mod.rs`.
When changing the default, resident credentials become undiscoverable without
user verification. This helps privacy, but can make usage less comfortable
for credentials that need less protection.
6. Increase the default minimum length for PINs in `ctap/storage.rs`.
1. Increase the default minimum length for PINs in `ctap/storage.rs`.
The current minimum is 4. Values from 4 to 63 are allowed. Requiring longer
PINs can help establish trust between users and relying parties. It makes
user verification harder to break, but less convenient.
NIST recommends at least 6-digit PINs in section 5.1.9.1:
https://pages.nist.gov/800-63-3/sp800-63b.html
You can add relying parties to the list of readers of the minimum PIN length.
1. In an enterprise setting, you can adapt `DEFAULT_MIN_PIN_LENGTH_RP_IDS` and
`MAX_RP_IDS_LENGTH` for tuning the `minPinLength` extension. The former
allows some relying parties to read the minimum PIN length by default. The
latter allows storing more relying parties that may check the minimum PIN
length.
### 3D printed enclosure
@@ -148,7 +161,7 @@ operation.
The additional output looks like the following.
```
```text
# Allocation of 256 byte(s), aligned on 1 byte(s). The allocated address is
# 0x2002401c. After this operation, 2 pointers have been allocated, totalling
# 384 bytes (the total heap usage may be larger, due to alignment and
@@ -163,12 +176,12 @@ 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).
* 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
@@ -177,10 +190,11 @@ 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.
* `--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
+1 -1
View File
@@ -48,7 +48,7 @@ static STRINGS: &'static [&'static str] = &[
// Product
"OpenSK",
// Serial number
"v0.1",
"v1.0",
];
// State for loading and holding applications.
-59
View File
@@ -12,11 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use openssl::asn1;
use openssl::ec;
use openssl::nid::Nid;
use openssl::pkey::PKey;
use openssl::x509;
use std::env;
use std::fs::File;
use std::io::Read;
@@ -25,65 +20,11 @@ use std::path::Path;
use uuid::Uuid;
fn main() {
println!("cargo:rerun-if-changed=crypto_data/opensk.key");
println!("cargo:rerun-if-changed=crypto_data/opensk_cert.pem");
println!("cargo:rerun-if-changed=crypto_data/aaguid.txt");
let out_dir = env::var_os("OUT_DIR").unwrap();
let priv_key_bin_path = Path::new(&out_dir).join("opensk_pkey.bin");
let cert_bin_path = Path::new(&out_dir).join("opensk_cert.bin");
let aaguid_bin_path = Path::new(&out_dir).join("opensk_aaguid.bin");
// Load the OpenSSL PEM ECC key
let ecc_data = include_bytes!("crypto_data/opensk.key");
let pkey =
ec::EcKey::private_key_from_pem(ecc_data).expect("Failed to load OpenSK private key file");
// Check key validity
pkey.check_key().unwrap();
assert_eq!(pkey.group().curve_name(), Some(Nid::X9_62_PRIME256V1));
// Private keys generated by OpenSSL have variable size but we only handle
// constant size. Serialization is done in big endian so if the size is less
// than 32 bytes, we need to prepend with null bytes.
// If the size is 33 bytes, this means the serialized BigInt is negative.
// Any other size is invalid.
let priv_key_hex = pkey.private_key().to_hex_str().unwrap();
let priv_key_vec = pkey.private_key().to_vec();
let key_len = priv_key_vec.len();
assert!(
key_len <= 33,
"Invalid private key (too big): {} ({:#?})",
priv_key_hex,
priv_key_vec,
);
// Copy OpenSSL generated key to our vec, starting from the end
let mut output_vec = [0u8; 32];
let min_key_len = std::cmp::min(key_len, 32);
output_vec[32 - min_key_len..].copy_from_slice(&priv_key_vec[key_len - min_key_len..]);
// Create the raw private key out of the OpenSSL data
let mut priv_key_bin_file = File::create(&priv_key_bin_path).unwrap();
priv_key_bin_file.write_all(&output_vec).unwrap();
// Convert the PEM certificate to DER and extract the serial for AAGUID
let input_pem_cert = include_bytes!("crypto_data/opensk_cert.pem");
let cert = x509::X509::from_pem(input_pem_cert).expect("Failed to load OpenSK certificate");
// Do some sanity check on the certificate
assert!(cert
.public_key()
.unwrap()
.public_eq(&PKey::from_ec_key(pkey).unwrap()));
let now = asn1::Asn1Time::days_from_now(0).unwrap();
assert!(cert.not_after() > now);
assert!(cert.not_before() <= now);
let mut cert_bin_file = File::create(&cert_bin_path).unwrap();
cert_bin_file.write_all(&cert.to_der().unwrap()).unwrap();
let mut aaguid_bin_file = File::create(&aaguid_bin_path).unwrap();
let mut aaguid_txt_file = File::open("crypto_data/aaguid.txt").unwrap();
let mut content = String::new();
+53 -9
View File
@@ -710,6 +710,22 @@ class OpenSKInstaller:
check=False,
timeout=None,
).returncode
# Configure OpenSK through vendor specific command if needed
if any([
self.args.lock_device,
self.args.config_cert,
self.args.config_pkey,
]):
# pylint: disable=g-import-not-at-top,import-outside-toplevel
import tools.configure
tools.configure.main(
argparse.Namespace(
batch=False,
certificate=self.args.config_cert,
priv_key=self.args.config_pkey,
lock=self.args.lock_device,
))
return 0
@@ -770,6 +786,33 @@ if __name__ == "__main__":
help=("Erases the persistent storage when installing an application. "
"All stored data will be permanently lost."),
)
main_parser.add_argument(
"--lock-device",
action="store_true",
default=False,
dest="lock_device",
help=("Try to disable JTAG at the end of the operations. This "
"operation may fail if the device is already locked or if "
"the certificate/private key are not programmed."),
)
main_parser.add_argument(
"--inject-certificate",
default=None,
metavar="PEM_FILE",
type=argparse.FileType("rb"),
dest="config_cert",
help=("If this option is set, the corresponding certificate "
"will be programmed into the key as the last operation."),
)
main_parser.add_argument(
"--inject-private-key",
default=None,
metavar="PEM_FILE",
type=argparse.FileType("rb"),
dest="config_pkey",
help=("If this option is set, the corresponding private key "
"will be programmed into the key as the last operation."),
)
main_parser.add_argument(
"--programmer",
metavar="METHOD",
@@ -838,14 +881,6 @@ if __name__ == "__main__":
help=("Compiles the OpenSK application without backward compatible "
"support for U2F/CTAP1 protocol."),
)
main_parser.add_argument(
"--ctap2.1",
action="append_const",
const="with_ctap2_1",
dest="features",
help=("Compiles the OpenSK application with backward compatible "
"support for CTAP2.1 protocol."),
)
main_parser.add_argument(
"--nfc",
action="append_const",
@@ -904,7 +939,16 @@ if __name__ == "__main__":
dest="application",
action="store_const",
const="store_latency",
help=("Compiles and installs the store_latency example."))
help=("Compiles and installs the store_latency example which prints "
"latency statistics of the persistent store library."))
apps_group.add_argument(
"--erase_storage",
dest="application",
action="store_const",
const="erase_storage",
help=("Compiles and installs the erase_storage example which erases "
"the storage. During operation the dongle red light is on. Once "
"the operation is completed the dongle green light is on."))
apps_group.add_argument(
"--panic_test",
dest="application",
+7 -3
View File
@@ -17,6 +17,7 @@ You will need one the following supported boards:
* [Nordic nRF52840 Dongle](https://www.nordicsemi.com/Software-and-tools/Development-Kits/nRF52840-Dongle)
to have a more practical form factor.
* [Makerdiary nRF52840-MDK USB dongle](https://wiki.makerdiary.com/nrf52840-mdk/).
* [Feitian OpenSK dongle](https://feitiantech.github.io/OpenSK_USB/).
In the case of the Nordic USB dongle, you may also need the following extra
hardware:
@@ -125,6 +126,7 @@ This is the expected content after running our `setup.sh` script:
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
@@ -136,9 +138,11 @@ File | Purpose
If you want to use your own attestation certificate and private key, simply
replace `opensk_cert.pem` and `opensk.key` files.
Our build script `build.rs` is responsible for converting `opensk_cert.pem` and
`opensk.key` files into raw data that is then used by the Rust file:
`src/ctap/key_material.rs`.
Our build script `build.rs` is responsible for converting the `aaguid.txt` file
into raw data that is then used by the Rust file `src/ctap/key_material.rs`.
Our configuration script `tools/configure.py` is responsible for configuring
an OpenSK device with the correct certificate and private key.
### Flashing a firmware
+53
View File
@@ -0,0 +1,53 @@
// 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.
#![no_std]
extern crate lang_items;
use core::fmt::Write;
use ctap2::embedded_flash::new_storage;
use libtock_drivers::console::Console;
use libtock_drivers::led;
use libtock_drivers::result::FlexUnwrap;
use persistent_store::{Storage, StorageIndex};
fn is_page_erased(storage: &dyn Storage, page: usize) -> bool {
let index = StorageIndex { page, byte: 0 };
let length = storage.page_size();
storage
.read_slice(index, length)
.unwrap()
.iter()
.all(|&x| x == 0xff)
}
fn main() {
led::get(1).flex_unwrap().on().flex_unwrap(); // red on dongle
const NUM_PAGES: usize = 20; // should be at least ctap::storage::NUM_PAGES
let mut storage = new_storage(NUM_PAGES);
writeln!(Console::new(), "Erase {} pages of storage:", NUM_PAGES).unwrap();
for page in 0..NUM_PAGES {
write!(Console::new(), "- Page {} ", page).unwrap();
if is_page_erased(&storage, page) {
writeln!(Console::new(), "skipped (was already erased).").unwrap();
} else {
storage.erase_page(page).unwrap();
writeln!(Console::new(), "erased.").unwrap();
}
}
writeln!(Console::new(), "Done.").unwrap();
led::get(1).flex_unwrap().off().flex_unwrap();
led::get(0).flex_unwrap().on().flex_unwrap(); // green on dongle
}
+6 -2
View File
@@ -62,8 +62,10 @@ impl SecKey {
// - https://www.secg.org/sec1-v2.pdf
}
// DH key agreement method defined in the FIDO2 specification, Section 5.5.4. "Getting
// sharedSecret from Authenticator"
/// Creates a shared key using the Diffie Hellman key agreement.
///
/// The key agreement is defined in the FIDO2 specification,
/// Section 6.5.5.4. "Obtaining the Shared Secret"
pub fn exchange_x_sha256(&self, other: &PubKey) -> [u8; 32] {
let p = self.exchange_raw(other);
let mut x: [u8; 32] = [Default::default(); 32];
@@ -83,11 +85,13 @@ impl PubKey {
self.p.to_bytes_uncompressed(bytes);
}
/// Creates a new PubKey from its coordinates on the elliptic curve.
pub fn from_coordinates(x: &[u8; NBYTES], y: &[u8; NBYTES]) -> Option<PubKey> {
PointP256::new_checked_vartime(Int256::from_bin(x), Int256::from_bin(y))
.map(|p| PubKey { p })
}
/// Writes the coordinates into the passed in arrays.
pub fn to_coordinates(&self, x: &mut [u8; NBYTES], y: &mut [u8; NBYTES]) {
self.p.getx().to_int().to_bin(x);
self.p.gety().to_int().to_bin(y);
+20 -40
View File
@@ -21,12 +21,15 @@ use super::rng256::Rng256;
use super::{Hash256, HashBlockSize64Bytes};
use alloc::vec;
use alloc::vec::Vec;
#[cfg(test)]
use arrayref::array_mut_ref;
#[cfg(feature = "std")]
use arrayref::array_ref;
use arrayref::{array_mut_ref, mut_array_refs};
use cbor::{cbor_bytes, cbor_map_options};
use arrayref::mut_array_refs;
use core::marker::PhantomData;
pub const NBYTES: usize = int256::NBYTES;
#[derive(Clone, PartialEq)]
#[cfg_attr(feature = "derive_debug", derive(Debug))]
pub struct SecKey {
@@ -38,6 +41,7 @@ pub struct Signature {
s: NonZeroExponentP256,
}
#[cfg_attr(feature = "derive_debug", derive(Clone))]
pub struct PubKey {
p: PointP256,
}
@@ -58,10 +62,11 @@ impl SecKey {
}
}
// ECDSA signature based on a RNG to generate a suitable randomization parameter.
// Under the hood, rejection sampling is used to make sure that the randomization parameter is
// uniformly distributed.
// The provided RNG must be cryptographically secure; otherwise this method is insecure.
/// Creates an ECDSA signature based on a RNG.
///
/// Under the hood, rejection sampling is used to make sure that the
/// randomization parameter is uniformly distributed. The provided RNG must
/// be cryptographically secure; otherwise this method is insecure.
pub fn sign_rng<H, R>(&self, msg: &[u8], rng: &mut R) -> Signature
where
H: Hash256,
@@ -77,8 +82,7 @@ impl SecKey {
}
}
// Deterministic ECDSA signature based on RFC 6979 to generate a suitable randomization
// parameter.
/// Creates a deterministic ECDSA signature based on RFC 6979.
pub fn sign_rfc6979<H>(&self, msg: &[u8]) -> Signature
where
H: Hash256 + HashBlockSize64Bytes,
@@ -101,8 +105,10 @@ impl SecKey {
}
}
// Try signing a curve element given a randomization parameter k. If no signature can be
// obtained from this k, None is returned and the caller should try again with another value.
/// Try signing a curve element given a randomization parameter k.
///
/// If no signature can be obtained from this k, None is returned and the
/// caller should try again with another value.
fn try_sign(&self, k: &NonZeroExponentP256, msg: &ExponentP256) -> Option<Signature> {
let r = ExponentP256::modn(PointP256::base_point_mul(k.as_exponent()).getx().to_int());
// The branching here is fine because all this reveals is that k generated an unsuitable r.
@@ -214,7 +220,6 @@ impl Signature {
}
impl PubKey {
pub const ES256_ALGORITHM: i64 = -7;
#[cfg(feature = "with_ctap1")]
const UNCOMPRESSED_LENGTH: usize = 1 + 2 * int256::NBYTES;
@@ -242,35 +247,10 @@ impl PubKey {
representation
}
// Encodes the key according to CBOR Object Signing and Encryption, defined in RFC 8152.
pub fn to_cose_key(&self) -> Option<Vec<u8>> {
const EC2_KEY_TYPE: i64 = 2;
const P_256_CURVE: i64 = 1;
let mut x_bytes = vec![0; int256::NBYTES];
self.p
.getx()
.to_int()
.to_bin(array_mut_ref![x_bytes.as_mut_slice(), 0, int256::NBYTES]);
let x_byte_cbor: cbor::Value = cbor_bytes!(x_bytes);
let mut y_bytes = vec![0; int256::NBYTES];
self.p
.gety()
.to_int()
.to_bin(array_mut_ref![y_bytes.as_mut_slice(), 0, int256::NBYTES]);
let y_byte_cbor: cbor::Value = cbor_bytes!(y_bytes);
let cbor_value = cbor_map_options! {
1 => EC2_KEY_TYPE,
3 => PubKey::ES256_ALGORITHM,
-1 => P_256_CURVE,
-2 => x_byte_cbor,
-3 => y_byte_cbor,
};
let mut encoded_key = Vec::new();
if cbor::write(cbor_value, &mut encoded_key) {
Some(encoded_key)
} else {
None
}
/// Writes the coordinates into the passed in arrays.
pub fn to_coordinates(&self, x: &mut [u8; NBYTES], y: &mut [u8; NBYTES]) {
self.p.getx().to_int().to_bin(x);
self.p.gety().to_int().to_bin(y);
}
#[cfg(feature = "std")]
+2 -2
View File
@@ -117,7 +117,7 @@ index d72d20482..118ea6d68 100644
+ // Product
+ "OpenSK",
+ // Serial number
+ "v0.1",
+ "v1.0",
+];
+
// State for loading and holding applications.
@@ -189,7 +189,7 @@ index 2ebb384d8..4a7bfffdd 100644
+ // Product
+ "OpenSK",
+ // Serial number
+ "v0.1",
+ "v1.0",
+];
+
// State for loading and holding applications.
+100
View File
@@ -0,0 +1,100 @@
diff --git a/chips/nrf52/src/uicr.rs b/chips/nrf52/src/uicr.rs
index 6bb6c86..3bb8b5a 100644
--- a/chips/nrf52/src/uicr.rs
+++ b/chips/nrf52/src/uicr.rs
@@ -1,38 +1,45 @@
//! User information configuration registers
-//!
-//! Minimal implementation to support activation of the reset button on
-//! nRF52-DK.
+
use enum_primitive::cast::FromPrimitive;
-use kernel::common::registers::{register_bitfields, ReadWrite};
+use kernel::common::registers::{register_bitfields, register_structs, ReadWrite};
use kernel::common::StaticRef;
+use kernel::hil;
+use kernel::ReturnCode;
use crate::gpio::Pin;
const UICR_BASE: StaticRef<UicrRegisters> =
- unsafe { StaticRef::new(0x10001200 as *const UicrRegisters) };
-
-#[repr(C)]
-struct UicrRegisters {
- /// Mapping of the nRESET function (see POWER chapter for details)
- /// - Address: 0x200 - 0x204
- pselreset0: ReadWrite<u32, Pselreset::Register>,
- /// Mapping of the nRESET function (see POWER chapter for details)
- /// - Address: 0x204 - 0x208
- pselreset1: ReadWrite<u32, Pselreset::Register>,
- /// Access Port protection
- /// - Address: 0x208 - 0x20c
- approtect: ReadWrite<u32, ApProtect::Register>,
- /// Setting of pins dedicated to NFC functionality: NFC antenna or GPIO
- /// - Address: 0x20c - 0x210
- nfcpins: ReadWrite<u32, NfcPins::Register>,
- _reserved1: [u32; 60],
- /// External circuitry to be supplied from VDD pin.
- /// - Address: 0x300 - 0x304
- extsupply: ReadWrite<u32, ExtSupply::Register>,
- /// GPIO reference voltage
- /// - Address: 0x304 - 0x308
- regout0: ReadWrite<u32, RegOut::Register>,
+ unsafe { StaticRef::new(0x10001000 as *const UicrRegisters) };
+
+register_structs! {
+ UicrRegisters {
+ (0x000 => _reserved1),
+ /// Reserved for Nordic firmware design
+ (0x014 => nrffw: [ReadWrite<u32>; 13]),
+ (0x048 => _reserved2),
+ /// Reserved for Nordic hardware design
+ (0x050 => nrfhw: [ReadWrite<u32>; 12]),
+ /// Reserved for customer
+ (0x080 => customer: [ReadWrite<u32>; 32]),
+ (0x100 => _reserved3),
+ /// Mapping of the nRESET function (see POWER chapter for details)
+ (0x200 => pselreset0: ReadWrite<u32, Pselreset::Register>),
+ /// Mapping of the nRESET function (see POWER chapter for details)
+ (0x204 => pselreset1: ReadWrite<u32, Pselreset::Register>),
+ /// Access Port protection
+ (0x208 => approtect: ReadWrite<u32, ApProtect::Register>),
+ /// Setting of pins dedicated to NFC functionality: NFC antenna or GPIO
+ /// - Address: 0x20c - 0x210
+ (0x20c => nfcpins: ReadWrite<u32, NfcPins::Register>),
+ (0x210 => debugctrl: ReadWrite<u32, DebugControl::Register>),
+ (0x214 => _reserved4),
+ /// External circuitry to be supplied from VDD pin.
+ (0x300 => extsupply: ReadWrite<u32, ExtSupply::Register>),
+ /// GPIO reference voltage
+ (0x304 => regout0: ReadWrite<u32, RegOut::Register>),
+ (0x308 => @END),
+ }
}
register_bitfields! [u32,
@@ -58,6 +65,21 @@ register_bitfields! [u32,
DISABLED = 0xff
]
],
+ /// Processor debug control
+ DebugControl [
+ CPUNIDEN OFFSET(0) NUMBITS(8) [
+ /// Enable
+ ENABLED = 0xff,
+ /// Disable
+ DISABLED = 0x00
+ ],
+ CPUFPBEN OFFSET(8) NUMBITS(8) [
+ /// Enable
+ ENABLED = 0xff,
+ /// Disable
+ DISABLED = 0x00
+ ]
+ ],
/// Setting of pins dedicated to NFC functionality: NFC antenna or GPIO
NfcPins [
/// Setting pins dedicated to NFC functionality
+426
View File
@@ -0,0 +1,426 @@
diff --git a/boards/components/src/firmware_protection.rs b/boards/components/src/firmware_protection.rs
new file mode 100644
index 0000000..58695af
--- /dev/null
+++ b/boards/components/src/firmware_protection.rs
@@ -0,0 +1,70 @@
+//! Component for firmware protection syscall interface.
+//!
+//! This provides one Component, `FirmwareProtectionComponent`, which implements a
+//! userspace syscall interface to enable the code readout protection.
+//!
+//! Usage
+//! -----
+//! ```rust
+//! let crp = components::firmware_protection::FirmwareProtectionComponent::new(
+//! board_kernel,
+//! nrf52840::uicr::Uicr::new()
+//! )
+//! .finalize(
+//! components::firmware_protection_component_helper!(uicr));
+//! ```
+
+use core::mem::MaybeUninit;
+
+use capsules::firmware_protection;
+use kernel::capabilities;
+use kernel::component::Component;
+use kernel::create_capability;
+use kernel::hil;
+use kernel::static_init_half;
+
+// Setup static space for the objects.
+#[macro_export]
+macro_rules! firmware_protection_component_helper {
+ ($C:ty) => {{
+ use capsules::firmware_protection;
+ use core::mem::MaybeUninit;
+ static mut BUF: MaybeUninit<firmware_protection::FirmwareProtection<$C>> =
+ MaybeUninit::uninit();
+ &mut BUF
+ };};
+}
+
+pub struct FirmwareProtectionComponent<C: hil::firmware_protection::FirmwareProtection> {
+ board_kernel: &'static kernel::Kernel,
+ crp: C,
+}
+
+impl<C: 'static + hil::firmware_protection::FirmwareProtection> FirmwareProtectionComponent<C> {
+ pub fn new(board_kernel: &'static kernel::Kernel, crp: C) -> FirmwareProtectionComponent<C> {
+ FirmwareProtectionComponent {
+ board_kernel: board_kernel,
+ crp: crp,
+ }
+ }
+}
+
+impl<C: 'static + hil::firmware_protection::FirmwareProtection> Component
+ for FirmwareProtectionComponent<C>
+{
+ type StaticInput = &'static mut MaybeUninit<firmware_protection::FirmwareProtection<C>>;
+ type Output = &'static firmware_protection::FirmwareProtection<C>;
+
+ unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output {
+ let grant_cap = create_capability!(capabilities::MemoryAllocationCapability);
+
+ static_init_half!(
+ static_buffer,
+ firmware_protection::FirmwareProtection<C>,
+ firmware_protection::FirmwareProtection::new(
+ self.crp,
+ self.board_kernel.create_grant(&grant_cap),
+ )
+ )
+ }
+}
diff --git a/boards/components/src/lib.rs b/boards/components/src/lib.rs
index 917497a..520408f 100644
--- a/boards/components/src/lib.rs
+++ b/boards/components/src/lib.rs
@@ -9,6 +9,7 @@ pub mod console;
pub mod crc;
pub mod debug_queue;
pub mod debug_writer;
+pub mod firmware_protection;
pub mod ft6x06;
pub mod gpio;
pub mod hd44780;
diff --git a/boards/nordic/nrf52840_dongle/src/main.rs b/boards/nordic/nrf52840_dongle/src/main.rs
index 118ea6d..76436f3 100644
--- a/boards/nordic/nrf52840_dongle/src/main.rs
+++ b/boards/nordic/nrf52840_dongle/src/main.rs
@@ -112,6 +112,7 @@ pub struct Platform {
'static,
nrf52840::usbd::Usbd<'static>,
>,
+ crp: &'static capsules::firmware_protection::FirmwareProtection<nrf52840::uicr::Uicr>,
}
impl kernel::Platform for Platform {
@@ -132,6 +133,7 @@ impl kernel::Platform for Platform {
capsules::analog_comparator::DRIVER_NUM => f(Some(self.analog_comparator)),
nrf52840::nvmc::DRIVER_NUM => f(Some(self.nvmc)),
capsules::usb::usb_ctap::DRIVER_NUM => f(Some(self.usb)),
+ capsules::firmware_protection::DRIVER_NUM => f(Some(self.crp)),
kernel::ipc::DRIVER_NUM => f(Some(&self.ipc)),
_ => f(None),
}
@@ -355,6 +357,14 @@ pub unsafe fn reset_handler() {
)
.finalize(components::usb_ctap_component_buf!(nrf52840::usbd::Usbd));
+ let crp = components::firmware_protection::FirmwareProtectionComponent::new(
+ board_kernel,
+ nrf52840::uicr::Uicr::new(),
+ )
+ .finalize(components::firmware_protection_component_helper!(
+ nrf52840::uicr::Uicr
+ ));
+
nrf52_components::NrfClockComponent::new().finalize(());
let platform = Platform {
@@ -371,6 +381,7 @@ pub unsafe fn reset_handler() {
analog_comparator,
nvmc,
usb,
+ crp,
ipc: kernel::ipc::IPC::new(board_kernel, &memory_allocation_capability),
};
diff --git a/boards/nordic/nrf52840dk/src/main.rs b/boards/nordic/nrf52840dk/src/main.rs
index b1d0d3c..3cfb38d 100644
--- a/boards/nordic/nrf52840dk/src/main.rs
+++ b/boards/nordic/nrf52840dk/src/main.rs
@@ -180,6 +180,7 @@ pub struct Platform {
'static,
nrf52840::usbd::Usbd<'static>,
>,
+ crp: &'static capsules::firmware_protection::FirmwareProtection<nrf52840::uicr::Uicr>,
}
impl kernel::Platform for Platform {
@@ -201,6 +202,7 @@ impl kernel::Platform for Platform {
capsules::nonvolatile_storage_driver::DRIVER_NUM => f(Some(self.nonvolatile_storage)),
nrf52840::nvmc::DRIVER_NUM => f(Some(self.nvmc)),
capsules::usb::usb_ctap::DRIVER_NUM => f(Some(self.usb)),
+ capsules::firmware_protection::DRIVER_NUM => f(Some(self.crp)),
kernel::ipc::DRIVER_NUM => f(Some(&self.ipc)),
_ => f(None),
}
@@ -480,6 +482,14 @@ pub unsafe fn reset_handler() {
)
.finalize(components::usb_ctap_component_buf!(nrf52840::usbd::Usbd));
+ let crp = components::firmware_protection::FirmwareProtectionComponent::new(
+ board_kernel,
+ nrf52840::uicr::Uicr::new(),
+ )
+ .finalize(components::firmware_protection_component_helper!(
+ nrf52840::uicr::Uicr
+ ));
+
nrf52_components::NrfClockComponent::new().finalize(());
let platform = Platform {
@@ -497,6 +507,7 @@ pub unsafe fn reset_handler() {
nonvolatile_storage,
nvmc,
usb,
+ crp,
ipc: kernel::ipc::IPC::new(board_kernel, &memory_allocation_capability),
};
diff --git a/capsules/src/driver.rs b/capsules/src/driver.rs
index ae458b3..f536dad 100644
--- a/capsules/src/driver.rs
+++ b/capsules/src/driver.rs
@@ -16,6 +16,7 @@ pub enum NUM {
Adc = 0x00005,
Dac = 0x00006,
AnalogComparator = 0x00007,
+ FirmwareProtection = 0x00008,
// Kernel
Ipc = 0x10000,
diff --git a/capsules/src/firmware_protection.rs b/capsules/src/firmware_protection.rs
new file mode 100644
index 0000000..8cf63d6
--- /dev/null
+++ b/capsules/src/firmware_protection.rs
@@ -0,0 +1,85 @@
+//! Provides userspace control of firmware protection on a board.
+//!
+//! This allows an application to enable firware readout protection,
+//! disabling JTAG interface and other ways to read/tamper the firmware.
+//! Of course, outside of a hardware bug, once set, the only way to enable
+//! programming/debugging is by fully erasing the flash.
+//!
+//! Usage
+//! -----
+//!
+//! ```rust
+//! # use kernel::static_init;
+//!
+//! let crp = static_init!(
+//! capsules::firmware_protection::FirmwareProtection,
+//! capsules::firmware_protection::FirmwareProtection::new(
+//! nrf52840::uicr::Uicr,
+//! board_kernel.create_grant(&grant_cap),
+//! );
+//! ```
+//!
+//! Syscall Interface
+//! -----------------
+//!
+//! - Stability: 0 - Draft
+//!
+//! ### Command
+//!
+//! Enable code readout protection on the current board.
+//!
+//! #### `command_num`
+//!
+//! - `0`: Driver check.
+//! - `1`: Get current firmware readout protection (aka CRP) state.
+//! - `2`: Set current firmware readout protection (aka CRP) state.
+//!
+
+use kernel::hil;
+use kernel::{AppId, Callback, Driver, Grant, ReturnCode};
+
+/// Syscall driver number.
+use crate::driver;
+pub const DRIVER_NUM: usize = driver::NUM::FirmwareProtection as usize;
+
+pub struct FirmwareProtection<C: hil::firmware_protection::FirmwareProtection> {
+ crp_unit: C,
+ apps: Grant<Option<Callback>>,
+}
+
+impl<C: hil::firmware_protection::FirmwareProtection> FirmwareProtection<C> {
+ pub fn new(crp_unit: C, apps: Grant<Option<Callback>>) -> Self {
+ Self { crp_unit, apps }
+ }
+}
+
+impl<C: hil::firmware_protection::FirmwareProtection> Driver for FirmwareProtection<C> {
+ ///
+ /// ### Command numbers
+ ///
+ /// * `0`: Returns non-zero to indicate the driver is present.
+ /// * `1`: Gets firmware protection state.
+ /// * `2`: Sets firmware protection state.
+ fn command(&self, command_num: usize, data: usize, _: usize, appid: AppId) -> ReturnCode {
+ match command_num {
+ // return if driver is available
+ 0 => ReturnCode::SUCCESS,
+
+ 1 => self
+ .apps
+ .enter(appid, |_, _| ReturnCode::SuccessWithValue {
+ value: self.crp_unit.get_protection() as usize,
+ })
+ .unwrap_or_else(|err| err.into()),
+
+ // sets firmware protection
+ 2 => self
+ .apps
+ .enter(appid, |_, _| self.crp_unit.set_protection(data.into()))
+ .unwrap_or_else(|err| err.into()),
+
+ // default
+ _ => ReturnCode::ENOSUPPORT,
+ }
+ }
+}
diff --git a/capsules/src/lib.rs b/capsules/src/lib.rs
index e4423fe..7538aad 100644
--- a/capsules/src/lib.rs
+++ b/capsules/src/lib.rs
@@ -22,6 +22,7 @@ pub mod crc;
pub mod dac;
pub mod debug_process_restart;
pub mod driver;
+pub mod firmware_protection;
pub mod fm25cl;
pub mod ft6x06;
pub mod fxos8700cq;
diff --git a/chips/nrf52/src/uicr.rs b/chips/nrf52/src/uicr.rs
index 3bb8b5a..ea96cb2 100644
--- a/chips/nrf52/src/uicr.rs
+++ b/chips/nrf52/src/uicr.rs
@@ -1,13 +1,14 @@
//! User information configuration registers
-
use enum_primitive::cast::FromPrimitive;
+use hil::firmware_protection::ProtectionLevel;
use kernel::common::registers::{register_bitfields, register_structs, ReadWrite};
use kernel::common::StaticRef;
use kernel::hil;
use kernel::ReturnCode;
use crate::gpio::Pin;
+use crate::nvmc::NVMC;
const UICR_BASE: StaticRef<UicrRegisters> =
unsafe { StaticRef::new(0x10001000 as *const UicrRegisters) };
@@ -210,3 +211,49 @@ impl Uicr {
self.registers.approtect.write(ApProtect::PALL::ENABLED);
}
}
+
+impl hil::firmware_protection::FirmwareProtection for Uicr {
+ fn get_protection(&self) -> ProtectionLevel {
+ let ap_protect_state = self.is_ap_protect_enabled();
+ let cpu_debug_state = self
+ .registers
+ .debugctrl
+ .matches_all(DebugControl::CPUNIDEN::ENABLED + DebugControl::CPUFPBEN::ENABLED);
+ match (ap_protect_state, cpu_debug_state) {
+ (false, _) => ProtectionLevel::NoProtection,
+ (true, true) => ProtectionLevel::JtagDisabled,
+ (true, false) => ProtectionLevel::FullyLocked,
+ }
+ }
+
+ fn set_protection(&self, level: ProtectionLevel) -> ReturnCode {
+ let current_level = self.get_protection();
+ if current_level > level || level == ProtectionLevel::Unknown {
+ return ReturnCode::EINVAL;
+ }
+ if current_level == level {
+ return ReturnCode::EALREADY;
+ }
+
+ unsafe { NVMC.configure_writeable() };
+ if level >= ProtectionLevel::JtagDisabled {
+ self.set_ap_protect();
+ }
+
+ if level >= ProtectionLevel::FullyLocked {
+ // Prevent CPU debug and flash patching. Leaving these enabled could
+ // allow to circumvent protection.
+ self.registers
+ .debugctrl
+ .write(DebugControl::CPUNIDEN::DISABLED + DebugControl::CPUFPBEN::DISABLED);
+ // TODO(jmichel): prevent returning into bootloader if present
+ }
+ unsafe { NVMC.configure_readonly() };
+
+ if self.get_protection() == level {
+ ReturnCode::SUCCESS
+ } else {
+ ReturnCode::FAIL
+ }
+ }
+}
diff --git a/kernel/src/hil/firmware_protection.rs b/kernel/src/hil/firmware_protection.rs
new file mode 100644
index 0000000..de08246
--- /dev/null
+++ b/kernel/src/hil/firmware_protection.rs
@@ -0,0 +1,48 @@
+//! Interface for Firmware Protection, also called Code Readout Protection.
+
+use crate::returncode::ReturnCode;
+
+#[derive(PartialOrd, PartialEq)]
+pub enum ProtectionLevel {
+ /// Unsupported feature
+ Unknown = 0,
+ /// This should be the factory default for the chip.
+ NoProtection = 1,
+ /// At this level, only JTAG/SWD are disabled but other debugging
+ /// features may still be enabled.
+ JtagDisabled = 2,
+ /// This is the maximum level of protection the chip supports.
+ /// At this level, JTAG and all other features are expected to be
+ /// disabled and only a full chip erase may allow to recover from
+ /// that state.
+ FullyLocked = 0xff,
+}
+
+impl From<usize> for ProtectionLevel {
+ fn from(value: usize) -> Self {
+ match value {
+ 1 => ProtectionLevel::NoProtection,
+ 2 => ProtectionLevel::JtagDisabled,
+ 0xff => ProtectionLevel::FullyLocked,
+ _ => ProtectionLevel::Unknown,
+ }
+ }
+}
+
+pub trait FirmwareProtection {
+ /// Gets the current firmware protection level.
+ /// This doesn't fail and always returns a value.
+ fn get_protection(&self) -> ProtectionLevel;
+
+ /// Sets the firmware protection level.
+ /// There are four valid return values:
+ /// - SUCCESS: protection level has been set to `level`
+ /// - FAIL: something went wrong while setting the protection
+ /// level and the effective protection level is not the one
+ /// that was requested.
+ /// - EALREADY: the requested protection level is already the
+ /// level that is set.
+ /// - EINVAL: unsupported protection level or the requested
+ /// protection level is lower than the currently set one.
+ fn set_protection(&self, level: ProtectionLevel) -> ReturnCode;
+}
diff --git a/kernel/src/hil/mod.rs b/kernel/src/hil/mod.rs
index 4f42afa..83e7702 100644
--- a/kernel/src/hil/mod.rs
+++ b/kernel/src/hil/mod.rs
@@ -8,6 +8,7 @@ pub mod dac;
pub mod digest;
pub mod eic;
pub mod entropy;
+pub mod firmware_protection;
pub mod flash;
pub mod gpio;
pub mod gpio_async;
-13
View File
@@ -44,7 +44,6 @@ cargo test --manifest-path tools/heapviz/Cargo.toml
echo "Checking that CTAP2 builds properly..."
cargo check --release --target=thumbv7em-none-eabi
cargo check --release --target=thumbv7em-none-eabi --features with_ctap1
cargo check --release --target=thumbv7em-none-eabi --features with_ctap2_1
cargo check --release --target=thumbv7em-none-eabi --features debug_ctap
cargo check --release --target=thumbv7em-none-eabi --features panic_console
cargo check --release --target=thumbv7em-none-eabi --features debug_allocations
@@ -116,16 +115,4 @@ then
echo "Running unit tests on the desktop (debug mode + CTAP1)..."
cargo test --features std,with_ctap1
echo "Running unit tests on the desktop (release mode + CTAP2.1)..."
cargo test --release --features std,with_ctap2_1
echo "Running unit tests on the desktop (debug mode + CTAP2.1)..."
cargo test --features std,with_ctap2_1
echo "Running unit tests on the desktop (release mode + CTAP1 + CTAP2.1)..."
cargo test --release --features std,with_ctap1,with_ctap2_1
echo "Running unit tests on the desktop (debug mode + CTAP1 + CTAP2.1)..."
cargo test --features std,with_ctap1,with_ctap2_1
fi
+3
View File
@@ -44,3 +44,6 @@ rustup target add thumbv7em-none-eabi
# Install dependency to create applications.
mkdir -p elf2tab
cargo install elf2tab --version 0.6.0 --root elf2tab/
# Install python dependencies to factory configure OpenSK (crypto, JTAG lockdown)
pip3 install --user --upgrade colorama tqdm cryptography fido2
+38 -43
View File
@@ -1,4 +1,4 @@
// Copyright 2020 Google LLC
// Copyright 2020-2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -16,43 +16,31 @@ use alloc::vec::Vec;
use byteorder::{BigEndian, ByteOrder};
use core::convert::TryFrom;
type ByteArray = &'static [u8];
const APDU_HEADER_LEN: usize = 4;
#[cfg_attr(test, derive(Clone, Debug))]
#[allow(non_camel_case_types)]
#[allow(non_camel_case_types, dead_code)]
#[derive(PartialEq)]
pub enum ApduStatusCode {
SW_SUCCESS,
SW_SUCCESS = 0x90_00,
/// Command successfully executed; 'XX' bytes of data are
/// available and can be requested using GET RESPONSE.
SW_GET_RESPONSE,
SW_WRONG_DATA,
SW_WRONG_LENGTH,
SW_COND_USE_NOT_SATISFIED,
SW_FILE_NOT_FOUND,
SW_INCORRECT_P1P2,
SW_GET_RESPONSE = 0x61_00,
SW_MEMERR = 0x65_01,
SW_WRONG_DATA = 0x6a_80,
SW_WRONG_LENGTH = 0x67_00,
SW_COND_USE_NOT_SATISFIED = 0x69_85,
SW_FILE_NOT_FOUND = 0x6a_82,
SW_INCORRECT_P1P2 = 0x6a_86,
/// Instruction code not supported or invalid
SW_INS_INVALID,
SW_CLA_INVALID,
SW_INTERNAL_EXCEPTION,
SW_INS_INVALID = 0x6d_00,
SW_CLA_INVALID = 0x6e_00,
SW_INTERNAL_EXCEPTION = 0x6f_00,
}
impl From<ApduStatusCode> for ByteArray {
fn from(status_code: ApduStatusCode) -> ByteArray {
match status_code {
ApduStatusCode::SW_SUCCESS => b"\x90\x00",
ApduStatusCode::SW_GET_RESPONSE => b"\x61\x00",
ApduStatusCode::SW_WRONG_DATA => b"\x6A\x80",
ApduStatusCode::SW_WRONG_LENGTH => b"\x67\x00",
ApduStatusCode::SW_COND_USE_NOT_SATISFIED => b"\x69\x85",
ApduStatusCode::SW_FILE_NOT_FOUND => b"\x6a\x82",
ApduStatusCode::SW_INCORRECT_P1P2 => b"\x6a\x86",
ApduStatusCode::SW_INS_INVALID => b"\x6d\x00",
ApduStatusCode::SW_CLA_INVALID => b"\x6e\x00",
ApduStatusCode::SW_INTERNAL_EXCEPTION => b"\x6f\x00",
}
impl From<ApduStatusCode> for u16 {
fn from(code: ApduStatusCode) -> Self {
code as u16
}
}
@@ -67,10 +55,10 @@ pub enum ApduInstructions {
#[allow(dead_code)]
#[derive(Default, PartialEq)]
pub struct ApduHeader {
cla: u8,
ins: u8,
p1: u8,
p2: u8,
pub cla: u8,
pub ins: u8,
pub p1: u8,
pub p2: u8,
}
impl From<&[u8; APDU_HEADER_LEN]> for ApduHeader {
@@ -110,11 +98,11 @@ pub enum ApduType {
#[allow(dead_code)]
#[derive(PartialEq)]
pub struct APDU {
header: ApduHeader,
lc: u16,
data: Vec<u8>,
le: u32,
case_type: ApduType,
pub header: ApduHeader,
pub lc: u16,
pub data: Vec<u8>,
pub le: u32,
pub case_type: ApduType,
}
impl TryFrom<&[u8]> for APDU {
@@ -182,12 +170,19 @@ impl TryFrom<&[u8]> for APDU {
}
if payload.len() > 2 {
// Lc is possibly three-bytes long
let extended_apdu_lc: usize = BigEndian::read_u16(&payload[1..]) as usize;
let extended_apdu_le_len: usize = if payload.len() > extended_apdu_lc {
payload.len() - extended_apdu_lc - 3
} else {
0
};
let extended_apdu_lc = BigEndian::read_u16(&payload[1..3]) as usize;
if payload.len() < extended_apdu_lc + 3 {
return Err(ApduStatusCode::SW_WRONG_LENGTH);
}
let extended_apdu_le_len: usize = payload
.len()
.checked_sub(extended_apdu_lc + 3)
.ok_or(ApduStatusCode::SW_WRONG_LENGTH)?;
if extended_apdu_le_len > 3 {
return Err(ApduStatusCode::SW_WRONG_LENGTH);
}
if byte_0 == 0 && extended_apdu_le_len <= 3 {
// If first byte is zero AND the next two bytes can be parsed as a big-endian
// length that covers the rest of the block (plus few additional bytes for Le), we
+214 -82
View File
@@ -1,4 +1,4 @@
// Copyright 2019 Google LLC
// Copyright 2019-2021 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,14 +13,17 @@
// limitations under the License.
use super::data_formats::{
extract_array, extract_byte_string, extract_map, extract_text_string, extract_unsigned,
ok_or_missing, ClientPinSubCommand, CoseKey, GetAssertionExtensions, GetAssertionOptions,
MakeCredentialExtensions, MakeCredentialOptions, PublicKeyCredentialDescriptor,
PublicKeyCredentialParameter, PublicKeyCredentialRpEntity, PublicKeyCredentialUserEntity,
extract_array, extract_bool, extract_byte_string, extract_map, extract_text_string,
extract_unsigned, ok_or_missing, ClientPinSubCommand, ConfigSubCommand, ConfigSubCommandParams,
CoseKey, GetAssertionExtensions, GetAssertionOptions, MakeCredentialExtensions,
MakeCredentialOptions, PublicKeyCredentialDescriptor, PublicKeyCredentialParameter,
PublicKeyCredentialRpEntity, PublicKeyCredentialUserEntity, SetMinPinLengthParams,
};
use super::key_material;
use super::status_code::Ctap2StatusCode;
use alloc::string::String;
use alloc::vec::Vec;
use arrayref::array_ref;
use cbor::destructure_cbor_map;
use core::convert::TryFrom;
@@ -38,9 +41,11 @@ pub enum Command {
AuthenticatorClientPin(AuthenticatorClientPinParameters),
AuthenticatorReset,
AuthenticatorGetNextAssertion,
#[cfg(feature = "with_ctap2_1")]
AuthenticatorSelection,
AuthenticatorConfig(AuthenticatorConfigParameters),
// TODO(kaczmarczyck) implement FIDO 2.1 commands (see below consts)
// Vendor specific commands
AuthenticatorVendorConfigure(AuthenticatorVendorConfigureParameters),
}
impl From<cbor::reader::DecoderError> for Ctap2StatusCode {
@@ -60,11 +65,13 @@ impl Command {
const AUTHENTICATOR_GET_NEXT_ASSERTION: u8 = 0x08;
// TODO(kaczmarczyck) use or remove those constants
const AUTHENTICATOR_BIO_ENROLLMENT: u8 = 0x09;
const AUTHENTICATOR_CREDENTIAL_MANAGEMENT: u8 = 0xA0;
const AUTHENTICATOR_SELECTION: u8 = 0xB0;
const AUTHENTICATOR_CONFIG: u8 = 0xC0;
const AUTHENTICATOR_VENDOR_FIRST: u8 = 0x40;
const AUTHENTICATOR_VENDOR_LAST: u8 = 0xBF;
const AUTHENTICATOR_CREDENTIAL_MANAGEMENT: u8 = 0x0A;
const AUTHENTICATOR_SELECTION: u8 = 0x0B;
const AUTHENTICATOR_LARGE_BLOBS: u8 = 0x0C;
const AUTHENTICATOR_CONFIG: u8 = 0x0D;
const _AUTHENTICATOR_VENDOR_FIRST: u8 = 0x40;
const AUTHENTICATOR_VENDOR_CONFIGURE: u8 = 0x40;
const _AUTHENTICATOR_VENDOR_LAST: u8 = 0xBF;
pub fn deserialize(bytes: &[u8]) -> Result<Command, Ctap2StatusCode> {
if bytes.is_empty() {
@@ -104,11 +111,22 @@ impl Command {
// Parameters are ignored.
Ok(Command::AuthenticatorGetNextAssertion)
}
#[cfg(feature = "with_ctap2_1")]
Command::AUTHENTICATOR_SELECTION => {
// Parameters are ignored.
Ok(Command::AuthenticatorSelection)
}
Command::AUTHENTICATOR_CONFIG => {
let decoded_cbor = cbor::read(&bytes[1..])?;
Ok(Command::AuthenticatorConfig(
AuthenticatorConfigParameters::try_from(decoded_cbor)?,
))
}
Command::AUTHENTICATOR_VENDOR_CONFIGURE => {
let decoded_cbor = cbor::read(&bytes[1..])?;
Ok(Command::AuthenticatorVendorConfigure(
AuthenticatorVendorConfigureParameters::try_from(decoded_cbor)?,
))
}
_ => Err(Ctap2StatusCode::CTAP1_ERR_INVALID_COMMAND),
}
}
@@ -279,13 +297,7 @@ pub struct AuthenticatorClientPinParameters {
pub pin_auth: Option<Vec<u8>>,
pub new_pin_enc: Option<Vec<u8>>,
pub pin_hash_enc: Option<Vec<u8>>,
#[cfg(feature = "with_ctap2_1")]
pub min_pin_length: Option<u8>,
#[cfg(feature = "with_ctap2_1")]
pub min_pin_length_rp_ids: Option<Vec<String>>,
#[cfg(feature = "with_ctap2_1")]
pub permissions: Option<u8>,
#[cfg(feature = "with_ctap2_1")]
pub permissions_rp_id: Option<String>,
}
@@ -293,7 +305,6 @@ impl TryFrom<cbor::Value> for AuthenticatorClientPinParameters {
type Error = Ctap2StatusCode;
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
#[cfg(not(feature = "with_ctap2_1"))]
destructure_cbor_map! {
let {
1 => pin_protocol,
@@ -302,19 +313,6 @@ impl TryFrom<cbor::Value> for AuthenticatorClientPinParameters {
4 => pin_auth,
5 => new_pin_enc,
6 => pin_hash_enc,
} = extract_map(cbor_value)?;
}
#[cfg(feature = "with_ctap2_1")]
destructure_cbor_map! {
let {
1 => pin_protocol,
2 => sub_command,
3 => key_agreement,
4 => pin_auth,
5 => new_pin_enc,
6 => pin_hash_enc,
7 => min_pin_length,
8 => min_pin_length_rp_ids,
9 => permissions,
10 => permissions_rp_id,
} = extract_map(cbor_value)?;
@@ -322,35 +320,16 @@ impl TryFrom<cbor::Value> for AuthenticatorClientPinParameters {
let pin_protocol = extract_unsigned(ok_or_missing(pin_protocol)?)?;
let sub_command = ClientPinSubCommand::try_from(ok_or_missing(sub_command)?)?;
let key_agreement = key_agreement.map(extract_map).transpose()?.map(CoseKey);
let key_agreement = key_agreement.map(CoseKey::try_from).transpose()?;
let pin_auth = pin_auth.map(extract_byte_string).transpose()?;
let new_pin_enc = new_pin_enc.map(extract_byte_string).transpose()?;
let pin_hash_enc = pin_hash_enc.map(extract_byte_string).transpose()?;
#[cfg(feature = "with_ctap2_1")]
let min_pin_length = min_pin_length
.map(extract_unsigned)
.transpose()?
.map(u8::try_from)
.transpose()
.map_err(|_| Ctap2StatusCode::CTAP2_ERR_PIN_POLICY_VIOLATION)?;
#[cfg(feature = "with_ctap2_1")]
let min_pin_length_rp_ids = match min_pin_length_rp_ids {
Some(entry) => Some(
extract_array(entry)?
.into_iter()
.map(extract_text_string)
.collect::<Result<Vec<String>, Ctap2StatusCode>>()?,
),
None => None,
};
#[cfg(feature = "with_ctap2_1")]
// We expect a bit field of 8 bits, and drop everything else.
// This means we ignore extensions in future versions.
let permissions = permissions
.map(extract_unsigned)
.transpose()?
.map(|p| p as u8);
#[cfg(feature = "with_ctap2_1")]
let permissions_rp_id = permissions_rp_id.map(extract_text_string).transpose()?;
Ok(AuthenticatorClientPinParameters {
@@ -360,18 +339,108 @@ impl TryFrom<cbor::Value> for AuthenticatorClientPinParameters {
pin_auth,
new_pin_enc,
pin_hash_enc,
#[cfg(feature = "with_ctap2_1")]
min_pin_length,
#[cfg(feature = "with_ctap2_1")]
min_pin_length_rp_ids,
#[cfg(feature = "with_ctap2_1")]
permissions,
#[cfg(feature = "with_ctap2_1")]
permissions_rp_id,
})
}
}
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
pub struct AuthenticatorConfigParameters {
pub sub_command: ConfigSubCommand,
pub sub_command_params: Option<ConfigSubCommandParams>,
pub pin_uv_auth_param: Option<Vec<u8>>,
pub pin_uv_auth_protocol: Option<u64>,
}
impl TryFrom<cbor::Value> for AuthenticatorConfigParameters {
type Error = Ctap2StatusCode;
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
destructure_cbor_map! {
let {
0x01 => sub_command,
0x02 => sub_command_params,
0x03 => pin_uv_auth_param,
0x04 => pin_uv_auth_protocol,
} = extract_map(cbor_value)?;
}
let sub_command = ConfigSubCommand::try_from(ok_or_missing(sub_command)?)?;
let sub_command_params = match sub_command {
ConfigSubCommand::SetMinPinLength => Some(ConfigSubCommandParams::SetMinPinLength(
SetMinPinLengthParams::try_from(ok_or_missing(sub_command_params)?)?,
)),
_ => None,
};
let pin_uv_auth_param = pin_uv_auth_param.map(extract_byte_string).transpose()?;
let pin_uv_auth_protocol = pin_uv_auth_protocol.map(extract_unsigned).transpose()?;
Ok(AuthenticatorConfigParameters {
sub_command,
sub_command_params,
pin_uv_auth_param,
pin_uv_auth_protocol,
})
}
}
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
pub struct AuthenticatorAttestationMaterial {
pub certificate: Vec<u8>,
pub private_key: [u8; key_material::ATTESTATION_PRIVATE_KEY_LENGTH],
}
impl TryFrom<cbor::Value> for AuthenticatorAttestationMaterial {
type Error = Ctap2StatusCode;
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
destructure_cbor_map! {
let {
1 => certificate,
2 => private_key,
} = extract_map(cbor_value)?;
}
let certificate = extract_byte_string(ok_or_missing(certificate)?)?;
let private_key = extract_byte_string(ok_or_missing(private_key)?)?;
if private_key.len() != key_material::ATTESTATION_PRIVATE_KEY_LENGTH {
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER);
}
let private_key = array_ref!(private_key, 0, key_material::ATTESTATION_PRIVATE_KEY_LENGTH);
Ok(AuthenticatorAttestationMaterial {
certificate,
private_key: *private_key,
})
}
}
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
pub struct AuthenticatorVendorConfigureParameters {
pub lockdown: bool,
pub attestation_material: Option<AuthenticatorAttestationMaterial>,
}
impl TryFrom<cbor::Value> for AuthenticatorVendorConfigureParameters {
type Error = Ctap2StatusCode;
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
destructure_cbor_map! {
let {
1 => lockdown,
2 => attestation_material,
} = extract_map(cbor_value)?;
}
let lockdown = lockdown.map_or(Ok(false), extract_bool)?;
let attestation_material = attestation_material
.map(AuthenticatorAttestationMaterial::try_from)
.transpose()?;
Ok(AuthenticatorVendorConfigureParameters {
lockdown,
attestation_material,
})
}
}
#[cfg(test)]
mod test {
use super::super::data_formats::{
@@ -380,8 +449,8 @@ mod test {
};
use super::super::ES256_CRED_PARAM;
use super::*;
use alloc::collections::BTreeMap;
use cbor::{cbor_array, cbor_map};
use crypto::rng256::ThreadRng256;
#[test]
fn test_from_cbor_make_credential_parameters() {
@@ -491,27 +560,18 @@ mod test {
#[test]
fn test_from_cbor_client_pin_parameters() {
// TODO(kaczmarczyck) inline the #cfg when #128 is resolved:
// https://github.com/google/OpenSK/issues/128
#[cfg(not(feature = "with_ctap2_1"))]
let mut rng = ThreadRng256 {};
let sk = crypto::ecdh::SecKey::gensk(&mut rng);
let pk = sk.genpk();
let cose_key = CoseKey::from(pk);
let cbor_value = cbor_map! {
1 => 1,
2 => ClientPinSubCommand::GetPinRetries,
3 => cbor_map!{},
3 => cbor::Value::from(cose_key.clone()),
4 => vec! [0xBB],
5 => vec! [0xCC],
6 => vec! [0xDD],
};
#[cfg(feature = "with_ctap2_1")]
let cbor_value = cbor_map! {
1 => 1,
2 => ClientPinSubCommand::GetPinRetries,
3 => cbor_map!{},
4 => vec! [0xBB],
5 => vec! [0xCC],
6 => vec! [0xDD],
7 => 4,
8 => cbor_array!["example.com"],
9 => 0x03,
10 => "example.com",
};
@@ -521,17 +581,11 @@ mod test {
let expected_pin_protocol_parameters = AuthenticatorClientPinParameters {
pin_protocol: 1,
sub_command: ClientPinSubCommand::GetPinRetries,
key_agreement: Some(CoseKey(BTreeMap::new())),
key_agreement: Some(cose_key),
pin_auth: Some(vec![0xBB]),
new_pin_enc: Some(vec![0xCC]),
pin_hash_enc: Some(vec![0xDD]),
#[cfg(feature = "with_ctap2_1")]
min_pin_length: Some(4),
#[cfg(feature = "with_ctap2_1")]
min_pin_length_rp_ids: Some(vec!["example.com".to_string()]),
#[cfg(feature = "with_ctap2_1")]
permissions: Some(0x03),
#[cfg(feature = "with_ctap2_1")]
permissions_rp_id: Some("example.com".to_string()),
};
@@ -563,11 +617,89 @@ mod test {
assert_eq!(command, Ok(Command::AuthenticatorGetNextAssertion));
}
#[cfg(feature = "with_ctap2_1")]
#[test]
fn test_deserialize_selection() {
let cbor_bytes = [Command::AUTHENTICATOR_SELECTION];
let command = Command::deserialize(&cbor_bytes);
assert_eq!(command, Ok(Command::AuthenticatorSelection));
}
#[test]
fn test_vendor_configure() {
// Incomplete command
let mut cbor_bytes = vec![Command::AUTHENTICATOR_VENDOR_CONFIGURE];
let command = Command::deserialize(&cbor_bytes);
assert_eq!(command, Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR));
cbor_bytes.extend(&[0xA1, 0x01, 0xF5]);
let command = Command::deserialize(&cbor_bytes);
assert_eq!(
command,
Ok(Command::AuthenticatorVendorConfigure(
AuthenticatorVendorConfigureParameters {
lockdown: true,
attestation_material: None
}
))
);
let dummy_cert = [0xddu8; 20];
let dummy_pkey = [0x41u8; key_material::ATTESTATION_PRIVATE_KEY_LENGTH];
// Attestation key is too short.
let cbor_value = cbor_map! {
1 => false,
2 => cbor_map! {
1 => dummy_cert,
2 => dummy_pkey[..key_material::ATTESTATION_PRIVATE_KEY_LENGTH - 1]
}
};
assert_eq!(
AuthenticatorVendorConfigureParameters::try_from(cbor_value),
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
);
// Missing private key
let cbor_value = cbor_map! {
1 => false,
2 => cbor_map! {
1 => dummy_cert
}
};
assert_eq!(
AuthenticatorVendorConfigureParameters::try_from(cbor_value),
Err(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)
);
// Missing certificate
let cbor_value = cbor_map! {
1 => false,
2 => cbor_map! {
2 => dummy_pkey
}
};
assert_eq!(
AuthenticatorVendorConfigureParameters::try_from(cbor_value),
Err(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)
);
// Valid
let cbor_value = cbor_map! {
1 => false,
2 => cbor_map! {
1 => dummy_cert,
2 => dummy_pkey
}
};
assert_eq!(
AuthenticatorVendorConfigureParameters::try_from(cbor_value),
Ok(AuthenticatorVendorConfigureParameters {
lockdown: false,
attestation_material: Some(AuthenticatorAttestationMaterial {
certificate: dummy_cert.to_vec(),
private_key: dummy_pkey
})
})
);
}
}
+272
View File
@@ -0,0 +1,272 @@
// Copyright 2020-2021 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 super::check_pin_uv_auth_protocol;
use super::command::AuthenticatorConfigParameters;
use super::data_formats::{ConfigSubCommand, ConfigSubCommandParams, SetMinPinLengthParams};
use super::pin_protocol_v1::PinProtocolV1;
use super::response::ResponseData;
use super::status_code::Ctap2StatusCode;
use super::storage::PersistentStore;
use alloc::vec;
/// Processes the subcommand setMinPINLength for AuthenticatorConfig.
fn process_set_min_pin_length(
persistent_store: &mut PersistentStore,
params: SetMinPinLengthParams,
) -> Result<ResponseData, Ctap2StatusCode> {
let SetMinPinLengthParams {
new_min_pin_length,
min_pin_length_rp_ids,
force_change_pin,
} = params;
let store_min_pin_length = persistent_store.min_pin_length()?;
let new_min_pin_length = new_min_pin_length.unwrap_or(store_min_pin_length);
if new_min_pin_length < store_min_pin_length {
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_POLICY_VIOLATION);
}
let mut force_change_pin = force_change_pin.unwrap_or(false);
if force_change_pin && persistent_store.pin_hash()?.is_none() {
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_NOT_SET);
}
if let Some(old_length) = persistent_store.pin_code_point_length()? {
force_change_pin |= new_min_pin_length > old_length;
}
if force_change_pin {
// TODO(kaczmarczyck) actually force a PIN change in PinProtocolV1
persistent_store.force_pin_change()?;
}
persistent_store.set_min_pin_length(new_min_pin_length)?;
if let Some(min_pin_length_rp_ids) = min_pin_length_rp_ids {
persistent_store.set_min_pin_length_rp_ids(min_pin_length_rp_ids)?;
}
Ok(ResponseData::AuthenticatorConfig)
}
/// Processes the AuthenticatorConfig command.
pub fn process_config(
persistent_store: &mut PersistentStore,
pin_protocol_v1: &mut PinProtocolV1,
params: AuthenticatorConfigParameters,
) -> Result<ResponseData, Ctap2StatusCode> {
let AuthenticatorConfigParameters {
sub_command,
sub_command_params,
pin_uv_auth_param,
pin_uv_auth_protocol,
} = params;
if persistent_store.pin_hash()?.is_some() {
// TODO(kaczmarczyck) The error code is specified inconsistently with other commands.
check_pin_uv_auth_protocol(pin_uv_auth_protocol)
.map_err(|_| Ctap2StatusCode::CTAP2_ERR_PUAT_REQUIRED)?;
let auth_param = pin_uv_auth_param.ok_or(Ctap2StatusCode::CTAP2_ERR_PUAT_REQUIRED)?;
// Constants are taken from the specification, section 6.11, step 4.2.
let mut config_data = vec![0xFF; 32];
config_data.extend(&[0x0D, sub_command as u8]);
if let Some(sub_command_params) = sub_command_params.clone() {
if !cbor::write(sub_command_params.into(), &mut config_data) {
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
}
}
if !pin_protocol_v1.verify_pin_auth_token(&config_data, &auth_param) {
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID);
}
}
match sub_command {
ConfigSubCommand::SetMinPinLength => {
if let Some(ConfigSubCommandParams::SetMinPinLength(params)) = sub_command_params {
process_set_min_pin_length(persistent_store, params)
} else {
Err(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)
}
}
_ => Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER),
}
}
#[cfg(test)]
mod test {
use super::*;
use crypto::rng256::ThreadRng256;
fn create_min_pin_config_params(
min_pin_length: u8,
min_pin_length_rp_ids: Option<Vec<String>>,
) -> AuthenticatorConfigParameters {
let set_min_pin_length_params = SetMinPinLengthParams {
new_min_pin_length: Some(min_pin_length),
min_pin_length_rp_ids,
force_change_pin: None,
};
AuthenticatorConfigParameters {
sub_command: ConfigSubCommand::SetMinPinLength,
sub_command_params: Some(ConfigSubCommandParams::SetMinPinLength(
set_min_pin_length_params,
)),
pin_uv_auth_param: None,
pin_uv_auth_protocol: Some(1),
}
}
#[test]
fn test_process_set_min_pin_length() {
let mut rng = ThreadRng256 {};
let mut persistent_store = PersistentStore::new(&mut rng);
let key_agreement_key = crypto::ecdh::SecKey::gensk(&mut rng);
let pin_uv_auth_token = [0x55; 32];
let mut pin_protocol_v1 = PinProtocolV1::new_test(key_agreement_key, pin_uv_auth_token);
// First, increase minimum PIN length from 4 to 6 without PIN auth.
let min_pin_length = 6;
let config_params = create_min_pin_config_params(min_pin_length, None);
let config_response =
process_config(&mut persistent_store, &mut pin_protocol_v1, config_params);
assert_eq!(config_response, Ok(ResponseData::AuthenticatorConfig));
assert_eq!(persistent_store.min_pin_length(), Ok(min_pin_length));
// Second, increase minimum PIN length from 6 to 8 with PIN auth.
// The stored PIN or its length don't matter since we control the token.
persistent_store.set_pin(&[0x88; 16], 8).unwrap();
let min_pin_length = 8;
let mut config_params = create_min_pin_config_params(min_pin_length, None);
let pin_auth = vec![
0x5C, 0x69, 0x71, 0x29, 0xBD, 0xCC, 0x53, 0xE8, 0x3C, 0x97, 0x62, 0xDD, 0x90, 0x29,
0xB2, 0xDE,
];
config_params.pin_uv_auth_param = Some(pin_auth);
let config_response =
process_config(&mut persistent_store, &mut pin_protocol_v1, config_params);
assert_eq!(config_response, Ok(ResponseData::AuthenticatorConfig));
assert_eq!(persistent_store.min_pin_length(), Ok(min_pin_length));
// Third, decreasing the minimum PIN length from 8 to 7 fails.
let mut config_params = create_min_pin_config_params(7, None);
let pin_auth = vec![
0xC5, 0xEA, 0xC1, 0x5E, 0x7F, 0x80, 0x70, 0x1A, 0x4E, 0xC4, 0xAD, 0x85, 0x35, 0xD8,
0xA7, 0x71,
];
config_params.pin_uv_auth_param = Some(pin_auth);
let config_response =
process_config(&mut persistent_store, &mut pin_protocol_v1, config_params);
assert_eq!(
config_response,
Err(Ctap2StatusCode::CTAP2_ERR_PIN_POLICY_VIOLATION)
);
assert_eq!(persistent_store.min_pin_length(), Ok(min_pin_length));
}
#[test]
fn test_process_set_min_pin_length_rp_ids() {
let mut rng = ThreadRng256 {};
let mut persistent_store = PersistentStore::new(&mut rng);
let key_agreement_key = crypto::ecdh::SecKey::gensk(&mut rng);
let pin_uv_auth_token = [0x55; 32];
let mut pin_protocol_v1 = PinProtocolV1::new_test(key_agreement_key, pin_uv_auth_token);
// First, set RP IDs without PIN auth.
let min_pin_length = 6;
let min_pin_length_rp_ids = vec!["example.com".to_string()];
let config_params =
create_min_pin_config_params(min_pin_length, Some(min_pin_length_rp_ids.clone()));
let config_response =
process_config(&mut persistent_store, &mut pin_protocol_v1, config_params);
assert_eq!(config_response, Ok(ResponseData::AuthenticatorConfig));
assert_eq!(persistent_store.min_pin_length(), Ok(min_pin_length));
assert_eq!(
persistent_store.min_pin_length_rp_ids(),
Ok(min_pin_length_rp_ids)
);
// Second, change the RP IDs with PIN auth.
let min_pin_length = 8;
let min_pin_length_rp_ids = vec!["another.example.com".to_string()];
// The stored PIN or its length don't matter since we control the token.
persistent_store.set_pin(&[0x88; 16], 8).unwrap();
let mut config_params =
create_min_pin_config_params(min_pin_length, Some(min_pin_length_rp_ids.clone()));
let pin_auth = vec![
0x40, 0x51, 0x2D, 0xAC, 0x2D, 0xE2, 0x15, 0x77, 0x5C, 0xF9, 0x5B, 0x62, 0x9A, 0x2D,
0xD6, 0xDA,
];
config_params.pin_uv_auth_param = Some(pin_auth.clone());
let config_response =
process_config(&mut persistent_store, &mut pin_protocol_v1, config_params);
assert_eq!(config_response, Ok(ResponseData::AuthenticatorConfig));
assert_eq!(persistent_store.min_pin_length(), Ok(min_pin_length));
assert_eq!(
persistent_store.min_pin_length_rp_ids(),
Ok(min_pin_length_rp_ids.clone())
);
// Third, changing RP IDs with bad PIN auth fails.
// One PIN auth shouldn't work for different lengths.
let mut config_params =
create_min_pin_config_params(9, Some(min_pin_length_rp_ids.clone()));
config_params.pin_uv_auth_param = Some(pin_auth.clone());
let config_response =
process_config(&mut persistent_store, &mut pin_protocol_v1, config_params);
assert_eq!(
config_response,
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
);
assert_eq!(persistent_store.min_pin_length(), Ok(min_pin_length));
assert_eq!(
persistent_store.min_pin_length_rp_ids(),
Ok(min_pin_length_rp_ids.clone())
);
// Forth, changing RP IDs with bad PIN auth fails.
// One PIN auth shouldn't work for different RP IDs.
let mut config_params = create_min_pin_config_params(
min_pin_length,
Some(vec!["counter.example.com".to_string()]),
);
config_params.pin_uv_auth_param = Some(pin_auth);
let config_response =
process_config(&mut persistent_store, &mut pin_protocol_v1, config_params);
assert_eq!(
config_response,
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
);
assert_eq!(persistent_store.min_pin_length(), Ok(min_pin_length));
assert_eq!(
persistent_store.min_pin_length_rp_ids(),
Ok(min_pin_length_rp_ids)
);
}
#[test]
fn test_process_config_vendor_prototype() {
let mut rng = ThreadRng256 {};
let mut persistent_store = PersistentStore::new(&mut rng);
let key_agreement_key = crypto::ecdh::SecKey::gensk(&mut rng);
let pin_uv_auth_token = [0x55; 32];
let mut pin_protocol_v1 = PinProtocolV1::new_test(key_agreement_key, pin_uv_auth_token);
let config_params = AuthenticatorConfigParameters {
sub_command: ConfigSubCommand::VendorPrototype,
sub_command_params: None,
pin_uv_auth_param: None,
pin_uv_auth_protocol: None,
};
let config_response =
process_config(&mut persistent_store, &mut pin_protocol_v1, config_params);
assert_eq!(
config_response,
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
);
}
}

Some files were not shown because too many files have changed in this diff Show More