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