Skip to content

Commit 76512c4

Browse files
fix(ctap2): decode unsignedExtensionOutputs at GetAssertion 0x08 (#244)
The assertion response mapped entry 0x08 to a value it should not have, while the protocol defines 0x08 as the unsigned extension outputs map. An authenticator that returns that map caused the whole assertion response to fail to decode, breaking the ceremony. This becomes more likely as newer authenticators emit unsigned extension outputs. This decodes entry 0x08 as the unsigned extension outputs map and removes two response fields that were never populated with valid data. Scope: this fixes the decode failure. The unsigned outputs are kept on the decoded response but are not yet merged into the caller-facing extension results, since arbitrary CBOR maps do not map cleanly to the typed JSON outputs. That can be a follow-up. The change also drops two unused fields from the public assertion type, a minor change for a pre-1.0 crate. Includes a regression test that decodes a response carrying the map and confirms it parses.
1 parent ba35dde commit 76512c4

3 files changed

Lines changed: 29 additions & 17 deletions

File tree

libwebauthn/src/ops/u2f.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,8 +216,7 @@ impl UpgradableResponse<GetAssertionResponse, SignRequest> for SignResponse {
216216
credentials_count: None,
217217
user_selected: None,
218218
large_blob_key: None,
219-
enterprise_attestation: None,
220-
attestation_statement: None,
219+
unsigned_extension_outputs: None,
221220
};
222221

223222
// This isn't great, but we have no access to the original request, and need to construct

libwebauthn/src/ops/webauthn/get_assertion.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ use crate::{
2525
},
2626
pin::PinUvAuthProtocol,
2727
proto::ctap2::{
28-
Ctap2AttestationStatement, Ctap2GetAssertionResponseExtensions,
29-
Ctap2PublicKeyCredentialDescriptor, Ctap2PublicKeyCredentialUserEntity,
28+
Ctap2GetAssertionResponseExtensions, Ctap2PublicKeyCredentialDescriptor,
29+
Ctap2PublicKeyCredentialUserEntity,
3030
},
3131
webauthn::CtapError,
3232
};
@@ -452,8 +452,6 @@ pub struct Assertion {
452452
pub credentials_count: Option<u32>,
453453
pub user_selected: Option<bool>,
454454
pub unsigned_extensions_output: Option<GetAssertionResponseUnsignedExtensions>,
455-
pub enterprise_attestation: Option<bool>,
456-
pub attestation_statement: Option<Ctap2AttestationStatement>,
457455
}
458456

459457
impl WebAuthnIDLResponse for Assertion {
@@ -1229,8 +1227,6 @@ mod tests {
12291227
credentials_count: None,
12301228
user_selected: None,
12311229
unsigned_extensions_output: None,
1232-
enterprise_attestation: None,
1233-
attestation_statement: None,
12341230
}
12351231
}
12361232

libwebauthn/src/proto/ctap2/model/get_assertion.rs

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -370,11 +370,7 @@ pub struct Ctap2GetAssertionResponse {
370370

371371
#[serde(skip_serializing_if = "Option::is_none")]
372372
#[serde(index = 0x08)]
373-
pub enterprise_attestation: Option<bool>,
374-
375-
#[serde(skip_serializing_if = "Option::is_none")]
376-
#[serde(index = 0x09)]
377-
pub attestation_statement: Option<Ctap2AttestationStatement>,
373+
pub unsigned_extension_outputs: Option<BTreeMap<Value, Value>>,
378374
}
379375

380376
impl Ctap2UserVerifiableRequest for Ctap2GetAssertionRequest {
@@ -462,8 +458,6 @@ impl Ctap2GetAssertionResponse {
462458
credentials_count: self.credentials_count,
463459
user_selected: self.user_selected,
464460
unsigned_extensions_output,
465-
enterprise_attestation: self.enterprise_attestation,
466-
attestation_statement: self.attestation_statement,
467461
}
468462
}
469463
}
@@ -575,8 +569,7 @@ mod tests {
575569
credentials_count: None,
576570
user_selected: None,
577571
large_blob_key: None,
578-
enterprise_attestation: None,
579-
attestation_statement: None,
572+
unsigned_extension_outputs: None,
580573
}
581574
}
582575

@@ -660,4 +653,28 @@ mod tests {
660653

661654
assert!(large_blob.blob.is_none());
662655
}
656+
657+
#[test]
658+
fn decodes_unsigned_extension_outputs_at_index_0x08() {
659+
// 0x08 is unsignedExtensionOutputs (a CBOR map), not enterprise attestation.
660+
let mut auth_data = vec![0u8; 37];
661+
auth_data[32] = AuthenticatorDataFlags::USER_PRESENT.bits();
662+
663+
let mut ueo = BTreeMap::new();
664+
ueo.insert(
665+
Value::Text("thirdPartyPayment".to_string()),
666+
Value::Bool(true),
667+
);
668+
669+
let mut response: BTreeMap<u64, Value> = BTreeMap::new();
670+
response.insert(0x02, Value::Bytes(auth_data));
671+
response.insert(0x03, Value::Bytes(vec![0xAAu8; 64]));
672+
response.insert(0x08, Value::Map(ueo.clone()));
673+
674+
let bytes = crate::proto::ctap2::cbor::to_vec(&response).unwrap();
675+
let parsed: Ctap2GetAssertionResponse =
676+
crate::proto::ctap2::cbor::from_slice(&bytes).unwrap();
677+
678+
assert_eq!(parsed.unsigned_extension_outputs, Some(ueo));
679+
}
663680
}

0 commit comments

Comments
 (0)