Current section
Files
Jump to
Current section
Files
lib/jwt/jws.ex
defmodule JWT.Jws do
@moduledoc """
Represent content to be secured with digital signatures or Message Authentication Codes (MACs)
see http://tools.ietf.org/html/rfc7515
"""
alias JWT.Jwa
@doc """
Return a JSON Web Signature (JWS), a string representing a digitally signed payload
## Example
iex> key = "gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr9C"
...> JWT.Jws.sign(%{alg: "HS256"}, "payload", key)
"eyJhbGciOiJIUzI1NiJ9.cGF5bG9hZA.uVTaOdyzp_f4mT_hfzU8LnCzdmlVC4t2itHDEYUZym4"
"""
@spec sign(map, binary, binary) :: binary
def sign(%{alg: alg} = header, payload, key) do
data = signing_input(header, payload)
"#{data}.#{do_sign(alg, key, data)}"
end
@doc """
Return a JWS that provides no integrity protection (i.e. lacks a signature)
## Example
iex> JWT.Jws.unsecured_message(%{alg: "none"}, "payload")
"eyJhbGciOiJub25lIn0.cGF5bG9hZA."
see http://tools.ietf.org/html/rfc7515#page-47
"""
def unsecured_message(%{alg: "none"} = header, payload) do
signing_input(header, payload) <> "."
end
defp signing_input(header, payload) do
"#{JWT.Coding.encode!(header)}.#{Base.url_encode64(payload, padding: false)}"
end
defp do_sign(algorithm, key, signing_input) do
Jwa.sign(algorithm, key, signing_input)
|> Base.url_encode64(padding: false)
end
@doc """
Return a tuple {:ok, jws (string)} if the signature is verified, or {:error, "invalid"} otherwise
## Example
iex> jws = "eyJhbGciOiJIUzI1NiJ9.cGF5bG9hZA.uVTaOdyzp_f4mT_hfzU8LnCzdmlVC4t2itHDEYUZym4"
...> key = "gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr9C"
...> JWT.Jws.verify(jws, "HS256", key)
{:ok, "eyJhbGciOiJIUzI1NiJ9.cGF5bG9hZA.uVTaOdyzp_f4mT_hfzU8LnCzdmlVC4t2itHDEYUZym4"}
"""
@spec verify(binary, binary, binary) :: {:ok, binary} | {:error, atom}
def verify(jws, algorithm, key) do
with :ok <- validate_alg(jws, algorithm),
jws_parts <- String.split(jws, "."),
:ok <- verify_signature(jws_parts, algorithm, key)
do
{:ok, jws}
else
{:error, reason} -> {:error, reason}
end
end
@spec verify!(binary, binary, binary) :: binary | no_return
def verify!(jws, algorithm, key) do
case verify(jws, algorithm, key) do
{:ok, jws} -> jws
{:error, :unmatched_algorithm} ->
raise JWT.UnmatchedAlgorithmError
{:error, :invalid_signature} ->
raise JWT.InvalidSignatureError
{:error, :missing_key} ->
raise JWT.MissingKeyError
end
end
defp validate_alg(jws, algorithm) do
[header | _] = String.split(jws, ".")
header = JWT.Coding.decode!(header)
if header["alg"] == algorithm, do: :ok, else: {:error, :unmatched_algorithm}
end
defp verify_signature(_jws_parts, "none", _key), do: :ok
defp verify_signature(_jws_parts, _algorithm, nil), do: {:error, :missing_key}
defp verify_signature([header, message, signature], algorithm, key) do
verified =
signature
|> Base.url_decode64!(padding: false)
|> Jwa.verify?(algorithm, key, "#{header}.#{message}")
if verified, do: :ok, else: {:error, :invalid_signature}
end
defp verify_signature(_jws_parts, _algorithm, _key), do: {:error, :invalid_signature}
end