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::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 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. 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 Display is uniform across every cause (the uniform-failure rule of the Security Considerations, §16.6); 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).
MandateKey
A secret 64-byte mandate master key (the mandate construction, §5.1). Zeroized on drop; never Debug/Display its bytes. One key both mints and verifies mandates (the symmetric-key property of the Security Considerations, §16.1).
MintBuilder
Builder for a single token. exp is required; tid defaults to a fresh UUIDv7 (the tid field, §8.2).
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 (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 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 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’s Debug) 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 Authorization value of the form "<scheme> <mandate>" (the Audiences section, §9). None if the token has no mandate half or is malformed. A thin convenience over mandate.
claims
Read the manifest of a token for display (the published manifest key, §5.2; the iss field, §8.6). Keyless and advisory — never authoritative (the non-authoritative-manifest rule of the Security Considerations, §16.7). Returns None on anything untrustworthy: no manifest, malformed token, bad encoding, auth failure, an unsupported algorithm, non-canonical CBOR, or a manifest missing its iss. Never an oracle.
generate_key
Generate a fresh MandateKey from the platform CSPRNG (the mandate construction, §5.1) — the free-function form of MandateKey::generate.
mandate
Return the token’s mandate half as a standalone, well-formed mandate-only token — the leading-separator .0mandate form (the Audiences section, §9). This is the value a front end forwards to the backend. None if 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). None if 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). None on 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 (the tid field, §8.2): 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.