Skip to content

Commit ee077e7

Browse files
feat(ctap2): enforce getInfo request limits (#278)
Authenticators report size and credential-list limits that the client did not act on, so oversized requests failed at the device with opaque errors. This keeps requests within the reported message size, drops credential ids too long to belong to the device, and reports an over-count list clearly. The checks run on both the preflight and direct paths. Closes #254.
1 parent 9187409 commit ee077e7

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)