I am trying to create a JWT using an existing ED25519 key, then later extract the subject from it, and the kid to verify it. Here is what I have so far:
def generate_my_jwk() do
raw_private_key =
Base.decode16!(Application.get_env(:balls_pds, :owner_private_key), case: :lower)
generate_jwk(raw_private_key)
end
def generate_jwk(raw_private_key) when is_binary(raw_private_key) do
public_key = :crypto.generate_key(:eddsa, :ed25519, raw_private_key) |> elem(0)
jwk = %{
"kty" => "OKP",
"alg" => "EdDSA",
"crv" => "Ed25519",
"d" => Base.url_encode64(raw_private_key, padding: false),
"x" => Base.url_encode64(public_key, padding: false),
"use" => "sig"
}
JOSE.JWK.from(jwk)
end
def generate_jwt(days \\ 30) when is_integer(days) and days > 0 do
jwk = generate_my_jwk()
signer = Joken.Signer.create("EdDSA", jwk)
id = Application.get_env(:balls_pds, :owner_ap_id)
claims = %{
"iss" => id,
"sub" => id,
"aud" => Application.get_env(:balls_pds, :owner_ap_id),
"iat" => DateTime.utc_now() |> DateTime.to_unix(),
"exp" => DateTime.utc_now() |> DateTime.add(30, :day) |> DateTime.to_unix()
}
Joken.generate_and_sign!(claims, signer)
end
defp get_kid(jwt) when is_binary(jwt) do
with {:kid, {:ok, %{"kid" => kid}}} <- {:kid, JOSE.JWT.peek_protected(jwt)} do
kid
else
_ -> nil
end
end
def extract_key_info(jwt) when is_binary(jwt) do
with {:subject, {:ok, %{"sub" => subject}}} <- {:subject, JOSE.JWT.peek_payload(jwt)},
{:kid, kid} <- {:kid, get_kid(jwt)} do
{:ok, %{subject: subject, id: kid}}
else
{err, {:error, error}} ->
Logger.error("extracting key info from JWT: #{err}: #{inspect(error)}")
{:error, error}
end
end
I created this through a combination of Internet searching, reading source code, trial and error, and asking an AI for help. However I only get this far:
% mix generate_token
** (FunctionClauseError) no function clause matching in JOSE.JWK.from_record/1
The following arguments were given to JOSE.JWK.from_record/1:
# 1
{:error, {:missing_required_keys, ["keys", "kty"]}}
Attempted function clauses (showing 2 out of 2):
def from_record({:jose_jwk, keys, kty, fields})
def from_record(list) when is_list(list)
(jose 1.11.10) lib/jose/jwk.ex:35: JOSE.JWK.from_record/1
(joken 2.6.2) lib/joken/signer.ex:107: Joken.Signer.create/3
(balls_pds 0.0.9) lib/balls_pds/jwt.ex:64: BallsPDS.JWT.generate_jwt/1
(balls_pds 0.0.9) lib/mix/tasks/generate_token.ex:5: Mix.Tasks.GenerateToken.run/1
(mix 1.17.3) lib/mix/task.ex:495: anonymous fn/3 in Mix.Task.run_task/5
(mix 1.17.3) lib/mix/cli.ex:96: Mix.CLI.run_task/2
/Users/user/.asdf/installs/elixir/1.17.3-otp-27/bin/mix:2: (file)
I could use any guidance for doing this properly, and hints of where to look to learn how to do this as I had trouble just finding information.