-
-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
signatures interface / adapter with dependency injection
- Loading branch information
Showing
19 changed files
with
485 additions
and
213 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,216 @@ | ||
defmodule Boruta.Internal.Signatures do | ||
@behaviour Boruta.Oauth.Signatures | ||
|
||
defmodule Token do | ||
@moduledoc false | ||
|
||
use Joken.Config | ||
|
||
def token_config, do: %{} | ||
end | ||
|
||
@moduledoc false | ||
|
||
alias Boruta.Internal.Signatures.KeyPair | ||
alias Boruta.Oauth.Client | ||
|
||
@signature_algorithms [ | ||
ES256: [type: :asymmetric, hash_algorithm: :SHA256, binary_size: 16], | ||
ES384: [type: :asymmetric, hash_algorithm: :SHA384, binary_size: 24], | ||
ES512: [type: :asymmetric, hash_algorithm: :SHA512, binary_size: 32], | ||
RS256: [type: :asymmetric, hash_algorithm: :SHA256, binary_size: 16], | ||
RS384: [type: :asymmetric, hash_algorithm: :SHA384, binary_size: 24], | ||
RS512: [type: :asymmetric, hash_algorithm: :SHA512, binary_size: 32], | ||
HS256: [type: :symmetric, hash_algorithm: :SHA256, binary_size: 16], | ||
HS384: [type: :symmetric, hash_algorithm: :SHA384, binary_size: 24], | ||
HS512: [type: :symmetric, hash_algorithm: :SHA512, binary_size: 32] | ||
] | ||
|
||
@spec signature_algorithms() :: list(atom()) | ||
def signature_algorithms, do: Keyword.keys(@signature_algorithms) | ||
|
||
@spec hash_alg(Client.t()) :: hash_alg :: atom() | ||
def hash_alg(%Client{id_token_signature_alg: signature_alg}), | ||
do: @signature_algorithms[String.to_atom(signature_alg)][:hash_algorithm] | ||
|
||
@spec hash_binary_size(Client.t()) :: binary_size :: integer() | ||
def hash_binary_size(%Client{id_token_signature_alg: signature_alg}), | ||
do: @signature_algorithms[String.to_atom(signature_alg)][:binary_size] | ||
|
||
@spec hash(string :: String.t(), client :: Client.t()) :: hash :: String.t() | ||
def hash(string, client) do | ||
hash_alg(client) | ||
|> Atom.to_string() | ||
|> String.downcase() | ||
|> String.to_atom() | ||
|> :crypto.hash(string) | ||
|> binary_part(0, hash_binary_size(client)) | ||
|> Base.url_encode64(padding: false) | ||
end | ||
|
||
@spec id_token_sign(payload :: map(), client :: Client.t()) :: | ||
jwt :: String.t() | {:error, reason :: String.t()} | ||
def id_token_sign( | ||
payload, | ||
%Client{ | ||
id_token_signature_alg: signature_alg | ||
} = client | ||
) do | ||
with {:ok, signing_key} <- get_signing_key(client, :id_token) do | ||
signer = | ||
case id_token_signature_type(client) do | ||
:symmetric -> | ||
Joken.Signer.create(signature_alg, signing_key.secret) | ||
|
||
:asymmetric -> | ||
Joken.Signer.create( | ||
signature_alg, | ||
%{"pem" => signing_key.private_key}, | ||
%{ | ||
"kid" => signing_key.kid, | ||
"trust_chain" => signing_key.trust_chain | ||
} | ||
) | ||
end | ||
|
||
case Token.encode_and_sign(payload, signer) do | ||
{:ok, token, _payload} -> | ||
token | ||
|
||
{:error, error} -> | ||
{:error, "Could not sign the given payload with client credentials: #{inspect(error)}"} | ||
end | ||
end | ||
end | ||
|
||
@spec verify_id_token_signature(id_token :: String.t(), jwk :: JOSE.JWK.t()) :: | ||
:ok | {:error, reason :: String.t()} | ||
def verify_id_token_signature(id_token, jwk) do | ||
case Joken.peek_header(id_token) do | ||
{:ok, %{"alg" => alg}} -> | ||
signer = Joken.Signer.create(alg, %{"pem" => JOSE.JWK.from_map(jwk) |> JOSE.JWK.to_pem()}) | ||
|
||
case Token.verify(id_token, signer) do | ||
{:ok, claims} -> {:ok, claims} | ||
{:error, reason} -> {:error, inspect(reason)} | ||
end | ||
|
||
{:error, reason} -> | ||
{:error, inspect(reason)} | ||
end | ||
end | ||
|
||
@spec userinfo_sign(payload :: map(), client :: Client.t()) :: | ||
jwt :: String.t() | {:error, reason :: String.t()} | ||
def userinfo_sign( | ||
payload, | ||
%Client{ | ||
userinfo_signed_response_alg: signature_alg | ||
} = client | ||
) do | ||
with {:ok, signing_key} <- get_signing_key(client, :userinfo) do | ||
signer = | ||
case userinfo_signature_type(client) do | ||
:symmetric -> | ||
Joken.Signer.create(signature_alg, signing_key.secret) | ||
|
||
:asymmetric -> | ||
Joken.Signer.create( | ||
signature_alg, | ||
%{"pem" => signing_key.private_key}, | ||
%{ | ||
"kid" => signing_key.kid, | ||
"trust_chain" => signing_key.trust_chain | ||
} | ||
) | ||
end | ||
|
||
case Token.encode_and_sign(payload, signer) do | ||
{:ok, token, _payload} -> | ||
token | ||
|
||
{:error, error} -> | ||
{:error, "Could not sign the given payload with client credentials: #{inspect(error)}"} | ||
end | ||
end | ||
end | ||
|
||
@spec verifiable_credential_sign(payload :: map(), client :: Client.t()) :: | ||
jwt :: String.t() | {:error, reason :: String.t()} | ||
def verifiable_credential_sign( | ||
payload, | ||
%Client{ | ||
id_token_signature_alg: signature_alg | ||
} = client | ||
) do | ||
with {:ok, signing_key} <- get_signing_key(client, :verifiable_credential) do | ||
signer = | ||
case id_token_signature_type(client) do | ||
:symmetric -> | ||
Joken.Signer.create(signature_alg, signing_key.secret) | ||
|
||
:asymmetric -> | ||
Joken.Signer.create( | ||
signature_alg, | ||
%{"pem" => signing_key.private_key}, | ||
%{ | ||
"typ" => "JWT", | ||
"kid" => signing_key.kid, | ||
"trust_chain" => signing_key.trust_chain | ||
} | ||
) | ||
end | ||
|
||
case Token.encode_and_sign(payload, signer) do | ||
{:ok, token, _payload} -> | ||
token | ||
|
||
{:error, error} -> | ||
{:error, "Could not sign the given payload with client credentials: #{inspect(error)}"} | ||
end | ||
end | ||
end | ||
|
||
@spec kid_from_private_key(private_pem :: String.t()) :: kid :: String.t() | ||
def kid_from_private_key(private_pem) do | ||
:crypto.hash(:md5, private_pem) |> Base.url_encode64() |> String.slice(0..16) | ||
end | ||
|
||
@spec userinfo_signature_type(Client.t()) :: userinfo_token_signature_type :: atom() | ||
def userinfo_signature_type(%Client{userinfo_signed_response_alg: signature_alg}), | ||
do: @signature_algorithms[String.to_atom(signature_alg)][:type] | ||
|
||
@spec id_token_signature_type(Client.t()) :: id_token_signature_type :: atom() | ||
def id_token_signature_type(%Client{id_token_signature_alg: signature_alg}), | ||
do: @signature_algorithms[String.to_atom(signature_alg)][:type] | ||
|
||
defp get_signing_key(client, :id_token) do | ||
{:ok, | ||
%KeyPair{ | ||
type: :internal, | ||
private_key: client.private_key, | ||
secret: client.secret, | ||
kid: client.id_token_kid || kid_from_private_key(client.private_key) | ||
}} | ||
end | ||
|
||
defp get_signing_key(client, :userinfo) do | ||
{:ok, | ||
%KeyPair{ | ||
type: :internal, | ||
private_key: client.private_key, | ||
secret: client.secret, | ||
kid: client.id | ||
}} | ||
end | ||
|
||
defp get_signing_key(client, :verifiable_credential) do | ||
{:ok, | ||
%KeyPair{ | ||
type: :internal, | ||
private_key: client.private_key, | ||
secret: client.secret, | ||
kid: client.did || client.id_token_kid || kid_from_private_key(client.private_key) | ||
}} | ||
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 Boruta.Internal.Signatures.KeyPair do | ||
@moduledoc false | ||
|
||
@enforce_keys [:type] | ||
defstruct [:type, :kid, :public_key, :private_key, :secret, :trust_chain] | ||
|
||
@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 | ||
} | ||
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,42 @@ | ||
defmodule Boruta.SignaturesAdapter do | ||
@moduledoc """ | ||
Encapsulate injected `Boruta.Oauth.Signatures` and `Boruta.Openid.Signatures` adapter in context configuration | ||
""" | ||
|
||
@behaviour Boruta.Oauth.Signatures | ||
@behaviour Boruta.Openid.Signatures | ||
|
||
import Boruta.Config, only: [signatures: 0] | ||
|
||
@impl Boruta.Oauth.Signatures | ||
def signature_algorithms, do: signatures().signature_algorithms() | ||
|
||
@impl Boruta.Oauth.Signatures | ||
def hash_alg(client), do: signatures().hash_alg(client) | ||
|
||
@impl Boruta.Oauth.Signatures | ||
def hash_binary_size(client), do: signatures().hash_binary_size(client) | ||
|
||
@impl Boruta.Oauth.Signatures | ||
def hash(string, client), do: signatures().hash(string, client) | ||
|
||
@impl Boruta.Oauth.Signatures | ||
def id_token_sign(payload, client), do: signatures().id_token_sign(payload, client) | ||
|
||
@impl Boruta.Oauth.Signatures | ||
def verify_id_token_signature(id_token, jwk), | ||
do: signatures().verify_id_token_signature(id_token, jwk) | ||
|
||
@impl Boruta.Oauth.Signatures | ||
def userinfo_sign(payload, client), do: signatures().userinfo_sign(payload, client) | ||
|
||
@impl Boruta.Openid.Signatures | ||
def verifiable_credential_sign(payload, client), | ||
do: signatures().verifiable_credential_sign(payload, client) | ||
|
||
@impl Boruta.Oauth.Signatures | ||
def kid_from_private_key(private_pem), do: signatures().kid_from_private_key(private_pem) | ||
|
||
@impl Boruta.Oauth.Signatures | ||
def userinfo_signature_type(client), do: signatures().userinfo_signature_type(client) | ||
end |
File renamed without changes.
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 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 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,19 @@ | ||
defmodule Boruta.Oauth.Signatures do | ||
@moduledoc """ | ||
TODO Utilities to provide signature abilities to OAuth clients | ||
""" | ||
|
||
@callback signature_algorithms() :: list(atom()) | ||
@callback hash_alg(Boruta.Oauth.Client.t()) :: hash_alg :: atom() | ||
@callback hash_binary_size(Boruta.Oauth.Client.t()) :: binary_size :: integer() | ||
@callback hash(string :: String.t(), client :: Boruta.Oauth.Client.t()) :: hash :: String.t() | ||
@callback id_token_sign(payload :: map(), client :: Boruta.Oauth.Client.t()) :: | ||
jwt :: String.t() | {:error, reason :: String.t()} | ||
@callback verify_id_token_signature(id_token :: String.t(), jwk :: JOSE.JWK.t()) :: | ||
:ok | {:error, reason :: String.t()} | ||
@callback userinfo_sign(payload :: map(), client :: Boruta.Oauth.Client.t()) :: | ||
jwt :: String.t() | {:error, reason :: String.t()} | ||
@callback kid_from_private_key(private_pem :: String.t()) :: kid :: String.t() | ||
@callback userinfo_signature_type(Boruta.Oauth.Client.t()) :: | ||
userinfo_token_signature_type :: atom() | ||
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
Oops, something went wrong.