use hmac::{Hmac, Mac, NewMac}; 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 { 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::::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 { 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::::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()) } }