RequestSeal.JOSE.Nested (RequestSeal v0.5.2)

Copy Markdown View Source

Verify exactly one compact JWS inside one authenticated compact JWE.

Both policies are explicit; no recursive dispatch or fallback occurs. Failure returns a redacted RequestSeal.JOSE.Error, never a decrypted partial result. These are envelope facts, not JWT claims validation or authorization.

Summary

Functions

Decrypt one compact JWE, then verify the compact JWS it carries.

Functions

verify(compact, jwe_policy, jws_policy, opts)

@spec verify(binary(), map(), map(), keyword()) ::
  {:ok, map()} | {:error, RequestSeal.JOSE.Error.t()}

Decrypt one compact JWE, then verify the compact JWS it carries.

The outer protected cty must be one of the selected content types. Inner envelopes (plaintext that is not a three-segment compact JWS) reject as :nesting_depth. So does an inner JWS whose cty names a JOSE content type — the registered application/jose and application/jose+json, the per-structure application/jws and application/jwe, and RFC 7519's application/jwt — all case-insensitively, with RFC 7515 Section 4.1.10's abbreviation rule (a value with no / prepends application/, so jose, JWS, and JWT land in that set) — because such a value declares the payload is another JOSE envelope and this module verifies exactly one level. Any other inner cty (for example application/json) names ordinary typed payload content and is the application's business. Only complete success returns a result. Each policy supplies its own stage timeout; there is no combined timeout option.

Parameters

Options

  • :content_types - required. Unique nonempty list of at most 64 nonempty strings, each at most 256 bytes, compared exactly with the outer cty.

Returns

Examples

iex> {_, seed} = :crypto.generate_key(:eddsa, :ed25519)
iex> {:ok, handle} = RequestSeal.Custody.Local.new({:jws, "EdDSA"}, {:ed25519, seed})
iex> {:ok, key} = RequestSeal.Custody.public_key(handle)
iex> {:ok, jws} = RequestSeal.JOSE.JWS.sign([{"alg", "EdDSA"}], ~s({"order":"A-1001","status":"shipped"}), handle)
iex> kek = {:aes, :crypto.strong_rand_bytes(32)}
iex> wrap = fn algorithm, cek -> RequestSeal.JOSE.KeyManagement.wrap(algorithm, cek, %{}, kek) end
iex> {:ok, jwe} = RequestSeal.JOSE.JWE.encrypt([{"alg", "A256GCMKW"}, {"enc", "A256GCM"}, {"cty", "JWT"}], jws, wrap)
iex> unwrap = fn bytes, header -> RequestSeal.JOSE.KeyManagement.unwrap("A256GCMKW", bytes, header, kek) end
iex> jwe_policy = %{algorithms: ["A256GCMKW"], encryption: ["A256GCM"], max_plaintext: 4_096, timeout: 5_000, key_resolver: fn _ -> {:ok, %{algorithm: "A256GCMKW", unwrap: unwrap}} end}
iex> jws_policy = %{algorithms: ["EdDSA"], timeout: 5_000, key_resolver: fn _ -> {:ok, %{algorithm: "EdDSA", key: key}} end}
iex> {:ok, %{jws: inner}} = RequestSeal.JOSE.Nested.verify(jwe, jwe_policy, jws_policy, content_types: ["JWT"])
iex> inner.payload
~s({"order":"A-1001","status":"shipped"})
iex> {:error, error} = RequestSeal.JOSE.Nested.verify(jwe, jwe_policy, jws_policy, content_types: ["application/example+jws"])
iex> error.reason
:content_type_mismatch
iex> RequestSeal.Custody.Local.release(handle)
:ok