1 extern crate aes_gcm; 2 3 use std::convert::TryInto; 4 use std::borrow::{Borrow, BorrowMut}; 5 6 use crate::secure::{base64, rand, Key}; 7 use crate::{Cookie, CookieJar}; 8 9 use self::aes_gcm::Aes256Gcm; 10 use self::aes_gcm::aead::{Aead, AeadInPlace, NewAead, generic_array::GenericArray, Payload}; 11 use self::rand::RngCore; 12 13 // Keep these in sync, and keep the key len synced with the `private` docs as 14 // well as the `KEYS_INFO` const in secure::Key. 15 pub(crate) const NONCE_LEN: usize = 12; 16 pub(crate) const TAG_LEN: usize = 16; 17 pub(crate) const KEY_LEN: usize = 32; 18 19 /// A child cookie jar that provides authenticated encryption for its cookies. 20 /// 21 /// A _private_ child jar signs and encrypts all the cookies added to it and 22 /// verifies and decrypts cookies retrieved from it. Any cookies stored in a 23 /// `PrivateJar` are simultaneously assured confidentiality, integrity, and 24 /// authenticity. In other words, clients cannot discover nor tamper with the 25 /// contents of a cookie, nor can they fabricate cookie data. 26 #[cfg_attr(all(nightly, doc), doc(cfg(feature = "private")))] 27 pub struct PrivateJar<J> { 28 parent: J, 29 key: [u8; KEY_LEN] 30 } 31 32 impl<J> PrivateJar<J> { 33 /// Creates a new child `PrivateJar` with parent `parent` and key `key`. 34 /// This method is typically called indirectly via the `signed` method of 35 /// `CookieJar`. new(parent: J, key: &Key) -> PrivateJar<J>36 pub(crate) fn new(parent: J, key: &Key) -> PrivateJar<J> { 37 PrivateJar { parent, key: key.encryption().try_into().expect("enc key len") } 38 } 39 40 /// Encrypts the cookie's value with authenticated encryption providing 41 /// confidentiality, integrity, and authenticity. encrypt_cookie(&self, cookie: &mut Cookie)42 fn encrypt_cookie(&self, cookie: &mut Cookie) { 43 // Create a vec to hold the [nonce | cookie value | tag]. 44 let cookie_val = cookie.value().as_bytes(); 45 let mut data = vec![0; NONCE_LEN + cookie_val.len() + TAG_LEN]; 46 47 // Split data into three: nonce, input/output, tag. Copy input. 48 let (nonce, in_out) = data.split_at_mut(NONCE_LEN); 49 let (in_out, tag) = in_out.split_at_mut(cookie_val.len()); 50 in_out.copy_from_slice(cookie_val); 51 52 // Fill nonce piece with random data. 53 let mut rng = self::rand::thread_rng(); 54 rng.try_fill_bytes(nonce).expect("couldn't random fill nonce"); 55 let nonce = GenericArray::clone_from_slice(nonce); 56 57 // Perform the actual sealing operation, using the cookie's name as 58 // associated data to prevent value swapping. 59 let aad = cookie.name().as_bytes(); 60 let aead = Aes256Gcm::new(GenericArray::from_slice(&self.key)); 61 let aad_tag = aead.encrypt_in_place_detached(&nonce, aad, in_out) 62 .expect("encryption failure!"); 63 64 // Copy the tag into the tag piece. 65 tag.copy_from_slice(&aad_tag); 66 67 // Base64 encode [nonce | encrypted value | tag]. 68 cookie.set_value(base64::encode(&data)); 69 } 70 71 /// Given a sealed value `str` and a key name `name`, where the nonce is 72 /// prepended to the original value and then both are Base64 encoded, 73 /// verifies and decrypts the sealed value and returns it. If there's a 74 /// problem, returns an `Err` with a string describing the issue. unseal(&self, name: &str, value: &str) -> Result<String, &'static str>75 fn unseal(&self, name: &str, value: &str) -> Result<String, &'static str> { 76 let data = base64::decode(value).map_err(|_| "bad base64 value")?; 77 if data.len() <= NONCE_LEN { 78 return Err("length of decoded data is <= NONCE_LEN"); 79 } 80 81 let (nonce, cipher) = data.split_at(NONCE_LEN); 82 let payload = Payload { msg: cipher, aad: name.as_bytes() }; 83 84 let aead = Aes256Gcm::new(GenericArray::from_slice(&self.key)); 85 aead.decrypt(GenericArray::from_slice(nonce), payload) 86 .map_err(|_| "invalid key/nonce/value: bad seal") 87 .and_then(|s| String::from_utf8(s).map_err(|_| "bad unsealed utf8")) 88 } 89 90 /// Authenticates and decrypts `cookie`, returning the plaintext version if 91 /// decryption succeeds or `None` otherwise. Authenticatation and decryption 92 /// _always_ succeeds if `cookie` was generated by a `PrivateJar` with the 93 /// same key as `self`. 94 /// 95 /// # Example 96 /// 97 /// ```rust 98 /// use cookie::{CookieJar, Cookie, Key}; 99 /// 100 /// let key = Key::generate(); 101 /// let mut jar = CookieJar::new(); 102 /// assert!(jar.private(&key).get("name").is_none()); 103 /// 104 /// jar.private_mut(&key).add(Cookie::new("name", "value")); 105 /// assert_eq!(jar.private(&key).get("name").unwrap().value(), "value"); 106 /// 107 /// let plain = jar.get("name").cloned().unwrap(); 108 /// assert_ne!(plain.value(), "value"); 109 /// let decrypted = jar.private(&key).decrypt(plain).unwrap(); 110 /// assert_eq!(decrypted.value(), "value"); 111 /// 112 /// let plain = Cookie::new("plaintext", "hello"); 113 /// assert!(jar.private(&key).decrypt(plain).is_none()); 114 /// ``` decrypt(&self, mut cookie: Cookie<'static>) -> Option<Cookie<'static>>115 pub fn decrypt(&self, mut cookie: Cookie<'static>) -> Option<Cookie<'static>> { 116 if let Ok(value) = self.unseal(cookie.name(), cookie.value()) { 117 cookie.set_value(value); 118 return Some(cookie); 119 } 120 121 None 122 } 123 } 124 125 impl<J: Borrow<CookieJar>> PrivateJar<J> { 126 /// Returns a reference to the `Cookie` inside this jar with the name `name` 127 /// and authenticates and decrypts the cookie's value, returning a `Cookie` 128 /// with the decrypted value. If the cookie cannot be found, or the cookie 129 /// fails to authenticate or decrypt, `None` is returned. 130 /// 131 /// # Example 132 /// 133 /// ```rust 134 /// use cookie::{CookieJar, Cookie, Key}; 135 /// 136 /// let key = Key::generate(); 137 /// let jar = CookieJar::new(); 138 /// assert!(jar.private(&key).get("name").is_none()); 139 /// 140 /// let mut jar = jar; 141 /// let mut private_jar = jar.private_mut(&key); 142 /// private_jar.add(Cookie::new("name", "value")); 143 /// assert_eq!(private_jar.get("name").unwrap().value(), "value"); 144 /// ``` get(&self, name: &str) -> Option<Cookie<'static>>145 pub fn get(&self, name: &str) -> Option<Cookie<'static>> { 146 self.parent.borrow().get(name).and_then(|c| self.decrypt(c.clone())) 147 } 148 } 149 150 impl<J: BorrowMut<CookieJar>> PrivateJar<J> { 151 /// Adds `cookie` to the parent jar. The cookie's value is encrypted with 152 /// authenticated encryption assuring confidentiality, integrity, and 153 /// authenticity. 154 /// 155 /// # Example 156 /// 157 /// ```rust 158 /// use cookie::{CookieJar, Cookie, Key}; 159 /// 160 /// let key = Key::generate(); 161 /// let mut jar = CookieJar::new(); 162 /// jar.private_mut(&key).add(Cookie::new("name", "value")); 163 /// 164 /// assert_ne!(jar.get("name").unwrap().value(), "value"); 165 /// assert_eq!(jar.private(&key).get("name").unwrap().value(), "value"); 166 /// ``` add(&mut self, mut cookie: Cookie<'static>)167 pub fn add(&mut self, mut cookie: Cookie<'static>) { 168 self.encrypt_cookie(&mut cookie); 169 self.parent.borrow_mut().add(cookie); 170 } 171 172 /// Adds an "original" `cookie` to parent jar. The cookie's value is 173 /// encrypted with authenticated encryption assuring confidentiality, 174 /// integrity, and authenticity. Adding an original cookie does not affect 175 /// the [`CookieJar::delta()`] computation. This method is intended to be 176 /// used to seed the cookie jar with cookies received from a client's HTTP 177 /// message. 178 /// 179 /// For accurate `delta` computations, this method should not be called 180 /// after calling `remove`. 181 /// 182 /// # Example 183 /// 184 /// ```rust 185 /// use cookie::{CookieJar, Cookie, Key}; 186 /// 187 /// let key = Key::generate(); 188 /// let mut jar = CookieJar::new(); 189 /// jar.private_mut(&key).add_original(Cookie::new("name", "value")); 190 /// 191 /// assert_eq!(jar.iter().count(), 1); 192 /// assert_eq!(jar.delta().count(), 0); 193 /// ``` add_original(&mut self, mut cookie: Cookie<'static>)194 pub fn add_original(&mut self, mut cookie: Cookie<'static>) { 195 self.encrypt_cookie(&mut cookie); 196 self.parent.borrow_mut().add_original(cookie); 197 } 198 199 /// Removes `cookie` from the parent jar. 200 /// 201 /// For correct removal, the passed in `cookie` must contain the same `path` 202 /// and `domain` as the cookie that was initially set. 203 /// 204 /// This is identical to [`CookieJar::remove()`]. See the method's 205 /// documentation for more details. 206 /// 207 /// # Example 208 /// 209 /// ```rust 210 /// use cookie::{CookieJar, Cookie, Key}; 211 /// 212 /// let key = Key::generate(); 213 /// let mut jar = CookieJar::new(); 214 /// let mut private_jar = jar.private_mut(&key); 215 /// 216 /// private_jar.add(Cookie::new("name", "value")); 217 /// assert!(private_jar.get("name").is_some()); 218 /// 219 /// private_jar.remove(Cookie::named("name")); 220 /// assert!(private_jar.get("name").is_none()); 221 /// ``` remove(&mut self, cookie: Cookie<'static>)222 pub fn remove(&mut self, cookie: Cookie<'static>) { 223 self.parent.borrow_mut().remove(cookie); 224 } 225 } 226 227 #[cfg(test)] 228 mod test { 229 use crate::{CookieJar, Cookie, Key}; 230 231 #[test] simple()232 fn simple() { 233 let key = Key::generate(); 234 let mut jar = CookieJar::new(); 235 assert_simple_behaviour!(jar, jar.private_mut(&key)); 236 } 237 238 #[test] secure()239 fn secure() { 240 let key = Key::generate(); 241 let mut jar = CookieJar::new(); 242 assert_secure_behaviour!(jar, jar.private_mut(&key)); 243 } 244 245 #[test] roundtrip()246 fn roundtrip() { 247 // Secret is SHA-256 hash of 'Super secret!' passed through HKDF-SHA256. 248 let key = Key::from(&[89, 202, 200, 125, 230, 90, 197, 245, 166, 249, 249 34, 169, 135, 31, 20, 197, 94, 154, 254, 79, 60, 26, 8, 143, 254, 250 24, 116, 138, 92, 225, 159, 60, 157, 41, 135, 129, 31, 226, 196, 16, 251 198, 168, 134, 4, 42, 1, 196, 24, 57, 103, 241, 147, 201, 185, 233, 252 10, 180, 170, 187, 89, 252, 137, 110, 107]); 253 254 let mut jar = CookieJar::new(); 255 jar.add(Cookie::new("encrypted_with_ring014", 256 "lObeZJorGVyeSWUA8khTO/8UCzFVBY9g0MGU6/J3NN1R5x11dn2JIA==")); 257 jar.add(Cookie::new("encrypted_with_ring016", 258 "SU1ujceILyMBg3fReqRmA9HUtAIoSPZceOM/CUpObROHEujXIjonkA==")); 259 260 let private = jar.private(&key); 261 assert_eq!(private.get("encrypted_with_ring014").unwrap().value(), "Tamper-proof"); 262 assert_eq!(private.get("encrypted_with_ring016").unwrap().value(), "Tamper-proof"); 263 } 264 } 265