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