1defmodule Plug.Crypto do 2 @moduledoc """ 3 Namespace and module for crypto functionality. 4 """ 5 6 use Bitwise 7 8 @doc """ 9 A restricted version a `:erlang.binary_to_term/1` that 10 forbids possibly unsafe terms. 11 """ 12 def safe_binary_to_term(binary) when is_binary(binary) do 13 term = :erlang.binary_to_term(binary) 14 safe_terms(term) 15 term 16 end 17 18 defp safe_terms(list) when is_list(list) do 19 safe_list(list) 20 end 21 defp safe_terms(tuple) when is_tuple(tuple) do 22 safe_tuple(tuple, tuple_size(tuple)) 23 end 24 defp safe_terms(map) when is_map(map) do 25 :maps.fold(fn key, value, acc -> 26 safe_terms(key) 27 safe_terms(value) 28 acc 29 end, map, map) 30 end 31 defp safe_terms(other) when is_atom(other) or is_number(other) or is_bitstring(other) or 32 is_pid(other) or is_reference(other) do 33 other 34 end 35 defp safe_terms(other) do 36 raise ArgumentError, "cannot deserialize #{inspect other}, the term is not safe for deserialization" 37 end 38 39 defp safe_list([]), do: :ok 40 defp safe_list([h | t]) when is_list(t) do 41 safe_terms(h) 42 safe_list(t) 43 end 44 defp safe_list([h | t]) do 45 safe_terms(h) 46 safe_terms(t) 47 end 48 49 defp safe_tuple(_tuple, 0), do: :ok 50 defp safe_tuple(tuple, n) do 51 safe_terms(:erlang.element(n, tuple)) 52 safe_tuple(tuple, n - 1) 53 end 54 55 @doc """ 56 Masks the token on the left with the token on the right. 57 58 Both tokens are required to have the same size. 59 """ 60 def mask(left, right) do 61 mask(left, right, "") 62 end 63 64 defp mask(<<x, left::binary>>, <<y, right::binary>>, acc) do 65 mask(left, right, <<acc::binary, x ^^^ y>>) 66 end 67 68 defp mask(<<>>, <<>>, acc) do 69 acc 70 end 71 72 @doc """ 73 Compares the two binaries (one being masked) in constant-time to avoid 74 timing attacks. 75 76 It is assumed the right token is masked according to the given mask. 77 """ 78 def masked_compare(left, right, mask) do 79 if byte_size(left) == byte_size(right) do 80 masked_compare(left, right, mask, 0) == 0 81 else 82 false 83 end 84 end 85 86 defp masked_compare(<<x, left::binary>>, <<y, right::binary>>, <<z, mask::binary>>, acc) do 87 masked_compare(left, right, mask, acc ||| (x ^^^ (y ^^^ z))) 88 end 89 90 defp masked_compare(<<>>, <<>>, <<>>, acc) do 91 acc 92 end 93 94 @doc """ 95 Compares the two binaries in constant-time to avoid timing attacks. 96 97 See: http://codahale.com/a-lesson-in-timing-attacks/ 98 """ 99 def secure_compare(left, right) do 100 if byte_size(left) == byte_size(right) do 101 secure_compare(left, right, 0) == 0 102 else 103 false 104 end 105 end 106 107 defp secure_compare(<<x, left :: binary>>, <<y, right :: binary>>, acc) do 108 secure_compare(left, right, acc ||| (x ^^^ y)) 109 end 110 111 defp secure_compare(<<>>, <<>>, acc) do 112 acc 113 end 114end 115