webauthn_authenticator_rs/ctap2/commands/
get_info.rs

1use std::str::FromStr;
2
3use serde::{Deserialize, Serialize};
4use serde_cbor_2::Value;
5use std::fmt;
6use uuid::Uuid;
7use webauthn_rs_proto::AuthenticatorTransport;
8
9use self::CBORCommand;
10use super::*;
11
12/// `authenticatorGetInfo` request type.
13///
14/// This request type has no fields.
15///
16/// Reference: <https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#authenticatorGetInfo>
17#[derive(Serialize, Debug, Clone)]
18pub struct GetInfoRequest {}
19
20impl CBORCommand for GetInfoRequest {
21    const CMD: u8 = 0x04;
22    const HAS_PAYLOAD: bool = false;
23    type Response = GetInfoResponse;
24}
25
26/// `authenticatorGetInfo` response type.
27///
28/// Reference: <https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#authenticatorGetInfo>
29#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
30#[serde(try_from = "BTreeMap<u32, Value>", into = "BTreeMap<u32, Value>")]
31pub struct GetInfoResponse {
32    /// All CTAP protocol versions which the token supports.
33    pub versions: BTreeSet<String>,
34    /// All protocol extensions which the token supports.
35    pub extensions: Option<Vec<String>>,
36    /// The claimed AAGUID.
37    pub aaguid: Option<Uuid>,
38    /// List of supported options.
39    pub options: Option<BTreeMap<String, bool>>,
40    /// Maximum message size supported by the authenticator.
41    pub max_msg_size: Option<u32>,
42    /// All PIN/UV auth protocols which the token supports.
43    pub pin_protocols: Option<Vec<u32>>,
44    pub max_cred_count_in_list: Option<u32>,
45    pub max_cred_id_len: Option<u32>,
46    /// List of supported transports as strings.
47    ///
48    /// Use [get_transports][Self::get_transports] to get a list of
49    /// [AuthenticatorTransport].
50    pub transports: Option<Vec<String>>,
51    /// List of supported algorithms for credential generation.
52    pub algorithms: Option<Value>,
53    pub max_serialized_large_blob_array: Option<usize>,
54    pub force_pin_change: bool,
55    /// Current minimum PIN length, in Unicode code points.
56    ///
57    /// Use [get_min_pin_length][Self::get_min_pin_length] to get a default
58    /// value for when this is not present.
59    pub min_pin_length: Option<usize>,
60    pub firmware_version: Option<i128>,
61    pub max_cred_blob_length: Option<usize>,
62    pub max_rpids_for_set_min_pin_length: Option<u32>,
63    pub preferred_platform_uv_attempts: Option<u32>,
64    pub uv_modality: Option<u32>,
65    pub certifications: Option<BTreeMap<String, u8>>,
66
67    /// Estimated number of additional discoverable credentials which could be
68    /// created on the authenticator, assuming *maximally-sized* fields for all
69    /// requests (ie: errs low).
70    ///
71    /// If a request to create a maximally-sized discoverable credential *might*
72    /// fail due to storage constraints, the authenticator reports 0.
73    ///
74    /// This value may vary over time, depending on the size of individual
75    /// discoverable credentials, and the token's storage allocation strategy.
76    ///
77    /// ## CTAP compatibility
78    ///
79    /// This field is **optional**, and may only be present on authenticators
80    /// supporting CTAP 2.1 and later.
81    ///
82    /// On authenticators supporting [credential management][0] (including CTAP
83    /// 2.1-PRE), an optimistic estimate (ie: presuming *minimally-sized*
84    /// fields) may be available from
85    /// [`CredentialStorageMetadata::max_possible_remaining_resident_credentials_count`][1].
86    ///
87    /// [0]: crate::ctap2::CredentialManagementAuthenticator
88    /// [1]: super::CredentialStorageMetadata::max_possible_remaining_resident_credentials_count
89    pub remaining_discoverable_credentials: Option<u32>,
90
91    pub vendor_prototype_config_commands: Option<BTreeSet<u64>>,
92}
93
94impl fmt::Display for GetInfoResponse {
95    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96        write!(f, "versions: ")?;
97        if self.versions.is_empty() {
98            writeln!(f, "N/A")?;
99        } else {
100            for v in self.versions.iter() {
101                write!(f, "{v} ")?;
102            }
103            writeln!(f)?;
104        }
105
106        write!(f, "extensions: ")?;
107        for e in self.extensions.iter().flatten() {
108            write!(f, "{e} ")?;
109        }
110        writeln!(f)?;
111
112        match self.aaguid {
113            Some(aaguid) => writeln!(f, "aaguid: {aaguid}")?,
114            None => writeln!(f, "aaguid: INVALID")?,
115        }
116
117        write!(f, "options: ")?;
118        for (o, b) in self.options.iter().flatten() {
119            write!(f, "{o}:{b} ")?;
120        }
121        writeln!(f)?;
122
123        if let Some(v) = self.max_msg_size {
124            writeln!(f, "max message size: {v}")?;
125        }
126
127        write!(f, "PIN protocols: ")?;
128        if !self
129            .pin_protocols
130            .as_ref()
131            .map(|v| !v.is_empty())
132            .unwrap_or_default()
133        {
134            writeln!(f, "N/A")?;
135        } else {
136            for e in self.pin_protocols.iter().flatten() {
137                write!(f, "{e} ")?;
138            }
139            writeln!(f)?;
140        }
141
142        if let Some(v) = self.max_cred_count_in_list {
143            writeln!(f, "max cred count in list: {v}")?;
144        }
145
146        if let Some(v) = self.max_cred_id_len {
147            writeln!(f, "max cred ID length: {v}")?;
148        }
149
150        write!(f, "transports: ")?;
151        if !self
152            .transports
153            .as_ref()
154            .map(|v| !v.is_empty())
155            .unwrap_or_default()
156        {
157            writeln!(f, "N/A")?;
158        } else {
159            for v in self.transports.iter().flatten() {
160                write!(f, "{v} ")?;
161            }
162            writeln!(f)?;
163        }
164
165        if let Some(v) = &self.algorithms {
166            writeln!(f, "algorithms: {v:?}")?;
167        }
168
169        if let Some(v) = self.max_serialized_large_blob_array {
170            writeln!(f, "max serialized large blob array: {v}")?;
171        }
172
173        writeln!(f, "force PIN change: {:?}", self.force_pin_change)?;
174
175        if let Some(v) = self.min_pin_length {
176            writeln!(f, "minimum PIN length: {v}")?;
177        }
178
179        if let Some(v) = self.firmware_version {
180            writeln!(f, "firmware version: 0x{v:X}")?;
181        }
182
183        if let Some(v) = self.max_cred_blob_length {
184            writeln!(f, "max cred blob length: {v}")?;
185        }
186
187        if let Some(v) = self.max_rpids_for_set_min_pin_length {
188            writeln!(f, "max RPIDs for set minimum PIN length: {v}")?;
189        }
190
191        if let Some(v) = self.preferred_platform_uv_attempts {
192            writeln!(f, "preferred platform UV attempts: {v}")?;
193        }
194
195        if let Some(v) = self.uv_modality {
196            writeln!(f, "UV modality: 0x{v:X}")?;
197        }
198
199        if let Some(v) = &self.certifications {
200            writeln!(f, "certifications: {v:?}")?;
201        }
202
203        if let Some(v) = self.remaining_discoverable_credentials {
204            writeln!(f, "remaining discoverable credentials: {v}")?;
205        }
206
207        if let Some(v) = &self.vendor_prototype_config_commands {
208            writeln!(f, "vendor prototype config commands: {v:x?}")?;
209        }
210
211        Ok(())
212    }
213}
214
215impl GetInfoResponse {
216    /// Current minimum PIN length, in Unicode code points.
217    ///
218    /// If this is not present, defaults to 4.
219    pub fn get_min_pin_length(&self) -> usize {
220        self.min_pin_length.unwrap_or(4)
221    }
222
223    /// Gets all supported transports for this authenticator which match known
224    /// [AuthenticatorTransport] values. Unknown values are silently discarded.
225    pub fn get_transports(&self) -> Option<Vec<AuthenticatorTransport>> {
226        self.transports.as_ref().map(|transports| {
227            transports
228                .iter()
229                .filter_map(|transport| FromStr::from_str(transport).ok())
230                .collect()
231        })
232    }
233
234    /// Gets the state of an option.
235    pub fn get_option(&self, option: &str) -> Option<bool> {
236        self.options
237            .as_ref()
238            .and_then(|o| o.get(option))
239            .map(|v| v.to_owned())
240    }
241
242    /// Checks if the authenticator supports and has configured CTAP 2.1
243    /// biometric authentication.
244    ///
245    /// See also [`GetInfoResponse::ctap21pre_biometrics`][] for CTAP 2.1-PRE
246    /// authenticators.
247    ///
248    /// # Returns
249    ///
250    /// * `None`: if not supported.
251    /// * `Some(false)`: if supported, but not configured.
252    /// * `Some(true)`: if supported and configured.
253    pub fn ctap21_biometrics(&self) -> Option<bool> {
254        self.get_option("bioEnroll")
255    }
256
257    /// Checks if the authenticator supports and has configured CTAP 2.1-PRE
258    /// biometric authentication.
259    ///
260    /// See also [`GetInfoResponse::ctap21_biometrics`][] for CTAP 2.1
261    /// authenticators.
262    ///
263    /// # Returns
264    ///
265    /// * `None`: if not supported.
266    /// * `Some(false)`: if supported, but not configured.
267    /// * `Some(true)`: if supported and configured.
268    pub fn ctap21pre_biometrics(&self) -> Option<bool> {
269        self.get_option("userVerificationMgmtPreview")
270    }
271
272    /// Returns `true` if the authenticator supports built-in user verification,
273    /// and it has been configured.
274    pub fn user_verification_configured(&self) -> bool {
275        self.get_option("uv").unwrap_or_default()
276    }
277
278    /// Returns `true` if the authenticator supports CTAP 2.1 authenticator
279    /// configuration commands.
280    pub fn supports_config(&self) -> bool {
281        self.get_option("authnrCfg").unwrap_or_default()
282    }
283
284    /// Returns `true` if the authenticator supports CTAP 2.1 enterprise
285    /// attestation.
286    pub fn supports_enterprise_attestation(&self) -> bool {
287        self.get_option("ep").is_some()
288    }
289
290    /// Returns `true` if user verification is not required for `makeCredential`
291    /// requests.
292    pub fn make_cred_uv_not_required(&self) -> bool {
293        self.get_option("makeCredUvNotRqd").unwrap_or_default()
294    }
295
296    /// Returns `true` if the authenticator supports CTAP 2.1 credential
297    /// management.
298    pub fn ctap21_credential_management(&self) -> bool {
299        self.get_option("credMgmt").unwrap_or_default()
300    }
301
302    /// Returns `true` if the authenticator supports CTAP 2.1-PRE credential
303    /// management.
304    pub fn ctap21pre_credential_management(&self) -> bool {
305        self.get_option("credentialMgmtPreview").unwrap_or_default()
306    }
307}
308
309impl TryFrom<BTreeMap<u32, Value>> for GetInfoResponse {
310    type Error = &'static str;
311
312    fn try_from(mut raw: BTreeMap<u32, Value>) -> Result<Self, Self::Error> {
313        trace!("raw = {:?}", raw);
314        let versions = raw
315            .remove(&0x01)
316            .and_then(|v| value_to_set_string(v, "0x01"))
317            .ok_or("0x01")?;
318
319        let extensions = raw
320            .remove(&0x02)
321            .and_then(|v| value_to_vec_string(v, "0x02"));
322
323        let aaguid = raw
324            .remove(&0x03)
325            .and_then(|v| match v {
326                Value::Bytes(x) => Some(x),
327                _ => {
328                    error!("Invalid type for 0x03: {:?}", v);
329                    None
330                }
331            })
332            .and_then(|v| Uuid::from_slice(&v).ok());
333
334        let options = raw.remove(&0x04).and_then(|v| {
335            if let Value::Map(v) = v {
336                let mut x = BTreeMap::new();
337                for (ka, va) in v.into_iter() {
338                    match (ka, va) {
339                        (Value::Text(s), Value::Bool(b)) => {
340                            x.insert(s, b);
341                        }
342                        _ => error!("Invalid value inside 0x04"),
343                    }
344                }
345                Some(x)
346            } else {
347                error!("Invalid type for 0x04: {:?}", v);
348                None
349            }
350        });
351
352        let max_msg_size = raw.remove(&0x05).and_then(|v| value_to_u32(&v, "0x05"));
353
354        let pin_protocols = raw.remove(&0x06).and_then(|v| value_to_vec_u32(v, "0x06"));
355
356        let max_cred_count_in_list = raw.remove(&0x07).and_then(|v| value_to_u32(&v, "0x07"));
357
358        let max_cred_id_len = raw.remove(&0x08).and_then(|v| value_to_u32(&v, "0x08"));
359
360        let transports = raw
361            .remove(&0x09)
362            .and_then(|v| value_to_vec_string(v, "0x09"));
363
364        let algorithms = raw.remove(&0x0A);
365
366        let max_serialized_large_blob_array =
367            raw.remove(&0x0b).and_then(|v| value_to_usize(v, "0x0b"));
368
369        let force_pin_change = raw
370            .remove(&0x0c)
371            .and_then(|v| value_to_bool(&v, "0x0c"))
372            .unwrap_or_default();
373
374        let min_pin_length = raw.remove(&0x0d).and_then(|v| value_to_usize(v, "0x0d"));
375
376        let firmware_version = raw.remove(&0x0e).and_then(|v| value_to_i128(v, "0x0e"));
377
378        let max_cred_blob_length = raw.remove(&0x0f).and_then(|v| value_to_usize(v, "0x0f"));
379
380        let max_rpids_for_set_min_pin_length =
381            raw.remove(&0x10).and_then(|v| value_to_u32(&v, "0x10"));
382
383        let preferred_platform_uv_attempts =
384            raw.remove(&0x11).and_then(|v| value_to_u32(&v, "0x11"));
385
386        let uv_modality = raw.remove(&0x12).and_then(|v| value_to_u32(&v, "0x12"));
387
388        let certifications = raw
389            .remove(&0x13)
390            .and_then(|v| value_to_map(v, "0x13"))
391            .map(|v| {
392                let mut x = BTreeMap::new();
393                for (ka, va) in v.into_iter() {
394                    if let (Value::Text(s), Value::Integer(i)) = (ka, va) {
395                        if let Ok(i) = u8::try_from(i) {
396                            x.insert(s, i);
397                            continue;
398                        }
399                    }
400                    error!("Invalid value inside 0x13");
401                }
402                x
403            });
404
405        let remaining_discoverable_credentials =
406            raw.remove(&0x14).and_then(|v| value_to_u32(&v, "0x14"));
407
408        let vendor_prototype_config_commands =
409            raw.remove(&0x15).and_then(|v| value_to_set_u64(v, "0x15"));
410
411        Ok(GetInfoResponse {
412            versions,
413            extensions,
414            aaguid,
415            options,
416            max_msg_size,
417            pin_protocols,
418            max_cred_count_in_list,
419            max_cred_id_len,
420            transports,
421            algorithms,
422            max_serialized_large_blob_array,
423            force_pin_change,
424            min_pin_length,
425            firmware_version,
426            max_cred_blob_length,
427            max_rpids_for_set_min_pin_length,
428            preferred_platform_uv_attempts,
429            uv_modality,
430            certifications,
431            remaining_discoverable_credentials,
432            vendor_prototype_config_commands,
433        })
434    }
435}
436
437impl From<GetInfoResponse> for BTreeMap<u32, Value> {
438    fn from(value: GetInfoResponse) -> Self {
439        let GetInfoResponse {
440            versions,
441            extensions,
442            aaguid,
443            options,
444            max_msg_size,
445            pin_protocols,
446            max_cred_count_in_list,
447            max_cred_id_len,
448            transports,
449            algorithms,
450            max_serialized_large_blob_array,
451            force_pin_change,
452            min_pin_length,
453            firmware_version,
454            max_cred_blob_length,
455            max_rpids_for_set_min_pin_length,
456            preferred_platform_uv_attempts,
457            uv_modality,
458            certifications,
459            remaining_discoverable_credentials,
460            vendor_prototype_config_commands,
461        } = value;
462
463        let mut o = BTreeMap::from([(
464            0x01,
465            Value::Array(versions.into_iter().map(Value::Text).collect()),
466        )]);
467
468        if let Some(extensions) = extensions {
469            o.insert(
470                0x02,
471                Value::Array(extensions.into_iter().map(Value::Text).collect()),
472            );
473        }
474
475        if let Some(aaguid) = aaguid {
476            o.insert(0x03, Value::Bytes(aaguid.as_bytes().to_vec()));
477        }
478
479        if let Some(options) = options {
480            o.insert(
481                0x04,
482                Value::Map(
483                    options
484                        .into_iter()
485                        .map(|(k, v)| (Value::Text(k), Value::Bool(v)))
486                        .collect(),
487                ),
488            );
489        }
490
491        if let Some(max_msg_size) = max_msg_size {
492            o.insert(0x05, Value::Integer(max_msg_size.into()));
493        }
494
495        if let Some(pin_protocols) = pin_protocols {
496            o.insert(
497                0x06,
498                Value::Array(
499                    pin_protocols
500                        .into_iter()
501                        .map(|v| Value::Integer(v.into()))
502                        .collect(),
503                ),
504            );
505        }
506
507        if let Some(max_cred_count_in_list) = max_cred_count_in_list {
508            o.insert(0x07, Value::Integer(max_cred_count_in_list.into()));
509        }
510
511        if let Some(max_cred_id_len) = max_cred_id_len {
512            o.insert(0x08, Value::Integer(max_cred_id_len.into()));
513        }
514
515        if let Some(transports) = transports {
516            o.insert(
517                0x09,
518                Value::Array(transports.into_iter().map(Value::Text).collect()),
519            );
520        }
521
522        if let Some(algorithms) = algorithms {
523            o.insert(0x0a, algorithms);
524        }
525
526        if let Some(v) = max_serialized_large_blob_array {
527            o.insert(0x0b, Value::Integer((v as u32).into()));
528        }
529
530        if force_pin_change {
531            o.insert(0x0c, Value::Bool(true));
532        }
533
534        if let Some(min_pin_length) = min_pin_length {
535            o.insert(0x0d, Value::Integer((min_pin_length as u64).into()));
536        }
537
538        if let Some(v) = firmware_version {
539            o.insert(0x0e, Value::Integer(v));
540        }
541
542        if let Some(v) = max_cred_blob_length {
543            o.insert(0x0f, Value::Integer((v as u64).into()));
544        }
545
546        if let Some(v) = max_rpids_for_set_min_pin_length {
547            o.insert(0x10, Value::Integer(v.into()));
548        }
549
550        if let Some(v) = preferred_platform_uv_attempts {
551            o.insert(0x11, Value::Integer(v.into()));
552        }
553
554        if let Some(v) = uv_modality {
555            o.insert(0x12, Value::Integer(v.into()));
556        }
557
558        if let Some(v) = certifications {
559            o.insert(
560                0x13,
561                Value::Map(
562                    v.into_iter()
563                        .map(|(k, v)| (Value::Text(k), Value::Integer(v.into())))
564                        .collect(),
565                ),
566            );
567        }
568
569        if let Some(v) = remaining_discoverable_credentials {
570            o.insert(0x14, Value::Integer(v.into()));
571        }
572
573        if let Some(v) = vendor_prototype_config_commands {
574            o.insert(
575                0x15,
576                Value::Array(v.into_iter().map(|v| Value::Integer(v.into())).collect()),
577            );
578        }
579
580        o
581    }
582}
583
584crate::deserialize_cbor!(GetInfoResponse);
585
586#[cfg(test)]
587mod tests {
588    use super::*;
589    use crate::transport::iso7816::ISO7816LengthForm;
590    use uuid::uuid;
591
592    #[test]
593    fn get_info_response_nfc_usb() {
594        let _ = tracing_subscriber::fmt().try_init();
595
596        let raw_apdu: Vec<u8> = vec![
597            170, 1, 131, 102, 85, 50, 70, 95, 86, 50, 104, 70, 73, 68, 79, 95, 50, 95, 48, 108, 70,
598            73, 68, 79, 95, 50, 95, 49, 95, 80, 82, 69, 2, 130, 107, 99, 114, 101, 100, 80, 114,
599            111, 116, 101, 99, 116, 107, 104, 109, 97, 99, 45, 115, 101, 99, 114, 101, 116, 3, 80,
600            47, 192, 87, 159, 129, 19, 71, 234, 177, 22, 187, 90, 141, 185, 32, 42, 4, 165, 98,
601            114, 107, 245, 98, 117, 112, 245, 100, 112, 108, 97, 116, 244, 105, 99, 108, 105, 101,
602            110, 116, 80, 105, 110, 245, 117, 99, 114, 101, 100, 101, 110, 116, 105, 97, 108, 77,
603            103, 109, 116, 80, 114, 101, 118, 105, 101, 119, 245, 5, 25, 4, 176, 6, 129, 1, 7, 8,
604            8, 24, 128, 9, 130, 99, 110, 102, 99, 99, 117, 115, 98, 10, 130, 162, 99, 97, 108, 103,
605            38, 100, 116, 121, 112, 101, 106, 112, 117, 98, 108, 105, 99, 45, 107, 101, 121, 162,
606            99, 97, 108, 103, 39, 100, 116, 121, 112, 101, 106, 112, 117, 98, 108, 105, 99, 45,
607            107, 101, 121,
608        ];
609
610        let a = <GetInfoResponse as CBORResponse>::try_from(raw_apdu.as_slice())
611            .expect("Falied to decode apdu");
612
613        // Assert the content
614        // info!(?a);
615
616        assert!(a.versions.len() == 3);
617        assert!(a.versions.contains("U2F_V2"));
618        assert!(a.versions.contains("FIDO_2_0"));
619        assert!(a.versions.contains("FIDO_2_1_PRE"));
620
621        assert!(a.extensions == Some(vec!["credProtect".to_string(), "hmac-secret".to_string()]));
622        assert_eq!(
623            a.aaguid,
624            Some(uuid!("2fc0579f-8113-47ea-b116-bb5a8db9202a"))
625        );
626
627        let m = a.options.as_ref().unwrap();
628        assert!(m.len() == 5);
629        assert!(m.get("clientPin") == Some(&true));
630        assert!(m.get("credentialMgmtPreview") == Some(&true));
631        assert!(m.get("plat") == Some(&false));
632        assert!(m.get("rk") == Some(&true));
633        assert!(m.get("up") == Some(&true));
634
635        assert!(a.max_msg_size == Some(1200));
636        assert!(a.max_cred_count_in_list == Some(8));
637        assert!(a.max_cred_id_len == Some(128));
638
639        assert!(a.transports == Some(vec!["nfc".to_string(), "usb".to_string()]));
640    }
641
642    #[test]
643    fn token2_nfc() {
644        let _ = tracing_subscriber::fmt().try_init();
645
646        let raw_apdu = vec![
647            178, 1, 132, 102, 85, 50, 70, 95, 86, 50, 104, 70, 73, 68, 79, 95, 50, 95, 48, 104, 70,
648            73, 68, 79, 95, 50, 95, 49, 108, 70, 73, 68, 79, 95, 50, 95, 49, 95, 80, 82, 69, 2,
649            133, 104, 99, 114, 101, 100, 66, 108, 111, 98, 107, 99, 114, 101, 100, 80, 114, 111,
650            116, 101, 99, 116, 107, 104, 109, 97, 99, 45, 115, 101, 99, 114, 101, 116, 108, 108,
651            97, 114, 103, 101, 66, 108, 111, 98, 75, 101, 121, 108, 109, 105, 110, 80, 105, 110,
652            76, 101, 110, 103, 116, 104, 3, 80, 171, 50, 240, 198, 34, 57, 175, 187, 196, 112, 210,
653            239, 78, 37, 77, 183, 4, 172, 98, 114, 107, 245, 98, 117, 112, 245, 100, 112, 108, 97,
654            116, 244, 104, 97, 108, 119, 97, 121, 115, 85, 118, 244, 104, 99, 114, 101, 100, 77,
655            103, 109, 116, 245, 105, 97, 117, 116, 104, 110, 114, 67, 102, 103, 245, 105, 99, 108,
656            105, 101, 110, 116, 80, 105, 110, 245, 106, 108, 97, 114, 103, 101, 66, 108, 111, 98,
657            115, 245, 110, 112, 105, 110, 85, 118, 65, 117, 116, 104, 84, 111, 107, 101, 110, 245,
658            111, 115, 101, 116, 77, 105, 110, 80, 73, 78, 76, 101, 110, 103, 116, 104, 245, 112,
659            109, 97, 107, 101, 67, 114, 101, 100, 85, 118, 78, 111, 116, 82, 113, 100, 245, 117,
660            99, 114, 101, 100, 101, 110, 116, 105, 97, 108, 77, 103, 109, 116, 80, 114, 101, 118,
661            105, 101, 119, 245, 5, 25, 6, 0, 6, 130, 2, 1, 7, 8, 8, 24, 96, 9, 130, 99, 117, 115,
662            98, 99, 110, 102, 99, 10, 129, 162, 99, 97, 108, 103, 38, 100, 116, 121, 112, 101, 106,
663            112, 117, 98, 108, 105, 99, 45, 107, 101, 121, 11, 25, 8, 0, 12, 244, 13, 4, 14, 25, 1,
664            0, 15, 24, 32, 16, 6, 19, 161, 100, 70, 73, 68, 79, 1, 20, 24, 50,
665        ];
666        let a = <GetInfoResponse as CBORResponse>::try_from(raw_apdu.as_slice())
667            .expect("Falied to decode apdu");
668
669        // info!(?a);
670        assert_eq!(a.versions.len(), 4);
671        assert!(a.versions.contains("U2F_V2"));
672        assert!(a.versions.contains("FIDO_2_0"));
673        assert!(a.versions.contains("FIDO_2_1_PRE"));
674        assert!(a.versions.contains("FIDO_2_1"));
675
676        assert_eq!(
677            a.extensions,
678            Some(vec![
679                "credBlob".to_string(),
680                "credProtect".to_string(),
681                "hmac-secret".to_string(),
682                "largeBlobKey".to_string(),
683                "minPinLength".to_string()
684            ])
685        );
686
687        assert_eq!(
688            a.aaguid,
689            Some(uuid!("ab32f0c6-2239-afbb-c470-d2ef4e254db7"))
690        );
691
692        assert_eq!(a.get_option("alwaysUv"), Some(false));
693        assert_eq!(a.get_option("authnrCfg"), Some(true));
694        assert!(a.supports_config());
695        assert_eq!(a.get_option("clientPin"), Some(true));
696        assert_eq!(a.get_option("credMgmt"), Some(true));
697        assert_eq!(a.get_option("credentialMgmtPreview"), Some(true));
698        assert_eq!(a.get_option("largeBlobs"), Some(true));
699        assert_eq!(a.get_option("makeCredUvNotRqd"), Some(true));
700        assert!(a.make_cred_uv_not_required());
701        assert_eq!(a.get_option("pinUvAuthToken"), Some(true));
702        assert_eq!(a.get_option("plat"), Some(false));
703        assert_eq!(a.get_option("rk"), Some(true));
704        assert_eq!(a.get_option("setMinPINLength"), Some(true));
705        assert_eq!(a.get_option("up"), Some(true));
706
707        assert!(a.ctap21_biometrics().is_none());
708        assert!(a.ctap21pre_biometrics().is_none());
709        assert!(!a.supports_enterprise_attestation());
710        assert!(!a.user_verification_configured());
711
712        assert_eq!(a.max_msg_size, Some(1536));
713        assert_eq!(a.pin_protocols, Some(vec![2, 1]));
714        assert_eq!(a.max_cred_count_in_list, Some(8));
715        assert_eq!(a.max_cred_id_len, Some(96));
716        assert_eq!(
717            a.transports,
718            Some(vec!["usb".to_string(), "nfc".to_string()])
719        );
720        assert_eq!(
721            a.get_transports(),
722            Some(vec![
723                AuthenticatorTransport::Usb,
724                AuthenticatorTransport::Nfc
725            ])
726        );
727
728        assert_eq!(a.max_serialized_large_blob_array, Some(2048));
729        assert!(!a.force_pin_change);
730        assert_eq!(a.min_pin_length, Some(4));
731        assert_eq!(a.get_min_pin_length(), 4);
732        assert_eq!(a.firmware_version, Some(0x100));
733        assert_eq!(a.max_cred_blob_length, Some(32));
734        assert_eq!(a.max_rpids_for_set_min_pin_length, Some(6));
735        assert!(a.preferred_platform_uv_attempts.is_none());
736        assert!(a.uv_modality.is_none());
737        assert_eq!(
738            a.certifications,
739            Some(BTreeMap::from([("FIDO".to_string(), 1)]))
740        );
741        assert_eq!(a.remaining_discoverable_credentials, Some(50));
742        assert!(a.vendor_prototype_config_commands.is_none());
743    }
744
745    #[test]
746    fn get_info_request() {
747        let req = GetInfoRequest {};
748        let short = vec![0x80, 0x10, 0, 0, 1, 0x4, 0];
749        let ext = vec![0x80, 0x10, 0, 0, 0, 0, 1, 0x4, 0, 0];
750
751        let a = to_short_apdus(&req.cbor().unwrap());
752        assert_eq!(1, a.len());
753        assert_eq!(short, a[0].to_bytes(&ISO7816LengthForm::ShortOnly).unwrap());
754        assert_eq!(short, a[0].to_bytes(&ISO7816LengthForm::Extended).unwrap());
755
756        assert_eq!(
757            ext,
758            to_extended_apdu(req.cbor().unwrap())
759                .to_bytes(&ISO7816LengthForm::Extended)
760                .unwrap()
761        );
762        assert_eq!(
763            ext,
764            to_extended_apdu(req.cbor().unwrap())
765                .to_bytes(&ISO7816LengthForm::ExtendedOnly)
766                .unwrap()
767        );
768        assert!(to_extended_apdu(req.cbor().unwrap())
769            .to_bytes(&ISO7816LengthForm::ShortOnly)
770            .is_err());
771    }
772}