Skip to content

Commit cd24f6b

Browse files
authored
Implement state update and add PresenceRequired status (#89)
1 parent 1eda27d commit cd24f6b

26 files changed

+641
-485
lines changed

libwebauthn/examples/authenticator_config_hid.rs

Lines changed: 55 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ use std::fmt::Display;
33
use std::time::Duration;
44

55
use libwebauthn::management::AuthenticatorConfig;
6-
use libwebauthn::pin::{UvProvider, StdinPromptPinProvider};
6+
use libwebauthn::pin::PinRequestReason;
77
use libwebauthn::proto::ctap2::{Ctap2, Ctap2GetInfoResponse};
88
use libwebauthn::transport::hid::list_devices;
99
use libwebauthn::transport::Device;
1010
use libwebauthn::webauthn::Error as WebAuthnError;
11+
use libwebauthn::UxUpdate;
1112
use std::io::{self, Write};
1213
use text_io::read;
14+
use tokio::sync::mpsc::Receiver;
1315
use tracing_subscriber::{self, EnvFilter};
1416

1517
const TIMEOUT: Duration = Duration::from_secs(10);
@@ -21,6 +23,46 @@ fn setup_logging() {
2123
.init();
2224
}
2325

26+
async fn handle_updates(mut state_recv: Receiver<UxUpdate>) {
27+
while let Some(update) = state_recv.recv().await {
28+
match update {
29+
UxUpdate::PresenceRequired => println!("Please touch your device!"),
30+
UxUpdate::UvRetry { attempts_left } => {
31+
print!("UV failed.");
32+
if let Some(attempts_left) = attempts_left {
33+
print!(" You have {attempts_left} attempts left.");
34+
}
35+
}
36+
UxUpdate::PinRequired(update) => {
37+
let mut attempts_str = String::new();
38+
if let Some(attempts) = update.attempts_left {
39+
attempts_str = format!(". You have {attempts} attempts left!");
40+
};
41+
42+
match update.reason {
43+
PinRequestReason::RelyingPartyRequest => println!("RP required a PIN."),
44+
PinRequestReason::AuthenticatorPolicy => {
45+
println!("Your device requires a PIN.")
46+
}
47+
PinRequestReason::FallbackFromUV => {
48+
println!("UV failed too often and is blocked. Falling back to PIN.")
49+
}
50+
}
51+
print!("PIN: Please enter the PIN for your authenticator{attempts_str}: ");
52+
io::stdout().flush().unwrap();
53+
let pin_raw: String = read!("{}\n");
54+
55+
if pin_raw.is_empty() {
56+
println!("PIN: No PIN provided, cancelling operation.");
57+
update.cancel();
58+
} else {
59+
let _ = update.send_pin(&pin_raw);
60+
}
61+
}
62+
}
63+
}
64+
}
65+
2466
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2567
enum Operation {
2668
ToggleAlwaysUv,
@@ -78,13 +120,14 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
78120

79121
let devices = list_devices().await.unwrap();
80122
println!("Devices found: {:?}", devices);
81-
let pin_provider: Box<dyn UvProvider> = Box::new(StdinPromptPinProvider::new());
82123

83124
for mut device in devices {
84125
println!("Selected HID authenticator: {}", &device);
85-
device.wink(TIMEOUT).await?;
126+
let (mut channel, state_recv) = device.channel().await?;
127+
channel.wink(TIMEOUT).await?;
128+
129+
tokio::spawn(handle_updates(state_recv));
86130

87-
let mut channel = device.channel().await?;
88131
let info = channel.ctap2_get_info().await?;
89132
let options = get_supported_options(&info);
90133

@@ -139,24 +182,18 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
139182

140183
loop {
141184
let action = match options[idx] {
142-
Operation::ToggleAlwaysUv => channel.toggle_always_uv(&pin_provider, TIMEOUT),
143-
Operation::SetMinPinLengthRpids => channel.set_min_pin_length_rpids(
144-
min_pin_length_rpids.clone(),
145-
&pin_provider,
146-
TIMEOUT,
147-
),
185+
Operation::ToggleAlwaysUv => channel.toggle_always_uv(TIMEOUT),
186+
Operation::SetMinPinLengthRpids => {
187+
channel.set_min_pin_length_rpids(min_pin_length_rpids.clone(), TIMEOUT)
188+
}
148189
Operation::SetMinPinLength(..) => {
149-
channel.set_min_pin_length(new_pin_length, &pin_provider, TIMEOUT)
190+
channel.set_min_pin_length(new_pin_length, TIMEOUT)
150191
}
151192
Operation::EnableEnterpriseAttestation => {
152-
channel.enable_enterprise_attestation(&pin_provider, TIMEOUT)
153-
}
154-
Operation::EnableForceChangePin => {
155-
channel.force_change_pin(true, &pin_provider, TIMEOUT)
156-
}
157-
Operation::DisableForceChangePin => {
158-
channel.force_change_pin(false, &pin_provider, TIMEOUT)
193+
channel.enable_enterprise_attestation(TIMEOUT)
159194
}
195+
Operation::EnableForceChangePin => channel.force_change_pin(true, TIMEOUT),
196+
Operation::DisableForceChangePin => channel.force_change_pin(false, TIMEOUT),
160197
};
161198
match action.await {
162199
Ok(_) => break Ok(()),

libwebauthn/examples/bio_enrollment_hid.rs

Lines changed: 62 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1+
use libwebauthn::UxUpdate;
12
use std::error::Error;
23
use std::fmt::Display;
34
use std::io::{self, Write};
45
use std::time::Duration;
56
use text_io::read;
7+
use tokio::sync::mpsc::Receiver;
68
use tracing_subscriber::{self, EnvFilter};
79

810
use libwebauthn::management::BioEnrollment;
9-
use libwebauthn::pin::{UvProvider, StdinPromptPinProvider};
11+
use libwebauthn::pin::PinRequestReason;
1012
use libwebauthn::proto::ctap2::{Ctap2, Ctap2GetInfoResponse, Ctap2LastEnrollmentSampleStatus};
1113
use libwebauthn::transport::hid::list_devices;
1214
use libwebauthn::transport::Device;
@@ -21,6 +23,46 @@ fn setup_logging() {
2123
.init();
2224
}
2325

26+
async fn handle_updates(mut state_recv: Receiver<UxUpdate>) {
27+
while let Some(update) = state_recv.recv().await {
28+
match update {
29+
UxUpdate::PresenceRequired => println!("Please touch your device!"),
30+
UxUpdate::UvRetry { attempts_left } => {
31+
print!("UV failed.");
32+
if let Some(attempts_left) = attempts_left {
33+
print!(" You have {attempts_left} attempts left.");
34+
}
35+
}
36+
UxUpdate::PinRequired(update) => {
37+
let mut attempts_str = String::new();
38+
if let Some(attempts) = update.attempts_left {
39+
attempts_str = format!(". You have {attempts} attempts left!");
40+
};
41+
42+
match update.reason {
43+
PinRequestReason::RelyingPartyRequest => println!("RP required a PIN."),
44+
PinRequestReason::AuthenticatorPolicy => {
45+
println!("Your device requires a PIN.")
46+
}
47+
PinRequestReason::FallbackFromUV => {
48+
println!("UV failed too often and is blocked. Falling back to PIN.")
49+
}
50+
}
51+
print!("PIN: Please enter the PIN for your authenticator{attempts_str}: ");
52+
io::stdout().flush().unwrap();
53+
let pin_raw: String = read!("{}\n");
54+
55+
if pin_raw.is_empty() {
56+
println!("PIN: No PIN provided, cancelling operation.");
57+
update.cancel();
58+
} else {
59+
let _ = update.send_pin(&pin_raw);
60+
}
61+
}
62+
}
63+
}
64+
}
65+
2466
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2567
enum Operation {
2668
GetModality,
@@ -103,13 +145,14 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
103145

104146
let devices = list_devices().await.unwrap();
105147
println!("Devices found: {:?}", devices);
106-
let mut pin_provider: Box<dyn UvProvider> = Box::new(StdinPromptPinProvider::new());
107148

108149
for mut device in devices {
109150
println!("Selected HID authenticator: {}", &device);
110-
device.wink(TIMEOUT).await?;
151+
let (mut channel, state_recv) = device.channel().await?;
152+
channel.wink(TIMEOUT).await?;
153+
154+
tokio::spawn(handle_updates(state_recv));
111155

112-
let mut channel = device.channel().await?;
113156
let info = channel.ctap2_get_info().await?;
114157
let options = get_supported_options(&info);
115158

@@ -131,15 +174,12 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
131174
.await
132175
.map(|x| format!("{x:?}")),
133176
Operation::EnumerateEnrollments => channel
134-
.get_bio_enrollments(&mut pin_provider, TIMEOUT)
177+
.get_bio_enrollments(TIMEOUT)
135178
.await
136179
.map(|x| format!("{x:?}")),
137180
Operation::RemoveEnrollment => {
138181
let enrollments = loop {
139-
match channel
140-
.get_bio_enrollments(&mut pin_provider, TIMEOUT)
141-
.await
142-
{
182+
match channel.get_bio_enrollments(TIMEOUT).await {
143183
Ok(r) => break r,
144184
Err(WebAuthnError::Ctap(ctap_error)) => {
145185
if ctap_error.is_retryable_user_error() {
@@ -159,18 +199,14 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
159199
channel
160200
.remove_bio_enrollment(
161201
&enrollments[idx].template_id.as_ref().unwrap(),
162-
&mut pin_provider,
163202
TIMEOUT,
164203
)
165204
.await
166205
.map(|x| format!("{x:?}"))
167206
}
168207
Operation::RenameEnrollment => {
169208
let enrollments = loop {
170-
match channel
171-
.get_bio_enrollments(&mut pin_provider, TIMEOUT)
172-
.await
173-
{
209+
match channel.get_bio_enrollments(TIMEOUT).await {
174210
Ok(r) => break r,
175211
Err(WebAuthnError::Ctap(ctap_error)) => {
176212
if ctap_error.is_retryable_user_error() {
@@ -194,36 +230,28 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
194230
.rename_bio_enrollment(
195231
&enrollments[idx].template_id.as_ref().unwrap(),
196232
&new_name,
197-
&mut pin_provider,
198233
TIMEOUT,
199234
)
200235
.await
201236
.map(|x| format!("{x:?}"))
202237
}
203238
Operation::AddNewEnrollment => {
204-
let (template_id, mut sample_status, mut remaining_samples) = match channel
205-
.start_new_bio_enrollment(&mut pin_provider, None, TIMEOUT)
206-
.await
207-
{
208-
Ok(r) => r,
209-
Err(WebAuthnError::Ctap(ctap_error)) => {
210-
if ctap_error.is_retryable_user_error() {
211-
println!("Oops, try again! Error: {}", ctap_error);
212-
continue;
239+
let (template_id, mut sample_status, mut remaining_samples) =
240+
match channel.start_new_bio_enrollment(None, TIMEOUT).await {
241+
Ok(r) => r,
242+
Err(WebAuthnError::Ctap(ctap_error)) => {
243+
if ctap_error.is_retryable_user_error() {
244+
println!("Oops, try again! Error: {}", ctap_error);
245+
continue;
246+
}
247+
break Err(WebAuthnError::Ctap(ctap_error));
213248
}
214-
break Err(WebAuthnError::Ctap(ctap_error));
215-
}
216-
Err(err) => break Err(err),
217-
};
249+
Err(err) => break Err(err),
250+
};
218251
while remaining_samples > 0 {
219252
print_status_update(sample_status, remaining_samples);
220253
(sample_status, remaining_samples) = match channel
221-
.capture_next_bio_enrollment_sample(
222-
&template_id,
223-
&mut pin_provider,
224-
None,
225-
TIMEOUT,
226-
)
254+
.capture_next_bio_enrollment_sample(&template_id, None, TIMEOUT)
227255
.await
228256
{
229257
Ok(r) => r,

libwebauthn/examples/change_pin_hid.rs

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
use std::error::Error;
22
use std::time::Duration;
33

4+
use libwebauthn::{
5+
pin::{PinManagement, PinRequestReason},
6+
UxUpdate,
7+
};
8+
use tokio::sync::mpsc::Receiver;
49
use tracing_subscriber::{self, EnvFilter};
510

6-
use libwebauthn::pin::{PinManagement, UvProvider, StdinPromptPinProvider};
711
use libwebauthn::transport::hid::list_devices;
812
use libwebauthn::transport::Device;
913
use libwebauthn::webauthn::Error as WebAuthnError;
@@ -19,19 +23,57 @@ fn setup_logging() {
1923
.init();
2024
}
2125

26+
async fn handle_updates(mut state_recv: Receiver<UxUpdate>) {
27+
while let Some(update) = state_recv.recv().await {
28+
match update {
29+
UxUpdate::PresenceRequired => println!("Please touch your device!"),
30+
UxUpdate::UvRetry { attempts_left } => {
31+
print!("UV failed.");
32+
if let Some(attempts_left) = attempts_left {
33+
print!(" You have {attempts_left} attempts left.");
34+
}
35+
}
36+
UxUpdate::PinRequired(update) => {
37+
let mut attempts_str = String::new();
38+
if let Some(attempts) = update.attempts_left {
39+
attempts_str = format!(". You have {attempts} attempts left!");
40+
};
41+
42+
match update.reason {
43+
PinRequestReason::RelyingPartyRequest => println!("RP required a PIN."),
44+
PinRequestReason::AuthenticatorPolicy => {
45+
println!("Your device requires a PIN.")
46+
}
47+
PinRequestReason::FallbackFromUV => {
48+
println!("UV failed too often and is blocked. Falling back to PIN.")
49+
}
50+
}
51+
print!("PIN: Please enter the PIN for your authenticator{attempts_str}: ");
52+
io::stdout().flush().unwrap();
53+
let pin_raw: String = read!("{}\n");
54+
55+
if pin_raw.is_empty() {
56+
println!("PIN: No PIN provided, cancelling operation.");
57+
update.cancel();
58+
} else {
59+
let _ = update.send_pin(&pin_raw);
60+
}
61+
}
62+
}
63+
}
64+
}
65+
2266
#[tokio::main]
2367
pub async fn main() -> Result<(), Box<dyn Error>> {
2468
setup_logging();
2569

2670
let devices = list_devices().await.unwrap();
2771
println!("Devices found: {:?}", devices);
28-
let pin_provider: Box<dyn UvProvider> = Box::new(StdinPromptPinProvider::new());
2972

3073
for mut device in devices {
3174
println!("Selected HID authenticator: {}", &device);
32-
device.wink(TIMEOUT).await?;
33-
34-
let mut channel = device.channel().await?;
75+
let (mut channel, state_recv) = device.channel().await?;
76+
channel.wink(TIMEOUT).await?;
3577

3678
print!("PIN: Please enter the _new_ PIN: ");
3779
io::stdout().flush().unwrap();
@@ -42,11 +84,10 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
4284
return Ok(());
4385
}
4486

87+
tokio::spawn(handle_updates(state_recv));
88+
4589
let response = loop {
46-
match channel
47-
.change_pin(&pin_provider, new_pin.clone(), TIMEOUT)
48-
.await
49-
{
90+
match channel.change_pin(new_pin.clone(), TIMEOUT).await {
5091
Ok(response) => break Ok(response),
5192
Err(WebAuthnError::Ctap(ctap_error)) => {
5293
if ctap_error.is_retryable_user_error() {
@@ -59,7 +100,7 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
59100
};
60101
}
61102
.unwrap();
62-
println!("WebAuthn MakeCredential response: {:?}", response);
103+
println!("WebAuthn response: {:?}", response);
63104
}
64105

65106
Ok(())

0 commit comments

Comments
 (0)