summaryrefslogtreecommitdiffstats
path: root/third_party/rust/cookie/src/secure/signed.rs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rust/cookie/src/secure/signed.rs
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/cookie/src/secure/signed.rs')
-rw-r--r--third_party/rust/cookie/src/secure/signed.rs251
1 files changed, 251 insertions, 0 deletions
diff --git a/third_party/rust/cookie/src/secure/signed.rs b/third_party/rust/cookie/src/secure/signed.rs
new file mode 100644
index 0000000000..b46fae4e80
--- /dev/null
+++ b/third_party/rust/cookie/src/secure/signed.rs
@@ -0,0 +1,251 @@
+use std::convert::TryInto;
+use std::borrow::{Borrow, BorrowMut};
+
+use sha2::Sha256;
+use hmac::{Hmac, Mac};
+
+use crate::secure::{base64, Key};
+use crate::{Cookie, CookieJar};
+
+// Keep these in sync, and keep the key len synced with the `signed` docs as
+// well as the `KEYS_INFO` const in secure::Key.
+pub(crate) const BASE64_DIGEST_LEN: usize = 44;
+pub(crate) const KEY_LEN: usize = 32;
+
+/// A child cookie jar that authenticates its cookies.
+///
+/// A _signed_ child jar signs all the cookies added to it and verifies cookies
+/// retrieved from it. Any cookies stored in a `SignedJar` are provided
+/// integrity and authenticity. In other words, clients cannot tamper with the
+/// contents of a cookie nor can they fabricate cookie values, but the data is
+/// visible in plaintext.
+#[cfg_attr(all(nightly, doc), doc(cfg(feature = "signed")))]
+pub struct SignedJar<J> {
+ parent: J,
+ key: [u8; KEY_LEN],
+}
+
+impl<J> SignedJar<J> {
+ /// Creates a new child `SignedJar` with parent `parent` and key `key`. This
+ /// method is typically called indirectly via the `signed{_mut}` methods of
+ /// `CookieJar`.
+ pub(crate) fn new(parent: J, key: &Key) -> SignedJar<J> {
+ SignedJar { parent, key: key.signing().try_into().expect("sign key len") }
+ }
+
+ /// Signs the cookie's value providing integrity and authenticity.
+ fn sign_cookie(&self, cookie: &mut Cookie) {
+ // Compute HMAC-SHA256 of the cookie's value.
+ let mut mac = Hmac::<Sha256>::new_from_slice(&self.key).expect("good key");
+ mac.update(cookie.value().as_bytes());
+
+ // Cookie's new value is [MAC | original-value].
+ let mut new_value = base64::encode(&mac.finalize().into_bytes());
+ new_value.push_str(cookie.value());
+ cookie.set_value(new_value);
+ }
+
+ /// Given a signed value `str` where the signature is prepended to `value`,
+ /// verifies the signed value and returns it. If there's a problem, returns
+ /// an `Err` with a string describing the issue.
+ fn _verify(&self, cookie_value: &str) -> Result<String, &'static str> {
+ if !cookie_value.is_char_boundary(BASE64_DIGEST_LEN) {
+ return Err("missing or invalid digest");
+ }
+
+ // Split [MAC | original-value] into its two parts.
+ let (digest_str, value) = cookie_value.split_at(BASE64_DIGEST_LEN);
+ let digest = base64::decode(digest_str).map_err(|_| "bad base64 digest")?;
+
+ // Perform the verification.
+ let mut mac = Hmac::<Sha256>::new_from_slice(&self.key).expect("good key");
+ mac.update(value.as_bytes());
+ mac.verify_slice(&digest)
+ .map(|_| value.to_string())
+ .map_err(|_| "value did not verify")
+ }
+
+ /// Verifies the authenticity and integrity of `cookie`, returning the
+ /// plaintext version if verification succeeds or `None` otherwise.
+ /// Verification _always_ succeeds if `cookie` was generated by a
+ /// `SignedJar` with the same key as `self`.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::{CookieJar, Cookie, Key};
+ ///
+ /// let key = Key::generate();
+ /// let mut jar = CookieJar::new();
+ /// assert!(jar.signed(&key).get("name").is_none());
+ ///
+ /// jar.signed_mut(&key).add(Cookie::new("name", "value"));
+ /// assert_eq!(jar.signed(&key).get("name").unwrap().value(), "value");
+ ///
+ /// let plain = jar.get("name").cloned().unwrap();
+ /// assert_ne!(plain.value(), "value");
+ /// let verified = jar.signed(&key).verify(plain).unwrap();
+ /// assert_eq!(verified.value(), "value");
+ ///
+ /// let plain = Cookie::new("plaintext", "hello");
+ /// assert!(jar.signed(&key).verify(plain).is_none());
+ /// ```
+ pub fn verify(&self, mut cookie: Cookie<'static>) -> Option<Cookie<'static>> {
+ if let Ok(value) = self._verify(cookie.value()) {
+ cookie.set_value(value);
+ return Some(cookie);
+ }
+
+ None
+ }
+}
+
+impl<J: Borrow<CookieJar>> SignedJar<J> {
+ /// Returns a reference to the `Cookie` inside this jar with the name `name`
+ /// and verifies the authenticity and integrity of the cookie's value,
+ /// returning a `Cookie` with the authenticated value. If the cookie cannot
+ /// be found, or the cookie fails to verify, `None` is returned.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::{CookieJar, Cookie, Key};
+ ///
+ /// let key = Key::generate();
+ /// let jar = CookieJar::new();
+ /// assert!(jar.signed(&key).get("name").is_none());
+ ///
+ /// let mut jar = jar;
+ /// let mut signed_jar = jar.signed_mut(&key);
+ /// signed_jar.add(Cookie::new("name", "value"));
+ /// assert_eq!(signed_jar.get("name").unwrap().value(), "value");
+ /// ```
+ pub fn get(&self, name: &str) -> Option<Cookie<'static>> {
+ self.parent.borrow().get(name).and_then(|c| self.verify(c.clone()))
+ }
+}
+
+impl<J: BorrowMut<CookieJar>> SignedJar<J> {
+ /// Adds `cookie` to the parent jar. The cookie's value is signed assuring
+ /// integrity and authenticity.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::{CookieJar, Cookie, Key};
+ ///
+ /// let key = Key::generate();
+ /// let mut jar = CookieJar::new();
+ /// jar.signed_mut(&key).add(Cookie::new("name", "value"));
+ ///
+ /// assert_ne!(jar.get("name").unwrap().value(), "value");
+ /// assert!(jar.get("name").unwrap().value().contains("value"));
+ /// assert_eq!(jar.signed(&key).get("name").unwrap().value(), "value");
+ /// ```
+ pub fn add(&mut self, mut cookie: Cookie<'static>) {
+ self.sign_cookie(&mut cookie);
+ self.parent.borrow_mut().add(cookie);
+ }
+
+ /// Adds an "original" `cookie` to this jar. The cookie's value is signed
+ /// assuring integrity and authenticity. Adding an original cookie does not
+ /// affect the [`CookieJar::delta()`] computation. This method is intended
+ /// to be used to seed the cookie jar with cookies received from a client's
+ /// HTTP message.
+ ///
+ /// For accurate `delta` computations, this method should not be called
+ /// after calling `remove`.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::{CookieJar, Cookie, Key};
+ ///
+ /// let key = Key::generate();
+ /// let mut jar = CookieJar::new();
+ /// jar.signed_mut(&key).add_original(Cookie::new("name", "value"));
+ ///
+ /// assert_eq!(jar.iter().count(), 1);
+ /// assert_eq!(jar.delta().count(), 0);
+ /// ```
+ pub fn add_original(&mut self, mut cookie: Cookie<'static>) {
+ self.sign_cookie(&mut cookie);
+ self.parent.borrow_mut().add_original(cookie);
+ }
+
+ /// Removes `cookie` from the parent jar.
+ ///
+ /// For correct removal, the passed in `cookie` must contain the same `path`
+ /// and `domain` as the cookie that was initially set.
+ ///
+ /// This is identical to [`CookieJar::remove()`]. See the method's
+ /// documentation for more details.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::{CookieJar, Cookie, Key};
+ ///
+ /// let key = Key::generate();
+ /// let mut jar = CookieJar::new();
+ /// let mut signed_jar = jar.signed_mut(&key);
+ ///
+ /// signed_jar.add(Cookie::new("name", "value"));
+ /// assert!(signed_jar.get("name").is_some());
+ ///
+ /// signed_jar.remove(Cookie::named("name"));
+ /// assert!(signed_jar.get("name").is_none());
+ /// ```
+ pub fn remove(&mut self, cookie: Cookie<'static>) {
+ self.parent.borrow_mut().remove(cookie);
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use crate::{CookieJar, Cookie, Key};
+
+ #[test]
+ fn simple() {
+ let key = Key::generate();
+ let mut jar = CookieJar::new();
+ assert_simple_behaviour!(jar, jar.signed_mut(&key));
+ }
+
+ #[test]
+ fn private() {
+ let key = Key::generate();
+ let mut jar = CookieJar::new();
+ assert_secure_behaviour!(jar, jar.signed_mut(&key));
+ }
+
+ #[test]
+ fn roundtrip() {
+ // Secret is SHA-256 hash of 'Super secret!' passed through HKDF-SHA256.
+ let key = Key::from(&[89, 202, 200, 125, 230, 90, 197, 245, 166, 249,
+ 34, 169, 135, 31, 20, 197, 94, 154, 254, 79, 60, 26, 8, 143, 254,
+ 24, 116, 138, 92, 225, 159, 60, 157, 41, 135, 129, 31, 226, 196, 16,
+ 198, 168, 134, 4, 42, 1, 196, 24, 57, 103, 241, 147, 201, 185, 233,
+ 10, 180, 170, 187, 89, 252, 137, 110, 107]);
+
+ let mut jar = CookieJar::new();
+ jar.add(Cookie::new("signed_with_ring014",
+ "3tdHXEQ2kf6fxC7dWzBGmpSLMtJenXLKrZ9cHkSsl1w=Tamper-proof"));
+ jar.add(Cookie::new("signed_with_ring016",
+ "3tdHXEQ2kf6fxC7dWzBGmpSLMtJenXLKrZ9cHkSsl1w=Tamper-proof"));
+
+ let signed = jar.signed(&key);
+ assert_eq!(signed.get("signed_with_ring014").unwrap().value(), "Tamper-proof");
+ assert_eq!(signed.get("signed_with_ring016").unwrap().value(), "Tamper-proof");
+ }
+
+ #[test]
+ fn issue_178() {
+ let data = "x=yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy£";
+ let c = Cookie::parse(data).expect("failed to parse cookie");
+ let key = Key::from(&[0u8; 64]);
+ let mut jar = CookieJar::new();
+ let signed = jar.signed_mut(&key);
+ assert!(signed.verify(c).is_none());
+ }
+}