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

Pure helpers for working with signing material as PEM strings.

An RSA private key already contains its public half, so there is never
a separately stored public PEM to drift out of sync. `public_pem/1`
derives the public key from a private PEM, giving exactly one source of
truth: the verification key always matches the signing key. This closes
a real failure mode where a tracked public PEM and a regenerated
private key silently mismatch and every verification fails.

`kid/1` is the RFC 7638 JWK thumbprint of a key's public half. It is
stable for a given key and changes iff the key changes, so rotating to a
new key yields a distinct `kid` automatically - no separate identifier
to assign or track.

# `verification_jwk_error`

```elixir
@type verification_jwk_error() ::
  :private_material
  | :invalid_use
  | :alg_mismatch
  | :incompatible_key
  | :weak_key
  | :oversized_key
  | :malformed_jwk
```

# `jwk`

```elixir
@spec jwk(String.t()) :: JOSE.JWK.t()
```

Parse a PEM (private or public) into a `JOSE.JWK`.

Raises `ArgumentError` if `pem` does not contain exactly one parseable
key. `JOSE.JWK.from_pem/1` returns `[]` for input with no key entry and
a list for a multi-key PEM; left unguarded, `thumbprint/1` of those
returns `[]` rather than a string, which would silently poison `kid/1`
and verification-key selection. Failing loudly here surfaces a
malformed keystore PEM as a configuration error instead of a
request-time mystery.

Also raises if the key type/curve is not supported by Attesto's
asymmetric signing algorithms. Algorithms are derived from trusted key
metadata, not from a presented token header.

# `kid`

```elixir
@spec kid(String.t()) :: String.t()
```

The RFC 7638 SHA-256 JWK thumbprint (`kid`) of the public half of the
key in `pem`. Accepts a private or public PEM; both yield the same
thumbprint because it is computed over the public members only.

# `public_pem`

```elixir
@spec public_pem(String.t()) :: String.t()
```

Derive the public key, in conventional SPKI
(`-----BEGIN PUBLIC KEY-----`) PEM form, from a private RSA key PEM.

Accepts the PKCS#1 (`RSA PRIVATE KEY`) and PKCS#8 (`PRIVATE KEY`) forms.
Raises `ArgumentError` - signing material is operator-provided, so a
misconfiguration is a deploy-time failure that should be loud rather
than silently verifying against garbage - if `pem` contains no key
entry, contains more than one, contains a non-RSA key, or is
public-only. EC/OKP deployments should publish JWKS instead.

# `signing_jwk`

```elixir
@spec signing_jwk(String.t()) :: JOSE.JWK.t()
```

Parse a private PEM into a `JOSE.JWK` whose public half can sign and
derive a `kid`.

Unlike `jwk/1`, this rejects public-only PEMs: they are valid
verification material, but cannot sign tokens.

# `verification_jwk`

```elixir
@spec verification_jwk(
  map(),
  keyword()
) :: {:ok, JOSE.JWK.t()} | {:error, verification_jwk_error()}
```

Validate and parse an untrusted JWK for signature verification.

The raw map is checked before parsing so private members and contradictory
RFC 7517 metadata cannot be normalized away by the JOSE library. By default,
private material is rejected and RSA verification keys must be at least 2048
bits.

`:alg` is required and is compared with a JWK's optional `alg` member.
`:require_public?` defaults to `true`, and `:minimum_rsa_bits` defaults to
2048.

To retain DPoP's error semantics, explicit Edwards identifiers are checked
against the key curve here, while other key-type/algorithm mismatches remain
the signature verifier's responsibility.

---

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