%% -*- mode: erlang; tab-width: 4; indent-tabs-mode: 1; st-rulers: [70] -*-
%% vim: ts=4 sw=4 ft=erlang noet
%%%-------------------------------------------------------------------
%%% @author Andrew Bennett <potatosaladx@gmail.com>
%%% @copyright 2014-2022, Andrew Bennett
%%% @doc
%%%
%%% @end
%%% Created :  21 Jul 2015 by Andrew Bennett <potatosaladx@gmail.com>
%%%-------------------------------------------------------------------
-module(jose_jwk).

-include("jose_jwe.hrl").
-include("jose_jwk.hrl").
-include("jose_jws.hrl").

-callback from_map(Fields) -> KTY
	when
		Fields :: map(),
		KTY    :: any().
-callback to_key(KTY) -> Key
	when
		KTY :: any(),
		Key :: any().
-callback to_map(KTY, Fields) -> Map
	when
		KTY    :: any(),
		Fields :: map(),
		Map    :: map().
-callback to_public_map(KTY, Fields) -> Map
	when
		KTY    :: any(),
		Fields :: map(),
		Map    :: map().
-callback to_thumbprint_map(KTY, Fields) -> Map
	when
		KTY    :: any(),
		Fields :: map(),
		Map    :: map().

%% Decode API
-export([from/1]).
-export([from/2]).
-export([from_binary/1]).
-export([from_binary/2]).
-export([from_der/1]).
-export([from_der/2]).
-export([from_der_file/1]).
-export([from_der_file/2]).
-export([from_file/1]).
-export([from_file/2]).
-export([from_firebase/1]).
-export([from_key/1]).
-export([from_map/1]).
-export([from_map/2]).
-export([from_oct/1]).
-export([from_oct/2]).
-export([from_oct_file/1]).
-export([from_oct_file/2]).
-export([from_okp/1]).
-export([from_openssh_key/1]).
-export([from_openssh_key_file/1]).
-export([from_pem/1]).
-export([from_pem/2]).
-export([from_pem_file/1]).
-export([from_pem_file/2]).
%% Encode API
-export([to_binary/1]).
-export([to_binary/2]).
-export([to_binary/3]).
-export([to_der/1]).
-export([to_der/2]).
-export([to_der_file/2]).
-export([to_der_file/3]).
-export([to_file/2]).
-export([to_file/3]).
-export([to_file/4]).
-export([to_key/1]).
-export([to_map/1]).
-export([to_map/2]).
-export([to_map/3]).
-export([to_oct/1]).
-export([to_oct/2]).
-export([to_oct/3]).
-export([to_oct_file/2]).
-export([to_oct_file/3]).
-export([to_oct_file/4]).
-export([to_okp/1]).
-export([to_openssh_key/1]).
-export([to_openssh_key_file/2]).
-export([to_pem/1]).
-export([to_pem/2]).
-export([to_pem_file/2]).
-export([to_pem_file/3]).
-export([to_public/1]).
-export([to_public_file/2]).
-export([to_public_key/1]).
-export([to_public_map/1]).
-export([to_thumbprint_map/1]).
%% API
-export([block_decrypt/2]).
-export([block_encrypt/2]).
-export([block_encrypt/3]).
-export([block_encryptor/1]).
-export([box_decrypt/2]).
-export([box_encrypt/2]).
-export([box_encrypt/3]).
-export([box_encrypt/4]).
-deprecated([{box_decrypt, 2, next_major_release}]).
-deprecated([{box_encrypt, 2, next_major_release}]).
-deprecated([{box_encrypt, 3, next_major_release}]).
-deprecated([{box_encrypt, 4, next_major_release}]).
-export([box_decrypt_ecdh_1pu/3]).
-export([box_encrypt_ecdh_1pu/3]).
-export([box_encrypt_ecdh_1pu/4]).
-export([box_encrypt_ecdh_1pu/5]).
-export([box_decrypt_ecdh_es/2]).
-export([box_encrypt_ecdh_es/2]).
-export([box_encrypt_ecdh_es/3]).
-export([box_encrypt_ecdh_es/4]).
-export([box_decrypt_ecdh_ss/2]).
-export([box_encrypt_ecdh_ss/2]).
-export([box_encrypt_ecdh_ss/3]).
-export([box_encrypt_ecdh_ss/4]).
-export([generate_key/1]).
-export([merge/2]).
-export([shared_secret/2]).
-export([sign/2]).
-export([sign/3]).
-export([signer/1]).
-export([thumbprint/1]).
-export([thumbprint/2]).
-export([thumbprint_concat/1]).
-export([thumbprint_concat/2]).
-export([verifier/1]).
-export([verify/2]).
-export([verify_strict/3]).

%% Types
-type key() :: #jose_jwk{}.

-export_type([key/0]).

-define(KEYS_MODULE,    jose_jwk_set).
-define(KTY_EC_MODULE,  jose_jwk_kty_ec).
-define(KTY_OCT_MODULE, jose_jwk_kty_oct).
-define(KTY_RSA_MODULE, jose_jwk_kty_rsa).

-define(KTY_OKP_Ed25519_MODULE,   jose_jwk_kty_okp_ed25519).
-define(KTY_OKP_Ed25519ph_MODULE, jose_jwk_kty_okp_ed25519ph).
-define(KTY_OKP_X25519_MODULE,    jose_jwk_kty_okp_x25519).
-define(KTY_OKP_Ed448_MODULE,     jose_jwk_kty_okp_ed448).
-define(KTY_OKP_Ed448ph_MODULE,   jose_jwk_kty_okp_ed448ph).
-define(KTY_OKP_X448_MODULE,      jose_jwk_kty_okp_x448).

%%====================================================================
%% Decode API functions
%%====================================================================

from(List) when is_list(List) ->
	[from(Element) || Element <- List];
from({Modules, Map}) when is_map(Modules) andalso is_map(Map) ->
	from_map({Modules, Map});
from({Modules, Binary}) when is_map(Modules) andalso is_binary(Binary) ->
	from_binary({Modules, Binary});
