Skip to content

Commit 42dbc6b

Browse files
committed
Implement AuthenticatorConfig
1 parent 90fc502 commit 42dbc6b

File tree

8 files changed

+580
-24
lines changed

8 files changed

+580
-24
lines changed
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.toogle_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+
}

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;

libwebauthn/src/management.rs

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
use serde_cbor::ser::to_vec;
2+
use std::time::Duration;
3+
4+
use crate::pin::{PinProvider, PinUvAuthProtocol};
5+
use crate::proto::ctap2::Ctap2AuthenticatorConfigCommand;
6+
pub use crate::transport::error::{CtapError, Error, TransportError};
7+
use crate::transport::Channel;
8+
use crate::webauthn::user_verification;
9+
use crate::{
10+
ops::webauthn::UserVerificationRequirement,
11+
proto::ctap2::{
12+
ClientPinRequestPermissions, Ctap2, Ctap2AuthenticatorConfigRequest,
13+
Ctap2UserVerifiableRequest,
14+
},
15+
};
16+
use async_trait::async_trait;
17+
use serde_bytes::ByteBuf;
18+
19+
#[async_trait]
20+
pub trait AuthenticatorConfig {
21+
async fn toogle_always_uv(
22+
&mut self,
23+
pin_provider: &Box<dyn PinProvider>,
24+
timeout: Duration,
25+
) -> Result<(), Error>;
26+
27+
async fn enable_enterprise_attestation(
28+
&mut self,
29+
pin_provider: &Box<dyn PinProvider>,
30+
timeout: Duration,
31+
) -> Result<(), Error>;
32+
33+
async fn set_min_pin_length(
34+
&mut self,
35+
new_pin_length: u64,
36+
pin_provider: &Box<dyn PinProvider>,
37+
timeout: Duration,
38+
) -> Result<(), Error>;
39+
40+
async fn force_change_pin(
41+
&mut self,
42+
force: bool,
43+
pin_provider: &Box<dyn PinProvider>,
44+
timeout: Duration,
45+
) -> Result<(), Error>;
46+
47+
async fn set_min_pin_length_rpids(
48+
&mut self,
49+
rpids: Vec<String>,
50+
pin_provider: &Box<dyn PinProvider>,
51+
timeout: Duration,
52+
) -> Result<(), Error>;
53+
}
54+
55+
#[async_trait]
56+
impl<C> AuthenticatorConfig for C
57+
where
58+
C: Channel,
59+
{
60+
async fn toogle_always_uv(
61+
&mut self,
62+
pin_provider: &Box<dyn PinProvider>,
63+
timeout: Duration,
64+
) -> Result<(), Error> {
65+
let mut req = Ctap2AuthenticatorConfigRequest::new_toggle_always_uv();
66+
67+
user_verification(
68+
self,
69+
UserVerificationRequirement::Required,
70+
&mut req,
71+
pin_provider,
72+
timeout,
73+
)
74+
.await?;
75+
76+
// On success, this is an all-empty Ctap2ClientPinResponse
77+
self.ctap2_authenticator_config(&req, timeout).await
78+
}
79+
80+
async fn enable_enterprise_attestation(
81+
&mut self,
82+
pin_provider: &Box<dyn PinProvider>,
83+
timeout: Duration,
84+
) -> Result<(), Error> {
85+
let mut req = Ctap2AuthenticatorConfigRequest::new_enable_enterprise_attestation();
86+
87+
user_verification(
88+
self,
89+
UserVerificationRequirement::Required,
90+
&mut req,
91+
pin_provider,
92+
timeout,
93+
)
94+
.await?;
95+
96+
// On success, this is an all-empty Ctap2ClientPinResponse
97+
self.ctap2_authenticator_config(&req, timeout).await
98+
}
99+
100+
async fn set_min_pin_length(
101+
&mut self,
102+
new_pin_length: u64,
103+
pin_provider: &Box<dyn PinProvider>,
104+
timeout: Duration,
105+
) -> Result<(), Error> {
106+
let mut req = Ctap2AuthenticatorConfigRequest::new_set_min_pin_length(new_pin_length);
107+
108+
user_verification(
109+
self,
110+
UserVerificationRequirement::Required,
111+
&mut req,
112+
pin_provider,
113+
timeout,
114+
)
115+
.await?;
116+
117+
// On success, this is an all-empty Ctap2ClientPinResponse
118+
self.ctap2_authenticator_config(&req, timeout).await
119+
}
120+
121+
async fn force_change_pin(
122+
&mut self,
123+
force: bool,
124+
pin_provider: &Box<dyn PinProvider>,
125+
timeout: Duration,
126+
) -> Result<(), Error> {
127+
let mut req = Ctap2AuthenticatorConfigRequest::new_force_change_pin(force);
128+
129+
user_verification(
130+
self,
131+
UserVerificationRequirement::Required,
132+
&mut req,
133+
pin_provider,
134+
timeout,
135+
)
136+
.await?;
137+
138+
// On success, this is an all-empty Ctap2ClientPinResponse
139+
self.ctap2_authenticator_config(&req, timeout).await
140+
}
141+
142+
async fn set_min_pin_length_rpids(
143+
&mut self,
144+
rpids: Vec<String>,
145+
pin_provider: &Box<dyn PinProvider>,
146+
timeout: Duration,
147+
) -> Result<(), Error> {
148+
let mut req = Ctap2AuthenticatorConfigRequest::new_set_min_pin_length_rpids(rpids);
149+
150+
user_verification(
151+
self,
152+
UserVerificationRequirement::Required,
153+
&mut req,
154+
pin_provider,
155+
timeout,
156+
)
157+
.await?;
158+
159+
// On success, this is an all-empty Ctap2ClientPinResponse
160+
self.ctap2_authenticator_config(&req, timeout).await
161+
}
162+
}
163+
164+
impl Ctap2UserVerifiableRequest for Ctap2AuthenticatorConfigRequest {
165+
fn ensure_uv_set(&mut self) {
166+
// No-op
167+
}
168+
169+
fn calculate_and_set_uv_auth(
170+
&mut self,
171+
uv_proto: &Box<dyn PinUvAuthProtocol>,
172+
uv_auth_token: &[u8],
173+
) {
174+
// pinUvAuthParam (0x04): the result of calling
175+
// authenticate(pinUvAuthToken, 32×0xff || 0x0d || uint8(subCommand) || subCommandParams).
176+
let mut data = vec![0xff; 32];
177+
data.push(0x0D);
178+
data.push(self.subcommand as u8);
179+
if self.subcommand == Ctap2AuthenticatorConfigCommand::SetMinPINLength {
180+
data.extend(to_vec(&self.subcommand_params).unwrap());
181+
}
182+
let uv_auth_param = uv_proto.authenticate(uv_auth_token, &data);
183+
self.protocol = Some(uv_proto.version());
184+
self.uv_auth_param = Some(ByteBuf::from(uv_auth_param));
185+
}
186+
187+
fn client_data_hash(&self) -> &[u8] {
188+
unreachable!()
189+
}
190+
191+
fn permissions(&self) -> ClientPinRequestPermissions {
192+
return ClientPinRequestPermissions::AUTHENTICATOR_CONFIGURATION;
193+
}
194+
195+
fn permissions_rpid(&self) -> Option<&str> {
196+
None
197+
}
198+
}

0 commit comments

Comments
 (0)