Skip to content

Commit 6e63811

Browse files
author
Martin Sirringhaus
committed
New struct members added in CTAP 2.2
1 parent 30a9f76 commit 6e63811

File tree

5 files changed

+143
-15
lines changed

5 files changed

+143
-15
lines changed

src/ctap2/commands/credential_management.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,10 @@ pub struct CredentialManagementResponse {
169169
pub cred_protect: Option<u64>,
170170
/// Large blob encryption key.
171171
pub large_blob_key: Option<Vec<u8>>,
172+
173+
// CTAP 2.2
174+
/// Whether the credential is third-party payment enabled, if supported by the authenticator.
175+
pub third_party_payment: Option<bool>,
172176
}
173177

174178
impl CtapResponse for CredentialManagementResponse {}
@@ -248,6 +252,7 @@ impl<'de> Deserialize<'de> for CredentialManagementResponse {
248252
let mut total_credentials = None; // (0x09) Unsigned Integer Total number of credentials present on the authenticator for the RP in question
249253
let mut cred_protect = None; // (0x0A) Unsigned Integer Credential protection policy.
250254
let mut large_blob_key = None; // (0x0B) Byte string Large blob encryption key.
255+
let mut third_party_payment = None; // (0x0C) bool
251256

252257
while let Some(key) = map.next_key()? {
253258
match key {
@@ -327,7 +332,12 @@ impl<'de> Deserialize<'de> for CredentialManagementResponse {
327332
// Using into_vec, to avoid any copy of large_blob_key
328333
large_blob_key = Some(map.next_value::<ByteBuf>()?.into_vec());
329334
}
330-
335+
0x0C => {
336+
if third_party_payment.is_some() {
337+
return Err(SerdeError::duplicate_field("third_party_payment"));
338+
}
339+
third_party_payment = Some(map.next_value()?);
340+
}
331341
k => {
332342
warn!("ClientPinResponse: unexpected key: {:?}", k);
333343
let _ = map.next_value::<IgnoredAny>()?;
@@ -348,6 +358,7 @@ impl<'de> Deserialize<'de> for CredentialManagementResponse {
348358
total_credentials,
349359
cred_protect,
350360
large_blob_key,
361+
third_party_payment,
351362
})
352363
}
353364
}

src/ctap2/commands/get_assertion.rs

Lines changed: 65 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ use serde::{
3030
};
3131
use serde_bytes::ByteBuf;
3232
use serde_cbor::{de::from_slice, ser, Value};
33+
use std::collections::HashMap;
3334
use std::fmt;
3435
use std::io::Cursor;
3536

@@ -145,6 +146,8 @@ pub struct GetAssertionExtensions {
145146
pub cred_blob: Option<bool>,
146147
#[serde(rename = "largeBlobKey", skip_serializing_if = "Option::is_none")]
147148
pub large_blob_key: Option<bool>,
149+
#[serde(rename = "thirdPartyPayment", skip_serializing_if = "Option::is_none")]
150+
pub third_party_payment: Option<bool>,
148151
}
149152

150153
impl From<AuthenticationExtensionsClientInputs> for GetAssertionExtensions {
@@ -156,14 +159,18 @@ impl From<AuthenticationExtensionsClientInputs> for GetAssertionExtensions {
156159
_ => None,
157160
},
158161
large_blob_key: input.large_blob_key,
162+
third_party_payment: input.third_party_payment,
159163
..Default::default()
160164
}
161165
}
162166
}
163167

164168
impl GetAssertionExtensions {
165169
fn has_content(&self) -> bool {
166-
self.hmac_secret.is_some() || self.cred_blob.is_some() || self.large_blob_key.is_some()
170+
self.hmac_secret.is_some()
171+
|| self.cred_blob.is_some()
172+
|| self.large_blob_key.is_some()
173+
|| self.third_party_payment.is_some()
167174
}
168175
}
169176

@@ -182,6 +189,10 @@ pub struct GetAssertion {
182189
pub extensions: GetAssertionExtensions,
183190
pub options: GetAssertionOptions,
184191
pub pin_uv_auth_param: Option<PinUvAuthParam>,
192+
193+
// CTAP 2.2:
194+
pub enterprise_attestation: Option<u64>,
195+
pub attestation_formats_preference: Option<Vec<String>>,
185196
}
186197

187198
impl GetAssertion {
@@ -199,6 +210,8 @@ impl GetAssertion {
199210
extensions,
200211
options,
201212
pin_uv_auth_param: None,
213+
enterprise_attestation: None,
214+
attestation_formats_preference: None,
202215
}
203216
}
204217