from(JWK=#jose_jwk{}) ->
	JWK;
from(Other) when is_map(Other) orelse is_binary(Other) ->
	from({#{}, Other}).

from(Key, List) when is_list(List) ->
	[from(Key, Element) || Element <- List];
from(Key, {Modules, EncryptedMap}) when is_map(Modules) andalso is_map(EncryptedMap) ->
	from_map(Key, {Modules, EncryptedMap});
from(Key, {Modules, EncryptedBinary}) when is_map(Modules) andalso is_binary(EncryptedBinary) ->
	from_binary(Key, {Modules, EncryptedBinary});
from(_Key, JWK=#jose_jwk{}) ->
	JWK;
from(Key, Other) when is_map(Other) orelse is_binary(Other) ->
	from(Key, {#{}, Other}).

from_binary(List) when is_list(List) ->
	[from_binary(Element) || Element <- List];
from_binary({Modules, Binary}) when is_map(Modules) andalso is_binary(Binary) ->
	from_map({Modules, jose:decode(Binary)});
from_binary(Binary) when is_binary(Binary) ->
	from_binary({#{}, Binary}).

from_binary(Key, List) when is_list(List) ->
	[from_binary(Key, Element) || Element <- List];
from_binary(Key, {Modules, Encrypted = << ${, _/binary >>}) when is_map(Modules) andalso is_binary(Encrypted) ->
	EncrypedMap = jose:decode(Encrypted),
	from_map(Key, {Modules, EncrypedMap});
from_binary(Key, {Modules, Encrypted}) when is_map(Modules) andalso is_binary(Encrypted) ->
	{JWKBinary, JWE=#jose_jwe{}} = jose_jwe:block_decrypt(Key, {Modules, Encrypted}),
	{JWE, from_binary({Modules, JWKBinary})};
from_binary(Key, Encrypted) when is_binary(Encrypted) ->
	from_binary(Key, {#{}, Encrypted}).

from_der(List) when is_list(List) ->
	[from_der(Element) || Element <- List];
from_der({#{ kty := Module }, Binary}) when is_binary(Binary) ->
	{KTY, Fields} = Module:from_der(Binary),
	#jose_jwk{ kty = {Module, KTY}, fields = Fields };
from_der({#{}, Binary}) when is_binary(Binary) ->
	case jose_jwk_der:from_binary(Binary) of
		{Module, {KTY, Fields}} when Module =/= error ->
			#jose_jwk{ kty = {Module, KTY}, fields = Fields };
		PEMError ->
			PEMError
	end;
from_der(Binary) when is_binary(Binary) ->
	from_der({#{}, Binary}).

from_der(Key, List) when is_list(List) ->
	[from_der(Key, Element) || Element <- List];
from_der(Key, {#{ kty := Module }, Binary}) when is_binary(Binary) ->
	{KTY, Fields} = Module:from_der(Key, Binary),
	#jose_jwk{ kty = {Module, KTY}, fields = Fields };
from_der(Key, {#{}, Binary}) when is_binary(Binary) ->
	case jose_jwk_der:from_binary(Key, Binary) of
		{Module, {KTY, Fields}} when Module =/= error ->
			#jose_jwk{ kty = {Module, KTY}, fields = Fields };
		PEMError ->
			PEMError
	end;
from_der(Key, Binary) when is_binary(Binary) ->
	from_der(Key, {#{}, Binary}).

from_der_file({Modules, File}) when is_map(Modules) andalso (is_binary(File) orelse is_list(File)) ->
	case file:read_file(File) of
		{ok, Binary} ->
			from_der({Modules, Binary});
		ReadError ->
			ReadError
	end;
from_der_file(File) when is_binary(File) orelse is_list(File) ->
	from_der_file({#{}, File}).

from_der_file(Key, {Modules, File}) when is_map(Modules) andalso (is_binary(File) orelse is_list(File)) ->
	case file:read_file(File) of
		{ok, Binary} ->
			from_der(Key, {Modules, Binary});
		ReadError ->
			ReadError
	end;
from_der_file(Key, File) when is_binary(File) orelse is_list(File) ->
	from_der_file(Key, {#{}, File}).

from_file({Modules, File}) when is_map(Modules) andalso (is_binary(File) orelse is_list(File)) ->
	case file:read_file(File) of
		{ok, Binary} ->
			from_binary({Modules, Binary});
		ReadError ->
			ReadError
	end;
from_file(File) when is_binary(File) orelse is_list(File) ->
	from_file({#{}, File}).

from_file(Key, {Modules, File}) when is_map(Modules) andalso (is_binary(File) orelse is_list(File)) ->
	case file:read_file(File) of
		{ok, Binary} ->
			from_binary(Key, {Modules, Binary});
		ReadError ->
			ReadError
	end;
from_file(Key, File) when is_binary(File) orelse is_list(File) ->
	from_file(Key, {#{}, File}).

from_firebase(Binary) when is_binary(Binary) ->
	from_firebase({#{}, Binary});
from_firebase({Modules, Binary}) when is_map(Modules) andalso is_binary(Binary) ->
	Map = jose:decode(Binary),
	from_firebase({Modules, Map});
from_firebase(Map) when is_map(Map) ->
	from_firebase({#{}, Map});
from_firebase({Modules, Map}) when is_map(Modules) andalso is_map(Map) ->
	Folder = fun (Key, Val, Acc) ->
		JWK = jose_jwk:merge(jose_jwk:from_pem({Modules, Val}), #{ <<"kid">> => Key }),
		maps:put(Key, JWK, Acc)
	end,
	maps:fold(Folder, #{}, Map).

from_key(List) when is_list(List) ->
	[from_key(Element) || Element <- List];
from_key(Key) ->
	case jose_jwk_kty:from_key(Key) of
		{KTYModule, {KTY, Fields}} when KTYModule =/= error ->
			#jose_jwk{ kty = {KTYModule, KTY}, fields = Fields };
		FromKeyError ->
			FromKeyError
	end.

from_map(List) when is_list(List) ->
	[from_map(Element) || Element <- List];
from_map(Map) when is_map(Map) ->
	from_map({#{}, Map});
from_map({Modules, Map}) when is_map(Modules) andalso is_map(Map) ->
	from_map({#jose_jwk{}, Modules, Map});
from_map({JWK, Modules=#{ keys := Module }, Map=#{ <<"keys">> := _ }}) ->
	{KEYS, Fields} = Module:from_map(Map),
	from_map({JWK#jose_jwk{ keys = {Module, KEYS} }, maps:remove(keys, Modules), Fields});
from_map({JWK, Modules=#{ kty := Module }, Map=#{ <<"kty">> := _ }}) ->
	{KTY, Fields} = Module:from_map(Map),
	from_map({JWK#jose_jwk{ kty = {Module, KTY} }, maps:remove(kty, Modules), Fields});
from_map({JWK, Modules, Map=#{ <<"keys">> := _ }}) ->
	from_map({JWK, Modules#{ keys => ?KEYS_MODULE }, Map});
from_map({JWK, Modules, Map=#{ <<"kty">> := <<"EC">> }}) ->
	from_map({JWK, Modules#{ kty => ?KTY_EC_MODULE }, Map});
from_map({JWK, Modules, Map=#{ <<"kty">> := <<"oct">> }}) ->
	from_map({JWK, Modules#{ kty => ?KTY_OCT_MODULE }, Map});
from_map({JWK, Modules, Map=#{ <<"kty">> := <<"OKP">>, <<"crv">> := <<"Ed25519">> }}) ->
	from_map({JWK, Modules#{ kty => ?KTY_OKP_Ed25519_MODULE }, Map});
from_map({JWK, Modules, Map=#{ <<"kty">> := <<"OKP">>, <<"crv">> := <<"Ed25519ph">> }}) ->
	from_map({JWK, Modules#{ kty => ?KTY_OKP_Ed25519ph_MODULE }, Map});
from_map({JWK, Modules, Map=#{ <<"kty">> := <<"OKP">>, <<"crv">> := <<"X25519">> }}) ->
	from_map({JWK, Modules#{ kty => ?KTY_OKP_X25519_MODULE }, Map});
from_map({JWK, Modules, Map=#{ <<"kty">> := <<"OKP">>, <<"crv">> := <<"Ed448">> }}) ->
	from_map({JWK, Modules#{ kty => ?KTY_OKP_Ed448_MODULE }, Map});
from_map({JWK, Modules, Map=#{ <<"kty">> := <<"OKP">>, <<"crv">> := <<"Ed448ph">> }}) ->
	from_map({JWK, Modules#{ kty => ?KTY_OKP_Ed448ph_MODULE }, Map});
from_map({JWK, Modules, Map=#{ <<"kty">> := <<"OKP">>, <<"crv">> := <<"X448">> }}) ->
	from_map({JWK, Modules#{ kty => ?KTY_OKP_X448_MODULE }, Map});
from_map({JWK, Modules, Map=#{ <<"kty">> := <<"RSA">> }}) ->
	from_map({JWK, Modules#{ kty => ?KTY_RSA_MODULE }, Map});
from_map({#jose_jwk{ keys = undefined, kty = undefined }, _Modules, _Map}) ->
	{error, {missing_required_keys, [<<"keys">>, <<"kty">>]}};
from_map({JWK, _Modules, Fields}) ->
	JWK#jose_jwk{ fields = Fields }.

from_map(Key, List) when is_list(List) ->
	[from_map(Key, Element) || Element <- List];
from_map(Key, {Modules, Encrypted}) when is_map(Modules) andalso is_map(Encrypted) ->
	{JWKBinary, JWE=#jose_jwe{}} = jose_jwe:block_decrypt(Key, {Modules, Encrypted}),
	{JWE, from_binary({Modules, JWKBinary})};
from_map(Key, Encrypted) when is_map(Encrypted) ->
	from_map(Key, {#{}, Encrypted}).

from_oct(List) when is_list(List) ->
	[from_oct(Element) || Element <- List];
from_oct({#{ kty := Module }, Binary}) when is_binary(Binary) ->
	{KTY, Fields} = Module:from_oct(Binary),
	#jose_jwk{ kty = {Module, KTY}, fields = Fields };
from_oct({#{}, Binary}) when is_binary(Binary) ->
	case jose_jwk_oct:from_binary(Binary) of
		{Module, {KTY, Fields}} when Module =/= error ->
			#jose_jwk{ kty = {Module, KTY}, fields = Fields };
		OCTError ->
			OCTError
	end;
from_oct(Binary) when is_binary(Binary) ->
	from_oct({#{}, Binary}).

from_oct(Key, List) when is_list(List) ->
	[from_oct(Key, Element) || Element <- List];
from_oct(Key, {Modules, Encrypted}) when is_map(Modules) andalso is_binary(Encrypted) ->
	{OCTBinary, JWE=#jose_jwe{}} = jose_jwe:block_decrypt(Key, {Modules, Encrypted}),
	{JWE, from_oct({Modules, OCTBinary})};
from_oct(Key, Encrypted) when is_binary(Encrypted) ->
	from_oct(Key, {#{}, Encrypted}).

from_oct_file({Modules, File}) when is_map(Modules) andalso (is_binary(File) orelse is_list(File)) ->
	case file:read_file(File) of
		{ok, Binary} ->
			from_oct({Modules, Binary});
		ReadError ->
			ReadError
	end;
from_oct_file(File) when is_binary(File) orelse is_list(File) ->
	from_oct_file({#{}, File}).

from_oct_file(Key, {Modules, File}) when is_map(Modules) andalso (is_binary(File) orelse is_list(File)) ->
	case file:read_file(File) of
		{ok, Binary} ->
			from_oct(Key, {Modules, Binary});
		ReadError ->
			ReadError
	end;
from_oct_file(Key, File) when is_binary(File) orelse is_list(File) ->
	from_oct_file(Key, {#{}, File}).

from_okp(List) when is_list(List) ->
	[from_okp(Element) || Element <- List];
from_okp({#{ kty := Module }, OKP}) ->
	{KTY, Fields} = Module:from_okp(OKP),
	#jose_jwk{ kty = {Module, KTY}, fields = Fields };
from_okp(P={'Ed25519', Binary}) when is_binary(Binary) ->
	from_okp({#{ kty => ?KTY_OKP_Ed25519_MODULE }, P});
from_okp(P={'Ed25519ph', Binary}) when is_binary(Binary) ->
	from_okp({#{ kty => ?KTY_OKP_Ed25519ph_MODULE }, P});
from_okp(P={'X25519', Binary}) when is_binary(Binary) ->
	from_okp({#{ kty => ?KTY_OKP_X25519_MODULE }, P});
from_okp(P={'Ed448', Binary}) when is_binary(Binary) ->
	from_okp({#{ kty => ?KTY_OKP_Ed448_MODULE }, P});
from_okp(P={'Ed448ph', Binary}) when is_binary(Binary) ->
	from_okp({#{ kty => ?KTY_OKP_Ed448ph_MODULE }, P});
from_okp(P={'X448', Binary}) when is_binary(Binary) ->
	from_okp({#{ kty => ?KTY_OKP_X448_MODULE }, P}).

from_openssh_key(List) when is_list(List) ->
	[from_openssh_key(Element) || Element <- List];
from_openssh_key({#{ kty := Module }, Binary}) when is_binary(Binary) ->
	{KTY, Fields} = Module:from_openssh_key(Binary),
	#jose_jwk{ kty = {Module, KTY}, fields = Fields };
from_openssh_key({#{}, Binary}) when is_binary(Binary) ->
	case jose_jwk_openssh_key:from_binary(Binary) of
		CurrentKey = [[{{Type, PK}, Key = {Type, PK, _SK, _Comment}} | _] | _] ->
			Module = case Type of
				<<"ssh-ed25519">> ->
					?KTY_OKP_Ed25519_MODULE;
				<<"ssh-ed25519ph">> ->
					?KTY_OKP_Ed25519ph_MODULE;
				<<"ssh-x25519">> ->
					?KTY_OKP_X25519_MODULE;
				<<"ssh-ed448">> ->
					?KTY_OKP_Ed448_MODULE;
				<<"ssh-ed448ph">> ->
					?KTY_OKP_Ed448ph_MODULE;
				<<"ssh-x448">> ->
					?KTY_OKP_X448_MODULE;
				_ ->
					{error, {unknown_key, CurrentKey}}
			end,
			case is_atom(Module) of
				true ->
					{KTY, Fields} = Module:from_openssh_key(Key),
					#jose_jwk{ kty = {Module, KTY}, fields = Fields };
				false ->
					Module
			end;
		UnknownKey ->
			{error, {unknown_key, UnknownKey}}
	end;
from_openssh_key(Binary) when is_binary(Binary) ->
	from_openssh_key({#{}, Binary}).

from_openssh_key_file({Modules, File}) when is_map(Modules) andalso (is_binary(File) orelse is_list(File)) ->
	case file:read_file(File) of
		{ok, Binary} ->
			from_openssh_key({Modules, Binary});
		ReadError ->
			ReadError
	end;
from_openssh_key_file(File) when is_binary(File) orelse is_list(File) ->
	from_openssh_key_file({#{}, File}).

from_pem(List) when is_list(List) ->
	[from_pem(Element) || Element <- List];
from_pem({#{ kty := Module }, Binary}) when is_binary(Binary) ->
	{KTY, Fields} = Module:from_pem(Binary),
	#jose_jwk{ kty = {Module, KTY}, fields = Fields };
from_pem({#{}, Binary}) when is_binary(Binary) ->
	case jose_jwk_pem:from_binary(Binary) of
		{Module, {KTY, Fields}} when Module =/= error ->
			#jose_jwk{ kty = {Module, KTY}, fields = Fields };
		PEMError ->
			PEMError
	end;
from_pem(Binary) when is_binary(Binary) ->
	from_pem({#{}, Binary}).

from_pem(Key, List) when is_list(List) ->
	[from_pem(Key, Element) || Element <- List];
from_pem(Key, {#{ kty := Module }, Binary}) when is_binary(Binary) ->
	{KTY, Fields} = Module:from_pem(Key, Binary),
	#jose_jwk{ kty = {Module, KTY}, fields = Fields };
from_pem(Key, {#{}, Binary}) when is_binary(Binary) ->
	case jose_jwk_pem:from_binary(Key, Binary) of
		{Module, {KTY, Fields}} when Module =/= error ->
			#jose_jwk{ kty = {Module, KTY}, fields = Fields };
		PEMError ->
			PEMError
	end;
from_pem(Key, Binary) when is_binary(Binary) ->
	from_pem(Key, {#{}, Binary}).

from_pem_file({Modules, File}) when is_map(Modules) andalso (is_binary(File) orelse is_list(File)) ->
	case file:read_file(File) of
		{ok, Binary} ->
			from_pem({Modules, Binary});
		ReadError ->
			ReadError
	end;
from_pem_file(File) when is_binary(File) orelse is_list(File) ->
	from_pem_file({#{}, File}).

from_pem_file(Key, {Modules, File}) when is_map(Modules) andalso (is_binary(File) orelse is_list(File)) ->
	case file:read_file(File) of
		{ok, Binary} ->
			from_pem(Key, {Modules, Binary});
		ReadError ->
			ReadError
	end;
from_pem_file(Key, File) when is_binary(File) orelse is_list(File) ->
	from_pem_file(Key, {#{}, File}).

%%====================================================================
%% Encode API functions
%%====================================================================

to_binary(List) when is_list(List) ->
	[to_binary(Element) || Element <- List];
to_binary(JWK=#jose_jwk{}) ->
	{Modules, Map} = to_map(JWK),
	{Modules, jose:encode(Map)};
to_binary(Other) ->
	to_binary(from(Other)).

to_binary(Key, List) when is_list(List) ->
	[to_binary(Key, Element) || Element <- List];
to_binary(Key, JWK=#jose_jwk{kty={Module, KTY}, fields=Fields}) ->
	to_binary(Key, Module:key_encryptor(KTY, Fields, Key), JWK);
to_binary(Key, Other) ->
	to_binary(Key, from(Other)).

to_binary(Key, JWE=#jose_jwe{}, JWK=#jose_jwk{}) ->
	{Modules, EncryptedMap} = to_map(Key, JWE, JWK),
	{Modules, jose:encode(EncryptedMap)};
to_binary(Key, JWEOther, JWKOther) ->
	to_binary(Key, jose_jwe:from(JWEOther), from(JWKOther)).

to_der(List) when is_list(List) ->
	[to_der(Element) || Element <- List];
to_der(#jose_jwk{kty={Module, KTY}}) ->
	{#{ kty => Module }, Module:to_der(KTY)};
to_der(Other) ->
	to_der(from(Other)).

to_der(Key, #jose_jwk{kty={Module, KTY}}) ->
	{#{ kty => Module }, Module:to_der(Key, KTY)};
to_der(Key, Other) ->
	to_der(Key, from(Other)).

to_der_file(File, JWK=#jose_jwk{}) when is_binary(File) orelse is_list(File) ->
	{Modules, Binary} = to_der(JWK),
	case file:write_file(File, Binary) of
		ok ->
			{Modules, File};
		WriteError ->
			WriteError
	end;
to_der_file(File, Other) when is_binary(File) orelse is_list(File) ->
	to_der_file(File, from(Other)).

to_der_file(Key, File, JWK=#jose_jwk{}) when is_binary(File) orelse is_list(File) ->
	{Modules, Binary} = to_der(Key, JWK),
	case file:write_file(File, Binary) of
		ok ->
			{Modules, File};
		WriteError ->
			WriteError
	end;
to_der_file(Key, File, Other) when is_binary(File) orelse is_list(File) ->
	to_der_file(Key, File, from(Other)).

to_file(File, JWK=#jose_jwk{}) when is_binary(File) orelse is_list(File) ->
	{Modules, Binary} = to_binary(JWK),
	case file:write_file(File, Binary) of
		ok ->
			{Modules, File};
		WriteError ->
			WriteError
	end;
to_file(File, Other) when is_binary(File) orelse is_list(File) ->
	to_file(File, from(Other)).

to_file(Key, File, JWK=#jose_jwk{kty={Module, KTY}, fields=Fields}) when is_binary(File) orelse is_list(File) ->
	to_file(Key, File, Module:key_encryptor(KTY, Fields, Key), JWK);
to_file(Key, File, Other) when is_binary(File) orelse is_list(File) ->
	to_file(Key, File, from(Other)).

to_file(Key, File, JWE=#jose_jwe{}, JWK=#jose_jwk{}) when is_binary(File) orelse is_list(File) ->
	{Modules, EncryptedBinary} = to_binary(Key, JWE, JWK),
	case file:write_file(File, EncryptedBinary) of
		ok ->
			{Modules, File};
		WriteError ->
			WriteError
	end;
to_file(Key, File, JWEOther, JWKOther) when is_binary(File) orelse is_list(File) ->
	to_file(Key, File, jose_jwe:from(JWEOther), from(JWKOther)).

to_key(List) when is_list(List) ->
	[to_key(Element) || Element <- List];
to_key(#jose_jwk{kty={Module, KTY}}) ->
	{#{ kty => Module }, Module:to_key(KTY)};
to_key(Other) ->
	to_key(from(Other)).

to_map(List) when is_list(List) ->
	[to_map(Element) || Element <- List];
to_map(JWK=#jose_jwk{fields=Fields}) ->
	record_to_map(JWK, #{}, Fields);
to_map(Other) ->
	to_map(from(Other)).

to_map(Key, JWK=#jose_jwk{kty={Module, KTY}, fields=Fields}) ->
	to_map(Key, Module:key_encryptor(KTY, Fields, Key), JWK);
to_map(Key, Other) ->
	to_map(Key, from(Other)).

to_map(Key, JWE=#jose_jwe{}, JWK=#jose_jwk{}) ->
	{Modules0, JWKBinary} = to_binary(JWK),
	{Modules1, Encrypted} = jose_jwe:block_encrypt(Key, JWKBinary, JWE),
	{maps:merge(Modules0, Modules1), Encrypted};
to_map(Key, JWEOther, JWKOther) ->
	to_map(Key, jose_jwe:from(JWEOther), from(JWKOther)).

to_oct(List) when is_list(List) ->
	[to_oct(Element) || Element <- List];
to_oct(#jose_jwk{kty={Module, KTY}}) ->
	{#{ kty => Module }, Module:to_oct(KTY)};
to_oct(Other) ->
	to_oct(from(Other)).

to_oct(Key, JWK=#jose_jwk{kty={Module, KTY}, fields=Fields}) ->
	to_oct(Key, Module:key_encryptor(KTY, Fields, Key), JWK);
to_oct(Key, Other) ->
	to_oct(Key, from(Other)).

to_oct(Key, JWE=#jose_jwe{}, JWK=#jose_jwk{}) ->
	{Modules0, OCTBinary} = to_oct(JWK),
	{Modules1, EncryptedMap} = jose_jwe:block_encrypt(Key, OCTBinary, JWE),
	jose_jwe:compact({maps:merge(Modules0, Modules1), EncryptedMap});
to_oct(Key, JWEOther, JWKOther) ->
	to_oct(Key, jose_jwe:from(JWEOther), from(JWKOther)).

to_oct_file(File, JWK=#jose_jwk{}) when is_binary(File) orelse is_list(File) ->
	{Modules, Binary} = to_oct(JWK),
	case file:write_file(File, Binary) of
		ok ->
			{Modules, File};
		WriteError ->
			WriteError
	end;
to_oct_file(File, Other) when is_binary(File) orelse is_list(File) ->
	to_oct_file(File, from(Other)).

to_oct_file(Key, File, JWK=#jose_jwk{kty={Module, KTY}, fields=Fields}) when is_binary(File) orelse is_list(File) ->
	to_oct_file(Key, File, Module:key_encryptor(KTY, Fields, Key), JWK);
to_oct_file(Key, File, Other) when is_binary(File) orelse is_list(File) ->
	to_oct_file(Key, File, from(Other)).

to_oct_file(Key, File, JWE=#jose_jwe{}, JWK=#jose_jwk{}) when is_binary(File) orelse is_list(File) ->
	{Modules, EncryptedBinary} = to_oct(Key, JWE, JWK),
	case file:write_file(File, EncryptedBinary) of
		ok ->
			{Modules, File};
		WriteError ->
			WriteError
	end;
to_oct_file(Key, File, JWEOther, JWKOther) when is_binary(File) orelse is_list(File) ->
	to_oct_file(Key, File, jose_jwe:from(JWEOther), from(JWKOther)).

to_okp(List) when is_list(List) ->
	[to_okp(Element) || Element <- List];
to_okp(#jose_jwk{kty={Module, KTY}}) ->
	{#{ kty => Module }, Module:to_okp(KTY)};
to_okp(Other) ->
	to_okp(from(Other)).

to_openssh_key(List) when is_list(List) ->
	[to_openssh_key(Element) || Element <- List];
to_openssh_key(#jose_jwk{kty={Module, KTY}, fields=Fields}) ->
	{#{ kty => Module }, Module:to_openssh_key(KTY, Fields)};
to_openssh_key(Other) ->
	to_openssh_key(from(Other)).

to_openssh_key_file(File, JWK=#jose_jwk{}) when is_binary(File) orelse is_list(File) ->
	{Modules, Binary} = to_openssh_key(JWK),
	case file:write_file(File, Binary) of
		ok ->
			{Modules, File};
		WriteError ->
			WriteError
	end;
to_openssh_key_file(File, Other) when is_binary(File) orelse is_list(File) ->
	to_openssh_key_file(File, from(Other)).

to_pem(List) when is_list(List) ->
	[to_pem(Element) || Element <- List];
to_pem(#jose_jwk{kty={Module, KTY}}) ->
	{#{ kty => Module }, Module:to_pem(KTY)};
to_pem(Other) ->
	to_pem(from(Other)).

to_pem(Key, #jose_jwk{kty={Module, KTY}}) ->
	{#{ kty => Module }, Module:to_pem(Key, KTY)};
to_pem(Key, Other) ->
	to_pem(Key, from(Other)).

to_pem_file(File, JWK=#jose_jwk{}) when is_binary(File) orelse is_list(File) ->
	{Modules, Binary} = to_pem(JWK),
	case file:write_file(File, Binary) of
		ok ->
			{Modules, File};
		WriteError ->
			WriteError
	end;
to_pem_file(File, Other) when is_binary(File) orelse is_list(File) ->
	to_pem_file(File, from(Other)).

to_pem_file(Key, File, JWK=#jose_jwk{}) when is_binary(File) orelse is_list(File) ->
	{Modules, Binary} = to_pem(Key, JWK),
	case file:write_file(File, Binary) of
		ok ->
			{Modules, File};
		WriteError ->
			WriteError
	end;
to_pem_file(Key, File, Other) when is_binary(File) orelse is_list(File) ->
	to_pem_file(Key, File, from(Other)).

to_public(List) when is_list(List) ->
	[to_public(Element) || Element <- List];
to_public(JWK=#jose_jwk{}) ->
	from_map(to_public_map(JWK));
to_public(Other) ->
	to_public(from(Other)).

to_public_file(File, JWK=#jose_jwk{}) when is_binary(File) orelse is_list(File) ->
	to_file(File, to_public(JWK));
to_public_file(File, Other) when is_binary(File) orelse is_list(File) ->
	to_public_file(File, from(Other)).

to_public_key(List) when is_list(List) ->
	[to_public_key(Element) || Element <- List];
to_public_key(JWT=#jose_jwk{}) ->
	to_key(to_public(JWT));
to_public_key(Other) ->
	to_public_key(from(Other)).

to_public_map(List) when is_list(List) ->
	[to_public_map(Element) || Element <- List];
to_public_map(#jose_jwk{kty={Module, KTY}, fields=Fields}) ->
	{#{ kty => Module }, Module:to_public_map(KTY, Fields)};
to_public_map(Other) ->
	to_public_map(from(Other)).

to_thumbprint_map(List) when is_list(List) ->
	[to_thumbprint_map(Element) || Element <- List];
to_thumbprint_map(#jose_jwk{kty={Module, KTY}, fields=Fields}) ->
	{#{ kty => Module }, Module:to_thumbprint_map(KTY, Fields)};
to_thumbprint_map(Other) ->
	to_thumbprint_map(from(Other)).

%%====================================================================
%% API functions
%%====================================================================

block_decrypt(Encrypted, JWK=#jose_jwk{}) ->
	jose_jwe:block_decrypt(JWK, Encrypted);
block_decrypt(Encrypted, Other) ->
	block_decrypt(Encrypted, from(Other)).

block_encrypt(PlainText, JWK=#jose_jwk{}) ->
	block_encrypt(PlainText, block_encryptor(JWK), JWK);
block_encrypt(PlainText, Other) ->
	block_encrypt(PlainText, from(Other)).

block_encrypt(PlainText, JWE=#jose_jwe{}, JWK=#jose_jwk{}) ->
	jose_jwe:block_encrypt(JWK, PlainText, JWE);
block_encrypt(PlainText, JWEOther, JWKOther) ->
	block_encrypt(PlainText, jose_jwe:from(JWEOther), from(JWKOther)).

block_encryptor(List) when is_list(List) ->
	[block_encryptor(Element) || Element <- List];
block_encryptor(#jose_jwk{kty={Module, KTY}, fields=Fields}) ->
	Module:block_encryptor(KTY, Fields);
block_encryptor(Other) ->
	block_encryptor(from(Other)).

box_decrypt(Encrypted, VStaticSecretKey) ->
	box_decrypt_ecdh_es(Encrypted, VStaticSecretKey).

%% @doc Generates an ephemeral private key based on receiver public key curve.
box_encrypt(PlainText, VStaticPublicKey=#jose_jwk{}) ->
	box_encrypt_ecdh_es(PlainText, VStaticPublicKey).

box_encrypt(PlainText, VStaticPublicKey, UEphemeralSecretKey) ->
	box_encrypt_ecdh_es(PlainText, VStaticPublicKey, UEphemeralSecretKey).

box_encrypt(PlainText, JWE, VStaticPublicKey, UEphemeralSecretKey) ->
	box_encrypt_ecdh_es(PlainText, JWE, VStaticPublicKey, UEphemeralSecretKey).

box_decrypt_ecdh_1pu(Encrypted, UStaticPublicKey=#jose_jwk{}, VStaticSecretKey=#jose_jwk{}) ->
	jose_jwe:block_decrypt({UStaticPublicKey, VStaticSecretKey}, Encrypted);
box_decrypt_ecdh_1pu(Encrypted, UStaticPublicKey, VStaticSecretKey) ->
	box_decrypt_ecdh_1pu(Encrypted, from(UStaticPublicKey), from(VStaticSecretKey)).

%% @doc Generates an ephemeral private key based on receiver public key curve.
box_encrypt_ecdh_1pu(PlainText, VStaticPublicKey=#jose_jwk{}, UStaticSecretKey=#jose_jwk{}) ->
	UEphemeralSecretKey = generate_key(VStaticPublicKey),
	{box_encrypt_ecdh_1pu(PlainText, VStaticPublicKey, UStaticSecretKey, UEphemeralSecretKey), UEphemeralSecretKey};
box_encrypt_ecdh_1pu(PlainText, VStaticPublicKey, UStaticSecretKey) ->
	box_encrypt_ecdh_1pu(PlainText, from(VStaticPublicKey), from(UStaticSecretKey)).

box_encrypt_ecdh_1pu(PlainText, VStaticPublicKey=#jose_jwk{}, UStaticSecretKey=#jose_jwk{}, UEphemeralSecretKey=#jose_jwk{}) ->
	UEphemeralPublicKey0 = #jose_jwk{fields=Fields0} = to_public(UEphemeralSecretKey),
	Fields1 = maps:put(<<"epk">>, element(2, to_map(UEphemeralPublicKey0)), Fields0),
	Fields2 =
		case Fields1 of
			#{ <<"alg">> := _ } ->
				Fields1;
			_ ->
				maps:put(<<"alg">>, <<"ECDH-1PU">>, Fields1)
		end,
	UEphemeralPublicKey = UEphemeralPublicKey0#jose_jwk{fields=Fields2},
	JWEFields = block_encryptor(UEphemeralPublicKey),
	box_encrypt_ecdh_1pu(PlainText, JWEFields, VStaticPublicKey, UStaticSecretKey, UEphemeralSecretKey);
box_encrypt_ecdh_1pu(PlainText, VStaticPublicKey, UStaticSecretKey, UEphemeralSecretKey) ->
	box_encrypt_ecdh_1pu(PlainText, from(VStaticPublicKey), from(UStaticSecretKey), from(UEphemeralSecretKey)).

box_encrypt_ecdh_1pu(PlainText, JWE=#jose_jwe{}, VStaticPublicKey=#jose_jwk{}, UStaticSecretKey=#jose_jwk{}, UEphemeralSecretKey=#jose_jwk{}) ->
	jose_jwe:block_encrypt({VStaticPublicKey, UStaticSecretKey, UEphemeralSecretKey}, PlainText, JWE);
box_encrypt_ecdh_1pu(PlainText, {JWEModules, JWEMap=#{ <<"apu">> := _, <<"apv">> := _, <<"epk">> := _, <<"skid">> := _ }}, VStaticPublicKey=#jose_jwk{}, UStaticSecretKey=#jose_jwk{}, UEphemeralSecretKey=#jose_jwk{}) ->
	box_encrypt_ecdh_1pu(PlainText, jose_jwe:from({JWEModules, JWEMap}), VStaticPublicKey, UStaticSecretKey, UEphemeralSecretKey);
box_encrypt_ecdh_1pu(PlainText, {JWEModules, JWEMap0}, VStaticPublicKey=#jose_jwk{}, UStaticSecretKey=#jose_jwk{}, UEphemeralSecretKey=#jose_jwk{}) ->
	Keys = [<<"apu">>, <<"apv">>, <<"epk">>, <<"skid">>] -- maps:keys(JWEMap0),
	JWEMap1 = normalize_ecdh_1pu(Keys, JWEMap0, VStaticPublicKey, UStaticSecretKey, UEphemeralSecretKey),
	box_encrypt_ecdh_1pu(PlainText, {JWEModules, JWEMap1}, VStaticPublicKey, UStaticSecretKey, UEphemeralSecretKey);
box_encrypt_ecdh_1pu(PlainText, JWEMap, VStaticPublicKey, UStaticSecretKey, UEphemeralSecretKey) when is_map(JWEMap) ->
	box_encrypt_ecdh_1pu(PlainText, {#{}, JWEMap}, VStaticPublicKey, UStaticSecretKey, UEphemeralSecretKey);
box_encrypt_ecdh_1pu(PlainText, JWE, VStaticPublicKey, UStaticSecretKey, UEphemeralSecretKey) ->
	box_encrypt_ecdh_1pu(PlainText, JWE, from(VStaticPublicKey), from(UStaticSecretKey), from(UEphemeralSecretKey)).

box_decrypt_ecdh_es(Encrypted, VStaticSecretKey=#jose_jwk{}) ->
	jose_jwe:block_decrypt(VStaticSecretKey, Encrypted);
box_decrypt_ecdh_es(Encrypted, VStaticSecretKey) ->
	box_decrypt_ecdh_es(Encrypted, from(VStaticSecretKey)).

%% @doc Generates an ephemeral private key based on receiver public key curve.
box_encrypt_ecdh_es(PlainText, VStaticPublicKey=#jose_jwk{}) ->
	UEphemeralSecretKey = generate_key(VStaticPublicKey),
	{box_encrypt_ecdh_es(PlainText, VStaticPublicKey, UEphemeralSecretKey), UEphemeralSecretKey};
box_encrypt_ecdh_es(PlainText, VStaticPublicKey) ->
	box_encrypt_ecdh_es(PlainText, from(VStaticPublicKey)).

box_encrypt_ecdh_es(PlainText, VStaticPublicKey=#jose_jwk{}, UEphemeralSecretKey=#jose_jwk{}) ->
	UEphemeralPublicKey0 = #jose_jwk{fields=Fields0} = to_public(UEphemeralSecretKey),
	Fields1 = maps:put(<<"epk">>, element(2, to_map(UEphemeralPublicKey0)), Fields0),
	UEphemeralPublicKey = UEphemeralPublicKey0#jose_jwk{fields=Fields1},
	JWEFields = block_encryptor(UEphemeralPublicKey),
	box_encrypt_ecdh_es(PlainText, JWEFields, VStaticPublicKey, UEphemeralSecretKey);
box_encrypt_ecdh_es(PlainText, VStaticPublicKey, UEphemeralSecretKey) ->
	box_encrypt_ecdh_es(PlainText, from(VStaticPublicKey), from(UEphemeralSecretKey)).

box_encrypt_ecdh_es(PlainText, JWE=#jose_jwe{}, VStaticPublicKey=#jose_jwk{}, UEphemeralSecretKey=#jose_jwk{}) ->
	jose_jwe:block_encrypt({VStaticPublicKey, UEphemeralSecretKey}, PlainText, JWE);
box_encrypt_ecdh_es(PlainText, {JWEModules, JWEMap=#{ <<"apu">> := _, <<"apv">> := _, <<"epk">> := _ }}, VStaticPublicKey=#jose_jwk{}, UEphemeralSecretKey=#jose_jwk{}) ->
	box_encrypt_ecdh_es(PlainText, jose_jwe:from({JWEModules, JWEMap}), VStaticPublicKey, UEphemeralSecretKey);
box_encrypt_ecdh_es(PlainText, {JWEModules, JWEMap0}, VStaticPublicKey=#jose_jwk{}, UEphemeralSecretKey=#jose_jwk{}) ->
	Keys = [<<"apu">>, <<"apv">>, <<"epk">>] -- maps:keys(JWEMap0),
	JWEMap1 = normalize_ecdh_es(Keys, JWEMap0, VStaticPublicKey, UEphemeralSecretKey),
	box_encrypt_ecdh_es(PlainText, {JWEModules, JWEMap1}, VStaticPublicKey, UEphemeralSecretKey);
box_encrypt_ecdh_es(PlainText, JWEMap, VStaticPublicKey, UEphemeralSecretKey) when is_map(JWEMap) ->
	box_encrypt_ecdh_es(PlainText, {#{}, JWEMap}, VStaticPublicKey, UEphemeralSecretKey);
box_encrypt_ecdh_es(PlainText, JWE, VStaticPublicKey, UEphemeralSecretKey) ->
	box_encrypt_ecdh_es(PlainText, JWE, from(VStaticPublicKey), from(UEphemeralSecretKey)).

box_decrypt_ecdh_ss(Encrypted, VStaticSecretKey0) ->
	VStaticSecretKey =
		case VStaticSecretKey0 of
			#jose_jwk{} ->
				VStaticSecretKey0;
			_ ->
				from(VStaticSecretKey0)
		end,
	jose_jwe:block_decrypt(VStaticSecretKey, Encrypted).

%% @doc Generates a new static secret key based on receiver public key curve.
box_encrypt_ecdh_ss(PlainText, VStaticPublicKey0) ->
	VStaticPublicKey =
		case VStaticPublicKey0 of
			#jose_jwk{} ->
				VStaticPublicKey0;
			_ ->
				from(VStaticPublicKey0)
		end,
	UStaticSecretKey = generate_key(VStaticPublicKey),
	{box_encrypt_ecdh_ss(PlainText, VStaticPublicKey, UStaticSecretKey), UStaticSecretKey}.

box_encrypt_ecdh_ss(PlainText, VStaticPublicKey0, UStaticSecretKey0) ->
	VStaticPublicKey =
		case VStaticPublicKey0 of
			#jose_jwk{} ->
				VStaticPublicKey0;
			_ ->
				from(VStaticPublicKey0)
		end,
	UStaticSecretKey =
		case UStaticSecretKey0 of
			#jose_jwk{} ->
				UStaticSecretKey0;
			_ ->
				from(UStaticSecretKey0)
		end,
	UStaticPublicKey0 = #jose_jwk{fields=Fields0} = to_public(UStaticSecretKey),
	Fields1 = maps:put(<<"spk">>, element(2, to_map(UStaticPublicKey0)), Fields0),
	Fields2 =
		case Fields1 of
			#{ <<"alg">> := _ } ->
				Fields1;
			_ ->
				maps:put(<<"alg">>, <<"ECDH-SS">>, Fields1)
		end,
	UStaticPublicKey = UStaticPublicKey0#jose_jwk{fields=Fields2},
	JWEFields = block_encryptor(UStaticPublicKey),
	box_encrypt_ecdh_ss(PlainText, JWEFields, VStaticPublicKey, UStaticSecretKey).

box_encrypt_ecdh_ss(PlainText, JWE0, VStaticPublicKey0, UStaticSecretKey0) ->
	VStaticPublicKey =
		case VStaticPublicKey0 of
			#jose_jwk{} ->
				VStaticPublicKey0;
			_ ->
				from(VStaticPublicKey0)
		end,
	UStaticSecretKey =
		case UStaticSecretKey0 of
			#jose_jwk{} ->
				UStaticSecretKey0;
			_ ->
				from(UStaticSecretKey0)
		end,
	JWE =
		case JWE0 of
			#jose_jwe{} ->
				JWE0;
			{JWEModules, JWEMap=#{ <<"apu">> := _, <<"spk">> := _ }} ->
				jose_jwe:from({JWEModules, JWEMap});
			{JWEModules, JWEMap0} when is_map(JWEMap0) ->
				Keys = [<<"apu">>, <<"apv">>, <<"spk">>] -- maps:keys(JWEMap0),
				JWEMap1 = normalize_ecdh_ss(Keys, JWEMap0, VStaticPublicKey, UStaticSecretKey),
				jose_jwe:from({JWEModules, JWEMap1});
			JWEMap0 when is_map(JWEMap0) ->
				Keys = [<<"apu">>, <<"apv">>, <<"spk">>] -- maps:keys(JWEMap0),
				JWEMap1 = normalize_ecdh_ss(Keys, JWEMap0, VStaticPublicKey, UStaticSecretKey),
				jose_jwe:from_map(JWEMap1);
			_ ->
				jose_jwe:from(JWE0)
		end,
	jose_jwe:block_encrypt({VStaticPublicKey, UStaticSecretKey}, PlainText, JWE).

generate_key(#jose_jwk{kty={Module, KTY}, fields=Fields}) ->
	{NewKTY, NewFields} = Module:generate_key(KTY, Fields),
	#jose_jwk{kty={Module, NewKTY}, fields=NewFields};
generate_key({#{ kty := Module }, Parameters}) ->
	{KTY, Fields} = Module:generate_key(Parameters),
	#jose_jwk{kty={Module, KTY}, fields=Fields};
generate_key(Parameters) ->
	jose_jwk_kty:generate_key(Parameters).

merge(LeftJWK=#jose_jwk{}, RightMap) when is_map(RightMap) ->
	{Modules, LeftMap} = to_map(LeftJWK),
	from_map({Modules, maps:merge(LeftMap, RightMap)});
merge(LeftOther, RightJWK=#jose_jwk{}) ->
	merge(LeftOther, element(2, to_map(RightJWK)));
merge(LeftOther, RightMap) when is_map(RightMap) ->
	merge(from(LeftOther), RightMap).

shared_secret(#jose_jwk{kty={Module, YourKTY}}, #jose_jwk{kty={Module, MyKTY}}) ->
	Module:derive_key(YourKTY, MyKTY);
shared_secret(YourJWK, MyJWK) ->
	shared_secret(from(YourJWK), from(MyJWK)).

sign(PlainText, JWK=#jose_jwk{}) ->
	sign(PlainText, signer(JWK), JWK);
sign(PlainText, KeyList)
		when is_list(KeyList) ->
	Folder = fun
		Folder(Key=#jose_jwk{}, {Signers, Keys}) ->
			Signer = signer(Key),
			{[Signer | Signers], [Key | Keys]};
		Folder(Other, Acc) ->
			Folder(from(Other), Acc)
	end,
	{Signers, Keys} = lists:foldr(Folder, {[], []}, KeyList),
	sign(PlainText, Signers, Keys);
sign(PlainText, Other) ->
	sign(PlainText, from(Other)).

sign(PlainText, JWS=#jose_jws{}, JWK=#jose_jwk{}) ->
	jose_jws:sign(JWK, PlainText, JWS);
sign(PlainText, SignerList, KeyList)
		when is_list(SignerList)
		andalso is_list(KeyList)
		andalso length(SignerList) =:= length(KeyList) ->
	Signers = jose_jws:from(SignerList),
	Keys = from(KeyList),
	jose_jws:sign(Keys, PlainText, Signers);
sign(PlainText, JWS=#jose_jws{}, KeyList)
		when is_list(KeyList) ->
	jose_jws:sign(from(KeyList), PlainText, JWS);
sign(PlainText, SignerList, KeyList)
		when is_list(SignerList)
		andalso is_list(KeyList)
		andalso length(SignerList) =/= length(KeyList) ->
	erlang:error({badarg, [PlainText, SignerList, KeyList]});
sign(PlainText, JWSOther, KeyList)
		when is_list(KeyList) ->
	sign(PlainText, jose_jws:from(JWSOther), KeyList);
sign(PlainText, JWSOther, JWKOther) ->
	sign(PlainText, jose_jws:from(JWSOther), from(JWKOther)).

signer(List) when is_list(List) ->
	[signer(Element) || Element <- List];
signer(#jose_jwk{kty={Module, KTY}, fields=Fields}) ->
	Module:signer(KTY, Fields);
signer(Other) ->
	signer(from(Other)).

%% See https://tools.ietf.org/html/rfc7638
thumbprint(List) when is_list(List) ->
	[thumbprint(Element) || Element <- List];
thumbprint(JWK=#jose_jwk{}) ->
	thumbprint(sha256, JWK);
thumbprint(Other) ->
	thumbprint(from(Other)).

thumbprint(DigestType, List) when is_list(List) ->
	[thumbprint(DigestType, Element) || Element <- List];
thumbprint(DigestType, JWK=#jose_jwk{}) ->
	{_, ThumbprintMap} = to_thumbprint_map(JWK),
	ThumbprintBinary = jose:encode(ThumbprintMap),
	jose_jwa_base64url:encode(crypto:hash(DigestType, ThumbprintBinary));
thumbprint(DigestType, Other) ->
	thumbprint(DigestType, from(Other)).

thumbprint_concat(List) when is_list(List) ->
	thumbprint_concat(sha256, List).

thumbprint_concat(DigestType, List) when is_list(List) ->
	jose_jwa_base64url:encode(crypto:hash(DigestType, do_thumbprint_concat(List))).

verifier(List) when is_list(List) ->
	[verifier(Element) || Element <- List];
verifier(#jose_jwk{kty={Module, KTY}, fields=Fields}) ->
	Module:verifier(KTY, Fields);
verifier(Other) ->
	verifier(from(Other)).

verify(Signed, JWK=#jose_jwk{}) ->
	jose_jws:verify(JWK, Signed);
verify(Signed, Other) ->
	verify(Signed, from(Other)).

verify_strict(Signed, Allow, JWK=#jose_jwk{}) ->
	jose_jws:verify_strict(JWK, Allow, Signed);
verify_strict(Signed, Allow, Other) ->
	verify_strict(Signed, Allow, from(Other)).

%%%-------------------------------------------------------------------
%%% Internal functions
%%%-------------------------------------------------------------------

%% @private
do_thumbprint_concat([]) ->
	[];
do_thumbprint_concat([H | T]) ->
	{_, ThumbprintMap} = to_thumbprint_map(H),
	ThumbprintBinary = jose:encode(ThumbprintMap),
	[ThumbprintBinary | do_thumbprint_concat(T)].

%% @private
normalize_ecdh_1pu([<<"apu">> | Keys], Map, VStaticPublicKey, UStaticSecretKey, UEphemeralSecretKey) ->
	APU = thumbprint_concat([UStaticSecretKey, UEphemeralSecretKey]),
	normalize_ecdh_1pu(Keys, Map#{ <<"apu">> => APU }, VStaticPublicKey, UStaticSecretKey, UEphemeralSecretKey);
normalize_ecdh_1pu([<<"apv">> | Keys], Map, VStaticPublicKey, UStaticSecretKey, UEphemeralSecretKey) ->
	APV = thumbprint(VStaticPublicKey),
	normalize_ecdh_1pu(Keys, Map#{ <<"apv">> => APV }, VStaticPublicKey, UStaticSecretKey, UEphemeralSecretKey);
normalize_ecdh_1pu([<<"epk">> | Keys], Map, VStaticPublicKey, UStaticSecretKey, UEphemeralSecretKey) ->
	{_, UEphemeralPublicKeyMap} = to_public_map(UEphemeralSecretKey),
	normalize_ecdh_1pu(Keys, Map#{ <<"epk">> => UEphemeralPublicKeyMap }, VStaticPublicKey, UStaticSecretKey, UEphemeralSecretKey);
normalize_ecdh_1pu([<<"skid">> | Keys], Map, VStaticPublicKey, UStaticSecretKey, UEphemeralSecretKey) ->
	SenderKeyId =
		case UStaticSecretKey of
			#jose_jwk{fields=#{ <<"kid">> := KID }} ->
				KID;
			_ ->
				thumbprint(UStaticSecretKey)
		end,
	normalize_ecdh_1pu(Keys, Map#{ <<"skid">> => SenderKeyId }, VStaticPublicKey, UStaticSecretKey, UEphemeralSecretKey);
normalize_ecdh_1pu([], Map, _, _, _) ->
	Map.

%% @private
normalize_ecdh_es([<<"apu">> | Keys], Map, VStaticPublicKey, UEphemeralSecretKey=#jose_jwk{fields=#{ <<"kid">> := KID }}) ->
	normalize_ecdh_es(Keys, Map#{ <<"apu">> => KID }, VStaticPublicKey, UEphemeralSecretKey);
normalize_ecdh_es([<<"apu">> | Keys], Map, VStaticPublicKey, UEphemeralSecretKey) ->
	normalize_ecdh_es(Keys, Map#{ <<"apu">> => thumbprint(UEphemeralSecretKey) }, VStaticPublicKey, UEphemeralSecretKey);
normalize_ecdh_es([<<"apv">> | Keys], Map, VStaticPublicKey=#jose_jwk{fields=#{ <<"kid">> := KID }}, UEphemeralSecretKey) ->
	normalize_ecdh_es(Keys, Map#{ <<"apv">> => KID }, VStaticPublicKey, UEphemeralSecretKey);
normalize_ecdh_es([<<"apv">> | Keys], Map, VStaticPublicKey, UEphemeralSecretKey) ->
	normalize_ecdh_es(Keys, Map#{ <<"apv">> => thumbprint(VStaticPublicKey) }, VStaticPublicKey, UEphemeralSecretKey);
normalize_ecdh_es([<<"epk">> | Keys], Map, VStaticPublicKey, UEphemeralSecretKey) ->
	{_, UEphemeralPublicKeyMap} = to_public_map(UEphemeralSecretKey),
	normalize_ecdh_es(Keys, Map#{ <<"epk">> => UEphemeralPublicKeyMap }, VStaticPublicKey, UEphemeralSecretKey);
normalize_ecdh_es([], Map, _, _) ->
	Map.

%% @private
normalize_ecdh_ss([<<"apu">> | Keys], Map, VStaticPublicKey, UStaticSecretKey) ->
	normalize_ecdh_ss(Keys, Map#{ <<"apu">> => jose_jwa_base64url:encode(crypto:strong_rand_bytes(64)) }, VStaticPublicKey, UStaticSecretKey);
normalize_ecdh_ss([<<"apv">> | Keys], Map, VStaticPublicKey=#jose_jwk{fields=#{ <<"kid">> := KID }}, UStaticSecretKey) ->
	normalize_ecdh_ss(Keys, Map#{ <<"apv">> => KID }, VStaticPublicKey, UStaticSecretKey);
normalize_ecdh_ss([<<"apv">> | Keys], Map, VStaticPublicKey, UStaticSecretKey) ->
	normalize_ecdh_ss(Keys, Map#{ <<"apv">> => thumbprint(VStaticPublicKey) }, VStaticPublicKey, UStaticSecretKey);
normalize_ecdh_ss([<<"spk">> | Keys], Map, VStaticPublicKey, UStaticSecretKey) ->
	{_, UStaticPublicKeyMap} = to_public_map(UStaticSecretKey),
	normalize_ecdh_ss(Keys, Map#{ <<"spk">> => UStaticPublicKeyMap }, VStaticPublicKey, UStaticSecretKey);
normalize_ecdh_ss([], Map, _, _) ->
	Map.

%% @private
record_to_map(JWK=#jose_jwk{keys={Module, KEYS}}, Modules, Fields0) ->
	Fields1 = Module:to_map(KEYS, Fields0),
	record_to_map(JWK#jose_jwk{keys=undefined}, Modules#{ keys => Module }, Fields1);
record_to_map(JWK=#jose_jwk{kty={Module, KTY}}, Modules, Fields0) ->
	Fields1 = Module:to_map(KTY, Fields0),
	record_to_map(JWK#jose_jwk{kty=undefined}, Modules#{ kty => Module }, Fields1);
record_to_map(_JWK, Modules, Fields) ->
	{Modules, Fields}.
