use crate::security::secret::SecureBytes;
use hmac::{Hmac, Mac};
use serde::Serialize;
use sha2::{Sha384, Sha512};
#[derive(Debug, thiserror::Error)]
pub enum SigningError {
#[error("invalid key for HMAC")]
InvalidKey,
#[error("serialization failed: {0}")]
Serialization(String),
}
type HmacSha512 = Hmac<Sha512>;
type HmacSha384 = Hmac<Sha384>;
pub fn sign_hmac_sha512(data: &[u8], key: &SecureBytes) -> Result<Vec<u8>, SigningError> {
let mut mac = HmacSha512::new_from_slice(key.expose()).map_err(|_| SigningError::InvalidKey)?;
mac.update(data);
Ok(mac.finalize().into_bytes().to_vec())
}
#[must_use]
pub fn verify_hmac_sha512(data: &[u8], signature: &[u8], key: &SecureBytes) -> bool {
let Ok(mut mac) = HmacSha512::new_from_slice(key.expose()) else {
return false;
};
mac.update(data);
mac.verify_slice(signature).is_ok()
}
pub fn sign_hmac_sha384(data: &[u8], key: &SecureBytes) -> Result<Vec<u8>, SigningError> {
let mut mac = HmacSha384::new_from_slice(key.expose()).map_err(|_| SigningError::InvalidKey)?;
mac.update(data);
Ok(mac.finalize().into_bytes().to_vec())
}
#[must_use]
pub fn verify_hmac_sha384(data: &[u8], signature: &[u8], key: &SecureBytes) -> bool {
let Ok(mut mac) = HmacSha384::new_from_slice(key.expose()) else {
return false;
};
mac.update(data);
mac.verify_slice(signature).is_ok()
}
pub fn sign_struct_excluding_field<T: Serialize>(
value: &T,
exclude_field: &str,
key: &SecureBytes,
) -> Result<Vec<u8>, SigningError> {
let json =
serde_json::to_value(value).map_err(|e| SigningError::Serialization(e.to_string()))?;
let json = match json {
serde_json::Value::Object(mut map) => {
map.remove(exclude_field);
serde_json::Value::Object(map)
}
other => other,
};
let data = serde_json::to_vec(&json).map_err(|e| SigningError::Serialization(e.to_string()))?;
sign_hmac_sha512(&data, key)
}
#[must_use]
pub fn verify_struct_excluding_field<T: Serialize>(
value: &T,
exclude_field: &str,
signature: &[u8],
key: &SecureBytes,
) -> bool {
let Ok(mut json) = serde_json::to_value(value) else {
return false;
};
if let serde_json::Value::Object(ref mut map) = json {
map.remove(exclude_field);
}
let Ok(data) = serde_json::to_vec(&json) else {
return false;
};
verify_hmac_sha512(&data, signature, key)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hmac_sha512_roundtrip() {
let key = SecureBytes::new(vec![1u8; 64]);
let data = b"hello";
let sig = sign_hmac_sha512(data, &key).unwrap();
assert!(verify_hmac_sha512(data, &sig, &key));
}
#[test]
fn struct_sign_verify_excluding_signature() {
#[derive(serde::Serialize)]
struct Sample {
a: u32,
b: String,
signature: Option<String>,
}
let key = SecureBytes::new(vec![42u8; 32]);
let s = Sample {
a: 7,
b: "x".to_string(),
signature: None,
};
let sig = sign_struct_excluding_field(&s, "signature", &key).unwrap();
assert!(verify_struct_excluding_field(&s, "signature", &sig, &key));
}
}