Software APIs
aes_gcm_timing_test.c
1 // Copyright lowRISC contributors (OpenTitan project).
2 // Licensed under the Apache License, Version 2.0, see LICENSE for details.
3 // SPDX-License-Identifier: Apache-2.0
4 
6 #include "sw/device/lib/crypto/drivers/entropy.h"
7 #include "sw/device/lib/crypto/impl/aes_gcm/aes_gcm.h"
10 #include "sw/device/lib/testing/test_framework/check.h"
12 #include "sw/device/tests/crypto/aes_gcm_testutils.h"
13 #include "sw/device/tests/crypto/aes_gcm_testvectors.h"
14 
15 // Global pointer to the current test vector.
16 static aes_gcm_test_t *current_test = NULL;
17 
18 /**
19  * Checks that decryption takes the same number of cycles regardless of tag.
20  *
21  * This test ensures that the AES-GCM tag check takes the exact same amount of
22  * time for all *incorrect* tags; if it didn't, for instance exiting early at
23  * the first incorrect byte, then an attacker could guess one byte at a time
24  * and observe the timing to see if the byte was correct.
25  *
26  * Invalidates icache before each test so that each starts from the same state.
27  *
28  * It's OK if decryption takes a different amount of time with a correct tag
29  * than with an incorrect one.
30  */
31 static status_t test_decrypt_timing(void) {
32  size_t tag_num_words = current_test->tag_len / sizeof(uint32_t);
33  TRY_CHECK(tag_num_words * sizeof(uint32_t) == current_test->tag_len,
34  "Tag length %d is not a multiple of the word size (%d).",
35  current_test->tag_len, sizeof(uint32_t));
36 
37  // Call AES-GCM decrypt with an incorrect tag (first word wrong).
38  current_test->tag[0]++;
39  uint32_t cycles_invalid1;
40  hardened_bool_t valid;
41  TRY(aes_gcm_testutils_decrypt(current_test, &valid, /*streaming=*/false,
42  &cycles_invalid1));
43  TRY_CHECK(valid == kHardenedBoolFalse);
44  current_test->tag[0]--;
45  LOG_INFO("First invalid tag: %d cycles", cycles_invalid1);
46 
47  // Call AES-GCM decrypt with an incorrect tag (middle word wrong).
48  current_test->tag[tag_num_words / 2]++;
49  uint32_t cycles_invalid2;
50  TRY(aes_gcm_testutils_decrypt(current_test, &valid, /*streaming=*/false,
51  &cycles_invalid2));
52  TRY_CHECK(valid == kHardenedBoolFalse);
53  current_test->tag[tag_num_words / 2]--;
54  LOG_INFO("Second invalid tag: %d cycles", cycles_invalid2);
55 
56  // Call AES-GCM decrypt with an incorrect tag (last word wrong).
57  current_test->tag[tag_num_words - 1]++;
58  uint32_t cycles_invalid3;
59  TRY(aes_gcm_testutils_decrypt(current_test, &valid, /*streaming=*/false,
60  &cycles_invalid3));
61  TRY_CHECK(valid == kHardenedBoolFalse);
62  current_test->tag[tag_num_words - 1]--;
63  LOG_INFO("Third invalid tag: %d cycles", cycles_invalid3);
64 
65  // Check that the cycle counts for the invalid tags match.
66  TRY_CHECK(
67  cycles_invalid1 == cycles_invalid2,
68  "AES-GCM decryption was not constant-time for different invalid tags");
69  TRY_CHECK(
70  cycles_invalid2 == cycles_invalid3,
71  "AES-GCM decryption was not constant-time for different invalid tags");
72  return OK_STATUS();
73 }
74 
75 OTTF_DEFINE_TEST_CONFIG();
76 bool test_main(void) {
77  status_t result;
78  CHECK_STATUS_OK(entropy_complex_init());
79  for (size_t i = 0; i < ARRAYSIZE(kAesGcmTestvectors); i++) {
80  current_test = &kAesGcmTestvectors[i];
81  LOG_INFO("Key length = %d", current_test->key_len * sizeof(uint32_t));
82  LOG_INFO("Aad length = %d", current_test->aad_len);
83  LOG_INFO("Encrypted data length = %d", current_test->plaintext_len);
84  LOG_INFO("Tag length = %d", current_test->tag_len);
85  EXECUTE_TEST(result, test_decrypt_timing);
86  }
87 
88  return status_ok(result);
89 }