Persist/parse slot_id in/from credential (#569)

* Persist/parse slot_id in/from credential

Persist slot_id into credential_id or the resident credential record
during MakeCredential, and parse it during GetAssertion. Add related
unittests.

* Fix styles

* Move enable_pin_uv back to ctap/mod.rs
This commit is contained in:
hcyang
2022-11-02 18:08:25 +08:00
committed by GitHub
parent 31774ef316
commit 81330e5d52
6 changed files with 902 additions and 68 deletions

View File

@@ -19,7 +19,7 @@ use super::data_formats::{
use super::status_code::Ctap2StatusCode;
use super::{cbor_read, cbor_write};
use crate::api::key_store::KeyStore;
use crate::ctap::data_formats::{extract_byte_string, extract_map};
use crate::ctap::data_formats::{extract_byte_string, extract_map, extract_unsigned};
use crate::env::Env;
use alloc::string::String;
use alloc::vec::Vec;
@@ -48,6 +48,7 @@ struct CredentialSource {
rp_id_hash: [u8; 32],
cred_protect_policy: Option<CredentialProtectionPolicy>,
cred_blob: Option<Vec<u8>>,
slot_id: Option<usize>,
}
// The data fields contained in the credential ID are serialized using CBOR maps.
@@ -57,6 +58,7 @@ enum CredentialSourceField {
RpIdHash = 1,
CredProtectPolicy = 2,
CredBlob = 3,
SlotId = 4,
}
impl From<CredentialSourceField> for sk_cbor::Value {
@@ -84,6 +86,7 @@ fn decrypt_legacy_credential_id(
rp_id_hash: plaintext[32..64].try_into().unwrap(),
cred_protect_policy: None,
cred_blob: None,
slot_id: None,
}))
}
@@ -102,6 +105,7 @@ fn decrypt_cbor_credential_id(
CredentialSourceField::RpIdHash=> rp_id_hash,
CredentialSourceField::CredProtectPolicy => cred_protect_policy,
CredentialSourceField::CredBlob => cred_blob,
CredentialSourceField::SlotId => slot_id,
} = extract_map(cbor_credential_source)?;
}
Ok(match (private_key, rp_id_hash) {
@@ -115,11 +119,19 @@ fn decrypt_cbor_credential_id(
.map(CredentialProtectionPolicy::try_from)
.transpose()?;
let cred_blob = cred_blob.map(extract_byte_string).transpose()?;
let slot_id = match slot_id.map(extract_unsigned).transpose()? {
Some(x) => Some(
usize::try_from(x)
.map_err(|_| Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?,
),
None => None,
};
Some(CredentialSource {
private_key,
rp_id_hash: rp_id_hash.try_into().unwrap(),
cred_protect_policy,
cred_blob,
slot_id,
})
}
_ => None,
@@ -167,6 +179,7 @@ pub fn encrypt_to_credential_id(
rp_id_hash: &[u8; 32],
cred_protect_policy: Option<CredentialProtectionPolicy>,
cred_blob: Option<Vec<u8>>,
slot_id: usize,
) -> Result<Vec<u8>, Ctap2StatusCode> {
let mut payload = Vec::new();
let cbor = cbor_map_options! {
@@ -174,6 +187,7 @@ pub fn encrypt_to_credential_id(
CredentialSourceField::RpIdHash=> rp_id_hash,
CredentialSourceField::CredProtectPolicy => cred_protect_policy,
CredentialSourceField::CredBlob => cred_blob,
CredentialSourceField::SlotId => slot_id as u64,
};
cbor_write(cbor, &mut payload)?;
add_padding(&mut payload)?;
@@ -262,6 +276,7 @@ pub fn decrypt_credential_id(
user_icon: None,
cred_blob: credential_source.cred_blob,
large_blob_key: None,
slot_id: credential_source.slot_id,
}))
}
@@ -282,7 +297,7 @@ mod test {
let rp_id_hash = [0x55; 32];
let encrypted_id =
encrypt_to_credential_id(&mut env, &private_key, &rp_id_hash, None, None).unwrap();
encrypt_to_credential_id(&mut env, &private_key, &rp_id_hash, None, None, 0).unwrap();
let decrypted_source = decrypt_credential_id(&mut env, encrypted_id, &rp_id_hash)
.unwrap()
.unwrap();
@@ -308,7 +323,7 @@ mod test {
let rp_id_hash = [0x55; 32];
let mut encrypted_id =
encrypt_to_credential_id(&mut env, &private_key, &rp_id_hash, None, None).unwrap();
encrypt_to_credential_id(&mut env, &private_key, &rp_id_hash, None, None, 0).unwrap();
encrypted_id[0] = UNSUPPORTED_CREDENTIAL_ID_VERSION;
// Override the HMAC to pass the check.
encrypted_id.truncate(&encrypted_id.len() - 32);
@@ -328,7 +343,7 @@ mod test {
let rp_id_hash = [0x55; 32];
let encrypted_id =
encrypt_to_credential_id(&mut env, &private_key, &rp_id_hash, None, None).unwrap();
encrypt_to_credential_id(&mut env, &private_key, &rp_id_hash, None, None, 0).unwrap();
for i in 0..encrypted_id.len() {
let mut modified_id = encrypted_id.clone();
modified_id[i] ^= 0x01;
@@ -356,7 +371,7 @@ mod test {
let rp_id_hash = [0x55; 32];
let encrypted_id =
encrypt_to_credential_id(&mut env, &private_key, &rp_id_hash, None, None).unwrap();
encrypt_to_credential_id(&mut env, &private_key, &rp_id_hash, None, None, 0).unwrap();
for length in (1..CBOR_CREDENTIAL_ID_SIZE).step_by(16) {
assert_eq!(
@@ -423,7 +438,7 @@ mod test {
let rp_id_hash = [0x55; 32];
let encrypted_id =
encrypt_to_credential_id(&mut env, &private_key, &rp_id_hash, None, None).unwrap();
encrypt_to_credential_id(&mut env, &private_key, &rp_id_hash, None, None, 0).unwrap();
assert_eq!(encrypted_id.len(), CBOR_CREDENTIAL_ID_SIZE);
}
@@ -444,6 +459,7 @@ mod test {
&rp_id_hash,
cred_protect_policy,
cred_blob,
0,
);
assert!(encrypted_id.is_ok());
@@ -461,6 +477,7 @@ mod test {
&rp_id_hash,
Some(CredentialProtectionPolicy::UserVerificationRequired),
None,
0,
)
.unwrap();
@@ -481,9 +498,15 @@ mod test {
let rp_id_hash = [0x55; 32];
let cred_blob = Some(vec![0x55; env.customization().max_cred_blob_length()]);
let encrypted_id =
encrypt_to_credential_id(&mut env, &private_key, &rp_id_hash, None, cred_blob.clone())
.unwrap();
let encrypted_id = encrypt_to_credential_id(
&mut env,
&private_key,
&rp_id_hash,
None,
cred_blob.clone(),
0,
)
.unwrap();
let decrypted_source = decrypt_credential_id(&mut env, encrypted_id, &rp_id_hash)
.unwrap()
@@ -491,4 +514,22 @@ mod test {
assert_eq!(decrypted_source.private_key, private_key);
assert_eq!(decrypted_source.cred_blob, cred_blob);
}
#[test]
fn test_slot_id_persisted() {
let mut env = TestEnv::new();
let private_key = PrivateKey::new(&mut env, SignatureAlgorithm::Es256);
let rp_id_hash = [0x55; 32];
let slot_id = 1;
let encrypted_id =
encrypt_to_credential_id(&mut env, &private_key, &rp_id_hash, None, None, slot_id)
.unwrap();
let decrypted_source = decrypt_credential_id(&mut env, encrypted_id, &rp_id_hash)
.unwrap()
.unwrap();
assert_eq!(decrypted_source.private_key, private_key);
assert_eq!(decrypted_source.slot_id, Some(slot_id));
}
}

View File

@@ -81,6 +81,7 @@ fn enumerate_credentials_response(
user_icon,
cred_blob: _,
large_blob_key,
slot_id: _,
} = credential;
let user = PublicKeyCredentialUserEntity {
user_id: user_handle,
@@ -183,12 +184,17 @@ fn process_enumerate_credentials_begin(
.rp_id_hash
.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?;
client_pin.has_no_or_rp_id_hash_permission(&rp_id_hash[..])?;
// enumerateCredentials needs UV, so slot_id must not be None.
let slot_id = client_pin
.get_slot_id_in_use_or_default(env)?
.ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?;
let mut iter_result = Ok(());
let iter = storage::iter_credentials(env, &mut iter_result)?;
let mut rp_credentials: Vec<usize> = iter
.filter_map(|(key, credential)| {
let cred_rp_id_hash = Sha256::hash(credential.rp_id.as_bytes());
if cred_rp_id_hash == rp_id_hash.as_slice() {
let slot_id_matches = credential.slot_id.unwrap_or(0) == slot_id;
if cred_rp_id_hash == rp_id_hash.as_slice() && slot_id_matches {
Some(key)
} else {
None
@@ -385,6 +391,7 @@ mod test {
user_icon: Some("icon".to_string()),
cred_blob: None,
large_blob_key: None,
slot_id: None,
}
}
@@ -766,6 +773,120 @@ mod test {
);
}
#[test]
fn test_process_enumerate_credentials_multi_pin() {
let mut env = TestEnv::new();
storage::_enable_multi_pin_for_test(&mut env).unwrap();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let client_pin = ClientPin::new_test(
&mut env,
1,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
// credential_source1 has no slot_id, so should be treated as slot 0. Only credential_source 2 and 4
// should be discovered.
let credential_source1 = create_credential_source(&mut env);
let mut credential_source2 = create_credential_source(&mut env);
credential_source2.user_handle = vec![0x02];
credential_source2.slot_id = Some(1);
let mut credential_source3 = create_credential_source(&mut env);
credential_source3.user_handle = vec![0x03];
credential_source3.slot_id = Some(2);
let mut credential_source4 = create_credential_source(&mut env);
credential_source4.user_handle = vec![0x04];
credential_source4.slot_id = Some(1);
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
ctap_state.client_pin = client_pin;
storage::store_credential(&mut env, credential_source1).unwrap();
storage::store_credential(&mut env, credential_source2).unwrap();
storage::store_credential(&mut env, credential_source3).unwrap();
storage::store_credential(&mut env, credential_source4).unwrap();
storage::set_pin(&mut env, 1, &[0u8; 16], 4).unwrap();
let pin_uv_auth_param = Some(vec![
0xF8, 0xB0, 0x3C, 0xC1, 0xD5, 0x58, 0x9C, 0xB7, 0x4D, 0x42, 0xA1, 0x64, 0x14, 0x28,
0x2B, 0x68,
]);
let sub_command_params = CredentialManagementSubCommandParameters {
rp_id_hash: Some(Sha256::hash(b"example.com").to_vec()),
credential_id: None,
user: None,
};
// RP ID hash:
// A379A6F6EEAFB9A55E378C118034E2751E682FAB9F2D30AB13D2125586CE1947
let cred_management_params = AuthenticatorCredentialManagementParameters {
sub_command: CredentialManagementSubCommand::EnumerateCredentialsBegin,
sub_command_params: Some(sub_command_params),
pin_uv_auth_protocol: Some(PinUvAuthProtocol::V1),
pin_uv_auth_param,
};
let cred_management_response = process_credential_management(
&mut env,
&mut ctap_state.stateful_command_permission,
&mut ctap_state.client_pin,
cred_management_params,
DUMMY_CHANNEL,
CtapInstant::new(0),
);
match cred_management_response.unwrap() {
ResponseData::AuthenticatorCredentialManagement(Some(response)) => {
assert!(response.user.is_some());
assert!(response.public_key.is_some());
assert_eq!(response.total_credentials, Some(2));
}
_ => panic!("Invalid response type"),
};
let cred_management_params = AuthenticatorCredentialManagementParameters {
sub_command: CredentialManagementSubCommand::EnumerateCredentialsGetNextCredential,
sub_command_params: None,
pin_uv_auth_protocol: None,
pin_uv_auth_param: None,
};
let cred_management_response = process_credential_management(
&mut env,
&mut ctap_state.stateful_command_permission,
&mut ctap_state.client_pin,
cred_management_params,
DUMMY_CHANNEL,
CtapInstant::new(0),
);
match cred_management_response.unwrap() {
ResponseData::AuthenticatorCredentialManagement(Some(response)) => {
assert!(response.user.is_some());
assert!(response.public_key.is_some());
assert_eq!(response.total_credentials, None);
}
_ => panic!("Invalid response type"),
};
let cred_management_params = AuthenticatorCredentialManagementParameters {
sub_command: CredentialManagementSubCommand::EnumerateCredentialsGetNextCredential,
sub_command_params: None,
pin_uv_auth_protocol: None,
pin_uv_auth_param: None,
};
let cred_management_response = process_credential_management(
&mut env,
&mut ctap_state.stateful_command_permission,
&mut ctap_state.client_pin,
cred_management_params,
DUMMY_CHANNEL,
CtapInstant::new(0),
);
assert_eq!(
cred_management_response,
Err(Ctap2StatusCode::CTAP2_ERR_NOT_ALLOWED)
);
}
#[test]
fn test_process_delete_credential() {
let mut env = TestEnv::new();

View File

@@ -249,7 +249,7 @@ impl Ctap1Command {
.ecdsa_key(env)
.map_err(|_| Ctap1StatusCode::SW_INTERNAL_EXCEPTION)?;
let pk = sk.genpk();
let key_handle = encrypt_to_credential_id(env, &private_key, &application, None, None)
let key_handle = encrypt_to_credential_id(env, &private_key, &application, None, None, 0)
.map_err(|_| Ctap1StatusCode::SW_INTERNAL_EXCEPTION)?;
if key_handle.len() > 0xFF {
// This is just being defensive with unreachable code.
@@ -499,7 +499,8 @@ mod test {
let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
let key_handle = encrypt_to_credential_id(&mut env, &sk, &application, None, None).unwrap();
let key_handle =
encrypt_to_credential_id(&mut env, &sk, &application, None, None, 0).unwrap();
let message = create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
let response =
@@ -517,7 +518,8 @@ mod test {
let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
let key_handle = encrypt_to_credential_id(&mut env, &sk, &application, None, None).unwrap();
let key_handle =
encrypt_to_credential_id(&mut env, &sk, &application, None, None, 0).unwrap();
let application = [0x55; 32];
let message = create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
@@ -536,7 +538,8 @@ mod test {
let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
let key_handle = encrypt_to_credential_id(&mut env, &sk, &application, None, None).unwrap();
let key_handle =
encrypt_to_credential_id(&mut env, &sk, &application, None, None, 0).unwrap();
let mut message = create_authenticate_message(
&application,
Ctap1Flags::DontEnforceUpAndSign,
@@ -574,7 +577,8 @@ mod test {
let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
let key_handle = encrypt_to_credential_id(&mut env, &sk, &application, None, None).unwrap();
let key_handle =
encrypt_to_credential_id(&mut env, &sk, &application, None, None, 0).unwrap();
let mut message =
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
message[0] = 0xEE;
@@ -594,7 +598,8 @@ mod test {
let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
let key_handle = encrypt_to_credential_id(&mut env, &sk, &application, None, None).unwrap();
let key_handle =
encrypt_to_credential_id(&mut env, &sk, &application, None, None, 0).unwrap();
let mut message =
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
message[1] = 0xEE;
@@ -614,7 +619,8 @@ mod test {
let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
let key_handle = encrypt_to_credential_id(&mut env, &sk, &application, None, None).unwrap();
let key_handle =
encrypt_to_credential_id(&mut env, &sk, &application, None, None, 0).unwrap();
let mut message =
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
message[2] = 0xEE;
@@ -642,7 +648,8 @@ mod test {
let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
let key_handle = encrypt_to_credential_id(&mut env, &sk, &application, None, None).unwrap();
let key_handle =
encrypt_to_credential_id(&mut env, &sk, &application, None, None, 0).unwrap();
let message =
create_authenticate_message(&application, Ctap1Flags::EnforceUpAndSign, &key_handle);
@@ -670,7 +677,8 @@ mod test {
let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
let key_handle = encrypt_to_credential_id(&mut env, &sk, &application, None, None).unwrap();
let key_handle =
encrypt_to_credential_id(&mut env, &sk, &application, None, None, 0).unwrap();
let message = create_authenticate_message(
&application,
Ctap1Flags::DontEnforceUpAndSign,

View File

@@ -595,6 +595,7 @@ pub struct PublicKeyCredentialSource {
pub user_icon: Option<String>,
pub cred_blob: Option<Vec<u8>>,
pub large_blob_key: Option<Vec<u8>>,
pub slot_id: Option<usize>,
}
// We serialize credentials for the persistent storage using CBOR maps. Each field of a credential
@@ -613,6 +614,7 @@ enum PublicKeyCredentialSourceField {
CredBlob = 10,
LargeBlobKey = 11,
PrivateKey = 12,
SlotId = 13,
// When a field is removed, its tag should be reserved and not used for new fields. We document
// those reserved tags below.
// Reserved tags:
@@ -639,6 +641,7 @@ impl From<PublicKeyCredentialSource> for cbor::Value {
PublicKeyCredentialSourceField::CredBlob => credential.cred_blob,
PublicKeyCredentialSourceField::LargeBlobKey => credential.large_blob_key,
PublicKeyCredentialSourceField::PrivateKey => credential.private_key,
PublicKeyCredentialSourceField::SlotId => credential.slot_id.map(|x| x as u64),
}
}
}
@@ -661,6 +664,7 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialSource {
PublicKeyCredentialSourceField::CredBlob => cred_blob,
PublicKeyCredentialSourceField::LargeBlobKey => large_blob_key,
PublicKeyCredentialSourceField::PrivateKey => private_key,
PublicKeyCredentialSourceField::SlotId => slot_id,
} = extract_map(cbor_value)?;
}
@@ -687,6 +691,12 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialSource {
.ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?,
(None, Some(k)) => k,
};
let slot_id = match slot_id.map(extract_unsigned).transpose()? {
Some(x) => Some(
usize::try_from(x).map_err(|_| Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?,
),
None => None,
};
// 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
@@ -711,6 +721,7 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialSource {
user_icon,
cred_blob,
large_blob_key,
slot_id,
})
}
}
@@ -2229,6 +2240,7 @@ mod test {
user_icon: None,
cred_blob: None,
large_blob_key: None,
slot_id: None,
};
assert_eq!(
@@ -2291,6 +2303,16 @@ mod test {
..credential
};
assert_eq!(
PublicKeyCredentialSource::try_from(cbor::Value::from(credential.clone())),
Ok(credential.clone())
);
let credential = PublicKeyCredentialSource {
slot_id: Some(1),
..credential
};
assert_eq!(
PublicKeyCredentialSource::try_from(cbor::Value::from(credential.clone())),
Ok(credential)
@@ -2315,6 +2337,7 @@ mod test {
user_icon: None,
cred_blob: None,
large_blob_key: None,
slot_id: None,
};
let source_cbor = cbor_map! {
@@ -2347,6 +2370,7 @@ mod test {
user_icon: None,
cred_blob: None,
large_blob_key: None,
slot_id: None,
};
let source_cbor = cbor_map! {

View File

@@ -743,7 +743,7 @@ impl CtapState {
Ok(())
}
fn check_cred_protect_for_listed_credential(
fn check_cred_protect(
&mut self,
credential: &Option<PublicKeyCredentialSource>,
has_uv: bool,
@@ -759,6 +759,29 @@ impl CtapState {
}
}
// This checks that either the credential contains no slot id and the expected slot id
// is 0, or the credential contains the expected slot id.
fn check_slot_id_matches(
&mut self,
credential: &PublicKeyCredentialSource,
slot_id: usize,
) -> bool {
credential.slot_id.unwrap_or(0) == slot_id
}
// Perform cred protect and slot id checks on a credential to determine whether it is
// "visible".
fn check_listed_credential_is_visible(
&mut self,
credential: &Option<PublicKeyCredentialSource>,
has_uv: bool,
slot_id: usize,
) -> bool {
credential.is_some()
&& self.check_cred_protect(credential, has_uv)
&& self.check_slot_id_matches(credential.as_ref().unwrap(), slot_id)
}
fn process_make_credential(
&mut self,
env: &mut impl Env,
@@ -865,12 +888,14 @@ impl CtapState {
let rp_id_hash = Sha256::hash(rp_id.as_bytes());
if let Some(exclude_list) = exclude_list {
for cred_desc in exclude_list {
if self.check_cred_protect_for_listed_credential(
if self.check_listed_credential_is_visible(
&storage::find_credential(env, &rp_id, &cred_desc.key_id)?,
has_uv,
) || self.check_cred_protect_for_listed_credential(
slot_id,
) || self.check_listed_credential_is_visible(
&decrypt_credential_id(env, cred_desc.key_id, &rp_id_hash)?,
has_uv,
slot_id,
) {
// Perform this check, so bad actors can't brute force exclude_list
// without user interaction.
@@ -917,7 +942,6 @@ impl CtapState {
// We decide on the algorithm early, but delay key creation since it takes time.
// We rather do that later so all intermediate checks may return faster.
let private_key = PrivateKey::new(env, algorithm);
// TODO: persist slot_id in the credential.
let credential_id = if options.rk {
let random_id = env.rng().gen_uniform_u8x32().to_vec();
let credential_source = PublicKeyCredentialSource {
@@ -941,6 +965,7 @@ impl CtapState {
.map(|s| truncate_to_char_boundary(&s, 64).to_string()),
cred_blob,
large_blob_key: large_blob_key.clone(),
slot_id: Some(slot_id),
};
storage::store_credential(env, credential_source)?;
random_id
@@ -951,6 +976,7 @@ impl CtapState {
&rp_id_hash,
cred_protect_policy,
cred_blob,
slot_id,
)?
};
@@ -1143,14 +1169,15 @@ impl CtapState {
rp_id: &str,
rp_id_hash: &[u8],
has_uv: bool,
slot_id: usize,
) -> Result<Option<PublicKeyCredentialSource>, Ctap2StatusCode> {
for allowed_credential in allow_list {
let credential = storage::find_credential(env, rp_id, &allowed_credential.key_id)?;
if self.check_cred_protect_for_listed_credential(&credential, has_uv) {
if self.check_listed_credential_is_visible(&credential, has_uv, slot_id) {
return Ok(credential);
}
let credential = decrypt_credential_id(env, allowed_credential.key_id, rp_id_hash)?;
if self.check_cred_protect_for_listed_credential(&credential, has_uv) {
if self.check_listed_credential_is_visible(&credential, has_uv, slot_id) {
return Ok(credential);
}
}
@@ -1233,7 +1260,6 @@ impl CtapState {
let slot_id = slot_id.ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?;
let rp_id_hash = Sha256::hash(rp_id.as_bytes());
// TODO: check slot_id in the credential matches.
let (credential, next_credential_keys) = if let Some(allow_list) = allow_list {
(
self.get_any_credential_from_allow_list(
@@ -1242,6 +1268,7 @@ impl CtapState {
&rp_id,
&rp_id_hash,
has_uv,
slot_id,
)?,
vec![],
)
@@ -1250,7 +1277,10 @@ impl CtapState {
let iter = storage::iter_credentials(env, &mut iter_result)?;
let mut stored_credentials: Vec<(usize, u64)> = iter
.filter_map(|(key, credential)| {
if credential.rp_id == rp_id && (has_uv || credential.is_discoverable()) {
if credential.rp_id == rp_id
&& (has_uv || credential.is_discoverable())
&& self.check_slot_id_matches(&credential, slot_id)
{
Some((key, credential.creation_order))
} else {
None
@@ -1599,6 +1629,34 @@ mod test {
#[cfg(feature = "vendor_hid")]
const VENDOR_CHANNEL: Channel = Channel::VendorHid([0x12, 0x34, 0x56, 0x78]);
const DUMMY_CLIENT_DATA_HASH: [u8; 1] = [0xCD];
fn enable_pin_uv(
state: &mut CtapState,
env: &mut impl Env,
pin_uv_auth_protocol: PinUvAuthProtocol,
slot_id: usize,
client_data_hash: &[u8],
) -> Result<Vec<u8>, Ctap2StatusCode> {
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x91; PIN_TOKEN_LENGTH];
let client_pin = ClientPin::new_test(
env,
slot_id,
key_agreement_key,
pin_uv_auth_token,
pin_uv_auth_protocol,
);
state.client_pin = client_pin;
storage::set_pin(env, slot_id, &[0x88; 16], 4)?;
Ok(authenticate_pin_uv_auth_token(
&pin_uv_auth_token,
client_data_hash,
pin_uv_auth_protocol,
))
}
fn check_make_response(
make_credential_response: &Result<ResponseData, Ctap2StatusCode>,
flags: u8,
@@ -1696,7 +1754,7 @@ mod test {
}
fn create_minimal_make_credential_parameters() -> AuthenticatorMakeCredentialParameters {
let client_data_hash = vec![0xCD];
let client_data_hash = DUMMY_CLIENT_DATA_HASH.to_vec();
let rp = PublicKeyCredentialRpEntity {
rp_id: String::from("example.com"),
rp_name: None,
@@ -1727,6 +1785,16 @@ mod test {
}
}
fn add_pin_uv_to_make_credential_parameters(
params: &mut AuthenticatorMakeCredentialParameters,
pin_uv_auth_protocol: PinUvAuthProtocol,
pin_uv_auth_param: Vec<u8>,
) {
params.options.uv = true;
params.pin_uv_auth_param = Some(pin_uv_auth_param);
params.pin_uv_auth_protocol = Some(pin_uv_auth_protocol);
}
fn create_make_credential_parameters_with_exclude_list(
excluded_credential_id: &[u8],
) -> AuthenticatorMakeCredentialParameters {
@@ -1844,6 +1912,7 @@ mod test {
user_icon: None,
cred_blob: None,
large_blob_key: None,
slot_id: None,
};
assert!(storage::store_credential(&mut env, excluded_credential_source).is_ok());
@@ -1855,6 +1924,116 @@ mod test {
);
}
#[test]
fn test_process_make_credential_credential_excluded_multi_pin_slot_matched() {
let mut env = TestEnv::new();
storage::_enable_multi_pin_for_test(&mut env).unwrap();
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
let excluded_private_key = PrivateKey::new_ecdsa(&mut env);
let excluded_credential_id = vec![0x01, 0x23, 0x45, 0x67];
let slot_id = 1;
let pin_uv_auth_protocol = PinUvAuthProtocol::V1;
let pin_uv_auth_param = enable_pin_uv(
&mut ctap_state,
&mut env,
pin_uv_auth_protocol,
slot_id,
&DUMMY_CLIENT_DATA_HASH,
)
.unwrap();
let mut make_credential_params =
create_make_credential_parameters_with_exclude_list(&excluded_credential_id);
add_pin_uv_to_make_credential_parameters(
&mut make_credential_params,
pin_uv_auth_protocol,
pin_uv_auth_param,
);
let excluded_credential_source = PublicKeyCredentialSource {
key_type: PublicKeyCredentialType::PublicKey,
credential_id: excluded_credential_id,
private_key: excluded_private_key,
rp_id: String::from("example.com"),
user_handle: vec![],
user_display_name: None,
cred_protect_policy: None,
creation_order: 0,
user_name: None,
user_icon: None,
cred_blob: None,
large_blob_key: None,
slot_id: Some(slot_id),
};
assert!(storage::store_credential(&mut env, excluded_credential_source).is_ok());
let make_credential_response =
ctap_state.process_make_credential(&mut env, make_credential_params, DUMMY_CHANNEL);
assert_eq!(
make_credential_response,
Err(Ctap2StatusCode::CTAP2_ERR_CREDENTIAL_EXCLUDED)
);
}
#[test]
fn test_process_make_credential_credential_excluded_multi_pin_slot_mismatched() {
let mut env = TestEnv::new();
storage::_enable_multi_pin_for_test(&mut env).unwrap();
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
let excluded_private_key = PrivateKey::new_ecdsa(&mut env);
let excluded_credential_id = vec![0x01, 0x23, 0x45, 0x67];
let slot_id = 1;
let pin_uv_auth_protocol = PinUvAuthProtocol::V1;
let pin_uv_auth_param = enable_pin_uv(
&mut ctap_state,
&mut env,
pin_uv_auth_protocol,
slot_id,
&DUMMY_CLIENT_DATA_HASH,
)
.unwrap();
let mut make_credential_params =
create_make_credential_parameters_with_exclude_list(&excluded_credential_id);
add_pin_uv_to_make_credential_parameters(
&mut make_credential_params,
pin_uv_auth_protocol,
pin_uv_auth_param,
);
let excluded_credential_source = PublicKeyCredentialSource {
key_type: PublicKeyCredentialType::PublicKey,
credential_id: excluded_credential_id,
private_key: excluded_private_key,
rp_id: String::from("example.com"),
user_handle: vec![],
user_display_name: None,
cred_protect_policy: None,
creation_order: 0,
user_name: None,
user_icon: None,
cred_blob: None,
large_blob_key: None,
// Doesn't match the current slot_id.
slot_id: Some(2),
};
assert!(storage::store_credential(&mut env, excluded_credential_source).is_ok());
let make_credential_response =
ctap_state.process_make_credential(&mut env, make_credential_params, DUMMY_CHANNEL);
check_make_response(
&make_credential_response,
0x45,
&storage::aaguid(&mut env).unwrap(),
0x20,
&[],
);
}
#[test]
fn test_process_make_credential_credential_with_cred_protect() {
let mut env = TestEnv::new();
@@ -1949,6 +2128,162 @@ mod test {
assert!(make_credential_response.is_ok());
}
#[test]
fn test_process_make_credential_multi_pin() {
let mut env = TestEnv::new();
storage::_enable_multi_pin_for_test(&mut env).unwrap();
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
let slot_id_1 = 1;
let pin_uv_auth_protocol = PinUvAuthProtocol::V1;
let mut pin_uv_auth_param = enable_pin_uv(
&mut ctap_state,
&mut env,
pin_uv_auth_protocol,
slot_id_1,
&DUMMY_CLIENT_DATA_HASH,
)
.unwrap();
let mut make_credential_params = create_minimal_make_credential_parameters();
add_pin_uv_to_make_credential_parameters(
&mut make_credential_params,
pin_uv_auth_protocol,
pin_uv_auth_param.clone(),
);
let make_credential_response =
ctap_state.process_make_credential(&mut env, make_credential_params, DUMMY_CHANNEL);
assert!(make_credential_response.is_ok());
let mut iter_result = Ok(());
let iter = storage::iter_credentials(&mut env, &mut iter_result).unwrap();
// There is only 1 credential, so last is good enough.
let (_, stored_credential) = iter.last().unwrap();
iter_result.unwrap();
let credential_id = stored_credential.credential_id;
assert_eq!(stored_credential.slot_id, Some(slot_id_1));
make_credential_params =
create_make_credential_parameters_with_exclude_list(&credential_id);
pin_uv_auth_param = enable_pin_uv(
&mut ctap_state,
&mut env,
pin_uv_auth_protocol,
slot_id_1,
&DUMMY_CLIENT_DATA_HASH,
)
.unwrap();
add_pin_uv_to_make_credential_parameters(
&mut make_credential_params,
pin_uv_auth_protocol,
pin_uv_auth_param,
);
let make_credential_response =
ctap_state.process_make_credential(&mut env, make_credential_params, DUMMY_CHANNEL);
assert_eq!(
make_credential_response,
Err(Ctap2StatusCode::CTAP2_ERR_CREDENTIAL_EXCLUDED)
);
make_credential_params =
create_make_credential_parameters_with_exclude_list(&credential_id);
let slot_id_2 = 2;
pin_uv_auth_param = enable_pin_uv(
&mut ctap_state,
&mut env,
pin_uv_auth_protocol,
slot_id_2,
&DUMMY_CLIENT_DATA_HASH,
)
.unwrap();
add_pin_uv_to_make_credential_parameters(
&mut make_credential_params,
pin_uv_auth_protocol,
pin_uv_auth_param,
);
let make_credential_response =
ctap_state.process_make_credential(&mut env, make_credential_params, DUMMY_CHANNEL);
assert!(make_credential_response.is_ok());
}
#[test]
fn test_non_resident_process_make_credential_multi_pin() {
let mut env = TestEnv::new();
storage::_enable_multi_pin_for_test(&mut env).unwrap();
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
let slot_id_1 = 1;
let pin_uv_auth_protocol = PinUvAuthProtocol::V1;
let mut pin_uv_auth_param = enable_pin_uv(
&mut ctap_state,
&mut env,
pin_uv_auth_protocol,
slot_id_1,
&DUMMY_CLIENT_DATA_HASH,
)
.unwrap();
let mut make_credential_params = create_minimal_make_credential_parameters();
add_pin_uv_to_make_credential_parameters(
&mut make_credential_params,
pin_uv_auth_protocol,
pin_uv_auth_param.clone(),
);
make_credential_params.options.rk = false;
let make_credential_response =
ctap_state.process_make_credential(&mut env, make_credential_params, DUMMY_CHANNEL);
assert!(make_credential_response.is_ok());
let credential_id = parse_credential_id_from_non_resident_make_credential_response(
&mut env,
make_credential_response.unwrap(),
);
make_credential_params =
create_make_credential_parameters_with_exclude_list(&credential_id);
pin_uv_auth_param = enable_pin_uv(
&mut ctap_state,
&mut env,
pin_uv_auth_protocol,
slot_id_1,
&DUMMY_CLIENT_DATA_HASH,
)
.unwrap();
add_pin_uv_to_make_credential_parameters(
&mut make_credential_params,
pin_uv_auth_protocol,
pin_uv_auth_param,
);
make_credential_params.options.rk = false;
let make_credential_response =
ctap_state.process_make_credential(&mut env, make_credential_params, DUMMY_CHANNEL);
assert_eq!(
make_credential_response,
Err(Ctap2StatusCode::CTAP2_ERR_CREDENTIAL_EXCLUDED)
);
make_credential_params =
create_make_credential_parameters_with_exclude_list(&credential_id);
let slot_id_2 = 2;
pin_uv_auth_param = enable_pin_uv(
&mut ctap_state,
&mut env,
pin_uv_auth_protocol,
slot_id_2,
&DUMMY_CLIENT_DATA_HASH,
)
.unwrap();
add_pin_uv_to_make_credential_parameters(
&mut make_credential_params,
pin_uv_auth_protocol,
pin_uv_auth_param,
);
make_credential_params.options.rk = false;
let make_credential_response =
ctap_state.process_make_credential(&mut env, make_credential_params, DUMMY_CHANNEL);
assert!(make_credential_response.is_ok());
}
#[test]
fn test_process_make_credential_hmac_secret() {
let mut env = TestEnv::new();
@@ -2148,30 +2483,22 @@ mod test {
pin_uv_auth_protocol: PinUvAuthProtocol,
) {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x91; PIN_TOKEN_LENGTH];
let client_pin = ClientPin::new_test(
&mut env,
0,
key_agreement_key,
pin_uv_auth_token,
pin_uv_auth_protocol,
);
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
ctap_state.client_pin = client_pin;
storage::set_pin(&mut env, 0, &[0x88; 16], 4).unwrap();
let client_data_hash = [0xCD];
let pin_uv_auth_param = authenticate_pin_uv_auth_token(
&pin_uv_auth_token,
&client_data_hash,
let pin_uv_auth_param = enable_pin_uv(
&mut ctap_state,
&mut env,
pin_uv_auth_protocol,
);
0,
&DUMMY_CLIENT_DATA_HASH,
)
.unwrap();
let mut make_credential_params = create_minimal_make_credential_parameters();
make_credential_params.options.uv = true;
make_credential_params.pin_uv_auth_param = Some(pin_uv_auth_param);
make_credential_params.pin_uv_auth_protocol = Some(pin_uv_auth_protocol);
add_pin_uv_to_make_credential_parameters(
&mut make_credential_params,
pin_uv_auth_protocol,
pin_uv_auth_param,
);
let make_credential_response = ctap_state.process_make_credential(
&mut env,
make_credential_params.clone(),
@@ -2460,9 +2787,10 @@ mod test {
);
}
fn check_assertion_response(
fn check_assertion_response_with_flags(
response: Result<ResponseData, Ctap2StatusCode>,
expected_user_id: Vec<u8>,
flags: u8,
signature_counter: u32,
expected_number_of_credentials: Option<u64>,
) {
@@ -2475,13 +2803,28 @@ mod test {
check_assertion_response_with_user(
response,
Some(expected_user),
0x00,
flags,
signature_counter,
expected_number_of_credentials,
&[],
);
}
fn check_assertion_response(
response: Result<ResponseData, Ctap2StatusCode>,
expected_user_id: Vec<u8>,
signature_counter: u32,
expected_number_of_credentials: Option<u64>,
) {
check_assertion_response_with_flags(
response,
expected_user_id,
0x00,
signature_counter,
expected_number_of_credentials,
)
}
#[test]
fn test_resident_process_get_assertion() {
let mut env = TestEnv::new();
@@ -2494,7 +2837,7 @@ mod test {
let get_assertion_params = AuthenticatorGetAssertionParameters {
rp_id: String::from("example.com"),
client_data_hash: vec![0xCD],
client_data_hash: DUMMY_CLIENT_DATA_HASH.to_vec(),
allow_list: None,
extensions: GetAssertionExtensions::default(),
options: GetAssertionOptions {
@@ -2555,7 +2898,7 @@ mod test {
let allow_list = credential_descriptor.map(|c| vec![c]);
AuthenticatorGetAssertionParameters {
rp_id: String::from("example.com"),
client_data_hash: vec![0xCD],
client_data_hash: DUMMY_CLIENT_DATA_HASH.to_vec(),
allow_list,
extensions: get_extensions,
options: GetAssertionOptions {
@@ -2709,12 +3052,13 @@ mod test {
user_icon: None,
cred_blob: None,
large_blob_key: None,
slot_id: None,
};
assert!(storage::store_credential(&mut env, credential).is_ok());
let get_assertion_params = AuthenticatorGetAssertionParameters {
rp_id: String::from("example.com"),
client_data_hash: vec![0xCD],
client_data_hash: DUMMY_CLIENT_DATA_HASH.to_vec(),
allow_list: None,
extensions: GetAssertionExtensions::default(),
options: GetAssertionOptions {
@@ -2737,7 +3081,7 @@ mod test {
let get_assertion_params = AuthenticatorGetAssertionParameters {
rp_id: String::from("example.com"),
client_data_hash: vec![0xCD],
client_data_hash: DUMMY_CLIENT_DATA_HASH.to_vec(),
allow_list: Some(vec![cred_desc.clone()]),
extensions: GetAssertionExtensions::default(),
options: GetAssertionOptions {
@@ -2769,12 +3113,13 @@ mod test {
user_icon: None,
cred_blob: None,
large_blob_key: None,
slot_id: None,
};
assert!(storage::store_credential(&mut env, credential).is_ok());
let get_assertion_params = AuthenticatorGetAssertionParameters {
rp_id: String::from("example.com"),
client_data_hash: vec![0xCD],
client_data_hash: DUMMY_CLIENT_DATA_HASH.to_vec(),
allow_list: Some(vec![cred_desc]),
extensions: GetAssertionExtensions::default(),
options: GetAssertionOptions {
@@ -2819,7 +3164,7 @@ mod test {
};
let get_assertion_params = AuthenticatorGetAssertionParameters {
rp_id: String::from("example.com"),
client_data_hash: vec![0xCD],
client_data_hash: DUMMY_CLIENT_DATA_HASH.to_vec(),
allow_list: Some(vec![cred_desc]),
extensions: GetAssertionExtensions::default(),
options: GetAssertionOptions {
@@ -2855,7 +3200,7 @@ mod test {
};
let get_assertion_params = AuthenticatorGetAssertionParameters {
rp_id: String::from("example.com"),
client_data_hash: vec![0xCD],
client_data_hash: DUMMY_CLIENT_DATA_HASH.to_vec(),
allow_list: Some(vec![cred_desc]),
extensions: GetAssertionExtensions::default(),
options: GetAssertionOptions {
@@ -2877,6 +3222,294 @@ mod test {
);
}
#[test]
fn test_resident_process_get_assertion_multi_pin() {
let mut env = TestEnv::new();
storage::_enable_multi_pin_for_test(&mut env).unwrap();
let private_key = PrivateKey::new_ecdsa(&mut env);
let credential_id = env.rng().gen_uniform_u8x32().to_vec();
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
let slot_id_1 = 1;
let slot_id_2 = 2;
let cred_desc = PublicKeyCredentialDescriptor {
key_type: PublicKeyCredentialType::PublicKey,
key_id: credential_id.clone(),
transports: None,
};
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![0x1D],
user_display_name: None,
cred_protect_policy: None,
creation_order: 0,
user_name: None,
user_icon: None,
cred_blob: None,
large_blob_key: None,
slot_id: Some(slot_id_1),
};
assert!(storage::store_credential(&mut env, credential).is_ok());
let pin_uv_auth_protocol = PinUvAuthProtocol::V1;
let get_assertion_params = AuthenticatorGetAssertionParameters {
rp_id: String::from("example.com"),
client_data_hash: DUMMY_CLIENT_DATA_HASH.to_vec(),
allow_list: Some(vec![cred_desc.clone()]),
extensions: GetAssertionExtensions::default(),
options: GetAssertionOptions {
up: false,
uv: false,
},
pin_uv_auth_param: Some(
enable_pin_uv(
&mut ctap_state,
&mut env,
pin_uv_auth_protocol,
slot_id_2,
&DUMMY_CLIENT_DATA_HASH,
)
.unwrap(),
),
pin_uv_auth_protocol: Some(pin_uv_auth_protocol),
};
let get_assertion_response = ctap_state.process_get_assertion(
&mut env,
get_assertion_params,
DUMMY_CHANNEL,
CtapInstant::new(0),
);
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: DUMMY_CLIENT_DATA_HASH.to_vec(),
allow_list: Some(vec![cred_desc.clone()]),
extensions: GetAssertionExtensions::default(),
options: GetAssertionOptions {
up: false,
uv: false,
},
pin_uv_auth_param: Some(
enable_pin_uv(
&mut ctap_state,
&mut env,
pin_uv_auth_protocol,
slot_id_1,
&DUMMY_CLIENT_DATA_HASH,
)
.unwrap(),
),
pin_uv_auth_protocol: Some(pin_uv_auth_protocol),
};
let get_assertion_response = ctap_state.process_get_assertion(
&mut env,
get_assertion_params,
DUMMY_CHANNEL,
CtapInstant::new(0),
);
let signature_counter = storage::global_signature_counter(&mut env, slot_id_1).unwrap();
check_assertion_response_with_flags(
get_assertion_response,
vec![0x1D],
UV_FLAG,
signature_counter,
None,
);
// No slot_id should be treated as a credential of slot 0, so the credential shouldn't be found
// if we're using slot 1's UV.
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: None,
large_blob_key: None,
slot_id: None,
};
assert!(storage::store_credential(&mut env, credential).is_ok());
let get_assertion_params = AuthenticatorGetAssertionParameters {
rp_id: String::from("example.com"),
client_data_hash: DUMMY_CLIENT_DATA_HASH.to_vec(),
allow_list: Some(vec![cred_desc.clone()]),
extensions: GetAssertionExtensions::default(),
options: GetAssertionOptions {
up: false,
uv: false,
},
pin_uv_auth_param: Some(
enable_pin_uv(
&mut ctap_state,
&mut env,
pin_uv_auth_protocol,
slot_id_1,
&DUMMY_CLIENT_DATA_HASH,
)
.unwrap(),
),
pin_uv_auth_protocol: Some(pin_uv_auth_protocol),
};
let get_assertion_response = ctap_state.process_get_assertion(
&mut env,
get_assertion_params,
DUMMY_CHANNEL,
CtapInstant::new(0),
);
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: DUMMY_CLIENT_DATA_HASH.to_vec(),
allow_list: Some(vec![cred_desc]),
extensions: GetAssertionExtensions::default(),
options: GetAssertionOptions {
up: false,
uv: false,
},
pin_uv_auth_param: Some(
enable_pin_uv(
&mut ctap_state,
&mut env,
pin_uv_auth_protocol,
0,
&DUMMY_CLIENT_DATA_HASH,
)
.unwrap(),
),
pin_uv_auth_protocol: Some(pin_uv_auth_protocol),
};
let get_assertion_response = ctap_state.process_get_assertion(
&mut env,
get_assertion_params,
DUMMY_CHANNEL,
CtapInstant::new(0),
);
let signature_counter = storage::global_signature_counter(&mut env, 0).unwrap();
check_assertion_response_with_flags(
get_assertion_response,
vec![0x1D],
UV_FLAG,
signature_counter,
None,
);
}
#[test]
fn test_non_resident_process_get_assertion_multi_pin() {
let mut env = TestEnv::new();
storage::_enable_multi_pin_for_test(&mut env).unwrap();
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
let pin_uv_auth_protocol = PinUvAuthProtocol::V1;
let slot_id_1 = 1;
let slot_id_2 = 2;
let mut make_credential_params = create_minimal_make_credential_parameters();
add_pin_uv_to_make_credential_parameters(
&mut make_credential_params,
pin_uv_auth_protocol,
enable_pin_uv(
&mut ctap_state,
&mut env,
pin_uv_auth_protocol,
slot_id_1,
&DUMMY_CLIENT_DATA_HASH,
)
.unwrap(),
);
make_credential_params.options.rk = false;
let make_credential_response =
ctap_state.process_make_credential(&mut env, make_credential_params, DUMMY_CHANNEL);
assert!(make_credential_response.is_ok());
let credential_id = parse_credential_id_from_non_resident_make_credential_response(
&mut env,
make_credential_response.unwrap(),
);
let cred_desc = PublicKeyCredentialDescriptor {
key_type: PublicKeyCredentialType::PublicKey,
key_id: credential_id,
transports: None,
};
let get_assertion_params = AuthenticatorGetAssertionParameters {
rp_id: String::from("example.com"),
client_data_hash: DUMMY_CLIENT_DATA_HASH.to_vec(),
allow_list: Some(vec![cred_desc.clone()]),
extensions: GetAssertionExtensions::default(),
options: GetAssertionOptions {
up: false,
uv: false,
},
pin_uv_auth_param: Some(
enable_pin_uv(
&mut ctap_state,
&mut env,
pin_uv_auth_protocol,
slot_id_1,
&DUMMY_CLIENT_DATA_HASH,
)
.unwrap(),
),
pin_uv_auth_protocol: Some(pin_uv_auth_protocol),
};
let get_assertion_response = ctap_state.process_get_assertion(
&mut env,
get_assertion_params,
DUMMY_CHANNEL,
CtapInstant::new(0),
);
assert!(get_assertion_response.is_ok());
let get_assertion_params = AuthenticatorGetAssertionParameters {
rp_id: String::from("example.com"),
client_data_hash: DUMMY_CLIENT_DATA_HASH.to_vec(),
allow_list: Some(vec![cred_desc]),
extensions: GetAssertionExtensions::default(),
options: GetAssertionOptions {
up: false,
uv: false,
},
pin_uv_auth_param: Some(
enable_pin_uv(
&mut ctap_state,
&mut env,
pin_uv_auth_protocol,
slot_id_2,
&DUMMY_CLIENT_DATA_HASH,
)
.unwrap(),
),
pin_uv_auth_protocol: Some(pin_uv_auth_protocol),
};
let get_assertion_response = ctap_state.process_get_assertion(
&mut env,
get_assertion_params,
DUMMY_CHANNEL,
CtapInstant::new(0),
);
assert_eq!(
get_assertion_response,
Err(Ctap2StatusCode::CTAP2_ERR_NO_CREDENTIALS),
);
}
#[test]
fn test_process_get_assertion_with_cred_blob() {
let mut env = TestEnv::new();
@@ -2897,6 +3530,7 @@ mod test {
user_icon: None,
cred_blob: Some(vec![0xCB]),
large_blob_key: None,
slot_id: None,
};
assert!(storage::store_credential(&mut env, credential).is_ok());
@@ -2906,7 +3540,7 @@ mod test {
};
let get_assertion_params = AuthenticatorGetAssertionParameters {
rp_id: String::from("example.com"),
client_data_hash: vec![0xCD],
client_data_hash: DUMMY_CLIENT_DATA_HASH.to_vec(),
allow_list: None,
extensions,
options: GetAssertionOptions {
@@ -2975,7 +3609,7 @@ mod test {
};
let get_assertion_params = AuthenticatorGetAssertionParameters {
rp_id: String::from("example.com"),
client_data_hash: vec![0xCD],
client_data_hash: DUMMY_CLIENT_DATA_HASH.to_vec(),
allow_list: Some(vec![cred_desc]),
extensions,
options: GetAssertionOptions {
@@ -3024,6 +3658,7 @@ mod test {
user_icon: None,
cred_blob: None,
large_blob_key: Some(vec![0x1C; 32]),
slot_id: None,
};
assert!(storage::store_credential(&mut env, credential).is_ok());
@@ -3033,7 +3668,7 @@ mod test {
};
let get_assertion_params = AuthenticatorGetAssertionParameters {
rp_id: String::from("example.com"),
client_data_hash: vec![0xCD],
client_data_hash: DUMMY_CLIENT_DATA_HASH.to_vec(),
allow_list: None,
extensions,
options: GetAssertionOptions {
@@ -3100,7 +3735,7 @@ mod test {
ctap_state.client_pin = client_pin;
// The PIN length is outside of the test scope and most likely incorrect.
storage::set_pin(&mut env, 0, &[0u8; 16], 4).unwrap();
let client_data_hash = vec![0xCD];
let client_data_hash = DUMMY_CLIENT_DATA_HASH.to_vec();
let pin_uv_auth_param = authenticate_pin_uv_auth_token(
&pin_uv_auth_token,
&client_data_hash,
@@ -3194,7 +3829,7 @@ mod test {
let get_assertion_params = AuthenticatorGetAssertionParameters {
rp_id: String::from("example.com"),
client_data_hash: vec![0xCD],
client_data_hash: DUMMY_CLIENT_DATA_HASH.to_vec(),
allow_list: None,
extensions: GetAssertionExtensions::default(),
options: GetAssertionOptions {
@@ -3255,7 +3890,7 @@ mod test {
let get_assertion_params = AuthenticatorGetAssertionParameters {
rp_id: String::from("example.com"),
client_data_hash: vec![0xCD],
client_data_hash: DUMMY_CLIENT_DATA_HASH.to_vec(),
allow_list: None,
extensions: GetAssertionExtensions::default(),
options: GetAssertionOptions {
@@ -3315,6 +3950,7 @@ mod test {
user_icon: None,
cred_blob: None,
large_blob_key: None,
slot_id: None,
};
assert!(storage::store_credential(&mut env, credential_source).is_ok());
assert!(storage::count_credentials(&mut env).unwrap() > 0);
@@ -3743,7 +4379,7 @@ mod test {
let get_assertion_params = AuthenticatorGetAssertionParameters {
rp_id: String::from("example.com"),
client_data_hash: vec![0xCD],
client_data_hash: DUMMY_CLIENT_DATA_HASH.to_vec(),
allow_list: None,
extensions: GetAssertionExtensions::default(),
options: GetAssertionOptions {
@@ -3820,6 +4456,7 @@ mod test {
user_icon: Some("icon".to_string()),
cred_blob: None,
large_blob_key: None,
slot_id: None,
};
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
@@ -3927,7 +4564,7 @@ mod test {
let get_assertion_params = AuthenticatorGetAssertionParameters {
rp_id: String::from("example.com"),
client_data_hash: vec![0xCD],
client_data_hash: DUMMY_CLIENT_DATA_HASH.to_vec(),
allow_list: None,
extensions: GetAssertionExtensions::default(),
options: GetAssertionOptions {

View File

@@ -623,9 +623,9 @@ pub fn has_multi_pin(env: &mut impl Env) -> Result<bool, Ctap2StatusCode> {
}
// TODO: Call this in config_commands after the whole multi-PIN feature is ready.
// Before that, this function should stay private, only for testing purpose.
// Before that, this function only be used for testing purpose.
/// Enables multi-PIN, when disabled.
fn _enable_multi_pin(env: &mut impl Env) -> Result<(), Ctap2StatusCode> {
pub fn _enable_multi_pin_for_test(env: &mut impl Env) -> Result<(), Ctap2StatusCode> {
if !has_multi_pin(env)? {
env.store().insert(key::MULTI_PIN, &[])?;
}
@@ -777,6 +777,7 @@ mod test {
user_icon: None,
cred_blob: None,
large_blob_key: None,
slot_id: None,
}
}
@@ -973,6 +974,7 @@ mod test {
user_icon: None,
cred_blob: None,
large_blob_key: None,
slot_id: None,
};
assert_eq!(found_credential, Some(expected_credential));
}
@@ -1515,7 +1517,7 @@ mod test {
let mut env = TestEnv::new();
assert!(!has_multi_pin(&mut env).unwrap());
assert_eq!(_enable_multi_pin(&mut env), Ok(()));
assert_eq!(_enable_multi_pin_for_test(&mut env), Ok(()));
assert!(has_multi_pin(&mut env).unwrap());
}
@@ -1536,6 +1538,7 @@ mod test {
user_icon: Some(String::from("icon")),
cred_blob: Some(vec![0xCB]),
large_blob_key: Some(vec![0x1B]),
slot_id: Some(1),
};
let serialized = serialize_credential(credential.clone()).unwrap();
let reconstructed = deserialize_credential(&serialized).unwrap();