Skip to content

Commit

Permalink
Merge branch 'feature/replace_enacl' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
aramallo committed Sep 3, 2024
2 parents 664c09b + 8682699 commit 920edfd
Show file tree
Hide file tree
Showing 20 changed files with 360 additions and 245 deletions.
2 changes: 0 additions & 2 deletions apps/bondy/src/bondy.app.src
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,7 @@
sasl,
tools,
%% 3rd-party Crypto
enacl,
jose,
pbkdf2,
stringprep,
%% 3rd-party Web Sevrver|client
cowboy,
Expand Down
36 changes: 9 additions & 27 deletions apps/bondy/src/bondy_auth_wamp_cryptosign.erl
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
-type state() :: map().
-type challenge_error() :: missing_pubkey | no_matching_pubkey.


%% BONDY_AUTH CALLBACKS
-export([init/1]).
-export([requirements/0]).
Expand All @@ -43,6 +42,9 @@






%% =============================================================================
%% BONDY_AUTH CALLBACKS
%% =============================================================================
Expand Down Expand Up @@ -109,7 +111,7 @@ challenge(Details, Ctxt, State) ->

case lists:member(Key, Keys) of
true ->
Challenge = enacl:randombytes(32),
Challenge = bondy_cryptosign:strong_rand_bytes(),
NewState = State#{
pubkey => Key,
challenge => Challenge
Expand Down Expand Up @@ -141,15 +143,14 @@ challenge(Details, Ctxt, State) ->
{ok, DataOut :: map(), CBState :: state()}
| {error, Reason :: any(), CBState :: state()}.

