%% -*- mode: erlang; tab-width: 4; indent-tabs-mode: 1; st-rulers: [70] -*-
%% vim: ts=4 sw=4 ft=erlang noet
%%%-------------------------------------------------------------------
%%% @author Andrew Bennett <potatosaladx@gmail.com>
%%% @copyright 2014-2015, Andrew Bennett
%%% @doc
%%%
%%% @end
%%% Created :  22 Jul 2015 by Andrew Bennett <potatosaladx@gmail.com>
%%%-------------------------------------------------------------------
-module(jose_jwe_enc_aes).
-behaviour(jose_jwe).
-behaviour(jose_jwe_enc).

%% jose_jwe callbacks
-export([from_map/1]).
-export([to_map/2]).
%% jose_jwe_enc callbacks
-export([algorithm/1]).
-export([bits/1]).
-export([block_decrypt/4]).
-export([block_encrypt/4]).
-export([next_cek/1]).
-export([next_iv/1]).
%% API
-export([cipher_supported/0]).
-export([hmac_supported/0]).

%% Types
-type cipher()   :: aes_cbc | aes_gcm.
-type key_size() :: 128 | 192 | 256.

-record(jose_jwe_enc_aes, {
	cipher  = undefined :: undefined | {cipher(), key_size()},
	bits    = undefined :: undefined | pos_integer(),
	cek_len = undefined :: undefined | pos_integer(),
	iv_len  = undefined :: undefined | pos_integer(),
	enc_len = undefined :: undefined | pos_integer(),
	mac_len = undefined :: undefined | pos_integer(),
	tag_len = undefined :: undefined | pos_integer(),
	hmac    = undefined :: undefined | sha256 | sha384 | sha512
}).

-type enc() :: #jose_jwe_enc_aes{}.

-export_type([enc/0]).

-define(AES_128_CBC_HMAC_SHA_256, #jose_jwe_enc_aes{
	cipher  = {aes_cbc, 128},
	bits    = 256,
	cek_len = 32,
	iv_len  = 16,
	enc_len = 16,
	mac_len = 16,
	tag_len = 16,
	hmac    = sha256
}).

-define(AES_192_CBC_HMAC_SHA_384, #jose_jwe_enc_aes{
	cipher  = {aes_cbc, 192},
	bits    = 384,
	cek_len = 48,
	iv_len  = 16,
	enc_len = 24,
	mac_len = 24,
	tag_len = 24,
	hmac    = sha384
}).

-define(AES_256_CBC_HMAC_SHA_512, #jose_jwe_enc_aes{
	cipher  = {aes_cbc, 256},
	bits    = 512,
	cek_len = 64,
	iv_len  = 16,
	enc_len = 32,
	mac_len = 32,
	tag_len = 32,
	hmac    = sha512
}).

-define(AES_128_GCM, #jose_jwe_enc_aes{
	cipher  = {aes_gcm, 128},
	bits    = 128,
	cek_len = 16,
	iv_len  = 12
}).

-define(AES_192_GCM, #jose_jwe_enc_aes{
	cipher  = {aes_gcm, 192},
	bits    = 192,
	cek_len = 24,
	iv_len  = 12
}).

-define(AES_256_GCM, #jose_jwe_enc_aes{
	cipher  = {aes_gcm, 256},
	bits    = 256,
	cek_len = 32,
	iv_len  = 12
}).

%%====================================================================
%% jose_jwe callbacks
%%====================================================================

