use crate::proto::ctap2::cbor;
use crate::{
ops::webauthn::UserVerificationRequirement,
pin::PinUvAuthProtocol,
proto::ctap2::{
Ctap2, Ctap2AuthTokenPermissionRole, Ctap2BioEnrollmentFingerprintKind,
Ctap2BioEnrollmentModality, Ctap2BioEnrollmentRequest, Ctap2BioEnrollmentTemplateId,
Ctap2ClientPinRequest, Ctap2GetInfoResponse, Ctap2LastEnrollmentSampleStatus,
Ctap2UserVerifiableRequest,
},
transport::Channel,
unwrap_field,
webauthn::{
error::{CtapError, Error, PlatformError},
handle_errors,
pin_uv_auth_token::{user_verification, UsedPinUvAuthToken},
},
UvUpdate,
};
use async_trait::async_trait;
use serde_bytes::ByteBuf;
use std::time::Duration;
use tracing::{info, warn};
#[async_trait]
pub trait BioEnrollment {
async fn get_bio_modality(
&mut self,
timeout: Duration,
) -> Result<Ctap2BioEnrollmentModality, Error>;
async fn get_fingerprint_sensor_info(
&mut self,
timeout: Duration,
) -> Result<Ctap2BioEnrollmentFingerprintSensorInfo, Error>;
async fn get_bio_enrollments(
&mut self,
timeout: Duration,
) -> Result<Vec<Ctap2BioEnrollmentTemplateId>, Error>;
async fn remove_bio_enrollment(
&mut self,
template_id: &[u8],
timeout: Duration,
) -> Result<(), Error>;
async fn rename_bio_enrollment(
&mut self,
template_id: &[u8],
template_friendly_name: &str,
timeout: Duration,
) -> Result<(), Error>;
async fn start_new_bio_enrollment(
&mut self,
enrollment_timeout: Option<Duration>,
timeout: Duration,
) -> Result<(Vec<u8>, Ctap2LastEnrollmentSampleStatus, u64), Error>;
async fn capture_next_bio_enrollment_sample(
&mut self,
template_id: &[u8],
enrollment_timeout: Option<Duration>,
timeout: Duration,
) -> Result<(Ctap2LastEnrollmentSampleStatus, u64), Error>;
async fn cancel_current_bio_enrollment(&mut self, timeout: Duration) -> Result<(), Error>;
}
#[derive(Debug, Clone)]
pub struct Ctap2BioEnrollmentFingerprintSensorInfo {
pub fingerprint_kind: Ctap2BioEnrollmentFingerprintKind,
pub max_capture_samples_required_for_enroll: Option<u64>,
pub max_template_friendly_name: Option<u64>,
}
#[async_trait]
impl<C> BioEnrollment for C
where
C: Channel,
{
async fn get_bio_modality(
&mut self,
timeout: Duration,
) -> Result<Ctap2BioEnrollmentModality, Error> {
let req = Ctap2BioEnrollmentRequest::new_get_modality();
let resp = self.ctap2_bio_enrollment(&req, timeout).await?;
match resp.modality {
Some(modality) => Ok(modality),
None => {
warn!("Channel did not return modality.");
Err(Error::Ctap(CtapError::Other))
}
}
}
async fn get_fingerprint_sensor_info(
&mut self,
timeout: Duration,
) -> Result<Ctap2BioEnrollmentFingerprintSensorInfo, Error> {
let req = Ctap2BioEnrollmentRequest::new_fingerprint_sensor_info();
let resp = self.ctap2_bio_enrollment(&req, timeout).await?;
let Some(fingerprint_kind) = resp.fingerprint_kind else {
warn!("Channel did not return fingerprint_kind in sensor info.");
return Err(Error::Ctap(CtapError::Other));
};
Ok(Ctap2BioEnrollmentFingerprintSensorInfo {
fingerprint_kind,
max_capture_samples_required_for_enroll: resp.max_capture_samples_required_for_enroll,
max_template_friendly_name: resp.max_template_friendly_name,
})
}
async fn get_bio_enrollments(
&mut self,
timeout: Duration,
) -> Result<Vec<Ctap2BioEnrollmentTemplateId>, Error> {
let mut req = Ctap2BioEnrollmentRequest::new_enumerate_enrollments();
let resp = loop {
let uv_auth_used = user_verification(
self,
UserVerificationRequirement::Preferred,
&mut req,
timeout,
)
.await?;
handle_errors!(
self,
self.ctap2_bio_enrollment(&req, timeout).await,
uv_auth_used,
timeout
)
};
Ok(resp?.template_infos.unwrap_or_default())
}
async fn remove_bio_enrollment(
&mut self,
template_id: &[u8],
timeout: Duration,
) -> Result<(), Error> {
let mut req = Ctap2BioEnrollmentRequest::new_remove_enrollment(template_id);
loop {
let uv_auth_used = user_verification(
self,
UserVerificationRequirement::Preferred,
&mut req,
timeout,
)
.await?;
handle_errors!(
self,
self.ctap2_bio_enrollment(&req, timeout).await,
uv_auth_used,
timeout
)
}?;
Ok(())
}
async fn rename_bio_enrollment(
&mut self,
template_id: &[u8],
template_friendly_name: &str,
timeout: Duration,
) -> Result<(), Error> {
let mut req =
Ctap2BioEnrollmentRequest::new_rename_enrollment(template_id, template_friendly_name);
loop {
let uv_auth_used = user_verification(
self,
UserVerificationRequirement::Preferred,
&mut req,
timeout,
)
.await?;
handle_errors!(
self,
self.ctap2_bio_enrollment(&req, timeout).await,
uv_auth_used,
timeout
)
}?;
Ok(())
}
async fn start_new_bio_enrollment(
&mut self,
enrollment_timeout: Option<Duration>,
timeout: Duration,
) -> Result<(Vec<u8>, Ctap2LastEnrollmentSampleStatus, u64), Error> {
let mut req = Ctap2BioEnrollmentRequest::new_start_new_enrollment(enrollment_timeout);
let resp = loop {
let uv_auth_used = user_verification(
self,
UserVerificationRequirement::Preferred,
&mut req,
timeout,
)
.await?;
handle_errors!(
self,
self.ctap2_bio_enrollment(&req, timeout).await,
uv_auth_used,
timeout
)
}?;
let remaining_samples = unwrap_field!(resp.remaining_samples);
let template_id = unwrap_field!(resp.template_id).clone();
let sample_status = unwrap_field!(resp.last_enroll_sample_status);
Ok((
cbor::to_vec(&template_id)?,
sample_status,
remaining_samples,
))
}
async fn capture_next_bio_enrollment_sample(
&mut self,
template_id: &[u8],
enrollment_timeout: Option<Duration>,
timeout: Duration,
) -> Result<(Ctap2LastEnrollmentSampleStatus, u64), Error> {
let mut req =
Ctap2BioEnrollmentRequest::new_next_enrollment(template_id, enrollment_timeout);
let resp = loop {
let uv_auth_used = user_verification(
self,
UserVerificationRequirement::Preferred,
&mut req,
timeout,
)
.await?;
handle_errors!(
self,
self.ctap2_bio_enrollment(&req, timeout).await,
uv_auth_used,
timeout
)
}?;
let remaining_samples = unwrap_field!(resp.remaining_samples);
let sample_status = unwrap_field!(resp.last_enroll_sample_status);
Ok((sample_status, remaining_samples))
}
async fn cancel_current_bio_enrollment(&mut self, timeout: Duration) -> Result<(), Error> {
let mut req = Ctap2BioEnrollmentRequest::new_cancel_current_enrollment();
loop {
let uv_auth_used = user_verification(
self,
UserVerificationRequirement::Preferred,
&mut req,
timeout,
)
.await?;
handle_errors!(
self,
self.ctap2_bio_enrollment(&req, timeout).await,
uv_auth_used,
timeout
)
}?;
Ok(())
}
}
impl Ctap2UserVerifiableRequest for Ctap2BioEnrollmentRequest {
fn ensure_uv_set(&mut self) {
}
fn calculate_and_set_uv_auth(
&mut self,
uv_proto: &dyn PinUvAuthProtocol,
uv_auth_token: &[u8],
) -> Result<(), Error> {
let subcommand = self
.subcommand
.ok_or(Error::Platform(PlatformError::InvalidDeviceResponse))?;
let mut data = vec![
Ctap2BioEnrollmentModality::Fingerprint as u8,
subcommand as u8,
];
if let Some(params) = &self.subcommand_params {
data.extend(cbor::to_vec(¶ms)?);
}
let uv_auth_param = uv_proto.authenticate(uv_auth_token, &data)?;
self.protocol = Some(uv_proto.version());
self.uv_auth_param = Some(ByteBuf::from(uv_auth_param));
Ok(())
}
fn permissions(&self) -> Ctap2AuthTokenPermissionRole {
Ctap2AuthTokenPermissionRole::BIO_ENROLLMENT
}
fn permissions_rpid(&self) -> Option<&str> {
None
}
fn can_use_uv(&self, info: &Ctap2GetInfoResponse) -> bool {
info.option_enabled("uvBioEnroll")
}
fn handle_legacy_preview(&mut self, info: &Ctap2GetInfoResponse) {
if let Some(options) = &info.options {
if options.get("bioEnroll") != Some(&true)
&& options.get("userVerificationMgmtPreview") == Some(&true)
{
self.use_legacy_preview = true;
}
}
}
fn needs_shared_secret(&self, _get_info_response: &Ctap2GetInfoResponse) -> bool {
false
}
}