# `Relyra.Metadata.TrustAnchor`
[🔗](https://github.com/szTheory/relyra/blob/v1.1.0/lib/relyra/metadata/trust_anchor.ex#L1)

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).

# `check`

```elixir
@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`

```elixir
@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).

---

*Consult [api-reference.md](api-reference.md) for complete listing*
