RequestSeal.Profile (RequestSeal v0.2.0)

Copy Markdown View Source

Shared HTTP signature pipeline for profiles owned by extension packages.

This extension surface is unstable; pin RequestSeal exactly.

An extension selects its source-specific rules, then uses dictionaries/2 to parse paired signature fields, preflight/4 to check coverage and freshness before discovery, and verify_label/6 to verify its selected dictionary member under an explicit RequestSeal.Policy. The input and signature values come from the same label returned by dictionaries/2. Verification re-parses the message under the policy's max_signatures and checks that both supplied values equal that label's entries before resolving a key.

verify_label/6 does not accept :representation or :digest_state options. Content checks can use retained message body bytes only; representation policies and stream digest states require generic RequestSeal.verify/3 with its validated options.

Profile maps contain at most eight atom keys and require name: {package, kind}, where both values are atoms and package is not :request_seal, :rfc9421, or :web_bot_auth. Use a compile-time constant in the extension module, never a name derived from wire input. Bare atom names, including the core-owned names :rfc9421 and :web_bot_auth, reject as :invalid_profile at the :input layer. Other values must be atoms, integers from -999,999,999,999,999 through 999,999,999,999,999, or binaries of at most 256 bytes.

This pipeline enforces the supplied generic policy, not an extension's source-specific predicates. Extensions own those checks and any trusted principal attribution. A profile stamp is not proof of profile enforcement; consumers must allowlist trusted profiles and their verification code. Replay commitments receive the selected profile in authenticated facts; commitments must bind facts.profile to separate profile security scopes. RequestSeal.Ash.scope/2 accepts only its core profile allowlist.

Summary

Functions

Parse paired signature dictionaries in encounter order under a member limit.

Check required coverage and freshness before resolving a selected key.

Check the semantic rules for one parsed RFC 9421 Signature-Input Inner List.

Verify a selected member under an explicit policy and package-namespaced profile.

Types

stamp()

@type stamp() :: %{
  :name => {atom(), atom()},
  optional(atom()) => atom() | integer() | binary()
}

Functions

dictionaries(message, max)

Parse paired signature dictionaries in encounter order under a member limit.

preflight(input, components, extra, freshness_policy)

@spec preflight(
  RequestSeal.StructuredFields.Value.t(),
  binary(),
  :allow | :reject,
  :not_evaluated | map()
) :: {:ok, :not_evaluated | map()} | {:error, RequestSeal.Error.t()}

Check required coverage and freshness before resolving a selected key.

valid_signature_input?(input)

@spec valid_signature_input?(term()) :: boolean()

Check the semantic rules for one parsed RFC 9421 Signature-Input Inner List.

Accepts a RequestSeal.StructuredFields.Value with type: :inner_list, ordered component items, and signature parameters. It delegates to the shared signature-field validator: at most 256 components, unique component identities, recognized derived components or lowercase field names, permitted component parameters, and integer created/expires or string nonce/alg/keyid/tag. Unknown signature parameters remain permitted under RFC 9421 Section 2.3. An empty component list is permitted; profiles select required coverage.

Parse or validate the Structured Fields representation first under an RFC 8941 schema. This predicate checks signature semantics, not the complete Structured Fields grammar, parameter uniqueness, freshness, key trust, or cryptography. It does not parse wire binaries. Non-inner lists and malformed structures that cannot be inspected return false.

iex> alias RequestSeal.StructuredFields.{Schema, Value}
iex> {:ok, schema} = Schema.new(%{revision: :rfc8941, type: :list, item_types: [:string], inner_lists: true})
iex> {:ok, %Value{value: [input]}} = RequestSeal.StructuredFields.parse(~s[("@method" "@path");created=1618884473], schema)
iex> RequestSeal.Profile.valid_signature_input?(input)
true
iex> RequestSeal.Profile.valid_signature_input?(~s[("@method")])
false

verify_label(message, policy, label, input, signature, profile)

Verify a selected member under an explicit policy and package-namespaced profile.