Skip to content

Commit

Permalink
Take crypto keys indirectly via 'Key' for improved security and usabi…
Browse files Browse the repository at this point in the history
…lity.
  • Loading branch information
SergioBenitez committed Mar 1, 2017
1 parent ab2339d commit 4ba4735
Show file tree
Hide file tree
Showing 5 changed files with 219 additions and 68 deletions.
40 changes: 14 additions & 26 deletions src/jar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::mem::replace;
use time::{self, Duration};

#[cfg(feature = "secure")]
use secure::{PrivateJar, SignedJar};
use secure::{PrivateJar, SignedJar, Key};
use delta::DeltaCookie;
use Cookie;

Expand Down Expand Up @@ -310,26 +310,20 @@ impl CookieJar {

/// Returns a `PrivateJar` with `self` as its parent jar using the key `key`
/// to sign/encrypt and verify/decrypt cookies added/retrieved from the
/// child jar. The key must be exactly 32 bytes. For security, the key
/// _must_ be cryptographically random and _different_ from a key used for
/// a signed jar, if any.
/// child jar.
///
/// Any modifications to the child jar will be reflected on the parent jar,
/// and any retrievals from the child jar will be made from the parent jar.
///
/// This method is only available when the `secure` feature is enabled.
///
/// # Panics
///
/// Panics if `key` is not exactly 32 bytes long.
///
/// # Example
///
/// ```rust
/// use cookie::{Cookie, CookieJar};
/// use cookie::{Cookie, CookieJar, Key};
///
/// // We use a bogus key for demonstration purposes.
/// let key: Vec<_> = (0..32).collect();
/// // Generate a secure key.
/// let key = Key::generate();
///
/// // Add a private (signed + encrypted) cookie.
/// let mut jar = CookieJar::new();
Expand All @@ -348,31 +342,25 @@ impl CookieJar {
/// assert!(jar.get("private").is_some());
/// ```
#[cfg(feature = "secure")]
pub fn private<'a, 'k>(&'a mut self, key: &[u8]) -> PrivateJar<'a> {
pub fn private(&mut self, key: &Key) -> PrivateJar {
PrivateJar::new(self, key)
}

/// Returns a `SignedJar` with `self` as its parent jar using the key `key`
/// to sign/verify cookies added/retrieved from the child jar. The key must
/// be exactly 32 bytes. For security, the key _must_ be cryptographically
/// random and _different_ from a key used for a private jar, if any.
/// to sign/verify cookies added/retrieved from the child jar.
///
/// Any modifications to the child jar will be reflected on the parent jar,
/// and any retrievals from the child jar will be made from the parent jar.
///
/// This method is only available when the `secure` feature is enabled.
///
/// # Panics
///
/// Panics if `key` is not exactly 32 bytes long.
///
/// # Example
///
/// ```rust
/// use cookie::{Cookie, CookieJar};
/// use cookie::{Cookie, CookieJar, Key};
///
/// // We use a bogus key for demonstration purposes.
/// let key: Vec<_> = (0..32).collect();
/// // Generate a secure key.
/// let key = Key::generate();
///
/// // Add a signed cookie.
/// let mut jar = CookieJar::new();
Expand All @@ -392,7 +380,7 @@ impl CookieJar {
/// assert!(jar.get("signed").is_some());
/// ```
#[cfg(feature = "secure")]
pub fn signed<'a>(&'a mut self, key: &[u8]) -> SignedJar<'a> {
pub fn signed(&mut self, key: &Key) -> SignedJar {
SignedJar::new(self, key)
}
}
Expand Down Expand Up @@ -471,7 +459,7 @@ mod test {
#[test]
#[cfg(feature = "secure")]
fn iter() {
let key: Vec<u8> = (0..64).collect();
let key = ::Key::generate();
let mut c = CookieJar::new();

c.add_original(Cookie::new("original", "original"));
Expand All @@ -481,8 +469,8 @@ mod test {
c.add(Cookie::new("test3", "test3"));
assert_eq!(c.iter().count(), 4);

c.signed(&key[..32]).add(Cookie::new("signed", "signed"));
c.private(&key[32..]).add(Cookie::new("encrypted", "encrypted"));
c.signed(&key).add(Cookie::new("signed", "signed"));
c.private(&key).add(Cookie::new("encrypted", "encrypted"));
assert_eq!(c.iter().count(), 6);

c.remove(Cookie::named("test"));
Expand Down
173 changes: 173 additions & 0 deletions src/secure/key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
use secure::ring::hkdf::expand;
use secure::ring::digest::{SHA256, Algorithm};
use secure::ring::hmac::SigningKey;
use secure::ring::rand::SystemRandom;

use secure::private::KEY_LEN as PRIVATE_KEY_LEN;
use secure::signed::KEY_LEN as SIGNED_KEY_LEN;

static HKDF_DIGEST: &'static Algorithm = &SHA256;
const KEYS_INFO: &'static str = "COOKIE;SIGNED:HMAC-SHA256;PRIVATE:AEAD-AES-256-GCM";

/// A cryptographic master key for use with `Signed` and/or `Private` jars.
///
/// This structure encapsulates secure, cryptographic keys for use with both
/// [PrivateJar](struct.PrivateJar.html) and [SignedJar](struct.SignedJar.html).
/// It can be derived from a single master key via
/// [from_master](#method.from_master) or generated from a secure random source
/// via [generate](#method.generate). A single instance of `Key` can be used for
/// both a `PrivateJar` and a `SignedJar`.
///
/// This type is only available when the `secure` feature is enabled.
pub struct Key {
signing_key: [u8; SIGNED_KEY_LEN],
encryption_key: [u8; PRIVATE_KEY_LEN]
}

impl Key {
/// 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::from_master(master_key);
/// ```
pub fn from_master(key: &[u8]) -> Key {
if key.len() < 32 {
panic!("bad master key length: expected at least 32 bytes, found {}", key.len());
}

// Expand the user's key into two.
let prk = SigningKey::new(HKDF_DIGEST, key);
let mut both_keys = [0; SIGNED_KEY_LEN + PRIVATE_KEY_LEN];
expand(&prk, KEYS_INFO.as_bytes(), &mut both_keys);

// Copy the keys into their respective arrays.
let mut signing_key = [0; SIGNED_KEY_LEN];
let mut encryption_key = [0; PRIVATE_KEY_LEN];
signing_key.copy_from_slice(&both_keys[..SIGNED_KEY_LEN]);
encryption_key.copy_from_slice(&both_keys[SIGNED_KEY_LEN..]);

Key {
signing_key: signing_key,
encryption_key: encryption_key
}
}

/// 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
/// [try_generate](#method.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> {
let mut sign_key = [0; SIGNED_KEY_LEN];
let mut enc_key = [0; PRIVATE_KEY_LEN];

let rng = SystemRandom::new();
if rng.fill(&mut sign_key).is_err() || rng.fill(&mut enc_key).is_err() {
return None
}

Some(Key { signing_key: sign_key, encryption_key: enc_key })
}

/// Returns the raw bytes of a key suitable for signing cookies.
///
/// # Example
///
/// ```rust
/// use cookie::Key;
///
/// let key = Key::generate();
/// let signing_key = key.signing();
/// ```
pub fn signing(&self) -> &[u8] {
&self.signing_key[..]
}

/// Returns the raw bytes of a key suitable for encrypting cookies.
///
/// # Example
///
/// ```rust
/// use cookie::Key;
///
/// let key = Key::generate();
/// let encryption_key = key.encryption();
/// ```
pub fn encryption(&self) -> &[u8] {
&self.encryption_key[..]
}
}

#[cfg(test)]
mod test {
use super::Key;

#[test]
fn deterministic_from_master() {
let master_key: Vec<u8> = (0..32).collect();

let key_a = Key::from_master(&master_key);
let key_b = Key::from_master(&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::from_master(&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());
}
}
2 changes: 2 additions & 0 deletions src/secure/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ extern crate rustc_serialize;
mod macros;
mod private;
mod signed;
mod key;

pub use self::private::*;
pub use self::signed::*;
pub use self::key::*;
36 changes: 15 additions & 21 deletions src/secure/private.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
use secure::ring::aead::{seal_in_place, open_in_place, Algorithm, AES_256_GCM};
use secure::ring::aead::{OpeningKey, SealingKey};
use secure::ring::rand::SystemRandom;
use secure::Key;

use secure::rustc_serialize::base64::{ToBase64, FromBase64, STANDARD};

use {Cookie, CookieJar};

// Keep these in sync, and keep the key len synced with the `private` docs.
// Keep these in sync, and keep the key len synced with the `private` docs as
// well as the `KEYS_INFO` const in secure::Key.
static ALGO: &'static Algorithm = &AES_256_GCM;
const KEY_LEN: usize = 32;
const NONCE_LEN: usize = 12;
pub const KEY_LEN: usize = 32;

/// A child cookie jar that provides authenticated encryption for its cookies.
///
Expand All @@ -29,18 +31,10 @@ impl<'a> PrivateJar<'a> {
/// Creates a new child `PrivateJar` with parent `parent` and key `key`.
/// This method is typically called indirectly via the `signed` method of
/// `CookieJar`.
///
/// # Panics
///
/// Panics if `key` is not exactly 32 bytes long.
#[doc(hidden)]
pub fn new(parent: &'a mut CookieJar, key: &[u8]) -> PrivateJar<'a> {
if key.len() != KEY_LEN {
panic!("bad key length: expected {} bytes, found {}", KEY_LEN, key.len());
}

pub fn new(parent: &'a mut CookieJar, key: &Key) -> PrivateJar<'a> {
let mut key_array = [0u8; KEY_LEN];
key_array.copy_from_slice(key);
key_array.copy_from_slice(key.encryption());
PrivateJar { parent: parent, key: key_array }
}

Expand Down Expand Up @@ -72,9 +66,9 @@ impl<'a> PrivateJar<'a> {
/// # Example
///
/// ```rust
/// use cookie::{CookieJar, Cookie};
/// use cookie::{CookieJar, Cookie, Key};
///
/// # let key: Vec<_> = (0..32).collect();
/// let key = Key::generate();
/// let mut jar = CookieJar::new();
/// let mut private_jar = jar.private(&key);
/// assert!(private_jar.get("name").is_none());
Expand All @@ -101,9 +95,9 @@ impl<'a> PrivateJar<'a> {
/// # Example
///
/// ```rust
/// use cookie::{CookieJar, Cookie};
/// use cookie::{CookieJar, Cookie, Key};
///
/// # let key: Vec<_> = (0..32).collect();
/// let key = Key::generate();
/// let mut jar = CookieJar::new();
/// jar.private(&key).add(Cookie::new("name", "value"));
///
Expand Down Expand Up @@ -149,9 +143,9 @@ impl<'a> PrivateJar<'a> {
/// # Example
///
/// ```rust
/// use cookie::{CookieJar, Cookie};
/// use cookie::{CookieJar, Cookie, Key};
///
/// # let key: Vec<_> = (0..32).collect();
/// let key = Key::generate();
/// let mut jar = CookieJar::new();
/// let mut private_jar = jar.private(&key);
///
Expand All @@ -168,18 +162,18 @@ impl<'a> PrivateJar<'a> {

#[cfg(test)]
mod test {
use {CookieJar, Cookie};
use {CookieJar, Cookie, Key};

#[test]
fn simple() {
let key: Vec<u8> = (0..super::KEY_LEN as u8).collect();
let key = Key::generate();
let mut jar = CookieJar::new();
assert_simple_behaviour!(jar, jar.private(&key));
}

#[test]
fn private() {
let key: Vec<u8> = (0..super::KEY_LEN as u8).collect();
let key = Key::generate();
let mut jar = CookieJar::new();
assert_secure_behaviour!(jar, jar.private(&key));
}
Expand Down
Loading

0 comments on commit 4ba4735

Please sign in to comment.