Skip to content

Commit a6eec93

Browse files
Merge branch 'master' into cable
2 parents 3b3c00a + 4eec738 commit a6eec93

File tree

14 files changed

+986
-153
lines changed

14 files changed

+986
-153
lines changed

.github/workflows/rust.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: Build and run tests
2-
on: [push]
2+
on: [push, pull_request]
33

44
jobs:
55
build:

Cargo.lock

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
use std::error::Error;
2+
use std::fmt::Display;
3+
use std::time::Duration;
4+
5+
use libwebauthn::management::AuthenticatorConfig;
6+
use libwebauthn::pin::{PinProvider, StdinPromptPinProvider};
7+
use libwebauthn::proto::ctap2::{Ctap2, Ctap2GetInfoResponse};
8+
use libwebauthn::transport::hid::list_devices;
9+
use libwebauthn::transport::Device;
10+
use libwebauthn::webauthn::Error as WebAuthnError;
11+
use std::io::{self, Write};
12+
use text_io::read;
13+
use tracing_subscriber::{self, EnvFilter};
14+
15+
const TIMEOUT: Duration = Duration::from_secs(10);
16+
17+
fn setup_logging() {
18+
tracing_subscriber::fmt()
19+
.with_env_filter(EnvFilter::from_default_env())
20+
.without_time()
21+
.init();
22+
}
23+
24+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
25+
enum Operation {
26+
ToggleAlwaysUv,
27+
EnableForceChangePin,
28+
DisableForceChangePin,
29+
SetMinPinLength(Option<u32>),
30+
SetMinPinLengthRpids,
31+
EnableEnterpriseAttestation,
32+
}
33+
34+
impl Display for Operation {
35+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36+
match self {
37+
Operation::ToggleAlwaysUv => f.write_str("Toggle AlwaysUV"),
38+
Operation::EnableForceChangePin => f.write_str("Enable force change pin"),
39+
Operation::DisableForceChangePin => f.write_str("Disable force change pin"),
40+
Operation::SetMinPinLength(l) => {
41+
if let Some(length) = l {
42+
f.write_fmt(format_args!("Set min PIN length. Current length: {length}"))
43+
} else {
44+
f.write_str("Set min PIN length.")
45+
}
46+
}
47+
Operation::SetMinPinLengthRpids => f.write_str("Set min PIN length RPIDs"),
48+
Operation::EnableEnterpriseAttestation => f.write_str("Enable enterprise attestation"),
49+
}
50+
}
51+
}
52+
53+
fn get_supported_options(info: &Ctap2GetInfoResponse) -> Vec<Operation> {
54+
let mut configure_ops = vec![];
55+
if let Some(options) = &info.options {
56+
if options.get("authnrCfg") == Some(&true) && options.get("alwaysUv").is_some() {
57+
configure_ops.push(Operation::ToggleAlwaysUv);
58+
}
59+
if options.get("authnrCfg") == Some(&true) && options.get("setMinPINLength").is_some() {
60+
if info.force_pin_change == Some(true) {
61+
configure_ops.push(Operation::DisableForceChangePin);
62+
} else {
63+
configure_ops.push(Operation::EnableForceChangePin);
64+
}
65+
configure_ops.push(Operation::SetMinPinLength(info.min_pin_length));
66+
configure_ops.push(Operation::SetMinPinLengthRpids);
67+
}
68+
if options.get("ep").is_some() {
69+
configure_ops.push(Operation::EnableEnterpriseAttestation);
70+
}
71+
}
72+
configure_ops
73+
}
74+
75+
#[tokio::main]
76+
pub async fn main() -> Result<(), Box<dyn Error>> {
77+
setup_logging();
78+
79+
let devices = list_devices().await.unwrap();
80+
println!("Devices found: {:?}", devices);
81+
let pin_provider: Box<dyn PinProvider> = Box::new(StdinPromptPinProvider::new());
82+
83+
for mut device in devices {
84+
println!("Selected HID authenticator: {}", &device);
85+
device.wink(TIMEOUT).await?;
86+
87+
let mut channel = device.channel().await?;
88+
let info = channel.ctap2_get_info().await?;
89+
let options = get_supported_options(&info);
90+
91+
println!("What do you want to do?");
92+
println!();
93+
for (idx, op) in options.iter().enumerate() {
94+
println!("({idx}) {op}");
95+
}
96+
97+
let idx = loop {
98+
print!("Your choice: ");
99+
io::stdout().flush().expect("Failed to flush stdout!");
100+
let input: String = read!("{}\n");
101+
if let Ok(idx) = input.trim().parse::<usize>() {
102+
if idx < options.len() {
103+
println!();
104+
break idx;
105+
}
106+
}
107+
};
108+
109+
let mut min_pin_length_rpids = Vec::new();
110+
if options[idx] == Operation::SetMinPinLengthRpids {
111+
loop {
112+
print!("Add RPIDs to list (enter empty string once finished): ");
113+
io::stdout().flush().expect("Failed to flush stdout!");
114+
let input: String = read!("{}\n");
115+
let trimmed = input.trim().to_string();
116+
if trimmed.is_empty() {
117+
break;
118+
} else {
119+
min_pin_length_rpids.push(trimmed);
120+
}
121+
}
122+
};
123+
124+
let new_pin_length = if matches!(options[idx], Operation::SetMinPinLength(..)) {
125+
loop {
126+
print!("New minimum PIN length: ");
127+
io::stdout().flush().expect("Failed to flush stdout!");
128+
let input: String = read!("{}\n");
129+
match input.trim().parse::<u64>() {
130+
Ok(l) => {
131+
break l;
132+
}
133+
Err(_) => continue,
134+
}
135+
}
136+
} else {
137+
0
138+
};
139+
140+
loop {
141+
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+
),
148+
Operation::SetMinPinLength(..) => {
149+
channel.set_min_pin_length(new_pin_length, &pin_provider, TIMEOUT)
150+
}
151+
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)
159+
}
160+
};
161+
match action.await {
162+
Ok(_) => break Ok(()),
163+
Err(WebAuthnError::Ctap(ctap_error)) => {
164+
if ctap_error.is_retryable_user_error() {
165+
println!("Oops, try again! Error: {}", ctap_error);
166+
continue;
167+
}
168+
break Err(WebAuthnError::Ctap(ctap_error));
169+
}
170+
Err(err) => break Err(err),
171+
};
172+
}
173+
.unwrap();
174+
println!("Authenticator config done!");
175+
}
176+
177+
Ok(())
178+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
use std::error::Error;
2+
use std::time::Duration;
3+
4+
use tracing_subscriber::{self, EnvFilter};
5+
6+
use libwebauthn::pin::{PinManagement, PinProvider, StdinPromptPinProvider};
7+
use libwebauthn::transport::hid::list_devices;
8+
use libwebauthn::transport::Device;
9+
use libwebauthn::webauthn::Error as WebAuthnError;
10+
use std::io::{self, Write};
11+
use text_io::read;
12+
13+
const TIMEOUT: Duration = Duration::from_secs(10);
14+
15+
fn setup_logging() {
16+
tracing_subscriber::fmt()
17+
.with_env_filter(EnvFilter::from_default_env())
18+
.without_time()
19+
.init();
20+
}
21+
22+
#[tokio::main]
23+
pub async fn main() -> Result<(), Box<dyn Error>> {
24+
setup_logging();
25+
26+
let devices = list_devices().await.unwrap();
27+
println!("Devices found: {:?}", devices);
28+
let pin_provider: Box<dyn PinProvider> = Box::new(StdinPromptPinProvider::new());
29+
30+
for mut device in devices {
31+
println!("Selected HID authenticator: {}", &device);
32+
device.wink(TIMEOUT).await?;
33+
34+
let mut channel = device.channel().await?;
35+
36+
print!("PIN: Please enter the _new_ PIN: ");
37+
io::stdout().flush().unwrap();
38+
let new_pin: String = read!("{}\n");
39+
40+
if &new_pin == "" {
41+
println!("PIN: No PIN provided, cancelling operation.");
42+
return Ok(());
43+
}
44+
45+
let response = loop {
46+
match channel
47+
.change_pin(&pin_provider, new_pin.clone(), TIMEOUT)
48+
.await
49+
{
50+
Ok(response) => break Ok(response),
51+
Err(WebAuthnError::Ctap(ctap_error)) => {
52+
if ctap_error.is_retryable_user_error() {
53+
println!("Oops, try again! Error: {}", ctap_error);
54+
continue;
55+
}
56+
break Err(WebAuthnError::Ctap(ctap_error));
57+
}
58+
Err(err) => break Err(err),
59+
};
60+
}
61+
.unwrap();
62+
println!("WebAuthn MakeCredential response: {:?}", response);
63+
}
64+
65+
Ok(())
66+
}

libwebauthn/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub mod fido;
2+
pub mod management;
23
pub mod ops;
34
pub mod pin;
45
pub mod proto;

0 commit comments

Comments
 (0)