Skip to content

Commit c0ca3ab

Browse files
committed
Add Cookie::{encrypt,decrypt,sign,verify}().
These methods allow encrypting/decrypting and signing/verifying cookies without using a `PrivateJar` or `SignedJar`. Resolves rwf2#132.
1 parent ba46fc5 commit c0ca3ab

File tree

3 files changed

+209
-109
lines changed

3 files changed

+209
-109
lines changed

src/lib.rs

+96-7
Original file line numberDiff line numberDiff line change
@@ -29,22 +29,29 @@
2929
//!
3030
//! * **`signed`**
3131
//!
32-
//! Enables _signed_ cookies via [`CookieJar::signed()`].
32+
//! Enables _signed_ cookies via [`Cookie::sign()`], [`Cookie::verify()`], and
33+
//! [`CookieJar::signed()`].
3334
//!
34-
//! When this feature is enabled, the [`CookieJar::signed()`] method,
35-
//! [`SignedJar`] type, and [`Key`] type are available. The jar acts as "child
35+
//! When this feature is enabled, the type [`Key`] and the methods
36+
//! [`Cookie::sign()`] and [`Cookie::verify()`] are available, which can be
37+
//! used to sign and verify cookies. Additionally, the type [`SignedJar`] and
38+
//! the method [`CookieJar::signed()`] is available. The jar acts as "child
3639
//! jar"; operations on the jar automatically sign and verify cookies as they
3740
//! are added and retrieved from the parent jar.
3841
//!
3942
//! * **`private`**
4043
//!
4144
//! Enables _private_ (authenticated, encrypted) cookies via
45+
//! [`Cookie::encrypt()`], [`Cookie::decrypt()`], and
4246
//! [`CookieJar::private()`].
4347
//!
44-
//! When this feature is enabled, the [`CookieJar::private()`] method,
45-
//! [`PrivateJar`] type, and [`Key`] type are available. The jar acts as "child
46-
//! jar"; operations on the jar automatically encrypt and decrypt/authenticate
47-
//! cookies as they are added and retrieved from the parent jar.
48+
//! When this feature is enabled, the type [`Key`] and the methods
49+
//! [`Cookie::encrypt()`] and [`Cookie::decrypt()`] are available, which can
50+
//! be used to encrypt and decrypt/authenticate cookies. Additionally, the
51+
//! type [`PrivateJar`] and the method [`CookieJar::private()`] is available.
52+
//! The jar acts as "child jar"; operations on the jar automatically encrypt
53+
//! and decrypt/authenticate cookies as they are added and retrieved from the
54+
//! parent jar.
4855
//!
4956
//! * **`key-expansion`**
5057
//!
@@ -1415,6 +1422,88 @@ assert_eq!(&c.stripped().encoded().to_string(), "key%3F=value");
14151422
pub fn stripped<'a>(&'a self) -> Display<'a, 'c> {
14161423
Display::new_stripped(self)
14171424
}
1425+
1426+
/// Encrypts and signs this cookie using authenticated encryption with the
1427+
/// provided key and returns the encrypted cookie.
1428+
///
1429+
/// # Example
1430+
///
1431+
/// ```rust
1432+
/// use cookie::{Cookie, Key};
1433+
///
1434+
/// let key = Key::generate();
1435+
/// let plain = Cookie::from(("name", "value"));
1436+
/// let encrypted = plain.encrypt(&key);
1437+
/// ```
1438+
#[cfg(feature = "private")]
1439+
#[cfg_attr(all(nightly, doc), doc(cfg(feature = "private")))]
1440+
pub fn encrypt(&self, key: &Key) -> Cookie<'static> {
1441+
let mut cookie = self.clone().into_owned();
1442+
secure::encrypt_cookie(&mut cookie, key.encryption());
1443+
cookie
1444+
}
1445+
1446+
/// Authenticates and decrypts this cookie with the provided key and returns
1447+
/// the plaintext version if decryption succeeds or `None` otherwise.
1448+
///
1449+
/// # Example
1450+
///
1451+
/// ```rust
1452+
/// use cookie::{Cookie, Key};
1453+
///
1454+
/// let key = Key::generate();
1455+
/// let plain = Cookie::from(("name", "value"));
1456+
/// let encrypted = plain.encrypt(&key);
1457+
///
1458+
/// let decrypted = encrypted.decrypt(&key).unwrap();
1459+
/// assert_eq!(decrypted.value(), "value");
1460+
/// ```
1461+
#[cfg(feature = "private")]
1462+
#[cfg_attr(all(nightly, doc), doc(cfg(feature = "private")))]
1463+
pub fn decrypt(&self, key: &Key) -> Option<Cookie<'static>> {
1464+
secure::decrypt_cookie(self, key.encryption())
1465+
}
1466+
1467+
/// Signs this cookie with the provided key and returns the signed cookie.
1468+
///
1469+
/// # Example
1470+
///
1471+
/// ```rust
1472+
/// use cookie::{Cookie, Key};
1473+
///
1474+
/// let key = Key::generate();
1475+
/// let plain = Cookie::from(("name", "value"));
1476+
/// let signed = plain.sign(&key);
1477+
/// ```
1478+
#[cfg(feature = "signed")]
1479+
#[cfg_attr(all(nightly, doc), doc(cfg(feature = "signed")))]
1480+
pub fn sign(&self, key: &Key) -> Cookie<'static> {
1481+
let mut cookie = self.clone().into_owned();
1482+
secure::sign_cookie(&mut cookie, key.signing());
1483+
cookie
1484+
}
1485+
1486+
/// Verifies the signature of this cookie with the provided key and returns
1487+
/// the plaintext version if verification succeeds or `None` otherwise.
1488+
///
1489+
/// # Example
1490+
///
1491+
/// ```rust
1492+
/// use cookie::{Cookie, Key};
1493+
///
1494+
/// let key = Key::generate();
1495+
/// let plain = Cookie::from(("name", "value"));
1496+
/// let signed = plain.sign(&key);
1497+
///
1498+
/// let verified = signed.verify(&key).unwrap();
1499+
/// assert_eq!(verified.value(), "value");
1500+
/// ```
1501+
#[cfg(feature = "signed")]
1502+
#[cfg_attr(all(nightly, doc), doc(cfg(feature = "signed")))]
1503+
pub fn verify(&self, key: &Key) -> Option<Cookie<'static>> {
1504+
secure::verify_cookie(self, key.signing())
1505+
}
1506+
14181507
}
14191508

