// limits.mbt — Resource limits for HTTP message signature processing.
//
// RFC 9421 defines no explicit input size limits, but an implementation that
// lets untrusted inputs drive unbounded allocation is not production safe.
// Every public entry point that parses or canonicalizes untrusted data checks
// the applicable limit before doing significant work.
///|
/// Hard resource limits applied while parsing and canonicalizing messages.
pub(all) struct Limits {
/// Maximum number of header/trailer fields in one message.
max_header_count : Int
/// Maximum length (UTF-8 bytes) of a single header name.
max_header_name_bytes : Int
/// Maximum length (UTF-8 bytes) of a single header value.
max_header_value_bytes : Int
/// Maximum serialized length of a Signature-Input or Signature field.
max_signature_field_bytes : Int
/// Maximum number of signature entries in one message.
max_signature_count : Int
/// Maximum number of covered components in a single signature.
max_components_per_signature : Int
/// Maximum number of parameters on a single item/inner-list/dictionary entry.
max_parameter_count : Int
/// Maximum length (UTF-8 bytes) of a keyid string.
max_keyid_bytes : Int
/// Maximum length (UTF-8 bytes) of a nonce string.
max_nonce_bytes : Int
/// Maximum length (UTF-8 bytes) of a tag string.
max_tag_bytes : Int
/// Maximum body size (bytes) that is eligible for Content-Digest binding.
max_body_bytes_for_digest : Int
}
///|
/// A conservative set of limits intended for production use.
pub fn Limits::default() -> Limits {
{
max_header_count: 128,
max_header_name_bytes: 256,
max_header_value_bytes: 16384,
max_signature_field_bytes: 32768,
max_signature_count: 16,
max_components_per_signature: 64,
max_parameter_count: 32,
max_keyid_bytes: 1024,
max_nonce_bytes: 256,
max_tag_bytes: 256,
max_body_bytes_for_digest: 16 * 1024 * 1024,
}
}
///|
/// A stricter profile for high-security deployments.
pub fn Limits::strict() -> Limits {
{
max_header_count: 64,
max_header_name_bytes: 256,
max_header_value_bytes: 8192,
max_signature_field_bytes: 16384,
max_signature_count: 8,
max_components_per_signature: 32,
max_parameter_count: 16,
max_keyid_bytes: 256,
max_nonce_bytes: 128,
max_tag_bytes: 128,
max_body_bytes_for_digest: 8 * 1024 * 1024,
}
}
///|
/// A permissive profile used only by tests and examples. Do not use in
/// production: it deliberately relaxes every bound.
pub fn Limits::permissive_for_tests() -> Limits {
{
max_header_count: 512,
max_header_name_bytes: 4096,
max_header_value_bytes: 65536,
max_signature_field_bytes: 262144,
max_signature_count: 64,
max_components_per_signature: 256,
max_parameter_count: 128,
max_keyid_bytes: 8192,
max_nonce_bytes: 2048,
max_tag_bytes: 2048,
max_body_bytes_for_digest: 256 * 1024 * 1024,
}
}
///|
/// Checks that a serialized signature field fits within the configured bound.
pub fn Limits::check_signature_field_size(
self : Limits,
len : Int,
what : String,
) -> Unit raise HsError {
if len > self.max_signature_field_bytes {
raise hs_error(
StructuredFieldParsing,
InputTooLarge,
what + " too large: " + len.to_string() + " bytes",
)
}
}
///|
/// Checks a body size against the Content-Digest bound.
pub fn Limits::check_body_size_for_digest(
self : Limits,
len : Int,
) -> Unit raise HsError {
if len > self.max_body_bytes_for_digest {
raise hs_error(
DigestBinding,
InputTooLarge,
"body too large for digest binding: " + len.to_string() + " bytes",
)
}
}
///|
/// Checks a keyid length against the configured bound.
pub fn Limits::check_keyid_length(
self : Limits,
keyid : String,
) -> Unit raise HsError {
let len = @utf8.encode(keyid).length()
if len > self.max_keyid_bytes {
raise hs_error(
PolicyValidation,
KeyIdTooLong,
"keyid too long: " + len.to_string() + " bytes",
)
}
}
///|
/// Checks a nonce length against the configured bound.
pub fn Limits::check_nonce_length(
self : Limits,
nonce : String,
) -> Unit raise HsError {
let len = @utf8.encode(nonce).length()
if len > self.max_nonce_bytes {
raise hs_error(
PolicyValidation,
NonceTooLong,
"nonce too long: " + len.to_string() + " bytes",
)
}
}
///|
/// Checks a tag length against the configured bound.
pub fn Limits::check_tag_length(
self : Limits,
tag : String,
) -> Unit raise HsError {
let len = @utf8.encode(tag).length()
if len > self.max_tag_bytes {
raise hs_error(
PolicyValidation,
InvalidTag,
"tag too long: " + len.to_string() + " bytes",
)
}
}