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