Merge remote-tracking branch 'upstream/master' into apdu-parser-kk
This commit is contained in:
2
.github/workflows/cargo_fuzz.yml
vendored
2
.github/workflows/cargo_fuzz.yml
vendored
@@ -29,3 +29,5 @@ jobs:
|
|||||||
run: cargo fuzz build
|
run: cargo fuzz build
|
||||||
- name: Cargo fuzz build (libraries/cbor)
|
- name: Cargo fuzz build (libraries/cbor)
|
||||||
run: cd libraries/cbor && cargo fuzz build && cd ../..
|
run: cd libraries/cbor && cargo fuzz build && cd ../..
|
||||||
|
- name: Cargo fuzz build (libraries/persistent_store)
|
||||||
|
run: cd libraries/persistent_store && cargo fuzz build && cd ../..
|
||||||
|
|||||||
2
.github/workflows/crypto_test.yml
vendored
2
.github/workflows/crypto_test.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
|||||||
- name: Set up OpenSK
|
- name: Set up OpenSK
|
||||||
run: ./setup.sh
|
run: ./setup.sh
|
||||||
|
|
||||||
- run: echo "::set-env name=RUSTFLAGS::-C target-feature=+aes"
|
- run: echo "RUSTFLAGS=-C target-feature=+aes" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Unit testing of crypto library (release mode)
|
- name: Unit testing of crypto library (release mode)
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
|
|||||||
@@ -928,6 +928,13 @@ if __name__ == "__main__":
|
|||||||
const="console_test",
|
const="console_test",
|
||||||
help=("Compiles and installs the console_test example that tests the "
|
help=("Compiles and installs the console_test example that tests the "
|
||||||
"console driver with messages of various lengths."))
|
"console driver with messages of various lengths."))
|
||||||
|
apps_group.add_argument(
|
||||||
|
"--nfct_test",
|
||||||
|
dest="application",
|
||||||
|
action="store_const",
|
||||||
|
const="nfct_test",
|
||||||
|
help=("Compiles and installs the nfct_test example that tests the "
|
||||||
|
"NFC driver."))
|
||||||
|
|
||||||
main_parser.set_defaults(features=["with_ctap1"])
|
main_parser.set_defaults(features=["with_ctap1"])
|
||||||
|
|
||||||
|
|||||||
249
examples/nfct_test.rs
Normal file
249
examples/nfct_test.rs
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
#![no_std]
|
||||||
|
|
||||||
|
extern crate alloc;
|
||||||
|
extern crate lang_items;
|
||||||
|
extern crate libtock_drivers;
|
||||||
|
|
||||||
|
use core::fmt::Write;
|
||||||
|
use libtock_drivers::console::Console;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "with_nfc"))]
|
||||||
|
mod example {
|
||||||
|
use super::Console;
|
||||||
|
use super::Write;
|
||||||
|
|
||||||
|
pub fn nfc(console: &mut Console) {
|
||||||
|
writeln!(console, "NFC feature flag is missing!").unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "with_nfc")]
|
||||||
|
mod example {
|
||||||
|
use super::Console;
|
||||||
|
use super::Write;
|
||||||
|
use libtock_core::result::CommandError;
|
||||||
|
use libtock_drivers::nfc::NfcTag;
|
||||||
|
use libtock_drivers::nfc::RecvOp;
|
||||||
|
use libtock_drivers::result::FlexUnwrap;
|
||||||
|
use libtock_drivers::result::TockError;
|
||||||
|
use libtock_drivers::timer;
|
||||||
|
use libtock_drivers::timer::Timer;
|
||||||
|
use libtock_drivers::timer::Timestamp;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
|
enum ReturnCode {
|
||||||
|
/// Operation completed successfully
|
||||||
|
SUCCESS,
|
||||||
|
/// Generic failure condition
|
||||||
|
FAIL,
|
||||||
|
/// Underlying system is busy; retry
|
||||||
|
EBUSY,
|
||||||
|
/// The component is powered down
|
||||||
|
EOFF,
|
||||||
|
/// An invalid parameter was passed
|
||||||
|
EINVAL,
|
||||||
|
/// Operation canceled by a call
|
||||||
|
ECANCEL,
|
||||||
|
/// Memory required not available
|
||||||
|
ENOMEM,
|
||||||
|
/// Operation or command is unsupported
|
||||||
|
ENOSUPPORT,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<isize> for ReturnCode {
|
||||||
|
fn from(original: isize) -> ReturnCode {
|
||||||
|
match original {
|
||||||
|
0 => ReturnCode::SUCCESS,
|
||||||
|
-1 => ReturnCode::FAIL,
|
||||||
|
-2 => ReturnCode::EBUSY,
|
||||||
|
-4 => ReturnCode::EOFF,
|
||||||
|
-6 => ReturnCode::EINVAL,
|
||||||
|
-8 => ReturnCode::ECANCEL,
|
||||||
|
-9 => ReturnCode::ENOMEM,
|
||||||
|
_ => ReturnCode::ENOSUPPORT,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function to write on console the received packet.
|
||||||
|
fn print_rx_buffer(buf: &mut [u8]) {
|
||||||
|
if let Some((last, bytes)) = buf.split_last() {
|
||||||
|
let mut console = Console::new();
|
||||||
|
write!(console, "RX:").unwrap();
|
||||||
|
for byte in bytes {
|
||||||
|
write!(console, " {:02x?}", byte).unwrap();
|
||||||
|
}
|
||||||
|
writeln!(console, " {:02x?}", last).unwrap();
|
||||||
|
console.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Function to identify the time elapsed for a transmission request.
|
||||||
|
fn bench_transmit(
|
||||||
|
console: &mut Console,
|
||||||
|
timer: &Timer,
|
||||||
|
title: &str,
|
||||||
|
mut buf: &mut [u8],
|
||||||
|
) -> ReturnCode {
|
||||||
|
let amount = buf.len();
|
||||||
|
let start = Timestamp::<f64>::from_clock_value(timer.get_current_clock().flex_unwrap());
|
||||||
|
match NfcTag::transmit(&mut buf, amount) {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(TockError::Command(CommandError {
|
||||||
|
return_code: -8, /* ECANCEL: No Field*/
|
||||||
|
..
|
||||||
|
})) => return ReturnCode::ECANCEL,
|
||||||
|
Err(_) => writeln!(Console::new(), " -- tx error!").unwrap(),
|
||||||
|
}
|
||||||
|
let end = Timestamp::<f64>::from_clock_value(timer.get_current_clock().flex_unwrap());
|
||||||
|
let elapsed = (end - start).ms();
|
||||||
|
writeln!(
|
||||||
|
console,
|
||||||
|
"{}\n{:.2} ms elapsed for {} bytes ({:.2} kbit/s)",
|
||||||
|
title,
|
||||||
|
elapsed,
|
||||||
|
amount,
|
||||||
|
(amount as f64) / elapsed * 8.
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
console.flush();
|
||||||
|
ReturnCode::SUCCESS
|
||||||
|
}
|
||||||
|
|
||||||
|
fn receive_packet(console: &mut Console, mut buf: &mut [u8; 256]) -> ReturnCode {
|
||||||
|
match NfcTag::receive(&mut buf) {
|
||||||
|
Ok(RecvOp {
|
||||||
|
recv_amount: amount,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
if amount <= buf.len() {
|
||||||
|
print_rx_buffer(&mut buf[..amount]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(TockError::Command(CommandError { return_code, .. })) => return return_code.into(),
|
||||||
|
Err(_) => {
|
||||||
|
writeln!(console, " -- RX Err").unwrap();
|
||||||
|
return ReturnCode::ECANCEL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ReturnCode::SUCCESS
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transmit_reply(mut console: &mut Console, timer: &Timer, buf: &[u8]) -> ReturnCode {
|
||||||
|
let mut return_code = ReturnCode::SUCCESS;
|
||||||
|
match buf[0] {
|
||||||
|
0xe0 /* RATS */=> {
|
||||||
|
let mut answer_to_select = [0x05, 0x78, 0x80, 0xB1, 0x00];
|
||||||
|
return_code = bench_transmit(&mut console, &timer, "TX: ATS", &mut answer_to_select);
|
||||||
|
}
|
||||||
|
0xc2 /* DESELECT */ => {
|
||||||
|
// Ignore the request
|
||||||
|
let mut command_error = [0x6A, 0x81];
|
||||||
|
return_code = bench_transmit(&mut console, &timer, "TX: DESELECT", &mut command_error);
|
||||||
|
}
|
||||||
|
0x02 | 0x03 /* APDU Prefix */ => match buf[2] {
|
||||||
|
// If the received packet is applet selection command (FIDO 2)
|
||||||
|
0xa4 /* SELECT */ => if buf[3] == 0x04 && buf[5] == 0x08 && buf[6] == 0xa0 {
|
||||||
|
// Vesion: "FIDO_2_0"
|
||||||
|
let mut reply = [buf[0], 0x46, 0x49, 0x44, 0x4f, 0x5f, 0x32, 0x5f, 0x30, 0x90, 0x00,];
|
||||||
|
return_code = bench_transmit(&mut console, &timer, "TX: Version Str", &mut reply);
|
||||||
|
} else if (buf[6] == 0xd2 && buf[7] == 0x76) || (buf[6] == 0xe1 && (buf[7] == 0x03 || buf[7] == 0x04)){
|
||||||
|
let mut reply = [buf[0], 0x90, 0x00];
|
||||||
|
return_code = bench_transmit(&mut console, &timer, "TX: 0x9000", &mut reply);
|
||||||
|
} else /* Unknown file */ {
|
||||||
|
let mut reply = [buf[0], 0x6a, 0x82];
|
||||||
|
return_code = bench_transmit(&mut console, &timer, "TX: 0x6A82", &mut reply);
|
||||||
|
}
|
||||||
|
0xb0 /* READ */ => match buf[5] {
|
||||||
|
0x02 => {
|
||||||
|
let mut reply = [buf[0], 0x12, 0x90, 0x00,];
|
||||||
|
return_code = bench_transmit(&mut console, &timer, "TX: File Size", &mut reply);
|
||||||
|
}
|
||||||
|
0x12 => {
|
||||||
|
let mut reply = [buf[0], 0xd1, 0x01, 0x0e, 0x55, 0x77, 0x77, 0x77, 0x2e, 0x6f, 0x70, 0x65,
|
||||||
|
0x6e, 0x73, 0x6b, 0x2e, 0x64, 0x65, 0x76, 0x90, 0x00,];
|
||||||
|
return_code = bench_transmit(&mut console, &timer, "TX: NDEF", &mut reply);
|
||||||
|
}
|
||||||
|
0x0f => {
|
||||||
|
let mut reply = [buf[0], 0x00, 0x0f, 0x20, 0x00, 0x7f, 0x00, 0x7f, 0x04, 0x06, 0xe1, 0x04,
|
||||||
|
0x00, 0x7f, 0x00, 0x00, 0x90, 0x00,];
|
||||||
|
return_code = bench_transmit(&mut console, &timer, "TX: CC", &mut reply);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let mut reply = [buf[0], 0x90, 0x00];
|
||||||
|
return_code = bench_transmit(&mut console, &timer, "TX: 0x9000", &mut reply);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let mut reply = [buf[0], 0x90, 0x00];
|
||||||
|
return_code = bench_transmit(&mut console, &timer, "TX: 0x9000", &mut reply);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0x26 | 0x52 | 0x50 /* REQA | WUPA | Halt */ => {
|
||||||
|
return ReturnCode::EOFF;
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
return_code
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn nfc(mut console: &mut Console) {
|
||||||
|
// Setup the timer with a dummy callback (we only care about reading the current time, but the
|
||||||
|
// API forces us to set an alarm callback too).
|
||||||
|
let mut with_callback = timer::with_callback(|_, _| {});
|
||||||
|
let timer = with_callback.init().flex_unwrap();
|
||||||
|
|
||||||
|
writeln!(
|
||||||
|
console,
|
||||||
|
"Clock frequency: {} Hz",
|
||||||
|
timer.clock_frequency().hz()
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut state_change_counter = 0;
|
||||||
|
loop {
|
||||||
|
let mut rx_buf = [0; 256];
|
||||||
|
match receive_packet(&mut console, &mut rx_buf) {
|
||||||
|
ReturnCode::EOFF => {
|
||||||
|
// Not configured
|
||||||
|
while !NfcTag::enable_emulation() {}
|
||||||
|
// Configure Type 4 tag
|
||||||
|
while !NfcTag::configure(4) {}
|
||||||
|
}
|
||||||
|
ReturnCode::ECANCEL /* field lost */ => {
|
||||||
|
NfcTag::disable_emulation();
|
||||||
|
}
|
||||||
|
ReturnCode::EBUSY /* awaiting select*/ => (),
|
||||||
|
ReturnCode::ENOMEM => {
|
||||||
|
writeln!(console, " -- Amount more than buffer limit").unwrap()
|
||||||
|
}
|
||||||
|
ReturnCode::FAIL => writeln!(console, " -- Invalid CRC").unwrap(),
|
||||||
|
ReturnCode::EINVAL /* covered in driver interface */ => (),
|
||||||
|
ReturnCode::ENOSUPPORT => (),
|
||||||
|
ReturnCode::SUCCESS => {
|
||||||
|
// If the reader restarts the communication then disable the tag.
|
||||||
|
match transmit_reply(&mut console, &timer, &rx_buf) {
|
||||||
|
ReturnCode::ECANCEL | ReturnCode::EOFF => {
|
||||||
|
if NfcTag::disable_emulation() {
|
||||||
|
writeln!(console, " -- TAG DISABLED").unwrap();
|
||||||
|
}
|
||||||
|
state_change_counter += 1;
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if state_change_counter > 100 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut console = Console::new();
|
||||||
|
writeln!(console, "****************************************").unwrap();
|
||||||
|
writeln!(console, "nfct_test application is installed").unwrap();
|
||||||
|
example::nfc(&mut console);
|
||||||
|
writeln!(console, "****************************************").unwrap();
|
||||||
|
}
|
||||||
@@ -147,7 +147,7 @@ fn process_message<CheckUserPresence>(
|
|||||||
pub fn process_ctap_any_type(data: &[u8]) {
|
pub fn process_ctap_any_type(data: &[u8]) {
|
||||||
// Initialize ctap state and hid and get the allocated cid.
|
// Initialize ctap state and hid and get the allocated cid.
|
||||||
let mut rng = ThreadRng256 {};
|
let mut rng = ThreadRng256 {};
|
||||||
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
|
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
|
||||||
let mut ctap_hid = CtapHid::new();
|
let mut ctap_hid = CtapHid::new();
|
||||||
let cid = initialize(&mut ctap_state, &mut ctap_hid);
|
let cid = initialize(&mut ctap_state, &mut ctap_hid);
|
||||||
// Wrap input as message with the allocated cid.
|
// Wrap input as message with the allocated cid.
|
||||||
@@ -165,7 +165,7 @@ pub fn process_ctap_specific_type(data: &[u8], input_type: InputType) {
|
|||||||
}
|
}
|
||||||
// Initialize ctap state and hid and get the allocated cid.
|
// Initialize ctap state and hid and get the allocated cid.
|
||||||
let mut rng = ThreadRng256 {};
|
let mut rng = ThreadRng256 {};
|
||||||
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
|
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
|
||||||
let mut ctap_hid = CtapHid::new();
|
let mut ctap_hid = CtapHid::new();
|
||||||
let cid = initialize(&mut ctap_state, &mut ctap_hid);
|
let cid = initialize(&mut ctap_state, &mut ctap_hid);
|
||||||
// Wrap input as message with allocated cid and command type.
|
// Wrap input as message with allocated cid and command type.
|
||||||
|
|||||||
4
libraries/persistent_store/fuzz/.gitignore
vendored
Normal file
4
libraries/persistent_store/fuzz/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
/Cargo.lock
|
||||||
|
/artifacts/
|
||||||
|
/corpus/
|
||||||
|
/target/
|
||||||
22
libraries/persistent_store/fuzz/Cargo.toml
Normal file
22
libraries/persistent_store/fuzz/Cargo.toml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
[package]
|
||||||
|
name = "fuzz-store"
|
||||||
|
version = "0.0.0"
|
||||||
|
authors = ["Julien Cretin <cretin@google.com>"]
|
||||||
|
publish = false
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[package.metadata]
|
||||||
|
cargo-fuzz = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
libfuzzer-sys = "0.3"
|
||||||
|
persistent_store = { path = "..", features = ["std"] }
|
||||||
|
strum = { version = "0.19", features = ["derive"] }
|
||||||
|
|
||||||
|
# Prevent this from interfering with workspaces
|
||||||
|
[workspace]
|
||||||
|
members = ["."]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "store"
|
||||||
|
path = "fuzz_targets/store.rs"
|
||||||
21
libraries/persistent_store/fuzz/fuzz_targets/store.rs
Normal file
21
libraries/persistent_store/fuzz/fuzz_targets/store.rs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
// Copyright 2019-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_main]
|
||||||
|
|
||||||
|
use libfuzzer_sys::fuzz_target;
|
||||||
|
|
||||||
|
fuzz_target!(|data: &[u8]| {
|
||||||
|
// TODO(ia0): Call fuzzing when implemented.
|
||||||
|
});
|
||||||
92
libraries/persistent_store/fuzz/src/histogram.rs
Normal file
92
libraries/persistent_store/fuzz/src/histogram.rs
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
// Copyright 2019-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 crate::num_bits;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
/// Histogram with logarithmic buckets.
|
||||||
|
///
|
||||||
|
/// This is used to compute coverage statistics of the fuzzing runs of a corpus. This is not used
|
||||||
|
/// during actual fuzzing, only when replaying a corpus to compute statistics.
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Histogram {
|
||||||
|
/// Maps each bucket to its count.
|
||||||
|
///
|
||||||
|
/// Buckets are numbers sharing the same highest bit. The first buckets are: only 0, only 1, 2
|
||||||
|
/// to 3, 4 to 7, 8 to 15. Buckets are identified by their lower-bound.
|
||||||
|
buckets: HashMap<usize, usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Histogram {
|
||||||
|
/// Increases the count of the bucket of an item.
|
||||||
|
///
|
||||||
|
/// The bucket of `item` is the highest power of two, lower or equal to `item`. If `item` is
|
||||||
|
/// zero, then its bucket is also zero.
|
||||||
|
pub fn add(&mut self, item: usize) {
|
||||||
|
*self.buckets.entry(get_bucket(item)).or_insert(0) += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Merges another histogram into this one.
|
||||||
|
pub fn merge(&mut self, other: &Histogram) {
|
||||||
|
for (&bucket, &count) in &other.buckets {
|
||||||
|
*self.buckets.entry(bucket).or_insert(0) += count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the bit-width of one past the highest non-empty bucket.
|
||||||
|
///
|
||||||
|
/// In other words, all non-empty buckets of the histogram have a bit-width smaller than the
|
||||||
|
/// returned width.
|
||||||
|
pub fn width_lim(&self) -> usize {
|
||||||
|
self.buckets.keys().max().map_or(0, |&x| num_bits(x) + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the count of a bucket.
|
||||||
|
pub fn get(&self, bucket: usize) -> Option<usize> {
|
||||||
|
self.buckets.get(&bucket).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the total count.
|
||||||
|
pub fn count(&self) -> usize {
|
||||||
|
self.buckets.values().sum()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the bucket of an item.
|
||||||
|
fn get_bucket(item: usize) -> usize {
|
||||||
|
let bucket = bucket_from_width(num_bits(item));
|
||||||
|
assert!(bucket <= item && (item == 0 || item / 2 < bucket));
|
||||||
|
bucket
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the bucket of an item given its bit-width.
|
||||||
|
pub fn bucket_from_width(width: usize) -> usize {
|
||||||
|
if width == 0 {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
1 << (width - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_bucket_ok() {
|
||||||
|
assert_eq!(get_bucket(0), 0);
|
||||||
|
assert_eq!(get_bucket(1), 1);
|
||||||
|
assert_eq!(get_bucket(2), 2);
|
||||||
|
assert_eq!(get_bucket(3), 2);
|
||||||
|
assert_eq!(get_bucket(4), 4);
|
||||||
|
assert_eq!(get_bucket(7), 4);
|
||||||
|
assert_eq!(get_bucket(8), 8);
|
||||||
|
assert_eq!(get_bucket(15), 8);
|
||||||
|
}
|
||||||
196
libraries/persistent_store/fuzz/src/lib.rs
Normal file
196
libraries/persistent_store/fuzz/src/lib.rs
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
// Copyright 2019-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.
|
||||||
|
|
||||||
|
//! Fuzzing library for the persistent store.
|
||||||
|
//!
|
||||||
|
//! The overall design principles are (in order of precedence):
|
||||||
|
//! - Determinism: fuzzing is a function from seeds (byte slices) to sequences of store
|
||||||
|
//! manipulations (things like creating a store, applying operations, interrupting operations,
|
||||||
|
//! interrupting reboots, checking invariant, etc). We can replay this function on the same input
|
||||||
|
//! to get the same sequence of manipulations (for the same fuzzing and store code).
|
||||||
|
//! - Coverage: fuzzing tries to coverage as much different behaviors as possible for small seeds.
|
||||||
|
//! Ideally, each seed bit would control a branch decision in the tree of execution paths.
|
||||||
|
//! - Surjectivity: all sequences of manipulations are reachable by fuzzing for some seed. The only
|
||||||
|
//! situation where coverage takes precedence over surjectivity is for the value of insert updates
|
||||||
|
//! where a pseudo-random generator is used to avoid wasting entropy.
|
||||||
|
|
||||||
|
// TODO(ia0): Remove when used.
|
||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
mod histogram;
|
||||||
|
mod stats;
|
||||||
|
|
||||||
|
pub use stats::{StatKey, Stats};
|
||||||
|
|
||||||
|
/// Bit-level entropy source based on a byte slice shared reference.
|
||||||
|
///
|
||||||
|
/// This is used to convert the byte slice provided by the fuzzer into the entropy used by the
|
||||||
|
/// fuzzing code to generate a sequence of store manipulations, among other things. Entropy
|
||||||
|
/// operations use the shortest necessary sequence of bits from the byte slice, such that fuzzer
|
||||||
|
/// mutations of the byte slice have local impact or cascading effects towards future operations
|
||||||
|
/// only.
|
||||||
|
///
|
||||||
|
/// The entropy has the following properties (in order of precedence):
|
||||||
|
/// - It always returns a result.
|
||||||
|
/// - It is deterministic: for a given slice and a given sequence of operations, the same results
|
||||||
|
/// are returned. This permits to replay and debug fuzzing artifacts.
|
||||||
|
/// - It uses the slice as a bit stream. In particular, it doesn't do big number arithmetic. This
|
||||||
|
/// permits to have a simple implementation.
|
||||||
|
/// - It doesn't waste information: for a given operation, the minimum integer number of bits is
|
||||||
|
/// used to produce the result. As a consequence fractional bits can be wasted at each operation.
|
||||||
|
/// - It uses the information uniformly: each bit is used exactly once, except when only a fraction
|
||||||
|
/// of it is used. In particular, a bit is not used more than once. A consequence of each bit
|
||||||
|
/// being used essentially once, is that the results are mostly uniformly distributed.
|
||||||
|
///
|
||||||
|
/// # Invariant
|
||||||
|
///
|
||||||
|
/// - The bit is a valid position in the slice, or one past: `bit <= 8 * data.len()`.
|
||||||
|
struct Entropy<'a> {
|
||||||
|
/// The byte slice shared reference providing the entropy.
|
||||||
|
data: &'a [u8],
|
||||||
|
|
||||||
|
/// The bit position in the byte slice of the next entropy bit.
|
||||||
|
bit: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Entropy<'_> {
|
||||||
|
/// Creates a bit-level entropy given a byte slice.
|
||||||
|
fn new(data: &[u8]) -> Entropy {
|
||||||
|
let bit = 0;
|
||||||
|
Entropy { data, bit }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consumes the remaining entropy.
|
||||||
|
fn consume_all(&mut self) {
|
||||||
|
self.bit = 8 * self.data.len();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether there is entropy remaining.
|
||||||
|
fn is_empty(&self) -> bool {
|
||||||
|
assert!(self.bit <= 8 * self.data.len());
|
||||||
|
self.bit == 8 * self.data.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads a bit.
|
||||||
|
fn read_bit(&mut self) -> bool {
|
||||||
|
if self.is_empty() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let b = self.bit;
|
||||||
|
self.bit += 1;
|
||||||
|
self.data[b / 8] & 1 << (b % 8) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads a number with a given bit-width.
|
||||||
|
///
|
||||||
|
/// # Preconditions
|
||||||
|
///
|
||||||
|
/// - The number should fit in the return type: `n <= 8 * size_of::<usize>()`.
|
||||||
|
fn read_bits(&mut self, n: usize) -> usize {
|
||||||
|
assert!(n <= 8 * std::mem::size_of::<usize>());
|
||||||
|
let mut r = 0;
|
||||||
|
for i in 0..n {
|
||||||
|
r |= (self.read_bit() as usize) << i;
|
||||||
|
}
|
||||||
|
r
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads a byte.
|
||||||
|
fn read_byte(&mut self) -> u8 {
|
||||||
|
self.read_bits(8) as u8
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads a slice.
|
||||||
|
fn read_slice(&mut self, length: usize) -> Vec<u8> {
|
||||||
|
let mut result = Vec::with_capacity(length);
|
||||||
|
for _ in 0..length {
|
||||||
|
result.push(self.read_byte());
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads a number between `min` and `max` (inclusive bounds).
|
||||||
|
///
|
||||||
|
/// The distribution is uniform if the range width is a power of two. Otherwise, the minimum
|
||||||
|
/// amount of entropy is used (the next power of two) and the distribution is the closest to
|
||||||
|
/// uniform for that entropy.
|
||||||
|
///
|
||||||
|
/// # Preconditions
|
||||||
|
///
|
||||||
|
/// - The bounds should be correctly ordered: `min <= max`.
|
||||||
|
/// - The upper-bound should not be too large: `max < usize::max_value()`.
|
||||||
|
fn read_range(&mut self, min: usize, max: usize) -> usize {
|
||||||
|
assert!(min <= max && max < usize::max_value());
|
||||||
|
let count = max - min + 1;
|
||||||
|
let delta = self.read_bits(num_bits(count - 1)) % count;
|
||||||
|
min + delta
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the number of bits necessary to represent a number.
|
||||||
|
fn num_bits(x: usize) -> usize {
|
||||||
|
8 * std::mem::size_of::<usize>() - x.leading_zeros() as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn num_bits_ok() {
|
||||||
|
assert_eq!(num_bits(0), 0);
|
||||||
|
assert_eq!(num_bits(1), 1);
|
||||||
|
assert_eq!(num_bits(2), 2);
|
||||||
|
assert_eq!(num_bits(3), 2);
|
||||||
|
assert_eq!(num_bits(4), 3);
|
||||||
|
assert_eq!(num_bits(7), 3);
|
||||||
|
assert_eq!(num_bits(8), 4);
|
||||||
|
assert_eq!(num_bits(15), 4);
|
||||||
|
assert_eq!(num_bits(16), 5);
|
||||||
|
assert_eq!(
|
||||||
|
num_bits(usize::max_value()),
|
||||||
|
8 * std::mem::size_of::<usize>()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_bit_ok() {
|
||||||
|
let mut entropy = Entropy::new(&[0b10110010]);
|
||||||
|
assert!(!entropy.read_bit());
|
||||||
|
assert!(entropy.read_bit());
|
||||||
|
assert!(!entropy.read_bit());
|
||||||
|
assert!(!entropy.read_bit());
|
||||||
|
assert!(entropy.read_bit());
|
||||||
|
assert!(entropy.read_bit());
|
||||||
|
assert!(!entropy.read_bit());
|
||||||
|
assert!(entropy.read_bit());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_bits_ok() {
|
||||||
|
let mut entropy = Entropy::new(&[0x83, 0x92]);
|
||||||
|
assert_eq!(entropy.read_bits(4), 0x3);
|
||||||
|
assert_eq!(entropy.read_bits(8), 0x28);
|
||||||
|
assert_eq!(entropy.read_bits(2), 0b01);
|
||||||
|
assert_eq!(entropy.read_bits(2), 0b10);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_range_ok() {
|
||||||
|
let mut entropy = Entropy::new(&[0b00101011]);
|
||||||
|
assert_eq!(entropy.read_range(0, 7), 0b011);
|
||||||
|
assert_eq!(entropy.read_range(1, 8), 1 + 0b101);
|
||||||
|
assert_eq!(entropy.read_range(4, 6), 4 + 0b00);
|
||||||
|
let mut entropy = Entropy::new(&[0b00101011]);
|
||||||
|
assert_eq!(entropy.read_range(0, 8), 0b1011 % 9);
|
||||||
|
assert_eq!(entropy.read_range(3, 15), 3 + 0b0010);
|
||||||
|
let mut entropy = Entropy::new(&[0x12, 0x34, 0x56, 0x78]);
|
||||||
|
assert_eq!(entropy.read_range(0, usize::max_value() - 1), 0x78563412);
|
||||||
|
}
|
||||||
187
libraries/persistent_store/fuzz/src/stats.rs
Normal file
187
libraries/persistent_store/fuzz/src/stats.rs
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
// Copyright 2019-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.
|
||||||
|
|
||||||
|
//! Helpers to compute and display fuzzing coverage statistics.
|
||||||
|
//!
|
||||||
|
//! This is not used during actual fuzzing, only when replaying a corpus to compute statistics.
|
||||||
|
|
||||||
|
use crate::histogram::{bucket_from_width, Histogram};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use strum::{Display, EnumIter, EnumString, IntoEnumIterator};
|
||||||
|
|
||||||
|
/// Statistics for each fuzzing run.
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, EnumIter, EnumString, Display)]
|
||||||
|
pub enum StatKey {
|
||||||
|
/// The available entropy in bytes.
|
||||||
|
Entropy,
|
||||||
|
|
||||||
|
/// The size of a page in bytes.
|
||||||
|
PageSize,
|
||||||
|
|
||||||
|
/// The number of pages.
|
||||||
|
NumPages,
|
||||||
|
|
||||||
|
/// The maximum number times a page can be erased.
|
||||||
|
MaxPageErases,
|
||||||
|
|
||||||
|
/// The dirty length of the initial storage in bytes.
|
||||||
|
///
|
||||||
|
/// This is the length of the prefix of the storage that is written using entropy before the
|
||||||
|
/// store is initialized. This permits to check the store against an invalid storage: it should
|
||||||
|
/// not crash but may misbehave.
|
||||||
|
DirtyLength,
|
||||||
|
|
||||||
|
/// The number of used erase cycles of the initial storage.
|
||||||
|
///
|
||||||
|
/// This permits to check the store as if it already consumed lifetime. In particular it permits
|
||||||
|
/// to check the store when lifetime is almost out.
|
||||||
|
InitCycles,
|
||||||
|
|
||||||
|
/// The number of words written during fuzzing.
|
||||||
|
///
|
||||||
|
/// This permits to get an idea of how much lifetime was exercised during fuzzing.
|
||||||
|
UsedLifetime,
|
||||||
|
|
||||||
|
/// Whether the store reached the end of the lifetime during fuzzing.
|
||||||
|
FinishedLifetime,
|
||||||
|
|
||||||
|
/// The number of times the store was fully compacted.
|
||||||
|
///
|
||||||
|
/// The store is considered fully compacted when all pages have been compacted once. So each
|
||||||
|
/// page has been compacted at least that number of times.
|
||||||
|
NumCompactions,
|
||||||
|
|
||||||
|
/// The number of times the store was powered on.
|
||||||
|
PowerOnCount,
|
||||||
|
|
||||||
|
/// The number of times a transaction was applied.
|
||||||
|
TransactionCount,
|
||||||
|
|
||||||
|
/// The number of times a clear operation was applied.
|
||||||
|
ClearCount,
|
||||||
|
|
||||||
|
/// The number of times a prepare operation was applied.
|
||||||
|
PrepareCount,
|
||||||
|
|
||||||
|
/// The number of times an insert update was applied.
|
||||||
|
InsertCount,
|
||||||
|
|
||||||
|
/// The number of times a remove update was applied.
|
||||||
|
RemoveCount,
|
||||||
|
|
||||||
|
/// The number of times a store operation was interrupted.
|
||||||
|
InterruptionCount,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Statistics about multiple fuzzing runs.
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Stats {
|
||||||
|
/// Maps each statistics to its histogram.
|
||||||
|
stats: HashMap<StatKey, Histogram>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Stats {
|
||||||
|
/// Adds a measure for a statistics.
|
||||||
|
pub fn add(&mut self, key: StatKey, value: usize) {
|
||||||
|
self.stats.entry(key).or_default().add(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Merges another statistics into this one.
|
||||||
|
pub fn merge(&mut self, other: &Stats) {
|
||||||
|
for (&key, other) in &other.stats {
|
||||||
|
self.stats.entry(key).or_default().merge(other);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the count of a bucket for a given key.
|
||||||
|
pub fn get_count(&self, key: StatKey, bucket: usize) -> Option<usize> {
|
||||||
|
self.stats.get(&key).and_then(|h| h.get(bucket))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the bit-width of one past the highest non-empty bucket.
|
||||||
|
///
|
||||||
|
/// In other words, all non-empty buckets of the histogram have a bit-width smaller than the
|
||||||
|
/// returned width.
|
||||||
|
fn width_lim(&self) -> usize {
|
||||||
|
self.stats
|
||||||
|
.values()
|
||||||
|
.map(|h| h.width_lim())
|
||||||
|
.max()
|
||||||
|
.unwrap_or(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Stats {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
|
||||||
|
let mut matrix: Vec<Vec<String>> = Vec::new();
|
||||||
|
let bits = self.width_lim();
|
||||||
|
|
||||||
|
let mut header = Vec::new();
|
||||||
|
header.push(String::new());
|
||||||
|
for width in 0..bits {
|
||||||
|
header.push(format!(" {}", bucket_from_width(width)));
|
||||||
|
}
|
||||||
|
header.push(" count".into());
|
||||||
|
matrix.push(header);
|
||||||
|
|
||||||
|
for key in StatKey::iter() {
|
||||||
|
let mut row = Vec::new();
|
||||||
|
row.push(format!("{}:", key));
|
||||||
|
for width in 0..bits {
|
||||||
|
row.push(match self.get_count(key, bucket_from_width(width)) {
|
||||||
|
None => String::new(),
|
||||||
|
Some(x) => format!(" {}", x),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let count = self.stats.get(&key).map_or(0, |h| h.count());
|
||||||
|
row.push(format!(" {}", count));
|
||||||
|
matrix.push(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
write_matrix(f, matrix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prints a string aligned to the right for a given width.
|
||||||
|
fn align(f: &mut std::fmt::Formatter, x: &str, n: usize) -> Result<(), std::fmt::Error> {
|
||||||
|
for _ in 0..n.saturating_sub(x.len()) {
|
||||||
|
write!(f, " ")?;
|
||||||
|
}
|
||||||
|
write!(f, "{}", x)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prints a matrix with columns of minimal width to fit all elements.
|
||||||
|
fn write_matrix(
|
||||||
|
f: &mut std::fmt::Formatter,
|
||||||
|
mut m: Vec<Vec<String>>,
|
||||||
|
) -> Result<(), std::fmt::Error> {
|
||||||
|
if m.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let num_cols = m.iter().map(|r| r.len()).max().unwrap();
|
||||||
|
let mut col_len = vec![0; num_cols];
|
||||||
|
for row in &mut m {
|
||||||
|
row.resize(num_cols, String::new());
|
||||||
|
for col in 0..num_cols {
|
||||||
|
col_len[col] = std::cmp::max(col_len[col], row[col].len());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for row in m {
|
||||||
|
for col in 0..num_cols {
|
||||||
|
align(f, &row[col], col_len[col])?;
|
||||||
|
}
|
||||||
|
writeln!(f)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -181,6 +181,12 @@ pub enum StoreInvariant {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<StoreError> for StoreInvariant {
|
||||||
|
fn from(error: StoreError) -> StoreInvariant {
|
||||||
|
StoreInvariant::StoreError(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl StoreDriver {
|
impl StoreDriver {
|
||||||
/// Provides read-only access to the storage.
|
/// Provides read-only access to the storage.
|
||||||
pub fn storage(&self) -> &BufferStorage {
|
pub fn storage(&self) -> &BufferStorage {
|
||||||
@@ -249,6 +255,10 @@ impl StoreDriverOff {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Powers on the store without interruption.
|
/// Powers on the store without interruption.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if the store cannot be powered on.
|
||||||
pub fn power_on(self) -> Result<StoreDriverOn, StoreInvariant> {
|
pub fn power_on(self) -> Result<StoreDriverOn, StoreInvariant> {
|
||||||
Ok(self
|
Ok(self
|
||||||
.partial_power_on(StoreInterruption::none())
|
.partial_power_on(StoreInterruption::none())
|
||||||
@@ -506,8 +516,8 @@ impl StoreDriverOn {
|
|||||||
/// Checks that the store and model are in sync.
|
/// Checks that the store and model are in sync.
|
||||||
fn check_model(&self) -> Result<(), StoreInvariant> {
|
fn check_model(&self) -> Result<(), StoreInvariant> {
|
||||||
let mut model_content = self.model.content().clone();
|
let mut model_content = self.model.content().clone();
|
||||||
for handle in self.store.iter().unwrap() {
|
for handle in self.store.iter()? {
|
||||||
let handle = handle.unwrap();
|
let handle = handle?;
|
||||||
let model_value = match model_content.remove(&handle.get_key()) {
|
let model_value = match model_content.remove(&handle.get_key()) {
|
||||||
None => {
|
None => {
|
||||||
return Err(StoreInvariant::OnlyInStore {
|
return Err(StoreInvariant::OnlyInStore {
|
||||||
@@ -516,7 +526,7 @@ impl StoreDriverOn {
|
|||||||
}
|
}
|
||||||
Some(x) => x,
|
Some(x) => x,
|
||||||
};
|
};
|
||||||
let store_value = handle.get_value(&self.store).unwrap().into_boxed_slice();
|
let store_value = handle.get_value(&self.store)?.into_boxed_slice();
|
||||||
if store_value != model_value {
|
if store_value != model_value {
|
||||||
return Err(StoreInvariant::DifferentValue {
|
return Err(StoreInvariant::DifferentValue {
|
||||||
key: handle.get_key(),
|
key: handle.get_key(),
|
||||||
@@ -528,7 +538,7 @@ impl StoreDriverOn {
|
|||||||
if let Some(&key) = model_content.keys().next() {
|
if let Some(&key) = model_content.keys().next() {
|
||||||
return Err(StoreInvariant::OnlyInModel { key });
|
return Err(StoreInvariant::OnlyInModel { key });
|
||||||
}
|
}
|
||||||
let store_capacity = self.store.capacity().unwrap().remaining();
|
let store_capacity = self.store.capacity()?.remaining();
|
||||||
let model_capacity = self.model.capacity().remaining();
|
let model_capacity = self.model.capacity().remaining();
|
||||||
if store_capacity != model_capacity {
|
if store_capacity != model_capacity {
|
||||||
return Err(StoreInvariant::DifferentCapacity {
|
return Err(StoreInvariant::DifferentCapacity {
|
||||||
@@ -544,8 +554,8 @@ impl StoreDriverOn {
|
|||||||
let format = self.model.format();
|
let format = self.model.format();
|
||||||
let storage = self.store.storage();
|
let storage = self.store.storage();
|
||||||
let num_words = format.page_size() / format.word_size();
|
let num_words = format.page_size() / format.word_size();
|
||||||
let head = self.store.head().unwrap();
|
let head = self.store.head()?;
|
||||||
let tail = self.store.tail().unwrap();
|
let tail = self.store.tail()?;
|
||||||
for page in 0..format.num_pages() {
|
for page in 0..format.num_pages() {
|
||||||
// Check the erase cycle of the page.
|
// Check the erase cycle of the page.
|
||||||
let store_erase = head.cycle(format) + (page < head.page(format)) as Nat;
|
let store_erase = head.cycle(format) + (page < head.page(format)) as Nat;
|
||||||
|
|||||||
46
metadata/metadata.json
Normal file
46
metadata/metadata.json
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"assertionScheme": "FIDOV2",
|
||||||
|
"keyProtection": 1,
|
||||||
|
"attestationRootCertificates": [],
|
||||||
|
"aaguid": "664d9f67-84a2-412a-9ff7-b4f7d8ee6d05",
|
||||||
|
"publicKeyAlgAndEncoding": 260,
|
||||||
|
"protocolFamily": "fido2",
|
||||||
|
"upv": [
|
||||||
|
{
|
||||||
|
"major": 1,
|
||||||
|
"minor": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"icon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAQKADAAQAAAABAAAAQAAAAABGUUKwAAAIQ0lEQVR4Ae1aCVSUVRT+kGVYBBQFBYzYFJFNLdPQVksz85QnszRNbaPNzDI0OaIH27VUUnOpzAqXMJNIszKTUEQWRXBnExRiUYEUBATs3lfzJw3LDP/MMOfMPI/M++97///uve+++9797jO7TgVGXLoYsexCdJMCTBZg5BowLQEjNwCYLMBkAUauAdMSMHIDgEVnKqC8/AKOZh2Do6MDAgMGwMbaWu/s6FUBTU1NyMnNQ8bRTPqfheI/SySBzc3N4devLwaGBGFgcBBcXJylNl1WzHQdDVbX1CDr2HEcJYEz6be6ukYteVxdewtFsEL6+vqgSxfduCudKaCgsBCbt27Dmexc8MzLKba2tggOCkDYszNgZmYm51Mq7+pGrTRMcXEJTp3Oli08c1xDVpR8KBW6gC50pgAVVRsoQWcKcHd3w4jht6N7924GKvo/bGl1F+C1fu78eWH+TdebcOeIUEyfOhkHk1OwJXY7OcBqg1OG1hRwICkZ38fF48LFS82EdHLqjkmPT8DihRF4b8nH4L3fkIrsJcCO6cuvYrD+i40qwrOgly5VYNWn65GUfAjhb7wGKysrQ5Jffji8a/ev2PfH/naF2rY9jma/HA+PG9tuX312kLUErly5grj4H9XmN3b7Dix4Kxz33n2H2u+czs5B9Mo1sLS01MlhSJYC0g5noL7+WjNh+NAydsxoMnVL/ETWcamiQmrPzy9AZWUV2C+oW/hY7KTDnUSWDygoKFSRY/pTk0kBo3D/yHvwyovPq7SXlpWr0Noi/PZ7gvAtDg4ObXXrcJssBdTV16sM7O7mJtFaDmhUE1HFxX/SqfGM9J6ykpySim82bRWPHjf1UZK1+itLAT1aMOWkg4ckBhMSVZ2ju5ur1M47yO5f9iAy6l18sHQ59tJsK0vigYNYu36DdPz18vJUNmn1V5YP4Bg+fufuZgz5+nhLzzY2NlKdKwED+qOJhN7xw04h2PETJ0V4rOz0VcwWnDh1WgQ8qWmHlWTxHBIcKD1rsyJLARy/e3t5Ii//rODJx9sLgwYGS/zdessgxGz+Fo2NjWL/f2LiBPxICtuzd5/U5/+VtPQj/yfB368fujk6qtC1QZC1BJiBZ5+eBtt/Z/qxRx9pxpODvT2G3z4UFhYWCHtuBi5fvgx2apqWUaNGavqK2v21ggcUFJ4Th6FpUyapDHzh4kXU1taK7W/l6nWoratT6dMWwfNmDyxa8FZbXWS1aUUB7XGQkZmF5dGr2+um0s7gx8KIufD0vFmlTVsE2UtAHUaCAwMI1vrPOarzDvcZN3aMToXnMfSiAMbzXnj+GXTrpr4jGzwoBOMffoh51GnRiwJYgh5OTpj35utqefOgwAGE/z2tdfyvJU3qxQfcOHAZHYU/Wb2WgJOiG8lSfXjoMMx4agrtHOYSTZcVvSuAham/dg2bt8Ti94RESTYbG2tMfXISQofdJtH0UekUBSgFY+g89rs4uLn1xrgHx8DevquySW+/naoAvUnZxkB6c4Jt8NCpTSYFdKr6DWDwDltAQ0Mjjh0/ifQjGWBsUFflfFERODTOyzsrDVFRUYnsnFzpuZ6AmRMnT3UIcu9QOMwBzocfrSDBq2FHGGBlVRVeCnuGQuEQiSltVDZs/AaHUtLg4XGTSLj08/XFrJkvIjX9MIGxu7BqxVKBKzAkn5uXT3HDPI2H7ZACNm2OFZcZoiLnw5ouNTDau/7zjVi29H1crb2KSpohOzs7nKVtjpnmCxDKwtgBzyBjCV272lGIfAWlZWXo5eKCMzk56EOQWq9eLigimCwh8QDmz52Dfn19UFpahrkRC8nqTig/JX7j4nciM+s4IubNaTZOs05tPGisAAY3+FbH1MmPC+H526PvH4mdu36mVHi2SITE0CHHxbkneJn8RRjA4kUR4ij8+YavxZLp2cNJoMVRkRHIzc8X0FcfyiU2NV0nwYso/J0vhOFLEympaXB3dxVKWfdpNCyIVkLK4JKSli4s4dWXw9BRzFBjH8D5PVbCjYENAx8c8FRV/SUY4z8L5ofjnagFQpB9dOLjmU88kIRIokdRmsy1d2/8smev6N/Q0IDXX3uF6Cy4o1jP/E1GlY9kZOLV2eGIXrUGZWQpyosSdYQrfEam70hocf/+ftK4mlY0VoBC8c89ntra/4ANFoATowprhRifESCFQgGeQR8vTzLxchQSaMLx/ScEikRELhYmXkaZIjP6x4UF5sLoEjs1LgyvLXl/MebMnolGsqa3310ilg+38Zh33TEC1+lfzL/IMdM1LRovAYXCSpgbz8ywoUPEeMp16evtTevxWDMeKigRwibPCuHZmzXzBVhZWgnGrSjbc/KUKhzOH2BInBMrbEn+NMPeXl4Ie3mWBKJyAubJSRPFzZGPlq9ECF2lGXLL4GZjq/OgsQL4oxMnjMey6FVY95k5nJ17CJCT/YDyLgDf6NhEfoADHN6ewt+YJYANPuszzs+MJlHK/B5KkXUxa9kI/f38sGXrd1i6LBpBgQG07eUJ6/D29kT64QwpVOa2kffeJRK0PAFKHtQRnvuYL6KibmdlP0548OUl9sx8BuAs0AOj7xPNnC3KpT2bEWEOeR98YJTYHi1pWQy5dTBKSkpxlvoM8PcjwHSYgMl5yfAdIC41NVfhRRAYO7XQ0KGEJ9aJJcROddqUyXDuyc61ATa2Ngjw7y/eYdSYcUcubjfkHQShnT9aD4YS/tiP7TviseLjD9oZ2jCaW7Y/GbzZkzPz8NBNGksGW62+qnULaHUkA23QugUYqJytsmVSQKuqMZIGkwUYyUS3KqbJAlpVjZE0mCzASCa6VTH/Bnoy/0KF7w+OAAAAAElFTkSuQmCC",
|
||||||
|
"matcherProtection": 1,
|
||||||
|
"supportedExtensions": [
|
||||||
|
{
|
||||||
|
"id": "hmac-secret",
|
||||||
|
"fail_if_unknown": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "credProtect",
|
||||||
|
"fail_if_unknown": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"cryptoStrength": 128,
|
||||||
|
"description": "OpenSK authenticator",
|
||||||
|
"authenticatorVersion": 1,
|
||||||
|
"isSecondFactorOnly": false,
|
||||||
|
"userVerificationDetails": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"userVerification": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"userVerification": 4
|
||||||
|
}
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"attachmentHint": 6,
|
||||||
|
"attestationTypes": [
|
||||||
|
15880
|
||||||
|
],
|
||||||
|
"authenticationAlgorithm": 1,
|
||||||
|
"tcDisplay": 0
|
||||||
|
}
|
||||||
@@ -32,6 +32,7 @@ cd ../..
|
|||||||
|
|
||||||
echo "Running Clippy lints..."
|
echo "Running Clippy lints..."
|
||||||
cargo clippy --all-targets --features std -- -A clippy::new_without_default -D warnings
|
cargo clippy --all-targets --features std -- -A clippy::new_without_default -D warnings
|
||||||
|
cargo clippy --all-targets --features std,with_nfc -- -A clippy::new_without_default -D warnings
|
||||||
|
|
||||||
echo "Building sha256sum tool..."
|
echo "Building sha256sum tool..."
|
||||||
cargo build --manifest-path third_party/tock/tools/sha256sum/Cargo.toml
|
cargo build --manifest-path third_party/tock/tools/sha256sum/Cargo.toml
|
||||||
@@ -54,12 +55,16 @@ cargo check --release --target=thumbv7em-none-eabi --features debug_ctap,with_ct
|
|||||||
|
|
||||||
echo "Checking that examples build properly..."
|
echo "Checking that examples build properly..."
|
||||||
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
|
||||||
|
|
||||||
echo "Checking that fuzz targets build properly..."
|
echo "Checking that fuzz targets build properly..."
|
||||||
cargo fuzz build
|
cargo fuzz build
|
||||||
cd libraries/cbor
|
cd libraries/cbor
|
||||||
cargo fuzz build
|
cargo fuzz build
|
||||||
cd ../..
|
cd ../..
|
||||||
|
cd libraries/persistent_store
|
||||||
|
cargo fuzz build
|
||||||
|
cd ../..
|
||||||
|
|
||||||
echo "Checking that CTAP2 builds and links properly (1 set of features)..."
|
echo "Checking that CTAP2 builds and links properly (1 set of features)..."
|
||||||
cargo build --release --target=thumbv7em-none-eabi --features with_ctap1
|
cargo build --release --target=thumbv7em-none-eabi --features with_ctap1
|
||||||
|
|||||||
@@ -57,8 +57,8 @@ impl Command {
|
|||||||
const AUTHENTICATOR_GET_INFO: u8 = 0x04;
|
const AUTHENTICATOR_GET_INFO: u8 = 0x04;
|
||||||
const AUTHENTICATOR_CLIENT_PIN: u8 = 0x06;
|
const AUTHENTICATOR_CLIENT_PIN: u8 = 0x06;
|
||||||
const AUTHENTICATOR_RESET: u8 = 0x07;
|
const AUTHENTICATOR_RESET: u8 = 0x07;
|
||||||
// TODO(kaczmarczyck) use or remove those constants
|
|
||||||
const AUTHENTICATOR_GET_NEXT_ASSERTION: u8 = 0x08;
|
const AUTHENTICATOR_GET_NEXT_ASSERTION: u8 = 0x08;
|
||||||
|
// TODO(kaczmarczyck) use or remove those constants
|
||||||
const AUTHENTICATOR_BIO_ENROLLMENT: u8 = 0x09;
|
const AUTHENTICATOR_BIO_ENROLLMENT: u8 = 0x09;
|
||||||
const AUTHENTICATOR_CREDENTIAL_MANAGEMENT: u8 = 0xA0;
|
const AUTHENTICATOR_CREDENTIAL_MANAGEMENT: u8 = 0xA0;
|
||||||
const AUTHENTICATOR_SELECTION: u8 = 0xB0;
|
const AUTHENTICATOR_SELECTION: u8 = 0xB0;
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use super::hid::ChannelID;
|
use super::hid::ChannelID;
|
||||||
use super::key_material::{ATTESTATION_CERTIFICATE, ATTESTATION_PRIVATE_KEY};
|
|
||||||
use super::status_code::Ctap2StatusCode;
|
use super::status_code::Ctap2StatusCode;
|
||||||
use super::CtapState;
|
use super::CtapState;
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
@@ -36,6 +35,8 @@ pub enum Ctap1StatusCode {
|
|||||||
SW_WRONG_LENGTH = 0x6700,
|
SW_WRONG_LENGTH = 0x6700,
|
||||||
SW_CLA_NOT_SUPPORTED = 0x6E00,
|
SW_CLA_NOT_SUPPORTED = 0x6E00,
|
||||||
SW_INS_NOT_SUPPORTED = 0x6D00,
|
SW_INS_NOT_SUPPORTED = 0x6D00,
|
||||||
|
SW_MEMERR = 0x6501,
|
||||||
|
SW_COMMAND_ABORTED = 0x6F00,
|
||||||
SW_VENDOR_KEY_HANDLE_TOO_LONG = 0xF000,
|
SW_VENDOR_KEY_HANDLE_TOO_LONG = 0xF000,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,6 +51,8 @@ impl TryFrom<u16> for Ctap1StatusCode {
|
|||||||
0x6700 => Ok(Ctap1StatusCode::SW_WRONG_LENGTH),
|
0x6700 => Ok(Ctap1StatusCode::SW_WRONG_LENGTH),
|
||||||
0x6E00 => Ok(Ctap1StatusCode::SW_CLA_NOT_SUPPORTED),
|
0x6E00 => Ok(Ctap1StatusCode::SW_CLA_NOT_SUPPORTED),
|
||||||
0x6D00 => Ok(Ctap1StatusCode::SW_INS_NOT_SUPPORTED),
|
0x6D00 => Ok(Ctap1StatusCode::SW_INS_NOT_SUPPORTED),
|
||||||
|
0x6501 => Ok(Ctap1StatusCode::SW_MEMERR),
|
||||||
|
0x6F00 => Ok(Ctap1StatusCode::SW_COMMAND_ABORTED),
|
||||||
0xF000 => Ok(Ctap1StatusCode::SW_VENDOR_KEY_HANDLE_TOO_LONG),
|
0xF000 => Ok(Ctap1StatusCode::SW_VENDOR_KEY_HANDLE_TOO_LONG),
|
||||||
_ => Err(()),
|
_ => Err(()),
|
||||||
}
|
}
|
||||||
@@ -288,21 +291,31 @@ impl Ctap1Command {
|
|||||||
let sk = crypto::ecdsa::SecKey::gensk(ctap_state.rng);
|
let sk = crypto::ecdsa::SecKey::gensk(ctap_state.rng);
|
||||||
let pk = sk.genpk();
|
let pk = sk.genpk();
|
||||||
let key_handle = ctap_state
|
let key_handle = ctap_state
|
||||||
.encrypt_key_handle(sk, &application)
|
.encrypt_key_handle(sk, &application, None)
|
||||||
.map_err(|_| Ctap1StatusCode::SW_VENDOR_KEY_HANDLE_TOO_LONG)?;
|
.map_err(|_| Ctap1StatusCode::SW_COMMAND_ABORTED)?;
|
||||||
if key_handle.len() > 0xFF {
|
if key_handle.len() > 0xFF {
|
||||||
// This is just being defensive with unreachable code.
|
// This is just being defensive with unreachable code.
|
||||||
return Err(Ctap1StatusCode::SW_VENDOR_KEY_HANDLE_TOO_LONG);
|
return Err(Ctap1StatusCode::SW_VENDOR_KEY_HANDLE_TOO_LONG);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut response =
|
let certificate = ctap_state
|
||||||
Vec::with_capacity(105 + key_handle.len() + ATTESTATION_CERTIFICATE.len());
|
.persistent_store
|
||||||
|
.attestation_certificate()
|
||||||
|
.map_err(|_| Ctap1StatusCode::SW_MEMERR)?
|
||||||
|
.ok_or(Ctap1StatusCode::SW_COMMAND_ABORTED)?;
|
||||||
|
let private_key = ctap_state
|
||||||
|
.persistent_store
|
||||||
|
.attestation_private_key()
|
||||||
|
.map_err(|_| Ctap1StatusCode::SW_MEMERR)?
|
||||||
|
.ok_or(Ctap1StatusCode::SW_COMMAND_ABORTED)?;
|
||||||
|
|
||||||
|
let mut response = Vec::with_capacity(105 + key_handle.len() + certificate.len());
|
||||||
response.push(Ctap1Command::LEGACY_BYTE);
|
response.push(Ctap1Command::LEGACY_BYTE);
|
||||||
let user_pk = pk.to_uncompressed();
|
let user_pk = pk.to_uncompressed();
|
||||||
response.extend_from_slice(&user_pk);
|
response.extend_from_slice(&user_pk);
|
||||||
response.push(key_handle.len() as u8);
|
response.push(key_handle.len() as u8);
|
||||||
response.extend(key_handle.clone());
|
response.extend(key_handle.clone());
|
||||||
response.extend_from_slice(&ATTESTATION_CERTIFICATE);
|
response.extend_from_slice(&certificate);
|
||||||
|
|
||||||
// The first byte is reserved.
|
// The first byte is reserved.
|
||||||
let mut signature_data = Vec::with_capacity(66 + key_handle.len());
|
let mut signature_data = Vec::with_capacity(66 + key_handle.len());
|
||||||
@@ -312,7 +325,7 @@ impl Ctap1Command {
|
|||||||
signature_data.extend(key_handle);
|
signature_data.extend(key_handle);
|
||||||
signature_data.extend_from_slice(&user_pk);
|
signature_data.extend_from_slice(&user_pk);
|
||||||
|
|
||||||
let attestation_key = crypto::ecdsa::SecKey::from_bytes(ATTESTATION_PRIVATE_KEY).unwrap();
|
let attestation_key = crypto::ecdsa::SecKey::from_bytes(private_key).unwrap();
|
||||||
let signature = attestation_key.sign_rfc6979::<crypto::sha256::Sha256>(&signature_data);
|
let signature = attestation_key.sign_rfc6979::<crypto::sha256::Sha256>(&signature_data);
|
||||||
|
|
||||||
response.extend(signature.to_asn1_der());
|
response.extend(signature.to_asn1_der());
|
||||||
@@ -373,7 +386,7 @@ impl Ctap1Command {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::super::{ENCRYPTED_CREDENTIAL_ID_SIZE, USE_SIGNATURE_COUNTER};
|
use super::super::{key_material, CREDENTIAL_ID_BASE_SIZE, USE_SIGNATURE_COUNTER};
|
||||||
use super::*;
|
use super::*;
|
||||||
use crypto::rng256::ThreadRng256;
|
use crypto::rng256::ThreadRng256;
|
||||||
use crypto::Hash256;
|
use crypto::Hash256;
|
||||||
@@ -413,12 +426,12 @@ mod test {
|
|||||||
0x00,
|
0x00,
|
||||||
0x00,
|
0x00,
|
||||||
0x00,
|
0x00,
|
||||||
65 + ENCRYPTED_CREDENTIAL_ID_SIZE as u8,
|
65 + CREDENTIAL_ID_BASE_SIZE as u8,
|
||||||
];
|
];
|
||||||
let challenge = [0x0C; 32];
|
let challenge = [0x0C; 32];
|
||||||
message.extend(&challenge);
|
message.extend(&challenge);
|
||||||
message.extend(application);
|
message.extend(application);
|
||||||
message.push(ENCRYPTED_CREDENTIAL_ID_SIZE as u8);
|
message.push(CREDENTIAL_ID_BASE_SIZE as u8);
|
||||||
message.extend(key_handle);
|
message.extend(key_handle);
|
||||||
message
|
message
|
||||||
}
|
}
|
||||||
@@ -427,28 +440,49 @@ mod test {
|
|||||||
fn test_process_register() {
|
fn test_process_register() {
|
||||||
let mut rng = ThreadRng256 {};
|
let mut rng = ThreadRng256 {};
|
||||||
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
|
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
|
||||||
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence);
|
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
|
||||||
|
|
||||||
let application = [0x0A; 32];
|
let application = [0x0A; 32];
|
||||||
let message = create_register_message(&application);
|
let message = create_register_message(&application);
|
||||||
ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE);
|
ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE);
|
||||||
ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE);
|
ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE);
|
||||||
|
let response = Ctap1Command::process_command(&message, &mut ctap_state, START_CLOCK_VALUE);
|
||||||
|
// Certificate and private key are missing
|
||||||
|
assert_eq!(response, Err(Ctap1StatusCode::SW_COMMAND_ABORTED));
|
||||||
|
|
||||||
|
let fake_key = [0x41u8; key_material::ATTESTATION_PRIVATE_KEY_LENGTH];
|
||||||
|
assert!(ctap_state
|
||||||
|
.persistent_store
|
||||||
|
.set_attestation_private_key(&fake_key)
|
||||||
|
.is_ok());
|
||||||
|
ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE);
|
||||||
|
ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE);
|
||||||
|
let response = Ctap1Command::process_command(&message, &mut ctap_state, START_CLOCK_VALUE);
|
||||||
|
// Certificate is still missing
|
||||||
|
assert_eq!(response, Err(Ctap1StatusCode::SW_COMMAND_ABORTED));
|
||||||
|
|
||||||
|
let fake_cert = [0x99u8; 100]; // Arbitrary length
|
||||||
|
assert!(ctap_state
|
||||||
|
.persistent_store
|
||||||
|
.set_attestation_certificate(&fake_cert[..])
|
||||||
|
.is_ok());
|
||||||
|
ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE);
|
||||||
|
ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE);
|
||||||
let response =
|
let response =
|
||||||
Ctap1Command::process_command(&message, &mut ctap_state, START_CLOCK_VALUE).unwrap();
|
Ctap1Command::process_command(&message, &mut ctap_state, START_CLOCK_VALUE).unwrap();
|
||||||
|
|
||||||
assert_eq!(response[0], Ctap1Command::LEGACY_BYTE);
|
assert_eq!(response[0], Ctap1Command::LEGACY_BYTE);
|
||||||
assert_eq!(response[66], ENCRYPTED_CREDENTIAL_ID_SIZE as u8);
|
assert_eq!(response[66], CREDENTIAL_ID_BASE_SIZE as u8);
|
||||||
assert!(ctap_state
|
assert!(ctap_state
|
||||||
.decrypt_credential_source(
|
.decrypt_credential_source(
|
||||||
response[67..67 + ENCRYPTED_CREDENTIAL_ID_SIZE].to_vec(),
|
response[67..67 + CREDENTIAL_ID_BASE_SIZE].to_vec(),
|
||||||
&application
|
&application
|
||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.is_some());
|
.is_some());
|
||||||
const CERT_START: usize = 67 + ENCRYPTED_CREDENTIAL_ID_SIZE;
|
const CERT_START: usize = 67 + CREDENTIAL_ID_BASE_SIZE;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&response[CERT_START..CERT_START + ATTESTATION_CERTIFICATE.len()],
|
&response[CERT_START..CERT_START + fake_cert.len()],
|
||||||
&ATTESTATION_CERTIFICATE[..]
|
&fake_cert[..]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -456,7 +490,7 @@ mod test {
|
|||||||
fn test_process_register_bad_message() {
|
fn test_process_register_bad_message() {
|
||||||
let mut rng = ThreadRng256 {};
|
let mut rng = ThreadRng256 {};
|
||||||
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
|
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
|
||||||
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence);
|
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
|
||||||
|
|
||||||
let application = [0x0A; 32];
|
let application = [0x0A; 32];
|
||||||
let message = create_register_message(&application);
|
let message = create_register_message(&application);
|
||||||
@@ -476,7 +510,7 @@ mod test {
|
|||||||
|
|
||||||
let mut rng = ThreadRng256 {};
|
let mut rng = ThreadRng256 {};
|
||||||
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
|
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
|
||||||
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence);
|
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
|
||||||
|
|
||||||
ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE);
|
ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE);
|
||||||
ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE);
|
ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE);
|
||||||
@@ -490,11 +524,13 @@ mod test {
|
|||||||
let mut rng = ThreadRng256 {};
|
let mut rng = ThreadRng256 {};
|
||||||
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
|
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
|
||||||
let sk = crypto::ecdsa::SecKey::gensk(&mut rng);
|
let sk = crypto::ecdsa::SecKey::gensk(&mut rng);
|
||||||
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence);
|
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
|
||||||
|
|
||||||
let rp_id = "example.com";
|
let rp_id = "example.com";
|
||||||
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
||||||
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
|
let key_handle = ctap_state
|
||||||
|
.encrypt_key_handle(sk, &application, None)
|
||||||
|
.unwrap();
|
||||||
let message = create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
|
let message = create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
|
||||||
|
|
||||||
let response = Ctap1Command::process_command(&message, &mut ctap_state, START_CLOCK_VALUE);
|
let response = Ctap1Command::process_command(&message, &mut ctap_state, START_CLOCK_VALUE);
|
||||||
@@ -506,11 +542,13 @@ mod test {
|
|||||||
let mut rng = ThreadRng256 {};
|
let mut rng = ThreadRng256 {};
|
||||||
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
|
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
|
||||||
let sk = crypto::ecdsa::SecKey::gensk(&mut rng);
|
let sk = crypto::ecdsa::SecKey::gensk(&mut rng);
|
||||||
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence);
|
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
|
||||||
|
|
||||||
let rp_id = "example.com";
|
let rp_id = "example.com";
|
||||||
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
||||||
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
|
let key_handle = ctap_state
|
||||||
|
.encrypt_key_handle(sk, &application, None)
|
||||||
|
.unwrap();
|
||||||
let application = [0x55; 32];
|
let application = [0x55; 32];
|
||||||
let message = create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
|
let message = create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
|
||||||
|
|
||||||
@@ -523,11 +561,13 @@ mod test {
|
|||||||
let mut rng = ThreadRng256 {};
|
let mut rng = ThreadRng256 {};
|
||||||
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
|
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
|
||||||
let sk = crypto::ecdsa::SecKey::gensk(&mut rng);
|
let sk = crypto::ecdsa::SecKey::gensk(&mut rng);
|
||||||
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence);
|
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
|
||||||
|
|
||||||
let rp_id = "example.com";
|
let rp_id = "example.com";
|
||||||
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
||||||
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
|
let key_handle = ctap_state
|
||||||
|
.encrypt_key_handle(sk, &application, None)
|
||||||
|
.unwrap();
|
||||||
let mut message =
|
let mut message =
|
||||||
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
|
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
|
||||||
|
|
||||||
@@ -547,11 +587,13 @@ mod test {
|
|||||||
let mut rng = ThreadRng256 {};
|
let mut rng = ThreadRng256 {};
|
||||||
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
|
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
|
||||||
let sk = crypto::ecdsa::SecKey::gensk(&mut rng);
|
let sk = crypto::ecdsa::SecKey::gensk(&mut rng);
|
||||||
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence);
|
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
|
||||||
|
|
||||||
let rp_id = "example.com";
|
let rp_id = "example.com";
|
||||||
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
||||||
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
|
let key_handle = ctap_state
|
||||||
|
.encrypt_key_handle(sk, &application, None)
|
||||||
|
.unwrap();
|
||||||
let mut message =
|
let mut message =
|
||||||
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
|
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
|
||||||
message[0] = 0xEE;
|
message[0] = 0xEE;
|
||||||
@@ -565,11 +607,13 @@ mod test {
|
|||||||
let mut rng = ThreadRng256 {};
|
let mut rng = ThreadRng256 {};
|
||||||
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
|
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
|
||||||
let sk = crypto::ecdsa::SecKey::gensk(&mut rng);
|
let sk = crypto::ecdsa::SecKey::gensk(&mut rng);
|
||||||
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence);
|
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
|
||||||
|
|
||||||
let rp_id = "example.com";
|
let rp_id = "example.com";
|
||||||
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
||||||
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
|
let key_handle = ctap_state
|
||||||
|
.encrypt_key_handle(sk, &application, None)
|
||||||
|
.unwrap();
|
||||||
let mut message =
|
let mut message =
|
||||||
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
|
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
|
||||||
message[1] = 0xEE;
|
message[1] = 0xEE;
|
||||||
@@ -583,11 +627,13 @@ mod test {
|
|||||||
let mut rng = ThreadRng256 {};
|
let mut rng = ThreadRng256 {};
|
||||||
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
|
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
|
||||||
let sk = crypto::ecdsa::SecKey::gensk(&mut rng);
|
let sk = crypto::ecdsa::SecKey::gensk(&mut rng);
|
||||||
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence);
|
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
|
||||||
|
|
||||||
let rp_id = "example.com";
|
let rp_id = "example.com";
|
||||||
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
||||||
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
|
let key_handle = ctap_state
|
||||||
|
.encrypt_key_handle(sk, &application, None)
|
||||||
|
.unwrap();
|
||||||
let mut message =
|
let mut message =
|
||||||
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
|
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
|
||||||
message[2] = 0xEE;
|
message[2] = 0xEE;
|
||||||
@@ -601,11 +647,13 @@ mod test {
|
|||||||
let mut rng = ThreadRng256 {};
|
let mut rng = ThreadRng256 {};
|
||||||
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
|
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
|
||||||
let sk = crypto::ecdsa::SecKey::gensk(&mut rng);
|
let sk = crypto::ecdsa::SecKey::gensk(&mut rng);
|
||||||
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence);
|
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
|
||||||
|
|
||||||
let rp_id = "example.com";
|
let rp_id = "example.com";
|
||||||
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
||||||
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
|
let key_handle = ctap_state
|
||||||
|
.encrypt_key_handle(sk, &application, None)
|
||||||
|
.unwrap();
|
||||||
let message =
|
let message =
|
||||||
create_authenticate_message(&application, Ctap1Flags::EnforceUpAndSign, &key_handle);
|
create_authenticate_message(&application, Ctap1Flags::EnforceUpAndSign, &key_handle);
|
||||||
|
|
||||||
@@ -626,11 +674,13 @@ mod test {
|
|||||||
let mut rng = ThreadRng256 {};
|
let mut rng = ThreadRng256 {};
|
||||||
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
|
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
|
||||||
let sk = crypto::ecdsa::SecKey::gensk(&mut rng);
|
let sk = crypto::ecdsa::SecKey::gensk(&mut rng);
|
||||||
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence);
|
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
|
||||||
|
|
||||||
let rp_id = "example.com";
|
let rp_id = "example.com";
|
||||||
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
||||||
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
|
let key_handle = ctap_state
|
||||||
|
.encrypt_key_handle(sk, &application, None)
|
||||||
|
.unwrap();
|
||||||
let message = create_authenticate_message(
|
let message = create_authenticate_message(
|
||||||
&application,
|
&application,
|
||||||
Ctap1Flags::DontEnforceUpAndSign,
|
Ctap1Flags::DontEnforceUpAndSign,
|
||||||
@@ -650,13 +700,13 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_process_authenticate_bad_key_handle() {
|
fn test_process_authenticate_bad_key_handle() {
|
||||||
let application = [0x0A; 32];
|
let application = [0x0A; 32];
|
||||||
let key_handle = vec![0x00; ENCRYPTED_CREDENTIAL_ID_SIZE];
|
let key_handle = vec![0x00; CREDENTIAL_ID_BASE_SIZE];
|
||||||
let message =
|
let message =
|
||||||
create_authenticate_message(&application, Ctap1Flags::EnforceUpAndSign, &key_handle);
|
create_authenticate_message(&application, Ctap1Flags::EnforceUpAndSign, &key_handle);
|
||||||
|
|
||||||
let mut rng = ThreadRng256 {};
|
let mut rng = ThreadRng256 {};
|
||||||
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
|
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
|
||||||
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence);
|
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
|
||||||
|
|
||||||
ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE);
|
ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE);
|
||||||
ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE);
|
ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE);
|
||||||
@@ -667,13 +717,13 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_process_authenticate_without_up() {
|
fn test_process_authenticate_without_up() {
|
||||||
let application = [0x0A; 32];
|
let application = [0x0A; 32];
|
||||||
let key_handle = vec![0x00; ENCRYPTED_CREDENTIAL_ID_SIZE];
|
let key_handle = vec![0x00; CREDENTIAL_ID_BASE_SIZE];
|
||||||
let message =
|
let message =
|
||||||
create_authenticate_message(&application, Ctap1Flags::EnforceUpAndSign, &key_handle);
|
create_authenticate_message(&application, Ctap1Flags::EnforceUpAndSign, &key_handle);
|
||||||
|
|
||||||
let mut rng = ThreadRng256 {};
|
let mut rng = ThreadRng256 {};
|
||||||
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
|
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
|
||||||
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence);
|
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
|
||||||
|
|
||||||
ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE);
|
ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE);
|
||||||
ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE);
|
ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE);
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialRpEntity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialuserentity
|
// https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialuserentity
|
||||||
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
|
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Clone, Debug, PartialEq))]
|
||||||
pub struct PublicKeyCredentialUserEntity {
|
pub struct PublicKeyCredentialUserEntity {
|
||||||
pub user_id: Vec<u8>,
|
pub user_id: Vec<u8>,
|
||||||
pub user_name: Option<String>,
|
pub user_name: Option<String>,
|
||||||
@@ -308,7 +308,8 @@ impl TryFrom<cbor::Value> for GetAssertionExtensions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Clone, Debug, PartialEq))]
|
#[derive(Clone)]
|
||||||
|
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
|
||||||
pub struct GetAssertionHmacSecretInput {
|
pub struct GetAssertionHmacSecretInput {
|
||||||
pub key_agreement: CoseKey,
|
pub key_agreement: CoseKey,
|
||||||
pub salt_enc: Vec<u8>,
|
pub salt_enc: Vec<u8>,
|
||||||
@@ -361,10 +362,8 @@ impl TryFrom<cbor::Value> for MakeCredentialOptions {
|
|||||||
Some(options_entry) => extract_bool(options_entry)?,
|
Some(options_entry) => extract_bool(options_entry)?,
|
||||||
None => false,
|
None => false,
|
||||||
};
|
};
|
||||||
if let Some(options_entry) = up {
|
if up.is_some() {
|
||||||
if !extract_bool(options_entry)? {
|
return Err(Ctap2StatusCode::CTAP2_ERR_INVALID_OPTION);
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_INVALID_OPTION);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
let uv = match uv {
|
let uv = match uv {
|
||||||
Some(options_entry) => extract_bool(options_entry)?,
|
Some(options_entry) => extract_bool(options_entry)?,
|
||||||
@@ -498,9 +497,12 @@ pub struct PublicKeyCredentialSource {
|
|||||||
pub private_key: ecdsa::SecKey, // TODO(kaczmarczyck) open for other algorithms
|
pub private_key: ecdsa::SecKey, // TODO(kaczmarczyck) open for other algorithms
|
||||||
pub rp_id: String,
|
pub rp_id: String,
|
||||||
pub user_handle: Vec<u8>, // not optional, but nullable
|
pub user_handle: Vec<u8>, // not optional, but nullable
|
||||||
pub other_ui: Option<String>,
|
pub user_display_name: Option<String>,
|
||||||
pub cred_random: Option<Vec<u8>>,
|
pub cred_random: Option<Vec<u8>>,
|
||||||
pub cred_protect_policy: Option<CredentialProtectionPolicy>,
|
pub cred_protect_policy: Option<CredentialProtectionPolicy>,
|
||||||
|
pub creation_order: u64,
|
||||||
|
pub user_name: Option<String>,
|
||||||
|
pub user_icon: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// We serialize credentials for the persistent storage using CBOR maps. Each field of a credential
|
// We serialize credentials for the persistent storage using CBOR maps. Each field of a credential
|
||||||
@@ -510,9 +512,12 @@ enum PublicKeyCredentialSourceField {
|
|||||||
PrivateKey = 1,
|
PrivateKey = 1,
|
||||||
RpId = 2,
|
RpId = 2,
|
||||||
UserHandle = 3,
|
UserHandle = 3,
|
||||||
OtherUi = 4,
|
UserDisplayName = 4,
|
||||||
CredRandom = 5,
|
CredRandom = 5,
|
||||||
CredProtectPolicy = 6,
|
CredProtectPolicy = 6,
|
||||||
|
CreationOrder = 7,
|
||||||
|
UserName = 8,
|
||||||
|
UserIcon = 9,
|
||||||
// When a field is removed, its tag should be reserved and not used for new fields. We document
|
// When a field is removed, its tag should be reserved and not used for new fields. We document
|
||||||
// those reserved tags below.
|
// those reserved tags below.
|
||||||
// Reserved tags: none.
|
// Reserved tags: none.
|
||||||
@@ -533,9 +538,12 @@ impl From<PublicKeyCredentialSource> for cbor::Value {
|
|||||||
PublicKeyCredentialSourceField::PrivateKey => Some(private_key.to_vec()),
|
PublicKeyCredentialSourceField::PrivateKey => Some(private_key.to_vec()),
|
||||||
PublicKeyCredentialSourceField::RpId => Some(credential.rp_id),
|
PublicKeyCredentialSourceField::RpId => Some(credential.rp_id),
|
||||||
PublicKeyCredentialSourceField::UserHandle => Some(credential.user_handle),
|
PublicKeyCredentialSourceField::UserHandle => Some(credential.user_handle),
|
||||||
PublicKeyCredentialSourceField::OtherUi => credential.other_ui,
|
PublicKeyCredentialSourceField::UserDisplayName => credential.user_display_name,
|
||||||
PublicKeyCredentialSourceField::CredRandom => credential.cred_random,
|
PublicKeyCredentialSourceField::CredRandom => credential.cred_random,
|
||||||
PublicKeyCredentialSourceField::CredProtectPolicy => credential.cred_protect_policy,
|
PublicKeyCredentialSourceField::CredProtectPolicy => credential.cred_protect_policy,
|
||||||
|
PublicKeyCredentialSourceField::CreationOrder => credential.creation_order,
|
||||||
|
PublicKeyCredentialSourceField::UserName => credential.user_name,
|
||||||
|
PublicKeyCredentialSourceField::UserIcon => credential.user_icon,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -550,9 +558,12 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialSource {
|
|||||||
PublicKeyCredentialSourceField::PrivateKey => private_key,
|
PublicKeyCredentialSourceField::PrivateKey => private_key,
|
||||||
PublicKeyCredentialSourceField::RpId => rp_id,
|
PublicKeyCredentialSourceField::RpId => rp_id,
|
||||||
PublicKeyCredentialSourceField::UserHandle => user_handle,
|
PublicKeyCredentialSourceField::UserHandle => user_handle,
|
||||||
PublicKeyCredentialSourceField::OtherUi => other_ui,
|
PublicKeyCredentialSourceField::UserDisplayName => user_display_name,
|
||||||
PublicKeyCredentialSourceField::CredRandom => cred_random,
|
PublicKeyCredentialSourceField::CredRandom => cred_random,
|
||||||
PublicKeyCredentialSourceField::CredProtectPolicy => cred_protect_policy,
|
PublicKeyCredentialSourceField::CredProtectPolicy => cred_protect_policy,
|
||||||
|
PublicKeyCredentialSourceField::CreationOrder => creation_order,
|
||||||
|
PublicKeyCredentialSourceField::UserName => user_name,
|
||||||
|
PublicKeyCredentialSourceField::UserIcon => user_icon,
|
||||||
} = extract_map(cbor_value)?;
|
} = extract_map(cbor_value)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -565,11 +576,14 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialSource {
|
|||||||
.ok_or(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR)?;
|
.ok_or(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR)?;
|
||||||
let rp_id = extract_text_string(ok_or_missing(rp_id)?)?;
|
let rp_id = extract_text_string(ok_or_missing(rp_id)?)?;
|
||||||
let user_handle = extract_byte_string(ok_or_missing(user_handle)?)?;
|
let user_handle = extract_byte_string(ok_or_missing(user_handle)?)?;
|
||||||
let other_ui = other_ui.map(extract_text_string).transpose()?;
|
let user_display_name = user_display_name.map(extract_text_string).transpose()?;
|
||||||
let cred_random = cred_random.map(extract_byte_string).transpose()?;
|
let cred_random = cred_random.map(extract_byte_string).transpose()?;
|
||||||
let cred_protect_policy = cred_protect_policy
|
let cred_protect_policy = cred_protect_policy
|
||||||
.map(CredentialProtectionPolicy::try_from)
|
.map(CredentialProtectionPolicy::try_from)
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
|
let creation_order = creation_order.map(extract_unsigned).unwrap_or(Ok(0))?;
|
||||||
|
let user_name = user_name.map(extract_text_string).transpose()?;
|
||||||
|
let user_icon = user_icon.map(extract_text_string).transpose()?;
|
||||||
// We don't return whether there were unknown fields in the CBOR value. This means that
|
// We don't return whether there were unknown fields in the CBOR value. This means that
|
||||||
// deserialization is not injective. In particular deserialization is only an inverse of
|
// deserialization is not injective. In particular deserialization is only an inverse of
|
||||||
// serialization at a given version of OpenSK. This is not a problem because:
|
// serialization at a given version of OpenSK. This is not a problem because:
|
||||||
@@ -586,9 +600,12 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialSource {
|
|||||||
private_key,
|
private_key,
|
||||||
rp_id,
|
rp_id,
|
||||||
user_handle,
|
user_handle,
|
||||||
other_ui,
|
user_display_name,
|
||||||
cred_random,
|
cred_random,
|
||||||
cred_protect_policy,
|
cred_protect_policy,
|
||||||
|
creation_order,
|
||||||
|
user_name,
|
||||||
|
user_icon,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -605,7 +622,8 @@ impl PublicKeyCredentialSource {
|
|||||||
// TODO(kaczmarczyck) we could decide to split this data type up
|
// TODO(kaczmarczyck) we could decide to split this data type up
|
||||||
// It depends on the algorithm though, I think.
|
// It depends on the algorithm though, I think.
|
||||||
// So before creating a mess, this is my workaround.
|
// So before creating a mess, this is my workaround.
|
||||||
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Clone, Debug, PartialEq))]
|
#[derive(Clone)]
|
||||||
|
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
|
||||||
pub struct CoseKey(pub BTreeMap<cbor::KeyType, cbor::Value>);
|
pub struct CoseKey(pub BTreeMap<cbor::KeyType, cbor::Value>);
|
||||||
|
|
||||||
// This is the algorithm specifier that is supposed to be used in a COSE key
|
// This is the algorithm specifier that is supposed to be used in a COSE key
|
||||||
@@ -1354,9 +1372,12 @@ mod test {
|
|||||||
private_key: crypto::ecdsa::SecKey::gensk(&mut rng),
|
private_key: crypto::ecdsa::SecKey::gensk(&mut rng),
|
||||||
rp_id: "example.com".to_string(),
|
rp_id: "example.com".to_string(),
|
||||||
user_handle: b"foo".to_vec(),
|
user_handle: b"foo".to_vec(),
|
||||||
other_ui: None,
|
user_display_name: None,
|
||||||
cred_random: None,
|
cred_random: None,
|
||||||
cred_protect_policy: None,
|
cred_protect_policy: None,
|
||||||
|
creation_order: 0,
|
||||||
|
user_name: None,
|
||||||
|
user_icon: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -1365,7 +1386,7 @@ mod test {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let credential = PublicKeyCredentialSource {
|
let credential = PublicKeyCredentialSource {
|
||||||
other_ui: Some("other".to_string()),
|
user_display_name: Some("Display Name".to_string()),
|
||||||
..credential
|
..credential
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1389,6 +1410,26 @@ mod test {
|
|||||||
..credential
|
..credential
|
||||||
};
|
};
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
PublicKeyCredentialSource::try_from(cbor::Value::from(credential.clone())),
|
||||||
|
Ok(credential.clone())
|
||||||
|
);
|
||||||
|
|
||||||
|
let credential = PublicKeyCredentialSource {
|
||||||
|
user_name: Some("name".to_string()),
|
||||||
|
..credential
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
PublicKeyCredentialSource::try_from(cbor::Value::from(credential.clone())),
|
||||||
|
Ok(credential.clone())
|
||||||
|
);
|
||||||
|
|
||||||
|
let credential = PublicKeyCredentialSource {
|
||||||
|
user_icon: Some("icon".to_string()),
|
||||||
|
..credential
|
||||||
|
};
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
PublicKeyCredentialSource::try_from(cbor::Value::from(credential.clone())),
|
PublicKeyCredentialSource::try_from(cbor::Value::from(credential.clone())),
|
||||||
Ok(credential)
|
Ok(credential)
|
||||||
|
|||||||
@@ -200,7 +200,8 @@ impl CtapHid {
|
|||||||
// Each transaction is atomic, so we process the command directly here and
|
// Each transaction is atomic, so we process the command directly here and
|
||||||
// don't handle any other packet in the meantime.
|
// don't handle any other packet in the meantime.
|
||||||
// TODO: Send keep-alive packets in the meantime.
|
// TODO: Send keep-alive packets in the meantime.
|
||||||
let response = ctap_state.process_command(&message.payload, cid);
|
let response =
|
||||||
|
ctap_state.process_command(&message.payload, cid, clock_value);
|
||||||
if let Some(iterator) = CtapHid::split_message(Message {
|
if let Some(iterator) = CtapHid::split_message(Message {
|
||||||
cid,
|
cid,
|
||||||
cmd: CtapHid::COMMAND_CBOR,
|
cmd: CtapHid::COMMAND_CBOR,
|
||||||
@@ -227,44 +228,35 @@ impl CtapHid {
|
|||||||
}
|
}
|
||||||
// CTAP specification (version 20190130) section 8.1.9.1.3
|
// CTAP specification (version 20190130) section 8.1.9.1.3
|
||||||
CtapHid::COMMAND_INIT => {
|
CtapHid::COMMAND_INIT => {
|
||||||
if cid == CtapHid::CHANNEL_BROADCAST {
|
if message.payload.len() != 8 {
|
||||||
if message.payload.len() != 8 {
|
return CtapHid::error_message(cid, CtapHid::ERR_INVALID_LEN);
|
||||||
return CtapHid::error_message(cid, CtapHid::ERR_INVALID_LEN);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
let new_cid = if cid == CtapHid::CHANNEL_BROADCAST {
|
||||||
// TODO: Prevent allocating 2^32 channels.
|
// TODO: Prevent allocating 2^32 channels.
|
||||||
self.allocated_cids += 1;
|
self.allocated_cids += 1;
|
||||||
let allocated_cid = (self.allocated_cids as u32).to_ne_bytes();
|
(self.allocated_cids as u32).to_ne_bytes()
|
||||||
|
|
||||||
let mut payload = vec![0; 17];
|
|
||||||
payload[..8].copy_from_slice(&message.payload);
|
|
||||||
payload[8..12].copy_from_slice(&allocated_cid);
|
|
||||||
payload[12] = CtapHid::PROTOCOL_VERSION;
|
|
||||||
payload[13] = CtapHid::DEVICE_VERSION_MAJOR;
|
|
||||||
payload[14] = CtapHid::DEVICE_VERSION_MINOR;
|
|
||||||
payload[15] = CtapHid::DEVICE_VERSION_BUILD;
|
|
||||||
payload[16] = CtapHid::CAPABILITIES;
|
|
||||||
|
|
||||||
// This unwrap is safe because the payload length is 17 <= 7609 bytes.
|
|
||||||
CtapHid::split_message(Message {
|
|
||||||
cid,
|
|
||||||
cmd: CtapHid::COMMAND_INIT,
|
|
||||||
payload,
|
|
||||||
})
|
|
||||||
.unwrap()
|
|
||||||
} else {
|
} else {
|
||||||
// Sync the channel and discard the current transaction.
|
// Sync the channel and discard the current transaction.
|
||||||
// TODO: The specification (version 20190130) wording isn't clear about
|
cid
|
||||||
// the payload format in this case.
|
};
|
||||||
//
|
|
||||||
// This unwrap is safe because the payload length is 0 <= 7609 bytes.
|
let mut payload = vec![0; 17];
|
||||||
CtapHid::split_message(Message {
|
payload[..8].copy_from_slice(&message.payload);
|
||||||
cid,
|
payload[8..12].copy_from_slice(&new_cid);
|
||||||
cmd: CtapHid::COMMAND_INIT,
|
payload[12] = CtapHid::PROTOCOL_VERSION;
|
||||||
payload: vec![],
|
payload[13] = CtapHid::DEVICE_VERSION_MAJOR;
|
||||||
})
|
payload[14] = CtapHid::DEVICE_VERSION_MINOR;
|
||||||
.unwrap()
|
payload[15] = CtapHid::DEVICE_VERSION_BUILD;
|
||||||
}
|
payload[16] = CtapHid::CAPABILITIES;
|
||||||
|
|
||||||
|
// This unwrap is safe because the payload length is 17 <= 7609 bytes.
|
||||||
|
CtapHid::split_message(Message {
|
||||||
|
cid,
|
||||||
|
cmd: CtapHid::COMMAND_INIT,
|
||||||
|
payload,
|
||||||
|
})
|
||||||
|
.unwrap()
|
||||||
}
|
}
|
||||||
// CTAP specification (version 20190130) section 8.1.9.1.4
|
// CTAP specification (version 20190130) section 8.1.9.1.4
|
||||||
CtapHid::COMMAND_PING => {
|
CtapHid::COMMAND_PING => {
|
||||||
@@ -307,7 +299,9 @@ impl CtapHid {
|
|||||||
HidPacketIterator::none()
|
HidPacketIterator::none()
|
||||||
}
|
}
|
||||||
Err((cid, error)) => {
|
Err((cid, error)) => {
|
||||||
if !self.is_allocated_channel(cid) {
|
if !self.is_allocated_channel(cid)
|
||||||
|
&& error != receive::Error::UnexpectedContinuation
|
||||||
|
{
|
||||||
CtapHid::error_message(cid, CtapHid::ERR_INVALID_CHANNEL)
|
CtapHid::error_message(cid, CtapHid::ERR_INVALID_CHANNEL)
|
||||||
} else {
|
} else {
|
||||||
match error {
|
match error {
|
||||||
@@ -523,11 +517,32 @@ mod test {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_spurious_continuation_packet() {
|
||||||
|
let mut rng = ThreadRng256 {};
|
||||||
|
let user_immediately_present = |_| Ok(());
|
||||||
|
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
|
||||||
|
let mut ctap_hid = CtapHid::new();
|
||||||
|
|
||||||
|
let mut packet = [0x00; 64];
|
||||||
|
packet[0..7].copy_from_slice(&[0xC1, 0xC1, 0xC1, 0xC1, 0x00, 0x51, 0x51]);
|
||||||
|
let mut assembler_reply = MessageAssembler::new();
|
||||||
|
for pkt_reply in ctap_hid.process_hid_packet(&packet, DUMMY_CLOCK_VALUE, &mut ctap_state) {
|
||||||
|
// Continuation packets are silently ignored.
|
||||||
|
assert_eq!(
|
||||||
|
assembler_reply
|
||||||
|
.parse_packet(&pkt_reply, DUMMY_TIMESTAMP)
|
||||||
|
.unwrap(),
|
||||||
|
None
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_command_init() {
|
fn test_command_init() {
|
||||||
let mut rng = ThreadRng256 {};
|
let mut rng = ThreadRng256 {};
|
||||||
let user_immediately_present = |_| Ok(());
|
let user_immediately_present = |_| Ok(());
|
||||||
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
|
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
|
||||||
let mut ctap_hid = CtapHid::new();
|
let mut ctap_hid = CtapHid::new();
|
||||||
|
|
||||||
let reply = process_messages(
|
let reply = process_messages(
|
||||||
@@ -568,11 +583,71 @@ mod test {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_command_init_for_sync() {
|
||||||
|
let mut rng = ThreadRng256 {};
|
||||||
|
let user_immediately_present = |_| Ok(());
|
||||||
|
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
|
||||||
|
let mut ctap_hid = CtapHid::new();
|
||||||
|
let cid = cid_from_init(&mut ctap_hid, &mut ctap_state);
|
||||||
|
|
||||||
|
// Ping packet with a length longer than one packet.
|
||||||
|
let mut packet1 = [0x51; 64];
|
||||||
|
packet1[..4].copy_from_slice(&cid);
|
||||||
|
packet1[4..7].copy_from_slice(&[0x81, 0x02, 0x00]);
|
||||||
|
// Init packet on the same channel.
|
||||||
|
let mut packet2 = [0x00; 64];
|
||||||
|
packet2[..4].copy_from_slice(&cid);
|
||||||
|
packet2[4..15].copy_from_slice(&[
|
||||||
|
0x86, 0x00, 0x08, 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0,
|
||||||
|
]);
|
||||||
|
let mut result = Vec::new();
|
||||||
|
let mut assembler_reply = MessageAssembler::new();
|
||||||
|
for pkt_request in &[packet1, packet2] {
|
||||||
|
for pkt_reply in
|
||||||
|
ctap_hid.process_hid_packet(&pkt_request, DUMMY_CLOCK_VALUE, &mut ctap_state)
|
||||||
|
{
|
||||||
|
if let Some(message) = assembler_reply
|
||||||
|
.parse_packet(&pkt_reply, DUMMY_TIMESTAMP)
|
||||||
|
.unwrap()
|
||||||
|
{
|
||||||
|
result.push(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
result,
|
||||||
|
vec![Message {
|
||||||
|
cid,
|
||||||
|
cmd: CtapHid::COMMAND_INIT,
|
||||||
|
payload: vec![
|
||||||
|
0x12, // Nonce
|
||||||
|
0x34,
|
||||||
|
0x56,
|
||||||
|
0x78,
|
||||||
|
0x9A,
|
||||||
|
0xBC,
|
||||||
|
0xDE,
|
||||||
|
0xF0,
|
||||||
|
cid[0], // Allocated CID
|
||||||
|
cid[1],
|
||||||
|
cid[2],
|
||||||
|
cid[3],
|
||||||
|
0x02, // Protocol version
|
||||||
|
0x00, // Device version
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
CtapHid::CAPABILITIES
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_command_ping() {
|
fn test_command_ping() {
|
||||||
let mut rng = ThreadRng256 {};
|
let mut rng = ThreadRng256 {};
|
||||||
let user_immediately_present = |_| Ok(());
|
let user_immediately_present = |_| Ok(());
|
||||||
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
|
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
|
||||||
let mut ctap_hid = CtapHid::new();
|
let mut ctap_hid = CtapHid::new();
|
||||||
let cid = cid_from_init(&mut ctap_hid, &mut ctap_state);
|
let cid = cid_from_init(&mut ctap_hid, &mut ctap_state);
|
||||||
|
|
||||||
|
|||||||
@@ -586,5 +586,33 @@ mod test {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_init_sync() {
|
||||||
|
let mut assembler = MessageAssembler::new();
|
||||||
|
// Ping packet with a length longer than one packet.
|
||||||
|
assert_eq!(
|
||||||
|
assembler.parse_packet(
|
||||||
|
&byte_extend(&[0x12, 0x34, 0x56, 0x78, 0x81, 0x02, 0x00], 0x51),
|
||||||
|
DUMMY_TIMESTAMP
|
||||||
|
),
|
||||||
|
Ok(None)
|
||||||
|
);
|
||||||
|
// Init packet on the same channel.
|
||||||
|
assert_eq!(
|
||||||
|
assembler.parse_packet(
|
||||||
|
&zero_extend(&[
|
||||||
|
0x12, 0x34, 0x56, 0x78, 0x86, 0x00, 0x08, 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC,
|
||||||
|
0xDE, 0xF0
|
||||||
|
]),
|
||||||
|
DUMMY_TIMESTAMP
|
||||||
|
),
|
||||||
|
Ok(Some(Message {
|
||||||
|
cid: [0x12, 0x34, 0x56, 0x78],
|
||||||
|
cmd: 0x06,
|
||||||
|
payload: vec![0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0]
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: more tests
|
// TODO: more tests
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,10 +12,14 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
pub const AAGUID: &[u8; 16] = include_bytes!(concat!(env!("OUT_DIR"), "/opensk_aaguid.bin"));
|
pub const ATTESTATION_PRIVATE_KEY_LENGTH: usize = 32;
|
||||||
|
pub const AAGUID_LENGTH: usize = 16;
|
||||||
|
|
||||||
|
pub const AAGUID: &[u8; AAGUID_LENGTH] =
|
||||||
|
include_bytes!(concat!(env!("OUT_DIR"), "/opensk_aaguid.bin"));
|
||||||
|
|
||||||
pub const ATTESTATION_CERTIFICATE: &[u8] =
|
pub const ATTESTATION_CERTIFICATE: &[u8] =
|
||||||
include_bytes!(concat!(env!("OUT_DIR"), "/opensk_cert.bin"));
|
include_bytes!(concat!(env!("OUT_DIR"), "/opensk_cert.bin"));
|
||||||
|
|
||||||
pub const ATTESTATION_PRIVATE_KEY: &[u8; 32] =
|
pub const ATTESTATION_PRIVATE_KEY: &[u8; ATTESTATION_PRIVATE_KEY_LENGTH] =
|
||||||
include_bytes!(concat!(env!("OUT_DIR"), "/opensk_pkey.bin"));
|
include_bytes!(concat!(env!("OUT_DIR"), "/opensk_pkey.bin"));
|
||||||
|
|||||||
1056
src/ctap/mod.rs
1056
src/ctap/mod.rs
File diff suppressed because it is too large
Load Diff
@@ -19,7 +19,6 @@ use super::status_code::Ctap2StatusCode;
|
|||||||
use super::storage::PersistentStore;
|
use super::storage::PersistentStore;
|
||||||
#[cfg(feature = "with_ctap2_1")]
|
#[cfg(feature = "with_ctap2_1")]
|
||||||
use alloc::string::String;
|
use alloc::string::String;
|
||||||
#[cfg(feature = "with_ctap2_1")]
|
|
||||||
use alloc::vec;
|
use alloc::vec;
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use arrayref::array_ref;
|
use arrayref::array_ref;
|
||||||
@@ -74,10 +73,9 @@ fn encrypt_hmac_secret_output(
|
|||||||
let mut cred_random_secret = [0u8; 32];
|
let mut cred_random_secret = [0u8; 32];
|
||||||
cred_random_secret.copy_from_slice(cred_random);
|
cred_random_secret.copy_from_slice(cred_random);
|
||||||
|
|
||||||
// Initialization of 4 blocks in any case makes this function more readable.
|
|
||||||
let mut blocks = [[0u8; 16]; 4];
|
|
||||||
// With the if clause restriction above, block_len can only be 2 or 4.
|
// With the if clause restriction above, block_len can only be 2 or 4.
|
||||||
let block_len = salt_enc.len() / 16;
|
let block_len = salt_enc.len() / 16;
|
||||||
|
let mut blocks = vec![[0u8; 16]; block_len];
|
||||||
for i in 0..block_len {
|
for i in 0..block_len {
|
||||||
blocks[i].copy_from_slice(&salt_enc[16 * i..16 * (i + 1)]);
|
blocks[i].copy_from_slice(&salt_enc[16 * i..16 * (i + 1)]);
|
||||||
}
|
}
|
||||||
@@ -85,8 +83,8 @@ fn encrypt_hmac_secret_output(
|
|||||||
|
|
||||||
let mut decrypted_salt1 = [0u8; 32];
|
let mut decrypted_salt1 = [0u8; 32];
|
||||||
decrypted_salt1[..16].copy_from_slice(&blocks[0]);
|
decrypted_salt1[..16].copy_from_slice(&blocks[0]);
|
||||||
let output1 = hmac_256::<Sha256>(&cred_random_secret, &decrypted_salt1[..]);
|
|
||||||
decrypted_salt1[16..].copy_from_slice(&blocks[1]);
|
decrypted_salt1[16..].copy_from_slice(&blocks[1]);
|
||||||
|
let output1 = hmac_256::<Sha256>(&cred_random_secret, &decrypted_salt1[..]);
|
||||||
for i in 0..2 {
|
for i in 0..2 {
|
||||||
blocks[i].copy_from_slice(&output1[16 * i..16 * (i + 1)]);
|
blocks[i].copy_from_slice(&output1[16 * i..16 * (i + 1)]);
|
||||||
}
|
}
|
||||||
@@ -633,41 +631,73 @@ impl PinProtocolV1 {
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub fn new_test(
|
||||||
|
key_agreement_key: crypto::ecdh::SecKey,
|
||||||
|
pin_uv_auth_token: [u8; 32],
|
||||||
|
) -> PinProtocolV1 {
|
||||||
|
PinProtocolV1 {
|
||||||
|
key_agreement_key,
|
||||||
|
pin_uv_auth_token,
|
||||||
|
consecutive_pin_mismatches: 0,
|
||||||
|
#[cfg(feature = "with_ctap2_1")]
|
||||||
|
permissions: 0xFF,
|
||||||
|
#[cfg(feature = "with_ctap2_1")]
|
||||||
|
permissions_rp_id: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use arrayref::array_refs;
|
|
||||||
use crypto::rng256::ThreadRng256;
|
use crypto::rng256::ThreadRng256;
|
||||||
|
|
||||||
// Stores a PIN hash corresponding to the dummy PIN "1234".
|
// Stores a PIN hash corresponding to the dummy PIN "1234".
|
||||||
fn set_standard_pin(persistent_store: &mut PersistentStore) {
|
fn set_standard_pin(persistent_store: &mut PersistentStore) {
|
||||||
let mut pin = [0u8; 64];
|
let mut pin = [0u8; 64];
|
||||||
pin[0] = 0x31;
|
pin[..4].copy_from_slice(b"1234");
|
||||||
pin[1] = 0x32;
|
|
||||||
pin[2] = 0x33;
|
|
||||||
pin[3] = 0x34;
|
|
||||||
let mut pin_hash = [0u8; 16];
|
let mut pin_hash = [0u8; 16];
|
||||||
pin_hash.copy_from_slice(&Sha256::hash(&pin[..])[..16]);
|
pin_hash.copy_from_slice(&Sha256::hash(&pin[..])[..16]);
|
||||||
persistent_store.set_pin_hash(&pin_hash).unwrap();
|
persistent_store.set_pin_hash(&pin_hash).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Encrypts the message with a zero IV and key derived from shared_secret.
|
||||||
|
fn encrypt_message(shared_secret: &[u8; 32], message: &[u8]) -> Vec<u8> {
|
||||||
|
assert!(message.len() % 16 == 0);
|
||||||
|
let block_len = message.len() / 16;
|
||||||
|
let mut blocks = vec![[0u8; 16]; block_len];
|
||||||
|
for i in 0..block_len {
|
||||||
|
blocks[i][..].copy_from_slice(&message[i * 16..(i + 1) * 16]);
|
||||||
|
}
|
||||||
|
let aes_enc_key = crypto::aes256::EncryptionKey::new(shared_secret);
|
||||||
|
let iv = [0u8; 16];
|
||||||
|
cbc_encrypt(&aes_enc_key, iv, &mut blocks);
|
||||||
|
blocks.iter().flatten().cloned().collect::<Vec<u8>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrypts the message with a zero IV and key derived from shared_secret.
|
||||||
|
fn decrypt_message(shared_secret: &[u8; 32], message: &[u8]) -> Vec<u8> {
|
||||||
|
assert!(message.len() % 16 == 0);
|
||||||
|
let block_len = message.len() / 16;
|
||||||
|
let mut blocks = vec![[0u8; 16]; block_len];
|
||||||
|
for i in 0..block_len {
|
||||||
|
blocks[i][..].copy_from_slice(&message[i * 16..(i + 1) * 16]);
|
||||||
|
}
|
||||||
|
let aes_enc_key = crypto::aes256::EncryptionKey::new(shared_secret);
|
||||||
|
let aes_dec_key = crypto::aes256::DecryptionKey::new(&aes_enc_key);
|
||||||
|
let iv = [0u8; 16];
|
||||||
|
cbc_decrypt(&aes_dec_key, iv, &mut blocks);
|
||||||
|
blocks.iter().flatten().cloned().collect::<Vec<u8>>()
|
||||||
|
}
|
||||||
|
|
||||||
// Fails on PINs bigger than 64 bytes.
|
// Fails on PINs bigger than 64 bytes.
|
||||||
fn encrypt_pin(shared_secret: &[u8; 32], pin: Vec<u8>) -> Vec<u8> {
|
fn encrypt_pin(shared_secret: &[u8; 32], pin: Vec<u8>) -> Vec<u8> {
|
||||||
assert!(pin.len() <= 64);
|
assert!(pin.len() <= 64);
|
||||||
let mut padded_pin = [0u8; 64];
|
let mut padded_pin = [0u8; 64];
|
||||||
padded_pin[..pin.len()].copy_from_slice(&pin[..]);
|
padded_pin[..pin.len()].copy_from_slice(&pin[..]);
|
||||||
let aes_enc_key = crypto::aes256::EncryptionKey::new(shared_secret);
|
encrypt_message(shared_secret, &padded_pin)
|
||||||
let mut blocks = [[0u8; 16]; 4];
|
|
||||||
let (b0, b1, b2, b3) = array_refs!(&padded_pin, 16, 16, 16, 16);
|
|
||||||
blocks[0][..].copy_from_slice(b0);
|
|
||||||
blocks[1][..].copy_from_slice(b1);
|
|
||||||
blocks[2][..].copy_from_slice(b2);
|
|
||||||
blocks[3][..].copy_from_slice(b3);
|
|
||||||
let iv = [0u8; 16];
|
|
||||||
cbc_encrypt(&aes_enc_key, iv, &mut blocks);
|
|
||||||
blocks.iter().flatten().cloned().collect::<Vec<u8>>()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encrypts the dummy PIN "1234".
|
// Encrypts the dummy PIN "1234".
|
||||||
@@ -677,22 +707,10 @@ mod test {
|
|||||||
|
|
||||||
// Encrypts the PIN hash corresponding to the dummy PIN "1234".
|
// Encrypts the PIN hash corresponding to the dummy PIN "1234".
|
||||||
fn encrypt_standard_pin_hash(shared_secret: &[u8; 32]) -> Vec<u8> {
|
fn encrypt_standard_pin_hash(shared_secret: &[u8; 32]) -> Vec<u8> {
|
||||||
let aes_enc_key = crypto::aes256::EncryptionKey::new(shared_secret);
|
|
||||||
let mut pin = [0u8; 64];
|
let mut pin = [0u8; 64];
|
||||||
pin[0] = 0x31;
|
pin[..4].copy_from_slice(b"1234");
|
||||||
pin[1] = 0x32;
|
|
||||||
pin[2] = 0x33;
|
|
||||||
pin[3] = 0x34;
|
|
||||||
let pin_hash = Sha256::hash(&pin);
|
let pin_hash = Sha256::hash(&pin);
|
||||||
|
encrypt_message(shared_secret, &pin_hash[..16])
|
||||||
let mut blocks = [[0u8; 16]; 1];
|
|
||||||
blocks[0].copy_from_slice(&pin_hash[..16]);
|
|
||||||
let iv = [0u8; 16];
|
|
||||||
cbc_encrypt(&aes_enc_key, iv, &mut blocks);
|
|
||||||
|
|
||||||
let mut encrypted_pin_hash = Vec::with_capacity(16);
|
|
||||||
encrypted_pin_hash.extend(&blocks[0]);
|
|
||||||
encrypted_pin_hash
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -1184,6 +1202,56 @@ mod test {
|
|||||||
output,
|
output,
|
||||||
Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION)
|
Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let mut salt_enc = [0x00; 32];
|
||||||
|
let cred_random = [0xC9; 32];
|
||||||
|
|
||||||
|
// Test values to check for reproducibility.
|
||||||
|
let salt1 = [0x01; 32];
|
||||||
|
let salt2 = [0x02; 32];
|
||||||
|
let expected_output1 = hmac_256::<Sha256>(&cred_random, &salt1);
|
||||||
|
let expected_output2 = hmac_256::<Sha256>(&cred_random, &salt2);
|
||||||
|
|
||||||
|
let salt_enc1 = encrypt_message(&shared_secret, &salt1);
|
||||||
|
salt_enc.copy_from_slice(salt_enc1.as_slice());
|
||||||
|
let output = encrypt_hmac_secret_output(&shared_secret, &salt_enc, &cred_random).unwrap();
|
||||||
|
let output_dec = decrypt_message(&shared_secret, &output);
|
||||||
|
assert_eq!(&output_dec, &expected_output1);
|
||||||
|
|
||||||
|
let salt_enc2 = &encrypt_message(&shared_secret, &salt2);
|
||||||
|
salt_enc.copy_from_slice(salt_enc2.as_slice());
|
||||||
|
let output = encrypt_hmac_secret_output(&shared_secret, &salt_enc, &cred_random).unwrap();
|
||||||
|
let output_dec = decrypt_message(&shared_secret, &output);
|
||||||
|
assert_eq!(&output_dec, &expected_output2);
|
||||||
|
|
||||||
|
let mut salt_enc = [0x00; 64];
|
||||||
|
let mut salt12 = [0x00; 64];
|
||||||
|
salt12[..32].copy_from_slice(&salt1);
|
||||||
|
salt12[32..].copy_from_slice(&salt2);
|
||||||
|
let salt_enc12 = encrypt_message(&shared_secret, &salt12);
|
||||||
|
salt_enc.copy_from_slice(salt_enc12.as_slice());
|
||||||
|
let output = encrypt_hmac_secret_output(&shared_secret, &salt_enc, &cred_random).unwrap();
|
||||||
|
let output_dec = decrypt_message(&shared_secret, &output);
|
||||||
|
assert_eq!(&output_dec[..32], &expected_output1);
|
||||||
|
assert_eq!(&output_dec[32..], &expected_output2);
|
||||||
|
|
||||||
|
let mut salt_enc = [0x00; 64];
|
||||||
|
let mut salt02 = [0x00; 64];
|
||||||
|
salt02[32..].copy_from_slice(&salt2);
|
||||||
|
let salt_enc02 = encrypt_message(&shared_secret, &salt02);
|
||||||
|
salt_enc.copy_from_slice(salt_enc02.as_slice());
|
||||||
|
let output = encrypt_hmac_secret_output(&shared_secret, &salt_enc, &cred_random).unwrap();
|
||||||
|
let output_dec = decrypt_message(&shared_secret, &output);
|
||||||
|
assert_eq!(&output_dec[32..], &expected_output2);
|
||||||
|
|
||||||
|
let mut salt_enc = [0x00; 64];
|
||||||
|
let mut salt10 = [0x00; 64];
|
||||||
|
salt10[..32].copy_from_slice(&salt1);
|
||||||
|
let salt_enc10 = encrypt_message(&shared_secret, &salt10);
|
||||||
|
salt_enc.copy_from_slice(salt_enc10.as_slice());
|
||||||
|
let output = encrypt_hmac_secret_output(&shared_secret, &salt_enc, &cred_random).unwrap();
|
||||||
|
let output_dec = decrypt_message(&shared_secret, &output);
|
||||||
|
assert_eq!(&output_dec[..32], &expected_output1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "with_ctap2_1")]
|
#[cfg(feature = "with_ctap2_1")]
|
||||||
|
|||||||
@@ -15,9 +15,9 @@
|
|||||||
#[cfg(feature = "with_ctap2_1")]
|
#[cfg(feature = "with_ctap2_1")]
|
||||||
use crate::ctap::data_formats::{extract_array, extract_text_string};
|
use crate::ctap::data_formats::{extract_array, extract_text_string};
|
||||||
use crate::ctap::data_formats::{CredentialProtectionPolicy, PublicKeyCredentialSource};
|
use crate::ctap::data_formats::{CredentialProtectionPolicy, PublicKeyCredentialSource};
|
||||||
|
use crate::ctap::key_material;
|
||||||
use crate::ctap::pin_protocol_v1::PIN_AUTH_LENGTH;
|
use crate::ctap::pin_protocol_v1::PIN_AUTH_LENGTH;
|
||||||
use crate::ctap::status_code::Ctap2StatusCode;
|
use crate::ctap::status_code::Ctap2StatusCode;
|
||||||
use crate::ctap::{key_material, USE_BATCH_ATTESTATION};
|
|
||||||
use crate::embedded_flash::{self, StoreConfig, StoreEntry, StoreError};
|
use crate::embedded_flash::{self, StoreConfig, StoreEntry, StoreError};
|
||||||
use alloc::string::String;
|
use alloc::string::String;
|
||||||
#[cfg(any(test, feature = "ram_storage", feature = "with_ctap2_1"))]
|
#[cfg(any(test, feature = "ram_storage", feature = "with_ctap2_1"))]
|
||||||
@@ -76,8 +76,6 @@ const MIN_PIN_LENGTH_RP_IDS: usize = 9;
|
|||||||
const NUM_TAGS: usize = 10;
|
const NUM_TAGS: usize = 10;
|
||||||
|
|
||||||
const MAX_PIN_RETRIES: u8 = 8;
|
const MAX_PIN_RETRIES: u8 = 8;
|
||||||
const ATTESTATION_PRIVATE_KEY_LENGTH: usize = 32;
|
|
||||||
const AAGUID_LENGTH: usize = 16;
|
|
||||||
#[cfg(feature = "with_ctap2_1")]
|
#[cfg(feature = "with_ctap2_1")]
|
||||||
const DEFAULT_MIN_PIN_LENGTH: u8 = 4;
|
const DEFAULT_MIN_PIN_LENGTH: u8 = 4;
|
||||||
// TODO(kaczmarczyck) use this for the minPinLength extension
|
// TODO(kaczmarczyck) use this for the minPinLength extension
|
||||||
@@ -231,22 +229,29 @@ impl PersistentStore {
|
|||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
// The following 3 entries are meant to be written by vendor-specific commands.
|
// TODO(jmichel): remove this when vendor command is in place
|
||||||
if USE_BATCH_ATTESTATION {
|
#[cfg(not(test))]
|
||||||
if self.store.find_one(&Key::AttestationPrivateKey).is_none() {
|
self.load_attestation_data_from_firmware();
|
||||||
self.set_attestation_private_key(key_material::ATTESTATION_PRIVATE_KEY)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
if self.store.find_one(&Key::AttestationCertificate).is_none() {
|
|
||||||
self.set_attestation_certificate(key_material::ATTESTATION_CERTIFICATE)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if self.store.find_one(&Key::Aaguid).is_none() {
|
if self.store.find_one(&Key::Aaguid).is_none() {
|
||||||
self.set_aaguid(key_material::AAGUID).unwrap();
|
self.set_aaguid(key_material::AAGUID).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(jmichel): remove this function when vendor command is in place.
|
||||||
|
#[cfg(not(test))]
|
||||||
|
fn load_attestation_data_from_firmware(&mut self) {
|
||||||
|
// The following 2 entries are meant to be written by vendor-specific commands.
|
||||||
|
if self.store.find_one(&Key::AttestationPrivateKey).is_none() {
|
||||||
|
self.set_attestation_private_key(key_material::ATTESTATION_PRIVATE_KEY)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
if self.store.find_one(&Key::AttestationCertificate).is_none() {
|
||||||
|
self.set_attestation_certificate(key_material::ATTESTATION_CERTIFICATE)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn find_credential(
|
pub fn find_credential(
|
||||||
&self,
|
&self,
|
||||||
rp_id: &str,
|
rp_id: &str,
|
||||||
@@ -337,6 +342,26 @@ impl PersistentStore {
|
|||||||
.count())
|
.count())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new_creation_order(&self) -> Result<u64, Ctap2StatusCode> {
|
||||||
|
Ok(self
|
||||||
|
.store
|
||||||
|
.find_all(&Key::Credential {
|
||||||
|
rp_id: None,
|
||||||
|
credential_id: None,
|
||||||
|
user_handle: None,
|
||||||
|
})
|
||||||
|
.filter_map(|(_, entry)| {
|
||||||
|
debug_assert_eq!(entry.tag, TAG_CREDENTIAL);
|
||||||
|
let credential = deserialize_credential(entry.data);
|
||||||
|
debug_assert!(credential.is_some());
|
||||||
|
credential
|
||||||
|
})
|
||||||
|
.map(|c| c.creation_order)
|
||||||
|
.max()
|
||||||
|
.unwrap_or(0)
|
||||||
|
.wrapping_add(1))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn global_signature_counter(&self) -> Result<u32, Ctap2StatusCode> {
|
pub fn global_signature_counter(&self) -> Result<u32, Ctap2StatusCode> {
|
||||||
Ok(self
|
Ok(self
|
||||||
.store
|
.store
|
||||||
@@ -525,29 +550,33 @@ impl PersistentStore {
|
|||||||
|
|
||||||
pub fn attestation_private_key(
|
pub fn attestation_private_key(
|
||||||
&self,
|
&self,
|
||||||
) -> Result<Option<&[u8; ATTESTATION_PRIVATE_KEY_LENGTH]>, Ctap2StatusCode> {
|
) -> Result<Option<&[u8; key_material::ATTESTATION_PRIVATE_KEY_LENGTH]>, Ctap2StatusCode> {
|
||||||
let data = match self.store.find_one(&Key::AttestationPrivateKey) {
|
let data = match self.store.find_one(&Key::AttestationPrivateKey) {
|
||||||
None => return Ok(None),
|
None => return Ok(None),
|
||||||
Some((_, entry)) => entry.data,
|
Some((_, entry)) => entry.data,
|
||||||
};
|
};
|
||||||
if data.len() != ATTESTATION_PRIVATE_KEY_LENGTH {
|
if data.len() != key_material::ATTESTATION_PRIVATE_KEY_LENGTH {
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
|
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
|
||||||
}
|
}
|
||||||
Ok(Some(array_ref!(data, 0, ATTESTATION_PRIVATE_KEY_LENGTH)))
|
Ok(Some(array_ref!(
|
||||||
|
data,
|
||||||
|
0,
|
||||||
|
key_material::ATTESTATION_PRIVATE_KEY_LENGTH
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_attestation_private_key(
|
pub fn set_attestation_private_key(
|
||||||
&mut self,
|
&mut self,
|
||||||
attestation_private_key: &[u8; ATTESTATION_PRIVATE_KEY_LENGTH],
|
attestation_private_key: &[u8; key_material::ATTESTATION_PRIVATE_KEY_LENGTH],
|
||||||
) -> Result<(), Ctap2StatusCode> {
|
) -> Result<(), Ctap2StatusCode> {
|
||||||
let entry = StoreEntry {
|
let entry = StoreEntry {
|
||||||
tag: ATTESTATION_PRIVATE_KEY,
|
tag: ATTESTATION_PRIVATE_KEY,
|
||||||
data: attestation_private_key,
|
data: attestation_private_key,
|
||||||
sensitive: false,
|
sensitive: true,
|
||||||
};
|
};
|
||||||
match self.store.find_one(&Key::AttestationPrivateKey) {
|
match self.store.find_one(&Key::AttestationPrivateKey) {
|
||||||
None => self.store.insert(entry)?,
|
None => self.store.insert(entry)?,
|
||||||
Some((index, _)) => self.store.replace(index, entry)?,
|
_ => return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR),
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -571,24 +600,27 @@ impl PersistentStore {
|
|||||||
};
|
};
|
||||||
match self.store.find_one(&Key::AttestationCertificate) {
|
match self.store.find_one(&Key::AttestationCertificate) {
|
||||||
None => self.store.insert(entry)?,
|
None => self.store.insert(entry)?,
|
||||||
Some((index, _)) => self.store.replace(index, entry)?,
|
_ => return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR),
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn aaguid(&self) -> Result<[u8; AAGUID_LENGTH], Ctap2StatusCode> {
|
pub fn aaguid(&self) -> Result<[u8; key_material::AAGUID_LENGTH], Ctap2StatusCode> {
|
||||||
let (_, entry) = self
|
let (_, entry) = self
|
||||||
.store
|
.store
|
||||||
.find_one(&Key::Aaguid)
|
.find_one(&Key::Aaguid)
|
||||||
.ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?;
|
.ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?;
|
||||||
let data = entry.data;
|
let data = entry.data;
|
||||||
if data.len() != AAGUID_LENGTH {
|
if data.len() != key_material::AAGUID_LENGTH {
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
|
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
|
||||||
}
|
}
|
||||||
Ok(*array_ref![data, 0, AAGUID_LENGTH])
|
Ok(*array_ref![data, 0, key_material::AAGUID_LENGTH])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_aaguid(&mut self, aaguid: &[u8; AAGUID_LENGTH]) -> Result<(), Ctap2StatusCode> {
|
pub fn set_aaguid(
|
||||||
|
&mut self,
|
||||||
|
aaguid: &[u8; key_material::AAGUID_LENGTH],
|
||||||
|
) -> Result<(), Ctap2StatusCode> {
|
||||||
let entry = StoreEntry {
|
let entry = StoreEntry {
|
||||||
tag: AAGUID,
|
tag: AAGUID,
|
||||||
data: aaguid,
|
data: aaguid,
|
||||||
@@ -687,9 +719,12 @@ mod test {
|
|||||||
private_key,
|
private_key,
|
||||||
rp_id: String::from(rp_id),
|
rp_id: String::from(rp_id),
|
||||||
user_handle,
|
user_handle,
|
||||||
other_ui: None,
|
user_display_name: None,
|
||||||
cred_random: None,
|
cred_random: None,
|
||||||
cred_protect_policy: None,
|
cred_protect_policy: None,
|
||||||
|
creation_order: 0,
|
||||||
|
user_name: None,
|
||||||
|
user_icon: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -725,6 +760,21 @@ mod test {
|
|||||||
assert!(persistent_store.count_credentials().unwrap() > 0);
|
assert!(persistent_store.count_credentials().unwrap() > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_credential_order() {
|
||||||
|
let mut rng = ThreadRng256 {};
|
||||||
|
let mut persistent_store = PersistentStore::new(&mut rng);
|
||||||
|
let credential_source = create_credential_source(&mut rng, "example.com", vec![]);
|
||||||
|
let current_latest_creation = credential_source.creation_order;
|
||||||
|
assert!(persistent_store.store_credential(credential_source).is_ok());
|
||||||
|
let mut credential_source = create_credential_source(&mut rng, "example.com", vec![]);
|
||||||
|
credential_source.creation_order = persistent_store.new_creation_order().unwrap();
|
||||||
|
assert!(credential_source.creation_order > current_latest_creation);
|
||||||
|
let current_latest_creation = credential_source.creation_order;
|
||||||
|
assert!(persistent_store.store_credential(credential_source).is_ok());
|
||||||
|
assert!(persistent_store.new_creation_order().unwrap() > current_latest_creation);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[allow(clippy::assertions_on_constants)]
|
#[allow(clippy::assertions_on_constants)]
|
||||||
fn test_fill_store() {
|
fn test_fill_store() {
|
||||||
@@ -848,11 +898,14 @@ mod test {
|
|||||||
private_key,
|
private_key,
|
||||||
rp_id: String::from("example.com"),
|
rp_id: String::from("example.com"),
|
||||||
user_handle: vec![0x00],
|
user_handle: vec![0x00],
|
||||||
other_ui: None,
|
user_display_name: None,
|
||||||
cred_random: None,
|
cred_random: None,
|
||||||
cred_protect_policy: Some(
|
cred_protect_policy: Some(
|
||||||
CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIdList,
|
CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIdList,
|
||||||
),
|
),
|
||||||
|
creation_order: 0,
|
||||||
|
user_name: None,
|
||||||
|
user_icon: None,
|
||||||
};
|
};
|
||||||
assert!(persistent_store.store_credential(credential).is_ok());
|
assert!(persistent_store.store_credential(credential).is_ok());
|
||||||
|
|
||||||
@@ -891,9 +944,12 @@ mod test {
|
|||||||
private_key: key0,
|
private_key: key0,
|
||||||
rp_id: String::from("example.com"),
|
rp_id: String::from("example.com"),
|
||||||
user_handle: vec![0x00],
|
user_handle: vec![0x00],
|
||||||
other_ui: None,
|
user_display_name: None,
|
||||||
cred_random: None,
|
cred_random: None,
|
||||||
cred_protect_policy: None,
|
cred_protect_policy: None,
|
||||||
|
creation_order: 0,
|
||||||
|
user_name: None,
|
||||||
|
user_icon: None,
|
||||||
};
|
};
|
||||||
assert_eq!(found_credential, Some(expected_credential));
|
assert_eq!(found_credential, Some(expected_credential));
|
||||||
}
|
}
|
||||||
@@ -910,9 +966,12 @@ mod test {
|
|||||||
private_key,
|
private_key,
|
||||||
rp_id: String::from("example.com"),
|
rp_id: String::from("example.com"),
|
||||||
user_handle: vec![0x00],
|
user_handle: vec![0x00],
|
||||||
other_ui: None,
|
user_display_name: None,
|
||||||
cred_random: None,
|
cred_random: None,
|
||||||
cred_protect_policy: Some(CredentialProtectionPolicy::UserVerificationRequired),
|
cred_protect_policy: Some(CredentialProtectionPolicy::UserVerificationRequired),
|
||||||
|
creation_order: 0,
|
||||||
|
user_name: None,
|
||||||
|
user_icon: None,
|
||||||
};
|
};
|
||||||
assert!(persistent_store.store_credential(credential).is_ok());
|
assert!(persistent_store.store_credential(credential).is_ok());
|
||||||
|
|
||||||
@@ -1087,9 +1146,12 @@ mod test {
|
|||||||
private_key,
|
private_key,
|
||||||
rp_id: String::from("example.com"),
|
rp_id: String::from("example.com"),
|
||||||
user_handle: vec![0x00],
|
user_handle: vec![0x00],
|
||||||
other_ui: None,
|
user_display_name: None,
|
||||||
cred_random: None,
|
cred_random: None,
|
||||||
cred_protect_policy: None,
|
cred_protect_policy: None,
|
||||||
|
creation_order: 0,
|
||||||
|
user_name: None,
|
||||||
|
user_icon: None,
|
||||||
};
|
};
|
||||||
let serialized = serialize_credential(credential.clone()).unwrap();
|
let serialized = serialize_credential(credential.clone()).unwrap();
|
||||||
let reconstructed = deserialize_credential(&serialized).unwrap();
|
let reconstructed = deserialize_credential(&serialized).unwrap();
|
||||||
|
|||||||
11
src/main.rs
11
src/main.rs
@@ -37,9 +37,11 @@ use libtock_drivers::console::Console;
|
|||||||
use libtock_drivers::led;
|
use libtock_drivers::led;
|
||||||
use libtock_drivers::result::{FlexUnwrap, TockError};
|
use libtock_drivers::result::{FlexUnwrap, TockError};
|
||||||
use libtock_drivers::timer;
|
use libtock_drivers::timer;
|
||||||
|
use libtock_drivers::timer::Duration;
|
||||||
#[cfg(feature = "debug_ctap")]
|
#[cfg(feature = "debug_ctap")]
|
||||||
use libtock_drivers::timer::Timer;
|
use libtock_drivers::timer::Timer;
|
||||||
use libtock_drivers::timer::{Duration, Timestamp};
|
#[cfg(feature = "debug_ctap")]
|
||||||
|
use libtock_drivers::timer::Timestamp;
|
||||||
use libtock_drivers::usb_ctap_hid;
|
use libtock_drivers::usb_ctap_hid;
|
||||||
|
|
||||||
const KEEPALIVE_DELAY_MS: isize = 100;
|
const KEEPALIVE_DELAY_MS: isize = 100;
|
||||||
@@ -57,12 +59,13 @@ fn main() {
|
|||||||
panic!("Cannot setup USB driver");
|
panic!("Cannot setup USB driver");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let boot_time = timer.get_current_clock().flex_unwrap();
|
||||||
let mut rng = TockRng256 {};
|
let mut rng = TockRng256 {};
|
||||||
let mut ctap_state = CtapState::new(&mut rng, check_user_presence);
|
let mut ctap_state = CtapState::new(&mut rng, check_user_presence, boot_time);
|
||||||
let mut ctap_hid = CtapHid::new();
|
let mut ctap_hid = CtapHid::new();
|
||||||
|
|
||||||
let mut led_counter = 0;
|
let mut led_counter = 0;
|
||||||
let mut last_led_increment = timer.get_current_clock().flex_unwrap();
|
let mut last_led_increment = boot_time;
|
||||||
|
|
||||||
// Main loop. If CTAP1 is used, we register button presses for U2F while receiving and waiting.
|
// Main loop. If CTAP1 is used, we register button presses for U2F while receiving and waiting.
|
||||||
// The way TockOS and apps currently interact, callbacks need a yield syscall to execute,
|
// The way TockOS and apps currently interact, callbacks need a yield syscall to execute,
|
||||||
@@ -115,7 +118,7 @@ fn main() {
|
|||||||
|
|
||||||
// These calls are making sure that even for long inactivity, wrapping clock values
|
// These calls are making sure that even for long inactivity, wrapping clock values
|
||||||
// never randomly wink or grant user presence for U2F.
|
// never randomly wink or grant user presence for U2F.
|
||||||
ctap_state.check_disable_reset(Timestamp::<isize>::from_clock_value(now));
|
ctap_state.update_command_permission(now);
|
||||||
ctap_hid.wink_permission = ctap_hid.wink_permission.check_expiration(now);
|
ctap_hid.wink_permission = ctap_hid.wink_permission.check_expiration(now);
|
||||||
|
|
||||||
if has_packet {
|
if has_packet {
|
||||||
|
|||||||
Reference in New Issue
Block a user