diff options
Diffstat (limited to 'third_party/rust/authenticator/src/ctap2/commands/bio_enrollment.rs')
-rw-r--r-- | third_party/rust/authenticator/src/ctap2/commands/bio_enrollment.rs | 660 |
1 files changed, 660 insertions, 0 deletions
diff --git a/third_party/rust/authenticator/src/ctap2/commands/bio_enrollment.rs b/third_party/rust/authenticator/src/ctap2/commands/bio_enrollment.rs new file mode 100644 index 0000000000..817bb5ac51 --- /dev/null +++ b/third_party/rust/authenticator/src/ctap2/commands/bio_enrollment.rs @@ -0,0 +1,660 @@ +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<u8> for BioEnrollmentModality { + fn from(value: u8) -> Self { + match value { + 0x01 => BioEnrollmentModality::Fingerprint, + x => BioEnrollmentModality::Other(x), + } + } +} + +impl From<BioEnrollmentModality> for u8 { + fn from(value: BioEnrollmentModality) -> Self { + match value { + BioEnrollmentModality::Fingerprint => 0x01, + BioEnrollmentModality::Other(x) => x, + } + } +} + +impl Serialize for BioEnrollmentModality { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + serializer.serialize_u8((*self).into()) + } +} + +impl<'de> Deserialize<'de> for BioEnrollmentModality { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + 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<E>(self, v: u8) -> Result<Self::Value, E> + where + E: SerdeError, + { + Ok(BioEnrollmentModality::from(v)) + } + } + + deserializer.deserialize_u8(BioEnrollmentModalityVisitor) + } +} + +pub type BioTemplateId = Vec<u8>; +#[derive(Debug, Clone, Deserialize, Default)] +struct BioEnrollmentParams { + template_id: Option<BioTemplateId>, // Template Identifier. + template_friendly_name: Option<String>, // Template Friendly Name. + timeout_milliseconds: Option<u64>, // 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<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + 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<u64>), + EnrollCaptureNextSample((BioTemplateId, Option<u64>)), + 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<PinUvAuthParam>, + 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<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + 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<PinUvAuthToken>, + ) -> 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<bool>) { + /* 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<Vec<u8>, HIDError> { + let output = to_vec(&self).map_err(CommandError::Serializing)?; + trace!("client subcommmand: {:04X?}", &output); + Ok(output) + } + + fn handle_response_ctap2<Dev>( + &self, + _dev: &mut Dev, + input: &[u8], + ) -> Result<Self::Output, HIDError> + 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<Value> = 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<Dev: crate::VirtualFidoDevice>( + &self, + _dev: &mut Dev, + ) -> Result<Self::Output, HIDError> { + 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<D>(deserializer: D) -> Result<Self, D::Error> + 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<E>(self, v: u8) -> Result<Self::Value, E> + 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<D>(deserializer: D) -> Result<Self, D::Error> + 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<E>(self, v: u8) -> Result<Self::Value, E> + 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<String>, +} + +impl<'de> Deserialize<'de> for BioTemplateInfo { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + 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<M>(self, mut map: M) -> Result<Self::Value, M::Error> + 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::<ByteBuf>()?.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::<IgnoredAny>()?; + 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<BioEnrollmentModality>, + /// 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<FingerprintKind>, + /// Indicates the maximum good samples required for enrollment. + pub(crate) max_capture_samples_required_for_enroll: Option<u64>, + /// Template Identifier. + pub(crate) template_id: Option<BioTemplateId>, + /// Last enrollment sample status. + pub(crate) last_enroll_sample_status: Option<LastEnrollmentSampleStatus>, + /// Number of more sample required for enrollment to complete + pub(crate) remaining_samples: Option<u64>, + /// Array of templateInfo’s + pub(crate) template_infos: Vec<BioTemplateInfo>, + /// Indicates the maximum number of bytes the authenticator will accept as a templateFriendlyName. + pub(crate) max_template_friendly_name: Option<u64>, +} + +impl CtapResponse for BioEnrollmentResponse {} + +impl<'de> Deserialize<'de> for BioEnrollmentResponse { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + 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<M>(self, mut map: M) -> Result<Self::Value, M::Error> + 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::<ByteBuf>()?.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::<IgnoredAny>()?; + 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<u8>, + pub template_friendly_name: Option<String>, +} + +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<u64>, +} + +#[derive(Debug, Serialize)] +pub enum BioEnrollmentResult { + EnrollmentList(Vec<EnrollmentInfo>), + DeleteSuccess(AuthenticatorInfo), + UpdateSuccess, + AddSuccess(AuthenticatorInfo), + FingerprintSensorInfo(FingerprintSensorInfo), + SampleStatus(LastEnrollmentSampleStatus, u64), +} |