update
This commit is contained in:
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
|
||||
Reference in New Issue
Block a user