1pub mod bitslice;
29pub mod portable;
30
31#[cfg(any(
35 test,
36 all(target_arch = "aarch64", feature = "aarch64-crypto", feature = "std")
37))]
38mod armv8_rounds;
39
40#[cfg(test)]
44mod armv8_model;
45
46#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "std"))]
47pub mod x86;
48#[cfg(all(
49 any(target_arch = "x86", target_arch = "x86_64"),
50 not(feature = "std"),
51 target_feature = "aes"
52))]
53pub mod x86;
54
55#[cfg(all(target_arch = "aarch64", feature = "aarch64-crypto", feature = "std"))]
56pub mod aarch64;
57
58use ic_core::traits::{Algorithm, BlockCipher, SelfTest};
59use ic_core::{ensure, Result};
60
61pub use portable::BLOCK_LEN;
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub enum Backend {
66 Portable,
68 Aesni,
70 Armv8,
72}
73
74impl Backend {
75 pub const fn id(self) -> &'static str {
77 match self {
78 Self::Portable => "portable-constant-time",
79 Self::Aesni => "aes-ni",
80 Self::Armv8 => "armv8-crypto",
81 }
82 }
83}
84
85#[inline]
87#[must_use]
88pub fn aesni_available() -> bool {
89 ic_core::cpu::has_aes()
92}
93
94#[inline]
103#[must_use]
104pub fn armv8_aes_available() -> bool {
105 #[cfg(all(target_arch = "aarch64", feature = "aarch64-crypto", feature = "std"))]
106 {
107 std::arch::is_aarch64_feature_detected!("aes")
108 }
109 #[cfg(not(all(target_arch = "aarch64", feature = "aarch64-crypto", feature = "std")))]
110 {
111 false
112 }
113}
114
115pub fn active_backend() -> Backend {
117 if armv8_aes_available() {
118 Backend::Armv8
119 } else if aesni_available() {
120 Backend::Aesni
121 } else {
122 Backend::Portable
123 }
124}
125
126#[allow(clippy::large_enum_variant)]
134#[derive(Clone)]
135enum Keys {
136 Portable(portable::Schedule),
137 #[cfg(any(
138 all(any(target_arch = "x86", target_arch = "x86_64"), feature = "std"),
139 all(
140 any(target_arch = "x86", target_arch = "x86_64"),
141 not(feature = "std"),
142 target_feature = "aes"
143 )
144 ))]
145 Aesni(x86::Keys),
146 #[cfg(all(target_arch = "aarch64", feature = "aarch64-crypto", feature = "std"))]
147 Armv8(aarch64::Keys),
148}
149
150fn expand(key: &[u8]) -> Result<Keys> {
152 let sched = portable::Schedule::expand(key)?;
155
156 #[cfg(any(
157 all(any(target_arch = "x86", target_arch = "x86_64"), feature = "std"),
158 all(
159 any(target_arch = "x86", target_arch = "x86_64"),
160 not(feature = "std"),
161 target_feature = "aes"
162 )
163 ))]
164 if aesni_available() {
165 let keys = unsafe { x86::Keys::load(&sched) };
167 return Ok(Keys::Aesni(keys));
168 }
169
170 #[cfg(all(target_arch = "aarch64", feature = "aarch64-crypto", feature = "std"))]
171 if armv8_aes_available() {
172 let keys = unsafe { aarch64::Keys::load(&sched) };
174 return Ok(Keys::Armv8(keys));
175 }
176
177 Ok(Keys::Portable(sched))
178}
179
180impl Keys {
181 #[inline]
182 fn encrypt_block(&self, block: &mut [u8]) -> Result<()> {
183 match self {
184 Keys::Portable(s) => portable::encrypt_block(s, block),
185 #[cfg(any(
186 all(any(target_arch = "x86", target_arch = "x86_64"), feature = "std"),
187 all(
188 any(target_arch = "x86", target_arch = "x86_64"),
189 not(feature = "std"),
190 target_feature = "aes"
191 )
192 ))]
193 Keys::Aesni(k) => unsafe { x86::encrypt_block(k, block) },
195 #[cfg(all(target_arch = "aarch64", feature = "aarch64-crypto", feature = "std"))]
196 Keys::Armv8(k) => unsafe { aarch64::encrypt_block(k, block) },
198 }
199 }
200
201 #[inline]
202 fn decrypt_block(&self, block: &mut [u8]) -> Result<()> {
203 match self {
204 Keys::Portable(s) => portable::decrypt_block(s, block),
205 #[cfg(any(
206 all(any(target_arch = "x86", target_arch = "x86_64"), feature = "std"),
207 all(
208 any(target_arch = "x86", target_arch = "x86_64"),
209 not(feature = "std"),
210 target_feature = "aes"
211 )
212 ))]
213 Keys::Aesni(k) => unsafe { x86::decrypt_block(k, block) },
215 #[cfg(all(target_arch = "aarch64", feature = "aarch64-crypto", feature = "std"))]
216 Keys::Armv8(k) => unsafe { aarch64::decrypt_block(k, block) },
218 }
219 }
220
221 #[inline]
222 fn encrypt_blocks(&self, data: &mut [u8]) -> Result<()> {
223 match self {
224 Keys::Portable(s) => {
225 ensure!(
226 data.len().is_multiple_of(BLOCK_LEN),
227 InvalidLength,
228 "aes batch must be block-aligned"
229 );
230 if data.len() >= bitslice::GROUP {
237 let keys = bitslice::RoundKeys::new(s);
238 let mut groups = data.chunks_exact_mut(bitslice::GROUP);
239 for group in &mut groups {
240 bitslice::encrypt_group(&keys, group);
241 }
242 for block in groups.into_remainder().chunks_exact_mut(BLOCK_LEN) {
243 portable::encrypt_block(s, block)?;
244 }
245 } else {
246 for block in data.chunks_exact_mut(BLOCK_LEN) {
247 portable::encrypt_block(s, block)?;
248 }
249 }
250 Ok(())
251 }
252 #[cfg(any(
253 all(any(target_arch = "x86", target_arch = "x86_64"), feature = "std"),
254 all(
255 any(target_arch = "x86", target_arch = "x86_64"),
256 not(feature = "std"),
257 target_feature = "aes"
258 )
259 ))]
260 Keys::Aesni(k) => unsafe { x86::encrypt_blocks(k, data) },
262 #[cfg(all(target_arch = "aarch64", feature = "aarch64-crypto", feature = "std"))]
263 Keys::Armv8(k) => unsafe { aarch64::encrypt_blocks(k, data) },
265 }
266 }
267
268 fn backend(&self) -> Backend {
269 match self {
270 Keys::Portable(_) => Backend::Portable,
271 #[cfg(any(
272 all(any(target_arch = "x86", target_arch = "x86_64"), feature = "std"),
273 all(
274 any(target_arch = "x86", target_arch = "x86_64"),
275 not(feature = "std"),
276 target_feature = "aes"
277 )
278 ))]
279 Keys::Aesni(_) => Backend::Aesni,
280 #[cfg(all(target_arch = "aarch64", feature = "aarch64-crypto", feature = "std"))]
281 Keys::Armv8(_) => Backend::Armv8,
282 }
283 }
284}
285
286macro_rules! aes_variant {
287 ($name:ident, $id:literal, $disp:literal, $keylen:literal, $kat_key:literal, $kat_ct:literal) => {
288 #[doc = concat!("FIPS 197 ", $disp, ".")]
289 #[derive(Clone)]
290 pub struct $name(Keys);
291
292 impl $name {
293 pub fn backend(&self) -> Backend {
295 self.0.backend()
296 }
297
298 #[cfg(all(target_arch = "x86_64", feature = "std"))]
301 #[inline]
302 pub(crate) fn aesni_keys(&self) -> Option<&x86::Keys> {
303 match &self.0 {
304 Keys::Aesni(k) => Some(k),
305 _ => None,
306 }
307 }
308
309 pub fn new_portable(key: &[u8]) -> Result<Self> {
314 ic_core::module::operational()?;
315 ensure!(key.len() == $keylen, InvalidLength, $id);
316 Ok(Self(Keys::Portable(portable::Schedule::expand(key)?)))
317 }
318 }
319
320 impl Algorithm for $name {
321 const ID: &'static str = $id;
322 const NAME: &'static str = $disp;
323 }
324
325 impl BlockCipher for $name {
326 const BLOCK_LEN: usize = BLOCK_LEN;
327 const KEY_LEN: usize = $keylen;
328
329 fn new(key: &[u8]) -> Result<Self> {
330 ic_core::module::operational()?;
331 ensure!(key.len() == $keylen, InvalidLength, $id);
332 Ok(Self(expand(key)?))
333 }
334
335 fn encrypt_block(&self, block: &mut [u8]) -> Result<()> {
336 self.0.encrypt_block(block)
337 }
338
339 fn decrypt_block(&self, block: &mut [u8]) -> Result<()> {
340 self.0.decrypt_block(block)
341 }
342
343 fn encrypt_blocks(&self, data: &mut [u8]) -> Result<()> {
344 self.0.encrypt_blocks(data)
345 }
346 }
347
348 impl crate::gcm::Ctr32 for $name {
349 fn ctr32_xor(&self, counter: &mut [u8; BLOCK_LEN], data: &mut [u8]) -> Result<()> {
350 #[cfg(any(
351 all(any(target_arch = "x86", target_arch = "x86_64"), feature = "std"),
352 all(
353 any(target_arch = "x86", target_arch = "x86_64"),
354 not(feature = "std"),
355 target_feature = "aes"
356 )
357 ))]
358 if let Keys::Aesni(keys) = &self.0 {
359 unsafe { x86::ctr32_xor(keys, counter, data) };
361 return Ok(());
362 }
363 crate::gcm::ctr32_xor_generic(self, counter, data)
364 }
365 }
366
367 impl SelfTest for $name {
368 fn self_test() -> Result<()> {
369 let mut key = [0u8; $keylen];
371 ic_core::codec::hex_decode($kat_key.as_bytes(), &mut key)?;
372 let mut want = [0u8; 16];
373 ic_core::codec::hex_decode($kat_ct.as_bytes(), &mut want)?;
374
375 let cipher = <Self as BlockCipher>::new(&key)?;
376 let mut block: [u8; 16] = core::array::from_fn(|i| (i * 0x11) as u8);
377 cipher.encrypt_block(&mut block)?;
378 ensure!(ic_core::ct::verify(&want, &block), SelfTestFailed, $id);
379
380 cipher.decrypt_block(&mut block)?;
381 let plain: [u8; 16] = core::array::from_fn(|i| (i * 0x11) as u8);
382 ensure!(ic_core::ct::verify(&plain, &block), SelfTestFailed, $id);
383
384 let reference = Self::new_portable(&key)?;
388 let mut a: [u8; 16] = core::array::from_fn(|i| (i * 0x11) as u8);
389 reference.encrypt_block(&mut a)?;
390 ensure!(ic_core::ct::verify(&want, &a), SelfTestFailed, $id);
391 Ok(())
392 }
393 }
394 };
395}
396
397aes_variant!(
398 Aes128,
399 "aes-128",
400 "AES-128",
401 16,
402 "000102030405060708090a0b0c0d0e0f",
403 "69c4e0d86a7b0430d8cdb78070b4c55a"
404);
405aes_variant!(
406 Aes192,
407 "aes-192",
408 "AES-192",
409 24,
410 "000102030405060708090a0b0c0d0e0f1011121314151617",
411 "dda97ca4864cdfe06eaf70a0ec0d7191"
412);
413aes_variant!(
414 Aes256,
415 "aes-256",
416 "AES-256",
417 32,
418 "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
419 "8ea2b7ca516745bfeafc49904b496089"
420);
421
422#[cfg(test)]
423mod tests {
424 use super::*;
425 use ic_core::codec::{hex, unhex};
426
427 fn enc<C: BlockCipher>(key: &str, pt: &str) -> String {
428 let c = C::new(&unhex(key).unwrap()).unwrap();
429 let mut b = unhex(pt).unwrap();
430 c.encrypt_block(&mut b).unwrap();
431 hex(&b)
432 }
433
434 #[test]
435 fn fips197_appendix_c_vectors() {
436 assert_eq!(
437 enc::<Aes128>(
438 "000102030405060708090a0b0c0d0e0f",
439 "00112233445566778899aabbccddeeff"
440 ),
441 "69c4e0d86a7b0430d8cdb78070b4c55a"
442 );
443 assert_eq!(
444 enc::<Aes192>(
445 "000102030405060708090a0b0c0d0e0f1011121314151617",
446 "00112233445566778899aabbccddeeff"
447 ),
448 "dda97ca4864cdfe06eaf70a0ec0d7191"
449 );
450 assert_eq!(
451 enc::<Aes256>(
452 "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
453 "00112233445566778899aabbccddeeff"
454 ),
455 "8ea2b7ca516745bfeafc49904b496089"
456 );
457 }
458
459 #[test]
462 fn sp800_38a_ecb_vector() {
463 assert_eq!(
464 enc::<Aes128>(
465 "2b7e151628aed2a6abf7158809cf4f3c",
466 "6bc1bee22e409f96e93d7e117393172a"
467 ),
468 "3ad77bb40d7a3660a89ecaf32466ef97"
469 );
470 }
471
472 #[test]
473 fn decryption_inverts_encryption() {
474 let key = [0x42u8; 32];
475 let c = Aes256::new(&key).unwrap();
476 let original: [u8; 16] = core::array::from_fn(|i| (i * 13) as u8);
477 let mut block = original;
478 c.encrypt_block(&mut block).unwrap();
479 assert_ne!(block, original);
480 c.decrypt_block(&mut block).unwrap();
481 assert_eq!(block, original);
482 }
483
484 #[test]
485 fn rejects_wrong_key_and_block_lengths() {
486 assert!(Aes128::new(&[0u8; 17]).is_err());
487 assert!(Aes256::new(&[0u8; 16]).is_err());
488 let c = Aes128::new(&[0u8; 16]).unwrap();
489 assert!(c.encrypt_block(&mut [0u8; 15]).is_err());
490 assert!(c.encrypt_blocks(&mut [0u8; 17]).is_err());
491 }
492
493 #[test]
498 fn active_backend_agrees_with_portable() {
499 for key_len in [16usize, 24, 32] {
500 let key: Vec<u8> = (0..key_len).map(|i| (i * 11 + 3) as u8).collect();
501
502 macro_rules! compare {
503 ($ty:ty) => {{
504 let fast = <$ty>::new(&key).unwrap();
505 let slow = <$ty>::new_portable(&key).unwrap();
506 for seed in 0..32u8 {
507 let original: [u8; 16] = core::array::from_fn(|i| seed ^ (i as u8 * 17));
508 let mut a = original;
509 let mut b = original;
510 fast.encrypt_block(&mut a).unwrap();
511 slow.encrypt_block(&mut b).unwrap();
512 assert_eq!(a, b, "encrypt, key_len {}", key_len);
513
514 let mut a = original;
515 let mut b = original;
516 fast.decrypt_block(&mut a).unwrap();
517 slow.decrypt_block(&mut b).unwrap();
518 assert_eq!(a, b, "decrypt, key_len {}", key_len);
519 }
520 }};
521 }
522 match key_len {
523 16 => compare!(Aes128),
524 24 => compare!(Aes192),
525 _ => compare!(Aes256),
526 }
527 }
528 }
529
530 #[test]
545 fn portable_batch_matches_single_blocks_at_every_length() {
546 for key_len in [16usize, 24, 32] {
547 let key: Vec<u8> = (0..key_len).map(|i| (i * 13 + 1) as u8).collect();
548
549 macro_rules! compare {
550 ($ty:ty) => {{
551 let c = <$ty>::new_portable(&key).unwrap();
552 for blocks in 0..13usize {
553 let data: Vec<u8> =
554 (0..blocks * BLOCK_LEN).map(|i| (i * 7 + 3) as u8).collect();
555
556 let mut batched = data.clone();
557 c.encrypt_blocks(&mut batched).unwrap();
558
559 let mut singly = data.clone();
560 for block in singly.chunks_exact_mut(BLOCK_LEN) {
561 c.encrypt_block(block).unwrap();
562 }
563 assert_eq!(batched, singly, "key_len {}, {} blocks", key_len, blocks);
564 }
565 }};
566 }
567 match key_len {
568 16 => compare!(Aes128),
569 24 => compare!(Aes192),
570 _ => compare!(Aes256),
571 }
572 }
573 }
574
575 #[test]
576 fn batch_matches_single_block() {
577 let c = Aes256::new(&[0x2bu8; 32]).unwrap();
578 for blocks in 0..20usize {
579 let data: Vec<u8> = (0..blocks * BLOCK_LEN).map(|i| (i * 7) as u8).collect();
580
581 let mut batched = data.clone();
582 c.encrypt_blocks(&mut batched).unwrap();
583
584 let mut singly = data.clone();
585 for block in singly.chunks_exact_mut(BLOCK_LEN) {
586 c.encrypt_block(block).unwrap();
587 }
588 assert_eq!(batched, singly, "{blocks} blocks");
589 }
590 }
591
592 #[test]
593 fn backend_is_reported_consistently() {
594 let c = Aes128::new(&[0u8; 16]).unwrap();
595 assert_eq!(c.backend(), active_backend());
596 assert_eq!(
597 Aes128::new_portable(&[0u8; 16]).unwrap().backend(),
598 Backend::Portable
599 );
600 assert_eq!(Backend::Portable.id(), "portable-constant-time");
601 }
602
603 #[test]
604 fn self_tests_pass() {
605 Aes128::self_test().unwrap();
606 Aes192::self_test().unwrap();
607 Aes256::self_test().unwrap();
608 }
609}