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::verify checks them against the
reserved fields (spec §11). The manifest is keyless and advisory; open
it with open_manifest. 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 (spec §9.2).
The normative format is the obsigil specification; section references in
this source (e.g. spec §5.2) point there.
§Example
use obsigil::{open_manifest, Issuer, Mandate, MandateKey, Manifest, 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
// `MandateKey::generate()` in production).
let issuer_key = MandateKey::from_bytes([42u8; 64])?;
// Issuer: an authoritative mandate plus an advisory public manifest.
let token = Issuer::new(issuer_key)
.mandate(&Access { role: "admin".into() })
.exp(4_000_000_000)
.audience(["api"])
.manifest("auth.example", &Ui { theme: "dark".into() })
.mint()?;
// Front end: read the manifest with no secret (advisory only).
let manifest: Manifest<Ui> = open_manifest(&token).expect("present");
assert_eq!(manifest.issuer(), "auth.example");
// Backend: verify the mandate (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 mandate: Mandate<Access> = Verifier::new()
.key(&verify_key)
.audience("api")
.now(1_000_000_000)
.verify(&token)?;
assert_eq!(mandate.app().role, "admin");Modules§
- lowlevel
- Low-level conformance surface (spec §10), 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.
Structs§
- Error
- The single, opaque failure a verifier returns. Its
Displayis uniform across every cause (spec §9.5); 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
- A verified mandate. Constructible only by
Verifier::verify, so its existence is proof the mandate authenticated and passed policy (spec §9.2, §11). - Mandate
Key - A secret 64-byte mandate master key (spec §4.1). Zeroized on drop; never
Debug/Displayits bytes. One key both mints and verifies mandates (spec §9.1). - Manifest
- An opened manifest. Advisory only — never authoritative (spec §9.6).
- Mint
Builder - Builder for a single token.
expis required;tiddefaults to a fresh UUIDv7 (spec §11.3). - 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 (spec §9). Verify against one or more candidate keys by trial decryption (spec §9.4); 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 (spec §5).
- Encoding
- A token’s text encoding, selected for the whole token by the separator
(spec §3):
.=> b64,~=> hex. - Format
- A half’s serialization, named by a one-byte tag sealed inside it (spec §7). Only pure data formats are registered.
- 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 spec
§9.5 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 (§4.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§
- open_
manifest - Open the manifest of a token for display (spec §4.2, §11.2). Keyless and
advisory — never authoritative (spec §9.6). Returns
Noneon anything untrustworthy: no manifest, malformed token, bad encoding, auth failure, unsupported algorithm/serialization, or a manifest missing itsiss. Never an oracle. - tid_
issued_ at - The mandate’s issue time, derived from a UUIDv7
tid(spec §11.3): 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.