webauthn_authenticator_rs/ctap2/commands/
mod.rs

1//! CTAP 2 commands.
2use serde::Serialize;
3use serde_cbor_2::{ser::to_vec_packed, Value};
4use std::borrow::Borrow;
5use std::collections::{BTreeMap, BTreeSet};
6
7#[cfg(any(all(doc, not(doctest)), feature = "ctap2-management"))]
8mod bio_enrollment;
9mod client_pin;
10#[cfg(any(all(doc, not(doctest)), feature = "ctap2-management"))]
11mod config;
12#[cfg(any(all(doc, not(doctest)), feature = "ctap2-management"))]
13mod credential_management;
14mod get_assertion;
15mod get_info;
16mod make_credential;
17#[cfg(any(all(doc, not(doctest)), feature = "ctap2-management"))]
18mod reset;
19mod selection;
20
21#[cfg(any(all(doc, not(doctest)), feature = "ctap2-management"))]
22pub use self::bio_enrollment::*;
23pub use self::client_pin::*;
24#[cfg(any(all(doc, not(doctest)), feature = "ctap2-management"))]
25pub use self::config::*;
26#[cfg(any(all(doc, not(doctest)), feature = "ctap2-management"))]
27pub use self::credential_management::*;
28pub use self::get_assertion::*;
29pub use self::get_info::*;
30pub use self::make_credential::*;
31#[cfg(any(all(doc, not(doctest)), feature = "ctap2-management"))]
32pub use self::reset::*;
33pub use self::selection::*;
34use crate::error::WebauthnCError;
35use crate::transport::iso7816::ISO7816RequestAPDU;
36
37const FRAG_MAX: usize = 0xF0;
38
39/// Common trait for all CBOR responses.
40///
41/// Ths handles some of the response deserialization process.
42pub trait CBORResponse: Sized + std::fmt::Debug + Send {
43    fn try_from(i: &[u8]) -> Result<Self, WebauthnCError>;
44}
45
46/// Common trait for all CBOR commands.
47///
48/// This handles some of the command serialization process.
49pub trait CBORCommand: Serialize + Sized + std::fmt::Debug + Send {
50    /// CTAP comand byte
51    const CMD: u8;
52
53    /// If true (default), then the command has a payload, which will be
54    /// serialized into CBOR format.
55    ///
56    /// If false, then the command has no payload.
57    const HAS_PAYLOAD: bool = true;
58
59    /// The response type associated with this command.
60    type Response: CBORResponse;
61
62    /// Converts a CTAP v2 command into a binary form.
63    fn cbor(&self) -> Result<Vec<u8>, serde_cbor_2::Error> {
64        // CTAP v2.1, s8.2.9.1.2 (USB CTAPHID_CBOR), s8.3.5 (NFC framing).
65        // Similar form used for caBLE.
66        // TODO: BLE is different, it includes a u16 length after the command?
67        if !Self::HAS_PAYLOAD {
68            return Ok(vec![Self::CMD]);
69        }
70
71        trace!("Sending: {:?}", self);
72        let mut b = to_vec_packed(self)?;
73        trace!(
74            "CBOR: cmd={}, cbor={:?}",
75            Self::CMD,
76            serde_cbor_2::from_slice::<'_, serde_cbor_2::Value>(&b[..])
77        );
78
79        b.reserve(1);
80        b.insert(0, Self::CMD);
81        Ok(b)
82    }
83}
84
85/// Converts a CTAP v2 command into a form suitable for transmission with
86/// short ISO/IEC 7816-4 APDUs (over NFC).
87pub fn to_short_apdus(cbor: &[u8]) -> Vec<ISO7816RequestAPDU> {
88    let chunks = cbor.chunks(FRAG_MAX).rev();
89    let mut o = Vec::with_capacity(chunks.len());
90    let mut last = true;
91
92    for chunk in chunks {
93        o.insert(
94            0,
95            ISO7816RequestAPDU {
96                cla: if last { 0x80 } else { 0x90 },
97                ins: 0x10,
98                p1: 0x00,
99                p2: 0x00,
100                data: chunk.to_vec(),
101                ne: if last { 256 } else { 0 },
102            },
103        );
104        last = false;
105    }
106
107    o
108}
109
110/// Converts a CTAP v2 command into a form suitable for transmission with
111/// extended ISO/IEC 7816-4 APDUs (over NFC).
112pub fn to_extended_apdu(cbor: Vec<u8>) -> ISO7816RequestAPDU {
113    ISO7816RequestAPDU {
114        cla: 0x80,
115        ins: 0x10,
116        p1: 0, // 0x80,  // client supports NFCCTAP_GETRESPONSE
117        p2: 0x00,
118        data: cbor,
119        ne: 65536,
120    }
121}
122
123fn value_to_vec_string(v: Value, loc: &str) -> Option<Vec<String>> {
124    if let Value::Array(v) = v {
125        let mut x = Vec::with_capacity(v.len());
126        for s in v.into_iter() {
127            if let Value::Text(s) = s {
128                x.push(s);
129            } else {
130                error!("Invalid value inside {}: {:?}", loc, s);
131            }
132        }
133        Some(x)
134    } else {
135        error!("Invalid type for {}: {:?}", loc, v);
136        None
137    }
138}
139
140fn value_to_set_string(v: Value, loc: &str) -> Option<BTreeSet<String>> {
141    if let Value::Array(v) = v {
142        let mut x = BTreeSet::new();
143        for s in v.into_iter() {
144            if let Value::Text(s) = s {
145                x.insert(s);
146            } else {
147                error!("Invalid value inside {}: {:?}", loc, s);
148            }
149        }
150        Some(x)
151    } else {
152        error!("Invalid type for {}: {:?}", loc, v);
153        None
154    }
155}
156
157fn value_to_set_u64(v: Value, loc: &str) -> Option<BTreeSet<u64>> {
158    if let Value::Array(v) = v {
159        let mut x = BTreeSet::new();
160        for i in v.into_iter() {
161            if let Value::Integer(i) = i {
162                if let Ok(i) = u64::try_from(i) {
163                    x.insert(i);
164                    continue;
165                }
166            }
167            error!("Invalid value inside {}: {:?}", loc, i);
168        }
169        Some(x)
170    } else {
171        error!("Invalid type for {}: {:?}", loc, v);
172        None
173    }
174}
175
176fn value_to_vec(v: Value, loc: &str) -> Option<Vec<Value>> {
177    if let Value::Array(v) = v {
178        Some(v)
179    } else {
180        error!("Invalid type for {}: {:?}", loc, v);
181        None
182    }
183}
184
185fn value_to_map(v: Value, loc: &str) -> Option<BTreeMap<Value, Value>> {
186    if let Value::Map(v) = v {
187        Some(v)
188    } else {
189        error!("Invalid type for {}: {:?}", loc, v);
190        None
191    }
192}
193
194fn value_to_vec_u32(v: Value, loc: &str) -> Option<Vec<u32>> {
195    value_to_vec(v, loc).map(|v| {
196        v.into_iter()
197            .filter_map(|i| value_to_u32(&i, loc))
198            .collect()
199    })
200}
201
202#[cfg(feature = "ctap2-management")]
203pub(crate) fn value_to_u8(v: &Value, loc: &str) -> Option<u8> {
204    if let Value::Integer(i) = v {
205        u8::try_from(*i)
206            .map_err(|_| error!("Invalid value inside {}: {:?}", loc, i))
207            .ok()
208    } else {
209        error!("Invalid type for {}: {:?}", loc, v);
210        None
211    }
212}
213
214pub(crate) fn value_to_u32(v: &Value, loc: &str) -> Option<u32> {
215    if let Value::Integer(i) = v {
216        u32::try_from(*i)
217            .map_err(|_| error!("Invalid value inside {}: {:?}", loc, i))
218            .ok()
219    } else {
220        error!("Invalid type for {}: {:?}", loc, v);
221        None
222    }
223}
224
225#[cfg(any(doc, feature = "cable"))]
226pub(crate) fn value_to_u64(v: &Value, loc: &str) -> Option<u64> {
227    if let Value::Integer(i) = v {
228        u64::try_from(*i)
229            .map_err(|_| error!("Invalid value inside {}: {:?}", loc, i))
230            .ok()
231    } else {
232        error!("Invalid type for {}: {:?}", loc, v);
233        None
234    }
235}
236
237fn value_to_i128(v: impl Borrow<Value>, loc: &str) -> Option<i128> {
238    let v = v.borrow();
239    if let Value::Integer(i) = v {
240        Some(*i)
241    } else {
242        error!("Invalid type for {}: {:?}", loc, v);
243        None
244    }
245}
246
247fn value_to_usize(v: impl Borrow<Value>, loc: &str) -> Option<usize> {
248    let v = v.borrow();
249    if let Value::Integer(i) = v {
250        usize::try_from(*i)
251            .map_err(|_| error!("Invalid value inside {}: {:?}", loc, i))
252            .ok()
253    } else {
254        error!("Invalid type for {}: {:?}", loc, v);
255        None
256    }
257}
258
259/// Converts a [`Value::Bool`] into [`Option<bool>`]. Returns [`Option::None`] for other [`Value`] types.
260pub(crate) fn value_to_bool(v: &Value, loc: &str) -> Option<bool> {
261    if let Value::Bool(b) = v {
262        Some(*b)
263    } else {
264        error!("Invalid type for {}: {:?}", loc, v);
265        None
266    }
267}
268
269/// Converts a [`Value::Bytes`] into [`Option<Vec<u8>>`]. Returns [`Option::None`] for other [`Value`] types.
270pub(crate) fn value_to_vec_u8(v: Value, loc: &str) -> Option<Vec<u8>> {
271    if let Value::Bytes(b) = v {
272        Some(b)
273    } else {
274        error!("Invalid type for {}: {:?}", loc, v);
275        None
276    }
277}
278
279pub(crate) fn value_to_string(v: Value, loc: &str) -> Option<String> {
280    if let Value::Text(s) = v {
281        Some(s)
282    } else {
283        error!("Invalid type for {}: {:?}", loc, v);
284        None
285    }
286}
287
288/// Type for commands which have no response data.
289#[derive(Debug)]
290pub struct NoResponse {}
291
292impl CBORResponse for NoResponse {
293    fn try_from(_raw: &[u8]) -> Result<Self, WebauthnCError> {
294        Ok(Self {})
295    }
296}
297
298fn map_int_keys(m: BTreeMap<Value, Value>) -> Result<BTreeMap<u32, Value>, WebauthnCError> {
299    m.into_iter()
300        .map(|(k, v)| {
301            let k = value_to_u32(&k, "map_int_keys").ok_or(WebauthnCError::Internal)?;
302
303            Ok((k, v))
304        })
305        .collect()
306}
307
308// TODO: switch to #derive
309#[macro_export]
310macro_rules! deserialize_cbor {
311    ($name:ident) => {
312        impl $crate::ctap2::commands::CBORResponse for $name {
313            fn try_from(i: &[u8]) -> Result<Self, $crate::error::WebauthnCError> {
314                if i.is_empty() {
315                    TryFrom::try_from(std::collections::BTreeMap::new()).map_err(|e| {
316                        error!("Tried to deserialise empty input, got error: {:?}", e);
317                        $crate::error::WebauthnCError::Cbor
318                    })
319                } else {
320                    // Convert to Value (Value::Map)
321                    let v =
322                        serde_cbor_2::from_slice::<'_, serde_cbor_2::Value>(&i).map_err(|e| {
323                            error!("deserialise: {:?}", e);
324                            $crate::error::WebauthnCError::Cbor
325                        })?;
326
327                    // Extract the BTreeMap
328                    let v = if let serde_cbor_2::Value::Map(v) = v {
329                        Ok(v)
330                    } else {
331                        error!("deserialise: unexpected CBOR type {:?}", v);
332                        Err($crate::error::WebauthnCError::Cbor)
333                    }?;
334
335                    // Convert BTreeMap<Value, Value> into BTreeMap<u32, Value>
336                    let v = $crate::ctap2::commands::map_int_keys(v)?;
337
338                    TryFrom::try_from(v).map_err(|_| {
339                        error!("deserialising structure");
340                        $crate::error::WebauthnCError::Cbor
341                    })
342                }
343            }
344        }
345    };
346}