Compact RFC 7516 encryption with explicitly pinned algorithms and AES GCM.
encrypt/4 takes ordered protected header pairs with alg/enc, plaintext
bytes, and a PublicKey or arity-two wrapper (wire_algorithm, generated_cek).
The wrapper returns {:ok, %{encrypted_key: bytes, header: additions_map}};
additions are protected, cannot replace caller members, and may contain only
iv/tag for GCMKW. Protected headers use compact JSON: top-level members keep
caller order, followed by additions sorted by member name; nested JSON objects
are serialized in map order. For JWS signatures needing exact protected bytes,
use RequestSeal.JOSE.JWS.sign_protected/4. For dir, the wrapper must
provision the generated CEK to its recipient through caller-owned custody
and return an empty encrypted key.
No symmetric secret can be supplied in a public key or header. Only
timeout: milliseconds is accepted (default 5,000; 1–300,000).
decrypt/2 requires exactly :algorithms, :encryption, :max_plaintext
(1–1,048,576), :timeout (1–300,000), and :key_resolver. Algorithms are
unique nonempty lists selected from RSA-OAEP-256, RSA-OAEP, A128GCMKW,
A256GCMKW, dir; content encryption is A128GCM or A256GCM. The arity-one
resolver receives %{algorithm: name, encryption: name, header: map} and
returns {:ok, %{algorithm: same_name, unwrap: arity_two_fun}},
{:ok, %{algorithm: same_name, key: custody_handle}}, or :error.
A custody handle must bind {:jwe, same_name} with :unwrap capability;
RSA-OAEP private operations stay inside its sensitive local holder. The
existing callback path can still use caller-owned raw key material.
Unwrap receives (encrypted_key_bytes, header) and returns {:ok, cek} or
{:error, term}. Resolver and callback unwrap execute in JWE's sensitive worker.
On the handle path, the CEK passes from the sensitive local holder through a
separate sensitive custody runner and middle process to that JWE worker.
These four processes see the CEK; JWE's outer middle process and the decrypt
caller receive only the authenticated result. All work shares one absolute
deadline. Unwrap failure uses a fresh random CEK and
still attempts tag authentication; both paths return :decryption_failed.
A released or unavailable resolved handle makes decryption fail with the same
complete error as a forged message, by design. Check
RequestSeal.Custody.public_key(handle) at startup: {:ok, public_key} confirms
public resolution; {:error, error} reports custody availability separately
from message processing. Handle algorithm/capability mismatch also follows the
unwrap-failure path. Resolver algorithm mismatch remains a key-selection error,
and deadline expiration remains distinct.
CEK/IV use the real CSPRNG. Decrypted data stays in the private worker until
full authentication succeeds; no partial result is returned. IVs are 12 bytes,
tags 16 bytes, CEKs exactly 16/32 bytes. The protected segment is verbatim AAD.
Returns {:ok, compact} / {:ok, Result} or {:error, JOSE.Error}.
Summary
Functions
@spec decrypt(binary(), map()) :: {:ok, RequestSeal.JOSE.JWE.Result.t()} | {:error, RequestSeal.JOSE.Error.t()}
@spec encrypt( RequestSeal.JOSE.header(), binary(), RequestSeal.PublicKey.t() | function(), keyword() ) :: {:ok, binary()} | {:error, RequestSeal.JOSE.Error.t()}