├── .gitignore ├── .travis.yml ├── CHANGES.md ├── Cargo.toml ├── LICENSE ├── README.md ├── src ├── caveat.rs ├── error.rs ├── lib.rs ├── token.rs ├── v1.rs └── verifier.rs └── tests └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | 3 | install: 4 | - wget https://github.com/jedisct1/libsodium/releases/download/1.0.11/libsodium-1.0.11.tar.gz 5 | - tar xvfz libsodium-1.0.11.tar.gz 6 | - cd libsodium-1.0.11 && ./configure --prefix=/usr && make && sudo make install && cd .. 7 | 8 | branches: 9 | only: 10 | - master 11 | 12 | rust: 13 | - stable 14 | - beta 15 | - nightly 16 | 17 | notifications: 18 | irc: 'irc.freenode.org#cryptosphere' 19 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | ## 0.3.3 (2017-02-08) 2 | 3 | * Update project links to use "macaroons-rs" 4 | 5 | ## 0.3.2 (2017-01-31) 6 | 7 | * Add "authentication" category to Cargo.toml. 8 | 9 | ## 0.3.1 (2016-07-31) 10 | 11 | * [#15](https://github.com/cryptosphere/macaroons-rs/pull/15) 12 | Get rid of Predicate tuple struct. 13 | ([@tarcieri]) 14 | 15 | ## 0.3.0 (2016-07-31) 16 | 17 | * [#14](https://github.com/cryptosphere/macaroons-rs/pull/14) 18 | Return Results for verification (instead of bool). 19 | ([@tarcieri]) 20 | 21 | * [#13](https://github.com/cryptosphere/macaroons-rs/pull/13) 22 | Return errors for out-of-order V1 packets. 23 | ([@tarcieri]) 24 | 25 | * [#12](https://github.com/cryptosphere/macaroons-rs/pull/12) 26 | Error type. 27 | ([@tarcieri]) 28 | 29 | * [#10](https://github.com/cryptosphere/macaroons-rs/pull/10) 30 | Token trait. 31 | ([@tarcieri]) 32 | 33 | * [#8](https://github.com/cryptosphere/macaroons-rs/pull/8) 34 | Namespace Token as v1::V1Token. 35 | ([@tarcieri]) 36 | 37 | * [#6](https://github.com/cryptosphere/macaroons-rs/pull/6) 38 | Use &[u8] instead of &Vec. 39 | ([@panicbit]) 40 | 41 | * [#5](https://github.com/cryptosphere/macaroons-rs/pull/5) 42 | Make locations an Option. 43 | ([@tarcieri]) 44 | 45 | * [#3](https://github.com/cryptosphere/macaroons-rs/pull/3) 46 | Initial verifier type. 47 | ([@ecordell]) 48 | 49 | ## 0.2.1 (2015-11-05) 50 | 51 | * Utilize sodiumoxide IUF HMAC API 52 | 53 | ## 0.2.0 (2015-10-13) 54 | 55 | * Initial serialization-only support for third-party caveats (no verification) 56 | 57 | ## 0.1.1 (2015-07-19) 58 | 59 | * Support for verifying Tokens against keys 60 | 61 | ## 0.1.0 (2015-05-18) 62 | 63 | * Initial release 64 | 65 | [@tarcieri]: https://github.com/tarcieri 66 | [@ecordell]: https://github.com/ecordell 67 | [@panicbit]: https://github.com/panicbit 68 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "macaroons" 3 | description = "Cookie-like bearer credentials with caveats for distributed authorization" 4 | version = "0.3.3" 5 | authors = ["Tony Arcieri "] 6 | homepage = "https://github.com/cryptosphere/macaroons-rs" 7 | repository = "https://github.com/cryptosphere/macaroons-rs.git" 8 | readme = "README.md" 9 | license = "MIT" 10 | keywords = ["cookies", "authorization", "credentials"] 11 | categories = ["authentication","cryptography"] 12 | 13 | [dependencies] 14 | sodiumoxide = "^0" 15 | libsodium-sys = "^0" 16 | rustc-serialize = "^0.3" 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Tony Arcieri 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Macaroons (for Rust!) [![Latest Version][crate-image]][crate-link] [![Build Status][build-image]][build-link] [![Apache 2 licensed][license-image]][license-link] 2 | 3 | [crate-image]: https://img.shields.io/crates/v/macaroons.svg 4 | [crate-link]: https://crates.io/crates/macaroons 5 | [build-image]: https://travis-ci.org/cryptosphere/macaroons-rs.svg?branch=master 6 | [build-link]: https://travis-ci.org/cryptosphere/macaroons-rs 7 | [license-image]: https://img.shields.io/badge/license-MIT-blue.svg 8 | [license-link]: https://github.com/cryptosphere/macaroons-rs/blob/master/LICENSE 9 | 10 | A better kind of cookie. 11 | 12 | Macaroons are a bearer credential format built around "caveats", i.e. conditions 13 | that must hold for a particular credential to be authorized. Using neat crypto 14 | tricks, anyone holding a Macaroon can add more caveats to a Macaroon, but once 15 | caveats are added they cannot be removed. 16 | 17 | http://macaroons.io 18 | 19 | ### Is it any good? 20 | 21 | [Yes.](http://news.ycombinator.com/item?id=3067434) 22 | 23 | ### Is it "Production Ready™"? 24 | 25 | The library is ready for eager early adopters. If you're using Rust, you're 26 | probably one of those anyway. 27 | 28 | The following features have been implemented: 29 | 30 | * Creating Macaroons 31 | * Verifying Macaroons 32 | * First-party caveats 33 | * Third-party caveats 34 | * Serializing to base64url-encoded binary format 35 | * Deserializing base64url-encoded Macaroons 36 | * Verifying first-party caveats 37 | 38 | The following features still need to be implemented: 39 | 40 | * Discharge macaroons 41 | * Verifying third-party caveats 42 | 43 | Additional planned work: 44 | 45 | * Nom-based parser (may require API changes) 46 | 47 | ## V2 Format Support 48 | 49 | [The Macaroons format is changing!](https://groups.google.com/forum/#!msg/macaroons/EIDUZQoelq8/KnbVukmGBQAJ) 50 | 51 | A specification for a new, more compact "V2" format has been published. 52 | 53 | This library has begun to implement it. In the process, the API is changing 54 | so that it can support both the old and new formats. 55 | 56 | Pardon our dust. 57 | 58 | ## Help and Discussion 59 | 60 | Interested in Macaroons? Join the Macaroons Google Group: 61 | 62 | https://groups.google.com/forum/#!forum/macaroons 63 | 64 | You can also join by email by sending an email message here: 65 | 66 | [macaroons+subscribe@googlegroups.com](mailto:macaroons+subscribe@googlegroups.com) 67 | 68 | We're also on IRC at #macaroons on irc.freenode.net. 69 | 70 | ## Usage 71 | 72 | Coming soon! 73 | 74 | ## Additional Reading 75 | 76 | * [Macaroons: Cookies with Contextual Caveats for Decentralized Authroization in the Cloud](https://static.googleusercontent.com/media/research.google.com/en/us/pubs/archive/41892.pdf) 77 | 78 | ## License 79 | 80 | Copyright (c) 2015-2016 Tony Arcieri. Distributed under the MIT License. 81 | See LICENSE.txt for further details. 82 | -------------------------------------------------------------------------------- /src/caveat.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | pub type Predicate = Vec; 4 | 5 | #[derive(Clone)] 6 | pub struct Caveat { 7 | pub caveat_id: Vec, 8 | pub caveat_key: Option>, 9 | pub verification_id: Option>, 10 | pub caveat_location: Option>, 11 | } 12 | 13 | impl Caveat { 14 | pub fn first_party(caveat_id: Predicate) -> Caveat { 15 | Caveat { 16 | caveat_id: caveat_id, 17 | caveat_key: None, 18 | verification_id: None, 19 | caveat_location: None, 20 | } 21 | } 22 | 23 | pub fn third_party(caveat_key: Vec, 24 | caveat_id: Vec, 25 | caveat_location: Vec) 26 | -> Caveat { 27 | Caveat { 28 | caveat_id: caveat_id, 29 | caveat_key: Some(caveat_key), 30 | verification_id: None, 31 | caveat_location: Some(caveat_location), 32 | } 33 | } 34 | } 35 | 36 | impl fmt::Display for Caveat { 37 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 38 | let caveat_id = String::from_utf8(self.caveat_id.clone()).unwrap(); 39 | write!(f, "{}", &caveat_id) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt, result}; 2 | use std::error::Error as StdError; 3 | 4 | #[derive(Debug, Eq, PartialEq, Copy, Clone)] 5 | pub enum Error { 6 | VerificationFailed, 7 | FirstPartyCaveatFailed, 8 | Base64, 9 | PacketLength, 10 | SignatureLength, 11 | MalformedPacket, 12 | PacketOrdering, 13 | UnknownPacketType, 14 | MissingIdentifier, 15 | MissingSignature, 16 | } 17 | 18 | impl fmt::Display for Error { 19 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 20 | write!(fmt, "{}", self.description()) 21 | } 22 | } 23 | 24 | impl StdError for Error { 25 | fn description(&self) -> &str { 26 | match *self { 27 | Error::VerificationFailed => "the token is inauthentic", 28 | Error::FirstPartyCaveatFailed => "a first-party caveat failed to verify", 29 | Error::Base64 => "unable to decode Base64", 30 | Error::PacketLength => "unable to decode packet length, or packet too long", 31 | Error::SignatureLength => "signature length incorrect", 32 | Error::MalformedPacket => "packet not properly structured", 33 | Error::PacketOrdering => "packet types are not in the right order", 34 | Error::UnknownPacketType => "packet found with unknown type", 35 | Error::MissingIdentifier => "no 'identifier' found at beginning of token", 36 | Error::MissingSignature => "no 'signature' found in token", 37 | } 38 | } 39 | } 40 | 41 | pub type Result = result::Result; 42 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![crate_name = "macaroons"] 2 | #![crate_type = "lib"] 3 | 4 | extern crate sodiumoxide; 5 | extern crate rustc_serialize; 6 | 7 | pub mod caveat; 8 | pub mod error; 9 | pub mod token; 10 | pub mod verifier; 11 | 12 | pub mod v1; 13 | 14 | // Macaroons personalize the HMAC key using the string 15 | // "macaroons-key-generator" padded to 32-bytes with zeroes 16 | pub const KEY_GENERATOR: &'static [u8; 32] = b"macaroons-key-generator\0\0\0\0\0\0\0\0\0"; 17 | -------------------------------------------------------------------------------- /src/token.rs: -------------------------------------------------------------------------------- 1 | 2 | use std::marker::Sized; 3 | 4 | use caveat::Caveat; 5 | use error::Result; 6 | use verifier::Verifier; 7 | 8 | pub trait Token { 9 | fn new(key: &[u8], identifier: Vec, location: Option>) -> Self; 10 | fn deserialize(macaroon: Vec) -> Result where Self: Sized; 11 | fn serialize(&self) -> Result>; 12 | fn add_caveat(&self, caveat: &Caveat) -> Self; 13 | fn verify(&self, key: &[u8], verifier: V) -> Result<()>; 14 | fn authenticate_without_verifying(&self, key: &[u8]) -> Result<()>; 15 | } 16 | -------------------------------------------------------------------------------- /src/v1.rs: -------------------------------------------------------------------------------- 1 | use std; 2 | 3 | use rustc_serialize::base64::{self, FromBase64, ToBase64}; 4 | 5 | use sodiumoxide::crypto::auth::hmacsha256::{self, Tag, Key, State, TAGBYTES}; 6 | use sodiumoxide::crypto::secretbox; 7 | use sodiumoxide::utils; 8 | 9 | use super::KEY_GENERATOR; 10 | use caveat::Caveat; 11 | use error::{Error, Result}; 12 | use token::Token; 13 | use verifier::Verifier; 14 | 15 | const PACKET_PREFIX_LENGTH: usize = 4; 16 | const MAX_PACKET_LENGTH: usize = 65535; 17 | 18 | pub struct V1Token { 19 | pub identifier: Vec, 20 | pub location: Option>, 21 | pub caveats: Vec, 22 | pub tag: [u8; TAGBYTES], 23 | } 24 | 25 | struct Packet { 26 | pub id: Vec, 27 | pub value: Vec, 28 | pub length: usize, 29 | } 30 | 31 | impl V1Token { 32 | fn packetize(result: &mut Vec, field: &str, value: &[u8]) -> Result<()> { 33 | let field_bytes: Vec = Vec::from(field); 34 | let packet_length = PACKET_PREFIX_LENGTH + field_bytes.len() + value.len() + 2; 35 | 36 | if packet_length > MAX_PACKET_LENGTH { 37 | return Err(Error::PacketLength); 38 | } 39 | 40 | let pkt_line = format!("{:04x}{} ", packet_length, field).into_bytes(); 41 | result.extend(pkt_line.into_iter()); 42 | result.extend(value.clone().into_iter()); 43 | result.push(b'\n'); 44 | 45 | Ok(()) 46 | } 47 | 48 | fn depacketize(data: &[u8], index: usize) -> Result { 49 | let length_str = try!(std::str::from_utf8(&data[index..index + PACKET_PREFIX_LENGTH]) 50 | .map_err(|_e| Error::PacketLength)); 51 | 52 | let packet_length = try!(usize::from_str_radix(length_str, 16) 53 | .map_err(|_e| Error::PacketLength)); 54 | 55 | let mut packet_bytes = data[index + PACKET_PREFIX_LENGTH..index + packet_length].to_vec(); 56 | 57 | let pos = try!(packet_bytes.iter() 58 | .position(|&byte| byte == b' ') 59 | .ok_or(Error::MalformedPacket)); 60 | 61 | let (id, value_arr) = packet_bytes.split_at_mut(pos); 62 | let mut value = value_arr.to_vec(); 63 | value.remove(0); 64 | 65 | if try!(value.pop().ok_or(Error::MalformedPacket)) != b'\n' { 66 | return Err(Error::MalformedPacket); 67 | } 68 | 69 | Ok(Packet { 70 | id: id.to_vec(), 71 | value: value, 72 | length: packet_length, 73 | }) 74 | } 75 | } 76 | 77 | impl Token for V1Token { 78 | fn new(key: &[u8], identifier: Vec, location: Option>) -> V1Token { 79 | let Tag(personalized_key) = hmacsha256::authenticate(&key, &Key(*KEY_GENERATOR)); 80 | let Tag(tag) = hmacsha256::authenticate(&identifier, &Key(personalized_key)); 81 | 82 | V1Token { 83 | location: location, 84 | identifier: identifier, 85 | caveats: Vec::new(), 86 | tag: tag, 87 | } 88 | } 89 | 90 | fn deserialize(macaroon: Vec) -> Result { 91 | let token_data = try!(macaroon.from_base64().map_err(|_e| Error::Base64)); 92 | let mut index: usize = 0; 93 | 94 | // Parse the (optional location and) identifier packets 95 | let packet1 = try!(V1Token::depacketize(&token_data, index)); 96 | index += packet1.length; 97 | 98 | let (identifier, location) = match &packet1.id[..] { 99 | b"identifier" => (packet1.value, None), 100 | b"location" => { 101 | let packet2 = try!(V1Token::depacketize(&token_data, index)); 102 | index += packet2.length; 103 | 104 | if &packet2.id[..] != b"identifier" { 105 | return Err(Error::MissingIdentifier); 106 | } 107 | 108 | (packet2.value, Some(packet1.value)) 109 | } 110 | _ => return Err(Error::MissingIdentifier), 111 | }; 112 | 113 | let mut caveats: Vec = Vec::new(); 114 | let mut tag: Option<[u8; TAGBYTES]> = None; 115 | 116 | // Parse caveats 117 | while index < token_data.len() { 118 | let packet = try!(V1Token::depacketize(&token_data, index)); 119 | 120 | index += packet.length; 121 | 122 | match &packet.id[..] { 123 | b"cid" => caveats.push(Caveat::first_party(packet.value)), 124 | b"vid" | b"cl" => { 125 | match caveats.pop() { 126 | Some(caveat) => { 127 | let caveat_id = caveat.caveat_id; 128 | let mut verification_id = caveat.verification_id; 129 | let mut caveat_location = caveat.caveat_location; 130 | 131 | match &packet.id[..] { 132 | b"vid" => verification_id = Some(packet.value), 133 | b"cl" => caveat_location = Some(packet.value), 134 | _ => return Err(Error::PacketOrdering), 135 | } 136 | 137 | caveats.push(Caveat { 138 | caveat_id: caveat_id, 139 | caveat_key: None, 140 | verification_id: verification_id, 141 | caveat_location: caveat_location, 142 | }) 143 | } 144 | None => return Err(Error::PacketOrdering), 145 | } 146 | } 147 | b"signature" => { 148 | // Make sure signature is the last packet 149 | if index != token_data.len() { 150 | return Err(Error::PacketOrdering); 151 | } 152 | 153 | if packet.value.len() != TAGBYTES { 154 | return Err(Error::SignatureLength); 155 | } 156 | 157 | let mut signature_bytes = [0u8; TAGBYTES]; 158 | signature_bytes.copy_from_slice(&packet.value); 159 | 160 | tag = Some(signature_bytes); 161 | } 162 | _ => return Err(Error::UnknownPacketType), 163 | } 164 | } 165 | 166 | if tag == None { 167 | return Err(Error::MissingSignature); 168 | } 169 | 170 | let token = V1Token { 171 | identifier: identifier, 172 | location: location, 173 | caveats: caveats, 174 | tag: tag.unwrap(), 175 | }; 176 | 177 | Ok(token) 178 | } 179 | 180 | fn serialize(&self) -> Result> { 181 | // TODO: estimate capacity and use Vec::with_capacity 182 | let mut result: Vec = Vec::new(); 183 | 184 | if self.location.is_some() { 185 | try!(V1Token::packetize(&mut result, "location", &self.location.clone().unwrap())); 186 | } 187 | 188 | try!(V1Token::packetize(&mut result, "identifier", &self.identifier)); 189 | 190 | for caveat in &self.caveats { 191 | try!(V1Token::packetize(&mut result, "cid", &caveat.caveat_id)); 192 | 193 | if caveat.verification_id.is_some() { 194 | try!(V1Token::packetize(&mut result, 195 | "vid", 196 | &caveat.verification_id.clone().unwrap())); 197 | } 198 | 199 | if caveat.caveat_location.is_some() { 200 | try!(V1Token::packetize(&mut result, 201 | "cl", 202 | &caveat.caveat_location.clone().unwrap())); 203 | } 204 | } 205 | 206 | try!(V1Token::packetize(&mut result, "signature", &self.tag.to_vec())); 207 | 208 | Ok(result.to_base64(base64::URL_SAFE).into_bytes()) 209 | } 210 | 211 | fn add_caveat(&self, caveat: &Caveat) -> V1Token { 212 | let key_bytes = self.tag; 213 | let mut new_caveats = self.caveats.to_vec(); 214 | 215 | let new_tag = match caveat.caveat_key { 216 | Some(ref key) => { 217 | let Tag(personalized_key) = hmacsha256::authenticate(&key, &Key(*KEY_GENERATOR)); 218 | let nonce = secretbox::gen_nonce(); 219 | 220 | let mut new_caveat = caveat.clone(); 221 | let verification_id = 222 | secretbox::seal(&key_bytes, 223 | &nonce, 224 | &secretbox::xsalsa20poly1305::Key(personalized_key)); 225 | 226 | let mut caveat_authenticator = State::init(&key_bytes); 227 | 228 | let Tag(caveat_id_tag) = hmacsha256::authenticate(&new_caveat.caveat_id, 229 | &Key(key_bytes)); 230 | caveat_authenticator.update(&caveat_id_tag); 231 | 232 | let Tag(verification_id_tag) = hmacsha256::authenticate(&verification_id, 233 | &Key(key_bytes)); 234 | caveat_authenticator.update(&verification_id_tag); 235 | 236 | new_caveat.verification_id = Some(verification_id); 237 | 238 | new_caveats.push(new_caveat); 239 | caveat_authenticator.finalize() 240 | } 241 | None => { 242 | new_caveats.push(caveat.clone()); 243 | hmacsha256::authenticate(&caveat.caveat_id, &Key(key_bytes)) 244 | } 245 | }; 246 | 247 | V1Token { 248 | identifier: self.identifier.clone(), 249 | location: self.location.clone(), 250 | caveats: new_caveats, 251 | tag: new_tag.0, 252 | } 253 | } 254 | 255 | fn verify(&self, key: &[u8], verifier: V) -> Result<()> { 256 | try!(self.authenticate_without_verifying(&key)); 257 | 258 | for caveat in &self.caveats { 259 | if caveat.verification_id == None { 260 | if !verifier.verify_first_party(&caveat.caveat_id) { 261 | return Err(Error::VerificationFailed); 262 | } 263 | } else { 264 | if !verifier.verify_third_party(&caveat.caveat_id) { 265 | return Err(Error::VerificationFailed); 266 | } 267 | } 268 | } 269 | 270 | Ok(()) 271 | } 272 | 273 | fn authenticate_without_verifying(&self, key: &[u8]) -> Result<()> { 274 | let mut verify_token = V1Token::new(&key, self.identifier.clone(), self.location.clone()); 275 | 276 | for caveat in &self.caveats { 277 | verify_token = verify_token.add_caveat(&caveat) 278 | } 279 | 280 | // Constant-time comparison function 281 | if utils::memcmp(&verify_token.tag, &self.tag) { 282 | Ok(()) 283 | } else { 284 | Err(Error::VerificationFailed) 285 | } 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /src/verifier.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | use std::rc::Rc; 3 | 4 | pub trait Verifier { 5 | fn verify_first_party(&self, _caveat: &[u8]) -> bool { false } 6 | fn verify_third_party(&self, _caveat: &[u8]) -> bool { false } 7 | } 8 | 9 | // Pointer primitives 10 | 11 | impl<'a, V: Verifier> Verifier for &'a V { 12 | fn verify_first_party(&self, caveat: &[u8]) -> bool { 13 | (**self).verify_first_party(caveat) 14 | } 15 | 16 | fn verify_third_party(&self, caveat: &[u8]) -> bool { 17 | (**self).verify_first_party(caveat) 18 | } 19 | } 20 | 21 | impl<'a, V: Verifier> Verifier for &'a mut V { 22 | fn verify_first_party(&self, caveat: &[u8]) -> bool { 23 | (**self).verify_first_party(caveat) 24 | } 25 | 26 | fn verify_third_party(&self, caveat: &[u8]) -> bool { 27 | (**self).verify_third_party(caveat) 28 | } 29 | } 30 | 31 | impl Verifier for Box { 32 | fn verify_first_party(&self, caveat: &[u8]) -> bool { 33 | (**self).verify_first_party(caveat) 34 | } 35 | 36 | fn verify_third_party(&self, caveat: &[u8]) -> bool { 37 | (**self).verify_third_party(caveat) 38 | } 39 | } 40 | 41 | impl Verifier for Rc { 42 | fn verify_first_party(&self, caveat: &[u8]) -> bool { 43 | (**self).verify_first_party(caveat) 44 | } 45 | 46 | fn verify_third_party(&self, caveat: &[u8]) -> bool { 47 | (**self).verify_third_party(caveat) 48 | } 49 | } 50 | 51 | impl Verifier for Arc { 52 | fn verify_first_party(&self, caveat: &[u8]) -> bool { 53 | (**self).verify_first_party(caveat) 54 | } 55 | 56 | fn verify_third_party(&self, caveat: &[u8]) -> bool { 57 | (**self).verify_third_party(caveat) 58 | } 59 | } 60 | 61 | // Func 62 | 63 | pub struct Func bool>(pub F); 64 | 65 | impl Verifier for Func where 66 | F: Fn(&str) -> bool 67 | { 68 | fn verify_first_party(&self, caveat: &[u8]) -> bool { 69 | ::std::str::from_utf8(&caveat) 70 | .map(&self.0) 71 | .unwrap_or(false) 72 | } 73 | } 74 | 75 | // ByteFunc 76 | 77 | pub struct ByteFunc bool>(pub F); 78 | 79 | impl Verifier for ByteFunc where 80 | F: Fn(&[u8]) -> bool 81 | { 82 | fn verify_first_party(&self, caveat: &[u8]) -> bool { 83 | (self.0)(caveat) 84 | } 85 | } 86 | 87 | // LinkedVerifier 88 | 89 | pub struct LinkedVerifier { 90 | verifier1: V1, 91 | verifier2: V2, 92 | } 93 | 94 | impl LinkedVerifier { 95 | pub fn from(verifier1: V1, verifier2: V2) -> Self { 96 | LinkedVerifier { 97 | verifier1: verifier1, 98 | verifier2: verifier2, 99 | } 100 | } 101 | } 102 | 103 | impl Verifier for LinkedVerifier { 104 | fn verify_first_party(&self, caveat: &[u8]) -> bool { 105 | self.verifier1.verify_first_party(caveat) 106 | || self.verifier2.verify_first_party(caveat) 107 | } 108 | 109 | fn verify_third_party(&self, caveat: &[u8]) -> bool { 110 | self.verifier1.verify_third_party(caveat) 111 | || self.verifier2.verify_third_party(caveat) 112 | } 113 | } 114 | 115 | // Eq 116 | 117 | pub struct Eq, Value: AsRef<[u8]>>(pub Tag, pub Value); 118 | 119 | impl, Value: AsRef<[u8]>> Verifier for Eq { 120 | fn verify_first_party(&self, caveat: &[u8]) -> bool { 121 | let tag = self.0.as_ref(); 122 | let op = b" = "; 123 | let value = self.1.as_ref(); 124 | let len = tag.len() + op.len() + value.len(); 125 | 126 | len == caveat.len() 127 | && &caveat[0 .. tag.len() ] == tag 128 | && &caveat[tag.len() .. tag.len() + op.len()] == op 129 | && &caveat[tag.len() + op.len() .. ] == value 130 | } 131 | } 132 | 133 | // LinkVerifier 134 | 135 | pub trait LinkVerifier: Verifier + Sized { 136 | fn link(self, verifier: V) -> LinkedVerifier; 137 | } 138 | 139 | impl LinkVerifier for T { 140 | fn link(self, verifier: V) -> LinkedVerifier { 141 | LinkedVerifier::from(verifier, self) 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /tests/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate macaroons; 2 | 3 | use macaroons::caveat::Caveat; 4 | use macaroons::token::Token; 5 | use macaroons::v1::V1Token; 6 | use macaroons::verifier::{Func, LinkVerifier}; 7 | 8 | const EMPTY_TAG: [u8; 32] = [0xe3, 0xd9, 0xe0, 0x29, 0x08, 0x52, 0x6c, 0x4c, 0x00, 0x39, 0xae, 9 | 0x15, 0x11, 0x41, 0x15, 0xd9, 0x7f, 0xdd, 0x68, 0xbf, 0x2b, 0xa3, 10 | 0x79, 0xb3, 0x42, 0xaa, 0xf0, 0xf6, 0x17, 0xd0, 0x55, 0x2f]; 11 | 12 | const EXPECTED_TAG_WITH_FIRST_PARTY_CAVEATS: [u8; 32] = [0x19, 0x7b, 0xac, 0x7a, 0x04, 0x4a, 0xf3, 13 | 0x33, 0x32, 0x86, 0x5b, 0x92, 0x66, 0xe2, 14 | 0x6d, 0x49, 0x3b, 0xdd, 0x66, 0x8a, 0x66, 15 | 0x0e, 0x44, 0xd8, 0x8c, 0xe1, 0xa9, 0x98, 16 | 0xc2, 0x3d, 0xbd, 0x67]; 17 | 18 | fn example_key() -> Vec { 19 | Vec::from("this is our super secret key; only we should know it") 20 | } 21 | 22 | fn invalid_key() -> Vec { 23 | Vec::from("this is not the key you are looking for; it is evil") 24 | } 25 | 26 | fn example_id() -> Vec { 27 | Vec::from("we used our secret key") 28 | } 29 | 30 | fn example_uri() -> Vec { 31 | Vec::from("http://mybank/") 32 | } 33 | 34 | fn example_first_party_caveat() -> Caveat { 35 | Caveat::first_party(Vec::from("test = caveat")) 36 | } 37 | 38 | fn example_first_party_caveat_different_prefix() -> Caveat { 39 | Caveat::first_party(Vec::from("other = test")) 40 | } 41 | 42 | fn verify_caveat(predicate: &str) -> bool { 43 | let (prefix, value) = predicate.split_at(7); 44 | 45 | if prefix != "test = " { 46 | return true; 47 | } 48 | 49 | value == "caveat" 50 | } 51 | 52 | fn verify_wrong_value(predicate: &str) -> bool { 53 | let (prefix, value) = predicate.split_at(7); 54 | 55 | if prefix != "test = " { 56 | return true; 57 | } 58 | 59 | value == "wrong" 60 | } 61 | 62 | fn verify_other(predicate: &str) -> bool { 63 | let (prefix, value) = predicate.split_at(7); 64 | 65 | if prefix != "other = " { 66 | return true; 67 | } 68 | 69 | value == "caveat" 70 | } 71 | 72 | fn example_caveat_key() -> Vec { 73 | Vec::from("4; guaranteed random by a fair toss of the dice") 74 | } 75 | 76 | fn example_third_party_caveat_id() -> Vec { 77 | Vec::from("this was how we remind auth of key/pred") 78 | } 79 | 80 | fn example_third_party_caveat_location() -> Vec { 81 | Vec::from("http://auth.mybank/") 82 | } 83 | 84 | fn example_third_party_caveat() -> Caveat { 85 | Caveat::third_party(example_caveat_key(), 86 | example_third_party_caveat_id(), 87 | example_third_party_caveat_location()) 88 | } 89 | 90 | fn example_token() -> V1Token { 91 | V1Token::new(&example_key(), example_id(), Some(example_uri())) 92 | } 93 | 94 | fn example_serialized_with_first_party_caveats() -> Vec { 95 | Vec::from("MDAxY2xvY2F0aW9uIGh0dHA6Ly9teWJhbmsvCjAwMjZpZGVudGlmaWVyIHdlIHVzZWQgb3VyIHNlY3JldCB\ 96 | rZXkKMDAxNmNpZCB0ZXN0ID0gY2F2ZWF0CjAwMmZzaWduYXR1cmUgGXusegRK8zMyhluSZuJtSTvdZopmDk\ 97 | TYjOGpmMI9vWcK") 98 | } 99 | 100 | #[test] 101 | fn empty_macaroon_signature() { 102 | let token = V1Token::new(&example_key(), example_id(), Some(example_uri())); 103 | assert_eq!(EMPTY_TAG, token.tag) 104 | } 105 | 106 | #[test] 107 | fn signature_with_first_party_caveat() { 108 | let token = example_token().add_caveat(&example_first_party_caveat()); 109 | assert_eq!(EXPECTED_TAG_WITH_FIRST_PARTY_CAVEATS, token.tag) 110 | } 111 | 112 | #[test] 113 | fn signature_with_third_party_caveat() { 114 | let mut token = example_token(); 115 | 116 | token = token.add_caveat(&example_first_party_caveat()); 117 | token = token.add_caveat(&example_third_party_caveat()); 118 | 119 | let token_serialized = token.serialize().unwrap(); 120 | let parsed_token = V1Token::deserialize(token_serialized).unwrap(); 121 | let third_party_caveat = &parsed_token.caveats[1]; 122 | 123 | assert_eq!(third_party_caveat.caveat_id, 124 | example_third_party_caveat_id()); 125 | assert_eq!(third_party_caveat.caveat_location, 126 | Some(example_third_party_caveat_location())); 127 | } 128 | 129 | #[test] 130 | fn binary_serialization() { 131 | let token = example_token().add_caveat(&example_first_party_caveat()); 132 | assert_eq!(example_serialized_with_first_party_caveats(), 133 | token.serialize().unwrap()); 134 | } 135 | 136 | #[test] 137 | fn binary_deserialization() { 138 | let token = V1Token::deserialize(example_serialized_with_first_party_caveats()).unwrap(); 139 | 140 | assert_eq!(example_uri(), token.location.unwrap()); 141 | assert_eq!(example_id(), token.identifier); 142 | 143 | assert_eq!(EXPECTED_TAG_WITH_FIRST_PARTY_CAVEATS, token.tag) 144 | } 145 | 146 | #[test] 147 | fn simple_verification() { 148 | let token = example_token().add_caveat(&example_first_party_caveat()); 149 | 150 | assert!(token.authenticate_without_verifying(&example_key()).is_ok(), "verifies with valid key"); 151 | assert!(token.authenticate_without_verifying(&invalid_key()).is_err(), "doesn't verify with invalid key"); 152 | } 153 | 154 | #[test] 155 | fn verifying_predicates() { 156 | let token = example_token() 157 | .add_caveat(&example_first_party_caveat()) 158 | .add_caveat(&example_first_party_caveat_different_prefix()); 159 | 160 | let matching_verifier = Func(verify_caveat); 161 | assert!(token.verify(&example_key(), &matching_verifier).is_ok()); 162 | assert!(token.verify(&invalid_key(), &matching_verifier).is_err()); 163 | 164 | let non_matching_verifier = Func(verify_wrong_value); 165 | assert!(token.verify(&example_key(), &non_matching_verifier).is_err()); 166 | assert!(token.verify(&invalid_key(), &non_matching_verifier).is_err()); 167 | 168 | let multiple_verifier = Func(verify_caveat).link(Func(verify_other)); 169 | assert!(token.verify(&example_key(), &multiple_verifier).is_ok()); 170 | } 171 | --------------------------------------------------------------------------------