Skip to content

Commit c466fbf

Browse files
fix(ctap2): do not send uv option together with pinUvAuthParam (#265)
This was tested with libwebauthn 0.5.1 in credentialsd master branch ([7c13749](linux-credentials/credentialsd@7c13749)) in a modified Teams for linux (FIDO2 passkey login login). Token2 version 3.1 logins without problems. Yubikey triggers `Ctap(InvalidOption)` error right after a PIN has been entered. The FIDO 2.1 spec.`§6.1.2. authenticatorMakeCredential` and `§6.2.2. authenticatorGetAssertion` says "If the [pinUvAuthParam](https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#getassert-pinuvauthparam) is present, let the "[uv](https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#getassert-uv)" [option](https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#getassert-option-key) be treated as being present with the value false." So having UV=true and pinUvAuthParam will cause errors on a strict key like Yubikey. --------- Co-authored-by: Alfie Fresta <alfie.fresta@gmail.com>
1 parent 68ea5db commit c466fbf

2 files changed

Lines changed: 64 additions & 0 deletions

File tree

libwebauthn/src/proto/ctap2/model/get_assertion.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,9 @@ impl Ctap2UserVerifiableRequest for Ctap2GetAssertionRequest {
405405
let uv_auth_param = uv_proto.authenticate(uv_auth_token, hash)?;
406406
self.pin_auth_proto = Some(uv_proto.version() as u32);
407407
self.pin_auth_param = Some(ByteBuf::from(uv_auth_param));
408+
if let Some(ref mut options) = self.options {
409+
options.require_user_verification = false;
410+
}
408411
Ok(())
409412
}
410413

@@ -688,6 +691,37 @@ mod tests {
688691
assert!(large_blob.blob.is_none());
689692
}
690693

694+
#[test]
695+
fn pin_uv_auth_param_clears_uv_option() {
696+
use crate::ops::webauthn::UserVerificationRequirement;
697+
use crate::pin::PinUvAuthProtocolOne;
698+
699+
let mut request = make_request(vec![]);
700+
request.user_verification = UserVerificationRequirement::Required;
701+
let mut ctap2 = Ctap2GetAssertionRequest::from(request);
702+
assert!(ctap2.options.unwrap().require_user_verification);
703+
704+
let proto = PinUvAuthProtocolOne::new();
705+
ctap2
706+
.calculate_and_set_uv_auth(&proto, &[0xAA; 32])
707+
.unwrap();
708+
709+
assert!(ctap2.pin_auth_param.is_some());
710+
assert!(!ctap2.options.unwrap().require_user_verification);
711+
712+
// Wire check: the options map (0x05) must not contain "uv".
713+
let bytes = crate::proto::ctap2::cbor::to_vec(&ctap2).unwrap();
714+
let parsed: BTreeMap<u64, Value> = crate::proto::ctap2::cbor::from_slice(&bytes).unwrap();
715+
let Some(Value::Map(options)) = parsed.get(&0x05) else {
716+
panic!("options map missing from serialized request");
717+
};
718+
assert!(!options.contains_key(&Value::Text("uv".to_string())));
719+
assert_eq!(
720+
options.get(&Value::Text("up".to_string())),
721+
Some(&Value::Bool(true))
722+
);
723+
}
724+
691725
#[test]
692726
fn decodes_unsigned_extension_outputs_at_index_0x08() {
693727
// 0x08 is unsignedExtensionOutputs (a CBOR map), not enterprise attestation.

libwebauthn/src/proto/ctap2/model/make_credential.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,9 @@ impl Ctap2UserVerifiableRequest for Ctap2MakeCredentialRequest {
389389
let uv_auth_param = uv_proto.authenticate(uv_auth_token, hash)?;
390390
self.pin_auth_proto = Some(uv_proto.version() as u32);
391391
self.pin_auth_param = Some(ByteBuf::from(uv_auth_param));
392+
if let Some(ref mut options) = self.options {
393+
options.deprecated_require_user_verification = None;
394+
}
392395
Ok(())
393396
}
394397

@@ -698,6 +701,33 @@ mod tests {
698701
assert_eq!(mc_in.salt_enc.len(), 16 + 64);
699702
}
700703

704+
#[test]
705+
fn pin_uv_auth_param_clears_deprecated_uv_option() {
706+
use crate::pin::PinUvAuthProtocolOne;
707+
708+
let info = Ctap2GetInfoResponse::default();
709+
let req = mc_request_with_prf(None);
710+
let mut ctap2 = Ctap2MakeCredentialRequest::from_webauthn_request(&req, &info).unwrap();
711+
712+
ctap2.ensure_uv_set();
713+
assert_eq!(
714+
ctap2.options.unwrap().deprecated_require_user_verification,
715+
Some(true)
716+
);
717+
718+
let proto = PinUvAuthProtocolOne::new();
719+
ctap2
720+
.calculate_and_set_uv_auth(&proto, &[0xAA; 32])
721+
.unwrap();
722+
723+
assert!(ctap2.pin_auth_param.is_some());
724+
assert!(ctap2
725+
.options
726+
.unwrap()
727+
.deprecated_require_user_verification
728+
.is_none());
729+
}
730+
701731
#[test]
702732
fn from_signed_extensions_decrypts_results_with_auth_data() {
703733
use crate::proto::ctap2::Ctap2UserVerificationOperation;

0 commit comments

Comments
 (0)