webauthn_authenticator_rs/ctap2/commands/
client_pin.rs

1#[cfg(doc)]
2use crate::stubs::*;
3
4use bitflags::bitflags;
5use serde::{Deserialize, Serialize};
6use serde_cbor_2::Value;
7use webauthn_rs_core::proto::{COSEEC2Key, COSEKey, COSEKeyType, COSEKeyTypeId, ECDSACurve};
8use webauthn_rs_proto::COSEAlgorithm;
9
10use self::CBORCommand;
11use super::*;
12
13/// `authenticatorclientPin` request type.
14///
15/// See:
16///
17/// * [ClientPinSubCommand] for command types
18/// * `crate::ctap2::pin_uv` constructs this command
19///
20/// Reference: <https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#authnrClientPin-cmd-dfn>
21#[derive(Serialize, Debug, Clone, Default, PartialEq, Eq)]
22#[serde(into = "BTreeMap<u32, Value>")]
23pub struct ClientPinRequest {
24    /// PIN / UV protocol version chosen by the platform
25    pub pin_uv_protocol: Option<u32>,
26    /// Action being requested
27    pub sub_command: ClientPinSubCommand,
28    /// The platform-agreement key
29    pub key_agreement: Option<COSEKey>,
30    /// Output of calling "Authenticate" on some context specific to
31    /// [sub_command][Self::sub_command]
32    pub pin_uv_auth_param: Option<Vec<u8>>,
33    /// An encrypted PIN
34    pub new_pin_enc: Option<Vec<u8>>,
35    /// An encrypted proof-of-knowledge of a PIN
36    pub pin_hash_enc: Option<Vec<u8>>,
37    /// Permissions bitfield for
38    /// [GetPinUvAuthTokenUsingUvWithPermissions][ClientPinSubCommand::GetPinUvAuthTokenUsingUvWithPermissions]
39    /// and
40    /// [GetPinUvAuthTokenUsingPinWithPermissions][ClientPinSubCommand::GetPinUvAuthTokenUsingPinWithPermissions].
41    ///
42    /// Field is omitted if `0`.
43    pub permissions: Permissions,
44    /// The RP ID to assign as the permissions RP ID
45    pub rp_id: Option<String>,
46}
47
48/// [ClientPinRequest::sub_command] type code
49///
50/// Reference: <https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#authnrClientPin-cmd-dfn>
51#[derive(Debug, Clone, PartialEq, Eq, Default)]
52#[repr(u32)]
53pub enum ClientPinSubCommand {
54    #[default]
55    GetPinRetries = 0x01,
56    GetKeyAgreement = 0x02,
57    SetPin = 0x03,
58    ChangePin = 0x04,
59    GetPinToken = 0x05,
60    GetPinUvAuthTokenUsingUvWithPermissions = 0x06,
61    GetUvRetries = 0x07,
62    GetPinUvAuthTokenUsingPinWithPermissions = 0x09,
63}
64
65bitflags! {
66    /// Permissions bitfield for
67    /// [GetPinUvAuthTokenUsingUvWithPermissions][ClientPinSubCommand::GetPinUvAuthTokenUsingUvWithPermissions]
68    /// and
69    /// [GetPinUvAuthTokenUsingPinWithPermissions][ClientPinSubCommand::GetPinUvAuthTokenUsingPinWithPermissions].
70    ///
71    /// Reference: <https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#gettingPinUvAuthToken>
72    #[derive(Default)]
73    pub struct Permissions: u8 {
74        const MAKE_CREDENTIAL = 0x01;
75        const GET_ASSERTION = 0x02;
76        const CREDENTIAL_MANAGEMENT = 0x04;
77        const BIO_ENROLLMENT = 0x08;
78        const LARGE_BLOB_WRITE = 0x10;
79        const AUTHENTICATOR_CONFIGURATION = 0x20;
80    }
81}
82
83impl CBORCommand for ClientPinRequest {
84    const CMD: u8 = 0x06;
85    type Response = ClientPinResponse;
86}
87
88/// `authenticatorClientPin` response type.
89#[derive(Deserialize, Debug, Default, PartialEq, Eq)]
90#[serde(try_from = "BTreeMap<u32, Value>")]
91pub struct ClientPinResponse {
92    /// The result of the authenticator calling `getPublicKey`, which can be
93    /// used to encapsulate encrypted payloads between the authenticator and
94    /// platform.
95    pub key_agreement: Option<COSEKey>,
96    /// The `pinUvAuthToken`, encrypted with the shared secret.
97    pub pin_uv_auth_token: Option<Vec<u8>>,
98    /// Number of PIN attempts remaining until lock-out.
99    pub pin_retries: Option<u32>,
100    /// If present and `true`, the authenticator requires a power cycle before
101    /// any future pin operation.
102    ///
103    /// Only included in response to [ClientPinSubCommand::GetPinRetries].
104    pub power_cycle_state: Option<bool>,
105    /// Number of UV attempts remaining until lock-out.
106    pub uv_retries: Option<u32>,
107}
108
109impl From<ClientPinRequest> for BTreeMap<u32, Value> {
110    fn from(value: ClientPinRequest) -> Self {
111        let ClientPinRequest {
112            pin_uv_protocol,
113            sub_command,
114            key_agreement,
115            pin_uv_auth_param,
116            new_pin_enc,
117            pin_hash_enc,
118            permissions,
119            rp_id,
120        } = value;
121
122        let mut keys = BTreeMap::new();
123
124        if let Some(v) = pin_uv_protocol {
125            keys.insert(0x01, Value::Integer(v.into()));
126        }
127        keys.insert(0x02, Value::Integer((sub_command as u32).into()));
128        if let Some(v) = key_agreement {
129            if let COSEKeyType::EC_EC2(e) = v.key {
130                // This uses the special type of COSE key for PinUvToken
131                let m = BTreeMap::from([
132                    (Value::Integer(1), Value::Integer(2)),         // kty
133                    (Value::Integer(3), Value::Integer(-25)),       // alg
134                    (Value::Integer(-1), Value::Integer(1)),        // crv
135                    (Value::Integer(-2), Value::Bytes(e.x.into())), // x
136                    (Value::Integer(-3), Value::Bytes(e.y.into())), // y
137                ]);
138
139                keys.insert(0x03, Value::Map(m));
140            }
141        }
142        if let Some(v) = pin_uv_auth_param {
143            keys.insert(0x04, Value::Bytes(v));
144        }
145        if let Some(v) = new_pin_enc {
146            keys.insert(0x05, Value::Bytes(v));
147        }
148        if let Some(v) = pin_hash_enc {
149            keys.insert(0x06, Value::Bytes(v));
150        }
151        if !permissions.is_empty() {
152            keys.insert(0x09, Value::Integer(permissions.bits().into()));
153        }
154        if let Some(v) = rp_id {
155            keys.insert(0x0a, Value::Text(v));
156        }
157
158        keys
159    }
160}
161
162impl TryFrom<BTreeMap<u32, Value>> for ClientPinResponse {
163    type Error = &'static str;
164    fn try_from(mut raw: BTreeMap<u32, Value>) -> Result<Self, Self::Error> {
165        trace!(?raw);
166        Ok(Self {
167            key_agreement: raw
168                .remove(&0x01)
169                .and_then(|v| if let Value::Map(m) = v { Some(m) } else { None })
170                .and_then(|mut m| {
171                    // Reference: https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#pinProto1
172                    //
173                    // Result of the authenticator calling `getPublicKey()`. This is a `COSE_Key` with some specific,
174                    // but partially incorrect values, so we need to deserialise it specially:
175                    //
176                    //  1 (kty) = 2 (EC2)
177                    //  3 (alg) = -25 (not the algorithm actually used)
178                    // -1 (crv) = 1 (P-256)
179                    if m.remove(&Value::Integer(1))
180                        != Some(Value::Integer(COSEKeyTypeId::EC_EC2 as i128))
181                        || m.remove(&Value::Integer(3)) != Some(Value::Integer(-25))
182                        || m.remove(&Value::Integer(-1))
183                            != Some(Value::Integer(ECDSACurve::SECP256R1 as i128))
184                    {
185                        return None;
186                    }
187
188                    // -2 (x) = 32 byte big-endian encoding of the x-coordinate of xB
189                    // -3 (y) = 32 byte big-endian encoding of the y-coordinate of xB
190                    let x = m
191                        .remove(&Value::Integer(-2))
192                        .and_then(|v| value_to_vec_u8(v, "-2"))?;
193                    let y = m
194                        .remove(&Value::Integer(-3))
195                        .and_then(|v| value_to_vec_u8(v, "-3"))?;
196                    if x.len() != 32 || y.len() != 32 {
197                        return None;
198                    }
199
200                    Some(COSEKey {
201                        type_: COSEAlgorithm::PinUvProtocol,
202                        key: COSEKeyType::EC_EC2(COSEEC2Key {
203                            curve: ECDSACurve::SECP256R1,
204                            x: x.to_vec().into(),
205                            y: y.to_vec().into(),
206                        }),
207                    })
208                }),
209            pin_uv_auth_token: raw.remove(&0x02).and_then(|v| value_to_vec_u8(v, "0x02")),
210            pin_retries: raw.remove(&0x03).and_then(|v| value_to_u32(&v, "0x03")),
211            power_cycle_state: raw.remove(&0x04).and_then(|v| value_to_bool(&v, "0x04")),
212            uv_retries: raw.remove(&0x05).and_then(|v| value_to_u32(&v, "0x05")),
213        })
214    }
215}
216
217crate::deserialize_cbor!(ClientPinResponse);
218
219#[cfg(test)]
220mod tests {
221    use super::*;
222
223    #[test]
224    fn get_pin_retries() {
225        let c = ClientPinRequest {
226            pin_uv_protocol: Some(1),
227            sub_command: ClientPinSubCommand::GetPinRetries,
228            ..Default::default()
229        };
230
231        // FIDO protocol requires definite length parameters
232        assert_eq!(
233            vec![0x06, (5 << 5) | 2, 1, 1, 2, 1],
234            c.cbor().expect("encode error")
235        );
236
237        let r = vec![0xa1, 0x03, 0x08];
238        let a = <ClientPinResponse as CBORResponse>::try_from(r.as_slice())
239            .expect("Failed to decode message");
240
241        assert_eq!(
242            ClientPinResponse {
243                pin_retries: Some(8),
244                ..Default::default()
245            },
246            a
247        );
248    }
249
250    #[test]
251    fn get_key_agreement() {
252        let c = ClientPinRequest {
253            pin_uv_protocol: Some(2),
254            sub_command: ClientPinSubCommand::GetKeyAgreement,
255            ..Default::default()
256        };
257
258        assert_eq!(
259            vec![0x06, (5 << 5) | 2, 1, 2, 2, 2],
260            c.cbor().expect("encode error")
261        );
262
263        let r = vec![
264            0xa1, 0x01, 0xa5, 0x01, 0x02, 0x03, 0x38, 0x18, 0x20, 0x01, 0x21, 0x58, 0x20, 0x74,
265            0xf4, 0x6b, 0xdc, 0x1c, 0x60, 0xac, 0xcc, 0xbb, 0xf3, 0x9a, 0x37, 0xe4, 0xcc, 0x9e,
266            0xac, 0x80, 0xf0, 0x01, 0x66, 0x27, 0xc7, 0xb6, 0x17, 0x44, 0x55, 0xb4, 0x4f, 0xe0,
267            0x4a, 0xc4, 0x70, 0x22, 0x58, 0x20, 0x38, 0xe6, 0xd6, 0xf1, 0x8d, 0xaa, 0x1f, 0x26,
268            0x9f, 0x3a, 0x95, 0xd1, 0x89, 0x34, 0xab, 0x72, 0x60, 0xe3, 0xd9, 0x50, 0x6a, 0x90,
269            0xe6, 0x8a, 0xc8, 0x35, 0xb4, 0x9f, 0xbe, 0xc4, 0x51, 0x21,
270        ];
271        let a = <ClientPinResponse as CBORResponse>::try_from(r.as_slice())
272            .expect("Failed to decode message");
273
274        assert_eq!(
275            ClientPinResponse {
276                key_agreement: Some(COSEKey {
277                    type_: COSEAlgorithm::PinUvProtocol,
278                    key: COSEKeyType::EC_EC2(COSEEC2Key {
279                        curve: ECDSACurve::SECP256R1,
280                        x: vec![
281                            0x74, 0xf4, 0x6b, 0xdc, 0x1c, 0x60, 0xac, 0xcc, 0xbb, 0xf3, 0x9a, 0x37,
282                            0xe4, 0xcc, 0x9e, 0xac, 0x80, 0xf0, 0x01, 0x66, 0x27, 0xc7, 0xb6, 0x17,
283                            0x44, 0x55, 0xb4, 0x4f, 0xe0, 0x4a, 0xc4, 0x70
284                        ]
285                        .into(),
286                        y: vec![
287                            0x38, 0xe6, 0xd6, 0xf1, 0x8d, 0xaa, 0x1f, 0x26, 0x9f, 0x3a, 0x95, 0xd1,
288                            0x89, 0x34, 0xab, 0x72, 0x60, 0xe3, 0xd9, 0x50, 0x6a, 0x90, 0xe6, 0x8a,
289                            0xc8, 0x35, 0xb4, 0x9f, 0xbe, 0xc4, 0x51, 0x21
290                        ]
291                        .into(),
292                    }),
293                }),
294                ..Default::default()
295            },
296            a,
297        );
298    }
299
300    #[test]
301    fn get_pin_token() {
302        let c = ClientPinRequest {
303            pin_uv_protocol: Some(2),
304            sub_command: ClientPinSubCommand::GetPinToken,
305            key_agreement: Some(COSEKey {
306                type_: COSEAlgorithm::PinUvProtocol,
307                key: COSEKeyType::EC_EC2(COSEEC2Key {
308                    curve: ECDSACurve::SECP256R1,
309                    x: vec![
310                        0x76, 0xa8, 0x64, 0x59, 0xf0, 0x2a, 0xa9, 0xa7, 0x87, 0x86, 0x21, 0x26,
311                        0x45, 0x6a, 0xcb, 0x6d, 0xe0, 0x7d, 0x81, 0x4b, 0x34, 0x1c, 0x10, 0xbe,
312                        0xee, 0x85, 0x8c, 0x09, 0x1d, 0x0c, 0xc4, 0xde,
313                    ]
314                    .into(),
315                    y: vec![
316                        0xc9, 0xf5, 0x4d, 0x23, 0x4b, 0x33, 0x97, 0xdc, 0x86, 0xa4, 0x32, 0x44,
317                        0x92, 0x0e, 0xe0, 0xfc, 0xd8, 0x81, 0x89, 0xd7, 0x58, 0xdc, 0x73, 0x92,
318                        0xad, 0xf8, 0x51, 0x28, 0xdf, 0x22, 0x14, 0x25,
319                    ]
320                    .into(),
321                }),
322            }),
323            pin_hash_enc: Some(vec![
324                0x27, 0x5e, 0xbb, 0x6d, 0x9b, 0x29, 0xbf, 0x25, 0x77, 0xed, 0x9f, 0xfc, 0x99, 0xae,
325                0x4f, 0x29, 0xba, 0x98, 0xa4, 0x0b, 0xc7, 0x32, 0x66, 0x2a, 0xcc, 0x25, 0xa3, 0x40,
326                0x3d, 0xfa, 0x79, 0x79,
327            ]),
328            ..Default::default()
329        };
330
331        assert_eq!(
332            vec![
333                0x06, 0xa4, 0x01, 0x02, 0x02, 0x05, 0x03, 0xa5, 0x01, 0x02, 0x03, 0x38, 0x18, 0x20,
334                0x01, 0x21, 0x58, 0x20, 0x76, 0xa8, 0x64, 0x59, 0xf0, 0x2a, 0xa9, 0xa7, 0x87, 0x86,
335                0x21, 0x26, 0x45, 0x6a, 0xcb, 0x6d, 0xe0, 0x7d, 0x81, 0x4b, 0x34, 0x1c, 0x10, 0xbe,
336                0xee, 0x85, 0x8c, 0x09, 0x1d, 0x0c, 0xc4, 0xde, 0x22, 0x58, 0x20, 0xc9, 0xf5, 0x4d,
337                0x23, 0x4b, 0x33, 0x97, 0xdc, 0x86, 0xa4, 0x32, 0x44, 0x92, 0x0e, 0xe0, 0xfc, 0xd8,
338                0x81, 0x89, 0xd7, 0x58, 0xdc, 0x73, 0x92, 0xad, 0xf8, 0x51, 0x28, 0xdf, 0x22, 0x14,
339                0x25, 0x06, 0x58, 0x20, 0x27, 0x5e, 0xbb, 0x6d, 0x9b, 0x29, 0xbf, 0x25, 0x77, 0xed,
340                0x9f, 0xfc, 0x99, 0xae, 0x4f, 0x29, 0xba, 0x98, 0xa4, 0x0b, 0xc7, 0x32, 0x66, 0x2a,
341                0xcc, 0x25, 0xa3, 0x40, 0x3d, 0xfa, 0x79, 0x79
342            ],
343            c.cbor().expect("encode error")
344        );
345
346        let r = vec![
347            0xa1, 0x02, 0x58, 0x30, 0xf2, 0xbd, 0x74, 0x05, 0xff, 0x54, 0xa5, 0x2e, 0xa4, 0x49,
348            0xdd, 0x82, 0x19, 0x9e, 0x1b, 0x76, 0x25, 0x86, 0x0a, 0x5a, 0xfd, 0x09, 0x1e, 0xed,
349            0xe3, 0x0a, 0x3f, 0x76, 0x34, 0x38, 0x5e, 0xf3, 0x51, 0x40, 0x51, 0xf3, 0x91, 0xe6,
350            0x7f, 0x1a, 0x69, 0x45, 0xa9, 0x49, 0x2f, 0x1f, 0xc6, 0xc3,
351        ];
352        let a = <ClientPinResponse as CBORResponse>::try_from(r.as_slice())
353            .expect("Failed to decode message");
354
355        assert_eq!(
356            ClientPinResponse {
357                pin_uv_auth_token: Some(vec![
358                    0xf2, 0xbd, 0x74, 0x05, 0xff, 0x54, 0xa5, 0x2e, 0xa4, 0x49, 0xdd, 0x82, 0x19,
359                    0x9e, 0x1b, 0x76, 0x25, 0x86, 0x0a, 0x5a, 0xfd, 0x09, 0x1e, 0xed, 0xe3, 0x0a,
360                    0x3f, 0x76, 0x34, 0x38, 0x5e, 0xf3, 0x51, 0x40, 0x51, 0xf3, 0x91, 0xe6, 0x7f,
361                    0x1a, 0x69, 0x45, 0xa9, 0x49, 0x2f, 0x1f, 0xc6, 0xc3
362                ]),
363                ..Default::default()
364            },
365            a
366        )
367    }
368
369    #[test]
370    fn get_pin_uv_auth_token_using_uv_with_permissions() {
371        let c = ClientPinRequest {
372            pin_uv_protocol: Some(2),
373            sub_command: ClientPinSubCommand::GetPinUvAuthTokenUsingUvWithPermissions,
374            key_agreement: Some(COSEKey {
375                type_: COSEAlgorithm::PinUvProtocol,
376                key: COSEKeyType::EC_EC2(COSEEC2Key {
377                    curve: ECDSACurve::SECP256R1,
378                    x: vec![
379                        0x18, 0x6d, 0x9d, 0x21, 0xad, 0xbe, 0x1b, 0xb0, 0x46, 0xac, 0xa9, 0x64,
380                        0xdf, 0x27, 0x58, 0xd7, 0xcb, 0xdc, 0x62, 0xb0, 0x4e, 0xe6, 0x34, 0xc5,
381                        0xb8, 0x12, 0xa7, 0x89, 0xd9, 0x40, 0xd9, 0xde,
382                    ]
383                    .into(),
384                    y: vec![
385                        0xfe, 0xd8, 0xf2, 0x72, 0x6f, 0x89, 0x21, 0xe2, 0xae, 0x68, 0xfe, 0x89,
386                        0x66, 0x1c, 0x01, 0x6c, 0x5d, 0x0d, 0x8e, 0xd0, 0x4a, 0xe2, 0x7a, 0xd1,
387                        0x1d, 0xfe, 0x49, 0xc8, 0xff, 0x7c, 0xc8, 0x7c,
388                    ]
389                    .into(),
390                }),
391            }),
392            permissions: Permissions::GET_ASSERTION,
393            rp_id: Some("localhost".to_string()),
394            ..Default::default()
395        };
396
397        assert_eq!(
398            vec![
399                0x06, 0xa5, 0x01, 0x02, 0x02, 0x06, 0x03, 0xa5, 0x01, 0x02, 0x03, 0x38, 0x18, 0x20,
400                0x01, 0x21, 0x58, 0x20, 0x18, 0x6d, 0x9d, 0x21, 0xad, 0xbe, 0x1b, 0xb0, 0x46, 0xac,
401                0xa9, 0x64, 0xdf, 0x27, 0x58, 0xd7, 0xcb, 0xdc, 0x62, 0xb0, 0x4e, 0xe6, 0x34, 0xc5,
402                0xb8, 0x12, 0xa7, 0x89, 0xd9, 0x40, 0xd9, 0xde, 0x22, 0x58, 0x20, 0xfe, 0xd8, 0xf2,
403                0x72, 0x6f, 0x89, 0x21, 0xe2, 0xae, 0x68, 0xfe, 0x89, 0x66, 0x1c, 0x01, 0x6c, 0x5d,
404                0x0d, 0x8e, 0xd0, 0x4a, 0xe2, 0x7a, 0xd1, 0x1d, 0xfe, 0x49, 0xc8, 0xff, 0x7c, 0xc8,
405                0x7c, 0x09, 0x02, 0x0a, 0x69, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74
406            ],
407            c.cbor().expect("encode error")
408        );
409
410        // Response tested in get_pin_token
411    }
412
413    #[test]
414    fn set_pin() {
415        let c = ClientPinRequest {
416            pin_uv_protocol: Some(2),
417            sub_command: ClientPinSubCommand::SetPin,
418            key_agreement: Some(COSEKey {
419                type_: COSEAlgorithm::PinUvProtocol,
420                key: COSEKeyType::EC_EC2(COSEEC2Key {
421                    curve: ECDSACurve::SECP256R1,
422                    x: vec![
423                        0x31, 0xe2, 0x7d, 0x95, 0xcd, 0x62, 0x93, 0x71, 0xf3, 0x26, 0x94, 0x8b,
424                        0xbe, 0xe0, 0xd9, 0x7c, 0xdd, 0x6c, 0x39, 0xb9, 0x9e, 0x58, 0x3c, 0x40,
425                        0x6d, 0x1f, 0xee, 0x60, 0xb1, 0x8f, 0xdc, 0xfd,
426                    ]
427                    .into(),
428                    y: vec![
429                        0xc0, 0xec, 0xb0, 0xae, 0x0b, 0xfc, 0xa8, 0xf6, 0x11, 0x42, 0x96, 0x4f,
430                        0x56, 0xaa, 0x61, 0x7c, 0x74, 0xc5, 0x1e, 0x31, 0xf6, 0x79, 0x40, 0xd1,
431                        0xb4, 0x55, 0x75, 0x20, 0xd0, 0xf1, 0xa4, 0xf7,
432                    ]
433                    .into(),
434                }),
435            }),
436            pin_uv_auth_param: Some(vec![
437                0x4c, 0xea, 0xbd, 0x24, 0x07, 0x61, 0xaf, 0xca, 0xf6, 0x80, 0x8c, 0x5c, 0x03, 0x93,
438                0x76, 0x3f, 0xdc, 0x90, 0x04, 0x9c, 0x1f, 0xef, 0x09, 0x18, 0x43, 0x80, 0x43, 0x5e,
439                0x18, 0xe1, 0xc0, 0x5e,
440            ]),
441            new_pin_enc: Some(vec![
442                0x66, 0x7c, 0xd5, 0xa6, 0x74, 0x8e, 0x51, 0xce, 0x8e, 0x98, 0x3a, 0x29, 0x98, 0x31,
443                0x5e, 0x1d, 0xfb, 0x33, 0x25, 0xc3, 0x36, 0xb0, 0xb5, 0xd4, 0xa7, 0xc9, 0xaa, 0x10,
444                0x28, 0x1a, 0xeb, 0xb4, 0xbf, 0x9c, 0xd4, 0x81, 0xf0, 0x67, 0x9c, 0x8b, 0x6d, 0x63,
445                0x3a, 0x76, 0xa4, 0x69, 0x07, 0x8b, 0x96, 0x92, 0xc7, 0x41, 0xac, 0xaa, 0x4e, 0xef,
446                0xc3, 0x82, 0xfe, 0x25, 0x90, 0x9a, 0x98, 0xf9, 0x2b, 0x02, 0x86, 0xfa, 0x2b, 0x53,
447                0xd6, 0x6b, 0xda, 0xa8, 0xef, 0x1d, 0x90, 0x1f, 0x9f, 0x9d,
448            ]),
449            ..Default::default()
450        };
451
452        assert_eq!(
453            vec![
454                0x06, 0xa5, 0x01, 0x02, 0x02, 0x03, 0x03, 0xa5, 0x01, 0x02, 0x03, 0x38, 0x18, 0x20,
455                0x01, 0x21, 0x58, 0x20, 0x31, 0xe2, 0x7d, 0x95, 0xcd, 0x62, 0x93, 0x71, 0xf3, 0x26,
456                0x94, 0x8b, 0xbe, 0xe0, 0xd9, 0x7c, 0xdd, 0x6c, 0x39, 0xb9, 0x9e, 0x58, 0x3c, 0x40,
457                0x6d, 0x1f, 0xee, 0x60, 0xb1, 0x8f, 0xdc, 0xfd, 0x22, 0x58, 0x20, 0xc0, 0xec, 0xb0,
458                0xae, 0x0b, 0xfc, 0xa8, 0xf6, 0x11, 0x42, 0x96, 0x4f, 0x56, 0xaa, 0x61, 0x7c, 0x74,
459                0xc5, 0x1e, 0x31, 0xf6, 0x79, 0x40, 0xd1, 0xb4, 0x55, 0x75, 0x20, 0xd0, 0xf1, 0xa4,
460                0xf7, 0x04, 0x58, 0x20, 0x4c, 0xea, 0xbd, 0x24, 0x07, 0x61, 0xaf, 0xca, 0xf6, 0x80,
461                0x8c, 0x5c, 0x03, 0x93, 0x76, 0x3f, 0xdc, 0x90, 0x04, 0x9c, 0x1f, 0xef, 0x09, 0x18,
462                0x43, 0x80, 0x43, 0x5e, 0x18, 0xe1, 0xc0, 0x5e, 0x05, 0x58, 0x50, 0x66, 0x7c, 0xd5,
463                0xa6, 0x74, 0x8e, 0x51, 0xce, 0x8e, 0x98, 0x3a, 0x29, 0x98, 0x31, 0x5e, 0x1d, 0xfb,
464                0x33, 0x25, 0xc3, 0x36, 0xb0, 0xb5, 0xd4, 0xa7, 0xc9, 0xaa, 0x10, 0x28, 0x1a, 0xeb,
465                0xb4, 0xbf, 0x9c, 0xd4, 0x81, 0xf0, 0x67, 0x9c, 0x8b, 0x6d, 0x63, 0x3a, 0x76, 0xa4,
466                0x69, 0x07, 0x8b, 0x96, 0x92, 0xc7, 0x41, 0xac, 0xaa, 0x4e, 0xef, 0xc3, 0x82, 0xfe,
467                0x25, 0x90, 0x9a, 0x98, 0xf9, 0x2b, 0x02, 0x86, 0xfa, 0x2b, 0x53, 0xd6, 0x6b, 0xda,
468                0xa8, 0xef, 0x1d, 0x90, 0x1f, 0x9f, 0x9d
469            ],
470            c.cbor().expect("encode error")
471        );
472
473        // Successfully setting the pin returns an empty result
474        assert_eq!(
475            ClientPinResponse::default(),
476            <ClientPinResponse as CBORResponse>::try_from(&[])
477                .expect("deserialising empty response")
478        );
479    }
480}