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}