%% -*- 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 PKCS #1: RSA Cryptography Specifications Version 2.1
%%% See RFC 3447: [https://tools.ietf.org/html/rfc3447]
%%% @end
%%% Created :  28 Jul 2015 by Andrew Bennett <potatosaladx@gmail.com>
%%%-------------------------------------------------------------------
-module(jose_jwa_pkcs1).

-include_lib("public_key/include/public_key.hrl").

%% Public Key API
-export([decrypt_private/3]).
-export([encrypt_public/3]).
-export([sign/4]).
-export([verify/5]).
%% API
-export([eme_oaep_decode/4]).
-export([eme_oaep_encode/5]).
-export([eme_pkcs1_decode/2]).
-export([eme_pkcs1_encode/2]).
-export([emsa_pkcs1_encode/4]).
-export([emsa_pss_encode/3]).
-export([emsa_pss_encode/4]).
-export([emsa_pss_verify/4]).
-export([emsa_pss_verify/5]).
-export([mgf1/3]).
-export([rsaes_oaep_decrypt/3]).
-export([rsaes_oaep_decrypt/4]).
-export([rsaes_oaep_encrypt/3]).
-export([rsaes_oaep_encrypt/4]).
-export([rsaes_oaep_encrypt/5]).
-export([rsaes_pkcs1_decrypt/2]).
-export([rsaes_pkcs1_encrypt/2]).
-export([rsassa_pkcs1_sign/3]).
-export([rsassa_pkcs1_sign/4]).
-export([rsassa_pkcs1_verify/4]).
-export([rsassa_pkcs1_verify/5]).
-export([rsassa_pss_sign/3]).
-export([rsassa_pss_sign/4]).
-export([rsassa_pss_verify/4]).
-export([rsassa_pss_verify/5]).

%% Types
-type rsa_digest_type() :: 'md5' | 'sha' | 'sha224' | 'sha256' | 'sha384' | 'sha512'.
-type rsa_hash_fun()    :: rsa_digest_type() | {hmac, rsa_digest_type(), iodata()} | fun((iodata()) -> binary()).
-type rsa_public_key()  :: #'RSAPublicKey'{}.
-type rsa_private_key() :: #'RSAPrivateKey'{}.

-define(PSS_TRAILER_FIELD, 16#BC).

%%====================================================================
%% Public Key API functions
%%====================================================================

decrypt_private(CipherText, RSAPrivateKey=#'RSAPrivateKey'{}, Options)
		when is_list(Options) ->
	case proplists:get_value(rsa_padding, Options) of
		rsa_pkcs1_oaep_padding ->
			Hash = proplists:get_value(rsa_oaep_md, Options, sha),
			Label = proplists:get_value(rsa_oaep_label, Options, <<>>),
			rsaes_oaep_decrypt(Hash, CipherText, Label, RSAPrivateKey);
		rsa_pkcs1_padding ->
			rsaes_pkcs1_decrypt(CipherText, RSAPrivateKey);
		_ ->
			erlang:error(notsup)
	end;
decrypt_private(CipherText, PrivateKey, Options) ->
	erlang:error(badarg, [CipherText, PrivateKey, Options]).

encrypt_public(PlainText, RSAPublicKey=#'RSAPublicKey'{}, Options)
		when is_list(Options) ->
	Res = case proplists:get_value(rsa_padding, Options) of
		rsa_pkcs1_oaep_padding ->
			Hash = proplists:get_value(rsa_oaep_md, Options, sha),
			Label = proplists:get_value(rsa_oaep_label, Options, <<>>),
			rsaes_oaep_encrypt(Hash, PlainText, Label, RSAPublicKey);
		rsa_pkcs1_padding ->
			rsaes_pkcs1_encrypt(PlainText, RSAPublicKey);
		_ ->
			erlang:error(notsup)
	end,
	case Res of
		{ok, Signature} ->
			Signature;
		{error, Reason} ->
			erlang:error(Reason)
	end;
encrypt_public(PlainText, PublicKey, Options) ->
	erlang:error(badarg, [PlainText, PublicKey, Options]).

sign(Message, DigestType, RSAPrivateKey=#'RSAPrivateKey'{}, Options)
		when is_list(Options) ->
	Res = case proplists:get_value(rsa_padding, Options) of
		rsa_pkcs1_pss_padding ->
			SaltLen = proplists:get_value(rsa_pss_saltlen, Options, -1),
			rsassa_pss_sign(DigestType, Message, SaltLen, RSAPrivateKey);
		rsa_pkcs1_padding ->
			rsassa_pkcs1_sign(DigestType, Message, RSAPrivateKey);
		_ ->
			erlang:error(notsup)
	end,
	case Res of
		{ok, Signature} ->
			Signature;
		{error, Reason} ->
			erlang:error(Reason)
	end;
sign(Message, DigestType, PrivateKey, Options) ->
	erlang:error(badarg, [Message, DigestType, PrivateKey, Options]).

verify(Message, DigestType, Signature, RSAPublicKey=#'RSAPublicKey'{}, Options)
		when is_list(Options) ->
	case proplists:get_value(rsa_padding, Options) of
		rsa_pkcs1_pss_padding ->
			SaltLen = proplists:get_value(rsa_pss_saltlen, Options, -1),
			rsassa_pss_verify(DigestType, Message, Signature, SaltLen, RSAPublicKey);
		rsa_pkcs1_padding ->
			rsassa_pkcs1_verify(DigestType, Message, Signature, RSAPublicKey);
		_ ->
			erlang:error(notsup)
	end;
verify(Message, DigestType, Signature, PublicKey, Options) ->
	erlang:error(badarg, [Message, DigestType, Signature, PublicKey, Options]).

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

%% See [https://tools.ietf.org/html/rfc3447#section-7.1.2]
-spec eme_oaep_decode(Hash, EM, Label, K) -> M | error
	when
		Hash  :: rsa_hash_fun(),
		EM    :: binary(),
		Label :: binary(),
		K     :: integer(),
		M     :: binary().
eme_oaep_decode(Hash, EM, Label, K)
		when is_function(Hash, 1)
		andalso is_binary(EM)
		andalso is_binary(Label)
		andalso is_integer(K) ->
	HLen = byte_size(Hash(<<>>)),
	LHash = Hash(Label),
	MaskedDBLen = K - HLen - 1,
	case EM of
		<< Y, MaskedSeed:HLen/binary, MaskedDB:MaskedDBLen/binary >> ->
			case mgf1(Hash, MaskedDB, HLen) of
				{ok, SeedMask} ->
					Seed = crypto:exor(MaskedSeed, SeedMask),
					case mgf1(Hash, Seed, K - HLen - 1) of
						{ok, DBMask} ->
							DB = crypto:exor(MaskedDB, DBMask),
							case DB of
								<< LHashPrime:HLen/binary, DBRight/binary >> ->
									case {Y, unpad_zero(DBRight), LHashPrime} of
										{16#00, << 16#01, M/binary >>, LHash} ->
											M;
										_BadPS ->
											error
									end;
								_BadDB ->
									error
							end;
						_DBMaskMGF1Error ->
							error
					end;
				_SeedMGF1Error ->
					error
			end;
		_BadEM ->
			error
	end;
eme_oaep_decode(Hash, EM, Label, K)
		when is_tuple(Hash)
		orelse is_atom(Hash) ->
	HashFun = resolve_hash(Hash),
	eme_oaep_decode(HashFun, EM, Label, K).

%% See [https://tools.ietf.org/html/rfc3447#section-7.1.1]
-spec eme_oaep_encode(Hash, DM, Label, Seed, K) -> {ok, EM} | {error, Reason}
	when
		Hash   :: rsa_hash_fun(),
		DM     :: binary(),
		Label  :: binary(),
		Seed   :: binary(),
		K      :: integer(),
		EM     :: binary(),
		Reason :: term().
eme_oaep_encode(Hash, DM, Label, Seed, K)
		when is_function(Hash, 1)
		andalso is_binary(DM)
		andalso is_binary(Label)
		andalso is_binary(Seed)
		andalso is_integer(K) ->
	HLen = byte_size(Hash(<<>>)),
	MLen = byte_size(DM),
	LHash = Hash(Label),
	PSLen = ((K - MLen - (2 * HLen) - 2) * 8),
	PS = case PSLen > 0 of
		true ->
			<< 0:PSLen >>;
		false ->
			<<>>
	end,
	DB = << LHash/binary, PS/binary, 16#01, DM/binary >>,
	case mgf1(Hash, Seed, K - HLen - 1) of
		{ok, DBMask} ->
			MaskedDB = crypto:exor(DB, DBMask),
			case mgf1(Hash, MaskedDB, HLen) of
				{ok, SeedMask} ->
					MaskedSeed = crypto:exor(Seed, SeedMask),
					EM = << 16#00, MaskedSeed/binary, MaskedDB/binary >>,
					{ok, EM};
				MGF1SeedError ->
					MGF1SeedError
			end;
		MGF1Error ->
			MGF1Error
	end;
eme_oaep_encode(Hash, DM, Label, Seed, K)
		when is_tuple(Hash)
		orelse is_atom(Hash) ->
	HashFun = resolve_hash(Hash),
	eme_oaep_encode(HashFun, DM, Label, Seed, K).

%% See [https://tools.ietf.org/html/rfc3447#section-7.2.2]
-spec eme_pkcs1_decode(EM, K) -> M | error
	when
		EM     :: binary(),
		K      :: integer(),
		M      :: binary().
eme_pkcs1_decode(<< 16#00, 16#02, Rest/binary >>, K)
		when is_integer(K) ->
	case binary:split(Rest, << 16#00 >>) of
		[PS, M] when byte_size(PS) >= 8 ->
			M;
		_ ->
			error
	end;
eme_pkcs1_decode(EM, K)
		when is_binary(EM)
		andalso is_integer(K) ->
	error.

%% See [https://tools.ietf.org/html/rfc3447#section-7.2.1]
-spec eme_pkcs1_encode(DM, K) -> {ok, EM} | {error, Reason}
	when
		DM     :: binary(),
		K      :: integer(),
		EM     :: binary(),
		Reason :: term().
eme_pkcs1_encode(DM, K)
		when is_binary(DM)
		andalso is_integer(K) ->
	MLen = byte_size(DM),
	PSLen = K - MLen - 3,
	PS = non_zero_strong_random_bytes(PSLen),
	EM = << 16#00, 16#02, PS/binary, 16#00, DM/binary >>,
	{ok, EM}.

%% See [https://tools.ietf.org/html/rfc3447#section-9.2]
-spec emsa_pkcs1_encode(Hash, Algorithm, Message, EMBits) -> {ok, EM} | {error, Reason}
	when
		Hash      :: rsa_hash_fun(),
		Algorithm :: md5 | sha | sha1 | sha256 | sha384 | sha512 | binary(),
		Message   :: binary(),
		EMBits    :: integer(),
		EM        :: binary(),
		Reason    :: term().
emsa_pkcs1_encode(Hash, Algorithm, Message, EMBits)
		when is_function(Hash, 1)
		andalso is_binary(Algorithm)
		andalso is_binary(Message)
		andalso is_integer(EMBits) ->
	H = Hash(Message),
	T = << Algorithm/binary, H/binary >>,
	TLen = byte_size(T),
	EMLen = ceiling(EMBits / 8),
	case EMLen < (TLen + 11) of
		false ->
			PSLen = EMLen - TLen - 3,
			PS = binary:copy(<< 16#FF >>, PSLen),
			EM = << 16#00, 16#01, PS/binary, 16#00, T/binary >>,
			{ok, EM};
		true ->
			{error, modulus_too_short}
	end;
emsa_pkcs1_encode(Hash, md5, Message, EMBits) ->
	Algorithm = <<
		16#30, 16#20, 16#30, 16#0c, 16#06, 16#08, 16#2a, 16#86,
		16#48, 16#86, 16#f7, 16#0d, 16#02, 16#05, 16#05, 16#00,
		16#04
	>>,
	emsa_pkcs1_encode(Hash, Algorithm, Message, EMBits);
emsa_pkcs1_encode(Hash, sha, Message, EMBits) ->
	emsa_pkcs1_encode(Hash, sha1, Message, EMBits);
emsa_pkcs1_encode(Hash, sha1, Message, EMBits) ->
	Algorithm = <<
		16#30, 16#21, 16#30, 16#09, 16#06, 16#05, 16#2b, 16#0e,
		16#03, 16#02, 16#1a, 16#05, 16#00, 16#04, 16#14
	>>,
	emsa_pkcs1_encode(Hash, Algorithm, Message, EMBits);
emsa_pkcs1_encode(Hash, sha256, Message, EMBits) ->
	Algorithm = <<
		16#30, 16#31, 16#30, 16#0d, 16#06, 16#09, 16#60, 16#86,
		16#48, 16#01, 16#65, 16#03, 16#04, 16#02, 16#01, 16#05,
		16#00, 16#04, 16#20
	>>,
	emsa_pkcs1_encode(Hash, Algorithm, Message, EMBits);
emsa_pkcs1_encode(Hash, sha384, Message, EMBits) ->
	Algorithm = <<
		16#30, 16#41, 16#30, 16#0d, 16#06, 16#09, 16#60, 16#86,
		16#48, 16#01, 16#65, 16#03, 16#04, 16#02, 16#02, 16#05,
		16#00, 16#04, 16#30
	>>,
	emsa_pkcs1_encode(Hash, Algorithm, Message, EMBits);
emsa_pkcs1_encode(Hash, sha512, Message, EMBits) ->
	Algorithm = <<
		16#30, 16#51, 16#30, 16#0d, 16#06, 16#09, 16#60, 16#86,
		16#48, 16#01, 16#65, 16#03, 16#04, 16#02, 16#03, 16#05,
		16#00, 16#04, 16#40
	>>,
	emsa_pkcs1_encode(Hash, Algorithm, Message, EMBits);
emsa_pkcs1_encode(Hash, Algorithm, Message, EMBits)
		when is_atom(Hash) ->
	HashFun = resolve_hash(Hash),
	emsa_pkcs1_encode(HashFun, Algorithm, Message, EMBits).

%% See [https://tools.ietf.org/html/rfc3447#section-9.1.1]
-spec emsa_pss_encode(Hash, Message, EMBits) -> {ok, EM} | {error, Reason}
	when
		Hash    :: rsa_hash_fun(),
		Message :: binary(),
		EMBits  :: integer(),
		EM      :: binary(),
		Reason  :: term().
emsa_pss_encode(Hash, Message, EMBits)
		when is_function(Hash, 1)
		andalso is_binary(Message)
		andalso is_integer(EMBits) ->
	emsa_pss_encode(Hash, Message, -2, EMBits);
emsa_pss_encode(Hash, Message, EMBits)
		when is_tuple(Hash)
		orelse is_atom(Hash) ->
	HashFun = resolve_hash(Hash),
	emsa_pss_encode(HashFun, Message, EMBits).

%% See [https://tools.ietf.org/html/rfc3447#section-9.1.1]
-spec emsa_pss_encode(Hash, Message, Salt, EMBits) -> {ok, EM} | {error, Reason}
	when
		Hash    :: rsa_hash_fun(),
		Message :: binary(),
		Salt    :: binary() | integer(),
		EMBits  :: integer(),
		EM      :: binary(),
		Reason  :: term().
emsa_pss_encode(Hash, Message, Salt, EMBits)
		when is_function(Hash, 1)
		andalso is_binary(Message)
		andalso is_binary(Salt)
		andalso is_integer(EMBits) ->
	MHash = Hash(Message),
	HashLen = byte_size(MHash),
	SaltLen = byte_size(Salt),
	EMLen = ceiling(EMBits / 8),
	case EMLen < (HashLen + SaltLen + 2) of
		false ->
			MPrime = << 0:64, MHash/binary, Salt/binary >>,
			H = Hash(MPrime),
			PS = << 0:((EMLen - SaltLen - HashLen - 2) * 8) >>,
			DB = << PS/binary, 16#01, Salt/binary >>,
			case mgf1(Hash, H, EMLen - HashLen - 1) of
				{ok, DBMask} ->
					LeftBits = (EMLen * 8) - EMBits,
					<< _:LeftBits/bitstring, MaskedDBRight/bitstring >> = crypto:exor(DB, DBMask),
					MaskedDB = << 0:LeftBits, MaskedDBRight/bitstring >>,
					EM = << MaskedDB/binary, H/binary, ?PSS_TRAILER_FIELD >>,
					{ok, EM};
				MGF1Error ->
					MGF1Error
			end;
		true ->
			{error, encoding_error}
	end;
emsa_pss_encode(Hash, Message, -2, EMBits)
		when is_function(Hash, 1)
		andalso is_integer(EMBits) ->
	HashLen = byte_size(Hash(<<>>)),
	EMLen = ceiling(EMBits / 8),
	SaltLen = EMLen - HashLen - 2,
	case SaltLen < 0 of
		false ->
			emsa_pss_encode(Hash, Message, SaltLen, EMBits);
		true ->
			{error, encoding_error}
	end;
emsa_pss_encode(Hash, Message, -1, EMBits)
		when is_function(Hash, 1) ->
	HashLen = byte_size(Hash(<<>>)),
	SaltLen = HashLen,
	emsa_pss_encode(Hash, Message, SaltLen, EMBits);
emsa_pss_encode(Hash, Message, SaltLen, EMBits)
		when is_integer(SaltLen)
		andalso SaltLen >= 0 ->
	Salt = crypto:strong_rand_bytes(SaltLen),
	emsa_pss_encode(Hash, Message, Salt, EMBits);
emsa_pss_encode(Hash, Message, Salt, EMBits)
		when is_tuple(Hash)
		orelse is_atom(Hash) ->
	HashFun = resolve_hash(Hash),
	emsa_pss_encode(HashFun, Message, Salt, EMBits).

%% See [https://tools.ietf.org/html/rfc3447#section-9.1.2]
-spec emsa_pss_verify(Hash, Message, EM, EMBits) -> boolean()
	when
		Hash    :: rsa_hash_fun(),
		Message :: binary(),
		EM      :: binary(),
		EMBits  :: integer().
emsa_pss_verify(Hash, Message, EM, EMBits)
		when is_function(Hash, 1)
		andalso is_binary(Message)
		andalso is_binary(EM)
		andalso is_integer(EMBits) ->
	emsa_pss_verify(Hash, Message, EM, -2, EMBits);
emsa_pss_verify(Hash, Message, EM, EMBits)
		when is_tuple(Hash)
		orelse is_atom(Hash) ->
	HashFun = resolve_hash(Hash),
	emsa_pss_verify(HashFun, Message, EM, EMBits).

%% See [https://tools.ietf.org/html/rfc3447#section-9.1.2]
-spec emsa_pss_verify(Hash, Message, EM, SaltLen, EMBits) -> boolean()
	when
		Hash    :: rsa_hash_fun(),
		Message :: binary(),
		EM      :: binary(),
		SaltLen :: integer(),
		EMBits  :: integer().
emsa_pss_verify(Hash, Message, EM, SaltLen, EMBits)
		when is_function(Hash, 1)
		andalso is_binary(Message)
		andalso is_integer(SaltLen)
		andalso SaltLen >= 0
		andalso is_integer(EMBits) ->
	MHash = Hash(Message),
	HashLen = byte_size(MHash),
	EMLen = ceiling(EMBits / 8),
	MaskedDBLen = (EMLen - HashLen - 1),
	case {EMLen < (HashLen + SaltLen + 2), byte_size(EM), EM} of
		{false, EMLen, << MaskedDB:MaskedDBLen/binary, H:HashLen/binary, ?PSS_TRAILER_FIELD >>} ->
			LeftBits = ((EMLen * 8) - EMBits),
			case MaskedDB of
				<< 0:LeftBits, _/bitstring >> ->
					case mgf1(Hash, H, EMLen - HashLen - 1) of
						{ok, DBMask} ->
							<< _:LeftBits/bitstring, DBRight/bitstring >> = crypto:exor(MaskedDB, DBMask),
							DB = << 0:LeftBits, DBRight/bitstring >>,
							PSLen = ((EMLen - HashLen - SaltLen - 2) * 8),
							case DB of
								<< 0:PSLen, 16#01, Salt:SaltLen/binary >> ->
									MPrime = << 0:64, MHash/binary, Salt/binary >>,
									HPrime = Hash(MPrime),
									H =:= HPrime;
								_BadDB ->
									false
							end;
						_MGF1Error ->
							false
					end;
				_BadMaskedDB ->
					false
			end;
		_BadEMLen ->
			false
	end;
emsa_pss_verify(Hash, Message, EM, -2, EMBits)
		when is_function(Hash, 1)
		andalso is_integer(EMBits) ->
	HashLen = byte_size(Hash(<<>>)),
	EMLen = ceiling(EMBits / 8),
	SaltLen = EMLen - HashLen - 2,
	case SaltLen < 0 of
		false ->
			emsa_pss_verify(Hash, Message, EM, SaltLen, EMBits);
		true ->
			false
	end;
emsa_pss_verify(Hash, Message, EM, -1, EMBits)
		when is_function(Hash, 1) ->
	HashLen = byte_size(Hash(<<>>)),
	SaltLen = HashLen,
	emsa_pss_verify(Hash, Message, EM, SaltLen, EMBits).

%% See [https://tools.ietf.org/html/rfc3447#appendix-B.2]
-spec mgf1(Hash, Seed, MaskLen) -> {ok, binary()} | {error, mask_too_long}
	when
		Hash    :: rsa_hash_fun(),
		Seed    :: binary(),
		MaskLen :: pos_integer().
mgf1(Hash, Seed, MaskLen)
		when is_function(Hash, 1)
		andalso is_binary(Seed)
		andalso is_integer(MaskLen)
		andalso MaskLen >= 0 ->
	HashLen = byte_size(Hash(<<>>)),
	case MaskLen > (16#FFFFFFFF * HashLen) of
		false ->
			Reps = ceiling(MaskLen / HashLen),
			{ok, derive_mgf1(Hash, 0, Reps, Seed, MaskLen, <<>>)};
		true ->
			{error, mask_too_long}
	end;
mgf1(Hash, Seed, MaskLen)
		when is_tuple(Hash)
		orelse is_atom(Hash) ->
	HashFun = resolve_hash(Hash),
	mgf1(HashFun, Seed, MaskLen).

%% See [https://tools.ietf.org/html/rfc3447#section-7.1.2]
-spec rsaes_oaep_decrypt(Hash, CipherText, RSAPrivateKey) -> PlainText
	when
		Hash          :: rsa_hash_fun(),
		CipherText    :: binary(),
		RSAPrivateKey :: rsa_private_key(),
		PlainText     :: binary().
rsaes_oaep_decrypt(Hash, CipherText, RSAPrivateKey=#'RSAPrivateKey'{})
		when is_function(Hash, 1)
		andalso is_binary(CipherText) ->
	rsaes_oaep_decrypt(Hash, CipherText, <<>>, RSAPrivateKey);
rsaes_oaep_decrypt(Hash, CipherText, RSAPrivateKey)
		when is_tuple(Hash)
		orelse is_atom(Hash) ->
	HashFun = resolve_hash(Hash),
	rsaes_oaep_decrypt(HashFun, CipherText, RSAPrivateKey).

%% See [https://tools.ietf.org/html/rfc3447#section-7.1.2]
-spec rsaes_oaep_decrypt(Hash, CipherText, Label, RSAPrivateKey) -> PlainText
	when
		Hash          :: rsa_hash_fun(),
		CipherText    :: binary(),
		Label         :: binary(),
		RSAPrivateKey :: rsa_private_key(),
		PlainText     :: binary().
rsaes_oaep_decrypt(Hash, CipherText, Label, RSAPrivateKey=#'RSAPrivateKey'{modulus=N})
		when is_function(Hash, 1)
		andalso is_binary(CipherText)
		andalso is_binary(Label) ->
	HLen = byte_size(Hash(<<>>)),
	K = int_to_byte_size(N),
	case {byte_size(CipherText), K < ((2 * HLen) + 2)} of
		{K, false} ->
			EM = pad_to_key_size(K, dp(CipherText, RSAPrivateKey)),
			eme_oaep_decode(Hash, EM, Label, K);
		_BadSize ->
			{error, {badsize, _BadSize}}
	end;
rsaes_oaep_decrypt(Hash, CipherText, Label, RSAPrivateKey)
		when is_tuple(Hash)
		orelse is_atom(Hash) ->
	HashFun = resolve_hash(Hash),
	rsaes_oaep_decrypt(HashFun, CipherText, Label, RSAPrivateKey).

%% See [https://tools.ietf.org/html/rfc3447#section-7.1.1]
-spec rsaes_oaep_encrypt(Hash, PlainText, RSAPublicKey) -> CipherText
	when
		Hash         :: rsa_hash_fun(),
		PlainText    :: binary(),
		RSAPublicKey :: rsa_public_key(),
		CipherText   :: binary().
rsaes_oaep_encrypt(Hash, PlainText, RSAPublicKey=#'RSAPublicKey'{})
		when is_function(Hash, 1)
		andalso is_binary(PlainText) ->
	rsaes_oaep_encrypt(Hash, PlainText, <<>>, RSAPublicKey);
rsaes_oaep_encrypt(Hash, PlainText, RSAPublicKey)
		when is_tuple(Hash)
		orelse is_atom(Hash) ->
	HashFun = resolve_hash(Hash),
	rsaes_oaep_encrypt(HashFun, PlainText, RSAPublicKey).

%% See [https://tools.ietf.org/html/rfc3447#section-7.1.1]
-spec rsaes_oaep_encrypt(Hash, PlainText, Label, RSAPublicKey) -> CipherText
	when
		Hash         :: rsa_hash_fun(),
		PlainText    :: binary(),
		Label        :: binary(),
		RSAPublicKey :: rsa_public_key(),
		CipherText   :: binary().
rsaes_oaep_encrypt(Hash, PlainText, Label, RSAPublicKey=#'RSAPublicKey'{})
		when is_function(Hash, 1)
		andalso is_binary(PlainText)
		andalso is_binary(Label) ->
	HLen = byte_size(Hash(<<>>)),
	Seed = crypto:strong_rand_bytes(HLen),
	rsaes_oaep_encrypt(Hash, PlainText, Label, Seed, RSAPublicKey);
rsaes_oaep_encrypt(Hash, PlainText, Label, RSAPublicKey)
		when is_tuple(Hash)
		orelse is_atom(Hash) ->
	HashFun = resolve_hash(Hash),
	rsaes_oaep_encrypt(HashFun, PlainText, Label, RSAPublicKey).

%% See [https://tools.ietf.org/html/rfc3447#section-7.1.1]
-spec rsaes_oaep_encrypt(Hash, PlainText, Label, Seed, RSAPublicKey) -> CipherText
	when
		Hash         :: rsa_hash_fun(),
		PlainText    :: binary(),
		Label        :: binary(),
		Seed         :: binary(),
		RSAPublicKey :: rsa_public_key(),
		CipherText   :: binary().
rsaes_oaep_encrypt(Hash, PlainText, Label, Seed, RSAPublicKey=#'RSAPublicKey'{modulus=N})
		when is_function(Hash, 1)
		andalso is_binary(PlainText)
		andalso is_binary(Label)
		andalso is_binary(Seed) ->
	HLen = byte_size(Hash(<<>>)),
	MLen = byte_size(PlainText),
	K = int_to_byte_size(N),
	case MLen > (K - (2 * HLen) - 2) of
		false ->
			case eme_oaep_encode(Hash, PlainText, Label, Seed, K) of
				{ok, EM} ->
					C = pad_to_key_size(K, ep(EM, RSAPublicKey)),
					{ok, C};
				EncodingError ->
					EncodingError
			end;
		true ->
			{error, message_too_long}
	end;
rsaes_oaep_encrypt(Hash, PlainText, Label, Seed, RSAPublicKey)
		when is_tuple(Hash)
		orelse is_atom(Hash) ->
	HashFun = resolve_hash(Hash),
	rsaes_oaep_encrypt(HashFun, PlainText, Label, Seed, RSAPublicKey).

%% See [https://tools.ietf.org/html/rfc3447#section-7.2.2]
-spec rsaes_pkcs1_decrypt(CipherText, RSAPrivateKey) -> PlainText
	when
		CipherText    :: binary(),
		RSAPrivateKey :: rsa_private_key(),
		PlainText     :: binary().
rsaes_pkcs1_decrypt(CipherText, RSAPrivateKey=#'RSAPrivateKey'{modulus=N})
		when is_binary(CipherText) ->
	K = int_to_byte_size(N),
	case {byte_size(CipherText), K < 11} of
		{K, false} ->
			EM = pad_to_key_size(K, dp(CipherText, RSAPrivateKey)),
			eme_pkcs1_decode(EM, K);
		_BadSize ->
			{error, {badsize, _BadSize}}
	end.

%% See [https://tools.ietf.org/html/rfc3447#section-7.2.1]
-spec rsaes_pkcs1_encrypt(PlainText, RSAPublicKey) -> CipherText
	when
		PlainText    :: binary(),
		RSAPublicKey :: rsa_public_key(),
		CipherText   :: binary().
rsaes_pkcs1_encrypt(PlainText, RSAPublicKey=#'RSAPublicKey'{modulus=N})
		when is_binary(PlainText) ->
	MLen = byte_size(PlainText),
	K = int_to_byte_size(N),
	case MLen > (K - 11) of
		false ->
			case eme_pkcs1_encode(PlainText, K) of
				{ok, EM} ->
					C = pad_to_key_size(K, ep(EM, RSAPublicKey)),
					{ok, C};
				EncodingError ->
					EncodingError
			end;
		true ->
			{error, message_too_long}
	end.

%% See [https://tools.ietf.org/html/rfc3447#section-8.1.1]
-spec rsassa_pkcs1_sign(Hash, Message, RSAPrivateKey) -> {ok, Signature} | {error, Reason}
	when
		Hash          :: rsa_hash_fun(),
		Message       :: binary(),
		RSAPrivateKey :: rsa_private_key(),
		Signature     :: binary(),
		Reason        :: term().
rsassa_pkcs1_sign(Hash, Message, RSAPrivateKey)
		when is_atom(Hash) ->
	rsassa_pkcs1_sign(Hash, Hash, Message, RSAPrivateKey).

%% See [https://tools.ietf.org/html/rfc3447#section-8.1.1]
-spec rsassa_pkcs1_sign(Hash, Algorithm, Message, RSAPrivateKey) -> {ok, Signature} | {error, Reason}
	when
		Hash          :: rsa_hash_fun(),
		Algorithm     :: md5 | sha | sha1 | sha256 | sha384 | sha512 | binary(),
		Message       :: binary(),
		RSAPrivateKey :: rsa_private_key(),
		Signature     :: binary(),
		Reason        :: term().
rsassa_pkcs1_sign(Hash, Algorithm, Message, RSAPrivateKey=#'RSAPrivateKey'{modulus=Modulus})
		when is_function(Hash, 1)
		andalso (is_atom(Algorithm) orelse is_binary(Algorithm))
		andalso is_binary(Message) ->
	ModBits = int_to_bit_size(Modulus),
	case emsa_pkcs1_encode(Hash, Algorithm, Message, ModBits - 1) of
		{ok, EM} ->
			ModBytes = int_to_byte_size(Modulus),
			S = pad_to_key_size(ModBytes, dp(EM, RSAPrivateKey)),
			{ok, S};
		EncodingError ->
			EncodingError
	end;
rsassa_pkcs1_sign(Hash, Algorithm, Message, RSAPrivateKey=#'RSAPrivateKey'{})
		when is_atom(Hash) ->
	HashFun = resolve_hash(Hash),
	rsassa_pkcs1_sign(HashFun, Algorithm, Message, RSAPrivateKey).

%% See [https://tools.ietf.org/html/rfc3447#section-8.2.2]
-spec rsassa_pkcs1_verify(Hash, Message, Signature, RSAPublicKey) -> boolean()
	when
		Hash         :: rsa_hash_fun(),
		Message      :: binary(),
		Signature    :: binary(),
		RSAPublicKey :: rsa_public_key().
rsassa_pkcs1_verify(Hash, Message, Signature, RSAPublicKey)
		when is_atom(Hash) ->
	rsassa_pkcs1_verify(Hash, Hash, Message, Signature, RSAPublicKey).

%% See [https://tools.ietf.org/html/rfc3447#section-8.2.2]
-spec rsassa_pkcs1_verify(Hash, Algorithm, Message, Signature, RSAPublicKey) -> boolean()
	when
		Hash         :: rsa_hash_fun(),
		Algorithm    :: md5 | sha | sha1 | sha256 | sha384 | sha512 | binary(),
		Message      :: binary(),
		Signature    :: binary(),
		RSAPublicKey :: rsa_public_key().
rsassa_pkcs1_verify(Hash, Algorithm, Message, Signature, RSAPublicKey=#'RSAPublicKey'{modulus=Modulus})
		when is_function(Hash, 1)
		andalso is_binary(Message)
		andalso is_binary(Signature) ->
	ModBytes = int_to_byte_size(Modulus),
	case byte_size(Signature) =:= ModBytes of
		true ->
			ModBits = int_to_bit_size(Modulus),
			EM = pad_to_key_size(ceiling((ModBits - 1) / 8), ep(Signature, RSAPublicKey)),
			case emsa_pkcs1_encode(Hash, Algorithm, Message, ModBits - 1) of
				{ok, EMPrime} ->
					jose_jwa:constant_time_compare(EM, EMPrime);
				_ ->
					false
			end;
		false ->
			false
	end;
rsassa_pkcs1_verify(Hash, Algorithm, Message, Signature, RSAPublicKey=#'RSAPublicKey'{})
		when is_atom(Hash) ->
	HashFun = resolve_hash(Hash),
	rsassa_pkcs1_verify(HashFun, Algorithm, Message, Signature, RSAPublicKey).

%% See [https://tools.ietf.org/html/rfc3447#section-8.1.1]
-spec rsassa_pss_sign(Hash, Message, RSAPrivateKey) -> {ok, Signature} | {error, Reason}
	when
		Hash          :: rsa_hash_fun(),
		Message       :: binary(),
		RSAPrivateKey :: rsa_private_key(),
		Signature     :: binary(),
		Reason        :: term().
rsassa_pss_sign(Hash, Message, RSAPrivateKey=#'RSAPrivateKey'{modulus=Modulus})
		when is_function(Hash, 1)
		andalso is_binary(Message) ->
	ModBits = int_to_bit_size(Modulus),
	case emsa_pss_encode(Hash, Message, ModBits - 1) of
		{ok, EM} ->
			ModBytes = int_to_byte_size(Modulus),
			S = pad_to_key_size(ModBytes, dp(EM, RSAPrivateKey)),
			{ok, S};
		EncodingError ->
			EncodingError
	end;
rsassa_pss_sign(Hash, Message, RSAPrivateKey=#'RSAPrivateKey'{})
		when is_tuple(Hash)
		orelse is_atom(Hash) ->
	HashFun = resolve_hash(Hash),
	rsassa_pss_sign(HashFun, Message, RSAPrivateKey).

%% See [https://tools.ietf.org/html/rfc3447#section-8.1.1]
-spec rsassa_pss_sign(Hash, Message, Salt, RSAPrivateKey) -> {ok, Signature} | {error, Reason}
	when
		Hash          :: rsa_hash_fun(),
		Message       :: binary(),
		Salt          :: binary() | integer(),
		RSAPrivateKey :: rsa_private_key(),
		Signature     :: binary(),
		Reason        :: term().
rsassa_pss_sign(Hash, Message, Salt, RSAPrivateKey=#'RSAPrivateKey'{modulus=Modulus})
		when is_function(Hash, 1)
		andalso is_binary(Message)
		andalso (is_binary(Salt) orelse is_integer(Salt)) ->
	ModBits = int_to_bit_size(Modulus),
	case emsa_pss_encode(Hash, Message, Salt, ModBits - 1) of
		{ok, EM} ->
			ModBytes = int_to_byte_size(Modulus),
			S = pad_to_key_size(ModBytes, dp(EM, RSAPrivateKey)),
			{ok, S};
		EncodingError ->
			EncodingError
	end;
rsassa_pss_sign(Hash, Message, Salt, RSAPrivateKey=#'RSAPrivateKey'{})
		when is_tuple(Hash)
		orelse is_atom(Hash) ->
	HashFun = resolve_hash(Hash),
	rsassa_pss_sign(HashFun, Message, Salt, RSAPrivateKey).

%% See [https://tools.ietf.org/html/rfc3447#section-8.1.2]
-spec rsassa_pss_verify(Hash, Message, Signature, RSAPublicKey) -> boolean()
	when
		Hash         :: rsa_hash_fun(),
		Message      :: binary(),
		Signature    :: binary(),
		RSAPublicKey :: rsa_public_key().
rsassa_pss_verify(Hash, Message, Signature, RSAPublicKey=#'RSAPublicKey'{modulus=Modulus})
		when is_function(Hash, 1)
		andalso is_binary(Message)
		andalso is_binary(Signature) ->
	ModBytes = int_to_byte_size(Modulus),
	case byte_size(Signature) =:= ModBytes of
		true ->
			ModBits = int_to_bit_size(Modulus),
			EM = pad_to_key_size(ceiling((ModBits - 1) / 8), ep(Signature, RSAPublicKey)),
			emsa_pss_verify(Hash, Message, EM, ModBits - 1);
		false ->
			false
	end;
rsassa_pss_verify(Hash, Message, Signature, RSAPublicKey=#'RSAPublicKey'{})
		when is_tuple(Hash)
		orelse is_atom(Hash) ->
	HashFun = resolve_hash(Hash),
	rsassa_pss_verify(HashFun, Message, Signature, RSAPublicKey).

%% See [https://tools.ietf.org/html/rfc3447#section-8.1.2]
-spec rsassa_pss_verify(Hash, Message, Signature, SaltLen, RSAPublicKey) -> boolean()
	when
		Hash         :: rsa_hash_fun(),
		Message      :: binary(),
		Signature    :: binary(),
		SaltLen      :: integer(),
		RSAPublicKey :: rsa_public_key().
rsassa_pss_verify(Hash, Message, Signature, SaltLen, RSAPublicKey=#'RSAPublicKey'{modulus=Modulus})
		when is_function(Hash, 1)
		andalso is_binary(Message)
		andalso is_binary(Signature)
		andalso is_integer(SaltLen) ->
	ModBytes = int_to_byte_size(Modulus),
	case byte_size(Signature) =:= ModBytes of
		true ->
			ModBits = int_to_bit_size(Modulus),
			EM = pad_to_key_size(ceiling((ModBits - 1) / 8), ep(Signature, RSAPublicKey)),
			emsa_pss_verify(Hash, Message, EM, SaltLen, ModBits - 1);
		false ->
			false
	end;
rsassa_pss_verify(Hash, Message, Signature, SaltLen, RSAPublicKey=#'RSAPublicKey'{})
		when is_tuple(Hash)
		orelse is_atom(Hash) ->
	HashFun = resolve_hash(Hash),
	rsassa_pss_verify(HashFun, Message, Signature, SaltLen, RSAPublicKey).

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

%% @private
ceiling(X) when X < 0 ->
	trunc(X);
ceiling(X) ->
	T = trunc(X),
	case X - T == 0 of
		false ->
			T + 1;
		true ->
			T
	end.

%% @private
derive_mgf1(_Hash, Reps, Reps, _Seed, MaskLen, T) ->
	binary:part(T, 0, MaskLen);
derive_mgf1(Hash, Counter, Reps, Seed, MaskLen, T) ->
	CounterBin = << Counter:8/unsigned-big-integer-unit:4 >>,
	NewT = << T/binary, (Hash(<< Seed/binary, CounterBin/binary >>))/binary >>,
	derive_mgf1(Hash, Counter + 1, Reps, Seed, MaskLen, NewT).

%% @private
dp(B, #'RSAPrivateKey'{modulus=N, privateExponent=E}) ->
	crypto:mod_pow(B, E, N).

%% @private
ep(B, #'RSAPublicKey'{modulus=N, publicExponent=E}) ->
	crypto:mod_pow(B, E, N).

%% @private
int_to_bit_size(I) ->
	int_to_bit_size(I, 0).

%% @private
int_to_bit_size(0, B) ->
	B;
int_to_bit_size(I, B) ->
	int_to_bit_size(I bsr 1, B + 1).

%% @private
int_to_byte_size(I) ->
	int_to_byte_size(I, 0).

%% @private
int_to_byte_size(0, B) ->
	B;
int_to_byte_size(I, B) ->
	int_to_byte_size(I bsr 8, B + 1).

%% @private
non_zero_strong_random_byte() ->
	case crypto:strong_rand_bytes(1) of
		<< 0 >> ->
			non_zero_strong_random_byte();
		Byte ->
			Byte
	end.

%% @private
non_zero_strong_random_bytes(N) ->
	<<
		<< (case C of
			0 ->
				<< (non_zero_strong_random_byte())/binary >>;
			_ ->
				<< C >>
		end)/binary >> || << C >> <= crypto:strong_rand_bytes(N)
	>>.

%% @private
pad_to_key_size(Bytes, Data) when byte_size(Data) < Bytes ->
	pad_to_key_size(Bytes, << 0, Data/binary >>);
pad_to_key_size(_Bytes, Data) ->
	Data.

%% @private
resolve_hash(HashFun) when is_function(HashFun, 1) ->
	HashFun;
resolve_hash(DigestType) when is_atom(DigestType) ->
	fun(Data) ->
		crypto:hash(DigestType, Data)
	end;
resolve_hash({hmac, DigestType, Key}) when is_atom(DigestType) ->
	fun(Data) ->
		jose_crypto_compat:mac(hmac, DigestType, Key, Data)
	end.

%% @private
unpad_zero(<< 0, Rest/binary >>) ->
	unpad_zero(Rest);
unpad_zero(Rest) ->
	Rest.
