zf

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

crypto.ex (12693B)


      1 defmodule Plug.Crypto do
      2   @moduledoc """
      3   Namespace and module for crypto-related functionality.
      4 
      5   For low-level functionality, see `Plug.Crypto.KeyGenerator`,
      6   `Plug.Crypto.MessageEncryptor`, and `Plug.Crypto.MessageVerifier`.
      7   """
      8 
      9   import Bitwise
     10   alias Plug.Crypto.{KeyGenerator, MessageVerifier, MessageEncryptor}
     11 
     12   @doc """
     13   Prunes the stacktrace to remove any argument trace.
     14 
     15   This is useful when working with functions that receives secrets
     16   and we want to make sure those secrets do not leak on error messages.
     17   """
     18   @spec prune_args_from_stacktrace(Exception.stacktrace()) :: Exception.stacktrace()
     19   def prune_args_from_stacktrace(stacktrace)
     20 
     21   def prune_args_from_stacktrace([{mod, fun, [_ | _] = args, info} | rest]),
     22     do: [{mod, fun, length(args), info} | rest]
     23 
     24   def prune_args_from_stacktrace(stacktrace) when is_list(stacktrace),
     25     do: stacktrace
     26 
     27   @doc false
     28   @deprecated "Use non_executable_binary_to_term/2"
     29   def safe_binary_to_term(binary, opts \\ []) do
     30     non_executable_binary_to_term(binary, opts)
     31   end
     32 
     33   @doc """
     34   A restricted version of `:erlang.binary_to_term/2` that forbids
     35   *executable* terms, such as anonymous functions.
     36 
     37   The `opts` are given to the underlying `:erlang.binary_to_term/2`
     38   call, with an empty list as a default.
     39 
     40   By default this function does not restrict atoms, as an atom
     41   interned in one node may not yet have been interned on another
     42   (except for releases, which preload all code).
     43 
     44   If you want to avoid atoms from being created, then you can pass
     45   `[:safe]` as options, as that will also enable the safety mechanisms
     46   from `:erlang.binary_to_term/2` itself.
     47   """
     48   @spec non_executable_binary_to_term(binary(), [atom()]) :: term()
     49   def non_executable_binary_to_term(binary, opts \\ []) when is_binary(binary) do
     50     term = :erlang.binary_to_term(binary, opts)
     51     non_executable_terms(term)
     52     term
     53   end
     54 
     55   defp non_executable_terms(list) when is_list(list) do
     56     non_executable_list(list)
     57   end
     58 
     59   defp non_executable_terms(tuple) when is_tuple(tuple) do
     60     non_executable_tuple(tuple, tuple_size(tuple))
     61   end
     62 
     63   defp non_executable_terms(map) when is_map(map) do
     64     folder = fn key, value, acc ->
     65       non_executable_terms(key)
     66       non_executable_terms(value)
     67       acc
     68     end
     69 
     70     :maps.fold(folder, map, map)
     71   end
     72 
     73   defp non_executable_terms(other)
     74        when is_atom(other) or is_number(other) or is_bitstring(other) or is_pid(other) or
     75               is_reference(other) do
     76     other
     77   end
     78 
     79   defp non_executable_terms(other) do
     80     raise ArgumentError,
     81           "cannot deserialize #{inspect(other)}, the term is not safe for deserialization"
     82   end
     83 
     84   defp non_executable_list([]), do: :ok
     85 
     86   defp non_executable_list([h | t]) when is_list(t) do
     87     non_executable_terms(h)
     88     non_executable_list(t)
     89   end
     90 
     91   defp non_executable_list([h | t]) do
     92     non_executable_terms(h)
     93     non_executable_terms(t)
     94   end
     95 
     96   defp non_executable_tuple(_tuple, 0), do: :ok
     97 
     98   defp non_executable_tuple(tuple, n) do
     99     non_executable_terms(:erlang.element(n, tuple))
    100     non_executable_tuple(tuple, n - 1)
    101   end
    102 
    103   @doc """
    104   Masks the token on the left with the token on the right.
    105 
    106   Both tokens are required to have the same size.
    107   """
    108   @spec mask(binary(), binary()) :: binary()
    109   def mask(left, right) do
    110     :crypto.exor(left, right)
    111   end
    112 
    113   @doc """
    114   Compares the two binaries (one being masked) in constant-time to avoid
    115   timing attacks.
    116 
    117   It is assumed the right token is masked according to the given mask.
    118   """
    119   @spec masked_compare(binary(), binary(), binary()) :: boolean()
    120   def masked_compare(left, right, mask)
    121       when is_binary(left) and is_binary(right) and is_binary(mask) do
    122     byte_size(left) == byte_size(right) and masked_compare(left, right, mask, 0)
    123   end
    124 
    125   defp masked_compare(<<x, left::binary>>, <<y, right::binary>>, <<z, mask::binary>>, acc) do
    126     xorred = bxor(x, bxor(y, z))
    127     masked_compare(left, right, mask, acc ||| xorred)
    128   end
    129 
    130   defp masked_compare(<<>>, <<>>, <<>>, acc) do
    131     acc === 0
    132   end
    133 
    134   @doc """
    135   Compares the two binaries in constant-time to avoid timing attacks.
    136 
    137   See: http://codahale.com/a-lesson-in-timing-attacks/
    138   """
    139   @spec secure_compare(binary(), binary()) :: boolean()
    140   def secure_compare(left, right) when is_binary(left) and is_binary(right) do
    141     byte_size(left) == byte_size(right) and secure_compare(left, right, 0)
    142   end
    143 
    144   defp secure_compare(<<x, left::binary>>, <<y, right::binary>>, acc) do
    145     xorred = bxor(x, y)
    146     secure_compare(left, right, acc ||| xorred)
    147   end
    148 
    149   defp secure_compare(<<>>, <<>>, acc) do
    150     acc === 0
    151   end
    152 
    153   @doc """
    154   Encodes and signs data into a token you can send to clients.
    155 
    156       Plug.Crypto.sign(conn.secret_key_base, "user-secret", {:elixir, :terms})
    157 
    158   A key will be derived from the secret key base and the given user secret.
    159   The key will also be cached for performance reasons on future calls.
    160 
    161   ## Options
    162 
    163     * `:key_iterations` - option passed to `Plug.Crypto.KeyGenerator`
    164       when generating the encryption and signing keys. Defaults to 1000
    165     * `:key_length` - option passed to `Plug.Crypto.KeyGenerator`
    166       when generating the encryption and signing keys. Defaults to 32
    167     * `:key_digest` - option passed to `Plug.Crypto.KeyGenerator`
    168       when generating the encryption and signing keys. Defaults to `:sha256`
    169     * `:signed_at` - set the timestamp of the token in seconds.
    170       Defaults to `System.system_time(:second)`
    171     * `:max_age` - the default maximum age of the token. Defaults to
    172       `86400` seconds (1 day) and it may be overridden on `verify/4`.
    173 
    174   """
    175   def sign(key_base, salt, data, opts \\ []) when is_binary(key_base) and is_binary(salt) do
    176     data
    177     |> encode(opts)
    178     |> MessageVerifier.sign(get_secret(key_base, salt, opts))
    179   end
    180 
    181   @doc """
    182   Encodes, encrypts, and signs data into a token you can send to clients.
    183 
    184       Plug.Crypto.encrypt(conn.secret_key_base, "user-secret", {:elixir, :terms})
    185 
    186   A key will be derived from the secret key base and the given user secret.
    187   The key will also be cached for performance reasons on future calls.
    188 
    189   ## Options
    190 
    191     * `:key_iterations` - option passed to `Plug.Crypto.KeyGenerator`
    192       when generating the encryption and signing keys. Defaults to 1000
    193     * `:key_length` - option passed to `Plug.Crypto.KeyGenerator`
    194       when generating the encryption and signing keys. Defaults to 32
    195     * `:key_digest` - option passed to `Plug.Crypto.KeyGenerator`
    196       when generating the encryption and signing keys. Defaults to `:sha256`
    197     * `:signed_at` - set the timestamp of the token in seconds.
    198       Defaults to `System.system_time(:second)`
    199     * `:max_age` - the default maximum age of the token. Defaults to
    200       `86400` seconds (1 day) and it may be overridden on `decrypt/4`.
    201 
    202   """
    203   def encrypt(key_base, secret, data, opts \\ [])
    204       when is_binary(key_base) and is_binary(secret) do
    205     encrypt(key_base, secret, nil, data, opts)
    206   end
    207 
    208   @doc false
    209   def encrypt(key_base, secret, salt, data, opts) do
    210     data
    211     |> encode(opts)
    212     |> MessageEncryptor.encrypt(
    213       get_secret(key_base, secret, opts),
    214       get_secret(key_base, salt, opts)
    215     )
    216   end
    217 
    218   defp encode(data, opts) do
    219     signed_at_seconds = Keyword.get(opts, :signed_at)
    220     signed_at_ms = if signed_at_seconds, do: trunc(signed_at_seconds * 1000), else: now_ms()
    221     max_age_in_seconds = Keyword.get(opts, :max_age, 86400)
    222     :erlang.term_to_binary({data, signed_at_ms, max_age_in_seconds})
    223   end
    224 
    225   @doc """
    226   Decodes the original data from the token and verifies its integrity.
    227 
    228   ## Examples
    229 
    230   In this scenario we will create a token, sign it, then provide it to a client
    231   application. The client will then use this token to authenticate requests for
    232   resources from the server. See `Plug.Crypto` summary for more info about
    233   creating tokens.
    234 
    235       iex> user_id    = 99
    236       iex> secret     = "kjoy3o1zeidquwy1398juxzldjlksahdk3"
    237       iex> user_salt  = "user salt"
    238       iex> token      = Plug.Crypto.sign(secret, user_salt, user_id)
    239 
    240   The mechanism for passing the token to the client is typically through a
    241   cookie, a JSON response body, or HTTP header. For now, assume the client has
    242   received a token it can use to validate requests for protected resources.
    243 
    244   When the server receives a request, it can use `verify/4` to determine if it
    245   should provide the requested resources to the client:
    246 
    247       iex> Plug.Crypto.verify(secret, user_salt, token, max_age: 86400)
    248       {:ok, 99}
    249 
    250   In this example, we know the client sent a valid token because `verify/4`
    251   returned a tuple of type `{:ok, user_id}`. The server can now proceed with
    252   the request.
    253 
    254   However, if the client had sent an expired or otherwise invalid token
    255   `verify/4` would have returned an error instead:
    256 
    257       iex> Plug.Crypto.verify(secret, user_salt, expired, max_age: 86400)
    258       {:error, :expired}
    259 
    260       iex> Plug.Crypto.verify(secret, user_salt, invalid, max_age: 86400)
    261       {:error, :invalid}
    262 
    263   ## Options
    264 
    265     * `:max_age` - verifies the token only if it has been generated
    266       "max age" ago in seconds. Defaults to the max age signed in the
    267       token (86400)
    268     * `:key_iterations` - option passed to `Plug.Crypto.KeyGenerator`
    269       when generating the encryption and signing keys. Defaults to 1000
    270     * `:key_length` - option passed to `Plug.Crypto.KeyGenerator`
    271       when generating the encryption and signing keys. Defaults to 32
    272     * `:key_digest` - option passed to `Plug.Crypto.KeyGenerator`
    273       when generating the encryption and signing keys. Defaults to `:sha256`
    274 
    275   """
    276   def verify(key_base, salt, token, opts \\ [])
    277 
    278   def verify(key_base, salt, token, opts)
    279       when is_binary(key_base) and is_binary(salt) and is_binary(token) do
    280     secret = get_secret(key_base, salt, opts)
    281 
    282     case MessageVerifier.verify(token, secret) do
    283       {:ok, message} -> decode(message, opts)
    284       :error -> {:error, :invalid}
    285     end
    286   end
    287 
    288   def verify(_key_base, salt, nil, _opts) when is_binary(salt) do
    289     {:error, :missing}
    290   end
    291 
    292   @doc """
    293   Decrypts the original data from the token and verifies its integrity.
    294 
    295   ## Options
    296 
    297     * `:max_age` - verifies the token only if it has been generated
    298       "max age" ago in seconds. A reasonable value is 1 day (86400
    299       seconds)
    300     * `:key_iterations` - option passed to `Plug.Crypto.KeyGenerator`
    301       when generating the encryption and signing keys. Defaults to 1000
    302     * `:key_length` - option passed to `Plug.Crypto.KeyGenerator`
    303       when generating the encryption and signing keys. Defaults to 32
    304     * `:key_digest` - option passed to `Plug.Crypto.KeyGenerator`
    305       when generating the encryption and signing keys. Defaults to `:sha256`
    306 
    307   """
    308   def decrypt(key_base, secret, token, opts \\ [])
    309       when is_binary(key_base) and is_binary(secret) and is_list(opts) do
    310     decrypt(key_base, secret, nil, token, opts)
    311   end
    312 
    313   @doc false
    314   def decrypt(key_base, secret, salt, token, opts) when is_binary(token) do
    315     secret = get_secret(key_base, secret, opts)
    316     salt = get_secret(key_base, salt, opts)
    317 
    318     case MessageEncryptor.decrypt(token, secret, salt) do
    319       {:ok, message} -> decode(message, opts)
    320       :error -> {:error, :invalid}
    321     end
    322   end
    323 
    324   def decrypt(_key_base, _secret, _salt, nil, _opts) do
    325     {:error, :missing}
    326   end
    327 
    328   defp decode(message, opts) do
    329     {data, signed, max_age} =
    330       case non_executable_binary_to_term(message) do
    331         {data, signed, max_age} -> {data, signed, max_age}
    332         # For backwards compatibility with Plug.Crypto v1.1
    333         {data, signed} -> {data, signed, 86400}
    334         # For backwards compatibility with Phoenix which had the original code
    335         %{data: data, signed: signed} -> {data, signed, 86400}
    336       end
    337 
    338     if expired?(signed, Keyword.get(opts, :max_age, max_age)) do
    339       {:error, :expired}
    340     else
    341       {:ok, data}
    342     end
    343   end
    344 
    345   ## Helpers
    346 
    347   # Gathers configuration and generates the key secrets and signing secrets.
    348   defp get_secret(_secret_key_base, nil, _opts) do
    349     ""
    350   end
    351 
    352   defp get_secret(secret_key_base, salt, opts) do
    353     iterations = Keyword.get(opts, :key_iterations, 1000)
    354     length = Keyword.get(opts, :key_length, 32)
    355     digest = Keyword.get(opts, :key_digest, :sha256)
    356     cache = Keyword.get(opts, :cache, Plug.Crypto.Keys)
    357     KeyGenerator.generate(secret_key_base, salt, iterations, length, digest, cache)
    358   end
    359 
    360   defp expired?(_signed, :infinity), do: false
    361   defp expired?(_signed, max_age_secs) when max_age_secs <= 0, do: true
    362   defp expired?(signed, max_age_secs), do: signed + trunc(max_age_secs * 1000) < now_ms()
    363 
    364   defp now_ms, do: System.system_time(:millisecond)
    365 end