lib/kitten_blue/jwk/x509.ex

defmodule KittenBlue.JWK.X509 do
  @moduledoc """
  Handling JWK modules with regard to X.509
  """

  defstruct [
    :x5c
  ]

  @type t :: %__MODULE__{x5c: [String.t()]}

  @type certificate :: X509.ASN1.record(:otp_certificate)

  @doc """
  Generate KittenBlue.JWK.X509 struct

  ```Elixir
  kid = "sample_202301"
  alg = "RS256"
  key = JOSE.JWK.from_pem_file("rsa-2048.pem")

  x5c = [cert |> X509.Certificate.to_der() |> Base.encode64()]
  x509 = KittenBlue.JWK.X509.new([x5c: x5c])
  kb_jwk = KittenBlue.JWK.new(%{kid: kid, alg: alg, key: key, x509: x509})
  ```
  """
  @spec new(params :: Keywords.t()) :: t
  def new(params = [x5c: _]) do
    struct(__MODULE__, Map.new(params))
  end

  @spec new(params :: Map.t()) :: t
  def new(params = %{x5c: _}) do
    struct(__MODULE__, params)
  end

  @doc """
  Generate KittenBlue.JWK from JWS Token that includes X.509 Certificate Chain
  """
  @spec new_from_jws_token(jws_token :: String.t(), trusted_cert :: certificate()) ::
          KittenBlue.JWK.t()
          | {:error, :invalid_certificate}
          | {:error, :invalid_jws_header}
          | {:error, :invalid_jws_format}
  def new_from_jws_token(jws_token, trusted_cert) do
    try do
      JOSE.JWS.peek_protected(jws_token)
      |> Jason.decode!()
      |> case do
        %{"kid" => kid, "alg" => alg, "x5c" => x5c} ->
          cert_chain = x5c |> Enum.map(&Base.decode64!/1) |> Enum.reverse()

          case :public_key.pkix_path_validation(trusted_cert, cert_chain, []) do
            {:ok, {{_, {:ECPoint, _} = key, key_params}, _}} ->
              %{
                kid: kid,
                alg: alg,
                key: {key, key_params} |> JOSE.JWK.from_key(),
                x509: KittenBlue.JWK.X509.new(x5c: x5c)
              }
              |> KittenBlue.JWK.new()

            {:ok, {{_, key, _}, _}} ->
              %{
                kid: kid,
                alg: alg,
                key: key |> JOSE.JWK.from_key(),
                x509: KittenBlue.JWK.X509.new(x5c: x5c)
              }
              |> KittenBlue.JWK.new()

            _ ->
              {:error, :invalid_certificate}
          end

        _ ->
          {:error, :invalid_jws_header}
      end
    rescue
      _ ->
        {:error, :invalid_jws_format}
    end
  end
end