1pub mod portable;
29
30#[cfg(any(
34 test,
35 all(target_arch = "aarch64", feature = "aarch64-crypto", feature = "std")
36))]
37mod armv8_rounds;
38
39#[cfg(test)]
43mod armv8_model;
44
45#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "std"))]
46pub mod x86;
47#[cfg(all(
48 any(target_arch = "x86", target_arch = "x86_64"),
49 not(feature = "std"),
50 target_feature = "aes"
51))]
52pub mod x86;
53
54#[cfg(all(target_arch = "aarch64", feature = "aarch64-crypto", feature = "std"))]
55pub mod aarch64;
56
57use ic_core::traits::{Algorithm, BlockCipher, SelfTest};
58use ic_core::{ensure, Result};
59
60pub use portable::BLOCK_LEN;
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64pub enum Backend {
65 Portable,
67 Aesni,
69 Armv8,
71}
72
73impl Backend {
74 pub const fn id(self) -> &'static str {
76 match self {
77 Self::Portable => "portable-constant-time",
78 Self::Aesni => "aes-ni",
79 Self::Armv8 => "armv8-crypto",
80 }
81 }
82}
83
84#[inline]
86#[must_use]
87pub fn aesni_available() -> bool {
88 ic_core::cpu::has_aes()
91}
92
93#[inline]
102#[must_use]
103pub fn armv8_aes_available() -> bool {
104 #[cfg(all(target_arch = "aarch64", feature = "aarch64-crypto", feature = "std"))]
105 {
106 std::arch::is_aarch64_feature_detected!("aes")
107 }
108 #[cfg(not(all(target_arch = "aarch64", feature = "aarch64-crypto", feature = "std")))]
109 {
110 false
111 }
112}
113
114pub fn active_backend() -> Backend {
116 if armv8_aes_available() {
117 Backend::Armv8
118 } else if aesni_available() {
119 Backend::Aesni
120 } else {
121 Backend::Portable
122 }
123}
124
125#[allow(clippy::large_enum_variant)]
133#[derive(Clone)]
134enum Keys {
135 Portable(portable::Schedule),
136 #[cfg(any(
137 all(any(target_arch = "x86", target_arch = "x86_64"), feature = "std"),
138 all(
139 any(target_arch = "x86", target_arch = "x86_64"),
140 not(feature = "std"),
141 target_feature = "aes"
142 )
143 ))]
144 Aesni(x86::Keys),
145 #[cfg(all(target_arch = "aarch64", feature = "aarch64-crypto", feature = "std"))]
146 Armv8(aarch64::Keys),
147}
148
149fn expand(key: &[u8]) -> Result<Keys> {
151 let sched = portable::Schedule::expand(key)?;
154
155 #[cfg(any(
156 all(any(target_arch = "x86", target_arch = "x86_64"), feature = "std"),
157 all(
158 any(target_arch = "x86", target_arch = "x86_64"),
159 not(feature = "std"),
160 target_feature = "aes"
161 )
162 ))]
163 if aesni_available() {
164 let keys = unsafe { x86::Keys::load(&sched) };
166 return Ok(Keys::Aesni(keys));
167 }
168
169 #[cfg(all(target_arch = "aarch64", feature = "aarch64-crypto", feature = "std"))]
170 if armv8_aes_available() {
171 let keys = unsafe { aarch64::Keys::load(&sched) };
173 return Ok(Keys::Armv8(keys));
174 }
175
176 Ok(Keys::Portable(sched))
177}
178
179impl Keys {
180 #[inline]
181 fn encrypt_block(&self, block: &mut [u8]) -> Result<()> {
182 match self {
183 Keys::Portable(s) => portable::encrypt_block(s, block),
184 #[cfg(any(
185 all(any(target_arch = "x86", target_arch = "x86_64"), feature = "std"),
186 all(
187 any(target_arch = "x86", target_arch = "x86_64"),
188 not(feature = "std"),
189 target_feature = "aes"
190 )
191 ))]
192 Keys::Aesni(k) => unsafe { x86::encrypt_block(k, block) },
194 #[cfg(all(target_arch = "aarch64", feature = "aarch64-crypto", feature = "std"))]
195 Keys::Armv8(k) => unsafe { aarch64::encrypt_block(k, block) },
197 }
198 }
199
200 #[inline]
201 fn decrypt_block(&self, block: &mut [u8]) -> Result<()> {
202 match self {
203 Keys::Portable(s) => portable::decrypt_block(s, block),
204 #[cfg(any(
205 all(any(target_arch = "x86", target_arch = "x86_64"), feature = "std"),
206 all(
207 any(target_arch = "x86", target_arch = "x86_64"),
208 not(feature = "std"),
209 target_feature = "aes"
210 )
211 ))]
212 Keys::Aesni(k) => unsafe { x86::decrypt_block(k, block) },
214 #[cfg(all(target_arch = "aarch64", feature = "aarch64-crypto", feature = "std"))]
215 Keys::Armv8(k) => unsafe { aarch64::decrypt_block(k, block) },
217 }
218 }
219
220 #[inline]
221 fn encrypt_blocks(&self, data: &mut [u8]) -> Result<()> {
222 match self {
223 Keys::Portable(s) => {
224 ensure!(
225 data.len() % BLOCK_LEN == 0,
226 InvalidLength,
227 "aes batch must be block-aligned"
228 );
229 for block in data.chunks_exact_mut(BLOCK_LEN) {
230 portable::encrypt_block(s, block)?;
231 }
232 Ok(())
233 }
234 #[cfg(any(
235 all(any(target_arch = "x86", target_arch = "x86_64"), feature = "std"),
236 all(
237 any(target_arch = "x86", target_arch = "x86_64"),
238 not(feature = "std"),
239 target_feature = "aes"
240 )
241 ))]
242 Keys::Aesni(k) => unsafe { x86::encrypt_blocks(k, data) },
244 #[cfg(all(target_arch = "aarch64", feature = "aarch64-crypto", feature = "std"))]
245 Keys::Armv8(k) => unsafe { aarch64::encrypt_blocks(k, data) },
247 }
248 }
249
250 fn backend(&self) -> Backend {
251 match self {
252 Keys::Portable(_) => Backend::Portable,
253 #[cfg(any(
254 all(any(target_arch = "x86", target_arch = "x86_64"), feature = "std"),
255 all(
256 any(target_arch = "x86", target_arch = "x86_64"),
257 not(feature = "std"),
258 target_feature = "aes"
259 )
260 ))]
261 Keys::Aesni(_) => Backend::Aesni,
262 #[cfg(all(target_arch = "aarch64", feature = "aarch64-crypto", feature = "std"))]
263 Keys::Armv8(_) => Backend::Armv8,
264 }
265 }
266}
267
268macro_rules! aes_variant {
269 ($name:ident, $id:literal, $disp:literal, $keylen:literal, $kat_key:literal, $kat_ct:literal) => {
270 #[doc = concat!("FIPS 197 ", $disp, ".")]
271 #[derive(Clone)]
272 pub struct $name(Keys);
273
274 impl $name {
275 pub fn backend(&self) -> Backend {
277 self.0.backend()
278 }
279
280 pub fn new_portable(key: &[u8]) -> Result<Self> {
285 ensure!(key.len() == $keylen, InvalidLength, $id);
286 Ok(Self(Keys::Portable(portable::Schedule::expand(key)?)))
287 }
288 }
289
290 impl Algorithm for $name {
291 const ID: &'static str = $id;
292 const NAME: &'static str = $disp;
293 }
294
295 impl BlockCipher for $name {
296 const BLOCK_LEN: usize = BLOCK_LEN;
297 const KEY_LEN: usize = $keylen;
298
299 fn new(key: &[u8]) -> Result<Self> {
300 ensure!(key.len() == $keylen, InvalidLength, $id);
301 Ok(Self(expand(key)?))
302 }
303
304 fn encrypt_block(&self, block: &mut [u8]) -> Result<()> {
305 self.0.encrypt_block(block)
306 }
307
308 fn decrypt_block(&self, block: &mut [u8]) -> Result<()> {
309 self.0.decrypt_block(block)
310 }
311
312 fn encrypt_blocks(&self, data: &mut [u8]) -> Result<()> {
313 self.0.encrypt_blocks(data)
314 }
315 }
316
317 impl SelfTest for $name {
318 fn self_test() -> Result<()> {
319 let mut key = [0u8; $keylen];
321 ic_core::codec::hex_decode($kat_key.as_bytes(), &mut key)?;
322 let mut want = [0u8; 16];
323 ic_core::codec::hex_decode($kat_ct.as_bytes(), &mut want)?;
324
325 let cipher = <Self as BlockCipher>::new(&key)?;
326 let mut block: [u8; 16] = core::array::from_fn(|i| (i * 0x11) as u8);
327 cipher.encrypt_block(&mut block)?;
328 ensure!(ic_core::ct::verify(&want, &block), SelfTestFailed, $id);
329
330 cipher.decrypt_block(&mut block)?;
331 let plain: [u8; 16] = core::array::from_fn(|i| (i * 0x11) as u8);
332 ensure!(ic_core::ct::verify(&plain, &block), SelfTestFailed, $id);
333
334 let reference = Self::new_portable(&key)?;
338 let mut a: [u8; 16] = core::array::from_fn(|i| (i * 0x11) as u8);
339 reference.encrypt_block(&mut a)?;
340 ensure!(ic_core::ct::verify(&want, &a), SelfTestFailed, $id);
341 Ok(())
342 }
343 }
344 };
345}
346
347aes_variant!(
348 Aes128,
349 "aes-128",
350 "AES-128",
351 16,
352 "000102030405060708090a0b0c0d0e0f",
353 "69c4e0d86a7b0430d8cdb78070b4c55a"
354);
355aes_variant!(
356 Aes192,
357 "aes-192",
358 "AES-192",
359 24,
360 "000102030405060708090a0b0c0d0e0f1011121314151617",
361 "dda97ca4864cdfe06eaf70a0ec0d7191"
362);
363aes_variant!(
364 Aes256,
365 "aes-256",
366 "AES-256",
367 32,
368 "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
369 "8ea2b7ca516745bfeafc49904b496089"
370);
371
372#[cfg(test)]
373mod tests {
374 use super::*;
375 use ic_core::codec::{hex, unhex};
376
377 fn enc<C: BlockCipher>(key: &str, pt: &str) -> String {
378 let c = C::new(&unhex(key).unwrap()).unwrap();
379 let mut b = unhex(pt).unwrap();
380 c.encrypt_block(&mut b).unwrap();
381 hex(&b)
382 }
383
384 #[test]
385 fn fips197_appendix_c_vectors() {
386 assert_eq!(
387 enc::<Aes128>(
388 "000102030405060708090a0b0c0d0e0f",
389 "00112233445566778899aabbccddeeff"
390 ),
391 "69c4e0d86a7b0430d8cdb78070b4c55a"
392 );
393 assert_eq!(
394 enc::<Aes192>(
395 "000102030405060708090a0b0c0d0e0f1011121314151617",
396 "00112233445566778899aabbccddeeff"
397 ),
398 "dda97ca4864cdfe06eaf70a0ec0d7191"
399 );
400 assert_eq!(
401 enc::<Aes256>(
402 "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
403 "00112233445566778899aabbccddeeff"
404 ),
405 "8ea2b7ca516745bfeafc49904b496089"
406 );
407 }
408
409 #[test]
412 fn sp800_38a_ecb_vector() {
413 assert_eq!(
414 enc::<Aes128>(
415 "2b7e151628aed2a6abf7158809cf4f3c",
416 "6bc1bee22e409f96e93d7e117393172a"
417 ),
418 "3ad77bb40d7a3660a89ecaf32466ef97"
419 );
420 }
421
422 #[test]
423 fn decryption_inverts_encryption() {
424 let key = [0x42u8; 32];
425 let c = Aes256::new(&key).unwrap();
426 let original: [u8; 16] = core::array::from_fn(|i| (i * 13) as u8);
427 let mut block = original;
428 c.encrypt_block(&mut block).unwrap();
429 assert_ne!(block, original);
430 c.decrypt_block(&mut block).unwrap();
431 assert_eq!(block, original);
432 }
433
434 #[test]
435 fn rejects_wrong_key_and_block_lengths() {
436 assert!(Aes128::new(&[0u8; 17]).is_err());
437 assert!(Aes256::new(&[0u8; 16]).is_err());
438 let c = Aes128::new(&[0u8; 16]).unwrap();
439 assert!(c.encrypt_block(&mut [0u8; 15]).is_err());
440 assert!(c.encrypt_blocks(&mut [0u8; 17]).is_err());
441 }
442
443 #[test]
448 fn active_backend_agrees_with_portable() {
449 for key_len in [16usize, 24, 32] {
450 let key: Vec<u8> = (0..key_len).map(|i| (i * 11 + 3) as u8).collect();
451
452 macro_rules! compare {
453 ($ty:ty) => {{
454 let fast = <$ty>::new(&key).unwrap();
455 let slow = <$ty>::new_portable(&key).unwrap();
456 for seed in 0..32u8 {
457 let original: [u8; 16] = core::array::from_fn(|i| seed ^ (i as u8 * 17));
458 let mut a = original;
459 let mut b = original;
460 fast.encrypt_block(&mut a).unwrap();
461 slow.encrypt_block(&mut b).unwrap();
462 assert_eq!(a, b, "encrypt, key_len {}", key_len);
463
464 let mut a = original;
465 let mut b = original;
466 fast.decrypt_block(&mut a).unwrap();
467 slow.decrypt_block(&mut b).unwrap();
468 assert_eq!(a, b, "decrypt, key_len {}", key_len);
469 }
470 }};
471 }
472 match key_len {
473 16 => compare!(Aes128),
474 24 => compare!(Aes192),
475 _ => compare!(Aes256),
476 }
477 }
478 }
479
480 #[test]
484 fn batch_matches_single_block() {
485 let c = Aes256::new(&[0x2bu8; 32]).unwrap();
486 for blocks in 0..20usize {
487 let data: Vec<u8> = (0..blocks * BLOCK_LEN).map(|i| (i * 7) as u8).collect();
488
489 let mut batched = data.clone();
490 c.encrypt_blocks(&mut batched).unwrap();
491
492 let mut singly = data.clone();
493 for block in singly.chunks_exact_mut(BLOCK_LEN) {
494 c.encrypt_block(block).unwrap();
495 }
496 assert_eq!(batched, singly, "{blocks} blocks");
497 }
498 }
499
500 #[test]
501 fn backend_is_reported_consistently() {
502 let c = Aes128::new(&[0u8; 16]).unwrap();
503 assert_eq!(c.backend(), active_backend());
504 assert_eq!(
505 Aes128::new_portable(&[0u8; 16]).unwrap().backend(),
506 Backend::Portable
507 );
508 assert_eq!(Backend::Portable.id(), "portable-constant-time");
509 }
510
511 #[test]
512 fn self_tests_pass() {
513 Aes128::self_test().unwrap();
514 Aes192::self_test().unwrap();
515 Aes256::self_test().unwrap();
516 }
517}