moon-httpsig

RFC 9421 HTTP Message Signatures canonicalization, HMAC signing, verification, and policy toolkit for MoonBit.

http
signature
rfc9421
security
hmac
webhook
api
integrity
moonbit
moon add xiguaAp6y3/moon-httpsig@0.1.0
Download zip
Version
0.1.0
License
Apache-2.0
Last updated
6 days ago
Downloads
4

Dependencies

README

#moon-httpsig

RFC 9421 HTTP Message Signatures canonicalization, HMAC signing, verification, and policy toolkit for MoonBit.

Project/module: xiguaAp6y3/moon-httpsig Repository: https://github.com/xiguaAp6y3/moon-httpsig Version: 0.1.0 License: Apache-2.0

#中文项目介绍

moon-httpsig 是使用 MoonBit 从零实现的 RFC 9421(HTTP Message Signatures)工具库。它提供:

  • HTTP 消息模型:保序、可含同名多值、区分 Header/Trailer 的请求与响应模型;
  • Structured Field 子集:RFC 9651 的 Dictionary / Inner List / Item / Parameters 解析与规范序列化;
  • 组件解析@method@target-uri@authority@scheme @request-target@path@query@query-param@status 等派生组件, 以及普通 Header / Trailer / related request 字段组件(含 sfbs keytrreq 参数);
  • 签名基:按 RFC 9421 §2.5 逐字节构造签名基;
  • 算法:内置 hmac-sha256(基于成熟 SHA-256 依赖,按 RFC 2104 构造), 其余算法名只预留、不实现、不返回假的验签成功;
  • 验证策略:算法白名单、时间(created/expires/最大年龄/时钟偏移)、 required components、keyid、nonce、tag、Content-Digest 绑定、多签名策略;
  • CLIhttpsig-tool,输出稳定 JSON;
  • 可执行示例:签名/验签/响应签名/多签名/防重放/Content-Digest 绑定;
  • HTTP/1.1 文本适配器adapters/http11

#English Summary

moon-httpsig is a from-scratch MoonBit implementation of RFC 9421 (HTTP Message Signatures). It covers the HTTP message model, an RFC 9651 structured-field subset, covered-component resolution, byte-exact signature-base construction, an HMAC-SHA256 provider built on a mature SHA-256 dependency, verification policy, replay protection, and RFC 9530 Content-Digest binding. It is not a JWT library, HTTP server/client, TLS implementation, general cryptography library, or the legacy Cavage draft.

#RFC 9421 简介

RFC 9421 定义了“HTTP 消息签名”:签名方选择 HTTP 消息中若干“覆盖组件” (covered components),按规范格式逐行拼接成签名基,再对签名基做密码学运算, 把元数据写入 Signature-Input 字段、把签名值写入 Signature 字段。 Accept-Signature 用于协商签名请求。本项目的实现以 RFC 9421 及已核实的 勘误为准,并参考 RFC 9530(Digest Fields)、RFC 9651(Structured Field Values)、RFC 9110(HTTP Semantics)。

#项目价值

  • 为 MoonBit 提供 RFC 9421 的标准能力,作为 API/Webhook 消息完整性基础件;
  • 签名基逐字节与 RFC Appendix B 测试向量比对;
  • 结构化错误(HsErrorStage/HsErrorKind/offset/context),便于自动化处理;
  • 默认安全:算法白名单、时间校验、防重放、Content-Digest 绑定;
  • 不联网解析 keyid,不把 keyid 当作可信身份。

#功能支持矩阵

