1require Record
2
3defmodule JOSE.JWT do
4  @moduledoc ~S"""
5  JWT stands for JSON Web Token which is defined in [RFC 7519](https://tools.ietf.org/html/rfc7519).
6
7  ## Encryption Examples
8
9  ## Signature Examples
10
11  All of the example keys generated below can be found here: [https://gist.github.com/potatosalad/925a8b74d85835e285b9](https://gist.github.com/potatosalad/925a8b74d85835e285b9)
12
13  See `JOSE.JWS` for more Signature examples.  For security purposes, `verify_strict/3` is recommended over `verify/2`.
14
15  ### HS256
16
17      # let's generate the key we'll use below and define our jwt
18      jwk_hs256 = JOSE.JWK.generate_key({:oct, 16})
19      jwt       = %{ "test" => true }
20
21      # HS256
22      iex> signed_hs256 = JOSE.JWT.sign(jwk_hs256, %{ "alg" => "HS256" }, jwt) |> JOSE.JWS.compact |> elem(1)
23      "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0Ijp0cnVlfQ.XYsFJDhfBZCAKnEZjR0WWd1l1ZPDD4bYpZYMHizexfQ"
24      # verify_strict/3 is recommended over verify/2
25      iex> JOSE.JWT.verify_strict(jwk_hs256, ["HS256"], signed_hs256)
26      {true, %JOSE.JWT{fields: %{"test" => true}},
27       %JOSE.JWS{alg: {:jose_jws_alg_hmac, {:jose_jws_alg_hmac, :sha256}},
28        b64: :undefined, fields: %{"typ" => "JWT"}}}
29      # verify/2 returns the same thing without "alg" whitelisting
30      iex> JOSE.JWT.verify(jwk_hs256, signed_hs256)
31      {true, %JOSE.JWT{fields: %{"test" => true}},
32       %JOSE.JWS{alg: {:jose_jws_alg_hmac, {:jose_jws_alg_hmac, :sha256}},
33        b64: :undefined, fields: %{"typ" => "JWT"}}}
34
35      # the default signing algorithm is also "HS256" based on the type of jwk used
36      iex> signed_hs256 == JOSE.JWT.sign(jwk_hs256, jwt) |> JOSE.JWS.compact |> elem(1)
37      true
38
39  """
40
41  record = Record.extract(:jose_jwt, from_lib: "jose/include/jose_jwt.hrl")
42  keys = :lists.map(&elem(&1, 0), record)
43  vals = :lists.map(&{&1, [], nil}, keys)
44  pairs = :lists.zip(keys, vals)
45
46  defstruct keys
47  @type t :: %__MODULE__{}
48
49  @doc """
50  Converts a `JOSE.JWT` struct to a `:jose_jwt` record.
51  """
52  def to_record(%JOSE.JWT{unquote_splicing(pairs)}) do
53    {:jose_jwt, unquote_splicing(vals)}
54  end
55
56  def to_record(list) when is_list(list), do: for(element <- list, into: [], do: to_record(element))
57
58  @doc """
59  Converts a `:jose_jwt` record into a `JOSE.JWT`.
60  """
61  def from_record(jose_jwt)
62
63  def from_record({:jose_jwt, unquote_splicing(vals)}) do
64    %JOSE.JWT{unquote_splicing(pairs)}
65  end
66
67  def from_record(list) when is_list(list), do: for(element <- list, into: [], do: from_record(element))
68
69  ## Decode API
70
71  @doc """
72  Converts a binary or map into a `JOSE.JWT`.
73
74      iex> JOSE.JWT.from(%{ "test" => true })
75      %JOSE.JWT{fields: %{"test" => true}}
76      iex> JOSE.JWT.from("{\"test\":true}")
77      %JOSE.JWT{fields: %{"test" => true}}
78
79  """
80  def from(list) when is_list(list), do: for(element <- list, into: [], do: from(element))
81  def from(jwt = %JOSE.JWT{}), do: from(to_record(jwt))
82  def from(any), do: :jose_jwt.from(any) |> from_record()
83
84  @doc """
85  Converts a binary into a `JOSE.JWT`.
86  """
87  def from_binary(list) when is_list(list), do: for(element <- list, into: [], do: from_binary(element))
88  def from_binary(binary), do: :jose_jwt.from_binary(binary) |> from_record()
89
90  @doc """
91  Reads file and calls `from_binary/1` to convert into a `JOSE.JWT`.
92  """
93  def from_file(file), do: :jose_jwt.from_file(file) |> from_record()
94
95  @doc """
96  Converts a map into a `JOSE.JWT`.
97  """
98  def from_map(list) when is_list(list), do: for(element <- list, into: [], do: from_map(element))
99  def from_map(map), do: :jose_jwt.from_map(map) |> from_record()
100
101  ## Encode API
102
103  @doc """
104  Converts a `JOSE.JWT` into a binary.
105  """
106  def to_binary(list) when is_list(list), do: for(element <- list, into: [], do: to_binary(element))
107  def to_binary(jwt = %JOSE.JWT{}), do: to_binary(to_record(jwt))
108  def to_binary(any), do: :jose_jwt.to_binary(any)
109
110  @doc """
111  Calls `to_binary/1` on a `JOSE.JWT` and then writes the binary to file.
112  """
113  def to_file(file, jwt = %JOSE.JWT{}), do: to_file(file, to_record(jwt))
114  def to_file(file, any), do: :jose_jwt.to_file(file, any)
115
116  @doc """
117  Converts a `JOSE.JWT` into a map.
118  """
119  def to_map(list) when is_list(list), do: for(element <- list, into: [], do: to_map(element))
120  def to_map(jwt = %JOSE.JWT{}), do: to_map(to_record(jwt))
121  def to_map(any), do: :jose_jwt.to_map(any)
122
123  ## API
124
125  @doc """
126  Decrypts an encrypted `JOSE.JWT` using the `jwk`.  See `JOSE.JWE.block_decrypt/2`.
127  """
128  def decrypt(jwk = %JOSE.JWK{}, encrypted), do: decrypt(JOSE.JWK.to_record(jwk), encrypted)
129
130  def decrypt(key, encrypted) do
131    case :jose_jwt.decrypt(key, encrypted) do
132      {jwe, jwt} when is_tuple(jwe) and is_tuple(jwt) ->
133        {JOSE.JWE.from_record(jwe), from_record(jwt)}
134
135      error ->
136        error
137    end
138  end
139
140  @doc """
141  Encrypts a `JOSE.JWT` using the `jwk` and the default block encryptor algorithm `jwe` for the key type.  See `encrypt/3`.
142  """
143  def encrypt(jwk = %JOSE.JWK{}, jwt), do: encrypt(JOSE.JWK.to_record(jwk), jwt)
144
145  def encrypt({your_public_jwk = %JOSE.JWK{}, my_private_jwk}, jwt),
146    do: encrypt({JOSE.JWK.to_record(your_public_jwk), my_private_jwk}, jwt)
147
148  def encrypt({your_public_jwk, my_private_jwk = %JOSE.JWK{}}, jwt),
149    do: encrypt({your_public_jwk, JOSE.JWK.to_record(my_private_jwk)}, jwt)
150
151  def encrypt(jwk, jwt = %JOSE.JWT{}), do: encrypt(jwk, to_record(jwt))
152  def encrypt(jwk, jwt), do: :jose_jwt.encrypt(jwk, jwt)
153
154  @doc """
155  Encrypts a `JOSE.JWT` using the `jwk` and the `jwe` algorithm.  See `JOSE.JWK.block_encrypt/3`.
156
157  If `"typ"` is not specified in the `jwe`, `%{ "typ" => "JWT" }` will be added.
158  """
159  def encrypt(jwk = %JOSE.JWK{}, jwe, jwt), do: encrypt(JOSE.JWK.to_record(jwk), jwe, jwt)
160
161  def encrypt({your_public_jwk = %JOSE.JWK{}, my_private_jwk}, jwe, jwt),
162    do: encrypt({JOSE.JWK.to_record(your_public_jwk), my_private_jwk}, jwe, jwt)
163
164  def encrypt({your_public_jwk, my_private_jwk = %JOSE.JWK{}}, jwe, jwt),
165    do: encrypt({your_public_jwk, JOSE.JWK.to_record(my_private_jwk)}, jwe, jwt)
166
167  def encrypt(jwk, jwe = %JOSE.JWE{}, jwt), do: encrypt(jwk, JOSE.JWE.to_record(jwe), jwt)
168  def encrypt(jwk, jwe, jwt = %JOSE.JWT{}), do: encrypt(jwk, jwe, to_record(jwt))
169  def encrypt(jwk, jwe, jwt), do: :jose_jwt.encrypt(jwk, jwe, jwt)
170
171  @doc """
172  Merges map on right into map on left.
173  """
174  def merge(left = %JOSE.JWT{}, right), do: merge(left |> to_record(), right)
175  def merge(left, right = %JOSE.JWT{}), do: merge(left, right |> to_record())
176  def merge(left, right), do: :jose_jwt.merge(left, right) |> from_record()
177
178  @doc """
179  See `peek_payload/1`.
180  """
181  def peek(signed), do: from_record(:jose_jwt.peek(signed))
182
183  @doc """
184  Returns the decoded payload as a `JOSE.JWT` of a signed binary or map without verifying the signature.  See `JOSE.JWS.peek_payload/1`.
185  """
186  def peek_payload(signed), do: from_record(:jose_jwt.peek_payload(signed))
187
188  @doc """
189  Returns the decoded protected as a `JOSE.JWS` of a signed binary or map without verifying the signature.  See `JOSE.JWS.peek_protected/1`.
190  """
191  def peek_protected(signed), do: JOSE.JWS.from_record(:jose_jwt.peek_protected(signed))
192
193  @doc """
194  Signs a `JOSE.JWT` using the `jwk` and the default signer algorithm `jws` for the key type.  See `sign/3`.
195  """
196  def sign(jwk = %JOSE.JWK{}, jwt), do: sign(JOSE.JWK.to_record(jwk), jwt)
197  def sign(jwk, jwt = %JOSE.JWT{}), do: sign(jwk, to_record(jwt))
198
199  def sign(jwk = [%JOSE.JWK{} | _], jwt) do
200    sign(
201      for k <- jwk do
202        case k do
203          %JOSE.JWK{} ->
204            JOSE.JWK.to_record(k)
205
206          _ ->
207            k
208        end
209      end,
210      jwt
211    )
212  end
213
214  def sign(jwk, jwt), do: :jose_jwt.sign(jwk, jwt)
215
216  @doc """
217  Signs a `JOSE.JWT` using the `jwk` and the `jws` algorithm.  See `JOSE.JWK.sign/3`.
218
219  If `"typ"` is not specified in the `jws`, `%{ "typ" => "JWT" }` will be added.
220  """
221  def sign(jwk = %JOSE.JWK{}, jws, jwt), do: sign(JOSE.JWK.to_record(jwk), jws, jwt)
222  def sign(jwk, jws = %JOSE.JWS{}, jwt), do: sign(jwk, JOSE.JWS.to_record(jws), jwt)
223  def sign(jwk, jws, jwt = %JOSE.JWT{}), do: sign(jwk, jws, to_record(jwt))
224
225  def sign(jwk = [%JOSE.JWK{} | _], jws, jwt) do
226    sign(
227      for k <- jwk do
228        case k do
229          %JOSE.JWK{} ->
230            JOSE.JWK.to_record(k)
231
232          _ ->
233            k
234        end
235      end,
236      jws,
237      jwt
238    )
239  end
240
241  def sign(jwk, jws, jwt), do: :jose_jwt.sign(jwk, jws, jwt)
242
243  @doc """
244  Verifies the `signed` using the `jwk` and calls `from/1` on the payload.  See `JOSE.JWS.verify/2`.
245  """
246  def verify(jwk = %JOSE.JWK{}, signed), do: verify(JOSE.JWK.to_record(jwk), signed)
247
248  def verify(jwk = [%JOSE.JWK{} | _], signed) do
249    verify(
250      for k <- jwk do
251        case k do
252          %JOSE.JWK{} ->
253            JOSE.JWK.to_record(k)
254
255          _ ->
256            k
257        end
258      end,
259      signed
260    )
261  end
262
263  def verify(key, signed) do
264    try do
265      case :jose_jwt.verify(key, signed) do
266        {verified, jwt, jws} when is_tuple(jwt) and is_tuple(jws) ->
267          {verified, from_record(jwt), JOSE.JWS.from_record(jws)}
268
269        list when is_list(list) ->
270          for {jwk, verifications} <- list do
271            {JOSE.JWK.from_record(jwk),
272             Enum.map(verifications, fn
273               {verified, jwt, jws} when is_tuple(jwt) and is_tuple(jws) ->
274                 {verified, from_record(jwt), JOSE.JWS.from_record(jws)}
275
276               other ->
277                 other
278             end)}
279          end
280
281        error ->
282          error
283      end
284    catch
285      class, reason ->
286        {class, reason}
287    end
288  end
289
290  @doc """
291  Verifies the `signed` using the `jwk`, whitelists the `"alg"` using `allow`, and calls `from/1` on the payload.  See `JOSE.JWS.verify_strict/3`.
292  """
293  def verify_strict(jwk = %JOSE.JWK{}, allow, signed), do: verify_strict(JOSE.JWK.to_record(jwk), allow, signed)
294
295  def verify_strict(jwk = [%JOSE.JWK{} | _], allow, signed) do
296    verify_strict(
297      for k <- jwk do
298        case k do
299          %JOSE.JWK{} ->
300            JOSE.JWK.to_record(k)
301
302          _ ->
303            k
304        end
305      end,
306      allow,
307      signed
308    )
309  end
310
311  def verify_strict(key, allow, signed) do
312    try do
313      case :jose_jwt.verify_strict(key, allow, signed) do
314        {verified, jwt, jws} when is_tuple(jwt) and is_tuple(jws) ->
315          {verified, from_record(jwt), JOSE.JWS.from_record(jws)}
316
317        list when is_list(list) ->
318          for {jwk, verifications} <- list do
319            {JOSE.JWK.from_record(jwk),
320             Enum.map(verifications, fn
321               {verified, jwt, jws} when is_tuple(jwt) and is_tuple(jws) ->
322                 {verified, from_record(jwt), JOSE.JWS.from_record(jws)}
323
324               other ->
325                 other
326             end)}
327          end
328
329        error ->
330          error
331      end
332    catch
333      class, reason ->
334        {class, reason}
335    end
336  end
337end
338