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