Skip to content

Commit

Permalink
WIP universal key management for oauth clients
Browse files Browse the repository at this point in the history
  • Loading branch information
patatoid committed Dec 15, 2024
1 parent 4b04501 commit 8695a2d
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 35 deletions.
23 changes: 21 additions & 2 deletions lib/boruta/adapters/ecto/schemas/client.ex
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ defmodule Boruta.Ecto.Client do
alias Boruta.Ecto.Scope
alias Boruta.Oauth
alias Boruta.Oauth.Client
alias Boruta.Universal
alias ExJsonSchema.Validator.Error.BorutaFormatter

@type t :: %__MODULE__{
Expand Down Expand Up @@ -69,10 +70,10 @@ defmodule Boruta.Ecto.Client do
@key_pair_type_schema %{
"type" => "object",
"properties" => %{
"type" => %{"type" => "string", "pattern" => "^ec|rsa"},
"type" => %{"type" => "string", "pattern" => "^ec|rsa|universal$"},
"modulus_size" => %{"type" => "string"},
"exponent_size" => %{"type" => "string"},
"curve" => %{"type" => "string", "pattern" => "^P-256|P-384|P-512"}
"curve" => %{"type" => "string", "pattern" => "^P-256|P-384|P-512$"}
},
"required" => ["type"]
}
Expand Down Expand Up @@ -457,6 +458,9 @@ defmodule Boruta.Ecto.Client do
%{"type" => "ec", "curve" => curve} ->
JOSE.JWK.generate_key({:ec, curve})

%{"type" => "universal"} ->
"universal"

_ ->
nil
end
Expand All @@ -465,6 +469,21 @@ defmodule Boruta.Ecto.Client do
nil ->
add_error(changeset, :private_key, "private_key_type is invalid")

"universal" ->
with {:ok, did, jwk} <- Did.create("key"),
{:ok, key} <- Universal.Signatures.SigningKey.get_key_by_did(did) do
public_key = JOSE.JWK.from_map(jwk)
{_type, public_pem} = JOSE.JWK.to_pem(public_key)

changeset
|> put_change(:private_key, key["id"])
|> put_change(:public_key, public_pem)
|> put_change(:did, did)
else
{:error, error} ->
add_error(changeset, :private_key, error)
end

private_key ->
public_key = JOSE.JWK.to_public(private_key)

Expand Down
46 changes: 46 additions & 0 deletions lib/boruta/adapters/universal/signatures/signging_key.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
defmodule Boruta.Universal.Signatures.SigningKey do
@moduledoc false

@enforce_keys [:type]
defstruct [:type, :kid, :public_key, :private_key, :secret, :trust_chain]

import Boruta.Config,
only: [
universal_did_auth: 0,
universal_keys_base_url: 0
]

@type t :: %__MODULE__{
type: :external | :internal,
public_key: String.t() | nil,
private_key: String.t() | nil,
kid: String.t() | nil,
secret: String.t() | nil,
trust_chain: list(String.t()) | nil
}

def get_key_by_did(did) do
with {:ok, %Finch.Response{status: 200, body: body}} <-
Finch.build(
:get,
universal_keys_base_url(),
[
{"Authorization", "Bearer #{universal_did_auth()[:token]}"},
{"Content-Type", "application/json"}
]
)
|> Finch.request(OpenIDHttpClient),
{:ok, keys} <- Jason.decode(body) do
case Enum.filter(keys, fn %{"controller" => controller} -> controller == did end) do
[key] ->
{:ok, key}

_ ->
{:error, "Could not fetch universal key."}
end
else
_ ->
{:error, "Could not fetch universal key."}
end
end
end
7 changes: 7 additions & 0 deletions lib/boruta/config.ex
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ defmodule Boruta.Config do
did_resolver_base_url: "https://api.godiddy.com/1.0.0/universal-resolver",
did_registrar_base_url: "https://api.godiddy.com/1.0.0/universal-registrar",
signature_credentials_base_url: "https://api.godiddy.com/1.0.0/universal-issuer/credentials/issue",
universal_keys_base_url: "https://api.godiddy.com/0.1.0/wallet-service/keys",
universal_did_auth: %{
type: "bearer",
token: nil
Expand Down Expand Up @@ -209,6 +210,12 @@ defmodule Boruta.Config do
Keyword.fetch!(oauth_config(), :signature_credentials_base_url)
end

@spec universal_keys_base_url() :: String.t()
@doc false
def universal_keys_base_url do
Keyword.fetch!(oauth_config(), :universal_keys_base_url)
end

@spec universal_did_auth() :: map()
@doc false
def universal_did_auth do
Expand Down
90 changes: 57 additions & 33 deletions lib/boruta/did.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ defmodule Boruta.Did do
resolver_url = "#{ebsi_did_resolver_base_url()}/identifiers/#{did}"

case Finch.build(:get, resolver_url)
|> Finch.request(OpenIDHttpClient) do
|> Finch.request(OpenIDHttpClient) do
{:ok, %Finch.Response{body: body, status: 200}} ->
case Jason.decode(body) do
{:ok, %{"didDocument" => did_document}} ->
{:ok, did_document}

{:ok, did_document} ->
{:ok, did_document}

{:error, error} ->
{:error, error}
end
Expand All @@ -40,61 +42,83 @@ defmodule Boruta.Did do
def resolve(did) do
resolver_url = "#{did_resolver_base_url()}/identifiers/#{did}"

with {:ok, %Finch.Response{body: body, status: 200}} <- Finch.build(:get, resolver_url, [
{"Authorization", "Bearer #{universal_did_auth()[:token]}"}
])
|> Finch.request(OpenIDHttpClient),
with {:ok, %Finch.Response{body: body, status: 200}} <-
Finch.build(:get, resolver_url, [
{"Authorization", "Bearer #{universal_did_auth()[:token]}"}
])
|> Finch.request(OpenIDHttpClient),
{:ok, %{"didDocument" => did_document}} <- Jason.decode(body) do
{:ok, did_document}

else
{:ok, %Finch.Response{body: body}} ->
{:error, body}

{:error, error} ->
{:error, inspect(error)}

{:ok, response} ->
{:error, "Invalid resolver response: \"#{inspect(response)}\""}
end
end

@spec create(method :: String.t(), jwk :: map()) ::
{:ok, did :: String.t()} | {:error, reason :: String.t()}
def create(method, jwk) do
def create("key" = method, jwk \\ nil) do
payload = %{
"didDocument" => %{
"@context" => ["https//www.w3.org/ns/did/v1"],
"service" => [],
"verificationMethod" => [
%{
"id" => "#temp",
"type" => "JsonWebKey2020",
"publicKeyJwk" => jwk
}
]
},
"options" => %{
"keyType" => "Ed25519",
"clientSecretMode" => true,
"jwkJcsPub" => true
"service" => []
},
"secret" => %{}
}

case Finch.build(
:post,
did_registrar_base_url() <> "/create?method=#{method}",
[
{"Authorization", "Bearer #{universal_did_auth()[:token]}"},
{"Content-Type", "application/json"}
],
Jason.encode!(payload)
)
|> Finch.request(OpenIDHttpClient) do
{:ok, %Finch.Response{status: 201, body: body}} ->
%{"didState" => %{"did" => did}} = Jason.decode!(body)
{:ok, did}
payload =
case jwk do
nil ->
payload
|> Map.put("options", %{
"keyType" => "Ed25519",
"jwkJcsPub" => true
})

jwk ->
Map.put(payload, "didDocument", %{
"@context" => ["https//www.w3.org/ns/did/v1"],
"service" => [],
"verificationMethod" => [
%{
"id" => "#temp",
"type" => "JsonWebKey2020",
"publicKeyJwk" => jwk
}
]
})
|> Map.put("options", %{
"keyType" => "Ed25519",
"clientSecretMode" => true,
"jwkJcsPub" => true
})
end

with {:ok, %Finch.Response{status: 201, body: body}} <-
Finch.build(
:post,
did_registrar_base_url() <> "/create?method=#{method}",
[
{"Authorization", "Bearer #{universal_did_auth()[:token]}"},
{"Content-Type", "application/json"}
],
Jason.encode!(payload)
)
|> Finch.request(OpenIDHttpClient),
%{
"didState" => %{
"did" => did,
"didDocument" => %{"verificationMethod" => [%{"publicKeyJwk" => jwk}]}
}
} <- Jason.decode!(body) do
{:ok, did, jwk}
else
_ ->
{:error, "Could not create did."}
end
Expand Down
6 changes: 6 additions & 0 deletions test/boruta/admin_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,12 @@ defmodule Boruta.Ecto.AdminTest do
assert {:ok, %Client{}} = Admin.create_client(@client_valid_attrs)
end

# TODO create an universal mock adapter
@tag :skip
test "creates a client with universal key" do
assert {:ok, %Client{}} = Admin.create_client(Map.put(@client_valid_attrs, :key_pair_type, %{"type" => "universal"}))
end

test "creates a client with supported_grant_types" do
assert {:ok, %Client{supported_grant_types: ["client_credentials"]}} =
Admin.create_client(
Expand Down

0 comments on commit 8695a2d

Please sign in to comment.