1#![allow(clippy::needless_range_loop)]
20
21use crate::aes::{Aes128, Aes192, Aes256, BLOCK_LEN};
22use crate::modes::increment_be32;
23use ic_core::traits::{Aead, Algorithm, BlockCipher, SelfTest};
24use ic_core::{ensure, Result, Zeroize};
25
26const R: u8 = 0xe1;
28
29pub struct GcmLimits;
33
34impl GcmLimits {
35 pub const MAX_PLAINTEXT_BYTES: u64 = (1 << 36) - 32;
37 pub const MAX_RANDOM_NONCE_INVOCATIONS: u64 = 1 << 32;
39 pub const RECOMMENDED_NONCE_LEN: usize = 12;
42}
43
44#[inline]
50#[must_use]
51pub fn ghash_accelerated() -> bool {
52 ic_core::cpu::has_ghash_clmul()
53}
54
55pub(crate) fn portable_ghash_mul(x: &mut [u8; BLOCK_LEN], h: &[u8; BLOCK_LEN]) {
60 let mut z = [0u8; BLOCK_LEN];
61 let mut v = *h;
62 for i in 0..128 {
63 let bit = (x[i / 8] >> (7 - (i % 8))) & 1;
64 let m = bit.wrapping_neg();
65 for j in 0..BLOCK_LEN {
66 z[j] ^= v[j] & m;
67 }
68 let lsb = v[BLOCK_LEN - 1] & 1;
70 let mut carry = 0u8;
71 for byte in v.iter_mut() {
72 let next = *byte & 1;
73 *byte = (*byte >> 1) | (carry << 7);
74 carry = next;
75 }
76 v[0] ^= R & lsb.wrapping_neg();
77 }
78 *x = z;
79 z.zeroize();
80 v.zeroize();
81}
82
83struct Ghash {
85 h: [u8; BLOCK_LEN],
86 powers: [[u8; BLOCK_LEN]; 3],
102 acc: [u8; BLOCK_LEN],
103 #[cfg(all(target_arch = "x86_64", feature = "std"))]
109 accelerated: bool,
110}
111
112impl Ghash {
113 fn new(h: [u8; BLOCK_LEN]) -> Self {
114 let mut me = Self {
115 h,
116 powers: [[0u8; BLOCK_LEN]; 3],
117 acc: [0u8; BLOCK_LEN],
118 #[cfg(all(target_arch = "x86_64", feature = "std"))]
119 accelerated: ghash_accelerated(),
120 };
121 let mut p = h;
124 for slot in 0..3 {
125 me.mul_by(&mut p, &h);
126 me.powers[slot] = p;
127 }
128 me
129 }
130
131 #[inline]
133 fn mul_by(&self, x: &mut [u8; BLOCK_LEN], y: &[u8; BLOCK_LEN]) {
134 #[cfg(all(target_arch = "x86_64", feature = "std"))]
135 if self.accelerated {
136 unsafe { crate::clmul::mul(x, y) };
139 return;
140 }
141 portable_ghash_mul(x, y);
142 }
143
144 #[inline]
148 fn absorb4(&mut self, blocks: &[u8]) {
149 debug_assert_eq!(blocks.len(), BLOCK_LEN * 4);
150 let mut terms = [[0u8; BLOCK_LEN]; 4];
151 for (i, t) in terms.iter_mut().enumerate() {
152 t.copy_from_slice(&blocks[i * BLOCK_LEN..(i + 1) * BLOCK_LEN]);
153 }
154 for j in 0..BLOCK_LEN {
157 terms[0][j] ^= self.acc[j];
158 }
159 let multipliers = [&self.powers[2], &self.powers[1], &self.powers[0], &self.h];
160 for (t, m) in terms.iter_mut().zip(multipliers) {
161 self.mul_by(t, m);
162 }
163 self.acc = terms[0];
164 for t in &terms[1..] {
165 for j in 0..BLOCK_LEN {
166 self.acc[j] ^= t[j];
167 }
168 }
169 }
170
171 #[inline]
173 fn mul_acc(&mut self) {
174 #[cfg(all(target_arch = "x86_64", feature = "std"))]
175 if self.accelerated {
176 unsafe { crate::clmul::mul(&mut self.acc, &self.h) };
179 return;
180 }
181 portable_ghash_mul(&mut self.acc, &self.h);
182 }
183
184 fn update_padded(&mut self, mut data: &[u8]) {
186 while data.len() >= BLOCK_LEN * 4 {
189 self.absorb4(&data[..BLOCK_LEN * 4]);
190 data = &data[BLOCK_LEN * 4..];
191 }
192 for chunk in data.chunks(BLOCK_LEN) {
193 let mut block = [0u8; BLOCK_LEN];
194 block[..chunk.len()].copy_from_slice(chunk);
195 for j in 0..BLOCK_LEN {
196 self.acc[j] ^= block[j];
197 }
198 self.mul_acc();
199 }
200 }
201
202 fn finalize(self) -> [u8; BLOCK_LEN] {
203 self.acc
204 }
205}
206
207impl Drop for Ghash {
208 fn drop(&mut self) {
209 self.h.zeroize();
210 self.acc.zeroize();
211 }
212}
213
214fn derive_j0(nonce: &[u8], h: &[u8; BLOCK_LEN]) -> [u8; BLOCK_LEN] {
216 if nonce.len() == 12 {
217 let mut j0 = [0u8; BLOCK_LEN];
218 j0[..12].copy_from_slice(nonce);
219 j0[15] = 1;
220 j0
221 } else {
222 let mut g = Ghash::new(*h);
223 g.update_padded(nonce);
224 let mut len_block = [0u8; BLOCK_LEN];
225 len_block[8..].copy_from_slice(&((nonce.len() as u64) * 8).to_be_bytes());
226 g.update_padded(&len_block);
227 g.finalize()
228 }
229}
230
231fn gcm_core<C: BlockCipher>(
233 cipher: &C,
234 nonce: &[u8],
235 aad: &[u8],
236 in_out: &mut [u8],
237 encrypting: bool,
238) -> Result<[u8; BLOCK_LEN]> {
239 ensure!(
240 !nonce.is_empty(),
241 InvalidParameter,
242 "gcm nonce must be non-empty"
243 );
244 ensure!(
245 in_out.len() as u64 <= GcmLimits::MAX_PLAINTEXT_BYTES,
246 CounterExhausted,
247 "gcm plaintext exceeds 2^39-256 bits"
248 );
249
250 let mut h = [0u8; BLOCK_LEN];
252 cipher.encrypt_block(&mut h)?;
253
254 let j0 = derive_j0(nonce, &h);
255
256 let mut g = Ghash::new(h);
259 g.update_padded(aad);
260 if !encrypting {
261 g.update_padded(in_out);
262 }
263
264 const CTR_BATCH: usize = 8;
267 let mut counter = j0;
268 increment_be32(&mut counter);
269 let mut keystream = [0u8; BLOCK_LEN * CTR_BATCH];
270 for chunk in in_out.chunks_mut(BLOCK_LEN * CTR_BATCH) {
271 let blocks = chunk.len().div_ceil(BLOCK_LEN);
272 for i in 0..blocks {
273 keystream[i * BLOCK_LEN..(i + 1) * BLOCK_LEN].copy_from_slice(&counter);
274 increment_be32(&mut counter);
275 }
276 cipher.encrypt_blocks(&mut keystream[..blocks * BLOCK_LEN])?;
277 for (d, k) in chunk.iter_mut().zip(keystream.iter()) {
278 *d ^= k;
279 }
280 }
281 keystream.zeroize();
282
283 if encrypting {
284 g.update_padded(in_out);
285 }
286
287 let mut len_block = [0u8; BLOCK_LEN];
288 len_block[..8].copy_from_slice(&((aad.len() as u64) * 8).to_be_bytes());
289 len_block[8..].copy_from_slice(&((in_out.len() as u64) * 8).to_be_bytes());
290 g.update_padded(&len_block);
291
292 let mut tag = g.finalize();
293 let mut ek_j0 = j0;
294 cipher.encrypt_block(&mut ek_j0)?;
295 for j in 0..BLOCK_LEN {
296 tag[j] ^= ek_j0[j];
297 }
298 ek_j0.zeroize();
299 h.zeroize();
300 Ok(tag)
301}
302
303macro_rules! aes_gcm {
304 ($name:ident, $inner:ty, $id:literal, $disp:literal, $keylen:literal) => {
305 #[doc = concat!("SP 800-38D ", $disp, ".")]
306 pub struct $name($inner);
307
308 impl Algorithm for $name {
309 const ID: &'static str = $id;
310 const NAME: &'static str = $disp;
311 }
312
313 impl Aead for $name {
314 const KEY_LEN: usize = $keylen;
315 const NONCE_LEN: usize = 12;
316 const TAG_LEN: usize = 16;
317
318 fn new(key: &[u8]) -> Result<Self> {
319 Ok(Self(<$inner as BlockCipher>::new(key)?))
320 }
321
322 fn seal_detached(
323 &self,
324 nonce: &[u8],
325 aad: &[u8],
326 in_out: &mut [u8],
327 tag: &mut [u8],
328 ) -> Result<()> {
329 ensure!(tag.len() == 16, InvalidLength, "gcm tag buffer");
330 let t = gcm_core(&self.0, nonce, aad, in_out, true)?;
331 tag.copy_from_slice(&t);
332 Ok(())
333 }
334
335 fn open_detached(
336 &self,
337 nonce: &[u8],
338 aad: &[u8],
339 in_out: &mut [u8],
340 tag: &[u8],
341 ) -> Result<()> {
342 ensure!(tag.len() == 16, InvalidLength, "gcm tag");
343 let expected = gcm_core(&self.0, nonce, aad, in_out, false)?;
344 if ic_core::ct::verify(&expected, tag) {
345 Ok(())
346 } else {
347 in_out.zeroize();
349 Err(ic_core::err!(AuthenticationFailed, $id))
350 }
351 }
352 }
353
354 impl SelfTest for $name {
355 fn self_test() -> Result<()> {
356 let key = [0u8; $keylen];
357 let nonce = [0u8; 12];
358 let c = <Self as Aead>::new(&key)?;
359 let mut buf = [0u8; 16];
360 let mut tag = [0u8; 16];
361 c.seal_detached(&nonce, &[], &mut buf, &mut tag)?;
362 c.open_detached(&nonce, &[], &mut buf, &tag)?;
363 ensure!(buf == [0u8; 16], SelfTestFailed, $id);
364 tag[0] ^= 1;
366 ensure!(
367 c.open_detached(&nonce, &[], &mut buf, &tag).is_err(),
368 SelfTestFailed,
369 $id
370 );
371 Ok(())
372 }
373 }
374 };
375}
376
377aes_gcm!(Aes128Gcm, Aes128, "aes-128-gcm", "AES-128-GCM", 16);
378aes_gcm!(Aes192Gcm, Aes192, "aes-192-gcm", "AES-192-GCM", 24);
379aes_gcm!(Aes256Gcm, Aes256, "aes-256-gcm", "AES-256-GCM", 32);
380
381#[cfg(test)]
382mod tests {
383 use super::*;
384
385 #[test]
394 fn the_batched_ghash_agrees_with_the_serial_one() {
395 let h = [
396 0x66, 0xe9, 0x4b, 0xd4, 0xef, 0x8a, 0x2c, 0x3b, 0x88, 0x4c, 0xfa, 0x59, 0xca, 0x34,
397 0x2b, 0x2e,
398 ];
399
400 let mut checked = 0;
401 for len in [
403 0usize, 1, 15, 16, 17, 31, 63, 64, 65, 79, 80, 127, 128, 129, 255, 256, 1024, 1025,
404 ] {
405 let data: std::vec::Vec<u8> = (0..len)
406 .map(|i| ((i as u64).wrapping_mul(0x9e37_79b9) >> 3) as u8)
407 .collect();
408
409 let mut batched = Ghash::new(h);
410 batched.update_padded(&data);
411
412 let mut serial = Ghash::new(h);
414 for chunk in data.chunks(BLOCK_LEN) {
415 let mut block = [0u8; BLOCK_LEN];
416 block[..chunk.len()].copy_from_slice(chunk);
417 for j in 0..BLOCK_LEN {
418 serial.acc[j] ^= block[j];
419 }
420 serial.mul_acc();
421 }
422
423 assert_eq!(
424 batched.acc, serial.acc,
425 "batched and serial GHASH disagree at {len} bytes"
426 );
427 checked += 1;
428 }
429 assert_eq!(checked, 18, "the comparison did not run");
430
431 let long = std::vec![0xa5u8; BLOCK_LEN * 4];
434 let mut g = Ghash::new(h);
435 g.absorb4(&long);
436 let mut serial = Ghash::new(h);
437 for chunk in long.chunks(BLOCK_LEN) {
438 for j in 0..BLOCK_LEN {
439 serial.acc[j] ^= chunk[j];
440 }
441 serial.mul_acc();
442 }
443 assert_eq!(g.acc, serial.acc, "absorb4 alone disagrees with four steps");
444 }
445
446 #[test]
448 fn the_precomputed_powers_are_powers_of_h() {
449 let h = [0x3cu8; BLOCK_LEN];
450 let g = Ghash::new(h);
451 let mut expect = h;
452 for (i, stored) in g.powers.iter().enumerate() {
453 g.mul_by(&mut expect, &h);
454 assert_eq!(*stored, expect, "power {} is not H^{}", i, i + 2);
455 }
456 assert_ne!(g.powers[0], g.powers[1]);
458 assert_ne!(g.powers[1], g.powers[2]);
459 assert_ne!(g.powers[0], h);
460 }
461 use ic_core::codec::{hex, unhex};
462
463 fn check(key: &str, nonce: &str, pt: &str, aad: &str, ct: &str, tag: &str) {
465 let k = unhex(key).unwrap();
466 let mut buf = unhex(pt).unwrap();
467 let mut got_tag = [0u8; 16];
468 let n = unhex(nonce).unwrap();
469 let a = unhex(aad).unwrap();
470
471 match k.len() {
472 16 => {
473 let c = Aes128Gcm::new(&k).unwrap();
474 c.seal_detached(&n, &a, &mut buf, &mut got_tag).unwrap();
475 }
476 24 => {
477 let c = Aes192Gcm::new(&k).unwrap();
478 c.seal_detached(&n, &a, &mut buf, &mut got_tag).unwrap();
479 }
480 _ => {
481 let c = Aes256Gcm::new(&k).unwrap();
482 c.seal_detached(&n, &a, &mut buf, &mut got_tag).unwrap();
483 }
484 }
485 assert_eq!(hex(&buf), ct, "ciphertext");
486 assert_eq!(hex(&got_tag), tag, "tag");
487 }
488
489 #[test]
490 fn gcm_spec_case_1_empty() {
491 check(
492 "00000000000000000000000000000000",
493 "000000000000000000000000",
494 "",
495 "",
496 "",
497 "58e2fccefa7e3061367f1d57a4e7455a",
498 );
499 }
500
501 #[test]
502 fn gcm_spec_case_2_single_block() {
503 check(
504 "00000000000000000000000000000000",
505 "000000000000000000000000",
506 "00000000000000000000000000000000",
507 "",
508 "0388dace60b6a392f328c2b971b2fe78",
509 "ab6e47d42cec13bdf53a67b21257bddf",
510 );
511 }
512
513 #[test]
517 fn gcm_spec_case_3_multi_block() {
518 check(
519 "feffe9928665731c6d6a8f9467308308",
520 "cafebabefacedbaddecaf888",
521 "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b391aafd255",
522 "",
523 "42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091473f5985",
524 "4d5c2af327cd64a62cf35abd2ba6fab4",
525 );
526 }
527
528 #[test]
529 fn gcm_spec_case_4_with_aad() {
530 check(
531 "feffe9928665731c6d6a8f9467308308",
532 "cafebabefacedbaddecaf888",
533 "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39",
534 "feedfacedeadbeeffeedfacedeadbeefabaddad2",
535 "42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091",
536 "5bc94fbc3221a5db94fae95ae7121a47",
537 );
538 }
539
540 #[test]
542 fn gcm_short_nonce_uses_ghash_j0() {
543 check(
544 "feffe9928665731c6d6a8f9467308308",
545 "cafebabefacedbad",
546 "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39",
547 "feedfacedeadbeeffeedfacedeadbeefabaddad2",
548 "61353b4c2806934a777ff51fa22a4755699b2a714fcdc6f83766e5f97b6c742373806900e49f24b22b097544d4896b424989b5e1ebac0f07c23f4598",
549 "3612d2e79e3b0785561be14aaca2fccb",
550 );
551 }
552
553 #[test]
554 fn aes256_gcm_vector() {
555 check(
556 "feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308",
557 "cafebabefacedbaddecaf888",
558 "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39",
559 "feedfacedeadbeeffeedfacedeadbeefabaddad2",
560 "522dc1f099567d07f47f37a32a84427d643a8cdcbfe5c0c97598a2bd2555d1aa8cb08e48590dbb3da7b08b1056828838c5f61e6393ba7a0abcc9f662",
561 "76fc6ece0f4e1768cddf8853bb2d551b",
562 );
563 }
564
565 #[test]
566 fn roundtrip_and_tamper_detection() {
567 let c = Aes256Gcm::new(&[7u8; 32]).unwrap();
568 let nonce = [9u8; 12];
569 let aad = b"header";
570 let plaintext = b"attack at dawn, bring the ontology";
571
572 let mut buf = plaintext.to_vec();
573 let mut tag = [0u8; 16];
574 c.seal_detached(&nonce, aad, &mut buf, &mut tag).unwrap();
575 assert_ne!(&buf[..], &plaintext[..]);
576
577 let mut ok = buf.clone();
578 c.open_detached(&nonce, aad, &mut ok, &tag).unwrap();
579 assert_eq!(&ok[..], &plaintext[..]);
580
581 let mut bad = buf.clone();
583 bad[0] ^= 1;
584 assert!(c.open_detached(&nonce, aad, &mut bad, &tag).is_err());
585 assert_eq!(
586 bad,
587 vec![0u8; bad.len()],
588 "plaintext must be wiped on failure"
589 );
590
591 let mut wrong_aad = buf.clone();
593 assert!(c
594 .open_detached(&nonce, b"other", &mut wrong_aad, &tag)
595 .is_err());
596
597 let mut wrong_nonce = buf.clone();
599 assert!(c
600 .open_detached(&[0u8; 12], aad, &mut wrong_nonce, &tag)
601 .is_err());
602 }
603
604 #[test]
605 fn self_tests_pass() {
606 Aes128Gcm::self_test().unwrap();
607 Aes192Gcm::self_test().unwrap();
608 Aes256Gcm::self_test().unwrap();
609 }
610}