@xemahq/sealed-credential
v0.2.0
Published
Layer-1 crypto primitive for the Xema OS credential broker. Seals an opaque credential payload into an audience-bound, short-lived (TTL ≤ 60s) JWE (`dir` + `A256GCM`) bound to a credentialBindingId + inputHash + correlationId, and unseals it with audience
Readme
@xemahq/sealed-credential
This package belongs to Layer 1 — a framework-agnostic crypto SDK whose only
dependency is jose. It is the credential-sealing primitive for the Xema OS
credential broker.
What it is
Seals an opaque credential payload into an audience-bound, short-lived
(TTL ≤ 60s) JWE (dir key-management + A256GCM content-encryption), bound to a
credentialBindingId + inputHash + correlationId, and unseals it with
audience + TTL verification.
deriveSealingKey(secret)— deterministic 32-byte key from a shared secret.sealCredentialEnvelope(key, input)— the broker seals a resolved credential for exactly one audience (the executing gateway/runner).unsealCredentialEnvelope(key, token, { audience })— the gateway/runner decrypts; mismatched audience, elapsed TTL, or tampering fail-fast with a closedSealedCredentialErrorCode.
Invariants
- The credential never travels unencrypted; only the holder of the key + the named audience can unseal, and only before the TTL elapses.
- Generic over the payload type — the broker (seals) and the gateway/runner (unseals) share one tamper-evident envelope; credential semantics live in the broker, not in this primitive.
- An audience must be NAMED on both sides. An empty or absent
audienceisINVALID_AUDIENCE, never "check nothing" — the underlying JOSE library treats a falsy audience as "skip the check", so an unseal caller whose config resolved to''would open every envelope sealed with that key, addressed to anybody, and the call would succeed. MAX_SEAL_TTL_SECONDSis enforced when READING, not only when sealing. A received envelope whoseexp - iatexceeds the ceiling, or that states no measurable window, is refused — otherwise the ceiling constrains only the honest producer.- An envelope missing what it promises is REFUSED, not returned empty. A
missing payload, or a missing/non-string binding claim, raises
INVALID_ENVELOPE. There is no path on which "there is no credential" arrives looking like a credential. - Nothing leaks. Error messages are fixed strings carrying no caller input,
no ciphertext and no key material, with no
causechain.
What this primitive does NOT do
- It is not single-use. There is no
jti, nonce or replay cache, so a captured envelope replays freely for the remainder of its TTL. The caller MUST cross-checkinputHashagainst the live request; that check is the only replay defence and it lives at the call site, not here. deriveSealingKeyis a bare SHA-256 of the shared secret, not a password-based KDF. It is safe for a high-entropy generated secret and weak for a human-chosen one — provision the secret accordingly.
