bananaphone

bananaphone

Create ED25519 JWK & JWT, later extract Subject and Kid from JWT

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.

Marked As Solved

ruslandoga

ruslandoga

:wave:

I think the problem are these two lines

jwk = generate_my_jwk()
signer = Joken.Signer.create("EdDSA", jwk)

Joken.Signer.create/2 doesn’t expect JOSE.JWK.t() but rather a plain map with string keys, ["keys", "kty"] in particular.

This seems to work:

$ openssl genpkey -algorithm ed25519 > test_key.pem
$ cat test_key.pem
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIMAjXAcsE6G7AWpRQWK4eLnVRxakXforWuebGlRaGKgC
-----END PRIVATE KEY-----
Mix.install([:jose, :joken, :jason])

test_key = """
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIMAjXAcsE6G7AWpRQWK4eLnVRxakXforWuebGlRaGKgC
-----END PRIVATE KEY-----
"""

jwk = JOSE.JWK.from_pem(test_key)
{_, jwk_map} = JOSE.JWK.to_map(jwk)
Joken.Signer.create("EdDSA", jwk_map)

But I’ve never used Joken before and don’t actually know what it does :slight_smile:

Where Next?

Popular in Questions Top

minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New
quazar
How to set Jason to encode all fields in ecto schema, I don’t care about security and implementing only is taking long list of attributes...
New
vac
Hi, I'm quite new in Elixir and I'm trying to format a string to a PEM format. I have the certificate value like MIIDBTCCAe2...... and ...
New
mathew4509
I have a list say x = ["23gh", "56kh", "97mh"] I would like to pass each element to Val in each iteration. Say, in iteration 1 -------...
New
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? https://hexdocs.pm/ecto/Ecto.Repo.h...
New
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
Exadra37
Sometimes I want to check if the input into a function is not a blank string. My first approach: defmodule Example do def do_stuff(s...
New
lucidguppy
I have a super simple question about elixir - how would I take a file like this foo bar baz and output a new file that enumerates th...
New

Other popular topics Top

9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
shahryarjb
Hello, I get Persian date from my client and convert it to normal calendar like this: def jalali_string_to_miladi_english_number(persi...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
yawaramin
In the Dialyzer docs ( http://erlang.org/doc/man/dialyzer.html#requesting-or-suppressing-warnings-in-source-files ), there is a way to tu...
New
AstonJ
You’re a programmer, so you don’t need spoon feeding with the conventional drivel about “this is an integer.” No. You need to know what’s...
New
vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
vac
Hi, I'm quite new in Elixir and I'm trying to format a string to a PEM format. I have the certificate value like MIIDBTCCAe2...... and ...
New
myronmarston
The Elixir Typespec docs show the following syntax for keyword lists in typespecs: # ... | [key: type] # keyword lis...
New
beno
I will often find my self writing things similar to: case some_value do nil -&gt; something() "" -&gt; something() _ -&gt; someth...
New

We're in Beta

About us Mission Statement