1use std::fmt;
6
7use ring;
8
9use crate::constant_time;
10
11pub const TAG_LEN: usize = 32;
13
14pub struct Key(ring::hmac::Key);
16
17impl Key {
18 pub fn from_seed(seed: &[u8; 32]) -> Self {
22 Self(ring::hmac::Key::new(ring::hmac::HMAC_SHA256, seed))
23 }
24
25 pub fn sign_and_append(
28 &self,
29 domain_separator: &[u8; 32],
30 aad: &[&[u8]],
31 data_size_hint: Option<usize>,
34 write_data_cb: &dyn Fn(&mut Vec<u8>),
36 ) -> Vec<u8> {
37 let capacity = data_size_hint.unwrap_or_default() + TAG_LEN;
38 let mut out = Vec::with_capacity(capacity);
39
40 write_data_cb(&mut out);
42
43 let tag = self.sign(domain_separator, aad, &out);
44
45 out.extend_from_slice(tag.as_ref());
47
48 out
49 }
50
51 pub fn verify<'a>(
54 &self,
55 domain_separator: &[u8; 32],
56 aad: &[&[u8]],
57 signed: &'a [u8],
58 ) -> Result<&'a [u8], &'static str> {
59 let (plaintext, tag) =
60 signed.split_last_chunk::<TAG_LEN>().ok_or("Tag mismatch")?;
61
62 let expected_tag = self.sign(domain_separator, aad, plaintext);
63 let expected_tag =
64 <&[u8; TAG_LEN]>::try_from(expected_tag.as_ref()).unwrap();
65
66 if constant_time::u8x32_eq(tag, expected_tag) {
67 Ok(plaintext)
68 } else {
69 Err("Tag mismatch")
70 }
71 }
72
73 fn sign(
75 &self,
76 domain_separator: &[u8; 32],
77 aad: &[&[u8]],
78 plaintext: &[u8],
79 ) -> ring::hmac::Tag {
80 let mut ctx = ring::hmac::Context::with_key(&self.0);
81 ctx.update(domain_separator);
82 for &datum in aad {
83 let datum_len: u64 = u64::try_from(datum.len()).unwrap();
84 ctx.update(&datum_len.to_le_bytes());
85 ctx.update(datum);
86 }
87 let plaintext_len: u64 = u64::try_from(plaintext.len()).unwrap();
88 ctx.update(&plaintext_len.to_le_bytes());
89 ctx.update(plaintext);
90 ctx.sign()
91 }
92}
93
94impl fmt::Debug for Key {
95 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96 f.write_str("hmac::Key(..)")
97 }
98}
99
100#[cfg(test)]
101mod test {
102 use std::{assert_eq, println};
103
104 use proptest::{
105 collection::vec,
106 prelude::*,
107 proptest,
108 strategy::ValueTree,
109 test_runner::{self, TestRng, TestRunner},
110 };
111
112 use super::*;
113 use crate::rng::{FastRng, RngExt};
114
115 fn sign(
116 key: &Key,
117 domain: &[u8; 32],
118 aad: &[&[u8]],
119 plaintext: &str,
120 ) -> Vec<u8> {
121 key.sign_and_append(domain, aad, Some(plaintext.len()), &|out| {
122 out.extend_from_slice(plaintext.as_bytes())
123 })
124 }
125
126 #[test]
129 fn sign_verify_roundtrip() {
130 proptest!(|(
131 seed: [u8; 32],
132 domain: [u8; 32],
133 aads in vec(
134 vec(any::<u8>(), 0..64),
135 0..=5
136 ),
137 plaintexts in vec(
138 vec(any::<u8>(), 0..64),
139 0..=5
140 )
141 )| {
142 let key = Key::from_seed(&seed);
143 let aad = aads.iter().map(Vec::as_slice).collect::<Vec<&[u8]>>();
144
145 for plaintext in plaintexts {
146 let signed = key.sign_and_append(
148 &domain,
149 &aad,
150 Some(plaintext.len()),
151 &|out| out.extend_from_slice(&plaintext),
152 );
153
154 prop_assert_eq!(signed.len(), plaintext.len() + TAG_LEN);
156
157 let verified = key.verify(&domain, &aad, &signed);
159 prop_assert_eq!(verified, Ok(plaintext.as_slice()));
160 }
161 });
162 }
163
164 #[test]
169 fn snapshot_compat() {
170 #[rustfmt::skip]
172 let snapshot: (&[u8; 32], &[u8; 32], &[&[u8]], &[u8], &[u8]) = (
173 &[194, 128, 19, 113, 226, 250, 32, 233, 155, 123, 134, 83, 255, 2, 82, 142, 75, 252, 132, 118, 107, 203, 153, 82, 115, 146, 100, 251, 113, 66, 0, 164],
174 &[205, 174, 107, 153, 218, 56, 43, 171, 131, 97, 216, 115, 245, 148, 158, 129, 94, 121, 23, 95, 106, 195, 55, 241, 87, 91, 201, 34, 195, 166, 69, 6],
175 &[&[174, 176, 193, 58, 9, 240, 69, 146, 14, 115, 42, 112, 120, 75, 200, 60, 90, 76, 223, 99, 120, 142, 248, 60, 152, 44, 128, 156, 129, 120, 142, 140, 156, 82, 164, 216, 173, 162, 141, 135, 248, 121, 233, 183, 248, 239]],
176 &[83, 53, 240, 125, 240, 64, 242, 207, 18, 70, 169, 92, 124, 37, 237, 27, 74, 139, 179, 117, 7, 130, 0, 146, 156, 249, 151, 75, 95, 75, 63, 189, 200, 167, 18, 13, 71, 25, 233, 20, 37, 164, 37, 218, 139, 161, 150, 85, 220, 238, 118, 18, 125, 54, 15, 143, 217, 202, 150],
177 &[83, 53, 240, 125, 240, 64, 242, 207, 18, 70, 169, 92, 124, 37, 237, 27, 74, 139, 179, 117, 7, 130, 0, 146, 156, 249, 151, 75, 95, 75, 63, 189, 200, 167, 18, 13, 71, 25, 233, 20, 37, 164, 37, 218, 139, 161, 150, 85, 220, 238, 118, 18, 125, 54, 15, 143, 217, 202, 150, 196, 167, 246, 228, 49, 114, 137, 165, 170, 79, 139, 49, 167, 196, 122, 69, 100, 33, 29, 234, 82, 57, 56, 152, 154, 75, 5, 71, 191, 149, 235, 183],
178 );
179 let (seed, domain, aad, plaintext, expected) = snapshot;
180
181 let key = Key::from_seed(seed);
182 let current =
183 key.sign_and_append(domain, aad, Some(plaintext.len()), &|out| {
184 out.extend_from_slice(plaintext)
185 });
186 assert_eq!(current, expected);
187
188 #[rustfmt::skip]
190 let snapshot: (&[u8; 32], &[u8; 32], &[&[u8]], &[u8], &[u8]) = (
191 &[173, 43, 12, 81, 7, 247, 91, 248, 211, 206, 87, 20, 13, 237, 65, 121, 194, 68, 248, 183, 167, 235, 201, 126, 239, 216, 89, 221, 132, 172, 69, 226],
192 &[46, 15, 88, 59, 109, 35, 131, 252, 22, 35, 193, 134, 136, 203, 32, 176, 160, 171, 73, 196, 15, 72, 242, 0, 45, 159, 65, 90, 45, 136, 92, 120],
193 &[&[81, 236, 111, 72, 77, 218, 20, 177, 176, 57, 243, 157, 41, 66, 15, 187, 164, 173, 85, 143, 4, 64, 213, 206, 147, 249, 6, 148, 65, 126, 77, 173, 183, 143, 87], &[105, 23, 87, 33, 135, 201, 32, 41, 87, 167, 87, 13, 94, 30, 49, 167, 56, 72, 36, 202, 75, 33, 241, 107, 84, 105, 255, 237, 27], &[32, 79, 153, 80, 253, 150, 181, 151, 5, 112, 247, 90, 28, 142, 64, 136, 139, 2, 46, 41, 173, 1, 174, 29, 107, 101, 139, 140, 94, 217, 180, 96, 73, 161, 157, 240, 11, 56, 121, 96, 151, 110, 188, 67, 45, 203, 13, 236, 32, 157, 220]],
194 &[110, 192, 94, 70, 121, 234, 52, 58, 15, 101, 180, 91, 21, 151, 236, 105, 62, 37, 67, 205, 90, 214, 117, 146, 201, 123, 167, 61, 7, 175, 67, 81, 80, 175, 92, 167, 250, 73, 179, 2, 207, 253, 31, 145, 122, 75],
195 &[110, 192, 94, 70, 121, 234, 52, 58, 15, 101, 180, 91, 21, 151, 236, 105, 62, 37, 67, 205, 90, 214, 117, 146, 201, 123, 167, 61, 7, 175, 67, 81, 80, 175, 92, 167, 250, 73, 179, 2, 207, 253, 31, 145, 122, 75, 237, 12, 73, 125, 242, 196, 242, 81, 49, 29, 160, 90, 115, 80, 173, 136, 168, 219, 49, 154, 171, 71, 205, 17, 180, 187, 62, 82, 19, 35, 81, 33],
196 );
197 let (seed, domain, aad, plaintext, expected) = snapshot;
198
199 let key = Key::from_seed(seed);
200 let current =
201 key.sign_and_append(domain, aad, Some(plaintext.len()), &|out| {
202 out.extend_from_slice(plaintext)
203 });
204 assert_eq!(current, expected);
205
206 #[rustfmt::skip]
208 let snapshot: (&[u8; 32], &[u8; 32], &[&[u8]], &[u8], &[u8]) = (
209 &[52, 174, 187, 80, 227, 50, 89, 135, 176, 155, 248, 40, 194, 93, 212, 236, 30, 160, 67, 46, 160, 123, 82, 81, 33, 150, 189, 122, 25, 221, 254, 139],
210 &[100, 169, 17, 187, 44, 171, 149, 246, 126, 214, 71, 249, 146, 242, 233, 251, 1, 116, 102, 161, 118, 195, 39, 95, 171, 208, 109, 71, 119, 22, 8, 249],
211 &[&[239, 141, 103, 219, 18, 254, 151, 204, 227, 159, 19, 138, 196], &[251, 82, 191, 138, 33, 129, 155, 205, 76, 19, 58, 114, 156]],
212 &[134, 159, 173, 109, 127, 231, 204, 230, 202, 129, 221, 5, 107, 202, 192, 165, 193, 216, 149, 124, 16, 9, 198, 65, 100, 154, 19, 150, 1, 93, 182, 253, 104, 63, 32, 150, 11, 174, 103, 185, 145, 255, 222, 242, 212, 13, 10, 67, 81, 152, 160, 50, 169, 45, 178, 241],
213 &[134, 159, 173, 109, 127, 231, 204, 230, 202, 129, 221, 5, 107, 202, 192, 165, 193, 216, 149, 124, 16, 9, 198, 65, 100, 154, 19, 150, 1, 93, 182, 253, 104, 63, 32, 150, 11, 174, 103, 185, 145, 255, 222, 242, 212, 13, 10, 67, 81, 152, 160, 50, 169, 45, 178, 241, 59, 126, 52, 102, 153, 18, 146, 129, 184, 46, 203, 30, 136, 181, 142, 128, 198, 66, 61, 227, 243, 15, 246, 190, 243, 138, 19, 227, 134, 141, 56, 184],
214 );
215 let (seed, domain, aad, plaintext, expected) = snapshot;
216
217 let key = Key::from_seed(seed);
218 let current =
219 key.sign_and_append(domain, aad, Some(plaintext.len()), &|out| {
220 out.extend_from_slice(plaintext)
221 });
222 assert_eq!(current, expected);
223 }
224
225 #[test]
226 fn verify_rejects_tampering() {
227 let key = Key::from_seed(&[42u8; 32]);
228 let domain = [7u8; 32];
229 let aad: &[&[u8]] = &[b"aad1", b"aad2"];
230
231 let signed = sign(&key, &domain, aad, "hello world");
232
233 for i in 0..signed.len() {
234 let mut tampered = signed.clone();
235 tampered[i] ^= 1;
236 key.verify(&domain, aad, &tampered).unwrap_err();
237 }
238 }
239
240 #[test]
241 fn verify_rejects_wrong_key_domain_or_aad() {
242 let key = Key::from_seed(&[1u8; 32]);
243 let wrong_key = Key::from_seed(&[2u8; 32]);
244
245 let domain = [7u8; 32];
246 let wrong_domain = [8u8; 32];
247
248 let aad: &[&[u8]] = &[b"aad1", b"aad2"];
249 let wrong_aad: &[&[u8]] = &[b"aad1", b"aad3"];
250
251 let signed = sign(&key, &domain, aad, "hello world");
252
253 wrong_key.verify(&domain, aad, &signed).unwrap_err();
255
256 key.verify(&wrong_domain, aad, &signed).unwrap_err();
258
259 key.verify(&domain, wrong_aad, &signed).unwrap_err();
261 key.verify(&domain, &[], &signed).unwrap_err();
262 }
263
264 #[test]
267 fn aad_serialization_is_unambiguous() {
268 let key = Key::from_seed(&[42u8; 32]);
269 let domain = [7u8; 32];
270
271 let signed = sign(&key, &domain, &[b"ab", b"c"], "hello world");
272
273 key.verify(&domain, &[b"a", b"bc"], &signed).unwrap_err();
275 key.verify(&domain, &[b"abc"], &signed).unwrap_err();
276 }
277
278 #[test]
279 fn verify_rejects_too_short() {
280 let key = Key::from_seed(&[42u8; 32]);
281 let domain = [7u8; 32];
282 let aad: &[&[u8]] = &[b"aad1", b"aad2"];
283
284 key.verify(&domain, aad, &[0u8; TAG_LEN - 1]).unwrap_err();
285 key.verify(&domain, aad, &[]).unwrap_err();
286 }
287
288 #[ignore]
295 #[test]
296 fn print_snapshot_data() {
297 let mut rng = FastRng::from_u64(202608041547);
298
299 let runner_rng = TestRng::from_seed(
300 test_runner::RngAlgorithm::ChaCha,
301 &rng.gen_bytes::<32>(),
302 );
303 let mut runner = TestRunner::new_with_rng(
304 test_runner::Config::default(),
305 runner_rng,
306 );
307
308 const NUM_CASES: usize = 3;
309 let strategy = (
310 any::<[u8; 32]>(), any::<[u8; 32]>(), vec(vec(any::<u8>(), 0..64), 0..=5), vec(any::<u8>(), 0..64), );
315 for i in 0..NUM_CASES {
316 let (seed, domain, aad, plaintext) =
317 strategy.new_tree(&mut runner).unwrap().current();
318 let key = Key::from_seed(&seed);
319 let aad = aad.iter().map(Vec::as_slice).collect::<Vec<&[u8]>>();
320 let signed = key.sign_and_append(
321 &domain,
322 &aad,
323 Some(plaintext.len()),
324 &|out| out.extend_from_slice(&plaintext),
325 );
326 const PAD: usize = "plaintext".len();
327 println!("--- ({i}) ---");
328 println!("{:<PAD$} : {seed:?}", "seed");
329 println!("{:<PAD$} : {domain:?}", "domain");
330 println!("{:<PAD$} : {aad:?}", "aad");
331 println!("{:<PAD$} : {plaintext:?}", "plaintext");
332 println!("{:<PAD$} : {signed:?}", "signed");
333 }
334 }
335}