Skip to content

Commit 9e462e6

Browse files
kaczmarczyckbradjc
andauthored
Uv bio enrollment, continued (#734)
* Add fingerprint and UV support * Refactors fingerprint code Introduces a compile flag to toggle fingerprint code. Makes data structures and style match the library's current standard. Removes some undesired customization and debug features. * Makes template IDs unique Also applies clippy lints. * Use all features for clippy, to reduce maintenance * Fixes misfiring unit test For some non-default customization settings, the test would give a false negative. --------- Co-authored-by: Brad Campbell <[email protected]>
1 parent ffef5ee commit 9e462e6

File tree

18 files changed

+1869
-77
lines changed

18 files changed

+1869
-77
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ with_ctap1 = ["opensk/with_ctap1"]
4343
with_nfc = ["libtock_drivers/with_nfc"]
4444
vendor_hid = ["opensk/vendor_hid"]
4545
ed25519 = ["ed25519-compact", "opensk/ed25519"]
46+
fingerprint = ["opensk/fingerprint"]
4647

4748
[dev-dependencies]
4849
enum-iterator = "0.6.0"

libraries/opensk/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ with_ctap1 = []
4242
vendor_hid = []
4343
fuzz = ["arbitrary", "std"]
4444
ed25519 = ["ed25519-compact"]
45+
fingerprint = []
4546

4647
[dev-dependencies]
4748
enum-iterator = "0.6.0"

libraries/opensk/src/api/customization.rs

Lines changed: 121 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ pub trait Customization {
145145
/// this value.
146146
fn max_msg_size(&self) -> usize;
147147

148-
/// Sets the number of consecutive failed PINs before blocking interaction.
148+
/// The number of consecutive failed PINs before blocking interaction.
149149
///
150150
/// # Invariant
151151
///
@@ -155,6 +155,42 @@ pub trait Customization {
155155
/// The fail retry counter is reset after entering the correct PIN.
156156
fn max_pin_retries(&self) -> u8;
157157

158+
/// Maximum number of UV retries performed before returning an error.
159+
///
160+
/// Corresponds to maxUvAttemptsForInternalRetries in CTAP 2.1 onwards.
161+
/// When internal retries are allowed, the authenticator will retry UV
162+
/// before communicating its result through CTAP.
163+
///
164+
/// # Invariant
165+
///
166+
/// - Must be in the range [1, `max_uv_retries`].
167+
/// - If `preferred_platform_uv_attempts` is not 1, must be in [1, 5].
168+
#[cfg(feature = "fingerprint")]
169+
fn max_uv_attempts_for_internal_retries(&self) -> u8;
170+
171+
/// The number of consecutive failed UV attempts before blocking built-in UV.
172+
///
173+
/// Corresponds to maxUvRetries in CTAP 2.1 onwards.
174+
///
175+
/// # Invariant
176+
///
177+
/// - Must be in the range [1, 25].
178+
#[cfg(feature = "fingerprint")]
179+
fn max_uv_retries(&self) -> u8;
180+
181+
/// Preferred number of UV attempts before falling back to PIN.
182+
///
183+
/// Corresponds to preferredPlatformUvAttempts in CTAP 2.1 onwards.
184+
/// States the preference how often the platform should invoke
185+
/// `getPinUvAuthTokenUsingUvWithPermissions` before falling back to
186+
/// `getPinUvAuthTokenUsingPinWithPermissions` or displaying an error.
187+
///
188+
/// # Invariant
189+
///
190+
/// - Must be greater than 0.
191+
#[cfg(feature = "fingerprint")]
192+
fn preferred_platform_uv_attempts(&self) -> usize;
193+
158194
/// Enables or disables basic attestation for FIDO2.
159195
///
160196
/// # Invariant
@@ -252,6 +288,12 @@ pub trait Customization {
252288
/// With P=20 and K=150, we have I=2M which is enough for 500 increments per day
253289
/// for 10 years.
254290
fn max_supported_resident_keys(&self) -> usize;
291+
292+
/// Maximum size of a friendly name for a UV template.
293+
///
294+
/// This value limits storage requirements for fingerprints.
295+
#[cfg(feature = "fingerprint")]
296+
fn max_template_friendly_name(&self) -> usize;
255297
}
256298

257299
#[derive(Clone)]
@@ -266,13 +308,21 @@ pub struct CustomizationImpl {
266308
pub enterprise_rp_id_list: &'static [&'static str],
267309
pub max_msg_size: usize,
268310
pub max_pin_retries: u8,
311+
#[cfg(feature = "fingerprint")]
312+
pub max_uv_attempts_for_internal_retries: u8,
313+
#[cfg(feature = "fingerprint")]
314+
pub max_uv_retries: u8,
315+
#[cfg(feature = "fingerprint")]
316+
pub preferred_platform_uv_attempts: usize,
269317
pub use_batch_attestation: bool,
270318
pub use_signature_counter: bool,
271319
pub max_cred_blob_length: usize,
272320
pub max_credential_count_in_list: Option<usize>,
273321
pub max_large_blob_array_size: usize,
274322
pub max_rp_ids_length: usize,
275323
pub max_supported_resident_keys: usize,
324+
#[cfg(feature = "fingerprint")]
325+
pub max_template_friendly_name: usize,
276326
}
277327

278328
pub const DEFAULT_CUSTOMIZATION: CustomizationImpl = CustomizationImpl {
@@ -286,13 +336,21 @@ pub const DEFAULT_CUSTOMIZATION: CustomizationImpl = CustomizationImpl {
286336
enterprise_rp_id_list: &[],
287337
max_msg_size: 7609,
288338
max_pin_retries: 8,
339+
#[cfg(feature = "fingerprint")]
340+
max_uv_attempts_for_internal_retries: 1,
341+
#[cfg(feature = "fingerprint")]
342+
max_uv_retries: 8,
343+
#[cfg(feature = "fingerprint")]
344+
preferred_platform_uv_attempts: 1,
289345
use_batch_attestation: false,
290346
use_signature_counter: true,
291347
max_cred_blob_length: 32,
292348
max_credential_count_in_list: None,
293349
max_large_blob_array_size: 2048,
294350
max_rp_ids_length: 8,
295351
max_supported_resident_keys: 150,
352+
#[cfg(feature = "fingerprint")]
353+
max_template_friendly_name: 64,
296354
};
297355

298356
impl Customization for CustomizationImpl {
@@ -347,6 +405,21 @@ impl Customization for CustomizationImpl {
347405
self.max_pin_retries
348406
}
349407

408+
#[cfg(feature = "fingerprint")]
409+
fn max_uv_attempts_for_internal_retries(&self) -> u8 {
410+
self.max_uv_attempts_for_internal_retries
411+
}
412+
413+
#[cfg(feature = "fingerprint")]
414+
fn max_uv_retries(&self) -> u8 {
415+
self.max_uv_retries
416+
}
417+
418+
#[cfg(feature = "fingerprint")]
419+
fn preferred_platform_uv_attempts(&self) -> usize {
420+
self.preferred_platform_uv_attempts
421+
}
422+
350423
fn use_batch_attestation(&self) -> bool {
351424
self.use_batch_attestation
352425
}
@@ -374,6 +447,11 @@ impl Customization for CustomizationImpl {
374447
fn max_supported_resident_keys(&self) -> usize {
375448
self.max_supported_resident_keys
376449
}
450+
451+
#[cfg(feature = "fingerprint")]
452+
fn max_template_friendly_name(&self) -> usize {
453+
self.max_template_friendly_name
454+
}
377455
}
378456

379457
#[cfg(feature = "std")]
@@ -446,6 +524,32 @@ pub fn is_valid(customization: &impl Customization) -> bool {
446524
return false;
447525
}
448526

527+
#[cfg(feature = "fingerprint")]
528+
{
529+
// Maximum UV retries must be in range [1, 25].
530+
if customization.max_uv_retries() < 1 || customization.max_uv_retries() > 25 {
531+
return false;
532+
}
533+
534+
// Maximum internal UV attemps must be in range [1, `max_uv_retries`].
535+
// If preferred platform UV attemps is not 1, must additionally be in [1, 5].
536+
if customization.max_uv_attempts_for_internal_retries() < 1
537+
|| customization.max_uv_attempts_for_internal_retries() > customization.max_uv_retries()
538+
{
539+
return false;
540+
}
541+
if customization.preferred_platform_uv_attempts() != 1
542+
&& customization.max_uv_attempts_for_internal_retries() > 5
543+
{
544+
return false;
545+
}
546+
547+
// Preferred number of UV attempts must be positive.
548+
if customization.preferred_platform_uv_attempts() == 0 {
549+
return false;
550+
}
551+
}
552+
449553
true
450554
}
451555

@@ -471,13 +575,21 @@ mod test {
471575
enterprise_rp_id_list: &[],
472576
max_msg_size: 7609,
473577
max_pin_retries: 8,
578+
#[cfg(feature = "fingerprint")]
579+
max_uv_attempts_for_internal_retries: 1,
580+
#[cfg(feature = "fingerprint")]
581+
max_uv_retries: 8,
582+
#[cfg(feature = "fingerprint")]
583+
preferred_platform_uv_attempts: 1,
474584
use_batch_attestation: true,
475585
use_signature_counter: true,
476586
max_cred_blob_length: 32,
477587
max_credential_count_in_list: Some(3),
478588
max_large_blob_array_size: 2048,
479589
max_rp_ids_length: 8,
480590
max_supported_resident_keys: 150,
591+
#[cfg(feature = "fingerprint")]
592+
max_template_friendly_name: 64,
481593
};
482594
assert_eq!(customization.aaguid(), &[0; AAGUID_LENGTH]);
483595
assert!(customization.allows_pin_protocol_v1());
@@ -492,12 +604,20 @@ mod test {
492604
assert!(customization.enterprise_rp_id_list().is_empty());
493605
assert_eq!(customization.max_msg_size(), 7609);
494606
assert_eq!(customization.max_pin_retries(), 8);
607+
#[cfg(feature = "fingerprint")]
608+
assert_eq!(customization.max_uv_attempts_for_internal_retries(), 1);
609+
#[cfg(feature = "fingerprint")]
610+
assert_eq!(customization.max_uv_retries(), 8);
611+
#[cfg(feature = "fingerprint")]
612+
assert_eq!(customization.preferred_platform_uv_attempts(), 1);
495613
assert!(customization.use_batch_attestation());
496614
assert!(customization.use_signature_counter());
497615
assert_eq!(customization.max_cred_blob_length(), 32);
498616
assert_eq!(customization.max_credential_count_in_list(), Some(3));
499617
assert_eq!(customization.max_large_blob_array_size(), 2048);
500618
assert_eq!(customization.max_rp_ids_length(), 8);
501619
assert_eq!(customization.max_supported_resident_keys(), 150);
620+
#[cfg(feature = "fingerprint")]
621+
assert_eq!(customization.max_template_friendly_name(), 64);
502622
}
503623
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
use crate::ctap::status_code::CtapResult;
2+
use alloc::vec::Vec;
3+
use sk_cbor as cbor;
4+
5+
#[derive(Debug, PartialEq, Eq)]
6+
pub enum FingerprintKind {
7+
Touch = 1,
8+
Swipe = 2,
9+
}
10+
11+
#[derive(Debug, PartialEq, Eq)]
12+
pub enum FingerprintCheckError {
13+
NoMatch,
14+
Timeout,
15+
Other,
16+
}
17+
18+
/// Status code for enrolling fingerprints
19+
///
20+
/// See lastEnrollSampleStatus in section authenticatorBioEnrollment
21+
#[derive(Debug, PartialEq, Eq)]
22+
pub enum Ctap2EnrollFeedback {
23+
FpGood = 0x00,
24+
FpTooHigh = 0x01,
25+
FpTooLow = 0x02,
26+
FpTooLeft = 0x03,
27+
FpTooRight = 0x04,
28+
FpTooFast = 0x05,
29+
FpTooSlow = 0x06,
30+
FpPoorQuality = 0x07,
31+
FpTooSkewed = 0x08,
32+
FpTooShort = 0x09,
33+
FpMergeFailure = 0x0A,
34+
FpExists = 0x0B,
35+
// 0x0C is intentionally unused
36+
NoUserActivity = 0x0D,
37+
NoUserPresenceTransition = 0x0E,
38+
}
39+
40+
impl From<Ctap2EnrollFeedback> for cbor::Value {
41+
fn from(feedback: Ctap2EnrollFeedback) -> Self {
42+
(feedback as u64).into()
43+
}
44+
}
45+
46+
pub trait Fingerprint {
47+
/// Starts the fingerprint enrollment process.
48+
///
49+
/// Returns the newly assigned template ID.
50+
fn prepare_enrollment(&mut self) -> CtapResult<Vec<u8>>;
51+
52+
/// Captures a fingerprint image.
53+
///
54+
/// Waits for the user to present a finger.
55+
/// If `timeout_ms` is provided, the function times out on user inaction.
56+
/// `prepare_enrollment` must be called first.
57+
/// A returned `Ctap2StatusCode` indicates an unexpected failure processing
58+
/// the command.
59+
/// The `Ctap2EnrollFeedback` contains expected errors from the fingerprint
60+
/// capture process.
61+
/// Also returns the expected number of remaining samples.
62+
fn capture_sample(
63+
&mut self,
64+
template_id: &[u8],
65+
timeout_ms: Option<usize>,
66+
) -> CtapResult<(Ctap2EnrollFeedback, usize)>;
67+
68+
/// Cancel a fingerprint enrollment.
69+
fn cancel_enrollment(&mut self) -> CtapResult<()>;
70+
71+
/// Delete the fingerprint matching the given template ID.
72+
///
73+
/// Does not delete stored information from persistent storage.
74+
/// This function signals to the sensor to remove the enrollment only.
75+
fn remove_enrollment(&mut self, template_id: &[u8]) -> CtapResult<()>;
76+
77+
/// Initialize hardware to prepare a fingerprint check.
78+
///
79+
/// Called before [`check_fingerprint`].
80+
/// Useful for starting any operation that needs to happen before
81+
/// potentially repeated fingerprint checks, such as blinking LEDs.
82+
fn check_fingerprint_init(&mut self);
83+
84+
/// Collects a fingerprint from the user and verifies it.
85+
///
86+
/// Waits for the user to present a finger, or the timeout to pass.
87+
/// Returns Ok if the fingerprint was valid, and an error otherwise.
88+
fn check_fingerprint(&mut self, timeout_ms: usize) -> Result<(), FingerprintCheckError>;
89+
90+
/// Deinitilize hardware after a fingerprint check.
91+
///
92+
/// Called after [`check_fingerprint`] is finished.
93+
fn check_fingerprint_complete(&mut self);
94+
95+
/// The kind of fingerprint sensor.
96+
fn fingerprint_kind(&self) -> FingerprintKind;
97+
98+
/// Maximum number of good samples required for enrollment.
99+
fn max_capture_samples_required_for_enroll(&self) -> usize;
100+
}

libraries/opensk/src/api/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ pub mod clock;
2121
pub mod connection;
2222
pub mod crypto;
2323
pub mod customization;
24+
#[cfg(feature = "fingerprint")]
25+
pub mod fingerprint;
2426
pub mod firmware_protection;
2527
pub mod key_store;
2628
pub mod persist;

0 commit comments

Comments
 (0)