From 0c33dc7ca446c8b26aeb2f40839cd76d137b2f03 Mon Sep 17 00:00:00 2001 From: Nelson Vides Date: Wed, 18 Dec 2024 10:25:32 +0100 Subject: [PATCH] Update documentation, linting, and formatting --- .github/workflows/ci.yml | 22 +- .gitignore | 2 +- README.md | 3 +- rebar.config | 80 +++- src/fast_scram.app.src | 32 +- src/fast_scram.erl | 271 +++++++----- src/fast_scram.hrl | 57 +++ src/fast_scram_attributes.erl | 89 ++-- src/fast_scram_configuration.erl | 203 +++++---- src/fast_scram_definitions.erl | 220 ++++++---- src/fast_scram_parse_rules.erl | 162 ++++--- src/types.hrl | 64 --- test/scram_SUITE.erl | 729 ++++++++++++++++++------------- 13 files changed, 1124 insertions(+), 810 deletions(-) create mode 100644 src/fast_scram.hrl delete mode 100644 src/types.hrl diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3066641..54a00ab 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,24 +12,28 @@ jobs: name: OTP ${{matrix.otp}} strategy: matrix: - otp: ['26.2', '25.3', '24.3'] - rebar3: ['3.22.1'] - runs-on: 'ubuntu-22.04' + otp: ['27', 26', '25'] + rebar3: ['3.24.0'] + runs-on: 'ubuntu-24.04' env: OTPVER: ${{ matrix.otp }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: erlef/setup-beam@v1 with: otp-version: ${{matrix.otp}} rebar3-version: ${{matrix.rebar3}} + - run: rebar3 fmt --check + - run: rebar3 lint - run: rebar3 do ct --cover - run: rebar3 as test codecov analyze - run: rebar3 dialyzer - if: ${{ matrix.otp == '26.2' }} - - uses: codecov/codecov-action@v3 - name: Upload coverage reports to Codecov - if: ${{ matrix.otp == '26.2' }} + if: ${{ matrix.otp == '27' }} + - name: Upload code coverage + uses: codecov/codecov-action@v4 + if: ${{ matrix.otp == '27' }} with: + files: _build/test/covertool/fast_scram.covertool.xml token: ${{ secrets.CODECOV_TOKEN }} - fail_ci_if_error: true # optional (default = false) + fail_ci_if_error: true + verbose: true diff --git a/.gitignore b/.gitignore index 04e07db..e77e18e 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,4 @@ rebar3.crashdump *.vim tags priv/ - +doc/ diff --git a/README.md b/README.md index 9dfdcbb..0df321c 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ [![Actions Status](https://github.com/esl/fast_scram/workflows/ci/badge.svg)](https://github.com/esl/fast_scram/actions) [![codecov](https://codecov.io/gh/esl/fast_scram/branch/master/graph/badge.svg)](https://codecov.io/gh/esl/fast_scram) [![Hex](http://img.shields.io/hexpm/v/fast_scram.svg)](https://hex.pm/packages/fast_scram) +[![Hex Docs](https://img.shields.io/badge/hex-docs-lightgreen.svg)](https://hexdocs.pm/fast_scram/) `fast_scram` is a purely-functional Erlang implementation of the _Salted Challenge Response Authentication Mechanism_, where the challenge algorithm is implemented as a carefully-optimised NIF using [fast_pbkdf2][fast_pbkdf2]. @@ -235,7 +236,7 @@ If for any particular reason you want to skip compiling and loading custom NIFs, * SCRAM: [RFC5802](https://tools.ietf.org/html/rfc5802) * SCRAM-SHA-256 update: [RFC7677](https://tools.ietf.org/html/rfc7677) * Password-Based Cryptography Specification (PBKDF2): [RFC8018](https://tools.ietf.org/html/rfc8018) -* HMAC: [RFC2104]( https://tools.ietf.org/html/rfc2104) +* HMAC: [RFC2104](https://tools.ietf.org/html/rfc2104) * SHAs and HMAC-SHA: [RFC6234](https://tools.ietf.org/html/rfc6234) [MIM]: https://github.com/esl/MongooseIM diff --git a/rebar.config b/rebar.config index d559f96..60e045c 100644 --- a/rebar.config +++ b/rebar.config @@ -1,39 +1,71 @@ {erl_opts, []}. {deps, [ - {fast_pbkdf2, "1.0.5"} + {fast_pbkdf2, "1.0.6"} ]}. {project_plugins, [ - rebar3_hex, - rebar3_ex_doc + {rebar3_hex, "7.0.8"}, + {rebar3_ex_doc, "0.2.25"}, + {rebar3_lint, "~> 3.2.6"}, + {erlfmt, "1.5.0"} ]}. - {profiles, [ - {test, [ - {erl_opts, []}, - {deps, [ - {proper, "1.4.0"}, - {base16, "2.0.1"} - ]}, - {plugins, [ - {rebar3_codecov, "0.6.0"} - ]}, - {cover_enabled, true}, - {cover_export_enabled, true} - ]}, - {prod, [ - {erl_opts, [inline_list_funcs, deterministic]} - ]} - ] -}. + {test, [ + {erl_opts, []}, + {deps, [ + {proper, "1.4.0"}, + {base16, "2.0.1"} + ]}, + {plugins, [ + {rebar3_codecov, "0.7.0"} + ]}, + {cover_enabled, true}, + {cover_export_enabled, true} + ]}, + {prod, [ + {erl_opts, [inline_list_funcs, deterministic]} + ]} +]}. + +{erlfmt, [ + write, + {files, [ + "src/*.{hrl,erl,app.src}", + "test/*.{hrl,erl,app.src}", + "rebar.config" + ]} +]}. + +{elvis, [ + #{ + dirs => ["src/**"], + filter => "*.erl", + ruleset => erl_files, + rules => [{elvis_style, private_data_types, disable}] + }, + #{ + dirs => ["."], + filter => "rebar.config", + ruleset => rebar_config + }, + #{ + dirs => ["src/**"], + filter => "*.hrl", + ruleset => hrl_files + } +]}. {hex, [ {doc, #{provider => ex_doc}} ]}. {ex_doc, [ - {source_url, <<"https://github.com/esl/fast_scram">>}, - {extras, [<<"README.md">>, <<"LICENSE">>]}, - {main, <<"readme">>} + {source_url, <<"https://github.com/esl/fast_scram">>}, + {main, <<"readme">>}, + {extras, [ + {'README.md', #{title => <<"README">>}}, + {'LICENSE', #{title => <<"License">>}} + ]}, + {main, <<"readme">>} ]}. diff --git a/src/fast_scram.app.src b/src/fast_scram.app.src index b6e6a39..900fb42 100644 --- a/src/fast_scram.app.src +++ b/src/fast_scram.app.src @@ -1,16 +1,16 @@ -{application, fast_scram, - [{description, "A fast Salted Challenge Response Authentication Mechanism"}, - {vsn, git}, - {registered, []}, - {applications, - [kernel, - stdlib, - crypto, - fast_pbkdf2 - ]}, - {optional_applications, [fast_pbkdf2]}, - {env,[]}, - {modules, []}, - {licenses, ["Apache 2.0"]}, - {links, [{"GitHub", "https://github.com/esl/fast_scram/"}]} - ]}. +{application, fast_scram, [ + {description, "A fast Salted Challenge Response Authentication Mechanism"}, + {vsn, git}, + {registered, []}, + {applications, [ + kernel, + stdlib, + crypto, + fast_pbkdf2 + ]}, + {optional_applications, [fast_pbkdf2]}, + {env, []}, + {modules, []}, + {licenses, ["Apache 2.0"]}, + {links, [{"GitHub", "https://github.com/esl/fast_scram/"}]} +]}. diff --git a/src/fast_scram.erl b/src/fast_scram.erl index fc336fe..4e40437 100644 --- a/src/fast_scram.erl +++ b/src/fast_scram.erl @@ -1,93 +1,148 @@ +%%% @doc SCRAM implementation, see the `README' for details. -module(fast_scram). --include("types.hrl"). - +-include("fast_scram.hrl"). + +-type sha_type() :: crypto:sha1() | crypto:sha2(). +%%% Supported underlying hashing algorithms. +-type configuration() :: #{ + entity := client | server, + hash_method := sha_type(), + _ => _ +}. +%%% Configuration for SCRAM, see the `README' for details. +-type username() :: binary(). +%%% Username for the algorithm. +%%% +%%% Required for a client. +-type retrieve_mechanism() :: + fun((username()) -> configuration()) + | fun((username(), state()) -> {configuration(), state()}). +%%% Callback to extract the configuration given a username. +%%% +%%% Required for the server. +-type auth_keys() :: password | salted_password | client_key | stored_key | server_key. +-type auth_data() :: #{auth_keys() => binary()}. +-type plus_variant() :: undefined | none | binary(). +-type nonce() :: #nonce{}. +%%% See `c-nonce' and `s-nonce' at +%%% [https://datatracker.ietf.org/doc/html/rfc5802#section-7]. +-type challenge() :: #challenge{}. +-type channel_binding() :: #channel_binding{}. +-type definitions() :: #scram_definitions{}. +-type state() :: #fast_scram_state{}. -type next_message() :: binary(). -type error_message() :: binary(). +%%% See `server-error-message' at +%%% [https://datatracker.ietf.org/doc/html/rfc5802#section-7]. -type final_message() :: binary(). - --spec mech_new(configuration()) -> - {ok, fast_scram_state()} | - {error, term()}. - --spec mech_step(fast_scram_state(), binary()) -> - {ok, final_message(), fast_scram_state()} | - {continue, next_message(), fast_scram_state()} | - {error, error_message(), fast_scram_state()}. +%%% See `client-final-message' and `server-final-message' at +%%% [https://datatracker.ietf.org/doc/html/rfc5802#section-7]. + +-export_type([ + sha_type/0, + auth_keys/0, + auth_data/0, + challenge/0, + nonce/0, + plus_variant/0, + channel_binding/0, + retrieve_mechanism/0, + definitions/0, + configuration/0, + next_message/0, + final_message/0, + error_message/0, + state/0 +]). -export([hi/4]). --export([mech_new/1, - mech_step/2 - ]). +-export([ + mech_new/1, + mech_step/2 +]). -export([ - mech_get/2, - mech_get/3, - mech_set/3 - ]). + mech_get/2, + mech_get/3, + mech_set/3 +]). -export([ - salted_password/4, - client_key/2, - stored_key/2, - client_signature/3, - client_proof/2, - server_key/2, - server_signature/3 - ]). + salted_password/4, + client_key/2, + stored_key/2, + client_signature/3, + client_proof/2, + server_key/2, + server_signature/3 +]). +-spec mech_new(configuration()) -> + {ok, state()} + | {error, term()}. mech_new(Config) -> fast_scram_configuration:mech_new(Config). --spec mech_get(term(), fast_scram_state()) -> term(). +-spec mech_get(term(), state()) -> term(). mech_get(Key, #fast_scram_state{data = PD}) -> maps:get(Key, PD, undefined). --spec mech_get(term(), fast_scram_state(), term()) -> term(). +-spec mech_get(term(), state(), term()) -> term(). mech_get(Key, #fast_scram_state{data = PD}, Default) -> maps:get(Key, PD, Default). --spec mech_set(term(), term(), fast_scram_state()) -> fast_scram_state(). +-spec mech_set(term(), term(), state()) -> state(). mech_set(Key, Value, #fast_scram_state{data = PD} = State) -> State#fast_scram_state{data = PD#{Key => Value}}. %%% CLIENT STEPS -mech_step(#fast_scram_state{ - step = 1, - nonce = Nonce, - channel_binding = CbConfig, - data = PrivData - } = State, <<>>) -> +-spec mech_step(state(), binary()) -> + {ok, final_message(), state()} + | {continue, next_message(), state()} + | {error, error_message(), state()}. +mech_step( + #fast_scram_state{ + step = 1, + nonce = Nonce, + channel_binding = CbConfig, + data = PrivData + } = State, + <<>> +) -> {GS2Header, ClientFirstBare} = fast_scram_attributes:client_first_message( - CbConfig, Nonce, PrivData), + CbConfig, Nonce, PrivData + ), ClientFirstMessage = <>, NewState = fast_scram_parse_rules:append_to_auth_message_in_state(State, ClientFirstBare), {continue, ClientFirstMessage, NewState#fast_scram_state{step = 3}}; - mech_step(#fast_scram_state{step = 3} = State, ServerIn) -> case parse_server_first_message(ServerIn, State) of - {ok, #fast_scram_state{ + {ok, + #fast_scram_state{ nonce = Nonce, challenge = Challenge, channel_binding = CbConfig, scram_definitions = Scram0, data = PrivData - } = NewState} -> + } = NewState} -> ClientFinalNoProof = fast_scram_attributes:client_final_message_without_proof( - CbConfig, Nonce, PrivData), + CbConfig, Nonce, PrivData + ), Scram1 = fast_scram_parse_rules:append_to_auth_message( - Scram0, <<",", ClientFinalNoProof/binary>>), + Scram0, <<",", ClientFinalNoProof/binary>> + ), Scram2 = fast_scram_definitions:scram_definitions_pipe(Scram1, Challenge, PrivData), ClientProof = Scram2#scram_definitions.client_proof, ClientFinalMessage = fast_scram_attributes:client_final_message( - ClientFinalNoProof, ClientProof), + ClientFinalNoProof, ClientProof + ), NewState1 = NewState#fast_scram_state{scram_definitions = Scram2}, {continue, ClientFinalMessage, NewState1#fast_scram_state{step = 5}}; {error, Reason} -> {error, Reason, State} end; - mech_step(#fast_scram_state{step = 5} = State, ServerIn) -> case parse_server_final_message(ServerIn, State) of {ok, NewState} -> @@ -95,7 +150,6 @@ mech_step(#fast_scram_state{step = 5} = State, ServerIn) -> {error, Reason} -> {error, Reason, State} end; - %%% SERVER STEPS %%% An retrieve_mechanism function can add data to the state %%% For example when the username is needed in order to complete the state data @@ -109,11 +163,14 @@ mech_step(#fast_scram_state{step = 2} = State0, ClientIn) -> Challenge = State2#fast_scram_state.challenge, Scram0 = State2#fast_scram_state.scram_definitions, ServerFirstMsg = fast_scram_attributes:server_first_message( - Nonce, Challenge), + Nonce, Challenge + ), Scram1 = fast_scram_definitions:scram_definitions_pipe( - Scram0, Challenge, PrivData), + Scram0, Challenge, PrivData + ), Scram2 = fast_scram_parse_rules:append_to_auth_message( - Scram1, <<",", ServerFirstMsg/binary>>), + Scram1, <<",", ServerFirstMsg/binary>> + ), NewState1 = State2#fast_scram_state{scram_definitions = Scram2}, {continue, ServerFirstMsg, NewState1#fast_scram_state{step = 4}}; {error, Reason} -> @@ -122,14 +179,14 @@ mech_step(#fast_scram_state{step = 2} = State0, ClientIn) -> {error, Reason} -> {error, Reason, State0} end; - mech_step(#fast_scram_state{step = 4} = State, ClientIn) -> case parse_client_final_message(ClientIn, State) of - {ok, #fast_scram_state{ + {ok, + #fast_scram_state{ challenge = Challenge, scram_definitions = Scram0, data = PrivData - } = NewState} -> + } = NewState} -> GivenClientProof = maps:get(client_proof, PrivData), Scram = fast_scram_definitions:scram_definitions_pipe(Scram0, Challenge, PrivData), NewState1 = NewState#fast_scram_state{scram_definitions = Scram}, @@ -137,7 +194,8 @@ mech_step(#fast_scram_state{step = 4} = State, ClientIn) -> ok -> ServerSignature = Scram#scram_definitions.server_signature, ServerLastMessage = fast_scram_attributes:server_final_message( - base64:encode(ServerSignature)), + base64:encode(ServerSignature) + ), {ok, ServerLastMessage, NewState1#fast_scram_state{step = 6}}; {error, Reason} -> ServerLastMessage = fast_scram_attributes:server_final_message({error, Reason}), @@ -149,13 +207,12 @@ mech_step(#fast_scram_state{step = 4} = State, ClientIn) -> end. -type username_to_config() :: fun((username()) -> configuration()). --type username_to_state() :: fun((username(), fast_scram_state()) -> - {configuration(), fast_scram_state()}). +-type username_to_state() :: fun((username(), state()) -> {configuration(), state()}). --spec apply_fun(Fun, State) -> Result - when Fun :: username_to_config() | username_to_state(), - State :: fast_scram_state(), - Result :: fast_scram_state() | {error, term()}. +-spec apply_fun(Fun, State) -> Result when + Fun :: username_to_config() | username_to_state(), + State :: state(), + Result :: state() | {error, term()}. apply_fun(Fun, State) when is_function(Fun, 1) -> Username = fast_scram:mech_get(username, State), Result = Fun(Username), @@ -165,11 +222,9 @@ apply_fun(Fun, State) when is_function(Fun, 2) -> Result = Fun(Username, State), apply_result(Result, State). -apply_result({Config = #{}, State = #fast_scram_state{}}, _) -> - fast_scram_configuration:mech_append(State, Config); -apply_result({State = #fast_scram_state{}, Config = #{}}, _) -> +apply_result({#{} = Config, #fast_scram_state{} = State}, _) -> fast_scram_configuration:mech_append(State, Config); -apply_result(Config = #{}, State) -> +apply_result(#{} = Config, State) -> fast_scram_configuration:mech_append(State, Config); apply_result({error, Reason}, _) -> {error, Reason}. @@ -179,23 +234,25 @@ apply_result({error, Reason}, _) -> %%%=================================================================== % client-first-message = % gs2-cbind-flag "," [authzid] "," [reserved-mext ","] username "," nonce ["," extensions] --spec parse_client_first_message(binary(), fast_scram_state()) -> parse_return(). +-spec parse_client_first_message(binary(), state()) -> fast_scram_parse_rules:parse_return(). parse_client_first_message(ClientIn, State0) -> Rules = [ - fun fast_scram_parse_rules:parse_gs2_cbind_flag/2, - fun fast_scram_parse_rules:parse_authzid/2, - fun fast_scram_parse_rules:parse_reserved_mext/2, - fun fast_scram_parse_rules:parse_username/2, - fun fast_scram_parse_rules:parse_nonce/2, - fun fast_scram_parse_rules:parse_extensions/2 - ], + fun fast_scram_parse_rules:parse_gs2_cbind_flag/2, + fun fast_scram_parse_rules:parse_authzid/2, + fun fast_scram_parse_rules:parse_reserved_mext/2, + fun fast_scram_parse_rules:parse_username/2, + fun fast_scram_parse_rules:parse_nonce/2, + fun fast_scram_parse_rules:parse_extensions/2 + ], Chunks = binary:split(ClientIn, <<",">>, [global]), case match_rules(Chunks, Rules, State0) of - {UnusedRules, State1 = #fast_scram_state{}} - when is_list(UnusedRules), length(UnusedRules) == 1 -> + {UnusedRules, State1 = #fast_scram_state{}} when + is_list(UnusedRules), length(UnusedRules) == 1 + -> ClientFirstMsgBare = extract_client_first_msg_bare_from_first(Chunks, ClientIn), State2 = fast_scram_parse_rules:append_to_auth_message_in_state( - State1, ClientFirstMsgBare), + State1, ClientFirstMsgBare + ), {ok, State2}; {error, Reason} -> {error, Reason} @@ -204,21 +261,23 @@ parse_client_first_message(ClientIn, State0) -> % server-first-message = % [reserved-mext ","] nonce "," salt "," % iteration-count ["," extensions] --spec parse_server_first_message(binary(), fast_scram_state()) -> parse_return(). +-spec parse_server_first_message(binary(), state()) -> fast_scram_parse_rules:parse_return(). parse_server_first_message(ServerIn, State0) -> Rules = [ - fun fast_scram_parse_rules:parse_reserved_mext/2, - fun fast_scram_parse_rules:parse_nonce/2, - fun fast_scram_parse_rules:parse_salt/2, - fun fast_scram_parse_rules:parse_iteration_count/2, - fun fast_scram_parse_rules:parse_extensions/2 - ], + fun fast_scram_parse_rules:parse_reserved_mext/2, + fun fast_scram_parse_rules:parse_nonce/2, + fun fast_scram_parse_rules:parse_salt/2, + fun fast_scram_parse_rules:parse_iteration_count/2, + fun fast_scram_parse_rules:parse_extensions/2 + ], ServerInChunks = binary:split(ServerIn, <<",">>, [global]), case match_rules(ServerInChunks, Rules, State0) of - {UnusedRules, State1 = #fast_scram_state{}} - when is_list(UnusedRules), length(UnusedRules) == 1 -> + {UnusedRules, State1 = #fast_scram_state{}} when + is_list(UnusedRules), length(UnusedRules) == 1 + -> State2 = fast_scram_parse_rules:append_to_auth_message_in_state( - State1, <<",", ServerIn/binary>>), + State1, <<",", ServerIn/binary>> + ), {ok, State2}; {error, Reason} -> {error, Reason} @@ -226,21 +285,23 @@ parse_server_first_message(ServerIn, State0) -> % client-final-message = % channel-binding "," nonce ["," extensions] "," proof --spec parse_client_final_message(binary(), fast_scram_state()) -> parse_return(). +-spec parse_client_final_message(binary(), state()) -> fast_scram_parse_rules:parse_return(). parse_client_final_message(ClientIn, State0) -> Rules = [ - fun fast_scram_parse_rules:parse_channel_binding/2, - fun fast_scram_parse_rules:parse_nonce/2, - fun fast_scram_parse_rules:parse_extensions/2, - fun fast_scram_parse_rules:parse_proof/2 - ], + fun fast_scram_parse_rules:parse_channel_binding/2, + fun fast_scram_parse_rules:parse_nonce/2, + fun fast_scram_parse_rules:parse_extensions/2, + fun fast_scram_parse_rules:parse_proof/2 + ], ClientInList = binary:split(ClientIn, <<",">>, [global]), case match_rules(ClientInList, Rules, State0) of - {UnusedRules, State1 = #fast_scram_state{}} - when is_list(UnusedRules), length(UnusedRules) == 0 -> + {UnusedRules, State1 = #fast_scram_state{}} when + is_list(UnusedRules), length(UnusedRules) == 0 + -> ClientFinalNoProof = extract_client_final_no_proof(ClientIn), State2 = fast_scram_parse_rules:append_to_auth_message_in_state( - State1, <<",", ClientFinalNoProof/binary>>), + State1, <<",", ClientFinalNoProof/binary>> + ), {ok, State2}; {error, Reason} -> {error, Reason}; @@ -250,16 +311,17 @@ parse_client_final_message(ClientIn, State0) -> % server-final-message = (server-error / verifier) % ["," extensions] --spec parse_server_final_message(binary(), fast_scram_state()) -> parse_return(). +-spec parse_server_final_message(binary(), state()) -> fast_scram_parse_rules:parse_return(). parse_server_final_message(ServerIn, State0) -> Rules = [ - fun fast_scram_parse_rules:parse_server_error_or_verifier/2, - fun fast_scram_parse_rules:parse_extensions/2 - ], + fun fast_scram_parse_rules:parse_server_error_or_verifier/2, + fun fast_scram_parse_rules:parse_extensions/2 + ], ServerInChunks = binary:split(ServerIn, <<",">>, [global]), case match_rules(ServerInChunks, Rules, State0) of - {UnusedRules, State1 = #fast_scram_state{}} - when is_list(UnusedRules), length(UnusedRules) == 1 -> + {UnusedRules, State1 = #fast_scram_state{}} when + is_list(UnusedRules), length(UnusedRules) == 1 + -> {ok, State1}; {error, Reason} -> {error, Reason} @@ -278,11 +340,15 @@ extract_client_final_no_proof(ClientIn) -> %%%=================================================================== match_rules(Inputs, Rules, State) -> lists:foldl( - fun(_, {error, Reason}) -> - {error, Reason}; - (Input, {RulesLeft, St}) -> - apply_rules_until_match(Input, RulesLeft, St) - end, {Rules, State}, Inputs). + fun + (_, {error, Reason}) -> + {error, Reason}; + (Input, {RulesLeft, St}) -> + apply_rules_until_match(Input, RulesLeft, St) + end, + {Rules, State}, + Inputs + ). apply_rules_until_match(_, [], _) -> {error, <<"error-too-much-input">>}; @@ -327,6 +393,7 @@ server_key(Sha, SaltedPassword) -> server_signature(Sha, ServerKey, AuthMessage) -> fast_scram_definitions:server_signature(Sha, ServerKey, AuthMessage). +%%% @doc See `Hi(str, salt, i)' at [https://datatracker.ietf.org/doc/html/rfc5802#section-2.2] -spec hi(sha_type(), binary(), binary(), non_neg_integer()) -> binary(). hi(Hash, Password, Salt, IterationCount) -> fast_scram_definitions:salted_password(Hash, Password, Salt, IterationCount). diff --git a/src/fast_scram.hrl b/src/fast_scram.hrl new file mode 100644 index 0000000..217d37b --- /dev/null +++ b/src/fast_scram.hrl @@ -0,0 +1,57 @@ +-include_lib("kernel/include/logger.hrl"). + +-define(IS_POSITIVE_INTEGER(N), is_integer(N) andalso N > 0). +-define(IS_VALID_HASH(H), + H =:= sha orelse + H =:= sha224 orelse H =:= sha256 orelse + H =:= sha384 orelse H =:= sha512 orelse + H =:= sha3_512 +). + +-record(nonce, { + client = <<>> :: binary(), + server = <<>> :: binary() +}). + +-record(challenge, { + salt = <<>> :: binary(), + it_count = 1 :: pos_integer() +}). + +-record(channel_binding, { + variant = undefined :: fast_scram:plus_variant(), + data = <<>> :: binary() +}). + +-record(scram_definitions, { + hash_method :: fast_scram:sha_type(), + %Hi(Normalize(password), salt, i), + salted_password = <<>> :: binary(), + %HMAC(SaltedPassword, "Client Key"), + client_key = <<>> :: binary(), + %H(ClientKey), + stored_key = <<>> :: binary(), + %client-first-message-bare + "," + + auth_message = <<>> :: binary(), + %server-first-message + "," + + %client-final-message-without-proof + + %HMAC(StoredKey, AuthMessage), + client_signature = <<>> :: binary(), + %ClientKey XOR ClientSignature, + client_proof = <<>> :: binary(), + %HMAC(SaltedPassword, "Server Key"), + server_key = <<>> :: binary(), + %HMAC(ServerKey, AuthMessage) + server_signature = <<>> :: binary() +}). + +-record(fast_scram_state, { + %% Steps 1 & 3 are client, 2 & 4 are server + step :: 1..6, + nonce = #nonce{} :: fast_scram:nonce(), + challenge = #challenge{} :: fast_scram:challenge(), + channel_binding = #channel_binding{} :: fast_scram:channel_binding(), + scram_definitions = #scram_definitions{} :: fast_scram:definitions(), + data = #{} :: map() +}). diff --git a/src/fast_scram_attributes.erl b/src/fast_scram_attributes.erl index 8cf7185..a5a3da3 100644 --- a/src/fast_scram_attributes.erl +++ b/src/fast_scram_attributes.erl @@ -1,19 +1,21 @@ +%% @private +%% @see fast_scram -module(fast_scram_attributes). --include("types.hrl"). +-include("fast_scram.hrl"). -export([ - reserved_scram_codes/0, - gs2_header/2, - nonce/1, - cbind_input/2, - client_first_message_bare/2, - client_final_message_without_proof/3, - server_first_message/2, - client_first_message/3, - client_final_message/2, - server_final_message/1 - ]). + reserved_scram_codes/0, + gs2_header/2, + nonce/1, + cbind_input/2, + client_first_message_bare/2, + client_final_message_without_proof/3, + server_first_message/2, + client_first_message/3, + client_final_message/2, + server_final_message/1 +]). %%%=================================================================== %%% SCRAM attributes @@ -31,10 +33,17 @@ % 'v=' | % base64 ServerSignature % 'e='. % error that occurred during auth reserved_scram_codes() -> - [<<"p">>, <<"n">>, <<"r">>, - <<"c">>, <<"s">>, <<"i">>, - <<"a">>, <<"v">>, <<"e">>]. - + [ + <<"p">>, + <<"n">>, + <<"r">>, + <<"c">>, + <<"s">>, + <<"i">>, + <<"a">>, + <<"v">>, + <<"e">> + ]. % gs2-cbind-flag = ("p=" cb-name) / "n" / "y" % ;; "n" -> client doesn't support channel binding. @@ -62,8 +71,7 @@ authzid(ID) when is_binary(ID) -> % ;; don't include that flag). gs2_header(#channel_binding{variant = Variant}, Data) -> AuthZID = maps:get(auth_zid, Data, <<>>), - <<(gs2_cbind_flag(Variant))/binary, ",", - (authzid(AuthZID))/binary, ",">>. + <<(gs2_cbind_flag(Variant))/binary, ",", (authzid(AuthZID))/binary, ",">>. % reserved-mext = "m=" 1*(value-char) % ;; Reserved for signaling mandatory extensions. @@ -96,7 +104,7 @@ salt(Salt) -> % iteration-count = "i=" posit-number % ;; A positive number. -iteration_count(IterationCount) when ?is_positive_integer(IterationCount) -> +iteration_count(IterationCount) when ?IS_POSITIVE_INTEGER(IterationCount) -> <<"i=", (integer_to_binary(IterationCount))/binary>>. % channel-binding = "c=" base64 @@ -108,7 +116,7 @@ cbind_input(GS2Header, CBindData) -> base64:encode(<>). % proof = "p=" base64 -proof(Proof)-> +proof(Proof) -> <<"p=", (base64:encode(Proof))/binary>>. %%%=================================================================== @@ -117,23 +125,25 @@ proof(Proof)-> % client-first-message-bare = % [reserved-mext ","] % username "," nonce ["," extensions] --spec client_first_message_bare(map(), nonce()) -> binary(). -client_first_message_bare(#{username := Username}, Nonce)-> - <<(reserved_mext())/binary, - (username(Username))/binary, ",", - (nonce(Nonce))/binary, - (extensions())/binary>>. +-spec client_first_message_bare(map(), fast_scram:nonce()) -> binary(). +client_first_message_bare(#{username := Username}, Nonce) -> + << + (reserved_mext())/binary, + (username(Username))/binary, + ",", + (nonce(Nonce))/binary, + (extensions())/binary + >>. % client-final-message-without-proof = % channel-binding "," nonce ["," extensions] -client_final_message_without_proof(CBConfig, Nonce, Data) -> - <<(channel_binding(CBConfig, Data))/binary, ",", - (nonce(Nonce))/binary, - (extensions())/binary>>. +client_final_message_without_proof(CbConfig, Nonce, Data) -> + <<(channel_binding(CbConfig, Data))/binary, ",", (nonce(Nonce))/binary, (extensions())/binary>>. % client-first-message = % gs2-header client-first-message-bare --spec client_first_message(channel_binding(), nonce(), map()) -> {binary(), binary()}. +-spec client_first_message(fast_scram:channel_binding(), fast_scram:nonce(), map()) -> + {binary(), binary()}. client_first_message(CbConfig, Nonce, Data) -> GS2Header = gs2_header(CbConfig, Data), ClientFirstMessageBare = client_first_message_bare(Data, Nonce), @@ -143,17 +153,20 @@ client_first_message(CbConfig, Nonce, Data) -> % [reserved-mext ","] nonce "," salt "," % iteration-count ["," extensions] server_first_message(Nonce, #challenge{salt = Salt, it_count = IterationCount}) -> - <<(reserved_mext())/binary, - (nonce(Nonce))/binary, ",", - (salt(Salt))/binary, ",", - (iteration_count(IterationCount))/binary, - (extensions())/binary>>. + << + (reserved_mext())/binary, + (nonce(Nonce))/binary, + ",", + (salt(Salt))/binary, + ",", + (iteration_count(IterationCount))/binary, + (extensions())/binary + >>. % client-final-message = % client-final-message-without-proof "," proof client_final_message(ClientFinalNoProof, ClientProof) -> - <>. + <>. % server-error = "e=" server-error-value diff --git a/src/fast_scram_configuration.erl b/src/fast_scram_configuration.erl index 3149d3b..834bdef 100644 --- a/src/fast_scram_configuration.erl +++ b/src/fast_scram_configuration.erl @@ -1,43 +1,58 @@ +%% @private +%% @see fast_scram -module(fast_scram_configuration). --include("types.hrl"). +-include("fast_scram.hrl"). -define(DEFAULT_NONCE_SIZE, 16). --define(f, #fast_scram_state). -export([mech_new/1, mech_append/2]). % We first match that the strictly required is available -mech_new(#{entity := client, - hash_method := HashMethod, - username := _, - auth_data := AuthData - } = Config) -> - St = ?f{step = 1, scram_definitions = #scram_definitions{hash_method = HashMethod}}, +mech_new( + #{ + entity := client, + hash_method := HashMethod, + username := _, + auth_data := AuthData + } = Config +) -> + St = #fast_scram_state{ + step = 1, scram_definitions = #scram_definitions{hash_method = HashMethod} + }, Res = build_state(St, AuthData, Config), maybe_tag_ok(Res); -mech_new(#{entity := server, - hash_method := HashMethod, - retrieve_mechanism := Fun - } = Config) - when is_function(Fun, 1); is_function(Fun, 2) -> - St = ?f{step = 2, scram_definitions = #scram_definitions{hash_method = HashMethod}}, +mech_new( + #{ + entity := server, + hash_method := HashMethod, + retrieve_mechanism := Fun + } = Config +) when + is_function(Fun, 1); is_function(Fun, 2) +-> + St = #fast_scram_state{ + step = 2, scram_definitions = #scram_definitions{hash_method = HashMethod} + }, Config1 = ensure_full_config(St, Config), Res = maps:fold(fun set_val_in_state/3, St, Config1), maybe_tag_ok(Res); mech_new(_) -> {error, <<"Wrong configuration">>}. --spec maybe_tag_ok(fast_scram_state()) -> {ok, fast_scram_state()}; - ({error, T1, T2}) -> {error, T1, T2} - when T1 :: term(), T2 :: term(). -maybe_tag_ok(?f{} = St) -> +-spec maybe_tag_ok + (fast_scram:state()) -> {ok, fast_scram:state()}; + ({error, T1, T2}) -> {error, T1, T2} when + T1 :: term(), T2 :: term(). +maybe_tag_ok(#fast_scram_state{} = St) -> {ok, St}; maybe_tag_ok(Error) -> Error. --spec mech_append(fast_scram_state(), configuration()) -> - fast_scram_state() | {error, binary()}. -mech_append(?f{step = 2} = St, #{it_count := _, salt := _, auth_data := AuthData} = Config) -> +-spec mech_append(fast_scram:state(), fast_scram:configuration()) -> + fast_scram:state() | {error, binary()}. +mech_append( + #fast_scram_state{step = 2} = St, #{it_count := _, salt := _, auth_data := AuthData} = Config +) -> build_state(St, AuthData, Config); mech_append(_, _) -> {error, <<"Wrong configuration">>}. @@ -52,13 +67,14 @@ build_state(St, AuthData, Config) -> {error, <<"Invalid authentication configuration">>} end. -ensure_full_config(?f{nonce = #nonce{client = C, server = S}}, Config) - when C == <<>>, S == <<>> -> +ensure_full_config(#fast_scram_state{nonce = #nonce{client = C, server = S}}, Config) when + C == <<>>, S == <<>> +-> case (not maps:is_key(nonce_size, Config) andalso not maps:is_key(nonce, Config)) of true -> Config#{nonce_size => ?DEFAULT_NONCE_SIZE}; _ -> Config end; -ensure_full_config(?f{}, Config) -> +ensure_full_config(#fast_scram_state{}, Config) -> Config. % It will get just a combination of the given atoms and verify that they are the exact one @@ -70,7 +86,7 @@ ensure_full_config(?f{}, Config) -> % salted_password % stored_key & server_key % client_key & server_key --spec verify_mandatory_scram_data([auth_keys()]) -> boolean(). +-spec verify_mandatory_scram_data([fast_scram:auth_keys()]) -> boolean(). verify_mandatory_scram_data(List) -> case lists:sort(List) of [password] -> true; @@ -86,72 +102,89 @@ verify_mandatory_scram_data(List) -> %% @doc This only adds a key into the state, verifying typeness, but not if it is repeated. -type option() :: atom(). -type value() :: term(). --spec set_val_in_state(option(), value(), fast_scram_state()) -> - fast_scram_state() | {error, atom(), term()}. -set_val_in_state(entity, Ent, ?f{} = St) when is_atom(Ent) -> +-spec set_val_in_state(option(), value(), fast_scram:state()) -> + fast_scram:state() | {error, atom(), term()}. +set_val_in_state(entity, Ent, #fast_scram_state{} = St) when is_atom(Ent) -> case Ent of - client -> St?f{step = 1}; - server -> St?f{step = 2} + client -> St#fast_scram_state{step = 1}; + server -> St#fast_scram_state{step = 2} end; - -set_val_in_state(nonce_size, Num, ?f{} = St) when ?is_positive_integer(Num) -> +set_val_in_state(nonce_size, Num, #fast_scram_state{step = 1} = St) when + ?IS_POSITIVE_INTEGER(Num) +-> Bin = base64:encode(crypto:strong_rand_bytes(Num)), - case St?f.step of - 1 -> St?f{nonce = #nonce{client = Bin}}; - 2 -> St?f{nonce = #nonce{server = Bin}} - end; -set_val_in_state(nonce, Bin, ?f{} = St) when is_binary(Bin) -> - case St?f.step of - 1 -> St?f{nonce = #nonce{client = Bin}}; - 2 -> St?f{nonce = #nonce{server = Bin}} - end; - -set_val_in_state(it_count, Num, ?f{challenge = Ch} = St) when ?is_positive_integer(Num) -> - St?f{challenge = Ch#challenge{it_count = Num}}; -set_val_in_state(salt, Bin, ?f{challenge = Ch} = St) when is_binary(Bin) -> - St?f{challenge = Ch#challenge{salt = Bin}}; - -set_val_in_state(channel_binding, {Type, Data}, ?f{channel_binding = CB} = St) - when is_atom(Type) orelse is_binary(Type), is_binary(Data) -> - St?f{channel_binding = CB#channel_binding{variant = Type, data = Data}}; - -set_val_in_state(hash_method, HM, ?f{scram_definitions = SD} = St) when ?is_valid_hash(HM) -> - St?f{scram_definitions = SD#scram_definitions{hash_method = HM}}; -set_val_in_state(salted_password, Bin, ?f{scram_definitions = SD} = St) when is_binary(Bin) -> - St?f{scram_definitions = SD#scram_definitions{salted_password = Bin}}; -set_val_in_state(client_key, Bin, ?f{scram_definitions = SD} = St) when is_binary(Bin) -> - St?f{scram_definitions = SD#scram_definitions{client_key = Bin}}; -set_val_in_state(stored_key, Bin, ?f{scram_definitions = SD} = St) when is_binary(Bin) -> - St?f{scram_definitions = SD#scram_definitions{stored_key = Bin}}; -set_val_in_state(client_signature, Bin, ?f{scram_definitions = SD} = St) when is_binary(Bin) -> - St?f{scram_definitions = SD#scram_definitions{client_signature = Bin}}; -set_val_in_state(client_proof, Bin, ?f{scram_definitions = SD} = St) when is_binary(Bin) -> - St?f{scram_definitions = SD#scram_definitions{client_proof = Bin}}; -set_val_in_state(server_key, Bin, ?f{scram_definitions = SD} = St) when is_binary(Bin) -> - St?f{scram_definitions = SD#scram_definitions{server_key = Bin}}; -set_val_in_state(server_signature, Bin, ?f{scram_definitions = SD} = St) when is_binary(Bin) -> - St?f{scram_definitions = SD#scram_definitions{server_signature = Bin}}; - + St#fast_scram_state{nonce = #nonce{client = Bin}}; +set_val_in_state(nonce_size, Num, #fast_scram_state{step = 2} = St) when + ?IS_POSITIVE_INTEGER(Num) +-> + Bin = base64:encode(crypto:strong_rand_bytes(Num)), + St#fast_scram_state{nonce = #nonce{server = Bin}}; +set_val_in_state(nonce, Bin, #fast_scram_state{step = 1} = St) when is_binary(Bin) -> + St#fast_scram_state{nonce = #nonce{client = Bin}}; +set_val_in_state(nonce, Bin, #fast_scram_state{step = 2} = St) when is_binary(Bin) -> + St#fast_scram_state{nonce = #nonce{server = Bin}}; +set_val_in_state(it_count, Num, #fast_scram_state{challenge = Ch} = St) when + ?IS_POSITIVE_INTEGER(Num) +-> + St#fast_scram_state{challenge = Ch#challenge{it_count = Num}}; +set_val_in_state(salt, Bin, #fast_scram_state{challenge = Ch} = St) when is_binary(Bin) -> + St#fast_scram_state{challenge = Ch#challenge{salt = Bin}}; +set_val_in_state(channel_binding, {Type, Data}, #fast_scram_state{channel_binding = CB} = St) when + is_atom(Type) orelse is_binary(Type), is_binary(Data) +-> + St#fast_scram_state{channel_binding = CB#channel_binding{variant = Type, data = Data}}; +set_val_in_state(hash_method, HM, #fast_scram_state{scram_definitions = SD} = St) when + ?IS_VALID_HASH(HM) +-> + St#fast_scram_state{scram_definitions = SD#scram_definitions{hash_method = HM}}; +set_val_in_state(salted_password, Bin, #fast_scram_state{scram_definitions = SD} = St) when + is_binary(Bin) +-> + St#fast_scram_state{scram_definitions = SD#scram_definitions{salted_password = Bin}}; +set_val_in_state(client_key, Bin, #fast_scram_state{scram_definitions = SD} = St) when + is_binary(Bin) +-> + St#fast_scram_state{scram_definitions = SD#scram_definitions{client_key = Bin}}; +set_val_in_state(stored_key, Bin, #fast_scram_state{scram_definitions = SD} = St) when + is_binary(Bin) +-> + St#fast_scram_state{scram_definitions = SD#scram_definitions{stored_key = Bin}}; +set_val_in_state(client_signature, Bin, #fast_scram_state{scram_definitions = SD} = St) when + is_binary(Bin) +-> + St#fast_scram_state{scram_definitions = SD#scram_definitions{client_signature = Bin}}; +set_val_in_state(client_proof, Bin, #fast_scram_state{scram_definitions = SD} = St) when + is_binary(Bin) +-> + St#fast_scram_state{scram_definitions = SD#scram_definitions{client_proof = Bin}}; +set_val_in_state(server_key, Bin, #fast_scram_state{scram_definitions = SD} = St) when + is_binary(Bin) +-> + St#fast_scram_state{scram_definitions = SD#scram_definitions{server_key = Bin}}; +set_val_in_state(server_signature, Bin, #fast_scram_state{scram_definitions = SD} = St) when + is_binary(Bin) +-> + St#fast_scram_state{scram_definitions = SD#scram_definitions{server_signature = Bin}}; %% Stuff to data -set_val_in_state(username, UN, ?f{data = PD} = St) when is_binary(UN) -> - St?f{data = PD#{username => UN}}; -set_val_in_state(password, PW, ?f{data = PD} = St) when is_binary(PW) -> - St?f{data = PD#{password => PW}}; - -set_val_in_state(cached_challenge, {It, Salt}, ?f{data = PD} = St) - when ?is_positive_integer(It), is_binary(Salt) -> +set_val_in_state(username, UN, #fast_scram_state{data = PD} = St) when is_binary(UN) -> + St#fast_scram_state{data = PD#{username => UN}}; +set_val_in_state(password, PW, #fast_scram_state{data = PD} = St) when is_binary(PW) -> + St#fast_scram_state{data = PD#{password => PW}}; +set_val_in_state(cached_challenge, {It, Salt}, #fast_scram_state{data = PD} = St) when + ?IS_POSITIVE_INTEGER(It), is_binary(Salt) +-> Challenge = #challenge{it_count = It, salt = Salt}, - St?f{data = PD#{challenge => Challenge}}; -set_val_in_state(cached_challenge, {Salt, It}, ?f{data = PD} = St) - when ?is_positive_integer(It), is_binary(Salt) -> + St#fast_scram_state{data = PD#{challenge => Challenge}}; +set_val_in_state(cached_challenge, {Salt, It}, #fast_scram_state{data = PD} = St) when + ?IS_POSITIVE_INTEGER(It), is_binary(Salt) +-> Challenge = #challenge{it_count = It, salt = Salt}, - St?f{data = PD#{challenge => Challenge}}; - -set_val_in_state(retrieve_mechanism, Fun, ?f{data = PD} = St) - when is_function(Fun, 1); is_function(Fun, 2) -> - St?f{data = PD#{retrieve_mechanism => Fun}}; - -set_val_in_state(WrongKey, _, ?f{}) -> + St#fast_scram_state{data = PD#{challenge => Challenge}}; +set_val_in_state(retrieve_mechanism, Fun, #fast_scram_state{data = PD} = St) when + is_function(Fun, 1); is_function(Fun, 2) +-> + St#fast_scram_state{data = PD#{retrieve_mechanism => Fun}}; +set_val_in_state(WrongKey, _, #fast_scram_state{}) -> {error, wrong_key, WrongKey}; set_val_in_state(_, _, {error, wrong_key, _} = Error) -> Error. diff --git a/src/fast_scram_definitions.erl b/src/fast_scram_definitions.erl index aa8198f..d6ba614 100644 --- a/src/fast_scram_definitions.erl +++ b/src/fast_scram_definitions.erl @@ -1,21 +1,23 @@ +%% @private +%% @see fast_scram -module(fast_scram_definitions). --include("types.hrl"). +-include("fast_scram.hrl"). -export([ - salted_password/4, - client_key/2, - stored_key/2, - client_signature/3, - client_proof/2, - server_key/2, - server_signature/3 - ]). + salted_password/4, + client_key/2, + stored_key/2, + client_signature/3, + client_proof/2, + server_key/2, + server_signature/3 +]). -export([ - scram_definitions_pipe/3, - check_proof/2 - ]). + scram_definitions_pipe/3, + check_proof/2 +]). %%%=================================================================== %%% SCRAM Definitions @@ -32,46 +34,54 @@ %% ServerSignature := HMAC(ServerKey, AuthMessage) -ifdef(WITHOUT_NIFS). --spec salted_password(sha_type(), binary(), binary(), non_neg_integer()) -> binary(). -salted_password(Sha, Password, Salt, IterationCount) - when ?is_valid_hash(Sha), is_binary(Password), is_binary(Salt), ?is_positive_integer(IterationCount) -> +-spec salted_password(fast_scram:sha_type(), binary(), binary(), non_neg_integer()) -> binary(). +salted_password(Sha, Password, Salt, IterationCount) when + ?IS_VALID_HASH(Sha), is_binary(Password), is_binary(Salt), ?IS_POSITIVE_INTEGER(IterationCount) +-> #{size := KeyLength} = crypto:hash_info(Sha), crypto:pbkdf2_hmac(Sha, Password, Salt, IterationCount, KeyLength). -else. --spec salted_password(sha_type(), binary(), binary(), non_neg_integer()) -> binary(). -salted_password(Sha, Password, Salt, IterationCount) - when ?is_valid_hash(Sha), is_binary(Password), is_binary(Salt), ?is_positive_integer(IterationCount) -> +-spec salted_password(fast_scram:sha_type(), binary(), binary(), non_neg_integer()) -> binary(). +salted_password(Sha, Password, Salt, IterationCount) when + ?IS_VALID_HASH(Sha), is_binary(Password), is_binary(Salt), ?IS_POSITIVE_INTEGER(IterationCount) +-> fast_pbkdf2:pbkdf2(Sha, Password, Salt, IterationCount). -endif. --spec client_key(sha_type(), binary()) -> binary(). -client_key(Sha, SaltedPassword) - when ?is_valid_hash(Sha), is_binary(SaltedPassword) -> +-spec client_key(fast_scram:sha_type(), binary()) -> binary(). +client_key(Sha, SaltedPassword) when + ?IS_VALID_HASH(Sha), is_binary(SaltedPassword) +-> crypto_hmac(Sha, SaltedPassword, <<"Client Key">>). --spec stored_key(sha_type(), binary()) -> binary(). -stored_key(Sha, ClientKey) - when ?is_valid_hash(Sha), is_binary(ClientKey) -> +-spec stored_key(fast_scram:sha_type(), binary()) -> binary(). +stored_key(Sha, ClientKey) when + ?IS_VALID_HASH(Sha), is_binary(ClientKey) +-> crypto:hash(Sha, ClientKey). --spec client_signature(sha_type(), binary(), binary()) -> binary(). -client_signature(Sha, StoredKey, AuthMessage) - when ?is_valid_hash(Sha), is_binary(StoredKey), is_binary(AuthMessage) -> +-spec client_signature(fast_scram:sha_type(), binary(), binary()) -> binary(). +client_signature(Sha, StoredKey, AuthMessage) when + ?IS_VALID_HASH(Sha), is_binary(StoredKey), is_binary(AuthMessage) +-> crypto_hmac(Sha, StoredKey, AuthMessage). -spec client_proof(binary(), binary()) -> binary(). -client_proof(ClientKey, ClientSignature) - when is_binary(ClientKey), is_binary(ClientSignature) -> +client_proof(ClientKey, ClientSignature) when + is_binary(ClientKey), is_binary(ClientSignature) +-> crypto:exor(ClientKey, ClientSignature). --spec server_key(sha_type(), binary()) -> binary(). -server_key(Sha, SaltedPassword) - when ?is_valid_hash(Sha), is_binary(SaltedPassword) -> +-spec server_key(fast_scram:sha_type(), binary()) -> binary(). +server_key(Sha, SaltedPassword) when + ?IS_VALID_HASH(Sha), is_binary(SaltedPassword) +-> crypto_hmac(Sha, SaltedPassword, <<"Server Key">>). --spec server_signature(sha_type(), binary(), binary()) -> binary(). -server_signature(Sha, ServerKey, AuthMessage) - when ?is_valid_hash(Sha), is_binary(ServerKey), is_binary(AuthMessage) -> +-spec server_signature(fast_scram:sha_type(), binary(), binary()) -> binary(). +server_signature(Sha, ServerKey, AuthMessage) when + ?IS_VALID_HASH(Sha), is_binary(ServerKey), is_binary(AuthMessage) +-> crypto_hmac(Sha, ServerKey, AuthMessage). -ifdef(OTP_RELEASE). @@ -87,15 +97,18 @@ crypto_hmac(Sha, Bin1, Bin2) -> crypto:hmac(Sha, Bin1, Bin2). -endif. --spec scram_definitions_pipe(scram_definitions(), challenge(), map()) -> - scram_definitions(). +-spec scram_definitions_pipe(fast_scram:definitions(), fast_scram:challenge(), map()) -> + fast_scram:definitions(). %% Typical scenario, auth_message is full and we only have the plain password scram_definitions_pipe( - #scram_definitions{hash_method = HashMethod, - salted_password = <<>>, - auth_message = AuthMessage} = Scram, - #challenge{salt = Salt, it_count = ItCount}, - #{password := Password}) when AuthMessage =/= <<>> -> + #scram_definitions{ + hash_method = HashMethod, + salted_password = <<>>, + auth_message = AuthMessage + } = Scram, + #challenge{salt = Salt, it_count = ItCount}, + #{password := Password} +) when AuthMessage =/= <<>> -> SaltedPassword = salted_password(HashMethod, Password, Salt, ItCount), ClientKey = client_key(HashMethod, SaltedPassword), StoredKey = stored_key(HashMethod, ClientKey), @@ -104,62 +117,75 @@ scram_definitions_pipe( ServerKey = server_key(HashMethod, SaltedPassword), ServerSignature = server_signature(HashMethod, ServerKey, AuthMessage), Scram#scram_definitions{ - salted_password = SaltedPassword, - client_key = ClientKey, - stored_key = StoredKey, - auth_message = AuthMessage, - client_signature = ClientSignature, - client_proof = ClientProof, - server_key = ServerKey, - server_signature = ServerSignature - }; + salted_password = SaltedPassword, + client_key = ClientKey, + stored_key = StoredKey, + auth_message = AuthMessage, + client_signature = ClientSignature, + client_proof = ClientProof, + server_key = ServerKey, + server_signature = ServerSignature + }; %% We have a cached challenge, we need to verify if it is correct scram_definitions_pipe( - #scram_definitions{hash_method = HashMethod, auth_message = AuthMessage} = Scram, - #challenge{} = GivenChallenge, - #{challenge := StoredChallenge} = Data) -> + #scram_definitions{hash_method = HashMethod, auth_message = AuthMessage} = Scram, + #challenge{} = GivenChallenge, + #{challenge := StoredChallenge} = Data +) -> case GivenChallenge =:= StoredChallenge of - true -> % This means that the client has cached the challenge correctly + % This means that the client has cached the challenge correctly + true -> partial_compute(Scram); - false -> % Invalid cache, remove all knowledge and try again - ScramWithoutCached = #scram_definitions{hash_method = HashMethod, auth_message = AuthMessage}, + % Invalid cache, remove all knowledge and try again + false -> + ScramWithoutCached = #scram_definitions{ + hash_method = HashMethod, auth_message = AuthMessage + }, DataWithoutCached = maps:remove(challenge, Data), scram_definitions_pipe(ScramWithoutCached, GivenChallenge, DataWithoutCached) end; scram_definitions_pipe(Scram, _, _) -> partial_compute(Scram). -partial_compute(Scram = - #scram_definitions{ - hash_method = HashMethod, auth_message = AuthMessage, - client_key = ClientKey, server_key = ServerKey - }) when ClientKey =/= <<>>, ServerKey =/= <<>> -> +partial_compute( + #scram_definitions{ + hash_method = HashMethod, + auth_message = AuthMessage, + client_key = ClientKey, + server_key = ServerKey + } = Scram +) when ClientKey =/= <<>>, ServerKey =/= <<>> -> StoredKey = stored_key(HashMethod, ClientKey), ClientSignature = client_signature(HashMethod, StoredKey, AuthMessage), ClientProof = client_proof(ClientKey, ClientSignature), ServerSignature = server_signature(HashMethod, ServerKey, AuthMessage), Scram#scram_definitions{ - stored_key = StoredKey, - client_signature = ClientSignature, - client_proof = ClientProof, - server_signature = ServerSignature - }; -partial_compute(Scram = - #scram_definitions{ - hash_method = HashMethod, auth_message = AuthMessage, - stored_key = StoredKey, server_key = ServerKey - }) when StoredKey =/= <<>>, ServerKey =/= <<>> -> + stored_key = StoredKey, + client_signature = ClientSignature, + client_proof = ClientProof, + server_signature = ServerSignature + }; +partial_compute( + #scram_definitions{ + hash_method = HashMethod, + auth_message = AuthMessage, + stored_key = StoredKey, + server_key = ServerKey + } = Scram +) when StoredKey =/= <<>>, ServerKey =/= <<>> -> ClientSignature = client_signature(HashMethod, StoredKey, AuthMessage), ServerSignature = server_signature(HashMethod, ServerKey, AuthMessage), Scram#scram_definitions{ - client_signature = ClientSignature, - server_signature = ServerSignature - }; -partial_compute(Scram = - #scram_definitions{ - hash_method = HashMethod, auth_message = AuthMessage, - salted_password = SaltedPassword - }) when SaltedPassword =/= <<>> -> + client_signature = ClientSignature, + server_signature = ServerSignature + }; +partial_compute( + #scram_definitions{ + hash_method = HashMethod, + auth_message = AuthMessage, + salted_password = SaltedPassword + } = Scram +) when SaltedPassword =/= <<>> -> ClientKey = client_key(HashMethod, SaltedPassword), StoredKey = stored_key(HashMethod, ClientKey), ClientSignature = client_signature(HashMethod, StoredKey, AuthMessage), @@ -167,25 +193,31 @@ partial_compute(Scram = ServerKey = server_key(HashMethod, SaltedPassword), ServerSignature = server_signature(HashMethod, ServerKey, AuthMessage), Scram#scram_definitions{ - client_key = ClientKey, - stored_key = StoredKey, - client_signature = ClientSignature, - client_proof = ClientProof, - server_key = ServerKey, - server_signature = ServerSignature - }; + client_key = ClientKey, + stored_key = StoredKey, + client_signature = ClientSignature, + client_proof = ClientProof, + server_key = ServerKey, + server_signature = ServerSignature + }; partial_compute(Scram) -> ?LOG_DEBUG(#{what => scram_no_pipe_match}), Scram. --spec check_proof(scram_definitions(), binary()) -> ok | {error, binary()}. -check_proof(#scram_definitions{client_proof = CalculatedClientProof}, GivenClientProof) - when CalculatedClientProof =:= GivenClientProof -> +-spec check_proof(fast_scram:definitions(), binary()) -> ok | {error, binary()}. +check_proof(#scram_definitions{client_proof = CalculatedClientProof}, GivenClientProof) when + CalculatedClientProof =:= GivenClientProof +-> ok; -check_proof(#scram_definitions{hash_method = HashMethod, - client_proof = <<>>, - stored_key = StoredKey, - client_signature = ClientSignature}, GivenClientProof) -> +check_proof( + #scram_definitions{ + hash_method = HashMethod, + client_proof = <<>>, + stored_key = StoredKey, + client_signature = ClientSignature + }, + GivenClientProof +) -> ClientKey = client_proof(GivenClientProof, ClientSignature), CalculatedStoredKey = stored_key(HashMethod, ClientKey), case CalculatedStoredKey =:= StoredKey of diff --git a/src/fast_scram_parse_rules.erl b/src/fast_scram_parse_rules.erl index 416a6f3..779bf8f 100644 --- a/src/fast_scram_parse_rules.erl +++ b/src/fast_scram_parse_rules.erl @@ -1,26 +1,34 @@ +%% @private +%% @see fast_scram -module(fast_scram_parse_rules). --include("types.hrl"). +-include("fast_scram.hrl"). + +-type parse_return() :: + {ok, fast_scram:state()} + | {skip_rule, fast_scram:state()} + | {error, binary()}. +-export_type([parse_return/0]). -export([ - parse_gs2_cbind_flag/2, - parse_authzid/2, - parse_username/2, - parse_nonce/2, - parse_salt/2, - parse_iteration_count/2, - parse_reserved_mext/2, - parse_extensions/2, - parse_proof/2, - parse_channel_binding/2, - parse_server_error_or_verifier/2 - ]). + parse_gs2_cbind_flag/2, + parse_authzid/2, + parse_username/2, + parse_nonce/2, + parse_salt/2, + parse_iteration_count/2, + parse_reserved_mext/2, + parse_extensions/2, + parse_proof/2, + parse_channel_binding/2, + parse_server_error_or_verifier/2 +]). -export([ - append_to_auth_message/2, - append_to_auth_message_in_state/2 - ]). + append_to_auth_message/2, + append_to_auth_message_in_state/2 +]). --spec parse_gs2_cbind_flag(binary(), fast_scram_state()) -> parse_return(). +-spec parse_gs2_cbind_flag(binary(), fast_scram:state()) -> parse_return(). parse_gs2_cbind_flag(<<>>, _State) -> {error, <<"no-resources">>}; parse_gs2_cbind_flag(CBind, #fast_scram_state{channel_binding = CbConfig} = State) -> @@ -32,7 +40,7 @@ parse_gs2_cbind_flag(CBind, #fast_scram_state{channel_binding = CbConfig} = Stat {error, Reason} end. --spec parse_authzid(binary(), fast_scram_state()) -> parse_return(). +-spec parse_authzid(binary(), fast_scram:state()) -> parse_return(). parse_authzid(<<>>, State) -> {ok, State}; parse_authzid(<<"a=", _Rest/binary>>, _State) -> @@ -44,17 +52,17 @@ parse_authzid(_, _State) -> % other MUST cause authentication failure (the server SHOULD send % the "extensions-not-supported" server-error-value). % Unknown optional extensions MUST be ignored upon receipt. --spec parse_reserved_mext(binary(), fast_scram_state()) -> parse_return(). +-spec parse_reserved_mext(binary(), fast_scram:state()) -> parse_return(). parse_reserved_mext(<<"m=", _/binary>>, _State) -> {error, <<"extensions-not-supported">>}; parse_reserved_mext(_, State) -> {skip_rule, State}. --spec parse_username(binary(), fast_scram_state()) -> parse_return(). +-spec parse_username(binary(), fast_scram:state()) -> parse_return(). parse_username(<<>>, _State) -> {error, <<"unknown-user">>}; parse_username(<<"n=", UnescapedUsername/binary>>, #fast_scram_state{data = Data} = State) -> - case replace_2c_3d(UnescapedUsername) of + case replace_escaped_chars(UnescapedUsername) of {ok, EscapedUsername} -> case maps:get(username, Data, undefined) of CachedName when is_binary(CachedName), CachedName =/= EscapedUsername -> @@ -63,12 +71,13 @@ parse_username(<<"n=", UnescapedUsername/binary>>, #fast_scram_state{data = Data NewData = Data#{username => EscapedUsername}, {ok, State#fast_scram_state{data = NewData}} end; - E -> E + E -> + E end; parse_username(_, _) -> {error, <<"other-error">>}. --spec parse_nonce(binary(), fast_scram_state()) -> parse_return(). +-spec parse_nonce(binary(), fast_scram:state()) -> parse_return(). parse_nonce(<<>>, _State) -> {error, <<"no-resources">>}; parse_nonce(<<"r=", Nonce/binary>>, State) -> @@ -81,7 +90,7 @@ parse_nonce(<<"r=", Nonce/binary>>, State) -> parse_nonce(_, _) -> {error, <<"other-error">>}. --spec parse_salt(binary(), fast_scram_state()) -> parse_return(). +-spec parse_salt(binary(), fast_scram:state()) -> parse_return(). parse_salt(<<>>, _State) -> {error, <<"no-resources">>}; parse_salt(<<"s=", Salt0/binary>>, State) -> @@ -95,39 +104,46 @@ parse_salt(<<"s=", Salt0/binary>>, State) -> parse_salt(_, _) -> {error, <<"other-error">>}. --spec parse_iteration_count(binary(), fast_scram_state()) -> parse_return(). +-spec parse_iteration_count(binary(), fast_scram:state()) -> parse_return(). parse_iteration_count(<<>>, _State) -> {error, <<"no-resources">>}; parse_iteration_count(<<"i=", ItCount/binary>>, State) -> Challenge = State#fast_scram_state.challenge, - case catch binary_to_integer(ItCount) of - It when ?is_positive_integer(It) -> - {ok, State#fast_scram_state{challenge = Challenge#challenge{it_count = It}}}; - _ -> + try binary_to_integer(ItCount) of + It when ?IS_POSITIVE_INTEGER(It) -> + {ok, State#fast_scram_state{challenge = Challenge#challenge{it_count = It}}} + catch + _:_ -> {error, <<"invalid-iteration-count">>} end; parse_iteration_count(_, _) -> {error, <<"other-error">>}. --spec parse_extensions(binary(), fast_scram_state()) -> parse_return(). +-spec parse_extensions(binary(), fast_scram:state()) -> parse_return(). parse_extensions(<<>>, _) -> {error, <<"other-error">>}; parse_extensions(<<"m=", _/binary>>, _) -> {error, <<"extensions-not-supported">>}; parse_extensions(<>, State) -> - case lists:any(fun(El) -> Char =:= El end, - fast_scram_attributes:reserved_scram_codes()) of + case + lists:any( + fun(El) -> Char =:= El end, + fast_scram_attributes:reserved_scram_codes() + ) + of true -> {skip_rule, State}; false -> {error, <<"extensions-not-supported">>} end. --spec parse_proof(binary(), fast_scram_state()) -> parse_return(). +-spec parse_proof(binary(), fast_scram:state()) -> parse_return(). parse_proof(<<>>, _State) -> {error, <<"no-resources">>}; parse_proof(<<"p=">>, _State) -> {error, <<"invalid-proof">>}; -parse_proof(<<"p=", Proof0/binary>>, - #fast_scram_state{data = Data} = State) -> +parse_proof( + <<"p=", Proof0/binary>>, + #fast_scram_state{data = Data} = State +) -> case maybe_base64_decode(Proof0) of {error, Reason} -> {error, Reason}; @@ -135,11 +151,16 @@ parse_proof(<<"p=", Proof0/binary>>, {ok, State#fast_scram_state{data = Data#{client_proof => Proof}}} end. --spec parse_channel_binding(binary(), fast_scram_state()) -> parse_return(). +-spec parse_channel_binding(binary(), fast_scram:state()) -> parse_return(). parse_channel_binding(<<>>, _State) -> {error, <<"no-resources">>}; -parse_channel_binding(<<"c=", CB/binary>>, #fast_scram_state{channel_binding = CbConfig, - data = Data} = State) -> +parse_channel_binding( + <<"c=", CB/binary>>, + #fast_scram_state{ + channel_binding = CbConfig, + data = Data + } = State +) -> case verify_cbind_input(CB, CbConfig, Data) of ok -> {ok, State}; @@ -149,10 +170,11 @@ parse_channel_binding(<<"c=", CB/binary>>, #fast_scram_state{channel_binding = C parse_channel_binding(_, _) -> {error, <<"no-resources">>}. --spec parse_server_error_or_verifier(binary(), fast_scram_state()) -> parse_return(). +-spec parse_server_error_or_verifier(binary(), fast_scram:state()) -> parse_return(). parse_server_error_or_verifier( - <<"v=", Verifier/binary>>, - #fast_scram_state{scram_definitions = #scram_definitions{} = ScramDefs} = State) -> + <<"v=", Verifier/binary>>, + #fast_scram_state{scram_definitions = #scram_definitions{} = ScramDefs} = State +) -> ServerSignature = ScramDefs#scram_definitions.server_signature, case maybe_base64_decode(Verifier) of ServerSignature -> @@ -165,13 +187,13 @@ parse_server_error_or_verifier( parse_server_error_or_verifier(<<"e=", Error/binary>>, _State) -> {error, Error}. --spec append_to_auth_message(scram_definitions(), binary()) -> scram_definitions(). +-spec append_to_auth_message(fast_scram:definitions(), binary()) -> fast_scram:definitions(). append_to_auth_message(ScramDefs, NewChunk) -> CurrentAuthMessage = ScramDefs#scram_definitions.auth_message, NewAppendedAuthMessage = <>, ScramDefs#scram_definitions{auth_message = NewAppendedAuthMessage}. --spec append_to_auth_message_in_state(fast_scram_state(), binary()) -> fast_scram_state(). +-spec append_to_auth_message_in_state(fast_scram:state(), binary()) -> fast_scram:state(). append_to_auth_message_in_state(State, NewChunk) -> ScramDefs0 = State#fast_scram_state.scram_definitions, ScramDefs1 = append_to_auth_message(ScramDefs0, NewChunk), @@ -183,8 +205,8 @@ append_to_auth_message_in_state(State, NewChunk) -> %% if any "=" char is not preceded with either "2C" or "3D". %% @end %%-------------------------------------------------------------------- --spec replace_2c_3d(binary()) -> {ok, binary()} | {error, binary()}. -replace_2c_3d(UnescapedUsername) -> +-spec replace_escaped_chars(binary()) -> {ok, binary()} | {error, binary()}. +replace_escaped_chars(UnescapedUsername) -> case binary:match(UnescapedUsername, <<"=">>) of nomatch -> {ok, UnescapedUsername}; @@ -199,36 +221,42 @@ replace_2c_3d(UnescapedUsername) -> end end. --spec supported_channel_binding_flag(binary(), channel_binding()) -> - channel_binding() | {error, binary()}. +-spec supported_channel_binding_flag(binary(), fast_scram:channel_binding()) -> + fast_scram:channel_binding() | {error, binary()}. supported_channel_binding_flag( - <<"p=", Type/binary>>, - #channel_binding{variant = Type} = CbConfig) when Type =/= undefined -> + <<"p=", Type/binary>>, + #channel_binding{variant = Type} = CbConfig +) when Type =/= undefined -> CbConfig; supported_channel_binding_flag( - <<"p=", _Type/binary>>, - #channel_binding{variant = OtherType}) when OtherType =/= undefined -> + <<"p=", _Type/binary>>, + #channel_binding{variant = OtherType} +) when OtherType =/= undefined -> {error, <<"unsupported-channel-binding-type">>}; supported_channel_binding_flag( - <<"p=", _Type/binary>>, - #channel_binding{variant = undefined}) -> + <<"p=", _Type/binary>>, + #channel_binding{variant = undefined} +) -> {error, <<"channel-binding-not-supported">>}; supported_channel_binding_flag( - <<"y">>, - #channel_binding{variant = undefined} = CbConfig) -> + <<"y">>, + #channel_binding{variant = undefined} = CbConfig +) -> CbConfig; supported_channel_binding_flag( - <<"y">>, - #channel_binding{variant = Type}) when Type =/= undefined -> + <<"y">>, + #channel_binding{variant = Type} +) when Type =/= undefined -> {error, <<"server-does-support-channel-binding">>}; supported_channel_binding_flag( - <<"n">>, - CbConfig) -> + <<"n">>, + CbConfig +) -> CbConfig#channel_binding{variant = undefined}; supported_channel_binding_flag(_, _) -> {error, <<"other-error">>}. --spec update_append_nonce(binary(), nonce()) -> nonce() | {error, binary()}. +-spec update_append_nonce(binary(), fast_scram:nonce()) -> fast_scram:nonce() | {error, binary()}. update_append_nonce(ClientNonce, #nonce{client = <<>>} = Nonce) -> Nonce#nonce{client = ClientNonce}; update_append_nonce(FullNonce, #nonce{client = Client, server = <<>>} = Nonce) -> @@ -246,11 +274,12 @@ update_append_nonce(FullNonce, #nonce{client = Client, server = Server} = Nonce) {error, <<"invalid-nonce">>} end. --spec verify_cbind_input(binary(), channel_binding(), map()) -> ok | {error, binary()}. -verify_cbind_input(CBindInput, #channel_binding{data = CBindData} = CBConfig, Data) -> +-spec verify_cbind_input(binary(), fast_scram:channel_binding(), map()) -> ok | {error, binary()}. +verify_cbind_input(CBindInput, #channel_binding{data = CBindData} = CbConfig, Data) -> Constructed = fast_scram_attributes:cbind_input( - fast_scram_attributes:gs2_header(CBConfig, Data), - CBindData), + fast_scram_attributes:gs2_header(CbConfig, Data), + CBindData + ), case Constructed =:= CBindInput of true -> ok; false -> {error, <<"channel-bindings-dont-match">>} @@ -259,6 +288,7 @@ verify_cbind_input(CBindInput, #channel_binding{data = CBindData} = CBConfig, Da maybe_base64_decode(Binary) -> try base64:decode(Binary) of Decoded -> Decoded - catch error:badarg -> - {error, <<"invalid-encoding">>} + catch + error:badarg -> + {error, <<"invalid-encoding">>} end. diff --git a/src/types.hrl b/src/types.hrl deleted file mode 100644 index e1b0965..0000000 --- a/src/types.hrl +++ /dev/null @@ -1,64 +0,0 @@ --include_lib("kernel/include/logger.hrl"). - --define(is_positive_integer(N), is_integer(N) andalso N > 0). --define(is_valid_hash(H), H == sha orelse - H == sha224 orelse H == sha256 orelse - H == sha384 orelse H == sha512). - --type sha_type() :: crypto:sha1() | crypto:sha2(). - --type username() :: binary(). --type auth_keys() :: password | salted_password | client_key | stored_key | server_key. --type auth_data() :: #{auth_keys() => binary()}. --type configuration() :: map(). --type retrieve_mechanism() :: fun((username()) -> configuration()) - | fun((username(), fast_scram_state()) -> - {configuration(), fast_scram_state()}). - --type parse_return() :: {ok, fast_scram_state()} | - {skip_rule, fast_scram_state()} | - {error, binary()}. - --record(nonce, { - client = <<>> :: binary(), - server = <<>> :: binary()} - ). --type nonce() :: #nonce{}. - --record(challenge, { - salt = <<>> :: binary(), - it_count = 1 :: pos_integer() - }). --type challenge() :: #challenge{}. - --record(channel_binding, { - variant = undefined :: plus_variant(), - data = <<>> :: binary() - }). --type channel_binding() :: #channel_binding{}. --type plus_variant() :: undefined | none | binary(). - --record(scram_definitions, { - hash_method :: sha_type(), - salted_password = <<>> :: binary(), %Hi(Normalize(password), salt, i), - client_key = <<>> :: binary(), %HMAC(SaltedPassword, "Client Key"), - stored_key = <<>> :: binary(), %H(ClientKey), - auth_message = <<>> :: binary(), %client-first-message-bare + "," + - %server-first-message + "," + - %client-final-message-without-proof - client_signature = <<>> :: binary(), %HMAC(StoredKey, AuthMessage), - client_proof = <<>> :: binary(), %ClientKey XOR ClientSignature, - server_key = <<>> :: binary(), %HMAC(SaltedPassword, "Server Key"), - server_signature = <<>> :: binary() %HMAC(ServerKey, AuthMessage) - }). --type scram_definitions() :: #scram_definitions{}. - --record(fast_scram_state, { - step :: 1..6, %% Steps 1 & 3 are client, 2 & 4 are server - nonce = #nonce{} :: nonce(), - challenge = #challenge{} :: challenge(), - channel_binding = #channel_binding{} :: channel_binding(), - scram_definitions = #scram_definitions{} :: scram_definitions(), - data = #{} :: map() - }). --type fast_scram_state() :: #fast_scram_state{}. diff --git a/test/scram_SUITE.erl b/test/scram_SUITE.erl index efc6593..27ddfe3 100644 --- a/test/scram_SUITE.erl +++ b/test/scram_SUITE.erl @@ -1,142 +1,133 @@ -module(scram_SUITE). --include("src/types.hrl"). +-include("src/fast_scram.hrl"). %% API -export([all/0, groups/0]). %% test cases -export([ - regular_scram_authentication_example_from_the_rfc/1, - regular_scram_authentication/1, - wrong_configuration_key/1, - configuration_client_sends_wrong_username/1, - configuration_cached_keys_works_easily/0, - configuration_cached_keys_works_easily/1, - configuration_cached_keys_works_easily_v2/0, - configuration_cached_keys_works_easily_v2/1, - configuration_cached_wrong_simply_recalculates/0, - configuration_cached_wrong_simply_recalculates/1, - configuration_cached_wrong_without_password_fails/0, - configuration_cached_wrong_without_password_fails/1, - verification_name_escapes_values_correctly/1, - verification_name_does_not_escape_values_correctly/1, - authentication_server_last_message_is_an_error/1, - authentication_server_rejects_the_proof/1, - authentication_server_rejects_invalid_encoded_proof/1, - authentication_client_rejects_the_signature/1, - nonce_client_receives_invalid/1, - nonce_server_finds_non_matching/1, - channel_not_advertise_but_client_could_is_ok/1, - channel_binding_client_did_not_see_available_plus/1, - channel_server_offers_but_client_does_not_take_is_ok/1, - channel_type_does_not_match/1, - channel_type_matches_but_data_does_not/1, - channel_is_not_supported_by_the_server/1, - missing_username/1, - missing_authzid/1, - missing_gs2/1, - missing_gs2_info/1, - missing_nonce/1, - missing_salt/1, - missing_it_count/1, - missing_proof/1, - missing_proof_info/1, - missing_channel_binding/1, - missing_channel_binding_info/1, - wrong_flag_username/1, - wrong_flag_g2s/1, - wrong_flag_nonce/1, - wrong_flag_salt/1, - wrong_flag_it_count/1, - wrong_it_count/1, - too_much_input/1, - not_supported_authzid/1, - not_supported_mext/1, - not_supported_extension/1 - ]). - --include_lib("common_test/include/ct.hrl"). --include_lib("eunit/include/eunit.hrl"). + regular_scram_authentication_example_from_the_rfc/1, + regular_scram_authentication/1, + wrong_configuration_key/1, + configuration_client_sends_wrong_username/1, + configuration_cached_keys_works_easily/0, + configuration_cached_keys_works_easily/1, + configuration_cached_keys_works_easily_v2/0, + configuration_cached_keys_works_easily_v2/1, + configuration_cached_wrong_simply_recalculates/0, + configuration_cached_wrong_simply_recalculates/1, + configuration_cached_wrong_without_password_fails/0, + configuration_cached_wrong_without_password_fails/1, + verification_name_escapes_values_correctly/1, + verification_name_does_not_escape_values_correctly/1, + authentication_server_last_message_is_an_error/1, + authentication_server_rejects_the_proof/1, + authentication_server_rejects_invalid_encoded_proof/1, + authentication_client_rejects_the_signature/1, + nonce_client_receives_invalid/1, + nonce_server_finds_non_matching/1, + channel_not_advertise_but_client_could_is_ok/1, + channel_binding_client_did_not_see_available_plus/1, + channel_server_offers_but_client_does_not_take_is_ok/1, + channel_type_does_not_match/1, + channel_type_matches_but_data_does_not/1, + channel_is_not_supported_by_the_server/1, + missing_username/1, + missing_authzid/1, + missing_gs2/1, + missing_gs2_info/1, + missing_nonce/1, + missing_salt/1, + missing_it_count/1, + missing_proof/1, + missing_proof_info/1, + missing_channel_binding/1, + missing_channel_binding_info/1, + wrong_flag_username/1, + wrong_flag_g2s/1, + wrong_flag_nonce/1, + wrong_flag_salt/1, + wrong_flag_it_count/1, + wrong_it_count/1, + too_much_input/1, + not_supported_authzid/1, + not_supported_mext/1, + not_supported_extension/1 +]). + +-include_lib("stdlib/include/assert.hrl"). all() -> [ - {group, verifications}, - {group, authentication}, - {group, nonce}, - {group, channel}, - {group, missing_flags}, - {group, wrong_input}, - {group, not_supported} + {group, verifications}, + {group, authentication}, + {group, nonce}, + {group, channel}, + {group, missing_flags}, + {group, wrong_input}, + {group, not_supported} ]. - groups() -> [ - {verifications, [parallel], - [ - regular_scram_authentication_example_from_the_rfc, - regular_scram_authentication, - wrong_configuration_key, - verification_name_escapes_values_correctly, - verification_name_does_not_escape_values_correctly, - configuration_client_sends_wrong_username, - configuration_cached_keys_works_easily, - configuration_cached_keys_works_easily_v2, - configuration_cached_wrong_simply_recalculates, - configuration_cached_wrong_without_password_fails - ]}, - {authentication, [parallel], - [ - authentication_server_last_message_is_an_error, - authentication_server_rejects_the_proof, - authentication_server_rejects_invalid_encoded_proof, - authentication_client_rejects_the_signature - ]}, - {nonce, [parallel], - [ - nonce_client_receives_invalid, - nonce_server_finds_non_matching - ]}, - {channel, [parallel], - [ - channel_not_advertise_but_client_could_is_ok, - channel_binding_client_did_not_see_available_plus, - channel_server_offers_but_client_does_not_take_is_ok, - channel_type_does_not_match, - channel_type_matches_but_data_does_not, - channel_is_not_supported_by_the_server - ]}, - {missing_flags, [parallel], - [ - missing_username, - missing_authzid, - missing_gs2, - missing_gs2_info, - missing_nonce, - missing_salt, - missing_it_count, - missing_proof, - missing_proof_info, - missing_channel_binding, - missing_channel_binding_info - ]}, - {wrong_input, [], - [ - wrong_flag_username, - wrong_flag_g2s, - wrong_flag_nonce, - wrong_flag_salt, - wrong_flag_it_count, - wrong_it_count, - too_much_input - ]}, - {not_supported, [parallel], - [ - not_supported_authzid, - not_supported_mext, - not_supported_extension - ]} + {verifications, [parallel], [ + regular_scram_authentication_example_from_the_rfc, + regular_scram_authentication, + wrong_configuration_key, + verification_name_escapes_values_correctly, + verification_name_does_not_escape_values_correctly, + configuration_client_sends_wrong_username, + configuration_cached_keys_works_easily, + configuration_cached_keys_works_easily_v2, + configuration_cached_wrong_simply_recalculates, + configuration_cached_wrong_without_password_fails + ]}, + {authentication, [parallel], [ + authentication_server_last_message_is_an_error, + authentication_server_rejects_the_proof, + authentication_server_rejects_invalid_encoded_proof, + authentication_client_rejects_the_signature + ]}, + {nonce, [parallel], [ + nonce_client_receives_invalid, + nonce_server_finds_non_matching + ]}, + {channel, [parallel], [ + channel_not_advertise_but_client_could_is_ok, + channel_binding_client_did_not_see_available_plus, + channel_server_offers_but_client_does_not_take_is_ok, + channel_type_does_not_match, + channel_type_matches_but_data_does_not, + channel_is_not_supported_by_the_server + ]}, + {missing_flags, [parallel], [ + missing_username, + missing_authzid, + missing_gs2, + missing_gs2_info, + missing_nonce, + missing_salt, + missing_it_count, + missing_proof, + missing_proof_info, + missing_channel_binding, + missing_channel_binding_info + ]}, + {wrong_input, [], [ + wrong_flag_username, + wrong_flag_g2s, + wrong_flag_nonce, + wrong_flag_salt, + wrong_flag_it_count, + wrong_it_count, + too_much_input + ]}, + {not_supported, [parallel], [ + not_supported_authzid, + not_supported_mext, + not_supported_extension + ]} ]. %%%=================================================================== @@ -163,16 +154,21 @@ regular_scram_authentication_example_from_the_rfc(_Config) -> regular_scram_authentication(_Config) -> Password = base64:encode(crypto:strong_rand_bytes(8 + rand:uniform(8))), - {ok, ClientState1} = fast_scram:mech_new(#{entity => client, - hash_method => sha256, - username => <<"user">>, - auth_data => #{password => Password}}), + {ok, ClientState1} = fast_scram:mech_new(#{ + entity => client, + hash_method => sha256, + username => <<"user">>, + auth_data => #{password => Password} + }), {ok, ServerState2} = fast_scram:mech_new( - #{entity => server, - hash_method => sha256, - username => <<"user">>, - retrieve_mechanism => - fun(U, S) -> retrieve_mechanism(U, #{password => Password}, S) end}), + #{ + entity => server, + hash_method => sha256, + username => <<"user">>, + retrieve_mechanism => + fun(U, S) -> retrieve_mechanism(U, #{password => Password}, S) end + } + ), {continue, ClientFirst, ClientState3} = fast_scram:mech_step(ClientState1, <<>>), {continue, ServerFirst, ServerState4} = fast_scram:mech_step(ServerState2, ClientFirst), {continue, ClientFinal, ClientState5} = fast_scram:mech_step(ClientState3, ServerFirst), @@ -181,34 +177,46 @@ regular_scram_authentication(_Config) -> wrong_configuration_key(_Config) -> {error, wrong_key, bad_key} = fast_scram:mech_new( - #{entity => client, - hash_method => sha256, - username => <<"user">>, - bad_key => any_value, - auth_data => #{password => <<"pencil">>}}). + #{ + entity => client, + hash_method => sha256, + username => <<"user">>, + bad_key => any_value, + auth_data => #{password => <<"pencil">>} + } + ). configuration_cached_keys_works_easily() -> - [{timetrap,{seconds,1}}]. + [{timetrap, {seconds, 1}}]. configuration_cached_keys_works_easily(_Config) -> Cached = cached_heavy_scram_definitions(), {ok, ClientState1} = fast_scram:mech_new( - #{entity => client, - hash_method => sha, - username => <<"user">>, - cached_challenge => {base64:decode(<<"QSXCR+Q6sek8bf92">>), 409600000}, - auth_data => #{salted_password => Cached#scram_definitions.salted_password}}), + #{ + entity => client, + hash_method => sha, + username => <<"user">>, + cached_challenge => {base64:decode(<<"QSXCR+Q6sek8bf92">>), 409600000}, + auth_data => #{salted_password => Cached#scram_definitions.salted_password} + } + ), {ok, ServerState2} = fast_scram:mech_new( - #{entity => server, - hash_method => sha, - retrieve_mechanism => - fun(_) -> - #{salt => base64:decode(<<"QSXCR+Q6sek8bf92">>), - it_count => 409600000, - auth_data => #{ - stored_key => Cached#scram_definitions.stored_key, - server_key => Cached#scram_definitions.server_key}} - end}), + #{ + entity => server, + hash_method => sha, + retrieve_mechanism => + fun(_) -> + #{ + salt => base64:decode(<<"QSXCR+Q6sek8bf92">>), + it_count => 409600000, + auth_data => #{ + stored_key => Cached#scram_definitions.stored_key, + server_key => Cached#scram_definitions.server_key + } + } + end + } + ), {continue, ClientFirst, ClientState3} = fast_scram:mech_step(ClientState1, <<>>), {continue, ServerFirst, ServerState4} = fast_scram:mech_step(ServerState2, ClientFirst), {continue, ClientFinal, ClientState5} = fast_scram:mech_step(ClientState3, ServerFirst), @@ -216,29 +224,39 @@ configuration_cached_keys_works_easily(_Config) -> {ok, _Final, _ClientState7} = fast_scram:mech_step(ClientState5, ServerFinal). configuration_cached_keys_works_easily_v2() -> - [{timetrap,{seconds,1}}]. + [{timetrap, {seconds, 1}}]. configuration_cached_keys_works_easily_v2(_Config) -> Cached = cached_heavy_scram_definitions(), {ok, ClientState1} = fast_scram:mech_new( - #{entity => client, - hash_method => sha, - username => <<"user">>, - cached_challenge => {409600000, base64:decode(<<"QSXCR+Q6sek8bf92">>)}, - auth_data => #{ - client_key => Cached#scram_definitions.client_key, - server_key => Cached#scram_definitions.server_key}}), + #{ + entity => client, + hash_method => sha, + username => <<"user">>, + cached_challenge => {409600000, base64:decode(<<"QSXCR+Q6sek8bf92">>)}, + auth_data => #{ + client_key => Cached#scram_definitions.client_key, + server_key => Cached#scram_definitions.server_key + } + } + ), {ok, ServerState2} = fast_scram:mech_new( - #{entity => server, - hash_method => sha, - retrieve_mechanism => - fun(_) -> - #{salt => base64:decode(<<"QSXCR+Q6sek8bf92">>), - it_count => 409600000, - auth_data => #{ - salted_password => - Cached#scram_definitions.salted_password}} - end}), + #{ + entity => server, + hash_method => sha, + retrieve_mechanism => + fun(_) -> + #{ + salt => base64:decode(<<"QSXCR+Q6sek8bf92">>), + it_count => 409600000, + auth_data => #{ + salted_password => + Cached#scram_definitions.salted_password + } + } + end + } + ), {continue, ClientFirst, ClientState3} = fast_scram:mech_step(ClientState1, <<>>), {continue, ServerFirst, ServerState4} = fast_scram:mech_step(ServerState2, ClientFirst), {continue, ClientFinal, ClientState5} = fast_scram:mech_step(ClientState3, ServerFirst), @@ -246,32 +264,42 @@ configuration_cached_keys_works_easily_v2(_Config) -> {ok, _Final, _ClientState7} = fast_scram:mech_step(ClientState5, ServerFinal). configuration_cached_wrong_simply_recalculates() -> - [{timetrap,{seconds,1}}]. + [{timetrap, {seconds, 1}}]. configuration_cached_wrong_simply_recalculates(_Config) -> CachedRegular = cached_regular_scram_refinitions(), CachedHeavy = cached_heavy_scram_definitions(), {ok, ClientState1} = fast_scram:mech_new( - #{entity => client, - hash_method => sha, - username => <<"user">>, - cached_challenge => {409600000, base64:decode(<<"QSXCR+Q6sek8bf92">>)}, - auth_data => #{ - password => <<"pencil">>, - salted_password => CachedHeavy#scram_definitions.salted_password}}), + #{ + entity => client, + hash_method => sha, + username => <<"user">>, + cached_challenge => {409600000, base64:decode(<<"QSXCR+Q6sek8bf92">>)}, + auth_data => #{ + password => <<"pencil">>, + salted_password => CachedHeavy#scram_definitions.salted_password + } + } + ), {ok, ServerState2} = fast_scram:mech_new( - #{entity => server, - hash_method => sha, - retrieve_mechanism => - fun(_) -> - #{salt => base64:decode(<<"QSXCR+Q6sek8bf92">>), - it_count => 4096, - auth_data => #{ - password => <<"pencil">>, - stored_key => CachedRegular#scram_definitions.stored_key, - server_key => - CachedRegular#scram_definitions.server_key}} - end}), + #{ + entity => server, + hash_method => sha, + retrieve_mechanism => + fun(_) -> + #{ + salt => base64:decode(<<"QSXCR+Q6sek8bf92">>), + it_count => 4096, + auth_data => #{ + password => <<"pencil">>, + stored_key => CachedRegular#scram_definitions.stored_key, + server_key => + CachedRegular#scram_definitions.server_key + } + } + end + } + ), {continue, ClientFirst, ClientState3} = fast_scram:mech_step(ClientState1, <<>>), {continue, ServerFirst, ServerState4} = fast_scram:mech_step(ServerState2, ClientFirst), {continue, ClientFinal, ClientState5} = fast_scram:mech_step(ClientState3, ServerFirst), @@ -279,31 +307,41 @@ configuration_cached_wrong_simply_recalculates(_Config) -> {ok, _Final, _ClientState7} = fast_scram:mech_step(ClientState5, ServerFinal). configuration_cached_wrong_without_password_fails() -> - [{timetrap,{seconds,1}}]. + [{timetrap, {seconds, 1}}]. configuration_cached_wrong_without_password_fails(_Config) -> CachedRegular = cached_regular_scram_refinitions(), CachedHeavy = cached_heavy_scram_definitions(), {ok, ClientState1} = fast_scram:mech_new( - #{entity => client, - hash_method => sha, - username => <<"user">>, - cached_challenge => {409600000, base64:decode(<<"QSXCR+Q6sek8bf92">>)}, - auth_data => #{ - salted_password => CachedHeavy#scram_definitions.salted_password}}), + #{ + entity => client, + hash_method => sha, + username => <<"user">>, + cached_challenge => {409600000, base64:decode(<<"QSXCR+Q6sek8bf92">>)}, + auth_data => #{ + salted_password => CachedHeavy#scram_definitions.salted_password + } + } + ), {ok, ServerState2} = fast_scram:mech_new( - #{entity => server, - hash_method => sha, - retrieve_mechanism => - fun(_) -> - #{salt => base64:decode(<<"QSXCR+Q6sek8bf92">>), - it_count => 4096, - auth_data => #{ - password => <<"pencil">>, - stored_key => CachedRegular#scram_definitions.stored_key, - server_key => - CachedRegular#scram_definitions.server_key}} - end}), + #{ + entity => server, + hash_method => sha, + retrieve_mechanism => + fun(_) -> + #{ + salt => base64:decode(<<"QSXCR+Q6sek8bf92">>), + it_count => 4096, + auth_data => #{ + password => <<"pencil">>, + stored_key => CachedRegular#scram_definitions.stored_key, + server_key => + CachedRegular#scram_definitions.server_key + } + } + end + } + ), {continue, ClientFirst, ClientState3} = fast_scram:mech_step(ClientState1, <<>>), {continue, ServerFirst, ServerState4} = fast_scram:mech_step(ServerState2, ClientFirst), {continue, ClientFinal, _} = fast_scram:mech_step(ClientState3, ServerFirst), @@ -313,16 +351,16 @@ configuration_cached_wrong_without_password_fails(_Config) -> authentication_server_rejects_the_proof(_Config) -> ServerState2 = typical_scram_configuration(server), {continue, _, ServerState4} = fast_scram:mech_step(ServerState2, client_first()), - WrongProof = <<"c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,", - "p=", (base64:encode(<<"wrong_proof">>))/binary>>, + WrongProof = + <<"c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,", "p=", + (base64:encode(<<"wrong_proof">>))/binary>>, {error, Reason, _} = fast_scram:mech_step(ServerState4, WrongProof), ?assertEqual(<<"e=invalid-proof">>, Reason). authentication_server_rejects_invalid_encoded_proof(_Config) -> ServerState2 = typical_scram_configuration(server), {continue, _, ServerState4} = fast_scram:mech_step(ServerState2, client_first()), - WrongProof = <<"c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,", - "p=wrong_proof">>, + WrongProof = <<"c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,", "p=wrong_proof">>, {error, Reason, _} = fast_scram:mech_step(ServerState4, WrongProof), ?assertEqual(<<"e=invalid-encoding">>, Reason). @@ -336,14 +374,17 @@ authentication_client_rejects_the_signature(_Config) -> authentication_server_last_message_is_an_error(_Config) -> ClientState = typical_scram_configuration(client), - {error, Reason, _} = fast_scram:mech_step(ClientState#fast_scram_state{step = 5}, <<"e=invalid">>), + {error, Reason, _} = fast_scram:mech_step( + ClientState#fast_scram_state{step = 5}, <<"e=invalid">> + ), ?assertEqual(<<"invalid">>, Reason). configuration_client_sends_wrong_username(_Config) -> ClientState1 = typical_scram_configuration(client), ServerState0 = typical_scram_configuration(server), ServerState2 = ServerState0#fast_scram_state{ - data = #{username => <<"not-user">>, password => <<"pencil">>}}, + data = #{username => <<"not-user">>, password => <<"pencil">>} + }, {continue, ClientFirst, _} = fast_scram:mech_step(ClientState1, <<>>), {error, Reason, _} = fast_scram:mech_step(ServerState2, ClientFirst), ?assertEqual(<<"unknown-user">>, Reason). @@ -351,7 +392,8 @@ configuration_client_sends_wrong_username(_Config) -> verification_name_escapes_values_correctly(_Config) -> ServerState0 = #fast_scram_state{data = Data} = typical_scram_configuration(server), ServerState2 = ServerState0#fast_scram_state{ - data = Data#{username => <<"u,ser">>, password => <<"pencil">>}}, + data = Data#{username => <<"u,ser">>, password => <<"pencil">>} + }, Username = <<"n,,n=u=2Cser,r=fyko+d2lbbFgONRv9qkxdawL">>, {NextStep, _, _} = fast_scram:mech_step(ServerState2, Username), ?assertEqual(continue, NextStep). @@ -359,7 +401,8 @@ verification_name_escapes_values_correctly(_Config) -> verification_name_does_not_escape_values_correctly(_Config) -> ServerState0 = typical_scram_configuration(server), ServerState2 = ServerState0#fast_scram_state{ - data = #{username => <<"u,ser">>, password => <<"pencil">>}}, + data = #{username => <<"u,ser">>, password => <<"pencil">>} + }, Username = <<"n,,n=u=ser,r=fyko+d2lbbFgONRv9qkxdawL">>, {error, Reason, _} = fast_scram:mech_step(ServerState2, Username), ?assertEqual(<<"invalid-username-encoding">>, Reason). @@ -378,7 +421,8 @@ nonce_client_receives_invalid(_Config) -> nonce_server_finds_non_matching(_Config) -> ServerState2 = typical_scram_configuration(server), {continue, _, ServerState4} = fast_scram:mech_step(ServerState2, client_first()), - WrongNonce = <<"c=biws,r=bad_nonce_FgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=">>, + WrongNonce = + <<"c=biws,r=bad_nonce_FgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=">>, {error, Reason, _} = fast_scram:mech_step(ServerState4, WrongNonce), ?assertEqual(<<"e=invalid-nonce">>, Reason). @@ -392,83 +436,110 @@ nonce_server_finds_non_matching(_Config) -> %% suffixed SCRAM mechanism name(s)). channel_binding_client_did_not_see_available_plus(_Config) -> {ok, ServerState2} = fast_scram:mech_new( - #{entity => server, - hash_method => sha, - channel_binding => {<<"tls-unique">>, <<1,2,3,4,5,6,7,8>>}, - retrieve_mechanism => - fun(_) -> - #{salt => base64:decode(<<"QSXCR+Q6sek8bf92">>), - it_count => 4096, - auth_data => #{ - password => <<"pencil">>}} - end}), + #{ + entity => server, + hash_method => sha, + channel_binding => {<<"tls-unique">>, <<1, 2, 3, 4, 5, 6, 7, 8>>}, + retrieve_mechanism => + fun(_) -> + #{ + salt => base64:decode(<<"QSXCR+Q6sek8bf92">>), + it_count => 4096, + auth_data => #{ + password => <<"pencil">> + } + } + end + } + ), YesGS2Flag = <<"y,,n=user,r=fyko+d2lbbFgONRv9qkxdawL">>, {error, Reason, _} = fast_scram:mech_step(ServerState2, YesGS2Flag), ?assertEqual(<<"server-does-support-channel-binding">>, Reason). channel_server_offers_but_client_does_not_take_is_ok(_Config) -> {ok, ServerState2} = fast_scram:mech_new( - #{entity => server, - hash_method => sha, - channel_binding => {<<"tls-unique">>, <<1,2,3,4,5,6,7,8>>}, - retrieve_mechanism => - fun(_) -> - #{salt => base64:decode(<<"QSXCR+Q6sek8bf92">>), - it_count => 4096, - auth_data => #{password => <<"pencil">>}} - end}), + #{ + entity => server, + hash_method => sha, + channel_binding => {<<"tls-unique">>, <<1, 2, 3, 4, 5, 6, 7, 8>>}, + retrieve_mechanism => + fun(_) -> + #{ + salt => base64:decode(<<"QSXCR+Q6sek8bf92">>), + it_count => 4096, + auth_data => #{password => <<"pencil">>} + } + end + } + ), YesGS2Flag = <<"n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL">>, {continue, _, _} = fast_scram:mech_step(ServerState2, YesGS2Flag). - channel_not_advertise_but_client_could_is_ok(_Config) -> {ok, ServerState2} = fast_scram:mech_new( - #{entity => server, - hash_method => sha, - retrieve_mechanism => - fun(_) -> - #{salt => base64:decode(<<"QSXCR+Q6sek8bf92">>), - it_count => 4096, - auth_data => #{password => <<"pencil">>}} - end}), + #{ + entity => server, + hash_method => sha, + retrieve_mechanism => + fun(_) -> + #{ + salt => base64:decode(<<"QSXCR+Q6sek8bf92">>), + it_count => 4096, + auth_data => #{password => <<"pencil">>} + } + end + } + ), YesGS2Flag = <<"y,,n=user,r=fyko+d2lbbFgONRv9qkxdawL">>, {continue, _, _} = fast_scram:mech_step(ServerState2, YesGS2Flag). - %% If the channel binding flag was "p" and the server does not support %% the indicated channel binding type, then the server MUST fail authentication. channel_type_does_not_match(_Config) -> {ok, ServerState2} = fast_scram:mech_new( - #{entity => server, - hash_method => sha, - channel_binding => {<<"some_server_type">>, <<1,2,3,4,5,6,7,8>>}, - retrieve_mechanism => - fun(_) -> - #{salt => base64:decode(<<"QSXCR+Q6sek8bf92">>), - it_count => 4096, - auth_data => #{password => <<"pencil">>}} - end}), + #{ + entity => server, + hash_method => sha, + channel_binding => {<<"some_server_type">>, <<1, 2, 3, 4, 5, 6, 7, 8>>}, + retrieve_mechanism => + fun(_) -> + #{ + salt => base64:decode(<<"QSXCR+Q6sek8bf92">>), + it_count => 4096, + auth_data => #{password => <<"pencil">>} + } + end + } + ), ClientFirst = <<"p=some_client_type,,n=user,r=fyko+d2lbbFgONRv9qkxdawL">>, {error, Reason, _} = fast_scram:mech_step(ServerState2, ClientFirst), ?assertEqual(<<"unsupported-channel-binding-type">>, Reason). channel_type_matches_but_data_does_not(_Config) -> {ok, ClientState1} = fast_scram:mech_new( - #{entity => client, - hash_method => sha, - username => <<"user">>, - channel_binding => {<<"tls-unique">>, <<1,2,3,4,5,6,7,8>>}, - auth_data => #{password => <<"pencil">>}}), + #{ + entity => client, + hash_method => sha, + username => <<"user">>, + channel_binding => {<<"tls-unique">>, <<1, 2, 3, 4, 5, 6, 7, 8>>}, + auth_data => #{password => <<"pencil">>} + } + ), {ok, ServerState2} = fast_scram:mech_new( - #{entity => server, - hash_method => sha, - channel_binding => {<<"tls-unique">>, <<2,2,3,4,5,6,7,8>>}, - retrieve_mechanism => - fun(_) -> - #{salt => base64:decode(<<"QSXCR+Q6sek8bf92">>), - it_count => 4096, - auth_data => #{password => <<"pencil">>}} - end}), + #{ + entity => server, + hash_method => sha, + channel_binding => {<<"tls-unique">>, <<2, 2, 3, 4, 5, 6, 7, 8>>}, + retrieve_mechanism => + fun(_) -> + #{ + salt => base64:decode(<<"QSXCR+Q6sek8bf92">>), + it_count => 4096, + auth_data => #{password => <<"pencil">>} + } + end + } + ), {continue, ClientFirst, ClientState3} = fast_scram:mech_step(ClientState1, <<>>), {continue, ServerFirst, ServerState4} = fast_scram:mech_step(ServerState2, ClientFirst), {continue, ClientFinal, _} = fast_scram:mech_step(ClientState3, ServerFirst), @@ -570,13 +641,15 @@ wrong_flag_salt(_Config) -> wrong_flag_it_count(_Config) -> ClientState1 = typical_scram_configuration(client), {continue, _, ClientState3} = fast_scram:mech_step(ClientState1, <<>>), - ServerWrongItCount = <<"r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,wrong">>, + ServerWrongItCount = + <<"r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,wrong">>, {error, Reason, _} = fast_scram:mech_step(ClientState3, ServerWrongItCount), ?assertEqual(<<"other-error">>, Reason). wrong_it_count(_Config) -> ClientState1 = typical_scram_configuration(client), {continue, _, ClientState3} = fast_scram:mech_step(ClientState1, <<>>), - ServerWrongItCount = <<"r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=wrong">>, + ServerWrongItCount = + <<"r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=wrong">>, {error, Reason, _} = fast_scram:mech_step(ClientState3, ServerWrongItCount), ?assertEqual(<<"invalid-iteration-count">>, Reason). too_much_input(_Config) -> @@ -585,7 +658,6 @@ too_much_input(_Config) -> {error, Reason, _} = fast_scram:mech_step(ServerState2, Username), ?assertEqual(<<"error-too-much-input">>, Reason). - not_supported_authzid(_Config) -> ServerState2 = typical_scram_configuration(server), ClientFirst = <<"n,a=other_user,n=user,r=fyko+d2lbbFgONRv9qkxdawL">>, @@ -594,7 +666,8 @@ not_supported_authzid(_Config) -> not_supported_mext(_Config) -> ClientState1 = typical_scram_configuration(client), {continue, _, ClientState3} = fast_scram:mech_step(ClientState1, <<>>), - ServerWithMext = <<"m=mext,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096">>, + ServerWithMext = + <<"m=mext,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096">>, {error, Reason, _} = fast_scram:mech_step(ClientState3, ServerWithMext), ?assertEqual(<<"extensions-not-supported">>, Reason). not_supported_extension(_Config) -> @@ -606,7 +679,6 @@ not_supported_extension(_Config) -> {error, Reason, _} = fast_scram:mech_step(ServerState2, ClientFirst2), ?assertEqual(<<"extensions-not-supported">>, Reason). - %%%=================================================================== %%% Helper functions %%%=================================================================== @@ -615,59 +687,96 @@ typical_scram_configuration(Entity) -> typical_scram_configuration(Entity, #{password => <<"pencil">>}, #{}). typical_scram_configuration(client, AuthData, Other) -> - Config0 = #{entity => client, - hash_method => sha, - username => <<"user">>, - nonce => <<"fyko+d2lbbFgONRv9qkxdawL">>, - auth_data => AuthData}, + Config0 = #{ + entity => client, + hash_method => sha, + username => <<"user">>, + nonce => <<"fyko+d2lbbFgONRv9qkxdawL">>, + auth_data => AuthData + }, Config1 = maps:merge(Config0, Other), {ok, St} = fast_scram:mech_new(Config1), St; typical_scram_configuration(server, AuthData, Other) -> - Config0 = #{entity => server, - hash_method => sha, - nonce => <<"3rfcNHYJY1ZVvWVs7j">>, - retrieve_mechanism => fun(U, S) -> retrieve_mechanism(U, AuthData, S) end}, + Config0 = #{ + entity => server, + hash_method => sha, + nonce => <<"3rfcNHYJY1ZVvWVs7j">>, + retrieve_mechanism => fun(U, S) -> retrieve_mechanism(U, AuthData, S) end + }, Config1 = maps:merge(Config0, Other), {ok, St} = fast_scram:mech_new(Config1), St. retrieve_mechanism(_, AuthData, S) -> - X = #{salt => base64:decode(<<"QSXCR+Q6sek8bf92">>), - it_count => 4096, - auth_data => AuthData}, + X = #{ + salt => base64:decode(<<"QSXCR+Q6sek8bf92">>), + it_count => 4096, + auth_data => AuthData + }, {X, S}. client_first() -> <<"n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL">>. server_first() -> <<"r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096">>. -client_final() -> <<"c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=">>. +client_final() -> + <<"c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=">>. server_final() -> <<"v=rmF9pqV8S7suAoZWja4dJRkFsKQ=">>. %%% Precalculated with an iteration cout of 4096 cached_regular_scram_refinitions() -> #scram_definitions{ - hash_method = sha, - salted_password = <<29,150,238,58,82,155,90,95,158,71,192,31,34,154,44,184,166,225,95,125>>, - client_key = <<226,52,196,123,246,195,102,150,221,109,133,43,153,170,162,186,38,85,87,40>>, - stored_key = <<233,217,70,96,195,157,101,195,143,186,217,28,53,143,20,218,14,239,43,214>>, - auth_message = <<"n=user,r=fyko+d2lbbFgONRv9qkxdawL,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096,c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j">>, - client_signature = <<93,113,56,196,134,176,191,171,223,73,227,226,218,139,214,229,199,157,182,19>>, - client_proof = <<191,69,252,191,112,115,217,61,2,36,102,201,67,33,116,95,225,200,225,59>>, - server_key = <<15,224,146,88,179,172,133,43,165,2,204,98,186,144,62,170,205,191,125,49>>, - server_signature = <<174,97,125,166,165,124,75,187,46,2,134,86,141,174,29,37,25,5,176,164>> - }. + hash_method = sha, + salted_password = + <<29, 150, 238, 58, 82, 155, 90, 95, 158, 71, 192, 31, 34, 154, 44, 184, 166, 225, 95, + 125>>, + client_key = + <<226, 52, 196, 123, 246, 195, 102, 150, 221, 109, 133, 43, 153, 170, 162, 186, 38, 85, + 87, 40>>, + stored_key = + <<233, 217, 70, 96, 195, 157, 101, 195, 143, 186, 217, 28, 53, 143, 20, 218, 14, 239, + 43, 214>>, + auth_message = + <<"n=user,r=fyko+d2lbbFgONRv9qkxdawL,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096,c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j">>, + client_signature = + <<93, 113, 56, 196, 134, 176, 191, 171, 223, 73, 227, 226, 218, 139, 214, 229, 199, 157, + 182, 19>>, + client_proof = + <<191, 69, 252, 191, 112, 115, 217, 61, 2, 36, 102, 201, 67, 33, 116, 95, 225, 200, 225, + 59>>, + server_key = + <<15, 224, 146, 88, 179, 172, 133, 43, 165, 2, 204, 98, 186, 144, 62, 170, 205, 191, + 125, 49>>, + server_signature = + <<174, 97, 125, 166, 165, 124, 75, 187, 46, 2, 134, 86, 141, 174, 29, 37, 25, 5, 176, + 164>> + }. %%% This was calculated for the same parameters than as above, %%% except that the iteration count is 409600000, so it would take a long time cached_heavy_scram_definitions() -> #scram_definitions{ - hash_method = sha, - salted_password = <<88,214,221,58,163,214,103,20,235,222,209,209,41,158,166,159,61,23,116,62>>, - client_key = <<30,240,57,94,35,56,109,230,129,154,73,94,142,182,50,156,78,128,171,29>>, - stored_key = <<31,45,16,146,4,98,92,24,40,167,241,234,95,61,79,194,127,194,197,103>>, - auth_message = <<"n=user,r=fyko+d2lbbFgONRv9qkxdawL,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=409600000,c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j">>, - client_signature = <<209,237,202,96,155,147,96,108,48,218,110,140,122,223,86,215,92,171,193,19>>, - client_proof = <<207,29,243,62,184,171,13,138,177,64,39,210,244,105,100,75,18,43,106,14>>, - server_key = <<222,94,85,104,169,47,131,128,114,6,162,90,225,108,19,88,133,210,62,187>>, - server_signature = <<87,156,101,76,203,139,198,161,160,20,24,9,165,245,125,35,171,138,16,195>> - }. + hash_method = sha, + salted_password = + <<88, 214, 221, 58, 163, 214, 103, 20, 235, 222, 209, 209, 41, 158, 166, 159, 61, 23, + 116, 62>>, + client_key = + <<30, 240, 57, 94, 35, 56, 109, 230, 129, 154, 73, 94, 142, 182, 50, 156, 78, 128, 171, + 29>>, + stored_key = + <<31, 45, 16, 146, 4, 98, 92, 24, 40, 167, 241, 234, 95, 61, 79, 194, 127, 194, 197, + 103>>, + auth_message = + <<"n=user,r=fyko+d2lbbFgONRv9qkxdawL,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=409600000,c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j">>, + client_signature = + <<209, 237, 202, 96, 155, 147, 96, 108, 48, 218, 110, 140, 122, 223, 86, 215, 92, 171, + 193, 19>>, + client_proof = + <<207, 29, 243, 62, 184, 171, 13, 138, 177, 64, 39, 210, 244, 105, 100, 75, 18, 43, 106, + 14>>, + server_key = + <<222, 94, 85, 104, 169, 47, 131, 128, 114, 6, 162, 90, 225, 108, 19, 88, 133, 210, 62, + 187>>, + server_signature = + <<87, 156, 101, 76, 203, 139, 198, 161, 160, 20, 24, 9, 165, 245, 125, 35, 171, 138, 16, + 195>> + }.