from_map(F = #{ <<"enc">> := <<"A128CBC-HS256">> }) ->
	{?AES_128_CBC_HMAC_SHA_256, maps:remove(<<"enc">>, F)};
from_map(F = #{ <<"enc">> := <<"A192CBC-HS384">> }) ->
	{?AES_192_CBC_HMAC_SHA_384, maps:remove(<<"enc">>, F)};
from_map(F = #{ <<"enc">> := <<"A256CBC-HS512">> }) ->
	{?AES_256_CBC_HMAC_SHA_512, maps:remove(<<"enc">>, F)};
from_map(F = #{ <<"enc">> := <<"A128GCM">> }) ->
	{?AES_128_GCM, maps:remove(<<"enc">>, F)};
from_map(F = #{ <<"enc">> := <<"A192GCM">> }) ->
	{?AES_192_GCM, maps:remove(<<"enc">>, F)};
from_map(F = #{ <<"enc">> := <<"A256GCM">> }) ->
	{?AES_256_GCM, maps:remove(<<"enc">>, F)}.

to_map(?AES_128_CBC_HMAC_SHA_256, F) ->
	F#{ <<"enc">> => <<"A128CBC-HS256">> };
to_map(?AES_192_CBC_HMAC_SHA_384, F) ->
	F#{ <<"enc">> => <<"A192CBC-HS384">> };
to_map(?AES_256_CBC_HMAC_SHA_512, F) ->
	F#{ <<"enc">> => <<"A256CBC-HS512">> };
to_map(?AES_128_GCM, F) ->
	F#{ <<"enc">> => <<"A128GCM">> };
to_map(?AES_192_GCM, F) ->
	F#{ <<"enc">> => <<"A192GCM">> };
to_map(?AES_256_GCM, F) ->
	F#{ <<"enc">> => <<"A256GCM">> }.

%%====================================================================
%% jose_jwe_enc callbacks
%%====================================================================

algorithm(?AES_128_CBC_HMAC_SHA_256) -> <<"A128CBC-HS256">>;
algorithm(?AES_192_CBC_HMAC_SHA_384) -> <<"A192CBC-HS384">>;
algorithm(?AES_256_CBC_HMAC_SHA_512) -> <<"A256CBC-HS512">>;
algorithm(?AES_128_GCM)              -> <<"A128GCM">>;
algorithm(?AES_192_GCM)              -> <<"A192GCM">>;
algorithm(?AES_256_GCM)              -> <<"A256GCM">>.

bits(#jose_jwe_enc_aes{bits=Bits}) -> Bits.

block_decrypt({AAD, CipherText, CipherTag}, CEK, IV, #jose_jwe_enc_aes{
		cipher=Cipher,
		cek_len=CEKLen,
		hmac=undefined})
			when byte_size(CEK) =:= CEKLen
			andalso bit_size(IV) > 0 ->
	jose_jwa:block_decrypt(Cipher, CEK, IV, {AAD, CipherText, CipherTag});
block_decrypt({AAD, CipherText, CipherTag}, CEK, IV, #jose_jwe_enc_aes{
		cipher=Cipher,
		cek_len=CEKLen,
		iv_len=IVLen,
		enc_len=EncLen,
		mac_len=MacLen,
		tag_len=TagLen,
		hmac=HMAC})
			when byte_size(CEK) =:= CEKLen
			andalso byte_size(IV) =:= IVLen ->
	<< MacKey:MacLen/binary, EncKey:EncLen/binary >> = CEK,
	AADLength = << (bit_size(AAD)):1/unsigned-big-integer-unit:64 >>,
	MacData = << AAD/binary, IV/binary, CipherText/binary, AADLength/binary >>,
	case jose_crypto_compat:mac(hmac, HMAC, MacKey, MacData) of
		<< CipherTag:TagLen/binary, _/binary >> ->
			PlainText = jose_jwa_pkcs7:unpad(jose_jwa:block_decrypt(Cipher, EncKey, IV, CipherText)),
			PlainText;
		_ ->
			error
	end.

block_encrypt({AAD, PlainText}, CEK, IV, #jose_jwe_enc_aes{
		cipher=Cipher,
		cek_len=CEKLen,
		hmac=undefined})
			when byte_size(CEK) =:= CEKLen
			andalso bit_size(IV) > 0 ->
	jose_jwa:block_encrypt(Cipher, CEK, IV, {AAD, PlainText});
block_encrypt({AAD, PlainText}, CEK, IV, #jose_jwe_enc_aes{
		cipher=Cipher,
		cek_len=CEKLen,
		iv_len=IVLen,
		enc_len=EncLen,
		mac_len=MacLen,
		tag_len=TagLen,
		hmac=HMAC})
			when byte_size(CEK) =:= CEKLen
			andalso byte_size(IV) =:= IVLen ->
	<< MacKey:MacLen/binary, EncKey:EncLen/binary, _/binary >> = CEK,
	CipherText = jose_jwa:block_encrypt(Cipher, EncKey, IV, jose_jwa_pkcs7:pad(PlainText)),
	AADLength = << (bit_size(AAD)):1/unsigned-big-integer-unit:64 >>,
	MacData = << AAD/binary, IV/binary, CipherText/binary, AADLength/binary >>,
	<< CipherTag:TagLen/binary, _/binary >> = jose_crypto_compat:mac(hmac, HMAC, MacKey, MacData),
	{CipherText, CipherTag}.

next_cek(#jose_jwe_enc_aes{cek_len=CEKLen}) ->
	crypto:strong_rand_bytes(CEKLen).

next_iv(#jose_jwe_enc_aes{iv_len=IVLen}) ->
	crypto:strong_rand_bytes(IVLen).

%%====================================================================
%% API functions
%%====================================================================

cipher_supported() ->
	[aes_cbc, aes_gcm].

hmac_supported() ->
	[sha256, sha384, sha512].

%%%-------------------------------------------------------------------
%%% Internal functions
%%%-------------------------------------------------------------------
