Merge pull request #158 from ia0/v2_error

Return errors and Vecs from CTAP storage
This commit is contained in:
kaczmarczyck
2020-09-23 15:39:12 +02:00
committed by GitHub
8 changed files with 305 additions and 259 deletions

View File

@@ -1,9 +1,9 @@
c242b0237b93328eea0213411916f84e3ce631c3eea3bf56d6769ae5aa0b8d06 third_party/tock/target/thumbv7em-none-eabi/release/nrf52840dk.bin
9fca5250e6730276bc361661994495c9c9c707fb46d61cee75410bef5549dd62 target/nrf52840dk_merged.hex
7c734d21395170b977ea2cb0df1903bd7caf39a0a8113c935d1800e0932637de target/nrf52840dk_merged.hex
259a54fae11cb8f1306051354825d19223ba4c33f3adfdb2d3e9d9efc7229302 third_party/tock/target/thumbv7em-none-eabi/release/nrf52840_dongle.bin
6d4b85a31b8f16962478f6cb72ccec94bbed4f6e955484dc8e73405d8fcd27ea target/nrf52840_dongle_merged.hex
d0b4c27f419e0bf412437211e0baa4e80c8de90bff384ee2a761d208b767f579 target/nrf52840_dongle_merged.hex
49d647374a1448c9c3107758b6e0a1cabdf008c3e53524a3b047a16c27b8af29 third_party/tock/target/thumbv7em-none-eabi/release/nrf52840_dongle_dfu.bin
bf2407823be0550c7d606d97e240d3dc8b2572225f2f55383f903bbf58c4f26a target/nrf52840_dongle_dfu_merged.hex
12a2966faa2d3949e195026397f4d3c68c87b48ffb43e26333b673377e7225c2 target/nrf52840_dongle_dfu_merged.hex
06a38a0d6d356145467a73c765e28a945878f663664016f888393207097bfe10 third_party/tock/target/thumbv7em-none-eabi/release/nrf52840_mdk_dfu.bin
1dbc1c8aacfc2e996e8b318b0ef8f64db385e1b887f092ed6cff4d5425542836 target/nrf52840_mdk_dfu_merged.hex
09b7ff9a4d44cd675c4ee581d43713addb5f3931ea9e7c8d71d448e0539f7070 target/tab/ctap2.tab
93c0af94d9bc497edd07c0cdf9c19a2bef288d299cd0fc91e6133fe0097e72dc target/nrf52840_mdk_dfu_merged.hex
d16de1ade1c747deae9be7a4a33da57a525cb8d6dbad876afe6ad0714f2b9088 target/tab/ctap2.tab

View File

@@ -1,9 +1,9 @@
dab66677002854f6abfb1745d24d1c9e4e93c9bba286b14024e42cb8f2b99b9a third_party/tock/target/thumbv7em-none-eabi/release/nrf52840dk.bin
316b393bd4b18fd154e9203b15f6abb5d27a3ac2c98f19c95b80033091aeccb6 target/nrf52840dk_merged.hex
d2dac96ab8fb17e66e7539e071e6f5c10b462803c15e952da996072376856b57 target/nrf52840dk_merged.hex
70cf9f6c1d6b2149c1ca88664073a92e092215faaf551310f8bfd8e171d3d3a1 third_party/tock/target/thumbv7em-none-eabi/release/nrf52840_dongle.bin
a03a6b5239703c38c0eee7e76ab7b1b8a3813abdd7d84f51c495b8889d4b9605 target/nrf52840_dongle_merged.hex
0473486da4acc0c6d8adc5ba697cc29ef45ba0a2341f46bbc09e711d540e0b15 target/nrf52840_dongle_merged.hex
f550b6493c8cd401d191306510de50ddd6d4c709dcf90ea1bd3a9f1bcffd11b5 third_party/tock/target/thumbv7em-none-eabi/release/nrf52840_dongle_dfu.bin
249ed37202b5ce102ae4c112358e4f4bcd275a19ac6dea2c474920342bcf6e79 target/nrf52840_dongle_dfu_merged.hex
8dc4c3c5d426b781295012b117d69ff9c092302b5cde7fe97782e5708e2f7270 target/nrf52840_dongle_dfu_merged.hex
1baaf518a74c6077cb936d9cf178b6dd0232e7562fa56174886b05b77886cc32 third_party/tock/target/thumbv7em-none-eabi/release/nrf52840_mdk_dfu.bin
93e23a8e9964bf22794c2f453738af68ae348456fdd898cddfe8e2b3ada8bf79 target/nrf52840_mdk_dfu_merged.hex
0fe175c3cf5a77a9f6df8f5aa3f82715986536e2f737aee44cb680a1e801dac4 target/tab/ctap2.tab
b9ef7c68029b3c4e72749912a4a8cce89d08b96bc24d2082dcc7275718313c3d target/nrf52840_mdk_dfu_merged.hex
cbfa52e5e1dbdf7f6528e1dafdae74b18ee755c66bed533e2124d48923594fdd target/tab/ctap2.tab

View File

@@ -6,8 +6,8 @@ Min RAM size from segments in ELF: 20 bytes
Number of writeable flash regions: 0
Adding .crt0_header section. Offset: 64 (0x40). Length: 64 (0x40) bytes.
Entry point is in .text section
Adding .text section. Offset: 128 (0x80). Length: 186112 (0x2d700) bytes.
Adding .stack section. Offset: 186240 (0x2d780). Length: 16384 (0x4000) bytes.
Adding .text section. Offset: 128 (0x80). Length: 187312 (0x2dbb0) bytes.
Adding .stack section. Offset: 187440 (0x2dc30). Length: 16384 (0x4000) bytes.
Searching for .rel.X sections to add.
TBF Header:
version: 2 0x2
@@ -30,8 +30,8 @@ Min RAM size from segments in ELF: 20 bytes
Number of writeable flash regions: 0
Adding .crt0_header section. Offset: 64 (0x40). Length: 64 (0x40) bytes.
Entry point is in .text section
Adding .text section. Offset: 128 (0x80). Length: 186112 (0x2d700) bytes.
Adding .stack section. Offset: 186240 (0x2d780). Length: 16384 (0x4000) bytes.
Adding .text section. Offset: 128 (0x80). Length: 187312 (0x2dbb0) bytes.
Adding .stack section. Offset: 187440 (0x2dc30). Length: 16384 (0x4000) bytes.
Searching for .rel.X sections to add.
TBF Header:
version: 2 0x2
@@ -54,8 +54,8 @@ Min RAM size from segments in ELF: 20 bytes
Number of writeable flash regions: 0
Adding .crt0_header section. Offset: 64 (0x40). Length: 64 (0x40) bytes.
Entry point is in .text section
Adding .text section. Offset: 128 (0x80). Length: 186112 (0x2d700) bytes.
Adding .stack section. Offset: 186240 (0x2d780). Length: 16384 (0x4000) bytes.
Adding .text section. Offset: 128 (0x80). Length: 187312 (0x2dbb0) bytes.
Adding .stack section. Offset: 187440 (0x2dc30). Length: 16384 (0x4000) bytes.
Searching for .rel.X sections to add.
TBF Header:
version: 2 0x2
@@ -78,8 +78,8 @@ Min RAM size from segments in ELF: 20 bytes
Number of writeable flash regions: 0
Adding .crt0_header section. Offset: 64 (0x40). Length: 64 (0x40) bytes.
Entry point is in .text section
Adding .text section. Offset: 128 (0x80). Length: 186112 (0x2d700) bytes.
Adding .stack section. Offset: 186240 (0x2d780). Length: 16384 (0x4000) bytes.
Adding .text section. Offset: 128 (0x80). Length: 187312 (0x2dbb0) bytes.
Adding .stack section. Offset: 187440 (0x2dc30). Length: 16384 (0x4000) bytes.
Searching for .rel.X sections to add.
TBF Header:
version: 2 0x2

View File

