use super::{Command, CommandError, CtapResponse, PinUvAuthCommand, RequestCtap2, StatusCode}; use crate::{ crypto::{COSEKey, PinUvAuthParam, PinUvAuthToken}, ctap2::server::{ PublicKeyCredentialDescriptor, PublicKeyCredentialUserEntity, RelyingParty, RpIdHash, UserVerificationRequirement, }, errors::AuthenticatorError, transport::errors::HIDError, FidoDevice, }; use serde::{ de::{Error as SerdeError, IgnoredAny, MapAccess, Visitor}, Deserialize, Deserializer, Serialize, Serializer, }; use serde_bytes::ByteBuf; use serde_cbor::{de::from_slice, to_vec, Value}; use std::fmt; #[derive(Debug, Clone, Deserialize, Default)] struct CredManagementParams { rp_id_hash: Option, // RP ID SHA-256 hash credential_id: Option, // Credential Identifier user: Option, // User Entity } impl CredManagementParams { fn has_some(&self) -> bool { self.rp_id_hash.is_some() || self.credential_id.is_some() || self.user.is_some() } } impl Serialize for CredManagementParams { fn serialize(&self, serializer: S) -> Result where S: Serializer, { serialize_map_optional!( serializer, &0x01 => self.rp_id_hash.as_ref().map(|r| ByteBuf::from(r.as_ref())), &0x02 => &self.credential_id, &0x03 => &self.user, ) } } #[derive(Debug, Clone, Deserialize, Serialize)] pub(crate) enum CredManagementCommand { GetCredsMetadata, EnumerateRPsBegin, EnumerateRPsGetNextRP, EnumerateCredentialsBegin(RpIdHash), EnumerateCredentialsGetNextCredential, DeleteCredential(PublicKeyCredentialDescriptor), UpdateUserInformation((PublicKeyCredentialDescriptor, PublicKeyCredentialUserEntity)), } impl CredManagementCommand { fn to_id_and_param(&self) -> (u8, CredManagementParams) { let mut params = CredManagementParams::default(); match &self { CredManagementCommand::GetCredsMetadata => (0x01, params), CredManagementCommand::EnumerateRPsBegin => (0x02, params), CredManagementCommand::EnumerateRPsGetNextRP => (0x03, params), CredManagementCommand::EnumerateCredentialsBegin(rp_id_hash) => { params.rp_id_hash = Some(rp_id_hash.clone()); (0x04, params) } CredManagementCommand::EnumerateCredentialsGetNextCredential => (0x05, params), CredManagementCommand::DeleteCredential(cred_id) => { params.credential_id = Some(cred_id.clone()); (0x06, params) } CredManagementCommand::UpdateUserInformation((cred_id, user)) => { params.credential_id = Some(cred_id.clone()); params.user = Some(user.clone()); (0x07, params) } } } } #[derive(Debug)] pub struct CredentialManagement { pub(crate) subcommand: CredManagementCommand, // subCommand currently being requested pin_uv_auth_param: Option, // First 16 bytes of HMAC-SHA-256 of contents using pinUvAuthToken. use_legacy_preview: bool, } impl CredentialManagement { pub(crate) fn new(subcommand: CredManagementCommand, use_legacy_preview: bool) -> Self { Self { subcommand, pin_uv_auth_param: None, use_legacy_preview, } } } impl Serialize for CredentialManagement { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let (id, params) = self.subcommand.to_id_and_param(); serialize_map_optional!( serializer, &0x01 => Some(&id), &0x02 => params.has_some().then_some(¶ms), &0x03 => self.pin_uv_auth_param.as_ref().map(|p| p.pin_protocol.id()), &0x04 => &self.pin_uv_auth_param, ) } } #[derive(Debug, Default)] pub struct CredentialManagementResponse { /// Number of existing discoverable credentials present on the authenticator. pub existing_resident_credentials_count: Option, /// Number of maximum possible remaining discoverable credentials which can be created on the authenticator. pub max_possible_remaining_resident_credentials_count: Option, /// RP Information pub rp: Option, /// RP ID SHA-256 hash pub rp_id_hash: Option, /// Total number of RPs present on the authenticator pub total_rps: Option, /// User Information pub user: Option, /// Credential ID pub credential_id: Option, /// Public key of the credential. pub public_key: Option, /// Total number of credentials present on the authenticator for the RP in question pub total_credentials: Option, /// Credential protection policy. pub cred_protect: Option, /// Large blob encryption key. pub large_blob_key: Option>, } impl CtapResponse for CredentialManagementResponse {} #[derive(Debug, PartialEq, Eq, Serialize)] pub struct CredentialRpListEntry { /// RP Information pub rp: RelyingParty, /// RP ID SHA-256 hash pub rp_id_hash: RpIdHash, pub credentials: Vec, } #[derive(Debug, PartialEq, Eq, Serialize)] pub struct CredentialListEntry { /// User Information pub user: PublicKeyCredentialUserEntity, /// Credential ID pub credential_id: PublicKeyCredentialDescriptor, /// Public key of the credential. pub public_key: COSEKey, /// Credential protection policy. pub cred_protect: u64, /// Large blob encryption key. pub large_blob_key: Option>, } #[derive(Debug, Serialize)] pub enum CredentialManagementResult { CredentialList(CredentialList), DeleteSucess, UpdateSuccess, } #[derive(Debug, Default, Serialize)] pub struct CredentialList { /// Number of existing discoverable credentials present on the authenticator. pub existing_resident_credentials_count: u64, /// Number of maximum possible remaining discoverable credentials which can be created on the authenticator. pub max_possible_remaining_resident_credentials_count: u64, /// The found credentials pub credential_list: Vec, } impl CredentialList { pub fn new() -> Self { Default::default() } } impl<'de> Deserialize<'de> for CredentialManagementResponse { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { struct CredentialManagementResponseVisitor; impl<'de> Visitor<'de> for CredentialManagementResponseVisitor { type Value = CredentialManagementResponse; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a map") } fn visit_map(self, mut map: M) -> Result where M: MapAccess<'de>, { let mut existing_resident_credentials_count = None; // (0x01) Unsigned Integer Number of existing discoverable credentials present on the authenticator. let mut max_possible_remaining_resident_credentials_count = None; // (0x02) Unsigned Integer Number of maximum possible remaining discoverable credentials which can be created on the authenticator. let mut rp = None; // (0x03) PublicKeyCredentialRpEntity RP Information let mut rp_id_hash = None; // (0x04) Byte String RP ID SHA-256 hash let mut total_rps = None; // (0x05) Unsigned Integer total number of RPs present on the authenticator let mut user = None; // (0x06) PublicKeyCredentialUserEntity User Information let mut credential_id = None; // (0x07) PublicKeyCredentialDescriptor PublicKeyCredentialDescriptor let mut public_key = None; // (0x08) COSE_Key Public key of the credential. let mut total_credentials = None; // (0x09) Unsigned Integer Total number of credentials present on the authenticator for the RP in question let mut cred_protect = None; // (0x0A) Unsigned Integer Credential protection policy. let mut large_blob_key = None; // (0x0B) Byte string Large blob encryption key. while let Some(key) = map.next_key()? { match key { 0x01 => { if existing_resident_credentials_count.is_some() { return Err(SerdeError::duplicate_field( "existing_resident_credentials_count", )); } existing_resident_credentials_count = Some(map.next_value()?); } 0x02 => { if max_possible_remaining_resident_credentials_count.is_some() { return Err(SerdeError::duplicate_field( "max_possible_remaining_resident_credentials_count", )); } max_possible_remaining_resident_credentials_count = Some(map.next_value()?); } 0x03 => { if rp.is_some() { return Err(SerdeError::duplicate_field("rp")); } rp = Some(map.next_value()?); } 0x04 => { if rp_id_hash.is_some() { return Err(SerdeError::duplicate_field("rp_id_hash")); } let rp_raw = map.next_value::()?; rp_id_hash = Some(RpIdHash::from(rp_raw.as_slice()).map_err(|_| { SerdeError::invalid_length(rp_raw.len(), &"32") })?); } 0x05 => { if total_rps.is_some() { return Err(SerdeError::duplicate_field("total_rps")); } total_rps = Some(map.next_value()?); } 0x06 => { if user.is_some() { return Err(SerdeError::duplicate_field("user")); } user = Some(map.next_value()?); } 0x07 => { if credential_id.is_some() { return Err(SerdeError::duplicate_field("credential_id")); } credential_id = Some(map.next_value()?); } 0x08 => { if public_key.is_some() { return Err(SerdeError::duplicate_field("public_key")); } public_key = Some(map.next_value()?); } 0x09 => { if total_credentials.is_some() { return Err(SerdeError::duplicate_field("total_credentials")); } total_credentials = Some(map.next_value()?); } 0x0A => { if cred_protect.is_some() { return Err(SerdeError::duplicate_field("cred_protect")); } cred_protect = Some(map.next_value()?); } 0x0B => { if large_blob_key.is_some() { return Err(SerdeError::duplicate_field("large_blob_key")); } // Using into_vec, to avoid any copy of large_blob_key large_blob_key = Some(map.next_value::()?.into_vec()); } k => { warn!("ClientPinResponse: unexpected key: {:?}", k); let _ = map.next_value::()?; continue; } } } Ok(CredentialManagementResponse { existing_resident_credentials_count, max_possible_remaining_resident_credentials_count, rp, rp_id_hash, total_rps, user, credential_id, public_key, total_credentials, cred_protect, large_blob_key, }) } } deserializer.deserialize_bytes(CredentialManagementResponseVisitor) } } impl RequestCtap2 for CredentialManagement { type Output = CredentialManagementResponse; fn command(&self) -> Command { if self.use_legacy_preview { Command::CredentialManagementPreview } else { Command::CredentialManagement } } fn wire_format(&self) -> Result, HIDError> { let output = to_vec(&self).map_err(CommandError::Serializing)?; trace!("client subcommmand: {:04X?}", &output); Ok(output) } fn handle_response_ctap2( &self, _dev: &mut Dev, input: &[u8], ) -> Result where Dev: FidoDevice, { if input.is_empty() { return Err(CommandError::InputTooSmall.into()); } let status: StatusCode = input[0].into(); if status.is_ok() { if input.len() > 1 { trace!("parsing credential management data: {:#04X?}", &input); let credential_management = from_slice(&input[1..]).map_err(CommandError::Deserializing)?; Ok(credential_management) } else { // Some subcommands return only an OK-status without any data Ok(CredentialManagementResponse::default()) } } else { let data: Option = if input.len() > 1 { Some(from_slice(&input[1..]).map_err(CommandError::Deserializing)?) } else { None }; Err(CommandError::StatusCode(status, data).into()) } } fn send_to_virtual_device( &self, _dev: &mut Dev, ) -> Result { unimplemented!() } } impl PinUvAuthCommand for CredentialManagement { fn get_rp_id(&self) -> Option<&String> { None } fn set_pin_uv_auth_param( &mut self, pin_uv_auth_token: Option, ) -> Result<(), AuthenticatorError> { let mut param = None; if let Some(token) = pin_uv_auth_token { // pinUvAuthParam (0x04): the result of calling // authenticate(pinUvAuthToken, uint8(subCommand) || subCommandParams). let (id, params) = self.subcommand.to_id_and_param(); let mut data = vec![id]; if params.has_some() { data.extend(to_vec(¶ms).map_err(CommandError::Serializing)?); } param = Some(token.derive(&data).map_err(CommandError::Crypto)?); } self.pin_uv_auth_param = param; Ok(()) } fn can_skip_user_verification( &mut self, _info: &crate::AuthenticatorInfo, _uv: UserVerificationRequirement, ) -> bool { // "discouraged" does not exist for AuthenticatorConfig false } fn set_uv_option(&mut self, _uv: Option) { /* No-op */ } fn get_pin_uv_auth_param(&self) -> Option<&PinUvAuthParam> { self.pin_uv_auth_param.as_ref() } fn hmac_requested(&self) -> bool { false } } #[cfg(test)] mod test { use crate::ctap2::server::{ PublicKeyCredentialDescriptor, PublicKeyCredentialUserEntity, RelyingParty, Transport, }; use super::CredManagementParams; #[test] fn test_serialize_cred_management_params() { let cred_management_params = CredManagementParams { rp_id_hash: Some(RelyingParty::from("example.org").hash()), credential_id: Some(PublicKeyCredentialDescriptor { id: vec![1, 2, 3, 4], transports: vec![Transport::USB, Transport::NFC], }), user: Some(PublicKeyCredentialUserEntity { id: vec![5, 6, 7, 8], name: Some("testuser".to_string()), display_name: Some("Test User".to_string()), }), }; let serialized = serde_cbor::ser::to_vec(&cred_management_params).expect("Failed to serialize to CBOR"); assert_eq!( serialized, [ // Value copied from test failure output as regression test snapshot 163, 1, 88, 32, 191, 171, 195, 116, 50, 149, 139, 6, 51, 96, 211, 173, 100, 97, 201, 196, 115, 90, 231, 248, 237, 212, 101, 146, 165, 224, 240, 20, 82, 178, 228, 181, 2, 162, 98, 105, 100, 68, 1, 2, 3, 4, 100, 116, 121, 112, 101, 106, 112, 117, 98, 108, 105, 99, 45, 107, 101, 121, 3, 163, 98, 105, 100, 68, 5, 6, 7, 8, 100, 110, 97, 109, 101, 104, 116, 101, 115, 116, 117, 115, 101, 114, 107, 100, 105, 115, 112, 108, 97, 121, 78, 97, 109, 101, 105, 84, 101, 115, 116, 32, 85, 115, 101, 114 ] ); } }