Skip to main content

Crate obsigil

Crate obsigil 

Source
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 conformance feature. 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 Display is uniform across every cause (spec §9.5); the granular Reason is available via Error::reason for 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).
MandateKey
A secret 64-byte mandate master key (spec §4.1). Zeroized on drop; never Debug/Display its bytes. One key both mints and verifies mandates (spec §9.1).
Manifest
An opened manifest. Advisory only — never authoritative (spec §9.6).
MintBuilder
Builder for a single token. exp is required; tid defaults to a fresh UUIDv7 (spec §11.3).
NoApp
Use as the app-data type T when 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 MandateKey value.
MintError
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’s Debug) 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 None on anything untrustworthy: no manifest, malformed token, bad encoding, auth failure, unsupported algorithm/serialization, or a manifest missing its iss. 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 separate iat.

Type Aliases§

NumericDate
Seconds since the Unix epoch (JWT NumericDate); the type of exp.