Skip to content

Commit 3654ab6

Browse files
feat(ctap2): enforce authenticatorGetInfo credential-list and message-size limits
The maxMsgSize, maxCredentialCountInList and maxCredentialIdLength fields from authenticatorGetInfo were parsed but never read. Enforce them before sending make-credential and get-assertion requests on both the preflight and non-preflight paths so cable is covered. Drop allow and exclude entries whose id exceeds maxCredentialIdLength, reject lists over maxCredentialCountInList, and bound the serialized request by maxMsgSize using the 1024-byte default when the field is absent. A new PlatformError::RequestTooLarge surfaces these cases instead of relying on the authenticator CTAP status.
1 parent 9187409 commit 3654ab6

3 files changed

Lines changed: 383 additions & 4 deletions

File tree

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

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,29 @@ pub struct Ctap2GetInfoResponse {
155155
pub max_pin_length: Option<u32>,
156156
}
157157

158+
/// CTAP 2.1/2.2 6.4: platforms assume 1024 bytes when maxMsgSize is absent.
159+
pub const DEFAULT_MAX_MSG_SIZE: usize = 1024;
160+
158161
impl Ctap2GetInfoResponse {
162+
/// maxMsgSize (0x05), defaulting to 1024 bytes when the device omits it or advertises 0.
163+
pub fn max_msg_size(&self) -> usize {
164+
// A device may report 0, which cannot bound any real message; treat it as unset.
165+
self.max_msg_size
166+
.filter(|&size| size > 0)
167+
.map(|size| size as usize)
168+
.unwrap_or(DEFAULT_MAX_MSG_SIZE)
169+
}
170+
171+
/// maxCredentialCountInList (0x07), or None when the device sets no limit.
172+
pub fn max_credential_count_in_list(&self) -> Option<usize> {
173+
self.max_credential_count.map(|count| count as usize)
174+
}
175+
176+
/// maxCredentialIdLength (0x08), or None when the device sets no limit.
177+
pub fn max_credential_id_length(&self) -> Option<usize> {
178+
self.max_credential_id_length.map(|len| len as usize)
179+
}
180+
159181
/// Only checks if the option exists, i.e. is not None
160182
/// but does not check if the option is enabled (true)
161183
/// or disabled (false)
@@ -607,6 +629,49 @@ mod test {
607629
);
608630
}
609631

632+
#[test]
633+
fn max_msg_size_defaults_to_1024_when_absent() {
634+
let info = Ctap2GetInfoResponse::default();
635+
assert_eq!(info.max_msg_size(), super::DEFAULT_MAX_MSG_SIZE);
636+
assert_eq!(info.max_msg_size(), 1024);
637+
}
638+
639+
#[test]
640+
fn max_msg_size_uses_advertised_value() {
641+
let info = Ctap2GetInfoResponse {
642+
max_msg_size: Some(2048),
643+
..Default::default()
644+
};
645+
assert_eq!(info.max_msg_size(), 2048);
646+
}
647+
648+
#[test]
649+
fn max_msg_size_treats_zero_as_default() {
650+
let info = Ctap2GetInfoResponse {
651+
max_msg_size: Some(0),
652+
..Default::default()
653+
};
654+
assert_eq!(info.max_msg_size(), super::DEFAULT_MAX_MSG_SIZE);
655+
}
656+
657+
#[test]
658+
fn credential_count_and_id_length_are_unbounded_when_absent() {
659+
let info = Ctap2GetInfoResponse::default();
660+
assert_eq!(info.max_credential_count_in_list(), None);
661+
assert_eq!(info.max_credential_id_length(), None);
662+
}
663+
664+
#[test]
665+
fn credential_count_and_id_length_use_advertised_values() {
666+
let info = Ctap2GetInfoResponse {
667+
max_credential_count: Some(8),
668+
max_credential_id_length: Some(64),
669+
..Default::default()
670+
};
671+
assert_eq!(info.max_credential_count_in_list(), Some(8));
672+
assert_eq!(info.max_credential_id_length(), Some(64));
673+
}
674+
610675
#[test]
611676
fn device_pin_uv_auth_token_without_client_pin_does_not_panic() {
612677
let info = create_info(&[("pinUvAuthToken", true)]);

0 commit comments

Comments
 (0)