cookie.ex (7864B)
1 defmodule Plug.Session.COOKIE do 2 @moduledoc """ 3 Stores the session in a cookie. 4 5 This cookie store is based on `Plug.Crypto.MessageVerifier` 6 and `Plug.Crypto.MessageEncryptor` which encrypts and signs 7 each cookie to ensure they can't be read nor tampered with. 8 9 Since this store uses crypto features, it requires you to 10 set the `:secret_key_base` field in your connection. This 11 can be easily achieved with a plug: 12 13 plug :put_secret_key_base 14 15 def put_secret_key_base(conn, _) do 16 put_in conn.secret_key_base, "-- LONG STRING WITH AT LEAST 64 BYTES --" 17 end 18 19 ## Options 20 21 * `:secret_key_base` - the secret key base to built the cookie 22 signing/encryption on top of. If one is given on initialization, 23 the cookie store can precompute all relevant values at compilation 24 time. Otherwise, the value is taken from `conn.secret_key_base` 25 and cached. 26 27 * `:encryption_salt` - a salt used with `conn.secret_key_base` to generate 28 a key for encrypting/decrypting a cookie, can be either a binary or 29 an MFA returning a binary; 30 31 * `:signing_salt` - a salt used with `conn.secret_key_base` to generate a 32 key for signing/verifying a cookie, can be either a binary or 33 an MFA returning a binary; 34 35 * `:key_iterations` - option passed to `Plug.Crypto.KeyGenerator` 36 when generating the encryption and signing keys. Defaults to 1000; 37 38 * `:key_length` - option passed to `Plug.Crypto.KeyGenerator` 39 when generating the encryption and signing keys. Defaults to 32; 40 41 * `:key_digest` - option passed to `Plug.Crypto.KeyGenerator` 42 when generating the encryption and signing keys. Defaults to `:sha256`; 43 44 * `:serializer` - cookie serializer module that defines `encode/1` and 45 `decode/1` returning an `{:ok, value}` tuple. Defaults to 46 `:external_term_format`. 47 48 * `:log` - Log level to use when the cookie cannot be decoded. 49 Defaults to `:debug`, can be set to false to disable it. 50 51 * `:rotating_options` - additional list of options to use when decrypting and 52 verifying the cookie. These options are used only when the cookie could not 53 be decoded using primary options and are fetched on init so they cannot be 54 changed in runtime. Defaults to `[]`. 55 56 ## Examples 57 58 plug Plug.Session, store: :cookie, 59 key: "_my_app_session", 60 encryption_salt: "cookie store encryption salt", 61 signing_salt: "cookie store signing salt", 62 key_length: 64, 63 log: :debug 64 """ 65 66 require Logger 67 @behaviour Plug.Session.Store 68 69 alias Plug.Crypto.KeyGenerator 70 alias Plug.Crypto.MessageVerifier 71 alias Plug.Crypto.MessageEncryptor 72 73 @impl true 74 def init(opts) do 75 build_opts(opts) 76 |> build_rotating_opts(opts[:rotating_options]) 77 |> Map.delete(:secret_key_base) 78 end 79 80 @impl true 81 def get(conn, raw_cookie, opts) do 82 opts = Map.put(opts, :secret_key_base, conn.secret_key_base) 83 84 [opts | opts.rotating_options] 85 |> Enum.find_value(:error, &read_raw_cookie(raw_cookie, &1)) 86 |> decode(opts.serializer, opts.log) 87 end 88 89 @impl true 90 def put(conn, _sid, term, opts) do 91 %{serializer: serializer, key_opts: key_opts, signing_salt: signing_salt} = opts 92 binary = encode(term, serializer) 93 94 case opts do 95 %{encryption_salt: nil} -> 96 MessageVerifier.sign(binary, derive(conn.secret_key_base, signing_salt, key_opts)) 97 98 %{encryption_salt: encryption_salt} -> 99 MessageEncryptor.encrypt( 100 binary, 101 derive(conn.secret_key_base, encryption_salt, key_opts), 102 derive(conn.secret_key_base, signing_salt, key_opts) 103 ) 104 end 105 end 106 107 @impl true 108 def delete(_conn, _sid, _opts) do 109 :ok 110 end 111 112 defp encode(term, :external_term_format) do 113 :erlang.term_to_binary(term) 114 end 115 116 defp encode(term, serializer) do 117 {:ok, binary} = serializer.encode(term) 118 binary 119 end 120 121 defp decode({:ok, binary}, :external_term_format, log) do 122 {:term, 123 try do 124 Plug.Crypto.non_executable_binary_to_term(binary) 125 rescue 126 e -> 127 Logger.log( 128 log, 129 "Plug.Session could not decode incoming session cookie. Reason: " <> 130 Exception.message(e) 131 ) 132 133 %{} 134 end} 135 end 136 137 defp decode({:ok, binary}, serializer, _log) do 138 case serializer.decode(binary) do 139 {:ok, term} -> {:custom, term} 140 _ -> {:custom, %{}} 141 end 142 end 143 144 defp decode(:error, _serializer, false) do 145 {nil, %{}} 146 end 147 148 defp decode(:error, _serializer, log) do 149 Logger.log( 150 log, 151 "Plug.Session could not verify incoming session cookie. " <> 152 "This may happen when the session settings change or a stale cookie is sent." 153 ) 154 155 {nil, %{}} 156 end 157 158 defp prederive(secret_key_base, value, key_opts) 159 when is_binary(secret_key_base) and is_binary(value) do 160 {:prederived, derive(secret_key_base, value, Keyword.delete(key_opts, :cache))} 161 end 162 163 defp prederive(_secret_key_base, value, _key_opts) do 164 value 165 end 166 167 defp derive(_secret_key_base, {:prederived, value}, _key_opts) do 168 value 169 end 170 171 defp derive(secret_key_base, {module, function, args}, key_opts) do 172 derive(secret_key_base, apply(module, function, args), key_opts) 173 end 174 175 defp derive(secret_key_base, key, key_opts) do 176 secret_key_base 177 |> validate_secret_key_base() 178 |> KeyGenerator.generate(key, key_opts) 179 end 180 181 defp validate_secret_key_base(nil), 182 do: raise(ArgumentError, "cookie store expects conn.secret_key_base to be set") 183 184 defp validate_secret_key_base(secret_key_base) when byte_size(secret_key_base) < 64, 185 do: raise(ArgumentError, "cookie store expects conn.secret_key_base to be at least 64 bytes") 186 187 defp validate_secret_key_base(secret_key_base), do: secret_key_base 188 189 defp check_signing_salt(opts) do 190 case opts[:signing_salt] do 191 nil -> raise ArgumentError, "cookie store expects :signing_salt as option" 192 salt -> salt 193 end 194 end 195 196 defp check_serializer(serializer) when is_atom(serializer), do: serializer 197 198 defp check_serializer(_), 199 do: raise(ArgumentError, "cookie store expects :serializer option to be a module") 200 201 defp read_raw_cookie(raw_cookie, opts) do 202 signing_salt = derive(opts.secret_key_base, opts.signing_salt, opts.key_opts) 203 204 case opts do 205 %{encryption_salt: nil} -> 206 MessageVerifier.verify(raw_cookie, signing_salt) 207 208 %{encryption_salt: _} -> 209 encryption_salt = derive(opts.secret_key_base, opts.encryption_salt, opts.key_opts) 210 211 MessageEncryptor.decrypt(raw_cookie, encryption_salt, signing_salt) 212 end 213 |> case do 214 :error -> nil 215 result -> result 216 end 217 end 218 219 defp build_opts(opts) do 220 encryption_salt = opts[:encryption_salt] 221 signing_salt = check_signing_salt(opts) 222 223 iterations = Keyword.get(opts, :key_iterations, 1000) 224 length = Keyword.get(opts, :key_length, 32) 225 digest = Keyword.get(opts, :key_digest, :sha256) 226 log = Keyword.get(opts, :log, :debug) 227 secret_key_base = Keyword.get(opts, :secret_key_base) 228 key_opts = [iterations: iterations, length: length, digest: digest, cache: Plug.Keys] 229 230 serializer = check_serializer(opts[:serializer] || :external_term_format) 231 232 %{ 233 secret_key_base: secret_key_base, 234 encryption_salt: prederive(secret_key_base, encryption_salt, key_opts), 235 signing_salt: prederive(secret_key_base, signing_salt, key_opts), 236 key_opts: key_opts, 237 serializer: serializer, 238 log: log 239 } 240 end 241 242 defp build_rotating_opts(opts, rotating_opts) when is_list(rotating_opts) do 243 Map.put(opts, :rotating_options, Enum.map(rotating_opts, &build_opts/1)) 244 end 245 246 defp build_rotating_opts(opts, _), do: Map.put(opts, :rotating_options, []) 247 end