Keep old credential ID for existing credentials

In #59, we changed the format for serialized credentials to use shorter
field names for the RP and user entities.  This has an unintended side
effect:  For non-discoverable credentials that were generated with older
crate versions, the stripped data embedded into the credential ID
includes the RP and user.  If we change their serialization format, we
also change these credential IDs.

We already supported deserializing both formats using a serde alias.
This patch introduces helper enums that deserialize both formats using a
custom Deserialize implementation and keep track of the used format.
This format is then also used for serialization (using serde’s untagged
mechanism that is not available for deserialization in no-std contexts).

https://github.com/Nitrokey/fido-authenticator/pull/59

Fixes: https://github.com/Nitrokey/fido-authenticator/issues/111
This commit is contained in:
Robin Krahl
2024-12-02 14:33:29 +01:00
parent 86403fa9f2
commit 5c3aa0b8af
3 changed files with 520 additions and 300 deletions
+499 -285
View File
File diff suppressed because it is too large Load Diff
+8 -6
View File
@@ -9,7 +9,9 @@ use ctap_types::{
},
heapless::{String, Vec},
heapless_bytes::Bytes,
sizes, ByteArray, Error,
sizes,
webauthn::PublicKeyCredentialUserEntity,
ByteArray, Error,
};
use littlefs2_core::path;
use sha2::{Digest as _, Sha256};
@@ -388,7 +390,7 @@ impl<UP: UserPresence, T: TrussedRequirements> Authenticator for crate::Authenti
let serialized_credential = credential.serialize()?;
// first delete any other RK cred with same RP + UserId if there is one.
self.delete_resident_key_by_user_id(&rp_id_hash, &credential.user.id)
self.delete_resident_key_by_user_id(&rp_id_hash, credential.user.id())
.ok();
let mut key_store_full = self.can_fit(serialized_credential.len()) == Some(false)
@@ -1739,8 +1741,8 @@ impl<UP: UserPresence, T: TrussedRequirements> crate::Authenticator<UP, T> {
// User with empty IDs are ignored for compatibility
if is_rk {
if let Credential::Full(credential) = &credential {
if !credential.user.id.is_empty() {
let mut user = credential.user.clone();
if !credential.user.id().is_empty() {
let mut user: PublicKeyCredentialUserEntity = credential.user.clone().into();
// User identifiable information (name, DisplayName, icon) MUST not
// be returned if user verification is not done by the authenticator.
// For single account per RP case, authenticator returns "id" field.
@@ -1749,7 +1751,7 @@ impl<UP: UserPresence, T: TrussedRequirements> crate::Authenticator<UP, T> {
user.name = None;
user.display_name = None;
}
response.user = Some(user.into());
response.user = Some(user);
}
}
@@ -1793,7 +1795,7 @@ impl<UP: UserPresence, T: TrussedRequirements> crate::Authenticator<UP, T> {
let credential_maybe = FullCredential::deserialize(&credential_data);
if let Ok(old_credential) = credential_maybe {
if old_credential.user.id == user_id {
if old_credential.user.id() == user_id {
match old_credential.key {
credential::Key::ResidentKey(key) => {
info_now!(":: deleting resident key");
+13 -9
View File
@@ -174,7 +174,7 @@ where
let rp = credential.data.rp;
response.rp_id_hash = Some(ByteArray::new(self.hash(rp.id.as_ref())));
response.rp_id_hash = Some(ByteArray::new(self.hash(rp.id().as_ref())));
response.rp = Some(rp.into());
}
}
@@ -251,7 +251,7 @@ where
let rp = credential.data.rp;
response.rp_id_hash = Some(ByteArray::new(self.hash(rp.id.as_ref())));
response.rp_id_hash = Some(ByteArray::new(self.hash(rp.id().as_ref())));
response.rp = Some(rp.into());
// cache state for next call
@@ -524,18 +524,22 @@ where
// TODO: check remaining space, return KeyStoreFull
// the updated user ID must match the stored user ID
if credential.user.id != user.id {
if credential.user.id() != &user.id {
error!("updated user ID does not match original user ID");
return Err(Error::InvalidParameter);
}
// update user name and display name unless the values are not set or empty
credential.data.user.name = user.name.as_ref().filter(|s| !s.is_empty()).cloned();
credential.data.user.display_name = user
.display_name
.as_ref()
.filter(|s| !s.is_empty())
.cloned();
credential
.data
.user
.set_name(user.name.as_ref().filter(|s| !s.is_empty()).cloned());
credential.data.user.set_display_name(
user.display_name
.as_ref()
.filter(|s| !s.is_empty())
.cloned(),
);
// write updated credential
let serialized = credential.serialize()?;