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

Commit

Permalink
feat: added encoded_ucan parser
Browse files Browse the repository at this point in the history
  • Loading branch information
madclaws committed Oct 29, 2023
1 parent c57586f commit c716463
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 55 deletions.
23 changes: 13 additions & 10 deletions lib/ex_ucan.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
12 changes: 6 additions & 6 deletions lib/ex_ucan/core/plugins.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
38 changes: 15 additions & 23 deletions lib/ex_ucan/core/structs.ex
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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(),
Expand All @@ -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
Expand All @@ -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
64 changes: 50 additions & 14 deletions lib/ex_ucan/core/token.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -74,19 +76,23 @@ 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)
encoded_payload = encode_ucan_parts(payload)

signed_data = "#{encoded_header}.#{encoded_payload}"
signature = Keymaterial.sign(keypair, signed_data)

%Ucan{
header: header,
payload: payload,
Expand All @@ -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
9 changes: 7 additions & 2 deletions lib/ex_ucan/plugins/ed25519/keypair.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand All @@ -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

0 comments on commit c716463

Please sign in to comment.