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 /// # Invariant
/// ///
/// - The length must be at least 32. /// - 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; fn max_cred_blob_length(&self) -> usize;
/// Limits the number of considered entries in credential lists. /// Limits the number of considered entries in credential lists.
@@ -397,8 +399,8 @@ pub fn is_valid(customization: &impl Customization) -> bool {
return false; return false;
} }
// Max cred blob length should be at least 32. // Max cred blob length should be at least 32, and at most 64.
if customization.max_cred_blob_length() < 32 { if customization.max_cred_blob_length() < 32 || customization.max_cred_blob_length() > 64 {
return false; return false;
} }

View File

@@ -47,14 +47,16 @@ struct CredentialSource {
private_key: PrivateKey, private_key: PrivateKey,
rp_id_hash: [u8; 32], rp_id_hash: [u8; 32],
cred_protect_policy: Option<CredentialProtectionPolicy>, 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. // Each field is associated with a unique tag, implemented with a CBOR unsigned key.
enum CredentialSourceField { enum CredentialSourceField {
PrivateKey = 0, PrivateKey = 0,
RpIdHash = 1, RpIdHash = 1,
CredProtectPolicy = 2, CredProtectPolicy = 2,
CredBlob = 3,
} }
impl From<CredentialSourceField> for sk_cbor::Value { impl From<CredentialSourceField> for sk_cbor::Value {
@@ -81,6 +83,7 @@ fn decrypt_legacy_credential_id(
private_key, private_key,
rp_id_hash: plaintext[32..64].try_into().unwrap(), rp_id_hash: plaintext[32..64].try_into().unwrap(),
cred_protect_policy: None, cred_protect_policy: None,
cred_blob: None,
})) }))
} }
@@ -98,24 +101,25 @@ fn decrypt_cbor_credential_id(
CredentialSourceField::PrivateKey => private_key, CredentialSourceField::PrivateKey => private_key,
CredentialSourceField::RpIdHash=> rp_id_hash, CredentialSourceField::RpIdHash=> rp_id_hash,
CredentialSourceField::CredProtectPolicy => cred_protect_policy, CredentialSourceField::CredProtectPolicy => cred_protect_policy,
CredentialSourceField::CredBlob => cred_blob,
} = extract_map(cbor_credential_source)?; } = extract_map(cbor_credential_source)?;
} }
Ok(match (private_key, rp_id_hash, cred_protect_policy) { Ok(match (private_key, rp_id_hash) {
(Some(private_key), Some(rp_id_hash), cred_protect_policy) => { (Some(private_key), Some(rp_id_hash)) => {
let private_key = PrivateKey::try_from(private_key)?; let private_key = PrivateKey::try_from(private_key)?;
let rp_id_hash = extract_byte_string(rp_id_hash)?; let rp_id_hash = extract_byte_string(rp_id_hash)?;
if rp_id_hash.len() != 32 { if rp_id_hash.len() != 32 {
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR); return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
} }
let cred_protect_policy = if let Some(policy) = cred_protect_policy { let cred_protect_policy = cred_protect_policy
Some(CredentialProtectionPolicy::try_from(policy)?) .map(CredentialProtectionPolicy::try_from)
} else { .transpose()?;
None let cred_blob = cred_blob.map(extract_byte_string).transpose()?;
};
Some(CredentialSource { Some(CredentialSource {
private_key, private_key,
rp_id_hash: rp_id_hash.try_into().unwrap(), rp_id_hash: rp_id_hash.try_into().unwrap(),
cred_protect_policy, cred_protect_policy,
cred_blob,
}) })
} }
_ => None, _ => None,
@@ -153,7 +157,7 @@ fn remove_padding(data: &mut Vec<u8>) -> Result<(), Ctap2StatusCode> {
Ok(()) 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 /// Other information, such as a user name, are not stored. Since encrypted credential IDs are
/// stored server-side, this information is already available (unencrypted). /// stored server-side, this information is already available (unencrypted).
@@ -162,12 +166,14 @@ pub fn encrypt_to_credential_id(
private_key: &PrivateKey, private_key: &PrivateKey,
rp_id_hash: &[u8; 32], rp_id_hash: &[u8; 32],
cred_protect_policy: Option<CredentialProtectionPolicy>, cred_protect_policy: Option<CredentialProtectionPolicy>,
cred_blob: Option<Vec<u8>>,
) -> Result<Vec<u8>, Ctap2StatusCode> { ) -> Result<Vec<u8>, Ctap2StatusCode> {
let mut payload = Vec::new(); let mut payload = Vec::new();
let cbor = cbor_map_options! { let cbor = cbor_map_options! {
CredentialSourceField::PrivateKey => private_key, CredentialSourceField::PrivateKey => private_key,
CredentialSourceField::RpIdHash=> rp_id_hash, CredentialSourceField::RpIdHash=> rp_id_hash,
CredentialSourceField::CredProtectPolicy => cred_protect_policy, CredentialSourceField::CredProtectPolicy => cred_protect_policy,
CredentialSourceField::CredBlob => cred_blob,
}; };
cbor_write(cbor, &mut payload)?; cbor_write(cbor, &mut payload)?;
add_padding(&mut payload)?; add_padding(&mut payload)?;
@@ -254,7 +260,7 @@ pub fn decrypt_credential_id(
creation_order: 0, creation_order: 0,
user_name: None, user_name: None,
user_icon: None, user_icon: None,
cred_blob: None, cred_blob: credential_source.cred_blob,
large_blob_key: None, large_blob_key: None,
})) }))
} }
@@ -262,6 +268,7 @@ pub fn decrypt_credential_id(
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use crate::api::customization::Customization;
use crate::ctap::credential_id::CBOR_CREDENTIAL_ID_SIZE; use crate::ctap::credential_id::CBOR_CREDENTIAL_ID_SIZE;
use crate::ctap::SignatureAlgorithm; use crate::ctap::SignatureAlgorithm;
use crate::env::test::TestEnv; use crate::env::test::TestEnv;
@@ -275,7 +282,7 @@ mod test {
let rp_id_hash = [0x55; 32]; let rp_id_hash = [0x55; 32];
let encrypted_id = 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) let decrypted_source = decrypt_credential_id(&mut env, encrypted_id, &rp_id_hash)
.unwrap() .unwrap()
.unwrap(); .unwrap();
@@ -301,7 +308,7 @@ mod test {
let rp_id_hash = [0x55; 32]; let rp_id_hash = [0x55; 32];
let mut encrypted_id = 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; encrypted_id[0] = UNSUPPORTED_CREDENTIAL_ID_VERSION;
// Override the HMAC to pass the check. // Override the HMAC to pass the check.
encrypted_id.truncate(&encrypted_id.len() - 32); encrypted_id.truncate(&encrypted_id.len() - 32);
@@ -321,7 +328,7 @@ mod test {
let rp_id_hash = [0x55; 32]; let rp_id_hash = [0x55; 32];
let encrypted_id = 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() { for i in 0..encrypted_id.len() {
let mut modified_id = encrypted_id.clone(); let mut modified_id = encrypted_id.clone();
modified_id[i] ^= 0x01; modified_id[i] ^= 0x01;
@@ -349,7 +356,7 @@ mod test {
let rp_id_hash = [0x55; 32]; let rp_id_hash = [0x55; 32];
let encrypted_id = 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) { for length in (1..CBOR_CREDENTIAL_ID_SIZE).step_by(16) {
assert_eq!( assert_eq!(
@@ -416,10 +423,32 @@ mod test {
let rp_id_hash = [0x55; 32]; let rp_id_hash = [0x55; 32];
let encrypted_id = 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); 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] #[test]
fn test_cred_protect_persisted() { fn test_cred_protect_persisted() {
let mut env = TestEnv::new(); let mut env = TestEnv::new();
@@ -431,6 +460,7 @@ mod test {
&private_key, &private_key,
&rp_id_hash, &rp_id_hash,
Some(CredentialProtectionPolicy::UserVerificationRequired), Some(CredentialProtectionPolicy::UserVerificationRequired),
None,
) )
.unwrap(); .unwrap();
@@ -443,4 +473,22 @@ mod test {
Some(CredentialProtectionPolicy::UserVerificationRequired) 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) .ecdsa_key(env)
.map_err(|_| Ctap1StatusCode::SW_INTERNAL_EXCEPTION)?; .map_err(|_| Ctap1StatusCode::SW_INTERNAL_EXCEPTION)?;
let pk = sk.genpk(); 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)?; .map_err(|_| Ctap1StatusCode::SW_INTERNAL_EXCEPTION)?;
if key_handle.len() > 0xFF { if key_handle.len() > 0xFF {
// This is just being defensive with unreachable code. // This is just being defensive with unreachable code.
@@ -498,7 +498,7 @@ mod test {
let rp_id = "example.com"; let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes()); 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 message = create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
let response = let response =
@@ -516,7 +516,7 @@ mod test {
let rp_id = "example.com"; let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes()); 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 application = [0x55; 32];
let message = create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle); let message = create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
@@ -535,7 +535,7 @@ mod test {
let rp_id = "example.com"; let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes()); 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( let mut message = create_authenticate_message(
&application, &application,
Ctap1Flags::DontEnforceUpAndSign, Ctap1Flags::DontEnforceUpAndSign,
@@ -573,7 +573,7 @@ mod test {
let rp_id = "example.com"; let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes()); 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 = let mut message =
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle); create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
message[0] = 0xEE; message[0] = 0xEE;
@@ -593,7 +593,7 @@ mod test {
let rp_id = "example.com"; let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes()); 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 = let mut message =
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle); create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
message[1] = 0xEE; message[1] = 0xEE;
@@ -613,7 +613,7 @@ mod test {
let rp_id = "example.com"; let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes()); 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 = let mut message =
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle); create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
message[2] = 0xEE; message[2] = 0xEE;
@@ -641,7 +641,7 @@ mod test {
let rp_id = "example.com"; let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes()); 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 = let message =
create_authenticate_message(&application, Ctap1Flags::EnforceUpAndSign, &key_handle); create_authenticate_message(&application, Ctap1Flags::EnforceUpAndSign, &key_handle);
@@ -669,7 +669,7 @@ mod test {
let rp_id = "example.com"; let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes()); 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( let message = create_authenticate_message(
&application, &application,
Ctap1Flags::DontEnforceUpAndSign, Ctap1Flags::DontEnforceUpAndSign,

View File

@@ -880,7 +880,7 @@ impl CtapState {
let has_cred_blob_output = extensions.cred_blob.is_some(); let has_cred_blob_output = extensions.cred_blob.is_some();
let cred_blob = extensions let cred_blob = extensions
.cred_blob .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 { let cred_blob_output = if has_cred_blob_output {
Some(cred_blob.is_some()) Some(cred_blob.is_some())
} else { } else {
@@ -928,7 +928,13 @@ impl CtapState {
storage::store_credential(env, credential_source)?; storage::store_credential(env, credential_source)?;
random_id random_id
} else { } 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)?; 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]); const VENDOR_CHANNEL: Channel = Channel::VendorHid([0x12, 0x34, 0x56, 0x78]);
fn check_make_response( fn check_make_response(
make_credential_response: Result<ResponseData, Ctap2StatusCode>, make_credential_response: &Result<ResponseData, Ctap2StatusCode>,
flags: u8, flags: u8,
expected_aaguid: &[u8], expected_aaguid: &[u8],
expected_credential_id_size: u8, expected_credential_id_size: u8,
expected_extension_cbor: &[u8], expected_extension_cbor: &[u8],
) { ) {
match make_credential_response.unwrap() { match make_credential_response.as_ref().unwrap() {
ResponseData::AuthenticatorMakeCredential(make_credential_response) => { ResponseData::AuthenticatorMakeCredential(make_credential_response) => {
let AuthenticatorMakeCredentialResponse { let AuthenticatorMakeCredentialResponse {
fmt, fmt,
@@ -1590,7 +1596,7 @@ mod test {
); );
assert!(ep_att.is_none()); assert!(ep_att.is_none());
assert_eq!(att_stmt.alg, SignatureAlgorithm::Es256 as i64); 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"), _ => panic!("Invalid response type"),
} }
@@ -1708,6 +1714,22 @@ mod test {
make_credential_params 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] #[test]
fn test_resident_process_make_credential() { fn test_resident_process_make_credential() {
let mut env = TestEnv::new(); let mut env = TestEnv::new();
@@ -1718,7 +1740,7 @@ mod test {
ctap_state.process_make_credential(&mut env, make_credential_params, DUMMY_CHANNEL); ctap_state.process_make_credential(&mut env, make_credential_params, DUMMY_CHANNEL);
check_make_response( check_make_response(
make_credential_response, &make_credential_response,
0x41, 0x41,
&storage::aaguid(&mut env).unwrap(), &storage::aaguid(&mut env).unwrap(),
0x20, 0x20,
@@ -1737,7 +1759,7 @@ mod test {
ctap_state.process_make_credential(&mut env, make_credential_params, DUMMY_CHANNEL); ctap_state.process_make_credential(&mut env, make_credential_params, DUMMY_CHANNEL);
check_make_response( check_make_response(
make_credential_response, &make_credential_response,
0x41, 0x41,
&storage::aaguid(&mut env).unwrap(), &storage::aaguid(&mut env).unwrap(),
CBOR_CREDENTIAL_ID_SIZE as u8, CBOR_CREDENTIAL_ID_SIZE as u8,
@@ -1857,16 +1879,10 @@ mod test {
let make_credential_response = let make_credential_response =
ctap_state.process_make_credential(&mut env, make_credential_params, DUMMY_CHANNEL); ctap_state.process_make_credential(&mut env, make_credential_params, DUMMY_CHANNEL);
assert!(make_credential_response.is_ok()); assert!(make_credential_response.is_ok());
let credential_id = match make_credential_response.unwrap() { let credential_id = parse_credential_id_from_non_resident_make_credential_response(
ResponseData::AuthenticatorMakeCredential(make_credential_response) => { &mut env,
let auth_data = make_credential_response.auth_data; make_credential_response.unwrap(),
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 make_credential_params = let make_credential_params =
create_make_credential_parameters_with_exclude_list(&credential_id); create_make_credential_parameters_with_exclude_list(&credential_id);
let make_credential_response = let make_credential_response =
@@ -1883,16 +1899,10 @@ mod test {
let make_credential_response = let make_credential_response =
ctap_state.process_make_credential(&mut env, make_credential_params, DUMMY_CHANNEL); ctap_state.process_make_credential(&mut env, make_credential_params, DUMMY_CHANNEL);
assert!(make_credential_response.is_ok()); assert!(make_credential_response.is_ok());
let credential_id = match make_credential_response.unwrap() { let credential_id = parse_credential_id_from_non_resident_make_credential_response(
ResponseData::AuthenticatorMakeCredential(make_credential_response) => { &mut env,
let auth_data = make_credential_response.auth_data; make_credential_response.unwrap(),
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 make_credential_params = let make_credential_params =
create_make_credential_parameters_with_exclude_list(&credential_id); create_make_credential_parameters_with_exclude_list(&credential_id);
let make_credential_response = let make_credential_response =
@@ -1919,7 +1929,7 @@ mod test {
0xA1, 0x6B, 0x68, 0x6D, 0x61, 0x63, 0x2D, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0xF5, 0xA1, 0x6B, 0x68, 0x6D, 0x61, 0x63, 0x2D, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0xF5,
]; ];
check_make_response( check_make_response(
make_credential_response, &make_credential_response,
0xC1, 0xC1,
&storage::aaguid(&mut env).unwrap(), &storage::aaguid(&mut env).unwrap(),
CBOR_CREDENTIAL_ID_SIZE as u8, 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, 0xA1, 0x6B, 0x68, 0x6D, 0x61, 0x63, 0x2D, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0xF5,
]; ];
check_make_response( check_make_response(
make_credential_response, &make_credential_response,
0xC1, 0xC1,
&storage::aaguid(&mut env).unwrap(), &storage::aaguid(&mut env).unwrap(),
0x20, 0x20,
@@ -1968,7 +1978,7 @@ mod test {
let make_credential_response = let make_credential_response =
ctap_state.process_make_credential(&mut env, make_credential_params, DUMMY_CHANNEL); ctap_state.process_make_credential(&mut env, make_credential_params, DUMMY_CHANNEL);
check_make_response( check_make_response(
make_credential_response, &make_credential_response,
0x41, 0x41,
&storage::aaguid(&mut env).unwrap(), &storage::aaguid(&mut env).unwrap(),
0x20, 0x20,
@@ -1994,7 +2004,7 @@ mod test {
0x04, 0x04,
]; ];
check_make_response( check_make_response(
make_credential_response, &make_credential_response,
0xC1, 0xC1,
&storage::aaguid(&mut env).unwrap(), &storage::aaguid(&mut env).unwrap(),
0x20, 0x20,
@@ -2019,7 +2029,7 @@ mod test {
0xA1, 0x68, 0x63, 0x72, 0x65, 0x64, 0x42, 0x6C, 0x6F, 0x62, 0xF5, 0xA1, 0x68, 0x63, 0x72, 0x65, 0x64, 0x42, 0x6C, 0x6F, 0x62, 0xF5,
]; ];
check_make_response( check_make_response(
make_credential_response, &make_credential_response,
0xC1, 0xC1,
&storage::aaguid(&mut env).unwrap(), &storage::aaguid(&mut env).unwrap(),
0x20, 0x20,
@@ -2051,7 +2061,7 @@ mod test {
0xA1, 0x68, 0x63, 0x72, 0x65, 0x64, 0x42, 0x6C, 0x6F, 0x62, 0xF4, 0xA1, 0x68, 0x63, 0x72, 0x65, 0x64, 0x42, 0x6C, 0x6F, 0x62, 0xF4,
]; ];
check_make_response( check_make_response(
make_credential_response, &make_credential_response,
0xC1, 0xC1,
&storage::aaguid(&mut env).unwrap(), &storage::aaguid(&mut env).unwrap(),
0x20, 0x20,
@@ -2125,7 +2135,7 @@ mod test {
); );
check_make_response( check_make_response(
make_credential_response, &make_credential_response,
0x45, 0x45,
&storage::aaguid(&mut env).unwrap(), &storage::aaguid(&mut env).unwrap(),
0x20, 0x20,
@@ -2162,7 +2172,7 @@ mod test {
ctap_state.process_make_credential(&mut env, make_credential_params, DUMMY_CHANNEL); ctap_state.process_make_credential(&mut env, make_credential_params, DUMMY_CHANNEL);
check_make_response( check_make_response(
make_credential_response, &make_credential_response,
0x41, 0x41,
&storage::aaguid(&mut env).unwrap(), &storage::aaguid(&mut env).unwrap(),
CBOR_CREDENTIAL_ID_SIZE as u8, CBOR_CREDENTIAL_ID_SIZE as u8,
@@ -2350,7 +2360,7 @@ mod test {
fn check_assertion_response_with_user( fn check_assertion_response_with_user(
response: Result<ResponseData, Ctap2StatusCode>, response: Result<ResponseData, Ctap2StatusCode>,
expected_user: PublicKeyCredentialUserEntity, expected_user: Option<PublicKeyCredentialUserEntity>,
flags: u8, flags: u8,
signature_counter: u32, signature_counter: u32,
expected_number_of_credentials: Option<u64>, expected_number_of_credentials: Option<u64>,
@@ -2379,23 +2389,23 @@ mod test {
); );
expected_auth_data.extend(expected_extension_cbor); expected_auth_data.extend(expected_extension_cbor);
assert_eq!(auth_data, expected_auth_data); assert_eq!(auth_data, expected_auth_data);
assert_eq!(user, Some(expected_user)); assert_eq!(user, expected_user);
assert_eq!(number_of_credentials, expected_number_of_credentials); assert_eq!(number_of_credentials, expected_number_of_credentials);
} }
fn check_assertion_response_with_extension( fn check_assertion_response_with_extension(
response: Result<ResponseData, Ctap2StatusCode>, response: Result<ResponseData, Ctap2StatusCode>,
expected_user_id: Vec<u8>, expected_user_id: Option<Vec<u8>>,
signature_counter: u32, signature_counter: u32,
expected_number_of_credentials: Option<u64>, expected_number_of_credentials: Option<u64>,
expected_extension_cbor: &[u8], expected_extension_cbor: &[u8],
) { ) {
let expected_user = PublicKeyCredentialUserEntity { let expected_user = expected_user_id.map(|user_id| PublicKeyCredentialUserEntity {
user_id: expected_user_id, user_id,
user_name: None, user_name: None,
user_display_name: None, user_display_name: None,
user_icon: None, user_icon: None,
}; });
check_assertion_response_with_user( check_assertion_response_with_user(
response, response,
expected_user, expected_user,
@@ -2420,7 +2430,7 @@ mod test {
}; };
check_assertion_response_with_user( check_assertion_response_with_user(
response, response,
expected_user, Some(expected_user),
0x00, 0x00,
signature_counter, signature_counter,
expected_number_of_credentials, expected_number_of_credentials,
@@ -2528,16 +2538,10 @@ mod test {
let make_credential_response = let make_credential_response =
ctap_state.process_make_credential(&mut env, make_credential_params, DUMMY_CHANNEL); ctap_state.process_make_credential(&mut env, make_credential_params, DUMMY_CHANNEL);
assert!(make_credential_response.is_ok()); assert!(make_credential_response.is_ok());
let credential_id = match make_credential_response.unwrap() { let credential_id = parse_credential_id_from_non_resident_make_credential_response(
ResponseData::AuthenticatorMakeCredential(make_credential_response) => { &mut env,
let auth_data = make_credential_response.auth_data; make_credential_response.unwrap(),
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 client_pin_params = AuthenticatorClientPinParameters { let client_pin_params = AuthenticatorClientPinParameters {
pin_uv_auth_protocol, pin_uv_auth_protocol,
@@ -2760,16 +2764,10 @@ mod test {
let make_credential_response = let make_credential_response =
ctap_state.process_make_credential(&mut env, make_credential_params, DUMMY_CHANNEL); ctap_state.process_make_credential(&mut env, make_credential_params, DUMMY_CHANNEL);
assert!(make_credential_response.is_ok()); assert!(make_credential_response.is_ok());
let credential_id = match make_credential_response.unwrap() { let credential_id = parse_credential_id_from_non_resident_make_credential_response(
ResponseData::AuthenticatorMakeCredential(make_credential_response) => { &mut env,
let auth_data = make_credential_response.auth_data; make_credential_response.unwrap(),
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 cred_desc = PublicKeyCredentialDescriptor { let cred_desc = PublicKeyCredentialDescriptor {
key_type: PublicKeyCredentialType::PublicKey, key_type: PublicKeyCredentialType::PublicKey,
key_id: credential_id, key_id: credential_id,
@@ -2802,16 +2800,10 @@ mod test {
let make_credential_response = let make_credential_response =
ctap_state.process_make_credential(&mut env, make_credential_params, DUMMY_CHANNEL); ctap_state.process_make_credential(&mut env, make_credential_params, DUMMY_CHANNEL);
assert!(make_credential_response.is_ok()); assert!(make_credential_response.is_ok());
let credential_id = match make_credential_response.unwrap() { let credential_id = parse_credential_id_from_non_resident_make_credential_response(
ResponseData::AuthenticatorMakeCredential(make_credential_response) => { &mut env,
let auth_data = make_credential_response.auth_data; make_credential_response.unwrap(),
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 cred_desc = PublicKeyCredentialDescriptor { let cred_desc = PublicKeyCredentialDescriptor {
key_type: PublicKeyCredentialType::PublicKey, key_type: PublicKeyCredentialType::PublicKey,
key_id: credential_id, key_id: credential_id,
@@ -2892,7 +2884,76 @@ mod test {
]; ];
check_assertion_response_with_extension( check_assertion_response_with_extension(
get_assertion_response, 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, signature_counter,
None, None,
&expected_extension_cbor, &expected_extension_cbor,
@@ -3018,7 +3079,7 @@ mod test {
let signature_counter = storage::global_signature_counter(&mut env).unwrap(); let signature_counter = storage::global_signature_counter(&mut env).unwrap();
check_assertion_response_with_user( check_assertion_response_with_user(
get_assertion_response, get_assertion_response,
user2, Some(user2),
0x04, 0x04,
signature_counter, signature_counter,
Some(2), Some(2),
@@ -3028,7 +3089,7 @@ mod test {
let get_assertion_response = ctap_state.process_get_next_assertion(&mut env); let get_assertion_response = ctap_state.process_get_next_assertion(&mut env);
check_assertion_response_with_user( check_assertion_response_with_user(
get_assertion_response, get_assertion_response,
user1, Some(user1),
0x04, 0x04,
signature_counter, signature_counter,
None, None,