Expand description
§obsigil
A mandate-token format and shared-secret JWT alternative: a
token split into a public manifest and an encrypted
mandate. Each half is an
authenticated, deterministically-encrypted ciphertext — AES-SIV
(RFC 5297, code 0) or AES-GCM-SIV (RFC 8452, code 1) — joined by a
separator that names the text encoding (. b64, ~ hex), with a
per-half algorithm code in the clear:
token = [ manifest ALG ] SEP [ ALG mandate ]This crate is the backend side: an Issuer mints mandates under
a secret MandateKey, and Verifier::clauses checks them against the
reserved fields (the Reserved fields section, §8). The manifest is keyless and advisory; read
its claims with no secret. Verification is symmetric — the same
MandateKey both mints and verifies — so obsigil fits shared-secret
(HS256-style) JWT and JWE use cases, not public-key verification.
Built directly on RustCrypto (aes-siv, aes-gcm-siv, hkdf). Only
authenticated AEADs are ever compiled in, so an unauthenticated mandate
is structurally unrepresentable (the mandate-must-be-authenticated rule of the Security Considerations, §16.2).
The normative format is the obsigil specification; section references in this source (e.g. the manifest construction, §5.2) point there.
§Example
use obsigil::{claims, generate_key, Claims, Clauses, Issuer, MandateKey, Verifier};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
struct Access { role: String }
#[derive(Serialize, Deserialize)]
struct Ui { theme: String }
// One 64-byte secret, provisioned to both sides (use
// `generate_key()` in production).
let issuer_key = MandateKey::from_bytes([42u8; 64])?;
// Issuer: mint a token. The mandate carries the authoritative clauses;
// the optional manifest carries advisory claims.
let token = Issuer::new(issuer_key)
.clauses(&Access { role: "admin".into() })
.exp(4_000_000_000)
.audience(["api"])
.manifest("auth.example", &Ui { theme: "dark".into() })
.mint()?;
// Front end: read the manifest's claims with no secret (advisory only).
let advisory: Claims<Ui> = claims(&token).expect("present");
assert_eq!(advisory.issuer(), "auth.example");
// Backend: verify the mandate's clauses (authoritative). `now` is pinned
// here for a deterministic example; omit it to read the system clock.
let verify_key = MandateKey::from_bytes([42u8; 64])?;
let clauses: Clauses<Access> = Verifier::new()
.key(&verify_key)
.audience("api")
.now(1_000_000_000)
.clauses(&token)?;
assert_eq!(clauses.app().role, "admin");Modules§
- lowlevel
- Low-level conformance surface (the Conformance and test vectors section, §13), behind the
conformancefeature. Byte-level seal/open/encode/decode/parse on raw octets, for generating and checking the language-agnostic test vectors. This is not the everyday API — mint and verify are. A positive vector reproduces by sealing the given octets and matching the byte string; this module is the entry point that makes that possible without a serializer. The octets are a half’s canonical CBOR plaintext (the Serialization rules, §7).
Structs§
- Claims
- An opened manifest’s claims. Advisory only — never authoritative (the non-authoritative-manifest rule of the Security Considerations, §16.7).
- Clauses
- A verified mandate’s clauses. Constructible only by the verifier terminals, so its existence is proof the mandate authenticated and decoded as canonical CBOR (the mandate-must-be-authenticated rule of the Security Considerations, §16.2; the Reserved fields section, §8).
- Error
- The single, opaque failure a verifier returns. Its
Displayis uniform across every cause (the uniform-failure rule of the Security Considerations, §16.6); the granularReasonis available viaError::reasonfor internal logging only. - Issuer
- A configured token issuer. Holds the secret mandate key and the default algorithm/serialization/encoding for the tokens it mints. Mint under one key (create more issuers to mint under others).
- Mandate
Key - A secret 64-byte mandate master key (the mandate construction, §5.1). Zeroized on drop; never
Debug/Displayits bytes. One key both mints and verifies mandates (the symmetric-key property of the Security Considerations, §16.1). - Mint
Builder - Builder for a single token.
expis required;tiddefaults to a fresh UUIDv7 (thetidfield, §8.2). - NoApp
- Use as the app-data type
Twhen a half carries only reserved fields. Serializes as an empty map; ignores any extra fields on the way in. - Uuid
- A Universally Unique Identifier (UUID).
- Verifier
- A configured mandate verifier (the Verification configuration, §12.5). Verify against one or more candidate keys by trial decryption (the trial-decryption key selection of the Security Considerations, §16.5); reusable across tokens.
Enums§
- Alg
- The AEAD that seals a half, named by its single-character algorithm code in the clear next to the separator (the Algorithm registry, §6).
- Encoding
- A token’s text encoding, selected for the whole token by the separator
(the Token structure section, §4):
.=> b64,~=> hex. - KeyError
- A rejected
MandateKeyvalue. - Mint
Error - A failure while minting a token. Descriptive — minting is the trusted side, so detail here is not an oracle.
- Reason
- Why a verification was rejected. Internal/diagnostic only. Per
the uniform-failure rule of the Security Considerations (§16.6), a verifier MUST NOT signal why a token was rejected to the
bearer; this granular cause is for server-side logging and telemetry.
Never place it (or
Error’sDebug) in a bearer-facing response.
Constants§
- MANIFEST_
KEY - The public 64-byte manifest key pinned by the spec (the published manifest key, §5.2). Every conformant implementation MUST use this exact value. It is public: it opens and forges manifests, which is the point — the manifest is an encoding wrapper, not a security layer.
Functions§
- authorization_
header - Wrap a token’s forwardable mandate half as an HTTP
Authorizationvalue of the form"<scheme> <mandate>"(the Audiences section, §9).Noneif the token has no mandate half or is malformed. A thin convenience overmandate. - claims
- Read the manifest of a token for display (the published manifest key, §5.2; the
issfield, §8.6). Keyless and advisory — never authoritative (the non-authoritative-manifest rule of the Security Considerations, §16.7). ReturnsNoneon anything untrustworthy: no manifest, malformed token, bad encoding, auth failure, an unsupported algorithm, non-canonical CBOR, or a manifest missing itsiss. Never an oracle. - generate_
key - Generate a fresh
MandateKeyfrom the platform CSPRNG (the mandate construction, §5.1) — the free-function form ofMandateKey::generate. - mandate
- Return the token’s mandate half as a standalone, well-formed mandate-only
token — the leading-separator
.0mandateform (the Audiences section, §9). This is the value a front end forwards to the backend.Noneif the token has no mandate half or is malformed. Keyless and purely structural. - manifest
- Return the token’s manifest half as a standalone, well-formed
manifest-only token — the trailing-separator
manifest0.form (the Token structure section, §4).Noneif the token has no manifest half or is malformed. Keyless and purely structural: the ciphertext is sliced out, not decrypted. - manifest_
plaintext - Keyless decrypt of the token’s manifest half under the public
MANIFEST_KEY, returning its raw CBOR octets with no parsing (the published manifest key, §5.2).Noneon no manifest half, a malformed token, bad encoding, an unsupported algorithm, or tamper/auth failure. Advisory only. - tid_
issued_ at - The mandate’s issue time, derived from a UUIDv7
tid(thetidfield, §8.2): the 48-bit big-endian Unix-millisecond field, floored to whole seconds for NumericDate semantics. obsigil defines no separateiat.
Type Aliases§
- Numeric
Date - Seconds since the Unix epoch (JWT NumericDate); the type of
exp.