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