-module(pgl@internal@scram). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/pgl/internal/scram.gleam"). -export([client_first/2, parse_server_first/2, parse_server_final/1, get_nonce/1, client_final/4]). -export_type([server_first/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. ?MODULEDOC(false). -type server_first() :: {server_first, bitstring(), bitstring(), integer(), bitstring()}. -file("src/pgl/internal/scram.gleam", 15). ?DOC(false). -spec client_first(bitstring(), bitstring()) -> bitstring(). client_first(User, Nonce) -> <<"n,,n="/utf8, User/bitstring, ",r="/utf8, Nonce/bitstring>>. -file("src/pgl/internal/scram.gleam", 90). ?DOC(false). -spec parse_server_first(bitstring(), bitstring()) -> {ok, server_first()} | {error, pgl@internal:internal_error()}. parse_server_first(Server_first, Client_nonce) -> Parts = begin _pipe = gleam@bit_array:to_string(Server_first), _pipe@1 = gleam@result:map( _pipe, fun(_capture) -> gleam@string:split(_capture, <<","/utf8>>) end ), gleam@result:unwrap(_pipe@1, []) end, _pipe@2 = case Parts of [<<"r="/utf8, Nonce/binary>>, <<"s="/utf8, Salt/binary>>, <<"i="/utf8, Iters/binary>>] -> Nonce@1 = gleam_stdlib:identity(Nonce), gleam@result:'try'( gleam@bit_array:base64_decode(Salt), fun(Salt@1) -> gleam@result:'try'( gleam_stdlib:parse_int(Iters), fun(Iterations) -> Size = erlang:byte_size(Client_nonce), case Nonce@1 of <<_:Size/bitstring, _/bitstring>> -> {ok, {server_first, Nonce@1, Salt@1, Iterations, Server_first}}; _ -> {error, nil} end end ) end ); _ -> {error, nil} end, gleam@result:map_error( _pipe@2, fun(_) -> {protocol_error, sasl_server_first, <<"Failed to parse server_first"/utf8>>} end ). -file("src/pgl/internal/scram.gleam", 124). ?DOC(false). -spec parse_server_final(bitstring()) -> {ok, bitstring()} | {error, pgl@internal:internal_error()}. parse_server_final(Server_final) -> case Server_final of <<"v="/utf8, Final/bitstring>> -> _pipe = gleam@bit_array:to_string(Final), _pipe@1 = gleam@result:map( _pipe, fun(_capture) -> gleam@string:split(_capture, <<","/utf8>>) end ), _pipe@2 = gleam@result:'try'(_pipe@1, fun gleam@list:first/1), _pipe@3 = gleam@result:'try'( _pipe@2, fun gleam@bit_array:base64_decode/1 ), gleam@result:map_error( _pipe@3, fun(_) -> {protocol_error, sasl_server_final, <<"Failed to parse server_final"/utf8>>} end ); <<"e="/utf8, Error/bitstring>> -> Error_value = begin _pipe@4 = gleam@bit_array:to_string(Error), gleam@result:unwrap(_pipe@4, <<""/utf8>>) end, _pipe@5 = {protocol_error, sasl_server_error, <<<<"Server error: '"/utf8, Error_value/binary>>/binary, "'"/utf8>>}, {error, _pipe@5}; _ -> erlang:error(#{gleam_error => panic, message => <<"Unexpected SASL server final payload"/utf8>>, file => <>, module => <<"pgl/internal/scram"/utf8>>, function => <<"parse_server_final"/utf8>>, line => 153}) end. -file("src/pgl/internal/scram.gleam", 163). ?DOC(false). -spec do_hi(bitstring(), bitstring(), bitstring(), integer()) -> bitstring(). do_hi(Str, U, Hi, I) -> case I > 0 of false -> Hi; true -> U2 = gleam_crypto_ffi:hmac(U, sha256, Str), Hi1 = crypto:exor(Hi, U2), do_hi(Str, U2, Hi1, I - 1) end. -file("src/pgl/internal/scram.gleam", 157). ?DOC(false). -spec hi(bitstring(), bitstring(), integer()) -> bitstring(). hi(Str, Salt, I) -> U1 = gleam_crypto_ffi:hmac( <>, sha256, Str ), do_hi(Str, U1, U1, I - 1). -file("src/pgl/internal/scram.gleam", 19). ?DOC(false). -spec get_nonce(integer()) -> bitstring(). get_nonce(Num_random_bytes) -> Random = crypto:strong_rand_bytes(Num_random_bytes), Unique = <<(pgl_ffi:unique_int())>>, Nonce_bin = <>, _pipe = gleam_stdlib:base64_encode(Nonce_bin, true), gleam_stdlib:identity(_pipe). -file("src/pgl/internal/scram.gleam", 34). ?DOC(false). -spec client_final(server_first(), bitstring(), bitstring(), bitstring()) -> {bitstring(), bitstring()}. client_final(Server_first, Client_nonce, Username, Password) -> Channel_binding = <<"c=biws"/utf8>>, Nonce = [<<"r="/utf8>>, erlang:element(2, Server_first)], Salted_password = begin _pipe = Password, _pipe@1 = pgl@internal@sasl:validate(_pipe), _pipe@2 = gleam@result:unwrap(_pipe@1, <<>>), hi( _pipe@2, erlang:element(3, Server_first), erlang:element(4, Server_first) ) end, Client_key = gleam_crypto_ffi:hmac( <<"Client Key"/utf8>>, sha256, Salted_password ), Auth_message = begin _pipe@3 = <<"n="/utf8, Username/bitstring, ",r="/utf8, Client_nonce/bitstring>>, _pipe@4 = gleam@bytes_tree:from_bit_array(_pipe@3), _pipe@5 = gleam@bytes_tree:append(_pipe@4, <<","/utf8>>), _pipe@6 = gleam@bytes_tree:append( _pipe@5, erlang:element(5, Server_first) ), _pipe@7 = gleam@bytes_tree:append(_pipe@6, <<","/utf8>>), _pipe@8 = gleam@bytes_tree:append(_pipe@7, Channel_binding), _pipe@9 = gleam@bytes_tree:append(_pipe@8, <<","/utf8>>), _pipe@10 = gleam@list:fold( Nonce, _pipe@9, fun gleam@bytes_tree:append/2 ), erlang:list_to_bitstring(_pipe@10) end, Client_signature = begin _pipe@11 = sha256, _pipe@12 = gleam@crypto:hash(_pipe@11, Client_key), gleam_crypto_ffi:hmac(Auth_message, sha256, _pipe@12) end, Encoded_client_proof = begin _pipe@13 = Client_key, _pipe@14 = crypto:exor(_pipe@13, Client_signature), _pipe@15 = gleam_stdlib:base64_encode(_pipe@14, true), gleam_stdlib:identity(_pipe@15) end, Server_signature = begin _pipe@16 = Salted_password, _pipe@17 = gleam_crypto_ffi:hmac( <<"Server Key"/utf8>>, sha256, _pipe@16 ), gleam_crypto_ffi:hmac(Auth_message, sha256, _pipe@17) end, Encoded_client_final = begin _pipe@18 = gleam@bytes_tree:new(), _pipe@19 = gleam@bytes_tree:append(_pipe@18, Channel_binding), _pipe@20 = gleam@bytes_tree:append(_pipe@19, <<","/utf8>>), _pipe@21 = gleam@list:fold( Nonce, _pipe@20, fun gleam@bytes_tree:append/2 ), _pipe@22 = gleam@bytes_tree:append(_pipe@21, <<",p="/utf8>>), _pipe@23 = gleam@bytes_tree:append(_pipe@22, Encoded_client_proof), erlang:list_to_bitstring(_pipe@23) end, {Encoded_client_final, Server_signature}.