# `Ltix.JWK`
[🔗](https://github.com/DecoyLex/ltix/blob/main/lib/ltix/jwk.ex#L1)

RSA keys for LTI tool authentication.

Every LTI Advantage service call requires a signed JWT assertion, and
platforms verify it with the public keys your tool publishes. A JWK has a
key ID and a public key, and a private key when it was made from one.

## Generating a key

    jwk = Ltix.JWK.generate()

Store `kid`, `public_key_pem`, and `private_key_pem`, and encrypt the
private key.

## Signing

Load the private key and sign in your storage adapter's
`c:Ltix.StorageAdapter.sign/4` callback.

    {:ok, jwk} = Ltix.JWK.new(private_key_pem: pem, kid: kid)
    Ltix.JWK.sign(jwk, message)

## Serving your key set endpoint

Load the public keys and return them with `to_jwks/1`. The endpoint needs
no private key.

    def jwks(conn, _params) do
      json(conn, Ltix.JWK.to_jwks(MyApp.Lti.list_public_jwks()))
    end

Here `list_public_jwks/0` builds each JWK from what you stored.

    Ltix.JWK.new!(public_key_pem: pem, kid: kid)

During a rotation, include the new key and the old one so platforms can
verify with either.

For a key in a key management service, build the JWK from the public key
the service returns, as a PEM, a DER binary, or a JWK map.

# `t`

```elixir
@type t() :: %Ltix.JWK{
  kid: String.t(),
  private_key_pem: String.t() | nil,
  public_key_pem: String.t()
}
```

# `generate`

```elixir
@spec generate(keyword()) :: t()
```

Generate an RSA key pair for LTI tool authentication.

Returns a `%Ltix.JWK{}` struct. Persist `kid` and the key PEMs to build
the JWK again with `new/1`.

## Examples

    jwk = Ltix.JWK.generate()
    jwk = Ltix.JWK.generate(key_size: 4096)

## Options

* `:key_size` (`t:integer/0`) - RSA key size in bits (minimum 2048). The default can be overridden
  by setting `config :ltix, :default_key_size`.

# `new`

```elixir
@spec new(keyword() | map()) :: {:ok, t()} | {:error, Exception.t()}
```

Construct a JWK from existing key material.

Takes a keyword list or an atom-keyed map with `:kid` and one key. Unknown
fields are rejected. A JWK made from a public key has no private key, so
it can't sign.

## Fields

  * `:kid` - the key ID. Optional with `:public_jwk` or `:private_jwk` when
    the map has a `"kid"`, which a `:kid` given with it has to match.
  * `:public_key_pem` - an RSA public key as a PEM string
  * `:public_key_der` - an RSA public key as a DER binary
  * `:public_jwk` - an RSA public key as a string-keyed JWK map
  * `:private_key_pem` - an RSA private key as an unencrypted PEM string
  * `:private_key_der` - an RSA private key as a DER binary
  * `:private_jwk` - an RSA private key as a string-keyed JWK map

## Examples

    {:ok, jwk} = Ltix.JWK.new(private_key_pem: pem, kid: "my-key-id")
    {:ok, jwk} = Ltix.JWK.new(public_key_pem: public_pem, kid: "my-key-id")

# `new!`

```elixir
@spec new!(keyword() | map()) :: t()
```

Same as `new/1`, but returns the JWK or raises the error.

## Examples

    jwk = Ltix.JWK.new!(private_key_pem: pem, kid: "my-key-id")

# `sign`

```elixir
@spec sign(t(), binary()) :: {:ok, binary()} | {:error, Exception.t()}
```

Sign a message with RS256, as `c:Ltix.StorageAdapter.sign/4` requires.

Returns an error for a JWK without a private key.

## Examples

    iex> jwk = Ltix.JWK.generate(key_size: 2048)
    iex> {:ok, signature} = Ltix.JWK.sign(jwk, "message")
    iex> byte_size(signature)
    256

# `to_jwks`

```elixir
@spec to_jwks(t() | [t()]) :: map()
```

Build a JWKS (JSON Web Key Set) map from one or more JWKs.

The map has only public keys, so it is safe for public endpoints.
Accepts a single `%Ltix.JWK{}` or a list.

## Examples

    def jwks(conn, _params) do
      json(conn, Ltix.JWK.to_jwks(MyApp.Lti.list_public_jwks()))
    end

# `to_public_key`

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

Export the public key as a PEM string.

Useful for manual key exchange during platform registration setup.

## Examples

    jwk = Ltix.JWK.generate()
    public_pem = Ltix.JWK.to_public_key(jwk)

---

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