cookie/secure/private.rs
1extern crate aes_gcm;
2
3use std::convert::TryInto;
4use std::borrow::{Borrow, BorrowMut};
5
6use crate::secure::{base64, rand, Key};
7use crate::{Cookie, CookieJar};
8
9use self::aes_gcm::aead::{generic_array::GenericArray, Aead, AeadInPlace, KeyInit, Payload};
10use self::aes_gcm::Aes256Gcm;
11use 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.
15pub(crate) const NONCE_LEN: usize = 12;
16pub(crate) const TAG_LEN: usize = 16;
17pub(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")))]
27pub struct PrivateJar<J> {
28 parent: J,
29 key: [u8; KEY_LEN]
30}
31
32impl<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`.
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.
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.
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 /// ```
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
125impl<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 /// ```
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
150impl<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 /// ```
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 /// ```
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 /// ```
222 pub fn remove(&mut self, cookie: Cookie<'static>) {
223 self.parent.borrow_mut().remove(cookie);
224 }
225}
226
227#[cfg(test)]
228mod test {
229 use crate::{CookieJar, Cookie, Key};
230
231 #[test]
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]
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]
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}