Ltix.JWK (Ltix v0.2.0)

Copy Markdown View Source

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

Summary

Functions

Generate an RSA key pair for LTI tool authentication.

Construct a JWK from existing key material.

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

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

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

Export the public key as a PEM string.

Types

t()

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

Functions

generate(opts \\ [])

@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 (integer/0) - RSA key size in bits (minimum 2048). The default can be overridden by setting config :ltix, :default_key_size.

new(attrs)

@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!(attrs)

@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(jwk, message)

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

Sign a message with RS256, as 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(jwk)

@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(jwk)

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