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 pub fn new_portable(key: &[u8]) -> Result<Self> {
303 ensure!(key.len() == $keylen, InvalidLength, $id);
304 Ok(Self(Keys::Portable(portable::Schedule::expand(key)?)))
305 }
306 }
307
308 impl Algorithm for $name {
309 const ID: &'static str = $id;
310 const NAME: &'static str = $disp;
311 }
312
313 impl BlockCipher for $name {
314 const BLOCK_LEN: usize = BLOCK_LEN;
315 const KEY_LEN: usize = $keylen;
316
317 fn new(key: &[u8]) -> Result<Self> {
318 ensure!(key.len() == $keylen, InvalidLength, $id);
319 Ok(Self(expand(key)?))
320 }
321
322 fn encrypt_block(&self, block: &mut [u8]) -> Result<()> {
323 self.0.encrypt_block(block)
324 }
325
326 fn decrypt_block(&self, block: &mut [u8]) -> Result<()> {
327 self.0.decrypt_block(block)
328 }
329
330 fn encrypt_blocks(&self, data: &mut [u8]) -> Result<()> {
331 self.0.encrypt_blocks(data)
332 }
333 }
334
335 impl crate::gcm::Ctr32 for $name {
336 fn ctr32_xor(&self, counter: &mut [u8; BLOCK_LEN], data: &mut [u8]) -> Result<()> {
337 #[cfg(any(
338 all(any(target_arch = "x86", target_arch = "x86_64"), feature = "std"),
339 all(
340 any(target_arch = "x86", target_arch = "x86_64"),
341 not(feature = "std"),
342 target_feature = "aes"
343 )
344 ))]
345 if let Keys::Aesni(keys) = &self.0 {
346 unsafe { x86::ctr32_xor(keys, counter, data) };
348 return Ok(());
349 }
350 crate::gcm::ctr32_xor_generic(self, counter, data)
351 }
352 }
353
354 impl SelfTest for $name {
355 fn self_test() -> Result<()> {
356 let mut key = [0u8; $keylen];
358 ic_core::codec::hex_decode($kat_key.as_bytes(), &mut key)?;
359 let mut want = [0u8; 16];
360 ic_core::codec::hex_decode($kat_ct.as_bytes(), &mut want)?;
361
362 let cipher = <Self as BlockCipher>::new(&key)?;
363 let mut block: [u8; 16] = core::array::from_fn(|i| (i * 0x11) as u8);
364 cipher.encrypt_block(&mut block)?;
365 ensure!(ic_core::ct::verify(&want, &block), SelfTestFailed, $id);
366
367 cipher.decrypt_block(&mut block)?;
368 let plain: [u8; 16] = core::array::from_fn(|i| (i * 0x11) as u8);
369 ensure!(ic_core::ct::verify(&plain, &block), SelfTestFailed, $id);
370
371 let reference = Self::new_portable(&key)?;
375 let mut a: [u8; 16] = core::array::from_fn(|i| (i * 0x11) as u8);
376 reference.encrypt_block(&mut a)?;
377 ensure!(ic_core::ct::verify(&want, &a), SelfTestFailed, $id);
378 Ok(())
379 }
380 }
381 };
382}
383
384aes_variant!(
385 Aes128,
386 "aes-128",
387 "AES-128",
388 16,
389 "000102030405060708090a0b0c0d0e0f",
390 "69c4e0d86a7b0430d8cdb78070b4c55a"
391);
392aes_variant!(
393 Aes192,
394 "aes-192",
395 "AES-192",
396 24,
397 "000102030405060708090a0b0c0d0e0f1011121314151617",
398 "dda97ca4864cdfe06eaf70a0ec0d7191"
399);
400aes_variant!(
401 Aes256,
402 "aes-256",
403 "AES-256",
404 32,
405 "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
406 "8ea2b7ca516745bfeafc49904b496089"
407);
408
409#[cfg(test)]
410mod tests {
411 use super::*;
412 use ic_core::codec::{hex, unhex};
413
414 fn enc<C: BlockCipher>(key: &str, pt: &str) -> String {
415 let c = C::new(&unhex(key).unwrap()).unwrap();
416 let mut b = unhex(pt).unwrap();
417 c.encrypt_block(&mut b).unwrap();
418 hex(&b)
419 }
420
421 #[test]
422 fn fips197_appendix_c_vectors() {
423 assert_eq!(
424 enc::<Aes128>(
425 "000102030405060708090a0b0c0d0e0f",
426 "00112233445566778899aabbccddeeff"
427 ),
428 "69c4e0d86a7b0430d8cdb78070b4c55a"
429 );
430 assert_eq!(
431 enc::<Aes192>(
432 "000102030405060708090a0b0c0d0e0f1011121314151617",
433 "00112233445566778899aabbccddeeff"
434 ),
435 "dda97ca4864cdfe06eaf70a0ec0d7191"
436 );
437 assert_eq!(
438 enc::<Aes256>(
439 "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
440 "00112233445566778899aabbccddeeff"
441 ),
442 "8ea2b7ca516745bfeafc49904b496089"
443 );
444 }
445
446 #[test]
449 fn sp800_38a_ecb_vector() {
450 assert_eq!(
451 enc::<Aes128>(
452 "2b7e151628aed2a6abf7158809cf4f3c",
453 "6bc1bee22e409f96e93d7e117393172a"
454 ),
455 "3ad77bb40d7a3660a89ecaf32466ef97"
456 );
457 }
458
459 #[test]
460 fn decryption_inverts_encryption() {
461 let key = [0x42u8; 32];
462 let c = Aes256::new(&key).unwrap();
463 let original: [u8; 16] = core::array::from_fn(|i| (i * 13) as u8);
464 let mut block = original;
465 c.encrypt_block(&mut block).unwrap();
466 assert_ne!(block, original);
467 c.decrypt_block(&mut block).unwrap();
468 assert_eq!(block, original);
469 }
470
471 #[test]
472 fn rejects_wrong_key_and_block_lengths() {
473 assert!(Aes128::new(&[0u8; 17]).is_err());
474 assert!(Aes256::new(&[0u8; 16]).is_err());
475 let c = Aes128::new(&[0u8; 16]).unwrap();
476 assert!(c.encrypt_block(&mut [0u8; 15]).is_err());
477 assert!(c.encrypt_blocks(&mut [0u8; 17]).is_err());
478 }
479
480 #[test]
485 fn active_backend_agrees_with_portable() {
486 for key_len in [16usize, 24, 32] {
487 let key: Vec<u8> = (0..key_len).map(|i| (i * 11 + 3) as u8).collect();
488
489 macro_rules! compare {
490 ($ty:ty) => {{
491 let fast = <$ty>::new(&key).unwrap();
492 let slow = <$ty>::new_portable(&key).unwrap();
493 for seed in 0..32u8 {
494 let original: [u8; 16] = core::array::from_fn(|i| seed ^ (i as u8 * 17));
495 let mut a = original;
496 let mut b = original;
497 fast.encrypt_block(&mut a).unwrap();
498 slow.encrypt_block(&mut b).unwrap();
499 assert_eq!(a, b, "encrypt, key_len {}", key_len);
500
501 let mut a = original;
502 let mut b = original;
503 fast.decrypt_block(&mut a).unwrap();
504 slow.decrypt_block(&mut b).unwrap();
505 assert_eq!(a, b, "decrypt, key_len {}", key_len);
506 }
507 }};
508 }
509 match key_len {
510 16 => compare!(Aes128),
511 24 => compare!(Aes192),
512 _ => compare!(Aes256),
513 }
514 }
515 }
516
517 #[test]
532 fn portable_batch_matches_single_blocks_at_every_length() {
533 for key_len in [16usize, 24, 32] {
534 let key: Vec<u8> = (0..key_len).map(|i| (i * 13 + 1) as u8).collect();
535
536 macro_rules! compare {
537 ($ty:ty) => {{
538 let c = <$ty>::new_portable(&key).unwrap();
539 for blocks in 0..13usize {
540 let data: Vec<u8> =
541 (0..blocks * BLOCK_LEN).map(|i| (i * 7 + 3) as u8).collect();
542
543 let mut batched = data.clone();
544 c.encrypt_blocks(&mut batched).unwrap();
545
546 let mut singly = data.clone();
547 for block in singly.chunks_exact_mut(BLOCK_LEN) {
548 c.encrypt_block(block).unwrap();
549 }
550 assert_eq!(batched, singly, "key_len {}, {} blocks", key_len, blocks);
551 }
552 }};
553 }
554 match key_len {
555 16 => compare!(Aes128),
556 24 => compare!(Aes192),
557 _ => compare!(Aes256),
558 }
559 }
560 }
561
562 #[test]
563 fn batch_matches_single_block() {
564 let c = Aes256::new(&[0x2bu8; 32]).unwrap();
565 for blocks in 0..20usize {
566 let data: Vec<u8> = (0..blocks * BLOCK_LEN).map(|i| (i * 7) as u8).collect();
567
568 let mut batched = data.clone();
569 c.encrypt_blocks(&mut batched).unwrap();
570
571 let mut singly = data.clone();
572 for block in singly.chunks_exact_mut(BLOCK_LEN) {
573 c.encrypt_block(block).unwrap();
574 }
575 assert_eq!(batched, singly, "{blocks} blocks");
576 }
577 }
578
579 #[test]
580 fn backend_is_reported_consistently() {
581 let c = Aes128::new(&[0u8; 16]).unwrap();
582 assert_eq!(c.backend(), active_backend());
583 assert_eq!(
584 Aes128::new_portable(&[0u8; 16]).unwrap().backend(),
585 Backend::Portable
586 );
587 assert_eq!(Backend::Portable.id(), "portable-constant-time");
588 }
589
590 #[test]
591 fn self_tests_pass() {
592 Aes128::self_test().unwrap();
593 Aes192::self_test().unwrap();
594 Aes256::self_test().unwrap();
595 }
596}