Merge branch 'master' into aaguid
This commit is contained in:
@@ -99,6 +99,10 @@ a few things you can personalize:
|
|||||||
4. Depending on your available flash storage, choose an appropriate maximum
|
4. Depending on your available flash storage, choose an appropriate maximum
|
||||||
number of supported residential keys and number of pages in
|
number of supported residential keys and number of pages in
|
||||||
`ctap/storage.rs`.
|
`ctap/storage.rs`.
|
||||||
|
5. Change the default level for the credProtect extension in `ctap/mod.rs`.
|
||||||
|
When changing the default, resident credentials become undiscoverable without
|
||||||
|
user verification. This helps privacy, but can make usage less comfortable
|
||||||
|
for credentials that need less protection.
|
||||||
|
|
||||||
### 3D printed enclosure
|
### 3D printed enclosure
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
1003863864e06553e730eec6df4bf8d30c99f697ef9380efdc35eba679b4db78 third_party/tock/target/thumbv7em-none-eabi/release/nrf52840dk.bin
|
1003863864e06553e730eec6df4bf8d30c99f697ef9380efdc35eba679b4db78 third_party/tock/target/thumbv7em-none-eabi/release/nrf52840dk.bin
|
||||||
7ffc52ea6bfd1c3fde3398da4e894b5659770a74b466e052b4c3999436f9d78e target/nrf52840dk_merged.hex
|
022268c93fa8bbd9e54e082982b87c10a0e7c0486704de8219d1bb374304636a target/nrf52840dk_merged.hex
|
||||||
88f00a5e1dae6ab3f7571c254ac75f5f3e29ebea7f3ca46c16cfdc3708e804fc third_party/tock/target/thumbv7em-none-eabi/release/nrf52840_dongle.bin
|
88f00a5e1dae6ab3f7571c254ac75f5f3e29ebea7f3ca46c16cfdc3708e804fc third_party/tock/target/thumbv7em-none-eabi/release/nrf52840_dongle.bin
|
||||||
a0cd9144582b616a51d4f097713cbd697d418c19d031906f58fc630d7286ed85 target/nrf52840_dongle_merged.hex
|
8d68ecc700527789b8edf318f0872ca8fc3b72fa73236f4e06bec89a3682fcf8 target/nrf52840_dongle_merged.hex
|
||||||
1bc69b48a2c48da55db8b322902e1fe3f2e095c0dd8517db28837d86e0addc85 third_party/tock/target/thumbv7em-none-eabi/release/nrf52840_dongle_dfu.bin
|
1bc69b48a2c48da55db8b322902e1fe3f2e095c0dd8517db28837d86e0addc85 third_party/tock/target/thumbv7em-none-eabi/release/nrf52840_dongle_dfu.bin
|
||||||
5879d90971a7429e8890ce4a5db694499f391ffd7c6707c6820538ee8126ff5f target/nrf52840_dongle_dfu_merged.hex
|
af5465e4209914aaf74ee878d03e883a717827119e47b9295aa279ee21f0c5f4 target/nrf52840_dongle_dfu_merged.hex
|
||||||
f38ee31d3a09e7e11848e78b5318f95517b6dcd076afcb37e6e3d3e5e9995cc7 third_party/tock/target/thumbv7em-none-eabi/release/nrf52840_mdk_dfu.bin
|
f38ee31d3a09e7e11848e78b5318f95517b6dcd076afcb37e6e3d3e5e9995cc7 third_party/tock/target/thumbv7em-none-eabi/release/nrf52840_mdk_dfu.bin
|
||||||
a4e7451174ee75a27acfb9bdd3c977f5cf3e756e40219b706c97eab3a21c7ac0 target/nrf52840_mdk_dfu_merged.hex
|
23603386a615e4e8cb2173c5ce4762110e6cbb979efdbb6e8bef9bc1e3988de4 target/nrf52840_mdk_dfu_merged.hex
|
||||||
f364a923a4c56b5bbba8b590c8c296b29f4448f3117cedf433d4752867fac6ef target/tab/ctap2.tab
|
c2cbcc28b835934be4c3d3e3c5bdaba642a5811d760c1d2cb73d26b6474e4219 target/tab/ctap2.tab
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
c182bb4902fff51b2f56810fc2a27df3646cd66ba21359162354d53445623ab8 third_party/tock/target/thumbv7em-none-eabi/release/nrf52840dk.bin
|
c182bb4902fff51b2f56810fc2a27df3646cd66ba21359162354d53445623ab8 third_party/tock/target/thumbv7em-none-eabi/release/nrf52840dk.bin
|
||||||
9624888f52510e1e7a13681a959ecb9dd0e325b3856422b48d28abadc6546211 target/nrf52840dk_merged.hex
|
d8b62ece387a77cc21f2c10a5f5d65d0d57bf4739b47fd86d2c9ecdd90fbfd7e target/nrf52840dk_merged.hex
|
||||||
0a9929ba8fa57e8a502a49fc7c53177397202e1b11f4c7c3cb6ed68b2b99dd46 third_party/tock/target/thumbv7em-none-eabi/release/nrf52840_dongle.bin
|
0a9929ba8fa57e8a502a49fc7c53177397202e1b11f4c7c3cb6ed68b2b99dd46 third_party/tock/target/thumbv7em-none-eabi/release/nrf52840_dongle.bin
|
||||||
31b41cc1010c621765a4385ecd678950ddb2e1eaa11e0efaa9df818a1abfd022 target/nrf52840_dongle_merged.hex
|
380de1a910b4d9eeb0c814b11b074b2e66334968cc99a4bd34d52a1fce3c5a79 target/nrf52840_dongle_merged.hex
|
||||||
cca9086c9149c607589b23ffa599a5e4c26db7c20bd3700b79528bd3a5df991d third_party/tock/target/thumbv7em-none-eabi/release/nrf52840_dongle_dfu.bin
|
cca9086c9149c607589b23ffa599a5e4c26db7c20bd3700b79528bd3a5df991d third_party/tock/target/thumbv7em-none-eabi/release/nrf52840_dongle_dfu.bin
|
||||||
0a9c92d56b02b42c7d783606f7711c474fc73518a32b9c7e244c078011a67e6d target/nrf52840_dongle_dfu_merged.hex
|
4edd988b3e37991f1e58fc520e41f7666f8ae3e8d3993e1bb2fb71657a71fa50 target/nrf52840_dongle_dfu_merged.hex
|
||||||
8857488ba6a69e366f0da229bbfc012a2ad291d3a88d9494247d600c10bb19b7 third_party/tock/target/thumbv7em-none-eabi/release/nrf52840_mdk_dfu.bin
|
8857488ba6a69e366f0da229bbfc012a2ad291d3a88d9494247d600c10bb19b7 third_party/tock/target/thumbv7em-none-eabi/release/nrf52840_mdk_dfu.bin
|
||||||
a5fb5ebcf475f88be0273a4679975bcfee72014102a6191370a80120ca287f11 target/nrf52840_mdk_dfu_merged.hex
|
a51aba1cd12e55aa33fd9017af406583ebf14e1c690295b15cf147713dfe2561 target/nrf52840_mdk_dfu_merged.hex
|
||||||
7940a87663cf40941ea8c50ad1d99abf2ccbcacfcd157c1b0449dd3ed78e180e target/tab/ctap2.tab
|
40b413a8b645b4b47fae62a4311acb12cb0c57faff2757e45c18d9e5d441e52d target/tab/ctap2.tab
|
||||||
|
|||||||
@@ -13,10 +13,10 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use super::data_formats::{
|
use super::data_formats::{
|
||||||
ok_or_missing, read_array, read_byte_string, read_map, read_text_string, read_unsigned,
|
extract_array, extract_byte_string, extract_map, extract_text_string, extract_unsigned,
|
||||||
ClientPinSubCommand, CoseKey, Extensions, GetAssertionOptions, MakeCredentialOptions,
|
ok_or_missing, ClientPinSubCommand, CoseKey, GetAssertionExtensions, GetAssertionOptions,
|
||||||
PublicKeyCredentialDescriptor, PublicKeyCredentialParameter, PublicKeyCredentialRpEntity,
|
MakeCredentialExtensions, MakeCredentialOptions, PublicKeyCredentialDescriptor,
|
||||||
PublicKeyCredentialUserEntity,
|
PublicKeyCredentialParameter, PublicKeyCredentialRpEntity, PublicKeyCredentialUserEntity,
|
||||||
};
|
};
|
||||||
use super::status_code::Ctap2StatusCode;
|
use super::status_code::Ctap2StatusCode;
|
||||||
use alloc::string::String;
|
use alloc::string::String;
|
||||||
@@ -113,7 +113,7 @@ pub struct AuthenticatorMakeCredentialParameters {
|
|||||||
pub user: PublicKeyCredentialUserEntity,
|
pub user: PublicKeyCredentialUserEntity,
|
||||||
pub pub_key_cred_params: Vec<PublicKeyCredentialParameter>,
|
pub pub_key_cred_params: Vec<PublicKeyCredentialParameter>,
|
||||||
pub exclude_list: Option<Vec<PublicKeyCredentialDescriptor>>,
|
pub exclude_list: Option<Vec<PublicKeyCredentialDescriptor>>,
|
||||||
pub extensions: Option<Extensions>,
|
pub extensions: Option<MakeCredentialExtensions>,
|
||||||
// Even though options are optional, we can use the default if not present.
|
// Even though options are optional, we can use the default if not present.
|
||||||
pub options: MakeCredentialOptions,
|
pub options: MakeCredentialOptions,
|
||||||
pub pin_uv_auth_param: Option<Vec<u8>>,
|
pub pin_uv_auth_param: Option<Vec<u8>>,
|
||||||
@@ -124,30 +124,32 @@ 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 param_map = read_map(&cbor_value)?;
|
let mut param_map = extract_map(cbor_value)?;
|
||||||
|
|
||||||
let client_data_hash = read_byte_string(ok_or_missing(param_map.get(&cbor_unsigned!(1)))?)?;
|
let client_data_hash =
|
||||||
|
extract_byte_string(ok_or_missing(param_map.remove(&cbor_unsigned!(1)))?)?;
|
||||||
|
|
||||||
let rp = PublicKeyCredentialRpEntity::try_from(ok_or_missing(
|
let rp = PublicKeyCredentialRpEntity::try_from(ok_or_missing(
|
||||||
param_map.get(&cbor_unsigned!(2)),
|
param_map.remove(&cbor_unsigned!(2)),
|
||||||
)?)?;
|
)?)?;
|
||||||
|
|
||||||
let user = PublicKeyCredentialUserEntity::try_from(ok_or_missing(
|
let user = PublicKeyCredentialUserEntity::try_from(ok_or_missing(
|
||||||
param_map.get(&cbor_unsigned!(3)),
|
param_map.remove(&cbor_unsigned!(3)),
|
||||||
)?)?;
|
)?)?;
|
||||||
|
|
||||||
let cred_param_vec = read_array(ok_or_missing(param_map.get(&cbor_unsigned!(4)))?)?;
|
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
|
||||||
.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.get(&cbor_unsigned!(5)) {
|
let exclude_list = match param_map.remove(&cbor_unsigned!(5)) {
|
||||||
Some(entry) => {
|
Some(entry) => {
|
||||||
let exclude_list_vec = read_array(entry)?;
|
let exclude_list_vec = extract_array(entry)?;
|
||||||
|
let list_len = MAX_CREDENTIAL_COUNT_IN_LIST.unwrap_or(exclude_list_vec.len());
|
||||||
let exclude_list = exclude_list_vec
|
let exclude_list = exclude_list_vec
|
||||||
.iter()
|
.into_iter()
|
||||||
.take(MAX_CREDENTIAL_COUNT_IN_LIST.unwrap_or(exclude_list_vec.len()))
|
.take(list_len)
|
||||||
.map(PublicKeyCredentialDescriptor::try_from)
|
.map(PublicKeyCredentialDescriptor::try_from)
|
||||||
.collect::<Result<Vec<PublicKeyCredentialDescriptor>, Ctap2StatusCode>>()?;
|
.collect::<Result<Vec<PublicKeyCredentialDescriptor>, Ctap2StatusCode>>()?;
|
||||||
Some(exclude_list)
|
Some(exclude_list)
|
||||||
@@ -156,11 +158,11 @@ impl TryFrom<cbor::Value> for AuthenticatorMakeCredentialParameters {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let extensions = param_map
|
let extensions = param_map
|
||||||
.get(&cbor_unsigned!(6))
|
.remove(&cbor_unsigned!(6))
|
||||||
.map(Extensions::try_from)
|
.map(MakeCredentialExtensions::try_from)
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
|
|
||||||
let options = match param_map.get(&cbor_unsigned!(7)) {
|
let options = match param_map.remove(&cbor_unsigned!(7)) {
|
||||||
Some(entry) => MakeCredentialOptions::try_from(entry)?,
|
Some(entry) => MakeCredentialOptions::try_from(entry)?,
|
||||||
None => MakeCredentialOptions {
|
None => MakeCredentialOptions {
|
||||||
rk: false,
|
rk: false,
|
||||||
@@ -169,13 +171,13 @@ impl TryFrom<cbor::Value> for AuthenticatorMakeCredentialParameters {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let pin_uv_auth_param = param_map
|
let pin_uv_auth_param = param_map
|
||||||
.get(&cbor_unsigned!(8))
|
.remove(&cbor_unsigned!(8))
|
||||||
.map(read_byte_string)
|
.map(extract_byte_string)
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
|
|
||||||
let pin_uv_auth_protocol = param_map
|
let pin_uv_auth_protocol = param_map
|
||||||
.get(&cbor_unsigned!(9))
|
.remove(&cbor_unsigned!(9))
|
||||||
.map(read_unsigned)
|
.map(extract_unsigned)
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
|
|
||||||
Ok(AuthenticatorMakeCredentialParameters {
|
Ok(AuthenticatorMakeCredentialParameters {
|
||||||
@@ -197,7 +199,7 @@ pub struct AuthenticatorGetAssertionParameters {
|
|||||||
pub rp_id: String,
|
pub rp_id: String,
|
||||||
pub client_data_hash: Vec<u8>,
|
pub client_data_hash: Vec<u8>,
|
||||||
pub allow_list: Option<Vec<PublicKeyCredentialDescriptor>>,
|
pub allow_list: Option<Vec<PublicKeyCredentialDescriptor>>,
|
||||||
pub extensions: Option<Extensions>,
|
pub extensions: Option<GetAssertionExtensions>,
|
||||||
// Even though options are optional, we can use the default if not present.
|
// Even though options are optional, we can use the default if not present.
|
||||||
pub options: GetAssertionOptions,
|
pub options: GetAssertionOptions,
|
||||||
pub pin_uv_auth_param: Option<Vec<u8>>,
|
pub pin_uv_auth_param: Option<Vec<u8>>,
|
||||||
@@ -208,18 +210,20 @@ 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 param_map = read_map(&cbor_value)?;
|
let mut param_map = extract_map(cbor_value)?;
|
||||||
|
|
||||||
let rp_id = read_text_string(ok_or_missing(param_map.get(&cbor_unsigned!(1)))?)?;
|
let rp_id = extract_text_string(ok_or_missing(param_map.remove(&cbor_unsigned!(1)))?)?;
|
||||||
|
|
||||||
let client_data_hash = read_byte_string(ok_or_missing(param_map.get(&cbor_unsigned!(2)))?)?;
|
let client_data_hash =
|
||||||
|
extract_byte_string(ok_or_missing(param_map.remove(&cbor_unsigned!(2)))?)?;
|
||||||
|
|
||||||
let allow_list = match param_map.get(&cbor_unsigned!(3)) {
|
let allow_list = match param_map.remove(&cbor_unsigned!(3)) {
|
||||||
Some(entry) => {
|
Some(entry) => {
|
||||||
let allow_list_vec = read_array(entry)?;
|
let allow_list_vec = extract_array(entry)?;
|
||||||
|
let list_len = MAX_CREDENTIAL_COUNT_IN_LIST.unwrap_or(allow_list_vec.len());
|
||||||
let allow_list = allow_list_vec
|
let allow_list = allow_list_vec
|
||||||
.iter()
|
.into_iter()
|
||||||
.take(MAX_CREDENTIAL_COUNT_IN_LIST.unwrap_or(allow_list_vec.len()))
|
.take(list_len)
|
||||||
.map(PublicKeyCredentialDescriptor::try_from)
|
.map(PublicKeyCredentialDescriptor::try_from)
|
||||||
.collect::<Result<Vec<PublicKeyCredentialDescriptor>, Ctap2StatusCode>>()?;
|
.collect::<Result<Vec<PublicKeyCredentialDescriptor>, Ctap2StatusCode>>()?;
|
||||||
Some(allow_list)
|
Some(allow_list)
|
||||||
@@ -228,11 +232,11 @@ impl TryFrom<cbor::Value> for AuthenticatorGetAssertionParameters {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let extensions = param_map
|
let extensions = param_map
|
||||||
.get(&cbor_unsigned!(4))
|
.remove(&cbor_unsigned!(4))
|
||||||
.map(Extensions::try_from)
|
.map(GetAssertionExtensions::try_from)
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
|
|
||||||
let options = match param_map.get(&cbor_unsigned!(5)) {
|
let options = match param_map.remove(&cbor_unsigned!(5)) {
|
||||||
Some(entry) => GetAssertionOptions::try_from(entry)?,
|
Some(entry) => GetAssertionOptions::try_from(entry)?,
|
||||||
None => GetAssertionOptions {
|
None => GetAssertionOptions {
|
||||||
up: true,
|
up: true,
|
||||||
@@ -241,13 +245,13 @@ impl TryFrom<cbor::Value> for AuthenticatorGetAssertionParameters {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let pin_uv_auth_param = param_map
|
let pin_uv_auth_param = param_map
|
||||||
.get(&cbor_unsigned!(6))
|
.remove(&cbor_unsigned!(6))
|
||||||
.map(read_byte_string)
|
.map(extract_byte_string)
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
|
|
||||||
let pin_uv_auth_protocol = param_map
|
let pin_uv_auth_protocol = param_map
|
||||||
.get(&cbor_unsigned!(7))
|
.remove(&cbor_unsigned!(7))
|
||||||
.map(read_unsigned)
|
.map(extract_unsigned)
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
|
|
||||||
Ok(AuthenticatorGetAssertionParameters {
|
Ok(AuthenticatorGetAssertionParameters {
|
||||||
@@ -276,32 +280,32 @@ 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 param_map = read_map(&cbor_value)?;
|
let mut param_map = extract_map(cbor_value)?;
|
||||||
|
|
||||||
let pin_protocol = read_unsigned(ok_or_missing(param_map.get(&cbor_unsigned!(1)))?)?;
|
let pin_protocol = extract_unsigned(ok_or_missing(param_map.remove(&cbor_unsigned!(1)))?)?;
|
||||||
|
|
||||||
let sub_command =
|
let sub_command =
|
||||||
ClientPinSubCommand::try_from(ok_or_missing(param_map.get(&cbor_unsigned!(2)))?)?;
|
ClientPinSubCommand::try_from(ok_or_missing(param_map.remove(&cbor_unsigned!(2)))?)?;
|
||||||
|
|
||||||
let key_agreement = param_map
|
let key_agreement = param_map
|
||||||
.get(&cbor_unsigned!(3))
|
.remove(&cbor_unsigned!(3))
|
||||||
.map(read_map)
|
.map(extract_map)
|
||||||
.transpose()?
|
.transpose()?
|
||||||
.map(|x| CoseKey(x.clone()));
|
.map(|x| CoseKey(x));
|
||||||
|
|
||||||
let pin_auth = param_map
|
let pin_auth = param_map
|
||||||
.get(&cbor_unsigned!(4))
|
.remove(&cbor_unsigned!(4))
|
||||||
.map(read_byte_string)
|
.map(extract_byte_string)
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
|
|
||||||
let new_pin_enc = param_map
|
let new_pin_enc = param_map
|
||||||
.get(&cbor_unsigned!(5))
|
.remove(&cbor_unsigned!(5))
|
||||||
.map(read_byte_string)
|
.map(extract_byte_string)
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
|
|
||||||
let pin_hash_enc = param_map
|
let pin_hash_enc = param_map
|
||||||
.get(&cbor_unsigned!(6))
|
.remove(&cbor_unsigned!(6))
|
||||||
.map(read_byte_string)
|
.map(extract_byte_string)
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
|
|
||||||
Ok(AuthenticatorClientPinParameters {
|
Ok(AuthenticatorClientPinParameters {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
315
src/ctap/mod.rs
315
src/ctap/mod.rs
@@ -32,9 +32,10 @@ use self::command::{
|
|||||||
#[cfg(feature = "with_ctap2_1")]
|
#[cfg(feature = "with_ctap2_1")]
|
||||||
use self::data_formats::AuthenticatorTransport;
|
use self::data_formats::AuthenticatorTransport;
|
||||||
use self::data_formats::{
|
use self::data_formats::{
|
||||||
ClientPinSubCommand, CoseKey, GetAssertionHmacSecretInput, PackedAttestationStatement,
|
ClientPinSubCommand, CoseKey, CredentialProtectionPolicy, GetAssertionHmacSecretInput,
|
||||||
PublicKeyCredentialDescriptor, PublicKeyCredentialParameter, PublicKeyCredentialSource,
|
PackedAttestationStatement, PublicKeyCredentialDescriptor, PublicKeyCredentialParameter,
|
||||||
PublicKeyCredentialType, PublicKeyCredentialUserEntity, SignatureAlgorithm,
|
PublicKeyCredentialSource, PublicKeyCredentialType, PublicKeyCredentialUserEntity,
|
||||||
|
SignatureAlgorithm,
|
||||||
};
|
};
|
||||||
use self::hid::ChannelID;
|
use self::hid::ChannelID;
|
||||||
use self::response::{
|
use self::response::{
|
||||||
@@ -108,6 +109,10 @@ pub const ES256_CRED_PARAM: PublicKeyCredentialParameter = PublicKeyCredentialPa
|
|||||||
cred_type: PublicKeyCredentialType::PublicKey,
|
cred_type: PublicKeyCredentialType::PublicKey,
|
||||||
alg: SignatureAlgorithm::ES256,
|
alg: SignatureAlgorithm::ES256,
|
||||||
};
|
};
|
||||||
|
// You can change this value to one of the following for more privacy.
|
||||||
|
// - Some(CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIdList)
|
||||||
|
// - Some(CredentialProtectionPolicy::UserVerificationRequired)
|
||||||
|
const DEFAULT_CRED_PROTECT: Option<CredentialProtectionPolicy> = None;
|
||||||
|
|
||||||
fn check_pin_auth(hmac_key: &[u8], hmac_contents: &[u8], pin_auth: &[u8]) -> bool {
|
fn check_pin_auth(hmac_key: &[u8], hmac_contents: &[u8], pin_auth: &[u8]) -> bool {
|
||||||
if pin_auth.len() != PIN_AUTH_LENGTH {
|
if pin_auth.len() != PIN_AUTH_LENGTH {
|
||||||
@@ -334,6 +339,7 @@ where
|
|||||||
user_handle: vec![],
|
user_handle: vec![],
|
||||||
other_ui: None,
|
other_ui: None,
|
||||||
cred_random: None,
|
cred_random: None,
|
||||||
|
cred_protect_policy: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -427,30 +433,42 @@ where
|
|||||||
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM);
|
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM);
|
||||||
}
|
}
|
||||||
|
|
||||||
let use_hmac_extension =
|
let (use_hmac_extension, cred_protect_policy) = if let Some(extensions) = extensions {
|
||||||
extensions.map_or(Ok(false), |e| e.has_make_credential_hmac_secret())?;
|
let mut cred_protect = extensions.cred_protect;
|
||||||
if use_hmac_extension && !options.rk {
|
if cred_protect.unwrap_or(CredentialProtectionPolicy::UserVerificationOptional)
|
||||||
|
< DEFAULT_CRED_PROTECT
|
||||||
|
.unwrap_or(CredentialProtectionPolicy::UserVerificationOptional)
|
||||||
|
{
|
||||||
|
cred_protect = DEFAULT_CRED_PROTECT;
|
||||||
|
}
|
||||||
|
(extensions.hmac_secret, cred_protect)
|
||||||
|
} else {
|
||||||
|
(false, None)
|
||||||
|
};
|
||||||
|
|
||||||
|
let cred_random = if use_hmac_extension {
|
||||||
|
if !options.rk {
|
||||||
// The extension is actually supported, but we need resident keys.
|
// The extension is actually supported, but we need resident keys.
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION);
|
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION);
|
||||||
}
|
}
|
||||||
let cred_random = if use_hmac_extension {
|
|
||||||
Some(self.rng.gen_uniform_u8x32().to_vec())
|
Some(self.rng.gen_uniform_u8x32().to_vec())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
let ed_flag = if use_hmac_extension { ED_FLAG } else { 0 };
|
// TODO(kaczmarczyck) unsolicited output for default credProtect level
|
||||||
|
let has_extension_output = use_hmac_extension || cred_protect_policy.is_some();
|
||||||
|
|
||||||
let rp_id = rp.rp_id;
|
let rp_id = rp.rp_id;
|
||||||
if let Some(exclude_list) = exclude_list {
|
if let Some(exclude_list) = exclude_list {
|
||||||
for cred_desc in exclude_list {
|
for cred_desc in exclude_list {
|
||||||
if self
|
if self
|
||||||
.persistent_store
|
.persistent_store
|
||||||
.find_credential(&rp_id, &cred_desc.key_id)
|
.find_credential(&rp_id, &cred_desc.key_id, pin_uv_auth_param.is_none())
|
||||||
.is_some()
|
.is_some()
|
||||||
{
|
{
|
||||||
// Perform this check, so bad actors can't brute force exclude_list
|
// Perform this check, so bad actors can't brute force exclude_list
|
||||||
// without user interaction. Discard the user presence check's outcome.
|
// without user interaction.
|
||||||
let _ = (self.check_user_presence)(cid);
|
(self.check_user_presence)(cid)?;
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_CREDENTIAL_EXCLUDED);
|
return Err(Ctap2StatusCode::CTAP2_ERR_CREDENTIAL_EXCLUDED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -458,6 +476,7 @@ where
|
|||||||
|
|
||||||
// MakeCredential always requires user presence.
|
// MakeCredential always requires user presence.
|
||||||
// User verification depends on the PIN auth inputs, which are checked here.
|
// User verification depends on the PIN auth inputs, which are checked here.
|
||||||
|
let ed_flag = if has_extension_output { ED_FLAG } else { 0 };
|
||||||
let flags = match pin_uv_auth_param {
|
let flags = match pin_uv_auth_param {
|
||||||
Some(pin_auth) => {
|
Some(pin_auth) => {
|
||||||
if self.persistent_store.pin_hash().is_none() {
|
if self.persistent_store.pin_hash().is_none() {
|
||||||
@@ -500,6 +519,7 @@ where
|
|||||||
.user_display_name
|
.user_display_name
|
||||||
.map(|s| truncate_to_char_boundary(&s, 64).to_string()),
|
.map(|s| truncate_to_char_boundary(&s, 64).to_string()),
|
||||||
cred_random,
|
cred_random,
|
||||||
|
cred_protect_policy,
|
||||||
};
|
};
|
||||||
self.persistent_store.store_credential(credential_source)?;
|
self.persistent_store.store_credential(credential_source)?;
|
||||||
random_id
|
random_id
|
||||||
@@ -520,11 +540,13 @@ where
|
|||||||
None => return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_RESPONSE_CANNOT_WRITE_CBOR),
|
None => return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_RESPONSE_CANNOT_WRITE_CBOR),
|
||||||
};
|
};
|
||||||
auth_data.extend(cose_key);
|
auth_data.extend(cose_key);
|
||||||
if use_hmac_extension {
|
if has_extension_output {
|
||||||
let extensions = cbor_map! {
|
let hmac_secret_output = if use_hmac_extension { Some(true) } else { None };
|
||||||
"hmac-secret" => true,
|
let extensions_output = cbor_map_options! {
|
||||||
|
"hmac-secret" => hmac_secret_output,
|
||||||
|
"credProtect" => cred_protect_policy,
|
||||||
};
|
};
|
||||||
if !cbor::write(extensions, &mut auth_data) {
|
if !cbor::write(extensions_output, &mut auth_data) {
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_RESPONSE_CANNOT_WRITE_CBOR);
|
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_RESPONSE_CANNOT_WRITE_CBOR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -621,17 +643,15 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let get_assertion_hmac_secret_input = match extensions {
|
let hmac_secret_input = extensions.map(|e| e.hmac_secret).flatten();
|
||||||
Some(extensions) => extensions.get_assertion_hmac_secret().transpose()?,
|
if hmac_secret_input.is_some() && !options.up {
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
if get_assertion_hmac_secret_input.is_some() && !options.up {
|
|
||||||
// The extension is actually supported, but we need user presence.
|
// The extension is actually supported, but we need user presence.
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION);
|
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The user verification bit depends on the existance of PIN auth, whereas
|
// The user verification bit depends on the existance of PIN auth, since we do
|
||||||
// user presence is requested as an option.
|
// not support internal UV. User presence is requested as an option.
|
||||||
|
let has_uv = pin_uv_auth_param.is_some();
|
||||||
let mut flags = match pin_uv_auth_param {
|
let mut flags = match pin_uv_auth_param {
|
||||||
Some(pin_auth) => {
|
Some(pin_auth) => {
|
||||||
if self.persistent_store.pin_hash().is_none() {
|
if self.persistent_store.pin_hash().is_none() {
|
||||||
@@ -654,7 +674,7 @@ where
|
|||||||
if options.up {
|
if options.up {
|
||||||
flags |= UP_FLAG;
|
flags |= UP_FLAG;
|
||||||
}
|
}
|
||||||
if get_assertion_hmac_secret_input.is_some() {
|
if hmac_secret_input.is_some() {
|
||||||
flags |= ED_FLAG;
|
flags |= ED_FLAG;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -663,11 +683,14 @@ where
|
|||||||
let credentials = if let Some(allow_list) = allow_list {
|
let credentials = if let Some(allow_list) = allow_list {
|
||||||
let mut found_credentials = vec![];
|
let mut found_credentials = vec![];
|
||||||
for allowed_credential in allow_list {
|
for allowed_credential in allow_list {
|
||||||
match self
|
match self.persistent_store.find_credential(
|
||||||
.persistent_store
|
&rp_id,
|
||||||
.find_credential(&rp_id, &allowed_credential.key_id)
|
&allowed_credential.key_id,
|
||||||
{
|
!has_uv,
|
||||||
Some(credential) => found_credentials.push(credential),
|
) {
|
||||||
|
Some(credential) => {
|
||||||
|
found_credentials.push(credential);
|
||||||
|
}
|
||||||
None => {
|
None => {
|
||||||
if decrypted_credential.is_none() {
|
if decrypted_credential.is_none() {
|
||||||
decrypted_credential = self
|
decrypted_credential = self
|
||||||
@@ -679,7 +702,7 @@ where
|
|||||||
found_credentials
|
found_credentials
|
||||||
} else {
|
} else {
|
||||||
// TODO(kaczmarczyck) use GetNextAssertion
|
// TODO(kaczmarczyck) use GetNextAssertion
|
||||||
self.persistent_store.filter_credential(&rp_id)
|
self.persistent_store.filter_credential(&rp_id, !has_uv)
|
||||||
};
|
};
|
||||||
|
|
||||||
let credential = if let Some(credential) = credentials.first() {
|
let credential = if let Some(credential) = credentials.first() {
|
||||||
@@ -698,12 +721,12 @@ 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);
|
||||||
// Process extensions.
|
// Process extensions.
|
||||||
if let Some(get_assertion_hmac_secret_input) = get_assertion_hmac_secret_input {
|
if let Some(hmac_secret_input) = hmac_secret_input {
|
||||||
let GetAssertionHmacSecretInput {
|
let GetAssertionHmacSecretInput {
|
||||||
key_agreement,
|
key_agreement,
|
||||||
salt_enc,
|
salt_enc,
|
||||||
salt_auth,
|
salt_auth,
|
||||||
} = get_assertion_hmac_secret_input;
|
} = hmac_secret_input;
|
||||||
let pk: crypto::ecdh::PubKey = CoseKey::try_into(key_agreement)?;
|
let pk: crypto::ecdh::PubKey = CoseKey::try_into(key_agreement)?;
|
||||||
let shared_secret = self.key_agreement_key.exchange_x_sha256(&pk);
|
let shared_secret = self.key_agreement_key.exchange_x_sha256(&pk);
|
||||||
// HMAC-secret does the same 16 byte truncated check.
|
// HMAC-secret does the same 16 byte truncated check.
|
||||||
@@ -718,10 +741,10 @@ where
|
|||||||
None => return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION),
|
None => return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION),
|
||||||
};
|
};
|
||||||
|
|
||||||
let extensions = cbor_map! {
|
let extensions_output = cbor_map! {
|
||||||
"hmac-secret" => encrypted_output,
|
"hmac-secret" => encrypted_output,
|
||||||
};
|
};
|
||||||
if !cbor::write(extensions, &mut auth_data) {
|
if !cbor::write(extensions_output, &mut auth_data) {
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_RESPONSE_CANNOT_WRITE_CBOR);
|
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_RESPONSE_CANNOT_WRITE_CBOR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -791,6 +814,7 @@ where
|
|||||||
transports: Some(vec![AuthenticatorTransport::Usb]),
|
transports: Some(vec![AuthenticatorTransport::Usb]),
|
||||||
#[cfg(feature = "with_ctap2_1")]
|
#[cfg(feature = "with_ctap2_1")]
|
||||||
algorithms: Some(vec![ES256_CRED_PARAM]),
|
algorithms: Some(vec![ES256_CRED_PARAM]),
|
||||||
|
default_cred_protect: DEFAULT_CRED_PROTECT,
|
||||||
#[cfg(feature = "with_ctap2_1")]
|
#[cfg(feature = "with_ctap2_1")]
|
||||||
firmware_version: None,
|
firmware_version: None,
|
||||||
},
|
},
|
||||||
@@ -1095,8 +1119,8 @@ where
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::data_formats::{
|
use super::data_formats::{
|
||||||
Extensions, GetAssertionOptions, MakeCredentialOptions, PublicKeyCredentialRpEntity,
|
GetAssertionExtensions, GetAssertionOptions, MakeCredentialExtensions,
|
||||||
PublicKeyCredentialUserEntity,
|
MakeCredentialOptions, PublicKeyCredentialRpEntity, PublicKeyCredentialUserEntity,
|
||||||
};
|
};
|
||||||
use super::*;
|
use super::*;
|
||||||
use crypto::rng256::ThreadRng256;
|
use crypto::rng256::ThreadRng256;
|
||||||
@@ -1179,6 +1203,32 @@ mod test {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_make_credential_parameters_with_exclude_list(
|
||||||
|
excluded_credential_id: &[u8],
|
||||||
|
) -> AuthenticatorMakeCredentialParameters {
|
||||||
|
let excluded_credential_descriptor = PublicKeyCredentialDescriptor {
|
||||||
|
key_type: PublicKeyCredentialType::PublicKey,
|
||||||
|
key_id: excluded_credential_id.to_vec(),
|
||||||
|
transports: None,
|
||||||
|
};
|
||||||
|
let exclude_list = Some(vec![excluded_credential_descriptor]);
|
||||||
|
let mut make_credential_params = create_minimal_make_credential_parameters();
|
||||||
|
make_credential_params.exclude_list = exclude_list;
|
||||||
|
make_credential_params
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_make_credential_parameters_with_cred_protect_policy(
|
||||||
|
policy: CredentialProtectionPolicy,
|
||||||
|
) -> AuthenticatorMakeCredentialParameters {
|
||||||
|
let extensions = Some(MakeCredentialExtensions {
|
||||||
|
hmac_secret: false,
|
||||||
|
cred_protect: Some(policy),
|
||||||
|
});
|
||||||
|
let mut make_credential_params = create_minimal_make_credential_parameters();
|
||||||
|
make_credential_params.extensions = extensions;
|
||||||
|
make_credential_params
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_residential_process_make_credential() {
|
fn test_residential_process_make_credential() {
|
||||||
let mut rng = ThreadRng256 {};
|
let mut rng = ThreadRng256 {};
|
||||||
@@ -1277,46 +1327,93 @@ mod test {
|
|||||||
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
|
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
|
||||||
|
|
||||||
let excluded_credential_id = vec![0x01, 0x23, 0x45, 0x67];
|
let excluded_credential_id = vec![0x01, 0x23, 0x45, 0x67];
|
||||||
|
let make_credential_params =
|
||||||
|
create_make_credential_parameters_with_exclude_list(&excluded_credential_id);
|
||||||
let excluded_credential_source = PublicKeyCredentialSource {
|
let excluded_credential_source = PublicKeyCredentialSource {
|
||||||
key_type: PublicKeyCredentialType::PublicKey,
|
key_type: PublicKeyCredentialType::PublicKey,
|
||||||
credential_id: excluded_credential_id.clone(),
|
credential_id: excluded_credential_id,
|
||||||
private_key: excluded_private_key,
|
private_key: excluded_private_key,
|
||||||
rp_id: String::from("example.com"),
|
rp_id: String::from("example.com"),
|
||||||
user_handle: vec![],
|
user_handle: vec![],
|
||||||
other_ui: None,
|
other_ui: None,
|
||||||
cred_random: None,
|
cred_random: None,
|
||||||
|
cred_protect_policy: None,
|
||||||
};
|
};
|
||||||
assert!(ctap_state
|
assert!(ctap_state
|
||||||
.persistent_store
|
.persistent_store
|
||||||
.store_credential(excluded_credential_source)
|
.store_credential(excluded_credential_source)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
|
|
||||||
let excluded_credential_descriptor = PublicKeyCredentialDescriptor {
|
|
||||||
key_type: PublicKeyCredentialType::PublicKey,
|
|
||||||
key_id: excluded_credential_id,
|
|
||||||
transports: None,
|
|
||||||
};
|
|
||||||
let exclude_list = Some(vec![excluded_credential_descriptor]);
|
|
||||||
let mut make_credential_params = create_minimal_make_credential_parameters();
|
|
||||||
make_credential_params.exclude_list = exclude_list;
|
|
||||||
let make_credential_response =
|
let make_credential_response =
|
||||||
ctap_state.process_make_credential(make_credential_params, DUMMY_CHANNEL_ID);
|
ctap_state.process_make_credential(make_credential_params, DUMMY_CHANNEL_ID);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
make_credential_response,
|
make_credential_response,
|
||||||
Err(Ctap2StatusCode::CTAP2_ERR_CREDENTIAL_EXCLUDED)
|
Err(Ctap2StatusCode::CTAP2_ERR_CREDENTIAL_EXCLUDED)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process_make_credential_credential_with_cred_protect() {
|
||||||
|
let mut rng = ThreadRng256 {};
|
||||||
|
let user_immediately_present = |_| Ok(());
|
||||||
|
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
|
||||||
|
|
||||||
|
let test_policy = CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIdList;
|
||||||
|
let make_credential_params =
|
||||||
|
create_make_credential_parameters_with_cred_protect_policy(test_policy);
|
||||||
|
let make_credential_response =
|
||||||
|
ctap_state.process_make_credential(make_credential_params, DUMMY_CHANNEL_ID);
|
||||||
|
assert!(make_credential_response.is_ok());
|
||||||
|
|
||||||
|
let stored_credential = ctap_state
|
||||||
|
.persistent_store
|
||||||
|
.filter_credential("example.com", false)
|
||||||
|
.pop()
|
||||||
|
.unwrap();
|
||||||
|
let credential_id = stored_credential.credential_id;
|
||||||
|
assert_eq!(stored_credential.cred_protect_policy, Some(test_policy));
|
||||||
|
|
||||||
|
let make_credential_params =
|
||||||
|
create_make_credential_parameters_with_exclude_list(&credential_id);
|
||||||
|
let make_credential_response =
|
||||||
|
ctap_state.process_make_credential(make_credential_params, DUMMY_CHANNEL_ID);
|
||||||
|
assert_eq!(
|
||||||
|
make_credential_response,
|
||||||
|
Err(Ctap2StatusCode::CTAP2_ERR_CREDENTIAL_EXCLUDED)
|
||||||
|
);
|
||||||
|
|
||||||
|
let test_policy = CredentialProtectionPolicy::UserVerificationRequired;
|
||||||
|
let make_credential_params =
|
||||||
|
create_make_credential_parameters_with_cred_protect_policy(test_policy);
|
||||||
|
let make_credential_response =
|
||||||
|
ctap_state.process_make_credential(make_credential_params, DUMMY_CHANNEL_ID);
|
||||||
|
assert!(make_credential_response.is_ok());
|
||||||
|
|
||||||
|
let stored_credential = ctap_state
|
||||||
|
.persistent_store
|
||||||
|
.filter_credential("example.com", false)
|
||||||
|
.pop()
|
||||||
|
.unwrap();
|
||||||
|
let credential_id = stored_credential.credential_id;
|
||||||
|
assert_eq!(stored_credential.cred_protect_policy, Some(test_policy));
|
||||||
|
|
||||||
|
let make_credential_params =
|
||||||
|
create_make_credential_parameters_with_exclude_list(&credential_id);
|
||||||
|
let make_credential_response =
|
||||||
|
ctap_state.process_make_credential(make_credential_params, DUMMY_CHANNEL_ID);
|
||||||
|
assert!(make_credential_response.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_process_make_credential_hmac_secret() {
|
fn test_process_make_credential_hmac_secret() {
|
||||||
let mut rng = ThreadRng256 {};
|
let mut rng = ThreadRng256 {};
|
||||||
let user_immediately_present = |_| Ok(());
|
let user_immediately_present = |_| Ok(());
|
||||||
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
|
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
|
||||||
|
|
||||||
let mut extension_map = BTreeMap::new();
|
let extensions = Some(MakeCredentialExtensions {
|
||||||
extension_map.insert("hmac-secret".to_string(), cbor_bool!(true));
|
hmac_secret: true,
|
||||||
let extensions = Some(Extensions::new(extension_map));
|
cred_protect: None,
|
||||||
|
});
|
||||||
let mut make_credential_params = create_minimal_make_credential_parameters();
|
let mut make_credential_params = create_minimal_make_credential_parameters();
|
||||||
make_credential_params.extensions = extensions;
|
make_credential_params.extensions = extensions;
|
||||||
let make_credential_response =
|
let make_credential_response =
|
||||||
@@ -1426,9 +1523,10 @@ mod test {
|
|||||||
let user_immediately_present = |_| Ok(());
|
let user_immediately_present = |_| Ok(());
|
||||||
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
|
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
|
||||||
|
|
||||||
let mut extension_map = BTreeMap::new();
|
let make_extensions = Some(MakeCredentialExtensions {
|
||||||
extension_map.insert("hmac-secret".to_string(), cbor_bool!(true));
|
hmac_secret: true,
|
||||||
let make_extensions = Some(Extensions::new(extension_map));
|
cred_protect: None,
|
||||||
|
});
|
||||||
let mut make_credential_params = create_minimal_make_credential_parameters();
|
let mut make_credential_params = create_minimal_make_credential_parameters();
|
||||||
make_credential_params.extensions = make_extensions;
|
make_credential_params.extensions = make_extensions;
|
||||||
assert!(ctap_state
|
assert!(ctap_state
|
||||||
@@ -1436,15 +1534,15 @@ mod test {
|
|||||||
.is_ok());
|
.is_ok());
|
||||||
|
|
||||||
let pk = sk.genpk();
|
let pk = sk.genpk();
|
||||||
let hmac_secret_parameters = cbor_map! {
|
let hmac_secret_input = GetAssertionHmacSecretInput {
|
||||||
1 => cbor::Value::Map(CoseKey::from(pk).0),
|
key_agreement: CoseKey::from(pk),
|
||||||
2 => vec![0; 32],
|
salt_enc: vec![0x02; 32],
|
||||||
3 => vec![0; 16],
|
salt_auth: vec![0x03; 16],
|
||||||
};
|
};
|
||||||
let mut extension_map = BTreeMap::new();
|
let get_extensions = Some(GetAssertionExtensions {
|
||||||
extension_map.insert("hmac-secret".to_string(), hmac_secret_parameters);
|
hmac_secret: Some(hmac_secret_input),
|
||||||
|
});
|
||||||
|
|
||||||
let get_extensions = Some(Extensions::new(extension_map));
|
|
||||||
let get_assertion_params = AuthenticatorGetAssertionParameters {
|
let get_assertion_params = AuthenticatorGetAssertionParameters {
|
||||||
rp_id: String::from("example.com"),
|
rp_id: String::from("example.com"),
|
||||||
client_data_hash: vec![0xCD],
|
client_data_hash: vec![0xCD],
|
||||||
@@ -1466,6 +1564,106 @@ mod test {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_residential_process_get_assertion_with_cred_protect() {
|
||||||
|
let mut rng = ThreadRng256 {};
|
||||||
|
let private_key = crypto::ecdsa::SecKey::gensk(&mut rng);
|
||||||
|
let credential_id = rng.gen_uniform_u8x32().to_vec();
|
||||||
|
let user_immediately_present = |_| Ok(());
|
||||||
|
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
|
||||||
|
|
||||||
|
let cred_desc = PublicKeyCredentialDescriptor {
|
||||||
|
key_type: PublicKeyCredentialType::PublicKey,
|
||||||
|
key_id: credential_id.clone(),
|
||||||
|
transports: None, // You can set USB as a hint here.
|
||||||
|
};
|
||||||
|
let credential = PublicKeyCredentialSource {
|
||||||
|
key_type: PublicKeyCredentialType::PublicKey,
|
||||||
|
credential_id: credential_id.clone(),
|
||||||
|
private_key: private_key.clone(),
|
||||||
|
rp_id: String::from("example.com"),
|
||||||
|
user_handle: vec![0x00],
|
||||||
|
other_ui: None,
|
||||||
|
cred_random: None,
|
||||||
|
cred_protect_policy: Some(
|
||||||
|
CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIdList,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
assert!(ctap_state
|
||||||
|
.persistent_store
|
||||||
|
.store_credential(credential)
|
||||||
|
.is_ok());
|
||||||
|
|
||||||
|
let get_assertion_params = AuthenticatorGetAssertionParameters {
|
||||||
|
rp_id: String::from("example.com"),
|
||||||
|
client_data_hash: vec![0xCD],
|
||||||
|
allow_list: None,
|
||||||
|
extensions: None,
|
||||||
|
options: GetAssertionOptions {
|
||||||
|
up: false,
|
||||||
|
uv: false,
|
||||||
|
},
|
||||||
|
pin_uv_auth_param: None,
|
||||||
|
pin_uv_auth_protocol: None,
|
||||||
|
};
|
||||||
|
let get_assertion_response =
|
||||||
|
ctap_state.process_get_assertion(get_assertion_params, DUMMY_CHANNEL_ID);
|
||||||
|
assert_eq!(
|
||||||
|
get_assertion_response,
|
||||||
|
Err(Ctap2StatusCode::CTAP2_ERR_NO_CREDENTIALS),
|
||||||
|
);
|
||||||
|
|
||||||
|
let get_assertion_params = AuthenticatorGetAssertionParameters {
|
||||||
|
rp_id: String::from("example.com"),
|
||||||
|
client_data_hash: vec![0xCD],
|
||||||
|
allow_list: Some(vec![cred_desc.clone()]),
|
||||||
|
extensions: None,
|
||||||
|
options: GetAssertionOptions {
|
||||||
|
up: false,
|
||||||
|
uv: false,
|
||||||
|
},
|
||||||
|
pin_uv_auth_param: None,
|
||||||
|
pin_uv_auth_protocol: None,
|
||||||
|
};
|
||||||
|
let get_assertion_response =
|
||||||
|
ctap_state.process_get_assertion(get_assertion_params, DUMMY_CHANNEL_ID);
|
||||||
|
assert!(get_assertion_response.is_ok());
|
||||||
|
|
||||||
|
let credential = PublicKeyCredentialSource {
|
||||||
|
key_type: PublicKeyCredentialType::PublicKey,
|
||||||
|
credential_id,
|
||||||
|
private_key,
|
||||||
|
rp_id: String::from("example.com"),
|
||||||
|
user_handle: vec![0x00],
|
||||||
|
other_ui: None,
|
||||||
|
cred_random: None,
|
||||||
|
cred_protect_policy: Some(CredentialProtectionPolicy::UserVerificationRequired),
|
||||||
|
};
|
||||||
|
assert!(ctap_state
|
||||||
|
.persistent_store
|
||||||
|
.store_credential(credential)
|
||||||
|
.is_ok());
|
||||||
|
|
||||||
|
let get_assertion_params = AuthenticatorGetAssertionParameters {
|
||||||
|
rp_id: String::from("example.com"),
|
||||||
|
client_data_hash: vec![0xCD],
|
||||||
|
allow_list: Some(vec![cred_desc]),
|
||||||
|
extensions: None,
|
||||||
|
options: GetAssertionOptions {
|
||||||
|
up: false,
|
||||||
|
uv: false,
|
||||||
|
},
|
||||||
|
pin_uv_auth_param: None,
|
||||||
|
pin_uv_auth_protocol: None,
|
||||||
|
};
|
||||||
|
let get_assertion_response =
|
||||||
|
ctap_state.process_get_assertion(get_assertion_params, DUMMY_CHANNEL_ID);
|
||||||
|
assert_eq!(
|
||||||
|
get_assertion_response,
|
||||||
|
Err(Ctap2StatusCode::CTAP2_ERR_NO_CREDENTIALS),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_process_reset() {
|
fn test_process_reset() {
|
||||||
let mut rng = ThreadRng256 {};
|
let mut rng = ThreadRng256 {};
|
||||||
@@ -1482,6 +1680,7 @@ mod test {
|
|||||||
user_handle: vec![],
|
user_handle: vec![],
|
||||||
other_ui: None,
|
other_ui: None,
|
||||||
cred_random: None,
|
cred_random: None,
|
||||||
|
cred_protect_policy: None,
|
||||||
};
|
};
|
||||||
assert!(ctap_state
|
assert!(ctap_state
|
||||||
.persistent_store
|
.persistent_store
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
#[cfg(feature = "with_ctap2_1")]
|
#[cfg(feature = "with_ctap2_1")]
|
||||||
use super::data_formats::{AuthenticatorTransport, PublicKeyCredentialParameter};
|
use super::data_formats::{AuthenticatorTransport, PublicKeyCredentialParameter};
|
||||||
use super::data_formats::{
|
use super::data_formats::{
|
||||||
CoseKey, PackedAttestationStatement, PublicKeyCredentialDescriptor,
|
CoseKey, CredentialProtectionPolicy, PackedAttestationStatement, PublicKeyCredentialDescriptor,
|
||||||
PublicKeyCredentialUserEntity,
|
PublicKeyCredentialUserEntity,
|
||||||
};
|
};
|
||||||
use alloc::collections::BTreeMap;
|
use alloc::collections::BTreeMap;
|
||||||
@@ -119,6 +119,7 @@ pub struct AuthenticatorGetInfoResponse {
|
|||||||
pub transports: Option<Vec<AuthenticatorTransport>>,
|
pub transports: Option<Vec<AuthenticatorTransport>>,
|
||||||
#[cfg(feature = "with_ctap2_1")]
|
#[cfg(feature = "with_ctap2_1")]
|
||||||
pub algorithms: Option<Vec<PublicKeyCredentialParameter>>,
|
pub algorithms: Option<Vec<PublicKeyCredentialParameter>>,
|
||||||
|
pub default_cred_protect: Option<CredentialProtectionPolicy>,
|
||||||
#[cfg(feature = "with_ctap2_1")]
|
#[cfg(feature = "with_ctap2_1")]
|
||||||
pub firmware_version: Option<u64>,
|
pub firmware_version: Option<u64>,
|
||||||
}
|
}
|
||||||
@@ -137,6 +138,7 @@ impl From<AuthenticatorGetInfoResponse> for cbor::Value {
|
|||||||
max_credential_id_length,
|
max_credential_id_length,
|
||||||
transports,
|
transports,
|
||||||
algorithms,
|
algorithms,
|
||||||
|
default_cred_protect,
|
||||||
firmware_version,
|
firmware_version,
|
||||||
} = get_info_response;
|
} = get_info_response;
|
||||||
|
|
||||||
@@ -159,6 +161,7 @@ impl From<AuthenticatorGetInfoResponse> for cbor::Value {
|
|||||||
0x08 => max_credential_id_length,
|
0x08 => max_credential_id_length,
|
||||||
0x09 => transports.map(|vec| cbor_array_vec!(vec)),
|
0x09 => transports.map(|vec| cbor_array_vec!(vec)),
|
||||||
0x0A => algorithms.map(|vec| cbor_array_vec!(vec)),
|
0x0A => algorithms.map(|vec| cbor_array_vec!(vec)),
|
||||||
|
0x0C => default_cred_protect.map(|p| p as u64),
|
||||||
0x0E => firmware_version,
|
0x0E => firmware_version,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -172,6 +175,7 @@ impl From<AuthenticatorGetInfoResponse> for cbor::Value {
|
|||||||
options,
|
options,
|
||||||
max_msg_size,
|
max_msg_size,
|
||||||
pin_protocols,
|
pin_protocols,
|
||||||
|
default_cred_protect,
|
||||||
} = get_info_response;
|
} = get_info_response;
|
||||||
|
|
||||||
let options_cbor: Option<cbor::Value> = options.map(|options| {
|
let options_cbor: Option<cbor::Value> = options.map(|options| {
|
||||||
@@ -189,6 +193,7 @@ impl From<AuthenticatorGetInfoResponse> for cbor::Value {
|
|||||||
0x04 => options_cbor,
|
0x04 => options_cbor,
|
||||||
0x05 => max_msg_size,
|
0x05 => max_msg_size,
|
||||||
0x06 => pin_protocols.map(|vec| cbor_array_vec!(vec)),
|
0x06 => pin_protocols.map(|vec| cbor_array_vec!(vec)),
|
||||||
|
0x0C => default_cred_protect.map(|p| p as u64),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -290,6 +295,7 @@ mod test {
|
|||||||
transports: None,
|
transports: None,
|
||||||
#[cfg(feature = "with_ctap2_1")]
|
#[cfg(feature = "with_ctap2_1")]
|
||||||
algorithms: None,
|
algorithms: None,
|
||||||
|
default_cred_protect: None,
|
||||||
#[cfg(feature = "with_ctap2_1")]
|
#[cfg(feature = "with_ctap2_1")]
|
||||||
firmware_version: None,
|
firmware_version: None,
|
||||||
};
|
};
|
||||||
@@ -318,6 +324,7 @@ mod test {
|
|||||||
max_credential_id_length: Some(256),
|
max_credential_id_length: Some(256),
|
||||||
transports: Some(vec![AuthenticatorTransport::Usb]),
|
transports: Some(vec![AuthenticatorTransport::Usb]),
|
||||||
algorithms: Some(vec![ES256_CRED_PARAM]),
|
algorithms: Some(vec![ES256_CRED_PARAM]),
|
||||||
|
default_cred_protect: Some(CredentialProtectionPolicy::UserVerificationRequired),
|
||||||
firmware_version: Some(0),
|
firmware_version: Some(0),
|
||||||
};
|
};
|
||||||
let response_cbor: Option<cbor::Value> =
|
let response_cbor: Option<cbor::Value> =
|
||||||
@@ -333,6 +340,7 @@ mod test {
|
|||||||
0x08 => 256,
|
0x08 => 256,
|
||||||
0x09 => cbor_array_vec![vec!["usb"]],
|
0x09 => cbor_array_vec![vec!["usb"]],
|
||||||
0x0A => cbor_array_vec![vec![ES256_CRED_PARAM]],
|
0x0A => cbor_array_vec![vec![ES256_CRED_PARAM]],
|
||||||
|
0x0C => CredentialProtectionPolicy::UserVerificationRequired as u64,
|
||||||
0x0E => 0,
|
0x0E => 0,
|
||||||
};
|
};
|
||||||
assert_eq!(response_cbor, Some(expected_cbor));
|
assert_eq!(response_cbor, Some(expected_cbor));
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use crate::crypto::rng256::Rng256;
|
use crate::crypto::rng256::Rng256;
|
||||||
use crate::ctap::data_formats::PublicKeyCredentialSource;
|
use crate::ctap::data_formats::{CredentialProtectionPolicy, PublicKeyCredentialSource};
|
||||||
use crate::ctap::status_code::Ctap2StatusCode;
|
use crate::ctap::status_code::Ctap2StatusCode;
|
||||||
use crate::ctap::{key_material, PIN_AUTH_LENGTH, USE_BATCH_ATTESTATION};
|
use crate::ctap::{key_material, PIN_AUTH_LENGTH, USE_BATCH_ATTESTATION};
|
||||||
use alloc::string::String;
|
use alloc::string::String;
|
||||||
@@ -228,6 +228,7 @@ impl PersistentStore {
|
|||||||
&self,
|
&self,
|
||||||
rp_id: &str,
|
rp_id: &str,
|
||||||
credential_id: &[u8],
|
credential_id: &[u8],
|
||||||
|
check_cred_protect: bool,
|
||||||
) -> Option<PublicKeyCredentialSource> {
|
) -> Option<PublicKeyCredentialSource> {
|
||||||
let key = Key::Credential {
|
let key = Key::Credential {
|
||||||
rp_id: Some(rp_id.into()),
|
rp_id: Some(rp_id.into()),
|
||||||
@@ -238,8 +239,17 @@ impl PersistentStore {
|
|||||||
debug_assert_eq!(entry.tag, TAG_CREDENTIAL);
|
debug_assert_eq!(entry.tag, TAG_CREDENTIAL);
|
||||||
let result = deserialize_credential(entry.data);
|
let result = deserialize_credential(entry.data);
|
||||||
debug_assert!(result.is_some());
|
debug_assert!(result.is_some());
|
||||||
|
if check_cred_protect
|
||||||
|
&& result.as_ref().map_or(false, |cred| {
|
||||||
|
cred.cred_protect_policy
|
||||||
|
== Some(CredentialProtectionPolicy::UserVerificationRequired)
|
||||||
|
})
|
||||||
|
{
|
||||||
|
None
|
||||||
|
} else {
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn store_credential(
|
pub fn store_credential(
|
||||||
&mut self,
|
&mut self,
|
||||||
@@ -270,7 +280,11 @@ impl PersistentStore {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn filter_credential(&self, rp_id: &str) -> Vec<PublicKeyCredentialSource> {
|
pub fn filter_credential(
|
||||||
|
&self,
|
||||||
|
rp_id: &str,
|
||||||
|
check_cred_protect: bool,
|
||||||
|
) -> Vec<PublicKeyCredentialSource> {
|
||||||
self.store
|
self.store
|
||||||
.find_all(&Key::Credential {
|
.find_all(&Key::Credential {
|
||||||
rp_id: Some(rp_id.into()),
|
rp_id: Some(rp_id.into()),
|
||||||
@@ -283,6 +297,7 @@ impl PersistentStore {
|
|||||||
debug_assert!(credential.is_some());
|
debug_assert!(credential.is_some());
|
||||||
credential
|
credential
|
||||||
})
|
})
|
||||||
|
.filter(|cred| !check_cred_protect || cred.is_discoverable())
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -550,6 +565,7 @@ mod test {
|
|||||||
user_handle,
|
user_handle,
|
||||||
other_ui: None,
|
other_ui: None,
|
||||||
cred_random: None,
|
cred_random: None,
|
||||||
|
cred_protect_policy: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -634,7 +650,7 @@ mod test {
|
|||||||
.is_ok());
|
.is_ok());
|
||||||
assert_eq!(persistent_store.count_credentials(), 1);
|
assert_eq!(persistent_store.count_credentials(), 1);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&persistent_store.filter_credential("example.com"),
|
&persistent_store.filter_credential("example.com", false),
|
||||||
&[expected_credential]
|
&[expected_credential]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -682,7 +698,7 @@ mod test {
|
|||||||
.store_credential(credential_source2)
|
.store_credential(credential_source2)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
|
|
||||||
let filtered_credentials = persistent_store.filter_credential("example.com");
|
let filtered_credentials = persistent_store.filter_credential("example.com", false);
|
||||||
assert_eq!(filtered_credentials.len(), 2);
|
assert_eq!(filtered_credentials.len(), 2);
|
||||||
assert!(
|
assert!(
|
||||||
(filtered_credentials[0].credential_id == id0
|
(filtered_credentials[0].credential_id == id0
|
||||||
@@ -692,6 +708,30 @@ mod test {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_filter_with_cred_protect() {
|
||||||
|
let mut rng = ThreadRng256 {};
|
||||||
|
let mut persistent_store = PersistentStore::new(&mut rng);
|
||||||
|
assert_eq!(persistent_store.count_credentials(), 0);
|
||||||
|
let private_key = crypto::ecdsa::SecKey::gensk(&mut rng);
|
||||||
|
let credential = PublicKeyCredentialSource {
|
||||||
|
key_type: PublicKeyCredentialType::PublicKey,
|
||||||
|
credential_id: rng.gen_uniform_u8x32().to_vec(),
|
||||||
|
private_key,
|
||||||
|
rp_id: String::from("example.com"),
|
||||||
|
user_handle: vec![0x00],
|
||||||
|
other_ui: None,
|
||||||
|
cred_random: None,
|
||||||
|
cred_protect_policy: Some(
|
||||||
|
CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIdList,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
assert!(persistent_store.store_credential(credential).is_ok());
|
||||||
|
|
||||||
|
let no_credential = persistent_store.filter_credential("example.com", true);
|
||||||
|
assert_eq!(no_credential, vec![]);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_find() {
|
fn test_find() {
|
||||||
let mut rng = ThreadRng256 {};
|
let mut rng = ThreadRng256 {};
|
||||||
@@ -708,9 +748,9 @@ mod test {
|
|||||||
.store_credential(credential_source1)
|
.store_credential(credential_source1)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
|
|
||||||
let no_credential = persistent_store.find_credential("another.example.com", &id0);
|
let no_credential = persistent_store.find_credential("another.example.com", &id0, false);
|
||||||
assert_eq!(no_credential, None);
|
assert_eq!(no_credential, None);
|
||||||
let found_credential = persistent_store.find_credential("example.com", &id0);
|
let found_credential = persistent_store.find_credential("example.com", &id0, false);
|
||||||
let expected_credential = PublicKeyCredentialSource {
|
let expected_credential = PublicKeyCredentialSource {
|
||||||
key_type: PublicKeyCredentialType::PublicKey,
|
key_type: PublicKeyCredentialType::PublicKey,
|
||||||
credential_id: id0,
|
credential_id: id0,
|
||||||
@@ -719,10 +759,33 @@ mod test {
|
|||||||
user_handle: vec![0x00],
|
user_handle: vec![0x00],
|
||||||
other_ui: None,
|
other_ui: None,
|
||||||
cred_random: None,
|
cred_random: None,
|
||||||
|
cred_protect_policy: None,
|
||||||
};
|
};
|
||||||
assert_eq!(found_credential, Some(expected_credential));
|
assert_eq!(found_credential, Some(expected_credential));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_find_with_cred_protect() {
|
||||||
|
let mut rng = ThreadRng256 {};
|
||||||
|
let mut persistent_store = PersistentStore::new(&mut rng);
|
||||||
|
assert_eq!(persistent_store.count_credentials(), 0);
|
||||||
|
let private_key = crypto::ecdsa::SecKey::gensk(&mut rng);
|
||||||
|
let credential = PublicKeyCredentialSource {
|
||||||
|
key_type: PublicKeyCredentialType::PublicKey,
|
||||||
|
credential_id: rng.gen_uniform_u8x32().to_vec(),
|
||||||
|
private_key,
|
||||||
|
rp_id: String::from("example.com"),
|
||||||
|
user_handle: vec![0x00],
|
||||||
|
other_ui: None,
|
||||||
|
cred_random: None,
|
||||||
|
cred_protect_policy: Some(CredentialProtectionPolicy::UserVerificationRequired),
|
||||||
|
};
|
||||||
|
assert!(persistent_store.store_credential(credential).is_ok());
|
||||||
|
|
||||||
|
let no_credential = persistent_store.find_credential("example.com", &vec![0x00], true);
|
||||||
|
assert_eq!(no_credential, None);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_master_keys() {
|
fn test_master_keys() {
|
||||||
let mut rng = ThreadRng256 {};
|
let mut rng = ThreadRng256 {};
|
||||||
|
|||||||
Reference in New Issue
Block a user