Support credBlob for non-resident credentials (#518)

* Support credBlob for non-resident credentials

- Add a upper limit of max_cred_blob_length
- Add test cases for cred_blob in non-resident flows
- Modify the test helper functions in ctap/mod.rs a bit

* Fix some styles in credential_id.rs

Co-authored-by: kaczmarczyck <43844792+kaczmarczyck@users.noreply.github.com>
This commit is contained in:
hcyang
2022-08-16 22:23:49 +08:00
committed by GitHub
parent 5daf5f81d1
commit 87839af572
4 changed files with 212 additions and 101 deletions

View File

@@ -184,6 +184,8 @@ pub trait Customization {
/// # Invariant
///
/// - The length must be at least 32.
/// - OpenSK puts a limit that the length must be at most 64, as it needs to
/// be persisted in the credential ID.
fn max_cred_blob_length(&self) -> usize;
/// Limits the number of considered entries in credential lists.
@@ -397,8 +399,8 @@ pub fn is_valid(customization: &impl Customization) -> bool {
return false;
}
// Max cred blob length should be at least 32.
if customization.max_cred_blob_length() < 32 {
// Max cred blob length should be at least 32, and at most 64.
if customization.max_cred_blob_length() < 32 || customization.max_cred_blob_length() > 64 {
return false;
}

View File

@@ -47,14 +47,16 @@ struct CredentialSource {
private_key: PrivateKey,
rp_id_hash: [u8; 32],
cred_protect_policy: Option<CredentialProtectionPolicy>,
cred_blob: Option<Vec<u8>>,
}
// The data fields contained in the credential ID are serizlied using CBOR maps.
// The data fields contained in the credential ID are serialized using CBOR maps.
// Each field is associated with a unique tag, implemented with a CBOR unsigned key.
enum CredentialSourceField {
PrivateKey = 0,
RpIdHash = 1,
CredProtectPolicy = 2,
CredBlob = 3,
}
impl From<CredentialSourceField> for sk_cbor::Value {
@@ -81,6 +83,7 @@ fn decrypt_legacy_credential_id(
private_key,
rp_id_hash: plaintext[32..64].try_into().unwrap(),
cred_protect_policy: None,
cred_blob: None,
}))
}
@@ -98,24 +101,25 @@ fn decrypt_cbor_credential_id(
CredentialSourceField::PrivateKey => private_key,
CredentialSourceField::RpIdHash=> rp_id_hash,
CredentialSourceField::CredProtectPolicy => cred_protect_policy,
CredentialSourceField::CredBlob => cred_blob,
} = extract_map(cbor_credential_source)?;
}
Ok(match (private_key, rp_id_hash, cred_protect_policy) {
(Some(private_key), Some(rp_id_hash), cred_protect_policy) => {
Ok(match (private_key, rp_id_hash) {
(Some(private_key), Some(rp_id_hash)) => {
let private_key = PrivateKey::try_from(private_key)?;
let rp_id_hash = extract_byte_string(rp_id_hash)?;
if rp_id_hash.len() != 32 {
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
}
let cred_protect_policy = if let Some(policy) = cred_protect_policy {
Some(CredentialProtectionPolicy::try_from(policy)?)
} else {
None
};
let cred_protect_policy = cred_protect_policy
.map(CredentialProtectionPolicy::try_from)
.transpose()?;
let cred_blob = cred_blob.map(extract_byte_string).transpose()?;
Some(CredentialSource {
private_key,
rp_id_hash: rp_id_hash.try_into().unwrap(),
cred_protect_policy,
cred_blob,
})
}
_ => None,
@@ -153,7 +157,7 @@ fn remove_padding(data: &mut Vec<u8>) -> Result<(), Ctap2StatusCode> {
Ok(())
}
/// Encrypts the given private key, relying party ID hash, and cred protect policy into a credential ID.
/// Encrypts the given private key, relying party ID hash, and some other metadata into a credential ID.
///
/// Other information, such as a user name, are not stored. Since encrypted credential IDs are
/// stored server-side, this information is already available (unencrypted).
@@ -162,12 +166,14 @@ pub fn encrypt_to_credential_id(
private_key: &PrivateKey,
rp_id_hash: &[u8; 32],
cred_protect_policy: Option<CredentialProtectionPolicy>,
cred_blob: Option<Vec<u8>>,
) -> Result<Vec<u8>, Ctap2StatusCode> {
let mut payload = Vec::new();
let cbor = cbor_map_options! {
CredentialSourceField::PrivateKey => private_key,
CredentialSourceField::RpIdHash=> rp_id_hash,
CredentialSourceField::CredProtectPolicy => cred_protect_policy,
CredentialSourceField::CredBlob => cred_blob,
};
cbor_write(cbor, &mut payload)?;
add_padding(&mut payload)?;
@@ -254,7 +260,7 @@ pub fn decrypt_credential_id(
creation_order: 0,
user_name: None,
user_icon: None,
cred_blob: None,
cred_blob: credential_source.cred_blob,
large_blob_key: None,
}))
}
@@ -262,6 +268,7 @@ pub fn decrypt_credential_id(
#[cfg(test)]
mod test {
use super::*;
use crate::api::customization::Customization;
use crate::ctap::credential_id::CBOR_CREDENTIAL_ID_SIZE;
use crate::ctap::SignatureAlgorithm;
use crate::env::test::TestEnv;
@@ -275,7 +282,7 @@ mod test {
let rp_id_hash = [0x55; 32];
let encrypted_id =
encrypt_to_credential_id(&mut env, &private_key, &rp_id_hash, None).unwrap();
encrypt_to_credential_id(&mut env, &private_key, &rp_id_hash, None, None).unwrap();
let decrypted_source = decrypt_credential_id(&mut env, encrypted_id, &rp_id_hash)
.unwrap()
.unwrap();
@@ -301,7 +308,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).unwrap();
encrypt_to_credential_id(&mut env, &private_key, &rp_id_hash, None, None).unwrap();
encrypted_id[0] = UNSUPPORTED_CREDENTIAL_ID_VERSION;
// Override the HMAC to pass the check.
encrypted_id.truncate(&encrypted_id.len() - 32);
@@ -321,7 +328,7 @@ mod test {
let rp_id_hash = [0x55; 32];
let encrypted_id =
encrypt_to_credential_id(&mut env, &private_key, &rp_id_hash, None).unwrap();
encrypt_to_credential_id(&mut env, &private_key, &rp_id_hash, None, None).unwrap();
for i in 0..encrypted_id.len() {
let mut modified_id = encrypted_id.clone();
modified_id[i] ^= 0x01;
@@ -349,7 +356,7 @@ mod test {
let rp_id_hash = [0x55; 32];
let encrypted_id =
encrypt_to_credential_id(&mut env, &private_key, &rp_id_hash, None).unwrap();
encrypt_to_credential_id(&mut env, &private_key, &rp_id_hash, None, None).unwrap();
for length in (1..CBOR_CREDENTIAL_ID_SIZE).step_by(16) {
assert_eq!(
@@ -416,10 +423,32 @@ mod test {
let rp_id_hash = [0x55; 32];
let encrypted_id =
encrypt_to_credential_id(&mut env, &private_key, &rp_id_hash, None).unwrap();
encrypt_to_credential_id(&mut env, &private_key, &rp_id_hash, None, None).unwrap();
assert_eq!(encrypted_id.len(), CBOR_CREDENTIAL_ID_SIZE);
}
#[test]
fn test_encrypt_credential_max_cbor_size() {
// The cbor encoding length is variadic and depends on size of fields. Try to put maximum length
// for each encoded field and ensure that it doesn't go over the padding size.
let mut env = TestEnv::new();
// Currently all private key types have same length when transformed to bytes.
let private_key = PrivateKey::new(&mut env, SignatureAlgorithm::Es256);
let rp_id_hash = [0x55; 32];
let cred_protect_policy = Some(CredentialProtectionPolicy::UserVerificationOptional);
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,
cred_protect_policy,
cred_blob,
);
assert!(encrypted_id.is_ok());
}
#[test]
fn test_cred_protect_persisted() {
let mut env = TestEnv::new();
@@ -431,6 +460,7 @@ mod test {
&private_key,
&rp_id_hash,
Some(CredentialProtectionPolicy::UserVerificationRequired),
None,
)
.unwrap();
@@ -443,4 +473,22 @@ mod test {
Some(CredentialProtectionPolicy::UserVerificationRequired)
);
}
#[test]
fn test_cred_blob_persisted() {
let mut env = TestEnv::new();
let private_key = PrivateKey::new(&mut env, SignatureAlgorithm::Es256);
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 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.cred_blob, cred_blob);
}
}

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)
let key_handle = encrypt_to_credential_id(env, &private_key, &application, None, None)
.map_err(|_| Ctap1StatusCode::SW_INTERNAL_EXCEPTION)?;
if key_handle.len() > 0xFF {
// This is just being defensive with unreachable code.
@@ -498,7 +498,7 @@ 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).unwrap();
let key_handle = encrypt_to_credential_id(&mut env, &sk, &application, None, None).unwrap();
let message = create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
let response =
@@ -516,7 +516,7 @@ 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).unwrap();
let key_handle = encrypt_to_credential_id(&mut env, &sk, &application, None, None).unwrap();
let application = [0x55; 32];
let message = create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
@@ -535,7 +535,7 @@ 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).unwrap();
let key_handle = encrypt_to_credential_id(&mut env, &sk, &application, None, None).unwrap();
let mut message = create_authenticate_message(
&application,
Ctap1Flags::DontEnforceUpAndSign,
@@ -573,7 +573,7 @@ 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).unwrap();
let key_handle = encrypt_to_credential_id(&mut env, &sk, &application, None, None).unwrap();
let mut message =
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
message[0] = 0xEE;
@@ -593,7 +593,7 @@ 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).unwrap();
let key_handle = encrypt_to_credential_id(&mut env, &sk, &application, None, None).unwrap();
let mut message =
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
message[1] = 0xEE;
@@ -613,7 +613,7 @@ 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).unwrap();
let key_handle = encrypt_to_credential_id(&mut env, &sk, &application, None, None).unwrap();
let mut message =
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
message[2] = 0xEE;
@@ -641,7 +641,7 @@ 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).unwrap();
let key_handle = encrypt_to_credential_id(&mut env, &sk, &application, None, None).unwrap();
let message =
create_authenticate_message(&application, Ctap1Flags::EnforceUpAndSign, &key_handle);
@@ -669,7 +669,7 @@ 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).unwrap();
let key_handle = encrypt_to_credential_id(&mut env, &sk, &application, None, None).unwrap();
let message = create_authenticate_message(
&application,
Ctap1Flags::DontEnforceUpAndSign,

View File

@@ -880,7 +880,7 @@ impl CtapState {
let has_cred_blob_output = extensions.cred_blob.is_some();
let cred_blob = extensions
.cred_blob
.filter(|c| options.rk && c.len() <= env.customization().max_cred_blob_length());
.filter(|c| c.len() <= env.customization().max_cred_blob_length());
let cred_blob_output = if has_cred_blob_output {
Some(cred_blob.is_some())
} else {
@@ -928,7 +928,13 @@ impl CtapState {
storage::store_credential(env, credential_source)?;
random_id
} else {
encrypt_to_credential_id(env, &private_key, &rp_id_hash, cred_protect_policy)?
encrypt_to_credential_id(
env,
&private_key,
&rp_id_hash,
cred_protect_policy,
cred_blob,
)?
};
let mut auth_data = self.generate_auth_data(env, &rp_id_hash, flags)?;
@@ -1555,13 +1561,13 @@ mod test {
const VENDOR_CHANNEL: Channel = Channel::VendorHid([0x12, 0x34, 0x56, 0x78]);
fn check_make_response(
make_credential_response: Result<ResponseData, Ctap2StatusCode>,
make_credential_response: &Result<ResponseData, Ctap2StatusCode>,
flags: u8,
expected_aaguid: &[u8],
expected_credential_id_size: u8,
expected_extension_cbor: &[u8],
) {
match make_credential_response.unwrap() {
match make_credential_response.as_ref().unwrap() {
ResponseData::AuthenticatorMakeCredential(make_credential_response) => {
let AuthenticatorMakeCredentialResponse {
fmt,
@@ -1590,7 +1596,7 @@ mod test {
);
assert!(ep_att.is_none());
assert_eq!(att_stmt.alg, SignatureAlgorithm::Es256 as i64);
assert_eq!(large_blob_key, None);
assert_eq!(large_blob_key, &None);
}
_ => panic!("Invalid response type"),
}
@@ -1708,6 +1714,22 @@ mod test {
make_credential_params
}
fn parse_credential_id_from_non_resident_make_credential_response(
env: &mut impl Env,
make_credential_response: ResponseData,
) -> Vec<u8> {
match make_credential_response {
ResponseData::AuthenticatorMakeCredential(make_credential_response) => {
let auth_data = make_credential_response.auth_data;
let offset = 37 + storage::aaguid(env).unwrap().len();
assert_eq!(auth_data[offset], 0x00);
assert_eq!(auth_data[offset + 1] as usize, CBOR_CREDENTIAL_ID_SIZE);
auth_data[offset + 2..offset + 2 + CBOR_CREDENTIAL_ID_SIZE].to_vec()
}
_ => panic!("Invalid response type"),
}
}
#[test]
fn test_resident_process_make_credential() {
let mut env = TestEnv::new();
@@ -1718,7 +1740,7 @@ mod test {
ctap_state.process_make_credential(&mut env, make_credential_params, DUMMY_CHANNEL);
check_make_response(
make_credential_response,
&make_credential_response,
0x41,
&storage::aaguid(&mut env).unwrap(),
0x20,
@@ -1737,7 +1759,7 @@ mod test {
ctap_state.process_make_credential(&mut env, make_credential_params, DUMMY_CHANNEL);
check_make_response(
make_credential_response,
&make_credential_response,
0x41,
&storage::aaguid(&mut env).unwrap(),
CBOR_CREDENTIAL_ID_SIZE as u8,
@@ -1857,16 +1879,10 @@ mod test {
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 = match make_credential_response.unwrap() {
ResponseData::AuthenticatorMakeCredential(make_credential_response) => {
let auth_data = make_credential_response.auth_data;
let offset = 37 + storage::aaguid(&mut env).unwrap().len();
assert_eq!(auth_data[offset], 0x00);
assert_eq!(auth_data[offset + 1] as usize, CBOR_CREDENTIAL_ID_SIZE);
auth_data[offset + 2..offset + 2 + CBOR_CREDENTIAL_ID_SIZE].to_vec()
}
_ => panic!("Invalid response type"),
};
let credential_id = parse_credential_id_from_non_resident_make_credential_response(
&mut env,
make_credential_response.unwrap(),
);
let make_credential_params =
create_make_credential_parameters_with_exclude_list(&credential_id);
let make_credential_response =
@@ -1883,16 +1899,10 @@ mod test {
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 = match make_credential_response.unwrap() {
ResponseData::AuthenticatorMakeCredential(make_credential_response) => {
let auth_data = make_credential_response.auth_data;
let offset = 37 + storage::aaguid(&mut env).unwrap().len();
assert_eq!(auth_data[offset], 0x00);
assert_eq!(auth_data[offset + 1] as usize, CBOR_CREDENTIAL_ID_SIZE);
auth_data[offset + 2..offset + 2 + CBOR_CREDENTIAL_ID_SIZE].to_vec()
}
_ => panic!("Invalid response type"),
};
let credential_id = parse_credential_id_from_non_resident_make_credential_response(
&mut env,
make_credential_response.unwrap(),
);
let make_credential_params =
create_make_credential_parameters_with_exclude_list(&credential_id);
let make_credential_response =
@@ -1919,7 +1929,7 @@ mod test {
0xA1, 0x6B, 0x68, 0x6D, 0x61, 0x63, 0x2D, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0xF5,
];
check_make_response(
make_credential_response,
&make_credential_response,
0xC1,
&storage::aaguid(&mut env).unwrap(),
CBOR_CREDENTIAL_ID_SIZE as u8,
@@ -1945,7 +1955,7 @@ mod test {
0xA1, 0x6B, 0x68, 0x6D, 0x61, 0x63, 0x2D, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0xF5,
];
check_make_response(
make_credential_response,
&make_credential_response,
0xC1,
&storage::aaguid(&mut env).unwrap(),
0x20,
@@ -1968,7 +1978,7 @@ mod test {
let make_credential_response =
ctap_state.process_make_credential(&mut env, make_credential_params, DUMMY_CHANNEL);
check_make_response(
make_credential_response,
&make_credential_response,
0x41,
&storage::aaguid(&mut env).unwrap(),
0x20,
@@ -1994,7 +2004,7 @@ mod test {
0x04,
];
check_make_response(
make_credential_response,
&make_credential_response,
0xC1,
&storage::aaguid(&mut env).unwrap(),
0x20,
@@ -2019,7 +2029,7 @@ mod test {
0xA1, 0x68, 0x63, 0x72, 0x65, 0x64, 0x42, 0x6C, 0x6F, 0x62, 0xF5,
];
check_make_response(
make_credential_response,
&make_credential_response,
0xC1,
&storage::aaguid(&mut env).unwrap(),
0x20,
@@ -2051,7 +2061,7 @@ mod test {
0xA1, 0x68, 0x63, 0x72, 0x65, 0x64, 0x42, 0x6C, 0x6F, 0x62, 0xF4,
];
check_make_response(
make_credential_response,
&make_credential_response,
0xC1,
&storage::aaguid(&mut env).unwrap(),
0x20,
@@ -2125,7 +2135,7 @@ mod test {
);
check_make_response(
make_credential_response,
&make_credential_response,
0x45,
&storage::aaguid(&mut env).unwrap(),
0x20,
@@ -2162,7 +2172,7 @@ mod test {
ctap_state.process_make_credential(&mut env, make_credential_params, DUMMY_CHANNEL);
check_make_response(
make_credential_response,
&make_credential_response,
0x41,
&storage::aaguid(&mut env).unwrap(),
CBOR_CREDENTIAL_ID_SIZE as u8,
@@ -2350,7 +2360,7 @@ mod test {
fn check_assertion_response_with_user(
response: Result<ResponseData, Ctap2StatusCode>,
expected_user: PublicKeyCredentialUserEntity,
expected_user: Option<PublicKeyCredentialUserEntity>,
flags: u8,
signature_counter: u32,
expected_number_of_credentials: Option<u64>,
@@ -2379,23 +2389,23 @@ mod test {
);
expected_auth_data.extend(expected_extension_cbor);
assert_eq!(auth_data, expected_auth_data);
assert_eq!(user, Some(expected_user));
assert_eq!(user, expected_user);
assert_eq!(number_of_credentials, expected_number_of_credentials);
}
fn check_assertion_response_with_extension(
response: Result<ResponseData, Ctap2StatusCode>,
expected_user_id: Vec<u8>,
expected_user_id: Option<Vec<u8>>,
signature_counter: u32,
expected_number_of_credentials: Option<u64>,
expected_extension_cbor: &[u8],
) {
let expected_user = PublicKeyCredentialUserEntity {
user_id: expected_user_id,
let expected_user = expected_user_id.map(|user_id| PublicKeyCredentialUserEntity {
user_id,
user_name: None,
user_display_name: None,
user_icon: None,
};
});
check_assertion_response_with_user(
response,
expected_user,
@@ -2420,7 +2430,7 @@ mod test {
};
check_assertion_response_with_user(
response,
expected_user,
Some(expected_user),
0x00,
signature_counter,
expected_number_of_credentials,
@@ -2528,16 +2538,10 @@ mod test {
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 = match make_credential_response.unwrap() {
ResponseData::AuthenticatorMakeCredential(make_credential_response) => {
let auth_data = make_credential_response.auth_data;
let offset = 37 + storage::aaguid(&mut env).unwrap().len();
assert_eq!(auth_data[offset], 0x00);
assert_eq!(auth_data[offset + 1] as usize, CBOR_CREDENTIAL_ID_SIZE);
auth_data[offset + 2..offset + 2 + CBOR_CREDENTIAL_ID_SIZE].to_vec()
}
_ => panic!("Invalid response type"),
};
let credential_id = parse_credential_id_from_non_resident_make_credential_response(
&mut env,
make_credential_response.unwrap(),
);
let client_pin_params = AuthenticatorClientPinParameters {
pin_uv_auth_protocol,
@@ -2760,16 +2764,10 @@ mod test {
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 = match make_credential_response.unwrap() {
ResponseData::AuthenticatorMakeCredential(make_credential_response) => {
let auth_data = make_credential_response.auth_data;
let offset = 37 + storage::aaguid(&mut env).unwrap().len();
assert_eq!(auth_data[offset], 0x00);
assert_eq!(auth_data[offset + 1] as usize, CBOR_CREDENTIAL_ID_SIZE);
auth_data[offset + 2..offset + 2 + CBOR_CREDENTIAL_ID_SIZE].to_vec()
}
_ => panic!("Invalid response type"),
};
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,
@@ -2802,16 +2800,10 @@ mod test {
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 = match make_credential_response.unwrap() {
ResponseData::AuthenticatorMakeCredential(make_credential_response) => {
let auth_data = make_credential_response.auth_data;
let offset = 37 + storage::aaguid(&mut env).unwrap().len();
assert_eq!(auth_data[offset], 0x00);
assert_eq!(auth_data[offset + 1] as usize, CBOR_CREDENTIAL_ID_SIZE);
auth_data[offset + 2..offset + 2 + CBOR_CREDENTIAL_ID_SIZE].to_vec()
}
_ => panic!("Invalid response type"),
};
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,
@@ -2892,7 +2884,76 @@ mod test {
];
check_assertion_response_with_extension(
get_assertion_response,
vec![0x1D],
Some(vec![0x1D]),
signature_counter,
None,
&expected_extension_cbor,
);
}
#[test]
fn test_non_resident_process_get_assertion_with_cred_blob() {
let mut env = TestEnv::new();
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
let extensions = MakeCredentialExtensions {
cred_blob: Some(vec![0xCB]),
..Default::default()
};
let mut make_credential_params = create_minimal_make_credential_parameters();
make_credential_params.extensions = extensions;
make_credential_params.options.rk = false;
let make_credential_response =
ctap_state.process_make_credential(&mut env, make_credential_params, DUMMY_CHANNEL);
let expected_extension_cbor = [
0xA1, 0x68, 0x63, 0x72, 0x65, 0x64, 0x42, 0x6C, 0x6F, 0x62, 0xF5,
];
check_make_response(
&make_credential_response,
0xC1,
&storage::aaguid(&mut env).unwrap(),
CBOR_CREDENTIAL_ID_SIZE as u8,
&expected_extension_cbor,
);
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 extensions = GetAssertionExtensions {
cred_blob: true,
..Default::default()
};
let get_assertion_params = AuthenticatorGetAssertionParameters {
rp_id: String::from("example.com"),
client_data_hash: vec![0xCD],
allow_list: Some(vec![cred_desc]),
extensions,
options: GetAssertionOptions {
up: false,
uv: false,
},
pin_uv_auth_param: None,
pin_uv_auth_protocol: None,
};
let get_assertion_response = ctap_state.process_get_assertion(
&mut env,
get_assertion_params,
DUMMY_CHANNEL,
CtapInstant::new(0),
);
let signature_counter = storage::global_signature_counter(&mut env).unwrap();
let expected_extension_cbor = [
0xA1, 0x68, 0x63, 0x72, 0x65, 0x64, 0x42, 0x6C, 0x6F, 0x62, 0x41, 0xCB,
];
check_assertion_response_with_extension(
get_assertion_response,
None,
signature_counter,
None,
&expected_extension_cbor,
@@ -3018,7 +3079,7 @@ mod test {
let signature_counter = storage::global_signature_counter(&mut env).unwrap();
check_assertion_response_with_user(
get_assertion_response,
user2,
Some(user2),
0x04,
signature_counter,
Some(2),
@@ -3028,7 +3089,7 @@ mod test {
let get_assertion_response = ctap_state.process_get_next_assertion(&mut env);
check_assertion_response_with_user(
get_assertion_response,
user1,
Some(user1),
0x04,
signature_counter,
None,