Merge branch 'master' into aaguid

This commit is contained in:
Julien Cretin
2020-06-09 11:56:05 +02:00
8 changed files with 735 additions and 435 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)
// The extension is actually supported, but we need resident keys. < DEFAULT_CRED_PROTECT
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION); .unwrap_or(CredentialProtectionPolicy::UserVerificationOptional)
} {
cred_protect = DEFAULT_CRED_PROTECT;
}
(extensions.hmac_secret, cred_protect)
} else {
(false, None)
};
let cred_random = if use_hmac_extension { let cred_random = if use_hmac_extension {
if !options.rk {
// The extension is actually supported, but we need resident keys.
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_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

View File

@@ -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));

View File

@@ -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,7 +239,16 @@ 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());
result if check_cred_protect
&& result.as_ref().map_or(false, |cred| {
cred.cred_protect_policy
== Some(CredentialProtectionPolicy::UserVerificationRequired)
})
{
None
} else {
result
}
} }
pub fn store_credential( pub fn store_credential(
@@ -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 {};