Skip to content
This repository has been archived by the owner on Oct 30, 2023. It is now read-only.

Commit

Permalink
fix: keymaterial
Browse files Browse the repository at this point in the history
organized keymaterials + entry function for keypair generation
  • Loading branch information
madclaws committed Oct 29, 2023
1 parent 55e2e41 commit fa1983c
Show file tree
Hide file tree
Showing 14 changed files with 245 additions and 84 deletions.
15 changes: 12 additions & 3 deletions lib/ex_ucan.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,19 @@ defmodule ExUcan do
@moduledoc """
Documentation for `ExUcan`.
"""
alias ExUcan.Plugins.Ed25519
alias ExUcan.Keymaterial.Ed25519.Keypair
alias ExUcan.Core.Token
alias ExUcan.Core.Structs.Ucan

def create_keypair() do
Ed25519.Keypair.create()
@doc """
Creates a default keypair with EdDSA algorithm
This keypair can be later used for create UCAN tokens
Keypair generated with different algorithms like RSA will be coming soon..
"""
@spec create_default_keypair() :: Keypair.t()
def create_default_keypair() do
Keypair.create()
end

@spec build(struct(), map()) :: {:ok, Ucan.t()} | {:error, String.t()}
Expand All @@ -24,3 +31,5 @@ defmodule ExUcan do
Token.encode(ucan)
end
end

# TODO: Need a proper UCAN builder like rust.
55 changes: 55 additions & 0 deletions lib/ex_ucan/core/capability/data.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
defmodule ExUcan.Core.Capability do

Check warning on line 1 in lib/ex_ucan/core/capability/data.ex

View workflow job for this annotation

GitHub Actions / Build and test (1.15.7, 26.0.2)

Modules should have a @moduledoc tag.
# TODO: All the docs needed
@type t :: %__MODULE__{
resource: String.t(),
ability: String.t(),
caveat: list(map())
}
defstruct [:resource, :ability, :caveat]

@spec new(String.t(), String.t(), list()) :: __MODULE__.t()
def new(resource, ability, caveat) do
%__MODULE__{
resource: resource,
ability: ability,
caveat: caveat
}
end
end

defmodule ExUcan.Core.Capabilities do
@moduledoc """
Capabilities always deals with capabilites as map of maps
map<String: map<String: list()>>
"""
alias ExUcan.Core.Capability
# TODO: All the docs needed

# def validate(capabilities) when is_map(capabilities) do
# capabilities
# |> Enum.reduce_while(%{}, fn {resource, ability}, caps ->
# # ability should be map
# # iter through ability

# end)
# end

def validate(_), do: {:error, "Capabilities must be an object."}

@spec map_to_sequence(map()) :: list(Capability.t())
def map_to_sequence(capabilities) do
capabilities
|> Enum.reduce([], fn {resource, ability}, caps ->
[{ability, caveat}] = Map.to_list(ability)
caps ++ [Capability.new(resource, ability, caveat)]
end)
end

@spec sequence_to_map(list(Capability.t())) :: map()
def sequence_to_map(capabilites) do
capabilites
|> Enum.reduce(%{}, fn %Capability{} = cap, caps ->
Map.put(caps, cap.resource, %{cap.ability => cap.caveat})
end)
end
end
10 changes: 0 additions & 10 deletions lib/ex_ucan/core/keymaterial.ex

This file was deleted.

13 changes: 1 addition & 12 deletions lib/ex_ucan/core/structs.ex
Original file line number Diff line number Diff line change
@@ -1,18 +1,7 @@
defmodule ExUcan.Core.Structs.Capability do
# TODO: All the docs needed
@type t :: %__MODULE__{
resource: String.t(),
ability: String.t(),
caveat: list(map())
}
defstruct [:resource, :ability, :caveat]
end

defmodule ExUcan.Core.Structs.UcanHeader do
@moduledoc """
Ucan header
"""

