zf

zenflows testing
git clone https://s.sonu.ch/~srfsh/zf.git
Log | Files | Refs | Submodules | README | LICENSE

scram.ex (2731B)


      1 defmodule Postgrex.SCRAM do
      2   @moduledoc false
      3 
      4   @hash_length 32
      5   @nonce_length 24
      6   @nonce_rand_bytes div(@nonce_length * 6, 8)
      7   @nonce_prefix "n,,n=,r="
      8   @nonce_encoded_size <<byte_size(@nonce_prefix) + @nonce_length::signed-size(32)>>
      9 
     10   def challenge do
     11     nonce = @nonce_rand_bytes |> :crypto.strong_rand_bytes() |> Base.encode64()
     12     ["SCRAM-SHA-256", 0, @nonce_encoded_size, @nonce_prefix, nonce]
     13   end
     14 
     15   def verify(data, opts) do
     16     server =
     17       for kv <- :binary.split(data, ",", [:global]), into: %{} do
     18         <<k, "=", v::binary>> = kv
     19         {k, v}
     20       end
     21 
     22     {:ok, server_s} = Base.decode64(server[?s])
     23     server_i = String.to_integer(server[?i])
     24 
     25     pass = Keyword.fetch!(opts, :password)
     26     salted_pass = hash_password(pass, server_s, server_i)
     27 
     28     client_key = hmac(:sha256, salted_pass, "Client Key")
     29     client_nonce = binary_part(server[?r], 0, @nonce_length)
     30 
     31     message = ["n=,r=", client_nonce, ",r=", server[?r], ",s=", server[?s], ",i=", server[?i], ?,]
     32     message_without_proof = ["c=biws,r=", server[?r]]
     33 
     34     auth_message = IO.iodata_to_binary([message | message_without_proof])
     35     client_sig = hmac(:sha256, :crypto.hash(:sha256, client_key), auth_message)
     36     proof = Base.encode64(:crypto.exor(client_key, client_sig))
     37     [message_without_proof, ",p=", proof]
     38   end
     39 
     40   defp hash_password(secret, salt, iterations) do
     41     hash_password(secret, salt, iterations, 1, [], 0)
     42   end
     43 
     44   defp hash_password(_secret, _salt, _iterations, _block_index, acc, length)
     45        when length >= @hash_length do
     46     acc
     47     |> IO.iodata_to_binary()
     48     |> binary_part(0, @hash_length)
     49   end
     50 
     51   defp hash_password(secret, salt, iterations, block_index, acc, length) do
     52     initial = hmac(:sha256, secret, <<salt::binary, block_index::integer-size(32)>>)
     53     block = iterate(secret, iterations - 1, initial, initial)
     54     length = byte_size(block) + length
     55     hash_password(secret, salt, iterations, block_index + 1, [acc | block], length)
     56   end
     57 
     58   defp iterate(_secret, 0, _prev, acc), do: acc
     59 
     60   defp iterate(secret, iteration, prev, acc) do
     61     next = hmac(:sha256, secret, prev)
     62     iterate(secret, iteration - 1, next, :crypto.exor(next, acc))
     63   end
     64 
     65   # :crypto.mac/4 was added in OTP-22.1, and :crypto.hmac/3 removed in OTP-24.
     66   # Check which function to use at compile time to avoid doing a round-trip
     67   # to the code server on every call. The downside is this module won't work
     68   # if it's compiled on OTP-22.0 or older then executed on OTP-24 or newer.
     69   if Code.ensure_loaded?(:crypto) and function_exported?(:crypto, :mac, 4) do
     70     defp hmac(type, key, data), do: :crypto.mac(:hmac, type, key, data)
     71   else
     72     defp hmac(type, key, data), do: :crypto.hmac(type, key, data)
     73   end
     74 end