@@ -287,22 +300,34 @@ impl Serialize for GetAssertion {
287300
if self.pin_uv_auth_param.is_some() {
288301
map_len += 2;
289302
}
303+
if self.enterprise_attestation.is_some() {
304+
map_len += 1;
305+
}
306+
if self.attestation_formats_preference.is_some() {
307+
map_len += 1;
308+
}
290309

291310
let mut map = serializer.serialize_map(Some(map_len))?;
292-
map.serialize_entry(&1, &self.rp.id)?;
293-
map.serialize_entry(&2, &self.client_data_hash)?;
311+
map.serialize_entry(&0x01, &self.rp.id)?;
312+
map.serialize_entry(&0x02, &self.client_data_hash)?;
294313
if !self.allow_list.is_empty() {
295-
map.serialize_entry(&3, &self.allow_list)?;
314+
map.serialize_entry(&0x03, &self.allow_list)?;
296315
}
297316
if self.extensions.has_content() {
298-
map.serialize_entry(&4, &self.extensions)?;
317+
map.serialize_entry(&0x04, &self.extensions)?;
299318
}
300319
if self.options.has_some() {
301-
map.serialize_entry(&5, &self.options)?;
320+
map.serialize_entry(&0x05, &self.options)?;
302321
}
303322
if let Some(pin_uv_auth_param) = &self.pin_uv_auth_param {
304-
map.serialize_entry(&6, &pin_uv_auth_param)?;
305-
map.serialize_entry(&7, &pin_uv_auth_param.pin_protocol.id())?;
323+
map.serialize_entry(&0x06, &pin_uv_auth_param)?;
324+
map.serialize_entry(&0x07, &pin_uv_auth_param.pin_protocol.id())?;
325+
}
326+
if let Some(enterprise_attestation) = &self.enterprise_attestation {
327+
map.serialize_entry(&0x08, &enterprise_attestation)?;
328+
}
329+
if let Some(attestation_formats_preference) = &self.attestation_formats_preference {
330+
map.serialize_entry(&0x09, &attestation_formats_preference)?;
306331
}
307332
map.end()
308333
}
@@ -551,6 +576,9 @@ pub struct GetAssertionResponse {
551576
pub number_of_credentials: Option<usize>,
552577
pub user_selected: Option<bool>,
553578
pub large_blob_key: Option<Vec<u8>>,
579+
pub unsigned_extension_outputs: Option<HashMap<String, serde_cbor::Value>>,
580+
pub ep_attestation: Option<bool>,
581+
pub att_stmt: Option<HashMap<String, serde_cbor::Value>>,
554582
}
555583

556584
impl CtapResponse for GetAssertionResponse {}
@@ -580,6 +608,9 @@ impl<'de> Deserialize<'de> for GetAssertionResponse {
580608
let mut number_of_credentials = None;
581609
let mut user_selected = None;
582610
let mut large_blob_key = None;
611+
let mut unsigned_extension_outputs = None;
612+
let mut ep_attestation = None;
613+
let mut att_stmt = None;
583614

584615
while let Some(key) = map.next_key()? {
585616
match key {
@@ -627,6 +658,26 @@ impl<'de> Deserialize<'de> for GetAssertionResponse {
627658
let large_blob_key_bytes: ByteBuf = map.next_value()?;
628659
large_blob_key = Some(large_blob_key_bytes.into_vec());
629660
}
661+
0x08 => {
662+
if unsigned_extension_outputs.is_some() {
663+
return Err(M::Error::duplicate_field(
664+
"unsigned_extension_outputs",
665+
));
666+
}
667+
unsigned_extension_outputs = Some(map.next_value()?);
668+
}
669+
0x09 => {
670+
if ep_attestation.is_some() {
671+
return Err(M::Error::duplicate_field("ep_attestation"));
672+
}
673+
ep_attestation = Some(map.next_value()?);
674+
}
675+
0x0A => {
676+
if att_stmt.is_some() {
677+
return Err(M::Error::duplicate_field("att_stmt"));
678+
}
679+
att_stmt = Some(map.next_value()?);
680+
}
630681
k => return Err(M::Error::custom(format!("unexpected key: {k:?}"))),
631682
}
632683
}
@@ -642,6 +693,9 @@ impl<'de> Deserialize<'de> for GetAssertionResponse {
642693
number_of_credentials,
643694
user_selected,
644695
large_blob_key,
696+
unsigned_extension_outputs,
697+
ep_attestation,
698+
att_stmt,
645699
})
646700
}
647701
}
@@ -1228,6 +1282,9 @@ pub mod test {
12281282
certifications: None,
12291283
remaining_discoverable_credentials: None,
12301284
vendor_prototype_config_commands: None,
1285+
attestation_formats: None,
1286+
uv_count_since_last_pin_entry: None,
1287+
long_touch_for_reset: None,
12311288
});
12321289

