Skip to main content

libmacaroon/
lib.rs

1//! Implementation of [Macaroons](http://research.google.com/pubs/pub41892.html) for Rust, which are
2//! flexible authorization tokens for distributed systems. They are similar to cookies, but allow for
3//! more narrowly-focused authorization based on contextual caveats.
4//!
5//! # What Are Macaroons?
6//!
7//! Macaroons are bearer tokens (similar to cookies) which encode within them criteria within which the
8//! authorization is allowed to take place (referred to as "caveats"). For instance, authorization could
9//! be restricted to a particular user, account, time of day, really anything. These criteria can be either
10//! evaluated locally (a "first-party caveat"), or using special macaroons ("discharge macaroons") generated
11//! by a third party (a "third-party caveat").
12//!
13//! A first-party caveat consists simply of a predicate which, when evaluated as true, authorizes the caveat.
14//! The predicate is a string which is either evaluated using strict string comparison (`satisfy_exact`),
15//! or interpreted using a provided function (`satisfy_general`).
16//!
17//! A third-party caveat consists of a location string, an identifier, and a specially-generated signing key
18//! to authenticate the generated discharge macaroons. The key and identifier is passed to the third-party
19//! who generates the discharge macaroons. The receiver then binds each discharge macaroon to the original
20//! macaroon.
21//!
22//! During verification of a third-party caveat, a discharge macaroon is found from those received whose identifier
23//! matches that of the caveat. The binding signature is verified, and the discharge macaroon's caveats are verified
24//! using the same process as the original macaroon.
25//!
26//! The macaroon is considered authorized only if all its caveats are authorized by the above process.
27//!
28//! # Example
29//!
30//! ```rust
31//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
32//! use libmacaroon::{Macaroon, Verifier, MacaroonKey};
33//!
34//! // Create our key.
35//! let key = MacaroonKey::generate(b"key");
36//!
37//! // Create our macaroon. A location is optional.
38//! let mut macaroon = Macaroon::create(Some("location"), &key, "id")?;
39//!
40//! // Add a first-party caveat: only someone identified as account 12345678
41//! // is authorized to use this macaroon. Multiple caveats with different
42//! // predicates can be layered on.
43//! macaroon.add_first_party_caveat("account = 12345678")?;
44//!
45//! // Build a verifier with the predicates we're willing to accept.
46//! let mut verifier = Verifier::default();
47//! verifier.satisfy_exact("account = 12345678");
48//!
49//! // Verify. Returns Ok(()) on success.
50//! verifier.verify(&macaroon, &key, &[])?;
51//!
52//! // Now a third-party caveat: verification requires a discharge macaroon
53//! // issued by a third party under a separate key.
54//! let other_key = MacaroonKey::generate(b"different key");
55//! macaroon.add_third_party_caveat("https://auth.mybank", &other_key, "caveat id")?;
56//!
57//! // The third party creates the discharge using the same caveat id and key.
58//! let mut discharge = Macaroon::create(
59//!     Some("http://auth.mybank/"),
60//!     &other_key,
61//!     "caveat id",
62//! )?;
63//! discharge.add_first_party_caveat("account = 12345678")?;
64//!
65//! // Bind the discharge to the original macaroon so it cannot be reused
66//! // against a different authorizing macaroon.
67//! macaroon.bind(&mut discharge);
68//!
69//! // Same verifier, now with the discharge supplied.
70//! verifier.verify(&macaroon, &key, &[discharge])?;
71//! # Ok(())
72//! # }
73//! ```
74//!
75//! # Supported Features
76//!
77//! This crate supports all the following features:
78//!
79//! - verification of first-party caveats either via exact string match or passed-in function
80//! - verification of third-party caveats using discharge macaroons (including ones that themselves have embedded third-party caveats)
81//! - serialization and deserialization of caveats via version 1, 2 or 2J serialization formats (fully compatible with libmacaroons)
82
83use base64::{
84    alphabet,
85    engine::{DecodePaddingMode, GeneralPurpose, GeneralPurposeConfig},
86    Engine as _,
87};
88use log::debug;
89
90/// Base64 engines that tolerate both padded and unpadded input when decoding,
91/// matching the lenient behavior of the `base64 0.13` `STANDARD` / `URL_SAFE`
92/// configs. Needed so that macaroons serialized by other libraries — which may
93/// omit or include `=` padding — round-trip correctly.
94pub(crate) const STANDARD: GeneralPurpose = GeneralPurpose::new(
95    &alphabet::STANDARD,
96    GeneralPurposeConfig::new().with_decode_padding_mode(DecodePaddingMode::Indifferent),
97);
98pub(crate) const URL_SAFE: GeneralPurpose = GeneralPurpose::new(
99    &alphabet::URL_SAFE,
100    GeneralPurposeConfig::new().with_decode_padding_mode(DecodePaddingMode::Indifferent),
101);
102/// Used on the emit side for V2JSON fields (`i64`, `v64`, `s64`) so our
103/// output matches what libmacaroons/pymacaroons produce: URL-safe alphabet,
104/// no `=` padding. The decode side tolerates both padded and unpadded.
105pub(crate) const URL_SAFE_NO_PAD: GeneralPurpose = GeneralPurpose::new(
106    &alphabet::URL_SAFE,
107    GeneralPurposeConfig::new()
108        .with_encode_padding(false)
109        .with_decode_padding_mode(DecodePaddingMode::Indifferent),
110);
111
112mod caveat;
113mod crypto;
114mod error;
115mod serialization;
116mod verifier;
117
118pub use caveat::{Caveat, FirstParty, ThirdParty};
119pub use crypto::MacaroonKey;
120pub use error::MacaroonError;
121pub use serialization::Format;
122pub use verifier::Verifier;
123
124#[cfg(feature = "v2json")]
125use serde::de::Visitor;
126#[cfg(feature = "v2json")]
127use serde::{Deserialize, Deserializer, Serialize, Serializer};
128use std::fmt;
129
130pub type Result<T> = std::result::Result<T, MacaroonError>;
131
132/// Maximum number of caveats allowed on a single macaroon. Applied on both
133/// construction (`add_first_party_caveat`, `add_third_party_caveat`) and
134/// deserialization to bound memory and verification work.
135pub const MAX_CAVEATS: usize = 1000;
136
137/// Maximum byte length accepted for any single field (identifier, location,
138/// predicate, VID, signature) during construction and deserialization.
139///
140/// Bounds memory and parsing work on untrusted input, and ensures the V1
141/// packet format (whose size header is four hex digits, capping a packet at
142/// `0xFFFF` bytes) can represent every macaroon this crate produces. The cap
143/// applies symmetrically: a macaroon that this crate can serialize is always
144/// one this crate can parse back.
145pub const MAX_FIELD_SIZE_BYTES: usize = 65535;
146
147pub(crate) fn check_field_size(field: &'static str, size: usize) -> Result<()> {
148    if size > MAX_FIELD_SIZE_BYTES {
149        return Err(MacaroonError::FieldTooLarge { field, size });
150    }
151    Ok(())
152}
153
154// Internal type representing binary data. By spec, most fields in a macaroon
155// support binary encoded as base64.
156#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
157pub(crate) struct ByteString(pub(crate) Vec<u8>);
158
159impl AsRef<[u8]> for ByteString {
160    fn as_ref(&self) -> &[u8] {
161        &self.0
162    }
163}
164
165impl std::borrow::Borrow<[u8]> for ByteString {
166    fn borrow(&self) -> &[u8] {
167        &self.0
168    }
169}
170
171impl From<Vec<u8>> for ByteString {
172    fn from(v: Vec<u8>) -> ByteString {
173        ByteString(v)
174    }
175}
176
177impl From<&[u8]> for ByteString {
178    fn from(s: &[u8]) -> ByteString {
179        ByteString(s.to_vec())
180    }
181}
182
183impl From<&str> for ByteString {
184    fn from(s: &str) -> ByteString {
185        ByteString(s.as_bytes().to_vec())
186    }
187}
188
189impl From<String> for ByteString {
190    fn from(s: String) -> ByteString {
191        ByteString(s.as_bytes().to_vec())
192    }
193}
194
195impl From<[u8; 32]> for ByteString {
196    fn from(b: [u8; 32]) -> ByteString {
197        ByteString(b.to_vec())
198    }
199}
200
201impl fmt::Display for ByteString {
202    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203        // Emit URL-safe base64 without padding — matches libmacaroons /
204        // pymacaroons V2JSON output, which is the wire format these fields
205        // (`i64`, `v64`) travel in.
206        write!(f, "{}", URL_SAFE_NO_PAD.encode(&self.0))
207    }
208}
209
210#[cfg(feature = "v2json")]
211impl Serialize for ByteString {
212    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
213    where
214        S: Serializer,
215    {
216        serializer.serialize_str(&self.to_string())
217    }
218}
219
220#[cfg(feature = "v2json")]
221struct ByteStringVisitor;
222
223#[cfg(feature = "v2json")]
224impl<'de> Visitor<'de> for ByteStringVisitor {
225    type Value = ByteString;
226
227    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
228        formatter.write_str("base64 encoded string of bytes")
229    }
230
231    fn visit_str<E>(self, value: &str) -> std::result::Result<Self::Value, E>
232    where
233        E: serde::de::Error,
234    {
235        // Tolerate both standard and URL-safe alphabets, padded or unpadded:
236        // libmacaroons and pymacaroons emit URL-safe no-pad, but some clients
237        // use the standard alphabet. Accepting both here fixes a real interop
238        // break on V2JSON `i64` / `v64` fields.
239        let raw = base64_decode_flexible(value.as_bytes())
240            .map_err(|_| E::custom("unable to base64 decode value"))?;
241        Ok(ByteString(raw))
242    }
243}
244
245#[cfg(feature = "v2json")]
246impl<'de> Deserialize<'de> for ByteString {
247    fn deserialize<D>(deserializer: D) -> std::result::Result<ByteString, D::Error>
248    where
249        D: Deserializer<'de>,
250    {
251        deserializer.deserialize_str(ByteStringVisitor)
252    }
253}
254
255/// Internal helper to decode base64 tokens in either URL-safe or non-URL-safe format, with or
256/// without padding. The macaroons format specifies that macaroons should be accepted in any of
257/// these variations.
258///
259/// Logic is based on pymacaroons helper:
260/// https://github.com/ecordell/pymacaroons/blob/master/pymacaroons/utils.py#L109
261fn base64_decode_flexible(b: &[u8]) -> Result<Vec<u8>> {
262    if b.is_empty() {
263        return Err(MacaroonError::DeserializationError(
264            "empty token to deserialize".to_string(),
265        ));
266    }
267    if b.contains(&b'_') || b.contains(&b'-') {
268        Ok(URL_SAFE.decode(b)?)
269    } else {
270        Ok(STANDARD.decode(b)?)
271    }
272}
273
274// https://github.com/rescrv/libmacaroons/blob/master/doc/format.txt#L87
275#[test]
276fn test_base64_decode_flexible() {
277    let val = b"Ou?T".to_vec();
278    assert_eq!(val, base64_decode_flexible(b"T3U/VA==").unwrap());
279    assert_eq!(val, base64_decode_flexible(b"T3U_VA==").unwrap());
280    assert_eq!(val, base64_decode_flexible(b"T3U/VA").unwrap());
281    assert_eq!(val, base64_decode_flexible(b"T3U_VA").unwrap());
282
283    assert!(base64_decode_flexible(b"...").is_err());
284    assert!(base64_decode_flexible(b"").is_err());
285}
286
287#[derive(Clone, Debug, PartialEq, Eq)]
288pub struct Macaroon {
289    identifier: ByteString,
290    location: Option<String>,
291    signature: MacaroonKey,
292    caveats: Vec<Caveat>,
293}
294
295impl Macaroon {
296    /// Construct a macaroon, given a location and identifier, and a key to sign
297    /// it with. You can use a bare str or &[u8] containing arbitrary data with
298    /// `into` to automatically generate a suitable key.
299    ///
300    /// # Errors
301    ///
302    /// - [`MacaroonError::IncompleteMacaroon`] if the identifier is empty.
303    /// - [`MacaroonError::FieldTooLarge`] if the identifier or location
304    ///   exceeds [`MAX_FIELD_SIZE_BYTES`].
305    pub fn create(
306        location: Option<&str>,
307        key: &MacaroonKey,
308        identifier: impl AsRef<[u8]>,
309    ) -> Result<Macaroon> {
310        let identifier_bytes = identifier.as_ref();
311        check_field_size("identifier", identifier_bytes.len())?;
312        if let Some(loc) = location {
313            check_field_size("location", loc.len())?;
314        }
315        let identifier = ByteString(identifier_bytes.to_vec());
316        let signature = crypto::hmac(key, &identifier);
317        let macaroon = Macaroon {
318            location: location.map(str::to_string),
319            identifier,
320            signature,
321            caveats: Vec::new(),
322        };
323        debug!("Macaroon::create: {:?}", macaroon);
324        macaroon.validate()
325    }
326
327    /// Returns the identifier for the macaroon as a byte slice
328    pub fn identifier(&self) -> &[u8] {
329        &self.identifier.0
330    }
331
332    /// Returns a reference to the location for the macaroon
333    pub fn location(&self) -> Option<&str> {
334        self.location.as_deref()
335    }
336
337    /// Returns the macaroon's signature
338    ///
339    /// The [MacaroonKey] type is used because it is the same size and format as a signature, but
340    /// the signature is not and should not be used as a cryptographic key.
341    pub fn signature(&self) -> &MacaroonKey {
342        &self.signature
343    }
344
345    /// Returns a reference to the list of caveats
346    pub fn caveats(&self) -> &[Caveat] {
347        &self.caveats
348    }
349
350    /// Retrieve a list of references to the first-party caveats
351    pub fn first_party_caveats(&self) -> Vec<&Caveat> {
352        self.caveats
353            .iter()
354            .filter(|c| matches!(c, caveat::Caveat::FirstParty(_)))
355            .collect()
356    }
357
358    /// Retrieve a list of references to the third-party caveats
359    pub fn third_party_caveats(&self) -> Vec<&Caveat> {
360        self.caveats
361            .iter()
362            .filter(|c| matches!(c, caveat::Caveat::ThirdParty(_)))
363            .collect()
364    }
365
366    /// Validate that a Macaroon has all the expected fields
367    ///
368    /// This is a low-level function to confirm that a macaroon was constructured correctly. It
369    /// does *not* verify the signature, caveats, or in any way confirm that a macaroon is
370    /// authentic from a security standpoint.
371    fn validate(self) -> Result<Self> {
372        if self.identifier.0.is_empty() {
373            return Err(MacaroonError::IncompleteMacaroon("no identifier found"));
374        }
375        // The `signature` field is required by the struct definition and is
376        // always produced by `crypto::hmac` during construction or set from a
377        // validated 32-byte field during deserialization, so there is no
378        // "missing signature" state to guard against here.
379        Ok(self)
380    }
381
382    /// Add a first-party caveat to the macaroon
383    ///
384    /// A first-party caveat is just a string predicate in some
385    /// DSL which can be verified either by exact string match,
386    /// or by using a function to parse the string and validate it
387    /// (see Verifier for more info).
388    ///
389    /// # Errors
390    ///
391    /// - [`MacaroonError::TooManyCaveats`] if adding this caveat would exceed
392    ///   the [`MAX_CAVEATS`] limit.
393    /// - [`MacaroonError::FieldTooLarge`] if the predicate exceeds
394    ///   [`MAX_FIELD_SIZE_BYTES`].
395    ///
396    /// ```
397    /// # use libmacaroon::{Macaroon, MacaroonKey};
398    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
399    /// let key = MacaroonKey::generate(b"k");
400    /// let mut m = Macaroon::create(Some("loc"), &key, "id")?;
401    /// m.add_first_party_caveat("account = 12345")?
402    ///     .add_first_party_caveat("user = alice")?;
403    /// # Ok(()) }
404    /// ```
405    pub fn add_first_party_caveat(&mut self, predicate: impl AsRef<[u8]>) -> Result<&mut Self> {
406        let predicate_bytes = predicate.as_ref();
407        check_field_size("predicate", predicate_bytes.len())?;
408        self.check_caveat_capacity()?;
409        let caveat: caveat::Caveat = caveat::new_first_party(ByteString(predicate_bytes.to_vec()));
410        self.signature = caveat.sign(&self.signature);
411        self.caveats.push(caveat);
412        debug!("Macaroon::add_first_party_caveat: {:?}", self);
413        Ok(self)
414    }
415
416    /// Add a third-party caveat to the macaroon
417    ///
418    /// A third-party caveat is a caveat which must be verified by a third party
419    /// using macaroons provided by them (referred to as "discharge macaroons").
420    ///
421    /// # Errors
422    ///
423    /// - [`MacaroonError::TooManyCaveats`] if adding this caveat would exceed
424    ///   the [`MAX_CAVEATS`] limit.
425    /// - [`MacaroonError::FieldTooLarge`] if the location or id exceeds
426    ///   [`MAX_FIELD_SIZE_BYTES`].
427    /// - [`MacaroonError::RngError`] if the OS RNG fails while generating the
428    ///   nonce used to encrypt the caveat key (can happen in WASM environments
429    ///   without `crypto.getRandomValues`).
430    pub fn add_third_party_caveat(
431        &mut self,
432        location: &str,
433        key: &MacaroonKey,
434        id: impl AsRef<[u8]>,
435    ) -> Result<&mut Self> {
436        let id_bytes = id.as_ref();
437        check_field_size("caveat id", id_bytes.len())?;
438        check_field_size("caveat location", location.len())?;
439        self.check_caveat_capacity()?;
440        let vid: Vec<u8> = crypto::encrypt_key(&self.signature, key)?;
441        let caveat: caveat::Caveat =
442            caveat::new_third_party(ByteString(id_bytes.to_vec()), ByteString(vid), location);
443        self.signature = caveat.sign(&self.signature);
444        self.caveats.push(caveat);
445        debug!("Macaroon::add_third_party_caveat: {:?}", self);
446        Ok(self)
447    }
448
449    fn check_caveat_capacity(&self) -> Result<()> {
450        if self.caveats.len() >= MAX_CAVEATS {
451            return Err(MacaroonError::TooManyCaveats);
452        }
453        Ok(())
454    }
455
456    /// Bind a discharge macaroon to the original macaroon
457    ///
458    /// When a macaroon with third-party caveats must be authorized, you send off to the various
459    /// locations specified in the caveats, sending the caveat ID and key, and receive a set
460    /// of one or more "discharge macaroons" which are used to verify the caveat. In order to ensure
461    /// that the discharge macaroons aren't re-used in some other context, we bind them to the original
462    /// macaroon so that they can't be used in a different context.
463    pub fn bind(&self, discharge: &mut Macaroon) {
464        let zero_key = MacaroonKey::from([0; 32]);
465        discharge.signature = crypto::hmac2(&zero_key, &self.signature, &discharge.signature);
466        debug!(
467            "Macaroon::bind: original: {:?}, discharge: {:?}",
468            self, discharge
469        );
470    }
471
472    /// Serialize the macaroon using the serialization [Format] provided
473    ///
474    /// For V1 and V2, the binary format will be encoded as URL-safe base64 with padding
475    /// (`base64::URL_SAFE`). For V2JSON, the output will be JSON.
476    pub fn serialize(&self, format: serialization::Format) -> Result<String> {
477        match format {
478            serialization::Format::V1 => serialization::v1::serialize(self),
479            serialization::Format::V2 => serialization::v2::serialize(self),
480            #[cfg(feature = "v2json")]
481            serialization::Format::V2JSON => serialization::v2json::serialize(self),
482        }
483    }
484
485    /// Deserialize an encoded macaroon token, inferring the [Format].
486    ///
487    /// For V1 and V2 tokens, this assumes base64 encoding, in either "standard" or URL-safe
488    /// encoding, with or without padding.
489    ///
490    /// For V2JSON tokens, the token must begin with the `{` character with no preceeding whitespace.
491    ///
492    /// ## Usage
493    ///
494    /// ```rust
495    /// use libmacaroon::Macaroon;
496    /// # use std::error::Error;
497    /// # fn main() -> Result<(), Box<dyn Error>> {
498    ///
499    /// // '&str' gets automatically de-referenced to bytes ('&[u8]').
500    /// // 'b"byte-string"' or slice of 'u8' would also work.
501    /// let mac = Macaroon::deserialize("MDAxY2xvY2F0aW9uIGh0dHA6Ly9teWJhbmsvCjAwMjZpZGVudGlmaWVyIHdlIHVzZWQgb3VyIHNlY3JldCBrZXkKMDAxNmNpZCB0ZXN0ID0gY2F2ZWF0CjAwMmZzaWduYXR1cmUgGXusegRK8zMyhluSZuJtSTvdZopmDkTYjOGpmMI9vWcK")?;
502    ///
503    /// # #[cfg(feature = "v2json")] {
504    /// let mac_v2json = Macaroon::deserialize(r#"{"v":2,"l":"http://example.org/","i":"keyid", "c":[{"i":"account = 3735928559"},{"i":"user = alice"}],"s64": "S-lnzR6gxrJrr2pKlO6bBbFYhtoLqF6MQqk8jQ4SXvw"}"#)?;
505    ///
506    /// // expect this to fail; leading whitespace is not allowed
507    /// Macaroon::deserialize(r#"   {"v":2,"l":"http://example.org/","i":"keyid", "c":[{"i":"account = 3735928559"},{"i":"user = alice"}],"s64": "S-lnzR6gxrJrr2pKlO6bBbFYhtoLqF6MQqk8jQ4SXvw"}"#).unwrap_err();
508    /// # }
509    /// # Ok(()) }
510    /// ```
511    pub fn deserialize<T: AsRef<[u8]>>(token: T) -> Result<Macaroon> {
512        if token.as_ref().is_empty() {
513            return Err(MacaroonError::DeserializationError(
514                "empty token provided".to_string(),
515            ));
516        }
517        let mac: Macaroon = match token.as_ref()[0] as char {
518            #[cfg(feature = "v2json")]
519            '{' => serialization::v2json::deserialize(token.as_ref())?,
520            #[cfg(not(feature = "v2json"))]
521            '{' => {
522                return Err(MacaroonError::DeserializationError(
523                    "V2JSON support is disabled (the `v2json` feature is not enabled)".to_string(),
524                ))
525            }
526            _ => {
527                let binary = base64_decode_flexible(token.as_ref())?;
528                Macaroon::deserialize_binary(&binary)?
529            }
530        };
531        mac.validate()
532    }
533
534    /// Deserialize a binary macaroon token in binary, inferring the [Format]
535    ///
536    /// This works with V1 and V2 tokens, with no base64 encoding. It does not make sense to use
537    /// this with V2JSON tokens.
538    pub fn deserialize_binary(token: &[u8]) -> Result<Macaroon> {
539        if token.is_empty() {
540            return Err(MacaroonError::DeserializationError(
541                "empty macaroon token".to_string(),
542            ));
543        }
544        // V2 binary starts with the literal version byte 0x02. V1 binary
545        // starts with a four-hex-digit packet length, so the first byte must
546        // be a hex digit (the previous `'A'..='Z'` range accidentally
547        // accepted non-hex letters, which the parser would then reject with
548        // a less informative error).
549        let mac: Macaroon = match token[0] as char {
550            '\x02' => serialization::v2::deserialize(token)?,
551            '0'..='9' | 'a'..='f' | 'A'..='F' => serialization::v1::deserialize(token)?,
552            _ => {
553                return Err(MacaroonError::DeserializationError(
554                    "unknown macaroon serialization format".to_string(),
555                ))
556            }
557        };
558        mac.validate()
559    }
560}
561
562#[cfg(test)]
563mod tests {
564    use crate::{Caveat, Macaroon, MacaroonError, MacaroonKey, Result, Verifier};
565
566    #[test]
567    fn create_macaroon() {
568        let signature: MacaroonKey = [
569            20, 248, 23, 46, 70, 227, 253, 33, 123, 35, 116, 236, 130, 131, 211, 16, 41, 184, 51,
570            65, 213, 46, 109, 76, 49, 201, 186, 92, 114, 163, 214, 231,
571        ]
572        .into();
573        // NOTE: using byte string directly, not generating with HMAC
574        let key = MacaroonKey::from(b"this is a super duper secret key");
575        let macaroon_res = Macaroon::create(Some("location"), &key, "identifier");
576        assert!(macaroon_res.is_ok());
577        let macaroon = macaroon_res.unwrap();
578        assert!(macaroon.location.is_some());
579        assert_eq!("location", macaroon.location.as_deref().unwrap());
580        assert_eq!(macaroon.identifier(), b"identifier");
581        assert_eq!(signature, macaroon.signature);
582        assert_eq!(0, macaroon.caveats.len());
583    }
584
585    #[test]
586    fn create_invalid_macaroon() {
587        // NOTE: using byte string directly, not generating with HMAC
588        let key = MacaroonKey::from(b"this is a super duper secret key");
589        let macaroon_res: Result<Macaroon> = Macaroon::create(Some("location"), &key, "");
590        assert!(macaroon_res.is_err());
591        assert!(matches!(
592            macaroon_res,
593            Err(MacaroonError::IncompleteMacaroon(_))
594        ));
595        println!("{}", macaroon_res.unwrap_err());
596    }
597
598    #[test]
599    fn create_macaroon_errors() {
600        let deser_err = Macaroon::deserialize(b"\0");
601        assert!(matches!(
602            deser_err,
603            Err(MacaroonError::DeserializationError(_))
604        ));
605        println!("{}", deser_err.unwrap_err());
606
607        let key = MacaroonKey::generate(b"this is a super duper secret key");
608        let mut mac = Macaroon::create(Some("http://mybank"), &key, "identifier").unwrap();
609
610        let mut ver = Verifier::default();
611        let wrong_key = MacaroonKey::generate(b"not what was expected");
612        let sig_err = ver.verify(&mac, &wrong_key, &[]);
613        assert!(matches!(sig_err, Err(MacaroonError::InvalidSignature)));
614        println!("{}", sig_err.unwrap_err());
615        assert!(ver.verify(&mac, &key, &[]).is_ok());
616
617        mac.add_first_party_caveat("account = 3735928559").unwrap();
618        let cav_err = ver.verify(&mac, &key, &[]);
619        assert!(matches!(cav_err, Err(MacaroonError::CaveatNotSatisfied(_))));
620        println!("{}", cav_err.unwrap_err());
621        ver.satisfy_exact("account = 3735928559");
622        assert!(ver.verify(&mac, &key, &[]).is_ok());
623
624        let mut mac2 = mac.clone();
625        let cav_key = MacaroonKey::generate(b"My key");
626        mac2.add_third_party_caveat("other location", &cav_key, "other ident")
627            .unwrap();
628        let cav_err = ver.verify(&mac2, &key, &[]);
629        assert!(matches!(cav_err, Err(MacaroonError::CaveatNotSatisfied(_))));
630        println!("{}", cav_err.unwrap_err());
631
632        let discharge =
633            Macaroon::create(Some("http://auth.mybank/"), &cav_key, "other keyid").unwrap();
634        let disch_err = ver.verify(&mac, &key, &[discharge]);
635        assert!(matches!(disch_err, Err(MacaroonError::DischargeNotUsed)));
636        println!("{}", disch_err.unwrap_err());
637    }
638
639    #[test]
640    fn create_macaroon_with_first_party_caveat() {
641        let signature: MacaroonKey = [
642            14, 23, 21, 148, 48, 224, 4, 143, 81, 137, 60, 25, 201, 198, 245, 250, 249, 62, 233,
643            94, 93, 65, 247, 88, 25, 39, 170, 203, 8, 4, 167, 187,
644        ]
645        .into();
646        // NOTE: using byte string directly, not generating with HMAC
647        let key = MacaroonKey::from(b"this is a super duper secret key");
648        let mut macaroon = Macaroon::create(Some("location"), &key, "identifier").unwrap();
649        macaroon.add_first_party_caveat("predicate").unwrap();
650        assert_eq!(1, macaroon.caveats.len());
651        let predicate = match &macaroon.caveats[0] {
652            Caveat::FirstParty(fp) => fp.predicate().to_vec(),
653            _ => Vec::new(),
654        };
655        assert_eq!(b"predicate".to_vec(), predicate);
656        assert_eq!(signature, macaroon.signature);
657        assert_eq!(&macaroon.caveats[0], macaroon.first_party_caveats()[0]);
658    }
659
660    #[test]
661    fn create_macaroon_with_third_party_caveat() {
662        // NOTE: using byte string directly, not generating with HMAC
663        let key = MacaroonKey::from(b"this is a super duper secret key");
664        let mut macaroon = Macaroon::create(Some("location"), &key, "identifier").unwrap();
665        let location = "https://auth.mybank.com";
666        let cav_key = MacaroonKey::generate(b"My key");
667        let id = "My Caveat";
668        macaroon
669            .add_third_party_caveat(location, &cav_key, id)
670            .unwrap();
671        assert_eq!(1, macaroon.caveats.len());
672        let cav_id = match &macaroon.caveats[0] {
673            Caveat::ThirdParty(tp) => tp.id().to_vec(),
674            _ => Vec::new(),
675        };
676        let cav_location = match &macaroon.caveats[0] {
677            Caveat::ThirdParty(tp) => tp.location().to_string(),
678            _ => String::default(),
679        };
680        assert_eq!(location, cav_location);
681        assert_eq!(id.as_bytes().to_vec(), cav_id);
682        assert_eq!(&macaroon.caveats[0], macaroon.third_party_caveats()[0]);
683    }
684
685    #[test]
686    fn test_deserialize_bad_data() {
687        // these are all expected to fail... but not panic!
688        assert!(Macaroon::deserialize(b"").is_err());
689        assert!(Macaroon::deserialize(b"12345").is_err());
690        assert!(Macaroon::deserialize(b"\0").is_err());
691        assert!(Macaroon::deserialize(b"NDhJe_A==").is_err());
692
693        // examples that fail from fuzzing for the top-level deserialize function
694        assert!(Macaroon::deserialize(vec![10]).is_err());
695        assert!(Macaroon::deserialize(vec![70, 70, 102, 70]).is_err());
696        assert!(Macaroon::deserialize(vec![2, 2, 212, 212, 212, 212]).is_err());
697    }
698}
699
700// This will run rust code in the README as a test. Copied from:
701// https://github.com/rust-lang/cargo/issues/383#issuecomment-720873790
702#[cfg(doctest)]
703mod test_readme {
704    macro_rules! external_doc_test {
705        ($x:expr) => {
706            #[doc = $x]
707            extern "C" {}
708        };
709    }
710
711    external_doc_test!(include_str!("../README.md"));
712}