ctap_types/ctap2/
client_pin.rs

1use crate::Bytes;
2use bitflags::bitflags;
3use cosey::EcdhEsHkdf256PublicKey;
4use serde_indexed::{DeserializeIndexed, SerializeIndexed};
5use serde_repr::{Deserialize_repr, Serialize_repr};
6
7#[derive(Clone, Debug, Eq, PartialEq, Serialize_repr, Deserialize_repr)]
8#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
9#[non_exhaustive]
10#[repr(u8)]
11pub enum PinV1Subcommand {
12    GetRetries = 0x01,
13    GetKeyAgreement = 0x02,
14    SetPin = 0x03,
15    ChangePin = 0x04,
16    GetPinToken = 0x05,
17    GetPinUvAuthTokenUsingUvWithPermissions = 0x06,
18    GetUVRetries = 0x07,
19    GetPinUvAuthTokenUsingPinWithPermissions = 0x09,
20}
21
22bitflags! {
23    #[derive(Default)]
24    pub struct Permissions: u8 {
25        const MAKE_CREDENTIAL = 0x01;
26        const GET_ASSERTION = 0x02;
27        const CREDENTIAL_MANAGEMENT = 0x04;
28        const BIO_ENROLLMENT = 0x08;
29        const LARGE_BLOB_WRITE = 0x10;
30        const AUTHENTICATOR_CONFIGURATION = 0x20;
31    }
32}
33
34// minimum PIN length: 4 unicode
35// maximum PIN length: UTF-8 represented by <= 63 bytes
36// maximum consecutive incorrect PIN attempts: 8
37
38#[derive(Clone, Debug, Eq, PartialEq, SerializeIndexed, DeserializeIndexed)]
39#[non_exhaustive]
40#[serde_indexed(offset = 1)]
41pub struct Request<'a> {
42    // 0x01
43    // PIN protocol version chosen by the client.
44    // For this version of the spec, this SHALL be the number 1.
45    pub pin_protocol: u8,
46
47    // 0x02
48    // The authenticator Client PIN sub command currently being requested
49    pub sub_command: PinV1Subcommand,
50
51    // 0x03
52    // Public key of platformKeyAgreementKey.
53    // Must contain "alg" parameter, must not contain any other optional parameters
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub key_agreement: Option<EcdhEsHkdf256PublicKey>,
56
57    // 0x04
58    // First 16 bytes of HMAC-SHA-256 of encrypted contents
59    // using `sharedSecret`.
60    #[serde(skip_serializing_if = "Option::is_none")]
61    pub pin_auth: Option<&'a serde_bytes::Bytes>,
62
63    // 0x05
64    // Encrypted new PIN using `sharedSecret`.
65    // (Encryption over UTF-8 representation of new PIN).
66    #[serde(skip_serializing_if = "Option::is_none")]
67    pub new_pin_enc: Option<&'a serde_bytes::Bytes>,
68
69    // 0x06
70    // Encrypted first 16 bytes of SHA-256 of PIN using `sharedSecret`.
71    #[serde(skip_serializing_if = "Option::is_none")]
72    pub pin_hash_enc: Option<&'a serde_bytes::Bytes>,
73
74    // 0x07
75    #[serde(skip_serializing_if = "Option::is_none")]
76    pub(crate) _placeholder07: Option<()>,
77
78    // 0x08
79    #[serde(skip_serializing_if = "Option::is_none")]
80    pub(crate) _placeholder08: Option<()>,
81
82    // 0x09
83    // Bitfield of permissions
84    #[serde(skip_serializing_if = "Option::is_none")]
85    pub permissions: Option<u8>,
86
87    // 0x0A
88    // The RP ID to assign as the permissions RP ID
89    #[serde(skip_serializing_if = "Option::is_none")]
90    pub rp_id: Option<&'a str>,
91}
92
93#[derive(Clone, Debug, Default, Eq, PartialEq, SerializeIndexed, DeserializeIndexed)]
94#[non_exhaustive]
95#[serde_indexed(offset = 1)]
96pub struct Response {
97    // 0x01, like ClientPinParameters::key_agreement
98    #[serde(skip_serializing_if = "Option::is_none")]
99    pub key_agreement: Option<EcdhEsHkdf256PublicKey>,
100
101    // 0x02, encrypted `pinToken` using `sharedSecret`
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub pin_token: Option<Bytes<48>>,
104
105    // 0x03, number of PIN attempts remaining before lockout
106    #[serde(skip_serializing_if = "Option::is_none")]
107    pub retries: Option<u8>,
108
109    // 0x04, whether a power cycle is required before any future PIN operation
110    #[serde(skip_serializing_if = "Option::is_none")]
111    pub power_cycle_state: Option<bool>,
112
113    // 0x05, number of uv attempts remaining before lockout
114    #[serde(skip_serializing_if = "Option::is_none")]
115    pub uv_retries: Option<u8>,
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121    use hex_literal::hex;
122    use serde_test::{assert_de_tokens, assert_ser_tokens, assert_tokens, Token};
123
124    const KEY_AGREEMENT: &[u8] = &hex!("b174bc49c7ca254b70d2e5c207cee9cf174820ebd77ea3c65508c26da51b657c1cc6b952f8621697936482da0a6d3d3826a59095daf6cd7c03e2e60385d2f6d9");
125    const NEW_PIN_ENC: &[u8] = &[0xde; 64];
126    const PIN_AUTH: &[u8] = &[0xad; 32];
127    const PIN_HASH_ENC: &[u8] = &[0xda; 16];
128    const PIN_TOKEN: &[u8] = &[0xed; 32];
129
130    #[test]
131    fn test_de_request_get_retries() {
132        let request = Request {
133            pin_protocol: 1,
134            sub_command: PinV1Subcommand::GetRetries,
135            key_agreement: None,
136            pin_auth: None,
137            new_pin_enc: None,
138            pin_hash_enc: None,
139            _placeholder07: None,
140            _placeholder08: None,
141            permissions: None,
142            rp_id: None,
143        };
144        assert_tokens(
145            &request,
146            &[
147                Token::Map { len: Some(2) },
148                // 0x01: pinProtocol
149                Token::U64(0x01),
150                Token::U8(1),
151                // 0x02: subCommand
152                Token::U64(0x02),
153                Token::U8(0x01),
154                Token::MapEnd,
155            ],
156        );
157    }
158
159    #[test]
160    fn test_de_request_get_key_agreement() {
161        let request = Request {
162            pin_protocol: 1,
163            sub_command: PinV1Subcommand::GetKeyAgreement,
164            key_agreement: None,
165            pin_auth: None,
166            new_pin_enc: None,
167            pin_hash_enc: None,
168            _placeholder07: None,
169            _placeholder08: None,
170            permissions: None,
171            rp_id: None,
172        };
173        assert_tokens(
174            &request,
175            &[
176                Token::Map { len: Some(2) },
177                // 0x01: pinProtocol
178                Token::U64(0x01),
179                Token::U8(1),
180                // 0x02: subCommand
181                Token::U64(0x02),
182                Token::U8(0x02),
183                Token::MapEnd,
184            ],
185        );
186    }
187
188    #[test]
189    fn test_de_request_set_pin() {
190        let key_agreement = EcdhEsHkdf256PublicKey {
191            x: Bytes::from_slice(&KEY_AGREEMENT[..32]).unwrap(),
192            y: Bytes::from_slice(&KEY_AGREEMENT[32..]).unwrap(),
193        };
194        let request = Request {
195            pin_protocol: 1,
196            sub_command: PinV1Subcommand::SetPin,
197            key_agreement: Some(key_agreement),
198            pin_auth: Some(serde_bytes::Bytes::new(PIN_AUTH)),
199            new_pin_enc: Some(serde_bytes::Bytes::new(NEW_PIN_ENC)),
200            pin_hash_enc: None,
201            _placeholder07: None,
202            _placeholder08: None,
203            permissions: None,
204            rp_id: None,
205        };
206        assert_de_tokens(
207            &request,
208            &[
209                Token::Map { len: Some(5) },
210                // 0x01: pinProtocol
211                Token::U64(0x01),
212                Token::U8(1),
213                // 0x02: subCommand
214                Token::U64(0x02),
215                Token::U8(0x03),
216                // 0x03: keyAgreement
217                Token::U64(0x03),
218                Token::Map { len: Some(5) },
219                //       1: kty
220                Token::I8(1),
221                Token::I8(2),
222                //       3: alg
223                Token::I8(3),
224                Token::I8(-25),
225                //       -1: crv
226                Token::I8(-1),
227                Token::I8(1),
228                //       -2: x
229                Token::I8(-2),
230                Token::BorrowedBytes(&KEY_AGREEMENT[..32]),
231                //       -3: y
232                Token::I8(-3),
233                Token::BorrowedBytes(&KEY_AGREEMENT[32..]),
234                Token::MapEnd,
235                // 0x04: pinUvAuthParam
236                Token::U64(0x04),
237                Token::BorrowedBytes(PIN_AUTH),
238                // 0x05: newPinEnc
239                Token::U64(0x05),
240                Token::BorrowedBytes(NEW_PIN_ENC),
241                Token::MapEnd,
242            ],
243        );
244    }
245
246    #[test]
247    fn test_de_request_change_pin() {
248        let key_agreement = EcdhEsHkdf256PublicKey {
249            x: Bytes::from_slice(&KEY_AGREEMENT[..32]).unwrap(),
250            y: Bytes::from_slice(&KEY_AGREEMENT[32..]).unwrap(),
251        };
252        let request = Request {
253            pin_protocol: 1,
254            sub_command: PinV1Subcommand::ChangePin,
255            key_agreement: Some(key_agreement),
256            pin_auth: Some(serde_bytes::Bytes::new(PIN_AUTH)),
257            new_pin_enc: Some(serde_bytes::Bytes::new(NEW_PIN_ENC)),
258            pin_hash_enc: Some(serde_bytes::Bytes::new(PIN_HASH_ENC)),
259            _placeholder07: None,
260            _placeholder08: None,
261            permissions: None,
262            rp_id: None,
263        };
264        assert_de_tokens(
265            &request,
266            &[
267                Token::Map { len: Some(6) },
268                // 0x01: pinProtocol
269                Token::U64(0x01),
270                Token::U8(1),
271                // 0x02: subCommand
272                Token::U64(0x02),
273                Token::U8(0x04),
274                // 0x03: keyAgreement
275                Token::U64(0x03),
276                Token::Map { len: Some(5) },
277                //       1: kty
278                Token::I8(1),
279                Token::I8(2),
280                //       3: alg
281                Token::I8(3),
282                Token::I8(-25),
283                //       -1: crv
284                Token::I8(-1),
285                Token::I8(1),
286                //       -2: x
287                Token::I8(-2),
288                Token::BorrowedBytes(&KEY_AGREEMENT[..32]),
289                //       -3: y
290                Token::I8(-3),
291                Token::BorrowedBytes(&KEY_AGREEMENT[32..]),
292                Token::MapEnd,
293                // 0x04: pinUvAuthParam
294                Token::U64(0x04),
295                Token::BorrowedBytes(PIN_AUTH),
296                // 0x05: newPinEnc
297                Token::U64(0x05),
298                Token::BorrowedBytes(NEW_PIN_ENC),
299                // 0x06: pinHashEnc
300                Token::U64(0x06),
301                Token::BorrowedBytes(PIN_HASH_ENC),
302                Token::MapEnd,
303            ],
304        );
305    }
306
307    #[test]
308    fn test_de_get_pin_token() {
309        let key_agreement = EcdhEsHkdf256PublicKey {
310            x: Bytes::from_slice(&KEY_AGREEMENT[..32]).unwrap(),
311            y: Bytes::from_slice(&KEY_AGREEMENT[32..]).unwrap(),
312        };
313        let request = Request {
314            pin_protocol: 1,
315            sub_command: PinV1Subcommand::GetPinToken,
316            key_agreement: Some(key_agreement),
317            pin_auth: None,
318            new_pin_enc: None,
319            pin_hash_enc: Some(serde_bytes::Bytes::new(PIN_HASH_ENC)),
320            _placeholder07: None,
321            _placeholder08: None,
322            permissions: None,
323            rp_id: None,
324        };
325        assert_de_tokens(
326            &request,
327            &[
328                Token::Map { len: Some(4) },
329                // 0x01: pinProtocol
330                Token::U64(0x01),
331                Token::U8(1),
332                // 0x02: subCommand
333                Token::U64(0x02),
334                Token::U8(0x05),
335                // 0x03: keyAgreement
336                Token::U64(0x03),
337                Token::Map { len: Some(5) },
338                //       1: kty
339                Token::I8(1),
340                Token::I8(2),
341                //       3: alg
342                Token::I8(3),
343                Token::I8(-25),
344                //       -1: crv
345                Token::I8(-1),
346                Token::I8(1),
347                //       -2: x
348                Token::I8(-2),
349                Token::BorrowedBytes(&KEY_AGREEMENT[..32]),
350                //       -3: y
351                Token::I8(-3),
352                Token::BorrowedBytes(&KEY_AGREEMENT[32..]),
353                Token::MapEnd,
354                // 0x06: pinHashEnc
355                Token::U64(0x06),
356                Token::BorrowedBytes(PIN_HASH_ENC),
357                Token::MapEnd,
358            ],
359        );
360    }
361
362    #[test]
363    fn test_de_get_pin_token_with_permissions() {
364        let key_agreement = EcdhEsHkdf256PublicKey {
365            x: Bytes::from_slice(&KEY_AGREEMENT[..32]).unwrap(),
366            y: Bytes::from_slice(&KEY_AGREEMENT[32..]).unwrap(),
367        };
368        let request = Request {
369            pin_protocol: 1,
370            sub_command: PinV1Subcommand::GetPinUvAuthTokenUsingPinWithPermissions,
371            key_agreement: Some(key_agreement),
372            pin_auth: None,
373            new_pin_enc: None,
374            pin_hash_enc: Some(serde_bytes::Bytes::new(PIN_HASH_ENC)),
375            _placeholder07: None,
376            _placeholder08: None,
377            permissions: Some(0x04),
378            rp_id: Some("example.com"),
379        };
380        assert_de_tokens(
381            &request,
382            &[
383                Token::Map { len: Some(6) },
384                // 0x01: pinProtocol
385                Token::U64(0x01),
386                Token::U8(1),
387                // 0x02: subCommand
388                Token::U64(0x02),
389                Token::U8(0x09),
390                // 0x03: keyAgreement
391                Token::U64(0x03),
392                Token::Map { len: Some(5) },
393                //       1: kty
394                Token::I8(1),
395                Token::I8(2),
396                //       3: alg
397                Token::I8(3),
398                Token::I8(-25),
399                //       -1: crv
400                Token::I8(-1),
401                Token::I8(1),
402                //       -2: x
403                Token::I8(-2),
404                Token::BorrowedBytes(&KEY_AGREEMENT[..32]),
405                //       -3: y
406                Token::I8(-3),
407                Token::BorrowedBytes(&KEY_AGREEMENT[32..]),
408                Token::MapEnd,
409                // 0x06: pinHashEnc
410                Token::U64(0x06),
411                Token::BorrowedBytes(PIN_HASH_ENC),
412                // 0x09: permissions
413                Token::U64(0x09),
414                Token::U8(0x04),
415                // 0x0A: rpId
416                Token::U64(0x0A),
417                Token::BorrowedStr("example.com"),
418                Token::MapEnd,
419            ],
420        );
421    }
422
423    #[test]
424    fn test_ser_response_get_retries() {
425        let response = Response {
426            retries: Some(3),
427            ..Default::default()
428        };
429        assert_ser_tokens(
430            &response,
431            &[
432                Token::Map { len: Some(1) },
433                // 0x03: pinRetries
434                Token::U64(0x03),
435                Token::Some,
436                Token::U8(3),
437                Token::MapEnd,
438            ],
439        );
440    }
441
442    #[test]
443    fn test_ser_response_get_key_agreement() {
444        let key_agreement = EcdhEsHkdf256PublicKey {
445            x: Bytes::from_slice(&KEY_AGREEMENT[..32]).unwrap(),
446            y: Bytes::from_slice(&KEY_AGREEMENT[32..]).unwrap(),
447        };
448        let response = Response {
449            key_agreement: Some(key_agreement),
450            ..Default::default()
451        };
452        assert_ser_tokens(
453            &response,
454            &[
455                Token::Map { len: Some(1) },
456                // 0x01: keyAgreement
457                Token::U64(0x01),
458                Token::Some,
459                Token::Map { len: Some(5) },
460                //       1: kty
461                Token::I8(1),
462                Token::I8(2),
463                //       3: alg
464                Token::I8(3),
465                Token::I8(-25),
466                //       -1: crv
467                Token::I8(-1),
468                Token::I8(1),
469                //       -2: x
470                Token::I8(-2),
471                Token::BorrowedBytes(&KEY_AGREEMENT[..32]),
472                //       -3: y
473                Token::I8(-3),
474                Token::BorrowedBytes(&KEY_AGREEMENT[32..]),
475                Token::MapEnd,
476                Token::MapEnd,
477            ],
478        );
479    }
480
481    #[test]
482    fn test_ser_response_get_pin_token() {
483        let response = Response {
484            pin_token: Some(Bytes::from_slice(PIN_TOKEN).unwrap()),
485            ..Default::default()
486        };
487        assert_ser_tokens(
488            &response,
489            &[
490                Token::Map { len: Some(1) },
491                // 0x02: pinAuvAuthToken
492                Token::U64(0x02),
493                Token::Some,
494                Token::BorrowedBytes(PIN_TOKEN),
495                Token::MapEnd,
496            ],
497        );
498    }
499
500    #[test]
501    fn pin_v1_subcommand() {
502        // NB: This does *not* work without serde_repr, as the
503        // discriminant of a numerical enum does not have to coincide
504        // with its assigned value.
505        // E.g., for PinV1Subcommand, the first entry is set to
506        // value 1, but its discriminant (which our normal serialization
507        // to CBOR would output) is 0.
508        // The following test would then fail, as [1] != [2]
509        let mut buf = [0u8; 64];
510        let example = PinV1Subcommand::GetKeyAgreement;
511        let ser = crate::serde::cbor_serialize(&example, &mut buf).unwrap();
512        assert_eq!(ser, &[0x02]);
513    }
514}