update
This commit is contained in:
BIN
phoenix/deps/plug_crypto/.hex
Normal file
BIN
phoenix/deps/plug_crypto/.hex
Normal file
Binary file not shown.
57
phoenix/deps/plug_crypto/CHANGELOG.md
Normal file
57
phoenix/deps/plug_crypto/CHANGELOG.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# Changelog
|
||||
|
||||
## v2.1.1 (2025-04-03)
|
||||
|
||||
* Fall back `hash_equals` when missing OpenSSL support
|
||||
|
||||
## v2.1.0 (2024-05-02)
|
||||
|
||||
* Use `System.os_time/1` as the token signing date, since tokens are meant to be shared across machines
|
||||
|
||||
## v2.0.0 (2023-10-06)
|
||||
|
||||
* Update Elixir requirement to v1.11+ and require Erlang/OTP 23.
|
||||
* Encryption now uses XChaCha20-Poly1305, which is safer, faster, and generates smaller payloads. This means data encrypted with Plug.Crypto v2.0 cannot be decrypted on Plug.Crypto v1.x. However, Plug.Crypto v2.0 can still decrypt data from Plug.Crypto v1.0.
|
||||
* Optimize `secure_compare`, `masked_compare`, and key generator algorithms by relying on `:crypto` code when using more recent Erlang/OTP versions.
|
||||
|
||||
## v1.2.5 (2023-03-10)
|
||||
|
||||
* Allow AAD to be given as iolist
|
||||
|
||||
## v1.2.4 (2023-03-10)
|
||||
|
||||
* Allow AAD to be given as argument on message encryptor
|
||||
|
||||
## v1.2.3 (2022-08-19)
|
||||
|
||||
* Remove warnings on Elixir v1.14
|
||||
|
||||
## v1.2.2 (2021-03-25)
|
||||
|
||||
* Remove warnings on Elixir v1.12
|
||||
|
||||
## v1.2.1 (2021-02-17)
|
||||
|
||||
* Add support for Erlang/OTP 24
|
||||
|
||||
## v1.2.0 (2020-10-07)
|
||||
|
||||
* Update Elixir requirement to Elixir 1.7+.
|
||||
* Fixed a bug that allowed to sign and encrypt stuff with `nil` secret key base and salt.
|
||||
|
||||
## v1.1.2 (2020-02-16)
|
||||
|
||||
* Do not key derive empty salts (default to no salt instead).
|
||||
|
||||
## v1.1.1 (2020-02-14)
|
||||
|
||||
* Do not expose encryption with salt API.
|
||||
* Allow default `:max_age` to be set when signing/encrypting.
|
||||
|
||||
## v1.1.0 (2020-02-11)
|
||||
|
||||
* Add high-level `Plug.Crypto.sign/verify` and `Plug.Crypto.encrypt/decrypt`.
|
||||
|
||||
## v1.0.0 (2018-10-03)
|
||||
|
||||
* Split up the `plug_crypto` project from Plug as per [elixir-lang/plug#766](https://github.com/elixir-plug/plug/issues/766).
|
||||
13
phoenix/deps/plug_crypto/LICENSE
Normal file
13
phoenix/deps/plug_crypto/LICENSE
Normal file
@@ -0,0 +1,13 @@
|
||||
Copyright (c) 2018 Plataformatec.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
30
phoenix/deps/plug_crypto/README.md
Normal file
30
phoenix/deps/plug_crypto/README.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Plug.Crypto
|
||||
|
||||
[](https://hex.pm/packages/plug_crypto)
|
||||
[](https://github.com/elixir-plug/plug_crypto/actions?query=workflow%3ACI)
|
||||
|
||||
Crypto-related functionality for web applications, used by [Plug](https://github.com/elixir-plug/plug).
|
||||
|
||||
## Installation
|
||||
|
||||
You can use plug_crypto in your projects by adding it to your `mix.exs` dependencies:
|
||||
|
||||
```elixir
|
||||
def deps do
|
||||
[{:plug_crypto, "~> 2.0"}]
|
||||
end
|
||||
```
|
||||
|
||||
If you're using [Plug](https://github.com/elixir-plug/plug), you can already use the functionality in plug_crypto since Plug depends on it.
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome everyone to contribute to Plug.Crypto and help us tackle existing issues!
|
||||
|
||||
- Use the [issue tracker](https://github.com/elixir-plug/plug_crypto/issues) for bug reports or feature requests.
|
||||
- Open a [pull request](https://github.com/elixir-plug/plug_crypto/pulls) when you are ready to contribute.
|
||||
- Do not update the `CHANGELOG.md` when submitting a pull request.
|
||||
|
||||
## License
|
||||
|
||||
Plug.Crypto source code is released under Apache License 2.0. Check the [LICENSE](./LICENSE) file for more information.
|
||||
17
phoenix/deps/plug_crypto/hex_metadata.config
Normal file
17
phoenix/deps/plug_crypto/hex_metadata.config
Normal file
@@ -0,0 +1,17 @@
|
||||
{<<"links">>,
|
||||
[{<<"GitHub">>,<<"https://github.com/elixir-plug/plug_crypto">>}]}.
|
||||
{<<"name">>,<<"plug_crypto">>}.
|
||||
{<<"version">>,<<"2.1.1">>}.
|
||||
{<<"description">>,<<"Crypto-related functionality for the web">>}.
|
||||
{<<"elixir">>,<<"~> 1.11">>}.
|
||||
{<<"files">>,
|
||||
[<<"lib">>,<<"lib/plug">>,<<"lib/plug/crypto">>,
|
||||
<<"lib/plug/crypto/key_generator.ex">>,
|
||||
<<"lib/plug/crypto/message_encryptor.ex">>,
|
||||
<<"lib/plug/crypto/application.ex">>,
|
||||
<<"lib/plug/crypto/message_verifier.ex">>,<<"lib/plug/crypto.ex">>,
|
||||
<<"mix.exs">>,<<"README.md">>,<<"CHANGELOG.md">>,<<"LICENSE">>]}.
|
||||
{<<"app">>,<<"plug_crypto">>}.
|
||||
{<<"licenses">>,[<<"Apache-2.0">>]}.
|
||||
{<<"requirements">>,[]}.
|
||||
{<<"build_tools">>,[<<"mix">>]}.
|
||||
359
phoenix/deps/plug_crypto/lib/plug/crypto.ex
Normal file
359
phoenix/deps/plug_crypto/lib/plug/crypto.ex
Normal file
@@ -0,0 +1,359 @@
|
||||
defmodule Plug.Crypto do
|
||||
@moduledoc """
|
||||
Namespace and module for crypto-related functionality.
|
||||
|
||||
For low-level functionality, see `Plug.Crypto.KeyGenerator`,
|
||||
`Plug.Crypto.MessageEncryptor`, and `Plug.Crypto.MessageVerifier`.
|
||||
"""
|
||||
|
||||
alias Plug.Crypto.{KeyGenerator, MessageVerifier, MessageEncryptor}
|
||||
|
||||
@doc """
|
||||
Prunes the stacktrace to remove any argument trace.
|
||||
|
||||
This is useful when working with functions that receives secrets
|
||||
and we want to make sure those secrets do not leak on error messages.
|
||||
"""
|
||||
@spec prune_args_from_stacktrace(Exception.stacktrace()) :: Exception.stacktrace()
|
||||
def prune_args_from_stacktrace(stacktrace)
|
||||
|
||||
def prune_args_from_stacktrace([{mod, fun, [_ | _] = args, info} | rest]),
|
||||
do: [{mod, fun, length(args), info} | rest]
|
||||
|
||||
def prune_args_from_stacktrace(stacktrace) when is_list(stacktrace),
|
||||
do: stacktrace
|
||||
|
||||
@doc """
|
||||
A restricted version of `:erlang.binary_to_term/2` that forbids
|
||||
*executable* terms, such as anonymous functions.
|
||||
|
||||
The `opts` are given to the underlying `:erlang.binary_to_term/2`
|
||||
call, with an empty list as a default.
|
||||
|
||||
By default this function does not restrict atoms, as an atom
|
||||
interned in one node may not yet have been interned on another
|
||||
(except for releases, which preload all code).
|
||||
|
||||
If you want to avoid atoms from being created, then you can pass
|
||||
`[:safe]` as options, as that will also enable the safety mechanisms
|
||||
from `:erlang.binary_to_term/2` itself.
|
||||
"""
|
||||
@spec non_executable_binary_to_term(binary(), [atom()]) :: term()
|
||||
def non_executable_binary_to_term(binary, opts \\ []) when is_binary(binary) do
|
||||
term = :erlang.binary_to_term(binary, opts)
|
||||
non_executable_terms(term)
|
||||
term
|
||||
end
|
||||
|
||||
defp non_executable_terms(list) when is_list(list) do
|
||||
non_executable_list(list)
|
||||
end
|
||||
|
||||
defp non_executable_terms(tuple) when is_tuple(tuple) do
|
||||
non_executable_tuple(tuple, tuple_size(tuple))
|
||||
end
|
||||
|
||||
defp non_executable_terms(map) when is_map(map) do
|
||||
folder = fn key, value, acc ->
|
||||
non_executable_terms(key)
|
||||
non_executable_terms(value)
|
||||
acc
|
||||
end
|
||||
|
||||
:maps.fold(folder, map, map)
|
||||
end
|
||||
|
||||
defp non_executable_terms(other)
|
||||
when is_atom(other) or is_number(other) or is_bitstring(other) or is_pid(other) or
|
||||
is_reference(other) do
|
||||
other
|
||||
end
|
||||
|
||||
defp non_executable_terms(other) do
|
||||
raise ArgumentError,
|
||||
"cannot deserialize #{inspect(other)}, the term is not safe for deserialization"
|
||||
end
|
||||
|
||||
defp non_executable_list([]), do: :ok
|
||||
|
||||
defp non_executable_list([h | t]) when is_list(t) do
|
||||
non_executable_terms(h)
|
||||
non_executable_list(t)
|
||||
end
|
||||
|
||||
defp non_executable_list([h | t]) do
|
||||
non_executable_terms(h)
|
||||
non_executable_terms(t)
|
||||
end
|
||||
|
||||
defp non_executable_tuple(_tuple, 0), do: :ok
|
||||
|
||||
defp non_executable_tuple(tuple, n) do
|
||||
non_executable_terms(:erlang.element(n, tuple))
|
||||
non_executable_tuple(tuple, n - 1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Masks the token on the left with the token on the right.
|
||||
|
||||
Both tokens are required to have the same size.
|
||||
"""
|
||||
@spec mask(binary(), binary()) :: binary()
|
||||
def mask(left, right) do
|
||||
:crypto.exor(left, right)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Compares the two binaries (one being masked) in constant-time to avoid
|
||||
timing attacks.
|
||||
|
||||
It is assumed the right token is masked according to the given mask.
|
||||
"""
|
||||
@spec masked_compare(binary(), binary(), binary()) :: boolean()
|
||||
def masked_compare(left, right, mask)
|
||||
when is_binary(left) and is_binary(right) and is_binary(mask) do
|
||||
byte_size(left) == byte_size(right) and byte_size(right) == byte_size(mask) and
|
||||
crypto_exor_hash_equals(left, right, mask)
|
||||
end
|
||||
|
||||
defp crypto_exor_hash_equals(x, y, z) do
|
||||
crypto_hash_equals(mask(x, y), z)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Compares the two binaries in constant-time to avoid timing attacks.
|
||||
|
||||
See: http://codahale.com/a-lesson-in-timing-attacks/
|
||||
"""
|
||||
@spec secure_compare(binary(), binary()) :: boolean()
|
||||
def secure_compare(left, right) when is_binary(left) and is_binary(right) do
|
||||
byte_size(left) == byte_size(right) and crypto_hash_equals(left, right)
|
||||
end
|
||||
|
||||
# TODO: remove when we require OTP 25.0
|
||||
if Code.ensure_loaded?(:crypto) and function_exported?(:crypto, :hash_equals, 2) do
|
||||
defp crypto_hash_equals(x, y) do
|
||||
# Depending on the linked OpenSSL library hash_equals is available.
|
||||
# If not, we fall back to the legacy implementation.
|
||||
try do
|
||||
:crypto.hash_equals(x, y)
|
||||
rescue
|
||||
# Still can throw "Unsupported CRYPTO_memcmp"
|
||||
ErlangError ->
|
||||
legacy_secure_compare(x, y, 0)
|
||||
end
|
||||
end
|
||||
else
|
||||
defp crypto_hash_equals(x, y) do
|
||||
legacy_secure_compare(x, y, 0)
|
||||
end
|
||||
end
|
||||
|
||||
defp legacy_secure_compare(<<x, left::binary>>, <<y, right::binary>>, acc) do
|
||||
import Bitwise
|
||||
xorred = bxor(x, y)
|
||||
legacy_secure_compare(left, right, acc ||| xorred)
|
||||
end
|
||||
|
||||
defp legacy_secure_compare(<<>>, <<>>, acc) do
|
||||
acc === 0
|
||||
end
|
||||
|
||||
@doc """
|
||||
Encodes and signs data into a token you can send to clients.
|
||||
|
||||
Plug.Crypto.sign(conn.secret_key_base, "user-secret", {:elixir, :terms})
|
||||
|
||||
A key will be derived from the secret key base and the given user secret.
|
||||
The key will also be cached for performance reasons on future calls.
|
||||
|
||||
## Options
|
||||
|
||||
* `:key_iterations` - option passed to `Plug.Crypto.KeyGenerator`
|
||||
when generating the encryption and signing keys. Defaults to 1000
|
||||
* `:key_length` - option passed to `Plug.Crypto.KeyGenerator`
|
||||
when generating the encryption and signing keys. Defaults to 32
|
||||
* `:key_digest` - option passed to `Plug.Crypto.KeyGenerator`
|
||||
when generating the encryption and signing keys. Defaults to `:sha256`
|
||||
* `:signed_at` - set the timestamp of the token in seconds.
|
||||
Defaults to `System.os_time(:millisecond)`
|
||||
* `:max_age` - the default maximum age of the token. Defaults to
|
||||
`86400` seconds (1 day) and it may be overridden on `verify/4`.
|
||||
|
||||
"""
|
||||
def sign(key_base, salt, data, opts \\ []) when is_binary(key_base) and is_binary(salt) do
|
||||
data
|
||||
|> encode(opts)
|
||||
|> MessageVerifier.sign(get_secret(key_base, salt, opts))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Encodes, encrypts, and signs data into a token you can send to clients.
|
||||
|
||||
Plug.Crypto.encrypt(conn.secret_key_base, "user-secret", {:elixir, :terms})
|
||||
|
||||
A key will be derived from the secret key base and the given user secret.
|
||||
The key will also be cached for performance reasons on future calls.
|
||||
|
||||
## Options
|
||||
|
||||
* `:key_iterations` - option passed to `Plug.Crypto.KeyGenerator`
|
||||
when generating the encryption and signing keys. Defaults to 1000
|
||||
* `:key_length` - option passed to `Plug.Crypto.KeyGenerator`
|
||||
when generating the encryption and signing keys. Defaults to 32
|
||||
* `:key_digest` - option passed to `Plug.Crypto.KeyGenerator`
|
||||
when generating the encryption and signing keys. Defaults to `:sha256`
|
||||
* `:signed_at` - set the timestamp of the token in seconds.
|
||||
Defaults to `System.os_time(:millisecond)`
|
||||
* `:max_age` - the default maximum age of the token. Defaults to
|
||||
`86400` seconds (1 day) and it may be overridden on `decrypt/4`.
|
||||
|
||||
"""
|
||||
def encrypt(key_base, secret, data, opts \\ [])
|
||||
when is_binary(key_base) and is_binary(secret) do
|
||||
data
|
||||
|> encode(opts)
|
||||
|> MessageEncryptor.encrypt(get_secret(key_base, secret, opts), "")
|
||||
end
|
||||
|
||||
defp encode(data, opts) do
|
||||
signed_at_seconds = Keyword.get(opts, :signed_at)
|
||||
signed_at_ms = if signed_at_seconds, do: trunc(signed_at_seconds * 1000), else: now_ms()
|
||||
max_age_in_seconds = Keyword.get(opts, :max_age, 86400)
|
||||
:erlang.term_to_binary({data, signed_at_ms, max_age_in_seconds})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decodes the original data from the token and verifies its integrity.
|
||||
|
||||
## Examples
|
||||
|
||||
In this scenario we will create a token, sign it, then provide it to a client
|
||||
application. The client will then use this token to authenticate requests for
|
||||
resources from the server. See `Plug.Crypto` summary for more info about
|
||||
creating tokens.
|
||||
|
||||
iex> user_id = 99
|
||||
iex> secret = "kjoy3o1zeidquwy1398juxzldjlksahdk3"
|
||||
iex> user_salt = "user salt"
|
||||
iex> token = Plug.Crypto.sign(secret, user_salt, user_id)
|
||||
|
||||
The mechanism for passing the token to the client is typically through a
|
||||
cookie, a JSON response body, or HTTP header. For now, assume the client has
|
||||
received a token it can use to validate requests for protected resources.
|
||||
|
||||
When the server receives a request, it can use `verify/4` to determine if it
|
||||
should provide the requested resources to the client:
|
||||
|
||||
iex> Plug.Crypto.verify(secret, user_salt, token, max_age: 86400)
|
||||
{:ok, 99}
|
||||
|
||||
In this example, we know the client sent a valid token because `verify/4`
|
||||
returned a tuple of type `{:ok, user_id}`. The server can now proceed with
|
||||
the request.
|
||||
|
||||
However, if the client had sent an expired or otherwise invalid token
|
||||
`verify/4` would have returned an error instead:
|
||||
|
||||
iex> Plug.Crypto.verify(secret, user_salt, expired, max_age: 86400)
|
||||
{:error, :expired}
|
||||
|
||||
iex> Plug.Crypto.verify(secret, user_salt, invalid, max_age: 86400)
|
||||
{:error, :invalid}
|
||||
|
||||
## Options
|
||||
|
||||
* `:max_age` - verifies the token only if it has been generated
|
||||
"max age" ago in seconds. Defaults to the max age signed in the
|
||||
token (86400)
|
||||
* `:key_iterations` - option passed to `Plug.Crypto.KeyGenerator`
|
||||
when generating the encryption and signing keys. Defaults to 1000
|
||||
* `:key_length` - option passed to `Plug.Crypto.KeyGenerator`
|
||||
when generating the encryption and signing keys. Defaults to 32
|
||||
* `:key_digest` - option passed to `Plug.Crypto.KeyGenerator`
|
||||
when generating the encryption and signing keys. Defaults to `:sha256`
|
||||
|
||||
"""
|
||||
def verify(key_base, salt, token, opts \\ [])
|
||||
|
||||
def verify(key_base, salt, token, opts)
|
||||
when is_binary(key_base) and is_binary(salt) and is_binary(token) do
|
||||
secret = get_secret(key_base, salt, opts)
|
||||
|
||||
case MessageVerifier.verify(token, secret) do
|
||||
{:ok, message} -> decode(message, opts)
|
||||
:error -> {:error, :invalid}
|
||||
end
|
||||
end
|
||||
|
||||
def verify(_key_base, salt, nil, _opts) when is_binary(salt) do
|
||||
{:error, :missing}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decrypts the original data from the token and verifies its integrity.
|
||||
|
||||
## Options
|
||||
|
||||
* `:max_age` - verifies the token only if it has been generated
|
||||
"max age" ago in seconds. A reasonable value is 1 day (86400
|
||||
seconds)
|
||||
* `:key_iterations` - option passed to `Plug.Crypto.KeyGenerator`
|
||||
when generating the encryption and signing keys. Defaults to 1000
|
||||
* `:key_length` - option passed to `Plug.Crypto.KeyGenerator`
|
||||
when generating the encryption and signing keys. Defaults to 32
|
||||
* `:key_digest` - option passed to `Plug.Crypto.KeyGenerator`
|
||||
when generating the encryption and signing keys. Defaults to `:sha256`
|
||||
|
||||
"""
|
||||
def decrypt(key_base, secret, token, opts \\ [])
|
||||
|
||||
def decrypt(key_base, secret, nil, opts)
|
||||
when is_binary(key_base) and is_binary(secret) and is_list(opts) do
|
||||
{:error, :missing}
|
||||
end
|
||||
|
||||
def decrypt(key_base, secret, token, opts)
|
||||
when is_binary(key_base) and is_binary(secret) and is_list(opts) do
|
||||
secret = get_secret(key_base, secret, opts)
|
||||
|
||||
case MessageEncryptor.decrypt(token, secret, "") do
|
||||
{:ok, message} -> decode(message, opts)
|
||||
:error -> {:error, :invalid}
|
||||
end
|
||||
end
|
||||
|
||||
defp decode(message, opts) do
|
||||
{data, signed, max_age} =
|
||||
case non_executable_binary_to_term(message) do
|
||||
{data, signed, max_age} -> {data, signed, max_age}
|
||||
# For backwards compatibility with Plug.Crypto v1.1
|
||||
{data, signed} -> {data, signed, 86400}
|
||||
# For backwards compatibility with Phoenix which had the original code
|
||||
%{data: data, signed: signed} -> {data, signed, 86400}
|
||||
end
|
||||
|
||||
if expired?(signed, Keyword.get(opts, :max_age, max_age)) do
|
||||
{:error, :expired}
|
||||
else
|
||||
{:ok, data}
|
||||
end
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
# Gathers configuration and generates the key secrets and signing secrets.
|
||||
defp get_secret(secret_key_base, salt, opts) do
|
||||
iterations = Keyword.get(opts, :key_iterations, 1000)
|
||||
length = Keyword.get(opts, :key_length, 32)
|
||||
digest = Keyword.get(opts, :key_digest, :sha256)
|
||||
cache = Keyword.get(opts, :cache, Plug.Crypto.Keys)
|
||||
KeyGenerator.generate(secret_key_base, salt, iterations, length, digest, cache)
|
||||
end
|
||||
|
||||
defp expired?(_signed, :infinity), do: false
|
||||
defp expired?(_signed, max_age_secs) when max_age_secs <= 0, do: true
|
||||
defp expired?(signed, max_age_secs), do: signed + trunc(max_age_secs * 1000) < now_ms()
|
||||
|
||||
defp now_ms, do: System.os_time(:millisecond)
|
||||
end
|
||||
16
phoenix/deps/plug_crypto/lib/plug/crypto/application.ex
Normal file
16
phoenix/deps/plug_crypto/lib/plug/crypto/application.ex
Normal file
@@ -0,0 +1,16 @@
|
||||
defmodule Plug.Crypto.Application do
|
||||
@moduledoc false
|
||||
use Application
|
||||
|
||||
def start(_, _) do
|
||||
children = [
|
||||
{Agent, &start_crypto_keys/0}
|
||||
]
|
||||
|
||||
Supervisor.start_link(children, strategy: :one_for_one, name: __MODULE__)
|
||||
end
|
||||
|
||||
defp start_crypto_keys do
|
||||
Plug.Crypto.Keys = :ets.new(Plug.Crypto.Keys, [:named_table, :public, read_concurrency: true])
|
||||
end
|
||||
end
|
||||
115
phoenix/deps/plug_crypto/lib/plug/crypto/key_generator.ex
Normal file
115
phoenix/deps/plug_crypto/lib/plug/crypto/key_generator.ex
Normal file
@@ -0,0 +1,115 @@
|
||||
defmodule Plug.Crypto.KeyGenerator do
|
||||
@moduledoc """
|
||||
`KeyGenerator` implements PBKDF2 (Password-Based Key Derivation Function 2),
|
||||
part of PKCS #5 v2.0 (Password-Based Cryptography Specification).
|
||||
|
||||
It can be used to derive a number of keys for various purposes from a given
|
||||
secret. This lets applications have a single secure secret, but avoid reusing
|
||||
that key in multiple incompatible contexts.
|
||||
|
||||
The returned key is a binary. You may invoke functions in the `Base` module,
|
||||
such as `Base.url_encode64/2`, to convert this binary into a textual
|
||||
representation.
|
||||
|
||||
See http://tools.ietf.org/html/rfc2898#section-5.2
|
||||
"""
|
||||
|
||||
import Bitwise
|
||||
@max_length bsl(1, 32) - 1
|
||||
|
||||
@doc """
|
||||
Returns a derived key suitable for use.
|
||||
|
||||
## Options
|
||||
|
||||
* `:iterations` - defaults to 1000 (increase to at least 2^16 if used for passwords);
|
||||
* `:length` - a length in octets for the derived key. Defaults to 32;
|
||||
* `:digest` - an hmac function to use as the pseudo-random function. Defaults to `:sha256`;
|
||||
* `:cache` - an ETS table name to be used as cache.
|
||||
Only use an ETS table as cache if the secret and salt is a bound set of values.
|
||||
For example: `:ets.new(:your_name, [:named_table, :public, read_concurrency: true])`
|
||||
|
||||
"""
|
||||
def generate(secret, salt, opts \\ []) do
|
||||
iterations = Keyword.get(opts, :iterations, 1000)
|
||||
length = Keyword.get(opts, :length, 32)
|
||||
digest = Keyword.get(opts, :digest, :sha256)
|
||||
cache = Keyword.get(opts, :cache)
|
||||
generate(secret, salt, iterations, length, digest, cache)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def generate(secret, salt, iterations, length, digest, cache) do
|
||||
cond do
|
||||
not is_integer(iterations) or iterations < 1 ->
|
||||
raise ArgumentError, "iterations must be an integer >= 1"
|
||||
|
||||
length > @max_length ->
|
||||
raise ArgumentError, "length must be less than or equal to #{@max_length}"
|
||||
|
||||
true ->
|
||||
with_cache(cache, {secret, salt, iterations, length, digest}, fn ->
|
||||
pbkdf2_hmac(digest, secret, salt, iterations, length)
|
||||
end)
|
||||
end
|
||||
rescue
|
||||
e -> reraise e, Plug.Crypto.prune_args_from_stacktrace(__STACKTRACE__)
|
||||
end
|
||||
|
||||
defp with_cache(nil, _key, fun), do: fun.()
|
||||
|
||||
defp with_cache(ets, key, fun) do
|
||||
case :ets.lookup(ets, key) do
|
||||
[{_key, value}] ->
|
||||
value
|
||||
|
||||
[] ->
|
||||
value = fun.()
|
||||
:ets.insert(ets, [{key, value}])
|
||||
value
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: remove when we require OTP 24.2
|
||||
if Code.ensure_loaded?(:crypto) and function_exported?(:crypto, :pbkdf2_hmac, 5) do
|
||||
defp pbkdf2_hmac(digest, secret, salt, iterations, length) do
|
||||
:crypto.pbkdf2_hmac(digest, secret, salt, iterations, length)
|
||||
end
|
||||
else
|
||||
defp pbkdf2_hmac(digest, secret, salt, iterations, length) do
|
||||
legacy_pbkdf2_hmac(hmac_fun(digest, secret), salt, iterations, length, 1, [], 0)
|
||||
end
|
||||
|
||||
defp legacy_pbkdf2_hmac(_fun, _salt, _iterations, max_length, _block_index, acc, length)
|
||||
when length >= max_length do
|
||||
acc
|
||||
|> IO.iodata_to_binary()
|
||||
|> binary_part(0, max_length)
|
||||
end
|
||||
|
||||
defp legacy_pbkdf2_hmac(fun, salt, iterations, max_length, block_index, acc, length) do
|
||||
initial = fun.(<<salt::binary, block_index::integer-size(32)>>)
|
||||
block = iterate(fun, iterations - 1, initial, initial)
|
||||
length = byte_size(block) + length
|
||||
|
||||
legacy_pbkdf2_hmac(
|
||||
fun,
|
||||
salt,
|
||||
iterations,
|
||||
max_length,
|
||||
block_index + 1,
|
||||
[acc | block],
|
||||
length
|
||||
)
|
||||
end
|
||||
|
||||
defp iterate(_fun, 0, _prev, acc), do: acc
|
||||
|
||||
defp iterate(fun, iteration, prev, acc) do
|
||||
next = fun.(prev)
|
||||
iterate(fun, iteration - 1, next, :crypto.exor(next, acc))
|
||||
end
|
||||
|
||||
defp hmac_fun(digest, key), do: &:crypto.mac(:hmac, digest, key, &1)
|
||||
end
|
||||
end
|
||||
197
phoenix/deps/plug_crypto/lib/plug/crypto/message_encryptor.ex
Normal file
197
phoenix/deps/plug_crypto/lib/plug/crypto/message_encryptor.ex
Normal file
@@ -0,0 +1,197 @@
|
||||
defmodule Plug.Crypto.MessageEncryptor do
|
||||
@moduledoc ~S"""
|
||||
`MessageEncryptor` is a simple way to encrypt values which get stored
|
||||
somewhere you don't trust.
|
||||
|
||||
The encrypted key, initialization vector, cipher text, and cipher tag
|
||||
are base64url encoded and returned to you.
|
||||
|
||||
This can be used in situations similar to the `Plug.Crypto.MessageVerifier`,
|
||||
but where you don't want users to be able to determine the value of the payload.
|
||||
|
||||
The current algorithm used is XChaCha20-Poly1305.
|
||||
|
||||
## Example
|
||||
|
||||
iex> secret_key_base = "072d1e0157c008193fe48a670cce031faa4e..."
|
||||
...> encrypted_cookie_salt = "encrypted cookie"
|
||||
...> secret = KeyGenerator.generate(secret_key_base, encrypted_cookie_salt)
|
||||
...>
|
||||
...> data = "José"
|
||||
...> encrypted = MessageEncryptor.encrypt(data, secret, "UNUSED")
|
||||
...> MessageEncryptor.decrypt(encrypted, secret, "UNUSED")
|
||||
{:ok, "José"}
|
||||
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Encrypts a message using authenticated encryption.
|
||||
|
||||
The `sign_secret` is currently only used on decryption
|
||||
for backwards compatibility.
|
||||
|
||||
A custom authentication message can be provided.
|
||||
It defaults to "A128GCM" for backwards compatibility.
|
||||
"""
|
||||
def encrypt(message, aad \\ "A128GCM", secret, sign_secret)
|
||||
when is_binary(message) and (is_binary(aad) or is_list(aad)) and
|
||||
bit_size(secret) == 256 and
|
||||
is_binary(sign_secret) do
|
||||
iv = :crypto.strong_rand_bytes(24)
|
||||
{subkey, nonce} = xchacha20_subkey_and_nonce(secret, iv)
|
||||
{cipher_text, cipher_tag} = block_encrypt(:chacha20_poly1305, subkey, nonce, {aad, message})
|
||||
"XCP." <> Base.url_encode64(iv <> cipher_tag <> cipher_text, padding: false)
|
||||
rescue
|
||||
e -> reraise e, Plug.Crypto.prune_args_from_stacktrace(__STACKTRACE__)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decrypts a message using authenticated encryption.
|
||||
"""
|
||||
def decrypt(encrypted, aad \\ "A128GCM", secret, sign_secret)
|
||||
when is_binary(encrypted) and (is_binary(aad) or is_list(aad)) and
|
||||
bit_size(secret) in [128, 192, 256] and
|
||||
is_binary(sign_secret) do
|
||||
unguarded_decrypt(encrypted, aad, secret, sign_secret)
|
||||
rescue
|
||||
e -> reraise e, Plug.Crypto.prune_args_from_stacktrace(__STACKTRACE__)
|
||||
end
|
||||
|
||||
defp unguarded_decrypt("XCP." <> iv_cipher_text_cipher_tag, aad, secret, _sign_secret) do
|
||||
with {:ok, <<iv::192-bits, cipher_tag::128-bits, cipher_text::binary>>} <-
|
||||
Base.url_decode64(iv_cipher_text_cipher_tag, padding: false),
|
||||
{subkey, nonce} = xchacha20_subkey_and_nonce(secret, iv),
|
||||
plain_text when is_binary(plain_text) <-
|
||||
block_decrypt(:chacha20_poly1305, subkey, nonce, {aad, cipher_text, cipher_tag}) do
|
||||
{:ok, plain_text}
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
# Messages from Plug.Crypto v1.x
|
||||
defp unguarded_decrypt("QTEyOEdDTQ." <> rest, aad, secret, sign_secret) do
|
||||
with [encrypted_key, iv, cipher_text, cipher_tag] <- :binary.split(rest, ".", [:global]),
|
||||
{:ok, encrypted_key} <- Base.url_decode64(encrypted_key, padding: false),
|
||||
{:ok, iv} when bit_size(iv) === 96 <- Base.url_decode64(iv, padding: false),
|
||||
{:ok, cipher_text} <- Base.url_decode64(cipher_text, padding: false),
|
||||
{:ok, cipher_tag} when bit_size(cipher_tag) === 128 <-
|
||||
Base.url_decode64(cipher_tag, padding: false),
|
||||
{:ok, key} <- aes_gcm_key_unwrap(encrypted_key, secret, sign_secret),
|
||||
plain_text when is_binary(plain_text) <-
|
||||
block_decrypt(:aes_gcm, key, iv, {aad, cipher_text, cipher_tag}) do
|
||||
{:ok, plain_text}
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
defp unguarded_decrypt(_rest, _aad, _secret, _sign_secret) do
|
||||
:error
|
||||
end
|
||||
|
||||
defp block_encrypt(cipher, key, iv, {aad, payload}) do
|
||||
cipher = cipher_alias(cipher, bit_size(key))
|
||||
:crypto.crypto_one_time_aead(cipher, key, iv, payload, aad, true)
|
||||
catch
|
||||
:error, :notsup -> raise_notsup(cipher)
|
||||
end
|
||||
|
||||
defp block_decrypt(cipher, key, iv, {aad, payload, tag}) do
|
||||
cipher = cipher_alias(cipher, bit_size(key))
|
||||
:crypto.crypto_one_time_aead(cipher, key, iv, payload, aad, tag, false)
|
||||
catch
|
||||
:error, :notsup -> raise_notsup(cipher)
|
||||
end
|
||||
|
||||
defp cipher_alias(:aes_gcm, 128), do: :aes_128_gcm
|
||||
defp cipher_alias(:aes_gcm, 192), do: :aes_192_gcm
|
||||
defp cipher_alias(:aes_gcm, 256), do: :aes_256_gcm
|
||||
defp cipher_alias(other, _), do: other
|
||||
|
||||
defp raise_notsup(algo) do
|
||||
raise "the algorithm #{inspect(algo)} is not supported by your Erlang/OTP installation. " <>
|
||||
"Please make sure it was compiled with the correct OpenSSL/BoringSSL bindings"
|
||||
end
|
||||
|
||||
defp xchacha20_subkey_and_nonce(<<key::256-bits>>, <<nonce0::128-bits, nonce1::64-bits>>) do
|
||||
subkey = hchacha20(key, nonce0)
|
||||
nonce = <<0::32, nonce1::64-bits>>
|
||||
{subkey, nonce}
|
||||
end
|
||||
|
||||
defp hchacha20(<<key::256-bits>>, <<nonce::128-bits>>) do
|
||||
# ChaCha20 has an internal blocksize of 512-bits (64-bytes).
|
||||
# Let's use a Mask of random 64-bytes to blind the intermediate keystream.
|
||||
mask = <<mask_h::128-bits, _::256-bits, mask_t::128-bits>> = :crypto.strong_rand_bytes(64)
|
||||
|
||||
<<state_2h::128-bits, _::256-bits, state_2t::128-bits>> =
|
||||
:crypto.crypto_one_time(:chacha20, key, nonce, mask, true)
|
||||
|
||||
<<
|
||||
x00::32-unsigned-little-integer,
|
||||
x01::32-unsigned-little-integer,
|
||||
x02::32-unsigned-little-integer,
|
||||
x03::32-unsigned-little-integer,
|
||||
x12::32-unsigned-little-integer,
|
||||
x13::32-unsigned-little-integer,
|
||||
x14::32-unsigned-little-integer,
|
||||
x15::32-unsigned-little-integer
|
||||
>> =
|
||||
:crypto.exor(
|
||||
<<mask_h::128-bits, mask_t::128-bits>>,
|
||||
<<state_2h::128-bits, state_2t::128-bits>>
|
||||
)
|
||||
|
||||
## The final step of ChaCha20 is `State2 = State0 + State1', so let's
|
||||
## recover `State1' with subtraction: `State1 = State2 - State0'
|
||||
<<
|
||||
y00::32-unsigned-little-integer,
|
||||
y01::32-unsigned-little-integer,
|
||||
y02::32-unsigned-little-integer,
|
||||
y03::32-unsigned-little-integer,
|
||||
y12::32-unsigned-little-integer,
|
||||
y13::32-unsigned-little-integer,
|
||||
y14::32-unsigned-little-integer,
|
||||
y15::32-unsigned-little-integer
|
||||
>> = <<"expand 32-byte k", nonce::128-bits>>
|
||||
|
||||
<<
|
||||
x00 - y00::32-unsigned-little-integer,
|
||||
x01 - y01::32-unsigned-little-integer,
|
||||
x02 - y02::32-unsigned-little-integer,
|
||||
x03 - y03::32-unsigned-little-integer,
|
||||
x12 - y12::32-unsigned-little-integer,
|
||||
x13 - y13::32-unsigned-little-integer,
|
||||
x14 - y14::32-unsigned-little-integer,
|
||||
x15 - y15::32-unsigned-little-integer
|
||||
>>
|
||||
end
|
||||
|
||||
# Unwraps an encrypted content encryption key (CEK) with secret and
|
||||
# sign_secret using AES GCM mode. Accepts keys of 128, 192, or 256
|
||||
# bits based on the length of the secret key.
|
||||
#
|
||||
# See: https://tools.ietf.org/html/rfc7518#section-4.7
|
||||
defp aes_gcm_key_unwrap(wrapped_cek, secret, sign_secret)
|
||||
when bit_size(secret) in [128, 192, 256] and is_binary(sign_secret) do
|
||||
wrapped_cek
|
||||
|> case do
|
||||
<<cipher_text::128-bitstring, cipher_tag::128-bitstring, iv::96-bitstring>> ->
|
||||
block_decrypt(:aes_gcm, secret, iv, {sign_secret, cipher_text, cipher_tag})
|
||||
|
||||
<<cipher_text::192-bitstring, cipher_tag::128-bitstring, iv::96-bitstring>> ->
|
||||
block_decrypt(:aes_gcm, secret, iv, {sign_secret, cipher_text, cipher_tag})
|
||||
|
||||
<<cipher_text::256-bitstring, cipher_tag::128-bitstring, iv::96-bitstring>> ->
|
||||
block_decrypt(:aes_gcm, secret, iv, {sign_secret, cipher_text, cipher_tag})
|
||||
|
||||
_ ->
|
||||
:error
|
||||
end
|
||||
|> case do
|
||||
cek when bit_size(cek) in [128, 192, 256] -> {:ok, cek}
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
end
|
||||
73
phoenix/deps/plug_crypto/lib/plug/crypto/message_verifier.ex
Normal file
73
phoenix/deps/plug_crypto/lib/plug/crypto/message_verifier.ex
Normal file
@@ -0,0 +1,73 @@
|
||||
defmodule Plug.Crypto.MessageVerifier do
|
||||
@moduledoc """
|
||||
`MessageVerifier` makes it easy to generate and verify messages
|
||||
which are signed to prevent tampering.
|
||||
|
||||
For example, the cookie store uses this verifier to send data
|
||||
to the client. The data can be read by the client, but cannot be
|
||||
tampered with.
|
||||
|
||||
The message and its verification are base64url encoded and returned
|
||||
to you.
|
||||
|
||||
The current algorithm used is HMAC-SHA, with SHA256, SHA384, and
|
||||
SHA512 as supported digest types.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Signs a message according to the given secret.
|
||||
"""
|
||||
def sign(message, secret, digest_type \\ :sha256)
|
||||
when is_binary(message) and byte_size(secret) > 0 and
|
||||
digest_type in [:sha256, :sha384, :sha512] do
|
||||
hmac_sha2_sign(message, secret, digest_type)
|
||||
rescue
|
||||
e -> reraise e, Plug.Crypto.prune_args_from_stacktrace(__STACKTRACE__)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decodes and verifies the encoded binary was not tampered with.
|
||||
"""
|
||||
def verify(signed, secret) when is_binary(signed) and byte_size(secret) > 0 do
|
||||
hmac_sha2_verify(signed, secret)
|
||||
rescue
|
||||
e -> reraise e, Plug.Crypto.prune_args_from_stacktrace(__STACKTRACE__)
|
||||
end
|
||||
|
||||
## Signature Algorithms
|
||||
|
||||
defp hmac_sha2_to_protected(:sha256), do: "SFMyNTY"
|
||||
defp hmac_sha2_to_protected(:sha384), do: "SFMzODQ"
|
||||
defp hmac_sha2_to_protected(:sha512), do: "SFM1MTI"
|
||||
|
||||
defp hmac_sha2_to_digest_type("SFMyNTY"), do: :sha256
|
||||
defp hmac_sha2_to_digest_type("SFMzODQ"), do: :sha384
|
||||
defp hmac_sha2_to_digest_type("SFM1MTI"), do: :sha512
|
||||
|
||||
defp hmac_sha2_sign(payload, key, digest_type) do
|
||||
protected = hmac_sha2_to_protected(digest_type)
|
||||
plain_text = [protected, ?., Base.url_encode64(payload, padding: false)]
|
||||
signature = :crypto.mac(:hmac, digest_type, key, plain_text)
|
||||
IO.iodata_to_binary([plain_text, ".", Base.url_encode64(signature, padding: false)])
|
||||
end
|
||||
|
||||
defp hmac_sha2_verify(signed, key) when is_binary(signed) and is_binary(key) do
|
||||
with [protected, payload, signature] when protected in ["SFMyNTY", "SFMzODQ", "SFM1MTI"] <-
|
||||
:binary.split(signed, ".", [:global]),
|
||||
plain_text = [protected, ?., payload],
|
||||
{:ok, payload} <- Base.url_decode64(payload, padding: false),
|
||||
{:ok, signature} <- Base.url_decode64(signature, padding: false) do
|
||||
digest_type = hmac_sha2_to_digest_type(protected)
|
||||
challenge = :crypto.mac(:hmac, digest_type, key, plain_text)
|
||||
|
||||
if Plug.Crypto.secure_compare(challenge, signature) do
|
||||
{:ok, payload}
|
||||
else
|
||||
:error
|
||||
end
|
||||
else
|
||||
_ ->
|
||||
:error
|
||||
end
|
||||
end
|
||||
end
|
||||
52
phoenix/deps/plug_crypto/mix.exs
Normal file
52
phoenix/deps/plug_crypto/mix.exs
Normal file
@@ -0,0 +1,52 @@
|
||||
defmodule Plug.Crypto.MixProject do
|
||||
use Mix.Project
|
||||
|
||||
@version "2.1.1"
|
||||
@description "Crypto-related functionality for the web"
|
||||
@source_url "https://github.com/elixir-plug/plug_crypto"
|
||||
|
||||
def project do
|
||||
[
|
||||
app: :plug_crypto,
|
||||
version: @version,
|
||||
elixir: "~> 1.11",
|
||||
start_permanent: Mix.env() == :prod,
|
||||
deps: deps(),
|
||||
package: package(),
|
||||
name: "Plug.Crypto",
|
||||
description: @description,
|
||||
docs: [
|
||||
main: "Plug.Crypto",
|
||||
source_ref: "v#{@version}",
|
||||
source_url: @source_url,
|
||||
extras: ["CHANGELOG.md"]
|
||||
]
|
||||
]
|
||||
end
|
||||
|
||||
def application do
|
||||
[
|
||||
extra_applications: [:crypto],
|
||||
mod: {Plug.Crypto.Application, []}
|
||||
]
|
||||
end
|
||||
|
||||
defp deps do
|
||||
[{:ex_doc, "~> 0.21", only: :dev}]
|
||||
end
|
||||
|
||||
defp package do
|
||||
%{
|
||||
licenses: ["Apache-2.0"],
|
||||
maintainers: [
|
||||
"Aleksei Magusev",
|
||||
"Andrea Leopardi",
|
||||
"Eric Meadows-Jönsson",
|
||||
"Gary Rennie",
|
||||
"José Valim"
|
||||
],
|
||||
links: %{"GitHub" => @source_url},
|
||||
files: ["lib", "mix.exs", "README.md", "CHANGELOG.md", "LICENSE"]
|
||||
}
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user