14201509
/// An iterator over cookie parse `Result`s: `Result<Cookie, ParseError>`.

src/secure/private.rs

+66-60
Original file line numberDiff line numberDiff line change
@@ -37,56 +37,6 @@ impl<J> PrivateJar<J> {
3737
PrivateJar { parent, key: key.encryption().try_into().expect("enc key len") }
3838
}
3939

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-
9040
/// Authenticates and decrypts `cookie`, returning the plaintext version if
9141
/// decryption succeeds or `None` otherwise. Authenticatation and decryption
9242
/// _always_ succeeds if `cookie` was generated by a `PrivateJar` with the
@@ -112,13 +62,8 @@ impl<J> PrivateJar<J> {
11262
/// let plain = Cookie::new("plaintext", "hello");
11363
/// assert!(jar.private(&key).decrypt(plain).is_none());
11464
/// ```
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
65+
pub fn decrypt(&self, cookie: Cookie<'static>) -> Option<Cookie<'static>> {
66+
decrypt_cookie(&cookie, &self.key)
12267
}
12368
}
12469

@@ -143,7 +88,7 @@ impl<J: Borrow<CookieJar>> PrivateJar<J> {
14388
/// assert_eq!(private_jar.get("name").unwrap().value(), "value");
14489
/// ```
14590
pub fn get(&self, name: &str) -> Option<Cookie<'static>> {
146-
self.parent.borrow().get(name).and_then(|c| self.decrypt(c.clone()))
91+
self.parent.borrow().get(name).and_then(|c| decrypt_cookie(c, &self.key))
14792
}
14893
}
14994

@@ -166,7 +111,7 @@ impl<J: BorrowMut<CookieJar>> PrivateJar<J> {
166111
/// ```
167112
pub fn add<C: Into<Cookie<'static>>>(&mut self, cookie: C) {
168113
let mut cookie = cookie.into();
169-
self.encrypt_cookie(&mut cookie);
114+
encrypt_cookie(&mut cookie, &self.key);
170115
self.parent.borrow_mut().add(cookie);
171116
}
172117

