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() % BLOCK_LEN == 0,
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 SelfTest for $name {
336 fn self_test() -> Result<()> {
337 let mut key = [0u8; $keylen];
339 ic_core::codec::hex_decode($kat_key.as_bytes(), &mut key)?;
340 let mut want = [0u8; 16];
341 ic_core::codec::hex_decode($kat_ct.as_bytes(), &mut want)?;
342
343 let cipher = <Self as BlockCipher>::new(&key)?;
344 let mut block: [u8; 16] = core::array::from_fn(|i| (i * 0x11) as u8);
345 cipher.encrypt_block(&mut block)?;
346 ensure!(ic_core::ct::verify(&want, &block), SelfTestFailed, $id);
347
348 cipher.decrypt_block(&mut block)?;
349 let plain: [u8; 16] = core::array::from_fn(|i| (i * 0x11) as u8);
350 ensure!(ic_core::ct::verify(&plain, &block), SelfTestFailed, $id);
351
352 let reference = Self::new_portable(&key)?;
356 let mut a: [u8; 16] = core::array::from_fn(|i| (i * 0x11) as u8);
357 reference.encrypt_block(&mut a)?;
358 ensure!(ic_core::ct::verify(&want, &a), SelfTestFailed, $id);
359 Ok(())
360 }
361 }
362 };
363}
364
365aes_variant!(
366 Aes128,
367 "aes-128",
368 "AES-128",
369 16,
370 "000102030405060708090a0b0c0d0e0f",
371 "69c4e0d86a7b0430d8cdb78070b4c55a"
372);
373aes_variant!(
374 Aes192,
375 "aes-192",
376 "AES-192",
377 24,
378 "000102030405060708090a0b0c0d0e0f1011121314151617",
379 "dda97ca4864cdfe06eaf70a0ec0d7191"
380);
381aes_variant!(
382 Aes256,
383 "aes-256",
384 "AES-256",
385 32,
386 "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
387 "8ea2b7ca516745bfeafc49904b496089"
388);
389
390#[cfg(test)]
391mod tests {
392 use super::*;
393 use ic_core::codec::{hex, unhex};
394
395 fn enc<C: BlockCipher>(key: &str, pt: &str) -> String {
396 let c = C::new(&unhex(key).unwrap()).unwrap();
397 let mut b = unhex(pt).unwrap();
398 c.encrypt_block(&mut b).unwrap();
399 hex(&b)
400 }
401
402 #[test]
403 fn fips197_appendix_c_vectors() {
404 assert_eq!(
405 enc::<Aes128>(
406 "000102030405060708090a0b0c0d0e0f",
407 "00112233445566778899aabbccddeeff"
408 ),
409 "69c4e0d86a7b0430d8cdb78070b4c55a"
410 );
411 assert_eq!(
412 enc::<Aes192>(
413 "000102030405060708090a0b0c0d0e0f1011121314151617",
414 "00112233445566778899aabbccddeeff"
415 ),
416 "dda97ca4864cdfe06eaf70a0ec0d7191"
417 );
418 assert_eq!(
419 enc::<Aes256>(
420 "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
421 "00112233445566778899aabbccddeeff"
422 ),
423 "8ea2b7ca516745bfeafc49904b496089"
424 );
425 }
426
427 #[test]
430 fn sp800_38a_ecb_vector() {
431 assert_eq!(
432 enc::<Aes128>(
433 "2b7e151628aed2a6abf7158809cf4f3c",
434 "6bc1bee22e409f96e93d7e117393172a"
435 ),
436 "3ad77bb40d7a3660a89ecaf32466ef97"
437 );
438 }
439
440 #[test]
441 fn decryption_inverts_encryption() {
442 let key = [0x42u8; 32];
443 let c = Aes256::new(&key).unwrap();
444 let original: [u8; 16] = core::array::from_fn(|i| (i * 13) as u8);
445 let mut block = original;
446 c.encrypt_block(&mut block).unwrap();
447 assert_ne!(block, original);
448 c.decrypt_block(&mut block).unwrap();
449 assert_eq!(block, original);
450 }
451
452 #[test]
453 fn rejects_wrong_key_and_block_lengths() {
454 assert!(Aes128::new(&[0u8; 17]).is_err());
455 assert!(Aes256::new(&[0u8; 16]).is_err());
456 let c = Aes128::new(&[0u8; 16]).unwrap();
457 assert!(c.encrypt_block(&mut [0u8; 15]).is_err());
458 assert!(c.encrypt_blocks(&mut [0u8; 17]).is_err());
459 }
460
461 #[test]
466 fn active_backend_agrees_with_portable() {
467 for key_len in [16usize, 24, 32] {
468 let key: Vec<u8> = (0..key_len).map(|i| (i * 11 + 3) as u8).collect();
469
470 macro_rules! compare {
471 ($ty:ty) => {{
472 let fast = <$ty>::new(&key).unwrap();
473 let slow = <$ty>::new_portable(&key).unwrap();
474 for seed in 0..32u8 {
475 let original: [u8; 16] = core::array::from_fn(|i| seed ^ (i as u8 * 17));
476 let mut a = original;
477 let mut b = original;
478 fast.encrypt_block(&mut a).unwrap();
479 slow.encrypt_block(&mut b).unwrap();
480 assert_eq!(a, b, "encrypt, key_len {}", key_len);
481
482 let mut a = original;
483 let mut b = original;
484 fast.decrypt_block(&mut a).unwrap();
485 slow.decrypt_block(&mut b).unwrap();
486 assert_eq!(a, b, "decrypt, key_len {}", key_len);
487 }
488 }};
489 }
490 match key_len {
491 16 => compare!(Aes128),
492 24 => compare!(Aes192),
493 _ => compare!(Aes256),
494 }
495 }
496 }
497
498 #[test]
513 fn portable_batch_matches_single_blocks_at_every_length() {
514 for key_len in [16usize, 24, 32] {
515 let key: Vec<u8> = (0..key_len).map(|i| (i * 13 + 1) as u8).collect();
516
517 macro_rules! compare {
518 ($ty:ty) => {{
519 let c = <$ty>::new_portable(&key).unwrap();
520 for blocks in 0..13usize {
521 let data: Vec<u8> =
522 (0..blocks * BLOCK_LEN).map(|i| (i * 7 + 3) as u8).collect();
523
524 let mut batched = data.clone();
525 c.encrypt_blocks(&mut batched).unwrap();
526
527 let mut singly = data.clone();
528 for block in singly.chunks_exact_mut(BLOCK_LEN) {
529 c.encrypt_block(block).unwrap();
530 }
531 assert_eq!(batched, singly, "key_len {}, {} blocks", key_len, blocks);
532 }
533 }};
534 }
535 match key_len {
536 16 => compare!(Aes128),
537 24 => compare!(Aes192),
538 _ => compare!(Aes256),
539 }
540 }
541 }
542
543 #[test]
544 fn batch_matches_single_block() {
545 let c = Aes256::new(&[0x2bu8; 32]).unwrap();
546 for blocks in 0..20usize {
547 let data: Vec<u8> = (0..blocks * BLOCK_LEN).map(|i| (i * 7) as u8).collect();
548
549 let mut batched = data.clone();
550 c.encrypt_blocks(&mut batched).unwrap();
551
552 let mut singly = data.clone();
553 for block in singly.chunks_exact_mut(BLOCK_LEN) {
554 c.encrypt_block(block).unwrap();
555 }
556 assert_eq!(batched, singly, "{blocks} blocks");
557 }
558 }
559
560 #[test]
561 fn backend_is_reported_consistently() {
562 let c = Aes128::new(&[0u8; 16]).unwrap();
563 assert_eq!(c.backend(), active_backend());
564 assert_eq!(
565 Aes128::new_portable(&[0u8; 16]).unwrap().backend(),
566 Backend::Portable
567 );
568 assert_eq!(Backend::Portable.id(), "portable-constant-time");
569 }
570
571 #[test]
572 fn self_tests_pass() {
573 Aes128::self_test().unwrap();
574 Aes192::self_test().unwrap();
575 Aes256::self_test().unwrap();
576 }
577}