Caller-owned signing, secret verification, key unwrapping, and public metadata.
Each callback runs in a fresh runner, never linked to the caller. A monitored
middle process traps exits, monitors the caller before linking the runner, and
terminates the runner when the caller dies, even
when a callback is blocked. Both processes set Process.flag(:sensitive, true)
before any custody work, for every operation. Runner exceptions are reduced to
:custodian_failure without logging exception text.
Deadline expiry kills the runner and drains its
uniquely tagged result. Loading the library starts no process, store, or
background work. Local handle construction explicitly starts its key holder.
sign/3, verify/4, and unwrap/3 accept only timeout: milliseconds (default 5,000;
1–300,000, no infinity). Unknown and duplicate options reject. Public-key and
identity resolution use the default deadline. Exact input bytes are limited to
1,048,576; signatures and public container inputs are bounded to 16,384 bytes.
Signing returns bytes; verification establishes mathematical validity only.
Neither establishes identity attribution, authorization, or replay protection.
Custodians are trusted code: they must return only public keys and nonsecret identities, never log secrets, and must propagate the context deadline and cancellation to any additional work they own. Killing the worker closes sockets it owns; it cannot revoke work already accepted by an external peer.
Summary
Functions
Resolve internal trusted equivalence; keep the value out of general results.
Resolve validated public-only material; symmetric keys return :no_public_key.
Sign exact bounded bytes with the algorithm bound to a handle.
Unwrap a bounded RSA-OAEP encrypted key using the handle's exact algorithm.
Verify exact bytes through secret custody or the custodian's public key.
Types
@type algorithm() :: RequestSeal.Crypto.algorithm() | unwrap_algorithm()
@type unwrap_algorithm() :: {:jwe, binary()}
Callbacks
@callback identity(term()) :: RequestSeal.KeyIdentity.t()
@callback public_key(term()) :: {:ok, RequestSeal.PublicKey.t()} | {:error, RequestSeal.Custody.Error.reason()}
@callback sign( term(), RequestSeal.Crypto.algorithm(), binary(), RequestSeal.Custody.Context.t() ) :: {:ok, binary()} | {:error, RequestSeal.Custody.Error.reason()}
@callback unwrap(term(), unwrap_algorithm(), binary(), RequestSeal.Custody.Context.t()) :: {:ok, binary()} | {:error, RequestSeal.Custody.Error.reason()}
@callback verify( term(), RequestSeal.Crypto.algorithm(), binary(), binary(), RequestSeal.Custody.Context.t() ) :: :ok | {:error, RequestSeal.Custody.Error.reason()}
Functions
@spec identity(RequestSeal.KeyHandle.t()) :: {:ok, RequestSeal.KeyIdentity.t()} | {:error, RequestSeal.Custody.Error.t()}
Resolve internal trusted equivalence; keep the value out of general results.
@spec public_key(RequestSeal.KeyHandle.t()) :: {:ok, RequestSeal.PublicKey.t()} | {:error, RequestSeal.Custody.Error.t()}
Resolve validated public-only material; symmetric keys return :no_public_key.
@spec sign(RequestSeal.KeyHandle.t(), binary(), keyword()) :: {:ok, binary()} | {:error, RequestSeal.Custody.Error.t()}
Sign exact bounded bytes with the algorithm bound to a handle.
@spec unwrap(RequestSeal.KeyHandle.t(), binary(), keyword()) :: {:ok, binary()} | {:error, RequestSeal.Custody.Error.t()}
Unwrap a bounded RSA-OAEP encrypted key using the handle's exact algorithm.
Only {:jwe, "RSA-OAEP"} and {:jwe, "RSA-OAEP-256"} handles with :unwrap
capability are accepted. Encrypted keys are 1–1,024 bytes and must match the
RSA modulus width inside the custodian. Returns unwrapped bytes, never the
private key. OAEP failures return :decryption_failed; callers must authenticate
content before exposing plaintext. RequestSeal.JOSE.JWE performs that step
with a random fallback CEK on unwrap failure.
Unwrapped bytes pass from the sensitive local holder through the sensitive
custody runner and middle process to the caller. On the JWE handle path that
caller is JWE's sensitive worker, which authenticates content before returning
plaintext. A direct unwrap/3 caller receives the bytes and owns their protection.
An empty OAEP plaintext returns :decryption_failed.
JWE deliberately maps a released or unavailable handle to the same error as a
forged message. Operators should check RequestSeal.Custody.public_key(handle)
at startup to distinguish custody availability from message rejection;
{:ok, public_key} confirms public resolution, while {:error, error} exposes
the custody failure. Deadline expiration remains a distinct JWE error.
@spec verify(RequestSeal.KeyHandle.t(), binary(), binary(), keyword()) :: :ok | {:error, RequestSeal.Custody.Error.t()}
Verify exact bytes through secret custody or the custodian's public key.