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 : 21 Jul 2015 by Andrew Bennett <potatosaladx@gmail.com> 10%%%------------------------------------------------------------------- 11-module(jose_jwa). 12 13-include_lib("public_key/include/public_key.hrl"). 14 15%% Crypto API 16-export([block_decrypt/3]). 17-export([block_encrypt/3]). 18-export([block_decrypt/4]). 19-export([block_encrypt/4]). 20%% Public Key API 21-export([decrypt_private/3]). 22-export([encrypt_public/3]). 23-export([sign/4]). 24-export([verify/5]). 25%% API 26-export([block_cipher/1]). 27-export([crypto_ciphers/0]). 28-export([crypto_fallback/0]). 29-export([crypto_fallback/1]). 30-export([crypto_supports/0]). 31-export([constant_time_compare/2]). 32-export([ec_key_mode/0]). 33-export([is_block_cipher_supported/1]). 34-export([is_chacha20_poly1305_supported/0]). 35-export([is_rsa_crypt_supported/1]). 36-export([is_rsa_sign_supported/1]). 37-export([is_xchacha20_poly1305_supported/0]). 38-export([supports/0]). 39-export([unsecured_signing/0]). 40-export([unsecured_signing/1]). 41 42-define(TAB, ?MODULE). 43 44-define(MAYBE_START_JOSE(F), try 45 F 46catch 47 _:_ -> 48 _ = jose:start(), 49 F 50end). 51 52%%==================================================================== 53%% Crypto API functions 54%%==================================================================== 55 56block_decrypt(Cipher, Key, CipherText) 57 when is_binary(CipherText) -> 58 case block_cipher(Cipher) of 59 {crypto, aes_ecb} -> 60 << << (jose_crypto_compat:crypto_one_time(aes_128_ecb, Key, Block, false))/binary >> || << Block:128/bitstring >> <= CipherText >>; 61 {Module, BlockCipher} -> 62 Module:block_decrypt(BlockCipher, Key, CipherText) 63 end. 64 65block_encrypt(Cipher, Key, PlainText) 66 when is_binary(PlainText) -> 67 case block_cipher(Cipher) of 68 {crypto, aes_ecb} -> 69 << << (jose_crypto_compat:crypto_one_time(aes_128_ecb, Key, Block, true))/binary >> || << Block:128/bitstring >> <= PlainText >>; 70 {Module, BlockCipher} -> 71 Module:block_encrypt(BlockCipher, Key, PlainText) 72 end. 73 74block_decrypt(Cipher, Key, IV, CipherText) 75 when is_binary(CipherText) -> 76 {Module, BlockCipher} = block_cipher(Cipher), 77 Module:block_decrypt(BlockCipher, Key, IV, CipherText); 78block_decrypt(Cipher, Key, IV, {AAD, CipherText, CipherTag}) 79 when is_binary(AAD) 80 andalso is_binary(CipherText) 81 andalso is_binary(CipherTag) -> 82 {Module, BlockCipher} = block_cipher(Cipher), 83 Module:block_decrypt(BlockCipher, Key, IV, {AAD, CipherText, CipherTag}). 84 85block_encrypt(Cipher, Key, IV, PlainText) 86 when is_binary(PlainText) -> 87 {Module, BlockCipher} = block_cipher(Cipher), 88 Module:block_encrypt(BlockCipher, Key, IV, PlainText); 89block_encrypt(Cipher, Key, IV, {AAD, PlainText}) 90 when is_binary(AAD) 91 andalso is_binary(PlainText) -> 92 {Module, BlockCipher} = block_cipher(Cipher), 93 Module:block_encrypt(BlockCipher, Key, IV, {AAD, PlainText}). 94 95%%==================================================================== 96%% Public Key API functions 97%%==================================================================== 98 99decrypt_private(CipherText, RSAPrivateKey=#'RSAPrivateKey'{}, Algorithm) 100 when is_atom(Algorithm) -> 101 {Module, Options} = rsa_crypt(Algorithm), 102 Module:decrypt_private(CipherText, RSAPrivateKey, Options); 103decrypt_private(CipherText, PrivateKey, Options) -> 104 public_key:decrypt_private(CipherText, PrivateKey, Options). 105 106encrypt_public(PlainText, RSAPublicKey=#'RSAPublicKey'{}, Algorithm) 107 when is_atom(Algorithm) -> 108 {Module, Options} = rsa_crypt(Algorithm), 109 Module:encrypt_public(PlainText, RSAPublicKey, Options); 110encrypt_public(PlainText, PublicKey, Options) -> 111 public_key:encrypt_public(PlainText, PublicKey, Options). 112 113sign(Message, DigestType, RSAPrivateKey=#'RSAPrivateKey'{}, Padding) 114 when is_atom(Padding) -> 115 case rsa_sign(Padding) of 116 {Module, undefined} -> 117 Module:sign(Message, DigestType, RSAPrivateKey); 118 {Module, Options} -> 119 Module:sign(Message, DigestType, RSAPrivateKey, Options) 120 end; 121sign(Message, DigestType, PrivateKey, _Options) -> 122 public_key:sign(Message, DigestType, PrivateKey). 123 124verify(Message, DigestType, Signature, RSAPublicKey=#'RSAPublicKey'{}, Padding) 125 when is_atom(Padding) -> 126 case rsa_sign(Padding) of 127 {Module, undefined} -> 128 Module:verify(Message, DigestType, Signature, RSAPublicKey); 129 {Module, Options} -> 130 Module:verify(Message, DigestType, Signature, RSAPublicKey, Options) 131 end; 132verify(Message, DigestType, Signature, PublicKey, _Options) -> 133 public_key:verify(Message, DigestType, Signature, PublicKey). 134 135%%==================================================================== 136%% API functions 137%%==================================================================== 138 139block_cipher(Cipher) -> 140 ?MAYBE_START_JOSE(ets:lookup_element(?TAB, {cipher, Cipher}, 2)). 141 142crypto_ciphers() -> 143 ?MAYBE_START_JOSE(ets:select(?TAB, [{ 144 {{cipher, '$1'}, {'$2', '_'}}, 145 [{'=/=', '$2', 'jose_jwa_unsupported'}], 146 [{{'$1', '$2'}}] 147 }])). 148 149crypto_fallback() -> 150 application:get_env(jose, crypto_fallback, false). 151 152crypto_fallback(Boolean) when is_boolean(Boolean) -> 153 application:set_env(jose, crypto_fallback, Boolean), 154 ?MAYBE_START_JOSE(jose_server:config_change()). 155 156crypto_supports() -> 157 Ciphers = ?MAYBE_START_JOSE(ets:select(?TAB, [{ 158 {{cipher, '$1'}, {'$2', '_'}}, 159 [{'=/=', '$2', 'jose_jwa_unsupported'}], 160 ['$1'] 161 }])), 162 RSACrypt = ?MAYBE_START_JOSE(ets:select(?TAB, [{ 163 {{rsa_crypt, '$1'}, {'$2', '_'}}, 164 [{'=/=', '$2', 'jose_jwa_unsupported'}], 165 ['$1'] 166 }])), 167 RSASign = ?MAYBE_START_JOSE(ets:select(?TAB, [{ 168 {{rsa_sign, '$1'}, {'$2', '_'}}, 169 [{'=/=', '$2', 'jose_jwa_unsupported'}], 170 ['$1'] 171 }])), 172 ExternalHashs = external_checks([ 173 {poly1305, fun() -> jose_chacha20_poly1305:authenticate(<<>>, <<0:256>>, <<0:96>>) end}, 174 {shake256, fun() -> jose_sha3:shake256(<<>>, 0) end} 175 ]), 176 ExternalPublicKeys = external_checks([ 177 {ed25519, fun jose_curve25519:eddsa_keypair/0}, 178 {ed25519ph, fun jose_curve25519:eddsa_keypair/0}, 179 {ed448, fun jose_curve448:eddsa_keypair/0}, 180 {ed448ph, fun jose_curve448:eddsa_keypair/0}, 181 {x25519, fun jose_curve25519:x25519_keypair/0}, 182 {x448, fun jose_curve448:x448_keypair/0} 183 ]), 184 Supports = crypto:supports(), 185 RecommendedHashs = [md5, poly1305, sha, sha256, sha384, sha512, shake256], 186 Hashs = RecommendedHashs -- ((RecommendedHashs -- proplists:get_value(hashs, Supports)) -- ExternalHashs), 187 RecommendedPublicKeys = [ec_gf2m, ecdh, ecdsa, ed25519, ed25519ph, ed448, ed448ph, rsa, x25519, x448], 188 PublicKeys = RecommendedPublicKeys -- ((RecommendedPublicKeys -- proplists:get_value(public_keys, Supports)) -- ExternalPublicKeys), 189 [ 190 {ciphers, Ciphers}, 191 {hashs, Hashs}, 192 {public_keys, PublicKeys}, 193 {rsa_crypt, RSACrypt}, 194 {rsa_sign, RSASign} 195 ]. 196 197constant_time_compare(<<>>, _) -> 198 false; 199constant_time_compare(_, <<>>) -> 200 false; 201constant_time_compare(A, B) 202 when is_binary(A) andalso is_binary(B) 203 andalso (byte_size(A) =/= byte_size(B)) -> 204 false; 205constant_time_compare(A, B) 206 when is_binary(A) andalso is_binary(B) 207 andalso (byte_size(A) =:= byte_size(B)) -> 208 constant_time_compare(A, B, 0). 209 210ec_key_mode() -> 211 ?MAYBE_START_JOSE(ets:lookup_element(?TAB, ec_key_mode, 2)). 212 213is_block_cipher_supported(Cipher) -> 214 case catch block_cipher(Cipher) of 215 {crypto, _} -> 216 true; 217 _ -> 218 false 219 end. 220 221is_chacha20_poly1305_supported() -> 222 case catch ?MAYBE_START_JOSE(ets:lookup_element(?TAB, chacha20_poly1305_module, 2)) of 223 jose_chacha20_poly1305_unsupported -> 224 false; 225 _ -> 226 true 227 end. 228 229is_rsa_crypt_supported(Padding) -> 230 case catch rsa_crypt(Padding) of 231 {public_key, _} -> 232 true; 233 _ -> 234 false 235 end. 236 237is_rsa_sign_supported(Padding) -> 238 case catch rsa_sign(Padding) of 239 {public_key, _} -> 240 true; 241 _ -> 242 false 243 end. 244 245is_xchacha20_poly1305_supported() -> 246 case catch ?MAYBE_START_JOSE(ets:lookup_element(?TAB, xchacha20_poly1305_module, 2)) of 247 jose_xchacha20_poly1305_unsupported -> 248 false; 249 _ -> 250 true 251 end. 252 253supports() -> 254 Supports = crypto_supports(), 255 JWEALG = support_check([ 256 {<<"A128GCMKW">>, ciphers, {aes_gcm, 128}}, 257 {<<"A192GCMKW">>, ciphers, {aes_gcm, 192}}, 258 {<<"A256GCMKW">>, ciphers, {aes_gcm, 256}}, 259 {<<"A128KW">>, ciphers, {aes_ecb, 128}}, 260 {<<"A192KW">>, ciphers, {aes_ecb, 192}}, 261 {<<"A256KW">>, ciphers, {aes_ecb, 256}}, 262 {<<"C20PKW">>, ciphers, {chacha20_poly1305, 256}}, 263 <<"ECDH-1PU">>, 264 {<<"ECDH-1PU+A128GCMKW">>, ciphers, {aes_gcm, 128}}, 265 {<<"ECDH-1PU+A192GCMKW">>, ciphers, {aes_gcm, 192}}, 266 {<<"ECDH-1PU+A256GCMKW">>, ciphers, {aes_gcm, 256}}, 267 <<"ECDH-1PU+A128KW">>, 268 <<"ECDH-1PU+A192KW">>, 269 <<"ECDH-1PU+A256KW">>, 270 {<<"ECDH-1PU+C20PKW">>, ciphers, {chacha20_poly1305, 256}}, 271 {<<"ECDH-1PU+XC20PKW">>, ciphers, {xchacha20_poly1305, 256}}, 272 <<"ECDH-ES">>, 273 {<<"ECDH-ES+A128GCMKW">>, ciphers, {aes_gcm, 128}}, 274 {<<"ECDH-ES+A192GCMKW">>, ciphers, {aes_gcm, 192}}, 275 {<<"ECDH-ES+A256GCMKW">>, ciphers, {aes_gcm, 256}}, 276 <<"ECDH-ES+A128KW">>, 277 <<"ECDH-ES+A192KW">>, 278 <<"ECDH-ES+A256KW">>, 279 {<<"ECDH-ES+C20PKW">>, ciphers, {chacha20_poly1305, 256}}, 280 {<<"ECDH-ES+XC20PKW">>, ciphers, {xchacha20_poly1305, 256}}, 281 {<<"PBES2-HS256+A128GCMKW">>, ciphers, {aes_gcm, 128}}, 282 {<<"PBES2-HS384+A192GCMKW">>, ciphers, {aes_gcm, 192}}, 283 {<<"PBES2-HS512+A256GCMKW">>, ciphers, {aes_gcm, 256}}, 284 {<<"PBES2-HS256+A128KW">>, ciphers, {aes_ecb, 128}}, 285 {<<"PBES2-HS384+A192KW">>, ciphers, {aes_ecb, 192}}, 286 {<<"PBES2-HS512+A256KW">>, ciphers, {aes_ecb, 256}}, 287 {<<"PBES2-HS512+C20PKW">>, ciphers, {chacha20_poly1305, 256}}, 288 {<<"PBES2-HS512+XC20PKW">>, ciphers, {xchacha20_poly1305, 256}}, 289 {<<"RSA1_5">>, rsa_crypt, rsa1_5}, 290 {<<"RSA-OAEP">>, rsa_crypt, rsa_oaep}, 291 {<<"RSA-OAEP-256">>, rsa_crypt, rsa_oaep_256}, 292 {<<"XC20PKW">>, ciphers, {xchacha20_poly1305, 256}}, 293 <<"dir">> 294 ], Supports, []), 295 JWEENC = support_check([ 296 {<<"A128CBC-HS256">>, ciphers, {aes_cbc, 128}}, 297 {<<"A192CBC-HS384">>, ciphers, {aes_cbc, 192}}, 298 {<<"A256CBC-HS512">>, ciphers, {aes_cbc, 256}}, 299 {<<"A128GCM">>, ciphers, {aes_gcm, 128}}, 300 {<<"A192GCM">>, ciphers, {aes_gcm, 192}}, 301 {<<"A256GCM">>, ciphers, {aes_gcm, 256}}, 302 {<<"C20P">>, ciphers, {chacha20_poly1305, 256}}, 303 {<<"XC20P">>, ciphers, {xchacha20_poly1305, 256}} 304 ], Supports, []), 305 JWEZIP = support_check([ 306 <<"DEF">> 307 ], Supports, []), 308 JWKKTY = support_check([ 309 <<"EC">>, 310 <<"oct">>, 311 <<"OKP">>, 312 <<"RSA">> 313 ], Supports, []), 314 JWKKTYOKPcrv = support_check([ 315 {<<"Ed25519">>, public_keys, ed25519}, 316 {<<"Ed25519ph">>, public_keys, ed25519ph}, 317 {<<"Ed448">>, public_keys, ed448}, 318 {<<"Ed448ph">>, public_keys, ed448ph}, 319 {<<"X25519">>, public_keys, x25519}, 320 {<<"X448">>, public_keys, x448} 321 ], Supports, []), 322 JWSALG = support_check([ 323 {<<"Ed25519">>, public_keys, ed25519}, 324 {<<"Ed25519ph">>, public_keys, ed25519ph}, 325 {<<"Ed448">>, public_keys, ed448}, 326 {<<"Ed448ph">>, public_keys, ed448ph}, 327 {<<"ES256">>, public_keys, ecdsa}, 328 {<<"ES384">>, public_keys, ecdsa}, 329 {<<"ES512">>, public_keys, ecdsa}, 330 <<"HS256">>, 331 <<"HS384">>, 332 <<"HS512">>, 333 {<<"PS256">>, rsa_sign, rsa_pkcs1_pss_padding}, 334 {<<"PS384">>, rsa_sign, rsa_pkcs1_pss_padding}, 335 {<<"PS512">>, rsa_sign, rsa_pkcs1_pss_padding}, 336 {<<"Poly1305">>, hashs, poly1305}, 337 {<<"RS256">>, rsa_sign, rsa_pkcs1_padding}, 338 {<<"RS384">>, rsa_sign, rsa_pkcs1_padding}, 339 {<<"RS512">>, rsa_sign, rsa_pkcs1_padding}, 340 {<<"none">>, fun unsecured_signing/0} 341 ], Supports, []), 342 [ 343 {jwe, 344 {alg, JWEALG}, 345 {enc, JWEENC}, 346 {zip, JWEZIP}}, 347 {jwk, 348 {kty, JWKKTY}, 349 {kty_OKP_crv, JWKKTYOKPcrv}}, 350 {jws, 351 {alg, JWSALG}} 352 ]. 353 354unsecured_signing() -> 355 application:get_env(jose, unsecured_signing, false). 356 357unsecured_signing(Boolean) when is_boolean(Boolean) -> 358 application:set_env(jose, unsecured_signing, Boolean), 359 ?MAYBE_START_JOSE(jose_server:config_change()). 360 361%%%------------------------------------------------------------------- 362%%% Internal functions 363%%%------------------------------------------------------------------- 364 365%% @private 366constant_time_compare(<< AH, AT/binary >>, << BH, BT/binary >>, R) -> 367 constant_time_compare(AT, BT, R bor (BH bxor AH)); 368constant_time_compare(<<>>, <<>>, R) -> 369 R =:= 0. 370 371%% @private 372external_checks(Checks) -> 373 external_checks(Checks, []). 374 375%% @private 376external_checks([{Key, Check} | Checks], Acc) -> 377 try 378 Check(), 379 external_checks(Checks, [Key | Acc]) 380 catch 381 _:_ -> 382 external_checks(Checks, Acc) 383 end; 384external_checks([], Acc) -> 385 lists:reverse(Acc). 386 387%% @private 388rsa_crypt(Algorithm) -> 389 ?MAYBE_START_JOSE(ets:lookup_element(?TAB, {rsa_crypt, Algorithm}, 2)). 390 391%% @private 392rsa_sign(Padding) -> 393 ?MAYBE_START_JOSE(ets:lookup_element(?TAB, {rsa_sign, Padding}, 2)). 394 395%% @private 396support_check([], _Supports, Acc) -> 397 lists:usort(Acc); 398support_check([{ALG, Key, Val} | Rest], Supports, Acc) -> 399 case lists:member(Val, proplists:get_value(Key, Supports)) of 400 false -> 401 support_check(Rest, Supports, Acc); 402 true -> 403 support_check(Rest, Supports, [ALG | Acc]) 404 end; 405support_check([{ALG, Check} | Rest], Supports, Acc) when is_function(Check, 0) -> 406 case Check() of 407 false -> 408 support_check(Rest, Supports, Acc); 409 true -> 410 support_check(Rest, Supports, [ALG | Acc]) 411 end; 412support_check([ALG | Rest], Supports, Acc) when is_binary(ALG) -> 413 support_check(Rest, Supports, [ALG | Acc]). 414