Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/credentials_obfuscation.erl
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
-export([enabled/0, cipher/0, hash/0, iterations/0, secret/0]).

%% API
-export([set_secret/1, encrypt/1, decrypt/1, refresh_config/0]).
-export([set_secret/1, set_fallback_secret/1, encrypt/1, decrypt/1, refresh_config/0]).

-spec enabled() -> boolean().
enabled() ->
Expand All @@ -37,6 +37,11 @@ secret() ->
set_secret(Secret) when is_binary(Secret) ->
ok = credentials_obfuscation_svc:set_secret(Secret).


-spec set_fallback_secret(binary()) -> ok.
set_fallback_secret(Secret) when is_binary(Secret) ->
ok = credentials_obfuscation_svc:set_fallback_secret(Secret).

-spec encrypt(term()) -> {plaintext, term()} | {encrypted, binary()}.
encrypt(none) -> none;
encrypt(undefined) -> undefined;
Expand Down
61 changes: 53 additions & 8 deletions src/credentials_obfuscation_svc.erl
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
get_config/1,
refresh_config/0,
set_secret/1,
set_fallback_secret/1,
encrypt/1,
decrypt/1]).

Expand All @@ -31,9 +32,11 @@
cipher :: atom(),
hash :: atom(),
iterations :: non_neg_integer(),
secret :: binary() | '$pending-secret'}).
secret :: binary() | '$pending-secret',
fallback_secret :: binary() | undefined}).

-define(TIMEOUT, 30000).
-define(VALUE_TAG, credentials_obfuscation).

%%%===================================================================
%%% API functions
Expand All @@ -54,8 +57,13 @@ refresh_config() ->
set_secret(Secret) when is_binary(Secret) ->
gen_server:call(?MODULE, {set_secret, Secret}).

-spec set_fallback_secret(binary()) -> ok.
set_fallback_secret(Secret) when is_binary(Secret) ->
gen_server:call(?MODULE, {set_fallback_secret, Secret}).


-spec encrypt(term()) -> {plaintext, term()} | {encrypted, binary()}.
encrypt(Term) ->
encrypt(Term) when is_binary(Term); is_list(Term) ->
try
gen_server:call(?MODULE, {encrypt, Term}, ?TIMEOUT)
catch exit:{timeout, _} ->
Expand Down Expand Up @@ -99,19 +107,42 @@ handle_call({encrypt, Term}, _From, #state{enabled=false}=State) ->
handle_call({encrypt, Term}, _From, #state{cipher=Cipher,
hash=Hash,
iterations=Iterations,
secret=Secret}=State) ->
Encrypted = credentials_obfuscation_pbe:encrypt(Cipher, Hash, Iterations, Secret, Term),
{reply, Encrypted, State};
secret=Secret} = State) ->
% We need to wrap the data in a tuple to be able to say if the decryption was
% successful or not. We may just receive junk data if the secret is incorrect
% upon decryption.
ClearText = {?VALUE_TAG, to_binary(Term)},
Encrypted = credentials_obfuscation_pbe:encrypt_term(Cipher, Hash, Iterations, Secret, ClearText),
case Encrypted of
{plaintext, _} ->
{reply, {plaintext, Term}, State};
_ -> {reply, Encrypted, State}
end;
handle_call({decrypt, Term}, _From, #state{enabled=false}=State) ->
{reply, Term, State};
handle_call({decrypt, {plaintext, Term}}, _From, State) ->
{reply, Term, State};
handle_call({decrypt, Term}, _From, #state{cipher=Cipher,
hash=Hash,
iterations=Iterations,
secret=Secret}=State) ->
Decrypted = credentials_obfuscation_pbe:decrypt(Cipher, Hash, Iterations, Secret, Term),
{reply, Decrypted, State};
secret=Secret,
fallback_secret=FallbackSecret}=State) ->
case try_decrypt(Cipher, Hash, Iterations, Secret, Term) of
{ok, Decrypted} ->
{reply, Decrypted, State};
{error, _E} ->
case try_decrypt(Cipher, Hash, Iterations, FallbackSecret, Term) of
{ok, Decrypted2} ->
{reply, Decrypted2, State};
_E2 ->
{reply, Term, State}
end
end;
handle_call({set_secret, Secret}, _From, State0) ->
State1 = State0#state{secret = Secret},
{reply, ok, State1};
handle_call({set_fallback_secret, Secret}, _From, State0) ->
State1 = State0#state{fallback_secret = Secret},
{reply, ok, State1}.

handle_cast(_Message, State) ->
Expand Down Expand Up @@ -163,3 +194,17 @@ check(Cipher, Hash, Iterations) ->
E = credentials_obfuscation_pbe:encrypt(Cipher, Hash, Iterations, TempSecret, Value),
Value = credentials_obfuscation_pbe:decrypt(Cipher, Hash, Iterations, TempSecret, E),
ok.

try_decrypt(Cipher, Hash, Iterations, Secret, Term) ->
try
{?VALUE_TAG, Decrypted} =
credentials_obfuscation_pbe:decrypt_term(Cipher, Hash, Iterations, Secret, Term),
{ok, Decrypted}
catch
ErrorType:Error:_Stacktrace ->
{error, {ErrorType, Error}}
end.

% currently the callers may rely on this process converting strings to binary
to_binary(Term) when is_list(Term) -> erlang:list_to_binary(Term);
to_binary(Term) when is_binary(Term) -> Term.
31 changes: 31 additions & 0 deletions test/credentials_obfuscation_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ all() -> [encrypt_decrypt,
encrypt_decrypt_char_list_value,
use_predefined_secret,
use_cookie_as_secret,
change_of_secret_returns_passed_in_data,
fallback_secret,
encryption_happens_only_when_secret_available,
change_default_cipher,
disabled,
Expand Down Expand Up @@ -102,6 +104,35 @@ use_cookie_as_secret(_Config) ->
ok = net_kernel:stop(),
ok.

%% change of secret should not crash the credentials_obfuscation_svc process
change_of_secret_returns_passed_in_data(_Config) ->
Secret1 = crypto:strong_rand_bytes(128),
Secret2 = crypto:strong_rand_bytes(128),
Uri = <<"amqp://super:secret@localhost:5672">>,
ok = credentials_obfuscation:set_secret(Secret1),
Encrypted = credentials_obfuscation:encrypt(Uri),
ok = credentials_obfuscation:set_secret(Secret2),
?assertEqual(Encrypted, credentials_obfuscation:decrypt(Encrypted)),
ok.

fallback_secret(_Config) ->
Secret1 = crypto:strong_rand_bytes(128),
Secret2 = crypto:strong_rand_bytes(128),
Uri = <<"amqp://super:secret@localhost:5672">>,
ok = credentials_obfuscation:set_secret(Secret1),
Encrypted = credentials_obfuscation:encrypt(Uri),

ok = credentials_obfuscation:set_secret(Secret2),
Encrypted2 = credentials_obfuscation:encrypt(Uri),

?assertEqual(Encrypted, credentials_obfuscation:decrypt(Encrypted)),

ok = credentials_obfuscation:set_fallback_secret(Secret1),

?assertEqual(Uri, credentials_obfuscation:decrypt(Encrypted)),
?assertEqual(Uri, credentials_obfuscation:decrypt(Encrypted2)),
ok.

encryption_happens_only_when_secret_available(_Config) ->
_ = net_kernel:stop(),
Uri = <<"amqp://super:secret@localhost:5672">>,
Expand Down