%% -*- mode: erlang; tab-width: 4; indent-tabs-mode: 1; st-rulers: [70] -*- %% vim: ts=4 sw=4 ft=erlang noet %%%------------------------------------------------------------------- %%% @author Andrew Bennett %%% @copyright 2014-2022, Andrew Bennett %%% @doc %%% %%% @end %%% Created : 13 Aug 2015 by Andrew Bennett %%%------------------------------------------------------------------- -module(jose_server). -behaviour(gen_server). -include_lib("public_key/include/public_key.hrl"). -define(SERVER, ?MODULE). %% API -export([start_link/0]). -export([config_change/0]). -export([chacha20_poly1305_module/1]). -export([curve25519_module/1]). -export([curve448_module/1]). -export([json_module/1]). -export([pbes2_count_maximum/1]). -export([sha3_module/1]). -export([unsecured_signing/1]). -export([xchacha20_poly1305_module/1]). %% gen_server callbacks -export([init/1]). -export([handle_call/3]). -export([handle_cast/2]). -export([handle_info/2]). -export([terminate/2]). -export([code_change/3]). -define(CRYPTO_FALLBACK, application:get_env(jose, crypto_fallback, false)). -define(TAB, jose_jwa). -define(POISON_MAP, #{ <<"a">> => 1, <<"b">> => 2, <<"c">> => #{ <<"d">> => 3, <<"e">> => 4 } }). -define(POISON_BIN, <<"{\"a\":1,\"b\":2,\"c\":{\"d\":3,\"e\":4}}">>). %% Types -record(state, {}). %%%=================================================================== %%% API functions %%%=================================================================== start_link() -> gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). config_change() -> gen_server:call(?SERVER, config_change). chacha20_poly1305_module(ChaCha20Poly1305Module) when is_atom(ChaCha20Poly1305Module) -> gen_server:call(?SERVER, {chacha20_poly1305_module, ChaCha20Poly1305Module}). curve25519_module(Curve25519Module) when is_atom(Curve25519Module) -> gen_server:call(?SERVER, {curve25519_module, Curve25519Module}). curve448_module(Curve448Module) when is_atom(Curve448Module) -> gen_server:call(?SERVER, {curve448_module, Curve448Module}). json_module(JSONModule) when is_atom(JSONModule) -> gen_server:call(?SERVER, {json_module, JSONModule}). -spec pbes2_count_maximum(PBES2CountMaximum) -> ok when PBES2CountMaximum :: non_neg_integer(). pbes2_count_maximum(PBES2CountMaximum) when is_integer(PBES2CountMaximum) andalso PBES2CountMaximum >= 0 -> gen_server:call(?SERVER, {pbes2_count_maximum, PBES2CountMaximum}). sha3_module(SHA3Module) when is_atom(SHA3Module) -> gen_server:call(?SERVER, {sha3_module, SHA3Module}). -spec unsecured_signing(UnsecuredSigning) -> ok when UnsecuredSigning :: boolean(). unsecured_signing(UnsecuredSigning) when is_boolean(UnsecuredSigning) -> gen_server:call(?SERVER, {unsecured_signing, UnsecuredSigning}). xchacha20_poly1305_module(XChaCha20Poly1305Module) when is_atom(XChaCha20Poly1305Module) -> gen_server:call(?SERVER, {xchacha20_poly1305_module, XChaCha20Poly1305Module}). %%%=================================================================== %%% gen_server callbacks %%%=================================================================== %% @private init([]) -> ?TAB = ets:new(?TAB, [ named_table, public, ordered_set, {read_concurrency, true} ]), ok = support_check(), {ok, #state{}}. %% @private handle_call(config_change, _From, State) -> {reply, support_check(), State}; handle_call({chacha20_poly1305_module, M}, _From, State) -> ChaCha20Poly1305Module = check_chacha20_poly1305_module(M), Entries = lists:flatten(check_crypto(?CRYPTO_FALLBACK, [{chacha20_poly1305_module, ChaCha20Poly1305Module}])), _ = ets:select_delete(?TAB, [{{{cipher, '_'}, '_'}, [], [true]}]), true = ets:insert(?TAB, Entries), {reply, ok, State}; handle_call({curve25519_module, M}, _From, State) -> Curve25519Module = check_curve25519_module(M), true = ets:insert(?TAB, {curve25519_module, Curve25519Module}), {reply, ok, State}; handle_call({curve448_module, M}, _From, State) -> Curve448Module = check_curve448_module(M), true = ets:insert(?TAB, {curve448_module, Curve448Module}), {reply, ok, State}; handle_call({json_module, M}, _From, State) -> JSONModule = check_json_module(M), true = ets:insert(?TAB, {json_module, JSONModule}), {reply, ok, State}; handle_call({pbes2_count_maximum, PBES2CountMaximum}, _From, State) when is_integer(PBES2CountMaximum) andalso PBES2CountMaximum >= 0 -> true = ets:insert(?TAB, {pbes2_count_maximum, PBES2CountMaximum}), {reply, ok, State}; handle_call({sha3_module, M}, _From, State) -> SHA3Module = check_sha3_module(M), true = ets:insert(?TAB, {sha3_module, SHA3Module}), {reply, ok, State}; handle_call({unsecured_signing, UnsecuredSigning}, _From, State) when is_boolean(UnsecuredSigning) -> true = ets:insert(?TAB, {unsecured_signing, UnsecuredSigning}), _ = spawn(fun() -> _ = catch jose_jwa:unsecured_signing(UnsecuredSigning), exit(normal) end), {reply, ok, State}; handle_call({xchacha20_poly1305_module, M}, _From, State) -> XChaCha20Poly1305Module = check_xchacha20_poly1305_module(M), Entries = lists:flatten(check_crypto(?CRYPTO_FALLBACK, [{xchacha20_poly1305_module, XChaCha20Poly1305Module}])), _ = ets:select_delete(?TAB, [{{{cipher, '_'}, '_'}, [], [true]}]), true = ets:insert(?TAB, Entries), {reply, ok, State}; handle_call(_Request, _From, State) -> {reply, ignore, State}. %% @private handle_cast(_Request, State) -> {noreply, State}. %% @private handle_info(_Info, State) -> {noreply, State}. %% @private terminate(_Reason, _State) -> ok. %% @private code_change(_OldVsn, State, _Extra) -> {ok, State}. %%%------------------------------------------------------------------- %%% Internal functions %%%------------------------------------------------------------------- %% @private support_check() -> PBES2CountMaximum = case application:get_env(jose, pbes2_count_maximum, 10000) of V1 when is_integer(V1) andalso V1 >= 0 -> V1 end, UnsecuredSigning = case application:get_env(jose, unsecured_signing, false) of V2 when is_boolean(V2) -> V2 end, Fallback = ?CRYPTO_FALLBACK, Entries1 = lists:flatten(lists:foldl(fun(Check, Acc) -> Check(Fallback, Acc) end, [], [ fun check_sha3/2, fun check_ec_key_mode/2, fun check_chacha20_poly1305/2, fun check_xchacha20_poly1305/2, fun check_curve25519/2, fun check_curve448/2, fun check_json/2, fun check_crypto/2, fun check_public_key/2 ])), Entries2 = [ {pbes2_count_maximum, PBES2CountMaximum}, {unsecured_signing, UnsecuredSigning} | Entries1 ], true = ets:delete_all_objects(?TAB), true = ets:insert(?TAB, Entries2), ok. %%%------------------------------------------------------------------- %%% Internal check functions %%%------------------------------------------------------------------- %% @private check_ec_key_mode(_Fallback, Entries) -> PEMBin = << 48,119,2,1,1,4,32,104,152,88,12,19,82,251,156,171,31,222,207, 0,76,115,88,210,229,36,106,137,192,81,153,154,254,226,38,247, 70,226,157,160,10,6,8,42,134,72,206,61,3,1,7,161,68,3,66,0,4, 46,75,29,46,150,77,222,40,220,159,244,193,125,18,190,254,216, 38,191,11,52,115,159,213,230,77,27,131,94,17,46,21,186,71,62, 36,225,0,90,21,186,235,132,152,229,13,189,196,121,64,84,64, 229,173,12,24,23,127,175,67,247,29,139,91 >>, PEMEntry = {'ECPrivateKey', PEMBin, not_encrypted}, %% Erlang 24 changes 'ECPrivateKey' record in a way that makes record matching fail %% when this module is compiled on Erlang 23 (or earlier) but runs on 24. %% So we destructure tuples, as ugly as it may be. %% %% See erlang-jose#113 for details. PrivateKey = case list_to_integer(erlang:system_info(otp_release)) >= 24 of true -> {'ECPrivateKey', _Version, PrivKey0, _Params, _PubKey0, _Attributes} = public_key:pem_entry_decode(PEMEntry), PrivKey0; false -> {'ECPrivateKey', _Version, PrivKey0, _Params, _PubKey0} = public_key:pem_entry_decode(PEMEntry), PrivKey0 end, case is_binary(PrivateKey) of true -> [{ec_key_mode, binary} | Entries]; _ -> [{ec_key_mode, list} | Entries] end. %% @private check_chacha20_poly1305(false, Entries) -> check_chacha20_poly1305(jose_chacha20_poly1305_unsupported, Entries); check_chacha20_poly1305(true, Entries) -> check_chacha20_poly1305(jose_jwa_chacha20_poly1305, Entries); check_chacha20_poly1305(Fallback, Entries) -> true = ets:delete_object(?TAB, {chacha20_poly1305_module, jose_jwa_chacha20_poly1305}), true = ets:delete_object(?TAB, {chacha20_poly1305_module, jose_chacha20_poly1305_unsupported}), ChaCha20Poly1305Module = case ets:lookup(?TAB, chacha20_poly1305_module) of [{chacha20_poly1305_module, M}] when is_atom(M) -> M; [] -> case application:get_env(jose, chacha20_poly1305_module, undefined) of undefined -> check_chacha20_poly1305_modules(Fallback, [crypto, libsodium]); M when is_atom(M) -> check_chacha20_poly1305_module(M) end end, %% Potentially used by XChaCha20-Poly1305 related functions below, needs to be inserted early. true = ets:insert(?TAB, {chacha20_poly1305_module, ChaCha20Poly1305Module}), [{chacha20_poly1305_module, ChaCha20Poly1305Module} | Entries]. %% @private check_chacha20_poly1305_module(crypto) -> jose_chacha20_poly1305_crypto; check_chacha20_poly1305_module(libsodium) -> jose_chacha20_poly1305_libsodium; check_chacha20_poly1305_module(Module) when is_atom(Module) -> Module. %% @private check_chacha20_poly1305_modules(Fallback, [Module | Modules]) -> case code:ensure_loaded(Module) of {module, Module} -> _ = application:ensure_all_started(Module), M = check_chacha20_poly1305_module(Module), PT = crypto:strong_rand_bytes(8), CEK = crypto:strong_rand_bytes(32), IV = crypto:strong_rand_bytes(12), AAD = <<>>, try M:encrypt(PT, AAD, IV, CEK) of {CT, TAG} when is_binary(CT) andalso is_binary(TAG) -> try M:decrypt(CT, TAG, AAD, IV, CEK) of PT -> M; _ -> check_chacha20_poly1305_modules(Fallback, Modules) catch _:_ -> check_chacha20_poly1305_modules(Fallback, Modules) end; _ -> check_chacha20_poly1305_modules(Fallback, Modules) catch _:_ -> check_chacha20_poly1305_modules(Fallback, Modules) end; _ -> check_chacha20_poly1305_modules(Fallback, Modules) end; check_chacha20_poly1305_modules(Fallback, []) -> Fallback. %% @private check_curve25519(false, Entries) -> check_curve25519(jose_curve25519_unsupported, Entries); check_curve25519(true, Entries) -> check_curve25519(jose_jwa_curve25519, Entries); check_curve25519(Fallback, Entries) -> true = ets:delete_object(?TAB, {curve25519_module, jose_jwa_curve25519}), true = ets:delete_object(?TAB, {curve25519_module, jose_curve25519_unsupported}), Curve25519Module = case ets:lookup(?TAB, curve25519_module) of [{curve25519_module, M}] when is_atom(M) -> M; [] -> case application:get_env(jose, curve25519_module, undefined) of undefined -> check_curve25519_modules(Fallback, [libdecaf, libsodium, crypto]); M when is_atom(M) -> check_curve25519_module(M) end end, [{curve25519_module, Curve25519Module} | Entries]. %% @private check_curve25519_module(crypto) -> jose_curve25519_crypto; check_curve25519_module(libdecaf) -> jose_curve25519_libdecaf; check_curve25519_module(libsodium) -> jose_curve25519_libsodium; check_curve25519_module(Module) when is_atom(Module) -> Module. %% @private check_curve25519_modules(Fallback, [Module | Modules]) -> case code:ensure_loaded(Module) of {module, Module} -> _ = application:ensure_all_started(Module), RealFallback = jose_jwa_curve25519, RealModule = check_curve25519_module(Module), try check_curve25519_module_does_it_work(RealFallback, RealModule) of true -> RealModule; false -> check_curve25519_modules(Fallback, Modules) catch _Class:_Reason:_Stacktrace -> % io:format("Class = ~p~nReason = ~p~nStacktrace = ~p~n", [Class, Reason, Stacktrace]), check_curve25519_modules(Fallback, Modules) end; _ -> check_curve25519_modules(Fallback, Modules) end; check_curve25519_modules(Fallback, []) -> Fallback. %% @private check_curve25519_module_does_it_work(Fallback, Module) -> {PK, SK = <>} = Module:eddsa_keypair(), {PK, SK} = Module:eddsa_keypair(Secret), PK = Module:eddsa_secret_to_public(Secret), Message = crypto:strong_rand_bytes(16), Signature = Module:ed25519_sign(Message, SK), true = Module:ed25519_verify(Signature, Message, PK), true = Fallback:ed25519_verify(Signature, Message, PK), %% NOTE: Ed25519ctx and Ed25519ph are lower priority, no need to check for now. % Ctx = <<"ctx">>, % CtxSignature = Module:ed25519ctx_sign(Message, SK, Ctx), % true = Module:ed25519ctx_verify(CtxSignature, Message, PK, Ctx), % true = Fallback:ed25519ctx_verify(CtxSignature, Message, PK, Ctx), % PHSignature = Module:ed25519ph_sign(Message, SK), % true = Module:ed25519ph_verify(PHSignature, Message, PK), % true = Fallback:ed25519ph_verify(PHSignature, Message, PK), % CtxPHSignature = Module:ed25519ph_sign(Message, SK, Ctx), % true = Module:ed25519ph_verify(CtxPHSignature, Message, PK, Ctx), % true = Fallback:ed25519ph_verify(CtxPHSignature, Message, PK, Ctx), true. %% @private check_curve448(false, Entries) -> check_curve448(jose_curve448_unsupported, Entries); check_curve448(true, Entries) -> check_curve448(jose_jwa_curve448, Entries); check_curve448(Fallback, Entries) -> true = ets:delete_object(?TAB, {curve448_module, jose_jwa_curve448}), true = ets:delete_object(?TAB, {curve448_module, jose_curve448_unsupported}), Curve448Module = case ets:lookup(?TAB, curve448_module) of [{curve448_module, M}] when is_atom(M) -> M; [] -> case application:get_env(jose, curve448_module, undefined) of undefined -> check_curve448_modules(Fallback, [libdecaf, crypto]); M when is_atom(M) -> check_curve448_module(M) end end, [{curve448_module, Curve448Module} | Entries]. %% @private check_curve448_module(crypto) -> jose_curve448_crypto; check_curve448_module(libdecaf) -> jose_curve448_libdecaf; check_curve448_module(Module) when is_atom(Module) -> Module. %% @private check_curve448_modules(Fallback, [Module | Modules]) -> case code:ensure_loaded(Module) of {module, Module} -> _ = application:ensure_all_started(Module), RealFallback = jose_jwa_curve448, RealModule = check_curve448_module(Module), try check_curve448_module_does_it_work(RealFallback, RealModule) of true -> RealModule; false -> check_curve448_modules(Fallback, Modules) catch _Class:_Reason:_Stacktrace -> % io:format("Class = ~p~nReason = ~p~nStacktrace = ~p~n", [_Class, _Reason, _Stacktrace]), check_curve448_modules(Fallback, Modules) end; _ -> check_curve448_modules(Fallback, Modules) end; check_curve448_modules(Fallback, []) -> Fallback. %% @private check_curve448_module_does_it_work(Fallback, Module) -> {PK, SK = <>} = Module:eddsa_keypair(), {PK, SK} = Module:eddsa_keypair(Secret), PK = Module:eddsa_secret_to_public(Secret), Message = crypto:strong_rand_bytes(16), Signature = Module:ed448_sign(Message, SK), true = Module:ed448_verify(Signature, Message, PK), true = Fallback:ed448_verify(Signature, Message, PK), %% NOTE: Ed448ph is lower priority, no need to check for now. % Ctx = <<"ctx">>, % CtxSignature = Module:ed448_sign(Message, SK, Ctx), % true = Module:ed448_verify(CtxSignature, Message, PK, Ctx), % true = Fallback:ed448_verify(CtxSignature, Message, PK, Ctx), % PHSignature = Module:ed448ph_sign(Message, SK), % true = Module:ed448ph_verify(PHSignature, Message, PK), % true = Fallback:ed448ph_verify(PHSignature, Message, PK), % CtxPHSignature = Module:ed448ph_sign(Message, SK, Ctx), % true = Module:ed448ph_verify(CtxPHSignature, Message, PK, Ctx), % true = Fallback:ed448ph_verify(CtxPHSignature, Message, PK, Ctx), true. %% @private check_json(_Fallback, Entries) -> JSONModule = case ets:lookup(?TAB, json_module) of [{json_module, M}] when is_atom(M) -> M; [] -> case application:get_env(jose, json_module, undefined) of undefined -> case code:ensure_loaded(elixir) of {module, elixir} -> check_json_modules([json, ojson, 'Elixir.Jason', 'Elixir.Poison', jiffy, jsone, jsx, thoas]); _ -> check_json_modules([json, ojson, jiffy, jsone, jsx, thoas]) end; M when is_atom(M) -> check_json_module(M) end end, [{json_module, JSONModule} | Entries]. %% @private check_json_module(json) -> jose_json_otp; check_json_module(jiffy) -> jose_json_jiffy; check_json_module(jsx) -> jose_json_jsx; check_json_module(jsone) -> jose_json_jsone; check_json_module(ojson) -> jose_json_ojson; check_json_module(thoas) -> jose_json_thoas; check_json_module('Elixir.Jason') -> jose_json_jason; check_json_module('Elixir.Poison') -> Map = ?POISON_MAP, Bin = ?POISON_BIN, case jose_json_poison:encode(Map) of Bin -> jose_json_poison; _ -> check_json_module('Elixir.JOSE.Poison') end; check_json_module('Elixir.JOSE.Poison') -> Map = ?POISON_MAP, Bin = ?POISON_BIN, case code:ensure_loaded('Elixir.JOSE.Poison') of {module, 'Elixir.JOSE.Poison'} -> try jose_json_poison_lexical_encoder:encode(Map) of Bin -> jose_json_poison_lexical_encoder; _ -> check_json_module(jose_json_poison_compat_encoder) catch _:_ -> check_json_module(jose_json_poison_compat_encoder) end; _ -> check_json_module(jose_json_poison_compat_encoder) end; check_json_module(jose_json_poison_compat_encoder) -> Map = ?POISON_MAP, Bin = ?POISON_BIN, try jose_json_poison_compat_encoder:encode(Map) of Bin -> jose_json_poison_compat_encoder; _ -> jose_json_poison catch _:_ -> jose_json_poison end; check_json_module(Module) when is_atom(Module) -> Module. %% @private check_json_modules([Module | Modules]) -> case code:ensure_loaded(Module) of {module, Module} -> _ = application:ensure_all_started(Module), check_json_module(Module); _ -> check_json_modules(Modules) end; check_json_modules([]) -> jose_json_unsupported. %% @private check_sha3(false, Entries) -> check_sha3(jose_sha3_unsupported, Entries); check_sha3(true, Entries) -> check_sha3(jose_jwa_sha3, Entries); check_sha3(Fallback, Entries) -> true = ets:delete_object(?TAB, {sha3_module, jose_jwa_sha3}), true = ets:delete_object(?TAB, {sha3_module, jose_sha3_unsupported}), SHA3Module = case ets:lookup(?TAB, sha3_module) of [{sha3_module, M}] when is_atom(M) -> M; [] -> case application:get_env(jose, sha3_module, undefined) of undefined -> check_sha3_modules(Fallback, [libdecaf, keccakf1600]); M when is_atom(M) -> check_sha3_module(M) end end, %% Potentially used by Ed448 related functions below, needs to be inserted early. true = ets:insert(?TAB, {sha3_module, SHA3Module}), [{sha3_module, SHA3Module} | Entries]. %% @private check_sha3_module(keccakf1600) -> check_sha3_module(jose_sha3_keccakf1600); check_sha3_module(libdecaf) -> check_sha3_module(jose_sha3_libdecaf); check_sha3_module(jose_sha3_keccakf1600) -> _ = code:ensure_loaded(keccakf1600), case erlang:function_exported(keccakf1600, hash, 3) of false -> % version < 2 check_sha3_module(jose_sha3_keccakf1600_driver); true -> % version >= 2 check_sha3_module(jose_sha3_keccakf1600_nif) end; check_sha3_module(Module) when is_atom(Module) -> Module. %% @private check_sha3_modules(Fallback, [Module | Modules]) -> case code:ensure_loaded(Module) of {module, Module} -> _ = application:ensure_all_started(Module), check_sha3_module(Module); _ -> check_sha3_modules(Fallback, Modules) end; check_sha3_modules(Fallback, []) -> Fallback. %% @private check_crypto(false, Entries) -> check_crypto(jose_jwa_unsupported, Entries); check_crypto(true, Entries) -> check_crypto(jose_jwa_aes, Entries); check_crypto(Fallback, Entries) -> Ciphers = [ aes_cbc, aes_ecb, aes_gcm ], KeySizes = [ 128, 192, 256 ], CipherEntries0 = [begin case has_cipher(Cipher, KeySize) of false -> {{cipher, {Cipher, KeySize}}, {Fallback, {Cipher, KeySize}}}; {true, CryptoCipher} -> {{cipher, {Cipher, KeySize}}, {crypto, CryptoCipher}} end end || Cipher <- Ciphers, KeySize <- KeySizes], CipherEntries1 = case lists:keyfind(chacha20_poly1305_module, 1, Entries) of {chacha20_poly1305_module, jose_chacha20_poly1305_unsupported} -> CipherEntries0 ++ [{{cipher, {chacha20_poly1305, 256}}, {Fallback, {chacha20_poly1305, 256}}}]; _ -> CipherEntries0 ++ [{{cipher, {chacha20_poly1305, 256}}, {jose_chacha20_poly1305, {chacha20_poly1305, 256}}}] end, CipherEntries2 = case lists:keyfind(xchacha20_poly1305_module, 1, Entries) of {xchacha20_poly1305_module, jose_xchacha20_poly1305_unsupported} -> CipherEntries1 ++ [{{cipher, {xchacha20_poly1305, 256}}, {Fallback, {xchacha20_poly1305, 256}}}]; _ -> CipherEntries1 ++ [{{cipher, {xchacha20_poly1305, 256}}, {jose_xchacha20_poly1305, {xchacha20_poly1305, 256}}}] end, [CipherEntries2 | Entries]. %% @private check_public_key(Fallback, Entries) -> RSACrypt = check_rsa_crypt(Fallback), RSASign = check_rsa_sign(Fallback), [RSACrypt, RSASign | Entries]. %% @private check_rsa_crypt(false) -> check_rsa_crypt(jose_jwa_unsupported); check_rsa_crypt(true) -> check_rsa_crypt(jose_jwa_pkcs1); check_rsa_crypt(Fallback) -> Algorithms = [ %% Algorithm, LegacyOptions, FutureOptions {rsa1_5, [{rsa_pad, rsa_pkcs1_padding}], [{rsa_padding, rsa_pkcs1_padding}]}, {rsa_oaep, [{rsa_pad, rsa_pkcs1_oaep_padding}], [{rsa_padding, rsa_pkcs1_oaep_padding}]}, {rsa_oaep_256, notsup, [{rsa_padding, rsa_pkcs1_oaep_padding}, {rsa_oaep_md, sha256}]} ], _ = code:ensure_loaded(public_key), _ = application:ensure_all_started(public_key), Legacy = case erlang:function_exported(public_key, sign, 4) of false -> legacy; true -> future end, CryptEntries = [begin case has_rsa_crypt(Algorithm, Legacy, LegacyOptions, FutureOptions) of false -> {{rsa_crypt, Algorithm}, {Fallback, FutureOptions}}; {true, Module, Options} -> {{rsa_crypt, Algorithm}, {Module, Options}} end end || {Algorithm, LegacyOptions, FutureOptions} <- Algorithms], CryptEntries. %% @private check_rsa_sign(false) -> check_rsa_sign(jose_jwa_unsupported); check_rsa_sign(true) -> check_rsa_sign(jose_jwa_pkcs1); check_rsa_sign(Fallback) -> Paddings = [ rsa_pkcs1_padding, rsa_pkcs1_pss_padding ], _ = code:ensure_loaded(public_key), _ = application:ensure_all_started(public_key), Legacy = case erlang:function_exported(public_key, sign, 4) of false -> legacy; true -> future end, SignEntries = [begin case has_rsa_sign(Padding, Legacy, sha256) of false -> {{rsa_sign, Padding}, {Fallback, [{rsa_padding, Padding}]}}; {true, Module} -> {{rsa_sign, Padding}, {Module, undefined}}; {true, Module, Options} -> {{rsa_sign, Padding}, {Module, Options}} end end || Padding <- Paddings], SignEntries. %% @private check_xchacha20_poly1305(false, Entries) -> check_xchacha20_poly1305(jose_xchacha20_poly1305_unsupported, Entries); check_xchacha20_poly1305(true, Entries) -> check_xchacha20_poly1305(jose_jwa_xchacha20_poly1305, Entries); check_xchacha20_poly1305(Fallback, Entries) -> true = ets:delete_object(?TAB, {xchacha20_poly1305_module, jose_jwa_xchacha20_poly1305}), true = ets:delete_object(?TAB, {xchacha20_poly1305_module, jose_xchacha20_poly1305_unsupported}), ChaCha20Poly1305Module = case ets:lookup(?TAB, xchacha20_poly1305_module) of [{xchacha20_poly1305_module, M}] when is_atom(M) -> M; [] -> case application:get_env(jose, xchacha20_poly1305_module, undefined) of undefined -> check_xchacha20_poly1305_modules(Fallback, [libsodium, crypto]); M when is_atom(M) -> check_xchacha20_poly1305_module(M) end end, [{xchacha20_poly1305_module, ChaCha20Poly1305Module} | Entries]. %% @private check_xchacha20_poly1305_module(crypto) -> jose_xchacha20_poly1305_crypto; check_xchacha20_poly1305_module(libsodium) -> jose_xchacha20_poly1305_libsodium; check_xchacha20_poly1305_module(Module) when is_atom(Module) -> Module. %% @private check_xchacha20_poly1305_modules(Fallback, [Module | Modules]) -> case code:ensure_loaded(Module) of {module, Module} -> _ = application:ensure_all_started(Module), M = check_xchacha20_poly1305_module(Module), PT = crypto:strong_rand_bytes(8), CEK = crypto:strong_rand_bytes(32), IV = crypto:strong_rand_bytes(24), AAD = <<>>, try M:encrypt(PT, AAD, IV, CEK) of {CT, TAG} when is_binary(CT) andalso is_binary(TAG) -> try M:decrypt(CT, TAG, AAD, IV, CEK) of PT -> M; _ -> check_xchacha20_poly1305_modules(Fallback, Modules) catch _:_ -> check_xchacha20_poly1305_modules(Fallback, Modules) end; _ -> check_xchacha20_poly1305_modules(Fallback, Modules) catch _:_ -> check_xchacha20_poly1305_modules(Fallback, Modules) end; _ -> check_xchacha20_poly1305_modules(Fallback, Modules) end; check_xchacha20_poly1305_modules(Fallback, []) -> Fallback. %% @private has_cipher(aes_cbc, KeySize) -> Key = << 0:KeySize >>, IV = << 0:128 >>, PlainText = jose_jwa_pkcs7:pad(<<>>), case has_block_cipher(aes_cbc, {Key, IV, PlainText}) of false -> Cipher = list_to_atom("aes_" ++ integer_to_list(KeySize) ++ "_cbc"), has_block_cipher(Cipher, {Key, IV, PlainText}); Other -> Other end; has_cipher(aes_ecb, KeySize) -> Key = << 0:KeySize >>, PlainText = jose_jwa_pkcs7:pad(<<>>), case has_block_cipher(aes_ecb, {Key, PlainText}) of false -> Cipher = list_to_atom("aes_" ++ integer_to_list(KeySize) ++ "_ecb"), has_block_cipher(Cipher, {Key, PlainText}); Other -> Other end; has_cipher(aes_gcm, KeySize) -> Key = << 0:KeySize >>, IV = << 0:96 >>, AAD = <<>>, PlainText = jose_jwa_pkcs7:pad(<<>>), case has_block_cipher(aes_gcm, {Key, IV, AAD, PlainText}) of false -> Cipher = list_to_atom("aes_" ++ integer_to_list(KeySize) ++ "_gcm"), has_block_cipher(Cipher, {Key, IV, AAD, PlainText}); Other -> Other end. %% @private has_block_cipher(Cipher, {Key, PlainText}) -> case catch jose_crypto_compat:crypto_one_time(Cipher, Key, PlainText, true) of CipherText when is_binary(CipherText) -> case catch jose_crypto_compat:crypto_one_time(Cipher, Key, CipherText, false) of PlainText -> {true, Cipher}; _ -> false end; _ -> false end; has_block_cipher(Cipher, {Key, IV, PlainText}) -> case catch jose_crypto_compat:crypto_one_time(Cipher, Key, IV, PlainText, true) of CipherText when is_binary(CipherText) -> case catch jose_crypto_compat:crypto_one_time(Cipher, Key, IV, CipherText, false) of PlainText -> {true, Cipher}; _ -> false end; _ -> false end; has_block_cipher(Cipher, {Key, IV, AAD, PlainText}) -> case catch jose_crypto_compat:crypto_one_time(Cipher, Key, IV, {AAD, PlainText}, true) of {CipherText, CipherTag} when is_binary(CipherText) andalso is_binary(CipherTag) -> case catch jose_crypto_compat:crypto_one_time(Cipher, Key, IV, {AAD, CipherText, CipherTag}, false) of PlainText -> {true, Cipher}; _ -> false end; _ -> false end. %% @private has_rsa_crypt(Algorithm, future, _LegacyOptions, FutureOptions) -> PlainText = << 0:8 >>, PublicKey = rsa_public_key(), case catch public_key:encrypt_public(PlainText, PublicKey, FutureOptions) of CipherText when is_binary(CipherText) -> PrivateKey = rsa_private_key(), case catch public_key:decrypt_private(CipherText, PrivateKey, FutureOptions) of PlainText -> case catch public_key:decrypt_private(rsa_ciphertext(Algorithm), PrivateKey, FutureOptions) of <<"ciphertext">> -> {true, public_key, FutureOptions}; _ -> false end; _ -> false end; _ -> false end; has_rsa_crypt(_Algorithm, legacy, notsup, _FutureOptions) -> false; has_rsa_crypt(Algorithm, legacy, LegacyOptions, _FutureOptions) -> PlainText = << 0:8 >>, PublicKey = rsa_public_key(), case catch public_key:encrypt_public(PlainText, PublicKey, LegacyOptions) of CipherText when is_binary(CipherText) -> PrivateKey = rsa_private_key(), case catch public_key:decrypt_private(CipherText, PrivateKey, LegacyOptions) of PlainText -> case catch public_key:decrypt_private(rsa_ciphertext(Algorithm), PrivateKey, LegacyOptions) of <<"ciphertext">> -> {true, public_key, LegacyOptions}; _ -> false end; _ -> false end; _ -> false end. %% @private has_rsa_sign(Padding, future, DigestType) -> Message = << 0:8 >>, PrivateKey = rsa_private_key(), Options = [{rsa_padding, Padding}], case catch public_key:sign(Message, DigestType, PrivateKey, Options) of Signature when is_binary(Signature) -> PublicKey = rsa_public_key(), case catch public_key:verify(Message, DigestType, Signature, PublicKey, Options) of true -> {true, public_key, Options}; _ -> false end; _ -> false end; has_rsa_sign(rsa_pkcs1_padding, legacy, DigestType) -> Message = << 0:8 >>, PrivateKey = rsa_private_key(), case catch public_key:sign(Message, DigestType, PrivateKey) of Signature when is_binary(Signature) -> PublicKey = rsa_public_key(), case catch public_key:verify(Message, DigestType, Signature, PublicKey) of true -> {true, public_key}; _ -> false end; _ -> false end; has_rsa_sign(_Padding, legacy, _DigestType) -> false. %% @private read_pem_key(PEM) -> public_key:pem_entry_decode(hd(public_key:pem_decode(PEM))). %% @private rsa_ciphertext(rsa1_5) -> << 16#67, 16#3F, 16#BF, 16#D4, 16#93, 16#1E, 16#6C, 16#54, 16#67, 16#DE, 16#29, 16#3C, 16#71, 16#5F, 16#95, 16#BE, 16#69, 16#99, 16#D3, 16#6C, 16#E4, 16#81, 16#1E, 16#49, 16#BE, 16#5D, 16#91, 16#85, 16#E7, 16#1D, 16#04, 16#C5, 16#38, 16#0A, 16#6F, 16#3F, 16#32, 16#2C, 16#3D, 16#67, 16#53, 16#B1, 16#EA, 16#D7, 16#2E, 16#ED, 16#6A, 16#7A, 16#EB, 16#49, 16#79, 16#71, 16#CA, 16#F5, 16#71, 16#67, 16#FA, 16#8B, 16#B8, 16#A8, 16#30, 16#59, 16#2E, 16#88, 16#98, 16#19, 16#AE, 16#B2, 16#94, 16#BA, 16#6E, 16#D2, 16#EF, 16#28, 16#BE, 16#04, 16#4F, 16#90, 16#77, 16#CA, 16#3D, 16#11, 16#2B, 16#E7, 16#17, 16#D8, 16#89, 16#7F, 16#EC, 16#7A, 16#2C, 16#70, 16#A5, 16#08, 16#FB, 16#5B >>; rsa_ciphertext(rsa_oaep) -> << 16#B8, 16#F7, 16#0C, 16#A8, 16#F8, 16#30, 16#2A, 16#E9, 16#68, 16#8A, 16#DB, 16#3E, 16#5D, 16#AE, 16#84, 16#A7, 16#16, 16#FA, 16#9D, 16#E2, 16#FC, 16#81, 16#F7, 16#DF, 16#A8, 16#DB, 16#8F, 16#4F, 16#92, 16#A1, 16#51, 16#9E, 16#6B, 16#C5, 16#36, 16#CE, 16#93, 16#10, 16#11, 16#D9, 16#D5, 16#C2, 16#C9, 16#85, 16#14, 16#EF, 16#D5, 16#C3, 16#AC, 16#63, 16#BE, 16#49, 16#FA, 16#02, 16#1A, 16#FC, 16#3D, 16#D0, 16#2C, 16#83, 16#C5, 16#76, 16#1D, 16#F5, 16#FA, 16#A0, 16#D7, 16#42, 16#ED, 16#3F, 16#A4, 16#12, 16#32, 16#14, 16#93, 16#51, 16#79, 16#2E, 16#40, 16#FB, 16#14, 16#18, 16#DF, 16#30, 16#62, 16#9F, 16#F3, 16#59, 16#5D, 16#83, 16#0F, 16#4A, 16#8F, 16#9B, 16#3F, 16#39 >>; rsa_ciphertext(rsa_oaep_256) -> << 16#09, 16#24, 16#EA, 16#EB, 16#D4, 16#EF, 16#00, 16#BE, 16#8E, 16#02, 16#BE, 16#25, 16#24, 16#24, 16#18, 16#81, 16#8D, 16#7A, 16#A2, 16#EB, 16#F1, 16#BE, 16#5C, 16#DC, 16#D0, 16#71, 16#43, 16#09, 16#53, 16#12, 16#44, 16#AD, 16#8A, 16#CD, 16#F8, 16#45, 16#7F, 16#1F, 16#30, 16#B6, 16#54, 16#8E, 16#AB, 16#D2, 16#10, 16#14, 16#BC, 16#CE, 16#7A, 16#99, 16#DC, 16#A6, 16#8D, 16#16, 16#5A, 16#A0, 16#50, 16#3A, 16#93, 16#0E, 16#53, 16#4A, 16#B5, 16#6B, 16#51, 16#E8, 16#43, 16#8F, 16#BD, 16#2D, 16#E0, 16#63, 16#36, 16#24, 16#5B, 16#8D, 16#DD, 16#98, 16#AC, 16#37, 16#7C, 16#16, 16#DB, 16#03, 16#C8, 16#BD, 16#22, 16#D2, 16#15, 16#98, 16#91, 16#B7, 16#3C, 16#01, 16#CF, 16#0E >>. %% @private rsa_public_key() -> read_pem_key(<< "-----BEGIN PUBLIC KEY-----\n" "MHwwDQYJKoZIhvcNAQEBBQADawAwaAJhAL/f1xISwDSm4m6sYHm6WD4WK2egfyfZ\n" "hd0w4iVeZvHjUurZVRVQojs7hZC7DKBfjShl6M7BT9j7gkaYOXlJHLhK6/J+Zr0C\n" "g6PMkkbejQltgr4fUzbG8zUBo7BMs4Xm0wIDAQAB\n" "-----END PUBLIC KEY-----\n" >>). %% @private rsa_private_key() -> read_pem_key(<< "-----BEGIN RSA PRIVATE KEY-----\n" "MIIBzAIBAAJhAL/f1xISwDSm4m6sYHm6WD4WK2egfyfZhd0w4iVeZvHjUurZVRVQ\n" "ojs7hZC7DKBfjShl6M7BT9j7gkaYOXlJHLhK6/J+Zr0Cg6PMkkbejQltgr4fUzbG\n" "8zUBo7BMs4Xm0wIDAQABAmEAiisNO7WG9SNLoPi+TEn061iZjvjTOAX60Io3/0LY\n" "jMzu07EHBN9Yw6CcENmxQPcsdIRlSKLlt+UeUdBES6Zoccek5fJl+gnqExeX2Av1\n" "v0Y8vIP2yejV7Pw+SrNxpY5ZAjEA+WMEZEgFrK8cPJmZLR9Kj3jvN5P+AmIKzg00\n" "VMW93rS+sdHmYQUStqBuu2XRw5SlAjEAxPZlLCZ83GrqdStcmChCFpflzCRyU/wC\n" "qVVP8QYfct49Cca3TyC8lCywwXI5s5wXAjA1JQK0lByRdiegSmM4GGj9NhpUT7db\n" "rqT60BmMzy7tHLtejYp4tmoMfRfb25DeCvkCMQCO+usQ9NOZUsfmzNaH4lmvew8n\n" "daHFE+F+uV6x8ibsRSZ8LVQuze33hsW9eEUo/HsCMQDKkImE3DSqHgwfKPjtecFH\n" "oftdsGQ4u+MUGkST94Hh8479oNYaNveCRDOTJ4GJjUE=\n" "-----END RSA PRIVATE KEY-----\n" >>).