1require Record
2
3defmodule JOSE.JWS do
4  @moduledoc ~S"""
5  JWS stands for JSON Web Signature which is defined in [RFC 7515](https://tools.ietf.org/html/rfc7515).
6
7  ## Unsecured Signing Vulnerability
8
9  The [`"none"`](https://tools.ietf.org/html/rfc7515#appendix-A.5) signing
10  algorithm is disabled by default to prevent accidental verification of empty
11  signatures (read about the vulnerability [here](https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/)).
12
13  You may also enable the `"none"` algorithm as an application environment
14  variable for `:jose` or by using `JOSE.unsecured_signing/1`.
15
16  ## Strict Verification Recommended
17
18  `JOSE.JWS.verify_strict/3` is recommended over `JOSE.JWS.verify/2` so that
19  signing algorithms may be whitelisted during verification of signed input.
20
21  ## Algorithms
22
23  The following algorithms are currently supported by `JOSE.JWS` (some may need the `JOSE.crypto_fallback/1` option to be enabled):
24
25    * `"Ed25519"`
26    * `"Ed25519ph"`
27    * `"Ed448"`
28    * `"Ed448ph"`
29    * `"EdDSA"`
30    * `"ES256"`
31    * `"ES384"`
32    * `"ES512"`
33    * `"HS256"`
34    * `"HS384"`
35    * `"HS512"`
36    * `"Poly1305"`
37    * `"PS256"`
38    * `"PS384"`
39    * `"PS512"`
40    * `"RS256"`
41    * `"RS384"`
42    * `"RS512"`
43    * `"none"` (disabled by default, enable with `JOSE.unsecured_signing/1`)
44
45  ## Examples
46
47  All of the example keys generated below can be found here: [https://gist.github.com/potatosalad/925a8b74d85835e285b9](https://gist.github.com/potatosalad/925a8b74d85835e285b9)
48
49  ### Ed25519 and Ed25519ph
50
51      # let's generate the 2 keys we'll use below
52      jwk_ed25519   = JOSE.JWK.generate_key({:okp, :Ed25519})
53      jwk_ed25519ph = JOSE.JWK.generate_key({:okp, :Ed25519ph})
54
55      # Ed25519
56      iex> signed_ed25519 = JOSE.JWS.sign(jwk_ed25519, "{}", %{ "alg" => "Ed25519" }) |> JOSE.JWS.compact |> elem(1)
57      "eyJhbGciOiJFZDI1NTE5In0.e30.xyg2LTblm75KbLFJtROZRhEgAFJdlqH9bhx8a9LO1yvLxNLhO9fLqnFuU3ojOdbObr8bsubPkPqUfZlPkGHXCQ"
58      iex> JOSE.JWS.verify(jwk_ed25519, signed_ed25519) |> elem(0)
59      true
60
61      # Ed25519ph
62      iex> signed_ed25519ph = JOSE.JWS.sign(jwk_ed25519ph, "{}", %{ "alg" => "Ed25519ph" }) |> JOSE.JWS.compact |> elem(1)
63      "eyJhbGciOiJFZDI1NTE5cGgifQ.e30.R3je4TTxQvoBOupIKkel_b8eW-G8KaWmXuC14NMGSCcHCTalURtMmVqX2KbcIpFBeI-OKP3BLHNIpt1keKveDg"
64      iex> JOSE.JWS.verify(jwk_ed25519ph, signed_ed25519ph) |> elem(0)
65      true
66
67  ### Ed448 and Ed448ph
68
69      # let's generate the 2 keys we'll use below
70      jwk_ed448   = JOSE.JWK.generate_key({:okp, :Ed448})
71      jwk_ed448ph = JOSE.JWK.generate_key({:okp, :Ed448ph})
72
73      # Ed448
74      iex> signed_ed448 = JOSE.JWS.sign(jwk_ed448, "{}", %{ "alg" => "Ed448" }) |> JOSE.JWS.compact |> elem(1)
75      "eyJhbGciOiJFZDQ0OCJ9.e30.UlqTx962FvZP1G5pZOrScRXlAB0DJI5dtZkknNTm1E70AapkONi8vzpvKd355czflQdc7uyOzTeAz0-eLvffCKgWm_zebLly7L3DLBliynQk14qgJgz0si-60mBFYOIxRghk95kk5hCsFpxpVE45jRIA"
76      iex> JOSE.JWS.verify(jwk_ed448, signed_ed448) |> elem(0)
77      true
78
79      # Ed448ph
80      iex> signed_ed448ph = JOSE.JWS.sign(jwk_ed448ph, "{}", %{ "alg" => "Ed448ph" }) |> JOSE.JWS.compact |> elem(1)
81      "eyJhbGciOiJFZDQ0OHBoIn0.e30._7wxQF8Am-Fg3E-KgREXBv3Gr2vqLM6ja_7hs6kA5EakCrJVQ2QiAHrr4NriLABmiPbVd7F7IiaAApyR3Ud4ak3lGcHVxSyksjJjvBUbKnSB_xkT6v_QMmx27hV08JlxskUkfvjAG0-yKGC8BXoT9R0A"
82      iex> JOSE.JWS.verify(jwk_ed448ph, signed_ed448ph) |> elem(0)
83      true
84
85  ### EdDSA
86
87      # EdDSA works with Ed25519, Ed25519ph, Ed448, and Ed448ph keys.
88      # However, it defaults to Ed25519 for key generation.
89      jwk_eddsa = JOSE.JWS.generate_key(%{ "alg" => "EdDSA" })
90
91      # EdDSA
92      iex> signed_eddsa = JOSE.JWS.sign(jwk_eddsa, "{}", %{ "alg" => "EdDSA" }) |> JOSE.JWS.compact |> elem(1)
93      "eyJhbGciOiJFZERTQSJ9.e30.rhb5ZY7MllNbW9q-SCn_NglhYtaRGMXEUDj6BvJjltOt19tEI_1wFrVK__jL91i9hO7WtVqRH_OfHiilnO1CAQ"
94      iex> JOSE.JWS.verify(jwk_eddsa, signed_eddsa) |> elem(0)
95      true
96
97  ### ES256, ES384, and ES512
98
99      # let's generate the 3 keys we'll use below
100      jwk_es256 = JOSE.JWK.generate_key({:ec, :secp256r1})
101      jwk_es384 = JOSE.JWK.generate_key({:ec, :secp384r1})
102      jwk_es512 = JOSE.JWK.generate_key({:ec, :secp521r1})
103
104      # ES256
105      iex> signed_es256 = JOSE.JWS.sign(jwk_es256, "{}", %{ "alg" => "ES256" }) |> JOSE.JWS.compact |> elem(1)
106      "eyJhbGciOiJFUzI1NiJ9.e30.nb7cEQQuIi2NgcP5A468FHGG8UZg8gWZjloISyVIwNh3X6FiTTFZsvc0mL3RnulWoNJzKF6xwhae3botI1LbRg"
107      iex> JOSE.JWS.verify(jwk_es256, signed_es256) |> elem(0)
108      true
109
110      # ES384
111      iex> signed_es384 = JOSE.JWS.sign(jwk_es384, "{}", %{ "alg" => "ES384" }) |> JOSE.JWS.compact |> elem(1)
112      "eyJhbGciOiJFUzM4NCJ9.e30.-2kZkNe66y2SprhgvvtMa0qBrSb2imPhMYkbi_a7vx-vpEHuVKsxCpUyNVLe5_CXaHWhHyc2rNi4uEfU73c8XQB3e03rg_JOj0H5XGIGS5G9f4RmNMSCiYGwqshLSDFI"
113      iex> JOSE.JWS.verify(jwk_es384, signed_es384) |> elem(0)
114      true
115
116      # ES512
117      iex> signed_es512 = JOSE.JWS.sign(jwk_es512, "{}", %{ "alg" => "ES512" }) |> JOSE.JWS.compact |> elem(1)
118      "eyJhbGciOiJFUzUxMiJ9.e30.AOIw4KTq5YDu6QNrAYKtFP8R5IljAbhqXuPK1dUARPqlfc5F3mM0kmSh5KOVNHDmdCdapBv0F3b6Hl6glFDPlxpiASuSWtvvs9K8_CRfSkEzvToj8wf3WLGOarQHDwYXtlZoki1zMPGeWABwafTZNQaItNSpqYd_P9GtN0XM3AALdua0"
119      iex> JOSE.JWS.verify(jwk_es512, signed_es512) |> elem(0)
120      true
121
122  ### HS256, HS384, and HS512
123
124      # let's generate the 3 keys we'll use below
125      jwk_hs256 = JOSE.JWK.generate_key({:oct, 16})
126      jwk_hs384 = JOSE.JWK.generate_key({:oct, 24})
127      jwk_hs512 = JOSE.JWK.generate_key({:oct, 32})
128
129      # HS256
130      iex> signed_hs256 = JOSE.JWS.sign(jwk_hs256, "{}", %{ "alg" => "HS256" }) |> JOSE.JWS.compact |> elem(1)
131      "eyJhbGciOiJIUzI1NiJ9.e30.r2JwwMFHECoDZlrETLT-sgFT4qN3w0MLee9MrgkDwXs"
132      iex> JOSE.JWS.verify(jwk_hs256, signed_hs256) |> elem(0)
133      true
134
135      # HS384
136      iex> signed_hs384 = JOSE.JWS.sign(jwk_hs384, "{}", %{ "alg" => "HS384" }) |> JOSE.JWS.compact |> elem(1)
137      "eyJhbGciOiJIUzM4NCJ9.e30.brqQFXXM0XtMWDdKf0foEQcvK18swcoDkxBqCPeed_IO317_tisr60H2mz79SlNR"
138      iex> JOSE.JWS.verify(jwk_hs384, signed_hs384) |> elem(0)
139      true
140
141      # HS512
142      iex> signed_hs512 = JOSE.JWS.sign(jwk_hs512, "{}", %{ "alg" => "HS512" }) |> JOSE.JWS.compact |> elem(1)
143      "eyJhbGciOiJIUzUxMiJ9.e30.ge1JYomO8Fyl6sgxLbc4g3AMPbaMHLmeTl0jrUYAJZSloN9j4VyhjucX8d-RWIlMjzdG0xyklw53k1-kaTlRVQ"
144      iex> JOSE.JWS.verify(jwk_hs512, signed_hs512) |> elem(0)
145      true
146
147  ### Poly1305
148
149  This is highly experimental and based on [RFC 7539](https://tools.ietf.org/html/rfc7539).
150
151  Every signed message has a new 96-bit nonce generated which is used to generate a one-time key from the secret.
152
153      # let's generate the key we'll use below
154      jwk_poly1305 = JOSE.JWK.generate_key({:oct, 32})
155
156      # Poly1305
157      iex> signed_poly1305 = JOSE.JWS.sign(jwk_poly1305, "{}", %{ "alg" => "Poly1305" }) |> JOSE.JWS.compact |> elem(1)
158      "eyJhbGciOiJQb2x5MTMwNSIsIm5vbmNlIjoiTjhiR3A1QXdob0Y3Yk1YUiJ9.e30.XWcCkV1WU72cTO-XuiNRAQ"
159      iex> JOSE.JWS.verify(jwk_poly1305, signed_poly1305) |> elem(0)
160      true
161
162      # let's inspect the protected header to see the generated nonce
163      iex> JOSE.JWS.peek_protected(signed_poly1305)
164      "{\"alg\":\"Poly1305\",\"nonce\":\"N8bGp5AwhoF7bMXR\"}"
165
166  ### PS256, PS384, and PS512
167
168      # let's generate the 3 keys we'll use below (cutkey must be installed as a dependency)
169      jwk_ps256 = JOSE.JWK.generate_key({:rsa, 2048})
170      jwk_ps384 = JOSE.JWK.generate_key({:rsa, 4096})
171      jwk_ps512 = JOSE.JWK.generate_key({:rsa, 8192}) # this may take a few seconds
172
173      # PS256
174      iex> signed_ps256 = JOSE.JWS.sign(jwk_ps256, "{}", %{ "alg" => "PS256" }) |> JOSE.JWS.compact |> elem(1)
175      "eyJhbGciOiJQUzI1NiJ9.e30.RY5A3rG2TjmdlARE57eSSSFE6plkuQPKLKsyqz3WrqKRWZgSrvROACRTzoGyrx1sNvQEZJLZ-xVhrFvP-80Q14XzQbPfYLubvn-2wcMNCmih3OVQNVtFdFjA5U2NG-sF-SWAUmm9V_DvMShFGG0qHxLX7LqT83lAIgEulgsytb0xgOjtJObBru5jLjN_uEnc7fCfnxi3my1GAtnrs9NiKvMfuIVlttvOORDFBTO2aFiCv1F-S6Xgj16rc0FGImG0x3amQcmFAD9g41KY0_KsCXgUfoiVpC6CqO6saRC4UDykks91B7Nuoxjsm3nKWa_4vKh9QJy-V8Sf0gHxK58j8Q"
176      iex> JOSE.JWS.verify(jwk_ps256, signed_ps256) |> elem(0)
177      true
178
179      # PS384
180      iex> signed_ps384 = JOSE.JWS.sign(jwk_ps384, "{}", %{ "alg" => "PS384" }) |> JOSE.JWS.compact |> elem(1)
181      "eyJhbGciOiJQUzM4NCJ9.e30.xmYVenIhi75hDMy3bnL6WVpVlTzYmO1ejOZeq9AkSjkp_STrdIp6uUEs9H_y7CLD9LrGYYHDNDl9WmoH6cn95WZT9KJgAVNFFYd8owY6JUHGKU1jUbLkptAgvdphVpWZ1C5fVCRt4vmp8K9f6jy3er9jCBNjl9gSBdmToFwYdXI26ZKSBjfoVm2tFFQIOThye4YQWCWHbzSho6J7d5ATje72L30zDvWXavJ-XNvof5Tkju4WQQB-ukFoqTw4yV8RVwCa-DX61I1hNrq-Zr75_iWmHak3GqNkg5ACBEjDtvtyxJizqy9KINKSlbB9jGztiWoEiXZ6wJ5sSJ6ZrSFJuQVEmns_dLqzpSHEFkWfczEV_gj9Eu_EXwMp9YQlQ3GktfXaz-mzH_jUaLmudEUskQGCiR92gK9KR6_ROQPJfD54Tkqdh6snwg6y17k8GdlTc5qMM3V84q3R6zllmhrRhV1Dlduc0MEqKcsQSX_IX21-sfiVMIcUsW73dIPXVZI2jsNlEHKqwMjWdSfjYUf3YApxSGERU3u4lRS3F0yRrZur8KWS3ToilApjg0cNg9jKas8g8C8ZPgGFYM6StVxUnXRmsJILDnsZMIPjbUDAPHhB0DwLwOB7OqGUBcItX-zwur1OVnHR7aIh1DbfWfyTIml8VIhYfGfazgXfgQVcGEM"
182      iex> JOSE.JWS.verify(jwk_ps384, signed_ps384) |> elem(0)
183      true
184
185      # PS512
186      iex> signed_ps512 = JOSE.JWS.sign(jwk_ps512, "{}", %{ "alg" => "PS512" }) |> JOSE.JWS.compact |> elem(1)
187      "eyJhbGciOiJQUzUxMiJ9.e30.fJe52-PF3I7UrpQamLCnmVAGkBhP0HVeJi48qZqaFc1-_tQEiYTfxuwQBDlt01GQWpjTZRb097bZF6RcrKWwRHyAo3otOZdR32emWfOHddWLL3qotj_fTaDR2-OhLixwce6mFjnHqppHH1zjCmgbKPG8S2cAadNd5w10VR-IS6LdnFRhNZOahuuB7dzCEJaSjkGfm3_9xdj3I0ZRl4fauR_LO9NQIyvMMeCFevowz1sVGG1G-I2njPrEXvxhAMp7y2mao5Yik8UUORXRjcn2Wai3umy8Yh4nHYU5qqruHjLjDwudCPNDjxjg294z1uAUpt7S0v7VbrkgUvgutTFAT-bcHywFODiycajQuqIpFp1TCUAq3Xe2yk4DTRduvPIKcPkJQnFrVkClJAU9A4D4602xpdK-z2uCgWsBVHVokf5-9ba5EqVb8BJx2xYZUIA5CdrIiTBfoe_cI5Jh92uprcWC_llio2ZJvGdQpPgwCgca7-RQ94LAmIA4u3mAndrZj_z48T2GjHbaKzl18FOPQH0XEvK_W5oypUe5NOGlz9mMGZigbFqBY2lM-7oVVYc4ZA3VFy8Dv1nWhU6DGb2NnDnQUyChllyBREuZbwrkOTQEvqqdV-6lM6VwXNu1gqc3YHly9W6u5CmsnxtvlIxsUVg679HiqdtdWxLSaIJObd9Xji56-eEkWMEA08SNy9p-F9AgHOxzoZqgrAQDEwqyEwqoAW681xLc5Vck580AQDxO9Ha4IqLIPirpO5EODQjOd8-S_SlAP5o_wz1Oh38MC5T5V13PqPuZ70dbggB4bUgVaHYC4FE4XHCqP7W3xethaPc68cY9-g9f1RUvthmnEYXSRpvyaMY3iX0txZazWIS_Jg7pNTCEaWr9JCLTZd1MiLbFowPvKYGM-z-39K31OUbq5PIScy0I9OOz9joecm8KsCesA2ysPph1E7cL7Etiw5tGhCFzcdQwm8Gm6SDwj8vCEcZUkXeZJfhlS1cJtZk1sNu3KZNndevtZjRWaXi2m4WNKVxVE-nuaF7V3GWfDemh9RXxyFK8OC8aYLIqcc2pAKJM47ANVty2ll1xaCIB3q3CKdnk5fmsnzKkQI9SjKy70p9TWT-NNoYU682KG_mZo-ByEs5CvJ8w7qysmX8Xpb2I6oSJf7S3qjbqkqtXQcV5MuQ232vk7-g42CcQGL82xvRc09TuvwnmykpKHmjUaJ4U9k9zTN3g2iTdpkvl6vbnND9uG1SBaieVeFYWCT-6VdhovEiD9bvIdA7D_R7NZO8YHBt_lfBQRle_jDyLzHSlkP6kt9dYRhrc2SNMzF_4i3iEUAihbaQYvbNsGwWrHqyGofnva20pRXwc4GxOlw"
188      iex> JOSE.JWS.verify(jwk_ps512, signed_ps512) |> elem(0)
189      true
190
191  ### RS256, RS384, and RS512
192
193      # let's generate the 3 keys we'll use below
194      jwk_rs256 = JOSE.JWK.generate_key({:rsa, 1024})
195      jwk_rs384 = JOSE.JWK.generate_key({:rsa, 2048})
196      jwk_rs512 = JOSE.JWK.generate_key({:rsa, 4096})
197
198      # RS256
199      iex> signed_rs256 = JOSE.JWS.sign(jwk_rs256, "{}", %{ "alg" => "RS256" }) |> JOSE.JWS.compact |> elem(1)
200      "eyJhbGciOiJSUzI1NiJ9.e30.C0J8v5R-sEe9-g_s0SMgPorCh8VDdaZ9gLpWNm1Tn1Cv2xRph1Xn9Rzm10ZCEs84sj7kxA4v28fVShQ_P1AHN83yQ2mvstkKwsuwXxr-cludx_NLQL5CKKQtTR0ITD_pxUowjfAkBYuJv0677jUj-8lGKs1P5e2dbwW9IqFe4uE"
201      iex> JOSE.JWS.verify(jwk_rs256, signed_rs256) |> elem(0)
202      true
203
204      # RS384
205      iex> signed_rs384 = JOSE.JWS.sign(jwk_rs384, "{}", %{ "alg" => "RS384" }) |> JOSE.JWS.compact |> elem(1)
206      "eyJhbGciOiJSUzM4NCJ9.e30.fvPxeNhO0oitOsdqFmrBgpGE7Gn_NdJ1J8F5ArKon54pdHB2v30hua9wbG4V2Hr-hNAyflaBJtoGAwIpKVkfHn-IW7d06hKw_Hv0ecG-VvZr60cK2IJnHS149Htz_652egThZh1GIKRZN1IrRVlraLMozFcWP0Ojc-L-g5XjcTFafesmV0GFGfFubAiQWEiWIgNV3822L-wPe7ZGeFe5yYsZ70WMHQQ1tSuNsm5QUOUVInOThAhJ30FRTCNFgv46l4TEF9aaI9443cKAbwzd_EavD0FpvgpwEhGyNTVx0sxiCZIYUE_jN53aSaHXB82d0xwIr2-GXlr3Y-dLwERIMw"
207      iex> JOSE.JWS.verify(jwk_rs384, signed_rs384) |> elem(0)
208      true
209
210      # RS512
211      iex> signed_rs512 = JOSE.JWS.sign(jwk_rs512, "{}", %{ "alg" => "RS512" }) |> JOSE.JWS.compact |> elem(1)
212      "eyJhbGciOiJSUzUxMiJ9.e30.le2_kCnmj6Y02bl16Hh5EPqmLsFkB3YZpiEfvmA6xfdg9I3QJ5uSgOejs_HpuIbItuMFUdcqtkfW45_6YKlI7plB49iWiNnWY0PLxsvbiZaSmT4R4dOUWx9KlO_Ui5SE94XkigUoFanDTHTr9bh4NpvoIaNdi_xLdC7FYA-AqZspegRcgY-QZQv4kbD3NQJtxsEiAXk8-C8CX3lF6haRlh7s4pyAmgj7SJeElsPjhPNVZ7EduhTLZfVwiLrRmzLKQ6dJ_PrZDig1lgl9jf2NjzcsFpt6lvfrMsDdIQEGyJoh53-zXiD_ltyAZGS3pX-_tHRxoAZ1SyAPkkC4cCra6wc-03sBQPoUa26xyyhrgf4h7E2l-JqhKPXT7pJv6AbRPgKUH4prEH636gpoWQrRc-JxbDIJHR0ShdL8ssf5e-rKpcVVAZKnRI64NbSKXTg-JtDxhU9QG8JVEkHqOxSeo-VSXOoExdmm8lCfqylrw7qmDxjEwOq7TGjhINyjVaK1Op_64BWVuCzgooea6G2ZvCTIEl0-k8wY8s9VC7hxSrsgCAnpWeKpIcbLQoDIoyasG-6Qb5OuSLR367eg9NAQ8WMTbrrQkm-KLNCYvMFaxmlWzBFST2JDmIr0VH9BzXRAdfG81SymuyFA7_FdpiVYwAwEGR4Q5HYEpequ38tHu3Y"
213      iex> JOSE.JWS.verify(jwk_rs512, signed_rs512) |> elem(0)
214      true
215
216  """
217
218  record = Record.extract(:jose_jws, from_lib: "jose/include/jose_jws.hrl")
219  keys = :lists.map(&elem(&1, 0), record)
220  vals = :lists.map(&{&1, [], nil}, keys)
221  pairs = :lists.zip(keys, vals)
222
223  defstruct keys
224  @type t :: %__MODULE__{}
225
226  @doc """
227  Converts a `JOSE.JWS` struct to a `:jose_jws` record.
228  """
229  def to_record(%JOSE.JWS{unquote_splicing(pairs)}) do
230    {:jose_jws, unquote_splicing(vals)}
231  end
232
233  def to_record(list) when is_list(list), do: for(element <- list, into: [], do: to_record(element))
234
235  @doc """
236  Converts a `:jose_jws` record into a `JOSE.JWS`.
237  """
238  def from_record(jose_jws)
239
240  def from_record({:jose_jws, unquote_splicing(vals)}) do
241    %JOSE.JWS{unquote_splicing(pairs)}
242  end
243
244  def from_record(list) when is_list(list), do: for(element <- list, into: [], do: from_record(element))
245
246  ## Decode API
247
248  @doc """
249  Converts a binary or map into a `JOSE.JWS`.
250
251      iex> JOSE.JWS.from(%{ "alg" => "HS256" })
252      %JOSE.JWS{alg: {:jose_jws_alg_hmac, :HS256}, b64: :undefined, fields: %{}}
253      iex> JOSE.JWS.from("{\"alg\":\"HS256\"}")
254      %JOSE.JWS{alg: {:jose_jws_alg_hmac, :HS256}, b64: :undefined, fields: %{}}
255
256  Support for custom algorithms may be added by specifying a map tuple:
257
258      iex> JOSE.JWS.from({%{ alg: MyCustomAlgorithm }, %{ "alg" => "custom" }})
259      %JOSE.JWS{alg: {MyCustomAlgorithm, :state}, b64: :undefined, fields: %{}}
260
261  *Note:* `MyCustomAlgorithm` must implement the `:jose_jws` and `:jose_jws_alg` behaviours.
262  """
263  def from(list) when is_list(list), do: for(element <- list, into: [], do: from(element))
264  def from(jws = %JOSE.JWS{}), do: from(to_record(jws))
265  def from(any), do: :jose_jws.from(any) |> from_record()
266
267  @doc """
268  Converts a binary into a `JOSE.JWS`.
269  """
270  def from_binary(list) when is_list(list), do: for(element <- list, into: [], do: from_binary(element))
271  def from_binary(binary), do: :jose_jws.from_binary(binary) |> from_record()
272
273  @doc """
274  Reads file and calls `from_binary/1` to convert into a `JOSE.JWS`.
275  """
276  def from_file(file), do: :jose_jws.from_file(file) |> from_record()
277
278  @doc """
279  Converts a map into a `JOSE.JWS`.
280  """
281  def from_map(list) when is_list(list), do: for(element <- list, into: [], do: from_map(element))
282  def from_map(map), do: :jose_jws.from_map(map) |> from_record()
283
284  ## Encode API
285
286  @doc """
287  Converts a `JOSE.JWS` into a binary.
288  """
289  def to_binary(list) when is_list(list), do: for(element <- list, into: [], do: to_binary(element))
290  def to_binary(jws = %JOSE.JWS{}), do: to_binary(to_record(jws))
291  def to_binary(any), do: :jose_jws.to_binary(any)
292
293  @doc """
294  Calls `to_binary/1` on a `JOSE.JWS` and then writes the binary to file.
295  """
296  def to_file(file, jws = %JOSE.JWS{}), do: to_file(file, to_record(jws))
297  def to_file(file, any), do: :jose_jws.to_file(file, any)
298
299  @doc """
300  Converts a `JOSE.JWS` into a map.
301  """
302  def to_map(list) when is_list(list), do: for(element <- list, into: [], do: to_map(element))
303  def to_map(jws = %JOSE.JWS{}), do: to_map(to_record(jws))
304  def to_map(any), do: :jose_jws.to_map(any)
305
306  ## API
307
308  @doc """
309  Compacts an expanded signed map or signed list into a binary.
310
311      iex> JOSE.JWS.compact(%{"payload" => "e30",
312       "protected" => "eyJhbGciOiJIUzI1NiJ9",
313       "signature" => "5paAJxaOXSqRUIXrP_vJXUZu2SCBH-ojgP4D6Xr6GPU"})
314      {%{},
315       "eyJhbGciOiJIUzI1NiJ9.e30.5paAJxaOXSqRUIXrP_vJXUZu2SCBH-ojgP4D6Xr6GPU"}
316      iex> JOSE.JWS.compact(%{"payload" => "e30",
317       "signatures" => [
318        %{"protected" => "eyJhbGciOiJIUzI1NiJ9",
319          "signature" => "5paAJxaOXSqRUIXrP_vJXUZu2SCBH-ojgP4D6Xr6GPU"},
320        %{"protected" => "eyJhbGciOiJIUzI1NiJ9",
321          "signature" => "himAUXqVJnW2ZWOD8zaOZr0YzsA61lo48wu6-WP-Ks0"}]})
322      {%{},
323       ["eyJhbGciOiJIUzI1NiJ9.e30.5paAJxaOXSqRUIXrP_vJXUZu2SCBH-ojgP4D6Xr6GPU",
324        "eyJhbGciOiJIUzI1NiJ9.e30.himAUXqVJnW2ZWOD8zaOZr0YzsA61lo48wu6-WP-Ks0"]}}
325
326  See `expand/1`.
327  """
328  defdelegate compact(signed), to: :jose_jws
329
330  @doc """
331  Expands a compacted signed binary or list of signed binaries into a map.
332
333      iex> JOSE.JWS.expand("eyJhbGciOiJIUzI1NiJ9.e30.5paAJxaOXSqRUIXrP_vJXUZu2SCBH-ojgP4D6Xr6GPU")
334      {%{},
335       %{"payload" => "e30", "protected" => "eyJhbGciOiJIUzI1NiJ9",
336         "signature" => "5paAJxaOXSqRUIXrP_vJXUZu2SCBH-ojgP4D6Xr6GPU"}}
337      iex> JOSE.JWS.expand([
338       "eyJhbGciOiJIUzI1NiJ9.e30.5paAJxaOXSqRUIXrP_vJXUZu2SCBH-ojgP4D6Xr6GPU",
339       "eyJhbGciOiJIUzI1NiJ9.e30.himAUXqVJnW2ZWOD8zaOZr0YzsA61lo48wu6-WP-Ks0"])
340      {%{},
341       %{"payload" => "e30",
342         "signatures" => [
343          %{"protected" => "eyJhbGciOiJIUzI1NiJ9",
344            "signature" => "5paAJxaOXSqRUIXrP_vJXUZu2SCBH-ojgP4D6Xr6GPU"},
345          %{"protected" => "eyJhbGciOiJIUzI1NiJ9",
346            "signature" => "himAUXqVJnW2ZWOD8zaOZr0YzsA61lo48wu6-WP-Ks0"}]}}
347
348  See `compact/1`.
349  """
350  defdelegate expand(signed), to: :jose_jws
351
352  @doc """
353  Generates a new `JOSE.JWK` based on the algorithms of the specified `JOSE.JWS`.
354
355      iex> JOSE.JWS.generate_key(%{"alg" => "HS256"})
356      %JOSE.JWK{fields: %{"alg" => "HS256", "use" => "sig"},
357       keys: :undefined,
358       kty: {:jose_jwk_kty_oct,
359        <<150, 71, 29, 79, 228, 32, 218, 4, 111, 250, 212, 129, 226, 173, 86, 205, 72, 48, 98, 100, 66, 68, 113, 13, 43, 60, 122, 248, 179, 44, 140, 24>>}}
360
361  """
362  def generate_key(list) when is_list(list), do: for(element <- list, into: [], do: generate_key(element))
363  def generate_key(jws = %JOSE.JWS{}), do: generate_key(to_record(jws))
364  def generate_key(any), do: JOSE.JWK.from_record(:jose_jws.generate_key(any))
365
366  @doc """
367  Merges map on right into map on left.
368  """
369  def merge(left = %JOSE.JWS{}, right), do: merge(left |> to_record(), right)
370  def merge(left, right = %JOSE.JWS{}), do: merge(left, right |> to_record())
371  def merge(left, right), do: :jose_jws.merge(left, right) |> from_record()
372
373  @doc """
374  See `peek_payload/1`.
375  """
376  defdelegate peek(signed), to: :jose_jws
377
378  @doc """
379  Returns the decoded payload portion of a signed binary or map without verifying the signature.
380
381      iex> JOSE.JWS.peek_payload("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.dMAojPMVbFvvkouYUSI9AxIRBxgqretQMCvNF7KmTHU")
382      "{}"
383
384  """
385  defdelegate peek_payload(signed), to: :jose_jws
386
387  @doc """
388  Returns the decoded protected portion of a signed binary or map without verifying the signature.
389
390      iex> JOSE.JWS.peek_protected("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.dMAojPMVbFvvkouYUSI9AxIRBxgqretQMCvNF7KmTHU")
391      "{\"alg\":\"HS256\",\"typ\":\"JWT\"}"
392
393  """
394  defdelegate peek_protected(signed), to: :jose_jws
395
396  @doc """
397  Returns the decoded signature portion of a signed binary or map without verifying the signature.
398
399      iex> JOSE.JWS.peek_signature("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.dMAojPMVbFvvkouYUSI9AxIRBxgqretQMCvNF7KmTHU")
400      <<116, 192, 40, 140, 243, 21, 108, 91, 239, 146, 139, 152, 81, 34, 61, 3, 18, 17, 7, 24, 42, 173, 235, 80, 48, 43, 205, 23, 178, 166, 76, 117>>
401
402  """
403  defdelegate peek_signature(signed), to: :jose_jws
404
405  @doc """
406  Signs the `plain_text` using the `jwk` and algorithm specified by the `jws`.
407
408      iex> jwk = JOSE.JWK.from(%{"k" => "qUg4Yw", "kty" => "oct"})
409      %JOSE.JWK{fields: %{}, keys: :undefined,
410       kty: {:jose_jwk_kty_oct, <<169, 72, 56, 99>>}}
411      iex> JOSE.JWS.sign(jwk, "{}", %{ "alg" => "HS256" })
412      {%{alg: :jose_jws_alg_hmac},
413       %{"payload" => "e30", "protected" => "eyJhbGciOiJIUzI1NiJ9",
414         "signature" => "5paAJxaOXSqRUIXrP_vJXUZu2SCBH-ojgP4D6Xr6GPU"}}
415
416  If the `jwk` has a `"kid"` assigned, it will be added to the `"header"` on the signed map:
417
418      iex> jwk = JOSE.JWK.from(%{"k" => "qUg4Yw", "kid" => "eyHC48MN26DvoBpkaudvOVXuI5Sy8fKMxQMYiRWmjFw", "kty" => "oct"})
419      %JOSE.JWK{fields: %{"kid" => "eyHC48MN26DvoBpkaudvOVXuI5Sy8fKMxQMYiRWmjFw"},
420       keys: :undefined, kty: {:jose_jwk_kty_oct, <<169, 72, 56, 99>>}}
421      iex> JOSE.JWS.sign(jwk, "test", %{ "alg" => "HS256" })
422      {%{alg: :jose_jws_alg_hmac},
423       %{"header" => %{"kid" => "eyHC48MN26DvoBpkaudvOVXuI5Sy8fKMxQMYiRWmjFw"},
424         "payload" => "e30", "protected" => "eyJhbGciOiJIUzI1NiJ9",
425         "signature" => "5paAJxaOXSqRUIXrP_vJXUZu2SCBH-ojgP4D6Xr6GPU"}}
426
427  A list of `jwk` keys can also be specified to produce a signed list:
428
429      iex> jwk1 = JOSE.JWK.from(%{"k" => "qUg4Yw", "kty" => "oct"})
430      %JOSE.JWK{fields: %{}, keys: :undefined,
431       kty: {:jose_jwk_kty_oct, <<169, 72, 56, 99>>}}
432      iex> jwk2 = JOSE.JWK.from_map(%{"k" => "H-v_Nw", "kty" => "oct"})
433      %JOSE.JWK{fields: %{}, keys: :undefined,
434       kty: {:jose_jwk_kty_oct, <<31, 235, 255, 55>>}}
435      iex> JOSE.JWS.sign([jwk1, jwk2], "{}", %{ "alg" => "HS256" })
436      {%{alg: :jose_jws_alg_hmac},
437       %{"payload" => "e30",
438         "signatures" => [
439          %{"protected" => "eyJhbGciOiJIUzI1NiJ9",
440            "signature" => "5paAJxaOXSqRUIXrP_vJXUZu2SCBH-ojgP4D6Xr6GPU"},
441          %{"protected" => "eyJhbGciOiJIUzI1NiJ9",
442            "signature" => "himAUXqVJnW2ZWOD8zaOZr0YzsA61lo48wu6-WP-Ks0"}]}}
443
444  *Note:* Signed maps with a `"header"` or other fields will have data loss when used with `compact/1`.
445  """
446  def sign(jwk = %JOSE.JWK{}, plain_text, jws), do: sign(JOSE.JWK.to_record(jwk), plain_text, jws)
447  def sign(jwk, plain_text, jws = %JOSE.JWS{}), do: sign(jwk, plain_text, to_record(jws))
448
449  def sign(key_list, plain_text, signer_list)
450      when is_list(key_list) and is_list(signer_list) and length(key_list) === length(signer_list) do
451    keys =
452      for key <- key_list, into: [] do
453        case key do
454          %JOSE.JWK{} ->
455            JOSE.JWK.to_record(key)
456
457          _ ->
458            key
459        end
460      end
461
462    signers =
463      for signer <- signer_list, into: [] do
464        case signer do
465          %JOSE.JWS{} ->
466            JOSE.JWS.to_record(signer)
467
468          _ ->
469            signer
470        end
471      end
472
473    :jose_jws.sign(keys, plain_text, signers)
474  end
475
476  def sign(key_list, plain_text, jws) when is_list(key_list) and not is_list(jws) do
477    keys =
478      for key <- key_list, into: [] do
479        case key do
480          %JOSE.JWK{} ->
481            JOSE.JWK.to_record(key)
482
483          _ ->
484            key
485        end
486      end
487
488    :jose_jws.sign(keys, plain_text, jws)
489  end
490
491  def sign(jwk, plain_text, signer_list) when is_list(signer_list) and not is_list(jwk) do
492    signers =
493      for signer <- signer_list, into: [] do
494        case signer do
495          %JOSE.JWS{} ->
496            JOSE.JWS.to_record(signer)
497
498          _ ->
499            signer
500        end
501      end
502
503    :jose_jws.sign(jwk, plain_text, signers)
504  end
505
506  def sign(jwk, plain_text, jws), do: :jose_jws.sign(jwk, plain_text, jws)
507
508  @doc """
509  Signs the `plain_text` using the `jwk` and algorithm specified by the `jws` and adds the `header` to the signed map.
510
511      iex> jwk = JOSE.JWK.from(%{"k" => "qUg4Yw", "kty" => "oct"})
512      %JOSE.JWK{fields: %{}, keys: :undefined,
513       kty: {:jose_jwk_kty_oct, <<169, 72, 56, 99>>}}
514      iex> JOSE.JWS.sign(jwk, "{}", %{ "test" => true }, %{ "alg" => "HS256" })
515      {%{alg: :jose_jws_alg_hmac},
516       %{"header" => %{"test" => true}, "payload" => "e30",
517         "protected" => "eyJhbGciOiJIUzI1NiJ9",
518         "signature" => "5paAJxaOXSqRUIXrP_vJXUZu2SCBH-ojgP4D6Xr6GPU"}}
519
520  If the `jwk` has a `"kid"` assigned, it will be added to the `"header"` on the signed map.  See `sign/3`.
521  """
522  def sign(jwk = %JOSE.JWK{}, plain_text, header, jws), do: sign(JOSE.JWK.to_record(jwk), plain_text, header, jws)
523  def sign(jwk, plain_text, header, jws = %JOSE.JWS{}), do: sign(jwk, plain_text, header, to_record(jws))
524
525  def sign(key_list, plain_text, header, signer)
526      when is_list(key_list) and is_map(header) and not is_list(signer) do
527    headers = for _ <- key_list, into: [], do: header
528    signers = for _ <- key_list, into: [], do: signer
529    sign(key_list, plain_text, headers, signers)
530  end
531
532  def sign(key_list, plain_text, header, signer_list)
533      when is_list(key_list) and is_map(header) and is_list(signer_list) and length(key_list) === length(signer_list) do
534    headers = for _ <- key_list, into: [], do: header
535    sign(key_list, plain_text, headers, signer_list)
536  end
537
538  def sign(key_list, plain_text, header_list, signer)
539      when is_list(key_list) and is_list(header_list) and not is_list(signer) and length(key_list) === length(header_list) do
540    signers = for _ <- key_list, into: [], do: signer
541    sign(key_list, plain_text, header_list, signers)
542  end
543
544  def sign(key_list, plain_text, header_list, signer_list)
545      when is_list(key_list) and is_list(header_list) and is_list(signer_list) and length(key_list) === length(signer_list) and
546             length(key_list) === length(header_list) do
547    keys =
548      for key <- key_list, into: [] do
549        case key do
550          %JOSE.JWK{} ->
551            JOSE.JWK.to_record(key)
552
553          _ ->
554            key
555        end
556      end
557
558    signers =
559      for signer <- signer_list, into: [] do
560        case signer do
561          %JOSE.JWS{} ->
562            JOSE.JWS.to_record(signer)
563
564          _ ->
565            signer
566        end
567      end
568
569    :jose_jws.sign(keys, plain_text, header_list, signers)
570  end
571
572  def sign(jwk = [%JOSE.JWK{} | _], plain_text, header, jws) do
573    sign(
574      for k <- jwk do
575        case k do
576          %JOSE.JWK{} ->
577            JOSE.JWK.to_record(k)
578
579          _ ->
580            k
581        end
582      end,
583      plain_text,
584      header,
585      jws
586    )
587  end
588
589  def sign(jwk, plain_text, header, jws), do: :jose_jws.sign(jwk, plain_text, header, jws)
590
591  @doc """
592  Converts the `jws` to the `protected` argument used by `signing_input/3`.
593  """
594  def signing_input(payload, jws = %JOSE.JWS{}), do: signing_input(payload, to_record(jws))
595  def signing_input(payload, jws), do: :jose_jws.signing_input(payload, jws)
596
597  @doc """
598  Combines `payload` and `protected` based on the `"b64"` setting on the `jws` for the signing input used by `sign/3` and `sign/4`.
599
600  If `"b64"` is set to `false` on the `jws`, the raw `payload` will be used:
601
602      iex> JOSE.JWS.signing_input("{}", %{ "alg" => "HS256" })
603      "eyJhbGciOiJIUzI1NiJ9.e30"
604      iex> JOSE.JWS.signing_input("{}", %{ "alg" => "HS256", "b64" => false })
605      "eyJhbGciOiJIUzI1NiIsImI2NCI6ZmFsc2V9.{}"
606
607  See [JWS Unencoded Payload Option](https://tools.ietf.org/html/draft-ietf-jose-jws-signing-input-options-04) for more information.
608  """
609  def signing_input(payload, protected, jws = %JOSE.JWS{}), do: signing_input(payload, protected, to_record(jws))
610  def signing_input(payload, protected, jws), do: :jose_jws.signing_input(payload, protected, jws)
611
612  @doc """
613  Verifies the `signed` using the `jwk`.
614
615      iex> jwk = JOSE.JWK.from(%{"k" => "qUg4Yw", "kty" => "oct"})
616      %JOSE.JWK{fields: %{}, keys: :undefined,
617       kty: {:jose_jwk_kty_oct, <<169, 72, 56, 99>>}}
618      iex> JOSE.JWS.verify(jwk, "eyJhbGciOiJIUzI1NiJ9.e30.5paAJxaOXSqRUIXrP_vJXUZu2SCBH-ojgP4D6Xr6GPU")
619      {true, "{}",
620       %JOSE.JWS{alg: {:jose_jws_alg_hmac, :HS256}, b64: :undefined, fields: %{}}}
621
622  A list of `jwk` keys can also be specified where each key will be used to verify every entry in a signed list:
623
624      iex> jwk1 = JOSE.JWK.from(%{"k" => "qUg4Yw", "kty" => "oct"})
625      %JOSE.JWK{fields: %{}, keys: :undefined,
626       kty: {:jose_jwk_kty_oct, <<169, 72, 56, 99>>}}
627      iex> jwk2 = JOSE.JWK.from_map(%{"k" => "H-v_Nw", "kty" => "oct"})
628      %JOSE.JWK{fields: %{}, keys: :undefined,
629       kty: {:jose_jwk_kty_oct, <<31, 235, 255, 55>>}}
630      iex> JOSE.JWS.verify([jwk1, jwk2], %{"payload" => "e30",
631       "signatures" => [
632        %{"protected" => "eyJhbGciOiJIUzI1NiJ9",
633          "signature" => "5paAJxaOXSqRUIXrP_vJXUZu2SCBH-ojgP4D6Xr6GPU"},
634        %{"protected" => "eyJhbGciOiJIUzI1NiJ9",
635          "signature" => "himAUXqVJnW2ZWOD8zaOZr0YzsA61lo48wu6-WP-Ks0"}]})
636      [{%JOSE.JWK{fields: %{}, keys: :undefined,
637         kty: {:jose_jwk_kty_oct, <<169, 72, 56, 99>>}},
638        [{true, "{}",
639          %JOSE.JWS{alg: {:jose_jws_alg_hmac, :HS256}, b64: :undefined, fields: %{}}},
640         {false, "{}",
641          %JOSE.JWS{alg: {:jose_jws_alg_hmac, :HS256}, b64: :undefined,
642           fields: %{}}}]},
643       {%JOSE.JWK{fields: %{}, keys: :undefined,
644         kty: {:jose_jwk_kty_oct, <<31, 235, 255, 55>>}},
645        [{false, "{}",
646          %JOSE.JWS{alg: {:jose_jws_alg_hmac, :HS256}, b64: :undefined, fields: %{}}},
647         {true, "{}",
648          %JOSE.JWS{alg: {:jose_jws_alg_hmac, :HS256}, b64: :undefined,
649           fields: %{}}}]}]
650
651  """
652  def verify(jwk = %JOSE.JWK{}, signed), do: verify(JOSE.JWK.to_record(jwk), signed)
653
654  def verify(jwk = [%JOSE.JWK{} | _], signed) do
655    verify(
656      for k <- jwk do
657        case k do
658          %JOSE.JWK{} ->
659            JOSE.JWK.to_record(k)
660
661          _ ->
662            k
663        end
664      end,
665      signed
666    )
667  end
668
669  def verify(key, signed) do
670    try do
671      case :jose_jws.verify(key, signed) do
672        {verified, payload, jws} when is_tuple(jws) ->
673          {verified, payload, from_record(jws)}
674
675        list when is_list(list) ->
676          for {jwk, verifications} <- list do
677            {JOSE.JWK.from_record(jwk),
678             for {verified, payload, jws} <- verifications do
679               {verified, payload, from_record(jws)}
680             end}
681          end
682
683        error ->
684          error
685      end
686    catch
687      class, reason ->
688        {class, reason}
689    end
690  end
691
692  @doc """
693  Same as `verify/2`, but uses `allow` as a whitelist for `"alg"` which are allowed to verify against.
694
695  If the detected algorithm is not present in `allow`, then `false` is returned.
696
697      iex> jwk = JOSE.JWK.from(%{"k" => "qUg4Yw", "kty" => "oct"})
698      %JOSE.JWK{fields: %{}, keys: :undefined,
699       kty: {:jose_jwk_kty_oct, <<169, 72, 56, 99>>}}
700      iex> signed_hs256 = JOSE.JWS.sign(jwk, "{}", %{ "alg" => "HS256" }) |> JOSE.JWS.compact |> elem(1)
701      "eyJhbGciOiJIUzI1NiJ9.e30.5paAJxaOXSqRUIXrP_vJXUZu2SCBH-ojgP4D6Xr6GPU"
702      iex> signed_hs512 = JOSE.JWS.sign(jwk, "{}", %{ "alg" => "HS512" }) |> JOSE.JWS.compact |> elem(1)
703      "eyJhbGciOiJIUzUxMiJ9.e30.DN_JCks5rzQiDJJ15E6uJFskAMw-KcasGINKK_4S8xKo7W6tZ-a00ZL8UWOWgE7oHpcFrYnvSpNRldAMp19iyw"
704      iex> JOSE.JWS.verify_strict(jwk, ["HS256"], signed_hs256) |> elem(0)
705      true
706      iex> JOSE.JWS.verify_strict(jwk, ["HS256"], signed_hs512) |> elem(0)
707      false
708      iex> JOSE.JWS.verify_strict(jwk, ["HS256", "HS512"], signed_hs512) |> elem(0)
709      true
710
711  """
712  def verify_strict(jwk = %JOSE.JWK{}, allow, signed), do: verify_strict(JOSE.JWK.to_record(jwk), allow, signed)
713
714  def verify_strict(jwk = [%JOSE.JWK{} | _], allow, signed) do
715    verify_strict(
716      for k <- jwk do
717        case k do
718          %JOSE.JWK{} ->
719            JOSE.JWK.to_record(k)
720
721          _ ->
722            k
723        end
724      end,
725      allow,
726      signed
727    )
728  end
729
730  def verify_strict(key, allow, signed) do
731    try do
732      case :jose_jws.verify_strict(key, allow, signed) do
733        {verified, payload, jws} when is_tuple(jws) ->
734          {verified, payload, from_record(jws)}
735
736        list when is_list(list) ->
737          for {jwk, verifications} <- list do
738            {JOSE.JWK.from_record(jwk),
739             for {verified, payload, jws} <- verifications do
740               {verified, payload, from_record(jws)}
741             end}
742          end
743
744        error ->
745          error
746      end
747    catch
748      class, reason ->
749        {class, reason}
750    end
751  end
752end
753