This repository has been archived by the owner on Oct 30, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: added build and encode for ucans (unplugged version)
- Loading branch information
Showing
7 changed files
with
233 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# TODO: docs?? | ||
|
||
defprotocol ExUcan.Core.Keymaterial do | ||
alias ExUcan.Core.Structs.UcanPayload | ||
@spec did(struct()) :: String.t() | ||
def did(type) | ||
|
||
@spec sign(struct(), UcanPayload.t()) :: binary() | ||
def sign(type, payload) | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
defmodule ExUcan.Core.Plugins do | ||
# TODO: docs | ||
|
||
@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}"} | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
defmodule ExUcan.Core.Structs.UcanHeader do | ||
@moduledoc """ | ||
Ucan header | ||
""" | ||
|
||
@type t :: %__MODULE__{ | ||
alg: String.t(), | ||
typ: String.t() | ||
} | ||
|
||
@derive Jason.Encoder | ||
defstruct( | ||
alg: "", | ||
typ: "" | ||
) | ||
|
||
end | ||
|
||
defmodule ExUcan.Core.Structs.UcanPayload do | ||
@moduledoc """ | ||
Ucan Payload | ||
""" | ||
|
||
@type t :: %__MODULE__{ | ||
ucv: String.t(), | ||
iss: String.t(), | ||
aud: String.t(), | ||
nbf: integer(), | ||
exp: integer(), | ||
nnc: String.t(), | ||
fct: map(), | ||
cap: map(), | ||
prf: list(String.t()) | ||
} | ||
|
||
@derive Jason.Encoder | ||
defstruct( | ||
ucv: "", | ||
iss: "", | ||
aud: "", | ||
nbf: 0, | ||
exp: nil, | ||
nnc: "", | ||
fct: %{}, | ||
cap: %{}, | ||
prf: [] | ||
) | ||
end | ||
|
||
defmodule ExUcan.Core.Structs.Ucan do | ||
@moduledoc """ | ||
UCAN struct | ||
""" | ||
alias ExUcan.Core.Structs.UcanHeader | ||
alias ExUcan.Core.Structs.UcanPayload | ||
|
||
@type t :: %__MODULE__{ | ||
header: UcanHeader.t(), | ||
payload: UcanPayload.t(), | ||
signed_data: String.t(), | ||
signature: String.t() | ||
} | ||
|
||
@derive Jason.Encoder | ||
defstruct( | ||
header: nil, | ||
payload: nil, | ||
signed_data: "", | ||
signature: "" | ||
) | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
defmodule ExUcan.Core.Token do | ||
@moduledoc """ | ||
Creates and manages UCAN tokens | ||
""" | ||
alias ExUcan.Core.Structs.UcanHeader | ||
alias ExUcan.Core.Keymaterial | ||
alias ExUcan.Core.Structs.Ucan | ||
alias ExUcan.Core.Utils | ||
alias ExUcan.Core.Structs.UcanPayload | ||
|
||
@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() | ||
def build(params) do | ||
{:ok, payload} = build_payload(%{params | issuer: Keymaterial.did(params.issuer)}) | ||
sign_with_payload(payload, params.issuer) | ||
end | ||
|
||
# TODO: docs | ||
@spec build_payload( | ||
params :: %{ | ||
issuer: String.t(), | ||
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() | ||
} | ||
) :: UcanPayload.t() | ||
def build_payload(params) do | ||
with {:iss, true} <- {:iss, String.starts_with?(params.issuer, "did")}, | ||
{:aud, true} <- {:aud, String.starts_with?(params.audience, "did")} do | ||
current_time_in_seconds = DateTime.utc_now() |> DateTime.to_unix() | ||
exp = params.expiration || current_time_in_seconds + params.life_time_in_seconds | ||
|
||
{:ok, | ||
%{ | ||
ucv: "#{@version.major}.#{@version.minor}.#{@version.patch}", | ||
iss: params.issuer, | ||
aud: params.audience, | ||
nbf: params[:not_before] || nil, | ||
exp: exp, | ||
nnc: add_nonce(params[:add_nonce] || false), | ||
fct: params[:facts] || [], | ||
cap: params[:capabilities] || [], | ||
prf: params[:proofs] || [] | ||
}} | ||
else | ||
{:iss, false} -> {:error, "The issuer must be a DID"} | ||
{:aud, false} -> {:error, "The audience must be a DID"} | ||
end | ||
end | ||
|
||
@spec encode(Ucan.t()) :: String.t() | ||
def encode(%Ucan{} = ucan) do | ||
"#{ucan.signed_data}.#{ucan.signature}" | ||
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, | ||
signed_data: signed_data, | ||
signature: Base.url_encode64(signature, padding: false) | ||
} | ||
end | ||
|
||
@spec encode_ucan_parts(UcanHeader.t() | UcanPayload.t()) :: String.t() | ||
defp encode_ucan_parts(data) do | ||
data | ||
|> Jason.encode!() | ||
|> Base.url_encode64(padding: false) | ||
end | ||
|
||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
defmodule ExUcan.Core.Utils do | ||
@moduledoc """ | ||
Core utils | ||
""" | ||
@chars "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" | ||
|
||
# TODO: docs | ||
def generate_nonce(len \\ 6) | ||
|
||
def generate_nonce(len) do | ||
Enum.reduce(1..len, "", fn _, nonce -> | ||
nonce <> String.at(@chars, :rand.uniform(String.length(@chars) - 1)) | ||
end) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.