use secure::ring::aead::{Aad, Algorithm, Nonce, AES_256_GCM};
use secure::ring::aead::{LessSafeKey, UnboundKey};
use secure::ring::rand::{SecureRandom, SystemRandom};
use secure::{base64, Key};
use {Cookie, CookieJar};
static ALGO: &'static Algorithm = &AES_256_GCM;
const NONCE_LEN: usize = 12;
pub const KEY_LEN: usize = 32;
pub struct PrivateJar<'a> {
parent: &'a mut CookieJar,
key: [u8; KEY_LEN]
}
impl<'a> PrivateJar<'a> {
#[doc(hidden)]
pub fn new(parent: &'a mut CookieJar, key: &Key) -> PrivateJar<'a> {
let mut key_array = [0u8; KEY_LEN];
key_array.copy_from_slice(key.encryption());
PrivateJar { parent: parent, key: key_array }
}
fn unseal(&self, name: &str, value: &str) -> Result<String, &'static str> {
let mut data = base64::decode(value).map_err(|_| "bad base64 value")?;
if data.len() <= NONCE_LEN {
return Err("length of decoded data is <= NONCE_LEN");
}
let ad = Aad::from(name.as_bytes());
let key = LessSafeKey::new(UnboundKey::new(&ALGO, &self.key).expect("matching key length"));
let (nonce, mut sealed) = data.split_at_mut(NONCE_LEN);
let nonce = Nonce::try_assume_unique_for_key(nonce)
.expect("invalid length of `nonce`");
let unsealed = key.open_in_place(nonce, ad, &mut sealed)
.map_err(|_| "invalid key/nonce/value: bad seal")?;
::std::str::from_utf8(unsealed)
.map(|s| s.to_string())
.map_err(|_| "bad unsealed utf8")
}
pub fn get(&self, name: &str) -> Option<Cookie<'static>> {
if let Some(cookie_ref) = self.parent.get(name) {
let mut cookie = cookie_ref.clone();
if let Ok(value) = self.unseal(name, cookie.value()) {
cookie.set_value(value);
return Some(cookie);
}
}
None
}
pub fn add(&mut self, mut cookie: Cookie<'static>) {
self.encrypt_cookie(&mut cookie);
self.parent.add(cookie);
}
pub fn add_original(&mut self, mut cookie: Cookie<'static>) {
self.encrypt_cookie(&mut cookie);
self.parent.add_original(cookie);
}
fn encrypt_cookie(&self, cookie: &mut Cookie) {
let unbound = UnboundKey::new(&ALGO, &self.key).expect("matching key length");
let key = LessSafeKey::new(unbound);
let cookie_val = cookie.value().as_bytes();
let mut data = vec![0; NONCE_LEN + cookie_val.len() + ALGO.tag_len()];
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);
SystemRandom::new().fill(nonce).expect("couldn't random fill nonce");
let nonce = Nonce::try_assume_unique_for_key(nonce)
.expect("invalid `nonce` length");
let ad = Aad::from(cookie.name().as_bytes());
let ad_tag = key.seal_in_place_separate_tag(nonce, ad, in_out)
.expect("in-place seal");
tag.copy_from_slice(ad_tag.as_ref());
cookie.set_value(base64::encode(&data));
}
pub fn remove(&mut self, cookie: Cookie<'static>) {
self.parent.remove(cookie);
}
}
#[cfg(test)]
mod test {
use {CookieJar, Cookie, Key};
#[test]
fn simple() {
let key = Key::generate();
let mut jar = CookieJar::new();
assert_simple_behaviour!(jar, jar.private(&key));
}
#[test]
fn private() {
let key = Key::generate();
let mut jar = CookieJar::new();
assert_secure_behaviour!(jar, jar.private(&key));
}
#[test]
fn roundtrip_tests() {
use ::{Cookie, CookieJar};
let secret = b"\x1b\x1c\x6f\x1b\x31\x99\x82\x77\x0e\x05\xb6\x05\x54\x0b\xd9\xea\
\x54\x9f\x9a\x56\xf4\x0f\x97\xdc\x6e\xf2\x89\x86\x91\xe0\xa5\x79";
let key = Key::from_master(secret);
let mut jar = CookieJar::new();
jar.add(Cookie::new("signed_with_ring014", "3tdHXEQ2kf6fxC7dWzBGmpSLMtJenXLKrZ9cHkSsl1w=Tamper-proof"));
jar.add(Cookie::new("encrypted_with_ring014", "lObeZJorGVyeSWUA8khTO/8UCzFVBY9g0MGU6/J3NN1R5x11dn2JIA=="));
jar.add(Cookie::new("signed_with_ring016", "3tdHXEQ2kf6fxC7dWzBGmpSLMtJenXLKrZ9cHkSsl1w=Tamper-proof"));
jar.add(Cookie::new("encrypted_with_ring016", "SU1ujceILyMBg3fReqRmA9HUtAIoSPZceOM/CUpObROHEujXIjonkA=="));
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");
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");
}
}