1%% -*- mode: erlang; tab-width: 4; indent-tabs-mode: 1; st-rulers: [70] -*-
2%% vim: ts=4 sw=4 ft=erlang noet
3%%%-------------------------------------------------------------------
4%%% @author Andrew Bennett <potatosaladx@gmail.com>
5%%% @copyright 2014-2015, Andrew Bennett
6%%% @doc
7%%%
8%%% @end
9%%% Created :  13 Aug 2015 by Andrew Bennett <potatosaladx@gmail.com>
10%%%-------------------------------------------------------------------
11-module(jose_server).
12-behaviour(gen_server).
13
14-include_lib("public_key/include/public_key.hrl").
15
16-define(SERVER, ?MODULE).
17
18%% API
19-export([start_link/0]).
20-export([config_change/0]).
21-export([chacha20_poly1305_module/1]).
22-export([curve25519_module/1]).
23-export([curve448_module/1]).
24-export([json_module/1]).
25-export([sha3_module/1]).
26-export([xchacha20_poly1305_module/1]).
27
28%% gen_server callbacks
29-export([init/1]).
30-export([handle_call/3]).
31-export([handle_cast/2]).
32-export([handle_info/2]).
33-export([terminate/2]).
34-export([code_change/3]).
35
36-define(CRYPTO_FALLBACK, application:get_env(jose, crypto_fallback, false)).
37
38-define(TAB, jose_jwa).
39
40-define(POISON_MAP, #{
41	<<"a">> => 1,
42	<<"b">> => 2,
43	<<"c">> => #{
44		<<"d">> => 3,
45		<<"e">> => 4
46	}
47}).
48-define(POISON_BIN, <<"{\"a\":1,\"b\":2,\"c\":{\"d\":3,\"e\":4}}">>).
49
50%% Types
51-record(state, {}).
52
53%%%===================================================================
54%%% API functions
55%%%===================================================================
56
57start_link() ->
58	gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
59
60config_change() ->
61	gen_server:call(?SERVER, config_change).
62
63chacha20_poly1305_module(ChaCha20Poly1305Module) when is_atom(ChaCha20Poly1305Module) ->
64	gen_server:call(?SERVER, {chacha20_poly1305_module, ChaCha20Poly1305Module}).
65
66curve25519_module(Curve25519Module) when is_atom(Curve25519Module) ->
67	gen_server:call(?SERVER, {curve25519_module, Curve25519Module}).
68
69curve448_module(Curve448Module) when is_atom(Curve448Module) ->
70	gen_server:call(?SERVER, {curve448_module, Curve448Module}).
71
72json_module(JSONModule) when is_atom(JSONModule) ->
73	gen_server:call(?SERVER, {json_module, JSONModule}).
74
75sha3_module(SHA3Module) when is_atom(SHA3Module) ->
76	gen_server:call(?SERVER, {sha3_module, SHA3Module}).
77
78xchacha20_poly1305_module(XChaCha20Poly1305Module) when is_atom(XChaCha20Poly1305Module) ->
79	gen_server:call(?SERVER, {xchacha20_poly1305_module, XChaCha20Poly1305Module}).
80
81%%%===================================================================
82%%% gen_server callbacks
83%%%===================================================================
84
85%% @private
86init([]) ->
87	?TAB = ets:new(?TAB, [
88		named_table,
89		public,
90		ordered_set,
91		{read_concurrency, true}
92	]),
93	ok = support_check(),
94	{ok, #state{}}.
95
96%% @private
97handle_call(config_change, _From, State) ->
98	{reply, support_check(), State};
99handle_call({chacha20_poly1305_module, M}, _From, State) ->
100	ChaCha20Poly1305Module = check_chacha20_poly1305_module(M),
101	Entries = lists:flatten(check_crypto(?CRYPTO_FALLBACK, [{chacha20_poly1305_module, ChaCha20Poly1305Module}])),
102	_ = ets:select_delete(?TAB, [{{{cipher, '_'}, '_'}, [], [true]}]),
103	true = ets:insert(?TAB, Entries),
104	{reply, ok, State};
105handle_call({curve25519_module, M}, _From, State) ->
106	Curve25519Module = check_curve25519_module(M),
107	true = ets:insert(?TAB, {curve25519_module, Curve25519Module}),
108	{reply, ok, State};
109handle_call({curve448_module, M}, _From, State) ->
110	Curve448Module = check_curve448_module(M),
111	true = ets:insert(?TAB, {curve448_module, Curve448Module}),
112	{reply, ok, State};
113handle_call({json_module, M}, _From, State) ->
114	JSONModule = check_json_module(M),
115	true = ets:insert(?TAB, {json_module, JSONModule}),
116	{reply, ok, State};
117handle_call({sha3_module, M}, _From, State) ->
118	SHA3Module = check_sha3_module(M),
119	true = ets:insert(?TAB, {sha3_module, SHA3Module}),
120	{reply, ok, State};
121handle_call({xchacha20_poly1305_module, M}, _From, State) ->
122	XChaCha20Poly1305Module = check_xchacha20_poly1305_module(M),
123	Entries = lists:flatten(check_crypto(?CRYPTO_FALLBACK, [{xchacha20_poly1305_module, XChaCha20Poly1305Module}])),
124	_ = ets:select_delete(?TAB, [{{{cipher, '_'}, '_'}, [], [true]}]),
125	true = ets:insert(?TAB, Entries),
126	{reply, ok, State};
127handle_call(_Request, _From, State) ->
128	{reply, ignore, State}.
129
130%% @private
131handle_cast(_Request, State) ->
132	{noreply, State}.
133
134%% @private
135handle_info(_Info, State) ->
136	{noreply, State}.
137
138%% @private
139terminate(_Reason, _State) ->
140	ok.
141
142%% @private
143code_change(_OldVsn, State, _Extra) ->
144	{ok, State}.
145
146%%%-------------------------------------------------------------------
147%%% Internal functions
148%%%-------------------------------------------------------------------
149
150%% @private
151support_check() ->
152	Fallback = ?CRYPTO_FALLBACK,
153	Entries = lists:flatten(lists:foldl(fun(Check, Acc) ->
154		Check(Fallback, Acc)
155	end, [], [
156		fun check_ec_key_mode/2,
157		fun check_chacha20_poly1305/2,
158		fun check_xchacha20_poly1305/2,
159		fun check_curve25519/2,
160		fun check_curve448/2,
161		fun check_json/2,
162		fun check_sha3/2,
163		fun check_crypto/2,
164		fun check_public_key/2
165	])),
166	true = ets:delete_all_objects(?TAB),
167	true = ets:insert(?TAB, Entries),
168	ok.
169
170%%%-------------------------------------------------------------------
171%%% Internal check functions
172%%%-------------------------------------------------------------------
173
174%% @private
175check_ec_key_mode(_Fallback, Entries) ->
176	ECPEMEntry = {
177		'ECPrivateKey',
178		<<
179			48,119,2,1,1,4,32,104,152,88,12,19,82,251,156,171,31,222,207,
180			0,76,115,88,210,229,36,106,137,192,81,153,154,254,226,38,247,
181			70,226,157,160,10,6,8,42,134,72,206,61,3,1,7,161,68,3,66,0,4,
182			46,75,29,46,150,77,222,40,220,159,244,193,125,18,190,254,216,
183			38,191,11,52,115,159,213,230,77,27,131,94,17,46,21,186,71,62,
184			36,225,0,90,21,186,235,132,152,229,13,189,196,121,64,84,64,
185			229,173,12,24,23,127,175,67,247,29,139,91
186		>>,
187		not_encrypted
188	},
189	case public_key:pem_entry_decode(ECPEMEntry) of
190		#'ECPrivateKey'{ privateKey = PrivateKey, publicKey = PublicKey } when is_list(PrivateKey) andalso is_tuple(PublicKey) ->
191			[{ec_key_mode, list} | Entries];
192		#'ECPrivateKey'{ privateKey = PrivateKey, publicKey = PublicKey } when is_binary(PrivateKey) andalso is_binary(PublicKey) ->
193			[{ec_key_mode, binary} | Entries]
194	end.
195
196%% @private
197check_chacha20_poly1305(false, Entries) ->
198	check_chacha20_poly1305(jose_chacha20_poly1305_unsupported, Entries);
199check_chacha20_poly1305(true, Entries) ->
200	check_chacha20_poly1305(jose_jwa_chacha20_poly1305, Entries);
201check_chacha20_poly1305(Fallback, Entries) ->
202	true = ets:delete_object(?TAB, {chacha20_poly1305_module, jose_jwa_chacha20_poly1305}),
203	true = ets:delete_object(?TAB, {chacha20_poly1305_module, jose_chacha20_poly1305_unsupported}),
204	ChaCha20Poly1305Module = case ets:lookup(?TAB, chacha20_poly1305_module) of
205		[{chacha20_poly1305_module, M}] when is_atom(M) ->
206			M;
207		[] ->
208			case application:get_env(jose, chacha20_poly1305_module, undefined) of
209				undefined ->
210					check_chacha20_poly1305_modules(Fallback, [crypto, libsodium]);
211				M when is_atom(M) ->
212					check_chacha20_poly1305_module(M)
213			end
214	end,
215	[{chacha20_poly1305_module, ChaCha20Poly1305Module} | Entries].
216
217%% @private
218check_chacha20_poly1305_module(crypto) ->
219	jose_chacha20_poly1305_crypto;
220check_chacha20_poly1305_module(libsodium) ->
221	jose_chacha20_poly1305_libsodium;
222check_chacha20_poly1305_module(Module) when is_atom(Module) ->
223	Module.
224
225%% @private
226check_chacha20_poly1305_modules(Fallback, [Module | Modules]) ->
227	case code:ensure_loaded(Module) of
228		{module, Module} ->
229			_ = application:ensure_all_started(Module),
230			M = check_chacha20_poly1305_module(Module),
231			PT = crypto:strong_rand_bytes(8),
232			CEK = crypto:strong_rand_bytes(32),
233			IV = crypto:strong_rand_bytes(12),
234			AAD = <<>>,
235			try M:encrypt(PT, AAD, IV, CEK) of
236				{CT, TAG} when is_binary(CT) andalso is_binary(TAG) ->
237					try M:decrypt(CT, TAG, AAD, IV, CEK) of
238						PT ->
239							M;
240						_ ->
241							check_chacha20_poly1305_modules(Fallback, Modules)
242					catch
243						_:_ ->
244							check_chacha20_poly1305_modules(Fallback, Modules)
245					end;
246				_ ->
247					check_chacha20_poly1305_modules(Fallback, Modules)
248			catch
249				_:_ ->
250					check_chacha20_poly1305_modules(Fallback, Modules)
251			end;
252		_ ->
253			check_chacha20_poly1305_modules(Fallback, Modules)
254	end;
255check_chacha20_poly1305_modules(Fallback, []) ->
256	Fallback.
257
258%% @private
259check_curve25519(false, Entries) ->
260	check_curve25519(jose_curve25519_unsupported, Entries);
261check_curve25519(true, Entries) ->
262	check_curve25519(jose_jwa_curve25519, Entries);
263check_curve25519(Fallback, Entries) ->
264	true = ets:delete_object(?TAB, {curve25519_module, jose_jwa_curve25519}),
265	true = ets:delete_object(?TAB, {curve25519_module, jose_curve25519_unsupported}),
266	Curve25519Module = case ets:lookup(?TAB, curve25519_module) of
267		[{curve25519_module, M}] when is_atom(M) ->
268			M;
269		[] ->
270			case application:get_env(jose, curve25519_module, undefined) of
271				undefined ->
272					check_curve25519_modules(Fallback, [libdecaf, libsodium]);
273				M when is_atom(M) ->
274					check_curve25519_module(M)
275			end
276	end,
277	[{curve25519_module, Curve25519Module} | Entries].
278
279%% @private
280check_curve25519_module(libdecaf) ->
281	jose_curve25519_libdecaf;
282check_curve25519_module(libsodium) ->
283	jose_curve25519_libsodium;
284check_curve25519_module(Module) when is_atom(Module) ->
285	Module.
286
287%% @private
288check_curve25519_modules(Fallback, [Module | Modules]) ->
289	case code:ensure_loaded(Module) of
290		{module, Module} ->
291			_ = application:ensure_all_started(Module),
292			check_curve25519_module(Module);
293		_ ->
294			check_curve25519_modules(Fallback, Modules)
295	end;
296check_curve25519_modules(Fallback, []) ->
297	Fallback.
298
299%% @private
300check_curve448(false, Entries) ->
301	check_curve448(jose_curve448_unsupported, Entries);
302check_curve448(true, Entries) ->
303	check_curve448(jose_jwa_curve448, Entries);
304check_curve448(Fallback, Entries) ->
305	true = ets:delete_object(?TAB, {curve448_module, jose_jwa_curve448}),
306	true = ets:delete_object(?TAB, {curve448_module, jose_curve448_unsupported}),
307	Curve448Module = case ets:lookup(?TAB, curve448_module) of
308		[{curve448_module, M}] when is_atom(M) ->
309			M;
310		[] ->
311			case application:get_env(jose, curve448_module, undefined) of
312				undefined ->
313					check_curve448_modules(Fallback, [libdecaf]);
314				M when is_atom(M) ->
315					check_curve448_module(M)
316			end
317	end,
318	[{curve448_module, Curve448Module} | Entries].
319
320%% @private
321check_curve448_module(libdecaf) ->
322	jose_curve448_libdecaf;
323check_curve448_module(Module) when is_atom(Module) ->
324	Module.
325
326%% @private
327check_curve448_modules(Fallback, [Module | Modules]) ->
328	case code:ensure_loaded(Module) of
329		{module, Module} ->
330			_ = application:ensure_all_started(Module),
331			check_curve448_module(Module);
332		_ ->
333			check_curve448_modules(Fallback, Modules)
334	end;
335check_curve448_modules(Fallback, []) ->
336	Fallback.
337
338%% @private
339check_json(_Fallback, Entries) ->
340	JSONModule = case ets:lookup(?TAB, json_module) of
341		[{json_module, M}] when is_atom(M) ->
342			M;
343		[] ->
344			case application:get_env(jose, json_module, undefined) of
345				undefined ->
346					case code:ensure_loaded(elixir) of
347						{module, elixir} ->
348							check_json_modules([ojson, 'Elixir.Jason', 'Elixir.Poison', jiffy, jsone, jsx]);
349						_ ->
350							check_json_modules([ojson, jiffy, jsone, jsx])
351					end;
352				M when is_atom(M) ->
353					check_json_module(M)
354			end
355	end,
356	[{json_module, JSONModule} | Entries].
357
358%% @private
359check_json_module(jiffy) ->
360	jose_json_jiffy;
361check_json_module(jsx) ->
362	jose_json_jsx;
363check_json_module(jsone) ->
364	jose_json_jsone;
365check_json_module(ojson) ->
366	jose_json_ojson;
367check_json_module('Elixir.Jason') ->
368	jose_json_jason;
369check_json_module('Elixir.Poison') ->
370	Map = ?POISON_MAP,
371	Bin = ?POISON_BIN,
372	case jose_json_poison:encode(Map) of
373		Bin ->
374			jose_json_poison;
375		_ ->
376			check_json_module('Elixir.JOSE.Poison')
377	end;
378check_json_module('Elixir.JOSE.Poison') ->
379	Map = ?POISON_MAP,
380	Bin = ?POISON_BIN,
381	case code:ensure_loaded('Elixir.JOSE.Poison') of
382		{module, 'Elixir.JOSE.Poison'} ->
383			try jose_json_poison_lexical_encoder:encode(Map) of
384				Bin ->
385					jose_json_poison_lexical_encoder;
386				_ ->
387					check_json_module(jose_json_poison_compat_encoder)
388			catch
389				_:_ ->
390					check_json_module(jose_json_poison_compat_encoder)
391			end;
392		_ ->
393			check_json_module(jose_json_poison_compat_encoder)
394	end;
395check_json_module(jose_json_poison_compat_encoder) ->
396	Map = ?POISON_MAP,
397	Bin = ?POISON_BIN,
398	try jose_json_poison_compat_encoder:encode(Map) of
399		Bin ->
400			jose_json_poison_compat_encoder;
401		_ ->
402			jose_json_poison
403	catch
404		_:_ ->
405			jose_json_poison
406	end;
407check_json_module(Module) when is_atom(Module) ->
408	Module.
409
410%% @private
411check_json_modules([Module | Modules]) ->
412	case code:ensure_loaded(Module) of
413		{module, Module} ->
414			_ = application:ensure_all_started(Module),
415			check_json_module(Module);
416		_ ->
417			check_json_modules(Modules)
418	end;
419check_json_modules([]) ->
420	jose_json_unsupported.
421
422%% @private
423check_sha3(false, Entries) ->
424	check_sha3(jose_sha3_unsupported, Entries);
425check_sha3(true, Entries) ->
426	check_sha3(jose_jwa_sha3, Entries);
427check_sha3(Fallback, Entries) ->
428	true = ets:delete_object(?TAB, {sha3_module, jose_jwa_sha3}),
429	true = ets:delete_object(?TAB, {sha3_module, jose_sha3_unsupported}),
430	SHA3Module = case ets:lookup(?TAB, sha3_module) of
431		[{sha3_module, M}] when is_atom(M) ->
432			M;
433		[] ->
434			case application:get_env(jose, sha3_module, undefined) of
435				undefined ->
436					check_sha3_modules(Fallback, [keccakf1600, libdecaf]);
437				M when is_atom(M) ->
438					check_sha3_module(M)
439			end
440	end,
441	[{sha3_module, SHA3Module} | Entries].
442
443%% @private
444check_sha3_module(keccakf1600) ->
445	check_sha3_module(jose_sha3_keccakf1600);
446check_sha3_module(libdecaf) ->
447	check_sha3_module(jose_sha3_libdecaf);
448check_sha3_module(jose_sha3_keccakf1600) ->
449	_ = code:ensure_loaded(keccakf1600),
450	case erlang:function_exported(keccakf1600, hash, 3) of
451		false ->
452			% version < 2
453			check_sha3_module(jose_sha3_keccakf1600_driver);
454		true ->
455			% version >= 2
456			check_sha3_module(jose_sha3_keccakf1600_nif)
457	end;
458check_sha3_module(Module) when is_atom(Module) ->
459	Module.
460
461%% @private
462check_sha3_modules(Fallback, [Module | Modules]) ->
463	case code:ensure_loaded(Module) of
464		{module, Module} ->
465			_ = application:ensure_all_started(Module),
466			check_sha3_module(Module);
467		_ ->
468			check_sha3_modules(Fallback, Modules)
469	end;
470check_sha3_modules(Fallback, []) ->
471	Fallback.
472
473%% @private
474check_crypto(false, Entries) ->
475	check_crypto(jose_jwa_unsupported, Entries);
476check_crypto(true, Entries) ->
477	check_crypto(jose_jwa_aes, Entries);
478check_crypto(Fallback, Entries) ->
479	Ciphers = [
480		aes_cbc,
481		aes_ecb,
482		aes_gcm
483	],
484	KeySizes = [
485		128,
486		192,
487		256
488	],
489	CipherEntries0 = [begin
490		case has_cipher(Cipher, KeySize) of
491			false ->
492				{{cipher, {Cipher, KeySize}}, {Fallback, {Cipher, KeySize}}};
493			{true, CryptoCipher} ->
494				{{cipher, {Cipher, KeySize}}, {crypto, CryptoCipher}}
495		end
496	end || Cipher <- Ciphers, KeySize <- KeySizes],
497	CipherEntries1 =
498		case lists:keyfind(chacha20_poly1305_module, 1, Entries) of
499			{chacha20_poly1305_module, jose_chacha20_poly1305_unsupported} ->
500				CipherEntries0 ++ [{{cipher, {chacha20_poly1305, 256}}, {Fallback, {chacha20_poly1305, 256}}}];
501			_ ->
502				CipherEntries0 ++ [{{cipher, {chacha20_poly1305, 256}}, {jose_chacha20_poly1305, {chacha20_poly1305, 256}}}]
503		end,
504	CipherEntries2 =
505		case lists:keyfind(xchacha20_poly1305_module, 1, Entries) of
506			{xchacha20_poly1305_module, jose_xchacha20_poly1305_unsupported} ->
507				CipherEntries1 ++ [{{cipher, {xchacha20_poly1305, 256}}, {Fallback, {xchacha20_poly1305, 256}}}];
508			_ ->
509				CipherEntries1 ++ [{{cipher, {xchacha20_poly1305, 256}}, {jose_xchacha20_poly1305, {xchacha20_poly1305, 256}}}]
510		end,
511	[CipherEntries2 | Entries].
512
513%% @private
514check_public_key(Fallback, Entries) ->
515	RSACrypt = check_rsa_crypt(Fallback),
516	RSASign = check_rsa_sign(Fallback),
517	[RSACrypt, RSASign | Entries].
518
519%% @private
520check_rsa_crypt(false) ->
521	check_rsa_crypt(jose_jwa_unsupported);
522check_rsa_crypt(true) ->
523	check_rsa_crypt(jose_jwa_pkcs1);
524check_rsa_crypt(Fallback) ->
525	Algorithms = [
526		%% Algorithm,    LegacyOptions,                       FutureOptions
527		{rsa1_5,       [{rsa_pad, rsa_pkcs1_padding}],      [{rsa_padding, rsa_pkcs1_padding}]},
528		{rsa_oaep,     [{rsa_pad, rsa_pkcs1_oaep_padding}], [{rsa_padding, rsa_pkcs1_oaep_padding}]},
529		{rsa_oaep_256, notsup,                              [{rsa_padding, rsa_pkcs1_oaep_padding}, {rsa_oaep_md, sha256}]}
530	],
531	_ = code:ensure_loaded(public_key),
532	_ = application:ensure_all_started(public_key),
533	Legacy = case erlang:function_exported(public_key, sign, 4) of
534		false ->
535			legacy;
536		true ->
537			future
538	end,
539	CryptEntries = [begin
540		case has_rsa_crypt(Algorithm, Legacy, LegacyOptions, FutureOptions) of
541			false ->
542				{{rsa_crypt, Algorithm}, {Fallback, FutureOptions}};
543			{true, Module, Options} ->
544				{{rsa_crypt, Algorithm}, {Module, Options}}
545		end
546	end || {Algorithm, LegacyOptions, FutureOptions} <- Algorithms],
547	CryptEntries.
548
549%% @private
550check_rsa_sign(false) ->
551	check_rsa_sign(jose_jwa_unsupported);
552check_rsa_sign(true) ->
553	check_rsa_sign(jose_jwa_pkcs1);
554check_rsa_sign(Fallback) ->
555	Paddings = [
556		rsa_pkcs1_padding,
557		rsa_pkcs1_pss_padding
558	],
559	_ = code:ensure_loaded(public_key),
560	_ = application:ensure_all_started(public_key),
561	Legacy = case erlang:function_exported(public_key, sign, 4) of
562		false ->
563			legacy;
564		true ->
565			future
566	end,
567	SignEntries = [begin
568		case has_rsa_sign(Padding, Legacy, sha) of
569			false ->
570				{{rsa_sign, Padding}, {Fallback, [{rsa_padding, Padding}]}};
571			{true, Module} ->
572				{{rsa_sign, Padding}, {Module, undefined}};
573			{true, Module, Options} ->
574				{{rsa_sign, Padding}, {Module, Options}}
575		end
576	end || Padding <- Paddings],
577	SignEntries.
578
579%% @private
580check_xchacha20_poly1305(false, Entries) ->
581	check_xchacha20_poly1305(jose_xchacha20_poly1305_unsupported, Entries);
582check_xchacha20_poly1305(true, Entries) ->
583	check_xchacha20_poly1305(jose_jwa_xchacha20_poly1305, Entries);
584check_xchacha20_poly1305(Fallback, Entries) ->
585	true = ets:delete_object(?TAB, {xchacha20_poly1305_module, jose_jwa_xchacha20_poly1305}),
586	true = ets:delete_object(?TAB, {xchacha20_poly1305_module, jose_xchacha20_poly1305_unsupported}),
587	ChaCha20Poly1305Module = case ets:lookup(?TAB, xchacha20_poly1305_module) of
588		[{xchacha20_poly1305_module, M}] when is_atom(M) ->
589			M;
590		[] ->
591			case application:get_env(jose, xchacha20_poly1305_module, undefined) of
592				undefined ->
593					check_xchacha20_poly1305_modules(Fallback, [crypto]);
594				M when is_atom(M) ->
595					check_xchacha20_poly1305_module(M)
596			end
597	end,
598	[{xchacha20_poly1305_module, ChaCha20Poly1305Module} | Entries].
599
600%% @private
601check_xchacha20_poly1305_module(crypto) ->
602	jose_xchacha20_poly1305_crypto;
603check_xchacha20_poly1305_module(Module) when is_atom(Module) ->
604	Module.
605
606%% @private
607check_xchacha20_poly1305_modules(Fallback, [Module | Modules]) ->
608	case code:ensure_loaded(Module) of
609		{module, Module} ->
610			_ = application:ensure_all_started(Module),
611			M = check_xchacha20_poly1305_module(Module),
612			PT = crypto:strong_rand_bytes(8),
613			CEK = crypto:strong_rand_bytes(32),
614			IV = crypto:strong_rand_bytes(24),
615			AAD = <<>>,
616			try M:encrypt(PT, AAD, IV, CEK) of
617				{CT, TAG} when is_binary(CT) andalso is_binary(TAG) ->
618					try M:decrypt(CT, TAG, AAD, IV, CEK) of
619						PT ->
620							M;
621						_ ->
622							check_xchacha20_poly1305_modules(Fallback, Modules)
623					catch
624						_:_ ->
625							check_xchacha20_poly1305_modules(Fallback, Modules)
626					end;
627				_ ->
628					check_xchacha20_poly1305_modules(Fallback, Modules)
629			catch
630				_:_ ->
631					check_xchacha20_poly1305_modules(Fallback, Modules)
632			end;
633		_ ->
634			check_xchacha20_poly1305_modules(Fallback, Modules)
635	end;
636check_xchacha20_poly1305_modules(Fallback, []) ->
637	Fallback.
638
639%% @private
640has_cipher(aes_cbc, KeySize) ->
641	Key = << 0:KeySize >>,
642	IV = << 0:128 >>,
643	PlainText = jose_jwa_pkcs7:pad(<<>>),
644	case has_block_cipher(aes_cbc, {Key, IV, PlainText}) of
645		false ->
646			Cipher = list_to_atom("aes_" ++ integer_to_list(KeySize) ++ "_cbc"),
647			has_block_cipher(Cipher, {Key, IV, PlainText});
648		Other ->
649			Other
650	end;
651has_cipher(aes_ecb, KeySize) ->
652	Key = << 0:KeySize >>,
653	PlainText = jose_jwa_pkcs7:pad(<<>>),
654	case has_block_cipher(aes_ecb, {Key, PlainText}) of
655		false ->
656			Cipher = list_to_atom("aes_" ++ integer_to_list(KeySize) ++ "_ecb"),
657			has_block_cipher(Cipher, {Key, PlainText});
658		Other ->
659			Other
660	end;
661has_cipher(aes_gcm, KeySize) ->
662	Key = << 0:KeySize >>,
663	IV = << 0:96 >>,
664	AAD = <<>>,
665	PlainText = jose_jwa_pkcs7:pad(<<>>),
666	case has_block_cipher(aes_gcm, {Key, IV, AAD, PlainText}) of
667		false ->
668			Cipher = list_to_atom("aes_" ++ integer_to_list(KeySize) ++ "_gcm"),
669			has_block_cipher(Cipher, {Key, IV, AAD, PlainText});
670		Other ->
671			Other
672	end.
673
674%% @private
675has_block_cipher(Cipher, {Key, PlainText}) ->
676	case catch jose_crypto_compat:crypto_one_time(Cipher, Key, PlainText, true) of
677		CipherText when is_binary(CipherText) ->
678			case catch jose_crypto_compat:crypto_one_time(Cipher, Key, CipherText, false) of
679				PlainText ->
680					{true, Cipher};
681				_ ->
682					false
683			end;
684		_ ->
685			false
686	end;
687has_block_cipher(Cipher, {Key, IV, PlainText}) ->
688	case catch jose_crypto_compat:crypto_one_time(Cipher, Key, IV, PlainText, true) of
689		CipherText when is_binary(CipherText) ->
690			case catch jose_crypto_compat:crypto_one_time(Cipher, Key, IV, CipherText, false) of
691				PlainText ->
692					{true, Cipher};
693				_ ->
694					false
695			end;
696		_ ->
697			false
698	end;
699has_block_cipher(Cipher, {Key, IV, AAD, PlainText}) ->
700	case catch jose_crypto_compat:crypto_one_time(Cipher, Key, IV, {AAD, PlainText}, true) of
701		{CipherText, CipherTag} when is_binary(CipherText) andalso is_binary(CipherTag) ->
702			case catch jose_crypto_compat:crypto_one_time(Cipher, Key, IV, {AAD, CipherText, CipherTag}, false) of
703				PlainText ->
704					{true, Cipher};
705				_ ->
706					false
707			end;
708		_ ->
709			false
710	end.
711
712%% @private
713has_rsa_crypt(Algorithm, future, _LegacyOptions, FutureOptions) ->
714	PlainText = << 0:8 >>,
715	PublicKey = rsa_public_key(),
716	case catch public_key:encrypt_public(PlainText, PublicKey, FutureOptions) of
717		CipherText when is_binary(CipherText) ->
718			PrivateKey = rsa_private_key(),
719			case catch public_key:decrypt_private(CipherText, PrivateKey, FutureOptions) of
720				PlainText ->
721					case catch public_key:decrypt_private(rsa_ciphertext(Algorithm), PrivateKey, FutureOptions) of
722						<<"ciphertext">> ->
723							{true, public_key, FutureOptions};
724						_ ->
725							false
726					end;
727				_ ->
728					false
729			end;
730		_ ->
731			false
732	end;
733has_rsa_crypt(_Algorithm, legacy, notsup, _FutureOptions) ->
734	false;
735has_rsa_crypt(Algorithm, legacy, LegacyOptions, _FutureOptions) ->
736	PlainText = << 0:8 >>,
737	PublicKey = rsa_public_key(),
738	case catch public_key:encrypt_public(PlainText, PublicKey, LegacyOptions) of
739		CipherText when is_binary(CipherText) ->
740			PrivateKey = rsa_private_key(),
741			case catch public_key:decrypt_private(CipherText, PrivateKey, LegacyOptions) of
742				PlainText ->
743					case catch public_key:decrypt_private(rsa_ciphertext(Algorithm), PrivateKey, LegacyOptions) of
744						<<"ciphertext">> ->
745							{true, public_key, LegacyOptions};
746						_ ->
747							false
748					end;
749				_ ->
750					false
751			end;
752		_ ->
753			false
754	end.
755
756%% @private
757has_rsa_sign(Padding, future, DigestType) ->
758	Message = << 0:8 >>,
759	PrivateKey = rsa_private_key(),
760	Options = [{rsa_padding, Padding}],
761	case catch public_key:sign(Message, DigestType, PrivateKey, Options) of
762		Signature when is_binary(Signature) ->
763			PublicKey = rsa_public_key(),
764			case catch public_key:verify(Message, DigestType, Signature, PublicKey, Options) of
765				true ->
766					{true, public_key, Options};
767				_ ->
768					false
769			end;
770		_ ->
771			false
772	end;
773has_rsa_sign(rsa_pkcs1_padding, legacy, DigestType) ->
774	Message = << 0:8 >>,
775	PrivateKey = rsa_private_key(),
776	case catch public_key:sign(Message, DigestType, PrivateKey) of
777		Signature when is_binary(Signature) ->
778			PublicKey = rsa_public_key(),
779			case catch public_key:verify(Message, DigestType, Signature, PublicKey) of
780				true ->
781					{true, public_key};
782				_ ->
783					false
784			end;
785		_ ->
786			false
787	end;
788has_rsa_sign(_Padding, legacy, _DigestType) ->
789	false.
790
791%% @private
792read_pem_key(PEM) ->
793	public_key:pem_entry_decode(hd(public_key:pem_decode(PEM))).
794
795%% @private
796rsa_ciphertext(rsa1_5) ->
797	<<
798		16#67, 16#3F, 16#BF, 16#D4, 16#93, 16#1E, 16#6C, 16#54,
799		16#67, 16#DE, 16#29, 16#3C, 16#71, 16#5F, 16#95, 16#BE,
800		16#69, 16#99, 16#D3, 16#6C, 16#E4, 16#81, 16#1E, 16#49,
801		16#BE, 16#5D, 16#91, 16#85, 16#E7, 16#1D, 16#04, 16#C5,
802		16#38, 16#0A, 16#6F, 16#3F, 16#32, 16#2C, 16#3D, 16#67,
803		16#53, 16#B1, 16#EA, 16#D7, 16#2E, 16#ED, 16#6A, 16#7A,
804		16#EB, 16#49, 16#79, 16#71, 16#CA, 16#F5, 16#71, 16#67,
805		16#FA, 16#8B, 16#B8, 16#A8, 16#30, 16#59, 16#2E, 16#88,
806		16#98, 16#19, 16#AE, 16#B2, 16#94, 16#BA, 16#6E, 16#D2,
807		16#EF, 16#28, 16#BE, 16#04, 16#4F, 16#90, 16#77, 16#CA,
808		16#3D, 16#11, 16#2B, 16#E7, 16#17, 16#D8, 16#89, 16#7F,
809		16#EC, 16#7A, 16#2C, 16#70, 16#A5, 16#08, 16#FB, 16#5B
810	>>;
811rsa_ciphertext(rsa_oaep) ->
812	<<
813		16#B8, 16#F7, 16#0C, 16#A8, 16#F8, 16#30, 16#2A, 16#E9,
814		16#68, 16#8A, 16#DB, 16#3E, 16#5D, 16#AE, 16#84, 16#A7,
815		16#16, 16#FA, 16#9D, 16#E2, 16#FC, 16#81, 16#F7, 16#DF,
816		16#A8, 16#DB, 16#8F, 16#4F, 16#92, 16#A1, 16#51, 16#9E,
817		16#6B, 16#C5, 16#36, 16#CE, 16#93, 16#10, 16#11, 16#D9,
818		16#D5, 16#C2, 16#C9, 16#85, 16#14, 16#EF, 16#D5, 16#C3,
819		16#AC, 16#63, 16#BE, 16#49, 16#FA, 16#02, 16#1A, 16#FC,
820		16#3D, 16#D0, 16#2C, 16#83, 16#C5, 16#76, 16#1D, 16#F5,
821		16#FA, 16#A0, 16#D7, 16#42, 16#ED, 16#3F, 16#A4, 16#12,
822		16#32, 16#14, 16#93, 16#51, 16#79, 16#2E, 16#40, 16#FB,
823		16#14, 16#18, 16#DF, 16#30, 16#62, 16#9F, 16#F3, 16#59,
824		16#5D, 16#83, 16#0F, 16#4A, 16#8F, 16#9B, 16#3F, 16#39
825	>>;
826rsa_ciphertext(rsa_oaep_256) ->
827	<<
828		16#09, 16#24, 16#EA, 16#EB, 16#D4, 16#EF, 16#00, 16#BE,
829		16#8E, 16#02, 16#BE, 16#25, 16#24, 16#24, 16#18, 16#81,
830		16#8D, 16#7A, 16#A2, 16#EB, 16#F1, 16#BE, 16#5C, 16#DC,
831		16#D0, 16#71, 16#43, 16#09, 16#53, 16#12, 16#44, 16#AD,
832		16#8A, 16#CD, 16#F8, 16#45, 16#7F, 16#1F, 16#30, 16#B6,
833		16#54, 16#8E, 16#AB, 16#D2, 16#10, 16#14, 16#BC, 16#CE,
834		16#7A, 16#99, 16#DC, 16#A6, 16#8D, 16#16, 16#5A, 16#A0,
835		16#50, 16#3A, 16#93, 16#0E, 16#53, 16#4A, 16#B5, 16#6B,
836		16#51, 16#E8, 16#43, 16#8F, 16#BD, 16#2D, 16#E0, 16#63,
837		16#36, 16#24, 16#5B, 16#8D, 16#DD, 16#98, 16#AC, 16#37,
838		16#7C, 16#16, 16#DB, 16#03, 16#C8, 16#BD, 16#22, 16#D2,
839		16#15, 16#98, 16#91, 16#B7, 16#3C, 16#01, 16#CF, 16#0E
840	>>.
841
842%% @private
843rsa_public_key() ->
844	read_pem_key(<<
845		"-----BEGIN PUBLIC KEY-----\n"
846		"MHwwDQYJKoZIhvcNAQEBBQADawAwaAJhAL/f1xISwDSm4m6sYHm6WD4WK2egfyfZ\n"
847		"hd0w4iVeZvHjUurZVRVQojs7hZC7DKBfjShl6M7BT9j7gkaYOXlJHLhK6/J+Zr0C\n"
848		"g6PMkkbejQltgr4fUzbG8zUBo7BMs4Xm0wIDAQAB\n"
849		"-----END PUBLIC KEY-----\n"
850	>>).
851
852%% @private
853rsa_private_key() ->
854	read_pem_key(<<
855		"-----BEGIN RSA PRIVATE KEY-----\n"
856		"MIIBzAIBAAJhAL/f1xISwDSm4m6sYHm6WD4WK2egfyfZhd0w4iVeZvHjUurZVRVQ\n"
857		"ojs7hZC7DKBfjShl6M7BT9j7gkaYOXlJHLhK6/J+Zr0Cg6PMkkbejQltgr4fUzbG\n"
858		"8zUBo7BMs4Xm0wIDAQABAmEAiisNO7WG9SNLoPi+TEn061iZjvjTOAX60Io3/0LY\n"
859		"jMzu07EHBN9Yw6CcENmxQPcsdIRlSKLlt+UeUdBES6Zoccek5fJl+gnqExeX2Av1\n"
860		"v0Y8vIP2yejV7Pw+SrNxpY5ZAjEA+WMEZEgFrK8cPJmZLR9Kj3jvN5P+AmIKzg00\n"
861		"VMW93rS+sdHmYQUStqBuu2XRw5SlAjEAxPZlLCZ83GrqdStcmChCFpflzCRyU/wC\n"
862		"qVVP8QYfct49Cca3TyC8lCywwXI5s5wXAjA1JQK0lByRdiegSmM4GGj9NhpUT7db\n"
863		"rqT60BmMzy7tHLtejYp4tmoMfRfb25DeCvkCMQCO+usQ9NOZUsfmzNaH4lmvew8n\n"
864		"daHFE+F+uV6x8ibsRSZ8LVQuze33hsW9eEUo/HsCMQDKkImE3DSqHgwfKPjtecFH\n"
865		"oftdsGQ4u+MUGkST94Hh8479oNYaNveCRDOTJ4GJjUE=\n"
866		"-----END RSA PRIVATE KEY-----\n"
867	>>).
868