@@ -6,8 +6,8 @@ Min RAM size from segments in ELF: 20 bytes
Number of writeable flash regions: 0
Adding .crt0_header section. Offset: 64 (0x40). Length: 64 (0x40) bytes.
Entry point is in .text section
Adding .text section. Offset: 128 (0x80). Length: 186080 (0x2d6e0) bytes.
Adding .stack section. Offset: 186208 (0x2d760). Length: 16384 (0x4000) bytes.
Adding .text section. Offset: 128 (0x80). Length: 187280 (0x2db90) bytes.
Adding .stack section. Offset: 187408 (0x2dc10). Length: 16384 (0x4000) bytes.
Searching for .rel.X sections to add.
TBF Header:
version: 2 0x2
@@ -30,8 +30,8 @@ Min RAM size from segments in ELF: 20 bytes
Number of writeable flash regions: 0
Adding .crt0_header section. Offset: 64 (0x40). Length: 64 (0x40) bytes.
Entry point is in .text section
Adding .text section. Offset: 128 (0x80). Length: 186080 (0x2d6e0) bytes.
Adding .stack section. Offset: 186208 (0x2d760). Length: 16384 (0x4000) bytes.
Adding .text section. Offset: 128 (0x80). Length: 187280 (0x2db90) bytes.
Adding .stack section. Offset: 187408 (0x2dc10). Length: 16384 (0x4000) bytes.
Searching for .rel.X sections to add.
TBF Header:
version: 2 0x2
@@ -54,8 +54,8 @@ Min RAM size from segments in ELF: 20 bytes
Number of writeable flash regions: 0
Adding .crt0_header section. Offset: 64 (0x40). Length: 64 (0x40) bytes.
Entry point is in .text section
Adding .text section. Offset: 128 (0x80). Length: 186080 (0x2d6e0) bytes.
Adding .stack section. Offset: 186208 (0x2d760). Length: 16384 (0x4000) bytes.
Adding .text section. Offset: 128 (0x80). Length: 187280 (0x2db90) bytes.
Adding .stack section. Offset: 187408 (0x2dc10). Length: 16384 (0x4000) bytes.
Searching for .rel.X sections to add.
TBF Header:
version: 2 0x2
@@ -78,8 +78,8 @@ Min RAM size from segments in ELF: 20 bytes
Number of writeable flash regions: 0
Adding .crt0_header section. Offset: 64 (0x40). Length: 64 (0x40) bytes.
Entry point is in .text section
Adding .text section. Offset: 128 (0x80). Length: 186080 (0x2d6e0) bytes.
Adding .stack section. Offset: 186208 (0x2d760). Length: 16384 (0x4000) bytes.
Adding .text section. Offset: 128 (0x80). Length: 187280 (0x2db90) bytes.
Adding .stack section. Offset: 187408 (0x2dc10). Length: 16384 (0x4000) bytes.
Searching for .rel.X sections to add.
TBF Header:
version: 2 0x2

View File

