summaryrefslogtreecommitdiffstats
path: root/third_party/rust/cookie/src/secure
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /third_party/rust/cookie/src/secure
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/cookie/src/secure')
-rw-r--r--third_party/rust/cookie/src/secure/key.rs301
-rw-r--r--third_party/rust/cookie/src/secure/macros.rs49
-rw-r--r--third_party/rust/cookie/src/secure/mod.rs14
-rw-r--r--third_party/rust/cookie/src/secure/private.rs264
-rw-r--r--third_party/rust/cookie/src/secure/signed.rs251
5 files changed, 879 insertions, 0 deletions
diff --git a/third_party/rust/cookie/src/secure/key.rs b/third_party/rust/cookie/src/secure/key.rs
new file mode 100644
index 0000000000..9c2228eb3a
--- /dev/null
+++ b/third_party/rust/cookie/src/secure/key.rs
@@ -0,0 +1,301 @@
+use std::convert::TryFrom;
+
+const SIGNING_KEY_LEN: usize = 32;
+const ENCRYPTION_KEY_LEN: usize = 32;
+const COMBINED_KEY_LENGTH: usize = SIGNING_KEY_LEN + ENCRYPTION_KEY_LEN;
+
+// Statically ensure the numbers above are in-sync.
+#[cfg(feature = "signed")]
+const_assert!(crate::secure::signed::KEY_LEN == SIGNING_KEY_LEN);
+#[cfg(feature = "private")]
+const_assert!(crate::secure::private::KEY_LEN == ENCRYPTION_KEY_LEN);
+
+/// A cryptographic master key for use with `Signed` and/or `Private` jars.
+///
+/// This structure encapsulates secure, cryptographic keys for use with both
+/// [`PrivateJar`](crate::PrivateJar) and [`SignedJar`](crate::SignedJar). A
+/// single instance of a `Key` can be used for both a `PrivateJar` and a
+/// `SignedJar` simultaneously with no notable security implications.
+#[cfg_attr(all(nightly, doc), doc(cfg(any(feature = "private", feature = "signed"))))]
+#[derive(Clone)]
+pub struct Key([u8; COMBINED_KEY_LENGTH /* SIGNING | ENCRYPTION */]);
+
+impl PartialEq for Key {
+ fn eq(&self, other: &Self) -> bool {
+ use subtle::ConstantTimeEq;
+
+ self.0.ct_eq(&other.0).into()
+ }
+}
+
+impl Key {
+ // An empty key structure, to be filled.
+ const fn zero() -> Self {
+ Key([0; COMBINED_KEY_LENGTH])
+ }
+
+ /// Creates a new `Key` from a 512-bit cryptographically random string.
+ ///
+ /// The supplied key must be at least 512-bits (64 bytes). For security, the
+ /// master key _must_ be cryptographically random.
+ ///
+ /// # Panics
+ ///
+ /// Panics if `key` is less than 64 bytes in length.
+ ///
+ /// For a non-panicking version, use [`Key::try_from()`] or generate a key with
+ /// [`Key::generate()`] or [`Key::try_generate()`].
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::Key;
+ ///
+ /// # /*
+ /// let key = { /* a cryptographically random key >= 64 bytes */ };
+ /// # */
+ /// # let key: &Vec<u8> = &(0..64).collect();
+ ///
+ /// let key = Key::from(key);
+ /// ```
+ #[inline]
+ pub fn from(key: &[u8]) -> Key {
+ Key::try_from(key).unwrap()
+ }
+
+ /// Derives new signing/encryption keys from a master key.
+ ///
+ /// The master key must be at least 256-bits (32 bytes). For security, the
+ /// master key _must_ be cryptographically random. The keys are derived
+ /// deterministically from the master key.
+ ///
+ /// # Panics
+ ///
+ /// Panics if `key` is less than 32 bytes in length.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::Key;
+ ///
+ /// # /*
+ /// let master_key = { /* a cryptographically random key >= 32 bytes */ };
+ /// # */
+ /// # let master_key: &Vec<u8> = &(0..32).collect();
+ ///
+ /// let key = Key::derive_from(master_key);
+ /// ```
+ #[cfg(feature = "key-expansion")]
+ #[cfg_attr(all(nightly, doc), doc(cfg(feature = "key-expansion")))]
+ pub fn derive_from(master_key: &[u8]) -> Self {
+ if master_key.len() < 32 {
+ panic!("bad master key length: expected >= 32 bytes, found {}", master_key.len());
+ }
+
+ // Expand the master key into two HKDF generated keys.
+ const KEYS_INFO: &[u8] = b"COOKIE;SIGNED:HMAC-SHA256;PRIVATE:AEAD-AES-256-GCM";
+ let mut both_keys = [0; COMBINED_KEY_LENGTH];
+ let hk = hkdf::Hkdf::<sha2::Sha256>::from_prk(master_key).expect("key length prechecked");
+ hk.expand(KEYS_INFO, &mut both_keys).expect("expand into keys");
+ Key::from(&both_keys)
+ }
+
+ /// Generates signing/encryption keys from a secure, random source. Keys are
+ /// generated nondeterministically.
+ ///
+ /// # Panics
+ ///
+ /// Panics if randomness cannot be retrieved from the operating system. See
+ /// [`Key::try_generate()`] for a non-panicking version.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::Key;
+ ///
+ /// let key = Key::generate();
+ /// ```
+ pub fn generate() -> Key {
+ Self::try_generate().expect("failed to generate `Key` from randomness")
+ }
+
+ /// Attempts to generate signing/encryption keys from a secure, random
+ /// source. Keys are generated nondeterministically. If randomness cannot be
+ /// retrieved from the underlying operating system, returns `None`.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::Key;
+ ///
+ /// let key = Key::try_generate();
+ /// ```
+ pub fn try_generate() -> Option<Key> {
+ use crate::secure::rand::RngCore;
+
+ let mut rng = crate::secure::rand::thread_rng();
+ let mut key = Key::zero();
+ rng.try_fill_bytes(&mut key.0).ok()?;
+ Some(key)
+ }
+
+ /// Returns the raw bytes of a key suitable for signing cookies. Guaranteed
+ /// to be at least 32 bytes.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::Key;
+ ///
+ /// let key = Key::generate();
+ /// let signing_key = key.signing();
+ /// ```
+ pub fn signing(&self) -> &[u8] {
+ &self.0[..SIGNING_KEY_LEN]
+ }
+
+ /// Returns the raw bytes of a key suitable for encrypting cookies.
+ /// Guaranteed to be at least 32 bytes.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::Key;
+ ///
+ /// let key = Key::generate();
+ /// let encryption_key = key.encryption();
+ /// ```
+ pub fn encryption(&self) -> &[u8] {
+ &self.0[SIGNING_KEY_LEN..]
+ }
+
+ /// Returns the raw bytes of the master key. Guaranteed to be at least 64
+ /// bytes.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::Key;
+ ///
+ /// let key = Key::generate();
+ /// let master_key = key.master();
+ /// ```
+ pub fn master(&self) -> &[u8] {
+ &self.0
+ }
+}
+
+/// An error indicating an issue with generating or constructing a key.
+#[cfg_attr(all(nightly, doc), doc(cfg(any(feature = "private", feature = "signed"))))]
+#[derive(Debug)]
+#[non_exhaustive]
+pub enum KeyError {
+ /// Too few bytes (`.0`) were provided to generate a key.
+ ///
+ /// See [`Key::from()`] for minimum requirements.
+ TooShort(usize),
+}
+
+impl std::error::Error for KeyError { }
+
+impl std::fmt::Display for KeyError {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ KeyError::TooShort(n) => {
+ write!(f, "key material is too short: expected >= {} bytes, got {} bytes",
+ COMBINED_KEY_LENGTH, n)
+ }
+ }
+ }
+}
+
+impl TryFrom<&[u8]> for Key {
+ type Error = KeyError;
+
+ /// A fallible version of [`Key::from()`].
+ ///
+ /// Succeeds when [`Key::from()`] succeds and returns an error where
+ /// [`Key::from()`] panics, namely, if `key` is too short.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// # use std::convert::TryFrom;
+ /// use cookie::Key;
+ ///
+ /// # /*
+ /// let key = { /* a cryptographically random key >= 64 bytes */ };
+ /// # */
+ /// # let key: &Vec<u8> = &(0..64).collect();
+ /// # let key: &[u8] = &key[..];
+ /// assert!(Key::try_from(key).is_ok());
+ ///
+ /// // A key that's far too short to use.
+ /// let key = &[1, 2, 3, 4][..];
+ /// assert!(Key::try_from(key).is_err());
+ /// ```
+ fn try_from(key: &[u8]) -> Result<Self, Self::Error> {
+ if key.len() < COMBINED_KEY_LENGTH {
+ Err(KeyError::TooShort(key.len()))
+ } else {
+ let mut output = Key::zero();
+ output.0.copy_from_slice(&key[..COMBINED_KEY_LENGTH]);
+ Ok(output)
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::Key;
+
+ #[test]
+ fn from_works() {
+ let key = Key::from(&(0..64).collect::<Vec<_>>());
+
+ let signing: Vec<u8> = (0..32).collect();
+ assert_eq!(key.signing(), &*signing);
+
+ let encryption: Vec<u8> = (32..64).collect();
+ assert_eq!(key.encryption(), &*encryption);
+ }
+
+ #[test]
+ fn try_from_works() {
+ use core::convert::TryInto;
+ let data = (0..64).collect::<Vec<_>>();
+ let key_res: Result<Key, _> = data[0..63].try_into();
+ assert!(key_res.is_err());
+
+ let key_res: Result<Key, _> = data.as_slice().try_into();
+ assert!(key_res.is_ok());
+ }
+
+ #[test]
+ #[cfg(feature = "key-expansion")]
+ fn deterministic_derive() {
+ let master_key: Vec<u8> = (0..32).collect();
+
+ let key_a = Key::derive_from(&master_key);
+ let key_b = Key::derive_from(&master_key);
+
+ assert_eq!(key_a.signing(), key_b.signing());
+ assert_eq!(key_a.encryption(), key_b.encryption());
+ assert_ne!(key_a.encryption(), key_a.signing());
+
+ let master_key_2: Vec<u8> = (32..64).collect();
+ let key_2 = Key::derive_from(&master_key_2);
+
+ assert_ne!(key_2.signing(), key_a.signing());
+ assert_ne!(key_2.encryption(), key_a.encryption());
+ }
+
+ #[test]
+ fn non_deterministic_generate() {
+ let key_a = Key::generate();
+ let key_b = Key::generate();
+
+ assert_ne!(key_a.signing(), key_b.signing());
+ assert_ne!(key_a.encryption(), key_b.encryption());
+ }
+}
diff --git a/third_party/rust/cookie/src/secure/macros.rs b/third_party/rust/cookie/src/secure/macros.rs
new file mode 100644
index 0000000000..dfbddcf928
--- /dev/null
+++ b/third_party/rust/cookie/src/secure/macros.rs
@@ -0,0 +1,49 @@
+#[cfg(test)]
+macro_rules! assert_simple_behaviour {
+ ($clear:expr, $secure:expr) => ({
+ assert_eq!($clear.iter().count(), 0);
+
+ $secure.add(Cookie::new("name", "val"));
+ assert_eq!($clear.iter().count(), 1);
+ assert_eq!($secure.get("name").unwrap().value(), "val");
+ assert_ne!($clear.get("name").unwrap().value(), "val");
+
+ $secure.add(Cookie::new("another", "two"));
+ assert_eq!($clear.iter().count(), 2);
+
+ $clear.remove(Cookie::named("another"));
+ assert_eq!($clear.iter().count(), 1);
+
+ $secure.remove(Cookie::named("name"));
+ assert_eq!($clear.iter().count(), 0);
+ })
+}
+
+#[cfg(test)]
+macro_rules! assert_secure_behaviour {
+ ($clear:expr, $secure:expr) => ({
+ $secure.add(Cookie::new("secure", "secure"));
+ assert!($clear.get("secure").unwrap().value() != "secure");
+ assert!($secure.get("secure").unwrap().value() == "secure");
+
+ let mut cookie = $clear.get("secure").unwrap().clone();
+ let new_val = format!("{}l", cookie.value());
+ cookie.set_value(new_val);
+ $clear.add(cookie);
+ assert!($secure.get("secure").is_none());
+
+ let mut cookie = $clear.get("secure").unwrap().clone();
+ cookie.set_value("foobar");
+ $clear.add(cookie);
+ assert!($secure.get("secure").is_none());
+ })
+}
+
+// This is courtesty of `static_assertions`. That library is Copyright (c) 2017
+// Nikolai Vazquez. See https://github.com/nvzqz/static-assertions-rs for more.
+macro_rules! const_assert {
+ ($x:expr $(,)?) => {
+ #[allow(unknown_lints, clippy::eq_op)]
+ const _: [(); 0 - !{ const ASSERT: bool = $x; ASSERT } as usize] = [];
+ };
+}
diff --git a/third_party/rust/cookie/src/secure/mod.rs b/third_party/rust/cookie/src/secure/mod.rs
new file mode 100644
index 0000000000..ca066888e7
--- /dev/null
+++ b/third_party/rust/cookie/src/secure/mod.rs
@@ -0,0 +1,14 @@
+extern crate rand;
+extern crate base64;
+
+#[macro_use]
+mod macros;
+mod key;
+
+pub use self::key::*;
+
+#[cfg(feature = "private")] mod private;
+#[cfg(feature = "private")] pub use self::private::*;
+
+#[cfg(feature = "signed")] mod signed;
+#[cfg(feature = "signed")] pub use self::signed::*;
diff --git a/third_party/rust/cookie/src/secure/private.rs b/third_party/rust/cookie/src/secure/private.rs
new file mode 100644
index 0000000000..1264c3983c
--- /dev/null
+++ b/third_party/rust/cookie/src/secure/private.rs
@@ -0,0 +1,264 @@
+extern crate aes_gcm;
+
+use std::convert::TryInto;
+use std::borrow::{Borrow, BorrowMut};
+
+use crate::secure::{base64, rand, Key};
+use crate::{Cookie, CookieJar};
+
+use self::aes_gcm::aead::{generic_array::GenericArray, Aead, AeadInPlace, KeyInit, Payload};
+use self::aes_gcm::Aes256Gcm;
+use self::rand::RngCore;
+
+// Keep these in sync, and keep the key len synced with the `private` docs as
+// well as the `KEYS_INFO` const in secure::Key.
+pub(crate) const NONCE_LEN: usize = 12;
+pub(crate) const TAG_LEN: usize = 16;
+pub(crate) const KEY_LEN: usize = 32;
+
+/// A child cookie jar that provides authenticated encryption for its cookies.
+///
+/// A _private_ child jar signs and encrypts all the cookies added to it and
+/// verifies and decrypts cookies retrieved from it. Any cookies stored in a
+/// `PrivateJar` are simultaneously assured confidentiality, integrity, and
+/// authenticity. In other words, clients cannot discover nor tamper with the
+/// contents of a cookie, nor can they fabricate cookie data.
+#[cfg_attr(all(nightly, doc), doc(cfg(feature = "private")))]
+pub struct PrivateJar<J> {
+ parent: J,
+ key: [u8; KEY_LEN]
+}
+
+impl<J> PrivateJar<J> {
+ /// Creates a new child `PrivateJar` with parent `parent` and key `key`.
+ /// This method is typically called indirectly via the `signed` method of
+ /// `CookieJar`.
+ pub(crate) fn new(parent: J, key: &Key) -> PrivateJar<J> {
+ PrivateJar { parent, key: key.encryption().try_into().expect("enc key len") }
+ }
+
+ /// Encrypts the cookie's value with authenticated encryption providing
+ /// confidentiality, integrity, and authenticity.
+ fn encrypt_cookie(&self, cookie: &mut Cookie) {
+ // Create a vec to hold the [nonce | cookie value | tag].
+ let cookie_val = cookie.value().as_bytes();
+ let mut data = vec![0; NONCE_LEN + cookie_val.len() + TAG_LEN];
+
+ // Split data into three: nonce, input/output, tag. Copy input.
+ let (nonce, in_out) = data.split_at_mut(NONCE_LEN);
+ let (in_out, tag) = in_out.split_at_mut(cookie_val.len());
+ in_out.copy_from_slice(cookie_val);
+
+ // Fill nonce piece with random data.
+ let mut rng = self::rand::thread_rng();
+ rng.try_fill_bytes(nonce).expect("couldn't random fill nonce");
+ let nonce = GenericArray::clone_from_slice(nonce);
+
+ // Perform the actual sealing operation, using the cookie's name as
+ // associated data to prevent value swapping.
+ let aad = cookie.name().as_bytes();
+ let aead = Aes256Gcm::new(GenericArray::from_slice(&self.key));
+ let aad_tag = aead.encrypt_in_place_detached(&nonce, aad, in_out)
+ .expect("encryption failure!");
+
+ // Copy the tag into the tag piece.
+ tag.copy_from_slice(&aad_tag);
+
+ // Base64 encode [nonce | encrypted value | tag].
+ cookie.set_value(base64::encode(&data));
+ }
+
+ /// Given a sealed value `str` and a key name `name`, where the nonce is
+ /// prepended to the original value and then both are Base64 encoded,
+ /// verifies and decrypts the sealed value and returns it. If there's a
+ /// problem, returns an `Err` with a string describing the issue.
+ fn unseal(&self, name: &str, value: &str) -> Result<String, &'static str> {
+ let data = base64::decode(value).map_err(|_| "bad base64 value")?;
+ if data.len() <= NONCE_LEN {
+ return Err("length of decoded data is <= NONCE_LEN");
+ }
+
+ let (nonce, cipher) = data.split_at(NONCE_LEN);
+ let payload = Payload { msg: cipher, aad: name.as_bytes() };
+
+ let aead = Aes256Gcm::new(GenericArray::from_slice(&self.key));
+ aead.decrypt(GenericArray::from_slice(nonce), payload)
+ .map_err(|_| "invalid key/nonce/value: bad seal")
+ .and_then(|s| String::from_utf8(s).map_err(|_| "bad unsealed utf8"))
+ }
+
+ /// Authenticates and decrypts `cookie`, returning the plaintext version if
+ /// decryption succeeds or `None` otherwise. Authenticatation and decryption
+ /// _always_ succeeds if `cookie` was generated by a `PrivateJar` with the
+ /// same key as `self`.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::{CookieJar, Cookie, Key};
+ ///
+ /// let key = Key::generate();
+ /// let mut jar = CookieJar::new();
+ /// assert!(jar.private(&key).get("name").is_none());
+ ///
+ /// jar.private_mut(&key).add(Cookie::new("name", "value"));
+ /// assert_eq!(jar.private(&key).get("name").unwrap().value(), "value");
+ ///
+ /// let plain = jar.get("name").cloned().unwrap();
+ /// assert_ne!(plain.value(), "value");
+ /// let decrypted = jar.private(&key).decrypt(plain).unwrap();
+ /// assert_eq!(decrypted.value(), "value");
+ ///
+ /// let plain = Cookie::new("plaintext", "hello");
+ /// assert!(jar.private(&key).decrypt(plain).is_none());
+ /// ```
+ pub fn decrypt(&self, mut cookie: Cookie<'static>) -> Option<Cookie<'static>> {
+ if let Ok(value) = self.unseal(cookie.name(), cookie.value()) {
+ cookie.set_value(value);
+ return Some(cookie);
+ }
+
+ None
+ }
+}
+
+impl<J: Borrow<CookieJar>> PrivateJar<J> {
+ /// Returns a reference to the `Cookie` inside this jar with the name `name`
+ /// and authenticates and decrypts the cookie's value, returning a `Cookie`
+ /// with the decrypted value. If the cookie cannot be found, or the cookie
+ /// fails to authenticate or decrypt, `None` is returned.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::{CookieJar, Cookie, Key};
+ ///
+ /// let key = Key::generate();
+ /// let jar = CookieJar::new();
+ /// assert!(jar.private(&key).get("name").is_none());
+ ///
+ /// let mut jar = jar;
+ /// let mut private_jar = jar.private_mut(&key);
+ /// private_jar.add(Cookie::new("name", "value"));
+ /// assert_eq!(private_jar.get("name").unwrap().value(), "value");
+ /// ```
+ pub fn get(&self, name: &str) -> Option<Cookie<'static>> {
+ self.parent.borrow().get(name).and_then(|c| self.decrypt(c.clone()))
+ }
+}
+
+impl<J: BorrowMut<CookieJar>> PrivateJar<J> {
+ /// Adds `cookie` to the parent jar. The cookie's value is encrypted with
+ /// authenticated encryption assuring confidentiality, integrity, and
+ /// authenticity.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::{CookieJar, Cookie, Key};
+ ///
+ /// let key = Key::generate();
+ /// let mut jar = CookieJar::new();
+ /// jar.private_mut(&key).add(Cookie::new("name", "value"));
+ ///
+ /// assert_ne!(jar.get("name").unwrap().value(), "value");
+ /// assert_eq!(jar.private(&key).get("name").unwrap().value(), "value");
+ /// ```
+ pub fn add(&mut self, mut cookie: Cookie<'static>) {
+ self.encrypt_cookie(&mut cookie);
+ self.parent.borrow_mut().add(cookie);
+ }
+
+ /// Adds an "original" `cookie` to parent jar. The cookie's value is
+ /// encrypted with authenticated encryption assuring confidentiality,
+ /// 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.private_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.encrypt_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 private_jar = jar.private_mut(&key);
+ ///
+ /// private_jar.add(Cookie::new("name", "value"));
+ /// assert!(private_jar.get("name").is_some());
+ ///
+ /// private_jar.remove(Cookie::named("name"));
+ /// assert!(private_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.private_mut(&key));
+ }
+
+ #[test]
+ fn secure() {
+ let key = Key::generate();
+ let mut jar = CookieJar::new();
+ assert_secure_behaviour!(jar, jar.private_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("encrypted_with_ring014",
+ "lObeZJorGVyeSWUA8khTO/8UCzFVBY9g0MGU6/J3NN1R5x11dn2JIA=="));
+ jar.add(Cookie::new("encrypted_with_ring016",
+ "SU1ujceILyMBg3fReqRmA9HUtAIoSPZceOM/CUpObROHEujXIjonkA=="));
+
+ let private = jar.private(&key);
+ assert_eq!(private.get("encrypted_with_ring014").unwrap().value(), "Tamper-proof");
+ assert_eq!(private.get("encrypted_with_ring016").unwrap().value(), "Tamper-proof");
+ }
+}
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());
+ }
+}