authenticate(EncSignature, _, _, #{pubkey := PK} = State)
authenticate(EncSignature, _, _, #{pubkey := Pub} = State)
when is_binary(EncSignature) ->
try
Challenge = maps:get(challenge, State),
Signature0 = decode_hex(EncSignature),
Signature = normalise_signature(Signature0, Challenge),
Signature = decode_hex(EncSignature),

%% Verify that the Challenge was signed using the Ed25519 key
case enacl:sign_verify_detached(Signature, Challenge, PK) of
case bondy_cryptosign:verify(Signature, Challenge, Pub) of
true ->
{ok, #{}, State};

Expand All @@ -159,19 +160,18 @@ when is_binary(EncSignature) ->
end
catch
error:badarg ->
%% enacl failed
{error, invalid_signature, State};

error:invalid_signature ->
%% normalise failed
{error, invalid_signature, State};

throw:invalid_hex_encoding ->
{error, invalid_signature, State}
end.





%% =============================================================================
%% PRIVATE
%% =============================================================================
Expand Down Expand Up @@ -199,21 +199,3 @@ encode_hex(Bin) when is_binary(Bin) ->
list_to_binary(hex_utils:bin_to_hexstr(Bin)).


%% @private
%% @doc As the cryptosign spec is not formal some clients e.g. Python
%% return Signature(64) ++ Challenge(32) while others e.g. JS return just the
%% Signature(64).
%% @end
normalise_signature(Signature, _) when byte_size(Signature) == 64 ->
Signature;

normalise_signature(Signature, Challenge) when byte_size(Signature) == 96 ->
case binary:match(Signature, Challenge) of
{64, 32} ->
binary:part(Signature, {0, 64});
_ ->
throw(invalid_signature)
end;

normalise_signature(_, _) ->
throw(invalid_signature).
14 changes: 8 additions & 6 deletions apps/bondy/src/bondy_bridge_relay_client.erl
Original file line number Diff line number Diff line change
Expand Up @@ -1198,22 +1198,22 @@ signer(PubKey, #{cryptosign := #{exec := Filename}}) ->
error(Reason)
end;

signer(_, #{cryptosign := #{privkey := HexString}}) ->
signer(PubKey, #{cryptosign := #{privkey := HexString}}) ->
%% For testing only, this will be remove on 1.0.0
fun(Message) ->
PrivKey = hex_utils:hexstr_to_bin(HexString),
sign(Message, PrivKey)
sign(Message, PrivKey, PubKey)
end;

signer(_, #{cryptosign := #{privkey_env_var := Var}}) ->
signer(PubKey, #{cryptosign := #{privkey_env_var := Var}}) ->

case os:getenv(Var) of
false ->
error({invalid_config, {privkey_env_var, Var}});
HexString ->
fun(Message) ->
PrivKey = hex_utils:hexstr_to_bin(HexString),
sign(Message, PrivKey)
sign(Message, PrivKey, PubKey)
end
end;

Expand All @@ -1222,10 +1222,12 @@ signer(_, Conf) ->


%% @private
sign(Message, PrivKey) ->
sign(Message, PrivKey, PubKey) ->
list_to_binary(
hex_utils:bin_to_hexstr(
enacl:sign_detached(Message, PrivKey)
bondy_cryptosign:sign(
Message, #{public => PubKey, secret => PrivKey}
)
)
).

Expand Down
127 changes: 127 additions & 0 deletions apps/bondy/src/bondy_cryptosign.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
%% =============================================================================
%% bondy_cryptosign.erl -
%%
%% Copyright (c) 2016-2024 Leapsight. All rights reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%% =============================================================================


%% -----------------------------------------------------------------------------
%% @doc This modules provides the necessary functions to support the
%% Cryptosign capabilities.
%% @end
%% -----------------------------------------------------------------------------
-module(bondy_cryptosign).

-type key_pair() :: #{public => binary(), secret => binary()}.

%% API
-export([generate_key/0]).
-export([normalise_signature/2]).
-export([sign/2]).
-export([strong_rand_bytes/0]).
-export([strong_rand_bytes/1]).
-export([verify/3]).


%% =============================================================================
%% API
%% =============================================================================



%% -----------------------------------------------------------------------------
%% @doc
%% @end
%% -----------------------------------------------------------------------------
-spec generate_key() -> KeyPair :: key_pair().

generate_key() ->
{Pub, Priv} = crypto:generate_key(eddsa, ed25519),
#{public => Pub, secret => Priv}.


%% -----------------------------------------------------------------------------
%% @doc Calls `strong_rand_bytes/1' with the default length value `32`.
%% @end
%% -----------------------------------------------------------------------------
-spec strong_rand_bytes() -> binary().

strong_rand_bytes() ->
strong_rand_bytes(32).


%% -----------------------------------------------------------------------------
%% @doc
%% @end
%% -----------------------------------------------------------------------------
-spec strong_rand_bytes(pos_integer()) -> binary().

strong_rand_bytes(Length) when is_integer(Length) andalso Length >= 0 ->
crypto:strong_rand_bytes(Length).


%% -----------------------------------------------------------------------------
%% @doc
%% @end
%% -----------------------------------------------------------------------------
-spec sign(Challenge :: binary(), KeyPair :: key_pair()) ->
Signature :: binary().

sign(Challenge, #{public := Pub, secret := Priv}) ->
public_key:sign(Challenge, ignored, {ed_pri, ed25519, Pub, Priv}, []).


%% -----------------------------------------------------------------------------
%% @doc
%% @end
%% -----------------------------------------------------------------------------
-spec verify(
Signature :: binary(), Challenge :: binary(), PublicKey :: binary()) -> boolean() | no_return().

verify(Signature, Challenge, PublicKey) ->
Normalised = normalise_signature(Signature, Challenge),

public_key:verify(
Challenge, ignored, Normalised, {ed_pub, ed25519, PublicKey}
).



%% =============================================================================
%% PRIVATE
%% =============================================================================



%% -----------------------------------------------------------------------------
%% @private
%% @doc As the cryptosign spec is not formal some clients e.g. Python
%% return Signature(64) ++ Challenge(32) while others e.g. JS return just the
%% Signature(64).
%% @end
%% -----------------------------------------------------------------------------
normalise_signature(Signature, _) when byte_size(Signature) == 64 ->
Signature;

normalise_signature(Signature, Challenge) when byte_size(Signature) == 96 ->
case binary:match(Signature, Challenge) of
{64, 32} ->
binary:part(Signature, {0, 64});
_ ->
error(invalid_signature)
end;

normalise_signature(_, _) ->
error(invalid_signature).
36 changes: 16 additions & 20 deletions apps/bondy/src/bondy_password.erl
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
key => kdf,
required => true,
default => bondy_config:get([security, password, scram, kdf]),
datatype => {in, [pbkdf2, <<"pbkdf2">>, argon2id13, <<"argon2id13">>]},
datatype => {in, [pbkdf2, argon2id13, <<"pbkdf2">>, <<"argon2id13">>]},
validator => fun bondy_data_validators:existing_atom/1
},
iterations => #{
Expand Down Expand Up @@ -199,7 +199,6 @@ replace(Password, PWD) ->
new(Password, #{protocol => Protocol, params => Params}).



%% -----------------------------------------------------------------------------
%% @doc
%% @end
Expand Down Expand Up @@ -234,7 +233,6 @@ opts_validator() ->
?OPTS_VALIDATOR.



%% -----------------------------------------------------------------------------
%% @doc
%% @end
Expand Down Expand Up @@ -332,17 +330,17 @@ verify_hash(_Hash, #{version := ?VERSION, protocol := scram} = _PW) ->
error(not_implemented);

verify_hash(Hash, #{version := ?VERSION, protocol := cra} = PW) ->
SPassword = maps_utils:get_path([data, salted_password], PW),
pbkdf2:compare_secure(pbkdf2:to_hex(Hash), pbkdf2:to_hex(SPassword));
Salted = maps_utils:get_path([data, salted_password], PW),
crypto:hash_equals(Hash, Salted);

verify_hash(Hash, #{version := <<"1.1">>} = PW) ->
#{hash_pass := StoredHash} = PW,
%% StoredHash is base64 encoded
pbkdf2:compare_secure(pbkdf2:to_hex(Hash), pbkdf2:to_hex(StoredHash));
#{hash_pass := Salted} = PW,
%% Stored Salted is base64 encoded in 1.1
crypto:hash_equals(Hash, Salted);

verify_hash(Hash, #{version := <<"1.0">>} = PW) when is_binary(Hash) ->
#{hash_pass := StoredHash} = PW,
pbkdf2:compare_secure(pbkdf2:to_hex(Hash), StoredHash);
#{hash_pass := Salted} = PW,
crypto:hash_equals(Hash, Salted);

verify_hash(Hash, #{} = PW) ->
verify_string(Hash, add_version(PW)).
Expand Down Expand Up @@ -373,31 +371,29 @@ verify_string(String, #{version := ?VERSION, protocol := cra} = PW) ->

verify_string(String, #{version := <<"1.1">>} = PW) ->
#{
hash_pass := StoredHash,
hash_pass := Salted,
hash_func := HashFun,
iterations := HashIter,
salt := Salt
} = PW,
HashLen = hash_length(PW),

%% We use keylen in version > 1.0
{ok, Hash0} = pbkdf2:pbkdf2(HashFun, String, Salt, HashIter, HashLen),
Hash0 = crypto:pbkdf2_hmac(HashFun, String, Salt, HashIter, HashLen),

pbkdf2:compare_secure(
pbkdf2:to_hex(StoredHash),
pbkdf2:to_hex(base64:encode(Hash0)) %% StoredHash is base64 encoded
);
%% Stored Salted is base64 encoded in 1.1
crypto:hash_equals(Salted, base64:encode(Hash0));

verify_string(String, #{version := <<"1.0">>} = PW) ->
#{
hash_pass := StoredHash,
hash_pass := Salted,
hash_func := HashFun,
iterations := HashIter,
salt := Salt
} = PW,
{ok, Hash} = pbkdf2:pbkdf2(HashFun, String, Salt, HashIter),
%% StoredHash is hex value
pbkdf2:compare_secure(pbkdf2:to_hex(Hash), StoredHash);
HashLen = hash_length(PW),
Hash = crypto:pbkdf2_hmac(HashFun, String, Salt, HashIter, HashLen),
crypto:hash_equals(Hash, Salted);

%% to handle the error: reason=function_clause
%% example: [{bondy_password,verify_string,[<<\"Nes 2907\">>,[{hash_pass,<<\"adcebee9a2cbbe4e26c340f95da646a1ab60c676\">>},{auth_name,pbkdf2},{hash_func,sha},{salt,<<76,202,0,27,196,167,217,222,194,142,96,185,219,169,96,233>>},{iterations,65536}]]
Expand Down
8 changes: 4 additions & 4 deletions apps/bondy/src/bondy_password_cra.erl
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ nonce_length() ->
-spec salt() -> binary().

salt() ->
base64:encode(enacl:randombytes(salt_length())).
base64:encode(crypto:strong_rand_bytes(salt_length())).


%% -----------------------------------------------------------------------------
Expand All @@ -176,7 +176,7 @@ salt() ->
-spec nonce() -> binary().

nonce() ->
base64:encode(enacl:randombytes(nonce_length())).
base64:encode(crypto:strong_rand_bytes(nonce_length())).


%% -----------------------------------------------------------------------------
Expand All @@ -192,7 +192,7 @@ salted_password(Password, Salt, #{kdf := pbkdf2} = Params) ->
hash_length := HashLen
} = Params,

{ok, SaltedPassword} = pbkdf2:pbkdf2(
SaltedPassword = crypto:pbkdf2_hmac(
HashFun, Password, Salt, Iterations, HashLen
),
base64:encode(SaltedPassword).
Expand All @@ -205,7 +205,7 @@ salted_password(Password, Salt, #{kdf := pbkdf2} = Params) ->
-spec compare(binary(), binary()) -> boolean().

compare(A, B) ->
pbkdf2:compare_secure(pbkdf2:to_hex(A), pbkdf2:to_hex(B)).
crypto:hash_equals(A, B).



Expand Down
Loading

0 comments on commit 920edfd

Please sign in to comment.