krabka_security/
delegation_token.rs1use std::fmt;
5
6use bytes::Bytes;
7use ring::hmac;
8
9#[derive(Clone, PartialEq, Eq)]
10pub struct SecretBytes(Bytes);
11
12impl SecretBytes {
13 #[must_use]
14 pub fn new(bytes: impl Into<Bytes>) -> Self {
15 Self(bytes.into())
16 }
17 #[must_use]
18 pub fn as_bytes(&self) -> &[u8] {
19 &self.0
20 }
21 #[must_use]
22 pub fn len(&self) -> usize {
23 self.0.len()
24 }
25 #[must_use]
26 pub fn is_empty(&self) -> bool {
27 self.0.is_empty()
28 }
29}
30
31impl fmt::Debug for SecretBytes {
32 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33 write!(f, "SecretBytes(<{} bytes redacted>)", self.0.len())
34 }
35}
36
37#[must_use]
44pub fn compute_token_hmac(secret_key: &[u8], token_id: &str) -> Vec<u8> {
45 let key = hmac::Key::new(hmac::HMAC_SHA512, secret_key);
46 hmac::sign(&key, token_id.as_bytes()).as_ref().to_vec()
47}
48
49#[cfg(test)]
50mod tests {
51 use assert2::check;
52
53 use super::*;
54
55 #[test]
56 fn secret_bytes_accessors() {
57 let s = SecretBytes::new(Bytes::from_static(b"abc"));
58 check!(
59 (
60 s.as_bytes(),
61 s.len(),
62 s.is_empty(),
63 SecretBytes::new(Bytes::new()).is_empty(),
64 ) == (b"abc".as_slice(), 3, false, true)
65 );
66 }
67
68 #[test]
71 fn hmac_is_hmac_sha512_of_the_token_id() {
72 let expected = hex::decode(
73 "0e527131aafda6c68f4161e8593b8e6eb591403471ef360854b63c883f5a3247\
74 fde5b7720739ea603cb7226cd209ab14718a27b4d3c1b384da9beb197ee7cb3c",
75 )
76 .expect("valid hex");
77 assert2::assert!(compute_token_hmac(b"k", "tok-1") == expected);
78 }
79
80 #[test]
81 fn hmac_diverges_on_key_change() {
82 let h1 = compute_token_hmac(b"k1", "tok-1");
83 let h2 = compute_token_hmac(b"k2", "tok-1");
84 assert2::assert!(h1 != h2);
85 }
86
87 #[test]
88 fn secret_bytes_debug_does_not_leak_bytes() {
89 let s = SecretBytes::new(b"super-secret-master-key".to_vec());
90 let d = format!("{s:?}");
91 assert2::assert!(d.contains("redacted"));
92 assert2::assert!(!d.contains("super-secret"));
93 }
94}