Explicit bounded HTTPS discovery of HTTP Message Signatures Directories, JWKS
and Client ID Metadata Documents. Construct a Discovery.Source from trusted
caller configuration. A message/key ID never defines a URL to fetch.
fetch/2 performs one discovery operation with no cache. Options are :timeout
(1–300,000 milliseconds, capped by the source timeout) and :clock (arity-zero
Unix-seconds function, default system time). Unknown/duplicate options reject.
The caller starts SSL; the library starts no application, listener, store,
timer or background refresh. A monitored worker enforces a single absolute
deadline across DNS, TLS, redirects and CIMD's optional JWKS subresource, and
terminates socket owners on caller death or timeout.
Source policy controls exact private-address exceptions, redirects and resource
limits. Every address in a DNS response is vetted; TLS connects by tuple and
checks its connected peer. Responses require HTTP 200 and the selected media
type; framing rejects ambiguous Content-Length/Transfer-Encoding. Trailer
signatures cannot establish a proof. Gzip is expanded under a separate bound.
Content-coding tokens are case-insensitive; x-gzip is accepted as gzip per
RFC 9110 Section 8.4.1.3. Unknown or stacked codings reject.
Required public JWK members produce RFC 7638/8037 SHA-256 key IDs. A supplied
kid must equal that value by default. Explicit Source.key_id: :directory
requires unique printable directory-assigned IDs while retaining thumbprints
for identity, revocation and cache removal; private material and malformed keys reject the
entire set. HTTP alg tokens map to the JOSE restriction held by PublicKey.
Keys whose use/operation restrictions forbid verification, and not-yet-valid,
expired or revoked keys, are not resolvable. Duplicate IDs reject among eligible
entries only. Directory sources require thumbprint mode. Directory
signatures use the existing RequestSeal.verify/3 contract to bind @authority;req, digest, created/expires, key ID and tag.
Directory proof means cryptographic possession, never principal attribution.
Proof selection uses the directory label or a directory-tagged member whose
key ID names a published directory key. Unrelated labels are ignored; missing
or invalid selected members reject.
resolver/2 bridges a snapshot or {cache, source} to Policy.key_resolver.
It accepts only algorithms: [...], ignores label/tag and returns only
{:ok, %{algorithm: ..., key: ...}} or :error. A key ID never selects a URL.
Discovery.Cache is an optional caller-started bounded cache with explicit
refresh/removal/invalidation; it never serves stale keys.
The generic resolver bridge evaluates cache freshness and key expiry with the cache's configured clock; snapshot resolution uses system wall time. This preserves the generic caller's explicit clock policy. Named profiles enforce their own key/proof validity clock independently of request-freshness policy.
Web Bot Auth protocol-00 Appendix C.4 states: "A verifier should not fetch the
directory for every request." Freshness follows Cache-Control: max-age before
Expires, accounting for Date and Age under RFC 9111. Without either
freshness field, the default is 300 seconds, caller-configurable via min_ttl.
Invalid or past Expires is immediately stale. max_ttl, key/signature expiry,
and no-cache/no-store still bound freshness.
Appendix C.5 states: "Negative cache entries should expire after no more than
five minutes." Accordingly, negative_ttl cannot exceed 300 seconds.
It also states: "Network failures, TLS failures, and 5xx responses should be
treated as transient unless local policy says otherwise." This adapter marks
5xx :unexpected_status retryable and 4xx non-retryable; retry scheduling and
decisions remain caller-owned.
Sources: Web Bot Auth protocol-00, CIMD-02, RFC 7638, RFC 8037, RFC 9110 Section 8.4.1.3, RFC 9111 Section 4.2.1, RFC 9112.
Summary
Functions
Fetch a configured source once under a cancellable absolute deadline.
Build a Policy resolver using source-selected IDs and an algorithm allowlist.
Functions
@spec fetch(RequestSeal.Discovery.Source.t(), keyword()) :: {:ok, RequestSeal.Discovery.KeySet.t()} | {:error, RequestSeal.Discovery.Error.t()}
Fetch a configured source once under a cancellable absolute deadline.
@spec resolver( RequestSeal.Discovery.KeySet.t() | {GenServer.server(), RequestSeal.Discovery.Source.t()}, keyword() ) :: (map() -> term())
Build a Policy resolver using source-selected IDs and an algorithm allowlist.