webauthn_authenticator_rs/ctap2/commands/
make_credential.rs

1use base64urlsafedata::Base64UrlSafeData;
2use serde::{Deserialize, Serialize};
3use serde_cbor_2::{value::to_value, Value};
4use std::collections::BTreeMap;
5use webauthn_rs_proto::{PubKeyCredParams, PublicKeyCredentialDescriptor, RelyingParty, User};
6
7use crate::ctap2::commands::{value_to_map, value_to_vec_u8};
8
9use super::{value_to_bool, value_to_string, CBORCommand};
10
11/// `authenticatorMakeCredential` request type.
12///
13/// Reference: <https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#authenticatorMakeCredential>
14#[derive(Deserialize, Serialize, Debug, Clone)]
15#[serde(into = "BTreeMap<u32, Value>", try_from = "BTreeMap<u32, Value>")]
16pub struct MakeCredentialRequest {
17    /// Hash of the ClientData binding specified by the host.
18    pub client_data_hash: Vec<u8>,
19    /// Describes the relying party which the new credential will be associated
20    /// with.
21    pub rp: RelyingParty,
22    /// Describes the user account to which the new credential will be
23    /// associated with by the [RelyingParty].
24    pub user: User,
25    /// List of supported algorithms for credential generation.
26    pub pub_key_cred_params: Vec<PubKeyCredParams>,
27    /// List of existing credentials which the [RelyingParty] has for this
28    /// [User]. This prevents re-enrollment of the same authenticator.
29    pub exclude_list: Vec<PublicKeyCredentialDescriptor>,
30    // TODO: extensions
31    /// Parameters to influence operation.
32    pub options: Option<BTreeMap<String, bool>>,
33    /// Result of calling `authenticate(pin_uv_auth_token, client_data_hash)`.
34    pub pin_uv_auth_param: Option<Vec<u8>>,
35    /// PIN/UV protocol version chosen by the platform.
36    pub pin_uv_auth_proto: Option<u32>,
37    /// Enterprise attestation support.  **Not yet implemented**.
38    pub enterprise_attest: Option<u32>,
39}
40
41impl CBORCommand for MakeCredentialRequest {
42    const CMD: u8 = 0x01;
43    type Response = MakeCredentialResponse;
44}
45
46/// `authenticatorMakeCredential` response type.
47///
48/// Reference: <https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#authenticatormakecredential-response-structure>
49///
50/// ## Implementation notes
51///
52/// This needs to be (de)serialisable to/from both `Map<u32, Value>` **and**
53/// `Map<String, Value>`:
54///
55/// * The authenticator itself uses a map with `u32` keys. This is needed to get
56///   the value from from the authenticator, and to re-serialise values for
57///   caBLE (via `AuthenticatorBackendWithRequests`)
58///
59/// * `AuthenticatorAttestationResponseRaw` uses a map with `String` keys, which
60///   need the same names as `AttestationObjectInner`.
61#[derive(Deserialize, Serialize, Debug, Clone, Default, PartialEq, Eq)]
62#[serde(rename_all = "camelCase")]
63pub struct MakeCredentialResponse {
64    /// The attestation statement format identifier.
65    pub fmt: Option<String>,
66    /// The authenticator data object.
67    pub auth_data: Option<Value>,
68    /// The attestation statement.
69    pub att_stmt: Option<Value>,
70    /// Indicates whether an enterprise attestation was returned for this
71    /// credential.
72    pub epp_att: Option<bool>,
73    /// Contains the `largeBlobKey` for the credential, if requested with the
74    /// `largeBlobKey` extension.
75    ///
76    /// **Not yet supported.**
77    pub large_blob_key: Option<Value>,
78    // TODO: extensions
79}
80
81impl From<MakeCredentialRequest> for BTreeMap<u32, Value> {
82    fn from(value: MakeCredentialRequest) -> Self {
83        let MakeCredentialRequest {
84            client_data_hash,
85            rp,
86            user,
87            pub_key_cred_params,
88            exclude_list,
89            options,
90            pin_uv_auth_param,
91            pin_uv_auth_proto,
92            enterprise_attest: _,
93        } = value;
94
95        let mut keys = BTreeMap::new();
96
97        keys.insert(0x01, Value::Bytes(client_data_hash));
98
99        if let Ok(rp_value) = to_value(rp) {
100            keys.insert(0x2, rp_value);
101        }
102
103        // Because of how webauthn-rs is made, we build this in a way that optimises for text, not
104        // to ctap.
105        let User {
106            id,
107            name,
108            display_name,
109        } = user;
110
111        let mut user_map = BTreeMap::new();
112        // info!("{:?}", id);
113        user_map.insert(Value::Text("id".to_string()), Value::Bytes(id.into()));
114        user_map.insert(Value::Text("name".to_string()), Value::Text(name));
115        user_map.insert(
116            Value::Text("displayName".to_string()),
117            Value::Text(display_name),
118        );
119
120        let user_value = Value::Map(user_map);
121        // info!("{:?}", user_value);
122        keys.insert(0x3, user_value);
123
124        if let Ok(ps) = to_value(pub_key_cred_params) {
125            keys.insert(0x4, ps);
126        }
127
128        if !exclude_list.is_empty() {
129            keys.insert(
130                0x05,
131                Value::Array(
132                    exclude_list
133                        .iter()
134                        .map(|a| {
135                            let mut m = BTreeMap::from([
136                                (
137                                    Value::Text("type".to_string()),
138                                    Value::Text(a.type_.to_owned()),
139                                ),
140                                (Value::Text("id".to_string()), Value::Bytes(a.id.to_vec())),
141                            ]);
142
143                            if let Some(transports) = &a.transports {
144                                let transports: Vec<Value> = transports
145                                    .iter()
146                                    .map(|t| Value::Text(t.to_string()))
147                                    .collect();
148
149                                if !transports.is_empty() {
150                                    m.insert(
151                                        Value::Text("transports".to_string()),
152                                        Value::Array(transports),
153                                    );
154                                }
155                            }
156
157                            Value::Map(m)
158                        })
159                        .collect(),
160                ),
161            );
162        }
163
164        if let Some(o) = options {
165            let mut options_map = BTreeMap::new();
166            for (option, value) in &o {
167                options_map.insert(Value::Text(option.to_string()), Value::Bool(*value));
168            }
169
170            let options_value = Value::Map(options_map);
171            keys.insert(0x7, options_value);
172        }
173
174        if let Some(p) = pin_uv_auth_param {
175            keys.insert(0x08, Value::Bytes(p));
176        }
177
178        if let Some(p) = pin_uv_auth_proto {
179            keys.insert(0x09, Value::Integer(p.into()));
180        }
181
182        keys
183    }
184}
185
186impl TryFrom<BTreeMap<u32, Value>> for MakeCredentialRequest {
187    type Error = &'static str;
188    fn try_from(mut raw: BTreeMap<u32, Value>) -> Result<Self, Self::Error> {
189        trace!("raw: {:?}", raw);
190        Ok(Self {
191            client_data_hash: raw
192                .remove(&0x01)
193                .and_then(|v| value_to_vec_u8(v, "0x01"))
194                .ok_or("parsing clientDataHash")?,
195            rp: raw
196                .remove(&0x02)
197                .and_then(|v| serde_cbor_2::value::from_value(v).ok())
198                .ok_or("parsing rp")?,
199            user: raw
200                .remove(&0x03)
201                .and_then(|v| if let Value::Map(v) = v { Some(v) } else { None })
202                .and_then(|mut v| {
203                    Some(User {
204                        id: Base64UrlSafeData::from(value_to_vec_u8(
205                            v.remove(&Value::Text("id".to_string()))?,
206                            "id",
207                        )?),
208                        name: value_to_string(v.remove(&Value::Text("name".to_string()))?, "name")?,
209                        display_name: value_to_string(
210                            v.remove(&Value::Text("displayName".to_string()))?,
211                            "displayName",
212                        )?,
213                    })
214                })
215                .ok_or("parsing user")?,
216            pub_key_cred_params: raw
217                .remove(&0x04)
218                .and_then(|v| serde_cbor_2::value::from_value(v).ok())
219                .ok_or("parsing pubKeyCredParams")?,
220            exclude_list: raw
221                .remove(&0x05)
222                .and_then(|v| serde_cbor_2::value::from_value(v).ok())
223                .unwrap_or_default(),
224            options: raw
225                .remove(&0x07)
226                .and_then(|v| value_to_map(v, "0x07"))
227                .map(|v| {
228                    v.into_iter()
229                        .filter_map(|(key, value)| match (key, value) {
230                            (Value::Text(key), Value::Bool(value)) => Some((key, value)),
231                            _ => None,
232                        })
233                        .collect()
234                }),
235            // TODO
236            pin_uv_auth_param: None,
237            pin_uv_auth_proto: None,
238            enterprise_attest: None,
239        })
240    }
241}
242
243impl From<MakeCredentialResponse> for BTreeMap<u32, Value> {
244    fn from(value: MakeCredentialResponse) -> Self {
245        let MakeCredentialResponse {
246            fmt,
247            auth_data,
248            att_stmt,
249            epp_att,
250            large_blob_key,
251        } = value;
252
253        let mut keys = BTreeMap::new();
254        if let Some(fmt) = fmt {
255            keys.insert(0x01, Value::Text(fmt));
256        }
257        if let Some(auth_data) = auth_data {
258            keys.insert(0x02, auth_data);
259        }
260        if let Some(att_stmt) = att_stmt {
261            keys.insert(0x03, att_stmt);
262        }
263        if let Some(epp_att) = epp_att {
264            keys.insert(0x04, epp_att.into());
265        }
266        if let Some(large_blob_key) = large_blob_key {
267            keys.insert(0x05, large_blob_key);
268        }
269        keys
270    }
271}
272
273impl TryFrom<BTreeMap<u32, Value>> for MakeCredentialResponse {
274    type Error = &'static str;
275    fn try_from(mut raw: BTreeMap<u32, Value>) -> Result<Self, Self::Error> {
276        trace!(?raw);
277        Ok(Self {
278            fmt: raw.remove(&0x01).and_then(|v| value_to_string(v, "0x01")),
279            auth_data: raw.remove(&0x02),
280            att_stmt: raw.remove(&0x03),
281            epp_att: raw.remove(&0x04).and_then(|v| value_to_bool(&v, "0x04")),
282            large_blob_key: raw.remove(&0x05),
283        })
284    }
285}
286
287crate::deserialize_cbor!(MakeCredentialRequest);
288crate::deserialize_cbor!(MakeCredentialResponse);
289
290#[cfg(test)]
291mod test {
292    use crate::ctap2::CBORResponse;
293
294    use super::*;
295    use base64urlsafedata::Base64UrlSafeData;
296    use serde_cbor_2::{from_slice, to_vec, Value};
297    use webauthn_rs_proto::{PubKeyCredParams, RelyingParty, User};
298
299    #[test]
300    fn sample_make_credential_request() {
301        let _ = tracing_subscriber::fmt::try_init();
302        // https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#example-1a030b94
303        /*
304        {
305        Integer(1): Bytes([104, 113, 52, 150, 130, 34, 236, 23, 32, 46, 66, 80, 95, 142, 210, 177, 106, 226, 47, 22, 187, 5, 184, 140, 37, 219, 158, 96, 38, 69, 241, 65]),
306        Integer(2): Map({Text("id"): Text("test.ctap"), Text("name"): Text("test.ctap")}),
307        Integer(3): Map({Text("id"): Bytes([43, 102, 137, 187, 24, 244, 22, 159, 6, 159, 188, 223, 80, 203, 110, 163, 198, 10, 134, 27, 154, 123, 99, 148, 105, 131, 224, 181, 119, 183, 140, 112]),
308            Text("name"): Text("testctap@ctap.com"), Text("displayName"): Text("Test Ctap")}),
309        Integer(4): Array([
310            Map({Text("alg"): Integer(-7), Text("type"): Text("public-key")}),
311            Map({Text("alg"): Integer(-257), Text("type"): Text("public-key")}),
312            Map({Text("alg"): Integer(-37), Text("type"): Text("public-key")})]),
313        Integer(6): Map({Text("hmac-secret"): Bool(true)}),
314        Integer(7): Map({Text("rk"): Bool(true)}),
315        Integer(8): Bytes([252, 67, 170, 164, 17, 217, 72, 204, 108, 55, 6, 139, 141, 161, 213, 8]),
316        Integer(9): Integer(1),
317        }
318         */
319        let expected = vec![
320            1, //
321            // Extensions not yet supported
322            // 168,
323            167, //
324            // ClientDataHash
325            1, 88, 32, 104, 113, 52, 150, 130, 34, 236, 23, 32, 46, 66, 80, 95, 142, 210, 177, 106,
326            226, 47, 22, 187, 5, 184, 140, 37, 219, 158, 96, 38, 69, 241, 65, //
327            // RelyingParty
328            2, 162, 98, 105, 100, 105, 116, 101, 115, 116, 46, 99, 116, 97, 112, 100, 110, 97, 109,
329            101, 105, 116, 101, 115, 116, 46, 99, 116, 97, 112, //
330            // User
331            3, 163, 98, 105, 100, 88, 32, 43, 102, 137, 187, 24, 244, 22, 159, 6, 159, 188, 223, 80,
332            203, 110, 163, 198, 10, 134, 27, 154, 123, 99, 148, 105, 131, 224, 181, 119, 183, 140,
333            112, 100, 110, 97, 109, 101, 113, 116, 101, 115, 116, 99, 116, 97, 112, 64, 99, 116,
334            97, 112, 46, 99, 111, 109, 107, 100, 105, 115, 112, 108, 97, 121, 78, 97, 109, 101,
335            105, 84, 101, 115, 116, 32, 67, 116, 97, 112, //
336            // PubKeyCredParams
337            4, 131, 162, 99, 97, 108, 103, 38, 100, 116, 121, 112, 101, 106, 112, 117, 98, 108, 105,
338            99, 45, 107, 101, 121, 162, 99, 97, 108, 103, 57, 1, 0, 100, 116, 121, 112, 101, 106,
339            112, 117, 98, 108, 105, 99, 45, 107, 101, 121, 162, 99, 97, 108, 103, 56, 36, 100, 116,
340            121, 112, 101, 106, 112, 117, 98, 108, 105, 99, 45, 107, 101, 121,
341            // Extensions not yet supported
342            // 6, 161, 107, 104, 109, 97, 99, 45, 115, 101, 99, 114, 101, 116, 245,
343            // Options
344            7, 161, 98, 114, 107, 245, //
345            // pin_uv_auth_param
346            8, 80, 252, 67, 170, 164, 17, 217, 72, 204, 108, 55, 6, 139, 141, 161, 213, 8,
347            // pin_uv_auth_proto
348            9, 1,
349        ];
350
351        let req = MakeCredentialRequest {
352            client_data_hash: vec![
353                104, 113, 52, 150, 130, 34, 236, 23, 32, 46, 66, 80, 95, 142, 210, 177, 106, 226,
354                47, 22, 187, 5, 184, 140, 37, 219, 158, 96, 38, 69, 241, 65,
355            ],
356            rp: RelyingParty {
357                name: "test.ctap".to_owned(),
358                id: "test.ctap".to_owned(),
359            },
360            user: User {
361                id: Base64UrlSafeData::from(vec![
362                    43, 102, 137, 187, 24, 244, 22, 159, 6, 159, 188, 223, 80, 203, 110, 163, 198,
363                    10, 134, 27, 154, 123, 99, 148, 105, 131, 224, 181, 119, 183, 140, 112,
364                ]),
365                name: "testctap@ctap.com".to_owned(),
366                display_name: "Test Ctap".to_owned(),
367            },
368            pub_key_cred_params: vec![
369                PubKeyCredParams {
370                    type_: "public-key".to_owned(),
371                    alg: -7,
372                },
373                PubKeyCredParams {
374                    type_: "public-key".to_owned(),
375                    alg: -257,
376                },
377                PubKeyCredParams {
378                    type_: "public-key".to_owned(),
379                    alg: -37,
380                },
381            ],
382            exclude_list: vec![],
383            options: Some(BTreeMap::from([("rk".to_owned(), true)])),
384            pin_uv_auth_param: Some(vec![
385                252, 67, 170, 164, 17, 217, 72, 204, 108, 55, 6, 139, 141, 161, 213, 8,
386            ]),
387            pin_uv_auth_proto: Some(1),
388            enterprise_attest: None,
389        };
390
391        assert_eq!(expected, req.cbor().expect("encode error"));
392
393        let decoded = <MakeCredentialRequest as CBORResponse>::try_from(&expected[1..]).unwrap();
394        trace!(?decoded);
395
396        let r = vec![
397            163, 1, 102, 112, 97, 99, 107, 101, 100, 2, 89, 0, 162, 0, 33, 245, 252, 11, 133, 205,
398            34, 230, 6, 35, 188, 215, 209, 202, 72, 148, 137, 9, 36, 155, 71, 118, 235, 81, 81, 84,
399            229, 123, 102, 174, 18, 197, 0, 0, 0, 85, 248, 160, 17, 243, 140, 10, 77, 21, 128, 6,
400            23, 17, 31, 158, 220, 125, 0, 16, 244, 213, 123, 35, 221, 12, 183, 133, 104, 12, 218,
401            167, 247, 228, 79, 96, 165, 1, 2, 3, 38, 32, 1, 33, 88, 32, 223, 1, 125, 11, 40, 103,
402            149, 190, 161, 83, 209, 102, 160, 161, 91, 79, 107, 103, 163, 175, 74, 16, 30, 16, 232,
403            73, 111, 61, 211, 197, 209, 169, 34, 88, 32, 148, 178, 37, 81, 230, 50, 93, 119, 51,
404            196, 27, 178, 245, 166, 66, 173, 238, 65, 124, 151, 224, 144, 97, 151, 181, 176, 205,
405            139, 141, 108, 107, 167, 161, 107, 104, 109, 97, 99, 45, 115, 101, 99, 114, 101, 116,
406            245, 3, 163, 99, 97, 108, 103, 38, 99, 115, 105, 103, 88, 71, 48, 69, 2, 32, 124, 202,
407            197, 122, 30, 67, 223, 36, 176, 132, 126, 235, 241, 25, 210, 141, 205, 197, 4, 143,
408            125, 205, 142, 221, 121, 231, 151, 33, 196, 27, 207, 45, 2, 33, 0, 216, 158, 199, 91,
409            146, 206, 143, 249, 228, 111, 231, 248, 200, 121, 149, 105, 74, 99, 229, 183, 138, 184,
410            92, 71, 185, 218, 28, 88, 10, 142, 200, 58, 99, 120, 53, 99, 129, 89, 1, 151, 48, 130,
411            1, 147, 48, 130, 1, 56, 160, 3, 2, 1, 2, 2, 9, 0, 133, 155, 114, 108, 178, 75, 76, 41,
412            48, 10, 6, 8, 42, 134, 72, 206, 61, 4, 3, 2, 48, 71, 49, 11, 48, 9, 6, 3, 85, 4, 6, 19,
413            2, 85, 83, 49, 20, 48, 18, 6, 3, 85, 4, 10, 12, 11, 89, 117, 98, 105, 99, 111, 32, 84,
414            101, 115, 116, 49, 34, 48, 32, 6, 3, 85, 4, 11, 12, 25, 65, 117, 116, 104, 101, 110,
415            116, 105, 99, 97, 116, 111, 114, 32, 65, 116, 116, 101, 115, 116, 97, 116, 105, 111,
416            110, 48, 30, 23, 13, 49, 54, 49, 50, 48, 52, 49, 49, 53, 53, 48, 48, 90, 23, 13, 50,
417            54, 49, 50, 48, 50, 49, 49, 53, 53, 48, 48, 90, 48, 71, 49, 11, 48, 9, 6, 3, 85, 4, 6,
418            19, 2, 85, 83, 49, 20, 48, 18, 6, 3, 85, 4, 10, 12, 11, 89, 117, 98, 105, 99, 111, 32,
419            84, 101, 115, 116, 49, 34, 48, 32, 6, 3, 85, 4, 11, 12, 25, 65, 117, 116, 104, 101,
420            110, 116, 105, 99, 97, 116, 111, 114, 32, 65, 116, 116, 101, 115, 116, 97, 116, 105,
421            111, 110, 48, 89, 48, 19, 6, 7, 42, 134, 72, 206, 61, 2, 1, 6, 8, 42, 134, 72, 206, 61,
422            3, 1, 7, 3, 66, 0, 4, 173, 17, 235, 14, 136, 82, 229, 58, 213, 223, 237, 134, 180, 30,
423            97, 52, 161, 142, 196, 225, 175, 143, 34, 26, 60, 125, 110, 99, 108, 128, 234, 19, 195,
424            213, 4, 255, 46, 118, 33, 27, 180, 69, 37, 177, 150, 196, 76, 180, 132, 153, 121, 207,
425            111, 137, 110, 205, 43, 184, 96, 222, 27, 244, 55, 107, 163, 13, 48, 11, 48, 9, 6, 3,
426            85, 29, 19, 4, 2, 48, 0, 48, 10, 6, 8, 42, 134, 72, 206, 61, 4, 3, 2, 3, 73, 0, 48, 70,
427            2, 33, 0, 233, 163, 159, 27, 3, 25, 117, 37, 247, 55, 62, 16, 206, 119, 231, 128, 33,
428            115, 27, 148, 208, 192, 63, 63, 218, 31, 210, 45, 179, 208, 48, 231, 2, 33, 0, 196,
429            250, 236, 52, 69, 168, 32, 207, 67, 18, 156, 219, 0, 170, 190, 253, 154, 226, 216, 116,
430            249, 197, 211, 67, 203, 47, 17, 61, 162, 55, 35, 243,
431        ];
432        let a = <MakeCredentialResponse as CBORResponse>::try_from(r.as_slice())
433            .expect("Failed to decode message");
434        info!("r = {}", hex::encode(r));
435
436        assert_eq!(
437            a,
438            MakeCredentialResponse {
439                fmt: Some("packed".to_owned()),
440                auth_data: Some(Value::Bytes(vec![
441                    0, 33, 245, 252, 11, 133, 205, 34, 230, 6, 35, 188, 215, 209, 202, 72, 148,
442                    137, 9, 36, 155, 71, 118, 235, 81, 81, 84, 229, 123, 102, 174, 18, 197, 0, 0,
443                    0, 85, 248, 160, 17, 243, 140, 10, 77, 21, 128, 6, 23, 17, 31, 158, 220, 125,
444                    0, 16, 244, 213, 123, 35, 221, 12, 183, 133, 104, 12, 218, 167, 247, 228, 79,
445                    96, 165, 1, 2, 3, 38, 32, 1, 33, 88, 32, 223, 1, 125, 11, 40, 103, 149, 190,
446                    161, 83, 209, 102, 160, 161, 91, 79, 107, 103, 163, 175, 74, 16, 30, 16, 232,
447                    73, 111, 61, 211, 197, 209, 169, 34, 88, 32, 148, 178, 37, 81, 230, 50, 93,
448                    119, 51, 196, 27, 178, 245, 166, 66, 173, 238, 65, 124, 151, 224, 144, 97, 151,
449                    181, 176, 205, 139, 141, 108, 107, 167, 161, 107, 104, 109, 97, 99, 45, 115,
450                    101, 99, 114, 101, 116, 245
451                ])),
452                att_stmt: Some(Value::Map(BTreeMap::from([
453                    (Value::Text("alg".to_owned()), Value::Integer(-7)),
454                    (
455                        Value::Text("sig".to_owned()),
456                        Value::Bytes(vec![
457                            48, 69, 2, 32, 124, 202, 197, 122, 30, 67, 223, 36, 176, 132, 126, 235,
458                            241, 25, 210, 141, 205, 197, 4, 143, 125, 205, 142, 221, 121, 231, 151,
459                            33, 196, 27, 207, 45, 2, 33, 0, 216, 158, 199, 91, 146, 206, 143, 249,
460                            228, 111, 231, 248, 200, 121, 149, 105, 74, 99, 229, 183, 138, 184, 92,
461                            71, 185, 218, 28, 88, 10, 142, 200, 58
462                        ])
463                    ),
464                    (
465                        Value::Text("x5c".to_owned()),
466                        Value::Array(vec![Value::Bytes(vec![
467                            48, 130, 1, 147, 48, 130, 1, 56, 160, 3, 2, 1, 2, 2, 9, 0, 133, 155,
468                            114, 108, 178, 75, 76, 41, 48, 10, 6, 8, 42, 134, 72, 206, 61, 4, 3, 2,
469                            48, 71, 49, 11, 48, 9, 6, 3, 85, 4, 6, 19, 2, 85, 83, 49, 20, 48, 18,
470                            6, 3, 85, 4, 10, 12, 11, 89, 117, 98, 105, 99, 111, 32, 84, 101, 115,
471                            116, 49, 34, 48, 32, 6, 3, 85, 4, 11, 12, 25, 65, 117, 116, 104, 101,
472                            110, 116, 105, 99, 97, 116, 111, 114, 32, 65, 116, 116, 101, 115, 116,
473                            97, 116, 105, 111, 110, 48, 30, 23, 13, 49, 54, 49, 50, 48, 52, 49, 49,
474                            53, 53, 48, 48, 90, 23, 13, 50, 54, 49, 50, 48, 50, 49, 49, 53, 53, 48,
475                            48, 90, 48, 71, 49, 11, 48, 9, 6, 3, 85, 4, 6, 19, 2, 85, 83, 49, 20,
476                            48, 18, 6, 3, 85, 4, 10, 12, 11, 89, 117, 98, 105, 99, 111, 32, 84,
477                            101, 115, 116, 49, 34, 48, 32, 6, 3, 85, 4, 11, 12, 25, 65, 117, 116,
478                            104, 101, 110, 116, 105, 99, 97, 116, 111, 114, 32, 65, 116, 116, 101,
479                            115, 116, 97, 116, 105, 111, 110, 48, 89, 48, 19, 6, 7, 42, 134, 72,
480                            206, 61, 2, 1, 6, 8, 42, 134, 72, 206, 61, 3, 1, 7, 3, 66, 0, 4, 173,
481                            17, 235, 14, 136, 82, 229, 58, 213, 223, 237, 134, 180, 30, 97, 52,
482                            161, 142, 196, 225, 175, 143, 34, 26, 60, 125, 110, 99, 108, 128, 234,
483                            19, 195, 213, 4, 255, 46, 118, 33, 27, 180, 69, 37, 177, 150, 196, 76,
484                            180, 132, 153, 121, 207, 111, 137, 110, 205, 43, 184, 96, 222, 27, 244,
485                            55, 107, 163, 13, 48, 11, 48, 9, 6, 3, 85, 29, 19, 4, 2, 48, 0, 48, 10,
486                            6, 8, 42, 134, 72, 206, 61, 4, 3, 2, 3, 73, 0, 48, 70, 2, 33, 0, 233,
487                            163, 159, 27, 3, 25, 117, 37, 247, 55, 62, 16, 206, 119, 231, 128, 33,
488                            115, 27, 148, 208, 192, 63, 63, 218, 31, 210, 45, 179, 208, 48, 231, 2,
489                            33, 0, 196, 250, 236, 52, 69, 168, 32, 207, 67, 18, 156, 219, 0, 170,
490                            190, 253, 154, 226, 216, 116, 249, 197, 211, 67, 203, 47, 17, 61, 162,
491                            55, 35, 243
492                        ])])
493                    )
494                ]))),
495                ..Default::default()
496            }
497        );
498    }
499
500    #[test]
501    fn make_credential() {
502        let _ = tracing_subscriber::fmt().try_init();
503
504        // clientDataHash 0x01
505        // rp 0x02
506        // user 0x03
507        // pubKeyCredParams 0x04
508        // excludeList 0x05
509        // extensions 0x06
510        // options 0x07
511        // pinUvAuthParam 0x08
512        // pinUvAuthProtocol 0x09
513        // enterpriseAttestation 0x0A
514
515        /*
516        Dec 28 18:41:01.160  INFO webauthn_authenticator_rs::nfc::apdu::tests: got APDU Value response: Ok(Map(
517
518        Integer(1): Bytes([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
519        Integer(1): Bytes([104, 113, 52, 150, 130, 34, 236, 23, 32, 46, 66, 80, 95, 142, 210, 177, 106, 226, 47, 22, 187, 5, 184, 140, 37, 219, 158, 96, 38, 69, 241, 65]),
520        Integer(2): Map({
521            Text("id"): Text("test"),
522            Text("name"): Text("test")
523        }),
524        Integer(2): Map({
525            Text("id"): Text("test.ctap"),
526            Text("name"): Text("test.ctap")
527        }),
528        Integer(3): Map({
529            Text("id"): Bytes([116, 101, 115, 116]),
530            Text("name"): Text("test"),
531            Text("displayName"): Text("test")
532        }),
533        Integer(3): Map({
534            Text("id"): Bytes([43, 102, 137, 187, 24, 244, 22, 159, 6, 159, 188, 223, 80, 203, 110, 163, 198, 10, 134, 27, 154, 123, 99, 148, 105, 131, 224, 181, 119, 183, 140, 112]),
535            Text("name"): Text("testctap@ctap.com"),
536            Text("displayName"): Text("Test Ctap")
537        }),
538        Integer(4): Array([
539            Map({Text("alg"): Integer(-7), Text("type"): Text("public-key")})
540        ])
541        Integer(4): Array([
542            Map({Text("alg"): Integer(-7), Text("type"): Text("public-key")}),
543            Map({Text("alg"): Integer(-257), Text("type"): Text("public-key")}),
544            Map({Text("alg"): Integer(-37), Text("type"): Text("public-key")})
545        ]),
546        // I think this is incorrect?
547        Integer(6): Map({Text("hmac-secret"): Bool(true)}),
548        // May need to set uv false?
549        Integer(7): Map({Text("rk"): Bool(true)}),
550        // Not needed
551        Integer(8): Bytes([252, 67, 170, 164, 17, 217, 72, 204, 108, 55, 6, 139, 141, 161, 213, 8]),
552        // Not needed
553        Integer(9): Integer(1)}))
554        */
555
556        // Response APDU has a prepended 0x01 (error code)
557        let bytes = vec![
558            168, 1, 88, 32, 104, 113, 52, 150, 130, 34, 236, 23, 32, 46, 66, 80, 95, 142, 210, 177,
559            106, 226, 47, 22, 187, 5, 184, 140, 37, 219, 158, 96, 38, 69, 241, 65, 2, 162, 98, 105,
560            100, 105, 116, 101, 115, 116, 46, 99, 116, 97, 112, 100, 110, 97, 109, 101, 105, 116,
561            101, 115, 116, 46, 99, 116, 97, 112, 3, 163, 98, 105, 100, 88, 32, 43, 102, 137, 187,
562            24, 244, 22, 159, 6, 159, 188, 223, 80, 203, 110, 163, 198, 10, 134, 27, 154, 123, 99,
563            148, 105, 131, 224, 181, 119, 183, 140, 112, 100, 110, 97, 109, 101, 113, 116, 101,
564            115, 116, 99, 116, 97, 112, 64, 99, 116, 97, 112, 46, 99, 111, 109, 107, 100, 105, 115,
565            112, 108, 97, 121, 78, 97, 109, 101, 105, 84, 101, 115, 116, 32, 67, 116, 97, 112, 4,
566            131, 162, 99, 97, 108, 103, 38, 100, 116, 121, 112, 101, 106, 112, 117, 98, 108, 105,
567            99, 45, 107, 101, 121, 162, 99, 97, 108, 103, 57, 1, 0, 100, 116, 121, 112, 101, 106,
568            112, 117, 98, 108, 105, 99, 45, 107, 101, 121, 162, 99, 97, 108, 103, 56, 36, 100, 116,
569            121, 112, 101, 106, 112, 117, 98, 108, 105, 99, 45, 107, 101, 121, 6, 161, 107, 104,
570            109, 97, 99, 45, 115, 101, 99, 114, 101, 116, 245, 7, 161, 98, 114, 107, 245, 8, 80,
571            252, 67, 170, 164, 17, 217, 72, 204, 108, 55, 6, 139, 141, 161, 213, 8, 9, 1,
572        ];
573
574        let v: Result<Value, _> = from_slice(bytes.as_slice());
575        info!("got APDU Value response: {:?}", v);
576
577        let mc = MakeCredentialRequest {
578            client_data_hash: vec![0; 32],
579            rp: RelyingParty {
580                name: "test".to_string(),
581                id: "test".to_string(),
582            },
583            user: User {
584                id: Base64UrlSafeData::from(b"test"),
585                name: "test".to_string(),
586                display_name: "test".to_string(),
587            },
588            pub_key_cred_params: vec![PubKeyCredParams {
589                type_: "public-key".to_string(),
590                alg: -7,
591            }],
592            options: None,
593            exclude_list: vec![],
594            pin_uv_auth_param: None,
595            pin_uv_auth_proto: None,
596            enterprise_attest: None,
597        };
598
599        let b = to_vec(&mc).unwrap();
600
601        let v2: Result<Value, _> = from_slice(b.as_slice());
602        info!("got APDU Value encoded: {:?}", v2);
603
604        // let pdu = mc.to_short_apdus();
605        // info!("got APDU: {:?}", pdu);
606        info!("got inner APDU: {}", hex::encode(b));
607    }
608}