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