From 5b864fede5d3630cb62436cb0cd6114322e9088f Mon Sep 17 00:00:00 2001 From: Pascal Nasahl Date: Fri, 17 Jan 2025 15:37:54 +0100 Subject: [PATCH 1/3] [sca,aes] Move trigger into aes_encrypt Previously, the pentest_set_trigger_high/low functions were called at different places inside the code. However, this is not really - instead we could do it only inside the aes_encrypt function. This simplifies the code. Signed-off-by: Pascal Nasahl --- sw/device/sca/aes_serial.c | 12 +---- .../penetrationtests/firmware/sca/aes_sca.c | 44 ++----------------- 2 files changed, 6 insertions(+), 50 deletions(-) diff --git a/sw/device/sca/aes_serial.c b/sw/device/sca/aes_serial.c index e4300ee4417e8..00360ea8d8c65 100644 --- a/sw/device/sca/aes_serial.c +++ b/sw/device/sca/aes_serial.c @@ -245,7 +245,9 @@ static void aes_encrypt(const uint8_t *plaintext, size_t plaintext_len) { // Using the SecAesStartTriggerDelay hardware parameter, the AES unit is // configured to start operation 40 cycles after receiving the start trigger. // This allows Ibex to go to sleep in order to not disturb the capture. + pentest_set_trigger_high(); pentest_call_and_sleep(aes_manual_trigger, kIbexAesSleepCycles, false, false); + pentest_set_trigger_low(); } /** @@ -293,9 +295,7 @@ static void aes_serial_single_encrypt(const uint8_t *plaintext, block_ctr = 1; } - pentest_set_trigger_high(); aes_encrypt(plaintext, plaintext_len); - pentest_set_trigger_low(); aes_send_ciphertext(false); } @@ -374,12 +374,10 @@ static void aes_serial_batch_encrypt(const uint8_t *data, size_t data_len) { block_ctr = num_encryptions; } - pentest_set_trigger_high(); for (uint32_t i = 0; i < num_encryptions; ++i) { aes_encrypt(plaintext_random, kAesTextLength); aes_serial_advance_random(); } - pentest_set_trigger_low(); aes_send_ciphertext(true); } @@ -427,7 +425,6 @@ static void aes_serial_batch_alternative_encrypt(const uint8_t *data, // Set trigger high outside of loop // On FPGA, the trigger is AND-ed with AES !IDLE and creates a LO-HI-LO per // AES operation - pentest_set_trigger_high(); dif_aes_data_t ciphertext; for (uint32_t i = 0; i < num_encryptions; ++i) { // Encrypt @@ -444,7 +441,6 @@ static void aes_serial_batch_alternative_encrypt(const uint8_t *data, // Use ciphertext as next plaintext (incl. next call to this function) memcpy(batch_plaintext, ciphertext.data, kAesTextLength); } - pentest_set_trigger_low(); // Acknowledge command simple_serial_send_status(0); // send last ciphertext @@ -563,12 +559,10 @@ static void aes_serial_fvsr_key_batch_encrypt(const uint8_t *data, num_encryptions = read_32(data); SS_CHECK(num_encryptions <= kNumBatchOpsMax); - pentest_set_trigger_high(); for (uint32_t i = 0; i < num_encryptions; ++i) { aes_key_mask_and_config(batch_keys[i], kAesKeyLength); aes_encrypt(batch_plaintexts[i], kAesTextLength); } - pentest_set_trigger_low(); // Acknowledge command simple_serial_send_status(0); @@ -625,12 +619,10 @@ static void aes_serial_fvsr_data_batch_encrypt(const uint8_t *data, sample_fixed = pentest_next_lfsr(1, kPentestLfsrOrder) & 0x1; } - pentest_set_trigger_high(); for (uint32_t i = 0; i < num_encryptions; ++i) { aes_key_mask_and_config(batch_keys[i], kAesKeyLength); aes_encrypt(batch_plaintexts[i], kAesTextLength); } - pentest_set_trigger_low(); // Acknowledge command simple_serial_send_status(0); diff --git a/sw/device/tests/penetrationtests/firmware/sca/aes_sca.c b/sw/device/tests/penetrationtests/firmware/sca/aes_sca.c index 5696122ef27f4..f2150fbd402e2 100644 --- a/sw/device/tests/penetrationtests/firmware/sca/aes_sca.c +++ b/sw/device/tests/penetrationtests/firmware/sca/aes_sca.c @@ -221,9 +221,12 @@ static aes_sca_error_t aes_encrypt(const uint8_t *plaintext, // Start AES operation (this triggers the capture) and go to sleep. if (fpga_mode) { - // On the FPGA, the AES block automatically sets and unsets the trigger. + // On FPGA, the trigger is AND-ed with AES !IDLE and creates a LO-HI-LO per. + // Activate the GPIO by setting the GPIO. + pentest_set_trigger_high(); pentest_call_and_sleep(aes_manual_trigger, kIbexAesSleepCycles, false, false); + pentest_set_trigger_low(); } else { // On the chip, we need to manually set and unset the trigger. This is done // in this function to have the trigger as close as possible to the AES @@ -368,12 +371,6 @@ status_t handle_aes_sca_batch_alternative_encrypt(ujson_t *uj) { // First plaintext has been set through command into batch_plaintext - // Set trigger high outside of loop - // On FPGA, the trigger is AND-ed with AES !IDLE and creates a LO-HI-LO per - // AES operation - if (fpga_mode) { - pentest_set_trigger_high(); - } dif_aes_data_t ciphertext; for (uint32_t i = 0; i < num_encryptions; ++i) { // Encrypt @@ -394,9 +391,6 @@ status_t handle_aes_sca_batch_alternative_encrypt(ujson_t *uj) { // Use ciphertext as next plaintext (incl. next call to this function) memcpy(batch_plaintext, ciphertext.data, kAesTextLength); } - if (fpga_mode) { - pentest_set_trigger_low(); - } // send last ciphertext aes_sca_ciphertext_t uj_output; @@ -423,18 +417,12 @@ status_t handle_aes_sca_batch_encrypt(ujson_t *uj) { block_ctr = num_encryptions; } - if (fpga_mode) { - pentest_set_trigger_high(); - } for (uint32_t i = 0; i < num_encryptions; ++i) { if (aes_encrypt(plaintext_random, kAesTextLength) != aesScaOk) { return ABORTED(); } aes_serial_advance_random(); } - if (fpga_mode) { - pentest_set_trigger_low(); - } TRY(aes_send_ciphertext(true, uj)); @@ -458,9 +446,6 @@ status_t handle_aes_sca_batch_encrypt_random(ujson_t *uj) { block_ctr = num_encryptions; } - if (fpga_mode) { - pentest_set_trigger_high(); - } for (uint32_t i = 0; i < num_encryptions; ++i) { if (aes_key_mask_and_config(key_random, kAesKeyLength) != aesScaOk) { return ABORTED(); @@ -470,9 +455,6 @@ status_t handle_aes_sca_batch_encrypt_random(ujson_t *uj) { } aes_serial_advance_random(); } - if (fpga_mode) { - pentest_set_trigger_low(); - } TRY(aes_send_ciphertext(true, uj)); @@ -512,16 +494,10 @@ status_t handle_aes_sca_fvsr_data_batch_encrypt(ujson_t *uj) { sample_fixed = pentest_next_lfsr(1, kPentestLfsrOrder) & 0x1; } - if (fpga_mode) { - pentest_set_trigger_high(); - } for (uint32_t i = 0; i < num_encryptions; ++i) { aes_key_mask_and_config(batch_keys[i], kAesKeyLength); aes_encrypt(batch_plaintexts[i], kAesTextLength); } - if (fpga_mode) { - pentest_set_trigger_low(); - } TRY(aes_send_ciphertext(false, uj)); @@ -538,9 +514,6 @@ status_t handle_aes_sca_fvsr_key_batch_encrypt(ujson_t *uj) { return OUT_OF_RANGE(); } - if (fpga_mode) { - pentest_set_trigger_high(); - } for (uint32_t i = 0; i < num_encryptions; ++i) { if (aes_key_mask_and_config(batch_keys[i], kAesKeyLength) != aesScaOk) { return ABORTED(); @@ -549,9 +522,6 @@ status_t handle_aes_sca_fvsr_key_batch_encrypt(ujson_t *uj) { return ABORTED(); } } - if (fpga_mode) { - pentest_set_trigger_low(); - } TRY(aes_send_ciphertext(false, uj)); @@ -737,15 +707,9 @@ status_t handle_aes_sca_single_encrypt(ujson_t *uj) { block_ctr = 1; } - if (fpga_mode) { - pentest_set_trigger_high(); - } if (aes_encrypt(uj_data.text, uj_data.text_length) != aesScaOk) { return ABORTED(); } - if (fpga_mode) { - pentest_set_trigger_low(); - } TRY(aes_send_ciphertext(false, uj)); return OK_STATUS(); From e91a4d2037e4249724f5581ba604588e261f5dce Mon Sep 17 00:00:00 2001 From: Pascal Nasahl Date: Fri, 17 Jan 2025 15:45:47 +0100 Subject: [PATCH 2/3] [sca,aes] Get rid of aes_sca_error_t When porting the AES SCA tests from the old simpleserial to the new uJSON framework, we also copied the aes_sca_error_t structure. This structure was used to indicate whether a function inside the aes_sca program returned with an error. Instead, we simply can use the default status_t with the TRY() construct. This (a) simplifies the code and (b) follows the coding style of the OpenTitan code base. Signed-off-by: Pascal Nasahl --- .../penetrationtests/firmware/sca/aes_sca.c | 76 ++++++------------- .../penetrationtests/firmware/sca/aes_sca.h | 19 ----- 2 files changed, 23 insertions(+), 72 deletions(-) diff --git a/sw/device/tests/penetrationtests/firmware/sca/aes_sca.c b/sw/device/tests/penetrationtests/firmware/sca/aes_sca.c index f2150fbd402e2..112a25bed35e8 100644 --- a/sw/device/tests/penetrationtests/firmware/sca/aes_sca.c +++ b/sw/device/tests/penetrationtests/firmware/sca/aes_sca.c @@ -158,10 +158,9 @@ dif_aes_transaction_t transaction = { * @param key Key. * @param key_len Key length. */ -static aes_sca_error_t aes_key_mask_and_config(const uint8_t *key, - size_t key_len) { +static status_t aes_key_mask_and_config(const uint8_t *key, size_t key_len) { if (key_len != kAesKeyLength) { - return aesScaOutOfRange; + return ABORTED(); } dif_aes_key_share_t key_shares; // Mask the provided key. @@ -177,11 +176,9 @@ static aes_sca_error_t aes_key_mask_and_config(const uint8_t *key, key_shares.share0[i] = pentest_non_linear_layer(pentest_next_lfsr(1, kPentestLfsrMasking)); } - if (dif_aes_start(&aes, &transaction, &key_shares, NULL) != kDifOk) { - return aesScaAborted; - } + TRY(dif_aes_start(&aes, &transaction, &key_shares, NULL)); - return aesScaOk; + return OK_STATUS(); } /** @@ -201,23 +198,18 @@ static void aes_manual_trigger(void) { * @param plaintext Plaintext. * @param plaintext_len Length of the plaintext. */ -static aes_sca_error_t aes_encrypt(const uint8_t *plaintext, - size_t plaintext_len) { +static status_t aes_encrypt(const uint8_t *plaintext, size_t plaintext_len) { bool ready = false; do { - if (dif_aes_get_status(&aes, kDifAesStatusInputReady, &ready) != kDifOk) { - return aesScaAborted; - } + TRY(dif_aes_get_status(&aes, kDifAesStatusInputReady, &ready)); } while (!ready); dif_aes_data_t data; if (plaintext_len != sizeof(data.data)) { - return aesScaOutOfRange; + return ABORTED(); } memcpy(data.data, plaintext, plaintext_len); - if (dif_aes_load_data(&aes, data)) { - return aesScaAborted; - } + TRY(dif_aes_load_data(&aes, data)); // Start AES operation (this triggers the capture) and go to sleep. if (fpga_mode) { @@ -234,7 +226,7 @@ static aes_sca_error_t aes_encrypt(const uint8_t *plaintext, pentest_call_and_sleep(aes_manual_trigger, kIbexAesSleepCycles, true, false); } - return aesScaOk; + return OK_STATUS(); } /** @@ -365,7 +357,7 @@ status_t handle_aes_sca_batch_alternative_encrypt(ujson_t *uj) { // peripheral might trigger the reseeding of the internal masking PRNG which // disturbs SCA measurements. if (block_ctr > kBlockCtrMax) { - aes_key_mask_and_config(key_fixed, kAesKeyLength); + TRY(aes_key_mask_and_config(key_fixed, kAesKeyLength)); block_ctr = num_encryptions; } @@ -374,9 +366,7 @@ status_t handle_aes_sca_batch_alternative_encrypt(ujson_t *uj) { dif_aes_data_t ciphertext; for (uint32_t i = 0; i < num_encryptions; ++i) { // Encrypt - if (aes_encrypt(batch_plaintext, kAesTextLength) != aesScaOk) { - return ABORTED(); - } + TRY(aes_encrypt(batch_plaintext, kAesTextLength)); // Get ciphertext bool ready = false; @@ -411,16 +401,12 @@ status_t handle_aes_sca_batch_encrypt(ujson_t *uj) { // peripheral might trigger the reseeding of the internal masking PRNG which // disturbs SCA measurements. if (block_ctr > kBlockCtrMax) { - if (aes_key_mask_and_config(key_fixed, kAesKeyLength) != aesScaOk) { - return ABORTED(); - } + TRY(aes_key_mask_and_config(key_fixed, kAesKeyLength)); block_ctr = num_encryptions; } for (uint32_t i = 0; i < num_encryptions; ++i) { - if (aes_encrypt(plaintext_random, kAesTextLength) != aesScaOk) { - return ABORTED(); - } + TRY(aes_encrypt(plaintext_random, kAesTextLength)); aes_serial_advance_random(); } @@ -440,19 +426,13 @@ status_t handle_aes_sca_batch_encrypt_random(ujson_t *uj) { // peripheral might trigger the reseeding of the internal masking PRNG which // disturbs SCA measurements. if (block_ctr > kBlockCtrMax) { - if (aes_key_mask_and_config(key_random, kAesKeyLength) != aesScaOk) { - return ABORTED(); - } + TRY(aes_key_mask_and_config(key_random, kAesKeyLength)); block_ctr = num_encryptions; } for (uint32_t i = 0; i < num_encryptions; ++i) { - if (aes_key_mask_and_config(key_random, kAesKeyLength) != aesScaOk) { - return ABORTED(); - } - if (aes_encrypt(plaintext_random, kAesTextLength) != aesScaOk) { - return ABORTED(); - } + TRY(aes_key_mask_and_config(key_random, kAesKeyLength)); + TRY(aes_encrypt(plaintext_random, kAesTextLength)); aes_serial_advance_random(); } @@ -495,8 +475,8 @@ status_t handle_aes_sca_fvsr_data_batch_encrypt(ujson_t *uj) { } for (uint32_t i = 0; i < num_encryptions; ++i) { - aes_key_mask_and_config(batch_keys[i], kAesKeyLength); - aes_encrypt(batch_plaintexts[i], kAesTextLength); + TRY(aes_key_mask_and_config(batch_keys[i], kAesKeyLength)); + TRY(aes_encrypt(batch_plaintexts[i], kAesTextLength)); } TRY(aes_send_ciphertext(false, uj)); @@ -515,12 +495,8 @@ status_t handle_aes_sca_fvsr_key_batch_encrypt(ujson_t *uj) { } for (uint32_t i = 0; i < num_encryptions; ++i) { - if (aes_key_mask_and_config(batch_keys[i], kAesKeyLength) != aesScaOk) { - return ABORTED(); - } - if (aes_encrypt(batch_plaintexts[i], kAesTextLength) != aesScaOk) { - return ABORTED(); - } + TRY(aes_key_mask_and_config(batch_keys[i], kAesKeyLength)); + TRY(aes_encrypt(batch_plaintexts[i], kAesTextLength)); } TRY(aes_send_ciphertext(false, uj)); @@ -637,9 +613,7 @@ status_t handle_aes_sca_key_set(ujson_t *uj) { memcpy(key_fixed, uj_key_data.key, uj_key_data.key_length); block_ctr = 0; - if (aes_key_mask_and_config(key_fixed, uj_key_data.key_length) != aesScaOk) { - return ABORTED(); - } + TRY(aes_key_mask_and_config(key_fixed, uj_key_data.key_length)); return OK_STATUS(); } @@ -701,15 +675,11 @@ status_t handle_aes_sca_single_encrypt(ujson_t *uj) { // peripheral might trigger the reseeding of the internal masking PRNG which // disturbs SCA measurements. if (block_ctr > kBlockCtrMax) { - if (aes_key_mask_and_config(key_fixed, kAesKeyLength) != aesScaOk) { - return ABORTED(); - } + TRY(aes_key_mask_and_config(key_fixed, kAesKeyLength)); block_ctr = 1; } - if (aes_encrypt(uj_data.text, uj_data.text_length) != aesScaOk) { - return ABORTED(); - } + TRY(aes_encrypt(uj_data.text, uj_data.text_length)); TRY(aes_send_ciphertext(false, uj)); return OK_STATUS(); diff --git a/sw/device/tests/penetrationtests/firmware/sca/aes_sca.h b/sw/device/tests/penetrationtests/firmware/sca/aes_sca.h index de85070a845ba..9cc325128ecad 100644 --- a/sw/device/tests/penetrationtests/firmware/sca/aes_sca.h +++ b/sw/device/tests/penetrationtests/firmware/sca/aes_sca.h @@ -8,25 +8,6 @@ #include "sw/device/lib/base/status.h" #include "sw/device/lib/ujson/ujson.h" -/** - * The result of an AES SCA operation. - */ -typedef enum aes_sca_error { - /** - * Indicates that the operation succeeded. - */ - aesScaOk = 0, - /** - * Indicates some unspecified failure. - */ - aesScaAborted = 1, - /** - * Indicates that the attempted operation would attempt a read/write to an - * address that would go out of range. - */ - aesScaOutOfRange = 2 -} aes_sca_error_t; - /** * Simple serial 'a' (alternative batch encrypt) command handler. * From 8a379c6e26e5902cddd8063942c5b09ba224bafb Mon Sep 17 00:00:00 2001 From: Pascal Nasahl Date: Fri, 17 Jan 2025 17:14:18 +0100 Subject: [PATCH 3/3] [pentest] Load fixed seed after each dif_aes_start With the recent changes in the PRNG, it is necessary to reseed the internal AES state after the AUX register has been set by using the dif_aes_start() function. We need to do this when we want to switch off masking for AES SCA tests. Signed-off-by: Pascal Nasahl --- sw/device/sca/aes_serial.c | 47 +++++++++++++++++-- .../penetrationtests/firmware/sca/aes_sca.c | 40 +++++++++++++--- 2 files changed, 75 insertions(+), 12 deletions(-) diff --git a/sw/device/sca/aes_serial.c b/sw/device/sca/aes_serial.c index 00360ea8d8c65..d58b67b53c458 100644 --- a/sw/device/sca/aes_serial.c +++ b/sw/device/sca/aes_serial.c @@ -165,6 +165,37 @@ dif_aes_transaction_t transaction = { .ctrl_aux_lock = false, }; +/** + * Poll the IDLE AES register. + * + */ +static void aes_sca_wait_for_idle(void) { + bool idle = false; + do { + SS_CHECK_DIF_OK(dif_aes_get_status(&aes, kDifAesStatusIdle, &idle)); + } while (!idle); +} + +/** + * Load fixed seed into AES. + * + * Before calling this function, use + * aes_testutils_masking_prng_zero_output_seed() to initialize the entropy + * complex for performing AES SCA measurements with masking switched off. This + * function then loads the fixed seed into the AES, allowing the disable the + * masking. + * + * @param key Key. + * @param key_len Key length. + */ +static void aes_sca_load_fixed_seed(void) { + aes_sca_wait_for_idle(); + // Load magic seed such that masking is turned off. We need to do this after + // dif_aes_start() as then the force_masks is correctly set. + SS_CHECK_DIF_OK(dif_aes_trigger(&aes, kDifAesTriggerPrngReseed)); + aes_sca_wait_for_idle(); +} + /** * Mask and configure key. * @@ -192,7 +223,17 @@ static void aes_key_mask_and_config(const uint8_t *key, size_t key_len) { key_shares.share0[i] = pentest_non_linear_layer(pentest_next_lfsr(1, kPentestLfsrMasking)); } +#if !OT_IS_ENGLISH_BREAKFAST + CHECK_STATUS_OK(aes_testutils_masking_prng_zero_output_seed()); +#endif SS_CHECK_DIF_OK(dif_aes_start(&aes, &transaction, &key_shares, NULL)); + +#if !OT_IS_ENGLISH_BREAKFAST + if (transaction.force_masks) { + // Disable masking. Force the masking PRNG output value to 0. + aes_sca_load_fixed_seed(); + } +#endif } /** @@ -768,11 +809,7 @@ bool test_main(void) { if (transaction.force_masks) { LOG_INFO("Initializing entropy complex."); CHECK_STATUS_OK(aes_testutils_masking_prng_zero_output_seed()); - CHECK_DIF_OK(dif_aes_trigger(&aes, kDifAesTriggerPrngReseed)); - bool idle = false; - do { - CHECK_DIF_OK(dif_aes_get_status(&aes, kDifAesStatusIdle, &idle)); - } while (!idle); + aes_sca_load_fixed_seed(); } #endif CHECK_DIF_OK(dif_aes_trigger(&aes, kDifAesTriggerDataOutClear)); diff --git a/sw/device/tests/penetrationtests/firmware/sca/aes_sca.c b/sw/device/tests/penetrationtests/firmware/sca/aes_sca.c index 112a25bed35e8..09a99f1473419 100644 --- a/sw/device/tests/penetrationtests/firmware/sca/aes_sca.c +++ b/sw/device/tests/penetrationtests/firmware/sca/aes_sca.c @@ -31,6 +31,7 @@ enum { kAesKeyLengthMax = 32, kAesKeyLength = 16, kAesTextLength = 16, + kTestTimeout = (1000 * 1000), /** * Number of cycles (at `kClockFreqCpuHz`) that Ibex should sleep to minimize * noise during AES operations. Caution: This number should be chosen to @@ -147,6 +148,28 @@ dif_aes_transaction_t transaction = { .ctrl_aux_lock = false, }; +/** + * Load fixed seed into AES. + * + * Before calling this function, use + * aes_testutils_masking_prng_zero_output_seed() to initialize the entropy + * complex for performing AES SCA measurements with masking switched off. This + * function then loads the fixed seed into the AES, allowing the disable the + * masking. + * + * @param key Key. + * @param key_len Key length. + */ +static status_t aes_sca_load_fixed_seed(void) { + AES_TESTUTILS_WAIT_FOR_STATUS(&aes, kDifAesStatusIdle, true, kTestTimeout); + // Load magic seed such that masking is turned off. We need to do this after + // dif_aes_start() as then the force_masks is correctly set. + TRY(dif_aes_trigger(&aes, kDifAesTriggerPrngReseed)); + AES_TESTUTILS_WAIT_FOR_STATUS(&aes, kDifAesStatusIdle, true, kTestTimeout); + + return OK_STATUS(); +} + /** * Mask and configure key. * @@ -178,6 +201,13 @@ static status_t aes_key_mask_and_config(const uint8_t *key, size_t key_len) { } TRY(dif_aes_start(&aes, &transaction, &key_shares, NULL)); +#if !OT_IS_ENGLISH_BREAKFAST + if (transaction.force_masks) { + // Disable masking. Force the masking PRNG output value to 0. + TRY(aes_sca_load_fixed_seed()); + } +#endif + return OK_STATUS(); } @@ -640,16 +670,12 @@ status_t handle_aes_pentest_seed_lfsr(ujson_t *uj) { } // Load the magic seed into the PRNG. After this, the PRNG outputs // an all-zero vector. - TRY(dif_aes_trigger(&aes, kDifAesTriggerPrngReseed)); - bool idle = false; - do { - TRY(dif_aes_get_status(&aes, kDifAesStatusIdle, &idle)); - } while (!idle); - // Load the PRNG output into the buffer stage. - TRY(dif_aes_trigger(&aes, kDifAesTriggerDataOutClear)); + TRY(aes_sca_load_fixed_seed()); } #endif + TRY(dif_aes_trigger(&aes, kDifAesTriggerDataOutClear)); + AES_TESTUTILS_WAIT_FOR_STATUS(&aes, kDifAesStatusIdle, true, kTestTimeout); return OK_STATUS(); }