Merge branch 'master' into authenticator-selection
This commit is contained in:
@@ -12,6 +12,142 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
|
use crate::values::{KeyType, Value};
|
||||||
|
use alloc::collections::btree_map;
|
||||||
|
use core::cmp::Ordering;
|
||||||
|
use core::iter::Peekable;
|
||||||
|
|
||||||
|
/// This macro generates code to extract multiple values from a `BTreeMap<KeyType, Value>` at once
|
||||||
|
/// in an optimized manner, consuming the input map.
|
||||||
|
///
|
||||||
|
/// It takes as input a `BTreeMap` as well as a list of identifiers and keys, and generates code
|
||||||
|
/// that assigns the corresponding values to new variables using the given identifiers. Each of
|
||||||
|
/// these variables has type `Option<Value>`, to account for the case where keys aren't found.
|
||||||
|
///
|
||||||
|
/// **Important:** Keys passed to the `destructure_cbor_map!` macro **must be sorted** in increasing
|
||||||
|
/// order. If not, the algorithm can yield incorrect results, such a assigning `None` to a variable
|
||||||
|
/// even if the corresponding key existed in the map. **No runtime checks** are made for this in the
|
||||||
|
/// `destructure_cbor_map!` macro, in order to avoid overhead at runtime. However, assertions that
|
||||||
|
/// keys are sorted are added in `cfg(test)` mode, so that unit tests can verify ahead of time that
|
||||||
|
/// the keys are indeed sorted. This macro is therefore **not suitable for dynamic keys** that can
|
||||||
|
/// change at runtime.
|
||||||
|
///
|
||||||
|
/// Semantically, provided that the keys are sorted as specified above, the following two snippets
|
||||||
|
/// of code are equivalent, but the `destructure_cbor_map!` version is more optimized, as it doesn't
|
||||||
|
/// re-balance the `BTreeMap` for each key, contrary to the `BTreeMap::remove` operations.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate alloc;
|
||||||
|
/// # #[macro_use]
|
||||||
|
/// # extern crate cbor;
|
||||||
|
/// #
|
||||||
|
/// # fn main() {
|
||||||
|
/// # let map = alloc::collections::BTreeMap::new();
|
||||||
|
/// destructure_cbor_map! {
|
||||||
|
/// let {
|
||||||
|
/// 1 => x,
|
||||||
|
/// "key" => y,
|
||||||
|
/// } = map;
|
||||||
|
/// }
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate alloc;
|
||||||
|
/// # #[macro_use]
|
||||||
|
/// # extern crate cbor;
|
||||||
|
/// #
|
||||||
|
/// # fn main() {
|
||||||
|
/// # let mut map = alloc::collections::BTreeMap::<cbor::KeyType, _>::new();
|
||||||
|
/// use cbor::values::IntoCborKey;
|
||||||
|
/// let x: Option<cbor::Value> = map.remove(&1.into_cbor_key());
|
||||||
|
/// let y: Option<cbor::Value> = map.remove(&"key".into_cbor_key());
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! destructure_cbor_map {
|
||||||
|
( let { $( $key:expr => $variable:ident, )+ } = $map:expr; ) => {
|
||||||
|
// A pre-requisite for this algorithm to work is that the keys to extract from the map are
|
||||||
|
// sorted - the behavior is unspecified if the keys are not sorted.
|
||||||
|
// Therefore, in test mode we add assertions that the keys are indeed sorted.
|
||||||
|
#[cfg(test)]
|
||||||
|
assert_sorted_keys!($( $key, )+);
|
||||||
|
|
||||||
|
use $crate::values::{IntoCborKey, Value};
|
||||||
|
use $crate::macros::destructure_cbor_map_peek_value;
|
||||||
|
|
||||||
|
// This algorithm first converts the map into a peekable iterator - whose items are sorted
|
||||||
|
// in strictly increasing order of keys. Then, the repeated calls to the "peek value"
|
||||||
|
// helper function will consume this iterator and yield values (or `None`) when reaching
|
||||||
|
// the keys to extract.
|
||||||
|
//
|
||||||
|
// This is where the pre-requisite that keys to extract are sorted is important: the
|
||||||
|
// algorithm does a single linear scan over the iterator and therefore keys to extract have
|
||||||
|
// to come in the same order (i.e. sorted).
|
||||||
|
let mut it = $map.into_iter().peekable();
|
||||||
|
$(
|
||||||
|
let $variable: Option<Value> = destructure_cbor_map_peek_value(&mut it, $key.into_cbor_key());
|
||||||
|
)+
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This function is an internal detail of the `destructure_cbor_map!` macro, but has public
|
||||||
|
/// visibility so that users of the macro can use it.
|
||||||
|
///
|
||||||
|
/// Given a peekable iterator of key-value pairs sorted in strictly increasing key order and a
|
||||||
|
/// needle key, this function consumes all items whose key compares less than or equal to the
|
||||||
|
/// needle, and returns `Some(value)` if the needle was present as the key in the iterator and
|
||||||
|
/// `None` otherwise.
|
||||||
|
///
|
||||||
|
/// The logic is separated into its own function to reduce binary size, as otherwise the logic
|
||||||
|
/// would be inlined for every use case. As of June 2020, this saves ~40KB of binary size for the
|
||||||
|
/// CTAP2 application of OpenSK.
|
||||||
|
pub fn destructure_cbor_map_peek_value(
|
||||||
|
it: &mut Peekable<btree_map::IntoIter<KeyType, Value>>,
|
||||||
|
needle: KeyType,
|
||||||
|
) -> Option<Value> {
|
||||||
|
loop {
|
||||||
|
match it.peek() {
|
||||||
|
None => return None,
|
||||||
|
Some(item) => {
|
||||||
|
let key: &KeyType = &item.0;
|
||||||
|
match key.cmp(&needle) {
|
||||||
|
Ordering::Less => {
|
||||||
|
it.next();
|
||||||
|
}
|
||||||
|
Ordering::Equal => {
|
||||||
|
let value: Value = it.next().unwrap().1;
|
||||||
|
return Some(value);
|
||||||
|
}
|
||||||
|
Ordering::Greater => return None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! assert_sorted_keys {
|
||||||
|
// Last key
|
||||||
|
( $key:expr, ) => {
|
||||||
|
};
|
||||||
|
|
||||||
|
( $key1:expr, $key2:expr, $( $keys:expr, )* ) => {
|
||||||
|
{
|
||||||
|
use $crate::values::{IntoCborKey, KeyType};
|
||||||
|
let k1: KeyType = $key1.into_cbor_key();
|
||||||
|
let k2: KeyType = $key2.into_cbor_key();
|
||||||
|
assert!(
|
||||||
|
k1 < k2,
|
||||||
|
"{:?} < {:?} failed. The destructure_cbor_map! macro requires keys in sorted order.",
|
||||||
|
k1,
|
||||||
|
k2,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
assert_sorted_keys!($key2, $( $keys, )*);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! cbor_map {
|
macro_rules! cbor_map {
|
||||||
// trailing comma case
|
// trailing comma case
|
||||||
@@ -497,4 +633,99 @@ mod test {
|
|||||||
);
|
);
|
||||||
assert_eq!(a, b);
|
assert_eq!(a, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extract_map(cbor_value: Value) -> BTreeMap<KeyType, Value> {
|
||||||
|
match cbor_value {
|
||||||
|
Value::Map(map) => map,
|
||||||
|
_ => panic!("Expected CBOR map."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_destructure_cbor_map_simple() {
|
||||||
|
let map = cbor_map! {
|
||||||
|
1 => 10,
|
||||||
|
2 => 20,
|
||||||
|
};
|
||||||
|
|
||||||
|
destructure_cbor_map! {
|
||||||
|
let {
|
||||||
|
1 => x1,
|
||||||
|
2 => x2,
|
||||||
|
} = extract_map(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(x1, Some(cbor_unsigned!(10)));
|
||||||
|
assert_eq!(x2, Some(cbor_unsigned!(20)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn test_destructure_cbor_map_unsorted() {
|
||||||
|
let map = cbor_map! {
|
||||||
|
1 => 10,
|
||||||
|
2 => 20,
|
||||||
|
};
|
||||||
|
|
||||||
|
destructure_cbor_map! {
|
||||||
|
// The keys are not sorted here, which violates the precondition of
|
||||||
|
// destructure_cbor_map. An assertion should catch that and make the test panic.
|
||||||
|
let {
|
||||||
|
2 => _x2,
|
||||||
|
1 => _x1,
|
||||||
|
} = extract_map(map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_destructure_cbor_map_partial() {
|
||||||
|
let map = cbor_map! {
|
||||||
|
1 => 10,
|
||||||
|
2 => 20,
|
||||||
|
3 => 30,
|
||||||
|
4 => 40,
|
||||||
|
5 => 50,
|
||||||
|
6 => 60,
|
||||||
|
7 => 70,
|
||||||
|
8 => 80,
|
||||||
|
9 => 90,
|
||||||
|
};
|
||||||
|
|
||||||
|
destructure_cbor_map! {
|
||||||
|
let {
|
||||||
|
3 => x3,
|
||||||
|
7 => x7,
|
||||||
|
} = extract_map(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(x3, Some(cbor_unsigned!(30)));
|
||||||
|
assert_eq!(x7, Some(cbor_unsigned!(70)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_destructure_cbor_map_missing() {
|
||||||
|
let map = cbor_map! {
|
||||||
|
1 => 10,
|
||||||
|
3 => 30,
|
||||||
|
4 => 40,
|
||||||
|
};
|
||||||
|
|
||||||
|
destructure_cbor_map! {
|
||||||
|
let {
|
||||||
|
0 => x0,
|
||||||
|
1 => x1,
|
||||||
|
2 => x2,
|
||||||
|
3 => x3,
|
||||||
|
4 => x4,
|
||||||
|
5 => x5,
|
||||||
|
} = extract_map(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(x0, None);
|
||||||
|
assert_eq!(x1, Some(cbor_unsigned!(10)));
|
||||||
|
assert_eq!(x2, None);
|
||||||
|
assert_eq!(x3, Some(cbor_unsigned!(30)));
|
||||||
|
assert_eq!(x4, Some(cbor_unsigned!(40)));
|
||||||
|
assert_eq!(x5, None);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
0b54df6d548849e24d67b9b022ca09cb33c51f078ce85d0c9c4635ffc69902e1 third_party/tock/target/thumbv7em-none-eabi/release/nrf52840dk.bin
|
0b54df6d548849e24d67b9b022ca09cb33c51f078ce85d0c9c4635ffc69902e1 third_party/tock/target/thumbv7em-none-eabi/release/nrf52840dk.bin
|
||||||
9ff63684ca08375e643f14f33dc6dc8131681bb562fb0df18f9c7f637e90cc73 target/nrf52840dk_merged.hex
|
e93f56b4b6bb602ab37cf967f1c3fd3d253e05ccc85d4718762f68216c35d68c target/nrf52840dk_merged.hex
|
||||||
052eec0ae526038352b9f7573468d0cf7fb5ec331d4dc1a2df75fdbd514ea5ca third_party/tock/target/thumbv7em-none-eabi/release/nrf52840_dongle.bin
|
052eec0ae526038352b9f7573468d0cf7fb5ec331d4dc1a2df75fdbd514ea5ca third_party/tock/target/thumbv7em-none-eabi/release/nrf52840_dongle.bin
|
||||||
f976e2975d908567398456afd9996fd60639f0a5e55d0bd5a96768a7cb7797c2 target/nrf52840_dongle_merged.hex
|
8cff9a4d513be338ba6a3fd91d3d4cfdd63bc066e8bf9dc22f64176114da08b8 target/nrf52840_dongle_merged.hex
|
||||||
908d7f4f40936d968b91ab6e19b2406612fe8c2c273d9c0b71ef1f55116780e0 third_party/tock/target/thumbv7em-none-eabi/release/nrf52840_dongle_dfu.bin
|
908d7f4f40936d968b91ab6e19b2406612fe8c2c273d9c0b71ef1f55116780e0 third_party/tock/target/thumbv7em-none-eabi/release/nrf52840_dongle_dfu.bin
|
||||||
ff13439347688a8c9ab53391fd4006e9e944743c47ea754e6ae348998f7b8048 target/nrf52840_dongle_dfu_merged.hex
|
94452673fb0022a07ac886d4ab74576f067c2d727aed30ed368f4e5af382238f target/nrf52840_dongle_dfu_merged.hex
|
||||||
34ecbecaebf1188277f2310fe769c8c60310d8576493242712854deb4ba1036e third_party/tock/target/thumbv7em-none-eabi/release/nrf52840_mdk_dfu.bin
|
34ecbecaebf1188277f2310fe769c8c60310d8576493242712854deb4ba1036e third_party/tock/target/thumbv7em-none-eabi/release/nrf52840_mdk_dfu.bin
|
||||||
f76f9fe06b0db3bdf361ae8eb79f1ac9b75c376914142341e1a77c836d1bfe42 target/nrf52840_mdk_dfu_merged.hex
|
414aaf7fcc3a0121ab02b1222d508ae503e268cb7da0df5795a6d6a01aeed345 target/nrf52840_mdk_dfu_merged.hex
|
||||||
e3d20aa71a70d721b66bb704767db1b8ee9b3fb8fb508937f42bdf6816a61c90 target/tab/ctap2.tab
|
212698e7c7919fa4542e1263d56f601632902f86bdf3d48cf6300b96ad452cb1 target/tab/ctap2.tab
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
29382e72d0f3c6a72ce9517211952ff29ea270193d7f0ddc48ca69009ee29925 third_party/tock/target/thumbv7em-none-eabi/release/nrf52840dk.bin
|
29382e72d0f3c6a72ce9517211952ff29ea270193d7f0ddc48ca69009ee29925 third_party/tock/target/thumbv7em-none-eabi/release/nrf52840dk.bin
|
||||||
bb2fbf0d9dab2b489a49d1dc3db8923086ab109d14f1f1aa8296f086a03b75dd target/nrf52840dk_merged.hex
|
4d5165d8ff46148a585ade23d3030c8a95928a158d283ccd7c93e14902452b6f target/nrf52840dk_merged.hex
|
||||||
30f239390ae9bef0825731e4c82d40470fc5e9bded2bf0d942e92dbb5d4faba1 third_party/tock/target/thumbv7em-none-eabi/release/nrf52840_dongle.bin
|
30f239390ae9bef0825731e4c82d40470fc5e9bded2bf0d942e92dbb5d4faba1 third_party/tock/target/thumbv7em-none-eabi/release/nrf52840_dongle.bin
|
||||||
e3e9dd3a633a0ceb4b971bcd82e3cd02dd37ccaca486e44f136ce79e4a5a407a target/nrf52840_dongle_merged.hex
|
8204a87c9e93909ed79907f2d7b655d07397161ecd64bd213bc483630a38e8c9 target/nrf52840_dongle_merged.hex
|
||||||
e3acf15d5ae3a22aecff6cc58db5fc311f538f47328d348b7ad7db7f9ab5e72c third_party/tock/target/thumbv7em-none-eabi/release/nrf52840_dongle_dfu.bin
|
e3acf15d5ae3a22aecff6cc58db5fc311f538f47328d348b7ad7db7f9ab5e72c third_party/tock/target/thumbv7em-none-eabi/release/nrf52840_dongle_dfu.bin
|
||||||
fba2fb9f4fdd40ded014a7b77d387d867808281d7d751a08eafb999a360e9e3f target/nrf52840_dongle_dfu_merged.hex
|
b937eaeea7ae9ca3c26bee082cb5af596942947c84171cb4d03cc66bc31d35da target/nrf52840_dongle_dfu_merged.hex
|
||||||
cae312a26a513ada6c198fdc59b2bba3860c51726b817a9fd17a4331ee12c882 third_party/tock/target/thumbv7em-none-eabi/release/nrf52840_mdk_dfu.bin
|
cae312a26a513ada6c198fdc59b2bba3860c51726b817a9fd17a4331ee12c882 third_party/tock/target/thumbv7em-none-eabi/release/nrf52840_mdk_dfu.bin
|
||||||
7f9773fa345ee47e5c344bd4ce4929d93a0b036087cb1bec427c275077bbe389 target/nrf52840_mdk_dfu_merged.hex
|
6d125877a207744a73b0b315152188a85329d31e8f85e8205ac6033e46056931 target/nrf52840_mdk_dfu_merged.hex
|
||||||
f4f734bb9a0681f5cad0edc0c23bb30f2790ab2f7029dda5f152cee6d68d90c7 target/tab/ctap2.tab
|
e6dbbc68daa1b5269dce5ddbc91ea00169f9c8ed8d94a574dac1524e63c21b18 target/tab/ctap2.tab
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ Min RAM size from sections in ELF: 16 bytes
|
|||||||
Number of writeable flash regions: 0
|
Number of writeable flash regions: 0
|
||||||
Adding .crt0_header section. Offset: 64 (0x40). Length: 64 (0x40) bytes.
|
Adding .crt0_header section. Offset: 64 (0x40). Length: 64 (0x40) bytes.
|
||||||
Entry point is in .text section
|
Entry point is in .text section
|
||||||
Adding .text section. Offset: 128 (0x80). Length: 175524 (0x2ada4) bytes.
|
Adding .text section. Offset: 128 (0x80). Length: 179204 (0x2bc04) bytes.
|
||||||
Adding .stack section. Offset: 175652 (0x2ae24). Length: 16384 (0x4000) bytes.
|
Adding .stack section. Offset: 179332 (0x2bc84). Length: 16384 (0x4000) bytes.
|
||||||
Searching for .rel.X sections to add.
|
Searching for .rel.X sections to add.
|
||||||
TBF Header:
|
TBF Header:
|
||||||
version: 2 0x2
|
version: 2 0x2
|
||||||
@@ -24,8 +24,8 @@ Min RAM size from sections in ELF: 16 bytes
|
|||||||
Number of writeable flash regions: 0
|
Number of writeable flash regions: 0
|
||||||
Adding .crt0_header section. Offset: 64 (0x40). Length: 64 (0x40) bytes.
|
Adding .crt0_header section. Offset: 64 (0x40). Length: 64 (0x40) bytes.
|
||||||
Entry point is in .text section
|
Entry point is in .text section
|
||||||
Adding .text section. Offset: 128 (0x80). Length: 175524 (0x2ada4) bytes.
|
Adding .text section. Offset: 128 (0x80). Length: 179204 (0x2bc04) bytes.
|
||||||
Adding .stack section. Offset: 175652 (0x2ae24). Length: 16384 (0x4000) bytes.
|
Adding .stack section. Offset: 179332 (0x2bc84). Length: 16384 (0x4000) bytes.
|
||||||
Searching for .rel.X sections to add.
|
Searching for .rel.X sections to add.
|
||||||
TBF Header:
|
TBF Header:
|
||||||
version: 2 0x2
|
version: 2 0x2
|
||||||
@@ -43,8 +43,8 @@ Min RAM size from sections in ELF: 16 bytes
|
|||||||
Number of writeable flash regions: 0
|
Number of writeable flash regions: 0
|
||||||
Adding .crt0_header section. Offset: 64 (0x40). Length: 64 (0x40) bytes.
|
Adding .crt0_header section. Offset: 64 (0x40). Length: 64 (0x40) bytes.
|
||||||
Entry point is in .text section
|
Entry point is in .text section
|
||||||
Adding .text section. Offset: 128 (0x80). Length: 175524 (0x2ada4) bytes.
|
Adding .text section. Offset: 128 (0x80). Length: 179204 (0x2bc04) bytes.
|
||||||
Adding .stack section. Offset: 175652 (0x2ae24). Length: 16384 (0x4000) bytes.
|
Adding .stack section. Offset: 179332 (0x2bc84). Length: 16384 (0x4000) bytes.
|
||||||
Searching for .rel.X sections to add.
|
Searching for .rel.X sections to add.
|
||||||
TBF Header:
|
TBF Header:
|
||||||
version: 2 0x2
|
version: 2 0x2
|
||||||
@@ -62,8 +62,8 @@ Min RAM size from sections in ELF: 16 bytes
|
|||||||
Number of writeable flash regions: 0
|
Number of writeable flash regions: 0
|
||||||
Adding .crt0_header section. Offset: 64 (0x40). Length: 64 (0x40) bytes.
|
Adding .crt0_header section. Offset: 64 (0x40). Length: 64 (0x40) bytes.
|
||||||
Entry point is in .text section
|
Entry point is in .text section
|
||||||
Adding .text section. Offset: 128 (0x80). Length: 175524 (0x2ada4) bytes.
|
Adding .text section. Offset: 128 (0x80). Length: 179204 (0x2bc04) bytes.
|
||||||
Adding .stack section. Offset: 175652 (0x2ae24). Length: 16384 (0x4000) bytes.
|
Adding .stack section. Offset: 179332 (0x2bc84). Length: 16384 (0x4000) bytes.
|
||||||
Searching for .rel.X sections to add.
|
Searching for .rel.X sections to add.
|
||||||
TBF Header:
|
TBF Header:
|
||||||
version: 2 0x2
|
version: 2 0x2
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ Min RAM size from sections in ELF: 16 bytes
|
|||||||
Number of writeable flash regions: 0
|
Number of writeable flash regions: 0
|
||||||
Adding .crt0_header section. Offset: 64 (0x40). Length: 64 (0x40) bytes.
|
Adding .crt0_header section. Offset: 64 (0x40). Length: 64 (0x40) bytes.
|
||||||
Entry point is in .text section
|
Entry point is in .text section
|
||||||
Adding .text section. Offset: 128 (0x80). Length: 174820 (0x2aae4) bytes.
|
Adding .text section. Offset: 128 (0x80). Length: 179236 (0x2bc24) bytes.
|
||||||
Adding .stack section. Offset: 174948 (0x2ab64). Length: 16384 (0x4000) bytes.
|
Adding .stack section. Offset: 179364 (0x2bca4). Length: 16384 (0x4000) bytes.
|
||||||
Searching for .rel.X sections to add.
|
Searching for .rel.X sections to add.
|
||||||
TBF Header:
|
TBF Header:
|
||||||
version: 2 0x2
|
version: 2 0x2
|
||||||
@@ -24,8 +24,8 @@ Min RAM size from sections in ELF: 16 bytes
|
|||||||
Number of writeable flash regions: 0
|
Number of writeable flash regions: 0
|
||||||
Adding .crt0_header section. Offset: 64 (0x40). Length: 64 (0x40) bytes.
|
Adding .crt0_header section. Offset: 64 (0x40). Length: 64 (0x40) bytes.
|
||||||
Entry point is in .text section
|
Entry point is in .text section
|
||||||
Adding .text section. Offset: 128 (0x80). Length: 174820 (0x2aae4) bytes.
|
Adding .text section. Offset: 128 (0x80). Length: 179236 (0x2bc24) bytes.
|
||||||
Adding .stack section. Offset: 174948 (0x2ab64). Length: 16384 (0x4000) bytes.
|
Adding .stack section. Offset: 179364 (0x2bca4). Length: 16384 (0x4000) bytes.
|
||||||
Searching for .rel.X sections to add.
|
Searching for .rel.X sections to add.
|
||||||
TBF Header:
|
TBF Header:
|
||||||
version: 2 0x2
|
version: 2 0x2
|
||||||
@@ -43,8 +43,8 @@ Min RAM size from sections in ELF: 16 bytes
|
|||||||
Number of writeable flash regions: 0
|
Number of writeable flash regions: 0
|
||||||
Adding .crt0_header section. Offset: 64 (0x40). Length: 64 (0x40) bytes.
|
Adding .crt0_header section. Offset: 64 (0x40). Length: 64 (0x40) bytes.
|
||||||
Entry point is in .text section
|
Entry point is in .text section
|
||||||
Adding .text section. Offset: 128 (0x80). Length: 174820 (0x2aae4) bytes.
|
Adding .text section. Offset: 128 (0x80). Length: 179236 (0x2bc24) bytes.
|
||||||
Adding .stack section. Offset: 174948 (0x2ab64). Length: 16384 (0x4000) bytes.
|
Adding .stack section. Offset: 179364 (0x2bca4). Length: 16384 (0x4000) bytes.
|
||||||
Searching for .rel.X sections to add.
|
Searching for .rel.X sections to add.
|
||||||
TBF Header:
|
TBF Header:
|
||||||
version: 2 0x2
|
version: 2 0x2
|
||||||
@@ -62,8 +62,8 @@ Min RAM size from sections in ELF: 16 bytes
|
|||||||
Number of writeable flash regions: 0
|
Number of writeable flash regions: 0
|
||||||
Adding .crt0_header section. Offset: 64 (0x40). Length: 64 (0x40) bytes.
|
Adding .crt0_header section. Offset: 64 (0x40). Length: 64 (0x40) bytes.
|
||||||
Entry point is in .text section
|
Entry point is in .text section
|
||||||
Adding .text section. Offset: 128 (0x80). Length: 174820 (0x2aae4) bytes.
|
Adding .text section. Offset: 128 (0x80). Length: 179236 (0x2bc24) bytes.
|
||||||
Adding .stack section. Offset: 174948 (0x2ab64). Length: 16384 (0x4000) bytes.
|
Adding .stack section. Offset: 179364 (0x2bca4). Length: 16384 (0x4000) bytes.
|
||||||
Searching for .rel.X sections to add.
|
Searching for .rel.X sections to add.
|
||||||
TBF Header:
|
TBF Header:
|
||||||
version: 2 0x2
|
version: 2 0x2
|
||||||
|
|||||||
@@ -131,26 +131,31 @@ impl TryFrom<cbor::Value> for AuthenticatorMakeCredentialParameters {
|
|||||||
type Error = Ctap2StatusCode;
|
type Error = Ctap2StatusCode;
|
||||||
|
|
||||||
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
|
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
|
||||||
let mut param_map = extract_map(cbor_value)?;
|
destructure_cbor_map! {
|
||||||
|
let {
|
||||||
|
1 => client_data_hash,
|
||||||
|
2 => rp,
|
||||||
|
3 => user,
|
||||||
|
4 => cred_param_vec,
|
||||||
|
5 => exclude_list,
|
||||||
|
6 => extensions,
|
||||||
|
7 => options,
|
||||||
|
8 => pin_uv_auth_param,
|
||||||
|
9 => pin_uv_auth_protocol,
|
||||||
|
} = extract_map(cbor_value)?;
|
||||||
|
}
|
||||||
|
|
||||||
let client_data_hash =
|
let client_data_hash = extract_byte_string(ok_or_missing(client_data_hash)?)?;
|
||||||
extract_byte_string(ok_or_missing(param_map.remove(&cbor_unsigned!(1)))?)?;
|
let rp = PublicKeyCredentialRpEntity::try_from(ok_or_missing(rp)?)?;
|
||||||
|
let user = PublicKeyCredentialUserEntity::try_from(ok_or_missing(user)?)?;
|
||||||
|
|
||||||
let rp = PublicKeyCredentialRpEntity::try_from(ok_or_missing(
|
let cred_param_vec = extract_array(ok_or_missing(cred_param_vec)?)?;
|
||||||
param_map.remove(&cbor_unsigned!(2)),
|
|
||||||
)?)?;
|
|
||||||
|
|
||||||
let user = PublicKeyCredentialUserEntity::try_from(ok_or_missing(
|
|
||||||
param_map.remove(&cbor_unsigned!(3)),
|
|
||||||
)?)?;
|
|
||||||
|
|
||||||
let cred_param_vec = extract_array(ok_or_missing(param_map.remove(&cbor_unsigned!(4)))?)?;
|
|
||||||
let pub_key_cred_params = cred_param_vec
|
let pub_key_cred_params = cred_param_vec
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(PublicKeyCredentialParameter::try_from)
|
.map(PublicKeyCredentialParameter::try_from)
|
||||||
.collect::<Result<Vec<PublicKeyCredentialParameter>, Ctap2StatusCode>>()?;
|
.collect::<Result<Vec<PublicKeyCredentialParameter>, Ctap2StatusCode>>()?;
|
||||||
|
|
||||||
let exclude_list = match param_map.remove(&cbor_unsigned!(5)) {
|
let exclude_list = match exclude_list {
|
||||||
Some(entry) => {
|
Some(entry) => {
|
||||||
let exclude_list_vec = extract_array(entry)?;
|
let exclude_list_vec = extract_array(entry)?;
|
||||||
let list_len = MAX_CREDENTIAL_COUNT_IN_LIST.unwrap_or(exclude_list_vec.len());
|
let list_len = MAX_CREDENTIAL_COUNT_IN_LIST.unwrap_or(exclude_list_vec.len());
|
||||||
@@ -164,12 +169,11 @@ impl TryFrom<cbor::Value> for AuthenticatorMakeCredentialParameters {
|
|||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let extensions = param_map
|
let extensions = extensions
|
||||||
.remove(&cbor_unsigned!(6))
|
|
||||||
.map(MakeCredentialExtensions::try_from)
|
.map(MakeCredentialExtensions::try_from)
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
|
|
||||||
let options = match param_map.remove(&cbor_unsigned!(7)) {
|
let options = match options {
|
||||||
Some(entry) => MakeCredentialOptions::try_from(entry)?,
|
Some(entry) => MakeCredentialOptions::try_from(entry)?,
|
||||||
None => MakeCredentialOptions {
|
None => MakeCredentialOptions {
|
||||||
rk: false,
|
rk: false,
|
||||||
@@ -177,15 +181,8 @@ impl TryFrom<cbor::Value> for AuthenticatorMakeCredentialParameters {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let pin_uv_auth_param = param_map
|
let pin_uv_auth_param = pin_uv_auth_param.map(extract_byte_string).transpose()?;
|
||||||
.remove(&cbor_unsigned!(8))
|
let pin_uv_auth_protocol = pin_uv_auth_protocol.map(extract_unsigned).transpose()?;
|
||||||
.map(extract_byte_string)
|
|
||||||
.transpose()?;
|
|
||||||
|
|
||||||
let pin_uv_auth_protocol = param_map
|
|
||||||
.remove(&cbor_unsigned!(9))
|
|
||||||
.map(extract_unsigned)
|
|
||||||
.transpose()?;
|
|
||||||
|
|
||||||
Ok(AuthenticatorMakeCredentialParameters {
|
Ok(AuthenticatorMakeCredentialParameters {
|
||||||
client_data_hash,
|
client_data_hash,
|
||||||
@@ -217,14 +214,22 @@ impl TryFrom<cbor::Value> for AuthenticatorGetAssertionParameters {
|
|||||||
type Error = Ctap2StatusCode;
|
type Error = Ctap2StatusCode;
|
||||||
|
|
||||||
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
|
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
|
||||||
let mut param_map = extract_map(cbor_value)?;
|
destructure_cbor_map! {
|
||||||
|
let {
|
||||||
|
1 => rp_id,
|
||||||
|
2 => client_data_hash,
|
||||||
|
3 => allow_list,
|
||||||
|
4 => extensions,
|
||||||
|
5 => options,
|
||||||
|
6 => pin_uv_auth_param,
|
||||||
|
7 => pin_uv_auth_protocol,
|
||||||
|
} = extract_map(cbor_value)?;
|
||||||
|
}
|
||||||
|
|
||||||
let rp_id = extract_text_string(ok_or_missing(param_map.remove(&cbor_unsigned!(1)))?)?;
|
let rp_id = extract_text_string(ok_or_missing(rp_id)?)?;
|
||||||
|
let client_data_hash = extract_byte_string(ok_or_missing(client_data_hash)?)?;
|
||||||
|
|
||||||
let client_data_hash =
|
let allow_list = match allow_list {
|
||||||
extract_byte_string(ok_or_missing(param_map.remove(&cbor_unsigned!(2)))?)?;
|
|
||||||
|
|
||||||
let allow_list = match param_map.remove(&cbor_unsigned!(3)) {
|
|
||||||
Some(entry) => {
|
Some(entry) => {
|
||||||
let allow_list_vec = extract_array(entry)?;
|
let allow_list_vec = extract_array(entry)?;
|
||||||
let list_len = MAX_CREDENTIAL_COUNT_IN_LIST.unwrap_or(allow_list_vec.len());
|
let list_len = MAX_CREDENTIAL_COUNT_IN_LIST.unwrap_or(allow_list_vec.len());
|
||||||
@@ -238,12 +243,11 @@ impl TryFrom<cbor::Value> for AuthenticatorGetAssertionParameters {
|
|||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let extensions = param_map
|
let extensions = extensions
|
||||||
.remove(&cbor_unsigned!(4))
|
|
||||||
.map(GetAssertionExtensions::try_from)
|
.map(GetAssertionExtensions::try_from)
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
|
|
||||||
let options = match param_map.remove(&cbor_unsigned!(5)) {
|
let options = match options {
|
||||||
Some(entry) => GetAssertionOptions::try_from(entry)?,
|
Some(entry) => GetAssertionOptions::try_from(entry)?,
|
||||||
None => GetAssertionOptions {
|
None => GetAssertionOptions {
|
||||||
up: true,
|
up: true,
|
||||||
@@ -251,15 +255,8 @@ impl TryFrom<cbor::Value> for AuthenticatorGetAssertionParameters {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let pin_uv_auth_param = param_map
|
let pin_uv_auth_param = pin_uv_auth_param.map(extract_byte_string).transpose()?;
|
||||||
.remove(&cbor_unsigned!(6))
|
let pin_uv_auth_protocol = pin_uv_auth_protocol.map(extract_unsigned).transpose()?;
|
||||||
.map(extract_byte_string)
|
|
||||||
.transpose()?;
|
|
||||||
|
|
||||||
let pin_uv_auth_protocol = param_map
|
|
||||||
.remove(&cbor_unsigned!(7))
|
|
||||||
.map(extract_unsigned)
|
|
||||||
.transpose()?;
|
|
||||||
|
|
||||||
Ok(AuthenticatorGetAssertionParameters {
|
Ok(AuthenticatorGetAssertionParameters {
|
||||||
rp_id,
|
rp_id,
|
||||||
@@ -287,33 +284,26 @@ impl TryFrom<cbor::Value> for AuthenticatorClientPinParameters {
|
|||||||
type Error = Ctap2StatusCode;
|
type Error = Ctap2StatusCode;
|
||||||
|
|
||||||
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
|
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
|
||||||
let mut param_map = extract_map(cbor_value)?;
|
destructure_cbor_map! {
|
||||||
|
let {
|
||||||
|
1 => pin_protocol,
|
||||||
|
2 => sub_command,
|
||||||
|
3 => key_agreement,
|
||||||
|
4 => pin_auth,
|
||||||
|
5 => new_pin_enc,
|
||||||
|
6 => pin_hash_enc,
|
||||||
|
} = extract_map(cbor_value)?;
|
||||||
|
}
|
||||||
|
|
||||||
let pin_protocol = extract_unsigned(ok_or_missing(param_map.remove(&cbor_unsigned!(1)))?)?;
|
let pin_protocol = extract_unsigned(ok_or_missing(pin_protocol)?)?;
|
||||||
|
let sub_command = ClientPinSubCommand::try_from(ok_or_missing(sub_command)?)?;
|
||||||
let sub_command =
|
let key_agreement = key_agreement
|
||||||
ClientPinSubCommand::try_from(ok_or_missing(param_map.remove(&cbor_unsigned!(2)))?)?;
|
|
||||||
|
|
||||||
let key_agreement = param_map
|
|
||||||
.remove(&cbor_unsigned!(3))
|
|
||||||
.map(extract_map)
|
.map(extract_map)
|
||||||
.transpose()?
|
.transpose()?
|
||||||
.map(|x| CoseKey(x));
|
.map(|x| CoseKey(x));
|
||||||
|
let pin_auth = pin_auth.map(extract_byte_string).transpose()?;
|
||||||
let pin_auth = param_map
|
let new_pin_enc = new_pin_enc.map(extract_byte_string).transpose()?;
|
||||||
.remove(&cbor_unsigned!(4))
|
let pin_hash_enc = pin_hash_enc.map(extract_byte_string).transpose()?;
|
||||||
.map(extract_byte_string)
|
|
||||||
.transpose()?;
|
|
||||||
|
|
||||||
let new_pin_enc = param_map
|
|
||||||
.remove(&cbor_unsigned!(5))
|
|
||||||
.map(extract_byte_string)
|
|
||||||
.transpose()?;
|
|
||||||
|
|
||||||
let pin_hash_enc = param_map
|
|
||||||
.remove(&cbor_unsigned!(6))
|
|
||||||
.map(extract_byte_string)
|
|
||||||
.transpose()?;
|
|
||||||
|
|
||||||
Ok(AuthenticatorClientPinParameters {
|
Ok(AuthenticatorClientPinParameters {
|
||||||
pin_protocol,
|
pin_protocol,
|
||||||
|
|||||||
@@ -31,16 +31,18 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialRpEntity {
|
|||||||
type Error = Ctap2StatusCode;
|
type Error = Ctap2StatusCode;
|
||||||
|
|
||||||
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
|
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
|
||||||
let mut rp_map = extract_map(cbor_value)?;
|
destructure_cbor_map! {
|
||||||
let rp_id = extract_text_string(ok_or_missing(rp_map.remove(&cbor_text!("id")))?)?;
|
let {
|
||||||
let rp_name = rp_map
|
"id" => rp_id,
|
||||||
.remove(&cbor_text!("name"))
|
"icon" => rp_icon,
|
||||||
.map(extract_text_string)
|
"name" => rp_name,
|
||||||
.transpose()?;
|
} = extract_map(cbor_value)?;
|
||||||
let rp_icon = rp_map
|
}
|
||||||
.remove(&cbor_text!("icon"))
|
|
||||||
.map(extract_text_string)
|
let rp_id = extract_text_string(ok_or_missing(rp_id)?)?;
|
||||||
.transpose()?;
|
let rp_name = rp_name.map(extract_text_string).transpose()?;
|
||||||
|
let rp_icon = rp_icon.map(extract_text_string).transpose()?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
rp_id,
|
rp_id,
|
||||||
rp_name,
|
rp_name,
|
||||||
@@ -62,20 +64,20 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialUserEntity {
|
|||||||
type Error = Ctap2StatusCode;
|
type Error = Ctap2StatusCode;
|
||||||
|
|
||||||
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
|
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
|
||||||
let mut user_map = extract_map(cbor_value)?;
|
destructure_cbor_map! {
|
||||||
let user_id = extract_byte_string(ok_or_missing(user_map.remove(&cbor_text!("id")))?)?;
|
let {
|
||||||
let user_name = user_map
|
"id" => user_id,
|
||||||
.remove(&cbor_text!("name"))
|
"icon" => user_icon,
|
||||||
.map(extract_text_string)
|
"name" => user_name,
|
||||||
.transpose()?;
|
"displayName" => user_display_name,
|
||||||
let user_display_name = user_map
|
} = extract_map(cbor_value)?;
|
||||||
.remove(&cbor_text!("displayName"))
|
}
|
||||||
.map(extract_text_string)
|
|
||||||
.transpose()?;
|
let user_id = extract_byte_string(ok_or_missing(user_id)?)?;
|
||||||
let user_icon = user_map
|
let user_name = user_name.map(extract_text_string).transpose()?;
|
||||||
.remove(&cbor_text!("icon"))
|
let user_display_name = user_display_name.map(extract_text_string).transpose()?;
|
||||||
.map(extract_text_string)
|
let user_icon = user_icon.map(extract_text_string).transpose()?;
|
||||||
.transpose()?;
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
user_id,
|
user_id,
|
||||||
user_name,
|
user_name,
|
||||||
@@ -141,13 +143,15 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialParameter {
|
|||||||
type Error = Ctap2StatusCode;
|
type Error = Ctap2StatusCode;
|
||||||
|
|
||||||
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
|
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
|
||||||
let mut cred_param_map = extract_map(cbor_value)?;
|
destructure_cbor_map! {
|
||||||
let cred_type = PublicKeyCredentialType::try_from(ok_or_missing(
|
let {
|
||||||
cred_param_map.remove(&cbor_text!("type")),
|
"alg" => alg,
|
||||||
)?)?;
|
"type" => cred_type,
|
||||||
let alg = SignatureAlgorithm::try_from(ok_or_missing(
|
} = extract_map(cbor_value)?;
|
||||||
cred_param_map.remove(&cbor_text!("alg")),
|
}
|
||||||
)?)?;
|
|
||||||
|
let cred_type = PublicKeyCredentialType::try_from(ok_or_missing(cred_type)?)?;
|
||||||
|
let alg = SignatureAlgorithm::try_from(ok_or_missing(alg)?)?;
|
||||||
Ok(Self { cred_type, alg })
|
Ok(Self { cred_type, alg })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -209,12 +213,17 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialDescriptor {
|
|||||||
type Error = Ctap2StatusCode;
|
type Error = Ctap2StatusCode;
|
||||||
|
|
||||||
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
|
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
|
||||||
let mut cred_desc_map = extract_map(cbor_value)?;
|
destructure_cbor_map! {
|
||||||
let key_type = PublicKeyCredentialType::try_from(ok_or_missing(
|
let {
|
||||||
cred_desc_map.remove(&cbor_text!("type")),
|
"id" => key_id,
|
||||||
)?)?;
|
"type" => key_type,
|
||||||
let key_id = extract_byte_string(ok_or_missing(cred_desc_map.remove(&cbor_text!("id")))?)?;
|
"transports" => transports,
|
||||||
let transports = match cred_desc_map.remove(&cbor_text!("transports")) {
|
} = extract_map(cbor_value)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let key_type = PublicKeyCredentialType::try_from(ok_or_missing(key_type)?)?;
|
||||||
|
let key_id = extract_byte_string(ok_or_missing(key_id)?)?;
|
||||||
|
let transports = match transports {
|
||||||
Some(exclude_entry) => {
|
Some(exclude_entry) => {
|
||||||
let transport_vec = extract_array(exclude_entry)?;
|
let transport_vec = extract_array(exclude_entry)?;
|
||||||
let transports = transport_vec
|
let transports = transport_vec
|
||||||
@@ -225,6 +234,7 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialDescriptor {
|
|||||||
}
|
}
|
||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
key_type,
|
key_type,
|
||||||
key_id,
|
key_id,
|
||||||
@@ -253,12 +263,15 @@ impl TryFrom<cbor::Value> for MakeCredentialExtensions {
|
|||||||
type Error = Ctap2StatusCode;
|
type Error = Ctap2StatusCode;
|
||||||
|
|
||||||
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
|
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
|
||||||
let mut extensions_map = extract_map(cbor_value)?;
|
destructure_cbor_map! {
|
||||||
let hmac_secret = extensions_map
|
let {
|
||||||
.remove(&cbor_text!("hmac-secret"))
|
"credProtect" => cred_protect,
|
||||||
.map_or(Ok(false), extract_bool)?;
|
"hmac-secret" => hmac_secret,
|
||||||
let cred_protect = extensions_map
|
} = extract_map(cbor_value)?;
|
||||||
.remove(&cbor_text!("credProtect"))
|
}
|
||||||
|
|
||||||
|
let hmac_secret = hmac_secret.map_or(Ok(false), extract_bool)?;
|
||||||
|
let cred_protect = cred_protect
|
||||||
.map(CredentialProtectionPolicy::try_from)
|
.map(CredentialProtectionPolicy::try_from)
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
@@ -277,9 +290,13 @@ impl TryFrom<cbor::Value> for GetAssertionExtensions {
|
|||||||
type Error = Ctap2StatusCode;
|
type Error = Ctap2StatusCode;
|
||||||
|
|
||||||
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
|
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
|
||||||
let mut extensions_map = extract_map(cbor_value)?;
|
destructure_cbor_map! {
|
||||||
let hmac_secret = extensions_map
|
let {
|
||||||
.remove(&cbor_text!("hmac-secret"))
|
"hmac-secret" => hmac_secret,
|
||||||
|
} = extract_map(cbor_value)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let hmac_secret = hmac_secret
|
||||||
.map(GetAssertionHmacSecretInput::try_from)
|
.map(GetAssertionHmacSecretInput::try_from)
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
Ok(Self { hmac_secret })
|
Ok(Self { hmac_secret })
|
||||||
@@ -297,10 +314,17 @@ impl TryFrom<cbor::Value> for GetAssertionHmacSecretInput {
|
|||||||
type Error = Ctap2StatusCode;
|
type Error = Ctap2StatusCode;
|
||||||
|
|
||||||
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
|
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
|
||||||
let mut input_map = extract_map(cbor_value)?;
|
destructure_cbor_map! {
|
||||||
let cose_key = extract_map(ok_or_missing(input_map.remove(&cbor_unsigned!(1)))?)?;
|
let {
|
||||||
let salt_enc = extract_byte_string(ok_or_missing(input_map.remove(&cbor_unsigned!(2)))?)?;
|
1 => cose_key,
|
||||||
let salt_auth = extract_byte_string(ok_or_missing(input_map.remove(&cbor_unsigned!(3)))?)?;
|
2 => salt_enc,
|
||||||
|
3 => salt_auth,
|
||||||
|
} = extract_map(cbor_value)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cose_key = extract_map(ok_or_missing(cose_key)?)?;
|
||||||
|
let salt_enc = extract_byte_string(ok_or_missing(salt_enc)?)?;
|
||||||
|
let salt_auth = extract_byte_string(ok_or_missing(salt_auth)?)?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
key_agreement: CoseKey(cose_key),
|
key_agreement: CoseKey(cose_key),
|
||||||
salt_enc,
|
salt_enc,
|
||||||
@@ -320,17 +344,24 @@ impl TryFrom<cbor::Value> for MakeCredentialOptions {
|
|||||||
type Error = Ctap2StatusCode;
|
type Error = Ctap2StatusCode;
|
||||||
|
|
||||||
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
|
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
|
||||||
let mut options_map = extract_map(cbor_value)?;
|
destructure_cbor_map! {
|
||||||
let rk = match options_map.remove(&cbor_text!("rk")) {
|
let {
|
||||||
|
"rk" => rk,
|
||||||
|
"up" => up,
|
||||||
|
"uv" => uv,
|
||||||
|
} = extract_map(cbor_value)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let rk = match rk {
|
||||||
Some(options_entry) => extract_bool(options_entry)?,
|
Some(options_entry) => extract_bool(options_entry)?,
|
||||||
None => false,
|
None => false,
|
||||||
};
|
};
|
||||||
if let Some(options_entry) = options_map.remove(&cbor_text!("up")) {
|
if let Some(options_entry) = up {
|
||||||
if !extract_bool(options_entry)? {
|
if !extract_bool(options_entry)? {
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_INVALID_OPTION);
|
return Err(Ctap2StatusCode::CTAP2_ERR_INVALID_OPTION);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let uv = match options_map.remove(&cbor_text!("uv")) {
|
let uv = match uv {
|
||||||
Some(options_entry) => extract_bool(options_entry)?,
|
Some(options_entry) => extract_bool(options_entry)?,
|
||||||
None => false,
|
None => false,
|
||||||
};
|
};
|
||||||
@@ -348,17 +379,24 @@ impl TryFrom<cbor::Value> for GetAssertionOptions {
|
|||||||
type Error = Ctap2StatusCode;
|
type Error = Ctap2StatusCode;
|
||||||
|
|
||||||
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
|
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
|
||||||
let mut options_map = extract_map(cbor_value)?;
|
destructure_cbor_map! {
|
||||||
if let Some(options_entry) = options_map.remove(&cbor_text!("rk")) {
|
let {
|
||||||
|
"rk" => rk,
|
||||||
|
"up" => up,
|
||||||
|
"uv" => uv,
|
||||||
|
} = extract_map(cbor_value)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(options_entry) = rk {
|
||||||
// This is only for returning the correct status code.
|
// This is only for returning the correct status code.
|
||||||
extract_bool(options_entry)?;
|
extract_bool(options_entry)?;
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_INVALID_OPTION);
|
return Err(Ctap2StatusCode::CTAP2_ERR_INVALID_OPTION);
|
||||||
}
|
}
|
||||||
let up = match options_map.remove(&cbor_text!("up")) {
|
let up = match up {
|
||||||
Some(options_entry) => extract_bool(options_entry)?,
|
Some(options_entry) => extract_bool(options_entry)?,
|
||||||
None => true,
|
None => true,
|
||||||
};
|
};
|
||||||
let uv = match options_map.remove(&cbor_text!("uv")) {
|
let uv = match uv {
|
||||||
Some(options_entry) => extract_bool(options_entry)?,
|
Some(options_entry) => extract_bool(options_entry)?,
|
||||||
None => false,
|
None => false,
|
||||||
};
|
};
|
||||||
@@ -501,27 +539,33 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialSource {
|
|||||||
type Error = Ctap2StatusCode;
|
type Error = Ctap2StatusCode;
|
||||||
|
|
||||||
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
|
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
|
||||||
use PublicKeyCredentialSourceField::*;
|
use PublicKeyCredentialSourceField::{
|
||||||
let mut map = extract_map(cbor_value)?;
|
CredProtectPolicy, CredRandom, CredentialId, OtherUi, PrivateKey, RpId, UserHandle,
|
||||||
let credential_id = extract_byte_string(ok_or_missing(map.remove(&CredentialId.into()))?)?;
|
};
|
||||||
let private_key = extract_byte_string(ok_or_missing(map.remove(&PrivateKey.into()))?)?;
|
destructure_cbor_map! {
|
||||||
|
let {
|
||||||
|
CredentialId => credential_id,
|
||||||
|
PrivateKey => private_key,
|
||||||
|
RpId => rp_id,
|
||||||
|
UserHandle => user_handle,
|
||||||
|
OtherUi => other_ui,
|
||||||
|
CredRandom => cred_random,
|
||||||
|
CredProtectPolicy => cred_protect_policy,
|
||||||
|
} = extract_map(cbor_value)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let credential_id = extract_byte_string(ok_or_missing(credential_id)?)?;
|
||||||
|
let private_key = extract_byte_string(ok_or_missing(private_key)?)?;
|
||||||
if private_key.len() != 32 {
|
if private_key.len() != 32 {
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR);
|
return Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR);
|
||||||
}
|
}
|
||||||
let private_key = ecdsa::SecKey::from_bytes(array_ref!(private_key, 0, 32))
|
let private_key = ecdsa::SecKey::from_bytes(array_ref!(private_key, 0, 32))
|
||||||
.ok_or(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR)?;
|
.ok_or(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR)?;
|
||||||
let rp_id = extract_text_string(ok_or_missing(map.remove(&RpId.into()))?)?;
|
let rp_id = extract_text_string(ok_or_missing(rp_id)?)?;
|
||||||
let user_handle = extract_byte_string(ok_or_missing(map.remove(&UserHandle.into()))?)?;
|
let user_handle = extract_byte_string(ok_or_missing(user_handle)?)?;
|
||||||
let other_ui = map
|
let other_ui = other_ui.map(extract_text_string).transpose()?;
|
||||||
.remove(&OtherUi.into())
|
let cred_random = cred_random.map(extract_byte_string).transpose()?;
|
||||||
.map(extract_text_string)
|
let cred_protect_policy = cred_protect_policy
|
||||||
.transpose()?;
|
|
||||||
let cred_random = map
|
|
||||||
.remove(&CredRandom.into())
|
|
||||||
.map(extract_byte_string)
|
|
||||||
.transpose()?;
|
|
||||||
let cred_protect_policy = map
|
|
||||||
.remove(&CredProtectPolicy.into())
|
|
||||||
.map(CredentialProtectionPolicy::try_from)
|
.map(CredentialProtectionPolicy::try_from)
|
||||||
.transpose()?;
|
.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
|
||||||
@@ -599,27 +643,37 @@ impl TryFrom<CoseKey> for ecdh::PubKey {
|
|||||||
type Error = Ctap2StatusCode;
|
type Error = Ctap2StatusCode;
|
||||||
|
|
||||||
fn try_from(cose_key: CoseKey) -> Result<Self, Ctap2StatusCode> {
|
fn try_from(cose_key: CoseKey) -> Result<Self, Ctap2StatusCode> {
|
||||||
let mut cose_map = cose_key.0;
|
destructure_cbor_map! {
|
||||||
let key_type = extract_integer(ok_or_missing(cose_map.remove(&cbor_int!(1)))?)?;
|
let {
|
||||||
|
1 => key_type,
|
||||||
|
3 => algorithm,
|
||||||
|
-1 => curve,
|
||||||
|
-2 => x_bytes,
|
||||||
|
-3 => y_bytes,
|
||||||
|
} = cose_key.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let key_type = extract_integer(ok_or_missing(key_type)?)?;
|
||||||
if key_type != EC2_KEY_TYPE {
|
if key_type != EC2_KEY_TYPE {
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM);
|
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM);
|
||||||
}
|
}
|
||||||
let algorithm = extract_integer(ok_or_missing(cose_map.remove(&cbor_int!(3)))?)?;
|
let algorithm = extract_integer(ok_or_missing(algorithm)?)?;
|
||||||
if algorithm != ECDH_ALGORITHM && algorithm != ES256_ALGORITHM {
|
if algorithm != ECDH_ALGORITHM && algorithm != ES256_ALGORITHM {
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM);
|
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM);
|
||||||
}
|
}
|
||||||
let curve = extract_integer(ok_or_missing(cose_map.remove(&cbor_int!(-1)))?)?;
|
let curve = extract_integer(ok_or_missing(curve)?)?;
|
||||||
if curve != P_256_CURVE {
|
if curve != P_256_CURVE {
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM);
|
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM);
|
||||||
}
|
}
|
||||||
let x_bytes = extract_byte_string(ok_or_missing(cose_map.remove(&cbor_int!(-2)))?)?;
|
let x_bytes = extract_byte_string(ok_or_missing(x_bytes)?)?;
|
||||||
if x_bytes.len() != ecdh::NBYTES {
|
if x_bytes.len() != ecdh::NBYTES {
|
||||||
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER);
|
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER);
|
||||||
}
|
}
|
||||||
let y_bytes = extract_byte_string(ok_or_missing(cose_map.remove(&cbor_int!(-3)))?)?;
|
let y_bytes = extract_byte_string(ok_or_missing(y_bytes)?)?;
|
||||||
if y_bytes.len() != ecdh::NBYTES {
|
if y_bytes.len() != ecdh::NBYTES {
|
||||||
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER);
|
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER);
|
||||||
}
|
}
|
||||||
|
|
||||||
let x_array_ref = array_ref![x_bytes.as_slice(), 0, ecdh::NBYTES];
|
let x_array_ref = array_ref![x_bytes.as_slice(), 0, ecdh::NBYTES];
|
||||||
let y_array_ref = array_ref![y_bytes.as_slice(), 0, ecdh::NBYTES];
|
let y_array_ref = array_ref![y_bytes.as_slice(), 0, ecdh::NBYTES];
|
||||||
ecdh::PubKey::from_coordinates(x_array_ref, y_array_ref)
|
ecdh::PubKey::from_coordinates(x_array_ref, y_array_ref)
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ use self::data_formats::{
|
|||||||
SignatureAlgorithm,
|
SignatureAlgorithm,
|
||||||
};
|
};
|
||||||
use self::hid::ChannelID;
|
use self::hid::ChannelID;
|
||||||
use self::key_material::{AAGUID, ATTESTATION_CERTIFICATE, ATTESTATION_PRIVATE_KEY};
|
|
||||||
use self::response::{
|
use self::response::{
|
||||||
AuthenticatorClientPinResponse, AuthenticatorGetAssertionResponse,
|
AuthenticatorClientPinResponse, AuthenticatorGetAssertionResponse,
|
||||||
AuthenticatorGetInfoResponse, AuthenticatorMakeCredentialResponse, ResponseData,
|
AuthenticatorGetInfoResponse, AuthenticatorMakeCredentialResponse, ResponseData,
|
||||||
@@ -531,7 +530,7 @@ where
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut auth_data = self.generate_auth_data(&rp_id_hash, flags);
|
let mut auth_data = self.generate_auth_data(&rp_id_hash, flags);
|
||||||
auth_data.extend(AAGUID);
|
auth_data.extend(self.persistent_store.aaguid()?);
|
||||||
// The length is fixed to 0x20 or 0x70 and fits one byte.
|
// The length is fixed to 0x20 or 0x70 and fits one byte.
|
||||||
if credential_id.len() > 0xFF {
|
if credential_id.len() > 0xFF {
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_RESPONSE_TOO_LONG);
|
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_RESPONSE_TOO_LONG);
|
||||||
@@ -556,18 +555,25 @@ where
|
|||||||
|
|
||||||
let mut signature_data = auth_data.clone();
|
let mut signature_data = auth_data.clone();
|
||||||
signature_data.extend(client_data_hash);
|
signature_data.extend(client_data_hash);
|
||||||
let (signature, x5c) = if USE_BATCH_ATTESTATION {
|
// We currently use the presence of the attestation private key in the persistent storage to
|
||||||
let attestation_key =
|
// decide whether batch attestation is needed.
|
||||||
crypto::ecdsa::SecKey::from_bytes(ATTESTATION_PRIVATE_KEY).unwrap();
|
let (signature, x5c) = match self.persistent_store.attestation_private_key()? {
|
||||||
(
|
Some(attestation_private_key) => {
|
||||||
attestation_key.sign_rfc6979::<crypto::sha256::Sha256>(&signature_data),
|
let attestation_key =
|
||||||
Some(vec![ATTESTATION_CERTIFICATE.to_vec()]),
|
crypto::ecdsa::SecKey::from_bytes(attestation_private_key).unwrap();
|
||||||
)
|
let attestation_certificate = self
|
||||||
} else {
|
.persistent_store
|
||||||
(
|
.attestation_certificate()?
|
||||||
|
.ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?;
|
||||||
|
(
|
||||||
|
attestation_key.sign_rfc6979::<crypto::sha256::Sha256>(&signature_data),
|
||||||
|
Some(vec![attestation_certificate]),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
None => (
|
||||||
sk.sign_rfc6979::<crypto::sha256::Sha256>(&signature_data),
|
sk.sign_rfc6979::<crypto::sha256::Sha256>(&signature_data),
|
||||||
None,
|
None,
|
||||||
)
|
),
|
||||||
};
|
};
|
||||||
let attestation_statement = PackedAttestationStatement {
|
let attestation_statement = PackedAttestationStatement {
|
||||||
alg: SignatureAlgorithm::ES256 as i64,
|
alg: SignatureAlgorithm::ES256 as i64,
|
||||||
@@ -794,7 +800,7 @@ where
|
|||||||
String::from(FIDO2_VERSION_STRING),
|
String::from(FIDO2_VERSION_STRING),
|
||||||
],
|
],
|
||||||
extensions: Some(vec![String::from("hmac-secret")]),
|
extensions: Some(vec![String::from("hmac-secret")]),
|
||||||
aaguid: *AAGUID,
|
aaguid: *self.persistent_store.aaguid()?,
|
||||||
options: Some(options_map),
|
options: Some(options_map),
|
||||||
max_msg_size: Some(1024),
|
max_msg_size: Some(1024),
|
||||||
pin_protocols: Some(vec![
|
pin_protocols: Some(vec![
|
||||||
@@ -1160,7 +1166,7 @@ mod test {
|
|||||||
0x02, 0x81, 0x6B, 0x68, 0x6D, 0x61, 0x63, 0x2D, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74,
|
0x02, 0x81, 0x6B, 0x68, 0x6D, 0x61, 0x63, 0x2D, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74,
|
||||||
0x03, 0x50,
|
0x03, 0x50,
|
||||||
]);
|
]);
|
||||||
expected_response.extend(AAGUID);
|
expected_response.extend(ctap_state.persistent_store.aaguid().unwrap());
|
||||||
expected_response.extend(&[
|
expected_response.extend(&[
|
||||||
0x04, 0xA3, 0x62, 0x72, 0x6B, 0xF5, 0x62, 0x75, 0x70, 0xF5, 0x69, 0x63, 0x6C, 0x69,
|
0x04, 0xA3, 0x62, 0x72, 0x6B, 0xF5, 0x62, 0x75, 0x70, 0xF5, 0x69, 0x63, 0x6C, 0x69,
|
||||||
0x65, 0x6E, 0x74, 0x50, 0x69, 0x6E, 0xF4, 0x05, 0x19, 0x04, 0x00, 0x06, 0x81, 0x01,
|
0x65, 0x6E, 0x74, 0x50, 0x69, 0x6E, 0xF4, 0x05, 0x19, 0x04, 0x00, 0x06, 0x81, 0x01,
|
||||||
@@ -1259,7 +1265,7 @@ mod test {
|
|||||||
0x34, 0xE2, 0x75, 0x1E, 0x68, 0x2F, 0xAB, 0x9F, 0x2D, 0x30, 0xAB, 0x13, 0xD2,
|
0x34, 0xE2, 0x75, 0x1E, 0x68, 0x2F, 0xAB, 0x9F, 0x2D, 0x30, 0xAB, 0x13, 0xD2,
|
||||||
0x12, 0x55, 0x86, 0xCE, 0x19, 0x47, 0x41, 0x00, 0x00, 0x00, 0x00,
|
0x12, 0x55, 0x86, 0xCE, 0x19, 0x47, 0x41, 0x00, 0x00, 0x00, 0x00,
|
||||||
];
|
];
|
||||||
expected_auth_data.extend(AAGUID);
|
expected_auth_data.extend(ctap_state.persistent_store.aaguid().unwrap());
|
||||||
expected_auth_data.extend(&[0x00, 0x20]);
|
expected_auth_data.extend(&[0x00, 0x20]);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
auth_data[0..expected_auth_data.len()],
|
auth_data[0..expected_auth_data.len()],
|
||||||
@@ -1296,7 +1302,7 @@ mod test {
|
|||||||
0x34, 0xE2, 0x75, 0x1E, 0x68, 0x2F, 0xAB, 0x9F, 0x2D, 0x30, 0xAB, 0x13, 0xD2,
|
0x34, 0xE2, 0x75, 0x1E, 0x68, 0x2F, 0xAB, 0x9F, 0x2D, 0x30, 0xAB, 0x13, 0xD2,
|
||||||
0x12, 0x55, 0x86, 0xCE, 0x19, 0x47, 0x41, 0x00, 0x00, 0x00, 0x00,
|
0x12, 0x55, 0x86, 0xCE, 0x19, 0x47, 0x41, 0x00, 0x00, 0x00, 0x00,
|
||||||
];
|
];
|
||||||
expected_auth_data.extend(AAGUID);
|
expected_auth_data.extend(ctap_state.persistent_store.aaguid().unwrap());
|
||||||
expected_auth_data.extend(&[0x00, ENCRYPTED_CREDENTIAL_ID_SIZE as u8]);
|
expected_auth_data.extend(&[0x00, ENCRYPTED_CREDENTIAL_ID_SIZE as u8]);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
auth_data[0..expected_auth_data.len()],
|
auth_data[0..expected_auth_data.len()],
|
||||||
@@ -1439,7 +1445,7 @@ mod test {
|
|||||||
0x34, 0xE2, 0x75, 0x1E, 0x68, 0x2F, 0xAB, 0x9F, 0x2D, 0x30, 0xAB, 0x13, 0xD2,
|
0x34, 0xE2, 0x75, 0x1E, 0x68, 0x2F, 0xAB, 0x9F, 0x2D, 0x30, 0xAB, 0x13, 0xD2,
|
||||||
0x12, 0x55, 0x86, 0xCE, 0x19, 0x47, 0xC1, 0x00, 0x00, 0x00, 0x00,
|
0x12, 0x55, 0x86, 0xCE, 0x19, 0x47, 0xC1, 0x00, 0x00, 0x00, 0x00,
|
||||||
];
|
];
|
||||||
expected_auth_data.extend(AAGUID);
|
expected_auth_data.extend(ctap_state.persistent_store.aaguid().unwrap());
|
||||||
expected_auth_data.extend(&[0x00, 0x20]);
|
expected_auth_data.extend(&[0x00, 0x20]);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
auth_data[0..expected_auth_data.len()],
|
auth_data[0..expected_auth_data.len()],
|
||||||
|
|||||||
@@ -67,5 +67,11 @@ pub enum Ctap2StatusCode {
|
|||||||
// CTAP2_ERR_VENDOR_FIRST = 0xF0,
|
// CTAP2_ERR_VENDOR_FIRST = 0xF0,
|
||||||
CTAP2_ERR_VENDOR_RESPONSE_TOO_LONG = 0xF0,
|
CTAP2_ERR_VENDOR_RESPONSE_TOO_LONG = 0xF0,
|
||||||
CTAP2_ERR_VENDOR_RESPONSE_CANNOT_WRITE_CBOR = 0xF1,
|
CTAP2_ERR_VENDOR_RESPONSE_CANNOT_WRITE_CBOR = 0xF1,
|
||||||
|
|
||||||
|
/// An internal invariant is broken.
|
||||||
|
///
|
||||||
|
/// This type of error is unexpected and the current state is undefined.
|
||||||
|
CTAP2_ERR_VENDOR_INTERNAL_ERROR = 0xF2,
|
||||||
|
|
||||||
CTAP2_ERR_VENDOR_LAST = 0xFF,
|
CTAP2_ERR_VENDOR_LAST = 0xFF,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
use crate::crypto::rng256::Rng256;
|
use crate::crypto::rng256::Rng256;
|
||||||
use crate::ctap::data_formats::{CredentialProtectionPolicy, PublicKeyCredentialSource};
|
use crate::ctap::data_formats::{CredentialProtectionPolicy, PublicKeyCredentialSource};
|
||||||
use crate::ctap::status_code::Ctap2StatusCode;
|
use crate::ctap::status_code::Ctap2StatusCode;
|
||||||
use crate::ctap::PIN_AUTH_LENGTH;
|
use crate::ctap::{key_material, PIN_AUTH_LENGTH, USE_BATCH_ATTESTATION};
|
||||||
use alloc::string::String;
|
use alloc::string::String;
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use core::convert::TryInto;
|
use core::convert::TryInto;
|
||||||
@@ -56,9 +56,14 @@ const GLOBAL_SIGNATURE_COUNTER: usize = 1;
|
|||||||
const MASTER_KEYS: usize = 2;
|
const MASTER_KEYS: usize = 2;
|
||||||
const PIN_HASH: usize = 3;
|
const PIN_HASH: usize = 3;
|
||||||
const PIN_RETRIES: usize = 4;
|
const PIN_RETRIES: usize = 4;
|
||||||
const NUM_TAGS: usize = 5;
|
const ATTESTATION_PRIVATE_KEY: usize = 5;
|
||||||
|
const ATTESTATION_CERTIFICATE: usize = 6;
|
||||||
|
const AAGUID: usize = 7;
|
||||||
|
const NUM_TAGS: usize = 8;
|
||||||
|
|
||||||
const MAX_PIN_RETRIES: u8 = 6;
|
const MAX_PIN_RETRIES: u8 = 6;
|
||||||
|
const ATTESTATION_PRIVATE_KEY_LENGTH: usize = 32;
|
||||||
|
const AAGUID_LENGTH: usize = 16;
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||||
enum Key {
|
enum Key {
|
||||||
@@ -73,6 +78,9 @@ enum Key {
|
|||||||
MasterKeys,
|
MasterKeys,
|
||||||
PinHash,
|
PinHash,
|
||||||
PinRetries,
|
PinRetries,
|
||||||
|
AttestationPrivateKey,
|
||||||
|
AttestationCertificate,
|
||||||
|
Aaguid,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct MasterKeys<'a> {
|
pub struct MasterKeys<'a> {
|
||||||
@@ -124,6 +132,9 @@ impl StoreConfig for Config {
|
|||||||
MASTER_KEYS => add(Key::MasterKeys),
|
MASTER_KEYS => add(Key::MasterKeys),
|
||||||
PIN_HASH => add(Key::PinHash),
|
PIN_HASH => add(Key::PinHash),
|
||||||
PIN_RETRIES => add(Key::PinRetries),
|
PIN_RETRIES => add(Key::PinRetries),
|
||||||
|
ATTESTATION_PRIVATE_KEY => add(Key::AttestationPrivateKey),
|
||||||
|
ATTESTATION_CERTIFICATE => add(Key::AttestationCertificate),
|
||||||
|
AAGUID => add(Key::Aaguid),
|
||||||
_ => debug_assert!(false),
|
_ => debug_assert!(false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -197,6 +208,20 @@ impl PersistentStore {
|
|||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
// The following 3 entries are meant to be written by vendor-specific commands.
|
||||||
|
if USE_BATCH_ATTESTATION {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self.store.find_one(&Key::Aaguid).is_none() {
|
||||||
|
self.set_aaguid(key_material::AAGUID).unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_credential(
|
pub fn find_credential(
|
||||||
@@ -395,10 +420,88 @@ impl PersistentStore {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn attestation_private_key(
|
||||||
|
&self,
|
||||||
|
) -> Result<Option<&[u8; ATTESTATION_PRIVATE_KEY_LENGTH]>, Ctap2StatusCode> {
|
||||||
|
let data = match self.store.find_one(&Key::AttestationPrivateKey) {
|
||||||
|
None => return Ok(None),
|
||||||
|
Some((_, entry)) => entry.data,
|
||||||
|
};
|
||||||
|
if data.len() != ATTESTATION_PRIVATE_KEY_LENGTH {
|
||||||
|
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
|
||||||
|
}
|
||||||
|
Ok(Some(array_ref!(data, 0, ATTESTATION_PRIVATE_KEY_LENGTH)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_attestation_private_key(
|
||||||
|
&mut self,
|
||||||
|
attestation_private_key: &[u8; ATTESTATION_PRIVATE_KEY_LENGTH],
|
||||||
|
) -> Result<(), Ctap2StatusCode> {
|
||||||
|
let entry = StoreEntry {
|
||||||
|
tag: ATTESTATION_PRIVATE_KEY,
|
||||||
|
data: attestation_private_key,
|
||||||
|
sensitive: false,
|
||||||
|
};
|
||||||
|
match self.store.find_one(&Key::AttestationPrivateKey) {
|
||||||
|
None => self.store.insert(entry)?,
|
||||||
|
Some((index, _)) => self.store.replace(index, entry)?,
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn attestation_certificate(&self) -> Result<Option<Vec<u8>>, Ctap2StatusCode> {
|
||||||
|
let data = match self.store.find_one(&Key::AttestationCertificate) {
|
||||||
|
None => return Ok(None),
|
||||||
|
Some((_, entry)) => entry.data,
|
||||||
|
};
|
||||||
|
Ok(Some(data.to_vec()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_attestation_certificate(
|
||||||
|
&mut self,
|
||||||
|
attestation_certificate: &[u8],
|
||||||
|
) -> Result<(), Ctap2StatusCode> {
|
||||||
|
let entry = StoreEntry {
|
||||||
|
tag: ATTESTATION_CERTIFICATE,
|
||||||
|
data: attestation_certificate,
|
||||||
|
sensitive: false,
|
||||||
|
};
|
||||||
|
match self.store.find_one(&Key::AttestationCertificate) {
|
||||||
|
None => self.store.insert(entry)?,
|
||||||
|
Some((index, _)) => self.store.replace(index, entry)?,
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn aaguid(&self) -> Result<&[u8; AAGUID_LENGTH], Ctap2StatusCode> {
|
||||||
|
let (_, entry) = self
|
||||||
|
.store
|
||||||
|
.find_one(&Key::Aaguid)
|
||||||
|
.ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?;
|
||||||
|
let data = entry.data;
|
||||||
|
if data.len() != AAGUID_LENGTH {
|
||||||
|
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
|
||||||
|
}
|
||||||
|
Ok(array_ref!(data, 0, AAGUID_LENGTH))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_aaguid(&mut self, aaguid: &[u8; AAGUID_LENGTH]) -> Result<(), Ctap2StatusCode> {
|
||||||
|
let entry = StoreEntry {
|
||||||
|
tag: AAGUID,
|
||||||
|
data: aaguid,
|
||||||
|
sensitive: false,
|
||||||
|
};
|
||||||
|
match self.store.find_one(&Key::Aaguid) {
|
||||||
|
None => self.store.insert(entry)?,
|
||||||
|
Some((index, _)) => self.store.replace(index, entry)?,
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn reset(&mut self, rng: &mut impl Rng256) {
|
pub fn reset(&mut self, rng: &mut impl Rng256) {
|
||||||
loop {
|
loop {
|
||||||
let index = {
|
let index = {
|
||||||
let mut iter = self.store.iter();
|
let mut iter = self.store.iter().filter(|(_, entry)| should_reset(entry));
|
||||||
match iter.next() {
|
match iter.next() {
|
||||||
None => break,
|
None => break,
|
||||||
Some((index, _)) => index,
|
Some((index, _)) => index,
|
||||||
@@ -420,6 +523,13 @@ impl From<StoreError> for Ctap2StatusCode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn should_reset(entry: &StoreEntry<'_>) -> bool {
|
||||||
|
match entry.tag {
|
||||||
|
ATTESTATION_PRIVATE_KEY | ATTESTATION_CERTIFICATE | AAGUID => false,
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn deserialize_credential(data: &[u8]) -> Option<PublicKeyCredentialSource> {
|
fn deserialize_credential(data: &[u8]) -> Option<PublicKeyCredentialSource> {
|
||||||
let cbor = cbor::read(data).ok()?;
|
let cbor = cbor::read(data).ok()?;
|
||||||
cbor.try_into().ok()
|
cbor.try_into().ok()
|
||||||
@@ -745,4 +855,41 @@ mod test {
|
|||||||
persistent_store.reset_pin_retries();
|
persistent_store.reset_pin_retries();
|
||||||
assert_eq!(persistent_store.pin_retries(), MAX_PIN_RETRIES);
|
assert_eq!(persistent_store.pin_retries(), MAX_PIN_RETRIES);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_persistent_keys() {
|
||||||
|
let mut rng = ThreadRng256 {};
|
||||||
|
let mut persistent_store = PersistentStore::new(&mut rng);
|
||||||
|
|
||||||
|
// Make sure the attestation are absent. There is no batch attestation in tests.
|
||||||
|
assert!(persistent_store
|
||||||
|
.attestation_private_key()
|
||||||
|
.unwrap()
|
||||||
|
.is_none());
|
||||||
|
assert!(persistent_store
|
||||||
|
.attestation_certificate()
|
||||||
|
.unwrap()
|
||||||
|
.is_none());
|
||||||
|
|
||||||
|
// Make sure the persistent keys are initialized.
|
||||||
|
persistent_store
|
||||||
|
.set_attestation_private_key(key_material::ATTESTATION_PRIVATE_KEY)
|
||||||
|
.unwrap();
|
||||||
|
persistent_store
|
||||||
|
.set_attestation_certificate(key_material::ATTESTATION_CERTIFICATE)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(persistent_store.aaguid().unwrap(), key_material::AAGUID);
|
||||||
|
|
||||||
|
// The persistent keys stay initialized and preserve their value after a reset.
|
||||||
|
persistent_store.reset(&mut rng);
|
||||||
|
assert_eq!(
|
||||||
|
persistent_store.attestation_private_key().unwrap().unwrap(),
|
||||||
|
key_material::ATTESTATION_PRIVATE_KEY
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
persistent_store.attestation_certificate().unwrap().unwrap(),
|
||||||
|
key_material::ATTESTATION_CERTIFICATE
|
||||||
|
);
|
||||||
|
assert_eq!(persistent_store.aaguid().unwrap(), key_material::AAGUID);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user