use hmac::{Hmac, KeyInit, Mac};
use sha2::Sha256;
use vti_common::error::AppError;
use vti_common::store::KeyspaceHandle;
const KEY_ROW: &str = "fingerprint-key";
pub async fn key(external_secrets: &KeyspaceHandle) -> Result<[u8; 32], AppError> {
if let Some(k) = external_secrets.get_raw(KEY_ROW).await? {
return to_key(&k);
}
let mut fresh = [0u8; 32];
rand::fill(&mut fresh);
if external_secrets
.insert_raw_if_absent(KEY_ROW, fresh.to_vec())
.await?
{
return Ok(fresh);
}
let k = external_secrets
.get_raw(KEY_ROW)
.await?
.ok_or_else(|| AppError::Internal("fingerprint key vanished after a race".into()))?;
to_key(&k)
}
fn to_key(bytes: &[u8]) -> Result<[u8; 32], AppError> {
bytes
.try_into()
.map_err(|_| AppError::Internal("stored fingerprint key is not 32 bytes".into()))
}
pub fn of(key: &[u8; 32], secret: &[u8]) -> String {
use base64::Engine as _;
let mut mac =
<Hmac<Sha256> as KeyInit>::new_from_slice(key).expect("HMAC takes any key length");
mac.update(secret);
let tag = mac.finalize().into_bytes();
format!(
"hmacsha256:{}",
base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(&tag[..16])
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_fingerprint_depends_on_the_key_and_the_secret() {
let a = of(&[1; 32], b"secret");
assert!(a.starts_with("hmacsha256:"), "{a}");
assert_eq!(a.len(), "hmacsha256:".len() + 22, "{a}");
assert_eq!(a, of(&[1; 32], b"secret"));
assert_ne!(a, of(&[2; 32], b"secret"), "keyed");
assert_ne!(a, of(&[1; 32], b"secreT"));
}
}