# `Attesto.Keystore`
[🔗](https://github.com/XukuLLC/attesto/blob/v2.0.1/lib/attesto/keystore.ex#L1)

The behaviour Attesto uses to obtain signing and verification keys.

A keystore answers two questions:

  * **What key do we sign new tokens with?** `signing_pem/0` returns the
    private signing key PEM. Attesto derives the public half and the `kid`
    from it (`Attesto.Key`), so the keystore never has to compute a
    thumbprint.

  * **What keys may verify a presented token?** `verification_pems/0`
    returns a list of PEMs (private or public) whose public halves are
    trusted. With a single key this is just `[signing_pem()]`. During a
    key rotation it carries both the outgoing and incoming keys so
    tokens minted under either verify, and `Attesto.Token.verify/3`
    selects the right one by the JWS header `kid`.

Implementations decide *where* keys come from - an environment variable, a
secrets manager, or a file. The legacy signing path consumes a private PEM.
A module backed by an HSM, KMS, enclave, or other non-extractable custody
system instead implements `Attesto.Signer` alongside this behaviour and may
omit `signing_pem/0`; its `verification_pems/0` still publishes public keys.

A FAPI deployment that uses RSA must provision every server signing and
verification key with a modulus of at least 2048 bits. The generic keystore
contract remains profile-neutral so non-FAPI applications can make their
own compatibility decisions.

`Attesto.Keystore.Static` is a ready-made implementation for the common
single-key (or manually-rotated) case.

# `key_metadata`

```elixir
@type key_metadata() :: %{optional(:not_after) =&gt; DateTime.t() | non_neg_integer()}
```

# `rotation_health`

```elixir
@type rotation_health() :: %{
  status: :healthy | :warning | :invalid,
  signing_kid: String.t(),
  overlap?: boolean(),
  key_count: non_neg_integer(),
  keys: [rotation_key()],
  unknown_metadata_kids: [String.t()],
  issues: [atom()]
}
```

# `rotation_key`

```elixir
@type rotation_key() :: %{
  kid: String.t(),
  alg: String.t(),
  current?: boolean(),
  not_after: DateTime.t() | nil,
  state: :current | :overlap | :expiring | :expired
}
```

# `key_algs`
*optional* 

```elixir
@callback key_algs() :: %{required(String.t()) =&gt; String.t()} | keyword(String.t())
```

Optional per-key JOSE algorithm metadata, keyed by RFC 7638 `kid`.

When omitted, Attesto infers an algorithm from the public key type and curve:
RSA -> RS256, P-256 -> ES256, P-384 -> ES384, P-521 -> ES512, and
Ed25519/Ed448 -> legacy EdDSA. Use this callback to label RSA keys that
should verify as PS256, select RFC 9864 `Ed25519` / `Ed448` for the matching
curve, or make a rotation window explicit. Ed448 deployments must configure
JOSE with Curve448 and SHAKE256 support.

# `signing_alg`
*optional* 

```elixir
@callback signing_alg() :: String.t()
```

Optional global algorithm for the current signing key.

This is a convenience for single-key RSA deployments that want PS256
without precomputing the signing key's `kid`. A custom keystore whose value
differs from key inference MUST expose the same binding through `key_algs/0`
so its newly minted tokens also verify. `Attesto.Keystore.Static` does that
automatically. Verification otherwise uses `key_algs/0` when present, then
key inference.

# `signing_pem`
*optional* 

```elixir
@callback signing_pem() :: String.t()
```

The private signing-key PEM used to sign newly issued tokens.

The key must support one of the asymmetric algorithms accepted by
`Attesto.SigningAlg`. FAPI server deployments using RSA require a modulus of
at least 2048 bits.

# `verification_key_metadata`
*optional* 

```elixir
@callback verification_key_metadata() :: %{optional(String.t()) =&gt; key_metadata()}
```

Optional operator metadata for verification keys, keyed by RFC 7638 `kid`.

`:not_after` is either a UTC `DateTime` or a Unix timestamp. It is operational
metadata: JWT verification continues to use token validity and the published
key set, while `rotation_health/2` surfaces expired or soon-to-expire keys.
Keys must be non-empty string `kid` values that exist in
`verification_pems/0`; malformed and unknown metadata fails closed.

# `verification_pems`

```elixir
@callback verification_pems() :: [String.t()]
```

The PEMs (private or public) whose public halves are trusted to verify
a presented token. MUST include the public half of whatever
`signing_pem/0` currently returns.

# `rotation_health`

```elixir
@spec rotation_health(
  module(),
  keyword()
) :: rotation_health()
```

Inspect a keystore's rotation contract.

The current signing key must appear in `verification_pems/0`; otherwise newly
issued tokens cannot be verified and the result is `:invalid`. More than one
verification key is reported as an active overlap window. Optional
`verification_key_metadata/0` expiry values turn expired and soon-to-expire
keys into explicit health issues. An expired current signing key and metadata
for an unknown key make the result `:invalid`; expired overlap keys warn.

Options:

  * `:now` - UTC `DateTime` or Unix timestamp; defaults to the current time.
  * `:expiry_warning_seconds` - warning horizon; defaults to seven days.

---

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