diff options
Diffstat (limited to 'src/security')
-rw-r--r-- | src/security/signed.rs | 72 |
1 files changed, 72 insertions, 0 deletions
diff --git a/src/security/signed.rs b/src/security/signed.rs new file mode 100644 index 0000000..4da6760 --- /dev/null +++ b/src/security/signed.rs @@ -0,0 +1,72 @@ +use hmac::{Hmac,NewMac,Mac}; +use sha2::Sha256; + +const SIGNED_KEY_LEN: usize = 32; +const BASE64_DIGEST_LEN: usize = 44; + +/// A convenience wrapper around HMAC. +/// +/// This code was adapted from the [`cookie`] crate which does not make the sign and verify functions public +/// forcing the use of [`CookieJar`](cookie::CookieJar)s, which are akward to work with without a high-level framework. +// Thanks to Sergio Benitez for writing the original code and releasing it under MIT! +pub struct Key (pub [u8; SIGNED_KEY_LEN]); + +impl Key { + const fn zero() -> Self { + Key ( [0; SIGNED_KEY_LEN]) + } + + /// 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`. + pub fn try_generate() -> Option<Key> { + use rand::RngCore; + + let mut rng = rand::thread_rng(); + let mut both_keys = [0; SIGNED_KEY_LEN]; + rng.try_fill_bytes(&mut both_keys).ok()?; + Some(Key::from(&both_keys)) + } + + /// Creates a new Key from a 32 byte cryptographically random string. + pub fn from(key: &[u8]) -> Key { + if key.len() < SIGNED_KEY_LEN { + panic!("bad key length: expected >= 32 bytes, found {}", key.len()); + } + + let mut output = Key::zero(); + output.0.copy_from_slice(&key[..SIGNED_KEY_LEN]); + output + } + + /// Signs the value providing integrity and authenticity. + pub fn sign(&self, value: &str) -> String { + // Compute HMAC-SHA256 of the cookie's value. + let mut mac = Hmac::<Sha256>::new_varkey(&self.0).expect("good key"); + mac.update(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(value); + new_value + } + + /// Extracts the value from a string signed with [`Key::sign`]. + /// Fails if the string is malformed or the signature doesn't match. + pub fn verify(&self, value: &str) -> Result<String, String> { + if value.len() < BASE64_DIGEST_LEN { + return Err("length of value is <= BASE64_DIGEST_LEN".to_string()); + } + + // Split [MAC | original-value] into its two parts. + let (digest_str, value) = 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_varkey(&self.0).expect("good key"); + mac.update(value.as_bytes()); + mac.verify(&digest) + .map(|_| value.to_string()) + .map_err(|_| "value did not verify".to_string()) + } +}
\ No newline at end of file |