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:
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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! {
|
||||
|
||||
729
src/ctap/mod.rs
729
src/ctap/mod.rs
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user