diff --git a/src/api/customization.rs b/src/api/customization.rs index 8399d2a..3531002 100644 --- a/src/api/customization.rs +++ b/src/api/customization.rs @@ -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; } diff --git a/src/ctap/credential_id.rs b/src/ctap/credential_id.rs index 52c028d..20d9b73 100644 --- a/src/ctap/credential_id.rs +++ b/src/ctap/credential_id.rs @@ -47,14 +47,16 @@ struct CredentialSource { private_key: PrivateKey, rp_id_hash: [u8; 32], cred_protect_policy: Option, + cred_blob: Option>, } -// 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 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) -> 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, + cred_blob: Option>, ) -> Result, 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); + } } diff --git a/src/ctap/ctap1.rs b/src/ctap/ctap1.rs index 0661c42..5752d05 100644 --- a/src/ctap/ctap1.rs +++ b/src/ctap/ctap1.rs @@ -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, diff --git a/src/ctap/mod.rs b/src/ctap/mod.rs index 1cc1b03..b47a8f2 100644 --- a/src/ctap/mod.rs +++ b/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, + make_credential_response: &Result, 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 { + 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, - expected_user: PublicKeyCredentialUserEntity, + expected_user: Option, flags: u8, signature_counter: u32, expected_number_of_credentials: Option, @@ -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, - expected_user_id: Vec, + expected_user_id: Option>, signature_counter: u32, expected_number_of_credentials: Option, 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,