Current section

Files

Jump to
gleam_mongo src mongo@scram.erl
Raw

src/mongo@scram.erl

-module(mongo@scram).
-compile([no_auto_import, nowarn_unused_vars]).
-export([first_message/1, parse_first_reply/1, parse_second_reply/2, first_payload/1, hi/3, second_message/5]).
-spec first_message(binary()) -> bson@value:value().
first_message(Payload) ->
Payload@1 = begin
_pipe = [<<"n,,"/utf8>>, Payload],
_pipe@1 = gleam@string:concat(_pipe),
bson@generic:from_string(_pipe@1)
end,
{document,
[{<<"saslStart"/utf8>>, {boolean, true}},
{<<"mechanism"/utf8>>, {str, <<"SCRAM-SHA-256"/utf8>>}},
{<<"payload"/utf8>>, {binary, {generic, Payload@1}}},
{<<"autoAuthorize"/utf8>>, {boolean, true}},
{<<"options"/utf8>>,
{document, [{<<"skipEmptyExchange"/utf8>>, {boolean, true}}]}}]}.
-spec parse_payload(binary()) -> {ok, list({binary(), binary()})} | {error, nil}.
parse_payload(Payload) ->
_pipe = Payload,
_pipe@1 = gleam@string:split(_pipe, <<","/utf8>>),
gleam@list:try_map(
_pipe@1,
fun(Item) -> gleam@string:split_once(Item, <<"="/utf8>>) end
).
-spec parse_first_reply(list({binary(), bson@value:value()})) -> {ok,
{{binary(), binary(), integer()}, binary(), integer()}} |
{error, nil}.
parse_first_reply(Reply) ->
case Reply of
[{<<"ok"/utf8>>, {double, 0.0}} | _] ->
{error, nil};
[{<<"conversationId"/utf8>>, {integer, Cid}},
{<<"done"/utf8>>, {boolean, false}},
{<<"payload"/utf8>>, {binary, {generic, Data}}},
{<<"ok"/utf8>>, {double, 1.0}}] ->
gleam@result:then(
bson@generic:to_string(Data),
fun(Data@1) ->
gleam@result:then(
parse_payload(Data@1),
fun(_use0) ->
[{<<"r"/utf8>>, Rnonce},
{<<"s"/utf8>>, Salt},
{<<"i"/utf8>>, I}] = _use0,
case gleam@int:parse(I) of
{ok, Iterations} ->
case Iterations >= 4096 of
true ->
{ok,
{{Rnonce, Salt, Iterations},
Data@1,
Cid}};
false ->
{error, nil}
end;
{error, nil} ->
{error, nil}
end
end
)
end
)
end.
-spec parse_second_reply(list({binary(), bson@value:value()}), bitstring()) -> {ok,
nil} |
{error, nil}.
parse_second_reply(Reply, Server_signature) ->
case Reply of
[{<<"ok"/utf8>>, {double, 0.0}} | _] ->
{error, nil};
[{<<"conversationId"/utf8>>, _},
{<<"done"/utf8>>, {boolean, true}},
{<<"payload"/utf8>>, {binary, {generic, Data}}},
{<<"ok"/utf8>>, {double, 1.0}}] ->
gleam@result:then(
bson@generic:to_string(Data),
fun(Data@1) ->
gleam@result:then(
parse_payload(Data@1),
fun(_use0) ->
[{<<"v"/utf8>>, Data@2}] = _use0,
gleam@result:then(
gleam@base:decode64(Data@2),
fun(Received_signature) ->
case (gleam@bit_string:byte_size(
Server_signature
)
=:= gleam@bit_string:byte_size(
Received_signature
))
andalso gleam@crypto:secure_compare(
Server_signature,
Received_signature
) of
true ->
{ok, nil};
false ->
{error, nil}
end
end
)
end
)
end
)
end.
-spec clean_username(binary()) -> binary().
clean_username(Username) ->
_pipe = Username,
_pipe@1 = gleam@string:replace(_pipe, <<"="/utf8>>, <<"=3D"/utf8>>),
gleam@string:replace(_pipe@1, <<","/utf8>>, <<"=2C"/utf8>>).
-spec first_payload(binary()) -> binary().
first_payload(Username) ->
Nonce = begin
_pipe = crypto:strong_rand_bytes(24),
gleam@base:encode64(_pipe, true)
end,
_pipe@1 = [<<"n="/utf8>>, clean_username(Username), <<",r="/utf8>>, Nonce],
gleam@string:concat(_pipe@1).
-spec hi(binary(), bitstring(), integer()) -> bitstring().
hi(Password, Salt, Iterations) ->
crypto:pbkdf2_hmac(sha256, Password, Salt, Iterations, 32).
-spec 'xor'(bitstring(), bitstring(), bitstring()) -> bitstring().
'xor'(A, B, Storage) ->
<<Fa, Ra/bitstring>> = A,
<<Fb, Rb/bitstring>> = B,
New_storage = begin
_pipe = [Storage, <<(gleam@bitwise:exclusive_or(Fa, Fb))>>],
gleam@bit_string:concat(_pipe)
end,
case [Ra, Rb] of
[<<>>, <<>>] ->
New_storage;
_ ->
'xor'(Ra, Rb, New_storage)
end.
-spec second_message(
{binary(), binary(), integer()},
binary(),
binary(),
integer(),
binary()
) -> {ok, {bson@value:value(), bitstring()}} | {error, nil}.
second_message(Server_params, First_payload, Server_payload, Cid, Password) ->
{Rnonce, Salt, Iterations} = Server_params,
gleam@result:then(
gleam@base:decode64(Salt),
fun(Salt@1) ->
Salted_password = hi(Password, Salt@1, Iterations),
Client_key = gleam@crypto:hmac(
gleam@bit_string:from_string(<<"Client Key"/utf8>>),
sha256,
Salted_password
),
Server_key = gleam@crypto:hmac(
gleam@bit_string:from_string(<<"Server Key"/utf8>>),
sha256,
Salted_password
),
Stored_key = crypto:hash(sha256, Client_key),
Auth_message = begin
_pipe = [First_payload,
<<","/utf8>>,
Server_payload,
<<",c=biws,r="/utf8>>,
Rnonce],
_pipe@1 = gleam@string:concat(_pipe),
bson@generic:from_string(_pipe@1)
end,
Client_signature = gleam@crypto:hmac(
bson@generic:to_bit_string(Auth_message),
sha256,
Stored_key
),
Second_payload = begin
_pipe@3 = [<<"c=biws,r="/utf8>>,
Rnonce,
<<",p="/utf8>>,
begin
_pipe@2 = 'xor'(Client_key, Client_signature, <<>>),
gleam@base:encode64(_pipe@2, true)
end],
_pipe@4 = gleam@string:concat(_pipe@3),
bson@generic:from_string(_pipe@4)
end,
Server_signature = gleam@crypto:hmac(
bson@generic:to_bit_string(Auth_message),
sha256,
Server_key
),
_pipe@5 = {{document,
[{<<"saslContinue"/utf8>>, {boolean, true}},
{<<"conversationId"/utf8>>, {integer, Cid}},
{<<"payload"/utf8>>,
{binary, {generic, Second_payload}}}]},
Server_signature},
{ok, _pipe@5}
end
).