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