ctap_types/
ctap2.rs

1//! Types for CTAP2.
2//!
3//! Note that all ctap2::Authenticators automatically implement RPC with [`Request`] and
4//! [`Response`].
5use bitflags::bitflags;
6use cbor_smol::cbor_deserialize;
7use serde::{Deserialize, Serialize};
8
9use crate::{sizes::*, Bytes, TryFromStrError, Vec};
10
11pub use crate::operation::{Operation, VendorOperation};
12
13pub mod client_pin;
14pub mod credential_management;
15pub mod get_assertion;
16pub mod get_info;
17pub mod large_blobs;
18pub mod make_credential;
19
20pub type Result<T> = core::result::Result<T, Error>;
21
22#[derive(Clone, Debug, PartialEq)]
23#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
24#[non_exhaustive]
25#[allow(clippy::large_enum_variant)]
26// clippy says...large size difference
27/// Enum of all CTAP2 requests.
28pub enum Request<'a> {
29    // 0x1
30    MakeCredential(make_credential::Request<'a>),
31    // 0x2
32    GetAssertion(get_assertion::Request<'a>),
33    // 0x8
34    GetNextAssertion,
35    // 0x4
36    GetInfo,
37    // 0x6
38    ClientPin(client_pin::Request<'a>),
39    // 0x7
40    Reset,
41    // 0xA
42    CredentialManagement(credential_management::Request<'a>),
43    // 0xB
44    Selection,
45    // 0xC
46    LargeBlobs(large_blobs::Request<'a>),
47    // vendor, to be embellished
48    // Q: how to handle the associated CBOR structures
49    Vendor(crate::operation::VendorOperation),
50}
51
52pub enum CtapMappingError {
53    InvalidCommand(u8),
54    ParsingError(cbor_smol::Error),
55}
56
57impl From<CtapMappingError> for Error {
58    fn from(mapping_error: CtapMappingError) -> Error {
59        match mapping_error {
60            CtapMappingError::InvalidCommand(_cmd) => Error::InvalidCommand,
61            CtapMappingError::ParsingError(cbor_error) => match cbor_error {
62                cbor_smol::Error::SerdeMissingField => Error::MissingParameter,
63                _ => Error::InvalidCbor,
64            },
65        }
66    }
67}
68
69impl<'a> Request<'a> {
70    /// Deserialize from CBOR where the first byte denotes the operation.
71    #[inline(never)]
72    pub fn deserialize(data: &'a [u8]) -> Result<Self> {
73        if data.is_empty() {
74            return Err(
75                CtapMappingError::ParsingError(cbor_smol::Error::DeserializeUnexpectedEnd).into(),
76            );
77        }
78
79        let (&op, data) = data.split_first().ok_or(CtapMappingError::ParsingError(
80            cbor_smol::Error::DeserializeUnexpectedEnd,
81        ))?;
82
83        let operation = Operation::try_from(op).map_err(|_| {
84            debug_now!("invalid operation {}", op);
85            CtapMappingError::InvalidCommand(op)
86        })?;
87
88        info!("deser {:?}", operation);
89        Ok(match operation {
90            Operation::MakeCredential => Request::MakeCredential(
91                cbor_deserialize(data).map_err(CtapMappingError::ParsingError)?,
92            ),
93
94            Operation::GetAssertion => Request::GetAssertion(
95                cbor_deserialize(data).map_err(CtapMappingError::ParsingError)?,
96            ),
97
98            Operation::GetNextAssertion => Request::GetNextAssertion,
99
100            Operation::CredentialManagement | Operation::PreviewCredentialManagement => {
101                Request::CredentialManagement(
102                    cbor_deserialize(data).map_err(CtapMappingError::ParsingError)?,
103                )
104            }
105
106            Operation::Reset => Request::Reset,
107
108            Operation::Selection => Request::Selection,
109
110            Operation::GetInfo => Request::GetInfo,
111
112            Operation::ClientPin => {
113                Request::ClientPin(cbor_deserialize(data).map_err(CtapMappingError::ParsingError)?)
114            }
115
116            Operation::LargeBlobs => {
117                Request::LargeBlobs(cbor_deserialize(data).map_err(CtapMappingError::ParsingError)?)
118            }
119
120            // NB: FIDO Alliance "stole" 0x40 and 0x41, so these are not available
121            Operation::Vendor(vendor_operation) => Request::Vendor(vendor_operation),
122
123            Operation::BioEnrollment | Operation::PreviewBioEnrollment | Operation::Config => {
124                debug_now!("unhandled CBOR operation {:?}", operation);
125                return Err(CtapMappingError::InvalidCommand(op).into());
126            }
127        })
128    }
129}
130
131#[derive(Clone, Debug, PartialEq)]
132#[non_exhaustive]
133/// Enum of all CTAP2 responses.
134#[allow(clippy::large_enum_variant)]
135pub enum Response {
136    MakeCredential(make_credential::Response),
137    GetAssertion(get_assertion::Response),
138    GetNextAssertion(get_assertion::Response),
139    GetInfo(get_info::Response),
140    ClientPin(client_pin::Response),
141    Reset,
142    Selection,
143    CredentialManagement(credential_management::Response),
144    LargeBlobs(large_blobs::Response),
145    // Q: how to handle the associated CBOR structures
146    Vendor,
147}
148
149impl Response {
150    #[inline(never)]
151    pub fn serialize<const N: usize>(&self, buffer: &mut Vec<u8, N>) {
152        buffer.resize_default(buffer.capacity()).ok();
153        let (status, data) = buffer.split_first_mut().unwrap();
154        use cbor_smol::cbor_serialize;
155        use Response::*;
156        let outcome = match self {
157            GetInfo(response) => cbor_serialize(response, data),
158            MakeCredential(response) => cbor_serialize(response, data),
159            ClientPin(response) => cbor_serialize(response, data),
160            GetAssertion(response) | GetNextAssertion(response) => cbor_serialize(response, data),
161            CredentialManagement(response) => cbor_serialize(response, data),
162            LargeBlobs(response) => cbor_serialize(response, data),
163            Reset | Selection | Vendor => Ok([].as_slice()),
164        };
165        if let Ok(slice) = outcome {
166            *status = 0;
167            // Instead of an empty CBOR map (0xA0), we return an empty response
168            if slice == [0xA0] {
169                buffer.resize_default(1).ok();
170            } else {
171                let l = slice.len();
172                buffer.resize_default(l + 1).ok();
173            }
174        } else {
175            *status = Error::Other as u8;
176            buffer.resize_default(1).ok();
177        }
178    }
179}
180
181#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
182#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
183#[non_exhaustive]
184pub struct AuthenticatorOptions {
185    #[serde(skip_serializing_if = "Option::is_none")]
186    pub rk: Option<bool>,
187    #[serde(skip_serializing_if = "Option::is_none")]
188    pub up: Option<bool>,
189    #[serde(skip_serializing_if = "Option::is_none")]
190    /// Note: This flag asks to perform UV *within the authenticator*,
191    /// for instance with biometrics or on-device PIN entry,
192    /// use of pinAuth is implicit where required.
193    pub uv: Option<bool>,
194}
195
196bitflags! {
197    pub struct AuthenticatorDataFlags: u8 {
198        const USER_PRESENCE = 1 << 0;
199        const USER_VERIFIED = 1 << 2;
200        const ATTESTED_CREDENTIAL_DATA = 1 << 6;
201        const EXTENSION_DATA = 1 << 7;
202    }
203}
204
205pub trait SerializeAttestedCredentialData {
206    fn serialize(&self, buffer: &mut SerializedAuthenticatorData) -> Result<()>;
207}
208
209#[derive(Clone, Debug, Eq, PartialEq)]
210pub struct AuthenticatorData<'a, A, E> {
211    pub rp_id_hash: &'a [u8; 32],
212    pub flags: AuthenticatorDataFlags,
213    pub sign_count: u32,
214    pub attested_credential_data: Option<A>,
215    pub extensions: Option<E>,
216}
217
218pub type SerializedAuthenticatorData = Bytes<AUTHENTICATOR_DATA_LENGTH>;
219
220// The reason for this non-use of CBOR is for compatibility with
221// FIDO U2F authentication signatures.
222impl<A: SerializeAttestedCredentialData, E: serde::Serialize> AuthenticatorData<'_, A, E> {
223    #[inline(never)]
224    pub fn serialize(&self) -> Result<SerializedAuthenticatorData> {
225        let mut bytes = SerializedAuthenticatorData::new();
226
227        // 32 bytes, the RP id's hash
228        bytes
229            .extend_from_slice(self.rp_id_hash)
230            .map_err(|_| Error::Other)?;
231        // flags
232        bytes.push(self.flags.bits()).map_err(|_| Error::Other)?;
233        // signature counts as 32-bit unsigned big-endian integer.
234        bytes
235            .extend_from_slice(&self.sign_count.to_be_bytes())
236            .map_err(|_| Error::Other)?;
237
238        // the attested credential data
239        if let Some(attested_credential_data) = &self.attested_credential_data {
240            attested_credential_data.serialize(&mut bytes)?;
241        }
242
243        // the extensions data
244        if let Some(extensions) = self.extensions.as_ref() {
245            cbor_smol::cbor_serialize_to(extensions, &mut bytes).map_err(|_| Error::Other)?;
246        }
247
248        Ok(bytes)
249    }
250}
251
252#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
253#[non_exhaustive]
254#[serde(untagged)]
255#[allow(clippy::large_enum_variant)]
256pub enum AttestationStatement {
257    None(NoneAttestationStatement),
258    Packed(PackedAttestationStatement),
259}
260
261#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
262#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
263#[non_exhaustive]
264#[serde(into = "&str", try_from = "&str")]
265pub enum AttestationStatementFormat {
266    None,
267    Packed,
268}
269
270impl AttestationStatementFormat {
271    const NONE: &'static str = "none";
272    const PACKED: &'static str = "packed";
273}
274
275impl From<AttestationStatementFormat> for &str {
276    fn from(format: AttestationStatementFormat) -> Self {
277        match format {
278            AttestationStatementFormat::None => AttestationStatementFormat::NONE,
279            AttestationStatementFormat::Packed => AttestationStatementFormat::PACKED,
280        }
281    }
282}
283
284impl TryFrom<&str> for AttestationStatementFormat {
285    type Error = TryFromStrError;
286
287    fn try_from(s: &str) -> core::result::Result<Self, Self::Error> {
288        match s {
289            Self::NONE => Ok(Self::None),
290            Self::PACKED => Ok(Self::Packed),
291            _ => Err(TryFromStrError),
292        }
293    }
294}
295
296#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
297pub struct NoneAttestationStatement {}
298
299#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
300pub struct PackedAttestationStatement {
301    pub alg: i32,
302    pub sig: Bytes<ASN1_SIGNATURE_LENGTH>,
303    #[serde(skip_serializing_if = "Option::is_none")]
304    pub x5c: Option<Vec<Bytes<1024>, 1>>,
305}
306
307#[derive(Clone, Debug, Default, Eq, PartialEq)]
308pub struct AttestationFormatsPreference {
309    pub(crate) known_formats: Vec<AttestationStatementFormat, 2>,
310    pub(crate) unknown: bool,
311}
312
313impl AttestationFormatsPreference {
314    pub fn known_formats(&self) -> &[AttestationStatementFormat] {
315        &self.known_formats
316    }
317
318    pub fn includes_unknown_formats(&self) -> bool {
319        self.unknown
320    }
321}
322
323impl<'de> Deserialize<'de> for AttestationFormatsPreference {
324    fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
325    where
326        D: serde::Deserializer<'de>,
327    {
328        struct ValueVisitor;
329
330        impl<'de> serde::de::Visitor<'de> for ValueVisitor {
331            type Value = AttestationFormatsPreference;
332
333            fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
334                formatter.write_str("a sequence")
335            }
336
337            fn visit_seq<A>(self, mut seq: A) -> core::result::Result<Self::Value, A::Error>
338            where
339                A: serde::de::SeqAccess<'de>,
340            {
341                let mut preference = AttestationFormatsPreference::default();
342                while let Some(value) = seq.next_element::<&str>()? {
343                    if let Ok(format) = AttestationStatementFormat::try_from(value) {
344                        preference.known_formats.push(format).ok();
345                    } else {
346                        preference.unknown = true;
347                    }
348                }
349                Ok(preference)
350            }
351        }
352
353        deserializer.deserialize_seq(ValueVisitor)
354    }
355}
356
357#[derive(Clone, Copy, Debug, Eq, PartialEq)]
358#[non_exhaustive]
359pub enum Error {
360    Success = 0x00,
361    InvalidCommand = 0x01,
362    InvalidParameter = 0x02,
363    InvalidLength = 0x03,
364    InvalidSeq = 0x04,
365    Timeout = 0x05,
366    ChannelBusy = 0x06,
367    LockRequired = 0x0A,
368    InvalidChannel = 0x0B,
369    CborUnexpectedType = 0x11,
370    InvalidCbor = 0x12,
371    MissingParameter = 0x14,
372    LimitExceeded = 0x15,
373    UnsupportedExtension = 0x16,
374    FingerprintDatabaseFull = 0x17,
375    LargeBlobStorageFull = 0x18,
376    CredentialExcluded = 0x19,
377    Processing = 0x21,
378    InvalidCredential = 0x22,
379    UserActionPending = 0x23,
380    OperationPending = 0x24,
381    NoOperations = 0x25,
382    UnsupportedAlgorithm = 0x26,
383    OperationDenied = 0x27,
384    KeyStoreFull = 0x28,
385    NotBusy = 0x29,
386    NoOperationPending = 0x2A,
387    UnsupportedOption = 0x2B,
388    InvalidOption = 0x2C,
389    KeepaliveCancel = 0x2D,
390    NoCredentials = 0x2E,
391    UserActionTimeout = 0x2F,
392    NotAllowed = 0x30,
393    PinInvalid = 0x31,
394    PinBlocked = 0x32,
395    PinAuthInvalid = 0x33,
396    PinAuthBlocked = 0x34,
397    PinNotSet = 0x35,
398    PinRequired = 0x36,
399    PinPolicyViolation = 0x37,
400    PinTokenExpired = 0x38,
401    RequestTooLarge = 0x39,
402    ActionTimeout = 0x3A,
403    UpRequired = 0x3B,
404    UvBlocked = 0x3C,
405    IntegrityFailure = 0x3D,
406    InvalidSubcommand = 0x3E,
407    UvInvalid = 0x3F,
408    UnauthorizedPermission = 0x40,
409    Other = 0x7F,
410    SpecLast = 0xDF,
411    ExtensionFirst = 0xE0,
412    ExtensionLast = 0xEF,
413    VendorFirst = 0xF0,
414    VendorLast = 0xFF,
415}
416
417/// CTAP2 authenticator API
418///
419/// Note that all Authenticators automatically implement [`crate::Rpc`] with [`Request`] and
420/// [`Response`].
421pub trait Authenticator {
422    fn get_info(&mut self) -> get_info::Response;
423    fn make_credential(
424        &mut self,
425        request: &make_credential::Request,
426    ) -> Result<make_credential::Response>;
427    fn get_assertion(
428        &mut self,
429        request: &get_assertion::Request,
430    ) -> Result<get_assertion::Response>;
431    fn get_next_assertion(&mut self) -> Result<get_assertion::Response>;
432    fn reset(&mut self) -> Result<()>;
433    fn client_pin(&mut self, request: &client_pin::Request) -> Result<client_pin::Response>;
434    fn credential_management(
435        &mut self,
436        request: &credential_management::Request,
437    ) -> Result<credential_management::Response>;
438    fn selection(&mut self) -> Result<()>;
439    fn vendor(&mut self, op: VendorOperation) -> Result<()>;
440
441    // Optional extensions
442    fn large_blobs(&mut self, request: &large_blobs::Request) -> Result<large_blobs::Response> {
443        let _ = request;
444        Err(Error::InvalidCommand)
445    }
446
447    /// Dispatches the enum of possible requests into the appropriate trait method.
448    #[inline(never)]
449    fn call_ctap2(&mut self, request: &Request) -> Result<Response> {
450        match request {
451            // 0x4
452            Request::GetInfo => {
453                debug_now!("CTAP2.GI");
454                Ok(Response::GetInfo(self.get_info()))
455            }
456
457            // 0x2
458            Request::MakeCredential(request) => {
459                debug_now!("CTAP2.MC");
460                Ok(Response::MakeCredential(
461                    self.make_credential(request).inspect_err(|_e| {
462                        debug!("error: {:?}", _e);
463                    })?,
464                ))
465            }
466
467            // 0x1
468            Request::GetAssertion(request) => {
469                debug_now!("CTAP2.GA");
470                Ok(Response::GetAssertion(
471                    self.get_assertion(request).inspect_err(|_e| {
472                        debug!("error: {:?}", _e);
473                    })?,
474                ))
475            }
476
477            // 0x8
478            Request::GetNextAssertion => {
479                debug_now!("CTAP2.GNA");
480                Ok(Response::GetNextAssertion(
481                    self.get_next_assertion().inspect_err(|_e| {
482                        debug!("error: {:?}", _e);
483                    })?,
484                ))
485            }
486
487            // 0x7
488            Request::Reset => {
489                debug_now!("CTAP2.RST");
490                self.reset().inspect_err(|_e| {
491                    debug!("error: {:?}", _e);
492                })?;
493                Ok(Response::Reset)
494            }
495
496            // 0x6
497            Request::ClientPin(request) => {
498                debug_now!("CTAP2.PIN");
499                Ok(Response::ClientPin(self.client_pin(request).inspect_err(
500                    |_e| {
501                        debug!("error: {:?}", _e);
502                    },
503                )?))
504            }
505
506            // 0xA
507            Request::CredentialManagement(request) => {
508                debug_now!("CTAP2.CM");
509                Ok(Response::CredentialManagement(
510                    self.credential_management(request).inspect_err(|_e| {
511                        debug!("error: {:?}", _e);
512                    })?,
513                ))
514            }
515
516            // 0xB
517            Request::Selection => {
518                debug_now!("CTAP2.SEL");
519                self.selection().inspect_err(|_e| {
520                    debug!("error: {:?}", _e);
521                })?;
522                Ok(Response::Selection)
523            }
524
525            // 0xC
526            Request::LargeBlobs(request) => {
527                debug_now!("CTAP2.LB");
528                Ok(Response::LargeBlobs(
529                    self.large_blobs(request).inspect_err(|_e| {
530                        debug!("error: {:?}", _e);
531                    })?,
532                ))
533            }
534
535            // Not stable
536            Request::Vendor(op) => {
537                debug_now!("CTAP2.V");
538                self.vendor(*op).inspect_err(|_e| {
539                    debug!("error: {:?}", _e);
540                })?;
541                Ok(Response::Vendor)
542            }
543        }
544    }
545}
546
547impl<A: Authenticator> crate::Rpc<Error, Request<'_>, Response> for A {
548    /// Dispatches the enum of possible requests into the appropriate trait method.
549    #[inline(never)]
550    fn call(&mut self, request: &Request) -> Result<Response> {
551        self.call_ctap2(request)
552    }
553}