diff --git a/lib/ex_ucan.ex b/lib/ex_ucan.ex index 28a3c47..41e141e 100644 --- a/lib/ex_ucan.ex +++ b/lib/ex_ucan.ex @@ -2,17 +2,20 @@ defmodule ExUcan do @moduledoc """ Documentation for `ExUcan`. """ + alias ExUcan.Core.Token + alias ExUcan.Core.Structs.Ucan - @doc """ - Hello world. - - ## Examples - - iex> ExUcan.hello() - :world + @spec build(struct(), map()) :: {:ok, Ucan.t()} | {:error, String.t()} + def build(keypair, _params) do + Token.build(%{ + issuer: keypair, + audience: "did:key:z6MkwDK3M4PxU1FqcSt4quXghquH1MoWXGzTrNkNWTSy2NLD", + expiration: 86400 + }) + end - """ - def hello do - :world + @spec encode(Ucan.t()) :: String.t() + def encode(ucan) do + Token.encode(ucan) end end diff --git a/lib/ex_ucan/core/plugins.ex b/lib/ex_ucan/core/plugins.ex index 108da09..bf56774 100644 --- a/lib/ex_ucan/core/plugins.ex +++ b/lib/ex_ucan/core/plugins.ex @@ -3,18 +3,18 @@ defmodule ExUcan.Core.Plugins do @spec verify_issuer_alg(String.t(), String.t()) :: boolean() def verify_issuer_alg(did, jwt_alg) do - end @spec parseDidMethod(String.t()) :: String.t() defp parseDidMethod(did) do parts = String.split(did, ":") + with {true, _} <- {Enum.at(parts, 0) == "did", 0}, - {true, _} <- {String.length(Enum.at(parts, 1)) >=1, 1} do - {:ok, Enum.at(parts, 2)} - else - {false, 0} -> {:error, "Not a DID: #{did}"} - {false, 1} -> {:error, "No DID method included: #{did}"} + {true, _} <- {String.length(Enum.at(parts, 1)) >= 1, 1} do + {:ok, Enum.at(parts, 2)} + else + {false, 0} -> {:error, "Not a DID: #{did}"} + {false, 1} -> {:error, "No DID method included: #{did}"} end end end diff --git a/lib/ex_ucan/core/structs.ex b/lib/ex_ucan/core/structs.ex index 878848d..722ce08 100644 --- a/lib/ex_ucan/core/structs.ex +++ b/lib/ex_ucan/core/structs.ex @@ -1,3 +1,13 @@ +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 @@ -9,17 +19,14 @@ defmodule ExUcan.Core.Structs.UcanHeader do } @derive Jason.Encoder - defstruct( - alg: "", - typ: "" - ) - + defstruct [:alg, :typ] end defmodule ExUcan.Core.Structs.UcanPayload do @moduledoc """ Ucan Payload """ + alias ExUcan.Core.Structs.Capability @type t :: %__MODULE__{ ucv: String.t(), @@ -29,22 +36,12 @@ defmodule ExUcan.Core.Structs.UcanPayload do exp: integer(), nnc: String.t(), fct: map(), - cap: map(), + cap: list(Capability.t()), prf: list(String.t()) } @derive Jason.Encoder - defstruct( - ucv: "", - iss: "", - aud: "", - nbf: 0, - exp: nil, - nnc: "", - fct: %{}, - cap: %{}, - prf: [] - ) + defstruct [:ucv, :iss, :aud, :nbf, :exp, :nnc, :fct, :cap, :prf] end defmodule ExUcan.Core.Structs.Ucan do @@ -62,10 +59,5 @@ defmodule ExUcan.Core.Structs.Ucan do } @derive Jason.Encoder - defstruct( - header: nil, - payload: nil, - signed_data: "", - signature: "" - ) + defstruct [:header, :payload, :signed_data, :signature] end diff --git a/lib/ex_ucan/core/token.ex b/lib/ex_ucan/core/token.ex index f971270..d34ead0 100644 --- a/lib/ex_ucan/core/token.ex +++ b/lib/ex_ucan/core/token.ex @@ -11,19 +11,21 @@ defmodule ExUcan.Core.Token do @token_type "JWT" @version %{major: 0, minor: 10, patch: 0} - @spec build(params::%{ - issuer: struct(), - audience: String.t(), - # Add capabilities struct later - capabilities: list(), - life_time_in_seconds: number(), - expiration: number(), - not_before: number(), - # Add Facts struct later - facts: list(), - proofs: list(String.t()), - add_nonce?: boolean() - }) :: Ucan.t() + @spec build( + params :: %{ + issuer: struct(), + audience: String.t(), + # Add capabilities struct later + capabilities: list(), + life_time_in_seconds: number(), + expiration: number(), + not_before: number(), + # Add Facts struct later + facts: list(), + proofs: list(String.t()), + add_nonce?: boolean() + } + ) :: Ucan.t() def build(params) do {:ok, payload} = build_payload(%{params | issuer: Keymaterial.did(params.issuer)}) sign_with_payload(payload, params.issuer) @@ -74,12 +76,15 @@ defmodule ExUcan.Core.Token do "#{ucan.signed_data}.#{ucan.signature}" end + @spec validate(String.t()) :: {:ok, Ucan.t()} | {:error, String.t()} + def validate(encoded_ucan) do + end + defp add_nonce(true), do: Utils.generate_nonce() defp add_nonce(false), do: nil @spec sign_with_payload(payload :: UcanPayload.t(), keypair :: struct()) :: Ucan.t() defp sign_with_payload(payload, keypair) do - # TODO ExUcan.Core.Plugins.verify_issuer_alg header = %UcanHeader{alg: keypair.jwt_alg, typ: @token_type} encoded_header = encode_ucan_parts(header) @@ -87,6 +92,7 @@ defmodule ExUcan.Core.Token do signed_data = "#{encoded_header}.#{encoded_payload}" signature = Keymaterial.sign(keypair, signed_data) + %Ucan{ header: header, payload: payload, @@ -102,4 +108,34 @@ defmodule ExUcan.Core.Token do |> Base.url_encode64(padding: false) end + @spec is_expired?() :: boolean() + defp is_expired?() do + end + + @spec parse_encoded_ucan(String.t()) :: + {:ok, {UcanHeader.t(), UcanPayload.t()}} | {:error, String.t()} + def parse_encoded_ucan(encoded_ucan) do + opts = [padding: false] + + with {:ok, {header, payload, sign}} <- tear_into_parts(encoded_ucan), + {:ok, decoded_header} <- Base.url_decode64(header, opts) |> IO.inspect(), + {:ok, header} <- Jason.decode(decoded_header, keys: :atoms) |> IO.inspect(), + {:ok, decoded_payload} <- Base.url_decode64(payload, opts), + {:ok, payload} <- Jason.decode(decoded_payload, keys: :atoms) do + {:ok, struct(UcanHeader, header), struct(UcanPayload, payload)} + end + end + + @spec tear_into_parts(String.t()) :: {:ok, tuple()} | {:error, String.t()} + defp tear_into_parts(encoded_ucan) do + err_msg = + "Can't parse UCAN: #{encoded_ucan}: Expected JWT format: 3 dot-separated base64url-encoded values." + + case String.split(encoded_ucan, ".") |> List.to_tuple() do + {"", _, _} -> {:error, err_msg} + {_, "", _} -> {:error, err_msg} + {_, _, ""} -> {:error, err_msg} + ucan_parts -> {:ok, ucan_parts} + end + end end diff --git a/lib/ex_ucan/plugins/ed25519/keypair.ex b/lib/ex_ucan/plugins/ed25519/keypair.ex index 2af4062..ffd5165 100644 --- a/lib/ex_ucan/plugins/ed25519/keypair.ex +++ b/lib/ex_ucan/plugins/ed25519/keypair.ex @@ -4,7 +4,6 @@ defmodule ExUcan.Plugins.Ed25519.Keypair do """ alias ExUcan.Core.Keymaterial alias ExUcan.Plugins.Ed25519.Crypto - @behaviour Keymaterial # TODO: more doc.. # TODO: Need type doc @@ -27,6 +26,7 @@ defmodule ExUcan.Plugins.Ed25519.Keypair do def create(exportable?) do {pub, priv} = :crypto.generate_key(:eddsa, :ed25519) + %__MODULE__{ jwt_alg: "EdDSA", secret_key: priv, @@ -41,7 +41,12 @@ defmodule ExUcan.Plugins.Ed25519.Keypair do end def sign(keypair, payload) do - :public_key.sign(payload, :ignored, {:ed_pri, :ed25519, keypair.public_key, keypair.secret_key}, []) + :public_key.sign( + payload, + :ignored, + {:ed_pri, :ed25519, keypair.public_key, keypair.secret_key}, + [] + ) end end end