Return errors and Vecs from CTAP storage
This commit is contained in:
@@ -286,7 +286,9 @@ impl Ctap1Command {
|
|||||||
{
|
{
|
||||||
let sk = crypto::ecdsa::SecKey::gensk(ctap_state.rng);
|
let sk = crypto::ecdsa::SecKey::gensk(ctap_state.rng);
|
||||||
let pk = sk.genpk();
|
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 {
|
if key_handle.len() > 0xFF {
|
||||||
// This is just being defensive with unreachable code.
|
// This is just being defensive with unreachable code.
|
||||||
return Err(Ctap1StatusCode::SW_VENDOR_KEY_HANDLE_TOO_LONG);
|
return Err(Ctap1StatusCode::SW_VENDOR_KEY_HANDLE_TOO_LONG);
|
||||||
@@ -341,14 +343,19 @@ impl Ctap1Command {
|
|||||||
R: Rng256,
|
R: Rng256,
|
||||||
CheckUserPresence: Fn(ChannelID) -> Result<(), Ctap2StatusCode>,
|
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 let Some(credential_source) = credential_source {
|
||||||
if flags == Ctap1Flags::CheckOnly {
|
if flags == Ctap1Flags::CheckOnly {
|
||||||
return Err(Ctap1StatusCode::SW_CONDITIONS_NOT_SATISFIED);
|
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
|
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);
|
signature_data.extend(&challenge);
|
||||||
let signature = credential_source
|
let signature = credential_source
|
||||||
.private_key
|
.private_key
|
||||||
@@ -435,6 +442,7 @@ mod test {
|
|||||||
response[67..67 + ENCRYPTED_CREDENTIAL_ID_SIZE].to_vec(),
|
response[67..67 + ENCRYPTED_CREDENTIAL_ID_SIZE].to_vec(),
|
||||||
&application
|
&application
|
||||||
)
|
)
|
||||||
|
.unwrap()
|
||||||
.is_some());
|
.is_some());
|
||||||
const CERT_START: usize = 67 + ENCRYPTED_CREDENTIAL_ID_SIZE;
|
const CERT_START: usize = 67 + ENCRYPTED_CREDENTIAL_ID_SIZE;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -485,7 +493,7 @@ mod test {
|
|||||||
|
|
||||||
let rp_id = "example.com";
|
let rp_id = "example.com";
|
||||||
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
||||||
let key_handle = 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 message = create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
|
||||||
|
|
||||||
let response = Ctap1Command::process_command(&message, &mut ctap_state, START_CLOCK_VALUE);
|
let response = Ctap1Command::process_command(&message, &mut ctap_state, START_CLOCK_VALUE);
|
||||||
@@ -501,7 +509,7 @@ mod test {
|
|||||||
|
|
||||||
let rp_id = "example.com";
|
let rp_id = "example.com";
|
||||||
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
||||||
let key_handle = ctap_state.encrypt_key_handle(sk, &application);
|
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
|
||||||
let application = [0x55; 32];
|
let application = [0x55; 32];
|
||||||
let message = create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
|
let message = create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
|
||||||
|
|
||||||
@@ -518,7 +526,7 @@ mod test {
|
|||||||
|
|
||||||
let rp_id = "example.com";
|
let rp_id = "example.com";
|
||||||
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
||||||
let key_handle = ctap_state.encrypt_key_handle(sk, &application);
|
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
|
||||||
let mut message =
|
let mut message =
|
||||||
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
|
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
|
||||||
|
|
||||||
@@ -542,7 +550,7 @@ mod test {
|
|||||||
|
|
||||||
let rp_id = "example.com";
|
let rp_id = "example.com";
|
||||||
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
||||||
let key_handle = ctap_state.encrypt_key_handle(sk, &application);
|
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
|
||||||
let mut message =
|
let mut message =
|
||||||
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
|
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
|
||||||
message[0] = 0xEE;
|
message[0] = 0xEE;
|
||||||
@@ -560,7 +568,7 @@ mod test {
|
|||||||
|
|
||||||
let rp_id = "example.com";
|
let rp_id = "example.com";
|
||||||
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
||||||
let key_handle = ctap_state.encrypt_key_handle(sk, &application);
|
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
|
||||||
let mut message =
|
let mut message =
|
||||||
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
|
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
|
||||||
message[1] = 0xEE;
|
message[1] = 0xEE;
|
||||||
@@ -578,7 +586,7 @@ mod test {
|
|||||||
|
|
||||||
let rp_id = "example.com";
|
let rp_id = "example.com";
|
||||||
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
||||||
let key_handle = ctap_state.encrypt_key_handle(sk, &application);
|
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
|
||||||
let mut message =
|
let mut message =
|
||||||
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
|
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
|
||||||
message[2] = 0xEE;
|
message[2] = 0xEE;
|
||||||
@@ -596,7 +604,7 @@ mod test {
|
|||||||
|
|
||||||
let rp_id = "example.com";
|
let rp_id = "example.com";
|
||||||
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
||||||
let key_handle = ctap_state.encrypt_key_handle(sk, &application);
|
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
|
||||||
let message =
|
let message =
|
||||||
create_authenticate_message(&application, Ctap1Flags::EnforceUpAndSign, &key_handle);
|
create_authenticate_message(&application, Ctap1Flags::EnforceUpAndSign, &key_handle);
|
||||||
|
|
||||||
@@ -621,7 +629,7 @@ mod test {
|
|||||||
|
|
||||||
let rp_id = "example.com";
|
let rp_id = "example.com";
|
||||||
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
||||||
let key_handle = ctap_state.encrypt_key_handle(sk, &application);
|
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
|
||||||
let message = create_authenticate_message(
|
let message = create_authenticate_message(
|
||||||
&application,
|
&application,
|
||||||
Ctap1Flags::DontEnforceUpAndSign,
|
Ctap1Flags::DontEnforceUpAndSign,
|
||||||
|
|||||||
103
src/ctap/mod.rs
103
src/ctap/mod.rs
@@ -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 {
|
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
|
// Encrypts the private key and relying party ID hash into a credential ID. Other
|
||||||
@@ -188,9 +189,9 @@ where
|
|||||||
&mut self,
|
&mut self,
|
||||||
private_key: crypto::ecdsa::SecKey,
|
private_key: crypto::ecdsa::SecKey,
|
||||||
application: &[u8; 32],
|
application: &[u8; 32],
|
||||||
) -> Vec<u8> {
|
) -> Result<Vec<u8>, Ctap2StatusCode> {
|
||||||
let master_keys = self.persistent_store.master_keys();
|
let master_keys = self.persistent_store.master_keys()?;
|
||||||
let aes_enc_key = crypto::aes256::EncryptionKey::new(master_keys.encryption);
|
let aes_enc_key = crypto::aes256::EncryptionKey::new(master_keys.encryption());
|
||||||
let mut sk_bytes = [0; 32];
|
let mut sk_bytes = [0; 32];
|
||||||
private_key.to_bytes(&mut sk_bytes);
|
private_key.to_bytes(&mut sk_bytes);
|
||||||
let mut iv = [0; 16];
|
let mut iv = [0; 16];
|
||||||
@@ -208,9 +209,9 @@ where
|
|||||||
for b in &blocks {
|
for b in &blocks {
|
||||||
encrypted_id.extend(b);
|
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.extend(&id_hmac);
|
||||||
encrypted_id
|
Ok(encrypted_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decrypts a credential ID and writes the private key into a PublicKeyCredentialSource.
|
// Decrypts a credential ID and writes the private key into a PublicKeyCredentialSource.
|
||||||
@@ -220,20 +221,20 @@ where
|
|||||||
&self,
|
&self,
|
||||||
credential_id: Vec<u8>,
|
credential_id: Vec<u8>,
|
||||||
rp_id_hash: &[u8],
|
rp_id_hash: &[u8],
|
||||||
) -> Option<PublicKeyCredentialSource> {
|
) -> Result<Option<PublicKeyCredentialSource>, Ctap2StatusCode> {
|
||||||
if credential_id.len() != ENCRYPTED_CREDENTIAL_ID_SIZE {
|
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;
|
let payload_size = ENCRYPTED_CREDENTIAL_ID_SIZE - 32;
|
||||||
if !verify_hmac_256::<Sha256>(
|
if !verify_hmac_256::<Sha256>(
|
||||||
master_keys.hmac,
|
master_keys.hmac(),
|
||||||
&credential_id[..payload_size],
|
&credential_id[..payload_size],
|
||||||
array_ref![credential_id, payload_size, 32],
|
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 aes_dec_key = crypto::aes256::DecryptionKey::new(&aes_enc_key);
|
||||||
let mut iv = [0; 16];
|
let mut iv = [0; 16];
|
||||||
iv.copy_from_slice(&credential_id[..16]);
|
iv.copy_from_slice(&credential_id[..16]);
|
||||||
@@ -251,11 +252,11 @@ where
|
|||||||
decrypted_rp_id_hash[16..].clone_from_slice(&blocks[3]);
|
decrypted_rp_id_hash[16..].clone_from_slice(&blocks[3]);
|
||||||
|
|
||||||
if rp_id_hash != decrypted_rp_id_hash {
|
if rp_id_hash != decrypted_rp_id_hash {
|
||||||
return None;
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let sk_option = crypto::ecdsa::SecKey::from_bytes(&decrypted_sk);
|
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,
|
key_type: PublicKeyCredentialType::PublicKey,
|
||||||
credential_id,
|
credential_id,
|
||||||
private_key: sk,
|
private_key: sk,
|
||||||
@@ -264,7 +265,7 @@ where
|
|||||||
other_ui: None,
|
other_ui: None,
|
||||||
cred_random: None,
|
cred_random: None,
|
||||||
cred_protect_policy: None,
|
cred_protect_policy: None,
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_command(&mut self, command_cbor: &[u8], cid: ChannelID) -> Vec<u8> {
|
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 {
|
if let Some(auth_param) = &pin_uv_auth_param {
|
||||||
// This case was added in FIDO 2.1.
|
// This case was added in FIDO 2.1.
|
||||||
if auth_param.is_empty() {
|
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);
|
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_NOT_SET);
|
||||||
} else {
|
} else {
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID);
|
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID);
|
||||||
@@ -389,7 +390,7 @@ where
|
|||||||
for cred_desc in exclude_list {
|
for cred_desc in exclude_list {
|
||||||
if self
|
if self
|
||||||
.persistent_store
|
.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()
|
.is_some()
|
||||||
{
|
{
|
||||||
// Perform this check, so bad actors can't brute force exclude_list
|
// 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 ed_flag = if has_extension_output { ED_FLAG } else { 0 };
|
||||||
let flags = match pin_uv_auth_param {
|
let flags = match pin_uv_auth_param {
|
||||||
Some(pin_auth) => {
|
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.
|
// Specification is unclear, could be CTAP2_ERR_INVALID_OPTION.
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_NOT_SET);
|
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_NOT_SET);
|
||||||
}
|
}
|
||||||
@@ -424,7 +425,7 @@ where
|
|||||||
UP_FLAG | UV_FLAG | AT_FLAG | ed_flag
|
UP_FLAG | UV_FLAG | AT_FLAG | ed_flag
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
if self.persistent_store.pin_hash().is_some() {
|
if self.persistent_store.pin_hash()?.is_some() {
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_REQUIRED);
|
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_REQUIRED);
|
||||||
}
|
}
|
||||||
if options.uv {
|
if options.uv {
|
||||||
@@ -459,11 +460,11 @@ where
|
|||||||
self.persistent_store.store_credential(credential_source)?;
|
self.persistent_store.store_credential(credential_source)?;
|
||||||
random_id
|
random_id
|
||||||
} else {
|
} 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);
|
let mut auth_data = self.generate_auth_data(&rp_id_hash, flags)?;
|
||||||
auth_data.extend(self.persistent_store.aaguid()?);
|
auth_data.append(&mut self.persistent_store.aaguid()?);
|
||||||
// The length is fixed to 0x20 or 0x70 and fits one byte.
|
// The length is fixed to 0x20 or 0x70 and fits one byte.
|
||||||
if credential_id.len() > 0xFF {
|
if credential_id.len() > 0xFF {
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_RESPONSE_TOO_LONG);
|
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_RESPONSE_TOO_LONG);
|
||||||
@@ -492,6 +493,7 @@ where
|
|||||||
// decide whether batch attestation is needed.
|
// decide whether batch attestation is needed.
|
||||||
let (signature, x5c) = match self.persistent_store.attestation_private_key()? {
|
let (signature, x5c) = match self.persistent_store.attestation_private_key()? {
|
||||||
Some(attestation_private_key) => {
|
Some(attestation_private_key) => {
|
||||||
|
let attestation_private_key = array_ref![attestation_private_key, 0, 32];
|
||||||
let attestation_key =
|
let attestation_key =
|
||||||
crypto::ecdsa::SecKey::from_bytes(attestation_private_key).unwrap();
|
crypto::ecdsa::SecKey::from_bytes(attestation_private_key).unwrap();
|
||||||
let attestation_certificate = self
|
let attestation_certificate = self
|
||||||
@@ -541,7 +543,7 @@ where
|
|||||||
if let Some(auth_param) = &pin_uv_auth_param {
|
if let Some(auth_param) = &pin_uv_auth_param {
|
||||||
// This case was added in FIDO 2.1.
|
// This case was added in FIDO 2.1.
|
||||||
if auth_param.is_empty() {
|
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);
|
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_NOT_SET);
|
||||||
} else {
|
} else {
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID);
|
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID);
|
||||||
@@ -560,7 +562,7 @@ where
|
|||||||
|
|
||||||
// This case was added in FIDO 2.1.
|
// This case was added in FIDO 2.1.
|
||||||
if pin_uv_auth_param == Some(vec![]) {
|
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);
|
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_NOT_SET);
|
||||||
} else {
|
} else {
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID);
|
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID);
|
||||||
@@ -589,7 +591,7 @@ where
|
|||||||
let has_uv = pin_uv_auth_param.is_some();
|
let has_uv = pin_uv_auth_param.is_some();
|
||||||
let mut flags = match pin_uv_auth_param {
|
let mut flags = match pin_uv_auth_param {
|
||||||
Some(pin_auth) => {
|
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.
|
// Specification is unclear, could be CTAP2_ERR_UNSUPPORTED_OPTION.
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_NOT_SET);
|
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_NOT_SET);
|
||||||
}
|
}
|
||||||
@@ -631,14 +633,16 @@ where
|
|||||||
&rp_id,
|
&rp_id,
|
||||||
&allowed_credential.key_id,
|
&allowed_credential.key_id,
|
||||||
!has_uv,
|
!has_uv,
|
||||||
) {
|
)? {
|
||||||
Some(credential) => {
|
Some(credential) => {
|
||||||
found_credentials.push(credential);
|
found_credentials.push(credential);
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
if decrypted_credential.is_none() {
|
if decrypted_credential.is_none() {
|
||||||
decrypted_credential = self
|
decrypted_credential = self.decrypt_credential_source(
|
||||||
.decrypt_credential_source(allowed_credential.key_id, &rp_id_hash);
|
allowed_credential.key_id,
|
||||||
|
&rp_id_hash,
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -646,7 +650,7 @@ where
|
|||||||
found_credentials
|
found_credentials
|
||||||
} else {
|
} else {
|
||||||
// TODO(kaczmarczyck) use GetNextAssertion
|
// 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() {
|
let credential = if let Some(credential) = credentials.first() {
|
||||||
@@ -661,9 +665,9 @@ where
|
|||||||
(self.check_user_presence)(cid)?;
|
(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.
|
// Process extensions.
|
||||||
if let Some(hmac_secret_input) = hmac_secret_input {
|
if let Some(hmac_secret_input) = hmac_secret_input {
|
||||||
let encrypted_output = self
|
let encrypted_output = self
|
||||||
@@ -716,8 +720,9 @@ where
|
|||||||
options_map.insert(String::from("up"), true);
|
options_map.insert(String::from("up"), true);
|
||||||
options_map.insert(
|
options_map.insert(
|
||||||
String::from("clientPin"),
|
String::from("clientPin"),
|
||||||
self.persistent_store.pin_hash().is_some(),
|
self.persistent_store.pin_hash()?.is_some(),
|
||||||
);
|
);
|
||||||
|
let aaguid = self.persistent_store.aaguid()?;
|
||||||
Ok(ResponseData::AuthenticatorGetInfo(
|
Ok(ResponseData::AuthenticatorGetInfo(
|
||||||
AuthenticatorGetInfoResponse {
|
AuthenticatorGetInfoResponse {
|
||||||
versions: vec![
|
versions: vec![
|
||||||
@@ -726,7 +731,7 @@ where
|
|||||||
String::from(FIDO2_VERSION_STRING),
|
String::from(FIDO2_VERSION_STRING),
|
||||||
],
|
],
|
||||||
extensions: Some(vec![String::from("hmac-secret")]),
|
extensions: Some(vec![String::from("hmac-secret")]),
|
||||||
aaguid: *self.persistent_store.aaguid()?,
|
aaguid: *array_ref![aaguid, 0, 16],
|
||||||
options: Some(options_map),
|
options: Some(options_map),
|
||||||
max_msg_size: Some(1024),
|
max_msg_size: Some(1024),
|
||||||
pin_protocols: Some(vec![
|
pin_protocols: Some(vec![
|
||||||
@@ -744,7 +749,7 @@ where
|
|||||||
algorithms: Some(vec![ES256_CRED_PARAM]),
|
algorithms: Some(vec![ES256_CRED_PARAM]),
|
||||||
default_cred_protect: DEFAULT_CRED_PROTECT,
|
default_cred_protect: DEFAULT_CRED_PROTECT,
|
||||||
#[cfg(feature = "with_ctap2_1")]
|
#[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")]
|
#[cfg(feature = "with_ctap2_1")]
|
||||||
firmware_version: None,
|
firmware_version: None,
|
||||||
},
|
},
|
||||||
@@ -769,7 +774,7 @@ where
|
|||||||
}
|
}
|
||||||
(self.check_user_presence)(cid)?;
|
(self.check_user_presence)(cid)?;
|
||||||
|
|
||||||
self.persistent_store.reset(self.rng);
|
self.persistent_store.reset(self.rng)?;
|
||||||
self.pin_protocol_v1.reset(self.rng);
|
self.pin_protocol_v1.reset(self.rng);
|
||||||
#[cfg(feature = "with_ctap1")]
|
#[cfg(feature = "with_ctap1")]
|
||||||
{
|
{
|
||||||
@@ -791,7 +796,11 @@ where
|
|||||||
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_COMMAND)
|
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![];
|
let mut auth_data = vec![];
|
||||||
auth_data.extend(rp_id_hash);
|
auth_data.extend(rp_id_hash);
|
||||||
auth_data.push(flag_byte);
|
auth_data.push(flag_byte);
|
||||||
@@ -800,10 +809,10 @@ where
|
|||||||
let mut signature_counter = [0u8; 4];
|
let mut signature_counter = [0u8; 4];
|
||||||
BigEndian::write_u32(
|
BigEndian::write_u32(
|
||||||
&mut signature_counter,
|
&mut signature_counter,
|
||||||
self.persistent_store.global_signature_counter(),
|
self.persistent_store.global_signature_counter()?,
|
||||||
);
|
);
|
||||||
auth_data.extend(&signature_counter);
|
auth_data.extend(&signature_counter);
|
||||||
auth_data
|
Ok(auth_data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1060,6 +1069,7 @@ mod test {
|
|||||||
let stored_credential = ctap_state
|
let stored_credential = ctap_state
|
||||||
.persistent_store
|
.persistent_store
|
||||||
.filter_credential("example.com", false)
|
.filter_credential("example.com", false)
|
||||||
|
.unwrap()
|
||||||
.pop()
|
.pop()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let credential_id = stored_credential.credential_id;
|
let credential_id = stored_credential.credential_id;
|
||||||
@@ -1084,6 +1094,7 @@ mod test {
|
|||||||
let stored_credential = ctap_state
|
let stored_credential = ctap_state
|
||||||
.persistent_store
|
.persistent_store
|
||||||
.filter_credential("example.com", false)
|
.filter_credential("example.com", false)
|
||||||
|
.unwrap()
|
||||||
.pop()
|
.pop()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let credential_id = stored_credential.credential_id;
|
let credential_id = stored_credential.credential_id;
|
||||||
@@ -1378,12 +1389,12 @@ mod test {
|
|||||||
.persistent_store
|
.persistent_store
|
||||||
.store_credential(credential_source)
|
.store_credential(credential_source)
|
||||||
.is_ok());
|
.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 reset_reponse = ctap_state.process_command(&[0x07], DUMMY_CHANNEL_ID);
|
||||||
let expected_response = vec![0x00];
|
let expected_response = vec![0x00];
|
||||||
assert_eq!(reset_reponse, expected_response);
|
assert_eq!(reset_reponse, expected_response);
|
||||||
assert!(ctap_state.persistent_store.count_credentials() == 0);
|
assert!(ctap_state.persistent_store.count_credentials().unwrap() == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -1422,9 +1433,12 @@ mod test {
|
|||||||
// Usually, the relying party ID or its hash is provided by the client.
|
// 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.
|
// We are not testing the correctness of our SHA256 here, only if it is checked.
|
||||||
let rp_id_hash = [0x55; 32];
|
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
|
let decrypted_source = ctap_state
|
||||||
.decrypt_credential_source(encrypted_id, &rp_id_hash)
|
.decrypt_credential_source(encrypted_id, &rp_id_hash)
|
||||||
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(private_key, decrypted_source.private_key);
|
assert_eq!(private_key, decrypted_source.private_key);
|
||||||
@@ -1439,12 +1453,15 @@ mod test {
|
|||||||
|
|
||||||
// Same as above.
|
// Same as above.
|
||||||
let rp_id_hash = [0x55; 32];
|
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() {
|
for i in 0..encrypted_id.len() {
|
||||||
let mut modified_id = encrypted_id.clone();
|
let mut modified_id = encrypted_id.clone();
|
||||||
modified_id[i] ^= 0x01;
|
modified_id[i] ^= 0x01;
|
||||||
assert!(ctap_state
|
assert!(ctap_state
|
||||||
.decrypt_credential_source(modified_id, &rp_id_hash)
|
.decrypt_credential_source(modified_id, &rp_id_hash)
|
||||||
|
.unwrap()
|
||||||
.is_none());
|
.is_none());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ fn check_and_store_new_pin(
|
|||||||
.ok_or(Ctap2StatusCode::CTAP2_ERR_PIN_POLICY_VIOLATION)?;
|
.ok_or(Ctap2StatusCode::CTAP2_ERR_PIN_POLICY_VIOLATION)?;
|
||||||
|
|
||||||
#[cfg(feature = "with_ctap2_1")]
|
#[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"))]
|
#[cfg(not(feature = "with_ctap2_1"))]
|
||||||
let min_pin_length = 4;
|
let min_pin_length = 4;
|
||||||
if pin.len() < min_pin_length || pin.len() == PIN_PADDED_LENGTH {
|
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];
|
let mut pin_hash = [0u8; 16];
|
||||||
pin_hash.copy_from_slice(&Sha256::hash(&pin[..])[..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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,16 +210,12 @@ impl PinProtocolV1 {
|
|||||||
aes_dec_key: &crypto::aes256::DecryptionKey,
|
aes_dec_key: &crypto::aes256::DecryptionKey,
|
||||||
pin_hash_enc: Vec<u8>,
|
pin_hash_enc: Vec<u8>,
|
||||||
) -> Result<(), Ctap2StatusCode> {
|
) -> Result<(), Ctap2StatusCode> {
|
||||||
match persistent_store.pin_hash() {
|
match persistent_store.pin_hash()? {
|
||||||
Some(pin_hash) => {
|
Some(pin_hash) => {
|
||||||
if self.consecutive_pin_mismatches >= 3 {
|
if self.consecutive_pin_mismatches >= 3 {
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_BLOCKED);
|
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_BLOCKED);
|
||||||
}
|
}
|
||||||
// We need to copy the pin hash, because decrementing the pin retries below mutably
|
persistent_store.decr_pin_retries()?;
|
||||||
// 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();
|
|
||||||
if pin_hash_enc.len() != PIN_AUTH_LENGTH {
|
if pin_hash_enc.len() != PIN_AUTH_LENGTH {
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID);
|
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID);
|
||||||
}
|
}
|
||||||
@@ -231,7 +227,7 @@ impl PinProtocolV1 {
|
|||||||
|
|
||||||
if !bool::from(pin_hash.ct_eq(&blocks[0])) {
|
if !bool::from(pin_hash.ct_eq(&blocks[0])) {
|
||||||
self.key_agreement_key = crypto::ecdh::SecKey::gensk(rng);
|
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);
|
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_BLOCKED);
|
||||||
}
|
}
|
||||||
self.consecutive_pin_mismatches += 1;
|
self.consecutive_pin_mismatches += 1;
|
||||||
@@ -244,7 +240,7 @@ impl PinProtocolV1 {
|
|||||||
// This status code is not explicitly mentioned in the specification.
|
// This status code is not explicitly mentioned in the specification.
|
||||||
None => return Err(Ctap2StatusCode::CTAP2_ERR_PIN_REQUIRED),
|
None => return Err(Ctap2StatusCode::CTAP2_ERR_PIN_REQUIRED),
|
||||||
}
|
}
|
||||||
persistent_store.reset_pin_retries();
|
persistent_store.reset_pin_retries()?;
|
||||||
self.consecutive_pin_mismatches = 0;
|
self.consecutive_pin_mismatches = 0;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -276,7 +272,7 @@ impl PinProtocolV1 {
|
|||||||
Ok(AuthenticatorClientPinResponse {
|
Ok(AuthenticatorClientPinResponse {
|
||||||
key_agreement: None,
|
key_agreement: None,
|
||||||
pin_token: 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>,
|
pin_auth: Vec<u8>,
|
||||||
new_pin_enc: Vec<u8>,
|
new_pin_enc: Vec<u8>,
|
||||||
) -> Result<(), Ctap2StatusCode> {
|
) -> Result<(), Ctap2StatusCode> {
|
||||||
if persistent_store.pin_hash().is_some() {
|
if persistent_store.pin_hash()?.is_some() {
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID);
|
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID);
|
||||||
}
|
}
|
||||||
let pin_decryption_key =
|
let pin_decryption_key =
|
||||||
self.exchange_decryption_key(key_agreement, &pin_auth, &new_pin_enc)?;
|
self.exchange_decryption_key(key_agreement, &pin_auth, &new_pin_enc)?;
|
||||||
check_and_store_new_pin(persistent_store, &pin_decryption_key, 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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -315,7 +311,7 @@ impl PinProtocolV1 {
|
|||||||
new_pin_enc: Vec<u8>,
|
new_pin_enc: Vec<u8>,
|
||||||
pin_hash_enc: Vec<u8>,
|
pin_hash_enc: Vec<u8>,
|
||||||
) -> Result<(), Ctap2StatusCode> {
|
) -> Result<(), Ctap2StatusCode> {
|
||||||
if persistent_store.pin_retries() == 0 {
|
if persistent_store.pin_retries()? == 0 {
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_BLOCKED);
|
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_BLOCKED);
|
||||||
}
|
}
|
||||||
let mut auth_param_data = new_pin_enc.clone();
|
let mut auth_param_data = new_pin_enc.clone();
|
||||||
@@ -336,7 +332,7 @@ impl PinProtocolV1 {
|
|||||||
key_agreement: CoseKey,
|
key_agreement: CoseKey,
|
||||||
pin_hash_enc: Vec<u8>,
|
pin_hash_enc: Vec<u8>,
|
||||||
) -> Result<AuthenticatorClientPinResponse, Ctap2StatusCode> {
|
) -> Result<AuthenticatorClientPinResponse, Ctap2StatusCode> {
|
||||||
if persistent_store.pin_retries() == 0 {
|
if persistent_store.pin_retries()? == 0 {
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_BLOCKED);
|
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_BLOCKED);
|
||||||
}
|
}
|
||||||
let pk: crypto::ecdh::PubKey = CoseKey::try_into(key_agreement)?;
|
let pk: crypto::ecdh::PubKey = CoseKey::try_into(key_agreement)?;
|
||||||
@@ -412,7 +408,7 @@ impl PinProtocolV1 {
|
|||||||
if min_pin_length_rp_ids.is_some() {
|
if min_pin_length_rp_ids.is_some() {
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION);
|
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION);
|
||||||
}
|
}
|
||||||
if persistent_store.pin_hash().is_some() {
|
if persistent_store.pin_hash()?.is_some() {
|
||||||
match pin_auth {
|
match pin_auth {
|
||||||
Some(pin_auth) => {
|
Some(pin_auth) => {
|
||||||
if self.consecutive_pin_mismatches >= 3 {
|
if self.consecutive_pin_mismatches >= 3 {
|
||||||
@@ -438,10 +434,10 @@ impl PinProtocolV1 {
|
|||||||
None => return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID),
|
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);
|
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
|
// TODO(kaczmarczyck) commented code is useful for the extension
|
||||||
// https://github.com/google/OpenSK/issues/129
|
// https://github.com/google/OpenSK/issues/129
|
||||||
// if let Some(min_pin_length_rp_ids) = min_pin_length_rp_ids {
|
// if let Some(min_pin_length_rp_ids) = min_pin_length_rp_ids {
|
||||||
@@ -650,7 +646,7 @@ mod test {
|
|||||||
pin[3] = 0x34;
|
pin[3] = 0x34;
|
||||||
let mut pin_hash = [0u8; 16];
|
let mut pin_hash = [0u8; 16];
|
||||||
pin_hash.copy_from_slice(&Sha256::hash(&pin[..])[..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.
|
// 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,
|
0x01, 0xD9, 0x88, 0x40, 0x50, 0xBB, 0xD0, 0x7A, 0x23, 0x1A, 0xEB, 0x69, 0xD8, 0x36,
|
||||||
0xC4, 0x12,
|
0xC4, 0x12,
|
||||||
];
|
];
|
||||||
persistent_store.set_pin_hash(&pin_hash);
|
persistent_store.set_pin_hash(&pin_hash).unwrap();
|
||||||
let shared_secret = [0x88; 32];
|
let shared_secret = [0x88; 32];
|
||||||
let aes_enc_key = crypto::aes256::EncryptionKey::new(&shared_secret);
|
let aes_enc_key = crypto::aes256::EncryptionKey::new(&shared_secret);
|
||||||
let aes_dec_key = crypto::aes256::DecryptionKey::new(&aes_enc_key);
|
let aes_dec_key = crypto::aes256::DecryptionKey::new(&aes_enc_key);
|
||||||
@@ -782,7 +778,7 @@ mod test {
|
|||||||
let expected_response = Ok(AuthenticatorClientPinResponse {
|
let expected_response = Ok(AuthenticatorClientPinResponse {
|
||||||
key_agreement: None,
|
key_agreement: None,
|
||||||
pin_token: None,
|
pin_token: None,
|
||||||
retries: Some(persistent_store.pin_retries() as u64),
|
retries: Some(persistent_store.pin_retries().unwrap() as u64),
|
||||||
});
|
});
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
pin_protocol_v1.process_get_pin_retries(&mut persistent_store),
|
pin_protocol_v1.process_get_pin_retries(&mut persistent_store),
|
||||||
@@ -866,8 +862,8 @@ mod test {
|
|||||||
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
|
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
|
||||||
);
|
);
|
||||||
|
|
||||||
while persistent_store.pin_retries() > 0 {
|
while persistent_store.pin_retries().unwrap() > 0 {
|
||||||
persistent_store.decr_pin_retries();
|
persistent_store.decr_pin_retries().unwrap();
|
||||||
}
|
}
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
pin_protocol_v1.process_change_pin(
|
pin_protocol_v1.process_change_pin(
|
||||||
@@ -999,7 +995,7 @@ mod test {
|
|||||||
Some(pin_auth.clone()),
|
Some(pin_auth.clone()),
|
||||||
);
|
);
|
||||||
assert_eq!(response, Ok(()));
|
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(
|
let response = pin_protocol_v1.process_set_min_pin_length(
|
||||||
&mut persistent_store,
|
&mut persistent_store,
|
||||||
7,
|
7,
|
||||||
@@ -1010,7 +1006,7 @@ mod test {
|
|||||||
response,
|
response,
|
||||||
Err(Ctap2StatusCode::CTAP2_ERR_PIN_POLICY_VIOLATION)
|
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]
|
#[test]
|
||||||
@@ -1133,16 +1129,16 @@ mod test {
|
|||||||
),
|
),
|
||||||
];
|
];
|
||||||
for (pin, result) in test_cases {
|
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);
|
let new_pin_enc = encrypt_pin(&shared_secret, pin);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
check_and_store_new_pin(&mut persistent_store, &aes_dec_key, new_pin_enc),
|
check_and_store_new_pin(&mut persistent_store, &aes_dec_key, new_pin_enc),
|
||||||
result
|
result
|
||||||
);
|
);
|
||||||
if result.is_ok() {
|
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 {
|
} else {
|
||||||
assert_eq!(old_pin_hash.as_ref(), persistent_store.pin_hash());
|
assert_eq!(old_pin_hash, persistent_store.pin_hash().unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,9 +105,16 @@ enum Key {
|
|||||||
MinPinLengthRpIds,
|
MinPinLengthRpIds,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct MasterKeys<'a> {
|
pub struct MasterKeys(Vec<u8>);
|
||||||
pub encryption: &'a [u8; 32],
|
|
||||||
pub hmac: &'a [u8; 32],
|
impl MasterKeys {
|
||||||
|
pub fn encryption(&self) -> &[u8; 32] {
|
||||||
|
array_ref!(&self.0, 0, 32)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hmac(&self) -> &[u8; 32] {
|
||||||
|
array_ref!(&self.0, 32, 32)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Config;
|
struct Config;
|
||||||
@@ -246,13 +253,16 @@ impl PersistentStore {
|
|||||||
rp_id: &str,
|
rp_id: &str,
|
||||||
credential_id: &[u8],
|
credential_id: &[u8],
|
||||||
check_cred_protect: bool,
|
check_cred_protect: bool,
|
||||||
) -> Option<PublicKeyCredentialSource> {
|
) -> Result<Option<PublicKeyCredentialSource>, Ctap2StatusCode> {
|
||||||
let key = Key::Credential {
|
let key = Key::Credential {
|
||||||
rp_id: Some(rp_id.into()),
|
rp_id: Some(rp_id.into()),
|
||||||
credential_id: Some(credential_id.into()),
|
credential_id: Some(credential_id.into()),
|
||||||
user_handle: None,
|
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);
|
debug_assert_eq!(entry.tag, TAG_CREDENTIAL);
|
||||||
let result = deserialize_credential(entry.data);
|
let result = deserialize_credential(entry.data);
|
||||||
debug_assert!(result.is_some());
|
debug_assert!(result.is_some());
|
||||||
@@ -262,9 +272,9 @@ impl PersistentStore {
|
|||||||
== Some(CredentialProtectionPolicy::UserVerificationRequired)
|
== Some(CredentialProtectionPolicy::UserVerificationRequired)
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
None
|
Ok(None)
|
||||||
} else {
|
} else {
|
||||||
result
|
Ok(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,7 +288,7 @@ impl PersistentStore {
|
|||||||
user_handle: Some(credential.user_handle.clone()),
|
user_handle: Some(credential.user_handle.clone()),
|
||||||
};
|
};
|
||||||
let old_entry = self.store.find_one(&key);
|
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);
|
return Err(Ctap2StatusCode::CTAP2_ERR_KEY_STORE_FULL);
|
||||||
}
|
}
|
||||||
let credential = serialize_credential(credential)?;
|
let credential = serialize_credential(credential)?;
|
||||||
@@ -301,8 +311,9 @@ impl PersistentStore {
|
|||||||
&self,
|
&self,
|
||||||
rp_id: &str,
|
rp_id: &str,
|
||||||
check_cred_protect: bool,
|
check_cred_protect: bool,
|
||||||
) -> Vec<PublicKeyCredentialSource> {
|
) -> Result<Vec<PublicKeyCredentialSource>, Ctap2StatusCode> {
|
||||||
self.store
|
Ok(self
|
||||||
|
.store
|
||||||
.find_all(&Key::Credential {
|
.find_all(&Key::Credential {
|
||||||
rp_id: Some(rp_id.into()),
|
rp_id: Some(rp_id.into()),
|
||||||
credential_id: None,
|
credential_id: None,
|
||||||
@@ -315,163 +326,162 @@ impl PersistentStore {
|
|||||||
credential
|
credential
|
||||||
})
|
})
|
||||||
.filter(|cred| !check_cred_protect || cred.is_discoverable())
|
.filter(|cred| !check_cred_protect || cred.is_discoverable())
|
||||||
.collect()
|
.collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn count_credentials(&self) -> usize {
|
pub fn count_credentials(&self) -> Result<usize, Ctap2StatusCode> {
|
||||||
self.store
|
Ok(self
|
||||||
|
.store
|
||||||
.find_all(&Key::Credential {
|
.find_all(&Key::Credential {
|
||||||
rp_id: None,
|
rp_id: None,
|
||||||
credential_id: None,
|
credential_id: None,
|
||||||
user_handle: None,
|
user_handle: None,
|
||||||
})
|
})
|
||||||
.count()
|
.count())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn global_signature_counter(&self) -> u32 {
|
pub fn global_signature_counter(&self) -> Result<u32, Ctap2StatusCode> {
|
||||||
self.store
|
Ok(self
|
||||||
|
.store
|
||||||
.find_one(&Key::GlobalSignatureCounter)
|
.find_one(&Key::GlobalSignatureCounter)
|
||||||
.map_or(0, |(_, entry)| {
|
.map_or(0, |(_, entry)| {
|
||||||
u32::from_ne_bytes(*array_ref!(entry.data, 0, 4))
|
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>()];
|
let mut buffer = [0; core::mem::size_of::<u32>()];
|
||||||
match self.store.find_one(&Key::GlobalSignatureCounter) {
|
match self.store.find_one(&Key::GlobalSignatureCounter) {
|
||||||
None => {
|
None => {
|
||||||
buffer.copy_from_slice(&1u32.to_ne_bytes());
|
buffer.copy_from_slice(&1u32.to_ne_bytes());
|
||||||
self.store
|
self.store.insert(StoreEntry {
|
||||||
.insert(StoreEntry {
|
tag: GLOBAL_SIGNATURE_COUNTER,
|
||||||
tag: GLOBAL_SIGNATURE_COUNTER,
|
data: &buffer,
|
||||||
data: &buffer,
|
sensitive: false,
|
||||||
sensitive: false,
|
})?;
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
Some((index, entry)) => {
|
Some((index, entry)) => {
|
||||||
let value = u32::from_ne_bytes(*array_ref!(entry.data, 0, 4));
|
let value = u32::from_ne_bytes(*array_ref!(entry.data, 0, 4));
|
||||||
// In hopes that servers handle the wrapping gracefully.
|
// In hopes that servers handle the wrapping gracefully.
|
||||||
buffer.copy_from_slice(&value.wrapping_add(1).to_ne_bytes());
|
buffer.copy_from_slice(&value.wrapping_add(1).to_ne_bytes());
|
||||||
self.store
|
self.store.replace(
|
||||||
.replace(
|
index,
|
||||||
index,
|
StoreEntry {
|
||||||
StoreEntry {
|
tag: GLOBAL_SIGNATURE_COUNTER,
|
||||||
tag: GLOBAL_SIGNATURE_COUNTER,
|
data: &buffer,
|
||||||
data: &buffer,
|
sensitive: false,
|
||||||
sensitive: false,
|
},
|
||||||
},
|
)?;
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn master_keys(&self) -> MasterKeys {
|
pub fn master_keys(&self) -> Result<MasterKeys, Ctap2StatusCode> {
|
||||||
// We have as invariant that there is always exactly one MasterKeys entry in the store.
|
|
||||||
let (_, entry) = self.store.find_one(&Key::MasterKeys).unwrap();
|
let (_, entry) = self.store.find_one(&Key::MasterKeys).unwrap();
|
||||||
let data = entry.data;
|
if entry.data.len() != 64 {
|
||||||
// And this entry is well formed: the encryption key followed by the hmac key.
|
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
|
||||||
let encryption = array_ref!(data, 0, 32);
|
}
|
||||||
let hmac = array_ref!(data, 32, 32);
|
Ok(MasterKeys(entry.data.to_vec()))
|
||||||
MasterKeys { encryption, hmac }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pin_hash(&self) -> Option<&[u8; PIN_AUTH_LENGTH]> {
|
pub fn pin_hash(&self) -> Result<Option<Vec<u8>>, Ctap2StatusCode> {
|
||||||
self.store
|
let data = match self.store.find_one(&Key::PinHash) {
|
||||||
.find_one(&Key::PinHash)
|
None => return Ok(None),
|
||||||
.map(|(_, entry)| array_ref!(entry.data, 0, PIN_AUTH_LENGTH))
|
Some((_, entry)) => entry.data,
|
||||||
|
};
|
||||||
|
if data.len() != PIN_AUTH_LENGTH {
|
||||||
|
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
|
||||||
|
}
|
||||||
|
Ok(Some(data.to_vec()))
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
let entry = StoreEntry {
|
||||||
tag: PIN_HASH,
|
tag: PIN_HASH,
|
||||||
data: pin_hash,
|
data: pin_hash,
|
||||||
sensitive: true,
|
sensitive: true,
|
||||||
};
|
};
|
||||||
match self.store.find_one(&Key::PinHash) {
|
Ok(match self.store.find_one(&Key::PinHash) {
|
||||||
None => self.store.insert(entry).unwrap(),
|
None => self.store.insert(entry)?,
|
||||||
Some((index, _)) => {
|
Some((index, _)) => self.store.replace(index, entry)?,
|
||||||
self.store.replace(index, entry).unwrap();
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pin_retries(&self) -> u8 {
|
pub fn pin_retries(&self) -> Result<u8, Ctap2StatusCode> {
|
||||||
self.store
|
Ok(self
|
||||||
|
.store
|
||||||
.find_one(&Key::PinRetries)
|
.find_one(&Key::PinRetries)
|
||||||
.map_or(MAX_PIN_RETRIES, |(_, entry)| {
|
.map_or(MAX_PIN_RETRIES, |(_, entry)| {
|
||||||
debug_assert_eq!(entry.data.len(), 1);
|
debug_assert_eq!(entry.data.len(), 1);
|
||||||
entry.data[0]
|
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) {
|
match self.store.find_one(&Key::PinRetries) {
|
||||||
None => {
|
None => {
|
||||||
self.store
|
self.store.insert(StoreEntry {
|
||||||
.insert(StoreEntry {
|
tag: PIN_RETRIES,
|
||||||
tag: PIN_RETRIES,
|
data: &[MAX_PIN_RETRIES.saturating_sub(1)],
|
||||||
data: &[MAX_PIN_RETRIES.saturating_sub(1)],
|
sensitive: false,
|
||||||
sensitive: false,
|
})?;
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
Some((index, entry)) => {
|
Some((index, entry)) => {
|
||||||
debug_assert_eq!(entry.data.len(), 1);
|
debug_assert_eq!(entry.data.len(), 1);
|
||||||
if entry.data[0] == 0 {
|
if entry.data[0] == 0 {
|
||||||
return;
|
return Ok(());
|
||||||
}
|
}
|
||||||
let new_value = entry.data[0].saturating_sub(1);
|
let new_value = entry.data[0].saturating_sub(1);
|
||||||
self.store
|
self.store.replace(
|
||||||
.replace(
|
index,
|
||||||
index,
|
StoreEntry {
|
||||||
StoreEntry {
|
tag: PIN_RETRIES,
|
||||||
tag: PIN_RETRIES,
|
data: &[new_value],
|
||||||
data: &[new_value],
|
sensitive: false,
|
||||||
sensitive: false,
|
},
|
||||||
},
|
)?;
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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) {
|
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")]
|
#[cfg(feature = "with_ctap2_1")]
|
||||||
pub fn min_pin_length(&self) -> u8 {
|
pub fn min_pin_length(&self) -> Result<u8, Ctap2StatusCode> {
|
||||||
self.store
|
Ok(self
|
||||||
|
.store
|
||||||
.find_one(&Key::MinPinLength)
|
.find_one(&Key::MinPinLength)
|
||||||
.map_or(DEFAULT_MIN_PIN_LENGTH, |(_, entry)| {
|
.map_or(DEFAULT_MIN_PIN_LENGTH, |(_, entry)| {
|
||||||
debug_assert_eq!(entry.data.len(), 1);
|
debug_assert_eq!(entry.data.len(), 1);
|
||||||
entry.data[0]
|
entry.data[0]
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "with_ctap2_1")]
|
#[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 {
|
let entry = StoreEntry {
|
||||||
tag: MIN_PIN_LENGTH,
|
tag: MIN_PIN_LENGTH,
|
||||||
data: &[min_pin_length],
|
data: &[min_pin_length],
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
};
|
};
|
||||||
match self.store.find_one(&Key::MinPinLength) {
|
Ok(match self.store.find_one(&Key::MinPinLength) {
|
||||||
None => {
|
None => self.store.insert(entry)?,
|
||||||
self.store.insert(entry).unwrap();
|
Some((index, _)) => self.store.replace(index, entry)?,
|
||||||
}
|
})
|
||||||
Some((index, _)) => {
|
|
||||||
self.store.replace(index, entry).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "with_ctap2_1")]
|
#[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
|
let rp_ids = self
|
||||||
.store
|
.store
|
||||||
.find_one(&Key::MinPinLengthRpIds)
|
.find_one(&Key::MinPinLengthRpIds)
|
||||||
@@ -479,7 +489,7 @@ impl PersistentStore {
|
|||||||
_deserialize_min_pin_length_rp_ids(entry.data)
|
_deserialize_min_pin_length_rp_ids(entry.data)
|
||||||
});
|
});
|
||||||
debug_assert!(rp_ids.is_some());
|
debug_assert!(rp_ids.is_some());
|
||||||
rp_ids.unwrap_or(vec![])
|
Ok(rp_ids.unwrap_or(vec![]))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "with_ctap2_1")]
|
#[cfg(feature = "with_ctap2_1")]
|
||||||
@@ -565,7 +575,7 @@ impl PersistentStore {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn aaguid(&self) -> Result<&[u8; AAGUID_LENGTH], Ctap2StatusCode> {
|
pub fn aaguid(&self) -> Result<Vec<u8>, Ctap2StatusCode> {
|
||||||
let (_, entry) = self
|
let (_, entry) = self
|
||||||
.store
|
.store
|
||||||
.find_one(&Key::Aaguid)
|
.find_one(&Key::Aaguid)
|
||||||
@@ -574,7 +584,7 @@ impl PersistentStore {
|
|||||||
if data.len() != AAGUID_LENGTH {
|
if data.len() != AAGUID_LENGTH {
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
|
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
|
||||||
}
|
}
|
||||||
Ok(array_ref!(data, 0, AAGUID_LENGTH))
|
Ok(data.to_vec())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_aaguid(&mut self, aaguid: &[u8; AAGUID_LENGTH]) -> Result<(), Ctap2StatusCode> {
|
pub fn set_aaguid(&mut self, aaguid: &[u8; AAGUID_LENGTH]) -> Result<(), Ctap2StatusCode> {
|
||||||
@@ -590,7 +600,7 @@ impl PersistentStore {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reset(&mut self, rng: &mut impl Rng256) {
|
pub fn reset(&mut self, rng: &mut impl Rng256) -> Result<(), Ctap2StatusCode> {
|
||||||
loop {
|
loop {
|
||||||
let index = {
|
let index = {
|
||||||
let mut iter = self.store.iter().filter(|(_, entry)| should_reset(entry));
|
let mut iter = self.store.iter().filter(|(_, entry)| should_reset(entry));
|
||||||
@@ -599,9 +609,10 @@ impl PersistentStore {
|
|||||||
Some((index, _)) => index,
|
Some((index, _)) => index,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
self.store.delete(index).unwrap();
|
self.store.delete(index)?;
|
||||||
}
|
}
|
||||||
self.init(rng);
|
self.init(rng);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -708,10 +719,10 @@ mod test {
|
|||||||
fn test_store() {
|
fn test_store() {
|
||||||
let mut rng = ThreadRng256 {};
|
let mut rng = ThreadRng256 {};
|
||||||
let mut persistent_store = PersistentStore::new(&mut rng);
|
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![]);
|
let credential_source = create_credential_source(&mut rng, "example.com", vec![]);
|
||||||
assert!(persistent_store.store_credential(credential_source).is_ok());
|
assert!(persistent_store.store_credential(credential_source).is_ok());
|
||||||
assert!(persistent_store.count_credentials() > 0);
|
assert!(persistent_store.count_credentials().unwrap() > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -719,7 +730,7 @@ mod test {
|
|||||||
fn test_fill_store() {
|
fn test_fill_store() {
|
||||||
let mut rng = ThreadRng256 {};
|
let mut rng = ThreadRng256 {};
|
||||||
let mut persistent_store = PersistentStore::new(&mut rng);
|
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.
|
// To make this test work for bigger storages, implement better int -> Vec conversion.
|
||||||
assert!(MAX_SUPPORTED_RESIDENTIAL_KEYS < 256);
|
assert!(MAX_SUPPORTED_RESIDENTIAL_KEYS < 256);
|
||||||
@@ -727,7 +738,7 @@ mod test {
|
|||||||
let credential_source =
|
let credential_source =
|
||||||
create_credential_source(&mut rng, "example.com", vec![i as u8]);
|
create_credential_source(&mut rng, "example.com", vec![i as u8]);
|
||||||
assert!(persistent_store.store_credential(credential_source).is_ok());
|
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(
|
let credential_source = create_credential_source(
|
||||||
&mut rng,
|
&mut rng,
|
||||||
@@ -739,7 +750,7 @@ mod test {
|
|||||||
Err(Ctap2StatusCode::CTAP2_ERR_KEY_STORE_FULL)
|
Err(Ctap2StatusCode::CTAP2_ERR_KEY_STORE_FULL)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
persistent_store.count_credentials(),
|
persistent_store.count_credentials().unwrap(),
|
||||||
MAX_SUPPORTED_RESIDENTIAL_KEYS
|
MAX_SUPPORTED_RESIDENTIAL_KEYS
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -749,7 +760,7 @@ mod test {
|
|||||||
fn test_overwrite() {
|
fn test_overwrite() {
|
||||||
let mut rng = ThreadRng256 {};
|
let mut rng = ThreadRng256 {};
|
||||||
let mut persistent_store = PersistentStore::new(&mut rng);
|
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.
|
// These should have different IDs.
|
||||||
let credential_source0 = create_credential_source(&mut rng, "example.com", vec![0x00]);
|
let credential_source0 = create_credential_source(&mut rng, "example.com", vec![0x00]);
|
||||||
let credential_source1 = create_credential_source(&mut rng, "example.com", vec![0x00]);
|
let credential_source1 = create_credential_source(&mut rng, "example.com", vec![0x00]);
|
||||||
@@ -761,9 +772,11 @@ mod test {
|
|||||||
assert!(persistent_store
|
assert!(persistent_store
|
||||||
.store_credential(credential_source1)
|
.store_credential(credential_source1)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
assert_eq!(persistent_store.count_credentials(), 1);
|
assert_eq!(persistent_store.count_credentials().unwrap(), 1);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&persistent_store.filter_credential("example.com", false),
|
&persistent_store
|
||||||
|
.filter_credential("example.com", false)
|
||||||
|
.unwrap(),
|
||||||
&[expected_credential]
|
&[expected_credential]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -773,7 +786,7 @@ mod test {
|
|||||||
let credential_source =
|
let credential_source =
|
||||||
create_credential_source(&mut rng, "example.com", vec![i as u8]);
|
create_credential_source(&mut rng, "example.com", vec![i as u8]);
|
||||||
assert!(persistent_store.store_credential(credential_source).is_ok());
|
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(
|
let credential_source = create_credential_source(
|
||||||
&mut rng,
|
&mut rng,
|
||||||
@@ -785,7 +798,7 @@ mod test {
|
|||||||
Err(Ctap2StatusCode::CTAP2_ERR_KEY_STORE_FULL)
|
Err(Ctap2StatusCode::CTAP2_ERR_KEY_STORE_FULL)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
persistent_store.count_credentials(),
|
persistent_store.count_credentials().unwrap(),
|
||||||
MAX_SUPPORTED_RESIDENTIAL_KEYS
|
MAX_SUPPORTED_RESIDENTIAL_KEYS
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -794,7 +807,7 @@ mod test {
|
|||||||
fn test_filter() {
|
fn test_filter() {
|
||||||
let mut rng = ThreadRng256 {};
|
let mut rng = ThreadRng256 {};
|
||||||
let mut persistent_store = PersistentStore::new(&mut rng);
|
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_source0 = create_credential_source(&mut rng, "example.com", vec![0x00]);
|
||||||
let credential_source1 = create_credential_source(&mut rng, "example.com", vec![0x01]);
|
let credential_source1 = create_credential_source(&mut rng, "example.com", vec![0x01]);
|
||||||
let credential_source2 =
|
let credential_source2 =
|
||||||
@@ -811,7 +824,9 @@ mod test {
|
|||||||
.store_credential(credential_source2)
|
.store_credential(credential_source2)
|
||||||
.is_ok());
|
.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_eq!(filtered_credentials.len(), 2);
|
||||||
assert!(
|
assert!(
|
||||||
(filtered_credentials[0].credential_id == id0
|
(filtered_credentials[0].credential_id == id0
|
||||||
@@ -825,7 +840,7 @@ mod test {
|
|||||||
fn test_filter_with_cred_protect() {
|
fn test_filter_with_cred_protect() {
|
||||||
let mut rng = ThreadRng256 {};
|
let mut rng = ThreadRng256 {};
|
||||||
let mut persistent_store = PersistentStore::new(&mut rng);
|
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 private_key = crypto::ecdsa::SecKey::gensk(&mut rng);
|
||||||
let credential = PublicKeyCredentialSource {
|
let credential = PublicKeyCredentialSource {
|
||||||
key_type: PublicKeyCredentialType::PublicKey,
|
key_type: PublicKeyCredentialType::PublicKey,
|
||||||
@@ -841,7 +856,9 @@ mod test {
|
|||||||
};
|
};
|
||||||
assert!(persistent_store.store_credential(credential).is_ok());
|
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![]);
|
assert_eq!(no_credential, vec![]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -849,7 +866,7 @@ mod test {
|
|||||||
fn test_find() {
|
fn test_find() {
|
||||||
let mut rng = ThreadRng256 {};
|
let mut rng = ThreadRng256 {};
|
||||||
let mut persistent_store = PersistentStore::new(&mut rng);
|
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_source0 = create_credential_source(&mut rng, "example.com", vec![0x00]);
|
||||||
let credential_source1 = create_credential_source(&mut rng, "example.com", vec![0x01]);
|
let credential_source1 = create_credential_source(&mut rng, "example.com", vec![0x01]);
|
||||||
let id0 = credential_source0.credential_id.clone();
|
let id0 = credential_source0.credential_id.clone();
|
||||||
@@ -861,9 +878,13 @@ mod test {
|
|||||||
.store_credential(credential_source1)
|
.store_credential(credential_source1)
|
||||||
.is_ok());
|
.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);
|
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 {
|
let expected_credential = PublicKeyCredentialSource {
|
||||||
key_type: PublicKeyCredentialType::PublicKey,
|
key_type: PublicKeyCredentialType::PublicKey,
|
||||||
credential_id: id0,
|
credential_id: id0,
|
||||||
@@ -881,7 +902,7 @@ mod test {
|
|||||||
fn test_find_with_cred_protect() {
|
fn test_find_with_cred_protect() {
|
||||||
let mut rng = ThreadRng256 {};
|
let mut rng = ThreadRng256 {};
|
||||||
let mut persistent_store = PersistentStore::new(&mut rng);
|
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 private_key = crypto::ecdsa::SecKey::gensk(&mut rng);
|
||||||
let credential = PublicKeyCredentialSource {
|
let credential = PublicKeyCredentialSource {
|
||||||
key_type: PublicKeyCredentialType::PublicKey,
|
key_type: PublicKeyCredentialType::PublicKey,
|
||||||
@@ -895,7 +916,9 @@ mod test {
|
|||||||
};
|
};
|
||||||
assert!(persistent_store.store_credential(credential).is_ok());
|
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);
|
assert_eq!(no_credential, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -905,19 +928,19 @@ mod test {
|
|||||||
let mut persistent_store = PersistentStore::new(&mut rng);
|
let mut persistent_store = PersistentStore::new(&mut rng);
|
||||||
|
|
||||||
// Master keys stay the same between resets.
|
// Master keys stay the same between resets.
|
||||||
let master_keys_1 = persistent_store.master_keys();
|
let master_keys_1 = persistent_store.master_keys().unwrap();
|
||||||
let master_keys_2 = persistent_store.master_keys();
|
let master_keys_2 = persistent_store.master_keys().unwrap();
|
||||||
assert_eq!(master_keys_2.encryption, master_keys_1.encryption);
|
assert_eq!(master_keys_2.encryption(), master_keys_1.encryption());
|
||||||
assert_eq!(master_keys_2.hmac, master_keys_1.hmac);
|
assert_eq!(master_keys_2.hmac(), master_keys_1.hmac());
|
||||||
|
|
||||||
// Master keys change after reset. This test may fail if the random generator produces the
|
// Master keys change after reset. This test may fail if the random generator produces the
|
||||||
// same keys.
|
// same keys.
|
||||||
let master_encryption_key = master_keys_1.encryption.to_vec();
|
let master_encryption_key = master_keys_1.encryption().to_vec();
|
||||||
let master_hmac_key = master_keys_1.hmac.to_vec();
|
let master_hmac_key = master_keys_1.hmac().to_vec();
|
||||||
persistent_store.reset(&mut rng);
|
persistent_store.reset(&mut rng).unwrap();
|
||||||
let master_keys_3 = persistent_store.master_keys();
|
let master_keys_3 = persistent_store.master_keys().unwrap();
|
||||||
assert!(master_keys_3.encryption as &[u8] != &master_encryption_key[..]);
|
assert!(master_keys_3.encryption() != &master_encryption_key[..]);
|
||||||
assert!(master_keys_3.hmac as &[u8] != &master_hmac_key[..]);
|
assert!(master_keys_3.hmac() != &master_hmac_key[..]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -926,23 +949,23 @@ mod test {
|
|||||||
let mut persistent_store = PersistentStore::new(&mut rng);
|
let mut persistent_store = PersistentStore::new(&mut rng);
|
||||||
|
|
||||||
// Pin hash is initially not set.
|
// 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.
|
// Setting the pin hash sets the pin hash.
|
||||||
let random_data = rng.gen_uniform_u8x32();
|
let random_data = rng.gen_uniform_u8x32();
|
||||||
assert_eq!(random_data.len(), 2 * PIN_AUTH_LENGTH);
|
assert_eq!(random_data.len(), 2 * PIN_AUTH_LENGTH);
|
||||||
let pin_hash_1 = array_ref!(random_data, 0, 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);
|
let pin_hash_2 = array_ref!(random_data, PIN_AUTH_LENGTH, PIN_AUTH_LENGTH);
|
||||||
persistent_store.set_pin_hash(&pin_hash_1);
|
persistent_store.set_pin_hash(&pin_hash_1).unwrap();
|
||||||
assert_eq!(persistent_store.pin_hash(), Some(pin_hash_1));
|
assert_eq!(persistent_store.pin_hash().unwrap().unwrap(), pin_hash_1);
|
||||||
assert_eq!(persistent_store.pin_hash(), Some(pin_hash_1));
|
assert_eq!(persistent_store.pin_hash().unwrap().unwrap(), pin_hash_1);
|
||||||
persistent_store.set_pin_hash(&pin_hash_2);
|
persistent_store.set_pin_hash(&pin_hash_2).unwrap();
|
||||||
assert_eq!(persistent_store.pin_hash(), Some(pin_hash_2));
|
assert_eq!(persistent_store.pin_hash().unwrap().unwrap(), pin_hash_2);
|
||||||
assert_eq!(persistent_store.pin_hash(), Some(pin_hash_2));
|
assert_eq!(persistent_store.pin_hash().unwrap().unwrap(), pin_hash_2);
|
||||||
|
|
||||||
// Resetting the storage resets the pin hash.
|
// Resetting the storage resets the pin hash.
|
||||||
persistent_store.reset(&mut rng);
|
persistent_store.reset(&mut rng).unwrap();
|
||||||
assert!(persistent_store.pin_hash().is_none());
|
assert!(persistent_store.pin_hash().unwrap().is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -951,21 +974,21 @@ mod test {
|
|||||||
let mut persistent_store = PersistentStore::new(&mut rng);
|
let mut persistent_store = PersistentStore::new(&mut rng);
|
||||||
|
|
||||||
// The pin retries is initially at the maximum.
|
// 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.
|
// Decrementing the pin retries decrements the pin retries.
|
||||||
for pin_retries in (0..MAX_PIN_RETRIES).rev() {
|
for pin_retries in (0..MAX_PIN_RETRIES).rev() {
|
||||||
persistent_store.decr_pin_retries();
|
persistent_store.decr_pin_retries().unwrap();
|
||||||
assert_eq!(persistent_store.pin_retries(), pin_retries);
|
assert_eq!(persistent_store.pin_retries().unwrap(), pin_retries);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decrementing the pin retries after zero does not modify the pin retries.
|
// Decrementing the pin retries after zero does not modify the pin retries.
|
||||||
persistent_store.decr_pin_retries();
|
persistent_store.decr_pin_retries().unwrap();
|
||||||
assert_eq!(persistent_store.pin_retries(), 0);
|
assert_eq!(persistent_store.pin_retries().unwrap(), 0);
|
||||||
|
|
||||||
// Resetting the pin retries resets the pin retries.
|
// Resetting the pin retries resets the pin retries.
|
||||||
persistent_store.reset_pin_retries();
|
persistent_store.reset_pin_retries().unwrap();
|
||||||
assert_eq!(persistent_store.pin_retries(), MAX_PIN_RETRIES);
|
assert_eq!(persistent_store.pin_retries().unwrap(), MAX_PIN_RETRIES);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -993,7 +1016,7 @@ mod test {
|
|||||||
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.
|
// 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!(
|
assert_eq!(
|
||||||
persistent_store.attestation_private_key().unwrap().unwrap(),
|
persistent_store.attestation_private_key().unwrap().unwrap(),
|
||||||
key_material::ATTESTATION_PRIVATE_KEY
|
key_material::ATTESTATION_PRIVATE_KEY
|
||||||
@@ -1012,12 +1035,20 @@ mod test {
|
|||||||
let mut persistent_store = PersistentStore::new(&mut rng);
|
let mut persistent_store = PersistentStore::new(&mut rng);
|
||||||
|
|
||||||
// The minimum PIN length is initially at the default.
|
// 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..
|
// Changes by the setter are reflected by the getter..
|
||||||
let new_min_pin_length = 8;
|
let new_min_pin_length = 8;
|
||||||
persistent_store.set_min_pin_length(new_min_pin_length);
|
persistent_store
|
||||||
assert_eq!(persistent_store.min_pin_length(), new_min_pin_length);
|
.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")]
|
#[cfg(feature = "with_ctap2_1")]
|
||||||
@@ -1028,7 +1059,7 @@ mod test {
|
|||||||
|
|
||||||
// The minimum PIN length RP IDs are initially at the default.
|
// The minimum PIN length RP IDs are initially at the default.
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
persistent_store._min_pin_length_rp_ids(),
|
persistent_store._min_pin_length_rp_ids().unwrap(),
|
||||||
_DEFAULT_MIN_PIN_LENGTH_RP_IDS
|
_DEFAULT_MIN_PIN_LENGTH_RP_IDS
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1043,7 +1074,7 @@ mod test {
|
|||||||
rp_ids.push(rp_id);
|
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]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user