<img src="ferris_macaroons_logo.svg" alt="Ferris holding French macaroons" width="200"/>
# libmacaroon
Rust implementation of
[macaroons](https://research.google.com/pubs/pub41892.html).
[](https://crates.io/crates/libmacaroon)
[](https://github.com/connyay/libmacaroon/actions)
[](https://docs.rs/libmacaroon)
This is a maintained fork of the [`macaroon`] crate
(`macaroon-rs/macaroon`, last released Oct 2022). The fork carries
forward the paper-faithful macaroon semantics and wire-compatibility
with libmacaroons / pymacaroons, and adds a pre-release hardening pass:
constant-time signature comparison, key zeroization on drop, DoS caps,
WASM support via pure-Rust `crypto_secretbox`, interop and property
tests, and a fuzz harness. See `ChangeLog.md` for the full list.
[`macaroon`]: https://crates.io/crates/macaroon
## Status
**Pre-1.0; no external formal security audit.** The API is expected to
break between `0.x` minor versions until we've shaken it out against
real-world use.
What *has* been done:
- Pure-Rust crypto stack: HMAC-SHA256 via the RustCrypto `hmac`/`sha2`
crates, XSalsa20-Poly1305 via `crypto_secretbox`. No FFI, no C
dependencies, no `unsafe`.
- Constant-time signature comparison (`subtle::ConstantTimeEq`), key
zeroization on drop (`ZeroizeOnDrop`), redacted `Debug`, no `Deref`
into key bytes — `MacaroonKey` is hard to accidentally leak.
- DoS caps on every input dimension: caveat count (`MAX_CAVEATS = 1000`),
field size (`MAX_FIELD_SIZE_BYTES = 65535`), verification recursion
depth (32). Applied symmetrically on construction and deserialization.
- Fallible RNG path: WASM environments without
`crypto.getRandomValues` surface `MacaroonError::RngError` instead of
aborting the module.
- Interop test vectors from libmacaroons, pymacaroons, and the Go
`macarooncompat` project. First- and third-party signatures match
byte-for-byte where the compared implementations permit deterministic
nonces.
- Property tests over the full serialization matrix (V1 / V2 / V2JSON
round-trip, verification, wrong-key rejection) plus fuzz harness under
`fuzz/` for both deserialization entry points.
- `cargo audit` clean; CI runs fmt, clippy `-D warnings`, test, MSRV,
WASM target, and audit on every PR.
What *has not* been done:
- External formal audit. If you're considering this crate for a security
boundary, please budget for one.
## What are Macaroons?
Macaroons are bearer tokens (similar to cookies) which encode within them
criteria within which the authorization is allowed to take place (referred to as
"caveats"). For instance, authorization could be restricted to a particular
user, account, time of day, really anything. These criteria can be either
evaluated locally (a "first-party caveat"), or using special macaroons
("discharge macaroons") generated by a third party (a "third-party caveat").
A first-party caveat consists simply of a predicate which, when evaluated as
true, authorizes the caveat. The predicate is a string which is either evaluated
using strict string comparison (`satisfy_exact`), or interpreted using a
provided function (`satisfy_general`).
A third-party caveat consists of a location string, an identifier, and a
specially-generated signing key to authenticate the generated discharge
macaroons. The key and identifier is passed to the third-party who generates the
discharge macaroons. The receiver then binds each discharge macaroon to the
original macaroon.
During verification of a third-party caveat, a discharge macaroon is found from
those received whose identifier matches that of the caveat. The binding
signature is verified, and the discharge macaroon's caveats are verified using
the same process as the original macaroon.
The macaroon is considered authorized only if all its caveats are authorized by
the above process.
## Functionality Implemented
- Creating macaroons, and adding first- and third-party caveats
- Serialization - versions 1, 2 & 2J are supported
- Validation (mostly for validating deserialized macaroons)
- Creation of discharge macaroons
- Verification of both first- and third-party caveats (the latter using
discharge macaroons)
### Examples
```rust
# fn main() -> Result<(), Box<dyn std::error::Error>> {
use libmacaroon::{Macaroon, Verifier, MacaroonKey};
// Create a key. `generate` derives via HMAC from any byte string; for a
// fresh random key use `MacaroonKey::generate_random()?`.
let key = MacaroonKey::generate(b"key");
// Create a macaroon. Location is optional; identifier accepts any
// `AsRef<[u8]>`.
let mut macaroon = Macaroon::create(Some("location"), &key, "id")?;
// Add a first-party caveat — an opaque predicate that the verifier will
// match. Predicates accept any `AsRef<[u8]>`. Add as many as you want.
macaroon.add_first_party_caveat("account = 12345678")?;
// Build a verifier with the predicates we're willing to accept, then verify.
// Returns `Ok(())` when every caveat is satisfied and the signature chain
// matches. Discharges are passed as a borrowed slice.
let mut verifier = Verifier::default();
verifier.satisfy_exact("account = 12345678");
verifier.verify(&macaroon, &key, &[])?;
// Add a third-party caveat. This binds the macaroon to an external
// authorization that will be represented by a discharge macaroon issued
// under `other_key`.
let other_key = MacaroonKey::generate(b"different key");
macaroon.add_third_party_caveat("https://auth.mybank", &other_key, "caveat id")?;
// The third party issues the discharge using the same caveat id and the
// caveat key. They can also attach further caveats to the discharge.
let mut discharge = Macaroon::create(
Some("http://auth.mybank/"),
&other_key,
"caveat id",
)?;
discharge.add_first_party_caveat("account = 12345678")?;
// Bind the discharge to the original macaroon so it cannot be reused
// against a different authorizing macaroon.
macaroon.bind(&mut discharge);
// Same verifier, now with the discharge supplied.
verifier.verify(&macaroon, &key, &[discharge])?;
# Ok(())
# }
```
## API changes in `libmacaroon 0.2.0`
- `Macaroon::create` takes `Option<&str>` for location instead of
`Option<String>`. Replace `Some("x".to_string())` / `Some("x".into())`
with `Some("x")`.
- `add_first_party_caveat` and `add_third_party_caveat` now return
`Result<&mut Self>` instead of `Result<()>`, enabling `?`-chained
builds:
```rust,ignore
let mut m = Macaroon::create(Some("loc"), &key, "id")?;
m.add_first_party_caveat("account = 12345")?
.add_first_party_caveat("user = alice")?;
```
Existing `macaroon.add_first_party_caveat("x")?;` code continues to
compile (the returned `&mut Self` is silently discarded).
## API changes from `macaroon 0.3.0`
The `libmacaroon 0.1.0` API carries forward the macaroon-rs 0.3.0
surface with these changes. Migrating from `macaroon` is mostly a
crate-name swap plus a handful of signature tweaks:
- **`initialize()` removed** — The library no longer requires explicit initialization.
The cryptographic backend is now pure Rust and thread-safe by default.
- **`ByteString` removed from public API** — All public methods now use `&[u8]`,
`&str`, or `impl AsRef<[u8]>` instead of `ByteString`.
- **`MacaroonKey`** — Now uses `ZeroizeOnDrop` to clear key material on drop,
constant-time equality (`ConstantTimeEq`), and a redacted `Debug` impl. No
longer implements `Copy`, `DerefMut`, or `Deref` (the `Deref<[u8]>` impl
was removed to keep `Debug` redaction effective — use `.as_ref()` to get a
byte slice). `to_vec()` was removed (it leaked un-zeroized `Vec<u8>`). Use
`.clone()` for explicit copies.
- **`MacaroonKey::generate_random()`** now returns `Result<MacaroonKey>` and
can fail with `MacaroonError::RngError` when the OS RNG (or the browser's
`crypto.getRandomValues` in WASM) is unavailable, instead of panicking.
- **`Macaroon` accessor changes:**
- `identifier()` returns `&[u8]` (was `ByteString`)
- `location()` returns `Option<&str>` (was `Option<String>`)
- `signature()` returns `&MacaroonKey` (was `MacaroonKey`)
- `caveats()` returns `&[Caveat]` (was `Vec<Caveat>`)
- `first_party_caveats()` / `third_party_caveats()` return `Vec<&Caveat>` (was `Vec<Caveat>`)
- **`Macaroon::create()`** — Identifier parameter now accepts `impl AsRef<[u8]>` (was `ByteString`).
Now validates that the identifier and (optional) location are within
`MAX_FIELD_SIZE_BYTES`, returning `MacaroonError::FieldTooLarge` if not.
- **`add_first_party_caveat()`** — Predicate now accepts `impl AsRef<[u8]>` (was `ByteString`).
Returns `Result<()>` and can fail with `MacaroonError::TooManyCaveats` (per-macaroon cap of 1000)
or `MacaroonError::FieldTooLarge` (per-field cap of 65535 bytes).
- **`add_third_party_caveat()`** — ID now accepts `impl AsRef<[u8]>` (was `ByteString`).
Returns `Result<()>` and can fail with `MacaroonError::TooManyCaveats`,
`MacaroonError::FieldTooLarge`, or `MacaroonError::RngError` (the nonce
used to encrypt the caveat key requires the OS RNG).
- **`Verifier::verify()`** — Discharges parameter is now `&[Macaroon]` (was `Vec<Macaroon>`).
- **`Verifier::satisfy_exact()`** — Now accepts `impl AsRef<[u8]>` (was `ByteString`).
- **`Verifier::satisfy_general()`** — Now accepts closures (`Fn(&[u8]) -> bool + Send + Sync + 'static`)
instead of only function pointers. The `Send + Sync` bound lets `&Verifier`
be shared across threads (typical HTTP server pattern). `VerifyFunc` type
alias removed.
- **`Caveat` sub-types exported** — `FirstParty` and `ThirdParty` are now public.
Their accessors return `&[u8]` and `&str` instead of owned types.
- **New public constants** — `MAX_CAVEATS` (1000) and `MAX_FIELD_SIZE_BYTES`
(65535) are now re-exported so downstream callers can reason about the
same limits the crate enforces.
- **New error variants** — `MacaroonError::FieldTooLarge { field, size }`
and `MacaroonError::RngError(&'static str)`.
- **`MacaroonError::InitializationError` removed** — No longer applicable.
- **V2JSON base64 interop** — `i64`, `v64`, `l64`, `s64` fields are now
emitted as URL-safe base64 without padding (matching libmacaroons /
pymacaroons wire format) and the decoder accepts both URL-safe and
standard alphabets, padded or unpadded.
- **Crypto backend** — migrated from the unmaintained `xsalsa20poly1305`
crate to `crypto_secretbox` (the RustCrypto-recommended successor). Same
XSalsa20-Poly1305 algorithm and wire format, so first-party and
third-party signatures are unchanged.
## Backwards compatibility
Pre-1.0; breaking changes may land in any `0.x.y` minor bump, with
changelog entries and migration notes per release. Once the API has
shaken out against real-world use, a 1.0 will commit to semver — no
breaking changes within the `1.x` line.
## WebAssembly (WASM) Support
This crate now supports WebAssembly! Use the `wasm` feature to enable WASM compatibility:
```toml
[dependencies]
libmacaroon = { version = "0.1", default-features = false, features = ["wasm"] }
```
The `wasm` feature enables browser-compatible random number generation. The
cryptographic backend is always pure Rust (RustCrypto), so WASM compilation
works out of the box — the feature flag only configures `getrandom` for
JavaScript environments.
### Building for WASM
```bash
# Build for WASM target
cargo build --target wasm32-unknown-unknown --features wasm
```
## Cryptographic Backend
This crate uses pure Rust implementations from the RustCrypto ecosystem
(HMAC-SHA256, XSalsa20-Poly1305). No C dependencies are required, and the
same backend works for both native and WASM targets.
### Feature Flags
- `wasm` - Enable WASM-compatible random number generation via `getrandom/js`
## Minimum Supported Rust Version
This crate targets Rust Language 2021 Edition and builds on stable Rust
1.71 (Jul 2023) and later. The library build (`cargo build`) is the
contract; `cargo test` pulls in dev-dep transitives that require newer
editions and isn't part of the MSRV guarantee.
MSRV bumps are not considered breaking for 0.x, but we only bump when
needed, and only to land bug fixes or to keep transitive deps on
supported versions.
Requires `std`.
## Contributing
We :heart: any contributions. Any fixes to make things simpler or more idiomatic
are also more than welcome. Please open a pull request if you have something you
want to contribute. As the project matures, we will add a more detailed
contributors guide