Relyra.Metadata.TrustAnchor (relyra v1.1.0)

Copy Markdown View Source

Operator-pinned trust-anchor check for Phase 21 scheduled metadata refresh per D-17.

The trust anchor is a list of SHA-256 hex fingerprints (lowercase) the operator pinned out-of-band before enabling scheduled refresh on a MetadataSource. This module checks that AT LEAST ONE candidate certificate (PEM) presented in the freshly-fetched metadata matches one of those pinned fingerprints.

Why no TOFU: the first fetch is the moment of maximum MITM exposure; institutionalizing "trust on first fetch" hands an attacker a one-shot window. (ruby-saml CVE-2024-45409 lesson — locked rejection per D-17.)

Why no reuse-of-assertion-cert: the SAML metadata signing key and the assertion signing key are spec-separate roles; conflating them breaks any IdP that follows the spec. The metadata trust anchor MUST be its own pinned set, populated via the admin LiveView pinning UX (D-22) or the mix relyra.metadata.pin task.

Pure: no I/O, no Ecto, no Repo. Reuses the existing fingerprint compute from lib/relyra/metadata/import.ex (Don't Hand-Roll row 5).

Summary

Functions

Returns :ok if at least one PEM in candidate_pems produces a SHA-256 fingerprint (lowercase hex, no colons) present in pinned_fingerprints.

Computes the canonical Phase-21 fingerprint for a PEM (SHA-256, lowercase hex, no colons). Mirrors Relyra.Metadata.Import.sha256/1 (line 125-126).

Functions

check(candidate_pems, pinned_fingerprints)

@spec check([String.t()], [String.t()]) :: :ok | {:error, Relyra.Error.t()}

Returns :ok if at least one PEM in candidate_pems produces a SHA-256 fingerprint (lowercase hex, no colons) present in pinned_fingerprints.

Returns {:error, %Relyra.Error{type: :trust_anchor_mismatch}} otherwise, including the empty-pinned-list case (the schema-level great-error from auto_refresh_changeset/2 should prevent this from ever happening, but the helper is still defensive).

fingerprint(pem)

@spec fingerprint(String.t()) :: String.t()

Computes the canonical Phase-21 fingerprint for a PEM (SHA-256, lowercase hex, no colons). Mirrors Relyra.Metadata.Import.sha256/1 (line 125-126).