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