Skip to main content

capsules_extra/test/
aes256.rs

1// Licensed under the Apache License, Version 2.0 or the MIT License.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright Tock Contributors 2022.
4
5//! Test the AES hardware for ECB, CBC, and CTR modes using NIST SP 800-38A vectors.
6//!
7//! Each test struct runs the following steps in sequence:
8//!   1. StandardEnc        — out-of-place encryption, full message
9//!   2. StandardEncInPlace — in-place encryption, full message
10//!   3. StandardDec        — out-of-place decryption, full message  (if test_decrypt)
11//!   4. StandardDecInPlace — in-place decryption, full message      (if test_decrypt)
12//!   5. ChunkEnc1          — in-place encryption, first half
13//!   6. ChunkEnc2          — in-place encryption, second half (no re-init, tests IV chaining)
14//!   7. ChunkDec1          — in-place decryption, first half         (if test_decrypt)
15//!   8. ChunkDec2          — in-place decryption, second half        (if test_decrypt)
16
17use capsules_core::test::capsule_test::{CapsuleTest, CapsuleTestClient};
18use core::cell::Cell;
19use kernel::debug;
20use kernel::hil;
21use kernel::hil::symmetric_encryption::{
22    AES, AES_BLOCK_SIZE, AES256, AES256_KEY_SIZE, AESCBC, AESCtr, AESECB,
23};
24use kernel::utilities::cells::OptionalCell;
25use kernel::utilities::cells::TakeCell;
26
27// The data buffer layout is:
28//   [0..DATA_OFFSET]              — guard region (never written, detects underflow)
29//   [DATA_OFFSET..DATA_OFFSET+DATA_LEN] — ciphertext / plaintext
30const DATA_OFFSET: usize = AES_BLOCK_SIZE;
31const DATA_LEN: usize = 4 * AES_BLOCK_SIZE;
32const CHUNK_LEN: usize = 2 * AES_BLOCK_SIZE; // must divide DATA_LEN evenly
33
34#[derive(Copy, Clone, Debug, PartialEq)]
35enum TestStep {
36    StandardEnc,
37    StandardEncInPlace,
38    StandardDec,
39    StandardDecInPlace,
40    ChunkEnc1,
41    ChunkEnc2,
42    ChunkDec1,
43    ChunkDec2,
44    Done,
45}
46
47// ---------------------------------------------------------------------------
48// ECB
49// ---------------------------------------------------------------------------
50
51pub struct TestAES256Ecb<'a, A: 'a> {
52    aes: &'a A,
53    key: TakeCell<'a, [u8]>,
54    source: TakeCell<'static, [u8]>,
55    data: TakeCell<'static, [u8]>,
56    test_decrypt: bool,
57    step: Cell<TestStep>,
58    client: OptionalCell<&'static dyn CapsuleTestClient>,
59}
60
61impl<'a, A: AES<'a, AES256> + AESECB> TestAES256Ecb<'a, A> {
62    pub fn new(
63        aes: &'a A,
64        key: &'a mut [u8],
65        source: &'static mut [u8],
66        data: &'static mut [u8],
67        test_decrypt: bool,
68    ) -> Self {
69        TestAES256Ecb {
70            aes,
71            key: TakeCell::new(key),
72            source: TakeCell::new(source),
73            data: TakeCell::new(data),
74            test_decrypt,
75            step: Cell::new(TestStep::StandardEnc),
76            client: OptionalCell::empty(),
77        }
78    }
79
80    pub fn run(&self) {
81        let step = self.step.get();
82        let encrypting = is_encrypting(step);
83        let in_place = is_in_place(step);
84
85        // Re-initialise hardware for every step except the second chunk, which
86        // intentionally reuses the hardware state to verify key/IV retention.
87        if !is_second_chunk(step) {
88            self.aes.enable();
89            self.aes.set_mode_aesecb(encrypting).unwrap();
90            self.key.map(|key| {
91                key[..KEY.len()].copy_from_slice(&KEY);
92                assert_eq!(self.aes.set_key(key), Ok(()));
93            });
94            let src = if encrypting { &PTXT } else { &CTXT_ECB };
95            self.source.map(|s| s[..src.len()].copy_from_slice(src));
96            self.aes.start_message();
97        }
98
99        prepare_in_place(step, in_place, &self.source, &self.data);
100
101        let (start, stop) = chunk_range(step);
102        run_crypt(self.aes, in_place, &self.source, &self.data, start, stop);
103    }
104}
105
106impl<'a, A: AES<'a, AES256> + AESECB> CapsuleTest for TestAES256Ecb<'a, A> {
107    fn set_client(&self, client: &'static dyn CapsuleTestClient) {
108        self.client.set(client);
109    }
110}
111
112impl<'a, A: AES<'a, AES256> + AESECB> hil::symmetric_encryption::Client<'a>
113    for TestAES256Ecb<'a, A>
114{
115    fn crypt_done(&'a self, source: Option<&'static mut [u8]>, dest: &'static mut [u8]) {
116        let step = self.step.get();
117        let encrypting = is_encrypting(step);
118        let in_place = is_in_place(step);
119
120        restore_source(in_place, source, &self.source);
121        self.data.replace(dest);
122
123        // ECB has no IV chaining, so we can verify after every step except
124        // ChunkEnc1/ChunkDec1 where we only have half the ciphertext yet.
125        if !is_first_chunk(step) {
126            let expected = if encrypting { &CTXT_ECB } else { &PTXT };
127            self.data.map(|d| {
128                assert_eq!(
129                    &d[DATA_OFFSET..DATA_OFFSET + DATA_LEN],
130                    expected.as_ref(),
131                    "aes_test ECB failed at step {:?}",
132                    step
133                );
134            });
135            debug!("aes_test ECB passed step: {:?}", step);
136            self.aes.disable();
137        }
138
139        let next = next_step(step, self.test_decrypt);
140        self.step.set(next);
141        if next == TestStep::Done {
142            self.client.map(|c| c.done(Ok(())));
143        } else {
144            self.run();
145        }
146    }
147}
148
149// ---------------------------------------------------------------------------
150// CBC
151// ---------------------------------------------------------------------------
152
153pub struct TestAES256Cbc<'a, A: 'a> {
154    aes: &'a A,
155    key: TakeCell<'a, [u8]>,
156    iv: TakeCell<'a, [u8]>,
157    source: TakeCell<'static, [u8]>,
158    data: TakeCell<'static, [u8]>,
159    test_decrypt: bool,
160    step: Cell<TestStep>,
161    client: OptionalCell<&'static dyn CapsuleTestClient>,
162}
163
164impl<'a, A: AES<'a, AES256> + AESCBC> TestAES256Cbc<'a, A> {
165    pub fn new(
166        aes: &'a A,
167        key: &'a mut [u8],
168        iv: &'a mut [u8],
169        source: &'static mut [u8],
170        data: &'static mut [u8],
171        test_decrypt: bool,
172    ) -> Self {
173        TestAES256Cbc {
174            aes,
175            key: TakeCell::new(key),
176            iv: TakeCell::new(iv),
177            source: TakeCell::new(source),
178            data: TakeCell::new(data),
179            test_decrypt,
180            step: Cell::new(TestStep::StandardEnc),
181            client: OptionalCell::empty(),
182        }
183    }
184
185    pub fn run(&self) {
186        let step = self.step.get();
187        let encrypting = is_encrypting(step);
188        let in_place = is_in_place(step);
189
190        if !is_second_chunk(step) {
191            self.aes.enable();
192            self.aes.set_mode_aescbc(encrypting).unwrap();
193            self.key.map(|key| {
194                key[..KEY.len()].copy_from_slice(&KEY);
195                assert_eq!(self.aes.set_key(key), Ok(()));
196            });
197            self.iv.map(|iv| {
198                iv[..IV_CBC.len()].copy_from_slice(&IV_CBC);
199                assert_eq!(self.aes.set_iv(iv), Ok(()));
200            });
201            let src = if encrypting { &PTXT } else { &CTXT_CBC };
202            self.source.map(|s| s[..src.len()].copy_from_slice(src));
203            self.aes.start_message();
204        }
205
206        prepare_in_place(step, in_place, &self.source, &self.data);
207
208        let (start, stop) = chunk_range(step);
209        run_crypt(self.aes, in_place, &self.source, &self.data, start, stop);
210    }
211}
212
213impl<'a, A: AES<'a, AES256> + AESCBC> CapsuleTest for TestAES256Cbc<'a, A> {
214    fn set_client(&self, client: &'static dyn CapsuleTestClient) {
215        self.client.set(client);
216    }
217}
218
219impl<'a, A: AES<'a, AES256> + AESCBC> hil::symmetric_encryption::Client<'a>
220    for TestAES256Cbc<'a, A>
221{
222    fn crypt_done(&'a self, source: Option<&'static mut [u8]>, dest: &'static mut [u8]) {
223        let step = self.step.get();
224        let encrypting = is_encrypting(step);
225        let in_place = is_in_place(step);
226
227        restore_source(in_place, source, &self.source);
228        self.data.replace(dest);
229
230        if !is_first_chunk(step) {
231            let expected = if encrypting { &CTXT_CBC } else { &PTXT };
232            self.data.map(|d| {
233                assert_eq!(
234                    &d[DATA_OFFSET..DATA_OFFSET + DATA_LEN],
235                    expected.as_ref(),
236                    "aes_test CBC failed at step {:?}",
237                    step
238                );
239            });
240            debug!("aes_test CBC passed step: {:?}", step);
241            self.aes.disable();
242        }
243
244        let next = next_step(step, self.test_decrypt);
245        self.step.set(next);
246        if next == TestStep::Done {
247            self.client.map(|c| c.done(Ok(())));
248        } else {
249            self.run();
250        }
251    }
252}
253
254// ---------------------------------------------------------------------------
255// CTR
256// ---------------------------------------------------------------------------
257
258pub struct TestAES256Ctr<'a, A: 'a> {
259    aes: &'a A,
260    key: TakeCell<'a, [u8]>,
261    iv: TakeCell<'a, [u8]>,
262    source: TakeCell<'static, [u8]>,
263    data: TakeCell<'static, [u8]>,
264    test_decrypt: bool,
265    step: Cell<TestStep>,
266    client: OptionalCell<&'static dyn CapsuleTestClient>,
267}
268
269impl<'a, A: AES<'a, AES256> + AESCtr> TestAES256Ctr<'a, A> {
270    pub fn new(
271        aes: &'a A,
272        key: &'a mut [u8],
273        iv: &'a mut [u8],
274        source: &'static mut [u8],
275        data: &'static mut [u8],
276        test_decrypt: bool,
277    ) -> Self {
278        TestAES256Ctr {
279            aes,
280            key: TakeCell::new(key),
281            iv: TakeCell::new(iv),
282            source: TakeCell::new(source),
283            data: TakeCell::new(data),
284            test_decrypt,
285            step: Cell::new(TestStep::StandardEnc),
286            client: OptionalCell::empty(),
287        }
288    }
289
290    pub fn run(&self) {
291        let step = self.step.get();
292        let encrypting = is_encrypting(step);
293        let in_place = is_in_place(step);
294
295        if !is_second_chunk(step) {
296            self.aes.enable();
297            self.aes.set_mode_aesctr(encrypting).unwrap();
298            self.key.map(|key| {
299                key[..KEY.len()].copy_from_slice(&KEY);
300                assert_eq!(self.aes.set_key(key), Ok(()));
301            });
302            self.iv.map(|iv| {
303                iv[..IV_CTR.len()].copy_from_slice(&IV_CTR);
304                assert_eq!(self.aes.set_iv(iv), Ok(()));
305            });
306            let src = if encrypting { &PTXT } else { &CTXT_CTR };
307            self.source.map(|s| s[..src.len()].copy_from_slice(src));
308            self.aes.start_message();
309        }
310
311        prepare_in_place(step, in_place, &self.source, &self.data);
312
313        let (start, stop) = chunk_range(step);
314        run_crypt(self.aes, in_place, &self.source, &self.data, start, stop);
315    }
316}
317
318impl<'a, A: AES<'a, AES256> + AESCtr> CapsuleTest for TestAES256Ctr<'a, A> {
319    fn set_client(&self, client: &'static dyn CapsuleTestClient) {
320        self.client.set(client);
321    }
322}
323
324impl<'a, A: AES<'a, AES256> + AESCtr> hil::symmetric_encryption::Client<'a>
325    for TestAES256Ctr<'a, A>
326{
327    fn crypt_done(&'a self, source: Option<&'static mut [u8]>, dest: &'static mut [u8]) {
328        let step = self.step.get();
329        let encrypting = is_encrypting(step);
330        let in_place = is_in_place(step);
331
332        restore_source(in_place, source, &self.source);
333        self.data.replace(dest);
334
335        if !is_first_chunk(step) {
336            let expected = if encrypting { &CTXT_CTR } else { &PTXT };
337            self.data.map(|d| {
338                assert_eq!(
339                    &d[DATA_OFFSET..DATA_OFFSET + DATA_LEN],
340                    expected.as_ref(),
341                    "aes_test CTR failed at step {:?}",
342                    step
343                );
344                // Verify guard region was not touched
345                assert_eq!(
346                    d[..DATA_OFFSET],
347                    [0u8; DATA_OFFSET],
348                    "aes_test CTR: guard region corrupted at step {:?}",
349                    step
350                );
351            });
352            debug!("aes_test CTR passed step: {:?}", step);
353            self.aes.disable();
354        }
355
356        let next = next_step(step, self.test_decrypt);
357        self.step.set(next);
358        if next == TestStep::Done {
359            self.client.map(|c| c.done(Ok(())));
360        } else {
361            self.run();
362        }
363    }
364}
365
366// ---------------------------------------------------------------------------
367// Shared helpers
368// ---------------------------------------------------------------------------
369
370fn is_encrypting(step: TestStep) -> bool {
371    !matches!(
372        step,
373        TestStep::StandardDec
374            | TestStep::StandardDecInPlace
375            | TestStep::ChunkDec1
376            | TestStep::ChunkDec2
377    )
378}
379
380fn is_in_place(step: TestStep) -> bool {
381    matches!(
382        step,
383        TestStep::StandardEncInPlace
384            | TestStep::StandardDecInPlace
385            | TestStep::ChunkEnc1
386            | TestStep::ChunkEnc2
387            | TestStep::ChunkDec1
388            | TestStep::ChunkDec2
389    )
390}
391
392fn is_first_chunk(step: TestStep) -> bool {
393    matches!(step, TestStep::ChunkEnc1 | TestStep::ChunkDec1)
394}
395
396fn is_second_chunk(step: TestStep) -> bool {
397    matches!(step, TestStep::ChunkEnc2 | TestStep::ChunkDec2)
398}
399
400/// Returns (start, stop) indices into the data buffer for this step.
401fn chunk_range(step: TestStep) -> (usize, usize) {
402    match step {
403        TestStep::ChunkEnc1 | TestStep::ChunkDec1 => (DATA_OFFSET, DATA_OFFSET + CHUNK_LEN),
404        TestStep::ChunkEnc2 | TestStep::ChunkDec2 => {
405            (DATA_OFFSET + CHUNK_LEN, DATA_OFFSET + DATA_LEN)
406        }
407        _ => (DATA_OFFSET, DATA_OFFSET + DATA_LEN),
408    }
409}
410
411/// For in-place steps, copy the relevant slice of source into dest at the
412/// correct offset so the driver reads plaintext/ciphertext from dest[start..].
413fn prepare_in_place(
414    step: TestStep,
415    in_place: bool,
416    source: &TakeCell<'static, [u8]>,
417    data: &TakeCell<'static, [u8]>,
418) {
419    if !in_place {
420        return;
421    }
422    let src_start = match step {
423        TestStep::ChunkEnc2 | TestStep::ChunkDec2 => CHUNK_LEN,
424        _ => 0,
425    };
426    let dst_start = match step {
427        TestStep::ChunkEnc2 | TestStep::ChunkDec2 => DATA_OFFSET + CHUNK_LEN,
428        _ => DATA_OFFSET,
429    };
430    let copy_len = match step {
431        TestStep::ChunkEnc1 | TestStep::ChunkDec1 => DATA_LEN,
432        TestStep::ChunkEnc2 | TestStep::ChunkDec2 => 0,
433        _ => DATA_LEN,
434    };
435    source.map(|src| {
436        data.map(|dst| {
437            dst[dst_start..dst_start + copy_len]
438                .copy_from_slice(&src[src_start..src_start + copy_len]);
439        });
440    });
441}
442
443fn run_crypt<'a, A: AES<'a, AES256>>(
444    aes: &'a A,
445    in_place: bool,
446    source: &TakeCell<'static, [u8]>,
447    data: &TakeCell<'static, [u8]>,
448    start: usize,
449    stop: usize,
450) {
451    let src = if in_place { None } else { source.take() };
452    match aes.crypt(src, data.take().unwrap(), start, stop) {
453        None => {}
454        Some((result, src_back, dest_back)) => {
455            source.put(src_back);
456            data.put(Some(dest_back));
457            panic!("crypt() returned error: {:?}", result);
458        }
459    }
460}
461
462fn restore_source(
463    in_place: bool,
464    source: Option<&'static mut [u8]>,
465    cell: &TakeCell<'static, [u8]>,
466) {
467    if !in_place {
468        cell.replace(source.expect("crypt_done: expected source buffer for out-of-place op"));
469    }
470}
471
472fn next_step(step: TestStep, test_decrypt: bool) -> TestStep {
473    match step {
474        TestStep::StandardEnc => TestStep::StandardEncInPlace,
475        TestStep::StandardEncInPlace => {
476            if test_decrypt {
477                TestStep::StandardDec
478            } else {
479                TestStep::ChunkEnc1
480            }
481        }
482        TestStep::StandardDec => TestStep::StandardDecInPlace,
483        TestStep::StandardDecInPlace => TestStep::ChunkEnc1,
484        TestStep::ChunkEnc1 => TestStep::ChunkEnc2,
485        TestStep::ChunkEnc2 => {
486            if test_decrypt {
487                TestStep::ChunkDec1
488            } else {
489                TestStep::Done
490            }
491        }
492        TestStep::ChunkDec1 => TestStep::ChunkDec2,
493        TestStep::ChunkDec2 => {
494            debug!("All tests passed");
495            TestStep::Done
496        }
497        _ => TestStep::Done,
498    }
499}
500
501// ---------------------------------------------------------------------------
502// NIST test vectors (AES-256)
503// ---------------------------------------------------------------------------
504
505#[rustfmt::skip]
506const KEY: [u8; AES256_KEY_SIZE] = [
507    0x60, 0x3d, 0xeb, 0x10, 0x15, 0xca, 0x71, 0xbe,
508    0x2b, 0x73, 0xae, 0xf0, 0x85, 0x7d, 0x77, 0x81,
509    0x1f, 0x35, 0x2c, 0x07, 0x3b, 0x61, 0x08, 0xd7,
510    0x2d, 0x98, 0x10, 0xa3, 0x09, 0x14, 0xdf, 0xf4,
511];
512
513#[rustfmt::skip]
514const IV_CTR: [u8; AES_BLOCK_SIZE] = [
515    0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
516    0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff,
517];
518
519#[rustfmt::skip]
520const IV_CBC: [u8; AES_BLOCK_SIZE] = [
521    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
522    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
523];
524
525#[rustfmt::skip]
526const PTXT: [u8; DATA_LEN] = [
527    0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96,
528    0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a,
529    0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c,
530    0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51,
531    0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11,
532    0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef,
533    0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17,
534    0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10,
535];
536
537#[rustfmt::skip]
538const CTXT_CTR: [u8; DATA_LEN] = [
539    0x60, 0x1e, 0xc3, 0x13, 0x77, 0x57, 0x89, 0xa5,
540    0xb7, 0xa7, 0xf5, 0x04, 0xbb, 0xf3, 0xd2, 0x28,
541    0xf4, 0x43, 0xe3, 0xca, 0x4d, 0x62, 0xb5, 0x9a,
542    0xca, 0x84, 0xe9, 0x90, 0xca, 0xca, 0xf5, 0xc5,
543    0x2b, 0x09, 0x30, 0xda, 0xa2, 0x3d, 0xe9, 0x4c,
544    0xe8, 0x70, 0x17, 0xba, 0x2d, 0x84, 0x98, 0x8d,
545    0xdf, 0xc9, 0xc5, 0x8d, 0xb6, 0x7a, 0xad, 0xa6,
546    0x13, 0xc2, 0xdd, 0x08, 0x45, 0x79, 0x41, 0xa6,
547];
548
549#[rustfmt::skip]
550const CTXT_CBC: [u8; DATA_LEN] = [
551    0xf5, 0x8c, 0x4c, 0x04, 0xd6, 0xe5, 0xf1, 0xba,
552    0x77, 0x9e, 0xab, 0xfb, 0x5f, 0x7b, 0xfb, 0xd6,
553    0x9c, 0xfc, 0x4e, 0x96, 0x7e, 0xdb, 0x80, 0x8d,
554    0x67, 0x9f, 0x77, 0x7b, 0xc6, 0x70, 0x2c, 0x7d,
555    0x39, 0xf2, 0x33, 0x69, 0xa9, 0xd9, 0xba, 0xcf,
556    0xa5, 0x30, 0xe2, 0x63, 0x04, 0x23, 0x14, 0x61,
557    0xb2, 0xeb, 0x05, 0xe2, 0xc3, 0x9b, 0xe9, 0xfc,
558    0xda, 0x6c, 0x19, 0x07, 0x8c, 0x6a, 0x9d, 0x1b,
559];
560
561#[rustfmt::skip]
562const CTXT_ECB: [u8; DATA_LEN] = [
563    0xf3, 0xee, 0xd1, 0xbd, 0xb5, 0xd2, 0xa0, 0x3c,
564    0x06, 0x4b, 0x5a, 0x7e, 0x3d, 0xb1, 0x81, 0xf8,
565    0x59, 0x1c, 0xcb, 0x10, 0xd4, 0x10, 0xed, 0x26,
566    0xdc, 0x5b, 0xa7, 0x4a, 0x31, 0x36, 0x28, 0x70,
567    0xb6, 0xed, 0x21, 0xb9, 0x9c, 0xa6, 0xf4, 0xf9,
568    0xf1, 0x53, 0xe7, 0xb1, 0xbe, 0xaf, 0xed, 0x1d,
569    0x23, 0x30, 0x4b, 0x7a, 0x39, 0xf9, 0xf3, 0xff,
570    0x06, 0x7d, 0x8d, 0x8f, 0x9e, 0x24, 0xec, 0xc7,
571];