diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d1539e..b72fcea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Extract PIN protocol implementation into separate module ([#62][]) - Implement PIN protocol 2 ([#63][]) - Implement PIN token permissions ([#63][]) +- Implement UpdateUserInformation subcommand for CredentialManagement [#26]: https://github.com/solokeys/fido-authenticator/issues/26 [#28]: https://github.com/solokeys/fido-authenticator/issues/28 diff --git a/src/ctap2.rs b/src/ctap2.rs index 62ba5a9..5adf984 100644 --- a/src/ctap2.rs +++ b/src/ctap2.rs @@ -990,7 +990,19 @@ impl Authenticator for crate::Authenti } // 0x7 - Subcommand::UpdateUserInformation => Err(Error::InvalidParameter), + Subcommand::UpdateUserInformation => { + let sub_parameters = sub_parameters.as_ref().ok_or(Error::MissingParameter)?; + let credential_id = sub_parameters + .credential_id + .as_ref() + .ok_or(Error::MissingParameter)?; + let user = sub_parameters + .user + .as_ref() + .ok_or(Error::MissingParameter)?; + + cred_mgmt.update_user_information(credential_id, user) + } } } @@ -1338,7 +1350,8 @@ impl crate::Authenticator { sub_command @ Subcommand::GetCredsMetadata | sub_command @ Subcommand::EnumerateRpsBegin | sub_command @ Subcommand::EnumerateCredentialsBegin - | sub_command @ Subcommand::DeleteCredential => { + | sub_command @ Subcommand::DeleteCredential + | sub_command @ Subcommand::UpdateUserInformation => { // check pinProtocol let pin_protocol = parameters // .sub_command_params.as_ref().ok_or(Error::MissingParameter)? @@ -1350,7 +1363,9 @@ impl crate::Authenticator { let mut data: Bytes<{ sizes::MAX_CREDENTIAL_ID_LENGTH_PLUS_256 }> = Bytes::from_slice(&[sub_command as u8]).unwrap(); let len = 1 + match sub_command { - Subcommand::EnumerateCredentialsBegin | Subcommand::DeleteCredential => { + Subcommand::EnumerateCredentialsBegin + | Subcommand::DeleteCredential + | Subcommand::UpdateUserInformation => { data.resize_to_capacity(); // ble, need to reserialize ctap_types::serde::cbor_serialize( @@ -1395,9 +1410,6 @@ impl crate::Authenticator { // of already checked CredMgmt subcommands Subcommand::EnumerateRpsGetNextRp | Subcommand::EnumerateCredentialsGetNextCredential => Ok(()), - - // not implemented - Subcommand::UpdateUserInformation => Err(Error::InvalidParameter), } } diff --git a/src/ctap2/credential_management.rs b/src/ctap2/credential_management.rs index 0aba519..6ec33ac 100644 --- a/src/ctap2/credential_management.rs +++ b/src/ctap2/credential_management.rs @@ -3,7 +3,7 @@ use core::convert::TryFrom; use trussed::{ - syscall, + syscall, try_syscall, types::{DirEntry, Location, Path, PathBuf}, }; @@ -11,7 +11,7 @@ use ctap_types::{ cose::PublicKey, ctap2::credential_management::{CredentialProtectionPolicy, Response}, heapless_bytes::Bytes, - webauthn::PublicKeyCredentialDescriptor, + webauthn::{PublicKeyCredentialDescriptor, PublicKeyCredentialUserEntity}, Error, }; @@ -460,22 +460,27 @@ where Ok(response) } - pub fn delete_credential( - &mut self, - credential_descriptor: &PublicKeyCredentialDescriptor, - ) -> Result { - info!("delete credential"); - let credential_id_hash = self.hash(&credential_descriptor.id[..]); + fn find_credential(&mut self, credential: &PublicKeyCredentialDescriptor) -> Option { + let credential_id_hash = self.hash(&credential.id[..]); let mut hex = [b'0'; 16]; super::format_hex(&credential_id_hash[..8], &mut hex); let dir = PathBuf::from(b"rk"); let filename = PathBuf::from(&hex); - let rk_path = syscall!(self + syscall!(self .trussed .locate_file(Location::Internal, Some(dir), filename,)) .path - .ok_or(Error::InvalidCredential)?; + } + + pub fn delete_credential( + &mut self, + credential_descriptor: &PublicKeyCredentialDescriptor, + ) -> Result { + info!("delete credential"); + let rk_path = self + .find_credential(credential_descriptor) + .ok_or(Error::InvalidCredential)?; // DELETE self.delete_resident_key_by_path(&rk_path)?; @@ -491,4 +496,49 @@ where let response = Default::default(); Ok(response) } + + pub fn update_user_information( + &mut self, + credential_descriptor: &PublicKeyCredentialDescriptor, + user: &PublicKeyCredentialUserEntity, + ) -> Result { + info!("update user information"); + + // locate and parse existing credential + let rk_path = self + .find_credential(credential_descriptor) + .ok_or(Error::NoCredentials)?; + let serialized = syscall!(self.trussed.read_file(Location::Internal, rk_path.clone())).data; + let mut credential = + FullCredential::deserialize(&serialized).map_err(|_| Error::InvalidCredential)?; + + // TODO: check remaining space, return KeyStoreFull + + // the updated user ID must match the stored 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()) + .map(Clone::clone); + credential.data.user.display_name = user + .display_name + .as_ref() + .filter(|s| !s.is_empty()) + .map(Clone::clone); + + // write updated credential + let serialized = credential.serialize()?; + try_syscall!(self + .trussed + .write_file(Location::Internal, rk_path, serialized, None)) + .map_err(|_| Error::KeyStoreFull)?; + + Ok(Default::default()) + } }