use crate::{ crypto::{PinUvAuthParam, PinUvAuthToken}, ctap2::server::UserVerificationRequirement, errors::AuthenticatorError, transport::errors::HIDError, AuthenticatorInfo, FidoDevice, }; use serde::{ de::{Error as SerdeError, IgnoredAny, MapAccess, Visitor}, ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer, }; use serde_bytes::ByteBuf; use serde_cbor::{from_slice, to_vec, Value}; use std::fmt; use super::{Command, CommandError, CtapResponse, PinUvAuthCommand, RequestCtap2, StatusCode}; #[derive(Debug, Clone, Copy)] pub enum BioEnrollmentModality { Fingerprint, Other(u8), } impl From for BioEnrollmentModality { fn from(value: u8) -> Self { match value { 0x01 => BioEnrollmentModality::Fingerprint, x => BioEnrollmentModality::Other(x), } } } impl From for u8 { fn from(value: BioEnrollmentModality) -> Self { match value { BioEnrollmentModality::Fingerprint => 0x01, BioEnrollmentModality::Other(x) => x, } } } impl Serialize for BioEnrollmentModality { fn serialize(&self, serializer: S) -> Result where S: Serializer, { serializer.serialize_u8((*self).into()) } } impl<'de> Deserialize<'de> for BioEnrollmentModality { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { struct BioEnrollmentModalityVisitor; impl<'de> Visitor<'de> for BioEnrollmentModalityVisitor { type Value = BioEnrollmentModality; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("an integer") } fn visit_u8(self, v: u8) -> Result where E: SerdeError, { Ok(BioEnrollmentModality::from(v)) } } deserializer.deserialize_u8(BioEnrollmentModalityVisitor) } } pub type BioTemplateId = Vec; #[derive(Debug, Clone, Deserialize, Default)] struct BioEnrollmentParams { template_id: Option, // Template Identifier. template_friendly_name: Option, // Template Friendly Name. timeout_milliseconds: Option, // Timeout in milliSeconds. } impl BioEnrollmentParams { fn has_some(&self) -> bool { self.template_id.is_some() || self.template_friendly_name.is_some() || self.timeout_milliseconds.is_some() } } impl Serialize for BioEnrollmentParams { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let mut map_len = 0; if self.template_id.is_some() { map_len += 1; } if self.template_friendly_name.is_some() { map_len += 1; } if self.timeout_milliseconds.is_some() { map_len += 1; } let mut map = serializer.serialize_map(Some(map_len))?; if let Some(template_id) = &self.template_id { map.serialize_entry(&0x01, &ByteBuf::from(template_id.as_slice()))?; } if let Some(template_friendly_name) = &self.template_friendly_name { map.serialize_entry(&0x02, template_friendly_name)?; } if let Some(timeout_milliseconds) = &self.timeout_milliseconds { map.serialize_entry(&0x03, timeout_milliseconds)?; } map.end() } } #[derive(Debug)] pub enum BioEnrollmentCommand { EnrollBegin(Option), EnrollCaptureNextSample((BioTemplateId, Option)), CancelCurrentEnrollment, EnumerateEnrollments, SetFriendlyName((BioTemplateId, String)), RemoveEnrollment(BioTemplateId), GetFingerprintSensorInfo, } impl BioEnrollmentCommand { fn to_id_and_param(&self) -> (u8, BioEnrollmentParams) { let mut params = BioEnrollmentParams::default(); match &self { BioEnrollmentCommand::EnrollBegin(timeout) => { params.timeout_milliseconds = *timeout; (0x01, params) } BioEnrollmentCommand::EnrollCaptureNextSample((id, timeout)) => { params.template_id = Some(id.clone()); params.timeout_milliseconds = *timeout; (0x02, params) } BioEnrollmentCommand::CancelCurrentEnrollment => (0x03, params), BioEnrollmentCommand::EnumerateEnrollments => (0x04, params), BioEnrollmentCommand::SetFriendlyName((id, name)) => { params.template_id = Some(id.clone()); params.template_friendly_name = Some(name.clone()); (0x05, params) } BioEnrollmentCommand::RemoveEnrollment(id) => { params.template_id = Some(id.clone()); (0x06, params) } BioEnrollmentCommand::GetFingerprintSensorInfo => (0x07, params), } } } #[derive(Debug)] pub struct BioEnrollment { /// The user verification modality being requested modality: BioEnrollmentModality, /// The authenticator user verification sub command currently being requested pub(crate) subcommand: BioEnrollmentCommand, /// First 16 bytes of HMAC-SHA-256 of contents using pinUvAuthToken. pin_uv_auth_param: Option, use_legacy_preview: bool, } impl BioEnrollment { pub(crate) fn new(subcommand: BioEnrollmentCommand, use_legacy_preview: bool) -> Self { Self { modality: BioEnrollmentModality::Fingerprint, // As per spec: Currently always "Fingerprint" subcommand, pin_uv_auth_param: None, use_legacy_preview, } } } impl Serialize for BioEnrollment { fn serialize(&self, serializer: S) -> Result where S: Serializer, { // Need to define how many elements are going to be in the map // beforehand let mut map_len = 2; let (id, params) = self.subcommand.to_id_and_param(); if params.has_some() { map_len += 1; } if self.pin_uv_auth_param.is_some() { map_len += 2; } let mut map = serializer.serialize_map(Some(map_len))?; map.serialize_entry(&0x01, &self.modality)?; // Per spec currently always Fingerprint map.serialize_entry(&0x02, &id)?; if params.has_some() { map.serialize_entry(&0x03, ¶ms)?; } if let Some(ref pin_uv_auth_param) = self.pin_uv_auth_param { map.serialize_entry(&0x04, &pin_uv_auth_param.pin_protocol.id())?; map.serialize_entry(&0x05, pin_uv_auth_param)?; } map.end() } } impl PinUvAuthCommand for BioEnrollment { 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, fingerprint (0x01) || uint8(subCommand) || subCommandParams). let (id, params) = self.subcommand.to_id_and_param(); let modality = self.modality.into(); let mut data = vec![modality, 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 BioEnrollment 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() } } impl RequestCtap2 for BioEnrollment { type Output = BioEnrollmentResponse; fn command(&self) -> Command { if self.use_legacy_preview { Command::BioEnrollmentPreview } else { Command::BioEnrollment } } 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 bio enrollment response data: {:#04X?}", &input); let bio_enrollment = from_slice(&input[1..]).map_err(CommandError::Deserializing)?; Ok(bio_enrollment) } else { // Some subcommands return only an OK-status without any data Ok(BioEnrollmentResponse::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!() } } #[derive(Debug, Copy, Clone, Serialize)] pub enum LastEnrollmentSampleStatus { /// Good fingerprint capture. Ctap2EnrollFeedbackFpGood, /// Fingerprint was too high. Ctap2EnrollFeedbackFpTooHigh, /// Fingerprint was too low. Ctap2EnrollFeedbackFpTooLow, /// Fingerprint was too left. Ctap2EnrollFeedbackFpTooLeft, /// Fingerprint was too right. Ctap2EnrollFeedbackFpTooRight, /// Fingerprint was too fast. Ctap2EnrollFeedbackFpTooFast, /// Fingerprint was too slow. Ctap2EnrollFeedbackFpTooSlow, /// Fingerprint was of poor quality. Ctap2EnrollFeedbackFpPoorQuality, /// Fingerprint was too skewed. Ctap2EnrollFeedbackFpTooSkewed, /// Fingerprint was too short. Ctap2EnrollFeedbackFpTooShort, /// Merge failure of the capture. Ctap2EnrollFeedbackFpMergeFailure, /// Fingerprint already exists. Ctap2EnrollFeedbackFpExists, /// (this error number is available) Unused, /// User did not touch/swipe the authenticator. Ctap2EnrollFeedbackNoUserActivity, /// User did not lift the finger off the sensor. Ctap2EnrollFeedbackNoUserPresenceTransition, /// Other possible failure cases that are not (yet) defined by the spec Ctap2EnrollFeedbackOther(u8), } impl<'de> Deserialize<'de> for LastEnrollmentSampleStatus { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { struct LastEnrollmentSampleStatusVisitor; impl<'de> Visitor<'de> for LastEnrollmentSampleStatusVisitor { type Value = LastEnrollmentSampleStatus; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("an integer") } fn visit_u8(self, v: u8) -> Result where E: SerdeError, { match v { 0x00 => Ok(LastEnrollmentSampleStatus::Ctap2EnrollFeedbackFpGood), 0x01 => Ok(LastEnrollmentSampleStatus::Ctap2EnrollFeedbackFpTooHigh), 0x02 => Ok(LastEnrollmentSampleStatus::Ctap2EnrollFeedbackFpTooLow), 0x03 => Ok(LastEnrollmentSampleStatus::Ctap2EnrollFeedbackFpTooLeft), 0x04 => Ok(LastEnrollmentSampleStatus::Ctap2EnrollFeedbackFpTooRight), 0x05 => Ok(LastEnrollmentSampleStatus::Ctap2EnrollFeedbackFpTooFast), 0x06 => Ok(LastEnrollmentSampleStatus::Ctap2EnrollFeedbackFpTooSlow), 0x07 => Ok(LastEnrollmentSampleStatus::Ctap2EnrollFeedbackFpPoorQuality), 0x08 => Ok(LastEnrollmentSampleStatus::Ctap2EnrollFeedbackFpTooSkewed), 0x09 => Ok(LastEnrollmentSampleStatus::Ctap2EnrollFeedbackFpTooShort), 0x0A => Ok(LastEnrollmentSampleStatus::Ctap2EnrollFeedbackFpMergeFailure), 0x0B => Ok(LastEnrollmentSampleStatus::Ctap2EnrollFeedbackFpExists), 0x0C => Ok(LastEnrollmentSampleStatus::Unused), 0x0D => Ok(LastEnrollmentSampleStatus::Ctap2EnrollFeedbackNoUserActivity), 0x0E => { Ok(LastEnrollmentSampleStatus::Ctap2EnrollFeedbackNoUserPresenceTransition) } x => Ok(LastEnrollmentSampleStatus::Ctap2EnrollFeedbackOther(x)), } } } deserializer.deserialize_u8(LastEnrollmentSampleStatusVisitor) } } #[derive(Debug, Copy, Clone, Serialize)] pub enum FingerprintKind { TouchSensor, SwipeSensor, // Not (yet) defined by the spec Other(u8), } impl<'de> Deserialize<'de> for FingerprintKind { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { struct FingerprintKindVisitor; impl<'de> Visitor<'de> for FingerprintKindVisitor { type Value = FingerprintKind; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("an integer") } fn visit_u8(self, v: u8) -> Result where E: SerdeError, { match v { 0x01 => Ok(FingerprintKind::TouchSensor), 0x02 => Ok(FingerprintKind::SwipeSensor), x => Ok(FingerprintKind::Other(x)), } } } deserializer.deserialize_u8(FingerprintKindVisitor) } } #[derive(Debug, Serialize)] pub(crate) struct BioTemplateInfo { template_id: BioTemplateId, template_friendly_name: Option, } impl<'de> Deserialize<'de> for BioTemplateInfo { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { struct BioTemplateInfoResponseVisitor; impl<'de> Visitor<'de> for BioTemplateInfoResponseVisitor { type Value = BioTemplateInfo; 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 template_id = None; // (0x01) let mut template_friendly_name = None; // (0x02) while let Some(key) = map.next_key()? { match key { 0x01 => { if template_id.is_some() { return Err(SerdeError::duplicate_field("template_id")); } template_id = Some(map.next_value::()?.into_vec()); } 0x02 => { if template_friendly_name.is_some() { return Err(SerdeError::duplicate_field("template_friendly_name")); } template_friendly_name = Some(map.next_value()?); } k => { warn!("BioTemplateInfo: unexpected key: {:?}", k); let _ = map.next_value::()?; continue; } } } if let Some(template_id) = template_id { Ok(BioTemplateInfo { template_id, template_friendly_name, }) } else { Err(SerdeError::missing_field("template_id")) } } } deserializer.deserialize_bytes(BioTemplateInfoResponseVisitor) } } #[derive(Default, Debug)] pub struct BioEnrollmentResponse { /// The user verification modality. pub(crate) modality: Option, /// Indicates the type of fingerprint sensor. For touch type sensor, its value is 1. For swipe type sensor its value is 2. pub(crate) fingerprint_kind: Option, /// Indicates the maximum good samples required for enrollment. pub(crate) max_capture_samples_required_for_enroll: Option, /// Template Identifier. pub(crate) template_id: Option, /// Last enrollment sample status. pub(crate) last_enroll_sample_status: Option, /// Number of more sample required for enrollment to complete pub(crate) remaining_samples: Option, /// Array of templateInfo’s pub(crate) template_infos: Vec, /// Indicates the maximum number of bytes the authenticator will accept as a templateFriendlyName. pub(crate) max_template_friendly_name: Option, } impl CtapResponse for BioEnrollmentResponse {} impl<'de> Deserialize<'de> for BioEnrollmentResponse { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { struct BioEnrollmentResponseVisitor; impl<'de> Visitor<'de> for BioEnrollmentResponseVisitor { type Value = BioEnrollmentResponse; 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 modality = None; // (0x01) let mut fingerprint_kind = None; // (0x02) let mut max_capture_samples_required_for_enroll = None; // (0x03) let mut template_id = None; // (0x04) let mut last_enroll_sample_status = None; // (0x05) let mut remaining_samples = None; // (0x06) let mut template_infos = None; // (0x07) let mut max_template_friendly_name = None; // (0x08) while let Some(key) = map.next_key()? { match key { 0x01 => { if modality.is_some() { return Err(SerdeError::duplicate_field("modality")); } modality = Some(map.next_value()?); } 0x02 => { if fingerprint_kind.is_some() { return Err(SerdeError::duplicate_field("fingerprint_kind")); } fingerprint_kind = Some(map.next_value()?); } 0x03 => { if max_capture_samples_required_for_enroll.is_some() { return Err(SerdeError::duplicate_field( "max_capture_samples_required_for_enroll", )); } max_capture_samples_required_for_enroll = Some(map.next_value()?); } 0x04 => { if template_id.is_some() { return Err(SerdeError::duplicate_field("template_id")); } template_id = Some(map.next_value::()?.into_vec()); } 0x05 => { if last_enroll_sample_status.is_some() { return Err(SerdeError::duplicate_field( "last_enroll_sample_status", )); } last_enroll_sample_status = Some(map.next_value()?); } 0x06 => { if remaining_samples.is_some() { return Err(SerdeError::duplicate_field("remaining_samples")); } remaining_samples = Some(map.next_value()?); } 0x07 => { if template_infos.is_some() { return Err(SerdeError::duplicate_field("template_infos")); } template_infos = Some(map.next_value()?); } 0x08 => { if max_template_friendly_name.is_some() { return Err(SerdeError::duplicate_field( "max_template_friendly_name", )); } max_template_friendly_name = Some(map.next_value()?); } k => { warn!("BioEnrollmentResponse: unexpected key: {:?}", k); let _ = map.next_value::()?; continue; } } } Ok(BioEnrollmentResponse { modality, fingerprint_kind, max_capture_samples_required_for_enroll, template_id, last_enroll_sample_status, remaining_samples, template_infos: template_infos.unwrap_or_default(), max_template_friendly_name, }) } } deserializer.deserialize_bytes(BioEnrollmentResponseVisitor) } } #[derive(Debug, Serialize)] pub struct EnrollmentInfo { pub template_id: Vec, pub template_friendly_name: Option, } impl From<&BioTemplateInfo> for EnrollmentInfo { fn from(value: &BioTemplateInfo) -> Self { Self { template_id: value.template_id.to_vec(), template_friendly_name: value.template_friendly_name.clone(), } } } #[derive(Debug, Serialize)] pub struct FingerprintSensorInfo { pub fingerprint_kind: FingerprintKind, pub max_capture_samples_required_for_enroll: u64, pub max_template_friendly_name: Option, } #[derive(Debug, Serialize)] pub enum BioEnrollmentResult { EnrollmentList(Vec), DeleteSuccess(AuthenticatorInfo), UpdateSuccess, AddSuccess(AuthenticatorInfo), FingerprintSensorInfo(FingerprintSensorInfo), SampleStatus(LastEnrollmentSampleStatus, u64), }