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