// signer.mbt — Signing requests and responses (RFC 9421 §3.1).
//
// The signer builds the Signature-Input entry, constructs the signature base,
// invokes the algorithm, and produces the two header field values. It never
// mutates the request or response it was given.
///|
/// Options for producing one signature.
pub(all) struct SignOptions {
/// The signature label.
label : String
/// The ordered covered components.
components : Array[CoveredComponent]
/// The signature metadata parameters.
parameters : SignatureParameters
/// The requested algorithm name (e.g. `hmac-sha256`).
algorithm : String
/// The key record whose material is used to sign.
key : KeyRecord
}
///|
/// The produced signature fields.
pub(all) struct SignedFields {
/// The value for the `Signature-Input` header.
signature_input : String
/// The value for the `Signature` header.
signature : String
/// The exact signature base that was signed.
signature_base : SignatureBase
}
///|
/// Signs a request message.
pub fn sign_request(
request : RequestContext,
options : SignOptions,
limits : Limits,
) -> Result[SignedFields, HsError] {
sign_target(TargetRequest(request), options, limits)
}
///|
/// Signs a response message.
pub fn sign_response(
response : ResponseContext,
options : SignOptions,
limits : Limits,
) -> Result[SignedFields, HsError] {
sign_target(TargetResponse(response), options, limits)
}
///|
/// Signs any target message.
pub fn sign_target(
target : SignTarget,
options : SignOptions,
limits : Limits,
) -> Result[SignedFields, HsError] {
try {
let fields = sign_target_raise(target, options, limits)
Ok(fields)
} catch {
e => Err(e)
}
}
///|
/// Internal: signs a target, raising on failure.
fn sign_target_raise(
target : SignTarget,
options : SignOptions,
limits : Limits,
) -> SignedFields raise HsError {
validate_sign_options(options, limits)
if options.components.length() > limits.max_components_per_signature {
raise hs_error(Signing, TooManyComponents, "too many covered components")
}
let entry : SignatureInputEntry = {
label: options.label,
covered_components: options.components,
parameters: options.parameters,
}
let base = match build_signature_base(target, entry, limits) {
Ok(b) => b
Err(e) => raise e
}
let provider = algorithm_provider(options.algorithm)
let signature = match provider.sign(base.bytes, options.key.material) {
Ok(sig) => sig
Err(e) => raise e
}
let input_field = match serialize_signature_input({ entries: [entry] }) {
Ok(s) => s
Err(e) => raise e
}
let sig_field = match
serialize_signature_field({
entries: [{ label: options.label, value: signature }],
}) {
Ok(s) => s
Err(e) => raise e
}
{ signature_input: input_field, signature: sig_field, signature_base: base }
}
///|
/// Validates signer options: non-empty label, non-empty components, and a
/// key whose algorithm matches the requested algorithm.
fn validate_sign_options(
options : SignOptions,
limits : Limits,
) -> Unit raise HsError {
if options.label.is_empty() {
raise hs_error(Signing, InvalidSignatureInput, "label must not be empty")
}
if options.components.is_empty() {
raise hs_error(Signing, InvalidCoveredComponent, "no covered components")
}
limits.check_keyid_length(options.key.keyid)
if options.key.algorithm != options.algorithm {
raise hs_error(
Signing,
AlgorithmMismatch,
"key algorithm " +
options.key.algorithm +
" does not match requested " +
options.algorithm,
)
}
}
///|
/// Builds an algorithm provider for a name. `hmac-sha256` is implemented;
/// every other registered name resolves to `UnsupportedAlgorithm`, which
/// refuses to sign rather than producing a fake signature.
pub fn algorithm_provider(algorithm : String) -> SignatureAlgorithmProvider {
if algorithm == "hmac-sha256" {
Hmac(HmacSha256)
} else {
Unsupported(UnsupportedAlgorithm::new(algorithm))
}
}