Skip to main content

obsigil/
lib.rs

1//! # obsigil
2//!
3//! A mandate-token format and shared-secret **JWT alternative**: a
4//! token split into a public **manifest** and an encrypted
5//! **mandate**. Each half is an
6//! authenticated, deterministically-encrypted ciphertext — AES-SIV
7//! (RFC 5297, code `0`) or AES-GCM-SIV (RFC 8452, code `1`) — joined by a
8//! separator that names the text encoding (`.` b64, `~` hex), with a
9//! per-half algorithm code in the clear:
10//!
11//! ```text
12//! token = [ manifest ALG ] SEP [ ALG mandate ]
13//! ```
14//!
15//! This crate is the **backend** side: an [`Issuer`] mints mandates under
16//! a secret [`MandateKey`], and [`Verifier::verify`] checks them against the
17//! reserved fields (spec §11). The manifest is keyless and advisory; open
18//! it with [`open_manifest`]. Verification is symmetric — the same
19//! [`MandateKey`] both mints and verifies — so obsigil fits shared-secret
20//! (HS256-style) JWT and JWE use cases, not public-key verification.
21//!
22//! Built directly on RustCrypto (`aes-siv`, `aes-gcm-siv`, `hkdf`). Only
23//! authenticated AEADs are ever compiled in, so an unauthenticated mandate
24//! is structurally unrepresentable (spec §9.2).
25//!
26//! The normative format is the obsigil specification; section references in
27//! this source (e.g. `spec §5.2`) point there.
28//!
29//! # Example
30//!
31//! ```rust
32//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
33//! use obsigil::{open_manifest, Issuer, Mandate, MandateKey, Manifest, Verifier};
34//! use serde::{Deserialize, Serialize};
35//!
36//! #[derive(Serialize, Deserialize)]
37//! struct Access { role: String }
38//! #[derive(Serialize, Deserialize)]
39//! struct Ui { theme: String }
40//!
41//! // One 64-byte secret, provisioned to both sides (use
42//! // `MandateKey::generate()` in production).
43//! let issuer_key = MandateKey::from_bytes([42u8; 64])?;
44//!
45//! // Issuer: an authoritative mandate plus an advisory public manifest.
46//! let token = Issuer::new(issuer_key)
47//!     .mandate(&Access { role: "admin".into() })
48//!     .exp(4_000_000_000)
49//!     .audience(["api"])
50//!     .manifest("auth.example", &Ui { theme: "dark".into() })
51//!     .mint()?;
52//!
53//! // Front end: read the manifest with no secret (advisory only).
54//! let manifest: Manifest<Ui> = open_manifest(&token).expect("present");
55//! assert_eq!(manifest.issuer(), "auth.example");
56//!
57//! // Backend: verify the mandate (authoritative). `now` is pinned here
58//! // for a deterministic example; omit it to read the system clock.
59//! let verify_key = MandateKey::from_bytes([42u8; 64])?;
60//! let mandate: Mandate<Access> = Verifier::new()
61//!     .key(&verify_key)
62//!     .audience("api")
63//!     .now(1_000_000_000)
64//!     .verify(&token)?;
65//! assert_eq!(mandate.app().role, "admin");
66//! # Ok(()) }
67//! ```
68
69// At least one serialization format must be compiled in; without one the
70// mint/verify API cannot encode or decode claims (spec §7).
71#[cfg(not(any(feature = "json", feature = "toml", feature = "cbor")))]
72compile_error!(
73    "obsigil requires at least one serialization feature: \
74     `json` (the default), `toml`, or `cbor`"
75);
76
77mod aead;
78mod encoding;
79mod error;
80mod key;
81mod mint;
82mod reserved;
83mod serial;
84mod token;
85mod types;
86mod verify;
87
88#[cfg(feature = "conformance")]
89pub mod lowlevel;
90
91pub use error::{Error, KeyError, MintError, Reason};
92pub use key::MandateKey;
93pub use mint::{Issuer, MintBuilder};
94pub use reserved::{Mandate, Manifest, NoApp};
95pub use types::{tid_issued_at, Alg, Encoding, Format, NumericDate, MANIFEST_KEY};
96pub use verify::{open_manifest, Verifier};
97
98// Re-exported for callers handling `tid` (spec §11.3).
99pub use uuid::Uuid;