@type t :: %__MODULE__{
alg: String.t(),
typ: String.t()
Expand All @@ -26,7 +15,7 @@ defmodule ExUcan.Core.Structs.UcanPayload do
@moduledoc """
Ucan Payload
"""
alias ExUcan.Core.Structs.Capability
alias ExUcan.Core.Capability

@type t :: %__MODULE__{
ucv: String.t(),
Expand Down
3 changes: 1 addition & 2 deletions lib/ex_ucan/core/token.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ defmodule ExUcan.Core.Token do
Creates and manages UCAN tokens
"""
alias ExUcan.Core.Plugins
alias ExUnit.DuplicateDescribeError
alias ExUcan.Core.Structs.UcanHeader
alias ExUcan.Core.Keymaterial
alias ExUcan.Keymaterial
alias ExUcan.Core.Structs.Ucan
alias ExUcan.Core.Utils
alias ExUcan.Core.Structs.UcanPayload
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
defmodule ExUcan.Plugins.Ed25519.Crypto do
defmodule ExUcan.Keymaterial.Ed25519.Crypto do
@moduledoc """
Crypto functions related to `ExUcan.Plugins.Ed25519.Keypair`
"""
alias ExUcan.Plugins.Utils
alias ExUcan.Keymaterial.Utils

@edwards_did_prefix <<0xED, 0x01>>

Expand Down
62 changes: 62 additions & 0 deletions lib/ex_ucan/keymaterial/ed25519/keypair.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
defmodule ExUcan.Keymaterial.Ed25519.Keypair do
@moduledoc """
Encapsulates Ed25519 Keypair generation and implements `Keymaterial` protocol
"""

alias ExUcan.Keymaterial
alias ExUcan.Keymaterial.Ed25519.Crypto

@typedoc """
A Keypair struct holds the generate keypairs and its metadata
jwt_alg - JWT algorith used, ex: edDSA, HMAC etc..
secret_key - Private key bytes
public_key - Public key bytes
"""
@type t :: %__MODULE__{
jwt_alg: String.t(),
secret_key: binary(),
public_key: binary()
}

defstruct [:jwt_alg, :secret_key, :public_key]

@doc """
Creates a keypair with EdDSA algorithm
This keypair can be later used for create UCAN tokens
"""
@spec create() :: __MODULE__.t()
def create() do
{pub, priv} = :crypto.generate_key(:eddsa, :ed25519)

%__MODULE__{
jwt_alg: "EdDSA",
secret_key: priv,
public_key: pub
}
end

defimpl Keymaterial do
def get_jwt_algorithm_name(keypair) do
keypair.jwt_alg
end

def get_did(keypair) do
Crypto.publickey_to_did(keypair.public_key)
end

def sign(keypair, payload) do
:public_key.sign(
payload,
:ignored,
{:ed_pri, :ed25519, keypair.public_key, keypair.secret_key},
[]
)
end

def verify(keypair, payload, signature) do
:public_key.verify(payload, :ignored, signature, {:ed_pub, :ed25519, keypair.public_key})
end
end
end
34 changes: 34 additions & 0 deletions lib/ex_ucan/keymaterial/protocol.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
defprotocol ExUcan.Keymaterial do
@moduledoc """
Keymaterial protocol used by Keypair generation modules like `ExUcan.Keymaterial.Ed25519.Keypair`
This protocol requires four functions to be implemented, `get_jwt_algorithm_name/1`,
`get_did/1`, `sign/2` and `verify/3`
"""
alias ExUcan.Core.Structs.UcanPayload

@doc """
Returns the Jwt algorithm used by the Keypair to create Ucan
"""
@spec get_jwt_algorithm_name(any()) :: String.t()
def get_jwt_algorithm_name(type)

@doc """
Retursn the did (Decentralized Identifiers) generated using the keypair
"""
@spec get_did(any()) :: String.t()
def get_did(type)

@doc """
Creates signature on the given payload with the keypair
"""
@spec sign(any(), binary()) :: binary()
def sign(type, payload)

@doc """
Verifies the signature with the keypair
"""
@spec verify(any(), binary(), binary()) :: boolean()
def verify(type, payload, signature)
end
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
defmodule ExUcan.Plugins.Utils do
defmodule ExUcan.Keymaterial.Utils do
@moduledoc """
Utilites related to plugins
"""
Expand Down
52 changes: 0 additions & 52 deletions lib/ex_ucan/plugins/ed25519/keypair.ex

This file was deleted.

42 changes: 42 additions & 0 deletions test/capability_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
defmodule CapabilityTest do
alias ExUcan.Core.Capabilities
alias ExUcan.Core.Capability
use ExUnit.Case

@tag :caps
test "can_cast_between_map_and_sequence" do
cap_foo = Capability.new("example//foo", "ability/foo", %{})
assert cap_foo.caveat == %{}
cap_bar = Capability.new("example://bar", "ability/bar", %{"beep" => 1})

cap_sequence = [cap_foo, cap_bar]

cap_maps = Capabilities.sequence_to_map(cap_sequence)
assert Capabilities.map_to_sequence(cap_maps) == cap_sequence
end

test "it_rejects_non_compliant_json" do
failure_cases = [
{
[],
"resources must be map"
},
{
%{"resource:foo" => []},
"abilities must be map"
},
{
%{"resource:foo" => {}},
"resource must have at least one ability"
},
{
%{"resource:foo" => %{"ability/read" => %{}}},
"caveats must be a list"
},
{
%{"resource:foo" => %{"ability/read" => [1]}},
"caveat must be object"
}
]
end
end
7 changes: 5 additions & 2 deletions test/ex_ucan_test.exs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
defmodule ExUcanTest do
alias ExUcan.Keymaterial.Ed25519.Keypair
use ExUnit.Case
doctest ExUcan

test "greets the world" do
assert ExUcan.hello() == :world
test "create_default_keypair" do
assert %Keypair{jwt_alg: "EdDSA"} = keypair = Keypair.create()
assert is_binary(keypair.public_key)
assert is_binary(keypair.secret_key)
end
end
29 changes: 29 additions & 0 deletions test/keymaterial/keypair_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
defmodule Keymaterial.KeypairTest do
alias ExUcan.Keymaterial.Ed25519.Keypair
alias ExUcan.Keymaterial
use ExUnit.Case

test "creating edDSA keypair" do
assert %Keypair{jwt_alg: "EdDSA"} = keypair = Keypair.create()
assert is_binary(keypair.public_key)
assert is_binary(keypair.secret_key)
end

test "testing success keymaterial implementation" do
assert %Keypair{jwt_alg: "EdDSA"} = keypair = Keypair.create()
assert Keymaterial.get_jwt_algorithm_name(keypair) == "EdDSA"
assert "did:key:z" <> _ = Keymaterial.get_did(keypair)
signature = Keymaterial.sign(keypair, "Hello world")
assert is_binary(signature)
assert Keymaterial.verify(keypair, "Hello world", signature)
end

test "testing failed cases, keymaterial implementations" do
assert %Keypair{jwt_alg: "EdDSA"} = keypair = Keypair.create()
assert Keymaterial.get_jwt_algorithm_name(keypair) == "EdDSA"
assert "did:key:z" <> _ = Keymaterial.get_did(keypair)
signature = Keymaterial.sign(keypair, "Hello world")
assert is_binary(signature)
refute Keymaterial.verify(keypair, "Hell world", signature)
end
end
1 change: 1 addition & 0 deletions test/test_helper.exs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
ExUnit.start()
ExUnit.configure(exclude: [:skip])

0 comments on commit fa1983c

Please sign in to comment.