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