功能状态
HTTP 消息模型(请求/响应/Header/Trailer)已实现并测试
RFC 9651 子集解析与序列化已实现并测试
派生组件解析(含 @query-param、@status、req)已实现并测试
字段组件解析(sf/bs/key/tr/req)已实现并测试
签名基构造(逐字节)已实现并测试
HMAC-SHA256 签名/验签已实现并测试
常量时间比较已实现并测试
验证策略(白名单/时间/required/keyid/nonce/tag)已实现并测试
防重放(NonceStore)已实现并测试
Content-Digest 绑定已实现并测试
多签名策略已实现并测试
CLI httpsig-tool已实现并测试
6 个可执行示例已实现并测试
HTTP/1.1 文本适配器已实现并测试
rsa-pss-sha512 / ecdsa-* / ed25519未实现(返回 AlgorithmNotAllowed
远程 Key Resolver / JWKS / DID不支持(按设计禁止)

#不支持内容

本项目不是

  • JWT 库;
  • HTTP 服务器或客户端;
  • TLS 实现;
  • 通用密码学库;
  • 身份认证/授权平台;
  • 旧版 Cavage HTTP Signatures Draft 实现。

#本地使用方式

环境要求:MoonBit(含 wasm-gc / js / native 目标)、Python 3。

git clone https://github.com/xiguaAp6y3/moon-httpsig.git cd moon-httpsig moon add gmlewis/sha256 # 已写入 moon.mod,无需重复执行 moon test # 运行全部测试(默认目标) moon run cmd/httpsig-tool -- --help

三目标验证:

moon check --target wasm-gc && moon build --target wasm-gc && moon test --target wasm-gc moon check --target js && moon build --target js && moon test --target js moon check --target native && moon build --target native && moon test --target native

一键验证脚本:

powershell -ExecutionPolicy Bypass -File scripts\verify_all.ps1

#请求签名

let options : SignOptions = {
label: "sig1",
components: [
covered_derived(DerivedComponent::Method),
covered_field("content-type"),
],
parameters: params, // created/keyid/...
algorithm: "hmac-sha256",
key,
}
let signed = sign_request(request, options, Limits::default()).unwrap()
// signed.signature_input / signed.signature / signed.signature_base

#请求验签

let report = verify_request(
request,
signed.signature_input,
signed.signature,
resolver, // InMemoryKeyResolver(生产环境应替换为共享存储)
policy, // VerificationPolicy
clock, // FixedClock / SystemClock
nonces, // InMemoryNonceStore
Limits::default(),
MultiSignaturePolicy::AnyValid,
).unwrap()
// report.verified / report.rejected

#响应签名

let req_component = covered_derived_with_params(
DerivedComponent::Method,
{ sf: false, key: None, bs: false, tr: false, req: true, name: None },
)
let signed = sign_response(response, options_with(req_component), Limits::default()).unwrap()

#多签名

MultiSignaturePolicy::{ AnyValid, AllPresentValid, SpecificLabel(label) } 决定多个签名标签的接受条件;VerificationReport 同时给出 verified 与 rejected 明细。

#HMAC 安全说明

  • hmac-sha256 是共享密钥方案,不提供公钥验签;密钥分发与管理由应用负责。
  • MAC 比较使用 constant_time_equal,禁止用 == 直接比较。
  • 本库不实现 SHA-256,而是使用成熟依赖 gmlewis/sha256(Apache-2.0)。
  • 详见 docs/security.md

#Content-Digest

HTTP 消息签名本身不保护 Body。只有同时满足:消息带 Content-Digest digest 已验证、且 content-digest 被覆盖组件包含,才能声称 Body 被绑定。 策略字段:require_content_digestrequire_content_digest_covered

#CLI

moon run cmd/httpsig-tool -- --help moon run cmd/httpsig-tool -- --version moon run cmd/httpsig-tool -- parse-input --signature-input 'sig1=("@method" "@target-uri");created=1618884473;keyid="k1"' moon run cmd/httpsig-tool -- parse-signature --signature 'sig1=:dGVzdA==:' moon run cmd/httpsig-tool -- parse-accept --accept-signature 'a=("@method");created;keyid="k"' moon run cmd/httpsig-tool -- build-base --method POST --path /foo --header 'content-type=application/json' --signature-input 'sig1=("@method" "content-type");created=1618884473;keyid="k1";alg="hmac-sha256"' moon run cmd/httpsig-tool -- sign-hmac --method POST --path /foo --header 'content-type=application/json' --secret-hex 736563726574 --keyid k1 --created 1700000000 --component @method moon run cmd/httpsig-tool -- verify-hmac --method POST --path /foo --header 'content-type=application/json' --secret-hex 736563726574 --keyid k1 --now 1700000000 --signature-input 'sig1=("@method");created=1700000000;keyid="k1";alg="hmac-sha256"' --signature 'sig1=:...:' moon run cmd/httpsig-tool -- inspect --signature-input 'sig1=("@method");created=1618884473;keyid="k1"' moon run cmd/httpsig-tool -- check-policy --signature-input 'sig1=("@method");created=1618884473;keyid="k1"' --required-component @method moon run cmd/httpsig-tool -- rfc-example

CLI 输出稳定 JSON,详见 docs/cli-reference.md

#Examples

moon run examples/sign_request moon run examples/verify_request moon run examples/sign_response moon run examples/multiple_signatures moon run examples/replay_policy moon run examples/content_digest_binding

#测试结果

  • 具名测试:101 个(根目录 94 + 适配器 7);
  • 表格案例:91 个表驱动条目;
  • 确定性属性测试:1100 组固定种子 sign→verify 循环(1000 组完整循环 + 100 组覆盖/未覆盖变更判定);
  • RFC 9421 Appendix B 的 HMAC 示例与签名基示例逐字节通过;
  • wasm-gcjsnative 三目标:check/build/test 均通过,0 errors, 0 warnings(reserved_keyword 因规范要求保留 method 字段名而被抑制)。

#目录结构

├── cmd/httpsig-tool/ CLI ├── adapters/http11/ HTTP/1.1 文本适配器 ├── docs/ 文档 ├── examples/ 6 个可执行示例 ├── scripts/ 行数统计、fixture 生成/校验、一键验证 ├── testdata/rfc9421/ RFC 测试向量 ├── moon.mod / moon.pkg └── *.mbt 核心库

#Roadmap

  • v0.1.0(当前):RFC 9421 核心、HMAC、策略、CLI、测试。
  • v0.2.0:完整 RFC 9651 Adapter、更多 HTTP Adapter。
  • v0.3.0:Ed25519 Provider 与远程 Resolver 示例。
  • v0.4.0:Webhook、ActivityPub、API Gateway Profile。

这些是未来计划,尚未完成。详见 docs/roadmap.md

#License

Apache-2.0,见 LICENSE

#发布状态

截至项目立项时的公开生态检索,未发现完整的 MoonBit RFC 9421 HTTP Message Signatures 实现。这不是绝对保证,仅代表立项时检索到的公开信息。 项目已公开发布到 GitHub(xiguaAp6y3/moon-httpsig);尚未发布到 Mooncakes 包仓库,也未创建 Release。

#
Clock

pub(open) trait Clock {
fn now_unix_seconds(Self) -> Int64
}

Provides the current UNIX timestamp in whole seconds.

#
DigestBinding

pub(open) trait DigestBinding {
fn validate(Self, OrderedHeaders, Bytes) -> Result[Unit, HsError]
}

Validates that a message body is bound to the signature via Content-Digest.

#
KeyResolver

pub(open) trait KeyResolver {
fn resolve(Self, String) -> Result[KeyRecord, HsError]
}

The resolver interface: maps a keyid to a KeyRecord.

#
NonceStore

pub(open) trait NonceStore {
fn check_and_store(Self, keyid : String, nonce : String, expires : Int64?) -> Result[Unit, HsError]
}

The nonce store interface.

#
SignatureAlgorithm

pub(open) trait SignatureAlgorithm {
fn name(Self) -> String
fn sign(Self, Bytes, KeyMaterial) -> Result[Bytes, HsError]
fn verify(Self, Bytes, Bytes, KeyMaterial) -> Result[Bool, HsError]
}

The interface every signature algorithm must implement.

#
HsError

pub(all) suberror HsError {
HsError(HsErrorStage, HsErrorKind, Int, String)
}

A structured error returned by every public API.

  • offset() is a UTF-8 byte offset into the input being processed when the offset is meaningful, otherwise 0.
  • context() is a short, caller-visible description. It never contains keys, full signatures, full bodies, or huge header values.

#
HsError::context

fn HsError::context(self : HsError) -> String

The short diagnostic context string.

#
HsError::kind

fn HsError::kind(self : HsError) -> HsErrorKind

The error kind.

#
HsError::kind_name

fn HsError::kind_name(self : HsError) -> String

Returns the error kind name as a stable string.

#
HsError::offset

fn HsError::offset(self : HsError) -> Int

The UTF-8 byte offset, or 0 when not meaningful.

#
HsError::stage

fn HsError::stage(self : HsError) -> HsErrorStage

The processing stage of the error.

#
HsError::stage_name

fn HsError::stage_name(self : HsError) -> String

Returns the stage name as a stable string (used by CLI JSON output).

#
HsError::to_debug_string

fn HsError::to_debug_string(self : HsError) -> String

Renders the error as a short human-readable line.

#
AcceptSignature

pub(all) struct AcceptSignature {
entries : Array[AcceptSignatureEntry]
}

The parsed Accept-Signature field.

#
AcceptSignature::new

Creates an empty Accept-Signature field.

#
AcceptSignatureEntry

pub(all) struct AcceptSignatureEntry {
label : String
covered_components : Array[CoveredComponent]
request : AcceptSignatureRequest
}

A single Accept-Signature entry.

#
AcceptSignatureRequest

pub(all) struct AcceptSignatureRequest {
request_created : Bool
request_expires : Bool
keyid : String?
alg : String?
nonce : String?
tag : String?
extensions : Array[SfParameter]
}

The signature-request parameters of one Accept-Signature entry.

#
CallbackDigestBinding

pub struct CallbackDigestBinding {
callback : (OrderedHeaders, Bytes) -> Result[Unit, HsError]
}

A binding that delegates validation to a callback.

#
CallbackDigestBinding::new

fn CallbackDigestBinding::new(callback : (OrderedHeaders, Bytes) -> Result[Unit, HsError]) -> CallbackDigestBinding

Constructs a CallbackDigestBinding.

#
ComponentParameters

pub(all) struct ComponentParameters {
sf : Bool
key : String?
bs : Bool
tr : Bool
req : Bool
name : String?
}

Component parameters attached to a covered component.

#
ComponentParameters::default

The default component parameters (all unset).

#
CoveredComponent

pub(all) enum CoveredComponent {
Derived(DerivedComponent, ComponentParameters)
Field(FieldComponent)
}

A covered component: derived or field.

Derived components carry their parameters so that req (and name for @query-param) survives parsing and is emitted in the signature base (RFC 9421 §2.4).

#
CoveredComponent::as_derived

Returns the derived component if this is a derived component.

#
CoveredComponent::as_field

Returns the field component if this is a field component.

#
CoveredComponent::field_parameters

fn CoveredComponent::field_parameters(self : CoveredComponent) -> ComponentParameters?

Returns the parameters of the component if it is a field component.

#
CoveredComponent::identifier

fn CoveredComponent::identifier(self : CoveredComponent) -> String

Returns the bare (unquoted) identifier used for internal matching, e.g. @method, content-type, @query-param.

#
CoveredComponent::query_param_name

fn CoveredComponent::query_param_name(self : CoveredComponent) -> String?

Returns the query-param name if this is @query-param, else None.

#
CoveredComponent::to_component_string

fn CoveredComponent::to_component_string(self : CoveredComponent) -> Result[String, HsError]

Returns the canonical component identifier string used in the signature base (RFC 9421 §2.5). Every component name is serialized as an sf-string, so the identifier is double-quoted; parameters follow the quotes. Examples: "@method", "content-type";sf, "@query-param";name="foo".

#
DerivedComponent

pub(all) enum DerivedComponent {
SignatureParams
Method
TargetUri
Authority
Scheme
RequestTarget
Path
Query
QueryParam(String)
Status
}

A derived component computed from the message. QueryParam carries the parameter name from its name parameter.

#
FieldComponent

pub(all) struct FieldComponent {
name : String
parameters : ComponentParameters
}

A field component: an HTTP header/trailer name plus its parameters.

#
FixedClock

pub struct FixedClock {
fixed : Int64
}

A clock pinned to a fixed timestamp (for tests and reproducible examples).
impl Clock for FixedClock

#
FixedClock::new

fn FixedClock::new(timestamp : Int64) -> FixedClock

Constructs a clock fixed at timestamp.

#
FixedClock::timestamp

fn FixedClock::timestamp(self : FixedClock) -> Int64

Returns the fixed timestamp.

#
HeaderField

pub(all) struct HeaderField {
name : String
value : String
trailer : Bool
}

A single HTTP field instance (header or trailer line).

#
HmacSha256

pub enum HmacSha256 {
HmacSha256
}

The HMAC-SHA256 algorithm provider.

#
HmacSha256::algorithm_name

fn HmacSha256::algorithm_name(_self : HmacSha256) -> String

Returns the registered algorithm name.

#
HmacSha256::new

fn HmacSha256::new() -> HmacSha256

Constructs the HMAC-SHA256 provider.

#
HmacSha256::sign_hmac

fn HmacSha256::sign_hmac(_self : HmacSha256, message : Bytes, key : Bytes) -> Bytes

Signs a message with a shared secret.

#
HmacSha256::verify_hmac

fn HmacSha256::verify_hmac(_self : HmacSha256, message : Bytes, signature : Bytes, key : Bytes) -> Bool

Verifies a MAC with a shared secret in constant time.

#
HsErrorKind

pub(all) enum HsErrorKind {
UnexpectedEnd
UnexpectedByte(Byte)
InvalidHeaderName
InvalidHeaderValue
InvalidMethod
InvalidScheme
InvalidAuthority
InvalidPath
InvalidStatus
DuplicateLabel
MissingSignatureInput
MissingSignature
LabelMismatch
InvalidSignatureInput
InvalidSignatureField
InvalidAcceptSignature
InvalidCoveredComponent
UnsupportedDerivedComponent
UnsupportedComponentParameter
MissingComponent
InvalidComponentCombination
InvalidStructuredField
InvalidBase64
InvalidInteger
InvalidTimestamp
CreatedInFuture
SignatureExpired
SignatureTooOld
MissingKeyId
KeyIdTooLong
KeyNotFound
AlgorithmMissing
AlgorithmNotAllowed
AlgorithmMismatch
InvalidKeyMaterial
SignatureMismatch
MissingRequiredComponent
DuplicateNonce
NonceRequired
NonceTooLong
InvalidTag
ContentDigestRequired
ContentDigestNotCovered
ContentDigestInvalid
InputTooLarge
TooManyHeaders
TooManySignatures
TooManyComponents
TooManyParameters
SerializationFailure
}

The concrete error category. Stable across versions so callers can switch on it without string matching.

#
HsErrorStage

pub(all) enum HsErrorStage {
MessageConstruction
StructuredFieldParsing
SignatureInputParsing
SignatureFieldParsing
ComponentResolution
SignatureBaseConstruction
Signing
KeyResolution
PolicyValidation
CryptographicVerification
ReplayProtection
DigestBinding
}

The processing stage in which an error was detected. Used to answer the question "where did this fail?" at a glance.

#
InMemoryKeyResolver

pub struct InMemoryKeyResolver {
keys : Array[KeyRecord]
}

An in-memory key resolver backed by an ordered list of records.

#
InMemoryKeyResolver::add

fn InMemoryKeyResolver::add(self : InMemoryKeyResolver, record : KeyRecord, limits : Limits) -> Result[Unit, HsError]

Adds or replaces a key record.

A keyid longer than limits.max_keyid_bytes is rejected.

#
InMemoryKeyResolver::get

fn InMemoryKeyResolver::get(self : InMemoryKeyResolver, keyid : String) -> KeyRecord?

Returns the key record for a keyid, or None.

#
InMemoryKeyResolver::length

fn InMemoryKeyResolver::length(self : InMemoryKeyResolver) -> Int

Returns the number of stored keys.

#
InMemoryKeyResolver::new

Constructs an empty in-memory resolver.

#
InMemoryKeyResolver::remove

fn InMemoryKeyResolver::remove(self : InMemoryKeyResolver, keyid : String) -> Unit

Removes a key by keyid.

#
InMemoryNonceStore

pub struct InMemoryNonceStore {
entries : Array[NonceEntry]
}

An in-memory nonce store. Not safe for multi-process use.

#
InMemoryNonceStore::length

fn InMemoryNonceStore::length(self : InMemoryNonceStore) -> Int

Returns the number of stored nonces.

#
InMemoryNonceStore::new

Constructs an empty in-memory nonce store.

#
InMemoryNonceStore::prune

fn InMemoryNonceStore::prune(self : InMemoryNonceStore, now : Int64) -> Int

Removes entries whose expires time is in the past (relative to now). Returns the number of entries removed.

#
KeyMaterial

pub(all) enum KeyMaterial {
SharedSecret(Bytes)
ExternalKey(String)
}

Key material for a signature algorithm.

#
KeyRecord

pub(all) struct KeyRecord {
keyid : String
algorithm : String
material : KeyMaterial
}

A resolved key record.

#
Limits

pub(all) struct Limits {
max_header_count : Int
max_header_name_bytes : Int
max_header_value_bytes : Int
max_signature_field_bytes : Int
max_signature_count : Int
max_components_per_signature : Int
max_parameter_count : Int
max_keyid_bytes : Int
max_nonce_bytes : Int
max_tag_bytes : Int
max_body_bytes_for_digest : Int
}

Hard resource limits applied while parsing and canonicalizing messages.

#
Limits::check_body_size_for_digest

fn Limits::check_body_size_for_digest(self : Limits, len : Int) -> Unit raise HsError

Checks a body size against the Content-Digest bound.

#
Limits::check_keyid_length

fn Limits::check_keyid_length(self : Limits, keyid : String) -> Unit raise HsError

Checks a keyid length against the configured bound.

#
Limits::check_nonce_length

fn Limits::check_nonce_length(self : Limits, nonce : String) -> Unit raise HsError

Checks a nonce length against the configured bound.

#
Limits::check_signature_field_size

fn Limits::check_signature_field_size(self : Limits, len : Int, what : String) -> Unit raise HsError

Checks that a serialized signature field fits within the configured bound.

#
Limits::check_tag_length

fn Limits::check_tag_length(self : Limits, tag : String) -> Unit raise HsError

Checks a tag length against the configured bound.

#
Limits::default

fn Limits::default() -> Limits

A conservative set of limits intended for production use.

#
Limits::permissive_for_tests

fn Limits::permissive_for_tests() -> Limits

A permissive profile used only by tests and examples. Do not use in production: it deliberately relaxes every bound.

#
Limits::strict

fn Limits::strict() -> Limits

A stricter profile for high-security deployments.

#
MultiSignaturePolicy

pub(all) enum MultiSignaturePolicy {
AnyValid
AllPresentValid
SpecificLabel(String)
}

How multiple signatures must relate for the overall verification to succeed.

#
NoDigestBinding

pub enum NoDigestBinding {
NoDigestBinding
}

A binding that never validates the body. Use when the application has already validated Content-Digest elsewhere or does not need body binding.

#
NoDigestBinding::new

Constructs a NoDigestBinding.

#
NonceEntry

type NonceEntry

A recorded nonce with an optional expiration.

#
OrderedHeaders

pub(all) struct OrderedHeaders {
fields : Array[HeaderField]
}

A wire-ordered list of header and trailer fields.

#
OrderedHeaders::append

fn OrderedHeaders::append(self : OrderedHeaders, name : String, value : String) -> Result[Unit, HsError]

Appends a normal header field. trailer is set to false.

#
OrderedHeaders::append_field

fn OrderedHeaders::append_field(self : OrderedHeaders, field : HeaderField) -> Result[Unit, HsError]

Appends a field carrying an explicit trailer flag. Callers with raw wire data use this to preserve the header/trailer distinction.

#
OrderedHeaders::append_trailer

fn OrderedHeaders::append_trailer(self : OrderedHeaders, name : String, value : String) -> Result[Unit, HsError]

Appends a trailer field (trailer = true).

#
OrderedHeaders::combined_value

fn OrderedHeaders::combined_value(self : OrderedHeaders, name : String) -> Result[String, HsError]

Combines multiple field lines for a single name per RFC 9110 §5.2.

For most fields, RFC 9110 defines a "combined value" by joining the field values with ", " — but only when the field is a list-based field whose grammar permits it. Some fields (notably set-cookie) must never be combined. This function applies the safe default ", " and rejects set-cookie, which requires an explicit per-field policy.

#
OrderedHeaders::combined_value_with

fn OrderedHeaders::combined_value_with(self : OrderedHeaders, name : String, joiner : String) -> Result[String, HsError]

Combines multiple field lines with the caller-supplied joiner. This is the escape hatch for fields (like set-cookie) whose combination rule is not the RFC 9110 default.

#
OrderedHeaders::contains

fn OrderedHeaders::contains(self : OrderedHeaders, name : String) -> Bool

Returns true if at least one field with the given name exists.

#
OrderedHeaders::entries

Returns all fields in wire order (including trailers).

#
OrderedHeaders::get_all

fn OrderedHeaders::get_all(self : OrderedHeaders, name : String) -> Array[String]

Returns all field values with the given name in wire order.

#
OrderedHeaders::get_first

fn OrderedHeaders::get_first(self : OrderedHeaders, name : String) -> String?

Returns the first field value with the given name, or None if absent.

#
OrderedHeaders::header_values

fn OrderedHeaders::header_values(self : OrderedHeaders) -> Array[(String, String)]

Returns all non-trailer header values keyed by lower-cased name, in wire order, with duplicates preserved.

#
OrderedHeaders::length

fn OrderedHeaders::length(self : OrderedHeaders) -> Int

Returns the number of stored fields.

#
OrderedHeaders::new

Creates an empty header set.

#
OrderedHeaders::remove

fn OrderedHeaders::remove(self : OrderedHeaders, name : String) -> Unit

Removes every field with the given name (case-insensitive).

#
OrderedHeaders::set

fn OrderedHeaders::set(self : OrderedHeaders, name : String, value : String) -> Result[Unit, HsError]

Replaces every field with the given name (case-insensitive) with a single field carrying value, preserving its position and the first instance's trailer flag. If no field matches, value is appended as a header.

#
OrderedHeaders::trailer_values

fn OrderedHeaders::trailer_values(self : OrderedHeaders) -> Array[(String, String)]

Returns all trailer values keyed by lower-cased name, in wire order.

#
Prng

pub struct Prng {
state : Int64
}

A deterministic pseudo-random generator (LCG).

#
Prng::generate_request

fn Prng::generate_request(self : Prng, case : Int) -> RequestContext

Generates a request message for a property-test case.

#
Prng::generate_sign_options

fn Prng::generate_sign_options(_self : Prng, case : Int, components : Array[CoveredComponent]) -> SignOptions

Generates signing options for a property-test case.

#
Prng::new

fn Prng::new(seed : Int64) -> Prng

Constructs a generator from a seed.

#
Prng::next_bool

fn Prng::next_bool(self : Prng) -> Bool

Returns a random boolean.

#
Prng::next_int

fn Prng::next_int(self : Prng, bound : Int) -> Int

Returns a value in 0..bound.

#
Prng::next_u32

fn Prng::next_u32(self : Prng) -> Int

Advances the generator and returns a 32-bit pseudo-random value.

#
Prng::pick

fn Prng::pick(self : Prng, choices : Array[String]) -> String

Picks a random element from a list.

#
Prng::pick_components

fn Prng::pick_components(self : Prng) -> Array[CoveredComponent]

Picks one of several covered-component templates.

#
RejectedSignature

pub(all) struct RejectedSignature {
label : String?
error : HsError
}

A signature that was rejected, with the reason.

#
RequestContext

pub(all) struct RequestContext {
method : String
scheme : String
authority : String
path : String
query : String?
headers : OrderedHeaders
body : Bytes?
}

An HTTP request message captured for signature canonicalization.

#
RequestContext::authority_component

fn RequestContext::authority_component(self : RequestContext) -> String

The @authority component value (verbatim).

#
RequestContext::has_query

fn RequestContext::has_query(self : RequestContext) -> Bool

Returns true if the request has a query (including an empty one), which is distinct from "no query" for RFC 9421 @query canonicalization.

#
RequestContext::new

fn RequestContext::new(method : String, scheme : String, authority : String, path : String, query : String?, headers : OrderedHeaders, body : Bytes?, limits : Limits) -> Result[RequestContext, HsError]

Validates and constructs a RequestContext.

The constructor performs the full validation suite: method token, scheme grammar, CR/LF hygiene on authority/path/query, header safety, and body size (when a body is present and limits is provided).

#
RequestContext::new_default

fn RequestContext::new_default(method : String, scheme : String, authority : String, path : String, query : String?, headers : OrderedHeaders, body : Bytes?) -> Result[RequestContext, HsError]

Constructs a RequestContext using the default limits.

#
RequestContext::path_component

fn RequestContext::path_component(self : RequestContext) -> String

The @path component value (verbatim).

#
RequestContext::query_component

fn RequestContext::query_component(self : RequestContext) -> String

The @query component value: the entire query string including the leading ? (RFC 9421 §2.2.7). When the query is absent, the value is a lone ?.

#
RequestContext::raw_query

fn RequestContext::raw_query(self : RequestContext) -> String

The raw query string without the leading ?, or the empty string when the request has no query.

#
RequestContext::request_target

fn RequestContext::request_target(self : RequestContext) -> String

Builds the @request-target value: method SP request-target.

The request-target is the origin-form path?query (with an empty path represented as empty), preserved verbatim.

#
RequestContext::target_uri

fn RequestContext::target_uri(self : RequestContext) -> String

Builds the @target-uri value: scheme://authority/path?query.

The components are concatenated verbatim. In particular the path is used as-is (including an empty path when the request-target was * or OPTIONS *); no percent-encoding or normalization is applied.

#
RequireCoveredContentDigest

pub struct RequireCoveredContentDigest {
covered : Array[CoveredComponent]
limits : Limits
}

A binding that requires Content-Digest to be present, to match the body, and to be covered by the covered components. Content-Digest values are RFC 9651 Dictionaries (sha-256=:base64:); the first recognized digest is checked and must match.

#
RequireCoveredContentDigest::new

Constructs a RequireCoveredContentDigest over the given covered components.

#
ResponseContext

pub(all) struct ResponseContext {
status : Int
headers : OrderedHeaders
body : Bytes?
related_request : RequestContext?
}

An HTTP response message captured for signature canonicalization.

related_request is optional and enables the req component parameter on response signatures (RFC 9421 §3.2).

#
ResponseContext::new

fn ResponseContext::new(status : Int, headers : OrderedHeaders, body : Bytes?, related_request : RequestContext?, limits : Limits) -> Result[ResponseContext, HsError]

Validates and constructs a ResponseContext.

Status codes must be in 100..=999 and header/body rules match requests.

#
ResponseContext::new_default

fn ResponseContext::new_default(status : Int, headers : OrderedHeaders, body : Bytes?, related_request : RequestContext?) -> Result[ResponseContext, HsError]

Constructs a ResponseContext using the default limits.

#
SfBareItem

pub(all) enum SfBareItem {
SfString(String)
SfToken(String)
SfInteger(Int64)
SfByteSequence(Bytes)
SfBoolean(Bool)
}

The "bare item" of a structured field item: the value without parameters.

#
SfDictionaryEntry

pub(all) struct SfDictionaryEntry {
key : String
value : SfMember
}

A single dictionary entry: a key plus its member value.

#
SfInnerList

pub(all) struct SfInnerList {
items : Array[SfItem]
parameters : Array[SfParameter]
}

A structured field inner list: (items) params.

#
SfItem

pub(all) struct SfItem {
value : SfBareItem
parameters : Array[SfParameter]
}

A structured field item: bare item plus its parameters.

#
SfItem::parameter

fn SfItem::parameter(self : SfItem, name : String) -> SfParameter?

Returns the parameter with the given name (exact case-sensitive match), or None.

#
SfMember

pub(all) enum SfMember {
ItemMember(SfItem)
InnerListMember(SfInnerList)
}

A dictionary member: either an item or an inner list.

#
SfParameter

pub(all) struct SfParameter {
name : String
value : SfBareItem
}

A single structured field parameter: name=value.

#
SignOptions

pub(all) struct SignOptions {
label : String
components : Array[CoveredComponent]
parameters : SignatureParameters
algorithm : String
key : KeyRecord
}

Options for producing one signature.

#
SignTarget

pub(all) enum SignTarget {
TargetRequest(RequestContext)
TargetResponse(ResponseContext)
}

The message being signed.

#
SignatureAlgorithmProvider

pub(all) enum SignatureAlgorithmProvider {
Hmac(HmacSha256)
Unsupported(UnsupportedAlgorithm)
}

A concrete, owned algorithm provider (either a built-in or an unsupported-algorithm marker). This is what the signer/verifier carry around so the SignatureAlgorithm trait can be used without lifetimes.

#
SignatureBase

pub(all) struct SignatureBase {
bytes : Bytes
text : String
lines : Array[String]
}

The constructed signature base.

#
SignatureEntry

pub(all) struct SignatureEntry {
label : String
value : Bytes
}

A single signature value: label plus raw signature bytes.

#
SignatureField

pub(all) struct SignatureField {
entries : Array[SignatureEntry]
}

The parsed Signature field: one entry per signature label.

#
SignatureField::length

fn SignatureField::length(self : SignatureField) -> Int

Returns the number of entries.

#
SignatureField::new

Creates an empty Signature field.

#
SignatureInput

pub(all) struct SignatureInput {
entries : Array[SignatureInputEntry]
}

The parsed Signature-Input field: one entry per signature label.

#
SignatureInput::new

Creates an empty Signature-Input field.

#
SignatureInputEntry

pub(all) struct SignatureInputEntry {
label : String
covered_components : Array[CoveredComponent]
parameters : SignatureParameters
}

A single signature's metadata from the Signature-Input field.

#
SignatureParameters

pub(all) struct SignatureParameters {
created : Int64?
expires : Int64?
keyid : String?
alg : String?
nonce : String?
tag : String?
extensions : Array[SfParameter]
}

The signature metadata parameters of one signature.

#
SignatureParameters::new

An empty parameter set.

#
SignedFields

pub(all) struct SignedFields {
signature_input : String
signature : String
signature_base : SignatureBase
}

The produced signature fields.

#
SystemClock

pub enum SystemClock {
SystemClock
}

A clock backed by the host's wall clock (@env.now, milliseconds since the Unix epoch, converted to whole seconds).

#
SystemClock::new

Constructs the system clock.

#
UnsupportedAlgorithm

pub struct UnsupportedAlgorithm {
algorithm : String
}

A provider that returns AlgorithmNotAllowed for every unimplemented algorithm name. Using this provider is safer than returning success.

#
UnsupportedAlgorithm::algorithm_name

fn UnsupportedAlgorithm::algorithm_name(self : UnsupportedAlgorithm) -> String

The algorithm name the marker stands for.

#
UnsupportedAlgorithm::new

fn UnsupportedAlgorithm::new(algorithm : String) -> UnsupportedAlgorithm

Constructs an UnsupportedAlgorithm marker for the given name.

#
UnsupportedAlgorithm::sign

fn UnsupportedAlgorithm::sign(self : UnsupportedAlgorithm, _message : Bytes, _key : KeyMaterial) -> Result[Bytes, HsError]

Returns AlgorithmNotAllowed for signing (unimplemented algorithm).

#
UnsupportedAlgorithm::verify

fn UnsupportedAlgorithm::verify(self : UnsupportedAlgorithm, _message : Bytes, _signature : Bytes, _key : KeyMaterial) -> Result[Bool, HsError]

Returns AlgorithmNotAllowed for verification (unimplemented algorithm).

#
VerificationPolicy

pub(all) struct VerificationPolicy {
allowed_algorithms : Array[String]
required_components : Array[CoveredComponent]
require_created : Bool
require_expires : Bool
require_keyid : Bool
require_nonce : Bool
max_signature_age_seconds : Int64?
allowed_clock_skew_seconds : Int64
max_future_seconds : Int64
expected_tag : String?
reject_unknown_parameters : Bool
require_content_digest : Bool
require_content_digest_covered : Bool
}

Application-level verification requirements.

#
VerificationPolicy::allows_algorithm

fn VerificationPolicy::allows_algorithm(self : VerificationPolicy, algorithm : String) -> Bool

Returns true if the algorithm is in the whitelist (or the whitelist is empty, which means no algorithm is acceptable).

#
VerificationPolicy::hmac_only

A policy that accepts only hmac-sha256 with reasonable defaults.

#
VerificationPolicy::permissive

A permissive policy that imposes no time/keyid/nonce requirements (cryptographic verification still runs and the algorithm whitelist still lists the implemented algorithm). Intended for tests only.

#
VerificationReport

pub(all) struct VerificationReport {
verified : Array[VerifiedSignature]
rejected : Array[RejectedSignature]
}

The aggregate outcome of verifying all labels.

#
VerifiedSignature

pub(all) struct VerifiedSignature {
label : String
keyid : String
algorithm : String
covered_components : Array[CoveredComponent]
created : Int64?
expires : Int64?
nonce : String?
tag : String?
}

A signature that passed every check.

#
algorithm_provider

fn algorithm_provider(algorithm : String) -> SignatureAlgorithmProvider

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.

#
append_signature

fn append_signature(field : SignatureField, entry : SignatureEntry) -> Result[SignatureField, HsError]

Appends a signature entry, rejecting a duplicate label.

#
append_signature_input

fn append_signature_input(input : SignatureInput, entry : SignatureInputEntry) -> Result[SignatureInput, HsError]

Appends an entry, rejecting a duplicate label.

#
assert_bytes_eq

fn assert_bytes_eq(a : Bytes, b : Bytes, what : String) -> Unit

Asserts that two byte buffers are equal (test helper).

#
base64_decode_bytes

fn base64_decode_bytes(s : String) -> Result[Bytes, HsError]

Decodes a standard padded base64 string into bytes.

Returns InvalidBase64 with a byte offset on any malformed input, including bad characters, bad padding, and stray whitespace.

#
base64_encode_bytes

fn base64_encode_bytes(bytes : Bytes) -> String

Encodes bytes as standard padded base64 (the sf-bytes serialization).

#
buffer_to_string

fn buffer_to_string(buf :
Buffer
) -> String raise HsError

Converts a UTF-8 byte buffer to a String.

Core's Buffer::to_string() interprets the buffer as UTF-16, which is wrong for content written with write_string_utf8/write_char_utf8. All string-building in this library uses UTF-8, so every conversion must go through @utf8.decode. The content we build is always valid UTF-8, so the raise can never fire in practice.

#
build_request_signature_base

fn build_request_signature_base(request : RequestContext, entry : SignatureInputEntry, limits : Limits) -> Result[SignatureBase, HsError]

Builds a signature base for a request.

#
build_response_signature_base

fn build_response_signature_base(response : ResponseContext, entry : SignatureInputEntry, limits : Limits) -> Result[SignatureBase, HsError]

Builds a signature base for a response.

#
build_signature_base

fn build_signature_base(target : SignTarget, entry : SignatureInputEntry, limits : Limits) -> Result[SignatureBase, HsError]

Builds a signature base for any target message.

#
check_key_algorithm

fn check_key_algorithm(record : KeyRecord, requested_algorithm : String?) -> Result[Unit, HsError]

Validates that the algorithm implied by a key matches the requested algorithm, raising AlgorithmMismatch otherwise.

#
components_cover

fn components_cover(components : Array[CoveredComponent], wanted : CoveredComponent) -> Bool

Returns true if component is covered by the given components.

#
constant_time_equal

fn constant_time_equal(a : Bytes, b : Bytes) -> Bool

Compares two byte buffers in constant time.

The comparison always iterates over the maximum length of the two inputs (padding mismatched positions with the 0xFF marker so no branch reveals which byte differed). Length differences are folded into the accumulator, so the timing depends only on the longest input, never on the content.

#
contains_cr_or_lf

fn contains_cr_or_lf(s : String) -> Bool

Returns true if the string contains a CR (\r) or LF (\n) byte, which is forbidden in header values and in URI components.

#
covered_component_to_sf_item

fn covered_component_to_sf_item(component : CoveredComponent) -> SfItem

Reconstructs an SfItem for a covered component (used when serializing a Signature-Input or signature base). The item value is the sf-string of the component name; component parameters are mapped back to SF parameters.

#
covered_derived

fn covered_derived(d : DerivedComponent) -> CoveredComponent

Constructs a derived covered component with default parameters.

#
covered_derived_with_params

fn covered_derived_with_params(d : DerivedComponent, parameters : ComponentParameters) -> CoveredComponent

Constructs a derived covered component with explicit parameters.

#
covered_field

fn covered_field(name : String) -> CoveredComponent

Constructs a field covered component with default parameters.

#
covered_field_with_params

fn covered_field_with_params(name : String, parameters : ComponentParameters) -> CoveredComponent

Constructs a field covered component with explicit parameters.

#
empty_report

fn empty_report() -> VerificationReport

Convenience: an empty verification report.

#
get_signature

fn get_signature(field : SignatureField, label : String) -> Result[Bytes, HsError]

Returns the signature bytes for the given label, or MissingSignature.

#
get_signature_input

fn get_signature_input(input : SignatureInput, label : String) -> Result[SignatureInputEntry, HsError]

Returns the entry with the given label, or MissingSignatureInput.

#
hex_decode_bytes

fn hex_decode_bytes(s : String) -> Result[Bytes, HsError]

Decodes a lowercase or uppercase hex string into bytes.

Returns InvalidBase64 (reusing the generic base64 kind for byte-decoding failures) when the input length is odd or a character is not hex.

#
hex_encode_bytes

fn hex_encode_bytes(bytes : Bytes) -> String

Encodes bytes as lowercase hex (used for SHA-256 digests in diagnostics and for the fixture integrity checksum).

#
hmac_sha256

fn hmac_sha256(key : Bytes, message : Bytes) -> Bytes

Computes HMAC-SHA256 of message under key per RFC 2104.

#
hs_error

fn hs_error(stage : HsErrorStage, kind : HsErrorKind, context : String) -> HsError

Constructs an HsError with the given stage, kind, and context and a zero offset.

#
hs_error_at

fn hs_error_at(stage : HsErrorStage, kind : HsErrorKind, offset : Int, context : String) -> HsError

Constructs an HsError carrying an explicit UTF-8 byte offset.

#
is_tchar

fn is_tchar(s : String) -> Bool

Returns true if every byte of the string is an HTTP tchar.

#
is_valid_field_name

fn is_valid_field_name(s : String) -> Bool

Returns true if the string is a valid HTTP field name (RFC 9110 §5.1): a non-empty sequence of tchar bytes. Field names are ASCII by definition, so any non-ASCII byte fails the check.

#
library_version

fn library_version() -> String

The library version, mirrored in moon.mod.

#
make_minimal_request

fn make_minimal_request() -> RequestContext

Builds a minimal request context for signing tests.

#
make_test_key

fn make_test_key(keyid : String, secret : String) -> KeyRecord

Builds an HMAC-SHA256 key record for tests.

#
make_test_request

fn make_test_request(method : String, path : String, query : String?, headers : OrderedHeaders) -> Result[RequestContext, HsError]

Builds a request context for tests with the given headers.

#
make_verify_ctx

fn make_verify_ctx(key : KeyRecord, created : Int64) -> (InMemoryKeyResolver, VerificationPolicy, FixedClock, InMemoryNonceStore, Limits)

Builds a default verification context: resolver, policy, clock, nonce store, and limits ready for verify_request.

#
parse_accept_request_parameters

fn parse_accept_request_parameters(params : Array[SfParameter]) -> Result[AcceptSignatureRequest, HsError]

Parses the request parameters of one Accept-Signature entry. created and expires are accepted as bare booleans (request flags) or integers.

#
parse_accept_signature

fn parse_accept_signature(input : String, limits : Limits) -> Result[AcceptSignature, HsError]

Parses a raw Accept-Signature field value.

#
parse_component_parameters

fn parse_component_parameters(params : Array[SfParameter]) -> Result[ComponentParameters, HsError]

Parses item parameters into ComponentParameters, enforcing RFC 9421 §3.1.2 rules:
  • unknown parameters are rejected;
  • sf/bs/req/tr must be booleans;
  • key/name must be strings;
  • name is only valid on @query-param (checked by the caller).

#
parse_covered_component

fn parse_covered_component(item : SfItem) -> Result[CoveredComponent, HsError]

Parses a single covered component from an SfItem.

Per RFC 9421 §2.5 the component identifier is an sf-string; we also accept an unquoted token for lenience. Identifiers starting with @ denote derived components, everything else is a field component.

#
parse_covered_components

fn parse_covered_components(items : Array[SfItem], max_components : Int) -> Result[Array[CoveredComponent], HsError]

Parses a covered-components inner list into an ordered array.

#
parse_int64_cli

fn parse_int64_cli(s : String) -> Int64?

Parses a decimal string into an Int64, returning None on invalid input. Used by the CLI so that external input never panics.

#
parse_sf_dictionary_string

fn parse_sf_dictionary_string(input : String, limits : Limits) -> Result[Array[SfDictionaryEntry], HsError]

Parses a complete dictionary from a string, requiring that no trailing garbage remains. Returns the ordered entries.

#
parse_sf_inner_list_string

fn parse_sf_inner_list_string(input : String, limits : Limits) -> Result[SfInnerList, HsError]

Parses a single inner list from a string, requiring no trailing garbage.

#
parse_sf_item_string

fn parse_sf_item_string(input : String, limits : Limits) -> Result[SfItem, HsError]

Parses a single item from a string, requiring no trailing garbage.

#
parse_signature_field

fn parse_signature_field(input : String, limits : Limits) -> Result[SignatureField, HsError]

Parses a raw Signature field value into a SignatureField.

Each member value must be a Byte Sequence; the raw bytes are preserved. Duplicate labels are rejected. Base64 decoding errors surface as InvalidBase64.

#
parse_signature_input

fn parse_signature_input(input : String, limits : Limits) -> Result[SignatureInput, HsError]

Parses a raw Signature-Input field value into a SignatureInput.

The dictionary order, inner-list order, and parameter order are all preserved. Duplicate labels are rejected. Each label must be a valid dictionary key.

#
parse_signature_parameters

fn parse_signature_parameters(params : Array[SfParameter]) -> Result[SignatureParameters, HsError]

Parses a parameter list into SignatureParameters.

Unknown parameters are stored in extensions (in order). Duplicate defined parameters are rejected. created/expires must be integers; keyid/alg/nonce/tag must be strings.

#
resolve_component

fn resolve_component(component : CoveredComponent, target : SignTarget) -> Result[String, HsError]

Resolves any covered component (derived or field) to its canonical value.

#
resolve_derived_component

fn resolve_derived_component(component : DerivedComponent, target : SignTarget, use_req : Bool) -> Result[String, HsError]

Resolves a derived component to its canonical string value.

use_req indicates the req parameter: when the target is a response, the component is resolved against the related request instead.

#
resolve_field_component

fn resolve_field_component(component : FieldComponent, target : SignTarget) -> Result[String, HsError]

Resolves a field component to its canonical string value.

#
serialize_accept_signature

fn serialize_accept_signature(accept : AcceptSignature) -> Result[String, HsError]

Serializes an Accept-Signature field back to its canonical form.

#
serialize_component_parameters

fn serialize_component_parameters(params : ComponentParameters) -> Result[String, HsError]

Serializes component parameters canonically: boolean-true parameters as bare ;name, other values as ;name=value.

#
serialize_sf_bare_item

fn serialize_sf_bare_item(v : SfBareItem) -> Result[String, HsError]

Serializes a bare item.

#
serialize_sf_boolean

fn serialize_sf_boolean(v : Bool) -> Result[String, HsError]

Serializes a boolean as ?0 or ?1.

#
serialize_sf_byte_sequence

fn serialize_sf_byte_sequence(v : Bytes) -> Result[String, HsError]

Serializes a byte sequence with standard padded base64.

#
serialize_sf_dictionary

fn serialize_sf_dictionary(entries : Array[SfDictionaryEntry]) -> Result[String, HsError]

Serializes a dictionary.

#
serialize_sf_inner_list

fn serialize_sf_inner_list(v : SfInnerList) -> Result[String, HsError]

Serializes an inner list.

#
serialize_sf_integer

fn serialize_sf_integer(v : Int64) -> Result[String, HsError]

Serializes an integer in canonical decimal form.

#
serialize_sf_item

fn serialize_sf_item(v : SfItem) -> Result[String, HsError]

Serializes an item.

#
serialize_sf_string

fn serialize_sf_string(v : String) -> Result[String, HsError]

Serializes a string value with escaping.

#
serialize_sf_token

fn serialize_sf_token(v : String) -> Result[String, HsError]

Serializes a token value, validating the token grammar.

#
serialize_signature_field

fn serialize_signature_field(field : SignatureField) -> Result[String, HsError]

Serializes a Signature field back to its canonical form.

#
serialize_signature_input

fn serialize_signature_input(input : SignatureInput) -> Result[String, HsError]

Serializes a Signature-Input field back to its canonical form.

#
serialize_signature_parameters

fn serialize_signature_parameters(params : SignatureParameters) -> Result[String, HsError]

Serializes signature parameters canonically in the order created, expires, keyid, nonce, alg, tag, then extensions. This order reproduces every RFC 9421 Appendix B example byte-for-byte.

#
sf_boolean

fn sf_boolean(v : Bool) -> SfItem

Constructs a bare boolean item.

#
sf_bytes

fn sf_bytes(v : Bytes) -> SfItem

Constructs a bare byte-sequence item.

#
sf_integer

fn sf_integer(v : Int64) -> SfItem

Constructs a bare integer item.

#
sf_param

fn sf_param(name : String, value : SfBareItem) -> SfParameter

Constructs a parameter with the given name and value.

#
sf_string

fn sf_string(v : String) -> SfItem

Constructs a bare string item.

#
sf_token

fn sf_token(v : String) -> SfItem

Constructs a bare token item.

#
sha256_raw

fn sha256_raw(data : Bytes) -> Bytes

Computes the raw 32-byte SHA-256 digest of data.

The gmlewis/sha256 package exposes a streaming digest whose check_sum returns lowercase hex; we decode it back to raw bytes.

#
sign_request

fn sign_request(request : RequestContext, options : SignOptions, limits : Limits) -> Result[SignedFields, HsError]

Signs a request message.

#
sign_response

fn sign_response(response : ResponseContext, options : SignOptions, limits : Limits) -> Result[SignedFields, HsError]

Signs a response message.

#
sign_target

fn sign_target(target : SignTarget, options : SignOptions, limits : Limits) -> Result[SignedFields, HsError]

Signs any target message.

#
sign_test_request

fn sign_test_request(components : Array[CoveredComponent], keyid : String, created : Int64) -> Result[SignedFields, HsError]

Signs a minimal request with the given covered components and a fixed created time, returning the signed fields.

#
strip_ows

fn strip_ows(s : String) -> String

Returns the string with one optional leading and trailing OWS removed. Per RFC 9110 field value semantics only leading/trailing OWS is trimmed; interior whitespace is preserved byte-for-byte.

#
validate_content_digest

fn validate_content_digest(covered : Array[CoveredComponent], headers : OrderedHeaders, body : Bytes, limits : Limits) -> Result[Unit, HsError]

Validates that the body matches a Content-Digest header that is covered by covered. When require_covered is false only the digest validity is checked.

#
validate_signature_input

fn validate_signature_input(input : SignatureInput, limits : Limits) -> Result[Unit, HsError]

Validates a parsed Signature-Input against RFC 9421 structural rules and the configured limits:
  • label uniqueness (already enforced at parse time);
  • expires must not be earlier than created;
  • keyid/nonce/tag length limits;
  • signature count limit.

#
validate_signature_labels

fn validate_signature_labels(input : SignatureInput, field : SignatureField) -> Result[Unit, HsError]

Validates that the Signature and Signature-Input labels match exactly: every label present in one field must be present in the other. The first mismatched label is reported as LabelMismatch.

#
verify_label

fn verify_label(target : SignTarget, entry : SignatureInputEntry, signature_bytes : Bytes, resolver : InMemoryKeyResolver, policy : VerificationPolicy, clock : FixedClock, nonce_store : InMemoryNonceStore, limits : Limits) -> Result[VerifiedSignature, HsError]

Verifies a single label, returning its details on success.

#
verify_label_outcome

fn verify_label_outcome(ctx : (InMemoryKeyResolver, VerificationPolicy, FixedClock, InMemoryNonceStore, Limits), target : SignTarget, entry : SignatureInputEntry, sig : Bytes) -> String

Returns the error kind of a single-label verification, or "ok".

#
verify_label_with_ctx

fn verify_label_with_ctx(ctx : (InMemoryKeyResolver, VerificationPolicy, FixedClock, InMemoryNonceStore, Limits), target : SignTarget, entry : SignatureInputEntry, sig : Bytes) -> Result[VerifiedSignature, HsError]

Verifies a single label using a make_verify_ctx context.

#
verify_outcome

fn verify_outcome(ctx : (InMemoryKeyResolver, VerificationPolicy, FixedClock, InMemoryNonceStore, Limits), req : RequestContext, input : String, sig : String) -> String

Returns "ok" when the request verifies, otherwise the rejection kind. Compact helper for negative tests.

#
verify_request

fn verify_request(request : RequestContext, signature_input : String, signature : String, resolver : InMemoryKeyResolver, policy : VerificationPolicy, clock : FixedClock, nonce_store : InMemoryNonceStore, limits : Limits, multi : MultiSignaturePolicy) -> Result[VerificationReport, HsError]

Verifies every signature on a request.

#
verify_response

fn verify_response(response : ResponseContext, signature_input : String, signature : String, resolver : InMemoryKeyResolver, policy : VerificationPolicy, clock : FixedClock, nonce_store : InMemoryNonceStore, limits : Limits, multi : MultiSignaturePolicy) -> Result[VerificationReport, HsError]

Verifies every signature on a response.

#
verify_target

fn verify_target(target : SignTarget, signature_input : String, signature : String, resolver : InMemoryKeyResolver, policy : VerificationPolicy, clock : FixedClock, nonce_store : InMemoryNonceStore, limits : Limits, multi : MultiSignaturePolicy) -> Result[VerificationReport, HsError]

Verifies every signature on any target message.

#
verify_test_request

fn verify_test_request(request : RequestContext, signed : SignedFields, key : KeyRecord, now : Int64) -> Result[VerificationReport, HsError]

Verifies a signed request with a fixed clock, returning the report.

#
verify_with_ctx

fn verify_with_ctx(ctx : (InMemoryKeyResolver, VerificationPolicy, FixedClock, InMemoryNonceStore, Limits), req : RequestContext, input : String, sig : String, multi : MultiSignaturePolicy) -> Result[VerificationReport, HsError]

Verifies a request using a make_verify_ctx context. Keeps test bodies short so that canonical formatting does not split long calls.