Current section

Files

Jump to
gleam_mongo src mongo@scram.erl
Raw

src/mongo@scram.erl

-module(mongo@scram).
-compile(no_auto_import).
-export([first_payload/1, first_message/1, parse_first_reply/1, second_message/5, parse_second_reply/2, hi/3]).
-spec first_payload(binary()) -> binary().
first_payload(Username) ->
Nonce = begin
_pipe = gleam@crypto:strong_random_bytes(24),
gleam@base:encode64(_pipe, true)
end,
_pipe@2 = [<<"n="/utf8>>,
begin
_pipe@1 = Username,
clean_username(_pipe@1)
end,
<<",r="/utf8>>,
Nonce],
gleam@string:concat(_pipe@2).
-spec first_message(binary()) -> bson@types: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_first_reply(list({binary(), bson@types:value()})) -> {ok,
{{binary(),
binary(),
integer()},
binary(),
integer()}} |
{error, nil}.
parse_first_reply(Reply) ->
case Reply of
[{<<"ok"/utf8>>, {double, 0.0}} | _@1] ->
{error, nil};
[{<<"conversationId"/utf8>>, {integer, Cid}},
{<<"done"/utf8>>, {boolean, false}},
{<<"payload"/utf8>>, {binary, {generic, Data}}},
{<<"ok"/utf8>>, {double, 1.0}}] ->
case begin
_pipe = Data,
bson@generic:to_string(_pipe)
end of
{error, _try} -> {error, _try};
{ok, Data@1} ->
case begin
_pipe@1 = Data@1,
parse_payload(_pipe@1)
end of
{error, _try@1} -> {error, _try@1};
{ok, [{<<"r"/utf8>>, Rnonce},
{<<"s"/utf8>>, Salt},
{<<"i"/utf8>>, I}]} ->
case begin
_pipe@2 = I,
gleam@int:parse(_pipe@2)
end 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 second_message(
{binary(), binary(), integer()},
binary(),
binary(),
integer(),
binary()
) -> {ok, {bson@types:value(), bitstring()}} | {error, nil}.
second_message(Server_params, First_payload, Server_payload, Cid, Password) ->
{Rnonce, Salt, Iterations} = Server_params,
case begin
_pipe = Salt,
gleam@base:decode64(_pipe)
end of
{error, _try} -> {error, _try};
{ok, Salt@1} ->
Salted_password = hi(Password, Salt@1, Iterations),
Client_key = gleam@crypto:hmac(
begin
_pipe@1 = <<"Client Key"/utf8>>,
gleam@bit_string:from_string(_pipe@1)
end,
sha256,
Salted_password
),
Server_key = gleam@crypto:hmac(
begin
_pipe@2 = <<"Server Key"/utf8>>,
gleam@bit_string:from_string(_pipe@2)
end,
sha256,
Salted_password
),
Stored_key = gleam@crypto:hash(sha256, Client_key),
Auth_message = begin
_pipe@3 = [First_payload,
<<","/utf8>>,
Server_payload,
<<",c=biws,r="/utf8>>,
Rnonce],
_pipe@4 = gleam@string:concat(_pipe@3),
bson@generic:from_string(_pipe@4)
end,
Client_signature = gleam@crypto:hmac(
begin
_pipe@5 = Auth_message,
bson@generic:to_bit_string(_pipe@5)
end,
sha256,
Stored_key
),
Second_payload = begin
_pipe@7 = [<<"c=biws,r="/utf8>>,
Rnonce,
<<",p="/utf8>>,
begin
_pipe@6 = 'xor'(
Client_key,
Client_signature,
<<>>
),
gleam@base:encode64(_pipe@6, true)
end],
_pipe@8 = gleam@string:concat(_pipe@7),
bson@generic:from_string(_pipe@8)
end,
Server_signature = gleam@crypto:hmac(
begin
_pipe@9 = Auth_message,
bson@generic:to_bit_string(_pipe@9)
end,
sha256,
Server_key
),
{ok,
{{document,
[{<<"saslContinue"/utf8>>, {boolean, true}},
{<<"conversationId"/utf8>>, {integer, Cid}},
{<<"payload"/utf8>>, {binary, {generic, Second_payload}}}]},
Server_signature}}
end.
-spec parse_second_reply(list({binary(), bson@types:value()}), bitstring()) -> {ok,
nil} |
{error, nil}.
parse_second_reply(Reply, Server_signature) ->
case Reply of
[{<<"ok"/utf8>>, {double, 0.0}} | _@1] ->
{error, nil};
[{<<"conversationId"/utf8>>, _@2},
{<<"done"/utf8>>, {boolean, true}},
{<<"payload"/utf8>>, {binary, {generic, Data}}},
{<<"ok"/utf8>>, {double, 1.0}}] ->
case begin
_pipe = Data,
bson@generic:to_string(_pipe)
end of
{error, _try} -> {error, _try};
{ok, Data@1} ->
case parse_payload(Data@1) of
{error, _try@1} -> {error, _try@1};
{ok, [{<<"v"/utf8>>, Data@2}]} ->
case gleam@base:decode64(Data@2) of
{error, _try@2} -> {error, _try@2};
{ok, 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 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) ->
_pipe@2 = Item,
gleam@string:split_once(_pipe@2, <<"="/utf8>>)
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 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;
_@1 ->
'xor'(Ra, Rb, New_storage)
end.