Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions libwebauthn/examples/authenticator_config_hid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ async fn handle_updates(mut state_recv: Receiver<UvUpdate>) {
while let Ok(update) = state_recv.recv().await {
match update {
UvUpdate::PresenceRequired => println!("Please touch your device!"),
// Shouldn't really happen, as we don't use UV required
UvUpdate::PinNotSet(_) => println!("Pin not set for your device!"),
UvUpdate::UvRetry { attempts_left } => {
print!("UV failed.");
if let Some(attempts_left) = attempts_left {
Expand Down
2 changes: 2 additions & 0 deletions libwebauthn/examples/bio_enrollment_hid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ async fn handle_updates(mut state_recv: Receiver<UvUpdate>) {
while let Ok(update) = state_recv.recv().await {
match update {
UvUpdate::PresenceRequired => println!("Please touch your device!"),
// Shouldn't really happen, as we don't use UV required
UvUpdate::PinNotSet(_) => println!("Pin not set for your device!"),
UvUpdate::UvRetry { attempts_left } => {
print!("UV failed.");
if let Some(attempts_left) = attempts_left {
Expand Down
2 changes: 2 additions & 0 deletions libwebauthn/examples/change_pin_hid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ async fn handle_updates(mut state_recv: Receiver<UvUpdate>) {
while let Ok(update) = state_recv.recv().await {
match update {
UvUpdate::PresenceRequired => println!("Please touch your device!"),
// Can't happen, as we are right now trying to set the PIN
UvUpdate::PinNotSet(_) => panic!("Pin not set for your device!"),
UvUpdate::UvRetry { attempts_left } => {
print!("UV failed.");
if let Some(attempts_left) = attempts_left {
Expand Down
2 changes: 2 additions & 0 deletions libwebauthn/examples/cred_management.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ async fn handle_updates(mut state_recv: Receiver<UvUpdate>) {
while let Ok(update) = state_recv.recv().await {
match update {
UvUpdate::PresenceRequired => println!("Please touch your device!"),
// Shouldn't really happen, as we don't use UV required
UvUpdate::PinNotSet(_) => println!("Pin not set for your device!"),
UvUpdate::UvRetry { attempts_left } => {
print!("UV failed.");
if let Some(attempts_left) = attempts_left {
Expand Down
2 changes: 2 additions & 0 deletions libwebauthn/examples/prf_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ async fn handle_updates(mut state_recv: Receiver<UvUpdate>) {
while let Ok(update) = state_recv.recv().await {
match update {
UvUpdate::PresenceRequired => println!("Please touch your device!"),
// Shouldn't really happen, as we don't use UV required
UvUpdate::PinNotSet(_) => println!("Pin not set for your device!"),
UvUpdate::UvRetry { attempts_left } => {
print!("UV failed.");
if let Some(attempts_left) = attempts_left {
Expand Down
2 changes: 2 additions & 0 deletions libwebauthn/examples/webauthn_cable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ async fn handle_updates(mut state_recv: Receiver<CableUxUpdate>) {
match update {
CableUxUpdate::UvUpdate(uv_update) => match uv_update {
UvUpdate::PresenceRequired => println!("Please touch your device!"),
// Can't happen, as we are using cable that doesn't do PIN
UvUpdate::PinNotSet(_) => panic!("Pin not set error for cable!"),
UvUpdate::UvRetry { attempts_left } => {
print!("UV failed.");
if let Some(attempts_left) = attempts_left {
Expand Down
2 changes: 2 additions & 0 deletions libwebauthn/examples/webauthn_extensions_hid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ async fn handle_updates(mut state_recv: Receiver<UvUpdate>) {
while let Ok(update) = state_recv.recv().await {
match update {
UvUpdate::PresenceRequired => println!("Please touch your device!"),
// Shouldn't really happen, as we don't use UV required
UvUpdate::PinNotSet(_) => println!("Pin not set for your device!"),
UvUpdate::UvRetry { attempts_left } => {
print!("UV failed.");
if let Some(attempts_left) = attempts_left {
Expand Down
35 changes: 33 additions & 2 deletions libwebauthn/examples/webauthn_hid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use libwebauthn::ops::webauthn::{
GetAssertionRequest, GetAssertionRequestExtensions, MakeCredentialRequest,
ResidentKeyRequirement, UserVerificationRequirement,
};
use libwebauthn::pin::PinRequestReason;
use libwebauthn::pin::{PinNotSetReason, PinRequestReason};
use libwebauthn::proto::ctap2::{
Ctap2CredentialType, Ctap2PublicKeyCredentialDescriptor, Ctap2PublicKeyCredentialRpEntity,
Ctap2PublicKeyCredentialUserEntity,
Expand All @@ -35,6 +35,25 @@ async fn handle_updates(mut state_recv: Receiver<UvUpdate>) {
while let Ok(update) = state_recv.recv().await {
match update {
UvUpdate::PresenceRequired => println!("Please touch your device!"),
UvUpdate::PinNotSet(update) => {
match update.reason {
PinNotSetReason::PinNotSet => println!("RP required a PIN, but your device has none set. Please set one now (this is NOT a RP-specific operation, but will require a PIN on your device from now on, if you continue. Leave the pini entry empty to cancel the operation.)"),
PinNotSetReason::PinTooShort => println!("The provided PIN was too short"),
PinNotSetReason::PinTooLong => println!("The provided PIN was too long"),
PinNotSetReason::PinPolicyViolation => println!("The provided PIN violated the pin policy set on the device."),
}
print!("PIN: Please set a new PIN for your device: ");
io::stdout().flush().unwrap();
let pin_raw: String = read!("{}\n");

if pin_raw.is_empty() {
println!("PIN: No PIN provided, cancelling operation.");
update.cancel();
} else {
let _ = update.set_pin(&pin_raw);
println!();
}
}
UvUpdate::UvRetry { attempts_left } => {
print!("UV failed.");
if let Some(attempts_left) = attempts_left {
Expand Down Expand Up @@ -86,6 +105,18 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
let mut channel = device.channel().await?;
channel.wink(TIMEOUT).await?;

// Ask user what kind of request should be issued
let user_verification = {
print!("Do you want to require user verification in the request? [y/N]: ");
io::stdout().flush().expect("Failed to flush stdout!");
let input: String = read!("{}\n");
if input.trim() == "y" || input.trim() == "Y" {
UserVerificationRequirement::Required
} else {
UserVerificationRequirement::Preferred
}
};

// Make Credentials ceremony
let make_credentials_request = MakeCredentialRequest {
challenge: Vec::from(challenge),
Expand All @@ -94,7 +125,7 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
relying_party: Ctap2PublicKeyCredentialRpEntity::new("example.org", "example.org"),
user: Ctap2PublicKeyCredentialUserEntity::new(&user_id, "mario.rossi", "Mario Rossi"),
resident_key: Some(ResidentKeyRequirement::Discouraged),
user_verification: UserVerificationRequirement::Preferred,
user_verification,
algorithms: vec![Ctap2CredentialType::default()],
exclude: None,
extensions: None,
Expand Down
2 changes: 2 additions & 0 deletions libwebauthn/examples/webauthn_json_hid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ async fn handle_updates(mut state_recv: Receiver<UvUpdate>) {
while let Ok(update) = state_recv.recv().await {
match update {
UvUpdate::PresenceRequired => println!("Please touch your device!"),
// Shouldn't really happen, as we don't use UV required
UvUpdate::PinNotSet(_) => println!("Pin not set for your device!"),
UvUpdate::UvRetry { attempts_left } => {
print!("UV failed.");
if let Some(attempts_left) = attempts_left {
Expand Down
35 changes: 33 additions & 2 deletions libwebauthn/examples/webauthn_nfc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use tracing_subscriber::{self, EnvFilter};
use libwebauthn::ops::webauthn::{
GetAssertionRequest, MakeCredentialRequest, ResidentKeyRequirement, UserVerificationRequirement,
};
use libwebauthn::pin::PinRequestReason;
use libwebauthn::pin::{PinNotSetReason, PinRequestReason};
use libwebauthn::proto::ctap2::{
Ctap2CredentialType, Ctap2PublicKeyCredentialDescriptor, Ctap2PublicKeyCredentialRpEntity,
Ctap2PublicKeyCredentialUserEntity,
Expand All @@ -33,6 +33,25 @@ async fn handle_updates(mut state_recv: Receiver<UvUpdate>) {
while let Ok(update) = state_recv.recv().await {
match update {
UvUpdate::PresenceRequired => println!("Please touch your device!"),
UvUpdate::PinNotSet(update) => {
match update.reason {
PinNotSetReason::PinNotSet => println!("RP required a PIN, but your device has none set. Please set one now (this is NOT a RP-specific operation, but will require a PIN on your device from now on, if you continue. Leave the pini entry empty to cancel the operation.)"),
PinNotSetReason::PinTooShort => println!("The provided PIN was too short"),
PinNotSetReason::PinTooLong => println!("The provided PIN was too long"),
PinNotSetReason::PinPolicyViolation => println!("The provided PIN violated the pin policy set on the device."),
}
print!("PIN: Please set a new PIN for your device: ");
io::stdout().flush().unwrap();
let pin_raw: String = read!("{}\n");

if pin_raw.is_empty() {
println!("PIN: No PIN provided, cancelling operation.");
update.cancel();
} else {
let _ = update.set_pin(&pin_raw);
println!();
}
}
UvUpdate::UvRetry { attempts_left } => {
print!("UV failed.");
if let Some(attempts_left) = attempts_left {
Expand Down Expand Up @@ -87,6 +106,18 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
println!("Selected NFC authenticator: {}", device);
let mut channel = device.channel().await?;

// Ask user what kind of request should be issued
let user_verification = {
print!("Do you want to require user verification in the request? [y/N]: ");
io::stdout().flush().expect("Failed to flush stdout!");
let input: String = read!("{}\n");
if input.trim() == "y" || input.trim() == "Y" {
UserVerificationRequirement::Required
} else {
UserVerificationRequirement::Preferred
}
};

// Make Credentials ceremony
let make_credentials_request = MakeCredentialRequest {
challenge: Vec::from(challenge),
Expand All @@ -95,7 +126,7 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
relying_party: Ctap2PublicKeyCredentialRpEntity::new("example.org", "example.org"),
user: Ctap2PublicKeyCredentialUserEntity::new(&user_id, "mario.rossi", "Mario Rossi"),
resident_key: Some(ResidentKeyRequirement::Discouraged),
user_verification: UserVerificationRequirement::Preferred,
user_verification,
algorithms: vec![Ctap2CredentialType::default()],
exclude: None,
extensions: None,
Expand Down
2 changes: 2 additions & 0 deletions libwebauthn/examples/webauthn_preflight_hid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ async fn handle_updates(mut state_recv: Receiver<UvUpdate>) {
while let Ok(update) = state_recv.recv().await {
match update {
UvUpdate::PresenceRequired => println!("Please touch your device!"),
// Shouldn't really happen, as we don't use UV required
UvUpdate::PinNotSet(_) => println!("Pin not set for your device!"),
UvUpdate::UvRetry { attempts_left } => {
print!("UV failed.");
if let Some(attempts_left) = attempts_left {
Expand Down
2 changes: 2 additions & 0 deletions libwebauthn/examples/webauthn_prf_hid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ async fn handle_updates(mut state_recv: Receiver<UvUpdate>) {
while let Ok(update) = state_recv.recv().await {
match update {
UvUpdate::PresenceRequired => println!("Please touch your device!"),
// Shouldn't really happen, as we don't use UV required
UvUpdate::PinNotSet(_) => println!("Pin not set for your device!"),
UvUpdate::UvRetry { attempts_left } => {
print!("UV failed.");
if let Some(attempts_left) = attempts_left {
Expand Down
38 changes: 37 additions & 1 deletion libwebauthn/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ macro_rules! unwrap_field {
}
}};
}
use pin::PinRequestReason;
use pin::{PinNotSetReason, PinRequestReason};
pub(crate) use unwrap_field;

#[derive(Debug)]
Expand All @@ -61,6 +61,7 @@ pub enum UvUpdate {
/// The ongoing operation may run into a timeout, no answer is provided in time.
PinRequired(PinRequiredUpdate),
PresenceRequired,
PinNotSet(PinNotSetUpdate),
}

#[derive(Debug, Clone)]
Expand Down Expand Up @@ -90,6 +91,31 @@ impl PinRequiredUpdate {
}
}

#[derive(Debug, Clone)]
pub struct PinNotSetUpdate {
reply_to: Arc<oneshot::Sender<String>>,
/// What caused the PIN request.
pub reason: PinNotSetReason,
}

impl PinNotSetUpdate {
/// This consumes `self`, because we should only ever send exactly one answer back.
pub fn set_pin(self, pin: &str) -> Result<(), String> {
match Arc::into_inner(self.reply_to) {
Some(sender) => sender
.send(pin.to_string())
.map_err(|_| "Failed to send PIN".to_string()),
None => Err("Multiple references to reply_to exist; cannot send PIN".to_string()),
}
}

/// The user cancels the PIN entry, without making an attempt.
pub fn cancel(self) {
// We hang up to signal an abort
drop(self.reply_to)
}
}

#[cfg(test)]
// This function is not _really_ `PartialEq`. We need it for testing purposes,
// but should not expose it like this to consumers, so gating it behind cfg(test)
Expand All @@ -100,6 +126,16 @@ impl PartialEq for PinRequiredUpdate {
}
}

#[cfg(test)]
// This function is not _really_ `PartialEq`. We need it for testing purposes,
// but should not expose it like this to consumers, so gating it behind cfg(test)
impl PartialEq for PinNotSetUpdate {
fn eq(&self, other: &Self) -> bool {
// We explicitly ignore `reply_to` and only compare the other fields.
self.reason == other.reason
}
}

pub fn available_transports() -> Vec<Transport> {
vec![Transport::Usb, Transport::Ble]
}
46 changes: 42 additions & 4 deletions libwebauthn/src/pin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use x509_parser::nom::AsBytes;

use crate::{
proto::{
ctap2::{Ctap2, Ctap2ClientPinRequest, Ctap2PinUvAuthProtocol},
ctap2::{Ctap2, Ctap2ClientPinRequest, Ctap2GetInfoResponse, Ctap2PinUvAuthProtocol},
CtapError,
},
transport::Channel,
Expand Down Expand Up @@ -49,6 +49,18 @@ pub enum PinRequestReason {
// Passkey
}

#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum PinNotSetReason {
/// Device does not have a PIN set, but needs one
PinNotSet,
/// PIN too short
PinTooShort,
/// PIN too long
PinTooLong,
/// PIN violates PinPolicy
PinPolicyViolation,
}

pub trait PinUvAuthProtocol: Send + Sync {
fn version(&self) -> Ctap2PinUvAuthProtocol;

Expand Down Expand Up @@ -422,7 +434,18 @@ pub fn hkdf_sha256(salt: Option<&[u8]>, ikm: &[u8], info: &[u8]) -> Result<Vec<u
}

#[async_trait]
pub trait PinManagement {
pub(crate) trait PinManagementInternal {
async fn change_pin_internal(
&mut self,
get_info_response: &Ctap2GetInfoResponse,
new_pin: String,
timeout: Duration,
) -> Result<(), Error>;
}

#[async_trait]
#[allow(private_bounds)]
pub trait PinManagement: PinManagementInternal {
Comment thread
msirringhaus marked this conversation as resolved.
async fn change_pin(&mut self, new_pin: String, timeout: Duration) -> Result<(), Error>;
}

Expand All @@ -433,7 +456,22 @@ where
{
async fn change_pin(&mut self, new_pin: String, timeout: Duration) -> Result<(), Error> {
let get_info_response = self.ctap2_get_info().await?;
self.change_pin_internal(&get_info_response, new_pin, timeout)
.await
}
}

#[async_trait]
impl<C> PinManagementInternal for C
where
C: Channel,
{
async fn change_pin_internal(
&mut self,
get_info_response: &Ctap2GetInfoResponse,
new_pin: String,
timeout: Duration,
) -> Result<(), Error> {
// If the minPINLength member of the authenticatorGetInfo response is absent, then let platformMinPINLengthInCodePoints be 4.
if new_pin.len() < get_info_response.min_pin_length.unwrap_or(4) as usize {
// If platformCollectedPinLengthInCodePoints is less than platformMinPINLengthInCodePoints then the platform SHOULD display a "PIN too short" error message to the user.
Expand All @@ -448,7 +486,7 @@ where
let Some(uv_proto) = select_uv_proto(
#[cfg(test)]
self.get_forced_pin_protocol(),
&get_info_response,
get_info_response,
)
.await
else {
Expand All @@ -466,7 +504,7 @@ where
Some(true) => Some(
obtain_pin(
self,
&get_info_response,
get_info_response,
uv_proto.version(),
PinRequestReason::AuthenticatorPolicy,
timeout,
Expand Down
Loading
Loading