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