implements the credBlob extensions
This commit is contained in:
@@ -123,6 +123,8 @@ a few things you can personalize:
|
|||||||
allows some relying parties to read the minimum PIN length by default. The
|
allows some relying parties to read the minimum PIN length by default. The
|
||||||
latter allows storing more relying parties that may check the minimum PIN
|
latter allows storing more relying parties that may check the minimum PIN
|
||||||
length.
|
length.
|
||||||
|
1. Increase the `MAX_CRED_BLOB_LENGTH` in `ctap/mod.rs`, if you expect blobs
|
||||||
|
bigger than the default value.
|
||||||
|
|
||||||
### 3D printed enclosure
|
### 3D printed enclosure
|
||||||
|
|
||||||
|
|||||||
@@ -147,8 +147,9 @@ 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<MakeCredentialExtensions>,
|
// Extensions are optional, but we can use defaults for all missing fields.
|
||||||
// Even though options are optional, we can use the default if not present.
|
pub extensions: MakeCredentialExtensions,
|
||||||
|
// Same for options, use defaults when not present.
|
||||||
pub options: MakeCredentialOptions,
|
pub options: MakeCredentialOptions,
|
||||||
pub pin_uv_auth_param: Option<Vec<u8>>,
|
pub pin_uv_auth_param: Option<Vec<u8>>,
|
||||||
pub pin_uv_auth_protocol: Option<u64>,
|
pub pin_uv_auth_protocol: Option<u64>,
|
||||||
@@ -198,15 +199,13 @@ impl TryFrom<cbor::Value> for AuthenticatorMakeCredentialParameters {
|
|||||||
|
|
||||||
let extensions = extensions
|
let extensions = extensions
|
||||||
.map(MakeCredentialExtensions::try_from)
|
.map(MakeCredentialExtensions::try_from)
|
||||||
.transpose()?;
|
.transpose()?
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
let options = match options {
|
let options = options
|
||||||
Some(entry) => MakeCredentialOptions::try_from(entry)?,
|
.map(MakeCredentialOptions::try_from)
|
||||||
None => MakeCredentialOptions {
|
.transpose()?
|
||||||
rk: false,
|
.unwrap_or_default();
|
||||||
uv: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let pin_uv_auth_param = pin_uv_auth_param.map(extract_byte_string).transpose()?;
|
let pin_uv_auth_param = pin_uv_auth_param.map(extract_byte_string).transpose()?;
|
||||||
let pin_uv_auth_protocol = pin_uv_auth_protocol.map(extract_unsigned).transpose()?;
|
let pin_uv_auth_protocol = pin_uv_auth_protocol.map(extract_unsigned).transpose()?;
|
||||||
@@ -230,8 +229,9 @@ 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<GetAssertionExtensions>,
|
// Extensions are optional, but we can use defaults for all missing fields.
|
||||||
// Even though options are optional, we can use the default if not present.
|
pub extensions: GetAssertionExtensions,
|
||||||
|
// Same for options, use defaults when not present.
|
||||||
pub options: GetAssertionOptions,
|
pub options: GetAssertionOptions,
|
||||||
pub pin_uv_auth_param: Option<Vec<u8>>,
|
pub pin_uv_auth_param: Option<Vec<u8>>,
|
||||||
pub pin_uv_auth_protocol: Option<u64>,
|
pub pin_uv_auth_protocol: Option<u64>,
|
||||||
@@ -272,15 +272,13 @@ impl TryFrom<cbor::Value> for AuthenticatorGetAssertionParameters {
|
|||||||
|
|
||||||
let extensions = extensions
|
let extensions = extensions
|
||||||
.map(GetAssertionExtensions::try_from)
|
.map(GetAssertionExtensions::try_from)
|
||||||
.transpose()?;
|
.transpose()?
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
let options = match options {
|
let options = options
|
||||||
Some(entry) => GetAssertionOptions::try_from(entry)?,
|
.map(GetAssertionOptions::try_from)
|
||||||
None => GetAssertionOptions {
|
.transpose()?
|
||||||
up: true,
|
.unwrap_or_default();
|
||||||
uv: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let pin_uv_auth_param = pin_uv_auth_param.map(extract_byte_string).transpose()?;
|
let pin_uv_auth_param = pin_uv_auth_param.map(extract_byte_string).transpose()?;
|
||||||
let pin_uv_auth_protocol = pin_uv_auth_protocol.map(extract_unsigned).transpose()?;
|
let pin_uv_auth_protocol = pin_uv_auth_protocol.map(extract_unsigned).transpose()?;
|
||||||
@@ -545,7 +543,7 @@ mod test {
|
|||||||
user,
|
user,
|
||||||
pub_key_cred_params: vec![ES256_CRED_PARAM],
|
pub_key_cred_params: vec![ES256_CRED_PARAM],
|
||||||
exclude_list: Some(vec![]),
|
exclude_list: Some(vec![]),
|
||||||
extensions: None,
|
extensions: MakeCredentialExtensions::default(),
|
||||||
options,
|
options,
|
||||||
pin_uv_auth_param: Some(vec![0x12, 0x34]),
|
pin_uv_auth_param: Some(vec![0x12, 0x34]),
|
||||||
pin_uv_auth_protocol: Some(1),
|
pin_uv_auth_protocol: Some(1),
|
||||||
@@ -591,7 +589,7 @@ mod test {
|
|||||||
rp_id,
|
rp_id,
|
||||||
client_data_hash,
|
client_data_hash,
|
||||||
allow_list: Some(vec![pub_key_cred_descriptor]),
|
allow_list: Some(vec![pub_key_cred_descriptor]),
|
||||||
extensions: None,
|
extensions: GetAssertionExtensions::default(),
|
||||||
options,
|
options,
|
||||||
pin_uv_auth_param: Some(vec![0x12, 0x34]),
|
pin_uv_auth_param: Some(vec![0x12, 0x34]),
|
||||||
pin_uv_auth_protocol: Some(1),
|
pin_uv_auth_protocol: Some(1),
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ fn enumerate_credentials_response(
|
|||||||
creation_order: _,
|
creation_order: _,
|
||||||
user_name,
|
user_name,
|
||||||
user_icon,
|
user_icon,
|
||||||
|
cred_blob: _,
|
||||||
} = credential;
|
} = credential;
|
||||||
let user = PublicKeyCredentialUserEntity {
|
let user = PublicKeyCredentialUserEntity {
|
||||||
user_id: user_handle,
|
user_id: user_handle,
|
||||||
@@ -346,6 +347,7 @@ mod test {
|
|||||||
creation_order: 0,
|
creation_order: 0,
|
||||||
user_name: Some("name".to_string()),
|
user_name: Some("name".to_string()),
|
||||||
user_icon: Some("icon".to_string()),
|
user_icon: Some("icon".to_string()),
|
||||||
|
cred_blob: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -275,11 +275,13 @@ impl From<PublicKeyCredentialDescriptor> for cbor::Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Clone, Debug, PartialEq))]
|
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Clone, Debug, PartialEq))]
|
||||||
pub struct MakeCredentialExtensions {
|
pub struct MakeCredentialExtensions {
|
||||||
pub hmac_secret: bool,
|
pub hmac_secret: bool,
|
||||||
pub cred_protect: Option<CredentialProtectionPolicy>,
|
pub cred_protect: Option<CredentialProtectionPolicy>,
|
||||||
pub min_pin_length: bool,
|
pub min_pin_length: bool,
|
||||||
|
pub cred_blob: Option<Vec<u8>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<cbor::Value> for MakeCredentialExtensions {
|
impl TryFrom<cbor::Value> for MakeCredentialExtensions {
|
||||||
@@ -288,6 +290,7 @@ impl TryFrom<cbor::Value> for MakeCredentialExtensions {
|
|||||||
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
|
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
|
||||||
destructure_cbor_map! {
|
destructure_cbor_map! {
|
||||||
let {
|
let {
|
||||||
|
"credBlob" => cred_blob,
|
||||||
"credProtect" => cred_protect,
|
"credProtect" => cred_protect,
|
||||||
"hmac-secret" => hmac_secret,
|
"hmac-secret" => hmac_secret,
|
||||||
"minPinLength" => min_pin_length,
|
"minPinLength" => min_pin_length,
|
||||||
@@ -299,17 +302,21 @@ impl TryFrom<cbor::Value> for MakeCredentialExtensions {
|
|||||||
.map(CredentialProtectionPolicy::try_from)
|
.map(CredentialProtectionPolicy::try_from)
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
let min_pin_length = min_pin_length.map_or(Ok(false), extract_bool)?;
|
let min_pin_length = min_pin_length.map_or(Ok(false), extract_bool)?;
|
||||||
|
let cred_blob = cred_blob.map(extract_byte_string).transpose()?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
hmac_secret,
|
hmac_secret,
|
||||||
cred_protect,
|
cred_protect,
|
||||||
min_pin_length,
|
min_pin_length,
|
||||||
|
cred_blob,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Clone, Debug, PartialEq))]
|
#[derive(Clone, Default)]
|
||||||
|
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
|
||||||
pub struct GetAssertionExtensions {
|
pub struct GetAssertionExtensions {
|
||||||
pub hmac_secret: Option<GetAssertionHmacSecretInput>,
|
pub hmac_secret: Option<GetAssertionHmacSecretInput>,
|
||||||
|
pub cred_blob: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<cbor::Value> for GetAssertionExtensions {
|
impl TryFrom<cbor::Value> for GetAssertionExtensions {
|
||||||
@@ -318,6 +325,7 @@ impl TryFrom<cbor::Value> for GetAssertionExtensions {
|
|||||||
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
|
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
|
||||||
destructure_cbor_map! {
|
destructure_cbor_map! {
|
||||||
let {
|
let {
|
||||||
|
"credBlob" => cred_blob,
|
||||||
"hmac-secret" => hmac_secret,
|
"hmac-secret" => hmac_secret,
|
||||||
} = extract_map(cbor_value)?;
|
} = extract_map(cbor_value)?;
|
||||||
}
|
}
|
||||||
@@ -325,7 +333,11 @@ impl TryFrom<cbor::Value> for GetAssertionExtensions {
|
|||||||
let hmac_secret = hmac_secret
|
let hmac_secret = hmac_secret
|
||||||
.map(GetAssertionHmacSecretInput::try_from)
|
.map(GetAssertionHmacSecretInput::try_from)
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
Ok(Self { hmac_secret })
|
let cred_blob = cred_blob.map_or(Ok(false), extract_bool)?;
|
||||||
|
Ok(Self {
|
||||||
|
hmac_secret,
|
||||||
|
cred_blob,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -361,6 +373,7 @@ impl TryFrom<cbor::Value> for GetAssertionHmacSecretInput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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.
|
||||||
|
#[derive(Default)]
|
||||||
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
|
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
|
||||||
pub struct MakeCredentialOptions {
|
pub struct MakeCredentialOptions {
|
||||||
pub rk: bool,
|
pub rk: bool,
|
||||||
@@ -400,6 +413,15 @@ pub struct GetAssertionOptions {
|
|||||||
pub uv: bool,
|
pub uv: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for GetAssertionOptions {
|
||||||
|
fn default() -> Self {
|
||||||
|
GetAssertionOptions {
|
||||||
|
up: true,
|
||||||
|
uv: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl TryFrom<cbor::Value> for GetAssertionOptions {
|
impl TryFrom<cbor::Value> for GetAssertionOptions {
|
||||||
type Error = Ctap2StatusCode;
|
type Error = Ctap2StatusCode;
|
||||||
|
|
||||||
@@ -523,6 +545,7 @@ pub struct PublicKeyCredentialSource {
|
|||||||
pub creation_order: u64,
|
pub creation_order: u64,
|
||||||
pub user_name: Option<String>,
|
pub user_name: Option<String>,
|
||||||
pub user_icon: Option<String>,
|
pub user_icon: Option<String>,
|
||||||
|
pub cred_blob: Option<Vec<u8>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// We serialize credentials for the persistent storage using CBOR maps. Each field of a credential
|
// We serialize credentials for the persistent storage using CBOR maps. Each field of a credential
|
||||||
@@ -537,6 +560,7 @@ enum PublicKeyCredentialSourceField {
|
|||||||
CreationOrder = 7,
|
CreationOrder = 7,
|
||||||
UserName = 8,
|
UserName = 8,
|
||||||
UserIcon = 9,
|
UserIcon = 9,
|
||||||
|
CredBlob = 10,
|
||||||
// When a field is removed, its tag should be reserved and not used for new fields. We document
|
// When a field is removed, its tag should be reserved and not used for new fields. We document
|
||||||
// those reserved tags below.
|
// those reserved tags below.
|
||||||
// Reserved tags:
|
// Reserved tags:
|
||||||
@@ -563,6 +587,7 @@ impl From<PublicKeyCredentialSource> for cbor::Value {
|
|||||||
PublicKeyCredentialSourceField::CreationOrder => credential.creation_order,
|
PublicKeyCredentialSourceField::CreationOrder => credential.creation_order,
|
||||||
PublicKeyCredentialSourceField::UserName => credential.user_name,
|
PublicKeyCredentialSourceField::UserName => credential.user_name,
|
||||||
PublicKeyCredentialSourceField::UserIcon => credential.user_icon,
|
PublicKeyCredentialSourceField::UserIcon => credential.user_icon,
|
||||||
|
PublicKeyCredentialSourceField::CredBlob => credential.cred_blob,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -582,6 +607,7 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialSource {
|
|||||||
PublicKeyCredentialSourceField::CreationOrder => creation_order,
|
PublicKeyCredentialSourceField::CreationOrder => creation_order,
|
||||||
PublicKeyCredentialSourceField::UserName => user_name,
|
PublicKeyCredentialSourceField::UserName => user_name,
|
||||||
PublicKeyCredentialSourceField::UserIcon => user_icon,
|
PublicKeyCredentialSourceField::UserIcon => user_icon,
|
||||||
|
PublicKeyCredentialSourceField::CredBlob => cred_blob,
|
||||||
} = extract_map(cbor_value)?;
|
} = extract_map(cbor_value)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -601,6 +627,7 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialSource {
|
|||||||
let creation_order = creation_order.map(extract_unsigned).unwrap_or(Ok(0))?;
|
let creation_order = creation_order.map(extract_unsigned).unwrap_or(Ok(0))?;
|
||||||
let user_name = user_name.map(extract_text_string).transpose()?;
|
let user_name = user_name.map(extract_text_string).transpose()?;
|
||||||
let user_icon = user_icon.map(extract_text_string).transpose()?;
|
let user_icon = user_icon.map(extract_text_string).transpose()?;
|
||||||
|
let cred_blob = cred_blob.map(extract_byte_string).transpose()?;
|
||||||
// We don't return whether there were unknown fields in the CBOR value. This means that
|
// We don't return whether there were unknown fields in the CBOR value. This means that
|
||||||
// deserialization is not injective. In particular deserialization is only an inverse of
|
// deserialization is not injective. In particular deserialization is only an inverse of
|
||||||
// serialization at a given version of OpenSK. This is not a problem because:
|
// serialization at a given version of OpenSK. This is not a problem because:
|
||||||
@@ -622,6 +649,7 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialSource {
|
|||||||
creation_order,
|
creation_order,
|
||||||
user_name,
|
user_name,
|
||||||
user_icon,
|
user_icon,
|
||||||
|
cred_blob,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1493,12 +1521,14 @@ mod test {
|
|||||||
"hmac-secret" => true,
|
"hmac-secret" => true,
|
||||||
"credProtect" => CredentialProtectionPolicy::UserVerificationRequired,
|
"credProtect" => CredentialProtectionPolicy::UserVerificationRequired,
|
||||||
"minPinLength" => true,
|
"minPinLength" => true,
|
||||||
|
"credBlob" => vec![0xCB],
|
||||||
};
|
};
|
||||||
let extensions = MakeCredentialExtensions::try_from(cbor_extensions);
|
let extensions = MakeCredentialExtensions::try_from(cbor_extensions);
|
||||||
let expected_extensions = MakeCredentialExtensions {
|
let expected_extensions = MakeCredentialExtensions {
|
||||||
hmac_secret: true,
|
hmac_secret: true,
|
||||||
cred_protect: Some(CredentialProtectionPolicy::UserVerificationRequired),
|
cred_protect: Some(CredentialProtectionPolicy::UserVerificationRequired),
|
||||||
min_pin_length: true,
|
min_pin_length: true,
|
||||||
|
cred_blob: Some(vec![0xCB]),
|
||||||
};
|
};
|
||||||
assert_eq!(extensions, Ok(expected_extensions));
|
assert_eq!(extensions, Ok(expected_extensions));
|
||||||
}
|
}
|
||||||
@@ -1515,6 +1545,7 @@ mod test {
|
|||||||
2 => vec![0x02; 32],
|
2 => vec![0x02; 32],
|
||||||
3 => vec![0x03; 16],
|
3 => vec![0x03; 16],
|
||||||
},
|
},
|
||||||
|
"credBlob" => true,
|
||||||
};
|
};
|
||||||
let extensions = GetAssertionExtensions::try_from(cbor_extensions);
|
let extensions = GetAssertionExtensions::try_from(cbor_extensions);
|
||||||
let expected_input = GetAssertionHmacSecretInput {
|
let expected_input = GetAssertionHmacSecretInput {
|
||||||
@@ -1524,6 +1555,7 @@ mod test {
|
|||||||
};
|
};
|
||||||
let expected_extensions = GetAssertionExtensions {
|
let expected_extensions = GetAssertionExtensions {
|
||||||
hmac_secret: Some(expected_input),
|
hmac_secret: Some(expected_input),
|
||||||
|
cred_blob: true,
|
||||||
};
|
};
|
||||||
assert_eq!(extensions, Ok(expected_extensions));
|
assert_eq!(extensions, Ok(expected_extensions));
|
||||||
}
|
}
|
||||||
@@ -1816,6 +1848,7 @@ mod test {
|
|||||||
creation_order: 0,
|
creation_order: 0,
|
||||||
user_name: None,
|
user_name: None,
|
||||||
user_icon: None,
|
user_icon: None,
|
||||||
|
cred_blob: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -1858,6 +1891,16 @@ mod test {
|
|||||||
..credential
|
..credential
|
||||||
};
|
};
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
PublicKeyCredentialSource::try_from(cbor::Value::from(credential.clone())),
|
||||||
|
Ok(credential.clone())
|
||||||
|
);
|
||||||
|
|
||||||
|
let credential = PublicKeyCredentialSource {
|
||||||
|
cred_blob: Some(vec![0xCB]),
|
||||||
|
..credential
|
||||||
|
};
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
PublicKeyCredentialSource::try_from(cbor::Value::from(credential.clone())),
|
PublicKeyCredentialSource::try_from(cbor::Value::from(credential.clone())),
|
||||||
Ok(credential)
|
Ok(credential)
|
||||||
|
|||||||
359
src/ctap/mod.rs
359
src/ctap/mod.rs
@@ -35,7 +35,7 @@ use self::command::{
|
|||||||
use self::config_command::process_config;
|
use self::config_command::process_config;
|
||||||
use self::credential_management::process_credential_management;
|
use self::credential_management::process_credential_management;
|
||||||
use self::data_formats::{
|
use self::data_formats::{
|
||||||
AuthenticatorTransport, CoseKey, CredentialProtectionPolicy, GetAssertionHmacSecretInput,
|
AuthenticatorTransport, CoseKey, CredentialProtectionPolicy, GetAssertionExtensions,
|
||||||
PackedAttestationStatement, PublicKeyCredentialDescriptor, PublicKeyCredentialParameter,
|
PackedAttestationStatement, PublicKeyCredentialDescriptor, PublicKeyCredentialParameter,
|
||||||
PublicKeyCredentialSource, PublicKeyCredentialType, PublicKeyCredentialUserEntity,
|
PublicKeyCredentialSource, PublicKeyCredentialType, PublicKeyCredentialUserEntity,
|
||||||
SignatureAlgorithm,
|
SignatureAlgorithm,
|
||||||
@@ -47,17 +47,18 @@ use self::response::{
|
|||||||
AuthenticatorMakeCredentialResponse, AuthenticatorVendorResponse, ResponseData,
|
AuthenticatorMakeCredentialResponse, AuthenticatorVendorResponse, ResponseData,
|
||||||
};
|
};
|
||||||
use self::status_code::Ctap2StatusCode;
|
use self::status_code::Ctap2StatusCode;
|
||||||
use self::storage::PersistentStore;
|
use self::storage::{PersistentStore, MAX_RP_IDS_LENGTH};
|
||||||
use self::timed_permission::TimedPermission;
|
use self::timed_permission::TimedPermission;
|
||||||
#[cfg(feature = "with_ctap1")]
|
#[cfg(feature = "with_ctap1")]
|
||||||
use self::timed_permission::U2fUserPresenceState;
|
use self::timed_permission::U2fUserPresenceState;
|
||||||
|
use alloc::boxed::Box;
|
||||||
use alloc::collections::BTreeMap;
|
use alloc::collections::BTreeMap;
|
||||||
use alloc::string::{String, ToString};
|
use alloc::string::{String, ToString};
|
||||||
use alloc::vec;
|
use alloc::vec;
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use arrayref::array_ref;
|
use arrayref::array_ref;
|
||||||
use byteorder::{BigEndian, ByteOrder};
|
use byteorder::{BigEndian, ByteOrder};
|
||||||
use cbor::{cbor_map, cbor_map_options};
|
use cbor::cbor_map_options;
|
||||||
#[cfg(feature = "debug_ctap")]
|
#[cfg(feature = "debug_ctap")]
|
||||||
use core::fmt::Write;
|
use core::fmt::Write;
|
||||||
use crypto::cbc::{cbc_decrypt, cbc_encrypt};
|
use crypto::cbc::{cbc_decrypt, cbc_encrypt};
|
||||||
@@ -124,6 +125,8 @@ pub const ES256_CRED_PARAM: PublicKeyCredentialParameter = PublicKeyCredentialPa
|
|||||||
// - Some(CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIdList)
|
// - Some(CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIdList)
|
||||||
// - Some(CredentialProtectionPolicy::UserVerificationRequired)
|
// - Some(CredentialProtectionPolicy::UserVerificationRequired)
|
||||||
const DEFAULT_CRED_PROTECT: Option<CredentialProtectionPolicy> = None;
|
const DEFAULT_CRED_PROTECT: Option<CredentialProtectionPolicy> = None;
|
||||||
|
// Maximum size stored with the credBlob extension. Must be at least 32.
|
||||||
|
const MAX_CRED_BLOB_LENGTH: usize = 32;
|
||||||
|
|
||||||
// Checks the PIN protocol parameter against all supported versions.
|
// Checks the PIN protocol parameter against all supported versions.
|
||||||
pub fn check_pin_uv_auth_protocol(
|
pub fn check_pin_uv_auth_protocol(
|
||||||
@@ -154,7 +157,7 @@ fn truncate_to_char_boundary(s: &str, mut max: usize) -> &str {
|
|||||||
pub struct AssertionInput {
|
pub struct AssertionInput {
|
||||||
client_data_hash: Vec<u8>,
|
client_data_hash: Vec<u8>,
|
||||||
auth_data: Vec<u8>,
|
auth_data: Vec<u8>,
|
||||||
hmac_secret_input: Option<GetAssertionHmacSecretInput>,
|
extensions: GetAssertionExtensions,
|
||||||
has_uv: bool,
|
has_uv: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,7 +171,7 @@ pub struct AssertionState {
|
|||||||
/// Stores which command currently holds state for subsequent calls.
|
/// Stores which command currently holds state for subsequent calls.
|
||||||
pub enum StatefulCommand {
|
pub enum StatefulCommand {
|
||||||
Reset,
|
Reset,
|
||||||
GetAssertion(AssertionState),
|
GetAssertion(Box<AssertionState>),
|
||||||
EnumerateRps(usize),
|
EnumerateRps(usize),
|
||||||
EnumerateCredentials(Vec<usize>),
|
EnumerateCredentials(Vec<usize>),
|
||||||
}
|
}
|
||||||
@@ -419,6 +422,7 @@ where
|
|||||||
creation_order: 0,
|
creation_order: 0,
|
||||||
user_name: None,
|
user_name: None,
|
||||||
user_icon: None,
|
user_icon: None,
|
||||||
|
cred_blob: None,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -558,27 +562,31 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
let rp_id = rp.rp_id;
|
let rp_id = rp.rp_id;
|
||||||
let (use_hmac_extension, cred_protect_policy, min_pin_length) =
|
let mut cred_protect_policy = extensions.cred_protect;
|
||||||
if let Some(extensions) = extensions {
|
if cred_protect_policy.unwrap_or(CredentialProtectionPolicy::UserVerificationOptional)
|
||||||
let mut cred_protect = extensions.cred_protect;
|
< DEFAULT_CRED_PROTECT.unwrap_or(CredentialProtectionPolicy::UserVerificationOptional)
|
||||||
if cred_protect.unwrap_or(CredentialProtectionPolicy::UserVerificationOptional)
|
{
|
||||||
< DEFAULT_CRED_PROTECT
|
cred_protect_policy = DEFAULT_CRED_PROTECT;
|
||||||
.unwrap_or(CredentialProtectionPolicy::UserVerificationOptional)
|
}
|
||||||
{
|
let min_pin_length = extensions.min_pin_length
|
||||||
cred_protect = DEFAULT_CRED_PROTECT;
|
&& self
|
||||||
}
|
.persistent_store
|
||||||
let min_pin_length = extensions.min_pin_length
|
.min_pin_length_rp_ids()?
|
||||||
&& self
|
.contains(&rp_id);
|
||||||
.persistent_store
|
// None for no input, false for invalid input, true for valid input.
|
||||||
.min_pin_length_rp_ids()?
|
let has_cred_blob_output = extensions.cred_blob.is_some();
|
||||||
.contains(&rp_id);
|
let cred_blob = extensions
|
||||||
(extensions.hmac_secret, cred_protect, min_pin_length)
|
.cred_blob
|
||||||
} else {
|
.filter(|c| options.rk && c.len() <= MAX_CRED_BLOB_LENGTH);
|
||||||
(false, DEFAULT_CRED_PROTECT, false)
|
let cred_blob_output = if has_cred_blob_output {
|
||||||
};
|
Some(cred_blob.is_some())
|
||||||
|
} else {
|
||||||
let has_extension_output =
|
None
|
||||||
use_hmac_extension || cred_protect_policy.is_some() || min_pin_length;
|
};
|
||||||
|
let has_extension_output = extensions.hmac_secret
|
||||||
|
|| cred_protect_policy.is_some()
|
||||||
|
|| min_pin_length
|
||||||
|
|| has_cred_blob_output;
|
||||||
|
|
||||||
let rp_id_hash = Sha256::hash(rp_id.as_bytes());
|
let rp_id_hash = Sha256::hash(rp_id.as_bytes());
|
||||||
if let Some(exclude_list) = exclude_list {
|
if let Some(exclude_list) = exclude_list {
|
||||||
@@ -656,6 +664,7 @@ where
|
|||||||
user_icon: user
|
user_icon: user
|
||||||
.user_icon
|
.user_icon
|
||||||
.map(|s| truncate_to_char_boundary(&s, 64).to_string()),
|
.map(|s| truncate_to_char_boundary(&s, 64).to_string()),
|
||||||
|
cred_blob,
|
||||||
};
|
};
|
||||||
self.persistent_store.store_credential(credential_source)?;
|
self.persistent_store.store_credential(credential_source)?;
|
||||||
random_id
|
random_id
|
||||||
@@ -675,7 +684,11 @@ where
|
|||||||
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
|
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
|
||||||
}
|
}
|
||||||
if has_extension_output {
|
if has_extension_output {
|
||||||
let hmac_secret_output = if use_hmac_extension { Some(true) } else { None };
|
let hmac_secret_output = if extensions.hmac_secret {
|
||||||
|
Some(true)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
let min_pin_length_output = if min_pin_length {
|
let min_pin_length_output = if min_pin_length {
|
||||||
Some(self.persistent_store.min_pin_length()? as u64)
|
Some(self.persistent_store.min_pin_length()? as u64)
|
||||||
} else {
|
} else {
|
||||||
@@ -685,6 +698,7 @@ where
|
|||||||
"hmac-secret" => hmac_secret_output,
|
"hmac-secret" => hmac_secret_output,
|
||||||
"credProtect" => cred_protect_policy,
|
"credProtect" => cred_protect_policy,
|
||||||
"minPinLength" => min_pin_length_output,
|
"minPinLength" => min_pin_length_output,
|
||||||
|
"credBlob" => cred_blob_output,
|
||||||
};
|
};
|
||||||
if !cbor::write(extensions_output, &mut auth_data) {
|
if !cbor::write(extensions_output, &mut auth_data) {
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
|
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
|
||||||
@@ -754,18 +768,30 @@ where
|
|||||||
let AssertionInput {
|
let AssertionInput {
|
||||||
client_data_hash,
|
client_data_hash,
|
||||||
mut auth_data,
|
mut auth_data,
|
||||||
hmac_secret_input,
|
extensions,
|
||||||
has_uv,
|
has_uv,
|
||||||
} = assertion_input;
|
} = assertion_input;
|
||||||
|
|
||||||
// Process extensions.
|
// Process extensions.
|
||||||
if let Some(hmac_secret_input) = hmac_secret_input {
|
if extensions.hmac_secret.is_some() || extensions.cred_blob {
|
||||||
let cred_random = self.generate_cred_random(&credential.private_key, has_uv)?;
|
let encrypted_output = if let Some(hmac_secret_input) = extensions.hmac_secret {
|
||||||
let encrypted_output = self
|
let cred_random = self.generate_cred_random(&credential.private_key, has_uv)?;
|
||||||
.pin_protocol_v1
|
Some(
|
||||||
.process_hmac_secret(hmac_secret_input, &cred_random)?;
|
self.pin_protocol_v1
|
||||||
let extensions_output = cbor_map! {
|
.process_hmac_secret(hmac_secret_input, &cred_random)?,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
// This could be written more nicely with `then_some` when stable.
|
||||||
|
let cred_blob = if extensions.cred_blob {
|
||||||
|
Some(credential.cred_blob.unwrap_or_default())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let extensions_output = cbor_map_options! {
|
||||||
"hmac-secret" => encrypted_output,
|
"hmac-secret" => encrypted_output,
|
||||||
|
"credBlob" => cred_blob,
|
||||||
};
|
};
|
||||||
if !cbor::write(extensions_output, &mut auth_data) {
|
if !cbor::write(extensions_output, &mut auth_data) {
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
|
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
|
||||||
@@ -854,8 +880,7 @@ where
|
|||||||
|
|
||||||
self.pin_uv_auth_precheck(&pin_uv_auth_param, pin_uv_auth_protocol, cid)?;
|
self.pin_uv_auth_precheck(&pin_uv_auth_param, pin_uv_auth_protocol, cid)?;
|
||||||
|
|
||||||
let hmac_secret_input = extensions.map(|e| e.hmac_secret).flatten();
|
if extensions.hmac_secret.is_some() && !options.up {
|
||||||
if 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_OPTION);
|
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_OPTION);
|
||||||
}
|
}
|
||||||
@@ -891,7 +916,7 @@ where
|
|||||||
if options.up {
|
if options.up {
|
||||||
flags |= UP_FLAG;
|
flags |= UP_FLAG;
|
||||||
}
|
}
|
||||||
if hmac_secret_input.is_some() {
|
if extensions.hmac_secret.is_some() || extensions.cred_blob {
|
||||||
flags |= ED_FLAG;
|
flags |= ED_FLAG;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -939,17 +964,17 @@ where
|
|||||||
let assertion_input = AssertionInput {
|
let assertion_input = AssertionInput {
|
||||||
client_data_hash,
|
client_data_hash,
|
||||||
auth_data: self.generate_auth_data(&rp_id_hash, flags)?,
|
auth_data: self.generate_auth_data(&rp_id_hash, flags)?,
|
||||||
hmac_secret_input,
|
extensions,
|
||||||
has_uv,
|
has_uv,
|
||||||
};
|
};
|
||||||
let number_of_credentials = if next_credential_keys.is_empty() {
|
let number_of_credentials = if next_credential_keys.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
let number_of_credentials = Some(next_credential_keys.len() + 1);
|
let number_of_credentials = Some(next_credential_keys.len() + 1);
|
||||||
let assertion_state = StatefulCommand::GetAssertion(AssertionState {
|
let assertion_state = StatefulCommand::GetAssertion(Box::new(AssertionState {
|
||||||
assertion_input: assertion_input.clone(),
|
assertion_input: assertion_input.clone(),
|
||||||
next_credential_keys,
|
next_credential_keys,
|
||||||
});
|
}));
|
||||||
self.stateful_command_permission
|
self.stateful_command_permission
|
||||||
.set_command(now, assertion_state);
|
.set_command(now, assertion_state);
|
||||||
number_of_credentials
|
number_of_credentials
|
||||||
@@ -993,22 +1018,21 @@ where
|
|||||||
String::from("hmac-secret"),
|
String::from("hmac-secret"),
|
||||||
String::from("credProtect"),
|
String::from("credProtect"),
|
||||||
String::from("minPinLength"),
|
String::from("minPinLength"),
|
||||||
|
String::from("credBlob"),
|
||||||
]),
|
]),
|
||||||
aaguid: self.persistent_store.aaguid()?,
|
aaguid: self.persistent_store.aaguid()?,
|
||||||
options: Some(options_map),
|
options: Some(options_map),
|
||||||
max_msg_size: Some(1024),
|
max_msg_size: Some(1024),
|
||||||
pin_protocols: Some(vec![PIN_PROTOCOL_VERSION]),
|
pin_protocols: Some(vec![PIN_PROTOCOL_VERSION]),
|
||||||
max_credential_count_in_list: MAX_CREDENTIAL_COUNT_IN_LIST.map(|c| c as u64),
|
max_credential_count_in_list: MAX_CREDENTIAL_COUNT_IN_LIST.map(|c| c as u64),
|
||||||
// TODO(#106) update with version 2.1 of HMAC-secret
|
|
||||||
max_credential_id_length: Some(CREDENTIAL_ID_SIZE as u64),
|
max_credential_id_length: Some(CREDENTIAL_ID_SIZE as u64),
|
||||||
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: DEFAULT_CRED_PROTECT,
|
default_cred_protect: DEFAULT_CRED_PROTECT,
|
||||||
min_pin_length: self.persistent_store.min_pin_length()?,
|
min_pin_length: self.persistent_store.min_pin_length()?,
|
||||||
firmware_version: None,
|
firmware_version: None,
|
||||||
max_cred_blob_length: None,
|
max_cred_blob_length: Some(MAX_CRED_BLOB_LENGTH as u64),
|
||||||
// TODO(kaczmarczyck) update when extension is implemented
|
max_rp_ids_for_set_min_pin_length: Some(MAX_RP_IDS_LENGTH as u64),
|
||||||
max_rp_ids_for_set_min_pin_length: None,
|
|
||||||
remaining_discoverable_credentials: Some(
|
remaining_discoverable_credentials: Some(
|
||||||
self.persistent_store.remaining_credentials()? as u64,
|
self.persistent_store.remaining_credentials()? as u64,
|
||||||
),
|
),
|
||||||
@@ -1149,11 +1173,11 @@ where
|
|||||||
mod test {
|
mod test {
|
||||||
use super::command::AuthenticatorAttestationMaterial;
|
use super::command::AuthenticatorAttestationMaterial;
|
||||||
use super::data_formats::{
|
use super::data_formats::{
|
||||||
CoseKey, GetAssertionExtensions, GetAssertionOptions, MakeCredentialExtensions,
|
CoseKey, GetAssertionHmacSecretInput, GetAssertionOptions, MakeCredentialExtensions,
|
||||||
MakeCredentialOptions, PublicKeyCredentialRpEntity, PublicKeyCredentialUserEntity,
|
MakeCredentialOptions, PublicKeyCredentialRpEntity, PublicKeyCredentialUserEntity,
|
||||||
};
|
};
|
||||||
use super::*;
|
use super::*;
|
||||||
use cbor::cbor_array;
|
use cbor::{cbor_array, cbor_map};
|
||||||
use crypto::rng256::ThreadRng256;
|
use crypto::rng256::ThreadRng256;
|
||||||
|
|
||||||
const CLOCK_FREQUENCY_HZ: usize = 32768;
|
const CLOCK_FREQUENCY_HZ: usize = 32768;
|
||||||
@@ -1209,7 +1233,7 @@ mod test {
|
|||||||
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
|
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
|
||||||
let info_reponse = ctap_state.process_command(&[0x04], DUMMY_CHANNEL_ID, DUMMY_CLOCK_VALUE);
|
let info_reponse = ctap_state.process_command(&[0x04], DUMMY_CHANNEL_ID, DUMMY_CLOCK_VALUE);
|
||||||
|
|
||||||
let mut expected_response = vec![0x00, 0xAB, 0x01];
|
let mut expected_response = vec![0x00, 0xAD, 0x01];
|
||||||
// The version array differs with CTAP1, always including 2.0 and 2.1.
|
// The version array differs with CTAP1, always including 2.0 and 2.1.
|
||||||
#[cfg(not(feature = "with_ctap1"))]
|
#[cfg(not(feature = "with_ctap1"))]
|
||||||
let version_count = 2;
|
let version_count = 2;
|
||||||
@@ -1221,10 +1245,11 @@ mod test {
|
|||||||
expected_response.extend(
|
expected_response.extend(
|
||||||
[
|
[
|
||||||
0x68, 0x46, 0x49, 0x44, 0x4F, 0x5F, 0x32, 0x5F, 0x30, 0x6C, 0x46, 0x49, 0x44, 0x4F,
|
0x68, 0x46, 0x49, 0x44, 0x4F, 0x5F, 0x32, 0x5F, 0x30, 0x6C, 0x46, 0x49, 0x44, 0x4F,
|
||||||
0x5F, 0x32, 0x5F, 0x31, 0x5F, 0x50, 0x52, 0x45, 0x02, 0x83, 0x6B, 0x68, 0x6D, 0x61,
|
0x5F, 0x32, 0x5F, 0x31, 0x5F, 0x50, 0x52, 0x45, 0x02, 0x84, 0x6B, 0x68, 0x6D, 0x61,
|
||||||
0x63, 0x2D, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x6B, 0x63, 0x72, 0x65, 0x64, 0x50,
|
0x63, 0x2D, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x6B, 0x63, 0x72, 0x65, 0x64, 0x50,
|
||||||
0x72, 0x6F, 0x74, 0x65, 0x63, 0x74, 0x6C, 0x6D, 0x69, 0x6E, 0x50, 0x69, 0x6E, 0x4C,
|
0x72, 0x6F, 0x74, 0x65, 0x63, 0x74, 0x6C, 0x6D, 0x69, 0x6E, 0x50, 0x69, 0x6E, 0x4C,
|
||||||
0x65, 0x6E, 0x67, 0x74, 0x68, 0x03, 0x50,
|
0x65, 0x6E, 0x67, 0x74, 0x68, 0x68, 0x63, 0x72, 0x65, 0x64, 0x42, 0x6C, 0x6F, 0x62,
|
||||||
|
0x03, 0x50,
|
||||||
]
|
]
|
||||||
.iter(),
|
.iter(),
|
||||||
);
|
);
|
||||||
@@ -1237,7 +1262,7 @@ mod test {
|
|||||||
0x65, 0x6E, 0x67, 0x74, 0x68, 0xF5, 0x05, 0x19, 0x04, 0x00, 0x06, 0x81, 0x01, 0x08,
|
0x65, 0x6E, 0x67, 0x74, 0x68, 0xF5, 0x05, 0x19, 0x04, 0x00, 0x06, 0x81, 0x01, 0x08,
|
||||||
0x18, 0x70, 0x09, 0x81, 0x63, 0x75, 0x73, 0x62, 0x0A, 0x81, 0xA2, 0x63, 0x61, 0x6C,
|
0x18, 0x70, 0x09, 0x81, 0x63, 0x75, 0x73, 0x62, 0x0A, 0x81, 0xA2, 0x63, 0x61, 0x6C,
|
||||||
0x67, 0x26, 0x64, 0x74, 0x79, 0x70, 0x65, 0x6A, 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63,
|
0x67, 0x26, 0x64, 0x74, 0x79, 0x70, 0x65, 0x6A, 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63,
|
||||||
0x2D, 0x6B, 0x65, 0x79, 0x0D, 0x04, 0x14, 0x18, 0x96,
|
0x2D, 0x6B, 0x65, 0x79, 0x0D, 0x04, 0x0F, 0x18, 0x20, 0x10, 0x08, 0x14, 0x18, 0x96,
|
||||||
]
|
]
|
||||||
.iter(),
|
.iter(),
|
||||||
);
|
);
|
||||||
@@ -1269,7 +1294,7 @@ mod test {
|
|||||||
user,
|
user,
|
||||||
pub_key_cred_params,
|
pub_key_cred_params,
|
||||||
exclude_list: None,
|
exclude_list: None,
|
||||||
extensions: None,
|
extensions: MakeCredentialExtensions::default(),
|
||||||
options,
|
options,
|
||||||
pin_uv_auth_param: None,
|
pin_uv_auth_param: None,
|
||||||
pin_uv_auth_protocol: None,
|
pin_uv_auth_protocol: None,
|
||||||
@@ -1293,11 +1318,12 @@ mod test {
|
|||||||
fn create_make_credential_parameters_with_cred_protect_policy(
|
fn create_make_credential_parameters_with_cred_protect_policy(
|
||||||
policy: CredentialProtectionPolicy,
|
policy: CredentialProtectionPolicy,
|
||||||
) -> AuthenticatorMakeCredentialParameters {
|
) -> AuthenticatorMakeCredentialParameters {
|
||||||
let extensions = Some(MakeCredentialExtensions {
|
let extensions = MakeCredentialExtensions {
|
||||||
hmac_secret: false,
|
hmac_secret: false,
|
||||||
cred_protect: Some(policy),
|
cred_protect: Some(policy),
|
||||||
min_pin_length: false,
|
min_pin_length: false,
|
||||||
});
|
cred_blob: 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;
|
||||||
make_credential_params
|
make_credential_params
|
||||||
@@ -1380,6 +1406,7 @@ mod test {
|
|||||||
creation_order: 0,
|
creation_order: 0,
|
||||||
user_name: None,
|
user_name: None,
|
||||||
user_icon: None,
|
user_icon: None,
|
||||||
|
cred_blob: None,
|
||||||
};
|
};
|
||||||
assert!(ctap_state
|
assert!(ctap_state
|
||||||
.persistent_store
|
.persistent_store
|
||||||
@@ -1458,11 +1485,12 @@ mod test {
|
|||||||
let user_immediately_present = |_| Ok(());
|
let user_immediately_present = |_| Ok(());
|
||||||
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
|
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
|
||||||
|
|
||||||
let extensions = Some(MakeCredentialExtensions {
|
let extensions = MakeCredentialExtensions {
|
||||||
hmac_secret: true,
|
hmac_secret: true,
|
||||||
cred_protect: None,
|
cred_protect: None,
|
||||||
min_pin_length: false,
|
min_pin_length: false,
|
||||||
});
|
cred_blob: None,
|
||||||
|
};
|
||||||
let mut make_credential_params = create_minimal_make_credential_parameters();
|
let mut make_credential_params = create_minimal_make_credential_parameters();
|
||||||
make_credential_params.options.rk = false;
|
make_credential_params.options.rk = false;
|
||||||
make_credential_params.extensions = extensions;
|
make_credential_params.extensions = extensions;
|
||||||
@@ -1487,11 +1515,12 @@ mod test {
|
|||||||
let user_immediately_present = |_| Ok(());
|
let user_immediately_present = |_| Ok(());
|
||||||
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
|
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
|
||||||
|
|
||||||
let extensions = Some(MakeCredentialExtensions {
|
let extensions = MakeCredentialExtensions {
|
||||||
hmac_secret: true,
|
hmac_secret: true,
|
||||||
cred_protect: None,
|
cred_protect: None,
|
||||||
min_pin_length: false,
|
min_pin_length: false,
|
||||||
});
|
cred_blob: 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 =
|
||||||
@@ -1516,11 +1545,12 @@ mod test {
|
|||||||
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
|
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
|
||||||
|
|
||||||
// First part: The extension is ignored, since the RP ID is not on the list.
|
// First part: The extension is ignored, since the RP ID is not on the list.
|
||||||
let extensions = Some(MakeCredentialExtensions {
|
let extensions = MakeCredentialExtensions {
|
||||||
hmac_secret: false,
|
hmac_secret: false,
|
||||||
cred_protect: None,
|
cred_protect: None,
|
||||||
min_pin_length: true,
|
min_pin_length: true,
|
||||||
});
|
cred_blob: 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 =
|
||||||
@@ -1541,11 +1571,12 @@ mod test {
|
|||||||
Ok(())
|
Ok(())
|
||||||
);
|
);
|
||||||
|
|
||||||
let extensions = Some(MakeCredentialExtensions {
|
let extensions = MakeCredentialExtensions {
|
||||||
hmac_secret: false,
|
hmac_secret: false,
|
||||||
cred_protect: None,
|
cred_protect: None,
|
||||||
min_pin_length: true,
|
min_pin_length: true,
|
||||||
});
|
cred_blob: 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 =
|
||||||
@@ -1563,6 +1594,82 @@ mod test {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process_make_credential_cred_blob_ok() {
|
||||||
|
let mut rng = ThreadRng256 {};
|
||||||
|
let user_immediately_present = |_| Ok(());
|
||||||
|
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
|
||||||
|
|
||||||
|
let extensions = MakeCredentialExtensions {
|
||||||
|
hmac_secret: false,
|
||||||
|
cred_protect: None,
|
||||||
|
min_pin_length: false,
|
||||||
|
cred_blob: Some(vec![0xCB]),
|
||||||
|
};
|
||||||
|
let mut make_credential_params = create_minimal_make_credential_parameters();
|
||||||
|
make_credential_params.extensions = extensions;
|
||||||
|
let make_credential_response =
|
||||||
|
ctap_state.process_make_credential(make_credential_params, DUMMY_CHANNEL_ID);
|
||||||
|
let expected_extension_cbor = [
|
||||||
|
0xA1, 0x68, 0x63, 0x72, 0x65, 0x64, 0x42, 0x6C, 0x6F, 0x62, 0xF5,
|
||||||
|
];
|
||||||
|
check_make_response(
|
||||||
|
make_credential_response,
|
||||||
|
0xC1,
|
||||||
|
&ctap_state.persistent_store.aaguid().unwrap(),
|
||||||
|
0x20,
|
||||||
|
&expected_extension_cbor,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut iter_result = Ok(());
|
||||||
|
let iter = ctap_state
|
||||||
|
.persistent_store
|
||||||
|
.iter_credentials(&mut iter_result)
|
||||||
|
.unwrap();
|
||||||
|
// There is only 1 credential, so last is good enough.
|
||||||
|
let (_, stored_credential) = iter.last().unwrap();
|
||||||
|
iter_result.unwrap();
|
||||||
|
assert_eq!(stored_credential.cred_blob, Some(vec![0xCB]));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process_make_credential_cred_blob_too_big() {
|
||||||
|
let mut rng = ThreadRng256 {};
|
||||||
|
let user_immediately_present = |_| Ok(());
|
||||||
|
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
|
||||||
|
|
||||||
|
let extensions = MakeCredentialExtensions {
|
||||||
|
hmac_secret: false,
|
||||||
|
cred_protect: None,
|
||||||
|
min_pin_length: false,
|
||||||
|
cred_blob: Some(vec![0xCB; MAX_CRED_BLOB_LENGTH + 1]),
|
||||||
|
};
|
||||||
|
let mut make_credential_params = create_minimal_make_credential_parameters();
|
||||||
|
make_credential_params.extensions = extensions;
|
||||||
|
let make_credential_response =
|
||||||
|
ctap_state.process_make_credential(make_credential_params, DUMMY_CHANNEL_ID);
|
||||||
|
let expected_extension_cbor = [
|
||||||
|
0xA1, 0x68, 0x63, 0x72, 0x65, 0x64, 0x42, 0x6C, 0x6F, 0x62, 0xF4,
|
||||||
|
];
|
||||||
|
check_make_response(
|
||||||
|
make_credential_response,
|
||||||
|
0xC1,
|
||||||
|
&ctap_state.persistent_store.aaguid().unwrap(),
|
||||||
|
0x20,
|
||||||
|
&expected_extension_cbor,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut iter_result = Ok(());
|
||||||
|
let iter = ctap_state
|
||||||
|
.persistent_store
|
||||||
|
.iter_credentials(&mut iter_result)
|
||||||
|
.unwrap();
|
||||||
|
// There is only 1 credential, so last is good enough.
|
||||||
|
let (_, stored_credential) = iter.last().unwrap();
|
||||||
|
iter_result.unwrap();
|
||||||
|
assert_eq!(stored_credential.cred_blob, None);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_process_make_credential_cancelled() {
|
fn test_process_make_credential_cancelled() {
|
||||||
let mut rng = ThreadRng256 {};
|
let mut rng = ThreadRng256 {};
|
||||||
@@ -1586,6 +1693,7 @@ mod test {
|
|||||||
flags: u8,
|
flags: u8,
|
||||||
signature_counter: u32,
|
signature_counter: u32,
|
||||||
expected_number_of_credentials: Option<u64>,
|
expected_number_of_credentials: Option<u64>,
|
||||||
|
expected_extension_cbor: &[u8],
|
||||||
) {
|
) {
|
||||||
match response.unwrap() {
|
match response.unwrap() {
|
||||||
ResponseData::AuthenticatorGetAssertion(get_assertion_response) => {
|
ResponseData::AuthenticatorGetAssertion(get_assertion_response) => {
|
||||||
@@ -1605,6 +1713,7 @@ mod test {
|
|||||||
&mut expected_auth_data[signature_counter_position..],
|
&mut expected_auth_data[signature_counter_position..],
|
||||||
signature_counter,
|
signature_counter,
|
||||||
);
|
);
|
||||||
|
expected_auth_data.extend(expected_extension_cbor);
|
||||||
assert_eq!(auth_data, expected_auth_data);
|
assert_eq!(auth_data, expected_auth_data);
|
||||||
assert_eq!(user, Some(expected_user));
|
assert_eq!(user, Some(expected_user));
|
||||||
assert_eq!(number_of_credentials, expected_number_of_credentials);
|
assert_eq!(number_of_credentials, expected_number_of_credentials);
|
||||||
@@ -1613,6 +1722,29 @@ mod test {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn check_assertion_response_with_extension(
|
||||||
|
response: Result<ResponseData, Ctap2StatusCode>,
|
||||||
|
expected_user_id: Vec<u8>,
|
||||||
|
signature_counter: u32,
|
||||||
|
expected_number_of_credentials: Option<u64>,
|
||||||
|
expected_extension_cbor: &[u8],
|
||||||
|
) {
|
||||||
|
let expected_user = PublicKeyCredentialUserEntity {
|
||||||
|
user_id: expected_user_id,
|
||||||
|
user_name: None,
|
||||||
|
user_display_name: None,
|
||||||
|
user_icon: None,
|
||||||
|
};
|
||||||
|
check_assertion_response_with_user(
|
||||||
|
response,
|
||||||
|
expected_user,
|
||||||
|
0x80,
|
||||||
|
signature_counter,
|
||||||
|
expected_number_of_credentials,
|
||||||
|
expected_extension_cbor,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
fn check_assertion_response(
|
fn check_assertion_response(
|
||||||
response: Result<ResponseData, Ctap2StatusCode>,
|
response: Result<ResponseData, Ctap2StatusCode>,
|
||||||
expected_user_id: Vec<u8>,
|
expected_user_id: Vec<u8>,
|
||||||
@@ -1631,6 +1763,7 @@ mod test {
|
|||||||
0x00,
|
0x00,
|
||||||
signature_counter,
|
signature_counter,
|
||||||
expected_number_of_credentials,
|
expected_number_of_credentials,
|
||||||
|
&[],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1649,7 +1782,7 @@ mod test {
|
|||||||
rp_id: String::from("example.com"),
|
rp_id: String::from("example.com"),
|
||||||
client_data_hash: vec![0xCD],
|
client_data_hash: vec![0xCD],
|
||||||
allow_list: None,
|
allow_list: None,
|
||||||
extensions: None,
|
extensions: GetAssertionExtensions::default(),
|
||||||
options: GetAssertionOptions {
|
options: GetAssertionOptions {
|
||||||
up: false,
|
up: false,
|
||||||
uv: false,
|
uv: false,
|
||||||
@@ -1676,11 +1809,12 @@ mod test {
|
|||||||
let user_immediately_present = |_| Ok(());
|
let user_immediately_present = |_| Ok(());
|
||||||
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
|
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
|
||||||
|
|
||||||
let make_extensions = Some(MakeCredentialExtensions {
|
let make_extensions = MakeCredentialExtensions {
|
||||||
hmac_secret: true,
|
hmac_secret: true,
|
||||||
cred_protect: None,
|
cred_protect: None,
|
||||||
min_pin_length: false,
|
min_pin_length: false,
|
||||||
});
|
cred_blob: None,
|
||||||
|
};
|
||||||
let mut make_credential_params = create_minimal_make_credential_parameters();
|
let mut make_credential_params = create_minimal_make_credential_parameters();
|
||||||
make_credential_params.options.rk = false;
|
make_credential_params.options.rk = false;
|
||||||
make_credential_params.extensions = make_extensions;
|
make_credential_params.extensions = make_extensions;
|
||||||
@@ -1704,9 +1838,10 @@ mod test {
|
|||||||
salt_enc: vec![0x02; 32],
|
salt_enc: vec![0x02; 32],
|
||||||
salt_auth: vec![0x03; 16],
|
salt_auth: vec![0x03; 16],
|
||||||
};
|
};
|
||||||
let get_extensions = Some(GetAssertionExtensions {
|
let get_extensions = GetAssertionExtensions {
|
||||||
hmac_secret: Some(hmac_secret_input),
|
hmac_secret: Some(hmac_secret_input),
|
||||||
});
|
cred_blob: false,
|
||||||
|
};
|
||||||
|
|
||||||
let cred_desc = PublicKeyCredentialDescriptor {
|
let cred_desc = PublicKeyCredentialDescriptor {
|
||||||
key_type: PublicKeyCredentialType::PublicKey,
|
key_type: PublicKeyCredentialType::PublicKey,
|
||||||
@@ -1744,11 +1879,12 @@ mod test {
|
|||||||
let user_immediately_present = |_| Ok(());
|
let user_immediately_present = |_| Ok(());
|
||||||
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
|
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
|
||||||
|
|
||||||
let make_extensions = Some(MakeCredentialExtensions {
|
let make_extensions = MakeCredentialExtensions {
|
||||||
hmac_secret: true,
|
hmac_secret: true,
|
||||||
cred_protect: None,
|
cred_protect: None,
|
||||||
min_pin_length: false,
|
min_pin_length: false,
|
||||||
});
|
cred_blob: 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
|
||||||
@@ -1761,9 +1897,10 @@ mod test {
|
|||||||
salt_enc: vec![0x02; 32],
|
salt_enc: vec![0x02; 32],
|
||||||
salt_auth: vec![0x03; 16],
|
salt_auth: vec![0x03; 16],
|
||||||
};
|
};
|
||||||
let get_extensions = Some(GetAssertionExtensions {
|
let get_extensions = GetAssertionExtensions {
|
||||||
hmac_secret: Some(hmac_secret_input),
|
hmac_secret: Some(hmac_secret_input),
|
||||||
});
|
cred_blob: false,
|
||||||
|
};
|
||||||
|
|
||||||
let get_assertion_params = AuthenticatorGetAssertionParameters {
|
let get_assertion_params = AuthenticatorGetAssertionParameters {
|
||||||
rp_id: String::from("example.com"),
|
rp_id: String::from("example.com"),
|
||||||
@@ -1800,7 +1937,7 @@ mod test {
|
|||||||
let cred_desc = PublicKeyCredentialDescriptor {
|
let cred_desc = PublicKeyCredentialDescriptor {
|
||||||
key_type: PublicKeyCredentialType::PublicKey,
|
key_type: PublicKeyCredentialType::PublicKey,
|
||||||
key_id: credential_id.clone(),
|
key_id: credential_id.clone(),
|
||||||
transports: None, // You can set USB as a hint here.
|
transports: None,
|
||||||
};
|
};
|
||||||
let credential = PublicKeyCredentialSource {
|
let credential = PublicKeyCredentialSource {
|
||||||
key_type: PublicKeyCredentialType::PublicKey,
|
key_type: PublicKeyCredentialType::PublicKey,
|
||||||
@@ -1815,6 +1952,7 @@ mod test {
|
|||||||
creation_order: 0,
|
creation_order: 0,
|
||||||
user_name: None,
|
user_name: None,
|
||||||
user_icon: None,
|
user_icon: None,
|
||||||
|
cred_blob: None,
|
||||||
};
|
};
|
||||||
assert!(ctap_state
|
assert!(ctap_state
|
||||||
.persistent_store
|
.persistent_store
|
||||||
@@ -1825,7 +1963,7 @@ mod test {
|
|||||||
rp_id: String::from("example.com"),
|
rp_id: String::from("example.com"),
|
||||||
client_data_hash: vec![0xCD],
|
client_data_hash: vec![0xCD],
|
||||||
allow_list: None,
|
allow_list: None,
|
||||||
extensions: None,
|
extensions: GetAssertionExtensions::default(),
|
||||||
options: GetAssertionOptions {
|
options: GetAssertionOptions {
|
||||||
up: false,
|
up: false,
|
||||||
uv: false,
|
uv: false,
|
||||||
@@ -1847,7 +1985,7 @@ mod test {
|
|||||||
rp_id: String::from("example.com"),
|
rp_id: String::from("example.com"),
|
||||||
client_data_hash: vec![0xCD],
|
client_data_hash: vec![0xCD],
|
||||||
allow_list: Some(vec![cred_desc.clone()]),
|
allow_list: Some(vec![cred_desc.clone()]),
|
||||||
extensions: None,
|
extensions: GetAssertionExtensions::default(),
|
||||||
options: GetAssertionOptions {
|
options: GetAssertionOptions {
|
||||||
up: false,
|
up: false,
|
||||||
uv: false,
|
uv: false,
|
||||||
@@ -1877,6 +2015,7 @@ mod test {
|
|||||||
creation_order: 0,
|
creation_order: 0,
|
||||||
user_name: None,
|
user_name: None,
|
||||||
user_icon: None,
|
user_icon: None,
|
||||||
|
cred_blob: None,
|
||||||
};
|
};
|
||||||
assert!(ctap_state
|
assert!(ctap_state
|
||||||
.persistent_store
|
.persistent_store
|
||||||
@@ -1887,7 +2026,7 @@ mod test {
|
|||||||
rp_id: String::from("example.com"),
|
rp_id: String::from("example.com"),
|
||||||
client_data_hash: vec![0xCD],
|
client_data_hash: vec![0xCD],
|
||||||
allow_list: Some(vec![cred_desc]),
|
allow_list: Some(vec![cred_desc]),
|
||||||
extensions: None,
|
extensions: GetAssertionExtensions::default(),
|
||||||
options: GetAssertionOptions {
|
options: GetAssertionOptions {
|
||||||
up: false,
|
up: false,
|
||||||
uv: false,
|
uv: false,
|
||||||
@@ -1906,6 +2045,69 @@ mod test {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process_get_assertion_with_cred_blob() {
|
||||||
|
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, DUMMY_CLOCK_VALUE);
|
||||||
|
|
||||||
|
let credential = PublicKeyCredentialSource {
|
||||||
|
key_type: PublicKeyCredentialType::PublicKey,
|
||||||
|
credential_id,
|
||||||
|
private_key,
|
||||||
|
rp_id: String::from("example.com"),
|
||||||
|
user_handle: vec![0x1D],
|
||||||
|
user_display_name: None,
|
||||||
|
cred_protect_policy: None,
|
||||||
|
creation_order: 0,
|
||||||
|
user_name: None,
|
||||||
|
user_icon: None,
|
||||||
|
cred_blob: Some(vec![0xCB]),
|
||||||
|
};
|
||||||
|
assert!(ctap_state
|
||||||
|
.persistent_store
|
||||||
|
.store_credential(credential)
|
||||||
|
.is_ok());
|
||||||
|
|
||||||
|
let extensions = GetAssertionExtensions {
|
||||||
|
hmac_secret: None,
|
||||||
|
cred_blob: true,
|
||||||
|
};
|
||||||
|
let get_assertion_params = AuthenticatorGetAssertionParameters {
|
||||||
|
rp_id: String::from("example.com"),
|
||||||
|
client_data_hash: vec![0xCD],
|
||||||
|
allow_list: None,
|
||||||
|
extensions,
|
||||||
|
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,
|
||||||
|
DUMMY_CLOCK_VALUE,
|
||||||
|
);
|
||||||
|
let signature_counter = ctap_state
|
||||||
|
.persistent_store
|
||||||
|
.global_signature_counter()
|
||||||
|
.unwrap();
|
||||||
|
let expected_extension_cbor = [
|
||||||
|
0xA1, 0x68, 0x63, 0x72, 0x65, 0x64, 0x42, 0x6C, 0x6F, 0x62, 0x41, 0xCB,
|
||||||
|
];
|
||||||
|
check_assertion_response_with_extension(
|
||||||
|
get_assertion_response,
|
||||||
|
vec![0x1D],
|
||||||
|
signature_counter,
|
||||||
|
None,
|
||||||
|
&expected_extension_cbor,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_process_get_next_assertion_two_credentials_with_uv() {
|
fn test_process_get_next_assertion_two_credentials_with_uv() {
|
||||||
let mut rng = ThreadRng256 {};
|
let mut rng = ThreadRng256 {};
|
||||||
@@ -1951,7 +2153,7 @@ mod test {
|
|||||||
rp_id: String::from("example.com"),
|
rp_id: String::from("example.com"),
|
||||||
client_data_hash: vec![0xCD],
|
client_data_hash: vec![0xCD],
|
||||||
allow_list: None,
|
allow_list: None,
|
||||||
extensions: None,
|
extensions: GetAssertionExtensions::default(),
|
||||||
options: GetAssertionOptions {
|
options: GetAssertionOptions {
|
||||||
up: false,
|
up: false,
|
||||||
uv: true,
|
uv: true,
|
||||||
@@ -1974,6 +2176,7 @@ mod test {
|
|||||||
0x04,
|
0x04,
|
||||||
signature_counter,
|
signature_counter,
|
||||||
Some(2),
|
Some(2),
|
||||||
|
&[],
|
||||||
);
|
);
|
||||||
|
|
||||||
let get_assertion_response = ctap_state.process_get_next_assertion(DUMMY_CLOCK_VALUE);
|
let get_assertion_response = ctap_state.process_get_next_assertion(DUMMY_CLOCK_VALUE);
|
||||||
@@ -1983,6 +2186,7 @@ mod test {
|
|||||||
0x04,
|
0x04,
|
||||||
signature_counter,
|
signature_counter,
|
||||||
None,
|
None,
|
||||||
|
&[],
|
||||||
);
|
);
|
||||||
|
|
||||||
let get_assertion_response = ctap_state.process_get_next_assertion(DUMMY_CLOCK_VALUE);
|
let get_assertion_response = ctap_state.process_get_next_assertion(DUMMY_CLOCK_VALUE);
|
||||||
@@ -2027,7 +2231,7 @@ mod test {
|
|||||||
rp_id: String::from("example.com"),
|
rp_id: String::from("example.com"),
|
||||||
client_data_hash: vec![0xCD],
|
client_data_hash: vec![0xCD],
|
||||||
allow_list: None,
|
allow_list: None,
|
||||||
extensions: None,
|
extensions: GetAssertionExtensions::default(),
|
||||||
options: GetAssertionOptions {
|
options: GetAssertionOptions {
|
||||||
up: false,
|
up: false,
|
||||||
uv: false,
|
uv: false,
|
||||||
@@ -2091,7 +2295,7 @@ mod test {
|
|||||||
rp_id: String::from("example.com"),
|
rp_id: String::from("example.com"),
|
||||||
client_data_hash: vec![0xCD],
|
client_data_hash: vec![0xCD],
|
||||||
allow_list: None,
|
allow_list: None,
|
||||||
extensions: None,
|
extensions: GetAssertionExtensions::default(),
|
||||||
options: GetAssertionOptions {
|
options: GetAssertionOptions {
|
||||||
up: false,
|
up: false,
|
||||||
uv: false,
|
uv: false,
|
||||||
@@ -2147,6 +2351,7 @@ mod test {
|
|||||||
creation_order: 0,
|
creation_order: 0,
|
||||||
user_name: None,
|
user_name: None,
|
||||||
user_icon: None,
|
user_icon: None,
|
||||||
|
cred_blob: None,
|
||||||
};
|
};
|
||||||
assert!(ctap_state
|
assert!(ctap_state
|
||||||
.persistent_store
|
.persistent_store
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ const DEFAULT_MIN_PIN_LENGTH: u8 = 4;
|
|||||||
const DEFAULT_MIN_PIN_LENGTH_RP_IDS: Vec<String> = Vec::new();
|
const DEFAULT_MIN_PIN_LENGTH_RP_IDS: Vec<String> = Vec::new();
|
||||||
// This constant is an attempt to limit storage requirements. If you don't set it to 0,
|
// This constant is an attempt to limit storage requirements. If you don't set it to 0,
|
||||||
// the stored strings can still be unbounded, but that is true for all RP IDs.
|
// the stored strings can still be unbounded, but that is true for all RP IDs.
|
||||||
const MAX_RP_IDS_LENGTH: usize = 8;
|
pub const MAX_RP_IDS_LENGTH: usize = 8;
|
||||||
|
|
||||||
/// Wrapper for master keys.
|
/// Wrapper for master keys.
|
||||||
pub struct MasterKeys {
|
pub struct MasterKeys {
|
||||||
@@ -690,6 +690,7 @@ mod test {
|
|||||||
creation_order: 0,
|
creation_order: 0,
|
||||||
user_name: None,
|
user_name: None,
|
||||||
user_icon: None,
|
user_icon: None,
|
||||||
|
cred_blob: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -906,6 +907,7 @@ mod test {
|
|||||||
creation_order: 0,
|
creation_order: 0,
|
||||||
user_name: None,
|
user_name: None,
|
||||||
user_icon: None,
|
user_icon: None,
|
||||||
|
cred_blob: None,
|
||||||
};
|
};
|
||||||
assert_eq!(found_credential, Some(expected_credential));
|
assert_eq!(found_credential, Some(expected_credential));
|
||||||
}
|
}
|
||||||
@@ -927,6 +929,7 @@ mod test {
|
|||||||
creation_order: 0,
|
creation_order: 0,
|
||||||
user_name: None,
|
user_name: None,
|
||||||
user_icon: None,
|
user_icon: None,
|
||||||
|
cred_blob: None,
|
||||||
};
|
};
|
||||||
assert!(persistent_store.store_credential(credential).is_ok());
|
assert!(persistent_store.store_credential(credential).is_ok());
|
||||||
|
|
||||||
@@ -1160,6 +1163,7 @@ mod test {
|
|||||||
creation_order: 0,
|
creation_order: 0,
|
||||||
user_name: None,
|
user_name: None,
|
||||||
user_icon: None,
|
user_icon: None,
|
||||||
|
cred_blob: None,
|
||||||
};
|
};
|
||||||
let serialized = serialize_credential(credential.clone()).unwrap();
|
let serialized = serialize_credential(credential.clone()).unwrap();
|
||||||
let reconstructed = deserialize_credential(&serialized).unwrap();
|
let reconstructed = deserialize_credential(&serialized).unwrap();
|
||||||
|
|||||||
Reference in New Issue
Block a user