Skip to main content

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