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/0returns the private signing key PEM. Attesto derives the public half and thekidfrom it (Attesto.Key), so the keystore never has to compute a thumbprint.What keys may verify a presented token?
verification_pems/0returns 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, andAttesto.Token.verify/3selects the right one by the JWS headerkid.
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.
Summary
Callbacks
Optional per-key JOSE algorithm metadata, keyed by RFC 7638 kid.
Optional global algorithm for the current signing key.
The private signing-key PEM used to sign newly issued tokens.
Optional operator metadata for verification keys, keyed by RFC 7638 kid.
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.
Functions
Inspect a keystore's rotation contract.
Types
@type key_metadata() :: %{optional(:not_after) => DateTime.t() | non_neg_integer()}
@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()] }
@type rotation_key() :: %{ kid: String.t(), alg: String.t(), current?: boolean(), not_after: DateTime.t() | nil, state: :current | :overlap | :expiring | :expired }
Callbacks
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.
@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.
@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.
@callback verification_key_metadata() :: %{optional(String.t()) => 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.
@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.
Functions
@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- UTCDateTimeor Unix timestamp; defaults to the current time.:expiry_warning_seconds- warning horizon; defaults to seven days.