1 use secure::ring::digest::{SHA256, Algorithm};
2 use secure::ring::hmac::{SigningKey, sign, verify_with_own_key as verify};
3 use secure::{base64, Key};
4 
5 use {Cookie, CookieJar};
6 
7 // Keep these in sync, and keep the key len synced with the `signed` docs as
8 // well as the `KEYS_INFO` const in secure::Key.
9 static HMAC_DIGEST: &'static Algorithm = &SHA256;
10 const BASE64_DIGEST_LEN: usize = 44;
11 pub const KEY_LEN: usize = 32;
12 
13 /// A child cookie jar that authenticates its cookies.
14 ///
15 /// A _signed_ child jar signs all the cookies added to it and verifies cookies
16 /// retrieved from it. Any cookies stored in a `SignedJar` are assured integrity
17 /// and authenticity. In other words, clients cannot tamper with the contents of
18 /// a cookie nor can they fabricate cookie values, but the data is visible in
19 /// plaintext.
20 ///
21 /// This type is only available when the `secure` feature is enabled.
22 pub struct SignedJar<'a> {
23     parent: &'a mut CookieJar,
24     key: SigningKey
25 }
26 
27 impl<'a> SignedJar<'a> {
28     /// Creates a new child `SignedJar` with parent `parent` and key `key`. This
29     /// method is typically called indirectly via the `signed` method of
30     /// `CookieJar`.
31     #[doc(hidden)]
new(parent: &'a mut CookieJar, key: &Key) -> SignedJar<'a>32     pub fn new(parent: &'a mut CookieJar, key: &Key) -> SignedJar<'a> {
33         SignedJar { parent: parent, key: SigningKey::new(HMAC_DIGEST, key.signing()) }
34     }
35 
36     /// Given a signed value `str` where the signature is prepended to `value`,
37     /// verifies the signed value and returns it. If there's a problem, returns
38     /// an `Err` with a string describing the issue.
verify(&self, cookie_value: &str) -> Result<String, &'static str>39     fn verify(&self, cookie_value: &str) -> Result<String, &'static str> {
40         if cookie_value.len() < BASE64_DIGEST_LEN {
41             return Err("length of value is <= BASE64_DIGEST_LEN");
42         }
43 
44         let (digest_str, value) = cookie_value.split_at(BASE64_DIGEST_LEN);
45         let sig = base64::decode(digest_str).map_err(|_| "bad base64 digest")?;
46 
47         verify(&self.key, value.as_bytes(), &sig)
48             .map(|_| value.to_string())
49             .map_err(|_| "value did not verify")
50     }
51 
52     /// Returns a reference to the `Cookie` inside this jar with the name `name`
53     /// and verifies the authenticity and integrity of the cookie's value,
54     /// returning a `Cookie` with the authenticated value. If the cookie cannot
55     /// be found, or the cookie fails to verify, `None` is returned.
56     ///
57     /// # Example
58     ///
59     /// ```rust
60     /// use cookie::{CookieJar, Cookie, Key};
61     ///
62     /// let key = Key::generate();
63     /// let mut jar = CookieJar::new();
64     /// let mut signed_jar = jar.signed(&key);
65     /// assert!(signed_jar.get("name").is_none());
66     ///
67     /// signed_jar.add(Cookie::new("name", "value"));
68     /// assert_eq!(signed_jar.get("name").unwrap().value(), "value");
69     /// ```
get(&self, name: &str) -> Option<Cookie<'static>>70     pub fn get(&self, name: &str) -> Option<Cookie<'static>> {
71         if let Some(cookie_ref) = self.parent.get(name) {
72             let mut cookie = cookie_ref.clone();
73             if let Ok(value) = self.verify(cookie.value()) {
74                 cookie.set_value(value);
75                 return Some(cookie);
76             }
77         }
78 
79         None
80     }
81 
82     /// Adds `cookie` to the parent jar. The cookie's value is signed assuring
83     /// integrity and authenticity.
84     ///
85     /// # Example
86     ///
87     /// ```rust
88     /// use cookie::{CookieJar, Cookie, Key};
89     ///
90     /// let key = Key::generate();
91     /// let mut jar = CookieJar::new();
92     /// jar.signed(&key).add(Cookie::new("name", "value"));
93     ///
94     /// assert_ne!(jar.get("name").unwrap().value(), "value");
95     /// assert!(jar.get("name").unwrap().value().contains("value"));
96     /// assert_eq!(jar.signed(&key).get("name").unwrap().value(), "value");
97     /// ```
add(&mut self, mut cookie: Cookie<'static>)98     pub fn add(&mut self, mut cookie: Cookie<'static>) {
99         self.sign_cookie(&mut cookie);
100         self.parent.add(cookie);
101     }
102 
103     /// Adds an "original" `cookie` to this jar. The cookie's value is signed
104     /// assuring integrity and authenticity. Adding an original cookie does not
105     /// affect the [`CookieJar::delta()`](struct.CookieJar.html#method.delta)
106     /// computation. This method is intended to be used to seed the cookie jar
107     /// with cookies received from a client's HTTP message.
108     ///
109     /// For accurate `delta` computations, this method should not be called
110     /// after calling `remove`.
111     ///
112     /// # Example
113     ///
114     /// ```rust
115     /// use cookie::{CookieJar, Cookie, Key};
116     ///
117     /// let key = Key::generate();
118     /// let mut jar = CookieJar::new();
119     /// jar.signed(&key).add_original(Cookie::new("name", "value"));
120     ///
121     /// assert_eq!(jar.iter().count(), 1);
122     /// assert_eq!(jar.delta().count(), 0);
123     /// ```
add_original(&mut self, mut cookie: Cookie<'static>)124     pub fn add_original(&mut self, mut cookie: Cookie<'static>) {
125         self.sign_cookie(&mut cookie);
126         self.parent.add_original(cookie);
127     }
128 
129     /// Signs the cookie's value assuring integrity and authenticity.
sign_cookie(&self, cookie: &mut Cookie)130     fn sign_cookie(&self, cookie: &mut Cookie) {
131         let digest = sign(&self.key, cookie.value().as_bytes());
132         let mut new_value = base64::encode(digest.as_ref());
133         new_value.push_str(cookie.value());
134         cookie.set_value(new_value);
135     }
136 
137     /// Removes `cookie` from the parent jar.
138     ///
139     /// For correct removal, the passed in `cookie` must contain the same `path`
140     /// and `domain` as the cookie that was initially set.
141     ///
142     /// See [CookieJar::remove](struct.CookieJar.html#method.remove) for more
143     /// details.
144     ///
145     /// # Example
146     ///
147     /// ```rust
148     /// use cookie::{CookieJar, Cookie, Key};
149     ///
150     /// let key = Key::generate();
151     /// let mut jar = CookieJar::new();
152     /// let mut signed_jar = jar.signed(&key);
153     ///
154     /// signed_jar.add(Cookie::new("name", "value"));
155     /// assert!(signed_jar.get("name").is_some());
156     ///
157     /// signed_jar.remove(Cookie::named("name"));
158     /// assert!(signed_jar.get("name").is_none());
159     /// ```
remove(&mut self, cookie: Cookie<'static>)160     pub fn remove(&mut self, cookie: Cookie<'static>) {
161         self.parent.remove(cookie);
162     }
163 }
164 
165 #[cfg(test)]
166 mod test {
167     use {CookieJar, Cookie, Key};
168 
169     #[test]
simple()170     fn simple() {
171         let key = Key::generate();
172         let mut jar = CookieJar::new();
173         assert_simple_behaviour!(jar, jar.signed(&key));
174     }
175 
176     #[test]
private()177     fn private() {
178         let key = Key::generate();
179         let mut jar = CookieJar::new();
180         assert_secure_behaviour!(jar, jar.signed(&key));
181     }
182 }
183