diff --git a/src/ctap/credential_management.rs b/src/ctap/credential_management.rs index 7665ea7..acc9278 100644 --- a/src/ctap/credential_management.rs +++ b/src/ctap/credential_management.rs @@ -81,6 +81,7 @@ fn enumerate_credentials_response( user_name, user_icon, cred_blob: _, + large_blob_key, } = credential; let user = PublicKeyCredentialUserEntity { user_id: user_handle, @@ -100,8 +101,7 @@ fn enumerate_credentials_response( public_key: Some(public_key), total_credentials, cred_protect: cred_protect_policy, - // TODO(kaczmarczyck) add when largeBlobKey extension is implemented - large_blob_key: None, + large_blob_key, ..Default::default() }) } @@ -348,6 +348,7 @@ mod test { user_name: Some("name".to_string()), user_icon: Some("icon".to_string()), cred_blob: None, + large_blob_key: None, } } diff --git a/src/ctap/data_formats.rs b/src/ctap/data_formats.rs index da992f8..ba9ca1a 100644 --- a/src/ctap/data_formats.rs +++ b/src/ctap/data_formats.rs @@ -282,6 +282,7 @@ pub struct MakeCredentialExtensions { pub cred_protect: Option, pub min_pin_length: bool, pub cred_blob: Option>, + pub large_blob_key: Option, } impl TryFrom for MakeCredentialExtensions { @@ -293,6 +294,7 @@ impl TryFrom for MakeCredentialExtensions { "credBlob" => cred_blob, "credProtect" => cred_protect, "hmac-secret" => hmac_secret, + "largeBlobKey" => large_blob_key, "minPinLength" => min_pin_length, } = extract_map(cbor_value)?; } @@ -303,11 +305,18 @@ impl TryFrom for MakeCredentialExtensions { .transpose()?; let min_pin_length = min_pin_length.map_or(Ok(false), extract_bool)?; let cred_blob = cred_blob.map(extract_byte_string).transpose()?; + let large_blob_key = large_blob_key.map(extract_bool).transpose()?; + if let Some(large_blob_key) = large_blob_key { + if !large_blob_key { + return Err(Ctap2StatusCode::CTAP2_ERR_INVALID_OPTION); + } + } Ok(Self { hmac_secret, cred_protect, min_pin_length, cred_blob, + large_blob_key, }) } } @@ -317,6 +326,7 @@ impl TryFrom for MakeCredentialExtensions { pub struct GetAssertionExtensions { pub hmac_secret: Option, pub cred_blob: bool, + pub large_blob_key: Option, } impl TryFrom for GetAssertionExtensions { @@ -327,6 +337,7 @@ impl TryFrom for GetAssertionExtensions { let { "credBlob" => cred_blob, "hmac-secret" => hmac_secret, + "largeBlobKey" => large_blob_key, } = extract_map(cbor_value)?; } @@ -334,9 +345,16 @@ impl TryFrom for GetAssertionExtensions { .map(GetAssertionHmacSecretInput::try_from) .transpose()?; let cred_blob = cred_blob.map_or(Ok(false), extract_bool)?; + let large_blob_key = large_blob_key.map(extract_bool).transpose()?; + if let Some(large_blob_key) = large_blob_key { + if !large_blob_key { + return Err(Ctap2StatusCode::CTAP2_ERR_INVALID_OPTION); + } + } Ok(Self { hmac_secret, cred_blob, + large_blob_key, }) } } @@ -546,6 +564,7 @@ pub struct PublicKeyCredentialSource { pub user_name: Option, pub user_icon: Option, pub cred_blob: Option>, + pub large_blob_key: Option>, } // We serialize credentials for the persistent storage using CBOR maps. Each field of a credential @@ -561,6 +580,7 @@ enum PublicKeyCredentialSourceField { UserName = 8, UserIcon = 9, CredBlob = 10, + LargeBlobKey = 11, // When a field is removed, its tag should be reserved and not used for new fields. We document // those reserved tags below. // Reserved tags: @@ -588,6 +608,7 @@ impl From for cbor::Value { PublicKeyCredentialSourceField::UserName => credential.user_name, PublicKeyCredentialSourceField::UserIcon => credential.user_icon, PublicKeyCredentialSourceField::CredBlob => credential.cred_blob, + PublicKeyCredentialSourceField::LargeBlobKey => credential.large_blob_key, } } } @@ -608,6 +629,7 @@ impl TryFrom for PublicKeyCredentialSource { PublicKeyCredentialSourceField::UserName => user_name, PublicKeyCredentialSourceField::UserIcon => user_icon, PublicKeyCredentialSourceField::CredBlob => cred_blob, + PublicKeyCredentialSourceField::LargeBlobKey => large_blob_key, } = extract_map(cbor_value)?; } @@ -628,6 +650,7 @@ impl TryFrom for PublicKeyCredentialSource { let user_name = user_name.map(extract_text_string).transpose()?; let user_icon = user_icon.map(extract_text_string).transpose()?; let cred_blob = cred_blob.map(extract_byte_string).transpose()?; + let large_blob_key = large_blob_key.map(extract_byte_string).transpose()?; // We don't return whether there were unknown fields in the CBOR value. This means that // deserialization is not injective. In particular deserialization is only an inverse of // serialization at a given version of OpenSK. This is not a problem because: @@ -650,6 +673,7 @@ impl TryFrom for PublicKeyCredentialSource { user_name, user_icon, cred_blob, + large_blob_key, }) } } @@ -1522,6 +1546,7 @@ mod test { "credProtect" => CredentialProtectionPolicy::UserVerificationRequired, "minPinLength" => true, "credBlob" => vec![0xCB], + "largeBlobKey" => true, }; let extensions = MakeCredentialExtensions::try_from(cbor_extensions); let expected_extensions = MakeCredentialExtensions { @@ -1529,6 +1554,7 @@ mod test { cred_protect: Some(CredentialProtectionPolicy::UserVerificationRequired), min_pin_length: true, cred_blob: Some(vec![0xCB]), + large_blob_key: Some(true), }; assert_eq!(extensions, Ok(expected_extensions)); } @@ -1546,6 +1572,7 @@ mod test { 3 => vec![0x03; 16], }, "credBlob" => true, + "largeBlobKey" => true, }; let extensions = GetAssertionExtensions::try_from(cbor_extensions); let expected_input = GetAssertionHmacSecretInput { @@ -1556,6 +1583,7 @@ mod test { let expected_extensions = GetAssertionExtensions { hmac_secret: Some(expected_input), cred_blob: true, + large_blob_key: Some(true), }; assert_eq!(extensions, Ok(expected_extensions)); } @@ -1849,6 +1877,7 @@ mod test { user_name: None, user_icon: None, cred_blob: None, + large_blob_key: None, }; assert_eq!( @@ -1901,6 +1930,16 @@ mod test { ..credential }; + assert_eq!( + PublicKeyCredentialSource::try_from(cbor::Value::from(credential.clone())), + Ok(credential.clone()) + ); + + let credential = PublicKeyCredentialSource { + large_blob_key: Some(vec![0x1B]), + ..credential + }; + assert_eq!( PublicKeyCredentialSource::try_from(cbor::Value::from(credential.clone())), Ok(credential) diff --git a/src/ctap/mod.rs b/src/ctap/mod.rs index 7b426c1..ab66177 100644 --- a/src/ctap/mod.rs +++ b/src/ctap/mod.rs @@ -49,7 +49,7 @@ use self::response::{ AuthenticatorMakeCredentialResponse, AuthenticatorVendorResponse, ResponseData, }; use self::status_code::Ctap2StatusCode; -use self::storage::{PersistentStore, MAX_RP_IDS_LENGTH}; +use self::storage::{PersistentStore, MAX_LARGE_BLOB_ARRAY_SIZE, MAX_RP_IDS_LENGTH}; use self::timed_permission::TimedPermission; #[cfg(feature = "with_ctap1")] use self::timed_permission::U2fUserPresenceState; @@ -427,6 +427,7 @@ where user_name: None, user_icon: None, cred_blob: None, + large_blob_key: None, })) } @@ -596,6 +597,10 @@ where || cred_protect_policy.is_some() || min_pin_length || has_cred_blob_output; + let large_blob_key = match (options.rk, extensions.large_blob_key) { + (true, Some(true)) => Some(self.rng.gen_uniform_u8x32().to_vec()), + _ => None, + }; let rp_id_hash = Sha256::hash(rp_id.as_bytes()); if let Some(exclude_list) = exclude_list { @@ -674,6 +679,7 @@ where .user_icon .map(|s| truncate_to_char_boundary(&s, 64).to_string()), cred_blob, + large_blob_key: large_blob_key.clone(), }; self.persistent_store.store_credential(credential_source)?; random_id @@ -749,6 +755,7 @@ where fmt: String::from("packed"), auth_data, att_stmt: attestation_statement, + large_blob_key, }, )) } @@ -806,6 +813,10 @@ where return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR); } } + let large_blob_key = match extensions.large_blob_key { + Some(true) => credential.large_blob_key, + _ => None, + }; let mut signature_data = auth_data.clone(); signature_data.extend(client_data_hash); @@ -841,6 +852,7 @@ where signature: signature.to_asn1_der(), user, number_of_credentials: number_of_credentials.map(|n| n as u64), + large_blob_key, }, )) } @@ -1006,19 +1018,18 @@ where fn process_get_info(&self) -> Result { let mut options_map = BTreeMap::new(); - // TODO(kaczmarczyck) add authenticatorConfig and credProtect options options_map.insert(String::from("rk"), true); - options_map.insert(String::from("up"), true); options_map.insert( String::from("clientPin"), self.persistent_store.pin_hash()?.is_some(), ); + options_map.insert(String::from("up"), true); + options_map.insert(String::from("pinUvAuthToken"), true); + options_map.insert(String::from("largeBlobs"), true); + options_map.insert(String::from("authnrCfg"), true); options_map.insert(String::from("credMgmt"), true); options_map.insert(String::from("setMinPINLength"), true); - options_map.insert( - String::from("forcePINChange"), - self.persistent_store.has_force_pin_change()?, - ); + options_map.insert(String::from("makeCredUvNotRqd"), true); Ok(ResponseData::AuthenticatorGetInfo( AuthenticatorGetInfoResponse { versions: vec![ @@ -1032,6 +1043,7 @@ where String::from("credProtect"), String::from("minPinLength"), String::from("credBlob"), + String::from("largeBlobKey"), ]), aaguid: self.persistent_store.aaguid()?, options: Some(options_map), @@ -1041,7 +1053,8 @@ where max_credential_id_length: Some(CREDENTIAL_ID_SIZE as u64), transports: Some(vec![AuthenticatorTransport::Usb]), algorithms: Some(vec![ES256_CRED_PARAM]), - default_cred_protect: DEFAULT_CRED_PROTECT, + max_serialized_large_blob_array: Some(MAX_LARGE_BLOB_ARRAY_SIZE as u64), + force_pin_change: Some(self.persistent_store.has_force_pin_change()?), min_pin_length: self.persistent_store.min_pin_length()?, firmware_version: None, max_cred_blob_length: Some(MAX_CRED_BLOB_LENGTH as u64), @@ -1214,6 +1227,7 @@ mod test { fmt, auth_data, att_stmt, + large_blob_key, } = make_credential_response; // The expected response is split to only assert the non-random parts. assert_eq!(fmt, "packed"); @@ -1234,6 +1248,7 @@ mod test { expected_extension_cbor ); assert_eq!(att_stmt.alg, SignatureAlgorithm::ES256 as i64); + assert_eq!(large_blob_key, None); } _ => panic!("Invalid response type"), } @@ -1258,15 +1273,19 @@ mod test { String::from("credProtect"), String::from("minPinLength"), String::from("credBlob"), + String::from("largeBlobKey"), ]], 0x03 => ctap_state.persistent_store.aaguid().unwrap(), 0x04 => cbor_map! { "rk" => true, - "up" => true, "clientPin" => false, + "up" => true, + "pinUvAuthToken" => true, + "largeBlobs" => true, + "authnrCfg" => true, "credMgmt" => true, "setMinPINLength" => true, - "forcePINChange" => false, + "makeCredUvNotRqd" => true, }, 0x05 => MAX_MSG_SIZE as u64, 0x06 => cbor_array_vec![vec![1]], @@ -1274,7 +1293,8 @@ mod test { 0x08 => CREDENTIAL_ID_SIZE as u64, 0x09 => cbor_array_vec![vec!["usb"]], 0x0A => cbor_array_vec![vec![ES256_CRED_PARAM]], - 0x0C => DEFAULT_CRED_PROTECT.map(|c| c as u64), + 0x0B => MAX_LARGE_BLOB_ARRAY_SIZE as u64, + 0x0C => false, 0x0D => ctap_state.persistent_store.min_pin_length().unwrap() as u64, 0x0F => MAX_CRED_BLOB_LENGTH as u64, 0x10 => MAX_RP_IDS_LENGTH as u64, @@ -1336,10 +1356,8 @@ mod test { policy: CredentialProtectionPolicy, ) -> AuthenticatorMakeCredentialParameters { let extensions = MakeCredentialExtensions { - hmac_secret: false, cred_protect: Some(policy), - min_pin_length: false, - cred_blob: None, + ..Default::default() }; let mut make_credential_params = create_minimal_make_credential_parameters(); make_credential_params.extensions = extensions; @@ -1424,6 +1442,7 @@ mod test { user_name: None, user_icon: None, cred_blob: None, + large_blob_key: None, }; assert!(ctap_state .persistent_store @@ -1504,9 +1523,7 @@ mod test { let extensions = MakeCredentialExtensions { hmac_secret: true, - cred_protect: None, - min_pin_length: false, - cred_blob: None, + ..Default::default() }; let mut make_credential_params = create_minimal_make_credential_parameters(); make_credential_params.options.rk = false; @@ -1534,9 +1551,7 @@ mod test { let extensions = MakeCredentialExtensions { hmac_secret: true, - cred_protect: None, - min_pin_length: false, - cred_blob: None, + ..Default::default() }; let mut make_credential_params = create_minimal_make_credential_parameters(); make_credential_params.extensions = extensions; @@ -1563,10 +1578,8 @@ mod test { // First part: The extension is ignored, since the RP ID is not on the list. let extensions = MakeCredentialExtensions { - hmac_secret: false, - cred_protect: None, min_pin_length: true, - cred_blob: None, + ..Default::default() }; let mut make_credential_params = create_minimal_make_credential_parameters(); make_credential_params.extensions = extensions; @@ -1589,10 +1602,8 @@ mod test { ); let extensions = MakeCredentialExtensions { - hmac_secret: false, - cred_protect: None, min_pin_length: true, - cred_blob: None, + ..Default::default() }; let mut make_credential_params = create_minimal_make_credential_parameters(); make_credential_params.extensions = extensions; @@ -1618,10 +1629,8 @@ mod test { let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE); let extensions = MakeCredentialExtensions { - hmac_secret: false, - cred_protect: None, - min_pin_length: false, cred_blob: Some(vec![0xCB]), + ..Default::default() }; let mut make_credential_params = create_minimal_make_credential_parameters(); make_credential_params.extensions = extensions; @@ -1656,10 +1665,8 @@ mod test { let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE); let extensions = MakeCredentialExtensions { - hmac_secret: false, - cred_protect: None, - min_pin_length: false, cred_blob: Some(vec![0xCB; MAX_CRED_BLOB_LENGTH + 1]), + ..Default::default() }; let mut make_credential_params = create_minimal_make_credential_parameters(); make_credential_params.extensions = extensions; @@ -1687,6 +1694,39 @@ mod test { assert_eq!(stored_credential.cred_blob, None); } + #[test] + fn test_process_make_credential_large_blob_key() { + let mut rng = ThreadRng256 {}; + let user_immediately_present = |_| Ok(()); + let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE); + + let extensions = MakeCredentialExtensions { + large_blob_key: Some(true), + ..Default::default() + }; + let mut make_credential_params = create_minimal_make_credential_parameters(); + make_credential_params.extensions = extensions; + let make_credential_response = + ctap_state.process_make_credential(make_credential_params, DUMMY_CHANNEL_ID); + let large_blob_key = match make_credential_response.unwrap() { + ResponseData::AuthenticatorMakeCredential(make_credential_response) => { + make_credential_response.large_blob_key.unwrap() + } + _ => panic!("Invalid response type"), + }; + assert_eq!(large_blob_key.len(), 32); + + let mut iter_result = Ok(()); + let iter = ctap_state + .persistent_store + .iter_credentials(&mut iter_result) + .unwrap(); + // There is only 1 credential, so last is good enough. + let (_, stored_credential) = iter.last().unwrap(); + iter_result.unwrap(); + assert_eq!(stored_credential.large_blob_key.unwrap(), large_blob_key); + } + #[test] fn test_process_make_credential_cancelled() { let mut rng = ThreadRng256 {}; @@ -1828,9 +1868,7 @@ mod test { let make_extensions = MakeCredentialExtensions { hmac_secret: true, - cred_protect: None, - min_pin_length: false, - cred_blob: None, + ..Default::default() }; let mut make_credential_params = create_minimal_make_credential_parameters(); make_credential_params.options.rk = false; @@ -1857,7 +1895,7 @@ mod test { }; let get_extensions = GetAssertionExtensions { hmac_secret: Some(hmac_secret_input), - cred_blob: false, + ..Default::default() }; let cred_desc = PublicKeyCredentialDescriptor { @@ -1898,9 +1936,7 @@ mod test { let make_extensions = MakeCredentialExtensions { hmac_secret: true, - cred_protect: None, - min_pin_length: false, - cred_blob: None, + ..Default::default() }; let mut make_credential_params = create_minimal_make_credential_parameters(); make_credential_params.extensions = make_extensions; @@ -1916,7 +1952,7 @@ mod test { }; let get_extensions = GetAssertionExtensions { hmac_secret: Some(hmac_secret_input), - cred_blob: false, + ..Default::default() }; let get_assertion_params = AuthenticatorGetAssertionParameters { @@ -1970,6 +2006,7 @@ mod test { user_name: None, user_icon: None, cred_blob: None, + large_blob_key: None, }; assert!(ctap_state .persistent_store @@ -2033,6 +2070,7 @@ mod test { user_name: None, user_icon: None, cred_blob: None, + large_blob_key: None, }; assert!(ctap_state .persistent_store @@ -2082,6 +2120,7 @@ mod test { user_name: None, user_icon: None, cred_blob: Some(vec![0xCB]), + large_blob_key: None, }; assert!(ctap_state .persistent_store @@ -2089,8 +2128,8 @@ mod test { .is_ok()); let extensions = GetAssertionExtensions { - hmac_secret: None, cred_blob: true, + ..Default::default() }; let get_assertion_params = AuthenticatorGetAssertionParameters { rp_id: String::from("example.com"), @@ -2125,6 +2164,63 @@ mod test { ); } + #[test] + fn test_process_get_assertion_with_large_blob_key() { + let mut rng = ThreadRng256 {}; + let private_key = crypto::ecdsa::SecKey::gensk(&mut rng); + let credential_id = rng.gen_uniform_u8x32().to_vec(); + let user_immediately_present = |_| Ok(()); + let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE); + + let credential = PublicKeyCredentialSource { + key_type: PublicKeyCredentialType::PublicKey, + credential_id, + private_key, + rp_id: String::from("example.com"), + user_handle: vec![0x1D], + user_display_name: None, + cred_protect_policy: None, + creation_order: 0, + user_name: None, + user_icon: None, + cred_blob: None, + large_blob_key: Some(vec![0x1C; 32]), + }; + assert!(ctap_state + .persistent_store + .store_credential(credential) + .is_ok()); + + let extensions = GetAssertionExtensions { + large_blob_key: Some(true), + ..Default::default() + }; + let get_assertion_params = AuthenticatorGetAssertionParameters { + rp_id: String::from("example.com"), + client_data_hash: vec![0xCD], + allow_list: None, + 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( + get_assertion_params, + DUMMY_CHANNEL_ID, + DUMMY_CLOCK_VALUE, + ); + let large_blob_key = match get_assertion_response.unwrap() { + ResponseData::AuthenticatorGetAssertion(get_assertion_response) => { + get_assertion_response.large_blob_key.unwrap() + } + _ => panic!("Invalid response type"), + }; + assert_eq!(large_blob_key, vec![0x1C; 32]); + } + #[test] fn test_process_get_next_assertion_two_credentials_with_uv() { let mut rng = ThreadRng256 {}; @@ -2369,6 +2465,7 @@ mod test { user_name: None, user_icon: None, cred_blob: None, + large_blob_key: None, }; assert!(ctap_state .persistent_store diff --git a/src/ctap/response.rs b/src/ctap/response.rs index 87d94cc..346a348 100644 --- a/src/ctap/response.rs +++ b/src/ctap/response.rs @@ -63,6 +63,7 @@ pub struct AuthenticatorMakeCredentialResponse { pub fmt: String, pub auth_data: Vec, pub att_stmt: PackedAttestationStatement, + pub large_blob_key: Option>, } impl From for cbor::Value { @@ -71,12 +72,14 @@ impl From for cbor::Value { fmt, auth_data, att_stmt, + large_blob_key, } = make_credential_response; cbor_map_options! { 0x01 => fmt, 0x02 => auth_data, 0x03 => att_stmt, + 0x05 => large_blob_key, } } } @@ -89,6 +92,7 @@ pub struct AuthenticatorGetAssertionResponse { pub signature: Vec, pub user: Option, pub number_of_credentials: Option, + pub large_blob_key: Option>, } impl From for cbor::Value { @@ -99,6 +103,7 @@ impl From for cbor::Value { signature, user, number_of_credentials, + large_blob_key, } = get_assertion_response; cbor_map_options! { @@ -107,6 +112,7 @@ impl From for cbor::Value { 0x03 => signature, 0x04 => user, 0x05 => number_of_credentials, + 0x07 => large_blob_key, } } } @@ -124,7 +130,8 @@ pub struct AuthenticatorGetInfoResponse { pub max_credential_id_length: Option, pub transports: Option>, pub algorithms: Option>, - pub default_cred_protect: Option, + pub max_serialized_large_blob_array: Option, + pub force_pin_change: Option, pub min_pin_length: u8, pub firmware_version: Option, pub max_cred_blob_length: Option, @@ -145,7 +152,8 @@ impl From for cbor::Value { max_credential_id_length, transports, algorithms, - default_cred_protect, + max_serialized_large_blob_array, + force_pin_change, min_pin_length, firmware_version, max_cred_blob_length, @@ -172,7 +180,8 @@ impl From for cbor::Value { 0x08 => max_credential_id_length, 0x09 => transports.map(|vec| cbor_array_vec!(vec)), 0x0A => algorithms.map(|vec| cbor_array_vec!(vec)), - 0x0C => default_cred_protect.map(|p| p as u64), + 0x0B => max_serialized_large_blob_array, + 0x0C => force_pin_change, 0x0D => min_pin_length as u64, 0x0E => firmware_version, 0x0F => max_cred_blob_length, @@ -297,7 +306,7 @@ mod test { use super::super::data_formats::{PackedAttestationStatement, PublicKeyCredentialType}; use super::super::ES256_CRED_PARAM; use super::*; - use cbor::{cbor_bytes, cbor_map}; + use cbor::{cbor_array, cbor_bytes, cbor_map}; use crypto::rng256::ThreadRng256; #[test] @@ -320,6 +329,7 @@ mod test { fmt: "packed".to_string(), auth_data: vec![0xAD], att_stmt, + large_blob_key: Some(vec![0x1B]), }; let response_cbor: Option = ResponseData::AuthenticatorMakeCredential(make_credential_response).into(); @@ -327,24 +337,50 @@ mod test { 0x01 => "packed", 0x02 => vec![0xAD], 0x03 => cbor_packed_attestation_statement, + 0x05 => vec![0x1B], }; assert_eq!(response_cbor, Some(expected_cbor)); } #[test] fn test_get_assertion_into_cbor() { + let pub_key_cred_descriptor = PublicKeyCredentialDescriptor { + key_type: PublicKeyCredentialType::PublicKey, + key_id: vec![0x2D, 0x2D, 0x2D, 0x2D], + transports: Some(vec![AuthenticatorTransport::Usb]), + }; + let user = PublicKeyCredentialUserEntity { + user_id: vec![0x1D, 0x1D, 0x1D, 0x1D], + user_name: Some("foo".to_string()), + user_display_name: Some("bar".to_string()), + user_icon: Some("example.com/foo/icon.png".to_string()), + }; let get_assertion_response = AuthenticatorGetAssertionResponse { - credential: None, + credential: Some(pub_key_cred_descriptor), auth_data: vec![0xAD], signature: vec![0x51], - user: None, - number_of_credentials: None, + user: Some(user), + number_of_credentials: Some(2), + large_blob_key: Some(vec![0x1B]), }; let response_cbor: Option = ResponseData::AuthenticatorGetAssertion(get_assertion_response).into(); let expected_cbor = cbor_map_options! { + 0x01 => cbor_map! { + "type" => "public-key", + "id" => vec![0x2D, 0x2D, 0x2D, 0x2D], + "transports" => cbor_array!["usb"], + }, 0x02 => vec![0xAD], 0x03 => vec![0x51], + 0x04 => cbor_map! { + "id" => vec![0x1D, 0x1D, 0x1D, 0x1D], + "name" => "foo".to_string(), + "displayName" => "bar".to_string(), + "icon" => "example.com/foo/icon.png".to_string(), + }, + 0x05 => 2, + 0x07 => vec![0x1B], }; assert_eq!(response_cbor, Some(expected_cbor)); } @@ -363,7 +399,8 @@ mod test { max_credential_id_length: None, transports: None, algorithms: None, - default_cred_protect: None, + max_serialized_large_blob_array: None, + force_pin_change: None, min_pin_length: 4, firmware_version: None, max_cred_blob_length: None, @@ -395,7 +432,8 @@ mod test { max_credential_id_length: Some(256), transports: Some(vec![AuthenticatorTransport::Usb]), algorithms: Some(vec![ES256_CRED_PARAM]), - default_cred_protect: Some(CredentialProtectionPolicy::UserVerificationRequired), + max_serialized_large_blob_array: Some(1024), + force_pin_change: Some(false), min_pin_length: 4, firmware_version: Some(0), max_cred_blob_length: Some(1024), @@ -415,7 +453,8 @@ mod test { 0x08 => 256, 0x09 => cbor_array_vec![vec!["usb"]], 0x0A => cbor_array_vec![vec![ES256_CRED_PARAM]], - 0x0C => CredentialProtectionPolicy::UserVerificationRequired as u64, + 0x0B => 1024, + 0x0C => false, 0x0D => 4, 0x0E => 0, 0x0F => 1024, diff --git a/src/ctap/storage.rs b/src/ctap/storage.rs index 43a00c7..b982922 100644 --- a/src/ctap/storage.rs +++ b/src/ctap/storage.rs @@ -756,6 +756,7 @@ mod test { user_name: None, user_icon: None, cred_blob: None, + large_blob_key: None, } } @@ -973,6 +974,7 @@ mod test { user_name: None, user_icon: None, cred_blob: None, + large_blob_key: None, }; assert_eq!(found_credential, Some(expected_credential)); } @@ -995,6 +997,7 @@ mod test { user_name: None, user_icon: None, cred_blob: None, + large_blob_key: None, }; assert!(persistent_store.store_credential(credential).is_ok()); @@ -1321,6 +1324,7 @@ mod test { user_name: None, user_icon: None, cred_blob: None, + large_blob_key: None, }; let serialized = serialize_credential(credential.clone()).unwrap(); let reconstructed = deserialize_credential(&serialized).unwrap();