Merge remote-tracking branch 'upstream/master' into ctap1-new-apdu-parser

This commit is contained in:
Kamran Khan
2020-12-10 21:18:53 -08:00
28 changed files with 902 additions and 3413 deletions
-6
View File
@@ -66,12 +66,6 @@ jobs:
command: check
args: --target thumbv7em-none-eabi --release --features debug_allocations
- name: Check OpenSK ram_storage
uses: actions-rs/cargo@v1
with:
command: check
args: --target thumbv7em-none-eabi --release --features ram_storage
- name: Check OpenSK verbose
uses: actions-rs/cargo@v1
with:
-7
View File
@@ -1,7 +0,0 @@
{
"recommendations": [
"davidanson.vscode-markdownlint",
"rust-lang.rust",
"ms-python.python"
]
}
-27
View File
@@ -1,27 +0,0 @@
{
"clang-format.fallbackStyle": "google",
"editor.detectIndentation": true,
"editor.formatOnPaste": false,
"editor.formatOnSave": true,
"editor.formatOnType": true,
"editor.insertSpaces": true,
"editor.tabSize": 4,
"files.insertFinalNewline": true,
"files.trimTrailingWhitespace": true,
"rust-client.channel": "nightly",
// The toolchain is updated from time to time so let's make sure that RLS is updated too
"rust-client.updateOnStartup": true,
"rust.clippy_preference": "on",
// Try to make VSCode formating as close as possible to the Google style.
"python.formatting.provider": "yapf",
"python.formatting.yapfArgs": [
"--style=yapf"
],
"python.linting.enabled": true,
"python.linting.lintOnSave": true,
"python.linting.pylintEnabled": true,
"python.linting.pylintPath": "pylint",
"[python]": {
"editor.tabSize": 2
},
}
+2 -2
View File
@@ -15,6 +15,7 @@ libtock_drivers = { path = "third_party/libtock-drivers" }
lang_items = { path = "third_party/lang-items" }
cbor = { path = "libraries/cbor" }
crypto = { path = "libraries/crypto" }
persistent_store = { path = "libraries/persistent_store" }
byteorder = { version = "1", default-features = false }
arrayref = "0.3.6"
subtle = { version = "2.2", default-features = false, features = ["nightly"] }
@@ -23,8 +24,7 @@ subtle = { version = "2.2", default-features = false, features = ["nightly"] }
debug_allocations = ["lang_items/debug_allocations"]
debug_ctap = ["crypto/derive_debug", "libtock_drivers/debug_ctap"]
panic_console = ["lang_items/panic_console"]
std = ["cbor/std", "crypto/std", "crypto/derive_debug", "lang_items/std"]
ram_storage = []
std = ["cbor/std", "crypto/std", "crypto/derive_debug", "lang_items/std", "persistent_store/std"]
verbose = ["debug_ctap", "libtock_drivers/verbose_usb"]
with_ctap1 = ["crypto/with_ctap1"]
with_ctap2_1 = []
+57
View File
@@ -0,0 +1,57 @@
{
"folders": [
{
"name": "OpenSK",
"path": "."
},
{
"name": "tock",
"path": "third_party/tock"
},
{
"name": "libtock-rs",
"path": "third_party/libtock-rs"
},
{
"name": "libtock-drivers",
"path": "third_party/libtock-drivers"
}
],
"settings": {
"clang-format.fallbackStyle": "google",
"editor.detectIndentation": true,
"editor.formatOnPaste": false,
"editor.formatOnSave": true,
"editor.formatOnType": true,
"editor.insertSpaces": true,
"editor.tabSize": 4,
"files.insertFinalNewline": true,
"files.trimTrailingWhitespace": true,
// Ensure we use the toolchain we set in rust-toolchain file
"rust-client.channel": "default",
// The toolchain is updated from time to time so let's make sure that RLS is updated too
"rust-client.updateOnStartup": true,
"rust.clippy_preference": "on",
"rust.target": "thumbv7em-none-eabi",
"rust.all_targets": false,
// Try to make VSCode formating as close as possible to the Google style.
"python.formatting.provider": "yapf",
"python.formatting.yapfArgs": [
"--style=yapf"
],
"python.linting.enabled": true,
"python.linting.lintOnSave": true,
"python.linting.pylintEnabled": true,
"python.linting.pylintPath": "pylint",
"[python]": {
"editor.tabSize": 2
},
},
"extensions": {
"recommendations": [
"davidanson.vscode-markdownlint",
"rust-lang.rust",
"ms-python.python"
]
}
}
+6 -8
View File
@@ -863,14 +863,6 @@ if __name__ == "__main__":
"This is useful to allow flashing multiple OpenSK authenticators "
"in a row without them being considered clones."),
)
main_parser.add_argument(
"--no-persistent-storage",
action="append_const",
const="ram_storage",
dest="features",
help=("Compiles and installs the OpenSK application without persistent "
"storage (i.e. unplugging the key will reset the key)."),
)
main_parser.add_argument(
"--elf2tab-output",
@@ -907,6 +899,12 @@ if __name__ == "__main__":
const="crypto_bench",
help=("Compiles and installs the crypto_bench example that benchmarks "
"the performance of the cryptographic algorithms on the board."))
apps_group.add_argument(
"--store_latency",
dest="application",
action="store_const",
const="store_latency",
help=("Compiles and installs the store_latency example."))
apps_group.add_argument(
"--panic_test",
dest="application",
+138
View File
@@ -0,0 +1,138 @@
// 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_std]
extern crate alloc;
extern crate lang_items;
use alloc::vec;
use core::fmt::Write;
use ctap2::embedded_flash::{new_storage, Storage};
use libtock_drivers::console::Console;
use libtock_drivers::timer::{self, Duration, Timer, Timestamp};
use persistent_store::Store;
fn timestamp(timer: &Timer) -> Timestamp<f64> {
Timestamp::<f64>::from_clock_value(timer.get_current_clock().ok().unwrap())
}
fn measure<T>(timer: &Timer, operation: impl FnOnce() -> T) -> (T, Duration<f64>) {
let before = timestamp(timer);
let result = operation();
let after = timestamp(timer);
(result, after - before)
}
// Only use one store at a time.
unsafe fn boot_store(num_pages: usize, erase: bool) -> Store<Storage> {
let mut storage = new_storage(num_pages);
if erase {
for page in 0..num_pages {
use persistent_store::Storage;
storage.erase_page(page).unwrap();
}
}
Store::new(storage).ok().unwrap()
}
fn compute_latency(timer: &Timer, num_pages: usize, key_increment: usize, word_length: usize) {
let mut console = Console::new();
writeln!(
console,
"\nLatency for num_pages={} key_increment={} word_length={}.",
num_pages, key_increment, word_length
)
.unwrap();
let mut store = unsafe { boot_store(num_pages, true) };
let total_capacity = store.capacity().unwrap().total();
assert_eq!(store.capacity().unwrap().used(), 0);
assert_eq!(store.lifetime().unwrap().used(), 0);
// Burn N words to align the end of the user capacity with the virtual capacity.
store.insert(0, &vec![0; 4 * (num_pages - 1)]).unwrap();
store.remove(0).unwrap();
assert_eq!(store.capacity().unwrap().used(), 0);
assert_eq!(store.lifetime().unwrap().used(), num_pages);
// Insert entries until there is space for one more.
let count = total_capacity / (1 + word_length) - 1;
let ((), time) = measure(timer, || {
for i in 0..count {
let key = 1 + key_increment * i;
// For some reason the kernel sometimes fails.
while store.insert(key, &vec![0; 4 * word_length]).is_err() {
// We never enter this loop in practice, but we still need it for the kernel.
writeln!(console, "Retry insert.").unwrap();
}
}
});
writeln!(console, "Setup: {:.1}ms for {} entries.", time.ms(), count).unwrap();
// Measure latency of insert.
let key = 1 + key_increment * count;
let ((), time) = measure(&timer, || {
store.insert(key, &vec![0; 4 * word_length]).unwrap()
});
writeln!(console, "Insert: {:.1}ms.", time.ms()).unwrap();
assert_eq!(
store.lifetime().unwrap().used(),
num_pages + (1 + count) * (1 + word_length)
);
// Measure latency of boot.
let (mut store, time) = measure(&timer, || unsafe { boot_store(num_pages, false) });
writeln!(console, "Boot: {:.1}ms.", time.ms()).unwrap();
// Measure latency of remove.
let ((), time) = measure(&timer, || store.remove(key).unwrap());
writeln!(console, "Remove: {:.1}ms.", time.ms()).unwrap();
// Measure latency of compaction.
let length = total_capacity + num_pages - store.lifetime().unwrap().used();
if length > 0 {
// Fill the store such that compaction is needed for one word.
store.insert(0, &vec![0; 4 * (length - 1)]).unwrap();
store.remove(0).unwrap();
}
assert!(store.capacity().unwrap().remaining() > 0);
assert_eq!(store.lifetime().unwrap().used(), num_pages + total_capacity);
let ((), time) = measure(timer, || store.prepare(1).unwrap());
writeln!(console, "Compaction: {:.1}ms.", time.ms()).unwrap();
assert!(store.lifetime().unwrap().used() > total_capacity + num_pages);
}
fn main() {
let mut with_callback = timer::with_callback(|_, _| {});
let timer = with_callback.init().ok().unwrap();
writeln!(Console::new(), "\nRunning 4 tests...").unwrap();
// Those non-overwritten 50 words entries simulate credentials.
compute_latency(&timer, 3, 1, 50);
compute_latency(&timer, 20, 1, 50);
// Those overwritten 1 word entries simulate counters.
compute_latency(&timer, 3, 0, 1);
compute_latency(&timer, 6, 0, 1);
writeln!(Console::new(), "\nDone.").unwrap();
// Results on nrf52840dk:
//
// | Pages | Overwrite | Length | Boot | Compaction | Insert | Remove |
// | ----- | --------- | --------- | ------- | ---------- | ------ | ------- |
// | 3 | no | 50 words | 2.0 ms | 132.5 ms | 4.8 ms | 1.2 ms |
// | 20 | no | 50 words | 7.4 ms | 135.5 ms | 10.2 ms | 3.9 ms |
// | 3 | yes | 1 word | 21.9 ms | 94.5 ms | 12.4 ms | 5.9 ms |
// | 6 | yes | 1 word | 55.2 ms | 100.8 ms | 24.8 ms | 12.1 ms |
}
+1 -1
View File
@@ -10,5 +10,5 @@ arrayref = "0.3.6"
libtock_drivers = { path = "../../third_party/libtock-drivers" }
crypto = { path = "../../libraries/crypto", features = ['std'] }
cbor = { path = "../../libraries/cbor", features = ['std'] }
ctap2 = { path = "../..", features = ['std', 'ram_storage'] }
ctap2 = { path = "../..", features = ['std'] }
lang_items = { path = "../../third_party/lang-items", features = ['std'] }
+22 -140
View File
@@ -26,6 +26,9 @@ use std::convert::TryInto;
// NOTE: We should be able to improve coverage by only checking the last operation. Because
// operations before the last could be checked with a shorter entropy.
// NOTE: Maybe we should split the fuzz target in smaller parts (like one per init). We should also
// name the fuzz targets with action names.
/// Checks the store against a sequence of manipulations.
///
/// The entropy to generate the sequence of manipulation should be provided in `data`. Debugging
@@ -181,7 +184,7 @@ impl<'a> Fuzzer<'a> {
println!("Power on the store.");
}
self.increment(StatKey::PowerOnCount);
let interruption = self.interruption(driver.delay_map());
let interruption = self.interruption(driver.count_operations());
match driver.partial_power_on(interruption) {
Err((storage, _)) if self.init.is_dirty() => {
self.entropy.consume_all();
@@ -198,7 +201,7 @@ impl<'a> Fuzzer<'a> {
if self.debug {
println!("{:?}", operation);
}
let interruption = self.interruption(driver.delay_map(&operation));
let interruption = self.interruption(driver.count_operations(&operation));
match driver.partial_apply(operation, interruption) {
Err((store, _)) if self.init.is_dirty() => {
self.entropy.consume_all();
@@ -334,59 +337,48 @@ impl<'a> Fuzzer<'a> {
/// Generates an interruption.
///
/// The `delay_map` describes the number of modified bits by the upcoming sequence of store
/// operations.
// TODO(ia0): We use too much CPU to compute the delay map. We should be able to just count the
// number of storage operations by checking the remaining delay. We can then use the entropy
// directly from the corruption function because it's called at most once.
fn interruption(
&mut self,
delay_map: Result<Vec<usize>, (usize, BufferStorage)>,
) -> StoreInterruption {
/// The `max_delay` describes the number of storage operations.
fn interruption(&mut self, max_delay: Option<usize>) -> StoreInterruption {
if self.init.is_dirty() {
// We only test that the store can power on without crashing. If it would get
// interrupted then it's like powering up with a different initial state, which would be
// tested with another fuzzing input.
return StoreInterruption::none();
}
let delay_map = match delay_map {
Ok(x) => x,
Err((delay, storage)) => {
print!("{}", storage);
panic!("delay={}", delay);
}
let max_delay = match max_delay {
Some(x) => x,
None => return StoreInterruption::none(),
};
let delay = self.entropy.read_range(0, delay_map.len() - 1);
let mut complete_bits = BitStack::default();
for _ in 0..delay_map[delay] {
complete_bits.push(self.entropy.read_bit());
}
let delay = self.entropy.read_range(0, max_delay);
if self.debug {
if delay == delay_map.len() - 1 {
assert!(complete_bits.is_empty());
if delay == max_delay {
println!("Do not interrupt.");
} else {
println!(
"Interrupt after {} operations with complete mask {}.",
delay, complete_bits
);
println!("Interrupt after {} operations.", delay);
}
}
if delay < delay_map.len() - 1 {
if delay < max_delay {
self.increment(StatKey::InterruptionCount);
}
let corrupt = Box::new(move |old: &mut [u8], new: &[u8]| {
let mut count = 0;
let mut total = 0;
for (old, new) in old.iter_mut().zip(new.iter()) {
for bit in 0..8 {
let mask = 1 << bit;
if *old & mask == *new & mask {
continue;
}
if complete_bits.pop().unwrap() {
total += 1;
if self.entropy.read_bit() {
count += 1;
*old ^= mask;
}
}
}
if self.debug {
println!("Flip {} bits out of {}.", count, total);
}
});
StoreInterruption { delay, corrupt }
}
@@ -432,113 +424,3 @@ impl Init {
}
}
}
/// Compact stack of bits.
// NOTE: This would probably go away once the delay map is simplified.
#[derive(Default, Clone, Debug)]
struct BitStack {
/// Bits stored in little-endian (for bytes and bits).
///
/// The last byte only contains `len` bits.
data: Vec<u8>,
/// Number of bits stored in the last byte.
///
/// It is 0 if the last byte is full, not 8.
len: usize,
}
impl BitStack {
/// Returns whether the stack is empty.
fn is_empty(&self) -> bool {
self.len() == 0
}
/// Returns the length of the stack.
fn len(&self) -> usize {
if self.len == 0 {
8 * self.data.len()
} else {
8 * (self.data.len() - 1) + self.len
}
}
/// Pushes a bit to the stack.
fn push(&mut self, value: bool) {
if self.len == 0 {
self.data.push(0);
}
if value {
*self.data.last_mut().unwrap() |= 1 << self.len;
}
self.len += 1;
if self.len == 8 {
self.len = 0;
}
}
/// Pops a bit from the stack.
fn pop(&mut self) -> Option<bool> {
if self.len == 0 {
if self.data.is_empty() {
return None;
}
self.len = 8;
}
self.len -= 1;
let result = self.data.last().unwrap() & 1 << self.len;
if self.len == 0 {
self.data.pop().unwrap();
}
Some(result != 0)
}
}
impl std::fmt::Display for BitStack {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
let mut bits = self.clone();
while let Some(bit) = bits.pop() {
write!(f, "{}", bit as usize)?;
}
write!(f, " ({} bits)", self.len())?;
Ok(())
}
}
#[test]
fn bit_stack_ok() {
let mut bits = BitStack::default();
assert_eq!(bits.pop(), None);
bits.push(true);
assert_eq!(bits.pop(), Some(true));
assert_eq!(bits.pop(), None);
bits.push(false);
assert_eq!(bits.pop(), Some(false));
assert_eq!(bits.pop(), None);
bits.push(true);
bits.push(false);
assert_eq!(bits.pop(), Some(false));
assert_eq!(bits.pop(), Some(true));
assert_eq!(bits.pop(), None);
bits.push(false);
bits.push(true);
assert_eq!(bits.pop(), Some(true));
assert_eq!(bits.pop(), Some(false));
assert_eq!(bits.pop(), None);
let n = 27;
for i in 0..n {
assert_eq!(bits.len(), i);
bits.push(true);
}
for i in (0..n).rev() {
assert_eq!(bits.pop(), Some(true));
assert_eq!(bits.len(), i);
}
assert_eq!(bits.pop(), None);
}
+16 -65
View File
@@ -311,31 +311,15 @@ impl StoreDriverOff {
})
}
/// Returns a mapping from delay time to number of modified bits.
/// Returns the number of storage operations to power on.
///
/// For example if the `i`-th value is `n`, it means that the `i`-th operation modifies `n` bits
/// in the storage. For convenience, the vector always ends with `0` for one past the last
/// operation. This permits to choose a random index in the vector and then a random set of bit
/// positions among the number of modified bits to simulate any possible corruption (including
/// no corruption with the last index).
pub fn delay_map(&self) -> Result<Vec<usize>, (usize, BufferStorage)> {
let mut result = Vec::new();
loop {
let delay = result.len();
let mut storage = self.storage.clone();
storage.arm_interruption(delay);
match Store::new(storage) {
Err((StoreError::StorageError, x)) => storage = x,
Err((StoreError::InvalidStorage, mut storage)) => {
storage.reset_interruption();
return Err((delay, storage));
}
Ok(_) | Err(_) => break,
}
result.push(count_modified_bits(&mut storage));
}
result.push(0);
Ok(result)
/// Returns `None` if the store cannot power on successfully.
pub fn count_operations(&self) -> Option<usize> {
let initial_delay = usize::MAX;
let mut storage = self.storage.clone();
storage.arm_interruption(initial_delay);
let mut store = Store::new(storage).ok()?;
Some(initial_delay - store.storage_mut().disarm_interruption())
}
}
@@ -422,29 +406,15 @@ impl StoreDriverOn {
})
}
/// Returns a mapping from delay time to number of modified bits.
/// Returns the number of storage operations to apply a store operation.
///
/// See the documentation of [`StoreDriverOff::delay_map`] for details.
///
/// [`StoreDriverOff::delay_map`]: struct.StoreDriverOff.html#method.delay_map
pub fn delay_map(
&self,
operation: &StoreOperation,
) -> Result<Vec<usize>, (usize, BufferStorage)> {
let mut result = Vec::new();
loop {
let delay = result.len();
let mut store = self.store.clone();
store.storage_mut().arm_interruption(delay);
match store.apply(operation).1 {
Err(StoreError::StorageError) => (),
Err(StoreError::InvalidStorage) => return Err((delay, store.extract_storage())),
Ok(()) | Err(_) => break,
}
result.push(count_modified_bits(store.storage_mut()));
}
result.push(0);
Ok(result)
/// Returns `None` if the store cannot apply the operation successfully.
pub fn count_operations(&self, operation: &StoreOperation) -> Option<usize> {
let initial_delay = usize::MAX;
let mut store = self.store.clone();
store.storage_mut().arm_interruption(initial_delay);
store.apply(operation).1.ok()?;
Some(initial_delay - store.storage_mut().disarm_interruption())
}
/// Powers off the store.
@@ -629,22 +599,3 @@ impl<'a> StoreInterruption<'a> {
}
}
}
/// Counts the number of bits modified by an interrupted operation.
///
/// # Panics
///
/// Panics if an interruption did not trigger.
fn count_modified_bits(storage: &mut BufferStorage) -> usize {
let mut modified_bits = 0;
storage.corrupt_operation(Box::new(|before, after| {
modified_bits = before
.iter()
.zip(after.iter())
.map(|(x, y)| (x ^ y).count_ones() as usize)
.sum();
}));
// We should never write the same slice or erase an erased page.
assert!(modified_bits > 0);
modified_bits
}
+2
View File
@@ -348,6 +348,7 @@
#[macro_use]
extern crate alloc;
#[cfg(feature = "std")]
mod buffer;
#[cfg(feature = "std")]
mod driver;
@@ -357,6 +358,7 @@ mod model;
mod storage;
mod store;
#[cfg(feature = "std")]
pub use self::buffer::{BufferCorruptFunction, BufferOptions, BufferStorage};
#[cfg(feature = "std")]
pub use self::driver::{
-1
View File
@@ -48,7 +48,6 @@ cargo check --release --target=thumbv7em-none-eabi --features with_ctap2_1
cargo check --release --target=thumbv7em-none-eabi --features debug_ctap
cargo check --release --target=thumbv7em-none-eabi --features panic_console
cargo check --release --target=thumbv7em-none-eabi --features debug_allocations
cargo check --release --target=thumbv7em-none-eabi --features ram_storage
cargo check --release --target=thumbv7em-none-eabi --features verbose
cargo check --release --target=thumbv7em-none-eabi --features debug_ctap,with_ctap1
cargo check --release --target=thumbv7em-none-eabi --features debug_ctap,with_ctap1,panic_console,debug_allocations,verbose
+14
View File
@@ -1,3 +1,17 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use alloc::vec::Vec;
use byteorder::{BigEndian, ByteOrder};
use core::convert::TryFrom;
+18 -37
View File
@@ -254,7 +254,7 @@ impl Ctap1Command {
let sk = crypto::ecdsa::SecKey::gensk(ctap_state.rng);
let pk = sk.genpk();
let key_handle = ctap_state
.encrypt_key_handle(sk, &application, None)
.encrypt_key_handle(sk, &application)
.map_err(|_| Ctap1StatusCode::SW_INTERNAL_EXCEPTION)?;
if key_handle.len() > 0xFF {
// This is just being defensive with unreachable code.
@@ -288,7 +288,7 @@ impl Ctap1Command {
signature_data.extend(key_handle);
signature_data.extend_from_slice(&user_pk);
let attestation_key = crypto::ecdsa::SecKey::from_bytes(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);
response.extend(signature.to_asn1_der());
@@ -349,7 +349,7 @@ impl Ctap1Command {
#[cfg(test)]
mod test {
use super::super::{key_material, CREDENTIAL_ID_BASE_SIZE, USE_SIGNATURE_COUNTER};
use super::super::{key_material, CREDENTIAL_ID_SIZE, USE_SIGNATURE_COUNTER};
use super::*;
use crypto::rng256::ThreadRng256;
use crypto::Hash256;
@@ -389,12 +389,12 @@ mod test {
0x00,
0x00,
0x00,
65 + CREDENTIAL_ID_BASE_SIZE as u8,
65 + CREDENTIAL_ID_SIZE as u8,
];
let challenge = [0x0C; 32];
message.extend(&challenge);
message.extend(application);
message.push(CREDENTIAL_ID_BASE_SIZE as u8);
message.push(CREDENTIAL_ID_SIZE as u8);
message.extend(key_handle);
message
}
@@ -434,15 +434,12 @@ mod test {
let response =
Ctap1Command::process_command(&message, &mut ctap_state, START_CLOCK_VALUE).unwrap();
assert_eq!(response[0], Ctap1Command::LEGACY_BYTE);
assert_eq!(response[66], CREDENTIAL_ID_BASE_SIZE as u8);
assert_eq!(response[66], CREDENTIAL_ID_SIZE as u8);
assert!(ctap_state
.decrypt_credential_source(
response[67..67 + CREDENTIAL_ID_BASE_SIZE].to_vec(),
&application
)
.decrypt_credential_source(response[67..67 + CREDENTIAL_ID_SIZE].to_vec(), &application)
.unwrap()
.is_some());
const CERT_START: usize = 67 + CREDENTIAL_ID_BASE_SIZE;
const CERT_START: usize = 67 + CREDENTIAL_ID_SIZE;
assert_eq!(
&response[CERT_START..CERT_START + fake_cert.len()],
&fake_cert[..]
@@ -491,9 +488,7 @@ mod test {
let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
let key_handle = ctap_state
.encrypt_key_handle(sk, &application, None)
.unwrap();
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
let message = create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
let response = Ctap1Command::process_command(&message, &mut ctap_state, START_CLOCK_VALUE);
@@ -509,9 +504,7 @@ mod test {
let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
let key_handle = ctap_state
.encrypt_key_handle(sk, &application, None)
.unwrap();
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
let application = [0x55; 32];
let message = create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
@@ -528,9 +521,7 @@ mod test {
let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
let key_handle = ctap_state
.encrypt_key_handle(sk, &application, None)
.unwrap();
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
let mut message = create_authenticate_message(
&application,
Ctap1Flags::DontEnforceUpAndSign,
@@ -563,9 +554,7 @@ mod test {
let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
let key_handle = ctap_state
.encrypt_key_handle(sk, &application, None)
.unwrap();
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
let mut message =
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
message[0] = 0xEE;
@@ -583,9 +572,7 @@ mod test {
let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
let key_handle = ctap_state
.encrypt_key_handle(sk, &application, None)
.unwrap();
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
let mut message =
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
message[1] = 0xEE;
@@ -603,9 +590,7 @@ mod test {
let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
let key_handle = ctap_state
.encrypt_key_handle(sk, &application, None)
.unwrap();
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
let mut message =
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
message[2] = 0xEE;
@@ -631,9 +616,7 @@ mod test {
let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
let key_handle = ctap_state
.encrypt_key_handle(sk, &application, None)
.unwrap();
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
let message =
create_authenticate_message(&application, Ctap1Flags::EnforceUpAndSign, &key_handle);
@@ -660,9 +643,7 @@ mod test {
let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
let key_handle = ctap_state
.encrypt_key_handle(sk, &application, None)
.unwrap();
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
let message = create_authenticate_message(
&application,
Ctap1Flags::DontEnforceUpAndSign,
@@ -684,7 +665,7 @@ mod test {
#[test]
fn test_process_authenticate_bad_key_handle() {
let application = [0x0A; 32];
let key_handle = vec![0x00; CREDENTIAL_ID_BASE_SIZE];
let key_handle = vec![0x00; CREDENTIAL_ID_SIZE];
let message =
create_authenticate_message(&application, Ctap1Flags::EnforceUpAndSign, &key_handle);
@@ -701,7 +682,7 @@ mod test {
#[test]
fn test_process_authenticate_without_up() {
let application = [0x0A; 32];
let key_handle = vec![0x00; CREDENTIAL_ID_BASE_SIZE];
let key_handle = vec![0x00; CREDENTIAL_ID_SIZE];
let message =
create_authenticate_message(&application, Ctap1Flags::EnforceUpAndSign, &key_handle);
+2 -18
View File
@@ -498,7 +498,6 @@ pub struct PublicKeyCredentialSource {
pub rp_id: String,
pub user_handle: Vec<u8>, // not optional, but nullable
pub user_display_name: Option<String>,
pub cred_random: Option<Vec<u8>>,
pub cred_protect_policy: Option<CredentialProtectionPolicy>,
pub creation_order: u64,
pub user_name: Option<String>,
@@ -513,14 +512,14 @@ enum PublicKeyCredentialSourceField {
RpId = 2,
UserHandle = 3,
UserDisplayName = 4,
CredRandom = 5,
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
// those reserved tags below.
// Reserved tags: none.
// Reserved tags:
// - CredRandom = 5,
}
impl From<PublicKeyCredentialSourceField> for cbor::KeyType {
@@ -539,7 +538,6 @@ impl From<PublicKeyCredentialSource> for cbor::Value {
PublicKeyCredentialSourceField::RpId => Some(credential.rp_id),
PublicKeyCredentialSourceField::UserHandle => Some(credential.user_handle),
PublicKeyCredentialSourceField::UserDisplayName => credential.user_display_name,
PublicKeyCredentialSourceField::CredRandom => credential.cred_random,
PublicKeyCredentialSourceField::CredProtectPolicy => credential.cred_protect_policy,
PublicKeyCredentialSourceField::CreationOrder => credential.creation_order,
PublicKeyCredentialSourceField::UserName => credential.user_name,
@@ -559,7 +557,6 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialSource {
PublicKeyCredentialSourceField::RpId => rp_id,
PublicKeyCredentialSourceField::UserHandle => user_handle,
PublicKeyCredentialSourceField::UserDisplayName => user_display_name,
PublicKeyCredentialSourceField::CredRandom => cred_random,
PublicKeyCredentialSourceField::CredProtectPolicy => cred_protect_policy,
PublicKeyCredentialSourceField::CreationOrder => creation_order,
PublicKeyCredentialSourceField::UserName => user_name,
@@ -577,7 +574,6 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialSource {
let rp_id = extract_text_string(ok_or_missing(rp_id)?)?;
let user_handle = extract_byte_string(ok_or_missing(user_handle)?)?;
let user_display_name = user_display_name.map(extract_text_string).transpose()?;
let cred_random = cred_random.map(extract_byte_string).transpose()?;
let cred_protect_policy = cred_protect_policy
.map(CredentialProtectionPolicy::try_from)
.transpose()?;
@@ -601,7 +597,6 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialSource {
rp_id,
user_handle,
user_display_name,
cred_random,
cred_protect_policy,
creation_order,
user_name,
@@ -1373,7 +1368,6 @@ mod test {
rp_id: "example.com".to_string(),
user_handle: b"foo".to_vec(),
user_display_name: None,
cred_random: None,
cred_protect_policy: None,
creation_order: 0,
user_name: None,
@@ -1395,16 +1389,6 @@ mod test {
Ok(credential.clone())
);
let credential = PublicKeyCredentialSource {
cred_random: Some(vec![0x00; 32]),
..credential
};
assert_eq!(
PublicKeyCredentialSource::try_from(cbor::Value::from(credential.clone())),
Ok(credential.clone())
);
let credential = PublicKeyCredentialSource {
cred_protect_policy: Some(CredentialProtectionPolicy::UserVerificationOptional),
..credential
+37 -97
View File
@@ -86,10 +86,8 @@ pub const INITIAL_SIGNATURE_COUNTER: u32 = 1;
// - 16 byte initialization vector for AES-256,
// - 32 byte ECDSA private key for the credential,
// - 32 byte relying party ID hashed with SHA256,
// - (optional) 32 byte for HMAC-secret,
// - 32 byte HMAC-SHA256 over everything else.
pub const CREDENTIAL_ID_BASE_SIZE: usize = 112;
pub const CREDENTIAL_ID_MAX_SIZE: usize = CREDENTIAL_ID_BASE_SIZE + 32;
pub const CREDENTIAL_ID_SIZE: usize = 112;
// Set this bit when checking user presence.
const UP_FLAG: u8 = 0x01;
// Set this bit when checking user verification.
@@ -142,6 +140,7 @@ struct AssertionInput {
client_data_hash: Vec<u8>,
auth_data: Vec<u8>,
hmac_secret_input: Option<GetAssertionHmacSecretInput>,
has_uv: bool,
}
struct AssertionState {
@@ -231,7 +230,6 @@ where
&mut self,
private_key: crypto::ecdsa::SecKey,
application: &[u8; 32],
cred_random: Option<&[u8; 32]>,
) -> Result<Vec<u8>, Ctap2StatusCode> {
let master_keys = self.persistent_store.master_keys()?;
let aes_enc_key = crypto::aes256::EncryptionKey::new(&master_keys.encryption);
@@ -240,19 +238,14 @@ where
let mut iv = [0; 16];
iv.copy_from_slice(&self.rng.gen_uniform_u8x32()[..16]);
let block_len = if cred_random.is_some() { 6 } else { 4 };
let mut blocks = vec![[0u8; 16]; block_len];
let mut blocks = [[0u8; 16]; 4];
blocks[0].copy_from_slice(&sk_bytes[..16]);
blocks[1].copy_from_slice(&sk_bytes[16..]);
blocks[2].copy_from_slice(&application[..16]);
blocks[3].copy_from_slice(&application[16..]);
if let Some(cred_random) = cred_random {
blocks[4].copy_from_slice(&cred_random[..16]);
blocks[5].copy_from_slice(&cred_random[16..]);
}
cbc_encrypt(&aes_enc_key, iv, &mut blocks);
let mut encrypted_id = Vec::with_capacity(16 * (block_len + 3));
let mut encrypted_id = Vec::with_capacity(0x70);
encrypted_id.extend(&iv);
for b in &blocks {
encrypted_id.extend(b);
@@ -270,11 +263,9 @@ where
credential_id: Vec<u8>,
rp_id_hash: &[u8],
) -> Result<Option<PublicKeyCredentialSource>, Ctap2StatusCode> {
let has_cred_random = match credential_id.len() {
CREDENTIAL_ID_BASE_SIZE => false,
CREDENTIAL_ID_MAX_SIZE => true,
_ => return Ok(None),
};
if credential_id.len() != CREDENTIAL_ID_SIZE {
return Ok(None);
}
let master_keys = self.persistent_store.master_keys()?;
let payload_size = credential_id.len() - 32;
if !verify_hmac_256::<Sha256>(
@@ -288,9 +279,8 @@ where
let aes_dec_key = crypto::aes256::DecryptionKey::new(&aes_enc_key);
let mut iv = [0; 16];
iv.copy_from_slice(&credential_id[..16]);
let block_len = if has_cred_random { 6 } else { 4 };
let mut blocks = vec![[0u8; 16]; block_len];
for i in 0..block_len {
let mut blocks = [[0u8; 16]; 4];
for i in 0..4 {
blocks[i].copy_from_slice(&credential_id[16 * (i + 1)..16 * (i + 2)]);
}
@@ -301,15 +291,6 @@ where
decrypted_sk[16..].clone_from_slice(&blocks[1]);
decrypted_rp_id_hash[..16].clone_from_slice(&blocks[2]);
decrypted_rp_id_hash[16..].clone_from_slice(&blocks[3]);
let cred_random = if has_cred_random {
let mut decrypted_cred_random = [0; 32];
decrypted_cred_random[..16].clone_from_slice(&blocks[4]);
decrypted_cred_random[16..].clone_from_slice(&blocks[5]);
Some(decrypted_cred_random.to_vec())
} else {
None
};
if rp_id_hash != decrypted_rp_id_hash {
return Ok(None);
}
@@ -322,7 +303,6 @@ where
rp_id: String::from(""),
user_handle: vec![],
user_display_name: None,
cred_random,
cred_protect_policy: None,
creation_order: 0,
user_name: None,
@@ -464,11 +444,6 @@ where
(false, DEFAULT_CRED_PROTECT)
};
let cred_random = if use_hmac_extension {
Some(self.rng.gen_uniform_u8x32())
} else {
None
};
let has_extension_output = use_hmac_extension || cred_protect_policy.is_some();
let rp_id = rp.rp_id;
@@ -543,7 +518,6 @@ where
user_display_name: user
.user_display_name
.map(|s| truncate_to_char_boundary(&s, 64).to_string()),
cred_random: cred_random.map(|c| c.to_vec()),
cred_protect_policy,
creation_order: self.persistent_store.new_creation_order()?,
user_name: user
@@ -556,7 +530,7 @@ where
self.persistent_store.store_credential(credential_source)?;
random_id
} else {
self.encrypt_key_handle(sk.clone(), &rp_id_hash, cred_random.as_ref())?
self.encrypt_key_handle(sk.clone(), &rp_id_hash)?
};
let mut auth_data = self.generate_auth_data(&rp_id_hash, flags)?;
@@ -592,7 +566,7 @@ where
.attestation_private_key()?
.ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?;
let attestation_key =
crypto::ecdsa::SecKey::from_bytes(attestation_private_key).unwrap();
crypto::ecdsa::SecKey::from_bytes(&attestation_private_key).unwrap();
let attestation_certificate = self
.persistent_store
.attestation_certificate()?
@@ -622,10 +596,23 @@ where
))
}
// Generates a different per-credential secret for each UV mode.
// The computation is deterministic, and private_key expected to be unique.
fn generate_cred_random(
&mut self,
private_key: &crypto::ecdsa::SecKey,
has_uv: bool,
) -> Result<[u8; 32], Ctap2StatusCode> {
let mut private_key_bytes = [0u8; 32];
private_key.to_bytes(&mut private_key_bytes);
let key = self.persistent_store.cred_random_secret(has_uv)?;
Ok(hmac_256::<Sha256>(&key, &private_key_bytes))
}
// Processes the input of a get_assertion operation for a given credential
// and returns the correct Get(Next)Assertion response.
fn assertion_response(
&self,
&mut self,
credential: PublicKeyCredentialSource,
assertion_input: AssertionInput,
number_of_credentials: Option<usize>,
@@ -634,13 +621,15 @@ where
client_data_hash,
mut auth_data,
hmac_secret_input,
has_uv,
} = assertion_input;
// Process extensions.
if let Some(hmac_secret_input) = hmac_secret_input {
let cred_random = self.generate_cred_random(&credential.private_key, has_uv)?;
let encrypted_output = self
.pin_protocol_v1
.process_hmac_secret(hmac_secret_input, &credential.cred_random)?;
.process_hmac_secret(hmac_secret_input, &cred_random)?;
let extensions_output = cbor_map! {
"hmac-secret" => encrypted_output,
};
@@ -807,6 +796,7 @@ where
client_data_hash,
auth_data: self.generate_auth_data(&rp_id_hash, flags)?,
hmac_secret_input,
has_uv,
};
let number_of_credentials = if applicable_credentials.is_empty() {
None
@@ -872,7 +862,7 @@ where
max_credential_count_in_list: MAX_CREDENTIAL_COUNT_IN_LIST.map(|c| c as u64),
// #TODO(106) update with version 2.1 of HMAC-secret
#[cfg(feature = "with_ctap2_1")]
max_credential_id_length: Some(CREDENTIAL_ID_BASE_SIZE as u64 + 32),
max_credential_id_length: Some(CREDENTIAL_ID_SIZE as u64),
#[cfg(feature = "with_ctap2_1")]
transports: Some(vec![AuthenticatorTransport::Usb]),
#[cfg(feature = "with_ctap2_1")]
@@ -1010,7 +1000,7 @@ mod test {
#[cfg(feature = "with_ctap2_1")]
expected_response.extend(
[
0x08, 0x18, 0x90, 0x09, 0x81, 0x63, 0x75, 0x73, 0x62, 0x0A, 0x81, 0xA2, 0x63, 0x61,
0x08, 0x18, 0x70, 0x09, 0x81, 0x63, 0x75, 0x73, 0x62, 0x0A, 0x81, 0xA2, 0x63, 0x61,
0x6C, 0x67, 0x26, 0x64, 0x74, 0x79, 0x70, 0x65, 0x6A, 0x70, 0x75, 0x62, 0x6C, 0x69,
0x63, 0x2D, 0x6B, 0x65, 0x79, 0x0D, 0x04,
]
@@ -1141,7 +1131,7 @@ mod test {
];
expected_auth_data.push(INITIAL_SIGNATURE_COUNTER as u8);
expected_auth_data.extend(&ctap_state.persistent_store.aaguid().unwrap());
expected_auth_data.extend(&[0x00, CREDENTIAL_ID_BASE_SIZE as u8]);
expected_auth_data.extend(&[0x00, CREDENTIAL_ID_SIZE as u8]);
assert_eq!(
auth_data[0..expected_auth_data.len()],
expected_auth_data[..]
@@ -1186,7 +1176,6 @@ mod test {
rp_id: String::from("example.com"),
user_handle: vec![],
user_display_name: None,
cred_random: None,
cred_protect_policy: None,
creation_order: 0,
user_name: None,
@@ -1291,7 +1280,7 @@ mod test {
];
expected_auth_data.push(INITIAL_SIGNATURE_COUNTER as u8);
expected_auth_data.extend(&ctap_state.persistent_store.aaguid().unwrap());
expected_auth_data.extend(&[0x00, CREDENTIAL_ID_MAX_SIZE as u8]);
expected_auth_data.extend(&[0x00, CREDENTIAL_ID_SIZE as u8]);
assert_eq!(
auth_data[0..expected_auth_data.len()],
expected_auth_data[..]
@@ -1488,8 +1477,8 @@ mod test {
let auth_data = make_credential_response.auth_data;
let offset = 37 + ctap_state.persistent_store.aaguid().unwrap().len();
assert_eq!(auth_data[offset], 0x00);
assert_eq!(auth_data[offset + 1] as usize, CREDENTIAL_ID_MAX_SIZE);
auth_data[offset + 2..offset + 2 + CREDENTIAL_ID_MAX_SIZE].to_vec()
assert_eq!(auth_data[offset + 1] as usize, CREDENTIAL_ID_SIZE);
auth_data[offset + 2..offset + 2 + CREDENTIAL_ID_SIZE].to_vec()
}
_ => panic!("Invalid response type"),
};
@@ -1604,7 +1593,6 @@ mod test {
rp_id: String::from("example.com"),
user_handle: vec![0x1D],
user_display_name: None,
cred_random: None,
cred_protect_policy: Some(
CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIdList,
),
@@ -1669,7 +1657,6 @@ mod test {
rp_id: String::from("example.com"),
user_handle: vec![0x1D],
user_display_name: None,
cred_random: None,
cred_protect_policy: Some(CredentialProtectionPolicy::UserVerificationRequired),
creation_order: 0,
user_name: None,
@@ -1942,7 +1929,6 @@ mod test {
rp_id: String::from("example.com"),
user_handle: vec![],
user_display_name: None,
cred_random: None,
cred_protect_policy: None,
creation_order: 0,
user_name: None,
@@ -2013,7 +1999,7 @@ mod test {
// We are not testing the correctness of our SHA256 here, only if it is checked.
let rp_id_hash = [0x55; 32];
let encrypted_id = ctap_state
.encrypt_key_handle(private_key.clone(), &rp_id_hash, None)
.encrypt_key_handle(private_key.clone(), &rp_id_hash)
.unwrap();
let decrypted_source = ctap_state
.decrypt_credential_source(encrypted_id, &rp_id_hash)
@@ -2023,29 +2009,6 @@ mod test {
assert_eq!(private_key, decrypted_source.private_key);
}
#[test]
fn test_encrypt_decrypt_credential_with_cred_random() {
let mut rng = ThreadRng256 {};
let user_immediately_present = |_| Ok(());
let private_key = crypto::ecdsa::SecKey::gensk(&mut rng);
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
// Usually, the relying party ID or its hash is provided by the client.
// We are not testing the correctness of our SHA256 here, only if it is checked.
let rp_id_hash = [0x55; 32];
let cred_random = [0xC9; 32];
let encrypted_id = ctap_state
.encrypt_key_handle(private_key.clone(), &rp_id_hash, Some(&cred_random))
.unwrap();
let decrypted_source = ctap_state
.decrypt_credential_source(encrypted_id, &rp_id_hash)
.unwrap()
.unwrap();
assert_eq!(private_key, decrypted_source.private_key);
assert_eq!(Some(cred_random.to_vec()), decrypted_source.cred_random);
}
#[test]
fn test_encrypt_decrypt_bad_hmac() {
let mut rng = ThreadRng256 {};
@@ -2056,30 +2019,7 @@ mod test {
// Same as above.
let rp_id_hash = [0x55; 32];
let encrypted_id = ctap_state
.encrypt_key_handle(private_key, &rp_id_hash, None)
.unwrap();
for i in 0..encrypted_id.len() {
let mut modified_id = encrypted_id.clone();
modified_id[i] ^= 0x01;
assert!(ctap_state
.decrypt_credential_source(modified_id, &rp_id_hash)
.unwrap()
.is_none());
}
}
#[test]
fn test_encrypt_decrypt_bad_hmac_with_cred_random() {
let mut rng = ThreadRng256 {};
let user_immediately_present = |_| Ok(());
let private_key = crypto::ecdsa::SecKey::gensk(&mut rng);
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
// Same as above.
let rp_id_hash = [0x55; 32];
let cred_random = [0xC9; 32];
let encrypted_id = ctap_state
.encrypt_key_handle(private_key, &rp_id_hash, Some(&cred_random))
.encrypt_key_handle(private_key, &rp_id_hash)
.unwrap();
for i in 0..encrypted_id.len() {
let mut modified_id = encrypted_id.clone();
+5 -25
View File
@@ -56,23 +56,16 @@ fn verify_pin_auth(hmac_key: &[u8], hmac_contents: &[u8], pin_auth: &[u8]) -> bo
fn encrypt_hmac_secret_output(
shared_secret: &[u8; 32],
salt_enc: &[u8],
cred_random: &[u8],
cred_random: &[u8; 32],
) -> Result<Vec<u8>, Ctap2StatusCode> {
if salt_enc.len() != 32 && salt_enc.len() != 64 {
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION);
}
if cred_random.len() != 32 {
// We are strict here. We need at least 32 byte, but expect exactly 32.
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION);
}
let aes_enc_key = crypto::aes256::EncryptionKey::new(shared_secret);
let aes_dec_key = crypto::aes256::DecryptionKey::new(&aes_enc_key);
// The specification specifically asks for a zero IV.
let iv = [0u8; 16];
let mut cred_random_secret = [0u8; 32];
cred_random_secret.copy_from_slice(cred_random);
// With the if clause restriction above, block_len can only be 2 or 4.
let block_len = salt_enc.len() / 16;
let mut blocks = vec![[0u8; 16]; block_len];
@@ -84,7 +77,7 @@ fn encrypt_hmac_secret_output(
let mut decrypted_salt1 = [0u8; 32];
decrypted_salt1[..16].copy_from_slice(&blocks[0]);
decrypted_salt1[16..].copy_from_slice(&blocks[1]);
let output1 = hmac_256::<Sha256>(&cred_random_secret, &decrypted_salt1[..]);
let output1 = hmac_256::<Sha256>(&cred_random[..], &decrypted_salt1[..]);
for i in 0..2 {
blocks[i].copy_from_slice(&output1[16 * i..16 * (i + 1)]);
}
@@ -93,7 +86,7 @@ fn encrypt_hmac_secret_output(
let mut decrypted_salt2 = [0u8; 32];
decrypted_salt2[..16].copy_from_slice(&blocks[2]);
decrypted_salt2[16..].copy_from_slice(&blocks[3]);
let output2 = hmac_256::<Sha256>(&cred_random_secret, &decrypted_salt2[..]);
let output2 = hmac_256::<Sha256>(&cred_random[..], &decrypted_salt2[..]);
for i in 0..2 {
blocks[i + 2].copy_from_slice(&output2[16 * i..16 * (i + 1)]);
}
@@ -588,7 +581,7 @@ impl PinProtocolV1 {
pub fn process_hmac_secret(
&self,
hmac_secret_input: GetAssertionHmacSecretInput,
cred_random: &Option<Vec<u8>>,
cred_random: &[u8; 32],
) -> Result<Vec<u8>, Ctap2StatusCode> {
let GetAssertionHmacSecretInput {
key_agreement,
@@ -602,12 +595,7 @@ impl PinProtocolV1 {
// Hard to tell what the correct error code here is.
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION);
}
match cred_random {
Some(cr) => encrypt_hmac_secret_output(&shared_secret, &salt_enc[..], cr),
// This is the case if the credential was not created with HMAC-secret.
None => Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION),
}
encrypt_hmac_secret_output(&shared_secret, &salt_enc[..], cred_random)
}
#[cfg(feature = "with_ctap2_1")]
@@ -1195,14 +1183,6 @@ mod test {
let output = encrypt_hmac_secret_output(&shared_secret, &salt_enc, &cred_random);
assert_eq!(output.unwrap().len(), 64);
let salt_enc = [0x5E; 32];
let cred_random = [0xC9; 33];
let output = encrypt_hmac_secret_output(&shared_secret, &salt_enc, &cred_random);
assert_eq!(
output,
Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION)
);
let mut salt_enc = [0x00; 32];
let cred_random = [0xC9; 32];
+5
View File
@@ -81,5 +81,10 @@ pub enum Ctap2StatusCode {
/// This type of error is unexpected and the current state is undefined.
CTAP2_ERR_VENDOR_INTERNAL_ERROR = 0xF2,
/// The hardware is malfunctioning.
///
/// It may be possible that some of those errors are actually internal errors.
CTAP2_ERR_VENDOR_HARDWARE_FAILURE = 0xF3,
CTAP2_ERR_VENDOR_LAST = 0xFF,
}
+379 -458
View File
File diff suppressed because it is too large Load Diff
+146
View File
@@ -0,0 +1,146 @@
// 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.
/// Number of keys that persist the CTAP reset command.
pub const NUM_PERSISTENT_KEYS: usize = 20;
/// Defines a key given its name and value or range of values.
macro_rules! make_key {
($(#[$doc: meta])* $name: ident = $key: literal..$end: literal) => {
$(#[$doc])* pub const $name: core::ops::Range<usize> = $key..$end;
};
($(#[$doc: meta])* $name: ident = $key: literal) => {
$(#[$doc])* pub const $name: usize = $key;
};
}
/// Returns the range of values of a key given its value description.
#[cfg(test)]
macro_rules! make_range {
($key: literal..$end: literal) => {
$key..$end
};
($key: literal) => {
$key..$key + 1
};
}
/// Helper to define keys as a partial partition of a range.
macro_rules! make_partition {
($range: expr,
$(
$(#[$doc: meta])*
$name: ident = $key: literal $(.. $end: literal)?;
)*) => {
$(
make_key!($(#[$doc])* $name = $key $(.. $end)?);
)*
#[cfg(test)]
const KEY_RANGE: core::ops::Range<usize> = $range;
#[cfg(test)]
const ALL_KEYS: &[core::ops::Range<usize>] = &[$(make_range!($key $(.. $end)?)),*];
};
}
make_partition! {
// We reserve 0 and 2048+ for possible migration purposes. We add persistent entries starting
// from 1 and going up. We add non-persistent entries starting from 2047 and going down. This
// way, we don't commit to a fixed number of persistent keys.
1..2048,
// WARNING: Keys should not be deleted but prefixed with `_` to avoid accidentally reusing them.
/// The attestation private key.
ATTESTATION_PRIVATE_KEY = 1;
/// The attestation certificate.
ATTESTATION_CERTIFICATE = 2;
/// The aaguid.
AAGUID = 3;
// This is the persistent key limit:
// - When adding a (persistent) key above this message, make sure its value is smaller than
// NUM_PERSISTENT_KEYS.
// - When adding a (non-persistent) key below this message, make sure its value is bigger or
// equal than NUM_PERSISTENT_KEYS.
/// Reserved for future credential-related objects.
///
/// In particular, additional credentials could be added there by reducing the lower bound of
/// the credential range below as well as the upper bound of this range in a similar manner.
_RESERVED_CREDENTIALS = 1000..1700;
/// The credentials.
///
/// Depending on `MAX_SUPPORTED_RESIDENTIAL_KEYS`, only a prefix of those keys is used. Each
/// board may configure `MAX_SUPPORTED_RESIDENTIAL_KEYS` depending on the storage size.
CREDENTIALS = 1700..2000;
/// The secret of the CredRandom feature.
CRED_RANDOM_SECRET = 2041;
/// List of RP IDs allowed to read the minimum PIN length.
#[cfg(feature = "with_ctap2_1")]
_MIN_PIN_LENGTH_RP_IDS = 2042;
/// The minimum PIN length.
///
/// If the entry is absent, the minimum PIN length is `DEFAULT_MIN_PIN_LENGTH`.
#[cfg(feature = "with_ctap2_1")]
MIN_PIN_LENGTH = 2043;
/// The number of PIN retries.
///
/// If the entry is absent, the number of PIN retries is `MAX_PIN_RETRIES`.
PIN_RETRIES = 2044;
/// The PIN hash.
///
/// If the entry is absent, there is no PIN set.
PIN_HASH = 2045;
/// The encryption and hmac keys.
///
/// This entry is always present. It is generated at startup if absent.
MASTER_KEYS = 2046;
/// The global signature counter.
///
/// If the entry is absent, the counter is 0.
GLOBAL_SIGNATURE_COUNTER = 2047;
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn enough_credentials() {
use super::super::MAX_SUPPORTED_RESIDENTIAL_KEYS;
assert!(MAX_SUPPORTED_RESIDENTIAL_KEYS <= CREDENTIALS.end - CREDENTIALS.start);
}
#[test]
fn keys_are_disjoint() {
// Check that keys are in the range.
for keys in ALL_KEYS {
assert!(KEY_RANGE.start <= keys.start && keys.end <= KEY_RANGE.end);
}
// Check that keys are assigned at most once, essentially partitioning the range.
for key in KEY_RANGE {
assert!(ALL_KEYS.iter().filter(|keys| keys.contains(&key)).count() <= 1);
}
}
}

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