@@ -286,7 +286,9 @@ impl Ctap1Command {
{
let sk = crypto::ecdsa::SecKey::gensk(ctap_state.rng);
let pk = sk.genpk();
let key_handle = ctap_state.encrypt_key_handle(sk, &application);
let key_handle = ctap_state
.encrypt_key_handle(sk, &application)
.map_err(|_| Ctap1StatusCode::SW_VENDOR_KEY_HANDLE_TOO_LONG)?;
if key_handle.len() > 0xFF {
// This is just being defensive with unreachable code.
return Err(Ctap1StatusCode::SW_VENDOR_KEY_HANDLE_TOO_LONG);
@@ -341,14 +343,19 @@ impl Ctap1Command {
R: Rng256,
CheckUserPresence: Fn(ChannelID) -> Result<(), Ctap2StatusCode>,
{
let credential_source = ctap_state.decrypt_credential_source(key_handle, &application);
let credential_source = ctap_state
.decrypt_credential_source(key_handle, &application)
.map_err(|_| Ctap1StatusCode::SW_WRONG_DATA)?;
if let Some(credential_source) = credential_source {
if flags == Ctap1Flags::CheckOnly {
return Err(Ctap1StatusCode::SW_CONDITIONS_NOT_SATISFIED);
}
ctap_state.increment_global_signature_counter();
ctap_state
.increment_global_signature_counter()
.map_err(|_| Ctap1StatusCode::SW_WRONG_DATA)?;
let mut signature_data = ctap_state
.generate_auth_data(&application, Ctap1Command::USER_PRESENCE_INDICATOR_BYTE);
.generate_auth_data(&application, Ctap1Command::USER_PRESENCE_INDICATOR_BYTE)
.map_err(|_| Ctap1StatusCode::SW_WRONG_DATA)?;
signature_data.extend(&challenge);
let signature = credential_source
.private_key
@@ -435,6 +442,7 @@ mod test {
response[67..67 + ENCRYPTED_CREDENTIAL_ID_SIZE].to_vec(),
&application
)
.unwrap()
.is_some());
const CERT_START: usize = 67 + ENCRYPTED_CREDENTIAL_ID_SIZE;
assert_eq!(
@@ -485,7 +493,7 @@ mod test {
let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
let key_handle = ctap_state.encrypt_key_handle(sk, &application);
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
let message = create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
let response = Ctap1Command::process_command(&message, &mut ctap_state, START_CLOCK_VALUE);
@@ -501,7 +509,7 @@ mod test {
let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
let key_handle = ctap_state.encrypt_key_handle(sk, &application);
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
let application = [0x55; 32];
let message = create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
@@ -518,7 +526,7 @@ mod test {
let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
let key_handle = ctap_state.encrypt_key_handle(sk, &application);
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
let mut message =
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
@@ -542,7 +550,7 @@ mod test {
let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
let key_handle = ctap_state.encrypt_key_handle(sk, &application);
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
let mut message =
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
message[0] = 0xEE;
@@ -560,7 +568,7 @@ mod test {
let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
let key_handle = ctap_state.encrypt_key_handle(sk, &application);
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
let mut message =
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
message[1] = 0xEE;
@@ -578,7 +586,7 @@ mod test {
let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
let key_handle = ctap_state.encrypt_key_handle(sk, &application);
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
let mut message =
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
message[2] = 0xEE;
@@ -596,7 +604,7 @@ mod test {
let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
let key_handle = ctap_state.encrypt_key_handle(sk, &application);
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
let message =
create_authenticate_message(&application, Ctap1Flags::EnforceUpAndSign, &key_handle);
@@ -621,7 +629,7 @@ mod test {
let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
let key_handle = ctap_state.encrypt_key_handle(sk, &application);
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
let message = create_authenticate_message(
&application,
Ctap1Flags::DontEnforceUpAndSign,

View File

@@ -174,10 +174,11 @@ where
}
}
pub fn increment_global_signature_counter(&mut self) {
pub fn increment_global_signature_counter(&mut self) -> Result<(), Ctap2StatusCode> {
if USE_SIGNATURE_COUNTER {
self.persistent_store.incr_global_signature_counter();
self.persistent_store.incr_global_signature_counter()?;
}
Ok(())
}
// Encrypts the private key and relying party ID hash into a credential ID. Other
@@ -188,9 +189,9 @@ where
&mut self,
private_key: crypto::ecdsa::SecKey,
application: &[u8; 32],
) -> Vec<u8> {
let master_keys = self.persistent_store.master_keys();
let aes_enc_key = crypto::aes256::EncryptionKey::new(master_keys.encryption);
) -> Result<Vec<u8>, Ctap2StatusCode> {
let master_keys = self.persistent_store.master_keys()?;
let aes_enc_key = crypto::aes256::EncryptionKey::new(&master_keys.encryption);
let mut sk_bytes = [0; 32];
private_key.to_bytes(&mut sk_bytes);
let mut iv = [0; 16];
@@ -208,9 +209,9 @@ where
for b in &blocks {
encrypted_id.extend(b);
}
let id_hmac = hmac_256::<Sha256>(master_keys.hmac, &encrypted_id[..]);
let id_hmac = hmac_256::<Sha256>(&master_keys.hmac, &encrypted_id[..]);
encrypted_id.extend(&id_hmac);
encrypted_id
Ok(encrypted_id)
}
// Decrypts a credential ID and writes the private key into a PublicKeyCredentialSource.
@@ -220,20 +221,20 @@ where
&self,
credential_id: Vec<u8>,
rp_id_hash: &[u8],
) -> Option<PublicKeyCredentialSource> {
) -> Result<Option<PublicKeyCredentialSource>, Ctap2StatusCode> {
if credential_id.len() != ENCRYPTED_CREDENTIAL_ID_SIZE {
return None;
return Ok(None);
}
let master_keys = self.persistent_store.master_keys();
let master_keys = self.persistent_store.master_keys()?;
let payload_size = ENCRYPTED_CREDENTIAL_ID_SIZE - 32;
if !verify_hmac_256::<Sha256>(
master_keys.hmac,
&master_keys.hmac,
&credential_id[..payload_size],
array_ref![credential_id, payload_size, 32],
) {
return None;
return Ok(None);
}
let aes_enc_key = crypto::aes256::EncryptionKey::new(master_keys.encryption);
let aes_enc_key = crypto::aes256::EncryptionKey::new(&master_keys.encryption);
let aes_dec_key = crypto::aes256::DecryptionKey::new(&aes_enc_key);
let mut iv = [0; 16];
iv.copy_from_slice(&credential_id[..16]);
@@ -251,11 +252,11 @@ where
decrypted_rp_id_hash[16..].clone_from_slice(&blocks[3]);
if rp_id_hash != decrypted_rp_id_hash {
return None;
return Ok(None);
}
let sk_option = crypto::ecdsa::SecKey::from_bytes(&decrypted_sk);
sk_option.map(|sk| PublicKeyCredentialSource {
Ok(sk_option.map(|sk| PublicKeyCredentialSource {
key_type: PublicKeyCredentialType::PublicKey,
credential_id,
private_key: sk,
@@ -264,7 +265,7 @@ where
other_ui: None,
cred_random: None,
cred_protect_policy: None,
})
}))
}
pub fn process_command(&mut self, command_cbor: &[u8], cid: ChannelID) -> Vec<u8> {
@@ -338,7 +339,7 @@ where
if let Some(auth_param) = &pin_uv_auth_param {
// This case was added in FIDO 2.1.
if auth_param.is_empty() {
if self.persistent_store.pin_hash().is_none() {
if self.persistent_store.pin_hash()?.is_none() {
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_NOT_SET);
} else {
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID);
@@ -389,7 +390,7 @@ where
for cred_desc in exclude_list {
if self
.persistent_store
.find_credential(&rp_id, &cred_desc.key_id, pin_uv_auth_param.is_none())
.find_credential(&rp_id, &cred_desc.key_id, pin_uv_auth_param.is_none())?
.is_some()
{
// Perform this check, so bad actors can't brute force exclude_list
@@ -405,7 +406,7 @@ where
let ed_flag = if has_extension_output { ED_FLAG } else { 0 };
let flags = match pin_uv_auth_param {
Some(pin_auth) => {
if self.persistent_store.pin_hash().is_none() {
if self.persistent_store.pin_hash()?.is_none() {
// Specification is unclear, could be CTAP2_ERR_INVALID_OPTION.
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_NOT_SET);
}
@@ -424,7 +425,7 @@ where
UP_FLAG | UV_FLAG | AT_FLAG | ed_flag
}
None => {
if self.persistent_store.pin_hash().is_some() {
if self.persistent_store.pin_hash()?.is_some() {
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_REQUIRED);
}
if options.uv {
@@ -459,11 +460,11 @@ where
self.persistent_store.store_credential(credential_source)?;
random_id
} else {
self.encrypt_key_handle(sk.clone(), &rp_id_hash)
self.encrypt_key_handle(sk.clone(), &rp_id_hash)?
};
let mut auth_data = self.generate_auth_data(&rp_id_hash, flags);
auth_data.extend(self.persistent_store.aaguid()?);
let mut auth_data = self.generate_auth_data(&rp_id_hash, flags)?;
auth_data.extend(&self.persistent_store.aaguid()?);
// The length is fixed to 0x20 or 0x70 and fits one byte.
if credential_id.len() > 0xFF {
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_RESPONSE_TOO_LONG);
@@ -541,7 +542,7 @@ where
if let Some(auth_param) = &pin_uv_auth_param {
// This case was added in FIDO 2.1.
if auth_param.is_empty() {
if self.persistent_store.pin_hash().is_none() {
if self.persistent_store.pin_hash()?.is_none() {
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_NOT_SET);
} else {
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID);
@@ -560,7 +561,7 @@ where
// This case was added in FIDO 2.1.
if pin_uv_auth_param == Some(vec![]) {
if self.persistent_store.pin_hash().is_none() {
if self.persistent_store.pin_hash()?.is_none() {
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_NOT_SET);
} else {
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID);
@@ -589,7 +590,7 @@ where
let has_uv = pin_uv_auth_param.is_some();
let mut flags = match pin_uv_auth_param {
Some(pin_auth) => {
if self.persistent_store.pin_hash().is_none() {
if self.persistent_store.pin_hash()?.is_none() {
// Specification is unclear, could be CTAP2_ERR_UNSUPPORTED_OPTION.
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_NOT_SET);
}
@@ -631,14 +632,16 @@ where
&rp_id,
&allowed_credential.key_id,
!has_uv,
) {
)? {
Some(credential) => {
found_credentials.push(credential);
}
None => {
if decrypted_credential.is_none() {
decrypted_credential = self
.decrypt_credential_source(allowed_credential.key_id, &rp_id_hash);
decrypted_credential = self.decrypt_credential_source(
allowed_credential.key_id,
&rp_id_hash,
)?;
}
}
}
@@ -646,7 +649,7 @@ where
found_credentials
} else {
// TODO(kaczmarczyck) use GetNextAssertion
self.persistent_store.filter_credential(&rp_id, !has_uv)
self.persistent_store.filter_credential(&rp_id, !has_uv)?
};
let credential = if let Some(credential) = credentials.first() {
@@ -661,9 +664,9 @@ where
(self.check_user_presence)(cid)?;
}
self.increment_global_signature_counter();
self.increment_global_signature_counter()?;
let mut auth_data = self.generate_auth_data(&rp_id_hash, flags);
let mut auth_data = self.generate_auth_data(&rp_id_hash, flags)?;
// Process extensions.
if let Some(hmac_secret_input) = hmac_secret_input {
let encrypted_output = self
@@ -716,7 +719,7 @@ where
options_map.insert(String::from("up"), true);
options_map.insert(
String::from("clientPin"),
self.persistent_store.pin_hash().is_some(),
self.persistent_store.pin_hash()?.is_some(),
);
Ok(ResponseData::AuthenticatorGetInfo(
AuthenticatorGetInfoResponse {
@@ -726,7 +729,7 @@ where
String::from(FIDO2_VERSION_STRING),
],
extensions: Some(vec![String::from("hmac-secret")]),
aaguid: *self.persistent_store.aaguid()?,
aaguid: self.persistent_store.aaguid()?,
options: Some(options_map),
max_msg_size: Some(1024),
pin_protocols: Some(vec![
@@ -744,7 +747,7 @@ where
algorithms: Some(vec![ES256_CRED_PARAM]),
default_cred_protect: DEFAULT_CRED_PROTECT,
#[cfg(feature = "with_ctap2_1")]
min_pin_length: self.persistent_store.min_pin_length(),
min_pin_length: self.persistent_store.min_pin_length()?,
#[cfg(feature = "with_ctap2_1")]
firmware_version: None,
},
@@ -769,7 +772,7 @@ where
}
(self.check_user_presence)(cid)?;
self.persistent_store.reset(self.rng);
self.persistent_store.reset(self.rng)?;
self.pin_protocol_v1.reset(self.rng);
#[cfg(feature = "with_ctap1")]
{
@@ -791,7 +794,11 @@ where
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_COMMAND)
}
pub fn generate_auth_data(&self, rp_id_hash: &[u8], flag_byte: u8) -> Vec<u8> {
pub fn generate_auth_data(
&self,
rp_id_hash: &[u8],
flag_byte: u8,
) -> Result<Vec<u8>, Ctap2StatusCode> {
let mut auth_data = vec![];
auth_data.extend(rp_id_hash);
auth_data.push(flag_byte);
@@ -800,10 +807,10 @@ where
let mut signature_counter = [0u8; 4];
BigEndian::write_u32(
&mut signature_counter,
self.persistent_store.global_signature_counter(),
self.persistent_store.global_signature_counter()?,
);
auth_data.extend(&signature_counter);
auth_data
Ok(auth_data)
}
}
@@ -846,7 +853,7 @@ mod test {
0x02, 0x81, 0x6B, 0x68, 0x6D, 0x61, 0x63, 0x2D, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74,
0x03, 0x50,
]);
expected_response.extend(ctap_state.persistent_store.aaguid().unwrap());
expected_response.extend(&ctap_state.persistent_store.aaguid().unwrap());
expected_response.extend(&[
0x04, 0xA3, 0x62, 0x72, 0x6B, 0xF5, 0x62, 0x75, 0x70, 0xF5, 0x69, 0x63, 0x6C, 0x69,
0x65, 0x6E, 0x74, 0x50, 0x69, 0x6E, 0xF4, 0x05, 0x19, 0x04, 0x00, 0x06, 0x81, 0x01,
@@ -945,7 +952,7 @@ mod test {
0x34, 0xE2, 0x75, 0x1E, 0x68, 0x2F, 0xAB, 0x9F, 0x2D, 0x30, 0xAB, 0x13, 0xD2,
0x12, 0x55, 0x86, 0xCE, 0x19, 0x47, 0x41, 0x00, 0x00, 0x00, 0x00,
];
expected_auth_data.extend(ctap_state.persistent_store.aaguid().unwrap());
expected_auth_data.extend(&ctap_state.persistent_store.aaguid().unwrap());
expected_auth_data.extend(&[0x00, 0x20]);
assert_eq!(
auth_data[0..expected_auth_data.len()],
@@ -982,7 +989,7 @@ mod test {
0x34, 0xE2, 0x75, 0x1E, 0x68, 0x2F, 0xAB, 0x9F, 0x2D, 0x30, 0xAB, 0x13, 0xD2,
0x12, 0x55, 0x86, 0xCE, 0x19, 0x47, 0x41, 0x00, 0x00, 0x00, 0x00,
];
expected_auth_data.extend(ctap_state.persistent_store.aaguid().unwrap());
expected_auth_data.extend(&ctap_state.persistent_store.aaguid().unwrap());
expected_auth_data.extend(&[0x00, ENCRYPTED_CREDENTIAL_ID_SIZE as u8]);
assert_eq!(
auth_data[0..expected_auth_data.len()],
@@ -1060,6 +1067,7 @@ mod test {
let stored_credential = ctap_state
.persistent_store
.filter_credential("example.com", false)
.unwrap()
.pop()
.unwrap();
let credential_id = stored_credential.credential_id;
@@ -1084,6 +1092,7 @@ mod test {
let stored_credential = ctap_state
.persistent_store
.filter_credential("example.com", false)
.unwrap()
.pop()
.unwrap();
let credential_id = stored_credential.credential_id;
@@ -1125,7 +1134,7 @@ mod test {
0x34, 0xE2, 0x75, 0x1E, 0x68, 0x2F, 0xAB, 0x9F, 0x2D, 0x30, 0xAB, 0x13, 0xD2,
0x12, 0x55, 0x86, 0xCE, 0x19, 0x47, 0xC1, 0x00, 0x00, 0x00, 0x00,
];
expected_auth_data.extend(ctap_state.persistent_store.aaguid().unwrap());
expected_auth_data.extend(&ctap_state.persistent_store.aaguid().unwrap());
expected_auth_data.extend(&[0x00, 0x20]);
assert_eq!(
auth_data[0..expected_auth_data.len()],
@@ -1378,12 +1387,12 @@ mod test {
.persistent_store
.store_credential(credential_source)
.is_ok());
assert!(ctap_state.persistent_store.count_credentials() > 0);
assert!(ctap_state.persistent_store.count_credentials().unwrap() > 0);
let reset_reponse = ctap_state.process_command(&[0x07], DUMMY_CHANNEL_ID);
let expected_response = vec![0x00];
assert_eq!(reset_reponse, expected_response);
assert!(ctap_state.persistent_store.count_credentials() == 0);
assert!(ctap_state.persistent_store.count_credentials().unwrap() == 0);
}
#[test]
@@ -1422,9 +1431,12 @@ mod test {
// Usually, the relying party ID or its hash is provided by the client.
// We are not testing the correctness of our SHA256 here, only if it is checked.
let rp_id_hash = [0x55; 32];
let encrypted_id = ctap_state.encrypt_key_handle(private_key.clone(), &rp_id_hash);
let encrypted_id = ctap_state
.encrypt_key_handle(private_key.clone(), &rp_id_hash)
.unwrap();
let decrypted_source = ctap_state
.decrypt_credential_source(encrypted_id, &rp_id_hash)
.unwrap()
.unwrap();
assert_eq!(private_key, decrypted_source.private_key);
@@ -1439,12 +1451,15 @@ mod test {
// Same as above.
let rp_id_hash = [0x55; 32];
let encrypted_id = ctap_state.encrypt_key_handle(private_key, &rp_id_hash);
let encrypted_id = ctap_state
.encrypt_key_handle(private_key, &rp_id_hash)
.unwrap();
for i in 0..encrypted_id.len() {
let mut modified_id = encrypted_id.clone();
modified_id[i] ^= 0x01;
assert!(ctap_state
.decrypt_credential_source(modified_id, &rp_id_hash)
.unwrap()
.is_none());
}
}

View File

@@ -148,7 +148,7 @@ fn check_and_store_new_pin(
.ok_or(Ctap2StatusCode::CTAP2_ERR_PIN_POLICY_VIOLATION)?;
#[cfg(feature = "with_ctap2_1")]
let min_pin_length = persistent_store.min_pin_length() as usize;
let min_pin_length = persistent_store.min_pin_length()? as usize;
#[cfg(not(feature = "with_ctap2_1"))]
let min_pin_length = 4;
if pin.len() < min_pin_length || pin.len() == PIN_PADDED_LENGTH {
@@ -157,7 +157,7 @@ fn check_and_store_new_pin(
}
let mut pin_hash = [0u8; 16];
pin_hash.copy_from_slice(&Sha256::hash(&pin[..])[..16]);
persistent_store.set_pin_hash(&pin_hash);
persistent_store.set_pin_hash(&pin_hash)?;
Ok(())
}
@@ -210,16 +210,12 @@ impl PinProtocolV1 {
aes_dec_key: &crypto::aes256::DecryptionKey,
pin_hash_enc: Vec<u8>,
) -> Result<(), Ctap2StatusCode> {
match persistent_store.pin_hash() {
match persistent_store.pin_hash()? {
Some(pin_hash) => {
if self.consecutive_pin_mismatches >= 3 {
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_BLOCKED);
}
// We need to copy the pin hash, because decrementing the pin retries below mutably
// borrows from the same persistent_store reference, and therefore invalidates the
// pin_hash reference.
let pin_hash = pin_hash.clone();
persistent_store.decr_pin_retries();
persistent_store.decr_pin_retries()?;
if pin_hash_enc.len() != PIN_AUTH_LENGTH {
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID);
}
@@ -231,7 +227,7 @@ impl PinProtocolV1 {
if !bool::from(pin_hash.ct_eq(&blocks[0])) {
self.key_agreement_key = crypto::ecdh::SecKey::gensk(rng);
if persistent_store.pin_retries() == 0 {
if persistent_store.pin_retries()? == 0 {
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_BLOCKED);
}
self.consecutive_pin_mismatches += 1;
@@ -244,7 +240,7 @@ impl PinProtocolV1 {
// This status code is not explicitly mentioned in the specification.
None => return Err(Ctap2StatusCode::CTAP2_ERR_PIN_REQUIRED),
}
persistent_store.reset_pin_retries();
persistent_store.reset_pin_retries()?;
self.consecutive_pin_mismatches = 0;
Ok(())
}
@@ -276,7 +272,7 @@ impl PinProtocolV1 {
Ok(AuthenticatorClientPinResponse {
key_agreement: None,
pin_token: None,
retries: Some(persistent_store.pin_retries() as u64),
retries: Some(persistent_store.pin_retries()? as u64),
})
}
@@ -296,13 +292,13 @@ impl PinProtocolV1 {
pin_auth: Vec<u8>,
new_pin_enc: Vec<u8>,
) -> Result<(), Ctap2StatusCode> {
if persistent_store.pin_hash().is_some() {
if persistent_store.pin_hash()?.is_some() {
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID);
}
let pin_decryption_key =
self.exchange_decryption_key(key_agreement, &pin_auth, &new_pin_enc)?;
check_and_store_new_pin(persistent_store, &pin_decryption_key, new_pin_enc)?;
persistent_store.reset_pin_retries();
persistent_store.reset_pin_retries()?;
Ok(())
}
@@ -315,7 +311,7 @@ impl PinProtocolV1 {
new_pin_enc: Vec<u8>,
pin_hash_enc: Vec<u8>,
) -> Result<(), Ctap2StatusCode> {
if persistent_store.pin_retries() == 0 {
if persistent_store.pin_retries()? == 0 {
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_BLOCKED);
}
let mut auth_param_data = new_pin_enc.clone();
@@ -336,7 +332,7 @@ impl PinProtocolV1 {
key_agreement: CoseKey,
pin_hash_enc: Vec<u8>,
) -> Result<AuthenticatorClientPinResponse, Ctap2StatusCode> {
if persistent_store.pin_retries() == 0 {
if persistent_store.pin_retries()? == 0 {
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_BLOCKED);
}
let pk: crypto::ecdh::PubKey = CoseKey::try_into(key_agreement)?;
@@ -412,7 +408,7 @@ impl PinProtocolV1 {
if min_pin_length_rp_ids.is_some() {
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION);
}
if persistent_store.pin_hash().is_some() {
if persistent_store.pin_hash()?.is_some() {
match pin_auth {
Some(pin_auth) => {
if self.consecutive_pin_mismatches >= 3 {
@@ -438,10 +434,10 @@ impl PinProtocolV1 {
None => return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID),
};
}
if min_pin_length < persistent_store.min_pin_length() {
if min_pin_length < persistent_store.min_pin_length()? {
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_POLICY_VIOLATION);
}
persistent_store.set_min_pin_length(min_pin_length);
persistent_store.set_min_pin_length(min_pin_length)?;
// TODO(kaczmarczyck) commented code is useful for the extension
// https://github.com/google/OpenSK/issues/129
// if let Some(min_pin_length_rp_ids) = min_pin_length_rp_ids {
@@ -650,7 +646,7 @@ mod test {
pin[3] = 0x34;
let mut pin_hash = [0u8; 16];
pin_hash.copy_from_slice(&Sha256::hash(&pin[..])[..16]);
persistent_store.set_pin_hash(&pin_hash);
persistent_store.set_pin_hash(&pin_hash).unwrap();
}
// Fails on PINs bigger than 64 bytes.
@@ -704,7 +700,7 @@ mod test {
0x01, 0xD9, 0x88, 0x40, 0x50, 0xBB, 0xD0, 0x7A, 0x23, 0x1A, 0xEB, 0x69, 0xD8, 0x36,
0xC4, 0x12,
];
persistent_store.set_pin_hash(&pin_hash);
persistent_store.set_pin_hash(&pin_hash).unwrap();
let shared_secret = [0x88; 32];
let aes_enc_key = crypto::aes256::EncryptionKey::new(&shared_secret);
let aes_dec_key = crypto::aes256::DecryptionKey::new(&aes_enc_key);
@@ -782,7 +778,7 @@ mod test {
let expected_response = Ok(AuthenticatorClientPinResponse {
key_agreement: None,
pin_token: None,
retries: Some(persistent_store.pin_retries() as u64),
retries: Some(persistent_store.pin_retries().unwrap() as u64),
});
assert_eq!(
pin_protocol_v1.process_get_pin_retries(&mut persistent_store),
@@ -866,8 +862,8 @@ mod test {
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
);
while persistent_store.pin_retries() > 0 {
persistent_store.decr_pin_retries();
while persistent_store.pin_retries().unwrap() > 0 {
persistent_store.decr_pin_retries().unwrap();
}
assert_eq!(
pin_protocol_v1.process_change_pin(
@@ -999,7 +995,7 @@ mod test {
Some(pin_auth.clone()),
);
assert_eq!(response, Ok(()));
assert_eq!(persistent_store.min_pin_length(), min_pin_length);
assert_eq!(persistent_store.min_pin_length().unwrap(), min_pin_length);
let response = pin_protocol_v1.process_set_min_pin_length(
&mut persistent_store,
7,
@@ -1010,7 +1006,7 @@ mod test {
response,
Err(Ctap2StatusCode::CTAP2_ERR_PIN_POLICY_VIOLATION)
);
assert_eq!(persistent_store.min_pin_length(), min_pin_length);
assert_eq!(persistent_store.min_pin_length().unwrap(), min_pin_length);
}
#[test]
@@ -1133,16 +1129,16 @@ mod test {
),
];
for (pin, result) in test_cases {
let old_pin_hash = persistent_store.pin_hash().cloned();
let old_pin_hash = persistent_store.pin_hash().unwrap();
let new_pin_enc = encrypt_pin(&shared_secret, pin);
assert_eq!(
check_and_store_new_pin(&mut persistent_store, &aes_dec_key, new_pin_enc),
result
);
if result.is_ok() {
assert_ne!(old_pin_hash.as_ref(), persistent_store.pin_hash());
assert_ne!(old_pin_hash, persistent_store.pin_hash().unwrap());
} else {
assert_eq!(old_pin_hash.as_ref(), persistent_store.pin_hash());
assert_eq!(old_pin_hash, persistent_store.pin_hash().unwrap());
}
}
}

View File

@@ -105,9 +105,9 @@ enum Key {
MinPinLengthRpIds,
}
pub struct MasterKeys<'a> {
pub encryption: &'a [u8; 32],
pub hmac: &'a [u8; 32],
pub struct MasterKeys {
pub encryption: [u8; 32],
pub hmac: [u8; 32],
}
struct Config;
@@ -246,13 +246,16 @@ impl PersistentStore {
rp_id: &str,
credential_id: &[u8],
check_cred_protect: bool,
) -> Option<PublicKeyCredentialSource> {
) -> Result<Option<PublicKeyCredentialSource>, Ctap2StatusCode> {
let key = Key::Credential {
rp_id: Some(rp_id.into()),
credential_id: Some(credential_id.into()),
user_handle: None,
};
let (_, entry) = self.store.find_one(&key)?;
let entry = match self.store.find_one(&key) {
None => return Ok(None),
Some((_, entry)) => entry,
};
debug_assert_eq!(entry.tag, TAG_CREDENTIAL);
let result = deserialize_credential(entry.data);
debug_assert!(result.is_some());
@@ -262,9 +265,9 @@ impl PersistentStore {
== Some(CredentialProtectionPolicy::UserVerificationRequired)
})
{
None
Ok(None)
} else {
result
Ok(result)
}
}
@@ -278,7 +281,7 @@ impl PersistentStore {
user_handle: Some(credential.user_handle.clone()),
};
let old_entry = self.store.find_one(&key);
if old_entry.is_none() && self.count_credentials() >= MAX_SUPPORTED_RESIDENTIAL_KEYS {
if old_entry.is_none() && self.count_credentials()? >= MAX_SUPPORTED_RESIDENTIAL_KEYS {
return Err(Ctap2StatusCode::CTAP2_ERR_KEY_STORE_FULL);
}
let credential = serialize_credential(credential)?;
@@ -301,8 +304,9 @@ impl PersistentStore {
&self,
rp_id: &str,
check_cred_protect: bool,
) -> Vec<PublicKeyCredentialSource> {
self.store
) -> Result<Vec<PublicKeyCredentialSource>, Ctap2StatusCode> {
Ok(self
.store
.find_all(&Key::Credential {
rp_id: Some(rp_id.into()),
credential_id: None,
@@ -315,163 +319,165 @@ impl PersistentStore {
credential
})
.filter(|cred| !check_cred_protect || cred.is_discoverable())
.collect()
.collect())
}
pub fn count_credentials(&self) -> usize {
self.store
pub fn count_credentials(&self) -> Result<usize, Ctap2StatusCode> {
Ok(self
.store
.find_all(&Key::Credential {
rp_id: None,
credential_id: None,
user_handle: None,
})
.count()
.count())
}
pub fn global_signature_counter(&self) -> u32 {
self.store
pub fn global_signature_counter(&self) -> Result<u32, Ctap2StatusCode> {
Ok(self
.store
.find_one(&Key::GlobalSignatureCounter)
.map_or(0, |(_, entry)| {
u32::from_ne_bytes(*array_ref!(entry.data, 0, 4))
})
}))
}
pub fn incr_global_signature_counter(&mut self) {
pub fn incr_global_signature_counter(&mut self) -> Result<(), Ctap2StatusCode> {
let mut buffer = [0; core::mem::size_of::<u32>()];
match self.store.find_one(&Key::GlobalSignatureCounter) {
None => {
buffer.copy_from_slice(&1u32.to_ne_bytes());
self.store
.insert(StoreEntry {
tag: GLOBAL_SIGNATURE_COUNTER,
data: &buffer,
sensitive: false,
})
.unwrap();
self.store.insert(StoreEntry {
tag: GLOBAL_SIGNATURE_COUNTER,
data: &buffer,
sensitive: false,
})?;
}
Some((index, entry)) => {
let value = u32::from_ne_bytes(*array_ref!(entry.data, 0, 4));
// In hopes that servers handle the wrapping gracefully.
buffer.copy_from_slice(&value.wrapping_add(1).to_ne_bytes());
self.store
.replace(
index,
StoreEntry {
tag: GLOBAL_SIGNATURE_COUNTER,
data: &buffer,
sensitive: false,
},
)
.unwrap();
self.store.replace(
index,
StoreEntry {
tag: GLOBAL_SIGNATURE_COUNTER,
data: &buffer,
sensitive: false,
},
)?;
}
}
Ok(())
}
pub fn master_keys(&self) -> MasterKeys {
// We have as invariant that there is always exactly one MasterKeys entry in the store.
pub fn master_keys(&self) -> Result<MasterKeys, Ctap2StatusCode> {
let (_, entry) = self.store.find_one(&Key::MasterKeys).unwrap();
let data = entry.data;
// And this entry is well formed: the encryption key followed by the hmac key.
let encryption = array_ref!(data, 0, 32);
let hmac = array_ref!(data, 32, 32);
MasterKeys { encryption, hmac }
if entry.data.len() != 64 {
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
}
Ok(MasterKeys {
encryption: *array_ref![entry.data, 0, 32],
hmac: *array_ref![entry.data, 32, 32],
})
}
pub fn pin_hash(&self) -> Option<&[u8; PIN_AUTH_LENGTH]> {
self.store
.find_one(&Key::PinHash)
.map(|(_, entry)| array_ref!(entry.data, 0, PIN_AUTH_LENGTH))
pub fn pin_hash(&self) -> Result<Option<[u8; PIN_AUTH_LENGTH]>, Ctap2StatusCode> {
let data = match self.store.find_one(&Key::PinHash) {
None => return Ok(None),
Some((_, entry)) => entry.data,
};
if data.len() != PIN_AUTH_LENGTH {
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
}
Ok(Some(*array_ref![data, 0, PIN_AUTH_LENGTH]))
}
pub fn set_pin_hash(&mut self, pin_hash: &[u8; PIN_AUTH_LENGTH]) {
pub fn set_pin_hash(
&mut self,
pin_hash: &[u8; PIN_AUTH_LENGTH],
) -> Result<(), Ctap2StatusCode> {
let entry = StoreEntry {
tag: PIN_HASH,
data: pin_hash,
sensitive: true,
};
match self.store.find_one(&Key::PinHash) {
None => self.store.insert(entry).unwrap(),
Some((index, _)) => {
self.store.replace(index, entry).unwrap();
}
}
Ok(match self.store.find_one(&Key::PinHash) {
None => self.store.insert(entry)?,
Some((index, _)) => self.store.replace(index, entry)?,
})
}
pub fn pin_retries(&self) -> u8 {
self.store
pub fn pin_retries(&self) -> Result<u8, Ctap2StatusCode> {
Ok(self
.store
.find_one(&Key::PinRetries)
.map_or(MAX_PIN_RETRIES, |(_, entry)| {
debug_assert_eq!(entry.data.len(), 1);
entry.data[0]
})
}))
}
pub fn decr_pin_retries(&mut self) {
pub fn decr_pin_retries(&mut self) -> Result<(), Ctap2StatusCode> {
match self.store.find_one(&Key::PinRetries) {
None => {
self.store
.insert(StoreEntry {
tag: PIN_RETRIES,
data: &[MAX_PIN_RETRIES.saturating_sub(1)],
sensitive: false,
})
.unwrap();
self.store.insert(StoreEntry {
tag: PIN_RETRIES,
data: &[MAX_PIN_RETRIES.saturating_sub(1)],
sensitive: false,
})?;
}
Some((index, entry)) => {
debug_assert_eq!(entry.data.len(), 1);
if entry.data[0] == 0 {
return;
return Ok(());
}
let new_value = entry.data[0].saturating_sub(1);
self.store
.replace(
index,
StoreEntry {
tag: PIN_RETRIES,
data: &[new_value],
sensitive: false,
},
)
.unwrap();
self.store.replace(
index,
StoreEntry {
tag: PIN_RETRIES,
data: &[new_value],
sensitive: false,
},
)?;
}
}
Ok(())
}
pub fn reset_pin_retries(&mut self) {
pub fn reset_pin_retries(&mut self) -> Result<(), Ctap2StatusCode> {
if let Some((index, _)) = self.store.find_one(&Key::PinRetries) {
self.store.delete(index).unwrap();
self.store.delete(index)?;
}
Ok(())
}
#[cfg(feature = "with_ctap2_1")]
pub fn min_pin_length(&self) -> u8 {
self.store
pub fn min_pin_length(&self) -> Result<u8, Ctap2StatusCode> {
Ok(self
.store
.find_one(&Key::MinPinLength)
.map_or(DEFAULT_MIN_PIN_LENGTH, |(_, entry)| {
debug_assert_eq!(entry.data.len(), 1);
entry.data[0]
})
}))
}
#[cfg(feature = "with_ctap2_1")]
pub fn set_min_pin_length(&mut self, min_pin_length: u8) {
pub fn set_min_pin_length(&mut self, min_pin_length: u8) -> Result<(), Ctap2StatusCode> {
let entry = StoreEntry {
tag: MIN_PIN_LENGTH,
data: &[min_pin_length],
sensitive: false,
};
match self.store.find_one(&Key::MinPinLength) {
None => {
self.store.insert(entry).unwrap();
}
Some((index, _)) => {
self.store.replace(index, entry).unwrap();
}
}
Ok(match self.store.find_one(&Key::MinPinLength) {
None => self.store.insert(entry)?,
Some((index, _)) => self.store.replace(index, entry)?,
})
}
#[cfg(feature = "with_ctap2_1")]
pub fn _min_pin_length_rp_ids(&self) -> Vec<String> {
pub fn _min_pin_length_rp_ids(&self) -> Result<Vec<String>, Ctap2StatusCode> {
let rp_ids = self
.store
.find_one(&Key::MinPinLengthRpIds)
@@ -479,7 +485,7 @@ impl PersistentStore {
_deserialize_min_pin_length_rp_ids(entry.data)
});
debug_assert!(rp_ids.is_some());
rp_ids.unwrap_or(vec![])
Ok(rp_ids.unwrap_or(vec![]))
}
#[cfg(feature = "with_ctap2_1")]
@@ -565,7 +571,7 @@ impl PersistentStore {
Ok(())
}
pub fn aaguid(&self) -> Result<&[u8; AAGUID_LENGTH], Ctap2StatusCode> {
pub fn aaguid(&self) -> Result<[u8; AAGUID_LENGTH], Ctap2StatusCode> {
let (_, entry) = self
.store
.find_one(&Key::Aaguid)
@@ -574,7 +580,7 @@ impl PersistentStore {
if data.len() != AAGUID_LENGTH {
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
}
Ok(array_ref!(data, 0, AAGUID_LENGTH))
Ok(*array_ref![data, 0, AAGUID_LENGTH])
}
pub fn set_aaguid(&mut self, aaguid: &[u8; AAGUID_LENGTH]) -> Result<(), Ctap2StatusCode> {
@@ -590,7 +596,7 @@ impl PersistentStore {
Ok(())
}
pub fn reset(&mut self, rng: &mut impl Rng256) {
pub fn reset(&mut self, rng: &mut impl Rng256) -> Result<(), Ctap2StatusCode> {
loop {
let index = {
let mut iter = self.store.iter().filter(|(_, entry)| should_reset(entry));
@@ -599,9 +605,10 @@ impl PersistentStore {
Some((index, _)) => index,
}
};
self.store.delete(index).unwrap();
self.store.delete(index)?;
}
self.init(rng);
Ok(())
}
}
@@ -708,10 +715,10 @@ mod test {
fn test_store() {
let mut rng = ThreadRng256 {};
let mut persistent_store = PersistentStore::new(&mut rng);
assert_eq!(persistent_store.count_credentials(), 0);
assert_eq!(persistent_store.count_credentials().unwrap(), 0);
let credential_source = create_credential_source(&mut rng, "example.com", vec![]);
assert!(persistent_store.store_credential(credential_source).is_ok());
assert!(persistent_store.count_credentials() > 0);
assert!(persistent_store.count_credentials().unwrap() > 0);
}
#[test]
@@ -719,7 +726,7 @@ mod test {
fn test_fill_store() {
let mut rng = ThreadRng256 {};
let mut persistent_store = PersistentStore::new(&mut rng);
assert_eq!(persistent_store.count_credentials(), 0);
assert_eq!(persistent_store.count_credentials().unwrap(), 0);
// To make this test work for bigger storages, implement better int -> Vec conversion.
assert!(MAX_SUPPORTED_RESIDENTIAL_KEYS < 256);
@@ -727,7 +734,7 @@ mod test {
let credential_source =
create_credential_source(&mut rng, "example.com", vec![i as u8]);
assert!(persistent_store.store_credential(credential_source).is_ok());
assert_eq!(persistent_store.count_credentials(), i + 1);
assert_eq!(persistent_store.count_credentials().unwrap(), i + 1);
}
let credential_source = create_credential_source(
&mut rng,
@@ -739,7 +746,7 @@ mod test {
Err(Ctap2StatusCode::CTAP2_ERR_KEY_STORE_FULL)
);
assert_eq!(
persistent_store.count_credentials(),
persistent_store.count_credentials().unwrap(),
MAX_SUPPORTED_RESIDENTIAL_KEYS
);
}
@@ -749,7 +756,7 @@ mod test {
fn test_overwrite() {
let mut rng = ThreadRng256 {};
let mut persistent_store = PersistentStore::new(&mut rng);
assert_eq!(persistent_store.count_credentials(), 0);
assert_eq!(persistent_store.count_credentials().unwrap(), 0);
// These should have different IDs.
let credential_source0 = create_credential_source(&mut rng, "example.com", vec![0x00]);
let credential_source1 = create_credential_source(&mut rng, "example.com", vec![0x00]);
@@ -761,9 +768,11 @@ mod test {
assert!(persistent_store
.store_credential(credential_source1)
.is_ok());
assert_eq!(persistent_store.count_credentials(), 1);
assert_eq!(persistent_store.count_credentials().unwrap(), 1);
assert_eq!(
&persistent_store.filter_credential("example.com", false),
&persistent_store
.filter_credential("example.com", false)
.unwrap(),
&[expected_credential]
);
@@ -773,7 +782,7 @@ mod test {
let credential_source =
create_credential_source(&mut rng, "example.com", vec![i as u8]);
assert!(persistent_store.store_credential(credential_source).is_ok());
assert_eq!(persistent_store.count_credentials(), i + 1);
assert_eq!(persistent_store.count_credentials().unwrap(), i + 1);
}
let credential_source = create_credential_source(
&mut rng,
@@ -785,7 +794,7 @@ mod test {
Err(Ctap2StatusCode::CTAP2_ERR_KEY_STORE_FULL)
);
assert_eq!(
persistent_store.count_credentials(),
persistent_store.count_credentials().unwrap(),
MAX_SUPPORTED_RESIDENTIAL_KEYS
);
}
@@ -794,7 +803,7 @@ mod test {
fn test_filter() {
let mut rng = ThreadRng256 {};
let mut persistent_store = PersistentStore::new(&mut rng);
assert_eq!(persistent_store.count_credentials(), 0);
assert_eq!(persistent_store.count_credentials().unwrap(), 0);
let credential_source0 = create_credential_source(&mut rng, "example.com", vec![0x00]);
let credential_source1 = create_credential_source(&mut rng, "example.com", vec![0x01]);
let credential_source2 =
@@ -811,7 +820,9 @@ mod test {
.store_credential(credential_source2)
.is_ok());
let filtered_credentials = persistent_store.filter_credential("example.com", false);
let filtered_credentials = persistent_store
.filter_credential("example.com", false)
.unwrap();
assert_eq!(filtered_credentials.len(), 2);
assert!(
(filtered_credentials[0].credential_id == id0
@@ -825,7 +836,7 @@ mod test {
fn test_filter_with_cred_protect() {
let mut rng = ThreadRng256 {};
let mut persistent_store = PersistentStore::new(&mut rng);
assert_eq!(persistent_store.count_credentials(), 0);
assert_eq!(persistent_store.count_credentials().unwrap(), 0);
let private_key = crypto::ecdsa::SecKey::gensk(&mut rng);
let credential = PublicKeyCredentialSource {
key_type: PublicKeyCredentialType::PublicKey,
@@ -841,7 +852,9 @@ mod test {
};
assert!(persistent_store.store_credential(credential).is_ok());
let no_credential = persistent_store.filter_credential("example.com", true);
let no_credential = persistent_store
.filter_credential("example.com", true)
.unwrap();
assert_eq!(no_credential, vec![]);
}
@@ -849,7 +862,7 @@ mod test {
fn test_find() {
let mut rng = ThreadRng256 {};
let mut persistent_store = PersistentStore::new(&mut rng);
assert_eq!(persistent_store.count_credentials(), 0);
assert_eq!(persistent_store.count_credentials().unwrap(), 0);
let credential_source0 = create_credential_source(&mut rng, "example.com", vec![0x00]);
let credential_source1 = create_credential_source(&mut rng, "example.com", vec![0x01]);
let id0 = credential_source0.credential_id.clone();
@@ -861,9 +874,13 @@ mod test {
.store_credential(credential_source1)
.is_ok());
let no_credential = persistent_store.find_credential("another.example.com", &id0, false);
let no_credential = persistent_store
.find_credential("another.example.com", &id0, false)
.unwrap();
assert_eq!(no_credential, None);
let found_credential = persistent_store.find_credential("example.com", &id0, false);
let found_credential = persistent_store
.find_credential("example.com", &id0, false)
.unwrap();
let expected_credential = PublicKeyCredentialSource {
key_type: PublicKeyCredentialType::PublicKey,
credential_id: id0,
@@ -881,7 +898,7 @@ mod test {
fn test_find_with_cred_protect() {
let mut rng = ThreadRng256 {};
let mut persistent_store = PersistentStore::new(&mut rng);
assert_eq!(persistent_store.count_credentials(), 0);
assert_eq!(persistent_store.count_credentials().unwrap(), 0);
let private_key = crypto::ecdsa::SecKey::gensk(&mut rng);
let credential = PublicKeyCredentialSource {
key_type: PublicKeyCredentialType::PublicKey,
@@ -895,7 +912,9 @@ mod test {
};
assert!(persistent_store.store_credential(credential).is_ok());
let no_credential = persistent_store.find_credential("example.com", &vec![0x00], true);
let no_credential = persistent_store
.find_credential("example.com", &vec![0x00], true)
.unwrap();
assert_eq!(no_credential, None);
}
@@ -905,8 +924,8 @@ mod test {
let mut persistent_store = PersistentStore::new(&mut rng);
// Master keys stay the same between resets.
let master_keys_1 = persistent_store.master_keys();
let master_keys_2 = persistent_store.master_keys();
let master_keys_1 = persistent_store.master_keys().unwrap();
let master_keys_2 = persistent_store.master_keys().unwrap();
assert_eq!(master_keys_2.encryption, master_keys_1.encryption);
assert_eq!(master_keys_2.hmac, master_keys_1.hmac);
@@ -914,10 +933,10 @@ mod test {
// same keys.
let master_encryption_key = master_keys_1.encryption.to_vec();
let master_hmac_key = master_keys_1.hmac.to_vec();
persistent_store.reset(&mut rng);
let master_keys_3 = persistent_store.master_keys();
assert!(master_keys_3.encryption as &[u8] != &master_encryption_key[..]);
assert!(master_keys_3.hmac as &[u8] != &master_hmac_key[..]);
persistent_store.reset(&mut rng).unwrap();
let master_keys_3 = persistent_store.master_keys().unwrap();
assert!(master_keys_3.encryption != &master_encryption_key[..]);
assert!(master_keys_3.hmac != &master_hmac_key[..]);
}
#[test]
@@ -926,23 +945,23 @@ mod test {
let mut persistent_store = PersistentStore::new(&mut rng);
// Pin hash is initially not set.
assert!(persistent_store.pin_hash().is_none());
assert!(persistent_store.pin_hash().unwrap().is_none());
// Setting the pin hash sets the pin hash.
let random_data = rng.gen_uniform_u8x32();
assert_eq!(random_data.len(), 2 * PIN_AUTH_LENGTH);
let pin_hash_1 = array_ref!(random_data, 0, PIN_AUTH_LENGTH);
let pin_hash_2 = array_ref!(random_data, PIN_AUTH_LENGTH, PIN_AUTH_LENGTH);
persistent_store.set_pin_hash(&pin_hash_1);
assert_eq!(persistent_store.pin_hash(), Some(pin_hash_1));
assert_eq!(persistent_store.pin_hash(), Some(pin_hash_1));
persistent_store.set_pin_hash(&pin_hash_2);
assert_eq!(persistent_store.pin_hash(), Some(pin_hash_2));
assert_eq!(persistent_store.pin_hash(), Some(pin_hash_2));
let pin_hash_1 = *array_ref!(random_data, 0, PIN_AUTH_LENGTH);
let pin_hash_2 = *array_ref!(random_data, PIN_AUTH_LENGTH, PIN_AUTH_LENGTH);
persistent_store.set_pin_hash(&pin_hash_1).unwrap();
assert_eq!(persistent_store.pin_hash().unwrap(), Some(pin_hash_1));
assert_eq!(persistent_store.pin_hash().unwrap(), Some(pin_hash_1));
persistent_store.set_pin_hash(&pin_hash_2).unwrap();
assert_eq!(persistent_store.pin_hash().unwrap(), Some(pin_hash_2));
assert_eq!(persistent_store.pin_hash().unwrap(), Some(pin_hash_2));
// Resetting the storage resets the pin hash.
persistent_store.reset(&mut rng);
assert!(persistent_store.pin_hash().is_none());
persistent_store.reset(&mut rng).unwrap();
assert!(persistent_store.pin_hash().unwrap().is_none());
}
#[test]
@@ -951,21 +970,21 @@ mod test {
let mut persistent_store = PersistentStore::new(&mut rng);
// The pin retries is initially at the maximum.
assert_eq!(persistent_store.pin_retries(), MAX_PIN_RETRIES);
assert_eq!(persistent_store.pin_retries().unwrap(), MAX_PIN_RETRIES);
// Decrementing the pin retries decrements the pin retries.
for pin_retries in (0..MAX_PIN_RETRIES).rev() {
persistent_store.decr_pin_retries();
assert_eq!(persistent_store.pin_retries(), pin_retries);
persistent_store.decr_pin_retries().unwrap();
assert_eq!(persistent_store.pin_retries().unwrap(), pin_retries);
}
// Decrementing the pin retries after zero does not modify the pin retries.
persistent_store.decr_pin_retries();
assert_eq!(persistent_store.pin_retries(), 0);
persistent_store.decr_pin_retries().unwrap();
assert_eq!(persistent_store.pin_retries().unwrap(), 0);
// Resetting the pin retries resets the pin retries.
persistent_store.reset_pin_retries();
assert_eq!(persistent_store.pin_retries(), MAX_PIN_RETRIES);
persistent_store.reset_pin_retries().unwrap();
assert_eq!(persistent_store.pin_retries().unwrap(), MAX_PIN_RETRIES);
}
#[test]
@@ -990,10 +1009,10 @@ mod test {
persistent_store
.set_attestation_certificate(key_material::ATTESTATION_CERTIFICATE)
.unwrap();
assert_eq!(persistent_store.aaguid().unwrap(), key_material::AAGUID);
assert_eq!(&persistent_store.aaguid().unwrap(), key_material::AAGUID);
// The persistent keys stay initialized and preserve their value after a reset.
persistent_store.reset(&mut rng);
persistent_store.reset(&mut rng).unwrap();
assert_eq!(
persistent_store.attestation_private_key().unwrap().unwrap(),
key_material::ATTESTATION_PRIVATE_KEY
@@ -1002,7 +1021,7 @@ mod test {
persistent_store.attestation_certificate().unwrap().unwrap(),
key_material::ATTESTATION_CERTIFICATE
);
assert_eq!(persistent_store.aaguid().unwrap(), key_material::AAGUID);
assert_eq!(&persistent_store.aaguid().unwrap(), key_material::AAGUID);
}
#[cfg(feature = "with_ctap2_1")]
@@ -1012,12 +1031,20 @@ mod test {
let mut persistent_store = PersistentStore::new(&mut rng);
// The minimum PIN length is initially at the default.
assert_eq!(persistent_store.min_pin_length(), DEFAULT_MIN_PIN_LENGTH);
assert_eq!(
persistent_store.min_pin_length().unwrap(),
DEFAULT_MIN_PIN_LENGTH
);
// Changes by the setter are reflected by the getter..
let new_min_pin_length = 8;
persistent_store.set_min_pin_length(new_min_pin_length);
assert_eq!(persistent_store.min_pin_length(), new_min_pin_length);
persistent_store
.set_min_pin_length(new_min_pin_length)
.unwrap();
assert_eq!(
persistent_store.min_pin_length().unwrap(),
new_min_pin_length
);
}
#[cfg(feature = "with_ctap2_1")]
@@ -1028,7 +1055,7 @@ mod test {
// The minimum PIN length RP IDs are initially at the default.
assert_eq!(
persistent_store._min_pin_length_rp_ids(),
persistent_store._min_pin_length_rp_ids().unwrap(),
_DEFAULT_MIN_PIN_LENGTH_RP_IDS
);
@@ -1043,7 +1070,7 @@ mod test {
rp_ids.push(rp_id);
}
}
assert_eq!(persistent_store._min_pin_length_rp_ids(), rp_ids);
assert_eq!(persistent_store._min_pin_length_rp_ids().unwrap(), rp_ids);
}
#[test]