use crate::Key;
use anyhow::Context;
use base64::Engine;
use base64::prelude::BASE64_URL_SAFE_NO_PAD;
use hmac::digest::OutputSizeUser;
use hmac::{Hmac, Mac};
use sha2::Sha256;
#[derive(Clone, Copy)]
pub(crate) struct SigningKey([u8; 32]);
impl std::fmt::Debug for SigningKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("SigningKey").field(&"***").finish()
}
}
impl PartialEq for SigningKey {
fn eq(&self, other: &Self) -> bool {
use subtle::ConstantTimeEq;
self.0.ct_eq(&other.0).into()
}
}
impl SigningKey {
pub(crate) fn derive(key: &Key) -> Self {
let mut derived = [0; 32];
hkdf::Hkdf::<Sha256>::from_prk(key.master())
.expect("Couldn't create HKDF from PRK")
.expand(b"COOKIE;HMAC-SHA256", &mut derived)
.expect("Failed to derive HMAC-SHA256 key from PRK");
Self(derived)
}
pub(crate) fn sign(&self, name: &str, value: &str) -> String {
let mut mac = Hmac::<Sha256>::new_from_slice(&self.0).expect("Key is too short");
mac.update(name.as_bytes());
mac.update(&[SEPARATOR]);
mac.update(value.as_bytes());
let mut new_value = Vec::with_capacity(Hmac::<Sha256>::output_size() + value.len());
new_value.extend(mac.finalize().into_bytes());
new_value.extend(value.as_bytes());
BASE64_URL_SAFE_NO_PAD.encode(&new_value)
}
pub(crate) fn verify(&self, name: &str, value: &str) -> Result<String, anyhow::Error> {
let value = BASE64_URL_SAFE_NO_PAD
.decode(value)
.context("Failed to decode cookie value using base64 (URL-safe, no padding)")?;
let digest_len = Hmac::<Sha256>::output_size();
if value.len() <= digest_len {
anyhow::bail!("The cookie value was too short to contain a MAC signature");
}
let (digest, value) = value.split_at(Hmac::<Sha256>::output_size());
let mut mac = Hmac::<Sha256>::new_from_slice(&self.0).expect("Key is too short");
mac.update(name.as_bytes());
mac.update(&[SEPARATOR]);
mac.update(value);
mac.verify_slice(digest)
.context("Failed to verify cookie value using HMAC")?;
Ok(std::str::from_utf8(value)
.context("Cookie value was not valid UTF-8")?
.to_string())
}
}
const SEPARATOR: u8 = 0;