@@ -194,7 +139,7 @@ impl<J: BorrowMut<CookieJar>> PrivateJar<J> {
194139
/// ```
195140
pub fn add_original<C: Into<Cookie<'static>>>(&mut self, cookie: C) {
196141
let mut cookie = cookie.into();
197-
self.encrypt_cookie(&mut cookie);
142+
encrypt_cookie(&mut cookie, &self.key);
198143
self.parent.borrow_mut().add_original(cookie);
199144
}
200145

@@ -226,6 +171,67 @@ impl<J: BorrowMut<CookieJar>> PrivateJar<J> {
226171
}
227172
}
228173

174+
/// Encrypts `cookie` in-place using authenticated encryption with the provided
175+
/// key `key`.
176+
pub(crate) fn encrypt_cookie(cookie: &mut Cookie<'static>, key: &[u8]) {
177+
// Create a vec to hold the [nonce | cookie value | tag].
178+
let cookie_val = cookie.value().as_bytes();
179+
let mut data = vec![0; NONCE_LEN + cookie_val.len() + TAG_LEN];
180+
181+
// Split data into three: nonce, input/output, tag. Copy input.
182+
let (nonce, in_out) = data.split_at_mut(NONCE_LEN);
183+
let (in_out, tag) = in_out.split_at_mut(cookie_val.len());
184+
in_out.copy_from_slice(cookie_val);
185+
186+
// Fill nonce piece with random data.
187+
let mut rng = rand::thread_rng();
188+
rng.try_fill_bytes(nonce).expect("couldn't random fill nonce");
189+
let nonce = GenericArray::clone_from_slice(nonce);
190+
191+
// Perform the actual sealing operation, using the cookie's name as
192+
// associated data to prevent value swapping.
193+
let aad = cookie.name().as_bytes();
194+
let aead = Aes256Gcm::new(GenericArray::from_slice(key));
195+
let aad_tag = aead.encrypt_in_place_detached(&nonce, aad, in_out)
196+
.expect("encryption failure!");
197+
198+
// Copy the tag into the tag piece.
199+
tag.copy_from_slice(&aad_tag);
200+
201+
// Base64 encode [nonce | encrypted value | tag].
202+
let new_value = base64::encode(&data);
203+
204+
// Return encrypted cookie.
205+
cookie.set_value(new_value);
206+
}
207+
208+
/// Authenticates and decrypts `cookie` using the provided key `key` and returns
209+
/// the plaintext version if decryption succeeds, otherwise `None`.
210+
pub(crate) fn decrypt_cookie<'a>(cookie: &Cookie<'a>, key: &[u8]) -> Option<Cookie<'static>> {
211+
if let Ok(value) = decrypt_cookie_impl(cookie, key) {
212+
let mut cookie = cookie.clone().into_owned();
213+
cookie.set_value(value);
214+
return Some(cookie);
215+
}
216+
217+
None
218+
}
219+
220+
fn decrypt_cookie_impl<'a>(cookie: &Cookie<'a>, key: &[u8]) -> Result<String, &'static str> {
221+
let data = base64::decode(cookie.value()).map_err(|_| "bad base64 value")?;
222+
if data.len() <= NONCE_LEN {
223+
return Err("length of decoded data is <= NONCE_LEN");
224+
}
225+
226+
let (nonce, cipher) = data.split_at(NONCE_LEN);
227+
let payload = Payload { msg: cipher, aad: cookie.name().as_bytes() };
228+
229+
let aead = Aes256Gcm::new(GenericArray::from_slice(key));
230+
aead.decrypt(GenericArray::from_slice(nonce), payload)
231+
.map_err(|_| "invalid key/nonce/value: bad seal")
232+
.and_then(|s| String::from_utf8(s).map_err(|_| "bad unsealed utf8"))
233+
}
234+
229235
#[cfg(test)]
230236
mod test {
231237
use crate::{CookieJar, Cookie, Key};

0 commit comments

Comments
 (0)