Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
68 changes: 59 additions & 9 deletions examples/ctap2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@
use authenticator::{
authenticatorservice::{AuthenticatorService, RegisterArgs, SignArgs},
crypto::COSEAlgorithm,
ctap2::server::{
AuthenticationExtensionsClientInputs, CredentialProtectionPolicy,
PublicKeyCredentialDescriptor, PublicKeyCredentialParameters,
PublicKeyCredentialUserEntity, RelyingParty, ResidentKeyRequirement, Transport,
UserVerificationRequirement,
ctap2::{
attestation::AuthenticatorDataFlags,
server::{
AuthenticationExtensionsClientInputs, CredentialProtectionPolicy,
PublicKeyCredentialDescriptor, PublicKeyCredentialParameters,
PublicKeyCredentialUserEntity, RelyingParty, ResidentKeyRequirement, Transport,
UserVerificationRequirement,
},
},
statecallback::StateCallback,
Pin, StatusPinUv, StatusUpdate,
Expand Down Expand Up @@ -48,6 +51,13 @@ fn main() {
opts.optflag("s", "hmac_secret", "With hmac-secret");
opts.optflag("h", "help", "print this help menu");
opts.optflag("f", "fallback", "Use CTAP1 fallback implementation");
opts.optopt(
"u",
"uv",
"User verification requirement (required, preferred, discouraged). Default is \"preferred\".",
"UV",
);

let matches = match opts.parse(&args[1..]) {
Ok(m) => m,
Err(f) => panic!("{}", f.to_string()),
Expand Down Expand Up @@ -77,6 +87,21 @@ fn main() {
}
};

let user_verification_req = match matches.opt_get_default::<UserVerificationRequirement>(
"uv",
UserVerificationRequirement::Preferred,
) {
Ok(uv) => {
println!("User verification requirement: {uv:?}");
uv
}
Err(e) => {
println!("Unknown user verification mode: {e}");
print_usage(&program, opts);
return;
}
};

println!("Asking a security key to register now...");
let mut chall_bytes = [0u8; 32];
thread_rng().fill_bytes(&mut chall_bytes);
Expand Down Expand Up @@ -181,7 +206,7 @@ fn main() {
],
transports: vec![Transport::USB, Transport::NFC],
}],
user_verification_req: UserVerificationRequirement::Preferred,
user_verification_req,
resident_key_req: ResidentKeyRequirement::Discouraged,
extensions: AuthenticationExtensionsClientInputs {
cred_props: Some(true),
Expand Down Expand Up @@ -212,6 +237,20 @@ fn main() {
.expect("Problem receiving, unable to continue");
match register_result {
Ok(a) => {
println!("Register result: {a:?}");

let uv = a
.att_obj
.auth_data
.flags
.contains(AuthenticatorDataFlags::USER_VERIFIED);
if user_verification_req == UserVerificationRequirement::Required && !uv {
panic!("User verification is required, but the authenticator did not set the UV flag (WebAuthn-3 §7.1, step 16)");
}
if uv {
println!("User verified!");
}

println!("Ok!");
attestation_object = a;
break;
Expand All @@ -220,8 +259,6 @@ fn main() {
};
}

println!("Register result: {:?}", &attestation_object);

println!();
println!("*********************************************************************");
println!("Asking a security key to sign now, with the data from the register...");
Expand All @@ -242,7 +279,7 @@ fn main() {
origin: format!("https://{rp_id}"),
relying_party_id: rp_id,
allow_list,
user_verification_req: UserVerificationRequirement::Preferred,
user_verification_req,
user_presence_req: true,
extensions: AuthenticationExtensionsClientInputs {
app_id: using_app_id.then(|| app_id.clone()),
Expand Down Expand Up @@ -270,6 +307,19 @@ fn main() {
match sign_result {
Ok(assertion_object) => {
println!("Assertion Object: {assertion_object:?}");

let uv = assertion_object
.assertion
.auth_data
.flags
.contains(AuthenticatorDataFlags::USER_VERIFIED);
if user_verification_req == UserVerificationRequirement::Required && !uv {
panic!("User verification is required, but the authenticator did not set the UV flag (WebAuthn-3 §7.2, step 17)");
}
if uv {
println!("User verified!");
}

if using_app_id {
println!(
"Used AppID: {}",
Expand Down
15 changes: 15 additions & 0 deletions src/ctap2/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use sha2::{Digest, Sha256};
use std::collections::HashMap;
use std::convert::{Into, TryFrom};
use std::fmt;
use std::str::FromStr;

#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct RpIdHash(pub [u8; 32]);
Expand Down Expand Up @@ -302,6 +303,20 @@ pub enum UserVerificationRequirement {
Required,
}

impl FromStr for UserVerificationRequirement {
type Err = String;

fn from_str(s: &str) -> Result<Self, Self::Err> {
// https://www.w3.org/TR/webauthn-3/#enumdef-userverificationrequirement
match s {
"required" => Ok(Self::Required),
"preferred" => Ok(Self::Preferred),
"discouraged" => Ok(Self::Discouraged),
s => Err(s.to_string()),
}
}
}

#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum CredentialProtectionPolicy {
UserVerificationOptional = 1,
Expand Down
Loading