Merge pull request #274 from kaczmarczyck/extension-large-blobs
largeBlobKey extension
This commit is contained in:
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -282,6 +282,7 @@ pub struct MakeCredentialExtensions {
|
||||
pub cred_protect: Option<CredentialProtectionPolicy>,
|
||||
pub min_pin_length: bool,
|
||||
pub cred_blob: Option<Vec<u8>>,
|
||||
pub large_blob_key: Option<bool>,
|
||||
}
|
||||
|
||||
impl TryFrom<cbor::Value> for MakeCredentialExtensions {
|
||||
@@ -293,6 +294,7 @@ impl TryFrom<cbor::Value> 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<cbor::Value> 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<cbor::Value> for MakeCredentialExtensions {
|
||||
pub struct GetAssertionExtensions {
|
||||
pub hmac_secret: Option<GetAssertionHmacSecretInput>,
|
||||
pub cred_blob: bool,
|
||||
pub large_blob_key: Option<bool>,
|
||||
}
|
||||
|
||||
impl TryFrom<cbor::Value> for GetAssertionExtensions {
|
||||
@@ -327,6 +337,7 @@ impl TryFrom<cbor::Value> for GetAssertionExtensions {
|
||||
let {
|
||||
"credBlob" => cred_blob,
|
||||
"hmac-secret" => hmac_secret,
|
||||
"largeBlobKey" => large_blob_key,
|
||||
} = extract_map(cbor_value)?;
|
||||
}
|
||||
|
||||
@@ -334,9 +345,16 @@ impl TryFrom<cbor::Value> 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<String>,
|
||||
pub user_icon: Option<String>,
|
||||
pub cred_blob: Option<Vec<u8>>,
|
||||
pub large_blob_key: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
// 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<PublicKeyCredentialSource> 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<cbor::Value> 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<cbor::Value> 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<cbor::Value> 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)
|
||||
|
||||
179
src/ctap/mod.rs
179
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<ResponseData, Ctap2StatusCode> {
|
||||
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
|
||||
|
||||
@@ -63,6 +63,7 @@ pub struct AuthenticatorMakeCredentialResponse {
|
||||
pub fmt: String,
|
||||
pub auth_data: Vec<u8>,
|
||||
pub att_stmt: PackedAttestationStatement,
|
||||
pub large_blob_key: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl From<AuthenticatorMakeCredentialResponse> for cbor::Value {
|
||||
@@ -71,12 +72,14 @@ impl From<AuthenticatorMakeCredentialResponse> 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<u8>,
|
||||
pub user: Option<PublicKeyCredentialUserEntity>,
|
||||
pub number_of_credentials: Option<u64>,
|
||||
pub large_blob_key: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl From<AuthenticatorGetAssertionResponse> for cbor::Value {
|
||||
@@ -99,6 +103,7 @@ impl From<AuthenticatorGetAssertionResponse> for cbor::Value {
|
||||
signature,
|
||||
user,
|
||||
number_of_credentials,
|
||||
large_blob_key,
|
||||
} = get_assertion_response;
|
||||
|
||||
cbor_map_options! {
|
||||
@@ -107,6 +112,7 @@ impl From<AuthenticatorGetAssertionResponse> 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<u64>,
|
||||
pub transports: Option<Vec<AuthenticatorTransport>>,
|
||||
pub algorithms: Option<Vec<PublicKeyCredentialParameter>>,
|
||||
pub default_cred_protect: Option<CredentialProtectionPolicy>,
|
||||
pub max_serialized_large_blob_array: Option<u64>,
|
||||
pub force_pin_change: Option<bool>,
|
||||
pub min_pin_length: u8,
|
||||
pub firmware_version: Option<u64>,
|
||||
pub max_cred_blob_length: Option<u64>,
|
||||
@@ -145,7 +152,8 @@ impl From<AuthenticatorGetInfoResponse> 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<AuthenticatorGetInfoResponse> 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<cbor::Value> =
|
||||
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<cbor::Value> =
|
||||
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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user