passkey_types/u2f/authenticate.rs
1use crate::ctap2::Flags;
2use std::array::TryFromSliceError;
3
4use super::ResponseStatusWords;
5
6/// The authentication Request MUST come with a parameter to determine it's use
7#[repr(u8)]
8#[derive(Debug)]
9pub enum AuthenticationParameter {
10 /// If the control byte is set to 0x07 by the FIDO Client, the U2F token is supposed to simply
11 /// check whether the provided key handle was originally created by this token, and whether it
12 /// was created for the provided application parameter. If so, the U2F token MUST respond with
13 /// an authentication response message:error:test-of-user-presence-required (note that despite
14 /// the name this signals a success condition). If the key handle was not created by this U2F
15 /// token, or if it was created for a different application parameter, the token MUST respond
16 /// with an authentication response message:error:bad-key-handle.
17 CheckOnly = 0x07,
18
19 /// If the FIDO client sets the control byte to 0x03, then the U2F token is supposed to perform
20 /// a real signature and respond with either an authentication response message:success or an
21 /// appropriate error response (see below). The signature SHOULD only be provided if user
22 /// presence could be validated.
23 EnforceUserPresence = 0x03,
24
25 /// If the FIDO client sets the control byte to 0x08, then the U2F token is supposed to perform
26 /// a real signature and respond with either an authentication response message:success or an
27 /// appropriate error response (see below). The signature MAY be provided without validating
28 /// user presence.
29 DontEnforceUserPresence = 0x08,
30}
31
32impl From<AuthenticationParameter> for u8 {
33 #[expect(clippy::as_conversions)]
34 fn from(src: AuthenticationParameter) -> Self {
35 src as u8
36 }
37}
38
39impl From<u8> for AuthenticationParameter {
40 fn from(src: u8) -> Self {
41 match src {
42 0x07 => AuthenticationParameter::CheckOnly,
43 0x03 => AuthenticationParameter::EnforceUserPresence,
44 0x08 => AuthenticationParameter::DontEnforceUserPresence,
45 _ => unreachable!("U2F Authentication parameter which is not in the spec"),
46 }
47 }
48}
49
50/// This message is used to initiate a U2F token authentication. The FIDO Client first contacts the
51/// relying party to obtain a challenge, and then constructs the authentication request message.
52#[derive(Debug)]
53pub struct AuthenticationRequest {
54 /// During registration, the FIDO Client MAY send authentication request messages to the U2F
55 /// token to figure out whether the U2F token has already been registered. In this case, the
56 /// FIDO client will use the [`AuthenticationParameter::CheckOnly`] value for the control byte.
57 /// In all other cases (i.e., during authentication), the FIDO Client MUST use the
58 /// [`AuthenticationParameter::EnforceUserPresence`] or
59 /// [`AuthenticationParameter::DontEnforceUserPresence`]
60 pub parameter: AuthenticationParameter,
61 /// The challenge parameter is the SHA-256 hash of the Client Data, a stringified JSON data
62 /// structure that the FIDO Client prepares. Among other things, the Client Data contains the
63 /// challenge from the relying party (hence the name of the parameter).
64 pub challenge: [u8; 32],
65 /// The application parameter is the SHA-256 hash of the UTF-8 encoding of the application
66 /// identity of the application requesting the authentication as provided by the relying party.
67 pub application: [u8; 32],
68 /// This is provided by the relying party, and was obtained by the relying party during registration.
69 pub key_handle: Vec<u8>,
70}
71
72impl AuthenticationRequest {
73 /// Try parsing a data payload into an authentication request with the given parameter taken from
74 /// the u2f message frame.
75 #[expect(clippy::as_conversions)]
76 pub fn try_from(
77 data: &[u8],
78 parameter: impl Into<AuthenticationParameter>,
79 ) -> Result<Self, TryFromSliceError> {
80 let (challenge, data) = data.split_at(32);
81 let (application, data) = data.split_at(32);
82 let (handle_len, data) = data.split_at(1);
83 let key_handle = data[..handle_len[0] as usize].to_vec();
84 Ok(Self {
85 parameter: parameter.into(),
86 challenge: challenge.try_into()?,
87 application: application.try_into()?,
88 key_handle,
89 })
90 }
91}
92
93/// This message is output by the U2F token after processing/signing the [`AuthenticationRequest`]
94/// message. Its raw representation is the concatenation of its fields.
95pub struct AuthenticationResponse {
96 /// Whether user presence was verified or not
97 pub user_presence: Flags,
98 /// This a counter value that the U2F token increments every time it performs an authentication
99 /// operation. It must be transported as big endian representation.
100 pub counter: u32,
101 /// This is a ECDSA signature (on P-256) over the following byte string.
102 /// 1. The application parameter [32 bytes] from the authentication request message.
103 /// 2. The above user presence byte [1 byte].
104 /// 3. The above counter [4 bytes].
105 /// 4. The challenge parameter [32 bytes] from the authentication request message.
106 ///
107 /// The signature is encoded in ANSI X9.62 format (see [ECDSA-ANSI] in bibliography). The
108 /// signature is to be verified by the relying party using the public key obtained during
109 /// registration.
110 pub signature: Vec<u8>,
111}
112
113impl AuthenticationResponse {
114 /// Encode the response to its successfull binary representation
115 pub fn encode(self) -> Vec<u8> {
116 [self.user_presence.into()]
117 .into_iter()
118 .chain(self.counter.to_be_bytes())
119 .chain(self.signature)
120 .chain(u16::from(ResponseStatusWords::NoError).to_be_bytes()) // NoError indicates success
121 .collect()
122 }
123}