From 1f0a4309aad1c906bf4d91e5eee85b81b5d2e535 Mon Sep 17 00:00:00 2001 From: Emil Lundberg Date: Tue, 10 Jun 2025 14:21:21 +0200 Subject: [PATCH 1/4] Extract function PinUvAuthProtocol::from_id --- src/crypto/mod.rs | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index 1ad99742..842ff6bd 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -40,9 +40,18 @@ pub use backend::ecdsa_p256_sha256_sign_raw; pub struct PinUvAuthProtocol(Box); impl PinUvAuthProtocol { + pub fn from_id(id: u64) -> Option { + match id { + 1 => Some(Self(Box::new(PinUvAuth1 {}))), + 2 => Some(Self(Box::new(PinUvAuth2 {}))), + _ => None, + } + } + pub fn id(&self) -> u64 { self.0.protocol_id() } + pub fn encapsulate(&self, peer_cose_key: &COSEKey) -> Result { self.0.encapsulate(peer_cose_key) } @@ -141,13 +150,11 @@ impl TryFrom<&AuthenticatorInfo> for PinUvAuthProtocol { // has no preference, it SHOULD select the one listed first in // pinUvAuthProtocols." if let Some(pin_protocols) = &info.pin_protocols { - for proto_id in pin_protocols.iter() { - match proto_id { - 1 => return Ok(PinUvAuthProtocol(Box::new(PinUvAuth1 {}))), - 2 => return Ok(PinUvAuthProtocol(Box::new(PinUvAuth2 {}))), - _ => continue, - } - } + pin_protocols + .iter() + .copied() + .find_map(PinUvAuthProtocol::from_id) + .ok_or(CommandError::UnsupportedPinProtocol) } else { match info.max_supported_version() { crate::ctap2::commands::get_info::AuthenticatorVersion::U2F_V2 => { @@ -162,7 +169,6 @@ impl TryFrom<&AuthenticatorInfo> for PinUvAuthProtocol { } } } - Err(CommandError::UnsupportedPinProtocol) } } From d57aecde245ab8fe4f37431363e0f61898352d5a Mon Sep 17 00:00:00 2001 From: Emil Lundberg Date: Tue, 10 Jun 2025 14:22:12 +0200 Subject: [PATCH 2/4] Remove now-redundant return statements --- src/crypto/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index 842ff6bd..c9a21f5e 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -158,14 +158,14 @@ impl TryFrom<&AuthenticatorInfo> for PinUvAuthProtocol { } else { match info.max_supported_version() { crate::ctap2::commands::get_info::AuthenticatorVersion::U2F_V2 => { - return Err(CommandError::UnsupportedPinProtocol) + Err(CommandError::UnsupportedPinProtocol) } crate::ctap2::commands::get_info::AuthenticatorVersion::FIDO_2_0 => { - return Ok(PinUvAuthProtocol(Box::new(PinUvAuth1 {}))) + Ok(PinUvAuthProtocol(Box::new(PinUvAuth1 {}))) } crate::ctap2::commands::get_info::AuthenticatorVersion::FIDO_2_1_PRE | crate::ctap2::commands::get_info::AuthenticatorVersion::FIDO_2_1 => { - return Ok(PinUvAuthProtocol(Box::new(PinUvAuth2 {}))) + Ok(PinUvAuthProtocol(Box::new(PinUvAuth2 {}))) } } } From 89464630429bfeff99f618b461bf071ffde6a8f2 Mon Sep 17 00:00:00 2001 From: Emil Lundberg Date: Tue, 10 Jun 2025 14:34:04 +0200 Subject: [PATCH 3/4] Use AuthenticatorVersion in crypto module --- src/crypto/mod.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index c9a21f5e..74900935 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use crate::ctap2::commands::client_pin::PinUvAuthTokenPermission; -use crate::ctap2::commands::get_info::AuthenticatorInfo; +use crate::ctap2::commands::get_info::{AuthenticatorInfo, AuthenticatorVersion}; use crate::errors::AuthenticatorError; use crate::{ctap2::commands::CommandError, transport::errors::HIDError}; use serde::{ @@ -157,14 +157,9 @@ impl TryFrom<&AuthenticatorInfo> for PinUvAuthProtocol { .ok_or(CommandError::UnsupportedPinProtocol) } else { match info.max_supported_version() { - crate::ctap2::commands::get_info::AuthenticatorVersion::U2F_V2 => { - Err(CommandError::UnsupportedPinProtocol) - } - crate::ctap2::commands::get_info::AuthenticatorVersion::FIDO_2_0 => { - Ok(PinUvAuthProtocol(Box::new(PinUvAuth1 {}))) - } - crate::ctap2::commands::get_info::AuthenticatorVersion::FIDO_2_1_PRE - | crate::ctap2::commands::get_info::AuthenticatorVersion::FIDO_2_1 => { + AuthenticatorVersion::U2F_V2 => Err(CommandError::UnsupportedPinProtocol), + AuthenticatorVersion::FIDO_2_0 => Ok(PinUvAuthProtocol(Box::new(PinUvAuth1 {}))), + AuthenticatorVersion::FIDO_2_1_PRE | AuthenticatorVersion::FIDO_2_1 => { Ok(PinUvAuthProtocol(Box::new(PinUvAuth2 {}))) } } From efa5f211d9ed230d0221bffee8c14cfa7517d74d Mon Sep 17 00:00:00 2001 From: Emil Lundberg Date: Tue, 10 Jun 2025 14:23:46 +0200 Subject: [PATCH 4/4] Tolerate unknown AuthenticatorVersion values while deserializing --- src/crypto/mod.rs | 4 +- src/ctap2/commands/get_info.rs | 69 ++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index 74900935..ca83fe5a 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -157,7 +157,9 @@ impl TryFrom<&AuthenticatorInfo> for PinUvAuthProtocol { .ok_or(CommandError::UnsupportedPinProtocol) } else { match info.max_supported_version() { - AuthenticatorVersion::U2F_V2 => Err(CommandError::UnsupportedPinProtocol), + AuthenticatorVersion::U2F_V2 | AuthenticatorVersion::Unknown => { + Err(CommandError::UnsupportedPinProtocol) + } AuthenticatorVersion::FIDO_2_0 => Ok(PinUvAuthProtocol(Box::new(PinUvAuth1 {}))), AuthenticatorVersion::FIDO_2_1_PRE | AuthenticatorVersion::FIDO_2_1 => { Ok(PinUvAuthProtocol(Box::new(PinUvAuth2 {}))) diff --git a/src/ctap2/commands/get_info.rs b/src/ctap2/commands/get_info.rs index f676605a..bf97a163 100644 --- a/src/ctap2/commands/get_info.rs +++ b/src/ctap2/commands/get_info.rs @@ -311,6 +311,8 @@ pub enum AuthenticatorVersion { FIDO_2_0, FIDO_2_1_PRE, FIDO_2_1, + #[serde(other)] + Unknown, } #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)] @@ -742,6 +744,40 @@ pub mod tests { 0x18, 0x18, // unsigned(24) ]; + pub const AUTHENTICATOR_INFO_FIDO_2_2: &[u8] = &[ + 0xa2, // map(2) + 0x01, // unsigned(1) + 0x83, // array(3) + 0x66, // text(6) + 0x55, 0x32, 0x46, 0x5f, 0x56, 0x32, // "U2F_V2" + 0x68, // text(8) + 0x46, 0x49, 0x44, 0x4f, 0x5f, 0x32, 0x5f, 0x30, // "FIDO_2_0" + 0x68, // text(8) + 0x46, 0x49, 0x44, 0x4f, 0x5f, 0x32, 0x5f, 0x32, // "FIDO_2_2" + 0x03, // unsigned(3) + 0x50, // bytes(16) + 0xf8, 0xa0, 0x11, 0xf3, 0x8c, 0x0a, 0x4d, 0x15, 0x80, 0x06, 0x17, 0x11, 0x1f, 0x9e, 0xdc, + 0x7d, // "\xF8\xA0\u0011\xF3\x8C\nM\u0015\x80\u0006\u0017\u0011\u001F\x9E\xDC}" + ]; + + pub const AUTHENTICATOR_INFO_UNKNOWN_VERSIONS: &[u8] = &[ + 0xa2, // map(2) + 0x01, // unsigned(1) + 0x84, // array(4) + 0x63, // text(3) + 0x66, 0x6f, 0x6f, // "foo" + 0x66, // text(6) + 0x55, 0x32, 0x46, 0x5f, 0x56, 0x32, // "U2F_V2" + 0x68, // text(8) + 0x46, 0x49, 0x44, 0x4f, 0x5f, 0x32, 0x5f, 0x30, // "FIDO_2_0" + 0x63, // text(3) + 0x62, 0x61, 0x72, // "bar" + 0x03, // unsigned(3) + 0x50, // bytes(16) + 0xf8, 0xa0, 0x11, 0xf3, 0x8c, 0x0a, 0x4d, 0x15, 0x80, 0x06, 0x17, 0x11, 0x1f, 0x9e, 0xdc, + 0x7d, // "\xF8\xA0\u0011\xF3\x8C\nM\u0015\x80\u0006\u0017\u0011\u001F\x9E\xDC}" + ]; + #[test] fn parse_authenticator_info() { let authenticator_info: AuthenticatorInfo = @@ -1051,4 +1087,37 @@ pub mod tests { let authenticator_info: AuthenticatorInfo = from_slice(&raw_list).unwrap(); assert_eq!(authenticator_info, expected); } + + #[test] + fn parse_authenticator_info_fido_2_2() { + assert_eq!( + from_slice::(&AUTHENTICATOR_INFO_FIDO_2_2).unwrap(), + AuthenticatorInfo { + versions: vec![ + AuthenticatorVersion::U2F_V2, + AuthenticatorVersion::FIDO_2_0, + AuthenticatorVersion::Unknown, + ], + aaguid: AAGuid(AAGUID_RAW), + ..Default::default() + }, + ); + } + + #[test] + fn parse_authenticator_info_unknown_versions() { + assert_eq!( + from_slice::(&AUTHENTICATOR_INFO_UNKNOWN_VERSIONS).unwrap(), + AuthenticatorInfo { + versions: vec![ + AuthenticatorVersion::Unknown, + AuthenticatorVersion::U2F_V2, + AuthenticatorVersion::FIDO_2_0, + AuthenticatorVersion::Unknown, + ], + aaguid: AAGuid(AAGUID_RAW), + ..Default::default() + }, + ); + } }