12331290
// Sending first GetAssertion with first allow_list-entry, that will return an error

src/ctap2/commands/get_info.rs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,10 @@ pub struct AuthenticatorInfo {
337337
pub certifications: Option<BTreeMap<String, u64>>,
338338
pub remaining_discoverable_credentials: Option<u64>,
339339
pub vendor_prototype_config_commands: Option<Vec<u64>>,
340+
// CTAP 2.2
341+
pub attestation_formats: Option<Vec<String>>,
342+
pub uv_count_since_last_pin_entry: Option<u64>,
343+
pub long_touch_for_reset: Option<bool>,
340344
}
341345

342346
impl AuthenticatorInfo {
@@ -418,6 +422,9 @@ impl<'de> Deserialize<'de> for AuthenticatorInfo {
418422
let mut certifications = None;
419423
let mut remaining_discoverable_credentials = None;
420424
let mut vendor_prototype_config_commands = None;
425+
let mut attestation_formats = None;
426+
let mut uv_count_since_last_pin_entry = None;
427+
let mut long_touch_for_reset = None;
421428
while let Some(key) = map.next_key()? {
422429
match key {
423430
0x01 => {
@@ -489,6 +496,15 @@ impl<'de> Deserialize<'de> for AuthenticatorInfo {
489496
0x15 => {
490497
parse_next_optional_value!(vendor_prototype_config_commands, map);
491498
}
499+
0x16 => {
500+
parse_next_optional_value!(attestation_formats, map);
501+
}
502+
0x17 => {
503+
parse_next_optional_value!(uv_count_since_last_pin_entry, map);
504+
}
505+
0x18 => {
506+
parse_next_optional_value!(long_touch_for_reset, map);
507+
}
492508
k => {
493509
warn!("GetInfo: unexpected key: {:?}", k);
494510
let _ = map.next_value::<IgnoredAny>()?;
@@ -535,6 +551,9 @@ impl<'de> Deserialize<'de> for AuthenticatorInfo {
535551
certifications,
536552
remaining_discoverable_credentials,
537553
vendor_prototype_config_commands,
554+
attestation_formats,
555+
uv_count_since_last_pin_entry,
556+
long_touch_for_reset,
538557
})
539558
} else {
540559
Err(M::Error::custom("No AAGuid specified".to_string()))
@@ -776,6 +795,9 @@ pub mod tests {
776795
certifications: None,
777796
remaining_discoverable_credentials: None,
778797
vendor_prototype_config_commands: None,
798+
attestation_formats: None,
799+
uv_count_since_last_pin_entry: None,
800+
long_touch_for_reset: None,
779801
};
780802

781803
assert_eq!(authenticator_info, expected);
@@ -786,7 +808,7 @@ pub mod tests {
786808
broken_payload[0] += 1;
787809
// Add the additional entry at the back with an invalid key
788810
broken_payload.extend_from_slice(&[
789-
0x17, // unsigned(23) -> invalid key-number. CTAP2.1 goes only to 0x15
811+
0x27, // unsigned(39) -> invalid key-number. CTAP2.2 goes only to 0x18
790812
0x6B, // text(11)
791813
0x69, 0x6E, 0x76, 0x61, 0x6C, 0x69, 0x64, 0x5F, 0x6B, 0x65, 0x79, // "invalid_key"
792814
]);
@@ -863,6 +885,9 @@ pub mod tests {
863885
certifications: None,
864886
remaining_discoverable_credentials: Some(24),
865887
vendor_prototype_config_commands: None,
888+
attestation_formats: None,
889+
uv_count_since_last_pin_entry: None,
890+
long_touch_for_reset: None,
866891
};
867892

868893
assert_eq!(authenticator_info, expected);
@@ -956,6 +981,9 @@ pub mod tests {
956981
certifications: None,
957982
remaining_discoverable_credentials: None,
958983
vendor_prototype_config_commands: None,
984+
attestation_formats: None,
985+
uv_count_since_last_pin_entry: None,
986+
long_touch_for_reset: None,
959987
};
960988

961989
assert_eq!(result, &expected);

0 commit comments

Comments
 (0)