From 680e92eacf7d2336eee87af4be77a8130f6a380f Mon Sep 17 00:00:00 2001 From: Jade Philipoom Date: Fri, 23 May 2025 15:32:31 +0200 Subject: [PATCH 01/51] [crypto] Randomize memory before generating symmetric keys. This is a side-channel mitigation. Signed-off-by: Jade Philipoom (cherry picked from commit beb00a033b669e46decc1be25df9f5ac6e8ba5da) --- sw/device/lib/crypto/impl/key_transport.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sw/device/lib/crypto/impl/key_transport.c b/sw/device/lib/crypto/impl/key_transport.c index 8a68113fa362e..5f4d411d83413 100644 --- a/sw/device/lib/crypto/impl/key_transport.c +++ b/sw/device/lib/crypto/impl/key_transport.c @@ -10,6 +10,7 @@ #include "sw/device/lib/crypto/impl/integrity.h" #include "sw/device/lib/crypto/impl/keyblob.h" #include "sw/device/lib/crypto/impl/status.h" +#include "sw/device/lib/crypto/drivers/entropy.h" #include "sw/device/lib/crypto/include/datatypes.h" #include "sw/device/lib/crypto/include/drbg.h" @@ -26,6 +27,9 @@ otcrypto_status_t otcrypto_symmetric_keygen( // hardware-backed or non-symmetric keys. HARDENED_TRY(keyblob_ensure_xor_masked(key->config)); + // Ensure that the entropy complex is initialized. + HARDENED_TRY(entropy_complex_check()); + // Get pointers to the shares within the keyblob. Fails if the key length // doesn't match the mode. uint32_t *share0; @@ -42,6 +46,10 @@ otcrypto_status_t otcrypto_symmetric_keygen( .len = keyblob_share_num_words(key->config), }; + // Randomize the memory before writing the shares. + hardened_memshred(share0_buf.data, share0_buf.len); + hardened_memshred(share1_buf.data, share1_buf.len); + // Construct an empty buffer for the "additional input" to the DRBG generate // function. otcrypto_const_byte_buf_t empty = {.data = NULL, .len = 0}; From b28b784777fe718271ff56eaa15adfacd304f370 Mon Sep 17 00:00:00 2001 From: Jade Philipoom Date: Fri, 23 May 2025 15:37:51 +0200 Subject: [PATCH 02/51] [crypto] Randomize buffer before constructing a keyblob. This is a side-channel mitigation. Signed-off-by: Jade Philipoom (cherry picked from commit c2e6bf29d56259bcea575804d31a605650cc6ac2) --- sw/device/lib/crypto/impl/keyblob.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sw/device/lib/crypto/impl/keyblob.c b/sw/device/lib/crypto/impl/keyblob.c index 7183ee6b1def1..d21f83ac7bff8 100644 --- a/sw/device/lib/crypto/impl/keyblob.c +++ b/sw/device/lib/crypto/impl/keyblob.c @@ -97,6 +97,9 @@ status_t keyblob_from_shares(const uint32_t *share0, const uint32_t *share1, // Entropy complex must be initialized for `hardened_memcpy`. HARDENED_TRY(entropy_complex_check()); + // Randomize the keyblob contents before writing shares. + hardened_memshred(keyblob, keyblob_num_words(config)); + size_t share_words = keyblob_share_num_words(config); hardened_memcpy(keyblob, share0, share_words); hardened_memcpy(keyblob + share_words, share1, share_words); From e6638294c4891e500a14d57dc6dfbdab8c7427ed Mon Sep 17 00:00:00 2001 From: Jade Philipoom Date: Fri, 23 May 2025 15:47:33 +0200 Subject: [PATCH 03/51] [crypto] Use the `unmask` function in HKDF. Noticed this incidentally; I suspect the convenience unmask function didn't exist when this was first written. Saves a bit of code size. Signed-off-by: Jade Philipoom (cherry picked from commit 0e07e38310e1f43258e4864216d21b89b5010623) --- sw/device/lib/crypto/impl/hkdf.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/sw/device/lib/crypto/impl/hkdf.c b/sw/device/lib/crypto/impl/hkdf.c index c0abafc215da6..fb64d9ea19fb3 100644 --- a/sw/device/lib/crypto/impl/hkdf.c +++ b/sw/device/lib/crypto/impl/hkdf.c @@ -172,13 +172,9 @@ otcrypto_status_t otcrypto_hkdf_extract(const otcrypto_blinded_key_t *ikm, // struct. // Unmask the input key. - uint32_t *ikm_share0; - uint32_t *ikm_share1; - HARDENED_TRY(keyblob_to_shares(ikm, &ikm_share0, &ikm_share1)); uint32_t unmasked_ikm_data[keyblob_share_num_words(ikm->config)]; - for (size_t i = 0; i < ARRAYSIZE(unmasked_ikm_data); i++) { - unmasked_ikm_data[i] = ikm_share0[i] ^ ikm_share1[i]; - } + HARDENED_TRY( + keyblob_key_unmask(ikm, ARRAYSIZE(unmasked_ikm_data), unmasked_ikm_data)); otcrypto_const_byte_buf_t unmasked_ikm = { .data = (unsigned char *)unmasked_ikm_data, .len = ikm->config.key_length, From 639a58f4e3cb7aaaaf8851d343630017c9703fe3 Mon Sep 17 00:00:00 2001 From: Jade Philipoom Date: Fri, 23 May 2025 15:57:00 +0200 Subject: [PATCH 04/51] [crypto] Randomize buffers in key transport functions. This is a side-channel mitigation Signed-off-by: Jade Philipoom (cherry picked from commit c192fbc8f3f745e3aa8f554af77bff00664b83a6) --- sw/device/lib/crypto/impl/key_transport.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/sw/device/lib/crypto/impl/key_transport.c b/sw/device/lib/crypto/impl/key_transport.c index 5f4d411d83413..3cf7da92d47c4 100644 --- a/sw/device/lib/crypto/impl/key_transport.c +++ b/sw/device/lib/crypto/impl/key_transport.c @@ -6,11 +6,11 @@ #include "sw/device/lib/base/hardened_memory.h" #include "sw/device/lib/base/memory.h" +#include "sw/device/lib/crypto/drivers/entropy.h" #include "sw/device/lib/crypto/impl/aes_kwp/aes_kwp.h" #include "sw/device/lib/crypto/impl/integrity.h" #include "sw/device/lib/crypto/impl/keyblob.h" #include "sw/device/lib/crypto/impl/status.h" -#include "sw/device/lib/crypto/drivers/entropy.h" #include "sw/device/lib/crypto/include/datatypes.h" #include "sw/device/lib/crypto/include/drbg.h" @@ -177,6 +177,9 @@ otcrypto_status_t otcrypto_key_wrap(const otcrypto_blinded_key_t *key_to_wrap, return OTCRYPTO_BAD_ARGS; } + // Ensure the entropy complex is initialized. + HARDENED_TRY(entropy_complex_check()); + // Check the integrity of the key material we are wrapping. if (launder32(integrity_blinded_key_check(key_to_wrap)) != kHardenedBoolTrue) { @@ -213,6 +216,7 @@ otcrypto_status_t otcrypto_key_wrap(const otcrypto_blinded_key_t *key_to_wrap, uint32_t config_words = sizeof(otcrypto_key_config_t) / sizeof(uint32_t); size_t plaintext_num_words = config_words + 2 + keyblob_words; uint32_t plaintext[plaintext_num_words]; + hardened_memshred(plaintext, ARRAYSIZE(plaintext)); hardened_memcpy(plaintext, (uint32_t *)&key_to_wrap->config, config_words); plaintext[config_words] = key_to_wrap->checksum; plaintext[config_words + 1] = keyblob_words; @@ -235,6 +239,9 @@ otcrypto_status_t otcrypto_key_unwrap(otcrypto_const_word32_buf_t wrapped_key, return OTCRYPTO_BAD_ARGS; } + // Ensure the entropy complex is initialized. + HARDENED_TRY(entropy_complex_check()); + // Check the integrity/lengths/mode of the key encryption key, and construct // an internal AES key. aes_key_t kek; @@ -247,6 +254,7 @@ otcrypto_status_t otcrypto_key_unwrap(otcrypto_const_word32_buf_t wrapped_key, // Unwrap the key. uint32_t plaintext[wrapped_key.len]; + hardened_memshred(plaintext, ARRAYSIZE(plaintext)); HARDENED_TRY(aes_kwp_unwrap(kek, wrapped_key.data, wrapped_key.len * sizeof(uint32_t), success, plaintext)); @@ -331,6 +339,9 @@ otcrypto_status_t otcrypto_export_blinded_key( return OTCRYPTO_BAD_ARGS; } + // Ensure the entropy complex is initialized. + HARDENED_TRY(entropy_complex_check()); + // Check key integrity. if (launder32(integrity_blinded_key_check(blinded_key)) != kHardenedBoolTrue) { @@ -359,6 +370,10 @@ otcrypto_status_t otcrypto_export_blinded_key( HARDENED_CHECK_EQ(key_share1.len, keyblob_share_num_words(blinded_key->config)); + // Randomize the destination buffers. + hardened_memshred(key_share0.data, key_share0.len); + hardened_memshred(key_share1.data, key_share1.len); + // Check the length of the keyblob. size_t keyblob_words = launder32(keyblob_num_words(blinded_key->config)); if ((blinded_key->keyblob_length % sizeof(uint32_t) != 0) || From 6d052a3f30a3d2c7b674af05d283638d7aa9d379 Mon Sep 17 00:00:00 2001 From: Jade Philipoom Date: Fri, 23 May 2025 16:09:07 +0200 Subject: [PATCH 05/51] [crypto] Randomize destination buffers for ECC keys and shared secrets. This is a side-channel mitigation. Signed-off-by: Jade Philipoom (cherry picked from commit fb21d9a8ecf630c62d91d514c810d02419dba316) --- sw/device/lib/crypto/impl/ecc_p256.c | 12 ++++++++++-- sw/device/lib/crypto/impl/ecc_p384.c | 14 +++++++++++--- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/sw/device/lib/crypto/impl/ecc_p256.c b/sw/device/lib/crypto/impl/ecc_p256.c index be0fd32cc6c32..3d7b7ddceeae1 100644 --- a/sw/device/lib/crypto/impl/ecc_p256.c +++ b/sw/device/lib/crypto/impl/ecc_p256.c @@ -4,6 +4,7 @@ #include "sw/device/lib/crypto/include/ecc_p256.h" +#include "sw/device/lib/base/hardened_memory.h" #include "sw/device/lib/crypto/drivers/entropy.h" #include "sw/device/lib/crypto/drivers/hmac.h" #include "sw/device/lib/crypto/impl/ecc/p256.h" @@ -202,14 +203,19 @@ static status_t internal_p256_keygen_finalize( HARDENED_TRY(p256_sideload_keygen_finalize(pk)); } else if (launder32(private_key->config.hw_backed) == kHardenedBoolFalse) { HARDENED_CHECK_EQ(private_key->config.hw_backed, kHardenedBoolFalse); + + // Randomize the keyblob before writing secret data. + hardened_memshred(private_key->keyblob, + keyblob_num_words(private_key->config)); + p256_masked_scalar_t *sk = (p256_masked_scalar_t *)private_key->keyblob; HARDENED_TRY(p256_keygen_finalize(sk, pk)); - private_key->checksum = integrity_blinded_checksum(private_key); } else { return OTCRYPTO_BAD_ARGS; } - // Prepare the public key. + // Set the key checksums. + private_key->checksum = integrity_blinded_checksum(private_key); public_key->checksum = integrity_unblinded_checksum(public_key); // Clear the OTBN sideload slot (in case the seed was sideloaded). @@ -495,6 +501,8 @@ otcrypto_status_t otcrypto_ecdh_p256_async_finalize( // occurs after this point then the keys would be unrecoverable. This should // be the last potentially error-causing line before returning to the caller. p256_ecdh_shared_key_t ss; + hardened_memshred(ss.share0, ARRAYSIZE(ss.share0)); + hardened_memshred(ss.share1, ARRAYSIZE(ss.share1)); HARDENED_TRY(p256_ecdh_finalize(&ss)); HARDENED_TRY(keyblob_from_shares(ss.share0, ss.share1, shared_secret->config, diff --git a/sw/device/lib/crypto/impl/ecc_p384.c b/sw/device/lib/crypto/impl/ecc_p384.c index 34a9c7bccbc3b..09e4e0cfc35af 100644 --- a/sw/device/lib/crypto/impl/ecc_p384.c +++ b/sw/device/lib/crypto/impl/ecc_p384.c @@ -4,6 +4,7 @@ #include "sw/device/lib/crypto/include/ecc_p384.h" +#include "sw/device/lib/base/hardened_memory.h" #include "sw/device/lib/crypto/drivers/entropy.h" #include "sw/device/lib/crypto/drivers/hmac.h" #include "sw/device/lib/crypto/impl/ecc/p384.h" @@ -201,18 +202,23 @@ static status_t internal_p384_keygen_finalize( HARDENED_CHECK_EQ(private_key->config.hw_backed, kHardenedBoolTrue); HARDENED_TRY(p384_sideload_keygen_finalize(pk)); } else if (launder32(private_key->config.hw_backed) == kHardenedBoolFalse) { - p384_masked_scalar_t *sk = (p384_masked_scalar_t *)private_key->keyblob; + HARDENED_CHECK_EQ(private_key->config.hw_backed, kHardenedBoolFalse); + + // Randomize the keyblob before writing secret data. + hardened_memshred(private_key->keyblob, + keyblob_num_words(private_key->config)); + // Note: This operation wipes DMEM after retrieving the keys, so if an error // occurs after this point then the keys would be unrecoverable. This should // be the last potentially error-causing line before returning to the // caller. - HARDENED_CHECK_EQ(private_key->config.hw_backed, kHardenedBoolFalse); + p384_masked_scalar_t *sk = (p384_masked_scalar_t *)private_key->keyblob; HARDENED_TRY(p384_keygen_finalize(sk, pk)); - private_key->checksum = integrity_blinded_checksum(private_key); } else { return OTCRYPTO_BAD_ARGS; } + private_key->checksum = integrity_blinded_checksum(private_key); public_key->checksum = integrity_unblinded_checksum(public_key); return OTCRYPTO_OK; } @@ -498,6 +504,8 @@ otcrypto_status_t otcrypto_ecdh_p384_async_finalize( // occurs after this point then the keys would be unrecoverable. This should // be the last potentially error-causing line before returning to the caller. p384_ecdh_shared_key_t ss; + hardened_memshred(ss.share0, ARRAYSIZE(ss.share0)); + hardened_memshred(ss.share1, ARRAYSIZE(ss.share1)); HARDENED_TRY(p384_ecdh_finalize(&ss)); HARDENED_TRY(keyblob_from_shares(ss.share0, ss.share1, shared_secret->config, From fd42774b7f58890d7e02a40a0fef67579f05f7cd Mon Sep 17 00:00:00 2001 From: Jade Philipoom Date: Fri, 23 May 2025 16:12:19 +0200 Subject: [PATCH 06/51] [crypto] Randomize destination buffer for KMAC-KDF. This is a side-channel mitigation. Signed-off-by: Jade Philipoom (cherry picked from commit 0bf3598b687b305ba0e4ae9ec3fe0d42a54e545c) --- sw/device/lib/crypto/impl/BUILD | 2 ++ sw/device/lib/crypto/impl/kmac_kdf.c | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/sw/device/lib/crypto/impl/BUILD b/sw/device/lib/crypto/impl/BUILD index 1df16b901e749..b3455a2aafd95 100644 --- a/sw/device/lib/crypto/impl/BUILD +++ b/sw/device/lib/crypto/impl/BUILD @@ -195,8 +195,10 @@ cc_library( ":integrity", ":keyblob", ":status", + "//sw/device/lib/base:hardened_memory", "//sw/device/lib/base:math", "//sw/device/lib/crypto/drivers:kmac", + "//sw/device/lib/crypto/drivers:rv_core_ibex", "//sw/device/lib/crypto/include:datatypes", ], ) diff --git a/sw/device/lib/crypto/impl/kmac_kdf.c b/sw/device/lib/crypto/impl/kmac_kdf.c index 2217c6ea41ac9..6e11fee833d00 100644 --- a/sw/device/lib/crypto/impl/kmac_kdf.c +++ b/sw/device/lib/crypto/impl/kmac_kdf.c @@ -4,6 +4,7 @@ #include "sw/device/lib/crypto/include/kmac_kdf.h" +#include "sw/device/lib/base/hardened_memory.h" #include "sw/device/lib/base/math.h" #include "sw/device/lib/crypto/drivers/kmac.h" #include "sw/device/lib/crypto/impl/integrity.h" @@ -107,6 +108,10 @@ otcrypto_status_t otcrypto_kmac_kdf( return OTCRYPTO_BAD_ARGS; } + // Randomize the keyblob memory. + hardened_memshred(output_key_material->keyblob, + keyblob_num_words(output_key_material->config)); + switch (launder32(key_derivation_key->config.key_mode)) { case kOtcryptoKeyModeKdfKmac128: { HARDENED_CHECK_EQ(key_derivation_key->config.key_mode, From 2c5818c3cdf2c4e08e16f09b95b6ccb3d276b098 Mon Sep 17 00:00:00 2001 From: Jade Philipoom Date: Fri, 23 May 2025 16:34:48 +0200 Subject: [PATCH 07/51] [crypto] Randomize plaintext and key buffers in RSA code. THis is a side-channel mitigation. Also fixes a byte-word confusion in RSA decryption that I noticed incidentally. Signed-off-by: Jade Philipoom (cherry picked from commit be8d02ab5a4df605d69ee3eab2d7c7f21c2ba9b5) --- sw/device/lib/crypto/impl/rsa.c | 13 +++++++++ .../lib/crypto/impl/rsa/rsa_encryption.c | 27 ++++++++++++------- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/sw/device/lib/crypto/impl/rsa.c b/sw/device/lib/crypto/impl/rsa.c index a612208f1a77d..95845a6241f8a 100644 --- a/sw/device/lib/crypto/impl/rsa.c +++ b/sw/device/lib/crypto/impl/rsa.c @@ -5,6 +5,7 @@ #include "sw/device/lib/crypto/include/rsa.h" #include "sw/device/lib/base/hardened_memory.h" +#include "sw/device/lib/base/math.h" #include "sw/device/lib/crypto/drivers/entropy.h" #include "sw/device/lib/crypto/impl/integrity.h" #include "sw/device/lib/crypto/impl/rsa/rsa_encryption.h" @@ -201,6 +202,10 @@ otcrypto_status_t otcrypto_rsa_private_key_from_exponents( // Check the mode and lengths for the private key. HARDENED_TRY(private_key_structural_check(size, private_key)); + // Randomize the keyblob. + hardened_memshred(private_key->keyblob, + ceil_div(private_key->keyblob_length, sizeof(uint32_t))); + switch (size) { case kOtcryptoRsaSize2048: { if (private_key->keyblob_length != sizeof(rsa_2048_private_key_t) || @@ -460,6 +465,10 @@ otcrypto_status_t otcrypto_rsa_keygen_async_finalize( // Check the caller-provided private key buffer. HARDENED_TRY(private_key_structural_check(size, private_key)); + // Randomize the keyblob memory. + hardened_memshred(private_key->keyblob, + ceil_div(private_key->keyblob_length, sizeof(uint32_t))); + // Call the required finalize() operation. switch (size) { case kOtcryptoRsaSize2048: { @@ -570,6 +579,10 @@ otcrypto_status_t otcrypto_rsa_keypair_from_cofactor_async_finalize( // Check the caller-provided private key buffer. HARDENED_TRY(private_key_structural_check(size, private_key)); + // Randomize the keyblob memory. + hardened_memshred(private_key->keyblob, + ceil_div(private_key->keyblob_length, sizeof(uint32_t))); + // Call the required finalize() operation. switch (size) { case kOtcryptoRsaSize2048: { diff --git a/sw/device/lib/crypto/impl/rsa/rsa_encryption.c b/sw/device/lib/crypto/impl/rsa/rsa_encryption.c index d6c0d15981b49..ce9f66fa886d9 100644 --- a/sw/device/lib/crypto/impl/rsa/rsa_encryption.c +++ b/sw/device/lib/crypto/impl/rsa/rsa_encryption.c @@ -7,6 +7,7 @@ #include "sw/device/lib/base/hardened.h" #include "sw/device/lib/base/hardened_memory.h" #include "sw/device/lib/base/math.h" +#include "sw/device/lib/base/memory.h" #include "sw/device/lib/crypto/drivers/entropy.h" #include "sw/device/lib/crypto/impl/rsa/rsa_modexp.h" #include "sw/device/lib/crypto/impl/rsa/rsa_padding.h" @@ -20,6 +21,7 @@ status_t rsa_encrypt_2048_start(const rsa_2048_public_key_t *public_key, const uint8_t *label, size_t label_bytelen) { // Encode the message. rsa_2048_int_t encoded_message; + hardened_memshred(encoded_message.data, ARRAYSIZE(encoded_message.data)); HARDENED_TRY(rsa_padding_oaep_encode( hash_mode, message, message_bytelen, label, label_bytelen, ARRAYSIZE(encoded_message.data), encoded_message.data)); @@ -43,27 +45,30 @@ status_t rsa_decrypt_2048_start(const rsa_2048_private_key_t *private_key, status_t rsa_decrypt_finalize(const otcrypto_hash_mode_t hash_mode, const uint8_t *label, size_t label_bytelen, - size_t plaintext_max_wordlen, uint8_t *plaintext, + size_t plaintext_max_bytelen, uint8_t *plaintext, size_t *plaintext_len) { // Wait for OTBN to complete and get the size for the last RSA operation. size_t num_words; HARDENED_TRY(rsa_modexp_wait(&num_words)); - // Guard against overflow in the plaintext length checks. - if (plaintext_max_wordlen > UINT32_MAX / sizeof(uint32_t)) { - return OTCRYPTO_BAD_ARGS; - } - // Check that enough space has been allocated for the plaintext. size_t max_plaintext_bytelen = 0; HARDENED_TRY(rsa_padding_oaep_max_message_bytelen(hash_mode, num_words, &max_plaintext_bytelen)); - if (plaintext_max_wordlen < - ceil_div(max_plaintext_bytelen, sizeof(uint32_t))) { + if (plaintext_max_bytelen < max_plaintext_bytelen) { return OTCRYPTO_BAD_ARGS; } - HARDENED_CHECK_GE(plaintext_max_wordlen * sizeof(uint32_t), - max_plaintext_bytelen); + HARDENED_CHECK_GE(plaintext_max_bytelen, max_plaintext_bytelen); + + // Randomize the plaintext destination buffer as best we can, considering its + // alignment. + ptrdiff_t misalignment = misalignment32_of((uintptr_t)plaintext); + size_t aligned_offset = + (sizeof(uint32_t) - (size_t)misalignment) % sizeof(uint32_t); + size_t num_aligned_full_words = + (plaintext_max_bytelen - aligned_offset) / sizeof(uint32_t); + hardened_memshred((uint32_t *)((uintptr_t)plaintext + aligned_offset), + num_aligned_full_words); // Call the appropriate `finalize()` operation to get the recovered encoded // message. @@ -105,6 +110,7 @@ status_t rsa_encrypt_3072_start(const rsa_3072_public_key_t *public_key, const uint8_t *label, size_t label_bytelen) { // Encode the message. rsa_3072_int_t encoded_message; + hardened_memshred(encoded_message.data, ARRAYSIZE(encoded_message.data)); HARDENED_TRY(rsa_padding_oaep_encode( hash_mode, message, message_bytelen, label, label_bytelen, ARRAYSIZE(encoded_message.data), encoded_message.data)); @@ -132,6 +138,7 @@ status_t rsa_encrypt_4096_start(const rsa_4096_public_key_t *public_key, const uint8_t *label, size_t label_bytelen) { // Encode the message. rsa_4096_int_t encoded_message; + hardened_memshred(encoded_message.data, ARRAYSIZE(encoded_message.data)); HARDENED_TRY(rsa_padding_oaep_encode( hash_mode, message, message_bytelen, label, label_bytelen, ARRAYSIZE(encoded_message.data), encoded_message.data)); From 55b1d2c17b44053c46d4f2fa6ea5caf5ba43c6a5 Mon Sep 17 00:00:00 2001 From: Jade Philipoom Date: Fri, 23 May 2025 16:51:47 +0200 Subject: [PATCH 08/51] [crypto] Improve side-channel defenses in DRBG. Randomize buffers where we can, being careful not to use hardening primitives that rely on entropy in the manual-DRBG routines. Signed-off-by: Jade Philipoom (cherry picked from commit 8cc70192597251570add5f072abeec195b4ab9e5) --- sw/device/lib/crypto/impl/drbg.c | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/sw/device/lib/crypto/impl/drbg.c b/sw/device/lib/crypto/impl/drbg.c index 103feb5b6bb13..228eb1f1ebe7e 100644 --- a/sw/device/lib/crypto/impl/drbg.c +++ b/sw/device/lib/crypto/impl/drbg.c @@ -25,7 +25,7 @@ * @param[out] seed_material Resulting entropy complex seed. * @return OK or error. */ -static otcrypto_status_t seed_material_construct( +static status_t seed_material_construct( otcrypto_const_byte_buf_t value, entropy_seed_material_t *seed_material) { if (value.len > kEntropySeedBytes) { return OTCRYPTO_BAD_ARGS; @@ -34,17 +34,14 @@ static otcrypto_status_t seed_material_construct( size_t nwords = ceil_div(value.len, sizeof(uint32_t)); seed_material->len = nwords; - // Initialize the set words to zero. - memset(seed_material->data, 0, nwords * sizeof(uint32_t)); - - if (value.len == 0) { - return OTCRYPTO_OK; - } - // Copy seed data. // TODO(#17711) Change to `hardened_memcpy`. memcpy(seed_material->data, value.data, value.len); + // Set any unset bytes to zero. + size_t unset_bytes = nwords * sizeof(uint32_t) - value.len; + memset(((unsigned char *)seed_material->data) + value.len, 0, unset_bytes); + return OTCRYPTO_OK; } @@ -95,6 +92,7 @@ otcrypto_status_t otcrypto_drbg_instantiate( HARDENED_TRY(entropy_complex_check()); entropy_seed_material_t seed_material; + hardened_memshred(seed_material.data, ARRAYSIZE(seed_material.data)); seed_material_construct(perso_string, &seed_material); HARDENED_TRY(entropy_csrng_uninstantiate()); @@ -113,6 +111,7 @@ otcrypto_status_t otcrypto_drbg_reseed( HARDENED_TRY(entropy_complex_check()); entropy_seed_material_t seed_material; + hardened_memshred(seed_material.data, ARRAYSIZE(seed_material.data)); seed_material_construct(additional_input, &seed_material); return entropy_csrng_reseed(/*disable_trng_input=*/kHardenedBoolFalse, @@ -195,6 +194,12 @@ static otcrypto_status_t generate(hardened_bool_t fips_check, otcrypto_status_t otcrypto_drbg_generate( otcrypto_const_byte_buf_t additional_input, otcrypto_word32_buf_t drbg_output) { + // Ensure the entropy complex is initialized. + HARDENED_TRY(entropy_complex_check()); + + // Randomize destination buffer. + hardened_memshred(drbg_output.data, drbg_output.len); + return generate(/*fips_check=*/kHardenedBoolTrue, additional_input, drbg_output); } From 5f1db5724214bfe82aef2a27114fce65010c7ab3 Mon Sep 17 00:00:00 2001 From: Jade Philipoom Date: Fri, 23 May 2025 17:22:07 +0200 Subject: [PATCH 09/51] [crypto] Randomize sensitive buffers in AES. This is a side channel mitigation. Also makes a small code size optimization around sideloaded-key clearing that I noticed incidentally. Signed-off-by: Jade Philipoom (cherry picked from commit 6b9962495a9414036f35736284139840359afaca) --- sw/device/lib/crypto/impl/aes.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/sw/device/lib/crypto/impl/aes.c b/sw/device/lib/crypto/impl/aes.c index b3396b37b866f..8e4e22cf5eff2 100644 --- a/sw/device/lib/crypto/impl/aes.c +++ b/sw/device/lib/crypto/impl/aes.c @@ -197,6 +197,9 @@ static status_t num_padded_blocks_get(size_t plaintext_len, /** * Return the block at index i for the given input and padding mode. * + * Uses hardening primitives internally that consume entropy; check the entropy + * complex is up before calling. + * * @param input Input data buffer. * @param padding Padding mode. * @param index Block index. @@ -214,6 +217,9 @@ static status_t get_block(otcrypto_const_byte_buf_t input, // padding. HARDENED_CHECK_LE(index, num_full_blocks + 1); + // Randomize the destination buffer. + hardened_memshred(block->data, ARRAYSIZE(block->data)); + if (launder32(index) < num_full_blocks) { HARDENED_CHECK_LT(index, num_full_blocks); // No need to worry about padding, just copy the data into the output @@ -352,7 +358,7 @@ otcrypto_status_t otcrypto_aes(otcrypto_blinded_key_t *key, // cipher. for (i = 0; launder32(i) < block_offset; ++i) { HARDENED_TRY(get_block(cipher_input, aes_padding, i, &block_in)); - TRY(aes_update(/*dest=*/NULL, &block_in)); + HARDENED_TRY(aes_update(/*dest=*/NULL, &block_in)); } // Check that the loop ran for the correct number of iterations. HARDENED_CHECK_EQ(i, block_offset); @@ -361,7 +367,8 @@ otcrypto_status_t otcrypto_aes(otcrypto_blinded_key_t *key, // output buffer. for (i = block_offset; launder32(i) < input_nblocks; ++i) { HARDENED_TRY(get_block(cipher_input, aes_padding, i, &block_in)); - TRY(aes_update(&block_out, &block_in)); + hardened_memshred(block_out.data, ARRAYSIZE(block_out.data)); + HARDENED_TRY(aes_update(&block_out, &block_in)); // TODO(#17711) Change to `hardened_memcpy`. memcpy(&cipher_output.data[(i - block_offset) * kAesBlockNumBytes], block_out.data, kAesBlockNumBytes); @@ -388,10 +395,6 @@ otcrypto_status_t otcrypto_aes(otcrypto_blinded_key_t *key, hardened_memcpy(iv.data, aes_iv.data, kAesBlockNumWords); } - // If the key was sideloaded, clear it. - if (key->config.hw_backed == kHardenedBoolTrue) { - HARDENED_TRY(keymgr_sideload_clear_aes()); - } - - return OTCRYPTO_OK; + // In case the key was sideloaded, clear it. + return keymgr_sideload_clear_aes(); } From 4cdbb0736ab39a98c5b54f6684596ab3a59e541e Mon Sep 17 00:00:00 2001 From: Jade Philipoom Date: Fri, 23 May 2025 17:33:20 +0200 Subject: [PATCH 10/51] [crypto] Randomize sensitive buffers in drivers. This is a side-channel mitigation. Signed-off-by: Jade Philipoom (cherry picked from commit 4d76c3dce79d31c8f12e4f76d2c988a6866f20b3) --- sw/device/lib/crypto/drivers/BUILD | 4 ++++ sw/device/lib/crypto/drivers/aes.c | 15 ++++++++++----- sw/device/lib/crypto/drivers/keymgr.c | 5 +++++ sw/device/lib/crypto/drivers/kmac.c | 11 +++++++++++ 4 files changed, 30 insertions(+), 5 deletions(-) diff --git a/sw/device/lib/crypto/drivers/BUILD b/sw/device/lib/crypto/drivers/BUILD index 6efaf66dae362..666bf7e2db76e 100644 --- a/sw/device/lib/crypto/drivers/BUILD +++ b/sw/device/lib/crypto/drivers/BUILD @@ -31,6 +31,7 @@ cc_library( hdrs = ["aes.h"], deps = [ ":entropy", + ":rv_core_ibex", "//hw/ip/aes/data:aes_c_regs", "//hw/top_earlgrey/sw/autogen:top_earlgrey", "//sw/device/lib/base:abs_mmio", @@ -66,10 +67,12 @@ cc_library( ], deps = [ ":entropy", + ":rv_core_ibex", "//hw/ip/keymgr/data:keymgr_c_regs", "//hw/top_earlgrey/sw/autogen:top_earlgrey", "//sw/device/lib/base:abs_mmio", "//sw/device/lib/base:bitfield", + "//sw/device/lib/base:hardened_memory", "//sw/device/lib/base:macros", "//sw/device/lib/crypto/impl:status", "//sw/device/lib/runtime:hart", @@ -112,6 +115,7 @@ cc_library( ], deps = [ ":entropy", + ":rv_core_ibex", "//hw/ip/kmac/data:kmac_c_regs", "//hw/top_earlgrey/sw/autogen:top_earlgrey", "//sw/device/lib/base:abs_mmio", diff --git a/sw/device/lib/crypto/drivers/aes.c b/sw/device/lib/crypto/drivers/aes.c index 33e25e8746f22..944e5de599748 100644 --- a/sw/device/lib/crypto/drivers/aes.c +++ b/sw/device/lib/crypto/drivers/aes.c @@ -10,6 +10,7 @@ #include "sw/device/lib/base/macros.h" #include "sw/device/lib/base/memory.h" #include "sw/device/lib/crypto/drivers/entropy.h" +#include "sw/device/lib/crypto/drivers/rv_core_ibex.h" #include "sw/device/lib/crypto/impl/status.h" #include "aes_regs.h" // Generated. @@ -24,6 +25,7 @@ enum { kAesKeyWordLen128 = 128 / (sizeof(uint32_t) * 8), kAesKeyWordLen192 = 192 / (sizeof(uint32_t) * 8), kAesKeyWordLen256 = 256 / (sizeof(uint32_t) * 8), + kAesKeyWordLenMax = kAesKeyWordLen256, }; /** @@ -47,6 +49,8 @@ static status_t spin_until(uint32_t bit) { * * If the key is sideloaded, this is a no-op. * + * Consumes randomness; the caller must ensure the entropy complex is up. + * * @param key AES key. * @return result, OK or error. */ @@ -75,12 +79,13 @@ static status_t aes_write_key(aes_key_t key) { } HARDENED_CHECK_EQ(i, key.key_len); - // NOTE: all eight share registers must be written; in the case we don't have - // enough key data, we fill it with zeroes. - for (size_t i = key.key_len; i < 8; ++i) { - abs_mmio_write32(share0 + i * sizeof(uint32_t), 0); - abs_mmio_write32(share1 + i * sizeof(uint32_t), 0); + // Write random words to remaining key registers. + for (; i < kAesKeyWordLenMax; i++) { + abs_mmio_write32(share0 + i * sizeof(uint32_t), ibex_rnd32_read()); + abs_mmio_write32(share1 + i * sizeof(uint32_t), ibex_rnd32_read()); } + HARDENED_CHECK_EQ(i, kAesKeyWordLenMax); + return spin_until(AES_STATUS_IDLE_BIT); } diff --git a/sw/device/lib/crypto/drivers/keymgr.c b/sw/device/lib/crypto/drivers/keymgr.c index c672de49bd9b6..62df634ae1e75 100644 --- a/sw/device/lib/crypto/drivers/keymgr.c +++ b/sw/device/lib/crypto/drivers/keymgr.c @@ -6,6 +6,7 @@ #include "sw/device/lib/base/abs_mmio.h" #include "sw/device/lib/base/bitfield.h" +#include "sw/device/lib/base/hardened_memory.h" #include "sw/device/lib/base/memory.h" #include "sw/device/lib/crypto/drivers/entropy.h" #include "sw/device/lib/crypto/impl/status.h" @@ -152,6 +153,10 @@ status_t keymgr_generate_key_sw(keymgr_diversification_t diversification, keymgr_start(diversification); HARDENED_TRY(keymgr_wait_until_done()); + // Randomize the destination buffer. + hardened_memshred(key->share0, kKeymgrOutputShareNumWords); + hardened_memshred(key->share1, kKeymgrOutputShareNumWords); + // Collect output. // TODO: for SCA hardening, randomize the order of these reads. for (size_t i = 0; i < kKeymgrOutputShareNumWords; i++) { diff --git a/sw/device/lib/crypto/drivers/kmac.c b/sw/device/lib/crypto/drivers/kmac.c index 988e9c070fde3..b9c008b3ac53d 100644 --- a/sw/device/lib/crypto/drivers/kmac.c +++ b/sw/device/lib/crypto/drivers/kmac.c @@ -525,6 +525,9 @@ static status_t kmac_init(kmac_operation_t operation, * * If the key is hardware-backed, this is a no-op. * + * Uses hardening primitives internally that consume entropy; the caller must + * ensure the entropy complex is up before calling. + * * @param key The input key passed as a struct. * @return Error code. */ @@ -546,6 +549,14 @@ static status_t kmac_write_key_block(kmac_blinded_key_t *key) { KMAC_KEY_LEN_REG_RESVAL, KMAC_KEY_LEN_LEN_FIELD, key_len_enum); abs_mmio_write32(kKmacBaseAddr + KMAC_KEY_LEN_REG_OFFSET, key_len_reg); + // Write random words to the key registers. + for (size_t i = 0; i * sizeof(uint32_t) < key->len; i++) { + abs_mmio_write32(kKmacKeyShare0Addr + i * sizeof(uint32_t), + ibex_rnd32_read()); + abs_mmio_write32(kKmacKeyShare1Addr + i * sizeof(uint32_t), + ibex_rnd32_read()); + } + for (size_t i = 0; i * sizeof(uint32_t) < key->len; i++) { abs_mmio_write32(kKmacKeyShare0Addr + i * sizeof(uint32_t), key->share0[i]); abs_mmio_write32(kKmacKeyShare1Addr + i * sizeof(uint32_t), key->share1[i]); From 38251fa9fe46c188517bb4137857fc146a3ea44e Mon Sep 17 00:00:00 2001 From: Jade Philipoom Date: Wed, 28 May 2025 09:52:05 +0200 Subject: [PATCH 11/51] [crypto] Randomize shares in separate loops in KMAC driver. Small defense-in-depth improvement in SCA defense. Signed-off-by: Jade Philipoom (cherry picked from commit 5e8bd4ccaa5e931708f5dcbd07d9d4e65f8b2fcb) --- sw/device/lib/crypto/drivers/kmac.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/sw/device/lib/crypto/drivers/kmac.c b/sw/device/lib/crypto/drivers/kmac.c index b9c008b3ac53d..d6e5070f26df0 100644 --- a/sw/device/lib/crypto/drivers/kmac.c +++ b/sw/device/lib/crypto/drivers/kmac.c @@ -549,16 +549,19 @@ static status_t kmac_write_key_block(kmac_blinded_key_t *key) { KMAC_KEY_LEN_REG_RESVAL, KMAC_KEY_LEN_LEN_FIELD, key_len_enum); abs_mmio_write32(kKmacBaseAddr + KMAC_KEY_LEN_REG_OFFSET, key_len_reg); - // Write random words to the key registers. + // Write random words to the key registers first for SCA defense. for (size_t i = 0; i * sizeof(uint32_t) < key->len; i++) { abs_mmio_write32(kKmacKeyShare0Addr + i * sizeof(uint32_t), ibex_rnd32_read()); + } + for (size_t i = 0; i * sizeof(uint32_t) < key->len; i++) { + abs_mmio_write32(kKmacKeyShare0Addr + i * sizeof(uint32_t), key->share0[i]); + } + for (size_t i = 0; i * sizeof(uint32_t) < key->len; i++) { abs_mmio_write32(kKmacKeyShare1Addr + i * sizeof(uint32_t), ibex_rnd32_read()); } - for (size_t i = 0; i * sizeof(uint32_t) < key->len; i++) { - abs_mmio_write32(kKmacKeyShare0Addr + i * sizeof(uint32_t), key->share0[i]); abs_mmio_write32(kKmacKeyShare1Addr + i * sizeof(uint32_t), key->share1[i]); } From 64b3cb575cb3e56cb3d09711411ca1c301d00a53 Mon Sep 17 00:00:00 2001 From: Pascal Nasahl Date: Thu, 10 Jul 2025 13:46:25 +0200 Subject: [PATCH 12/51] [crypto] Harden RSA padding selection against FI This commit adds checks to the RSA codebase where, based on the selected padding scheme, the control-flow diverges. These checks make sure that a fault cannot allow an attacker to switch to a different padding scheme. Signed-off-by: Pascal Nasahl (cherry picked from commit 27769f5ed36e11eca29b8fa9c2e30fc1f4b3ba77) --- sw/device/lib/crypto/impl/rsa/rsa_signature.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/sw/device/lib/crypto/impl/rsa/rsa_signature.c b/sw/device/lib/crypto/impl/rsa/rsa_signature.c index 9185fce68bb71..7506c797b61e1 100644 --- a/sw/device/lib/crypto/impl/rsa/rsa_signature.c +++ b/sw/device/lib/crypto/impl/rsa/rsa_signature.c @@ -74,11 +74,13 @@ static status_t message_encode(const otcrypto_hash_digest_t message_digest, // Check that the digest length is OK. HARDENED_TRY(digest_check(message_digest)); - switch (padding_mode) { + switch (launder32(padding_mode)) { case kRsaSignaturePaddingPkcs1v15: + HARDENED_CHECK_EQ(padding_mode, kRsaSignaturePaddingPkcs1v15); return rsa_padding_pkcs1v15_encode(message_digest, encoded_message_len, encoded_message); case kRsaSignaturePaddingPss: { + HARDENED_CHECK_EQ(padding_mode, kRsaSignaturePaddingPss); // Generate a random salt value whose length matches the digest length. uint32_t salt[message_digest.len]; HARDENED_TRY(entropy_complex_check()); @@ -126,11 +128,13 @@ static status_t encoded_message_verify( // Check that the digest length is OK. HARDENED_TRY(digest_check(message_digest)); - switch (padding_mode) { + switch (launder32(padding_mode)) { case kRsaSignaturePaddingPkcs1v15: + HARDENED_CHECK_EQ(padding_mode, kRsaSignaturePaddingPkcs1v15); return rsa_padding_pkcs1v15_verify(message_digest, encoded_message, encoded_message_len, result); case kRsaSignaturePaddingPss: + HARDENED_CHECK_EQ(padding_mode, kRsaSignaturePaddingPss); return rsa_padding_pss_verify(message_digest, encoded_message, encoded_message_len, result); default: From b3255a85369f54db08f0deddd0cf4ac8e9938c0a Mon Sep 17 00:00:00 2001 From: Lucas Baizer Date: Mon, 21 Jul 2025 13:29:05 -0700 Subject: [PATCH 13/51] [crypto] Fixed aes_gcm uninitialized output_len pointer Signed-off-by: Lucas Baizer (cherry picked from commit 47a4f45ed5d4ec6fb97b3ae2aec4457018511b8d) --- sw/device/lib/crypto/impl/aes_gcm/aes_gcm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sw/device/lib/crypto/impl/aes_gcm/aes_gcm.c b/sw/device/lib/crypto/impl/aes_gcm/aes_gcm.c index b2f982cbf23d4..5274c968a3012 100644 --- a/sw/device/lib/crypto/impl/aes_gcm/aes_gcm.c +++ b/sw/device/lib/crypto/impl/aes_gcm/aes_gcm.c @@ -131,6 +131,7 @@ static status_t aes_gcm_gctr(const aes_key_t key, aes_block_t *iv, // Not enough data for a full block; copy into the partial block. unsigned char *partial_bytes = (unsigned char *)partial->data; memcpy(partial_bytes + partial_len, input, input_len); + *output_len = 0; } else { // Construct a block from the partial data and the start of the new data. unsigned char *partial_bytes = (unsigned char *)partial->data; From eb29025a4c8ce038035ab9f6335ce98c137409ad Mon Sep 17 00:00:00 2001 From: Pascal Nasahl Date: Thu, 17 Jul 2025 14:58:14 +0200 Subject: [PATCH 14/51] [crypto] Add OTBN DMEM wipes to RSA code Always wipe the DMEM once the OTBN execution has been finished. Signed-off-by: Pascal Nasahl (cherry picked from commit 52c318fc12ac4f1dc17213e3e8afc60e985fbe3a) --- sw/device/lib/crypto/impl/rsa/rsa_3072_verify.c | 6 ++++-- sw/device/lib/crypto/impl/rsa/rsa_keygen.c | 4 +--- sw/device/lib/crypto/impl/rsa/rsa_modexp.c | 4 ++++ 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/sw/device/lib/crypto/impl/rsa/rsa_3072_verify.c b/sw/device/lib/crypto/impl/rsa/rsa_3072_verify.c index f445dd9e0f82f..a4653aea0afc8 100644 --- a/sw/device/lib/crypto/impl/rsa/rsa_3072_verify.c +++ b/sw/device/lib/crypto/impl/rsa/rsa_3072_verify.c @@ -147,7 +147,8 @@ status_t rsa_3072_compute_constants(const rsa_3072_public_key_t *public_key, HARDENED_TRY( otbn_dmem_read(kOtbnWideWordNumWords, kOtbnVarRsaM0Inv, result->m0_inv)); - return OTCRYPTO_OK; + // Wipe DMEM. + return otbn_dmem_sec_wipe(); } status_t rsa_3072_verify_start(const rsa_3072_int_t *signature, @@ -213,7 +214,8 @@ status_t rsa_3072_verify_finalize(const rsa_3072_int_t *message, } } - return OTCRYPTO_OK; + // Wipe DMEM. + return otbn_dmem_sec_wipe(); } status_t rsa_3072_verify(const rsa_3072_int_t *signature, diff --git a/sw/device/lib/crypto/impl/rsa/rsa_keygen.c b/sw/device/lib/crypto/impl/rsa/rsa_keygen.c index 280f6bea27695..6e520303fef65 100644 --- a/sw/device/lib/crypto/impl/rsa/rsa_keygen.c +++ b/sw/device/lib/crypto/impl/rsa/rsa_keygen.c @@ -95,9 +95,7 @@ static status_t keygen_finalize(uint32_t exp_mode, size_t num_words, HARDENED_TRY(otbn_dmem_read(num_words, kOtbnVarRsaD, d)); // Wipe DMEM. - HARDENED_TRY(otbn_dmem_sec_wipe()); - - return OTCRYPTO_OK; + return otbn_dmem_sec_wipe(); } status_t rsa_keygen_2048_start(void) { diff --git a/sw/device/lib/crypto/impl/rsa/rsa_modexp.c b/sw/device/lib/crypto/impl/rsa/rsa_modexp.c index ba30c5de72ebb..6fa97e2ed299a 100644 --- a/sw/device/lib/crypto/impl/rsa/rsa_modexp.c +++ b/sw/device/lib/crypto/impl/rsa/rsa_modexp.c @@ -70,6 +70,8 @@ status_t rsa_modexp_wait(size_t *num_words) { break; default: // Unrecognized mode. + // Wipe DMEM. + HARDENED_TRY(otbn_dmem_sec_wipe()); return OTCRYPTO_FATAL_ERR; } @@ -93,6 +95,8 @@ static status_t rsa_modexp_finalize(const size_t num_words, uint32_t *result) { // Check that the inferred result size matches expectations. if (num_words != num_words_inferred) { + // Wipe DMEM. + HARDENED_TRY(otbn_dmem_sec_wipe()); return OTCRYPTO_FATAL_ERR; } From bbb58b8699a6a4ae96ddd80c9cb4b0ee19417e7d Mon Sep 17 00:00:00 2001 From: Pascal Nasahl Date: Thu, 17 Jul 2025 15:55:25 +0200 Subject: [PATCH 15/51] [crypto] Wipe DMEM also in error cases When an error occurs, HARDENED_TRY() immediately returns. As we also want to wipe the OTBN DMEM when an error happens, this commit adds HARDENED_TRY_WIPE_DMEM() which wipes DMEM on an error before returning. Signed-off-by: Pascal Nasahl (cherry picked from commit a5952c37f0e392cc60f9fbd24155413d4f26a49f) --- sw/device/lib/crypto/drivers/otbn.h | 21 ++++++++ sw/device/lib/crypto/impl/ecc/p256.c | 50 +++++++++++-------- sw/device/lib/crypto/impl/ecc/p384.c | 50 +++++++++++-------- .../lib/crypto/impl/rsa/rsa_3072_verify.c | 11 ++-- sw/device/lib/crypto/impl/rsa/rsa_keygen.c | 9 ++-- sw/device/lib/crypto/impl/rsa/rsa_modexp.c | 8 +-- sw/device/lib/crypto/impl/sha2/sha256.c | 4 +- sw/device/lib/crypto/impl/sha2/sha512.c | 7 +-- 8 files changed, 102 insertions(+), 58 deletions(-) diff --git a/sw/device/lib/crypto/drivers/otbn.h b/sw/device/lib/crypto/drivers/otbn.h index d19a396b98afc..becb1f4ddb7e0 100644 --- a/sw/device/lib/crypto/drivers/otbn.h +++ b/sw/device/lib/crypto/drivers/otbn.h @@ -15,6 +15,27 @@ extern "C" { #endif +/** + * Hardened version of the `TRY` macro that wipes DMEM() on an error. + * + * Returns an error if either expr_ represents an error, or if the OK code does + * not match the expected hardened value. + * + * @param expr_ An expression that evaluates to a `status_t`. + * @return The enclosed OK value. + */ +#define HARDENED_TRY_WIPE_DMEM(expr_) \ + do { \ + status_t status_ = expr_; \ + if (launder32(OT_UNSIGNED(status_.value)) != kHardenedBoolTrue) { \ + otbn_dmem_sec_wipe(); \ + return (status_t){ \ + .value = (int32_t)(OT_UNSIGNED(status_.value) | 0x80000000)}; \ + } \ + HARDENED_CHECK_EQ(status_.value, kHardenedBoolTrue); \ + status_.value; \ + } while (false) + /** * Constants related to OTBN wide words */ diff --git a/sw/device/lib/crypto/impl/ecc/p256.c b/sw/device/lib/crypto/impl/ecc/p256.c index d3fadcde901b1..26d91a3998f6b 100644 --- a/sw/device/lib/crypto/impl/ecc/p256.c +++ b/sw/device/lib/crypto/impl/ecc/p256.c @@ -125,17 +125,19 @@ status_t p256_sideload_keygen_start(void) { status_t p256_keygen_finalize(p256_masked_scalar_t *private_key, p256_point_t *public_key) { // Spin here waiting for OTBN to complete. - HARDENED_TRY(otbn_busy_wait_for_done()); + HARDENED_TRY_WIPE_DMEM(otbn_busy_wait_for_done()); // Read the masked private key from OTBN dmem. - HARDENED_TRY(otbn_dmem_read(kP256MaskedScalarShareWords, kOtbnVarD0, - private_key->share0)); - HARDENED_TRY(otbn_dmem_read(kP256MaskedScalarShareWords, kOtbnVarD1, - private_key->share1)); + HARDENED_TRY_WIPE_DMEM(otbn_dmem_read(kP256MaskedScalarShareWords, kOtbnVarD0, + private_key->share0)); + HARDENED_TRY_WIPE_DMEM(otbn_dmem_read(kP256MaskedScalarShareWords, kOtbnVarD1, + private_key->share1)); // Read the public key from OTBN dmem. - HARDENED_TRY(otbn_dmem_read(kP256CoordWords, kOtbnVarX, public_key->x)); - HARDENED_TRY(otbn_dmem_read(kP256CoordWords, kOtbnVarY, public_key->y)); + HARDENED_TRY_WIPE_DMEM( + otbn_dmem_read(kP256CoordWords, kOtbnVarX, public_key->x)); + HARDENED_TRY_WIPE_DMEM( + otbn_dmem_read(kP256CoordWords, kOtbnVarY, public_key->y)); // Wipe DMEM. HARDENED_TRY(otbn_dmem_sec_wipe()); @@ -145,11 +147,13 @@ status_t p256_keygen_finalize(p256_masked_scalar_t *private_key, status_t p256_sideload_keygen_finalize(p256_point_t *public_key) { // Spin here waiting for OTBN to complete. - HARDENED_TRY(otbn_busy_wait_for_done()); + HARDENED_TRY_WIPE_DMEM(otbn_busy_wait_for_done()); // Read the public key from OTBN dmem. - HARDENED_TRY(otbn_dmem_read(kP256CoordWords, kOtbnVarX, public_key->x)); - HARDENED_TRY(otbn_dmem_read(kP256CoordWords, kOtbnVarY, public_key->y)); + HARDENED_TRY_WIPE_DMEM( + otbn_dmem_read(kP256CoordWords, kOtbnVarX, public_key->x)); + HARDENED_TRY_WIPE_DMEM( + otbn_dmem_read(kP256CoordWords, kOtbnVarY, public_key->y)); // Wipe DMEM. HARDENED_TRY(otbn_dmem_sec_wipe()); @@ -218,13 +222,15 @@ status_t p256_ecdsa_sideload_sign_start( status_t p256_ecdsa_sign_finalize(p256_ecdsa_signature_t *result) { // Spin here waiting for OTBN to complete. - HARDENED_TRY(otbn_busy_wait_for_done()); + HARDENED_TRY_WIPE_DMEM(otbn_busy_wait_for_done()); // Read signature R out of OTBN dmem. - HARDENED_TRY(otbn_dmem_read(kP256ScalarWords, kOtbnVarR, result->r)); + HARDENED_TRY_WIPE_DMEM( + otbn_dmem_read(kP256ScalarWords, kOtbnVarR, result->r)); // Read signature S out of OTBN dmem. - HARDENED_TRY(otbn_dmem_read(kP256ScalarWords, kOtbnVarS, result->s)); + HARDENED_TRY_WIPE_DMEM( + otbn_dmem_read(kP256ScalarWords, kOtbnVarS, result->s)); // Wipe DMEM. HARDENED_TRY(otbn_dmem_sec_wipe()); @@ -264,20 +270,21 @@ status_t p256_ecdsa_verify_start(const p256_ecdsa_signature_t *signature, status_t p256_ecdsa_verify_finalize(const p256_ecdsa_signature_t *signature, hardened_bool_t *result) { // Spin here waiting for OTBN to complete. - HARDENED_TRY(otbn_busy_wait_for_done()); + HARDENED_TRY_WIPE_DMEM(otbn_busy_wait_for_done()); // Read the status code out of DMEM (false if basic checks on the validity of // the signature and public key failed). uint32_t ok; - HARDENED_TRY(otbn_dmem_read(1, kOtbnVarOk, &ok)); + HARDENED_TRY_WIPE_DMEM(otbn_dmem_read(1, kOtbnVarOk, &ok)); if (launder32(ok) != kHardenedBoolTrue) { + HARDENED_TRY(otbn_dmem_sec_wipe()); return OTCRYPTO_BAD_ARGS; } HARDENED_CHECK_EQ(ok, kHardenedBoolTrue); // Read x_r (recovered R) out of OTBN dmem. uint32_t x_r[kP256ScalarWords]; - HARDENED_TRY(otbn_dmem_read(kP256ScalarWords, kOtbnVarXr, x_r)); + HARDENED_TRY_WIPE_DMEM(otbn_dmem_read(kP256ScalarWords, kOtbnVarXr, x_r)); *result = hardened_memeq(x_r, signature->r, kP256ScalarWords); @@ -311,19 +318,22 @@ status_t p256_ecdh_start(const p256_masked_scalar_t *private_key, status_t p256_ecdh_finalize(p256_ecdh_shared_key_t *shared_key) { // Spin here waiting for OTBN to complete. - HARDENED_TRY(otbn_busy_wait_for_done()); + HARDENED_TRY_WIPE_DMEM(otbn_busy_wait_for_done()); // Read the code indicating if the public key is valid. uint32_t ok; - HARDENED_TRY(otbn_dmem_read(1, kOtbnVarOk, &ok)); + HARDENED_TRY_WIPE_DMEM(otbn_dmem_read(1, kOtbnVarOk, &ok)); if (launder32(ok) != kHardenedBoolTrue) { + HARDENED_TRY(otbn_dmem_sec_wipe()); return OTCRYPTO_BAD_ARGS; } HARDENED_CHECK_EQ(ok, kHardenedBoolTrue); // Read the shares of the key from OTBN dmem (at vars x and y). - HARDENED_TRY(otbn_dmem_read(kP256CoordWords, kOtbnVarX, shared_key->share0)); - HARDENED_TRY(otbn_dmem_read(kP256CoordWords, kOtbnVarY, shared_key->share1)); + HARDENED_TRY_WIPE_DMEM( + otbn_dmem_read(kP256CoordWords, kOtbnVarX, shared_key->share0)); + HARDENED_TRY_WIPE_DMEM( + otbn_dmem_read(kP256CoordWords, kOtbnVarY, shared_key->share1)); // Wipe DMEM. HARDENED_TRY(otbn_dmem_sec_wipe()); diff --git a/sw/device/lib/crypto/impl/ecc/p384.c b/sw/device/lib/crypto/impl/ecc/p384.c index 8c429b5324bb7..4d54eb94515b5 100644 --- a/sw/device/lib/crypto/impl/ecc/p384.c +++ b/sw/device/lib/crypto/impl/ecc/p384.c @@ -166,17 +166,19 @@ status_t p384_keygen_start(void) { status_t p384_keygen_finalize(p384_masked_scalar_t *private_key, p384_point_t *public_key) { // Spin here waiting for OTBN to complete. - HARDENED_TRY(otbn_busy_wait_for_done()); + HARDENED_TRY_WIPE_DMEM(otbn_busy_wait_for_done()); // Read the masked private key from OTBN dmem. - HARDENED_TRY(otbn_dmem_read(kP384MaskedScalarShareWords, kOtbnVarD0, - private_key->share0)); - HARDENED_TRY(otbn_dmem_read(kP384MaskedScalarShareWords, kOtbnVarD1, - private_key->share1)); + HARDENED_TRY_WIPE_DMEM(otbn_dmem_read(kP384MaskedScalarShareWords, kOtbnVarD0, + private_key->share0)); + HARDENED_TRY_WIPE_DMEM(otbn_dmem_read(kP384MaskedScalarShareWords, kOtbnVarD1, + private_key->share1)); // Read the public key from OTBN dmem. - HARDENED_TRY(otbn_dmem_read(kP384CoordWords, kOtbnVarX, public_key->x)); - HARDENED_TRY(otbn_dmem_read(kP384CoordWords, kOtbnVarY, public_key->y)); + HARDENED_TRY_WIPE_DMEM( + otbn_dmem_read(kP384CoordWords, kOtbnVarX, public_key->x)); + HARDENED_TRY_WIPE_DMEM( + otbn_dmem_read(kP384CoordWords, kOtbnVarY, public_key->y)); // Wipe DMEM. HARDENED_TRY(otbn_dmem_sec_wipe()); @@ -198,11 +200,13 @@ status_t p384_sideload_keygen_start(void) { status_t p384_sideload_keygen_finalize(p384_point_t *public_key) { // Spin here waiting for OTBN to complete. - HARDENED_TRY(otbn_busy_wait_for_done()); + HARDENED_TRY_WIPE_DMEM(otbn_busy_wait_for_done()); // Read the public key from OTBN dmem. - HARDENED_TRY(otbn_dmem_read(kP384CoordWords, kOtbnVarX, public_key->x)); - HARDENED_TRY(otbn_dmem_read(kP384CoordWords, kOtbnVarY, public_key->y)); + HARDENED_TRY_WIPE_DMEM( + otbn_dmem_read(kP384CoordWords, kOtbnVarX, public_key->x)); + HARDENED_TRY_WIPE_DMEM( + otbn_dmem_read(kP384CoordWords, kOtbnVarY, public_key->y)); // Wipe DMEM. HARDENED_TRY(otbn_dmem_sec_wipe()); @@ -247,13 +251,15 @@ status_t p384_ecdsa_sideload_sign_start( status_t p384_ecdsa_sign_finalize(p384_ecdsa_signature_t *result) { // Spin here waiting for OTBN to complete. - HARDENED_TRY(otbn_busy_wait_for_done()); + HARDENED_TRY_WIPE_DMEM(otbn_busy_wait_for_done()); // Read signature R out of OTBN dmem. - HARDENED_TRY(otbn_dmem_read(kP384ScalarWords, kOtbnVarR, result->r)); + HARDENED_TRY_WIPE_DMEM( + otbn_dmem_read(kP384ScalarWords, kOtbnVarR, result->r)); // Read signature S out of OTBN dmem. - HARDENED_TRY(otbn_dmem_read(kP384ScalarWords, kOtbnVarS, result->s)); + HARDENED_TRY_WIPE_DMEM( + otbn_dmem_read(kP384ScalarWords, kOtbnVarS, result->s)); // Wipe DMEM. HARDENED_TRY(otbn_dmem_sec_wipe()); @@ -290,20 +296,21 @@ status_t p384_ecdsa_verify_start(const p384_ecdsa_signature_t *signature, status_t p384_ecdsa_verify_finalize(const p384_ecdsa_signature_t *signature, hardened_bool_t *result) { // Spin here waiting for OTBN to complete. - HARDENED_TRY(otbn_busy_wait_for_done()); + HARDENED_TRY_WIPE_DMEM(otbn_busy_wait_for_done()); // Read the status code out of DMEM (false if basic checks on the validity of // the signature and public key failed). uint32_t ok; - HARDENED_TRY(otbn_dmem_read(1, kOtbnVarOk, &ok)); + HARDENED_TRY_WIPE_DMEM(otbn_dmem_read(1, kOtbnVarOk, &ok)); if (launder32(ok) != kHardenedBoolTrue) { + HARDENED_TRY(otbn_dmem_sec_wipe()); return OTCRYPTO_BAD_ARGS; } HARDENED_CHECK_EQ(ok, kHardenedBoolTrue); // Read x_r (recovered R) out of OTBN dmem. uint32_t x_r[kP384ScalarWords]; - HARDENED_TRY(otbn_dmem_read(kP384ScalarWords, kOtbnVarXr, x_r)); + HARDENED_TRY_WIPE_DMEM(otbn_dmem_read(kP384ScalarWords, kOtbnVarXr, x_r)); *result = hardened_memeq(x_r, signature->r, kP384ScalarWords); @@ -334,20 +341,23 @@ status_t p384_ecdh_start(const p384_masked_scalar_t *private_key, status_t p384_ecdh_finalize(p384_ecdh_shared_key_t *shared_key) { // Spin here waiting for OTBN to complete. - HARDENED_TRY(otbn_busy_wait_for_done()); + HARDENED_TRY_WIPE_DMEM(otbn_busy_wait_for_done()); // Read the status code out of DMEM (false if basic checks on the validity of // the signature and public key failed). uint32_t ok; - HARDENED_TRY(otbn_dmem_read(1, kOtbnVarOk, &ok)); + HARDENED_TRY_WIPE_DMEM(otbn_dmem_read(1, kOtbnVarOk, &ok)); if (launder32(ok) != kHardenedBoolTrue) { + HARDENED_TRY(otbn_dmem_sec_wipe()); return OTCRYPTO_BAD_ARGS; } HARDENED_CHECK_EQ(ok, kHardenedBoolTrue); // Read the shares of the key from OTBN dmem (at vars x and y). - HARDENED_TRY(otbn_dmem_read(kP384CoordWords, kOtbnVarX, shared_key->share0)); - HARDENED_TRY(otbn_dmem_read(kP384CoordWords, kOtbnVarY, shared_key->share1)); + HARDENED_TRY_WIPE_DMEM( + otbn_dmem_read(kP384CoordWords, kOtbnVarX, shared_key->share0)); + HARDENED_TRY_WIPE_DMEM( + otbn_dmem_read(kP384CoordWords, kOtbnVarY, shared_key->share1)); // Wipe DMEM. HARDENED_TRY(otbn_dmem_sec_wipe()); diff --git a/sw/device/lib/crypto/impl/rsa/rsa_3072_verify.c b/sw/device/lib/crypto/impl/rsa/rsa_3072_verify.c index a4653aea0afc8..bb68e4f627101 100644 --- a/sw/device/lib/crypto/impl/rsa/rsa_3072_verify.c +++ b/sw/device/lib/crypto/impl/rsa/rsa_3072_verify.c @@ -138,13 +138,14 @@ status_t rsa_3072_compute_constants(const rsa_3072_public_key_t *public_key, HARDENED_TRY(otbn_execute()); // Spin here waiting for OTBN to complete. - HARDENED_TRY(otbn_busy_wait_for_done()); + HARDENED_TRY_WIPE_DMEM(otbn_busy_wait_for_done()); // Read constant rr out of DMEM. - HARDENED_TRY(read_rsa_3072_int_from_otbn(kOtbnVarRsaRR, &result->rr)); + HARDENED_TRY_WIPE_DMEM( + read_rsa_3072_int_from_otbn(kOtbnVarRsaRR, &result->rr)); // Read constant m0_inv out of DMEM. - HARDENED_TRY( + HARDENED_TRY_WIPE_DMEM( otbn_dmem_read(kOtbnWideWordNumWords, kOtbnVarRsaM0Inv, result->m0_inv)); // Wipe DMEM. @@ -198,11 +199,11 @@ status_t rsa_3072_verify_finalize(const rsa_3072_int_t *message, *result = kHardenedBoolFalse; // Spin here waiting for OTBN to complete. - HARDENED_TRY(otbn_busy_wait_for_done()); + HARDENED_TRY_WIPE_DMEM(otbn_busy_wait_for_done()); // Read recovered message out of OTBN dmem. rsa_3072_int_t recoveredMessage; - HARDENED_TRY( + HARDENED_TRY_WIPE_DMEM( read_rsa_3072_int_from_otbn(kOtbnVarRsaOutBuf, &recoveredMessage)); // TODO: harden this memory comparison diff --git a/sw/device/lib/crypto/impl/rsa/rsa_keygen.c b/sw/device/lib/crypto/impl/rsa/rsa_keygen.c index 6e520303fef65..8725eae43996f 100644 --- a/sw/device/lib/crypto/impl/rsa/rsa_keygen.c +++ b/sw/device/lib/crypto/impl/rsa/rsa_keygen.c @@ -79,20 +79,21 @@ static status_t keygen_start(uint32_t mode) { static status_t keygen_finalize(uint32_t exp_mode, size_t num_words, uint32_t *n, uint32_t *d) { // Spin here waiting for OTBN to complete. - HARDENED_TRY(otbn_busy_wait_for_done()); + HARDENED_TRY_WIPE_DMEM(otbn_busy_wait_for_done()); // Read the mode from OTBN dmem and panic if it's not as expected. uint32_t act_mode = 0; - HARDENED_TRY(otbn_dmem_read(1, kOtbnVarRsaMode, &act_mode)); + HARDENED_TRY_WIPE_DMEM(otbn_dmem_read(1, kOtbnVarRsaMode, &act_mode)); if (act_mode != exp_mode) { + HARDENED_TRY(otbn_dmem_sec_wipe()); return OTCRYPTO_FATAL_ERR; } // Read the public modulus (n) from OTBN dmem. - HARDENED_TRY(otbn_dmem_read(num_words, kOtbnVarRsaN, n)); + HARDENED_TRY_WIPE_DMEM(otbn_dmem_read(num_words, kOtbnVarRsaN, n)); // Read the private exponent (d) from OTBN dmem. - HARDENED_TRY(otbn_dmem_read(num_words, kOtbnVarRsaD, d)); + HARDENED_TRY_WIPE_DMEM(otbn_dmem_read(num_words, kOtbnVarRsaD, d)); // Wipe DMEM. return otbn_dmem_sec_wipe(); diff --git a/sw/device/lib/crypto/impl/rsa/rsa_modexp.c b/sw/device/lib/crypto/impl/rsa/rsa_modexp.c index 6fa97e2ed299a..c1d738301124a 100644 --- a/sw/device/lib/crypto/impl/rsa/rsa_modexp.c +++ b/sw/device/lib/crypto/impl/rsa/rsa_modexp.c @@ -45,11 +45,11 @@ enum { status_t rsa_modexp_wait(size_t *num_words) { // Spin here waiting for OTBN to complete. - HARDENED_TRY(otbn_busy_wait_for_done()); + HARDENED_TRY_WIPE_DMEM(otbn_busy_wait_for_done()); // Read the application mode. uint32_t mode; - HARDENED_TRY(otbn_dmem_read(1, kOtbnVarRsaMode, &mode)); + HARDENED_TRY_WIPE_DMEM(otbn_dmem_read(1, kOtbnVarRsaMode, &mode)); *num_words = 0; switch (mode) { @@ -91,7 +91,7 @@ status_t rsa_modexp_wait(size_t *num_words) { static status_t rsa_modexp_finalize(const size_t num_words, uint32_t *result) { // Wait for OTBN to complete and get the result size. size_t num_words_inferred; - HARDENED_TRY(rsa_modexp_wait(&num_words_inferred)); + HARDENED_TRY_WIPE_DMEM(rsa_modexp_wait(&num_words_inferred)); // Check that the inferred result size matches expectations. if (num_words != num_words_inferred) { @@ -101,7 +101,7 @@ static status_t rsa_modexp_finalize(const size_t num_words, uint32_t *result) { } // Read the result. - HARDENED_TRY(otbn_dmem_read(num_words, kOtbnVarRsaInOut, result)); + HARDENED_TRY_WIPE_DMEM(otbn_dmem_read(num_words, kOtbnVarRsaInOut, result)); // Wipe DMEM. return otbn_dmem_sec_wipe(); diff --git a/sw/device/lib/crypto/impl/sha2/sha256.c b/sw/device/lib/crypto/impl/sha2/sha256.c index 9ace990dcb2db..3cb0f68b59c55 100644 --- a/sw/device/lib/crypto/impl/sha2/sha256.c +++ b/sw/device/lib/crypto/impl/sha2/sha256.c @@ -87,7 +87,7 @@ static status_t process_message_buffer(sha256_otbn_ctx_t *ctx) { // Run the OTBN program. HARDENED_TRY(otbn_execute()); - HARDENED_TRY(otbn_busy_wait_for_done()); + HARDENED_TRY_WIPE_DMEM(otbn_busy_wait_for_done()); // Reset the message buffer counter. ctx->num_blocks = 0; @@ -245,7 +245,7 @@ static status_t process_message(sha256_state_t *state, const uint8_t *msg, } // Read the final state from OTBN dmem. - HARDENED_TRY( + HARDENED_TRY_WIPE_DMEM( otbn_dmem_read(kSha256StateWords, kOtbnVarSha256State, new_state.H)); // Clear OTBN's memory. diff --git a/sw/device/lib/crypto/impl/sha2/sha512.c b/sw/device/lib/crypto/impl/sha2/sha512.c index d0154dd8e59bb..5d76fed0b37b9 100644 --- a/sw/device/lib/crypto/impl/sha2/sha512.c +++ b/sw/device/lib/crypto/impl/sha2/sha512.c @@ -135,7 +135,7 @@ static status_t process_message_buffer(sha512_otbn_ctx_t *ctx) { // Run the OTBN program. HARDENED_TRY(otbn_execute()); - HARDENED_TRY(otbn_busy_wait_for_done()); + HARDENED_TRY_WIPE_DMEM(otbn_busy_wait_for_done()); // Reset the message buffer counter. ctx->num_blocks = 0; @@ -307,8 +307,9 @@ static status_t process_message(sha512_state_t *state, const uint8_t *msg, // boundaries. otbn_addr_t state_read_addr = kOtbnVarSha512State; for (size_t i = 0; i + 1 < kSha512StateWords; i += 2) { - HARDENED_TRY(otbn_dmem_read(1, state_read_addr, &new_state.H[i + 1])); - HARDENED_TRY( + HARDENED_TRY_WIPE_DMEM( + otbn_dmem_read(1, state_read_addr, &new_state.H[i + 1])); + HARDENED_TRY_WIPE_DMEM( otbn_dmem_read(1, state_read_addr + sizeof(uint32_t), &new_state.H[i])); state_read_addr += kOtbnWideWordNumBytes; } From ef5a8f3086f5c3cc67745d36935e05687ffb063c Mon Sep 17 00:00:00 2001 From: Pascal Nasahl Date: Tue, 26 Aug 2025 08:41:17 +0200 Subject: [PATCH 16/51] [crypto] Randomize HMAC key write order Use the hardened_memcpy function to randomize writing into the HMAC key register. Signed-off-by: Pascal Nasahl (cherry picked from commit ce38ad92d8ece99afd66afdda303267f5ac6955d) --- sw/device/lib/crypto/drivers/hmac.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/sw/device/lib/crypto/drivers/hmac.c b/sw/device/lib/crypto/drivers/hmac.c index 78fecac4c6407..6b88609455442 100644 --- a/sw/device/lib/crypto/drivers/hmac.c +++ b/sw/device/lib/crypto/drivers/hmac.c @@ -181,10 +181,8 @@ static void clear(void) { * @param key_wordlen The length of the key in words. */ static void key_write(const uint32_t *key, size_t key_wordlen) { - for (size_t i = 0; i < key_wordlen; i++) { - abs_mmio_write32( - kHmacBaseAddr + HMAC_KEY_0_REG_OFFSET + sizeof(uint32_t) * i, key[i]); - } + uint32_t key_reg = kHmacBaseAddr + HMAC_KEY_0_REG_OFFSET; + hardened_memcpy((uint32_t *)key_reg, key, key_wordlen); } /** From 173e216453cc6073563ab0c09df1e9e878dfe9be Mon Sep 17 00:00:00 2001 From: Pascal Nasahl Date: Tue, 26 Aug 2025 09:08:47 +0200 Subject: [PATCH 17/51] [crypto] Read back HMAC config To mitigate fault attacks, read back the HMAC config from the register and compare to the expected config. Signed-off-by: Pascal Nasahl (cherry picked from commit 8926c49ca9d4f7a97aa8077e829cff37e47b2563) --- sw/device/lib/crypto/drivers/hmac.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sw/device/lib/crypto/drivers/hmac.c b/sw/device/lib/crypto/drivers/hmac.c index 6b88609455442..661512d6cda2a 100644 --- a/sw/device/lib/crypto/drivers/hmac.c +++ b/sw/device/lib/crypto/drivers/hmac.c @@ -401,6 +401,10 @@ static status_t oneshot(const uint32_t cfg, const uint32_t *key, // Write the key (no-op if the key length is 0, e.g. for hashing). key_write(key, key_wordlen); + // Read back the HMAC configuration and compare to the expected configuration. + HARDENED_CHECK_EQ(abs_mmio_read32(kHmacBaseAddr + HMAC_CFG_REG_OFFSET), + launder32(cfg)); + // Send the START command. uint32_t cmd = bitfield_bit32_write(HMAC_CMD_REG_RESVAL, HMAC_CMD_HASH_START_BIT, 1); From 6b447449ddbc1c3e6a334126e3f9e57105a78ae9 Mon Sep 17 00:00:00 2001 From: Pascal Nasahl Date: Mon, 25 Aug 2025 14:38:20 +0200 Subject: [PATCH 18/51] [crypto] Check if AES key length was set Make sure that the key length was set in the ctrl_reg register. Signed-off-by: Pascal Nasahl (cherry picked from commit 2a7fce1ba43eabf25c18fe8bc0b17fa41c39aaaf) --- sw/device/lib/crypto/drivers/aes.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sw/device/lib/crypto/drivers/aes.c b/sw/device/lib/crypto/drivers/aes.c index 944e5de599748..45be0bae6acef 100644 --- a/sw/device/lib/crypto/drivers/aes.c +++ b/sw/device/lib/crypto/drivers/aes.c @@ -185,26 +185,31 @@ static status_t aes_begin(aes_key_t key, const aes_block_t *iv, // Translate the key length to the hardware-encoding value and write the // control reg field. + size_t key_len_written; switch (key.key_len) { case kAesKeyWordLen128: ctrl_reg = bitfield_field32_write(ctrl_reg, AES_CTRL_SHADOWED_KEY_LEN_FIELD, AES_CTRL_SHADOWED_KEY_LEN_VALUE_AES_128); + key_len_written = launder32(kAesKeyWordLen128); break; case kAesKeyWordLen192: ctrl_reg = bitfield_field32_write(ctrl_reg, AES_CTRL_SHADOWED_KEY_LEN_FIELD, AES_CTRL_SHADOWED_KEY_LEN_VALUE_AES_192); + key_len_written = launder32(kAesKeyWordLen192); break; case kAesKeyWordLen256: ctrl_reg = bitfield_field32_write(ctrl_reg, AES_CTRL_SHADOWED_KEY_LEN_FIELD, AES_CTRL_SHADOWED_KEY_LEN_VALUE_AES_256); + key_len_written = launder32(kAesKeyWordLen256); break; default: // Invalid value. return OTCRYPTO_BAD_ARGS; } + HARDENED_CHECK_EQ(key_len_written, key.key_len); // Never enable manual operation. ctrl_reg = bitfield_bit32_write( From 1eea54d9b56a8b3701c8bd07eb9c5bb3c3fbdea9 Mon Sep 17 00:00:00 2001 From: Pascal Nasahl Date: Mon, 25 Aug 2025 14:54:12 +0200 Subject: [PATCH 19/51] [crypto] Read back the AES config as a FI countermeasure Signed-off-by: Pascal Nasahl (cherry picked from commit cb9312757e122afcd78f3dcb2b71bd3e5bedaeaf) --- sw/device/lib/crypto/drivers/aes.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sw/device/lib/crypto/drivers/aes.c b/sw/device/lib/crypto/drivers/aes.c index 45be0bae6acef..f73e4f14b3cc1 100644 --- a/sw/device/lib/crypto/drivers/aes.c +++ b/sw/device/lib/crypto/drivers/aes.c @@ -235,6 +235,10 @@ static status_t aes_begin(aes_key_t key, const aes_block_t *iv, } } + // Read back the AES configuration and compare to the expected configuration. + HARDENED_CHECK_EQ(abs_mmio_read32(kBase + AES_CTRL_SHADOWED_REG_OFFSET), + launder32(ctrl_reg)); + // Check that AES is ready to receive input data. uint32_t status = abs_mmio_read32(kBase + AES_STATUS_REG_OFFSET); if (!bitfield_bit32_read(launder32(status), AES_STATUS_INPUT_READY_BIT)) { From 2825dd76899838cd744417ef02febc30027b745c Mon Sep 17 00:00:00 2001 From: Pascal Nasahl Date: Wed, 27 Aug 2025 11:13:53 +0200 Subject: [PATCH 20/51] [crypto] Check AES key integrity after writing to the core Add new functions to the AES driver that allows computing a checksum of the current AES key. The checksum is calculated when creating the AES key structure. The checksum is checked again after writing the key to the AES IP core. Signed-off-by: Pascal Nasahl (cherry picked from commit 997756745256ced41fd774ac43478ed799e6e6ac) --- sw/device/lib/crypto/drivers/BUILD | 1 + sw/device/lib/crypto/drivers/aes.c | 27 +++++++++++++++++++++++++ sw/device/lib/crypto/drivers/aes.h | 27 +++++++++++++++++++++++++ sw/device/lib/crypto/drivers/aes_test.c | 3 +++ sw/device/lib/crypto/impl/aes.c | 5 +++++ 5 files changed, 63 insertions(+) diff --git a/sw/device/lib/crypto/drivers/BUILD b/sw/device/lib/crypto/drivers/BUILD index 666bf7e2db76e..08719d922821a 100644 --- a/sw/device/lib/crypto/drivers/BUILD +++ b/sw/device/lib/crypto/drivers/BUILD @@ -36,6 +36,7 @@ cc_library( "//hw/top_earlgrey/sw/autogen:top_earlgrey", "//sw/device/lib/base:abs_mmio", "//sw/device/lib/base:bitfield", + "//sw/device/lib/base:crc32", "//sw/device/lib/base:hardened", "//sw/device/lib/base:macros", "//sw/device/lib/crypto/impl:status", diff --git a/sw/device/lib/crypto/drivers/aes.c b/sw/device/lib/crypto/drivers/aes.c index f73e4f14b3cc1..e40885a8ec821 100644 --- a/sw/device/lib/crypto/drivers/aes.c +++ b/sw/device/lib/crypto/drivers/aes.c @@ -6,6 +6,7 @@ #include "sw/device/lib/base/abs_mmio.h" #include "sw/device/lib/base/bitfield.h" +#include "sw/device/lib/base/crc32.h" #include "sw/device/lib/base/hardened.h" #include "sw/device/lib/base/macros.h" #include "sw/device/lib/base/memory.h" @@ -86,6 +87,9 @@ static status_t aes_write_key(aes_key_t key) { } HARDENED_CHECK_EQ(i, kAesKeyWordLenMax); + // Check the integrity of the key written to the AES core. + HARDENED_CHECK_EQ(aes_key_integrity_checksum_check(&key), kHardenedBoolTrue); + return spin_until(AES_STATUS_IDLE_BIT); } @@ -314,3 +318,26 @@ status_t aes_end(aes_block_t *iv) { return spin_until(AES_STATUS_IDLE_BIT); } + +uint32_t aes_key_integrity_checksum(const aes_key_t *key) { + uint32_t ctx; + crc32_init(&ctx); + crc32_add32(&ctx, key->mode); + crc32_add32(&ctx, key->sideload); + crc32_add32(&ctx, key->key_len); + // Compute the checksum only over a single share to avoid side-channel + // leakage. From a FI perspective only covering one key share is fine as + // (a) manipulating the second share with FI has only limited use to an + // adversary and (b) when manipulating the entire pointer to the key structure + // the checksum check fails. + crc32_add(&ctx, (unsigned char *)key->key_shares[0], key->key_len); + return crc32_finish(&ctx); +} + +hardened_bool_t aes_key_integrity_checksum_check(const aes_key_t *key) { + if (key->checksum == launder32(aes_key_integrity_checksum(key))) { + HARDENED_CHECK_EQ(key->checksum, aes_key_integrity_checksum(key)); + return kHardenedBoolTrue; + } + return kHardenedBoolFalse; +} diff --git a/sw/device/lib/crypto/drivers/aes.h b/sw/device/lib/crypto/drivers/aes.h index 5e2e9396775d2..ca90d882636cc 100644 --- a/sw/device/lib/crypto/drivers/aes.h +++ b/sw/device/lib/crypto/drivers/aes.h @@ -73,6 +73,11 @@ typedef struct aes_key { * of sufficient length. */ const uint32_t *key_shares[2]; + + /** + * The checksum of the key structure. + */ + uint32_t checksum; } aes_key_t; /** @@ -152,6 +157,28 @@ status_t aes_update(aes_block_t *dest, const aes_block_t *src); OT_WARN_UNUSED_RESULT status_t aes_end(aes_block_t *iv); +/** + * Compute the checksum of an AES key. + * + * Call this routine after creating or modifying the aes key structure. + * + * @param key AES key. + * @returns Checksum value. + */ +uint32_t aes_key_integrity_checksum(const aes_key_t *key); + +/** + * Perform an integrity check on the AES key. + * + * Returns `kHardenedBoolTrue` if the check passed and `kHardenedBoolFalse` + * otherwise. + * + * @param key AES key. + * @returns Whether the integrity check passed. + */ +OT_WARN_UNUSED_RESULT +hardened_bool_t aes_key_integrity_checksum_check(const aes_key_t *key); + #ifdef __cplusplus } #endif diff --git a/sw/device/lib/crypto/drivers/aes_test.c b/sw/device/lib/crypto/drivers/aes_test.c index a05e5807055ba..33808eb0b785a 100644 --- a/sw/device/lib/crypto/drivers/aes_test.c +++ b/sw/device/lib/crypto/drivers/aes_test.c @@ -88,6 +88,9 @@ static status_t run_aes_test(void) { .key_len = 4, .key_shares = {share0, share1}, }; + // Create the checksum of the key and store it in the key structure. + key.checksum = aes_key_integrity_checksum(&key); + TRY(aes_encrypt_begin(key, &kIv)); aes_block_t ciphertext[ARRAYSIZE(kCiphertext)] = {0}; diff --git a/sw/device/lib/crypto/impl/aes.c b/sw/device/lib/crypto/impl/aes.c index 8e4e22cf5eff2..25c46f38f6ff9 100644 --- a/sw/device/lib/crypto/impl/aes.c +++ b/sw/device/lib/crypto/impl/aes.c @@ -111,6 +111,11 @@ static status_t aes_key_construct(otcrypto_blinded_key_t *blinded_key, // Set the AES key length (in words). aes_key->key_len = keyblob_share_num_words(blinded_key->config); + if (aes_key->sideload == kHardenedBoolFalse) { + // Create the checksum of the key and store it in the key structure. + aes_key->checksum = aes_key_integrity_checksum(aes_key); + } + return OTCRYPTO_OK; } From 05f3d396120ef694b0839ed03d04978e47cecd9c Mon Sep 17 00:00:00 2001 From: Pascal Nasahl Date: Wed, 27 Aug 2025 12:28:23 +0200 Subject: [PATCH 21/51] [crypto] Check aes_key_t checksum for AES-GCM Signed-off-by: Pascal Nasahl (cherry picked from commit 6e77c087cc69bbfbb35ab97999ff7991c10da9ac) --- sw/device/lib/crypto/impl/aes_gcm.c | 5 +++++ sw/device/lib/crypto/include/aes_gcm.h | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/sw/device/lib/crypto/impl/aes_gcm.c b/sw/device/lib/crypto/impl/aes_gcm.c index e229dc955bd7f..0ef12067b7a94 100644 --- a/sw/device/lib/crypto/impl/aes_gcm.c +++ b/sw/device/lib/crypto/impl/aes_gcm.c @@ -87,6 +87,8 @@ status_t gcm_remask_key(aes_gcm_context_t *internal_ctx) { internal_ctx->key.key_len); hardened_xor((uint32_t *)internal_ctx->key.key_shares[1], mask, internal_ctx->key.key_len); + // Update the checksum. + internal_ctx->key.checksum = aes_key_integrity_checksum(&internal_ctx->key); } else { HARDENED_CHECK_EQ(internal_ctx->key.sideload, kHardenedBoolTrue); } @@ -163,6 +165,9 @@ static status_t aes_gcm_key_construct(otcrypto_blinded_key_t *blinded_key, } HARDENED_CHECK_EQ(aes_key->sideload, blinded_key->config.hw_backed); + // Create the checksum of the key and store it in the key structure. + aes_key->checksum = aes_key_integrity_checksum(aes_key); + return OTCRYPTO_OK; } diff --git a/sw/device/lib/crypto/include/aes_gcm.h b/sw/device/lib/crypto/include/aes_gcm.h index 65eaf269bd39d..e19354bf16c03 100644 --- a/sw/device/lib/crypto/include/aes_gcm.h +++ b/sw/device/lib/crypto/include/aes_gcm.h @@ -39,7 +39,7 @@ typedef enum otcrypto_aes_gcm_tag_len { * change.  */ typedef struct otcrypto_aes_gcm_context { - uint32_t data[98]; + uint32_t data[100]; } otcrypto_aes_gcm_context_t; /** From 82ee5a5a5b8cdee6e799c95d5902fe3559c45f63 Mon Sep 17 00:00:00 2001 From: Pascal Nasahl Date: Thu, 28 Aug 2025 10:22:32 +0200 Subject: [PATCH 22/51] [crypto] Check aes_key_t checksum for AES-KWP Signed-off-by: Pascal Nasahl (cherry picked from commit 83b8114beceb0685b8b2a7ef7e0274aa2a8393ae) --- sw/device/lib/crypto/impl/key_transport.c | 3 +++ sw/device/tests/crypto/aes_kwp_kat_functest.c | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/sw/device/lib/crypto/impl/key_transport.c b/sw/device/lib/crypto/impl/key_transport.c index 3cf7da92d47c4..dba5d000786da 100644 --- a/sw/device/lib/crypto/impl/key_transport.c +++ b/sw/device/lib/crypto/impl/key_transport.c @@ -166,6 +166,9 @@ static status_t aes_kwp_key_construct(const otcrypto_blinded_key_t *key_kek, aes_key->key_shares[0] = share0; aes_key->key_shares[1] = share1; + // Create the checksum of the key and store it in the key structure. + aes_key->checksum = aes_key_integrity_checksum(aes_key); + return OTCRYPTO_OK; } diff --git a/sw/device/tests/crypto/aes_kwp_kat_functest.c b/sw/device/tests/crypto/aes_kwp_kat_functest.c index 812e9b3ddde20..b178740dd4ad7 100644 --- a/sw/device/tests/crypto/aes_kwp_kat_functest.c +++ b/sw/device/tests/crypto/aes_kwp_kat_functest.c @@ -52,6 +52,8 @@ static status_t aes_kwp_wrap_kat(const uint32_t *kek, size_t kek_words, const uint32_t *ctext, size_t ctext_words) { // Construct an AES key. aes_key_t aes_kek = make_aes_key(kek, kek_words); + // Create the checksum of the key and store it in the key structure. + aes_kek.checksum = aes_key_integrity_checksum(&aes_kek); // Run key wrapping and check the result. uint32_t act_ctext[ctext_words + 1]; @@ -87,6 +89,8 @@ static status_t aes_kwp_unwrap_kat(const uint32_t *kek, size_t kek_words, size_t ptext_bytes) { // Construct an AES key. aes_key_t aes_kek = make_aes_key(kek, kek_words); + // Create the checksum of the key and store it in the key structure. + aes_kek.checksum = aes_key_integrity_checksum(&aes_kek); // Run key unwrapping. size_t ptext_words = (ptext_bytes + sizeof(uint32_t) - 1) / sizeof(uint32_t); From bad2439395f5a6641acb47683e0a47a7bb822fab Mon Sep 17 00:00:00 2001 From: Pascal Nasahl Date: Tue, 26 Aug 2025 11:27:04 +0200 Subject: [PATCH 23/51] [crypto] Harden AES & HMAC loops against FI Check if the loop ran for the expected number of iterations. Signed-off-by: Pascal Nasahl (cherry picked from commit 92c3db6c2382f99991ba10b92b94cdd1fa7d69e4) --- sw/device/lib/crypto/drivers/aes.c | 15 ++++++++++++--- sw/device/lib/crypto/drivers/hmac.c | 15 +++++++++++---- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/sw/device/lib/crypto/drivers/aes.c b/sw/device/lib/crypto/drivers/aes.c index e40885a8ec821..37c3d7a9b2297 100644 --- a/sw/device/lib/crypto/drivers/aes.c +++ b/sw/device/lib/crypto/drivers/aes.c @@ -278,18 +278,24 @@ status_t aes_update(aes_block_t *dest, const aes_block_t *src) { HARDENED_TRY(spin_until(AES_STATUS_OUTPUT_VALID_BIT)); uint32_t offset = kBase + AES_DATA_OUT_0_REG_OFFSET; - for (size_t i = 0; i < ARRAYSIZE(dest->data); ++i) { + size_t i; + for (i = 0; launder32(i) < ARRAYSIZE(dest->data); ++i) { dest->data[i] = abs_mmio_read32(offset + i * sizeof(uint32_t)); } + // Check that the loop ran for the correct number of iterations. + HARDENED_CHECK_EQ(i, ARRAYSIZE(dest->data)); } if (src != NULL) { HARDENED_TRY(spin_until(AES_STATUS_INPUT_READY_BIT)); uint32_t offset = kBase + AES_DATA_IN_0_REG_OFFSET; - for (size_t i = 0; i < ARRAYSIZE(src->data); ++i) { + size_t i; + for (i = 0; launder32(i) < ARRAYSIZE(src->data); ++i) { abs_mmio_write32(offset + i * sizeof(uint32_t), src->data[i]); } + // Check that the loop ran for the correct number of iterations. + HARDENED_CHECK_EQ(i, ARRAYSIZE(src->data)); } return OTCRYPTO_OK; @@ -304,9 +310,12 @@ status_t aes_end(aes_block_t *iv) { if (iv != NULL) { // Read back the current IV from the hardware. uint32_t iv_offset = kBase + AES_IV_0_REG_OFFSET; - for (size_t i = 0; i < ARRAYSIZE(iv->data); ++i) { + size_t i; + for (i = 0; launder32(i) < ARRAYSIZE(iv->data); ++i) { iv->data[i] = abs_mmio_read32(iv_offset + i * sizeof(uint32_t)); } + // Check that the loop ran for the correct number of iterations. + HARDENED_CHECK_EQ(i, ARRAYSIZE(iv->data)); } uint32_t trigger_reg = 0; diff --git a/sw/device/lib/crypto/drivers/hmac.c b/sw/device/lib/crypto/drivers/hmac.c index 661512d6cda2a..777cca4057f4d 100644 --- a/sw/device/lib/crypto/drivers/hmac.c +++ b/sw/device/lib/crypto/drivers/hmac.c @@ -232,11 +232,14 @@ static void context_restore(hmac_ctx_t *ctx) { // For SHA-256 and HMAC-256, we do not need to write to the second half of // DIGEST registers, but we do it anyway to keep the driver simple. - for (size_t i = 0; i < kHmacMaxDigestWords; i++) { + size_t i = 0; + for (; launder32(i) < kHmacMaxDigestWords; i++) { abs_mmio_write32( kHmacBaseAddr + HMAC_DIGEST_0_REG_OFFSET + sizeof(uint32_t) * i, ctx->H[i]); } + // Check that the loop ran for the correct number of iterations. + HARDENED_CHECK_EQ(i, kHmacMaxDigestWords); abs_mmio_write32(kHmacBaseAddr + HMAC_MSG_LENGTH_LOWER_REG_OFFSET, ctx->lower); abs_mmio_write32(kHmacBaseAddr + HMAC_MSG_LENGTH_UPPER_REG_OFFSET, @@ -282,21 +285,25 @@ static void msg_fifo_write(const uint8_t *message, size_t message_len) { // TODO(#23191): Should we handle backpressure here? // Begin by writing a one byte at a time until the data is aligned. size_t i = 0; - for (; misalignment32_of((uintptr_t)(&message[i])) > 0 && i < message_len; + for (; misalignment32_of((uintptr_t)(&message[i])) > 0 && + launder32(i) < message_len; i++) { abs_mmio_write8(kHmacBaseAddr + HMAC_MSG_FIFO_REG_OFFSET, message[i]); } // Write one word at a time as long as there is a full word available. - for (; i + sizeof(uint32_t) <= message_len; i += sizeof(uint32_t)) { + for (; launder32(i + sizeof(uint32_t)) <= message_len; + i += sizeof(uint32_t)) { uint32_t next_word = read_32(&message[i]); abs_mmio_write32(kHmacBaseAddr + HMAC_MSG_FIFO_REG_OFFSET, next_word); } // For the last few bytes, we need to write one byte at a time again. - for (; i < message_len; i++) { + for (; launder32(i) < message_len; i++) { abs_mmio_write8(kHmacBaseAddr + HMAC_MSG_FIFO_REG_OFFSET, message[i]); } + // Check that the loops ran for the correct number of iterations. + HARDENED_CHECK_EQ(i, message_len); } /** From 8e017041e6fe47512d8989b7d2d924031c423112 Mon Sep 17 00:00:00 2001 From: Pascal Nasahl Date: Thu, 14 Aug 2025 15:09:15 +0200 Subject: [PATCH 24/51] [crypto] Randomize key write in AES driver This commit randomizes the order we are writing the key shares to the AES block. Signed-off-by: Pascal Nasahl (cherry picked from commit fc4196bd6f8008861a3d65615a936c8596d1786a) --- sw/device/lib/crypto/drivers/BUILD | 1 + sw/device/lib/crypto/drivers/aes.c | 27 ++++++++------------------- 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/sw/device/lib/crypto/drivers/BUILD b/sw/device/lib/crypto/drivers/BUILD index 08719d922821a..1c385dc91b8df 100644 --- a/sw/device/lib/crypto/drivers/BUILD +++ b/sw/device/lib/crypto/drivers/BUILD @@ -38,6 +38,7 @@ cc_library( "//sw/device/lib/base:bitfield", "//sw/device/lib/base:crc32", "//sw/device/lib/base:hardened", + "//sw/device/lib/base:hardened_memory", "//sw/device/lib/base:macros", "//sw/device/lib/crypto/impl:status", ], diff --git a/sw/device/lib/crypto/drivers/aes.c b/sw/device/lib/crypto/drivers/aes.c index 37c3d7a9b2297..ac8311f57e9fd 100644 --- a/sw/device/lib/crypto/drivers/aes.c +++ b/sw/device/lib/crypto/drivers/aes.c @@ -8,6 +8,7 @@ #include "sw/device/lib/base/bitfield.h" #include "sw/device/lib/base/crc32.h" #include "sw/device/lib/base/hardened.h" +#include "sw/device/lib/base/hardened_memory.h" #include "sw/device/lib/base/macros.h" #include "sw/device/lib/base/memory.h" #include "sw/device/lib/crypto/drivers/entropy.h" @@ -66,26 +67,14 @@ static status_t aes_write_key(aes_key_t key) { uint32_t share0 = kBase + AES_KEY_SHARE0_0_REG_OFFSET; uint32_t share1 = kBase + AES_KEY_SHARE1_0_REG_OFFSET; - // Handle key shares in two separate loops to avoid dealing with + // Handle key shares in two separate hardened_memcpys to avoid dealing with // corresponding parts too close together, which could risk power - // side-channel leakage in the ALU. - // TODO: randomize iteration order. - size_t i = 0; - for (; i < key.key_len; ++i) { - abs_mmio_write32(share0 + i * sizeof(uint32_t), key.key_shares[0][i]); - } - HARDENED_CHECK_EQ(i, key.key_len); - for (i = 0; i < key.key_len; ++i) { - abs_mmio_write32(share1 + i * sizeof(uint32_t), key.key_shares[1][i]); - } - HARDENED_CHECK_EQ(i, key.key_len); - - // Write random words to remaining key registers. - for (; i < kAesKeyWordLenMax; i++) { - abs_mmio_write32(share0 + i * sizeof(uint32_t), ibex_rnd32_read()); - abs_mmio_write32(share1 + i * sizeof(uint32_t), ibex_rnd32_read()); - } - HARDENED_CHECK_EQ(i, kAesKeyWordLenMax); + // side-channel leakage in the ALU. Before writing the key shares, initialize + // the registers with random data. + hardened_memshred((uint32_t *)share0, kAesKeyWordLenMax); + hardened_memcpy((uint32_t *)share0, key.key_shares[0], key.key_len); + hardened_memshred((uint32_t *)share1, kAesKeyWordLenMax); + hardened_memcpy((uint32_t *)share1, key.key_shares[1], key.key_len); // Check the integrity of the key written to the AES core. HARDENED_CHECK_EQ(aes_key_integrity_checksum_check(&key), kHardenedBoolTrue); From 3a50ac89ff8f52144b8eb520bc97a83d7e777121 Mon Sep 17 00:00:00 2001 From: Pascal Nasahl Date: Thu, 14 Aug 2025 15:38:19 +0200 Subject: [PATCH 25/51] [crypto] Randomize key read in keymgr driver This commit randomizes the order we are reading the key shares from the keymanager. Signed-off-by: Pascal Nasahl (cherry picked from commit 2144e83b23a34ea3c2a9f157d45e9915a3abf069) --- sw/device/lib/crypto/drivers/keymgr.c | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/sw/device/lib/crypto/drivers/keymgr.c b/sw/device/lib/crypto/drivers/keymgr.c index 62df634ae1e75..5032829d96373 100644 --- a/sw/device/lib/crypto/drivers/keymgr.c +++ b/sw/device/lib/crypto/drivers/keymgr.c @@ -153,22 +153,15 @@ status_t keymgr_generate_key_sw(keymgr_diversification_t diversification, keymgr_start(diversification); HARDENED_TRY(keymgr_wait_until_done()); - // Randomize the destination buffer. + // Collect the output. To avoid side-channel lekage, first randomize the + // destination buffers using memshred. Then copy the key using a hardened + // memcpy. + uint32_t share0 = kBaseAddr + KEYMGR_SW_SHARE0_OUTPUT_0_REG_OFFSET; + uint32_t share1 = kBaseAddr + KEYMGR_SW_SHARE1_OUTPUT_0_REG_OFFSET; hardened_memshred(key->share0, kKeymgrOutputShareNumWords); + hardened_memcpy(key->share0, (uint32_t *)share0, kKeymgrOutputShareNumWords); hardened_memshred(key->share1, kKeymgrOutputShareNumWords); - - // Collect output. - // TODO: for SCA hardening, randomize the order of these reads. - for (size_t i = 0; i < kKeymgrOutputShareNumWords; i++) { - key->share0[i] = - abs_mmio_read32(kBaseAddr + KEYMGR_SW_SHARE0_OUTPUT_0_REG_OFFSET + - (i * sizeof(uint32_t))); - } - for (size_t i = 0; i < kKeymgrOutputShareNumWords; i++) { - key->share1[i] = - abs_mmio_read32(kBaseAddr + KEYMGR_SW_SHARE1_OUTPUT_0_REG_OFFSET + - (i * sizeof(uint32_t))); - } + hardened_memcpy(key->share1, (uint32_t *)share1, kKeymgrOutputShareNumWords); return OTCRYPTO_OK; } From 7813ad347935a4b3f4162ed9ce5860dbfb1bc727 Mon Sep 17 00:00:00 2001 From: Pascal Nasahl Date: Tue, 8 Jul 2025 17:37:51 +0200 Subject: [PATCH 26/51] [crypto] Protect HMAC against FI This commit adds fault injection protection to the HMAC cryptolib implementation. When `security_level == kOtcryptoKeySecurityLevelLow`, no protection is provided, similar until now. When `seucrity_level == kOtcryptoKeySecurityLevelMedium`, the CL now calls the CL-internal `hmac_hmac_sha256()` function twice. By comparing both tag outputs, an ongoing FI attack can be mitigated. When `security_level == kOtcryptoKeySecurityLevelHigh`, the CL now calls once the CL-internal `hmac_hmac_sha256()` function as well as the new `hmac_hmac_sha256_redundant()` function. Both functions again calculate the same tag and the CL compares both tages. However, the second implementation uses an alternative HMAC implementation that does not use the HMAC core itself but constructs HMAC over the SHA core. The idea is that two identical faults affect both implementations differently, which can be detected. Impact on the runtime for HMAC SHA256, 13 bytes message: otcrypto_hmac() runtime with security_level low: 6026 cycles otcrypto_hmac() runtime with security_level medium: 11852 cycles otcrypto_hmac() runtime with security_level high: 14975 cycles Signed-off-by: Pascal Nasahl (cherry picked from commit 9e6c1af9c3a1578a3b0388eaff677ccc1f3d7355) --- sw/device/lib/crypto/drivers/hmac.c | 114 ++++++++++++++++++ sw/device/lib/crypto/drivers/hmac.h | 69 +++++++++++ sw/device/lib/crypto/impl/BUILD | 1 + sw/device/lib/crypto/impl/hmac.c | 175 ++++++++++++++++++++++++++-- 4 files changed, 349 insertions(+), 10 deletions(-) diff --git a/sw/device/lib/crypto/drivers/hmac.c b/sw/device/lib/crypto/drivers/hmac.c index 777cca4057f4d..3684edb02ed1a 100644 --- a/sw/device/lib/crypto/drivers/hmac.c +++ b/sw/device/lib/crypto/drivers/hmac.c @@ -464,6 +464,44 @@ status_t hmac_hmac_sha256_cl(const uint32_t *key_block, const uint8_t *msg, kHmacSha256DigestWords, tag); } +status_t hmac_hmac_sha256_redundant(const uint32_t *key_block, + const uint8_t *msg, size_t msg_len, + uint32_t *tag) { + uint32_t o_key_pad[kHmacSha256BlockWords]; + uint32_t i_key_pad[kHmacSha256BlockWords]; + memset(o_key_pad, 0, kHmacSha256BlockBytes); + memset(i_key_pad, 0, kHmacSha256BlockBytes); + + // XOR the key K with the outer (opad) and inner (ipad) padding. + for (size_t it = 0; it < kHmacSha256BlockWords; it++) { + o_key_pad[it] = key_block[it] ^ 0x5c5c5c5c; + i_key_pad[it] = key_block[it] ^ 0x36363636; + } + + // Concatenate the message with the inner padded key. + uint8_t i_key_pad_msg[kHmacSha256BlockBytes + msg_len]; + memset(i_key_pad_msg, 0, sizeof(i_key_pad_msg)); + memcpy(i_key_pad_msg, i_key_pad, kHmacSha256BlockBytes); + memcpy(i_key_pad_msg + kHmacSha256BlockBytes, msg, msg_len); + + // h_i_key_pad_msg = H(i_key_pad || m). + uint32_t h_i_key_pad_msg[kHmacSha256DigestWords]; + memset(h_i_key_pad_msg, 0, sizeof(h_i_key_pad_msg)); + HARDENED_TRY(hmac_hash_sha256(i_key_pad_msg, kHmacSha256BlockBytes + msg_len, + h_i_key_pad_msg)); + + // Concatenate the outer padded key with h_i_key_pad_msg. + uint8_t o_key_pad_hash[kHmacSha256BlockBytes + kHmacSha256DigestBytes]; + memset(o_key_pad_hash, 0, sizeof(o_key_pad_hash)); + memcpy(o_key_pad_hash, o_key_pad, kHmacSha256BlockBytes); + memcpy(o_key_pad_hash + kHmacSha256BlockBytes, h_i_key_pad_msg, + kHmacSha256DigestBytes); + + // hmac = H(o_key_pad || h_i_key_pad_msg). + return hmac_hash_sha256(o_key_pad_hash, + kHmacSha256BlockBytes + kHmacSha256DigestBytes, tag); +} + status_t hmac_hmac_sha384(const uint32_t *key_block, const uint8_t *msg, size_t msg_len, uint32_t *tag) { // Always configure the key length as the underlying message block size. @@ -472,6 +510,44 @@ status_t hmac_hmac_sha384(const uint32_t *key_block, const uint8_t *msg, kHmacSha384DigestWords, tag); } +status_t hmac_hmac_sha384_redundant(const uint32_t *key_block, + const uint8_t *msg, size_t msg_len, + uint32_t *tag) { + uint32_t o_key_pad[kHmacSha384BlockWords]; + uint32_t i_key_pad[kHmacSha384BlockWords]; + memset(o_key_pad, 0, kHmacSha384BlockBytes); + memset(i_key_pad, 0, kHmacSha384BlockBytes); + + // XOR the key K with the outer (opad) and inner (ipad) padding. + for (size_t it = 0; it < kHmacSha384BlockWords; it++) { + o_key_pad[it] = key_block[it] ^ 0x5c5c5c5c; + i_key_pad[it] = key_block[it] ^ 0x36363636; + } + + // Concatenate the message with the inner padded key. + uint8_t i_key_pad_msg[kHmacSha384BlockBytes + msg_len]; + memset(i_key_pad_msg, 0, sizeof(i_key_pad_msg)); + memcpy(i_key_pad_msg, i_key_pad, kHmacSha384BlockBytes); + memcpy(i_key_pad_msg + kHmacSha384BlockBytes, msg, msg_len); + + // h_i_key_pad_msg = H(i_key_pad || m). + uint32_t h_i_key_pad_msg[kHmacSha384DigestWords]; + memset(h_i_key_pad_msg, 0, sizeof(h_i_key_pad_msg)); + HARDENED_TRY(hmac_hash_sha384(i_key_pad_msg, kHmacSha384BlockBytes + msg_len, + h_i_key_pad_msg)); + + // Concatenate the outer padded key with h_i_key_pad_msg. + uint8_t o_key_pad_hash[kHmacSha384BlockBytes + kHmacSha384DigestBytes]; + memset(o_key_pad_hash, 0, sizeof(o_key_pad_hash)); + memcpy(o_key_pad_hash, o_key_pad, kHmacSha384BlockBytes); + memcpy(o_key_pad_hash + kHmacSha384BlockBytes, h_i_key_pad_msg, + kHmacSha384DigestBytes); + + // hmac = H(o_key_pad || h_i_key_pad_msg). + return hmac_hash_sha384(o_key_pad_hash, + kHmacSha384BlockBytes + kHmacSha384DigestBytes, tag); +} + status_t hmac_hmac_sha512(const uint32_t *key_block, const uint8_t *msg, size_t msg_len, uint32_t *tag) { // Always configure the key length as the underlying message block size. @@ -480,6 +556,44 @@ status_t hmac_hmac_sha512(const uint32_t *key_block, const uint8_t *msg, kHmacSha512DigestWords, tag); } +status_t hmac_hmac_sha512_redundant(const uint32_t *key_block, + const uint8_t *msg, size_t msg_len, + uint32_t *tag) { + uint32_t o_key_pad[kHmacSha512BlockWords]; + uint32_t i_key_pad[kHmacSha512BlockWords]; + memset(o_key_pad, 0, kHmacSha512BlockBytes); + memset(i_key_pad, 0, kHmacSha512BlockBytes); + + // XOR the key K with the outer (opad) and inner (ipad) padding. + for (size_t it = 0; it < kHmacSha512BlockWords; it++) { + o_key_pad[it] = key_block[it] ^ 0x5c5c5c5c; + i_key_pad[it] = key_block[it] ^ 0x36363636; + } + + // Concatenate the message with the inner padded key. + uint8_t i_key_pad_msg[kHmacSha512BlockBytes + msg_len]; + memset(i_key_pad_msg, 0, sizeof(i_key_pad_msg)); + memcpy(i_key_pad_msg, i_key_pad, kHmacSha512BlockBytes); + memcpy(i_key_pad_msg + kHmacSha512BlockBytes, msg, msg_len); + + // h_i_key_pad_msg = H(i_key_pad || m). + uint32_t h_i_key_pad_msg[kHmacSha512DigestWords]; + memset(h_i_key_pad_msg, 0, sizeof(h_i_key_pad_msg)); + HARDENED_TRY(hmac_hash_sha512(i_key_pad_msg, kHmacSha512BlockBytes + msg_len, + h_i_key_pad_msg)); + + // Concatenate the outer padded key with h_i_key_pad_msg. + uint8_t o_key_pad_hash[kHmacSha512BlockBytes + kHmacSha512DigestBytes]; + memset(o_key_pad_hash, 0, sizeof(o_key_pad_hash)); + memcpy(o_key_pad_hash, o_key_pad, kHmacSha512BlockBytes); + memcpy(o_key_pad_hash + kHmacSha512BlockBytes, h_i_key_pad_msg, + kHmacSha512DigestBytes); + + // hmac = H(o_key_pad || h_i_key_pad_msg). + return hmac_hash_sha512(o_key_pad_hash, + kHmacSha512BlockBytes + kHmacSha512DigestBytes, tag); +} + /** * Initialize the context for a hashing operation. * diff --git a/sw/device/lib/crypto/drivers/hmac.h b/sw/device/lib/crypto/drivers/hmac.h index da28c1e599ea3..dca815e57aef3 100644 --- a/sw/device/lib/crypto/drivers/hmac.h +++ b/sw/device/lib/crypto/drivers/hmac.h @@ -122,6 +122,29 @@ OT_WARN_UNUSED_RESULT status_t hmac_hmac_sha256_cl(const uint32_t *key_block, const uint8_t *msg, size_t msg_len, uint32_t *tag); +/** + * Redundant implementation for a one-shot HMAC-SHA256 hash computation. + * + * The key should be pre-processed into a buffer the size of a full message + * block, according to FIPS 198-1, section 4. + * + * To be used together with hmac_hmac_sha256() to mitigate FI attacks. This + * implementation is different to hmac_hmac_sha256() as it manually assembles + * the HMAC functionality using SHA256. By using two different HMAC + * implementations, injecting two identical faults affect different parts during + * the HMAC compuation, which can be detected. + * + * @param key_block Input key block (`kHmacSha256BlockWords` words). + * @param msg Input message. + * @param msg_len Message length in bytes. + * @param[out] tag Authentication tag (`kHmacSha256DigestWords` bytes). + * @return OK or error. + */ +OT_WARN_UNUSED_RESULT +status_t hmac_hmac_sha256_redundant(const uint32_t *key_block, + const uint8_t *msg, size_t msg_len, + uint32_t *tag); + /** * One-shot HMAC-SHA384 hash computation. * @@ -138,6 +161,29 @@ OT_WARN_UNUSED_RESULT status_t hmac_hmac_sha384(const uint32_t *key_block, const uint8_t *msg, size_t msg_len, uint32_t *tag); +/** + * Redundant implementation for a one-shot HMAC-SHA384 hash computation. + * + * The key should be pre-processed into a buffer the size of a full message + * block, according to FIPS 198-1, section 4. + * + * To be used together with hmac_hmac_sha384() to mitigate FI attacks. This + * implementation is different to hmac_hmac_sha384() as it manually assembles + * the HMAC functionality using SHA384. By using two different HMAC + * implementations, injecting two identical faults affect different parts during + * the HMAC compuation, which can be detected. + * + * @param key_block Input key block (`kHmacSha384BlockWords` words). + * @param msg Input message. + * @param msg_len Message length in bytes. + * @param[out] tag Authentication tag (`kHmacSha384DigestWords` bytes). + * @return OK or error. + */ +OT_WARN_UNUSED_RESULT +status_t hmac_hmac_sha384_redundant(const uint32_t *key_block, + const uint8_t *msg, size_t msg_len, + uint32_t *tag); + /** * One-shot HMAC-SHA512 hash computation. * @@ -154,6 +200,29 @@ OT_WARN_UNUSED_RESULT status_t hmac_hmac_sha512(const uint32_t *key_block, const uint8_t *msg, size_t msg_len, uint32_t *tag); +/** + * Redundant implementation for a one-shot HMAC-SHA512 hash computation. + * + * The key should be pre-processed into a buffer the size of a full message + * block, according to FIPS 198-1, section 4. + * + * To be used together with hmac_hmac_sha512() to mitigate FI attacks. This + * implementation is different to hmac_hmac_sha512() as it manually assembles + * the HMAC functionality using SHA512. By using two different HMAC + * implementations, injecting two identical faults affect different parts during + * the HMAC compuation, which can be detected. + * + * @param key_block Input key block (`kHmacSha512BlockWords` words). + * @param msg Input message. + * @param msg_len Message length in bytes. + * @param[out] tag Authentication tag (`kHmacSha512DigestWords` bytes). + * @return OK or error. + */ +OT_WARN_UNUSED_RESULT +status_t hmac_hmac_sha512_redundant(const uint32_t *key_block, + const uint8_t *msg, size_t msg_len, + uint32_t *tag); + /** * Initializes the context for a streaming SHA256 hash computation. * diff --git a/sw/device/lib/crypto/impl/BUILD b/sw/device/lib/crypto/impl/BUILD index b3455a2aafd95..ec5bf193e7916 100644 --- a/sw/device/lib/crypto/impl/BUILD +++ b/sw/device/lib/crypto/impl/BUILD @@ -303,6 +303,7 @@ cc_library( ":sha2", "//sw/device/lib/base:hardened", "//sw/device/lib/base:hardened_memory", + "//sw/device/lib/crypto/drivers:entropy", "//sw/device/lib/crypto/drivers:hmac", "//sw/device/lib/crypto/drivers:rv_core_ibex", ], diff --git a/sw/device/lib/crypto/impl/hmac.c b/sw/device/lib/crypto/impl/hmac.c index 97227ea60819b..709404441ae92 100644 --- a/sw/device/lib/crypto/impl/hmac.c +++ b/sw/device/lib/crypto/impl/hmac.c @@ -5,6 +5,7 @@ #include "sw/device/lib/crypto/include/hmac.h" #include "sw/device/lib/base/hardened_memory.h" +#include "sw/device/lib/crypto/drivers/entropy.h" #include "sw/device/lib/crypto/drivers/hmac.h" #include "sw/device/lib/crypto/drivers/rv_core_ibex.h" #include "sw/device/lib/crypto/impl/integrity.h" @@ -101,10 +102,8 @@ static status_t check_key(const otcrypto_blinded_key_t *key) { return OTCRYPTO_BAD_ARGS; } - // The underlying HMAC hardware is unmasked, and does not have sideload - // support. - if (key->config.hw_backed != kHardenedBoolFalse || - key->config.security_level != kOtcryptoKeySecurityLevelLow) { + // The underlying HMAC hardware does not have sideload support. + if (key->config.hw_backed != kHardenedBoolFalse) { return OTCRYPTO_NOT_IMPLEMENTED; } @@ -130,28 +129,179 @@ otcrypto_status_t otcrypto_hmac(const otcrypto_blinded_key_t *key, // Check the key for null pointers or invalid configurations. HARDENED_TRY(check_key(key)); + if (key->config.security_level != kOtcryptoKeySecurityLevelLow) { + // Entropy complex must be initialized for `hardened_memeq`. + HARDENED_TRY(entropy_complex_check()); + } + // Call the appropriate function from the HMAC driver. switch (launder32(key->config.key_mode)) { case kOtcryptoKeyModeHmacSha256: { HARDENED_CHECK_EQ(key->config.key_mode, kOtcryptoKeyModeHmacSha256); uint32_t key_block[kHmacSha256BlockWords]; HARDENED_TRY(key_block_get(key, ARRAYSIZE(key_block), key_block)); - return hmac_hmac_sha256_cl(key_block, input_message.data, - input_message.len, tag.data); + if (launder32(key->config.security_level) == + kOtcryptoKeySecurityLevelLow) { + // No protection against FI. + HARDENED_CHECK_EQ(key->config.security_level, + kOtcryptoKeySecurityLevelLow); + return hmac_hmac_sha256_cl(key_block, input_message.data, + input_message.len, tag.data); + } else if (launder32(key->config.security_level) == + kOtcryptoKeySecurityLevelMedium) { + // Call the HMAC core twice and compare both tags. This serves as a FI + // countermeasure. + // First HMAC computation using the HMAC core. + HARDENED_CHECK_EQ(key->config.security_level, + kOtcryptoKeySecurityLevelMedium); + HARDENED_TRY(hmac_hmac_sha256_cl(key_block, input_message.data, + input_message.len, tag.data)); + // Second HMAC computation using the HMAC core. + uint32_t tag_redundant[tag.len]; + uint32_t key_block_redundant[kHmacSha256BlockWords]; + HARDENED_TRY(key_block_get(key, ARRAYSIZE(key_block_redundant), + key_block_redundant)); + HARDENED_TRY(hmac_hmac_sha256_cl(key_block_redundant, + input_message.data, input_message.len, + tag_redundant)); + // Comparison of both tags. + HARDENED_CHECK_EQ( + hardened_memeq(&tag.data[0], &tag_redundant[0], tag.len), + kHardenedBoolTrue); + return OTCRYPTO_OK; + } else { + // Perform two HMAC operations. The first call uses the HMAC core. The + // second use uses a HMAC implementation that does not use the HMAC + // core. This serves as a FI countermeasure. + HARDENED_CHECK_EQ(key->config.security_level, + kOtcryptoKeySecurityLevelHigh); + // First HMAC computation using the HMAC core. + HARDENED_TRY(hmac_hmac_sha256_cl(key_block, input_message.data, + input_message.len, tag.data)); + // Second HMAC computation without using the HMAC core. + uint32_t tag_redundant[tag.len]; + uint32_t key_block_redundant[kHmacSha256BlockWords]; + HARDENED_TRY(key_block_get(key, ARRAYSIZE(key_block_redundant), + key_block_redundant)); + HARDENED_TRY( + hmac_hmac_sha256_redundant(key_block_redundant, input_message.data, + input_message.len, tag_redundant)); + // Comparison of both tags. + HARDENED_CHECK_EQ( + hardened_memeq(&tag.data[0], &tag_redundant[0], tag.len), + kHardenedBoolTrue); + return OTCRYPTO_OK; + } } case kOtcryptoKeyModeHmacSha384: { HARDENED_CHECK_EQ(key->config.key_mode, kOtcryptoKeyModeHmacSha384); uint32_t key_block[kHmacSha384BlockWords]; HARDENED_TRY(key_block_get(key, ARRAYSIZE(key_block), key_block)); - return hmac_hmac_sha384(key_block, input_message.data, input_message.len, - tag.data); + if (launder32(key->config.security_level) == + kOtcryptoKeySecurityLevelLow) { + HARDENED_CHECK_EQ(key->config.security_level, + kOtcryptoKeySecurityLevelLow); + return hmac_hmac_sha384(key_block, input_message.data, + input_message.len, tag.data); + } else if (launder32(key->config.security_level) == + kOtcryptoKeySecurityLevelMedium) { + // Call the HMAC core twice and compare both tags. This serves as a FI + // countermeasure. + // First HMAC computation using the HMAC core. + HARDENED_CHECK_EQ(key->config.security_level, + kOtcryptoKeySecurityLevelMedium); + HARDENED_TRY(hmac_hmac_sha384(key_block, input_message.data, + input_message.len, tag.data)); + // Second HMAC computation using the HMAC core. + uint32_t tag_redundant[tag.len]; + uint32_t key_block_redundant[kHmacSha384BlockWords]; + HARDENED_TRY(key_block_get(key, ARRAYSIZE(key_block_redundant), + key_block_redundant)); + HARDENED_TRY(hmac_hmac_sha384(key_block_redundant, input_message.data, + input_message.len, tag_redundant)); + // Comparison of both tags. + HARDENED_CHECK_EQ( + hardened_memeq(&tag.data[0], &tag_redundant[0], tag.len), + kHardenedBoolTrue); + return OTCRYPTO_OK; + } else { + // Perform two HMAC operations. The first call uses the HMAC core. The + // second use uses a HMAC implementation that does not use the HMAC + // core. This serves as a FI countermeasure. + HARDENED_CHECK_EQ(key->config.security_level, + kOtcryptoKeySecurityLevelHigh); + // First HMAC computation using the HMAC core. + HARDENED_TRY(hmac_hmac_sha384(key_block, input_message.data, + input_message.len, tag.data)); + // Second HMAC computation without using the HMAC core. + uint32_t tag_redundant[tag.len]; + uint32_t key_block_redundant[kHmacSha384BlockWords]; + HARDENED_TRY(key_block_get(key, ARRAYSIZE(key_block_redundant), + key_block_redundant)); + HARDENED_TRY( + hmac_hmac_sha384_redundant(key_block_redundant, input_message.data, + input_message.len, tag_redundant)); + // Comparison of both tags. + HARDENED_CHECK_EQ( + hardened_memeq(&tag.data[0], &tag_redundant[0], tag.len), + kHardenedBoolTrue); + return OTCRYPTO_OK; + } } case kOtcryptoKeyModeHmacSha512: { HARDENED_CHECK_EQ(key->config.key_mode, kOtcryptoKeyModeHmacSha512); uint32_t key_block[kHmacSha512BlockWords]; HARDENED_TRY(key_block_get(key, ARRAYSIZE(key_block), key_block)); - return hmac_hmac_sha512(key_block, input_message.data, input_message.len, - tag.data); + if (launder32(key->config.security_level) == + kOtcryptoKeySecurityLevelLow) { + HARDENED_CHECK_EQ(key->config.security_level, + kOtcryptoKeySecurityLevelLow); + return hmac_hmac_sha512(key_block, input_message.data, + input_message.len, tag.data); + } else if (launder32(key->config.security_level) == + kOtcryptoKeySecurityLevelMedium) { + // Call the HMAC core twice and compare both tags. This serves as a FI + // countermeasure. + // First HMAC computation using the HMAC core. + HARDENED_CHECK_EQ(key->config.security_level, + kOtcryptoKeySecurityLevelMedium); + HARDENED_TRY(hmac_hmac_sha512(key_block, input_message.data, + input_message.len, tag.data)); + // Second HMAC computation using the HMAC core. + uint32_t tag_redundant[tag.len]; + uint32_t key_block_redundant[kHmacSha512BlockWords]; + HARDENED_TRY(key_block_get(key, ARRAYSIZE(key_block_redundant), + key_block_redundant)); + HARDENED_TRY(hmac_hmac_sha512(key_block_redundant, input_message.data, + input_message.len, tag_redundant)); + // Comparison of both tags. + HARDENED_CHECK_EQ( + hardened_memeq(&tag.data[0], &tag_redundant[0], tag.len), + kHardenedBoolTrue); + return OTCRYPTO_OK; + } else { + // Perform two HMAC operations. The first call uses the HMAC core. The + // second use uses a HMAC implementation that does not use the HMAC + // core. This serves as a FI countermeasure. + HARDENED_CHECK_EQ(key->config.security_level, + kOtcryptoKeySecurityLevelHigh); + // First HMAC computation using the HMAC core. + HARDENED_TRY(hmac_hmac_sha512(key_block, input_message.data, + input_message.len, tag.data)); + // Second HMAC computation without using the HMAC core. + uint32_t tag_redundant[tag.len]; + uint32_t key_block_redundant[kHmacSha512BlockWords]; + HARDENED_TRY(key_block_get(key, ARRAYSIZE(key_block_redundant), + key_block_redundant)); + HARDENED_TRY( + hmac_hmac_sha512_redundant(key_block_redundant, input_message.data, + input_message.len, tag_redundant)); + // Comparison of both tags. + HARDENED_CHECK_EQ( + hardened_memeq(&tag.data[0], &tag_redundant[0], tag.len), + kHardenedBoolTrue); + return OTCRYPTO_OK; + } } default: return OTCRYPTO_BAD_ARGS; @@ -171,6 +321,11 @@ otcrypto_status_t otcrypto_hmac_init(otcrypto_hmac_context_t *ctx, // Check the key for null pointers or invalid configurations. HARDENED_TRY(check_key(key)); + // Only security level low is supported for the streaming mode. + if (key->config.security_level != kOtcryptoKeySecurityLevelLow) { + return OTCRYPTO_NOT_IMPLEMENTED; + } + // Call the appropriate function from the HMAC driver. hmac_ctx_t hmac_ctx; uint32_t key_block[kHmacMaxBlockWords]; From 41795a7faa9e4675b56e3026bd4f8f3fb26241b5 Mon Sep 17 00:00:00 2001 From: Pascal Nasahl Date: Thu, 10 Jul 2025 10:51:02 +0200 Subject: [PATCH 27/51] [crypto] Protect AES against FI This commit adds fault injection protection for the AES cryptolib implementation. When `key.security_level > kOtcryptoKeySecurityLevelLow`, the cryptolib internally performs two AES operations. The result of the first AES operation is used as input of the second AES operation. The output of the second AES operation should match the input of the first AES operation. If the check fails, an UNIMP instruction is executed. Performance AES-128 ECB encryption of 50 bytes: otcrypto_aes() runtime with security_level low: 25407 cycles otcrypto_aes() runtime with security_level > low: 53964 cycles Signed-off-by: Pascal Nasahl (cherry picked from commit 15fd25589efa049ecd998ca640e318020b1a8bd2) --- sw/device/lib/crypto/impl/aes.c | 106 +++++++++++++++++++++++++++++--- 1 file changed, 96 insertions(+), 10 deletions(-) diff --git a/sw/device/lib/crypto/impl/aes.c b/sw/device/lib/crypto/impl/aes.c index 25c46f38f6ff9..7e1aaa60b87ca 100644 --- a/sw/device/lib/crypto/impl/aes.c +++ b/sw/device/lib/crypto/impl/aes.c @@ -256,22 +256,30 @@ otcrypto_status_t otcrypto_aes_padded_plaintext_length( return OTCRYPTO_OK; } -otcrypto_status_t otcrypto_aes(otcrypto_blinded_key_t *key, - otcrypto_word32_buf_t iv, - otcrypto_aes_mode_t aes_mode, - otcrypto_aes_operation_t aes_operation, - otcrypto_const_byte_buf_t cipher_input, - otcrypto_aes_padding_t aes_padding, - otcrypto_byte_buf_t cipher_output) { +/** + * Performs the AES operation. + * + * @param key Pointer to the blinded key struct with key shares. + * @param iv Initialization vector, used for CBC, CFB, OFB, CTR modes. May be + * NULL if mode is ECB. + * @param aes_mode Required AES mode of operation. + * @param aes_operation Required AES operation (encrypt or decrypt). + * @param cipher_input Input data to be ciphered. + * @param aes_padding Padding scheme to be used for the data. + * @param[out] cipher_output Output data after cipher operation. + * @return The result of the cipher operation. + */ +static otcrypto_status_t otcrypto_aes_impl( + otcrypto_blinded_key_t *key, otcrypto_word32_buf_t iv, + otcrypto_aes_mode_t aes_mode, otcrypto_aes_operation_t aes_operation, + otcrypto_const_byte_buf_t cipher_input, otcrypto_aes_padding_t aes_padding, + otcrypto_byte_buf_t cipher_output) { // Check for NULL pointers in input pointers and data buffers. if (key == NULL || (aes_mode != kOtcryptoAesModeEcb && iv.data == NULL) || cipher_input.data == NULL || cipher_output.data == NULL) { return OTCRYPTO_BAD_ARGS; } - // Ensure the entropy complex is initialized. - HARDENED_TRY(entropy_complex_check()); - // Calculate the number of blocks for the input, including the padding for // encryption. size_t input_nblocks; @@ -403,3 +411,81 @@ otcrypto_status_t otcrypto_aes(otcrypto_blinded_key_t *key, // In case the key was sideloaded, clear it. return keymgr_sideload_clear_aes(); } + +otcrypto_status_t otcrypto_aes(otcrypto_blinded_key_t *key, + otcrypto_word32_buf_t iv, + otcrypto_aes_mode_t aes_mode, + otcrypto_aes_operation_t aes_operation, + otcrypto_const_byte_buf_t cipher_input, + otcrypto_aes_padding_t aes_padding, + otcrypto_byte_buf_t cipher_output) { + // Check for NULL pointers in input pointers and data buffers. + if (key == NULL || (aes_mode != kOtcryptoAesModeEcb && iv.data == NULL) || + cipher_input.data == NULL || cipher_output.data == NULL) { + return OTCRYPTO_BAD_ARGS; + } + + // Ensure the entropy complex is initialized. + HARDENED_TRY(entropy_complex_check()); + + if (launder32(key->config.security_level) == kOtcryptoKeySecurityLevelLow) { + HARDENED_CHECK_EQ(key->config.security_level, kOtcryptoKeySecurityLevelLow); + // No additional FI protection. + return otcrypto_aes_impl(key, iv, aes_mode, aes_operation, cipher_input, + aes_padding, cipher_output); + } else { + HARDENED_CHECK_NE(key->config.security_level, kOtcryptoKeySecurityLevelLow); + // Protect the AES computation against faults. Recomputes the ciphertext or + // plaintexts after the actual AES operation and compares it to the input. + + // Copy the IV for the second AES computation. + uint32_t iv_data[iv.len]; + memcpy(iv_data, iv.data, sizeof(iv_data)); + otcrypto_word32_buf_t iv_redundant = { + .data = iv_data, + .len = iv.len, + }; + + // First AES operation using the intended AES mode (encryption or + // decryption). + HARDENED_TRY(otcrypto_aes_impl(key, iv, aes_mode, aes_operation, + cipher_input, aes_padding, cipher_output)); + + // Second AES operation using the counterpart of the AES mode (decryption or + // encryption). + otcrypto_aes_operation_t aes_operation_inverse; + size_t len_bytes; + if (aes_operation == kOtcryptoAesOperationEncrypt) { + aes_operation_inverse = kOtcryptoAesOperationDecrypt; + len_bytes = cipher_output.len; + } else { + aes_operation_inverse = kOtcryptoAesOperationEncrypt; + TRY(otcrypto_aes_padded_plaintext_length(cipher_output.len, aes_padding, + &len_bytes)); + } + + // Create the input buffer that contains the cipher_output of the first AES + // operation. + otcrypto_const_byte_buf_t cipher_input_redundant = { + .data = cipher_output.data, + .len = cipher_output.len, + }; + // Create the output buffer. + uint32_t output_buf[len_bytes / sizeof(uint32_t)]; + otcrypto_byte_buf_t cipher_input_recomputed = { + .data = (unsigned char *)output_buf, + .len = len_bytes, + }; + HARDENED_TRY(otcrypto_aes_impl( + key, iv_redundant, aes_mode, aes_operation_inverse, + cipher_input_redundant, aes_padding, cipher_input_recomputed)); + + // Comparison. + HARDENED_CHECK_EQ( + hardened_memeq((const uint32_t *)cipher_input.data, output_buf, + cipher_input.len / sizeof(uint32_t)), + kHardenedBoolTrue); + } + + return OTCRYPTO_OK; +} From 2feaf2145c6f585339725df06e29f53c930ed780 Mon Sep 17 00:00:00 2001 From: Pascal Nasahl Date: Mon, 8 Sep 2025 13:57:36 +0200 Subject: [PATCH 28/51] [crypto] Check ECC key integrity after writing to OTBN We should check the integrity of the public and private key after we have written them into OTBN. Signed-off-by: Pascal Nasahl (cherry picked from commit 65178123cb6d5415cbffd1552dafc7dffd704f9c) --- sw/device/lib/crypto/impl/ecc_p256.c | 54 +++++++++++++++++++++------- sw/device/lib/crypto/impl/ecc_p384.c | 54 +++++++++++++++++++++------- 2 files changed, 82 insertions(+), 26 deletions(-) diff --git a/sw/device/lib/crypto/impl/ecc_p256.c b/sw/device/lib/crypto/impl/ecc_p256.c index 3d7b7ddceeae1..d6299f63b0070 100644 --- a/sw/device/lib/crypto/impl/ecc_p256.c +++ b/sw/device/lib/crypto/impl/ecc_p256.c @@ -208,8 +208,8 @@ static status_t internal_p256_keygen_finalize( hardened_memshred(private_key->keyblob, keyblob_num_words(private_key->config)); - p256_masked_scalar_t *sk = (p256_masked_scalar_t *)private_key->keyblob; - HARDENED_TRY(p256_keygen_finalize(sk, pk)); + HARDENED_TRY( + p256_keygen_finalize((p256_masked_scalar_t *)private_key->keyblob, pk)); } else { return OTCRYPTO_BAD_ARGS; } @@ -277,17 +277,26 @@ otcrypto_status_t otcrypto_ecdsa_p256_sign_async_start( if (launder32(private_key->config.hw_backed) == kHardenedBoolFalse) { // Start the asynchronous signature-generation routine. HARDENED_CHECK_EQ(private_key->config.hw_backed, kHardenedBoolFalse); - p256_masked_scalar_t *sk = (p256_masked_scalar_t *)private_key->keyblob; - return p256_ecdsa_sign_start(message_digest.data, sk); + HARDENED_TRY(p256_ecdsa_sign_start( + message_digest.data, (p256_masked_scalar_t *)private_key->keyblob)); } else if (launder32(private_key->config.hw_backed) == kHardenedBoolTrue) { // Load the key and start in sideloaded-key mode. HARDENED_CHECK_EQ(private_key->config.hw_backed, kHardenedBoolTrue); HARDENED_TRY(keyblob_sideload_key_otbn(private_key)); - return p256_ecdsa_sideload_sign_start(message_digest.data); + HARDENED_TRY(p256_ecdsa_sideload_sign_start(message_digest.data)); + } else { + // Invalid value for private_key->hw_backed. + return OTCRYPTO_BAD_ARGS; } - // Invalid value for private_key->hw_backed. - return OTCRYPTO_BAD_ARGS; + // To detect forgeries of the pointer to the private key that we have passed + // to the ECC implementation, check again its integrity. If the pointer would + // have been tampered with between the first integrity check we did when + // entering the CryptoLib and here, we would detect this now. + HARDENED_CHECK_EQ(integrity_blinded_key_check(private_key), + kHardenedBoolTrue); + + return OTCRYPTO_OK; } /** @@ -371,7 +380,15 @@ otcrypto_status_t otcrypto_ecdsa_p256_verify_async_start( p256_ecdsa_signature_t *sig = (p256_ecdsa_signature_t *)signature.data; // Start the asynchronous signature-verification routine. - return p256_ecdsa_verify_start(sig, message_digest.data, pk); + HARDENED_TRY(p256_ecdsa_verify_start(sig, message_digest.data, pk)); + + // To detect forgeries of the pointer to the public key that we have passed + // to the ECC implementation, check again its integrity. If the pointer would + // have been tampered with between the first integrity check we did when + // entering the CryptoLib and here, we would detect this now. + HARDENED_CHECK_EQ(integrity_unblinded_key_check(public_key), + kHardenedBoolTrue); + return OTCRYPTO_OK; } otcrypto_status_t otcrypto_ecdsa_p256_verify_async_finalize( @@ -458,15 +475,26 @@ otcrypto_status_t otcrypto_ecdh_p256_async_start( if (launder32(private_key->config.hw_backed) == kHardenedBoolTrue) { HARDENED_CHECK_EQ(private_key->config.hw_backed, kHardenedBoolTrue); HARDENED_TRY(keyblob_sideload_key_otbn(private_key)); - return p256_sideload_ecdh_start(pk); + HARDENED_TRY(p256_sideload_ecdh_start(pk)); } else if (launder32(private_key->config.hw_backed) == kHardenedBoolFalse) { HARDENED_CHECK_EQ(private_key->config.hw_backed, kHardenedBoolFalse); - p256_masked_scalar_t *sk = (p256_masked_scalar_t *)private_key->keyblob; - return p256_ecdh_start(sk, pk); + HARDENED_TRY( + p256_ecdh_start((p256_masked_scalar_t *)private_key->keyblob, pk)); + } else { + // Invalid value for `hw_backed`. + return OTCRYPTO_BAD_ARGS; } - // Invalid value for `hw_backed`. - return OTCRYPTO_BAD_ARGS; + // To detect forgeries of the pointer to the public key that we have passed + // to the ECC implementation, check again its integrity. If the pointer would + // have been tampered with between the first integrity check we did when + // entering the CryptoLib and here, we would detect this now. + HARDENED_CHECK_EQ(integrity_blinded_key_check(private_key), + kHardenedBoolTrue); + HARDENED_CHECK_EQ(integrity_unblinded_key_check(public_key), + kHardenedBoolTrue); + + return OTCRYPTO_OK; } otcrypto_status_t otcrypto_ecdh_p256_async_finalize( diff --git a/sw/device/lib/crypto/impl/ecc_p384.c b/sw/device/lib/crypto/impl/ecc_p384.c index 09e4e0cfc35af..f762183ac775d 100644 --- a/sw/device/lib/crypto/impl/ecc_p384.c +++ b/sw/device/lib/crypto/impl/ecc_p384.c @@ -212,8 +212,8 @@ static status_t internal_p384_keygen_finalize( // occurs after this point then the keys would be unrecoverable. This should // be the last potentially error-causing line before returning to the // caller. - p384_masked_scalar_t *sk = (p384_masked_scalar_t *)private_key->keyblob; - HARDENED_TRY(p384_keygen_finalize(sk, pk)); + HARDENED_TRY( + p384_keygen_finalize((p384_masked_scalar_t *)private_key->keyblob, pk)); } else { return OTCRYPTO_BAD_ARGS; } @@ -281,17 +281,26 @@ otcrypto_status_t otcrypto_ecdsa_p384_sign_async_start( if (launder32(private_key->config.hw_backed) == kHardenedBoolFalse) { // Start the asynchronous signature-generation routine. HARDENED_CHECK_EQ(private_key->config.hw_backed, kHardenedBoolFalse); - p384_masked_scalar_t *sk = (p384_masked_scalar_t *)private_key->keyblob; - return p384_ecdsa_sign_start(message_digest.data, sk); + HARDENED_TRY(p384_ecdsa_sign_start( + message_digest.data, (p384_masked_scalar_t *)private_key->keyblob)); } else if (launder32(private_key->config.hw_backed) == kHardenedBoolTrue) { // Load the key and start in sideloaded-key mode. HARDENED_CHECK_EQ(private_key->config.hw_backed, kHardenedBoolTrue); HARDENED_TRY(keyblob_sideload_key_otbn(private_key)); - return p384_ecdsa_sideload_sign_start(message_digest.data); + HARDENED_TRY(p384_ecdsa_sideload_sign_start(message_digest.data)); + } else { + // Invalid value for private_key->hw_backed. + return OTCRYPTO_BAD_ARGS; } - // Invalid value for private_key->hw_backed. - return OTCRYPTO_BAD_ARGS; + // To detect forgeries of the pointer to the private key that we have passed + // to the ECC implementation, check again its integrity. If the pointer would + // have been tampered with between the first integrity check we did when + // entering the CryptoLib and here, we would detect this now. + HARDENED_CHECK_EQ(integrity_blinded_key_check(private_key), + kHardenedBoolTrue); + + return OTCRYPTO_OK; } /** @@ -374,7 +383,15 @@ otcrypto_status_t otcrypto_ecdsa_p384_verify_async_start( p384_ecdsa_signature_t *sig = (p384_ecdsa_signature_t *)signature.data; // Start the asynchronous signature-verification routine. - return p384_ecdsa_verify_start(sig, message_digest.data, pk); + HARDENED_TRY(p384_ecdsa_verify_start(sig, message_digest.data, pk)); + + // To detect forgeries of the pointer to the public key that we have passed + // to the ECC implementation, check again its integrity. If the pointer would + // have been tampered with between the first integrity check we did when + // entering the CryptoLib and here, we would detect this now. + HARDENED_CHECK_EQ(integrity_unblinded_key_check(public_key), + kHardenedBoolTrue); + return OTCRYPTO_OK; } otcrypto_status_t otcrypto_ecdsa_p384_verify_async_finalize( @@ -461,15 +478,26 @@ otcrypto_status_t otcrypto_ecdh_p384_async_start( if (launder32(private_key->config.hw_backed) == kHardenedBoolTrue) { HARDENED_CHECK_EQ(private_key->config.hw_backed, kHardenedBoolTrue); HARDENED_TRY(keyblob_sideload_key_otbn(private_key)); - return p384_sideload_ecdh_start(pk); + HARDENED_TRY(p384_sideload_ecdh_start(pk)); } else if (launder32(private_key->config.hw_backed) == kHardenedBoolFalse) { HARDENED_CHECK_EQ(private_key->config.hw_backed, kHardenedBoolFalse); - p384_masked_scalar_t *sk = (p384_masked_scalar_t *)private_key->keyblob; - return p384_ecdh_start(sk, pk); + HARDENED_TRY( + p384_ecdh_start((p384_masked_scalar_t *)private_key->keyblob, pk)); + } else { + // Invalid value for `hw_backed`. + return OTCRYPTO_BAD_ARGS; } - // Invalid value for `hw_backed`. - return OTCRYPTO_BAD_ARGS; + // To detect forgeries of the pointers to the public and private key that we + // have passed to the ECC implementation, check again their integrity. If the + // pointers would have been tampered with between the first integrity check we + // did when entering the CryptoLib and here, we would detect this now. + HARDENED_CHECK_EQ(integrity_blinded_key_check(private_key), + kHardenedBoolTrue); + HARDENED_CHECK_EQ(integrity_unblinded_key_check(public_key), + kHardenedBoolTrue); + + return OTCRYPTO_OK; } otcrypto_status_t otcrypto_ecdh_p384_async_finalize( From 88aa7638dbcec9267d3f529aa153e9f9a02d29e2 Mon Sep 17 00:00:00 2001 From: Pascal Nasahl Date: Fri, 5 Sep 2025 13:27:28 +0200 Subject: [PATCH 29/51] [crypto] Check return value of HMAC functions Some functions in the HMAC driver are security-sensitve. Use `HARDEND_TRY()` to check the return value of those functions. Signed-off-by: Pascal Nasahl (cherry picked from commit 8a6efc37ad925f3ea8469ec78b19ad4af9000b38) --- sw/device/lib/crypto/drivers/hmac.c | 53 ++++++++++++++++++----------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/sw/device/lib/crypto/drivers/hmac.c b/sw/device/lib/crypto/drivers/hmac.c index 3684edb02ed1a..0cc6af14d1a69 100644 --- a/sw/device/lib/crypto/drivers/hmac.c +++ b/sw/device/lib/crypto/drivers/hmac.c @@ -155,15 +155,19 @@ static status_t hmac_idle_wait(void) { * * It also clears the internal state of HMAC HWIP by overwriting sensitive * values with 1s. + * + * @return Result of the operation. */ -static void clear(void) { +static status_t clear(void) { // Do not clear the config yet, we just need to deassert sha_en, see #23014. uint32_t cfg = abs_mmio_read32(kHmacBaseAddr + HMAC_CFG_REG_OFFSET); cfg = bitfield_bit32_write(cfg, HMAC_CFG_SHA_EN_BIT, false); abs_mmio_write32(kHmacBaseAddr + HMAC_CFG_REG_OFFSET, cfg); - // TODO(#23191): Use a random value from EDN to wipe. - abs_mmio_write32(kHmacBaseAddr + HMAC_WIPE_SECRET_REG_OFFSET, UINT32_MAX); + // Use a random value from EDN to wipe HMAC. + abs_mmio_write32(kHmacBaseAddr + HMAC_WIPE_SECRET_REG_OFFSET, + (uint32_t)ibex_rnd32_read()); + return OTCRYPTO_OK; } /** @@ -179,10 +183,13 @@ static void clear(void) { * * @param key The buffer that points to the key. * @param key_wordlen The length of the key in words. + * @return Result of the operation. */ -static void key_write(const uint32_t *key, size_t key_wordlen) { +static status_t key_write(const uint32_t *key, size_t key_wordlen) { uint32_t key_reg = kHmacBaseAddr + HMAC_KEY_0_REG_OFFSET; hardened_memcpy((uint32_t *)key_reg, key, key_wordlen); + + return OTCRYPTO_OK; } /** @@ -208,11 +215,12 @@ static void digest_read(uint32_t *digest, size_t digest_wordlen) { * Resume a streaming operation from a saved context. * * @param ctx Context object from which to restore. + * @return Result of the operation. */ -static void context_restore(hmac_ctx_t *ctx) { +static status_t context_restore(hmac_ctx_t *ctx) { // The previous caller should have left it clean, but it doesn't hurt to // clear again. - clear(); + HARDENED_TRY(clear()); // Restore CFG register from `ctx->cfg_reg`. We need to keep `sha_en` low // until context is restored, see #23014. @@ -221,7 +229,7 @@ static void context_restore(hmac_ctx_t *ctx) { // Write to KEY registers for HMAC operations. If the operation is SHA-2, // `key_wordlen` is set to 0 during `ctx` initialization. - key_write(ctx->key, ctx->key_wordlen); + HARDENED_TRY(key_write(ctx->key, ctx->key_wordlen)); uint32_t cmd = HMAC_CMD_REG_RESVAL; // Decide if we need to invoke `start` or `continue` command. @@ -254,6 +262,8 @@ static void context_restore(hmac_ctx_t *ctx) { // Now we can finally write the command to the register to actually issue // `start` or `continue`. abs_mmio_write32(kHmacBaseAddr + HMAC_CMD_REG_OFFSET, cmd); + + return OTCRYPTO_OK; } /** @@ -280,8 +290,9 @@ static void context_save(hmac_ctx_t *ctx) { * * @param message The incoming message buffer to be fed into HMAC_FIFO. * @param message_len The length of `message` in bytes. + * @return Result of the operation. */ -static void msg_fifo_write(const uint8_t *message, size_t message_len) { +static status_t msg_fifo_write(const uint8_t *message, size_t message_len) { // TODO(#23191): Should we handle backpressure here? // Begin by writing a one byte at a time until the data is aligned. size_t i = 0; @@ -304,6 +315,8 @@ static void msg_fifo_write(const uint8_t *message, size_t message_len) { } // Check that the loops ran for the correct number of iterations. HARDENED_CHECK_EQ(i, message_len); + + return OTCRYPTO_OK; } /** @@ -406,7 +419,7 @@ static status_t oneshot(const uint32_t cfg, const uint32_t *key, abs_mmio_write32(kHmacBaseAddr + HMAC_CFG_REG_OFFSET, cfg); // Write the key (no-op if the key length is 0, e.g. for hashing). - key_write(key, key_wordlen); + HARDENED_TRY(key_write(key, key_wordlen)); // Read back the HMAC configuration and compare to the expected configuration. HARDENED_CHECK_EQ(abs_mmio_read32(kHmacBaseAddr + HMAC_CFG_REG_OFFSET), @@ -418,7 +431,7 @@ static status_t oneshot(const uint32_t cfg, const uint32_t *key, abs_mmio_write32(kHmacBaseAddr + HMAC_CMD_REG_OFFSET, cmd); // Write the message. - msg_fifo_write(msg, msg_len); + HARDENED_TRY(msg_fifo_write(msg, msg_len)); // Send the PROCESS command. cmd = bitfield_bit32_write(HMAC_CMD_REG_RESVAL, HMAC_CMD_HASH_PROCESS_BIT, 1); @@ -428,7 +441,7 @@ static status_t oneshot(const uint32_t cfg, const uint32_t *key, HARDENED_TRY(hmac_idle_wait()); digest_read(digest, digest_wordlen); - clear(); + HARDENED_TRY(clear()); return OTCRYPTO_OK; } @@ -692,12 +705,12 @@ status_t hmac_update(hmac_ctx_t *ctx, const uint8_t *data, size_t len) { // Retore context will restore the context and also hit start or continue // button as necessary. - context_restore(ctx); + HARDENED_TRY(context_restore(ctx)); // Write the partial block, then the new bytes. - msg_fifo_write((unsigned char *)ctx->partial_block, - ctx->partial_block_bytelen); - msg_fifo_write(data, len - leftover_len); + HARDENED_TRY(msg_fifo_write((unsigned char *)ctx->partial_block, + ctx->partial_block_bytelen)); + HARDENED_TRY(msg_fifo_write(data, len - leftover_len)); /* * TODO should be uncommented once the issue #24767 will be solved in the HW @@ -724,18 +737,18 @@ status_t hmac_update(hmac_ctx_t *ctx, const uint8_t *data, size_t len) { ctx->partial_block_bytelen = leftover_len; // Clean up. - clear(); + HARDENED_TRY(clear()); return OTCRYPTO_OK; } status_t hmac_final(hmac_ctx_t *ctx, uint32_t *digest) { // Retore context will restore the context and also hit start or continue // button as necessary. - context_restore(ctx); + HARDENED_TRY(context_restore(ctx)); // Feed the final leftover bytes to HMAC HWIP. - msg_fifo_write((unsigned char *)ctx->partial_block, - ctx->partial_block_bytelen); + HARDENED_TRY(msg_fifo_write((unsigned char *)ctx->partial_block, + ctx->partial_block_bytelen)); // All message bytes are fed, now hit the process button. uint32_t cmd = @@ -749,6 +762,6 @@ status_t hmac_final(hmac_ctx_t *ctx, uint32_t *digest) { // TODO(#23191): Destroy sensitive values in the ctx object. // Clean up. - clear(); + HARDENED_TRY(clear()); return OTCRYPTO_OK; } From e37115a5e8a3f4a164003f9c303cc7865ffd3141 Mon Sep 17 00:00:00 2001 From: Pascal Nasahl Date: Thu, 4 Sep 2025 17:37:09 +0200 Subject: [PATCH 30/51] [crypto] Return `status_t` for hardened_* functions As we also want to use CFI for those functions, return `status_t`. Signed-off-by: Pascal Nasahl (cherry picked from commit 757b303c3aabd01f878b6b6e413b8c5f926a3917) --- sw/device/lib/base/BUILD | 1 + sw/device/lib/base/hardened_memory.c | 16 +++++++++++----- sw/device/lib/base/hardened_memory.h | 14 +++++++++----- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/sw/device/lib/base/BUILD b/sw/device/lib/base/BUILD index ac33ec252f4aa..77a9007717405 100644 --- a/sw/device/lib/base/BUILD +++ b/sw/device/lib/base/BUILD @@ -286,6 +286,7 @@ cc_library( ":macros", ":memory", ":random_order", + "//sw/device/lib/crypto/impl:status", ], ) diff --git a/sw/device/lib/base/hardened_memory.c b/sw/device/lib/base/hardened_memory.c index 91397065b6ef2..c8965ef793e55 100644 --- a/sw/device/lib/base/hardened_memory.c +++ b/sw/device/lib/base/hardened_memory.c @@ -10,8 +10,8 @@ // NOTE: The three hardened_mem* functions have similar contents, but the parts // that are shared between them are commented only in `memcpy()`. -void hardened_memcpy(uint32_t *restrict dest, const uint32_t *restrict src, - size_t word_len) { +status_t hardened_memcpy(uint32_t *restrict dest, const uint32_t *restrict src, + size_t word_len) { random_order_t order; random_order_init(&order, word_len); @@ -77,9 +77,11 @@ void hardened_memcpy(uint32_t *restrict dest, const uint32_t *restrict src, } RANDOM_ORDER_HARDENED_CHECK_DONE(order); HARDENED_CHECK_EQ(count, expected_count); + + return OTCRYPTO_OK; } -void hardened_memshred(uint32_t *dest, size_t word_len) { +status_t hardened_memshred(uint32_t *dest, size_t word_len) { random_order_t order; random_order_init(&order, word_len); @@ -108,6 +110,8 @@ void hardened_memshred(uint32_t *dest, size_t word_len) { RANDOM_ORDER_HARDENED_CHECK_DONE(order); HARDENED_CHECK_EQ(count, expected_count); + + return OTCRYPTO_OK; } hardened_bool_t hardened_memeq(const uint32_t *lhs, const uint32_t *rhs, @@ -178,8 +182,8 @@ hardened_bool_t hardened_memeq(const uint32_t *lhs, const uint32_t *rhs, return kHardenedBoolFalse; } -void hardened_xor(uint32_t *restrict x, const uint32_t *restrict y, - size_t word_len) { +status_t hardened_xor(uint32_t *restrict x, const uint32_t *restrict y, + size_t word_len) { // Generate a random ordering. random_order_t order; random_order_init(&order, word_len); @@ -224,4 +228,6 @@ void hardened_xor(uint32_t *restrict x, const uint32_t *restrict y, } RANDOM_ORDER_HARDENED_CHECK_DONE(order); HARDENED_CHECK_EQ(count, expected_count); + + return OTCRYPTO_OK; } diff --git a/sw/device/lib/base/hardened_memory.h b/sw/device/lib/base/hardened_memory.h index c527da48c94fb..ae1ad3b9bcae5 100644 --- a/sw/device/lib/base/hardened_memory.h +++ b/sw/device/lib/base/hardened_memory.h @@ -15,6 +15,7 @@ #include "sw/device/lib/base/hardened.h" #include "sw/device/lib/base/macros.h" +#include "sw/device/lib/crypto/impl/status.h" #ifdef __cplusplus extern "C" { @@ -43,9 +44,10 @@ extern uint32_t hardened_memshred_random_word(void); * @param dest The destination of the copy. * @param src The source of the copy. * @param word_len The number of words to copy. + * @return OK or error. */ -void hardened_memcpy(uint32_t *OT_RESTRICT dest, - const uint32_t *OT_RESTRICT src, size_t word_len); +status_t hardened_memcpy(uint32_t *OT_RESTRICT dest, + const uint32_t *OT_RESTRICT src, size_t word_len); /** * Fills a 32-bit aligned region of memory with random data. @@ -63,8 +65,9 @@ void hardened_memcpy(uint32_t *OT_RESTRICT dest, * * @param dest The destination of the set. * @param word_len The number of words to write. + * @return OK or error. */ -void hardened_memshred(uint32_t *dest, size_t word_len); +status_t hardened_memshred(uint32_t *dest, size_t word_len); /** * Compare two potentially-overlapping 32-bit aligned regions of memory for @@ -100,9 +103,10 @@ hardened_bool_t hardened_memeq(const uint32_t *lhs, const uint32_t *rhs, * @param[in,out] x Pointer to the first operand (modified in-place). * @param y Pointer to the second operand. * @param word_len Length in words of each operand. + * @return OK or error. */ -void hardened_xor(uint32_t *OT_RESTRICT x, const uint32_t *OT_RESTRICT y, - size_t word_len); +status_t hardened_xor(uint32_t *OT_RESTRICT x, const uint32_t *OT_RESTRICT y, + size_t word_len); #ifdef __cplusplus } // extern "C" From d826fc701290fbbc20ed18e7148ce30241324c00 Mon Sep 17 00:00:00 2001 From: Pascal Nasahl Date: Fri, 5 Sep 2025 08:15:31 +0200 Subject: [PATCH 31/51] [crypto] Use `HARDENED_TRY()` for `hardenend*` functions As we want to have CFI for the `hardened*` functions, wrap them into the `HARDENED_TRY()` macro. Signed-off-by: Pascal Nasahl (cherry picked from commit da53d4765e14f4a2d5b1bc0cdc43d9fccff1cc34) --- sw/device/lib/crypto/drivers/hmac.c | 34 ++++++++-- sw/device/lib/crypto/drivers/keymgr.c | 10 +-- sw/device/lib/crypto/impl/aes.c | 8 +-- sw/device/lib/crypto/impl/aes_gcm.c | 48 +++++++------- sw/device/lib/crypto/impl/aes_gcm/aes_gcm.c | 4 +- sw/device/lib/crypto/impl/aes_kwp/aes_kwp.c | 31 +++++---- sw/device/lib/crypto/impl/drbg.c | 8 ++- sw/device/lib/crypto/impl/ecc_p256.c | 8 +-- sw/device/lib/crypto/impl/ecc_p384.c | 8 +-- sw/device/lib/crypto/impl/hmac.c | 2 +- sw/device/lib/crypto/impl/key_transport.c | 32 ++++++---- sw/device/lib/crypto/impl/keyblob.c | 15 +++-- sw/device/lib/crypto/impl/kmac_kdf.c | 5 +- sw/device/lib/crypto/impl/rsa.c | 35 +++++----- .../lib/crypto/impl/rsa/rsa_encryption.c | 14 ++-- sw/device/lib/crypto/impl/rsa/rsa_keygen.c | 16 ++--- sw/device/lib/crypto/impl/rsa/rsa_padding.c | 10 +-- sw/device/lib/crypto/impl/sha2/sha256.c | 39 +++++++---- sw/device/lib/crypto/impl/sha2/sha256.h | 2 +- sw/device/lib/crypto/impl/sha2/sha512.c | 64 ++++++++++++------- sw/device/lib/crypto/impl/sha2/sha512.h | 4 +- 21 files changed, 238 insertions(+), 159 deletions(-) diff --git a/sw/device/lib/crypto/drivers/hmac.c b/sw/device/lib/crypto/drivers/hmac.c index 0cc6af14d1a69..2f03901a2fb40 100644 --- a/sw/device/lib/crypto/drivers/hmac.c +++ b/sw/device/lib/crypto/drivers/hmac.c @@ -183,13 +183,11 @@ static status_t clear(void) { * * @param key The buffer that points to the key. * @param key_wordlen The length of the key in words. - * @return Result of the operation. + * @return OK or error. */ static status_t key_write(const uint32_t *key, size_t key_wordlen) { uint32_t key_reg = kHmacBaseAddr + HMAC_KEY_0_REG_OFFSET; - hardened_memcpy((uint32_t *)key_reg, key, key_wordlen); - - return OTCRYPTO_OK; + return hardened_memcpy((uint32_t *)key_reg, key, key_wordlen); } /** @@ -284,6 +282,34 @@ static void context_save(hmac_ctx_t *ctx) { abs_mmio_read32(kHmacBaseAddr + HMAC_MSG_LENGTH_UPPER_REG_OFFSET); } +/** + * Wipes the ctx struct by replacing sensitive data with randomness from the + * Ibex EDN interface. Non-sensitive variables are zeroized. + * + * @param[out] ctx Initialized context object for SHA2/HMAC-SHA2 operations. + * @return Result of the operation. + */ +static status_t hmac_context_wipe(hmac_ctx_t *ctx) { + // Ensure entropy complex is initialized. + HARDENED_TRY(entropy_complex_check()); + + // Randomize sensitive data. + HARDENED_TRY(hardened_memshred(ctx->key, kHmacMaxBlockWords)); + HARDENED_TRY(hardened_memshred(ctx->H, kHmacMaxDigestWords)); + HARDENED_TRY(hardened_memshred((uint32_t *)(ctx->partial_block), + kHmacMaxBlockBytes / sizeof(uint32_t))); + // Zero the remaining ctx fields. + ctx->cfg_reg = 0; + ctx->key_wordlen = 0; + ctx->msg_block_wordlen = 0; + ctx->digest_wordlen = 0; + ctx->lower = 0; + ctx->upper = 0; + ctx->partial_block_bytelen = 0; + + return OTCRYPTO_OK; +} + /** * Write given byte array into the `MSG_FIFO`. This function should only be * called when HMAC HWIP is already running and expecting further message bytes. diff --git a/sw/device/lib/crypto/drivers/keymgr.c b/sw/device/lib/crypto/drivers/keymgr.c index 5032829d96373..5e7869259d0d9 100644 --- a/sw/device/lib/crypto/drivers/keymgr.c +++ b/sw/device/lib/crypto/drivers/keymgr.c @@ -158,10 +158,12 @@ status_t keymgr_generate_key_sw(keymgr_diversification_t diversification, // memcpy. uint32_t share0 = kBaseAddr + KEYMGR_SW_SHARE0_OUTPUT_0_REG_OFFSET; uint32_t share1 = kBaseAddr + KEYMGR_SW_SHARE1_OUTPUT_0_REG_OFFSET; - hardened_memshred(key->share0, kKeymgrOutputShareNumWords); - hardened_memcpy(key->share0, (uint32_t *)share0, kKeymgrOutputShareNumWords); - hardened_memshred(key->share1, kKeymgrOutputShareNumWords); - hardened_memcpy(key->share1, (uint32_t *)share1, kKeymgrOutputShareNumWords); + HARDENED_TRY(hardened_memshred(key->share0, kKeymgrOutputShareNumWords)); + HARDENED_TRY(hardened_memcpy(key->share0, (uint32_t *)share0, + kKeymgrOutputShareNumWords)); + HARDENED_TRY(hardened_memshred(key->share1, kKeymgrOutputShareNumWords)); + HARDENED_TRY(hardened_memcpy(key->share1, (uint32_t *)share1, + kKeymgrOutputShareNumWords)); return OTCRYPTO_OK; } diff --git a/sw/device/lib/crypto/impl/aes.c b/sw/device/lib/crypto/impl/aes.c index 7e1aaa60b87ca..219caaaa52045 100644 --- a/sw/device/lib/crypto/impl/aes.c +++ b/sw/device/lib/crypto/impl/aes.c @@ -223,7 +223,7 @@ static status_t get_block(otcrypto_const_byte_buf_t input, HARDENED_CHECK_LE(index, num_full_blocks + 1); // Randomize the destination buffer. - hardened_memshred(block->data, ARRAYSIZE(block->data)); + HARDENED_TRY(hardened_memshred(block->data, ARRAYSIZE(block->data))); if (launder32(index) < num_full_blocks) { HARDENED_CHECK_LT(index, num_full_blocks); @@ -318,7 +318,7 @@ static otcrypto_status_t otcrypto_aes_impl( return OTCRYPTO_BAD_ARGS; } HARDENED_CHECK_EQ(iv.len, kAesBlockNumWords); - hardened_memcpy(aes_iv.data, iv.data, kAesBlockNumWords); + HARDENED_TRY(hardened_memcpy(aes_iv.data, iv.data, kAesBlockNumWords)); } // Parse the AES key. @@ -380,7 +380,7 @@ static otcrypto_status_t otcrypto_aes_impl( // output buffer. for (i = block_offset; launder32(i) < input_nblocks; ++i) { HARDENED_TRY(get_block(cipher_input, aes_padding, i, &block_in)); - hardened_memshred(block_out.data, ARRAYSIZE(block_out.data)); + HARDENED_TRY(hardened_memshred(block_out.data, ARRAYSIZE(block_out.data))); HARDENED_TRY(aes_update(&block_out, &block_in)); // TODO(#17711) Change to `hardened_memcpy`. memcpy(&cipher_output.data[(i - block_offset) * kAesBlockNumBytes], @@ -405,7 +405,7 @@ static otcrypto_status_t otcrypto_aes_impl( HARDENED_TRY(aes_end(NULL)); } else { HARDENED_TRY(aes_end(&aes_iv)); - hardened_memcpy(iv.data, aes_iv.data, kAesBlockNumWords); + HARDENED_TRY(hardened_memcpy(iv.data, aes_iv.data, kAesBlockNumWords)); } // In case the key was sideloaded, clear it. diff --git a/sw/device/lib/crypto/impl/aes_gcm.c b/sw/device/lib/crypto/impl/aes_gcm.c index 0ef12067b7a94..b11422e2f640f 100644 --- a/sw/device/lib/crypto/impl/aes_gcm.c +++ b/sw/device/lib/crypto/impl/aes_gcm.c @@ -47,11 +47,12 @@ enum { * * @param internal_ctx Internal context object to save. * @param[out] api_ctx Resulting API-facing context object. + * @return Result of the operation. */ -static inline void gcm_context_save(aes_gcm_context_t *internal_ctx, - otcrypto_aes_gcm_context_t *api_ctx) { - hardened_memcpy(api_ctx->data, (uint32_t *)internal_ctx, - kAesGcmContextNumWords); +static inline status_t gcm_context_save(aes_gcm_context_t *internal_ctx, + otcrypto_aes_gcm_context_t *api_ctx) { + return hardened_memcpy(api_ctx->data, (uint32_t *)internal_ctx, + kAesGcmContextNumWords); } /** @@ -59,11 +60,12 @@ static inline void gcm_context_save(aes_gcm_context_t *internal_ctx, * * @param api_ctx API-facing context object to restore from. * @param[out] internal_ctx Resulting internal context object. + * @return Result of the operation. */ -static inline void gcm_context_restore(otcrypto_aes_gcm_context_t *api_ctx, - aes_gcm_context_t *internal_ctx) { - hardened_memcpy((uint32_t *)internal_ctx, api_ctx->data, - kAesGcmContextNumWords); +static inline status_t gcm_context_restore(otcrypto_aes_gcm_context_t *api_ctx, + aes_gcm_context_t *internal_ctx) { + return hardened_memcpy((uint32_t *)internal_ctx, api_ctx->data, + kAesGcmContextNumWords); } /** @@ -80,13 +82,13 @@ status_t gcm_remask_key(aes_gcm_context_t *internal_ctx) { // Generate a fresh mask the size of one share. uint32_t mask[internal_ctx->key.key_len]; - hardened_memshred(mask, internal_ctx->key.key_len); + HARDENED_TRY(hardened_memshred(mask, internal_ctx->key.key_len)); // XOR each share with the mask. - hardened_xor((uint32_t *)internal_ctx->key.key_shares[0], mask, - internal_ctx->key.key_len); - hardened_xor((uint32_t *)internal_ctx->key.key_shares[1], mask, - internal_ctx->key.key_len); + HARDENED_TRY(hardened_xor((uint32_t *)internal_ctx->key.key_shares[0], mask, + internal_ctx->key.key_len)); + HARDENED_TRY(hardened_xor((uint32_t *)internal_ctx->key.key_shares[1], mask, + internal_ctx->key.key_len)); // Update the checksum. internal_ctx->key.checksum = aes_key_integrity_checksum(&internal_ctx->key); } else { @@ -381,7 +383,7 @@ otcrypto_status_t otcrypto_aes_gcm_encrypt_init( HARDENED_TRY(aes_gcm_encrypt_init(aes_key, iv.len, iv.data, &internal_ctx)); // Save the context and clear the key if needed. - gcm_context_save(&internal_ctx, ctx); + HARDENED_TRY(gcm_context_save(&internal_ctx, ctx)); HARDENED_TRY(clear_key_if_sideloaded(internal_ctx.key)); return OTCRYPTO_OK; } @@ -406,7 +408,7 @@ otcrypto_status_t otcrypto_aes_gcm_decrypt_init( HARDENED_TRY(aes_gcm_decrypt_init(aes_key, iv.len, iv.data, &internal_ctx)); // Save the context and clear the key if needed. - gcm_context_save(&internal_ctx, ctx); + HARDENED_TRY(gcm_context_save(&internal_ctx, ctx)); HARDENED_TRY(clear_key_if_sideloaded(internal_ctx.key)); return OTCRYPTO_OK; } @@ -427,14 +429,14 @@ otcrypto_status_t otcrypto_aes_gcm_update_aad(otcrypto_aes_gcm_context_t *ctx, // Restore the AES-GCM context object and load the key if needed. aes_gcm_context_t internal_ctx; - gcm_context_restore(ctx, &internal_ctx); + HARDENED_TRY(gcm_context_restore(ctx, &internal_ctx)); HARDENED_TRY(load_key_if_sideloaded(internal_ctx.key)); // Call the internal update operation. HARDENED_TRY(aes_gcm_update_aad(&internal_ctx, aad.len, aad.data)); // Save the context and clear the key if needed. - gcm_context_save(&internal_ctx, ctx); + HARDENED_TRY(gcm_context_save(&internal_ctx, ctx)); HARDENED_TRY(clear_key_if_sideloaded(internal_ctx.key)); return OTCRYPTO_OK; } @@ -458,7 +460,7 @@ otcrypto_status_t otcrypto_aes_gcm_update_encrypted_data( // Restore the AES-GCM context object and load the key if needed. aes_gcm_context_t internal_ctx; - gcm_context_restore(ctx, &internal_ctx); + HARDENED_TRY(gcm_context_restore(ctx, &internal_ctx)); HARDENED_TRY(load_key_if_sideloaded(internal_ctx.key)); // Remask the key if it is not sideloaded. HARDENED_TRY(gcm_remask_key(&internal_ctx)); @@ -481,7 +483,7 @@ otcrypto_status_t otcrypto_aes_gcm_update_encrypted_data( &internal_ctx, input.len, input.data, output_bytes_written, output.data)); // Save the context and clear the key if needed. - gcm_context_save(&internal_ctx, ctx); + HARDENED_TRY(gcm_context_save(&internal_ctx, ctx)); HARDENED_TRY(clear_key_if_sideloaded(internal_ctx.key)); return OTCRYPTO_OK; } @@ -507,7 +509,7 @@ otcrypto_status_t otcrypto_aes_gcm_encrypt_final( // Restore the AES-GCM context object and load the key if needed. aes_gcm_context_t internal_ctx; - gcm_context_restore(ctx, &internal_ctx); + HARDENED_TRY(gcm_context_restore(ctx, &internal_ctx)); HARDENED_TRY(load_key_if_sideloaded(internal_ctx.key)); // Remask the key if it is not sideloaded. HARDENED_TRY(gcm_remask_key(&internal_ctx)); @@ -525,7 +527,7 @@ otcrypto_status_t otcrypto_aes_gcm_encrypt_final( ciphertext.data)); // Clear the context and the key if needed. - hardened_memshred(ctx->data, ARRAYSIZE(ctx->data)); + HARDENED_TRY(hardened_memshred(ctx->data, ARRAYSIZE(ctx->data))); HARDENED_TRY(clear_key_if_sideloaded(internal_ctx.key)); return OTCRYPTO_OK; } @@ -552,7 +554,7 @@ otcrypto_status_t otcrypto_aes_gcm_decrypt_final( // Restore the AES-GCM context object and load the key if needed. aes_gcm_context_t internal_ctx; - gcm_context_restore(ctx, &internal_ctx); + HARDENED_TRY(gcm_context_restore(ctx, &internal_ctx)); HARDENED_TRY(load_key_if_sideloaded(internal_ctx.key)); // Remask the key if it is not sideloaded. HARDENED_TRY(gcm_remask_key(&internal_ctx)); @@ -570,7 +572,7 @@ otcrypto_status_t otcrypto_aes_gcm_decrypt_final( success)); // Clear the context and the key if needed. - hardened_memshred(ctx->data, ARRAYSIZE(ctx->data)); + HARDENED_TRY(hardened_memshred(ctx->data, ARRAYSIZE(ctx->data))); HARDENED_TRY(clear_key_if_sideloaded(internal_ctx.key)); return OTCRYPTO_OK; } diff --git a/sw/device/lib/crypto/impl/aes_gcm/aes_gcm.c b/sw/device/lib/crypto/impl/aes_gcm/aes_gcm.c index 5274c968a3012..61565aaf5a184 100644 --- a/sw/device/lib/crypto/impl/aes_gcm/aes_gcm.c +++ b/sw/device/lib/crypto/impl/aes_gcm/aes_gcm.c @@ -207,7 +207,7 @@ static status_t aes_gcm_counter(const size_t iv_len, const uint32_t *iv, ghash_context_t *ctx, aes_block_t *j0) { if (iv_len == 3) { // If the IV is 96 bits, then J0 = (IV || {0}^31 || 1). - hardened_memcpy(j0->data, iv, iv_len); + HARDENED_TRY(hardened_memcpy(j0->data, iv, iv_len)); // Set the last word to 1 (as a big-endian integer). j0->data[kAesBlockNumWords - 1] = __builtin_bswap32(1); } else if (iv_len == 4) { @@ -271,7 +271,7 @@ static status_t aes_gcm_get_tag(aes_gcm_context_t *ctx, size_t tag_len, // Truncate the tag if needed. NIST requires we take the most significant // bits in big-endian representation, which corresponds to the least // significant bits in Ibex's little-endian representation. - hardened_memcpy(tag, full_tag, tag_len); + HARDENED_TRY(hardened_memcpy(tag, full_tag, tag_len)); return OTCRYPTO_OK; } diff --git a/sw/device/lib/crypto/impl/aes_kwp/aes_kwp.c b/sw/device/lib/crypto/impl/aes_kwp/aes_kwp.c index ccc9a2f353f24..04315ca4c188f 100644 --- a/sw/device/lib/crypto/impl/aes_kwp/aes_kwp.c +++ b/sw/device/lib/crypto/impl/aes_kwp/aes_kwp.c @@ -56,8 +56,9 @@ status_t aes_kwp_wrap(const aes_key_t kek, const uint32_t *plaintext, // Initialize the output buffer with (A || plaintext || padding). size_t plaintext_words = ceil_div(plaintext_len, sizeof(uint32_t)); - hardened_memcpy(ciphertext, block.data, kSemiblockWords); - hardened_memcpy(ciphertext + kSemiblockWords, plaintext, plaintext_words); + HARDENED_TRY(hardened_memcpy(ciphertext, block.data, kSemiblockWords)); + HARDENED_TRY(hardened_memcpy(ciphertext + kSemiblockWords, plaintext, + plaintext_words)); unsigned char *pad_start = ((unsigned char *)ciphertext) + kSemiblockBytes + plaintext_len; memset(pad_start, 0, pad_len); @@ -66,8 +67,9 @@ status_t aes_kwp_wrap(const aes_key_t kek, const uint32_t *plaintext, for (size_t j = 0; j < 6; j++) { for (size_t i = 1; i <= plaintext_semiblocks; i++) { // Copy R[i] into the block (A should already be present). - hardened_memcpy(block.data + kSemiblockWords, - ciphertext + i * kSemiblockWords, kSemiblockWords); + HARDENED_TRY(hardened_memcpy(block.data + kSemiblockWords, + ciphertext + i * kSemiblockWords, + kSemiblockWords)); HARDENED_TRY(aes_update(/*dest=*/NULL, &block)); HARDENED_TRY(aes_update(&block, /*src=*/NULL)); @@ -78,13 +80,14 @@ status_t aes_kwp_wrap(const aes_key_t kek, const uint32_t *plaintext, t++; // Copy the last two words back into R[i]. - hardened_memcpy(ciphertext + i * kSemiblockWords, - block.data + kSemiblockWords, kSemiblockWords); + HARDENED_TRY(hardened_memcpy(ciphertext + i * kSemiblockWords, + block.data + kSemiblockWords, + kSemiblockWords)); } } // Copy A into the first semiblock of the ciphertext. - hardened_memcpy(ciphertext, block.data, kSemiblockWords); + HARDENED_TRY(hardened_memcpy(ciphertext, block.data, kSemiblockWords)); return OTCRYPTO_OK; } @@ -120,7 +123,7 @@ status_t aes_kwp_unwrap(const aes_key_t kek, const uint32_t *ciphertext, // Initialize the working buffer, R. uint32_t r[(ciphertext_semiblocks - 1) * kSemiblockWords]; - hardened_memcpy(r, ciphertext + kSemiblockWords, ARRAYSIZE(r)); + HARDENED_TRY(hardened_memcpy(r, ciphertext + kSemiblockWords, ARRAYSIZE(r))); uint64_t t = 6 * ((uint64_t)ciphertext_semiblocks - 1); for (size_t j = 0; j < 6; j++) { @@ -131,14 +134,16 @@ status_t aes_kwp_unwrap(const aes_key_t kek, const uint32_t *ciphertext, t--; // Copy R[i] into the block (A ^ t should already be present). - hardened_memcpy(block.data + kSemiblockWords, - r + (i - 1) * kSemiblockWords, kSemiblockWords); + HARDENED_TRY(hardened_memcpy(block.data + kSemiblockWords, + r + (i - 1) * kSemiblockWords, + kSemiblockWords)); HARDENED_TRY(aes_update(/*dest=*/NULL, &block)); HARDENED_TRY(aes_update(&block, /*src=*/NULL)); // Copy the last two words back into R[i]. - hardened_memcpy(r + (i - 1) * kSemiblockWords, - block.data + kSemiblockWords, kSemiblockWords); + HARDENED_TRY(hardened_memcpy(r + (i - 1) * kSemiblockWords, + block.data + kSemiblockWords, + kSemiblockWords)); } } @@ -174,7 +179,7 @@ status_t aes_kwp_unwrap(const aes_key_t kek, const uint32_t *ciphertext, // Copy the plaintext into the destination buffer. size_t plaintext_words = ceil_div(plaintext_len, sizeof(uint32_t)); - hardened_memcpy(plaintext, r, plaintext_words); + HARDENED_TRY(hardened_memcpy(plaintext, r, plaintext_words)); // Return success. *success = kHardenedBoolTrue; diff --git a/sw/device/lib/crypto/impl/drbg.c b/sw/device/lib/crypto/impl/drbg.c index 228eb1f1ebe7e..3f8639a461773 100644 --- a/sw/device/lib/crypto/impl/drbg.c +++ b/sw/device/lib/crypto/impl/drbg.c @@ -92,7 +92,8 @@ otcrypto_status_t otcrypto_drbg_instantiate( HARDENED_TRY(entropy_complex_check()); entropy_seed_material_t seed_material; - hardened_memshred(seed_material.data, ARRAYSIZE(seed_material.data)); + HARDENED_TRY( + hardened_memshred(seed_material.data, ARRAYSIZE(seed_material.data))); seed_material_construct(perso_string, &seed_material); HARDENED_TRY(entropy_csrng_uninstantiate()); @@ -111,7 +112,8 @@ otcrypto_status_t otcrypto_drbg_reseed( HARDENED_TRY(entropy_complex_check()); entropy_seed_material_t seed_material; - hardened_memshred(seed_material.data, ARRAYSIZE(seed_material.data)); + HARDENED_TRY( + hardened_memshred(seed_material.data, ARRAYSIZE(seed_material.data))); seed_material_construct(additional_input, &seed_material); return entropy_csrng_reseed(/*disable_trng_input=*/kHardenedBoolFalse, @@ -198,7 +200,7 @@ otcrypto_status_t otcrypto_drbg_generate( HARDENED_TRY(entropy_complex_check()); // Randomize destination buffer. - hardened_memshred(drbg_output.data, drbg_output.len); + HARDENED_TRY(hardened_memshred(drbg_output.data, drbg_output.len)); return generate(/*fips_check=*/kHardenedBoolTrue, additional_input, drbg_output); diff --git a/sw/device/lib/crypto/impl/ecc_p256.c b/sw/device/lib/crypto/impl/ecc_p256.c index d6299f63b0070..a0dd1d3f94564 100644 --- a/sw/device/lib/crypto/impl/ecc_p256.c +++ b/sw/device/lib/crypto/impl/ecc_p256.c @@ -205,8 +205,8 @@ static status_t internal_p256_keygen_finalize( HARDENED_CHECK_EQ(private_key->config.hw_backed, kHardenedBoolFalse); // Randomize the keyblob before writing secret data. - hardened_memshred(private_key->keyblob, - keyblob_num_words(private_key->config)); + HARDENED_TRY(hardened_memshred(private_key->keyblob, + keyblob_num_words(private_key->config))); HARDENED_TRY( p256_keygen_finalize((p256_masked_scalar_t *)private_key->keyblob, pk)); @@ -529,8 +529,8 @@ otcrypto_status_t otcrypto_ecdh_p256_async_finalize( // occurs after this point then the keys would be unrecoverable. This should // be the last potentially error-causing line before returning to the caller. p256_ecdh_shared_key_t ss; - hardened_memshred(ss.share0, ARRAYSIZE(ss.share0)); - hardened_memshred(ss.share1, ARRAYSIZE(ss.share1)); + HARDENED_TRY(hardened_memshred(ss.share0, ARRAYSIZE(ss.share0))); + HARDENED_TRY(hardened_memshred(ss.share1, ARRAYSIZE(ss.share1))); HARDENED_TRY(p256_ecdh_finalize(&ss)); HARDENED_TRY(keyblob_from_shares(ss.share0, ss.share1, shared_secret->config, diff --git a/sw/device/lib/crypto/impl/ecc_p384.c b/sw/device/lib/crypto/impl/ecc_p384.c index f762183ac775d..c3218f8899188 100644 --- a/sw/device/lib/crypto/impl/ecc_p384.c +++ b/sw/device/lib/crypto/impl/ecc_p384.c @@ -205,8 +205,8 @@ static status_t internal_p384_keygen_finalize( HARDENED_CHECK_EQ(private_key->config.hw_backed, kHardenedBoolFalse); // Randomize the keyblob before writing secret data. - hardened_memshred(private_key->keyblob, - keyblob_num_words(private_key->config)); + HARDENED_TRY(hardened_memshred(private_key->keyblob, + keyblob_num_words(private_key->config))); // Note: This operation wipes DMEM after retrieving the keys, so if an error // occurs after this point then the keys would be unrecoverable. This should @@ -532,8 +532,8 @@ otcrypto_status_t otcrypto_ecdh_p384_async_finalize( // occurs after this point then the keys would be unrecoverable. This should // be the last potentially error-causing line before returning to the caller. p384_ecdh_shared_key_t ss; - hardened_memshred(ss.share0, ARRAYSIZE(ss.share0)); - hardened_memshred(ss.share1, ARRAYSIZE(ss.share1)); + HARDENED_TRY(hardened_memshred(ss.share0, ARRAYSIZE(ss.share0))); + HARDENED_TRY(hardened_memshred(ss.share1, ARRAYSIZE(ss.share1))); HARDENED_TRY(p384_ecdh_finalize(&ss)); HARDENED_TRY(keyblob_from_shares(ss.share0, ss.share1, shared_secret->config, diff --git a/sw/device/lib/crypto/impl/hmac.c b/sw/device/lib/crypto/impl/hmac.c index 709404441ae92..b6a7f7d2a2059 100644 --- a/sw/device/lib/crypto/impl/hmac.c +++ b/sw/device/lib/crypto/impl/hmac.c @@ -76,7 +76,7 @@ static status_t key_block_get(const otcrypto_blinded_key_t *key, } else { HARDENED_CHECK_LE(key->config.key_length, key_block_wordlen * sizeof(uint32_t)); - hardened_memcpy(key_block, unmasked_key, unmasked_key_len); + HARDENED_TRY(hardened_memcpy(key_block, unmasked_key, unmasked_key_len)); // If the key size isn't a multiple of the word size, zero the last few // bytes. size_t offset = key->config.key_length % sizeof(uint32_t); diff --git a/sw/device/lib/crypto/impl/key_transport.c b/sw/device/lib/crypto/impl/key_transport.c index dba5d000786da..8f3c6bafcf2a4 100644 --- a/sw/device/lib/crypto/impl/key_transport.c +++ b/sw/device/lib/crypto/impl/key_transport.c @@ -47,8 +47,8 @@ otcrypto_status_t otcrypto_symmetric_keygen( }; // Randomize the memory before writing the shares. - hardened_memshred(share0_buf.data, share0_buf.len); - hardened_memshred(share1_buf.data, share1_buf.len); + HARDENED_TRY(hardened_memshred(share0_buf.data, share0_buf.len)); + HARDENED_TRY(hardened_memshred(share1_buf.data, share1_buf.len)); // Construct an empty buffer for the "additional input" to the DRBG generate // function. @@ -219,12 +219,13 @@ otcrypto_status_t otcrypto_key_wrap(const otcrypto_blinded_key_t *key_to_wrap, uint32_t config_words = sizeof(otcrypto_key_config_t) / sizeof(uint32_t); size_t plaintext_num_words = config_words + 2 + keyblob_words; uint32_t plaintext[plaintext_num_words]; - hardened_memshred(plaintext, ARRAYSIZE(plaintext)); - hardened_memcpy(plaintext, (uint32_t *)&key_to_wrap->config, config_words); + HARDENED_TRY(hardened_memshred(plaintext, ARRAYSIZE(plaintext))); + HARDENED_TRY(hardened_memcpy(plaintext, (uint32_t *)&key_to_wrap->config, + config_words)); plaintext[config_words] = key_to_wrap->checksum; plaintext[config_words + 1] = keyblob_words; - hardened_memcpy(plaintext + config_words + 2, key_to_wrap->keyblob, - keyblob_words); + HARDENED_TRY(hardened_memcpy(plaintext + config_words + 2, + key_to_wrap->keyblob, keyblob_words)); // Wrap the key. return aes_kwp_wrap(kek, plaintext, sizeof(plaintext), wrapped_key.data); @@ -257,7 +258,7 @@ otcrypto_status_t otcrypto_key_unwrap(otcrypto_const_word32_buf_t wrapped_key, // Unwrap the key. uint32_t plaintext[wrapped_key.len]; - hardened_memshred(plaintext, ARRAYSIZE(plaintext)); + HARDENED_TRY(hardened_memshred(plaintext, ARRAYSIZE(plaintext))); HARDENED_TRY(aes_kwp_unwrap(kek, wrapped_key.data, wrapped_key.len * sizeof(uint32_t), success, plaintext)); @@ -273,7 +274,8 @@ otcrypto_status_t otcrypto_key_unwrap(otcrypto_const_word32_buf_t wrapped_key, // Extract the key configuration. uint32_t config_words = sizeof(otcrypto_key_config_t) / sizeof(uint32_t); - hardened_memcpy((uint32_t *)&unwrapped_key->config, plaintext, config_words); + HARDENED_TRY(hardened_memcpy((uint32_t *)&unwrapped_key->config, plaintext, + config_words)); // Extract the checksum and keyblob length. unwrapped_key->checksum = plaintext[config_words]; @@ -287,8 +289,8 @@ otcrypto_status_t otcrypto_key_unwrap(otcrypto_const_word32_buf_t wrapped_key, if (unwrapped_key->keyblob_length != keyblob_words * sizeof(uint32_t)) { return OTCRYPTO_BAD_ARGS; } - hardened_memcpy(unwrapped_key->keyblob, plaintext + config_words + 2, - keyblob_words); + HARDENED_TRY(hardened_memcpy(unwrapped_key->keyblob, + plaintext + config_words + 2, keyblob_words)); // Finally, check the integrity of the key material we unwrapped. *success = integrity_blinded_key_check(unwrapped_key); @@ -374,8 +376,8 @@ otcrypto_status_t otcrypto_export_blinded_key( keyblob_share_num_words(blinded_key->config)); // Randomize the destination buffers. - hardened_memshred(key_share0.data, key_share0.len); - hardened_memshred(key_share1.data, key_share1.len); + HARDENED_TRY(hardened_memshred(key_share0.data, key_share0.len)); + HARDENED_TRY(hardened_memshred(key_share1.data, key_share1.len)); // Check the length of the keyblob. size_t keyblob_words = launder32(keyblob_num_words(blinded_key->config)); @@ -391,7 +393,9 @@ otcrypto_status_t otcrypto_export_blinded_key( uint32_t *keyblob_share1; HARDENED_TRY( keyblob_to_shares(blinded_key, &keyblob_share0, &keyblob_share1)); - hardened_memcpy(key_share0.data, keyblob_share0, key_share0.len); - hardened_memcpy(key_share1.data, keyblob_share1, key_share1.len); + HARDENED_TRY( + hardened_memcpy(key_share0.data, keyblob_share0, key_share0.len)); + HARDENED_TRY( + hardened_memcpy(key_share1.data, keyblob_share1, key_share1.len)); return OTCRYPTO_OK; } diff --git a/sw/device/lib/crypto/impl/keyblob.c b/sw/device/lib/crypto/impl/keyblob.c index d21f83ac7bff8..410148eec8eb7 100644 --- a/sw/device/lib/crypto/impl/keyblob.c +++ b/sw/device/lib/crypto/impl/keyblob.c @@ -98,11 +98,11 @@ status_t keyblob_from_shares(const uint32_t *share0, const uint32_t *share1, HARDENED_TRY(entropy_complex_check()); // Randomize the keyblob contents before writing shares. - hardened_memshred(keyblob, keyblob_num_words(config)); + HARDENED_TRY(hardened_memshred(keyblob, keyblob_num_words(config))); size_t share_words = keyblob_share_num_words(config); - hardened_memcpy(keyblob, share0, share_words); - hardened_memcpy(keyblob + share_words, share1, share_words); + HARDENED_TRY(hardened_memcpy(keyblob, share0, share_words)); + HARDENED_TRY(hardened_memcpy(keyblob + share_words, share1, share_words)); return OTCRYPTO_OK; } @@ -116,7 +116,8 @@ status_t keyblob_buffer_to_keymgr_diversification( HARDENED_TRY(entropy_complex_check()); // Copy the remainder of the keyblob into the salt. - hardened_memcpy(diversification->salt, &keyblob[1], kKeymgrSaltNumWords - 1); + HARDENED_TRY(hardened_memcpy(diversification->salt, &keyblob[1], + kKeymgrSaltNumWords - 1)); // Set the key mode as the last word of the salt. diversification->salt[kKeymgrSaltNumWords - 1] = launder32(mode); @@ -224,11 +225,11 @@ status_t keyblob_remask(otcrypto_blinded_key_t *key) { // Generate a fresh mask the size of one share. size_t key_share_words = keyblob_share_num_words(key->config); uint32_t mask[key_share_words]; - hardened_memshred(mask, key_share_words); + HARDENED_TRY(hardened_memshred(mask, key_share_words)); // XOR each share with the mask. - hardened_xor(share0, mask, key_share_words); - hardened_xor(share1, mask, key_share_words); + HARDENED_TRY(hardened_xor(share0, mask, key_share_words)); + HARDENED_TRY(hardened_xor(share1, mask, key_share_words)); // Update the key checksum. key->checksum = integrity_blinded_checksum(key); diff --git a/sw/device/lib/crypto/impl/kmac_kdf.c b/sw/device/lib/crypto/impl/kmac_kdf.c index 6e11fee833d00..566c571bf7656 100644 --- a/sw/device/lib/crypto/impl/kmac_kdf.c +++ b/sw/device/lib/crypto/impl/kmac_kdf.c @@ -109,8 +109,9 @@ otcrypto_status_t otcrypto_kmac_kdf( } // Randomize the keyblob memory. - hardened_memshred(output_key_material->keyblob, - keyblob_num_words(output_key_material->config)); + HARDENED_TRY( + hardened_memshred(output_key_material->keyblob, + keyblob_num_words(output_key_material->config))); switch (launder32(key_derivation_key->config.key_mode)) { case kOtcryptoKeyModeKdfKmac128: { diff --git a/sw/device/lib/crypto/impl/rsa.c b/sw/device/lib/crypto/impl/rsa.c index 95845a6241f8a..0505984a40ca5 100644 --- a/sw/device/lib/crypto/impl/rsa.c +++ b/sw/device/lib/crypto/impl/rsa.c @@ -88,7 +88,7 @@ otcrypto_status_t otcrypto_rsa_public_key_construct( } rsa_2048_public_key_t *pk = (rsa_2048_public_key_t *)public_key->key; pk->e = exponent; - hardened_memcpy(pk->n.data, modulus.data, modulus.len); + HARDENED_TRY(hardened_memcpy(pk->n.data, modulus.data, modulus.len)); break; } case kOtcryptoRsaSize3072: { @@ -98,7 +98,7 @@ otcrypto_status_t otcrypto_rsa_public_key_construct( } rsa_3072_public_key_t *pk = (rsa_3072_public_key_t *)public_key->key; pk->e = exponent; - hardened_memcpy(pk->n.data, modulus.data, modulus.len); + HARDENED_TRY(hardened_memcpy(pk->n.data, modulus.data, modulus.len)); break; } case kOtcryptoRsaSize4096: { @@ -108,7 +108,7 @@ otcrypto_status_t otcrypto_rsa_public_key_construct( } rsa_4096_public_key_t *pk = (rsa_4096_public_key_t *)public_key->key; pk->e = exponent; - hardened_memcpy(pk->n.data, modulus.data, modulus.len); + HARDENED_TRY(hardened_memcpy(pk->n.data, modulus.data, modulus.len)); break; } default: @@ -203,8 +203,9 @@ otcrypto_status_t otcrypto_rsa_private_key_from_exponents( HARDENED_TRY(private_key_structural_check(size, private_key)); // Randomize the keyblob. - hardened_memshred(private_key->keyblob, - ceil_div(private_key->keyblob_length, sizeof(uint32_t))); + HARDENED_TRY(hardened_memshred( + private_key->keyblob, + ceil_div(private_key->keyblob_length, sizeof(uint32_t)))); switch (size) { case kOtcryptoRsaSize2048: { @@ -214,8 +215,8 @@ otcrypto_status_t otcrypto_rsa_private_key_from_exponents( } rsa_2048_private_key_t *sk = (rsa_2048_private_key_t *)private_key->keyblob; - hardened_memcpy(sk->n.data, modulus.data, modulus.len); - hardened_memcpy(sk->d.data, d_share0.data, d_share0.len); + HARDENED_TRY(hardened_memcpy(sk->n.data, modulus.data, modulus.len)); + HARDENED_TRY(hardened_memcpy(sk->d.data, d_share0.data, d_share0.len)); // TODO: RSA keys are currently unblinded, so combine the shares. for (size_t i = 0; i < d_share1.len; i++) { sk->d.data[i] ^= d_share1.data[i]; @@ -229,8 +230,8 @@ otcrypto_status_t otcrypto_rsa_private_key_from_exponents( } rsa_3072_private_key_t *sk = (rsa_3072_private_key_t *)private_key->keyblob; - hardened_memcpy(sk->n.data, modulus.data, modulus.len); - hardened_memcpy(sk->d.data, d_share0.data, d_share0.len); + HARDENED_TRY(hardened_memcpy(sk->n.data, modulus.data, modulus.len)); + HARDENED_TRY(hardened_memcpy(sk->d.data, d_share0.data, d_share0.len)); // TODO: RSA keys are currently unblinded, so combine the shares. for (size_t i = 0; i < d_share1.len; i++) { sk->d.data[i] ^= d_share1.data[i]; @@ -244,8 +245,8 @@ otcrypto_status_t otcrypto_rsa_private_key_from_exponents( } rsa_4096_private_key_t *sk = (rsa_4096_private_key_t *)private_key->keyblob; - hardened_memcpy(sk->n.data, modulus.data, modulus.len); - hardened_memcpy(sk->d.data, d_share0.data, d_share0.len); + HARDENED_TRY(hardened_memcpy(sk->n.data, modulus.data, modulus.len)); + HARDENED_TRY(hardened_memcpy(sk->d.data, d_share0.data, d_share0.len)); // TODO: RSA keys are currently unblinded, so combine the shares. for (size_t i = 0; i < d_share1.len; i++) { sk->d.data[i] ^= d_share1.data[i]; @@ -466,8 +467,9 @@ otcrypto_status_t otcrypto_rsa_keygen_async_finalize( HARDENED_TRY(private_key_structural_check(size, private_key)); // Randomize the keyblob memory. - hardened_memshred(private_key->keyblob, - ceil_div(private_key->keyblob_length, sizeof(uint32_t))); + HARDENED_TRY(hardened_memshred( + private_key->keyblob, + ceil_div(private_key->keyblob_length, sizeof(uint32_t)))); // Call the required finalize() operation. switch (size) { @@ -539,7 +541,7 @@ otcrypto_status_t otcrypto_rsa_keypair_from_cofactor_async_start( cf->data[i] ^= cofactor_share1.data[i]; } rsa_2048_public_key_t pk; - hardened_memcpy(pk.n.data, modulus.data, modulus.len); + HARDENED_TRY(hardened_memcpy(pk.n.data, modulus.data, modulus.len)); pk.e = e; return rsa_keygen_from_cofactor_2048_start(&pk, cf); } @@ -580,8 +582,9 @@ otcrypto_status_t otcrypto_rsa_keypair_from_cofactor_async_finalize( HARDENED_TRY(private_key_structural_check(size, private_key)); // Randomize the keyblob memory. - hardened_memshred(private_key->keyblob, - ceil_div(private_key->keyblob_length, sizeof(uint32_t))); + HARDENED_TRY(hardened_memshred( + private_key->keyblob, + ceil_div(private_key->keyblob_length, sizeof(uint32_t)))); // Call the required finalize() operation. switch (size) { diff --git a/sw/device/lib/crypto/impl/rsa/rsa_encryption.c b/sw/device/lib/crypto/impl/rsa/rsa_encryption.c index ce9f66fa886d9..479ecb46bdeb9 100644 --- a/sw/device/lib/crypto/impl/rsa/rsa_encryption.c +++ b/sw/device/lib/crypto/impl/rsa/rsa_encryption.c @@ -21,7 +21,8 @@ status_t rsa_encrypt_2048_start(const rsa_2048_public_key_t *public_key, const uint8_t *label, size_t label_bytelen) { // Encode the message. rsa_2048_int_t encoded_message; - hardened_memshred(encoded_message.data, ARRAYSIZE(encoded_message.data)); + HARDENED_TRY( + hardened_memshred(encoded_message.data, ARRAYSIZE(encoded_message.data))); HARDENED_TRY(rsa_padding_oaep_encode( hash_mode, message, message_bytelen, label, label_bytelen, ARRAYSIZE(encoded_message.data), encoded_message.data)); @@ -67,8 +68,9 @@ status_t rsa_decrypt_finalize(const otcrypto_hash_mode_t hash_mode, (sizeof(uint32_t) - (size_t)misalignment) % sizeof(uint32_t); size_t num_aligned_full_words = (plaintext_max_bytelen - aligned_offset) / sizeof(uint32_t); - hardened_memshred((uint32_t *)((uintptr_t)plaintext + aligned_offset), - num_aligned_full_words); + HARDENED_TRY( + hardened_memshred((uint32_t *)((uintptr_t)plaintext + aligned_offset), + num_aligned_full_words)); // Call the appropriate `finalize()` operation to get the recovered encoded // message. @@ -110,7 +112,8 @@ status_t rsa_encrypt_3072_start(const rsa_3072_public_key_t *public_key, const uint8_t *label, size_t label_bytelen) { // Encode the message. rsa_3072_int_t encoded_message; - hardened_memshred(encoded_message.data, ARRAYSIZE(encoded_message.data)); + HARDENED_TRY( + hardened_memshred(encoded_message.data, ARRAYSIZE(encoded_message.data))); HARDENED_TRY(rsa_padding_oaep_encode( hash_mode, message, message_bytelen, label, label_bytelen, ARRAYSIZE(encoded_message.data), encoded_message.data)); @@ -138,7 +141,8 @@ status_t rsa_encrypt_4096_start(const rsa_4096_public_key_t *public_key, const uint8_t *label, size_t label_bytelen) { // Encode the message. rsa_4096_int_t encoded_message; - hardened_memshred(encoded_message.data, ARRAYSIZE(encoded_message.data)); + HARDENED_TRY( + hardened_memshred(encoded_message.data, ARRAYSIZE(encoded_message.data))); HARDENED_TRY(rsa_padding_oaep_encode( hash_mode, message, message_bytelen, label, label_bytelen, ARRAYSIZE(encoded_message.data), encoded_message.data)); diff --git a/sw/device/lib/crypto/impl/rsa/rsa_keygen.c b/sw/device/lib/crypto/impl/rsa/rsa_keygen.c index 8725eae43996f..a86cb1b36620c 100644 --- a/sw/device/lib/crypto/impl/rsa/rsa_keygen.c +++ b/sw/device/lib/crypto/impl/rsa/rsa_keygen.c @@ -109,8 +109,8 @@ status_t rsa_keygen_2048_finalize(rsa_2048_public_key_t *public_key, private_key->n.data, private_key->d.data)); // Copy the modulus to the public key. - hardened_memcpy(public_key->n.data, private_key->n.data, - ARRAYSIZE(private_key->n.data)); + HARDENED_TRY(hardened_memcpy(public_key->n.data, private_key->n.data, + ARRAYSIZE(private_key->n.data))); // Set the public exponent to F4, the only exponent our key generation // algorithm supports. @@ -129,8 +129,8 @@ status_t rsa_keygen_3072_finalize(rsa_3072_public_key_t *public_key, private_key->n.data, private_key->d.data)); // Copy the modulus to the public key. - hardened_memcpy(public_key->n.data, private_key->n.data, - ARRAYSIZE(private_key->n.data)); + HARDENED_TRY(hardened_memcpy(public_key->n.data, private_key->n.data, + ARRAYSIZE(private_key->n.data))); // Set the public exponent to F4, the only exponent our key generation // algorithm supports. @@ -149,8 +149,8 @@ status_t rsa_keygen_4096_finalize(rsa_4096_public_key_t *public_key, private_key->n.data, private_key->d.data)); // Copy the modulus to the public key. - hardened_memcpy(public_key->n.data, private_key->n.data, - ARRAYSIZE(private_key->n.data)); + HARDENED_TRY(hardened_memcpy(public_key->n.data, private_key->n.data, + ARRAYSIZE(private_key->n.data))); // Set the public exponent to F4, the only exponent our key generation // algorithm supports. @@ -188,8 +188,8 @@ status_t rsa_keygen_from_cofactor_2048_finalize( private_key->n.data, private_key->d.data)); // Copy the modulus to the public key. - hardened_memcpy(public_key->n.data, private_key->n.data, - ARRAYSIZE(private_key->n.data)); + HARDENED_TRY(hardened_memcpy(public_key->n.data, private_key->n.data, + ARRAYSIZE(private_key->n.data))); // Set the public exponent to F4, the only exponent our key generation // algorithm supports. diff --git a/sw/device/lib/crypto/impl/rsa/rsa_padding.c b/sw/device/lib/crypto/impl/rsa/rsa_padding.c index 416c3f34e7c07..995aef4d80b9e 100644 --- a/sw/device/lib/crypto/impl/rsa/rsa_padding.c +++ b/sw/device/lib/crypto/impl/rsa/rsa_padding.c @@ -342,7 +342,8 @@ static status_t mgf1(otcrypto_hash_mode_t hash_mode, const uint8_t *seed, // Digest won't fit in mask (last iteration). Use a temporary buffer. uint32_t digest[digest_wordlen]; HARDENED_TRY(hash(hash_mode, hash_input, sizeof(hash_input), digest)); - hardened_memcpy(mask, digest, ceil_div(mask_len, sizeof(uint32_t))); + HARDENED_TRY( + hardened_memcpy(mask, digest, ceil_div(mask_len, sizeof(uint32_t)))); mask_len = 0; } else { HARDENED_TRY(hash(hash_mode, hash_input, sizeof(hash_input), mask)); @@ -396,9 +397,10 @@ static status_t pss_construct_h(const otcrypto_hash_digest_t message_digest, m_prime[1] = 0; uint32_t *digest_dst = &m_prime[2]; uint32_t *salt_dst = digest_dst + message_digest.len; - hardened_memcpy(digest_dst, message_digest.data, message_digest.len); + HARDENED_TRY( + hardened_memcpy(digest_dst, message_digest.data, message_digest.len)); if (salt_len > 0) { - hardened_memcpy(salt_dst, salt, salt_len); + HARDENED_TRY(hardened_memcpy(salt_dst, salt, salt_len)); } // Construct H = Hash(M'). @@ -450,7 +452,7 @@ status_t rsa_padding_pss_encode(const otcrypto_hash_digest_t message_digest, // Compute the final encoded message and reverse the byte-order. // EM = maskedDB || H || 0xbc unsigned char *encoded_message_bytes = (unsigned char *)encoded_message; - hardened_memcpy(encoded_message, db, ARRAYSIZE(db)); + HARDENED_TRY(hardened_memcpy(encoded_message, db, ARRAYSIZE(db))); memcpy(encoded_message_bytes + db_bytelen, h, sizeof(h)); encoded_message_bytes[encoded_message_bytelen - 1] = 0xbc; reverse_bytes(encoded_message_len, encoded_message); diff --git a/sw/device/lib/crypto/impl/sha2/sha256.c b/sw/device/lib/crypto/impl/sha2/sha256.c index 3cb0f68b59c55..e06d150d55535 100644 --- a/sw/device/lib/crypto/impl/sha2/sha256.c +++ b/sw/device/lib/crypto/impl/sha2/sha256.c @@ -65,13 +65,16 @@ static const otbn_addr_t kOtbnVarSha256Msg = OTBN_ADDR_T_INIT(run_sha256, msg); static const otbn_addr_t kOtbnVarSha256NumMsgChunks = OTBN_ADDR_T_INIT(run_sha256, num_msg_chunks); -void sha256_init(sha256_state_t *state) { +status_t sha256_init(sha256_state_t *state) { // Set the initial state. - hardened_memcpy(state->H, kSha256InitialState, kSha256StateWords); + HARDENED_TRY( + hardened_memcpy(state->H, kSha256InitialState, kSha256StateWords)); // Set the partial block to 0 (the value is ignored). memset(state->partial_block, 0, kSha256MessageBlockBytes); // Set the message length so far to 0. state->total_len = 0ull; + + return OTCRYPTO_OK; } /** @@ -253,8 +256,9 @@ static status_t process_message(sha256_state_t *state, const uint8_t *msg, // At this point, no more errors are possible; it is safe to update the // context object. - hardened_memcpy(state->H, new_state.H, kSha256StateWords); - hardened_memcpy(state->partial_block, block.data, kSha256MessageBlockWords); + HARDENED_TRY(hardened_memcpy(state->H, new_state.H, kSha256StateWords)); + HARDENED_TRY(hardened_memcpy(state->partial_block, block.data, + kSha256MessageBlockWords)); state->total_len = new_state.total_len; return OTCRYPTO_OK; } @@ -271,11 +275,15 @@ status_t sha256_update(sha256_state_t *state, const uint8_t *msg, * The entropy complex must be initialized before calling this function. * * @param state The context object to shred. + * @return OK or error. */ -static void state_shred(sha256_state_t *state) { - hardened_memshred(state->H, kSha256StateWords); - hardened_memshred(state->partial_block, kSha256MessageBlockWords); +static status_t state_shred(sha256_state_t *state) { + HARDENED_TRY(hardened_memshred(state->H, kSha256StateWords)); + HARDENED_TRY( + hardened_memshred(state->partial_block, kSha256MessageBlockWords)); state->total_len = 0; + + return OTCRYPTO_OK; } /** @@ -290,14 +298,17 @@ static void state_shred(sha256_state_t *state) { * * @param state Context object. * @param[out] digest Destination buffer for digest. + * @return OK or error. */ -static void digest_get(sha256_state_t *state, uint32_t *digest) { +static status_t digest_get(sha256_state_t *state, uint32_t *digest) { for (size_t i = 0; i < kSha256StateWords / 2; i++) { uint32_t tmp = __builtin_bswap32(state->H[i]); state->H[i] = __builtin_bswap32(state->H[kSha256StateWords - 1 - i]); state->H[kSha256StateWords - 1 - i] = tmp; } - hardened_memcpy(digest, state->H, kSha256StateWords); + HARDENED_TRY(hardened_memcpy(digest, state->H, kSha256StateWords)); + + return OTCRYPTO_OK; } status_t sha256_final(sha256_state_t *state, uint32_t *digest) { @@ -308,8 +319,8 @@ status_t sha256_final(sha256_state_t *state, uint32_t *digest) { HARDENED_TRY(process_message(state, NULL, 0, kHardenedBoolTrue)); // Retrieve the final digest and destroy the state. - digest_get(state, digest); - state_shred(state); + HARDENED_TRY(digest_get(state, digest)); + HARDENED_TRY(state_shred(state)); return OTCRYPTO_OK; } @@ -318,13 +329,13 @@ status_t sha256(const uint8_t *msg, const size_t msg_len, uint32_t *digest) { HARDENED_TRY(entropy_complex_check()); sha256_state_t state; - sha256_init(&state); + HARDENED_TRY(sha256_init(&state)); // Process data with padding enabled. HARDENED_TRY(process_message(&state, msg, msg_len, kHardenedBoolTrue)); // Retrieve the final digest and destroy the state. - digest_get(&state, digest); - state_shred(&state); + HARDENED_TRY(digest_get(&state, digest)); + HARDENED_TRY(state_shred(&state)); return OTCRYPTO_OK; } diff --git a/sw/device/lib/crypto/impl/sha2/sha256.h b/sw/device/lib/crypto/impl/sha2/sha256.h index bf77b649331b5..e070f2eee54f5 100644 --- a/sw/device/lib/crypto/impl/sha2/sha256.h +++ b/sw/device/lib/crypto/impl/sha2/sha256.h @@ -105,7 +105,7 @@ status_t sha256(const uint8_t *msg, const size_t msg_len, uint32_t *digest); * @param[out] state Hash context object to initialize. * @return Result of the operation (OK or error). */ -void sha256_init(sha256_state_t *state); +status_t sha256_init(sha256_state_t *state); /** * Process new message data for a SHA-256 hash computation. diff --git a/sw/device/lib/crypto/impl/sha2/sha512.c b/sw/device/lib/crypto/impl/sha2/sha512.c index 5d76fed0b37b9..9bc9d605376a3 100644 --- a/sw/device/lib/crypto/impl/sha2/sha512.c +++ b/sw/device/lib/crypto/impl/sha2/sha512.c @@ -70,24 +70,30 @@ static const otbn_addr_t kOtbnVarSha512Msg = OTBN_ADDR_T_INIT(run_sha512, msg); static const otbn_addr_t kOtbnVarSha512NChunks = OTBN_ADDR_T_INIT(run_sha512, n_chunks); -void sha512_init(sha512_state_t *state) { +status_t sha512_init(sha512_state_t *state) { // Set the initial state. - hardened_memcpy(state->H, kSha512InitialState, kSha512StateWords); + HARDENED_TRY( + hardened_memcpy(state->H, kSha512InitialState, kSha512StateWords)); // Set the partial block to 0 (the value is ignored). memset(state->partial_block, 0, sizeof(state->partial_block)); // Set the message length so far to 0. state->total_len.lower = 0; state->total_len.upper = 0; + + return OTCRYPTO_OK; } -void sha384_init(sha512_state_t *state) { +status_t sha384_init(sha512_state_t *state) { // Set the initial state. - hardened_memcpy(state->H, kSha384InitialState, kSha512StateWords); + HARDENED_TRY( + hardened_memcpy(state->H, kSha384InitialState, kSha512StateWords)); // Set the partial block to 0 (the value is ignored). memset(state->partial_block, 0, sizeof(state->partial_block)); // Set the message length so far to 0. state->total_len.lower = 0; state->total_len.upper = 0; + + return OTCRYPTO_OK; } /** @@ -270,7 +276,8 @@ static status_t process_message(sha512_state_t *state, const uint8_t *msg, sha512_message_block_t block; size_t partial_block_len = (state->total_len.lower >> 3) % kSha512MessageBlockBytes; - hardened_memcpy(block.data, state->partial_block, kSha512MessageBlockWords); + HARDENED_TRY(hardened_memcpy(block.data, state->partial_block, + kSha512MessageBlockWords)); // Initialize the context for the OTBN message buffer. sha512_otbn_ctx_t ctx = {.num_blocks = 0}; @@ -319,8 +326,9 @@ static status_t process_message(sha512_state_t *state, const uint8_t *msg, // At this point, no more errors are possible; it is safe to update the // context object. - hardened_memcpy(state->H, new_state.H, kSha512StateWords); - hardened_memcpy(state->partial_block, block.data, kSha512MessageBlockWords); + HARDENED_TRY(hardened_memcpy(state->H, new_state.H, kSha512StateWords)); + HARDENED_TRY(hardened_memcpy(state->partial_block, block.data, + kSha512MessageBlockWords)); state->total_len.lower = new_state.total_len.lower; state->total_len.upper = new_state.total_len.upper; return OTCRYPTO_OK; @@ -336,10 +344,12 @@ status_t sha512_update(sha512_state_t *state, const uint8_t *msg, * Replace sensitive data in a SHA-512 context with non-sensitive values. * * @param state The context object to shred. + * @return OK or error. */ -static void state_shred(sha512_state_t *state) { - hardened_memshred(state->H, kSha512StateWords); - hardened_memshred(state->partial_block, kSha512MessageBlockWords); +static status_t state_shred(sha512_state_t *state) { + HARDENED_TRY(hardened_memshred(state->H, kSha512StateWords)); + HARDENED_TRY( + hardened_memshred(state->partial_block, kSha512MessageBlockWords)); state->total_len.lower = 0; state->total_len.upper = 0; } @@ -353,13 +363,16 @@ static void state_shred(sha512_state_t *state) { * * @param state Context object. * @param[out] digest Destination buffer for digest. + * @return OK or error. */ -static void sha512_digest_get(sha512_state_t *state, uint32_t *digest) { +static status_t sha512_digest_get(sha512_state_t *state, uint32_t *digest) { // Reverse the bytes in each word to match FIPS 180-4. for (size_t i = 0; i < kSha512StateWords; i++) { state->H[i] = __builtin_bswap32(state->H[i]); } - hardened_memcpy(digest, state->H, kSha512DigestWords); + HARDENED_TRY(hardened_memcpy(digest, state->H, kSha512DigestWords)); + + return OTCRYPTO_OK; } /** @@ -370,13 +383,16 @@ static void sha512_digest_get(sha512_state_t *state, uint32_t *digest) { * * @param state Context object. * @param[out] digest Destination buffer for digest. + * @return OK or error. */ -static void sha384_digest_get(sha512_state_t *state, uint32_t *digest) { +static status_t sha384_digest_get(sha512_state_t *state, uint32_t *digest) { // Reverse the bytes in each word to match FIPS 180-4. for (size_t i = 0; i < kSha512StateWords; i++) { state->H[i] = __builtin_bswap32(state->H[i]); } - hardened_memcpy(digest, state->H, kSha384DigestWords); + HARDENED_TRY(hardened_memcpy(digest, state->H, kSha384DigestWords)); + + return OTCRYPTO_OK; } status_t sha512_final(sha512_state_t *state, uint32_t *digest) { @@ -387,8 +403,8 @@ status_t sha512_final(sha512_state_t *state, uint32_t *digest) { HARDENED_TRY(process_message(state, NULL, 0, kHardenedBoolTrue)); // Copy the digest and then destroy the state. - sha512_digest_get(state, digest); - state_shred(state); + HARDENED_TRY(sha512_digest_get(state, digest)); + HARDENED_TRY(state_shred(state)); return OTCRYPTO_OK; } @@ -397,12 +413,12 @@ status_t sha512(const uint8_t *msg, const size_t msg_len, uint32_t *digest) { HARDENED_TRY(entropy_complex_check()); sha512_state_t state; - sha512_init(&state); + HARDENED_TRY(sha512_init(&state)); // Process data with padding enabled. HARDENED_TRY(process_message(&state, msg, msg_len, kHardenedBoolTrue)); - sha512_digest_get(&state, digest); - state_shred(&state); + HARDENED_TRY(sha512_digest_get(&state, digest)); + HARDENED_TRY(state_shred(&state)); return OTCRYPTO_OK; } @@ -420,8 +436,8 @@ status_t sha384_final(sha384_state_t *state, uint32_t *digest) { HARDENED_TRY(process_message(state, NULL, 0, kHardenedBoolTrue)); // Copy the digest and then destroy the state. - sha384_digest_get(state, digest); - state_shred(state); + HARDENED_TRY(sha384_digest_get(state, digest)); + HARDENED_TRY(state_shred(state)); return OTCRYPTO_OK; } @@ -430,11 +446,11 @@ status_t sha384(const uint8_t *msg, const size_t msg_len, uint32_t *digest) { HARDENED_TRY(entropy_complex_check()); sha384_state_t state; - sha384_init(&state); + HARDENED_TRY(sha384_init(&state)); // Process data with padding enabled. HARDENED_TRY(process_message(&state, msg, msg_len, kHardenedBoolTrue)); - sha384_digest_get(&state, digest); - state_shred(&state); + HARDENED_TRY(sha384_digest_get(&state, digest)); + HARDENED_TRY(state_shred(&state)); return OTCRYPTO_OK; } diff --git a/sw/device/lib/crypto/impl/sha2/sha512.h b/sw/device/lib/crypto/impl/sha2/sha512.h index 3605e75c22e4a..a1cf2c829f44a 100644 --- a/sw/device/lib/crypto/impl/sha2/sha512.h +++ b/sw/device/lib/crypto/impl/sha2/sha512.h @@ -144,7 +144,7 @@ status_t sha384(const uint8_t *msg, const size_t msg_len, uint32_t *digest); * @param[out] state Hash context object to initialize. * @return Result of the operation (OK or error). */ -void sha384_init(sha384_state_t *state); +status_t sha384_init(sha384_state_t *state); /** * Process new message data for a SHA-384 hash computation. @@ -208,7 +208,7 @@ status_t sha512(const uint8_t *msg, const size_t msg_len, uint32_t *digest); * @param[out] state Hash context object to initialize. * @return Result of the operation (OK or error). */ -void sha512_init(sha512_state_t *state); +status_t sha512_init(sha512_state_t *state); /** * Process new message data for a SHA-512 hash computation. From 659b7f9c37b96f16e2c4c7901ed66f1df560584d Mon Sep 17 00:00:00 2001 From: Pascal Nasahl Date: Wed, 1 Oct 2025 14:09:46 +0200 Subject: [PATCH 32/51] [crypto] Fix includes Signed-off-by: Pascal Nasahl (commit is original to earlgrey_1.0.0) --- sw/device/lib/crypto/drivers/BUILD | 3 ++- sw/device/lib/crypto/drivers/hmac.c | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/sw/device/lib/crypto/drivers/BUILD b/sw/device/lib/crypto/drivers/BUILD index 1c385dc91b8df..22bdca2704e9f 100644 --- a/sw/device/lib/crypto/drivers/BUILD +++ b/sw/device/lib/crypto/drivers/BUILD @@ -124,7 +124,6 @@ cc_library( "//sw/device/lib/base:bitfield", "//sw/device/lib/base:hardened", "//sw/device/lib/base:macros", - "//sw/device/lib/crypto/drivers:rv_core_ibex", "//sw/device/lib/crypto/impl:status", ], ) @@ -198,11 +197,13 @@ cc_library( srcs = ["hmac.c"], hdrs = ["hmac.h"], deps = [ + ":entropy", "//hw/ip/hmac/data:hmac_c_regs", "//hw/top_earlgrey/sw/autogen:top_earlgrey", "//sw/device/lib/base:abs_mmio", "//sw/device/lib/base:bitfield", "//sw/device/lib/base:hardened", + "//sw/device/lib/base:hardened_memory", "//sw/device/lib/base:macros", "//sw/device/lib/base:memory", "//sw/device/lib/crypto/drivers:rv_core_ibex", diff --git a/sw/device/lib/crypto/drivers/hmac.c b/sw/device/lib/crypto/drivers/hmac.c index 2f03901a2fb40..64e0c2d62b892 100644 --- a/sw/device/lib/crypto/drivers/hmac.c +++ b/sw/device/lib/crypto/drivers/hmac.c @@ -7,7 +7,9 @@ #include "sw/device/lib/base/abs_mmio.h" #include "sw/device/lib/base/bitfield.h" #include "sw/device/lib/base/hardened.h" +#include "sw/device/lib/base/hardened_memory.h" #include "sw/device/lib/base/memory.h" +#include "sw/device/lib/crypto/drivers/entropy.h" #include "sw/device/lib/crypto/drivers/rv_core_ibex.h" #include "sw/device/lib/crypto/impl/status.h" #include "sw/device/lib/runtime/ibex.h" From 8a41e98d815b9ce0dc5b86744f78f7987711ac69 Mon Sep 17 00:00:00 2001 From: Pascal Nasahl Date: Tue, 9 Sep 2025 09:27:22 +0200 Subject: [PATCH 33/51] [crypto] Improve switch case hardening in AES To protect switch case statements against FI, we shoud set in the case statement the condition and check afterwards if the condition matches. Moreover, use launder32() in the case statement when setting the condition. Signed-off-by: Pascal Nasahl Co-authored-by: Siemen Dhooghe (cherry picked from commit 6fc9cab69dd56cf55d51f660e3989a9114d244a8) --- sw/device/lib/crypto/drivers/aes.c | 36 ++++++++++++++----------- sw/device/lib/crypto/impl/aes.c | 42 +++++++++++++++++++++++------ sw/device/lib/crypto/impl/aes_gcm.c | 12 ++++++--- 3 files changed, 63 insertions(+), 27 deletions(-) diff --git a/sw/device/lib/crypto/drivers/aes.c b/sw/device/lib/crypto/drivers/aes.c index ac8311f57e9fd..07ff97f6f2703 100644 --- a/sw/device/lib/crypto/drivers/aes.c +++ b/sw/device/lib/crypto/drivers/aes.c @@ -102,79 +102,85 @@ static status_t aes_begin(aes_key_t key, const aes_block_t *iv, uint32_t ctrl_reg = AES_CTRL_SHADOWED_REG_RESVAL; // Set the operation (encrypt or decrypt). - hardened_bool_t operation_written = kHardenedBoolFalse; + hardened_bool_t operation_enc = launder32(0); switch (encrypt) { case kHardenedBoolTrue: ctrl_reg = bitfield_field32_write(ctrl_reg, AES_CTRL_SHADOWED_OPERATION_FIELD, AES_CTRL_SHADOWED_OPERATION_VALUE_AES_ENC); - operation_written = launder32(kHardenedBoolTrue); + operation_enc = launder32(operation_enc) | kHardenedBoolTrue; break; case kHardenedBoolFalse: ctrl_reg = bitfield_field32_write(ctrl_reg, AES_CTRL_SHADOWED_OPERATION_FIELD, AES_CTRL_SHADOWED_OPERATION_VALUE_AES_DEC); - operation_written = launder32(kHardenedBoolTrue); + operation_enc = launder32(operation_enc) | kHardenedBoolFalse; break; default: // Invalid value. return OTCRYPTO_BAD_ARGS; } - HARDENED_CHECK_EQ(operation_written, kHardenedBoolTrue); + // Check if we landed in the correct case statement. Use ORs for this to + // avoid that multiple cases were executed. + HARDENED_CHECK_EQ(launder32(operation_enc), encrypt); // Indicate whether the key will be sideloaded. - hardened_bool_t sideload_written = kHardenedBoolFalse; + hardened_bool_t which_sideload = launder32(0); switch (key.sideload) { case kHardenedBoolTrue: ctrl_reg = bitfield_bit32_write(ctrl_reg, AES_CTRL_SHADOWED_SIDELOAD_BIT, true); - sideload_written = launder32(kHardenedBoolTrue); + which_sideload = launder32(which_sideload) | kHardenedBoolTrue; break; case kHardenedBoolFalse: ctrl_reg = bitfield_bit32_write(ctrl_reg, AES_CTRL_SHADOWED_SIDELOAD_BIT, false); - sideload_written = launder32(kHardenedBoolTrue); + which_sideload = launder32(which_sideload) | kHardenedBoolFalse; break; default: // Invalid value. return OTCRYPTO_BAD_ARGS; } - HARDENED_CHECK_EQ(sideload_written, kHardenedBoolTrue); + // Check if we landed in the correct case statement. Use ORs for this to + // avoid that multiple cases were executed. + HARDENED_CHECK_EQ(launder32(which_sideload), key.sideload); // Translate the cipher mode to the hardware-encoding value and write the // control reg field. - aes_cipher_mode_t mode_written; + aes_cipher_mode_t mode_written = launder32(0); switch (launder32(key.mode)) { case kAesCipherModeEcb: ctrl_reg = bitfield_field32_write(ctrl_reg, AES_CTRL_SHADOWED_MODE_FIELD, AES_CTRL_SHADOWED_MODE_VALUE_AES_ECB); - mode_written = launder32(kAesCipherModeEcb); + mode_written = launder32(mode_written) | kAesCipherModeEcb; break; case kAesCipherModeCbc: ctrl_reg = bitfield_field32_write(ctrl_reg, AES_CTRL_SHADOWED_MODE_FIELD, AES_CTRL_SHADOWED_MODE_VALUE_AES_CBC); - mode_written = launder32(kAesCipherModeCbc); + mode_written = launder32(mode_written) | kAesCipherModeCbc; break; case kAesCipherModeCfb: ctrl_reg = bitfield_field32_write(ctrl_reg, AES_CTRL_SHADOWED_MODE_FIELD, AES_CTRL_SHADOWED_MODE_VALUE_AES_CFB); - mode_written = launder32(kAesCipherModeCfb); + mode_written = launder32(mode_written) | kAesCipherModeCfb; break; case kAesCipherModeOfb: ctrl_reg = bitfield_field32_write(ctrl_reg, AES_CTRL_SHADOWED_MODE_FIELD, AES_CTRL_SHADOWED_MODE_VALUE_AES_OFB); - mode_written = launder32(kAesCipherModeOfb); + mode_written = launder32(mode_written) | kAesCipherModeOfb; break; case kAesCipherModeCtr: ctrl_reg = bitfield_field32_write(ctrl_reg, AES_CTRL_SHADOWED_MODE_FIELD, AES_CTRL_SHADOWED_MODE_VALUE_AES_CTR); - mode_written = launder32(kAesCipherModeCtr); + mode_written = launder32(mode_written) | kAesCipherModeCtr; break; default: // Invalid value. return OTCRYPTO_BAD_ARGS; } - HARDENED_CHECK_EQ(mode_written, key.mode); + // Check if we landed in the correct case statement. Use ORs for this to + // avoid that multiple cases were executed. + HARDENED_CHECK_EQ(launder32(mode_written), key.mode); // Translate the key length to the hardware-encoding value and write the // control reg field. diff --git a/sw/device/lib/crypto/impl/aes.c b/sw/device/lib/crypto/impl/aes.c index 219caaaa52045..adeeda195dad6 100644 --- a/sw/device/lib/crypto/impl/aes.c +++ b/sw/device/lib/crypto/impl/aes.c @@ -82,25 +82,40 @@ static status_t aes_key_construct(otcrypto_blinded_key_t *blinded_key, } // Set the block cipher mode based on the key mode. + otcrypto_key_mode_t blinded_key_mode_used = launder32(0); switch (blinded_key->config.key_mode) { case kOtcryptoKeyModeAesEcb: aes_key->mode = kAesCipherModeEcb; + blinded_key_mode_used = + launder32(blinded_key_mode_used) | kOtcryptoKeyModeAesEcb; break; case kOtcryptoKeyModeAesCbc: - aes_key->mode = kAesCipherModeCbc; + aes_key->mode = launder32(kAesCipherModeCbc); + blinded_key_mode_used = + launder32(blinded_key_mode_used) | kOtcryptoKeyModeAesCbc; break; case kOtcryptoKeyModeAesCfb: - aes_key->mode = kAesCipherModeCfb; + aes_key->mode = launder32(kAesCipherModeCfb); + blinded_key_mode_used = + launder32(blinded_key_mode_used) | kOtcryptoKeyModeAesCfb; break; case kOtcryptoKeyModeAesOfb: - aes_key->mode = kAesCipherModeOfb; + aes_key->mode = launder32(kAesCipherModeOfb); + blinded_key_mode_used = + launder32(blinded_key_mode_used) | kOtcryptoKeyModeAesOfb; break; case kOtcryptoKeyModeAesCtr: - aes_key->mode = kAesCipherModeCtr; + aes_key->mode = launder32(kAesCipherModeCtr); + blinded_key_mode_used = + launder32(blinded_key_mode_used) | kOtcryptoKeyModeAesCtr; break; default: return OTCRYPTO_BAD_ARGS; } + // Check if we landed in the correct case statement. Use ORs for this to + // avoid that multiple cases were executed. + HARDENED_CHECK_EQ(launder32(blinded_key_mode_used), + blinded_key->config.key_mode); // Check that the key mode matches the requested block cipher mode. if (memcmp(&aes_key->mode, &aes_mode, sizeof(aes_key->mode)) != 0) { @@ -145,18 +160,19 @@ static status_t aes_padding_apply(otcrypto_aes_padding_t padding_mode, // Pad a full block if the last block is full, otherwise just fill the last // block. size_t padding_len = kAesBlockNumBytes - partial_data_len; - hardened_bool_t padding_written = kHardenedBoolFalse; + otcrypto_aes_padding_t padding_written = launder32(0); switch (launder32(padding_mode)) { case kOtcryptoAesPaddingPkcs7: // Pads with value same as the number of padding bytes. memset(padding, (uint8_t)padding_len, padding_len); - padding_written = kHardenedBoolTrue; + padding_written = launder32(padding_written) | kOtcryptoAesPaddingPkcs7; break; case kOtcryptoAesPaddingIso9797M2: // Pads with 0x80 (0b10000000), followed by zero bytes. memset(padding, 0x0, padding_len); padding[0] = 0x80; - padding_written = kHardenedBoolTrue; + padding_written = + launder32(padding_written) | kOtcryptoAesPaddingIso9797M2; break; case kOtcryptoAesPaddingNull: // This routine should not be called if padding is not needed. @@ -165,7 +181,9 @@ static status_t aes_padding_apply(otcrypto_aes_padding_t padding_mode, // Unrecognized padding mode. return OTCRYPTO_BAD_ARGS; } - HARDENED_CHECK_EQ(padding_written, kHardenedBoolTrue); + // Check if we landed in the correct case statement. Use ORs for this to + // avoid that multiple cases were executed. + HARDENED_CHECK_EQ(launder32(padding_written), padding_mode); return OTCRYPTO_OK; } @@ -326,16 +344,24 @@ static otcrypto_status_t otcrypto_aes_impl( HARDENED_TRY(aes_key_construct(key, aes_mode, &aes_key)); // Start the operation (encryption or decryption). + otcrypto_aes_operation_t aes_operation_started = launder32(0); switch (aes_operation) { case kOtcryptoAesOperationEncrypt: HARDENED_TRY(aes_encrypt_begin(aes_key, &aes_iv)); + aes_operation_started = + launder32(aes_operation_started) | kOtcryptoAesOperationEncrypt; break; case kOtcryptoAesOperationDecrypt: HARDENED_TRY(aes_decrypt_begin(aes_key, &aes_iv)); + aes_operation_started = + launder32(aes_operation_started) | kOtcryptoAesOperationDecrypt; break; default: return OTCRYPTO_BAD_ARGS; } + // Check if we landed in the correct case statement. Use ORs for this to + // avoid that multiple cases were executed. + HARDENED_CHECK_EQ(launder32(aes_operation_started), aes_operation); // Perform the cipher operation for all full blocks. The input and output are // offset by `block_offset` number of blocks, where `block_offset` can be 1 diff --git a/sw/device/lib/crypto/impl/aes_gcm.c b/sw/device/lib/crypto/impl/aes_gcm.c index b11422e2f640f..4ef1767694fb2 100644 --- a/sw/device/lib/crypto/impl/aes_gcm.c +++ b/sw/device/lib/crypto/impl/aes_gcm.c @@ -183,27 +183,31 @@ static status_t aes_gcm_key_construct(otcrypto_blinded_key_t *blinded_key, status_t aes_gcm_check_tag_length(size_t word_len, otcrypto_aes_gcm_tag_len_t tag_len) { size_t bit_len = 0; + otcrypto_aes_gcm_tag_len_t tag_len_set = launder32(0); switch (launder32(tag_len)) { case kOtcryptoAesGcmTagLen128: - HARDENED_CHECK_EQ(tag_len, kOtcryptoAesGcmTagLen128); bit_len = 128; + tag_len_set = launder32(tag_len_set) | kOtcryptoAesGcmTagLen128; break; case kOtcryptoAesGcmTagLen96: - HARDENED_CHECK_EQ(tag_len, kOtcryptoAesGcmTagLen96); bit_len = 96; + tag_len_set = launder32(tag_len_set) | kOtcryptoAesGcmTagLen96; break; case kOtcryptoAesGcmTagLen64: - HARDENED_CHECK_EQ(tag_len, kOtcryptoAesGcmTagLen64); bit_len = 64; + tag_len_set = launder32(tag_len_set) | kOtcryptoAesGcmTagLen64; break; case kOtcryptoAesGcmTagLen32: - HARDENED_CHECK_EQ(tag_len, kOtcryptoAesGcmTagLen32); bit_len = 32; + tag_len_set = launder32(tag_len_set) | kOtcryptoAesGcmTagLen32; break; default: // Invalid tag length. return OTCRYPTO_BAD_ARGS; } + // Check if we landed in the correct case statement. Use ORs for this to + // avoid that multiple cases were executed. + HARDENED_CHECK_EQ(launder32(tag_len_set), tag_len); HARDENED_CHECK_GT(bit_len, 0); HARDENED_CHECK_EQ(bit_len % 32, 0); From 5150427b917a4f5b6c04c3c1938652bfb0b0cadd Mon Sep 17 00:00:00 2001 From: Pascal Nasahl Date: Thu, 10 Jul 2025 15:13:38 +0200 Subject: [PATCH 34/51] [crypto] Add Verify-after-Sign for ECDSA as a FI countermeasure To mitigate fault injection attacks, this commit adds the new `otcrypto_ecdsa_p256_sign_verify()` and `otcrypto_ecdsa_p384_sign_verify()` functions to the CryptoLib. Before releasing the generated signatures, these functions first verify them. If the verification failed, the functions trap. Signed-off-by: Pascal Nasahl (cherry picked from commit 8a9b5a0e89c2d3ef02e59db040372b5e85f24243) --- sw/device/lib/crypto/impl/ecc_p256.c | 23 +++++++++++++++++++++ sw/device/lib/crypto/impl/ecc_p384.c | 23 +++++++++++++++++++++ sw/device/lib/crypto/include/ecc_p256.h | 27 +++++++++++++++++++++++-- sw/device/lib/crypto/include/ecc_p384.h | 27 +++++++++++++++++++++++-- 4 files changed, 96 insertions(+), 4 deletions(-) diff --git a/sw/device/lib/crypto/impl/ecc_p256.c b/sw/device/lib/crypto/impl/ecc_p256.c index a0dd1d3f94564..5fe4fd8cf4bb4 100644 --- a/sw/device/lib/crypto/impl/ecc_p256.c +++ b/sw/device/lib/crypto/impl/ecc_p256.c @@ -41,6 +41,29 @@ otcrypto_status_t otcrypto_ecdsa_p256_verify( verification_result); } +otcrypto_status_t otcrypto_ecdsa_p256_sign_verify( + const otcrypto_blinded_key_t *private_key, + const otcrypto_unblinded_key_t *public_key, + const otcrypto_hash_digest_t message_digest, + otcrypto_word32_buf_t signature) { + // Signature generation. + HARDENED_TRY( + otcrypto_ecdsa_p256_sign(private_key, message_digest, signature)); + + // Verify signature before releasing it. + otcrypto_const_word32_buf_t signature_check = { + .data = signature.data, + .len = signature.len, + }; + hardened_bool_t verification_result = kHardenedBoolFalse; + HARDENED_TRY(otcrypto_ecdsa_p256_verify( + public_key, message_digest, signature_check, &verification_result)); + + // Trap if signature verification failed. + HARDENED_CHECK_EQ(verification_result, kHardenedBoolTrue); + return OTCRYPTO_OK; +} + otcrypto_status_t otcrypto_ecdh_p256_keygen( otcrypto_blinded_key_t *private_key, otcrypto_unblinded_key_t *public_key) { HARDENED_TRY(otcrypto_ecdh_p256_keygen_async_start(private_key)); diff --git a/sw/device/lib/crypto/impl/ecc_p384.c b/sw/device/lib/crypto/impl/ecc_p384.c index c3218f8899188..e1047f88fa9c4 100644 --- a/sw/device/lib/crypto/impl/ecc_p384.c +++ b/sw/device/lib/crypto/impl/ecc_p384.c @@ -41,6 +41,29 @@ otcrypto_status_t otcrypto_ecdsa_p384_verify( verification_result); } +otcrypto_status_t otcrypto_ecdsa_p384_sign_verify( + const otcrypto_blinded_key_t *private_key, + const otcrypto_unblinded_key_t *public_key, + const otcrypto_hash_digest_t message_digest, + otcrypto_word32_buf_t signature) { + // Signature generation. + HARDENED_TRY( + otcrypto_ecdsa_p384_sign(private_key, message_digest, signature)); + + // Verify signature before releasing it. + otcrypto_const_word32_buf_t signature_check = { + .data = signature.data, + .len = signature.len, + }; + hardened_bool_t verification_result = kHardenedBoolFalse; + HARDENED_TRY(otcrypto_ecdsa_p384_verify( + public_key, message_digest, signature_check, &verification_result)); + + // Trap if signature verification failed. + HARDENED_CHECK_EQ(verification_result, kHardenedBoolTrue); + return OTCRYPTO_OK; +} + otcrypto_status_t otcrypto_ecdh_p384_keygen( otcrypto_blinded_key_t *private_key, otcrypto_unblinded_key_t *public_key) { HARDENED_TRY(otcrypto_ecdh_p384_keygen_async_start(private_key)); diff --git a/sw/device/lib/crypto/include/ecc_p256.h b/sw/device/lib/crypto/include/ecc_p256.h index d90697e966246..a3b69b0460b7c 100644 --- a/sw/device/lib/crypto/include/ecc_p256.h +++ b/sw/device/lib/crypto/include/ecc_p256.h @@ -40,7 +40,7 @@ otcrypto_status_t otcrypto_ecdsa_p256_keygen( * Generates an ECDSA signature with curve P-256. * * The message digest must be exactly 256 bits (32 bytes) long, but may use any - * hash mode. The caller is responsible for ensuring that the security + * hash mode. The caller is responsible for ensuring that the security * strength of the hash function is at least equal to the security strength of * the curve, but in some cases it may be truncated. See FIPS 186-5 for * details. @@ -56,11 +56,34 @@ otcrypto_status_t otcrypto_ecdsa_p256_sign( const otcrypto_hash_digest_t message_digest, otcrypto_word32_buf_t signature); +/** + * Generates an ECDSA signature with curve P-256 and verifies the signature + * before releasing it to mitigate fault injection attacks. + * + * The message digest must be exactly 256 bits (32 bytes) long, but may use any + * hash mode. The caller is responsible for ensuring that the security + * strength of the hash function is at least equal to the security strength of + * the curve, but in some cases it may be truncated. See FIPS 186-5 for + * details. + * + * @param private_key Pointer to the blinded private key (d) struct. + * @param public_key Pointer to the unblinded public key (Q) struct. + * @param message_digest Message digest to be signed (pre-hashed). + * @param[out] signature Pointer to the signature struct with (r,s) values. + * @return Result of the ECDSA signature generation. + */ +OT_WARN_UNUSED_RESULT +otcrypto_status_t otcrypto_ecdsa_p256_sign_verify( + const otcrypto_blinded_key_t *private_key, + const otcrypto_unblinded_key_t *public_key, + const otcrypto_hash_digest_t message_digest, + otcrypto_word32_buf_t signature); + /** * Verifies an ECDSA/P-256 signature. * * The message digest must be exactly 256 bits (32 bytes) long, but may use any - * hash mode. The caller is responsible for ensuring that the security + * hash mode. The caller is responsible for ensuring that the security * strength of the hash function is at least equal to the security strength of * the curve, but in some cases it may be truncated. See FIPS 186-5 for * details. diff --git a/sw/device/lib/crypto/include/ecc_p384.h b/sw/device/lib/crypto/include/ecc_p384.h index a6d0d9c3c63ec..96b84fa517a48 100644 --- a/sw/device/lib/crypto/include/ecc_p384.h +++ b/sw/device/lib/crypto/include/ecc_p384.h @@ -40,7 +40,7 @@ otcrypto_status_t otcrypto_ecdsa_p384_keygen( * Generates an ECDSA signature with curve P-384. * The message digest must be exactly 384 bits (48 bytes) long, but may use any - * hash mode. The caller is responsible for ensuring that the security + * hash mode. The caller is responsible for ensuring that the security * strength of the hash function is at least equal to the security strength of * the curve, but in some cases it may be truncated. See FIPS 186-5 for * details. @@ -56,11 +56,34 @@ otcrypto_status_t otcrypto_ecdsa_p384_sign( const otcrypto_hash_digest_t message_digest, otcrypto_word32_buf_t signature); +/** + * Generates an ECDSA signature with curve P-384 and verifies the signature + * before releasing it to mitigate fault injection attacks. + + * The message digest must be exactly 384 bits (48 bytes) long, but may use any + * hash mode. The caller is responsible for ensuring that the security + * strength of the hash function is at least equal to the security strength of + * the curve, but in some cases it may be truncated. See FIPS 186-5 for + * details. + * + * @param private_key Pointer to the blinded private key (d) struct. + * @param public_key Pointer to the unblinded public key (Q) struct. + * @param message_digest Message digest to be signed (pre-hashed). + * @param[out] signature Pointer to the signature struct with (r,s) values. + * @return Result of the ECDSA signature generation. + */ +OT_WARN_UNUSED_RESULT +otcrypto_status_t otcrypto_ecdsa_p384_sign_verify( + const otcrypto_blinded_key_t *private_key, + const otcrypto_unblinded_key_t *public_key, + const otcrypto_hash_digest_t message_digest, + otcrypto_word32_buf_t signature); + /** * Verifies an ECDSA/P-384 signature. * * The message digest must be exactly 384 bits (48 bytes) long, but may use any - * hash mode. The caller is responsible for ensuring that the security + * hash mode. The caller is responsible for ensuring that the security * strength of the hash function is at least equal to the security strength of * the curve, but in some cases it may be truncated. See FIPS 186-5 for * details. From d4c98894c5dd5a26db14da2a41b2224c15dce1d9 Mon Sep 17 00:00:00 2001 From: Pascal Nasahl Date: Thu, 10 Jul 2025 16:00:50 +0200 Subject: [PATCH 35/51] [cryptotest] Switch ecdsa_*_sign to ecdsa_*sign_verify Test the new sign_verify function instead of the sign functions. This is fine as interally sign_verify first also calls the same sign function. Signed-off-by: Pascal Nasahl (cherry picked from commit 2059925c84e92c4fbee3daa8245b6937ee68f5a4) --- .../tests/crypto/cryptotest/firmware/ecdsa.c | 18 ++++++++++-------- sw/device/tests/crypto/ecdsa_p256_functest.c | 4 ++-- .../crypto/ecdsa_p256_sideload_functest.c | 4 ++-- sw/device/tests/crypto/ecdsa_p384_functest.c | 4 ++-- .../crypto/ecdsa_p384_sideload_functest.c | 4 ++-- 5 files changed, 18 insertions(+), 16 deletions(-) diff --git a/sw/device/tests/crypto/cryptotest/firmware/ecdsa.c b/sw/device/tests/crypto/cryptotest/firmware/ecdsa.c index fe5b25c2e415f..5fe599d999d77 100644 --- a/sw/device/tests/crypto/cryptotest/firmware/ecdsa.c +++ b/sw/device/tests/crypto/cryptotest/firmware/ecdsa.c @@ -190,6 +190,7 @@ status_t interpret_verify_status(ujson_t *uj, otcrypto_status_t status, } status_t p256_sign(ujson_t *uj, cryptotest_ecdsa_private_key_t *uj_private_key, + otcrypto_unblinded_key_t *public_key, otcrypto_hash_digest_t message_digest, otcrypto_word32_buf_t signature_mut, cryptotest_ecdsa_signature_t *uj_signature) { @@ -205,8 +206,8 @@ status_t p256_sign(ujson_t *uj, cryptotest_ecdsa_private_key_t *uj_private_key, memcpy(private_key_masked.share1, uj_private_key->d1, kP256ScalarBytes); private_key.checksum = integrity_blinded_checksum(&private_key); - otcrypto_status_t status = - otcrypto_ecdsa_p256_sign(&private_key, message_digest, signature_mut); + otcrypto_status_t status = otcrypto_ecdsa_p256_sign_verify( + &private_key, public_key, message_digest, signature_mut); if (status.value != kOtcryptoStatusValueOk) { return INTERNAL(status.value); } @@ -225,6 +226,7 @@ status_t p256_sign(ujson_t *uj, cryptotest_ecdsa_private_key_t *uj_private_key, } status_t p384_sign(ujson_t *uj, cryptotest_ecdsa_private_key_t *uj_private_key, + otcrypto_unblinded_key_t *public_key, otcrypto_hash_digest_t message_digest, otcrypto_word32_buf_t signature_mut, cryptotest_ecdsa_signature_t *uj_signature) { @@ -240,8 +242,8 @@ status_t p384_sign(ujson_t *uj, cryptotest_ecdsa_private_key_t *uj_private_key, memcpy(private_key_masked.share1, uj_private_key->d1, kP384ScalarBytes); private_key.checksum = integrity_blinded_checksum(&private_key); - otcrypto_status_t status = - otcrypto_ecdsa_p384_sign(&private_key, message_digest, signature_mut); + otcrypto_status_t status = otcrypto_ecdsa_p384_sign_verify( + &private_key, public_key, message_digest, signature_mut); if (status.value != kOtcryptoStatusValueOk) { return INTERNAL(status.value); } @@ -353,12 +355,12 @@ status_t handle_ecdsa(ujson_t *uj) { case kCryptotestEcdsaOperationSign: { switch (uj_curve) { case kCryptotestEcdsaCurveP256: { - return p256_sign(uj, &uj_private_key, message_digest, signature_mut, - &uj_signature); + return p256_sign(uj, &uj_private_key, &public_key, message_digest, + signature_mut, &uj_signature); } case kCryptotestEcdsaCurveP384: { - return p384_sign(uj, &uj_private_key, message_digest, signature_mut, - &uj_signature); + return p384_sign(uj, &uj_private_key, &public_key, message_digest, + signature_mut, &uj_signature); } default: LOG_ERROR("Unsupported ECC curve: %d", uj_curve); diff --git a/sw/device/tests/crypto/ecdsa_p256_functest.c b/sw/device/tests/crypto/ecdsa_p256_functest.c index 20c1353138945..dedf074c565cf 100644 --- a/sw/device/tests/crypto/ecdsa_p256_functest.c +++ b/sw/device/tests/crypto/ecdsa_p256_functest.c @@ -71,8 +71,8 @@ status_t sign_then_verify_test(hardened_bool_t *verification_result) { // Generate a signature for the message. LOG_INFO("Signing..."); - CHECK_STATUS_OK(otcrypto_ecdsa_p256_sign( - &private_key, msg_digest, + CHECK_STATUS_OK(otcrypto_ecdsa_p256_sign_verify( + &private_key, &public_key, msg_digest, (otcrypto_word32_buf_t){.data = sig, .len = ARRAYSIZE(sig)})); // Verify the signature. diff --git a/sw/device/tests/crypto/ecdsa_p256_sideload_functest.c b/sw/device/tests/crypto/ecdsa_p256_sideload_functest.c index 5d5f90a6cf7f4..e63afce4b9f14 100644 --- a/sw/device/tests/crypto/ecdsa_p256_sideload_functest.c +++ b/sw/device/tests/crypto/ecdsa_p256_sideload_functest.c @@ -86,8 +86,8 @@ status_t sign_then_verify_test(void) { // Generate a signature for the message. LOG_INFO("Signing..."); - CHECK_STATUS_OK(otcrypto_ecdsa_p256_sign( - &private_key, message_digest, + CHECK_STATUS_OK(otcrypto_ecdsa_p256_sign_verify( + &private_key, &public_key, message_digest, (otcrypto_word32_buf_t){.data = sig, .len = ARRAYSIZE(sig)})); // Verify the signature. diff --git a/sw/device/tests/crypto/ecdsa_p384_functest.c b/sw/device/tests/crypto/ecdsa_p384_functest.c index 294ba92e69712..f69bee8aafc26 100644 --- a/sw/device/tests/crypto/ecdsa_p384_functest.c +++ b/sw/device/tests/crypto/ecdsa_p384_functest.c @@ -71,8 +71,8 @@ status_t sign_then_verify_test(hardened_bool_t *verification_result) { // Generate a signature for the message. LOG_INFO("Signing..."); - CHECK_STATUS_OK(otcrypto_ecdsa_p384_sign( - &private_key, msg_digest, + CHECK_STATUS_OK(otcrypto_ecdsa_p384_sign_verify( + &private_key, &public_key, msg_digest, (otcrypto_word32_buf_t){.data = sig, .len = ARRAYSIZE(sig)})); // Verify the signature. diff --git a/sw/device/tests/crypto/ecdsa_p384_sideload_functest.c b/sw/device/tests/crypto/ecdsa_p384_sideload_functest.c index 72a89875f8c67..781ddfa36ce0c 100644 --- a/sw/device/tests/crypto/ecdsa_p384_sideload_functest.c +++ b/sw/device/tests/crypto/ecdsa_p384_sideload_functest.c @@ -84,8 +84,8 @@ status_t sign_then_verify_test(void) { // Generate a signature for the message. LOG_INFO("Signing..."); - CHECK_STATUS_OK(otcrypto_ecdsa_p384_sign( - &private_key, msg_digest, + CHECK_STATUS_OK(otcrypto_ecdsa_p384_sign_verify( + &private_key, &public_key, msg_digest, (otcrypto_word32_buf_t){.data = sig, .len = ARRAYSIZE(sig)})); // Verify the signature. From 68283a42c6aa567ba09b3352c986d255aa1a542f Mon Sep 17 00:00:00 2001 From: Pascal Nasahl Date: Fri, 11 Jul 2025 11:34:26 +0200 Subject: [PATCH 36/51] [pentest] Switch ecdsa_*_sign to ecdsa_*sign_verify Use the FI hardened version of P256 and P384 ECDSA sign that performs a verification after the signing. Signed-off-by: Pascal Nasahl (cherry picked from commit d8a16feda4cc8e94dc0cee38ad48295f4e0440f7) --- .../firmware/fi/cryptolib_fi_asym_impl.c | 70 ++++++++----------- .../firmware/sca/cryptolib_sca_asym_impl.c | 70 ++++++++----------- 2 files changed, 56 insertions(+), 84 deletions(-) diff --git a/sw/device/tests/penetrationtests/firmware/fi/cryptolib_fi_asym_impl.c b/sw/device/tests/penetrationtests/firmware/fi/cryptolib_fi_asym_impl.c index 1c41e175af36b..1d14302ebc433 100644 --- a/sw/device/tests/penetrationtests/firmware/fi/cryptolib_fi_asym_impl.c +++ b/sw/device/tests/penetrationtests/firmware/fi/cryptolib_fi_asym_impl.c @@ -679,26 +679,18 @@ status_t cryptolib_fi_p256_sign_impl( memset(private_key_masked.share1, 0, kP256MaskedScalarShareBytes); private_key.checksum = integrity_blinded_checksum(&private_key); - // Allocate space for a public key. - uint32_t pk[kPentestP256Words * 2] = {0}; + // Create the public key. + p256_point_t pub_p256; otcrypto_unblinded_key_t public_key = { .key_mode = kOtcryptoKeyModeEcdsaP256, - .key_length = sizeof(pk), - .key = pk, + .key_length = sizeof(p256_point_t), + .key = (uint32_t *)&pub_p256, }; - - // Create a key pair if requested. - if (uj_input.cfg == 1) { - // Trigger window 0. - if (uj_input.trigger == 0) { - pentest_set_trigger_high(); - } - TRY(otcrypto_ecdsa_p256_keygen(&private_key, &public_key)); - pentest_set_trigger_low(); - if (uj_input.trigger == 0) { - pentest_set_trigger_low(); - } - } + memset(pub_p256.x, 0, kP256CoordBytes); + memcpy(pub_p256.x, uj_input.pubx, P256_CMD_BYTES); + memset(pub_p256.y, 0, kP256CoordBytes); + memcpy(pub_p256.y, uj_input.puby, P256_CMD_BYTES); + public_key.checksum = integrity_unblinded_checksum(&public_key); // Set up the message buffer. uint32_t message_buf[kPentestP256Words]; @@ -720,7 +712,8 @@ status_t cryptolib_fi_p256_sign_impl( // Trigger window. pentest_set_trigger_high(); - TRY(otcrypto_ecdsa_p256_sign(&private_key, message_digest, signature_mut)); + TRY(otcrypto_ecdsa_p256_sign_verify(&private_key, &public_key, message_digest, + signature_mut)); pentest_set_trigger_low(); // Return data back to host. @@ -733,9 +726,9 @@ status_t cryptolib_fi_p256_sign_impl( memcpy(uj_output->s, signature_p256->s, kP256ScalarBytes); // Return the public key. - p256_point_t *pub_p256 = (p256_point_t *)public_key.key; - memcpy(uj_output->pubx, pub_p256->x, P256_CMD_BYTES); - memcpy(uj_output->puby, pub_p256->y, P256_CMD_BYTES); + p256_point_t *pub = (p256_point_t *)public_key.key; + memcpy(uj_output->pubx, pub->x, P256_CMD_BYTES); + memcpy(uj_output->puby, pub->y, P256_CMD_BYTES); return OK_STATUS(); } @@ -893,26 +886,18 @@ status_t cryptolib_fi_p384_sign_impl( memset(private_key_masked.share1, 0, kP384MaskedScalarShareBytes); private_key.checksum = integrity_blinded_checksum(&private_key); - // Allocate space for a public key. - uint32_t pk[kPentestP384Words * 2] = {0}; + // Create the public key. + p384_point_t pub_p384; otcrypto_unblinded_key_t public_key = { .key_mode = kOtcryptoKeyModeEcdsaP384, - .key_length = sizeof(pk), - .key = pk, + .key_length = sizeof(p384_point_t), + .key = (uint32_t *)&pub_p384, }; - - // Create a key pair if requested. - if (uj_input.cfg == 1) { - // Trigger window 0. - if (uj_input.trigger == 0) { - pentest_set_trigger_high(); - } - TRY(otcrypto_ecdsa_p384_keygen(&private_key, &public_key)); - pentest_set_trigger_low(); - if (uj_input.trigger == 0) { - pentest_set_trigger_low(); - } - } + memset(pub_p384.x, 0, kP384CoordBytes); + memcpy(pub_p384.x, uj_input.pubx, P384_CMD_BYTES); + memset(pub_p384.y, 0, kP384CoordBytes); + memcpy(pub_p384.y, uj_input.puby, P384_CMD_BYTES); + public_key.checksum = integrity_unblinded_checksum(&public_key); // Set up the message buffer. uint32_t message_buf[kPentestP384Words]; @@ -934,7 +919,8 @@ status_t cryptolib_fi_p384_sign_impl( // Trigger window. pentest_set_trigger_high(); - TRY(otcrypto_ecdsa_p384_sign(&private_key, message_digest, signature_mut)); + TRY(otcrypto_ecdsa_p384_sign_verify(&private_key, &public_key, message_digest, + signature_mut)); pentest_set_trigger_low(); // Return data back to host. @@ -947,9 +933,9 @@ status_t cryptolib_fi_p384_sign_impl( memcpy(uj_output->s, signature_p384->s, kP384ScalarBytes); // Return the public key. - p384_point_t *pub_p384 = (p384_point_t *)public_key.key; - memcpy(uj_output->pubx, pub_p384->x, P384_CMD_BYTES); - memcpy(uj_output->puby, pub_p384->y, P384_CMD_BYTES); + p384_point_t *pub = (p384_point_t *)public_key.key; + memcpy(uj_output->pubx, pub->x, P384_CMD_BYTES); + memcpy(uj_output->puby, pub->y, P384_CMD_BYTES); return OK_STATUS(); } diff --git a/sw/device/tests/penetrationtests/firmware/sca/cryptolib_sca_asym_impl.c b/sw/device/tests/penetrationtests/firmware/sca/cryptolib_sca_asym_impl.c index bc37b05d25c1c..53b7171956085 100644 --- a/sw/device/tests/penetrationtests/firmware/sca/cryptolib_sca_asym_impl.c +++ b/sw/device/tests/penetrationtests/firmware/sca/cryptolib_sca_asym_impl.c @@ -466,26 +466,18 @@ status_t cryptolib_sca_p256_sign_impl( memset(private_key_masked.share1, 0, kP256MaskedScalarShareBytes); private_key.checksum = integrity_blinded_checksum(&private_key); - // Allocate space for a public key. - uint32_t pk[kPentestP256Words * 2] = {0}; + // Create the public key. + p256_point_t pub_p256; otcrypto_unblinded_key_t public_key = { .key_mode = kOtcryptoKeyModeEcdsaP256, - .key_length = sizeof(pk), - .key = pk, + .key_length = sizeof(p256_point_t), + .key = (uint32_t *)&pub_p256, }; - - // Create a key pair if requested. - if (uj_input.cfg == 1) { - // Trigger window 0. - if (uj_input.trigger == 0) { - pentest_set_trigger_high(); - } - TRY(otcrypto_ecdsa_p256_keygen(&private_key, &public_key)); - pentest_set_trigger_low(); - if (uj_input.trigger == 0) { - pentest_set_trigger_low(); - } - } + memset(pub_p256.x, 0, kP256CoordBytes); + memcpy(pub_p256.x, uj_input.pubx, P256_CMD_BYTES); + memset(pub_p256.y, 0, kP256CoordBytes); + memcpy(pub_p256.y, uj_input.puby, P256_CMD_BYTES); + public_key.checksum = integrity_unblinded_checksum(&public_key); // Set up the message buffer. uint32_t message_buf[kPentestP256Words]; @@ -507,7 +499,8 @@ status_t cryptolib_sca_p256_sign_impl( // Trigger window 1. pentest_set_trigger_high(); - TRY(otcrypto_ecdsa_p256_sign(&private_key, message_digest, signature_mut)); + TRY(otcrypto_ecdsa_p256_sign_verify(&private_key, &public_key, message_digest, + signature_mut)); pentest_set_trigger_low(); // Return data back to host. @@ -520,9 +513,9 @@ status_t cryptolib_sca_p256_sign_impl( memcpy(uj_output->s, signature_p256->s, kP256ScalarBytes); // Return the public key. - p256_point_t *pub_p256 = (p256_point_t *)public_key.key; - memcpy(uj_output->pubx, pub_p256->x, P256_CMD_BYTES); - memcpy(uj_output->puby, pub_p256->y, P256_CMD_BYTES); + p256_point_t *pub = (p256_point_t *)public_key.key; + memcpy(uj_output->pubx, pub->x, P256_CMD_BYTES); + memcpy(uj_output->puby, pub->y, P256_CMD_BYTES); return OK_STATUS(); } @@ -627,26 +620,18 @@ status_t cryptolib_sca_p384_sign_impl( memset(private_key_masked.share1, 0, kP384MaskedScalarShareBytes); private_key.checksum = integrity_blinded_checksum(&private_key); - // Allocate space for a public key. - uint32_t pk[kPentestP384Words * 2] = {0}; + // Create the public key. + p384_point_t pub_p384; otcrypto_unblinded_key_t public_key = { .key_mode = kOtcryptoKeyModeEcdsaP384, - .key_length = sizeof(pk), - .key = pk, + .key_length = sizeof(p384_point_t), + .key = (uint32_t *)&pub_p384, }; - - // Create a key pair if requested. - if (uj_input.cfg == 1) { - // Trigger window 0. - if (uj_input.trigger == 0) { - pentest_set_trigger_high(); - } - TRY(otcrypto_ecdsa_p384_keygen(&private_key, &public_key)); - pentest_set_trigger_low(); - if (uj_input.trigger == 0) { - pentest_set_trigger_low(); - } - } + memset(pub_p384.x, 0, kP384CoordBytes); + memcpy(pub_p384.x, uj_input.pubx, P384_CMD_BYTES); + memset(pub_p384.y, 0, kP384CoordBytes); + memcpy(pub_p384.y, uj_input.puby, P384_CMD_BYTES); + public_key.checksum = integrity_unblinded_checksum(&public_key); // Set up the message buffer. uint32_t message_buf[kPentestP384Words]; @@ -668,7 +653,8 @@ status_t cryptolib_sca_p384_sign_impl( // Trigger window. pentest_set_trigger_high(); - TRY(otcrypto_ecdsa_p384_sign(&private_key, message_digest, signature_mut)); + TRY(otcrypto_ecdsa_p384_sign_verify(&private_key, &public_key, message_digest, + signature_mut)); pentest_set_trigger_low(); // Return data back to host. @@ -681,9 +667,9 @@ status_t cryptolib_sca_p384_sign_impl( memcpy(uj_output->s, signature_p384->s, kP384ScalarBytes); // Return the public key. - p384_point_t *pub_p384 = (p384_point_t *)public_key.key; - memcpy(uj_output->pubx, pub_p384->x, P384_CMD_BYTES); - memcpy(uj_output->puby, pub_p384->y, P384_CMD_BYTES); + p384_point_t *pub = (p384_point_t *)public_key.key; + memcpy(uj_output->pubx, pub->x, P384_CMD_BYTES); + memcpy(uj_output->puby, pub->y, P384_CMD_BYTES); return OK_STATUS(); } From 06fe52fff4cb17eedaea9b9e589b58a5bbd6bc4f Mon Sep 17 00:00:00 2001 From: Pascal Nasahl Date: Fri, 11 Jul 2025 09:40:48 +0200 Subject: [PATCH 37/51] [pentest] Add KeyGen to P256/P384 Sign When the host sets cfg == 1, the P256/P384 Sign routines perform a keygen. Then, the new keypair is used instead of the provided key. The public key is returned back to the host. Signed-off-by: Pascal Nasahl (cherry picked from commit 8cb2871dde0605cba561af532a59ddf03caf0a7b) --- .../firmware/fi/cryptolib_fi_asym_impl.c | 49 ++++++++++++++++--- .../firmware/sca/cryptolib_sca_asym_impl.c | 46 +++++++++++++++-- .../testvectors/data/fi_asym_cryptolib.json | 4 +- .../testvectors/data/sca_asym_cryptolib.json | 4 +- 4 files changed, 88 insertions(+), 15 deletions(-) diff --git a/sw/device/tests/penetrationtests/firmware/fi/cryptolib_fi_asym_impl.c b/sw/device/tests/penetrationtests/firmware/fi/cryptolib_fi_asym_impl.c index 1d14302ebc433..c309e9011f9a7 100644 --- a/sw/device/tests/penetrationtests/firmware/fi/cryptolib_fi_asym_impl.c +++ b/sw/device/tests/penetrationtests/firmware/fi/cryptolib_fi_asym_impl.c @@ -692,6 +692,20 @@ status_t cryptolib_fi_p256_sign_impl( memcpy(pub_p256.y, uj_input.puby, P256_CMD_BYTES); public_key.checksum = integrity_unblinded_checksum(&public_key); + // Create a key pair if requested. + // This will overwrite the private and public key above. + if (uj_input.cfg == 1) { + // Trigger window 0. + if (uj_input.trigger == 0) { + pentest_set_trigger_high(); + } + TRY(otcrypto_ecdsa_p256_keygen(&private_key, &public_key)); + pentest_set_trigger_low(); + if (uj_input.trigger == 0) { + pentest_set_trigger_low(); + } + } + // Set up the message buffer. uint32_t message_buf[kPentestP256Words]; memset(message_buf, 0, sizeof(message_buf)); @@ -710,11 +724,16 @@ status_t cryptolib_fi_p256_sign_impl( .len = ARRAYSIZE(sig), }; - // Trigger window. - pentest_set_trigger_high(); + // Trigger window 1. + if (uj_input.trigger == 1) { + pentest_set_trigger_high(); + } + // Sign the message. TRY(otcrypto_ecdsa_p256_sign_verify(&private_key, &public_key, message_digest, signature_mut)); - pentest_set_trigger_low(); + if (uj_input.trigger == 1) { + pentest_set_trigger_low(); + } // Return data back to host. uj_output->cfg = 0; @@ -899,6 +918,20 @@ status_t cryptolib_fi_p384_sign_impl( memcpy(pub_p384.y, uj_input.puby, P384_CMD_BYTES); public_key.checksum = integrity_unblinded_checksum(&public_key); + // Create a key pair if requested. + // This will overwrite the private and public key above. + if (uj_input.cfg == 1) { + // Trigger window 0. + if (uj_input.trigger == 0) { + pentest_set_trigger_high(); + } + TRY(otcrypto_ecdsa_p384_keygen(&private_key, &public_key)); + pentest_set_trigger_low(); + if (uj_input.trigger == 0) { + pentest_set_trigger_low(); + } + } + // Set up the message buffer. uint32_t message_buf[kPentestP384Words]; memset(message_buf, 0, sizeof(message_buf)); @@ -917,11 +950,15 @@ status_t cryptolib_fi_p384_sign_impl( .len = ARRAYSIZE(sig), }; - // Trigger window. - pentest_set_trigger_high(); + // Trigger window 1. + if (uj_input.trigger == 1) { + pentest_set_trigger_high(); + } TRY(otcrypto_ecdsa_p384_sign_verify(&private_key, &public_key, message_digest, signature_mut)); - pentest_set_trigger_low(); + if (uj_input.trigger == 1) { + pentest_set_trigger_low(); + } // Return data back to host. uj_output->cfg = 0; diff --git a/sw/device/tests/penetrationtests/firmware/sca/cryptolib_sca_asym_impl.c b/sw/device/tests/penetrationtests/firmware/sca/cryptolib_sca_asym_impl.c index 53b7171956085..f0a40fa031a18 100644 --- a/sw/device/tests/penetrationtests/firmware/sca/cryptolib_sca_asym_impl.c +++ b/sw/device/tests/penetrationtests/firmware/sca/cryptolib_sca_asym_impl.c @@ -479,6 +479,20 @@ status_t cryptolib_sca_p256_sign_impl( memcpy(pub_p256.y, uj_input.puby, P256_CMD_BYTES); public_key.checksum = integrity_unblinded_checksum(&public_key); + // Create a key pair if requested. + // This will overwrite the private and public key above. + if (uj_input.cfg == 1) { + // Trigger window 0. + if (uj_input.trigger == 0) { + pentest_set_trigger_high(); + } + TRY(otcrypto_ecdsa_p256_keygen(&private_key, &public_key)); + pentest_set_trigger_low(); + if (uj_input.trigger == 0) { + pentest_set_trigger_low(); + } + } + // Set up the message buffer. uint32_t message_buf[kPentestP256Words]; memset(message_buf, 0, sizeof(message_buf)); @@ -498,10 +512,14 @@ status_t cryptolib_sca_p256_sign_impl( }; // Trigger window 1. - pentest_set_trigger_high(); + if (uj_input.trigger == 1) { + pentest_set_trigger_high(); + } TRY(otcrypto_ecdsa_p256_sign_verify(&private_key, &public_key, message_digest, signature_mut)); - pentest_set_trigger_low(); + if (uj_input.trigger == 1) { + pentest_set_trigger_low(); + } // Return data back to host. uj_output->cfg = 0; @@ -633,6 +651,20 @@ status_t cryptolib_sca_p384_sign_impl( memcpy(pub_p384.y, uj_input.puby, P384_CMD_BYTES); public_key.checksum = integrity_unblinded_checksum(&public_key); + // Create a key pair if requested. + // This will overwrite the private and public key above. + if (uj_input.cfg == 1) { + // Trigger window 0. + if (uj_input.trigger == 0) { + pentest_set_trigger_high(); + } + TRY(otcrypto_ecdsa_p384_keygen(&private_key, &public_key)); + pentest_set_trigger_low(); + if (uj_input.trigger == 0) { + pentest_set_trigger_low(); + } + } + // Set up the message buffer. uint32_t message_buf[kPentestP384Words]; memset(message_buf, 0, sizeof(message_buf)); @@ -651,11 +683,15 @@ status_t cryptolib_sca_p384_sign_impl( .len = ARRAYSIZE(sig), }; - // Trigger window. - pentest_set_trigger_high(); + // Trigger window 1. + if (uj_input.trigger == 1) { + pentest_set_trigger_high(); + } TRY(otcrypto_ecdsa_p384_sign_verify(&private_key, &public_key, message_digest, signature_mut)); - pentest_set_trigger_low(); + if (uj_input.trigger == 1) { + pentest_set_trigger_low(); + } // Return data back to host. uj_output->cfg = 0; diff --git a/sw/host/penetrationtests/testvectors/data/fi_asym_cryptolib.json b/sw/host/penetrationtests/testvectors/data/fi_asym_cryptolib.json index 7d36a8a6b8b7a..bc4ea67fa05ee 100644 --- a/sw/host/penetrationtests/testvectors/data/fi_asym_cryptolib.json +++ b/sw/host/penetrationtests/testvectors/data/fi_asym_cryptolib.json @@ -73,7 +73,7 @@ { "test_case_id": 9, "command": "P256Sign", - "input": "{\"scalar\": [240,45,145,122,29,206,131,106,94,161,109,193,195,45,18,242,85,226,125,202,25,195,63,78,53,21,158,78,28,204,101,150], \"pubx\": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], \"puby\": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], \"message\": [1194,91,234,230,56,255,141,205,55,14,3,166,248,156,89,76,85,190,209,39,126,225,77,131,187,176,239,120,58,5,23,199], \"cfg\": 0, \"trigger\": 0}", + "input": "{\"scalar\": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], \"pubx\": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], \"puby\": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], \"message\": [1194,91,234,230,56,255,141,205,55,14,3,166,248,156,89,76,85,190,209,39,126,225,77,131,187,176,239,120,58,5,23,199], \"cfg\": 1, \"trigger\": 0}", "expected_output": [ "{\"status\":0, \"r\": [0], \"s\": [0], \"pubx\": [0], \"puby\": [0], \"cfg\": 0}" ] @@ -113,7 +113,7 @@ { "test_case_id": 14, "command": "P384Sign", - "input": "{\"scalar\": [79,23,6,68,39,20,240,136,77,158,31,9,214,176,7,116,188,27,87,246,120,46,150,173,248,118,125,25,54,24,59,136,52,94,113,112,248,157,196,94,226,243,220,105,77,5,109,75], \"pubx\": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], \"puby\": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], \"message\": [249,177,39,240,216,30,188,209,123,123,160,234,19,28,102,13,52,11,5,206,85,124,130,22,14,15,121,61,224,125,56,23,144,35,148,40,113,172,183,0,45,250,253,255,252,141,234,206], \"cfg\": 0, \"trigger\": 1}", + "input": "{\"scalar\": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], \"pubx\": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], \"puby\": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], \"message\": [249,177,39,240,216,30,188,209,123,123,160,234,19,28,102,13,52,11,5,206,85,124,130,22,14,15,121,61,224,125,56,23,144,35,148,40,113,172,183,0,45,250,253,255,252,141,234,206], \"cfg\": 1, \"trigger\": 1}", "expected_output": [ "{\"status\":0, \"r\": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], \"s\": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], \"pubx\": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], \"puby\": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], \"cfg\": 0}" ] diff --git a/sw/host/penetrationtests/testvectors/data/sca_asym_cryptolib.json b/sw/host/penetrationtests/testvectors/data/sca_asym_cryptolib.json index 683fc7bfcda36..092c02419025f 100644 --- a/sw/host/penetrationtests/testvectors/data/sca_asym_cryptolib.json +++ b/sw/host/penetrationtests/testvectors/data/sca_asym_cryptolib.json @@ -70,7 +70,7 @@ { "test_case_id": 9, "command": "P256Sign", - "input": "{\"scalar\": [252, 193, 29, 106, 119, 65, 232, 228, 197, 32, 209, 68, 87, 194, 158, 104, 210, 136, 182, 102, 211, 128, 157, 43, 41, 70, 247, 236, 240, 17, 28, 116], \"pubx\": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], \"puby\": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], \"message\": [136,252,30,125,132,151,148,252,81,177,53,250,19,93,238,192,219,2,184,108,60,216,206,189,170,121,232,104,158,91,40,152], \"cfg\": 0, \"trigger\": 0}", + "input": "{\"scalar\": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], \"pubx\": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], \"puby\": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], \"message\": [136,252,30,125,132,151,148,252,81,177,53,250,19,93,238,192,219,2,184,108,60,216,206,189,170,121,232,104,158,91,40,152], \"cfg\": 1, \"trigger\": 0}", "expected_output": [ "{\"status\":0, \"r\": [0], \"s\": [0], \"pubx\": [0], \"puby\": [0], \"cfg\": 0}" ] @@ -110,7 +110,7 @@ { "test_case_id": 14, "command": "P384Sign", - "input": "{\"scalar\": [79,23,6,68,39,20,240,136,77,158,31,9,214,176,7,116,188,27,87,246,120,46,150,173,248,118,125,25,54,24,59,136,52,94,113,112,248,157,196,94,226,243,220,105,77,5,109,75], \"pubx\": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], \"puby\": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], \"message\": [249,177,39,240,216,30,188,209,123,123,160,234,19,28,102,13,52,11,5,206,85,124,130,22,14,15,121,61,224,125,56,23,144,35,148,40,113,172,183,0,45,250,253,255,252,141,234,206], \"cfg\": 0, \"trigger\": 1}", + "input": "{\"scalar\": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], \"pubx\": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], \"puby\": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], \"message\": [249,177,39,240,216,30,188,209,123,123,160,234,19,28,102,13,52,11,5,206,85,124,130,22,14,15,121,61,224,125,56,23,144,35,148,40,113,172,183,0,45,250,253,255,252,141,234,206], \"cfg\": 1, \"trigger\": 1}", "expected_output": [ "{\"status\":0, \"r\": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], \"s\": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], \"pubx\": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], \"puby\": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], \"cfg\": 0}" ] From 7d850f334765aa53829a65463cf55b4a91e027ef Mon Sep 17 00:00:00 2001 From: Pascal Nasahl Date: Thu, 14 Aug 2025 13:40:17 +0200 Subject: [PATCH 38/51] [crypto] Add new P256/P384 API calls to the API documentation Signed-off-by: Pascal Nasahl (cherry picked from commit 3f0105886eca710113c4f10a9667978bc049a6d3) --- doc/security/cryptolib/cryptolib_api.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/security/cryptolib/cryptolib_api.md b/doc/security/cryptolib/cryptolib_api.md index b1f98660ae2c1..dce876937b6e0 100644 --- a/doc/security/cryptolib/cryptolib_api.md +++ b/doc/security/cryptolib/cryptolib_api.md @@ -357,10 +357,12 @@ For ECDSA, the cryptography library supports keypair generation, signing, and si {{#header-snippet sw/device/lib/crypto/include/ecc_p256.h otcrypto_ecdsa_p256_keygen }} {{#header-snippet sw/device/lib/crypto/include/ecc_p256.h otcrypto_ecdsa_p256_sign }} +{{#header-snippet sw/device/lib/crypto/include/ecc_p256.h otcrypto_ecdsa_p256_sign_verify }} {{#header-snippet sw/device/lib/crypto/include/ecc_p256.h otcrypto_ecdsa_p256_verify }} {{#header-snippet sw/device/lib/crypto/include/ecc_p384.h otcrypto_ecdsa_p384_keygen }} {{#header-snippet sw/device/lib/crypto/include/ecc_p384.h otcrypto_ecdsa_p384_sign }} +{{#header-snippet sw/device/lib/crypto/include/ecc_p384.h otcrypto_ecdsa_p384_sign_verify }} {{#header-snippet sw/device/lib/crypto/include/ecc_p384.h otcrypto_ecdsa_p384_verify }} #### ECDH From 2bb3865d6a76dffcb3d0248346a8379010c0af96 Mon Sep 17 00:00:00 2001 From: Pascal Nasahl Date: Tue, 15 Jul 2025 15:45:13 +0200 Subject: [PATCH 39/51] [crypto] Harden AES-GCM against FI This commit adds additional hardening against FI to AES-GCM. When setting the key security_level > low, the AES operation performed by the AES IP block is checked by recomputing the input. Signed-off-by: Pascal Nasahl (cherry picked from commit 27b6844c3e9ddfadda862d61c52cdc40a79af9c1) --- sw/device/lib/crypto/impl/aes_gcm.c | 2 + sw/device/lib/crypto/impl/aes_gcm/BUILD | 1 + sw/device/lib/crypto/impl/aes_gcm/aes_gcm.c | 73 ++++++++++++++++----- sw/device/lib/crypto/impl/aes_gcm/aes_gcm.h | 5 ++ sw/device/lib/crypto/include/aes_gcm.h | 2 +- 5 files changed, 64 insertions(+), 19 deletions(-) diff --git a/sw/device/lib/crypto/impl/aes_gcm.c b/sw/device/lib/crypto/impl/aes_gcm.c index 4ef1767694fb2..36af807c06892 100644 --- a/sw/device/lib/crypto/impl/aes_gcm.c +++ b/sw/device/lib/crypto/impl/aes_gcm.c @@ -384,6 +384,7 @@ otcrypto_status_t otcrypto_aes_gcm_encrypt_init( // Call the internal init operation. aes_gcm_context_t internal_ctx; + internal_ctx.security_level = key->config.security_level; HARDENED_TRY(aes_gcm_encrypt_init(aes_key, iv.len, iv.data, &internal_ctx)); // Save the context and clear the key if needed. @@ -409,6 +410,7 @@ otcrypto_status_t otcrypto_aes_gcm_decrypt_init( // Call the internal init operation. aes_gcm_context_t internal_ctx; + internal_ctx.security_level = key->config.security_level; HARDENED_TRY(aes_gcm_decrypt_init(aes_key, iv.len, iv.data, &internal_ctx)); // Save the context and clear the key if needed. diff --git a/sw/device/lib/crypto/impl/aes_gcm/BUILD b/sw/device/lib/crypto/impl/aes_gcm/BUILD index 272575a4a18ad..12d5e4194685a 100644 --- a/sw/device/lib/crypto/impl/aes_gcm/BUILD +++ b/sw/device/lib/crypto/impl/aes_gcm/BUILD @@ -15,6 +15,7 @@ cc_library( "//sw/device/lib/base:memory", "//sw/device/lib/crypto/drivers:aes", "//sw/device/lib/crypto/drivers:rv_core_ibex", + "//sw/device/lib/crypto/include:datatypes", ], ) diff --git a/sw/device/lib/crypto/impl/aes_gcm/aes_gcm.c b/sw/device/lib/crypto/impl/aes_gcm/aes_gcm.c index 61565aaf5a184..c58279b2610a9 100644 --- a/sw/device/lib/crypto/impl/aes_gcm/aes_gcm.c +++ b/sw/device/lib/crypto/impl/aes_gcm/aes_gcm.c @@ -61,13 +61,38 @@ static inline void block_inc32(aes_block_t *block) { * One-shot version of the AES encryption API for a single block. */ OT_WARN_UNUSED_RESULT -static status_t aes_encrypt_block(const aes_key_t key, const aes_block_t *iv, - const aes_block_t *input, - aes_block_t *output) { - HARDENED_TRY(aes_encrypt_begin(key, iv)); - HARDENED_TRY(aes_update(/*dest=*/NULL, input)); - HARDENED_TRY(aes_update(output, /*src=*/NULL)); - return aes_end(NULL); +static status_t aes_encrypt_block( + const aes_key_t key, const aes_block_t *iv, const aes_block_t *input, + aes_block_t *output, otcrypto_key_security_level_t security_level) { + if (launder32(security_level) == kOtcryptoKeySecurityLevelLow) { + HARDENED_CHECK_EQ(security_level, kOtcryptoKeySecurityLevelLow); + // No additional FI protection. + HARDENED_TRY(aes_encrypt_begin(key, iv)); + HARDENED_TRY(aes_update(/*dest=*/NULL, input)); + HARDENED_TRY(aes_update(output, /*src=*/NULL)); + return aes_end(NULL); + } else { + HARDENED_CHECK_NE(security_level, kOtcryptoKeySecurityLevelLow); + // Additional FI hardening. + // First AES operation. Encrypt the input. + HARDENED_TRY(aes_encrypt_begin(key, iv)); + HARDENED_TRY(aes_update(/*dest=*/NULL, input)); + HARDENED_TRY(aes_update(output, /*src=*/NULL)); + + // Second AES operation. Decrypt the output of the first AES operation and + // check whether the same input is retrieved. + aes_block_t input_recalculated = (aes_block_t){.data = {0}}; + HARDENED_TRY(aes_decrypt_begin(key, iv)); + HARDENED_TRY(aes_update(/*dest=*/NULL, output)); + HARDENED_TRY(aes_update(&input_recalculated, /*src=*/NULL)); + HARDENED_TRY(aes_end(NULL)); + + HARDENED_CHECK_EQ( + hardened_memeq((const uint32_t *)input_recalculated.data, + (const uint32_t *)input->data, kAesBlockNumWords), + kHardenedBoolTrue); + return OTCRYPTO_OK; + } } /** @@ -78,12 +103,15 @@ static status_t aes_encrypt_block(const aes_key_t key, const aes_block_t *iv, * @param key The AES key * @param iv Initialization vector, 128 bits * @param input Input block + * @param security_level Security level configuration * @param[out] output Output block */ OT_WARN_UNUSED_RESULT static status_t gctr_process_block(const aes_key_t key, aes_block_t *iv, - aes_block_t *input, aes_block_t *output) { - HARDENED_TRY(aes_encrypt_block(key, iv, input, output)); + aes_block_t *input, + otcrypto_key_security_level_t security_level, + aes_block_t *output) { + HARDENED_TRY(aes_encrypt_block(key, iv, input, output, security_level)); block_inc32(iv); return OTCRYPTO_OK; } @@ -114,6 +142,7 @@ static status_t gctr_process_block(const aes_key_t key, aes_block_t *iv, * @param partial Partial AES block. * @param input_len Number of bytes for input and output * @param input Pointer to input buffer (may be NULL if `len` is 0) + * @param security_level Security level of the key * @param[out] output_len Number of output bytes written * @param[out] output Pointer to output buffer */ @@ -121,6 +150,7 @@ OT_WARN_UNUSED_RESULT static status_t aes_gcm_gctr(const aes_key_t key, aes_block_t *iv, size_t partial_len, aes_block_t *partial, size_t input_len, const uint8_t *input, + otcrypto_key_security_level_t security_level, size_t *output_len, uint8_t *output) { // Key must be intended for CTR mode. if (key.mode != kAesCipherModeCtr) { @@ -141,7 +171,8 @@ static status_t aes_gcm_gctr(const aes_key_t key, aes_block_t *iv, // Process the block. aes_block_t block_out; - HARDENED_TRY(gctr_process_block(key, iv, partial, &block_out)); + HARDENED_TRY( + gctr_process_block(key, iv, partial, security_level, &block_out)); memcpy(output, block_out.data, kAesBlockNumBytes); output += kAesBlockNumBytes; *output_len = kAesBlockNumBytes; @@ -149,7 +180,8 @@ static status_t aes_gcm_gctr(const aes_key_t key, aes_block_t *iv, // Process any remaining full blocks of input. while (input_len >= kAesBlockNumBytes) { memcpy(partial->data, input, kAesBlockNumBytes); - HARDENED_TRY(gctr_process_block(key, iv, partial, &block_out)); + HARDENED_TRY( + gctr_process_block(key, iv, partial, security_level, &block_out)); memcpy(output, block_out.data, kAesBlockNumBytes); output += kAesBlockNumBytes; *output_len += kAesBlockNumBytes; @@ -171,18 +203,22 @@ static status_t aes_gcm_gctr(const aes_key_t key, aes_block_t *iv, * output should not be used. * * @param key AES key + * @param security_level The security level of the key * @param[out] ctx Destination GHASH context object * @return OK or error */ OT_WARN_UNUSED_RESULT -static status_t aes_gcm_hash_subkey(const aes_key_t key, ghash_context_t *ctx) { +static status_t aes_gcm_hash_subkey( + const aes_key_t key, otcrypto_key_security_level_t security_level, + ghash_context_t *ctx) { // Compute the initial hash subkey H = AES_K(0). Note that to get this // result from AES_CTR, we set both the IV and plaintext to zero; this way, // AES-CTR's final XOR with the plaintext does nothing. aes_block_t zero; memset(zero.data, 0, kAesBlockNumBytes); aes_block_t hash_subkey; - HARDENED_TRY(aes_encrypt_block(key, &zero, &zero, &hash_subkey)); + HARDENED_TRY( + aes_encrypt_block(key, &zero, &zero, &hash_subkey, security_level)); // Set the key for the GHASH context. ghash_init_subkey(hash_subkey.data, ctx); @@ -260,8 +296,8 @@ static status_t aes_gcm_get_tag(aes_gcm_context_t *ctx, size_t tag_len, aes_block_t empty = {.data = {0}}; HARDENED_TRY(aes_gcm_gctr(ctx->key, &ctx->initial_counter_block, /*partial_len=*/0, &empty, kAesBlockNumBytes, - (unsigned char *)s.data, &full_tag_len, - (unsigned char *)full_tag)); + (unsigned char *)s.data, ctx->security_level, + &full_tag_len, (unsigned char *)full_tag)); // Sanity check. if (full_tag_len != kAesBlockNumBytes) { @@ -311,7 +347,7 @@ static status_t aes_gcm_init(const aes_key_t key, const size_t iv_len, } // Initialize the hash subkey H. - HARDENED_TRY(aes_gcm_hash_subkey(key, &ctx->ghash_ctx)); + HARDENED_TRY(aes_gcm_hash_subkey(key, ctx->security_level, &ctx->ghash_ctx)); // Compute the counter block (called J0 in the NIST specification). HARDENED_TRY(aes_gcm_counter(iv_len, iv, &ctx->ghash_ctx, @@ -414,7 +450,7 @@ status_t aes_gcm_update_encrypted_data(aes_gcm_context_t *ctx, size_t input_len, size_t partial_aes_block_len = ctx->input_len % kAesBlockNumBytes; HARDENED_TRY(aes_gcm_gctr(ctx->key, &ctx->gctr_iv, partial_aes_block_len, &ctx->partial_aes_block, input_len, input, - output_len, output)); + ctx->security_level, output_len, output)); // Accumulate any new ciphertext to the GHASH context. The ciphertext is the // output for encryption, and the input for decryption. @@ -479,7 +515,8 @@ status_t aes_gcm_final(aes_gcm_context_t *ctx, size_t tag_len, uint32_t *tag, kAesBlockNumBytes - partial_aes_block_len); aes_block_t block_out; HARDENED_TRY(gctr_process_block(ctx->key, &ctx->gctr_iv, - &ctx->partial_aes_block, &block_out)); + &ctx->partial_aes_block, + ctx->security_level, &block_out)); memcpy(output, block_out.data, partial_aes_block_len); *output_len = partial_aes_block_len; } diff --git a/sw/device/lib/crypto/impl/aes_gcm/aes_gcm.h b/sw/device/lib/crypto/impl/aes_gcm/aes_gcm.h index 80273ce5ade82..3c98608f84f7a 100644 --- a/sw/device/lib/crypto/impl/aes_gcm/aes_gcm.h +++ b/sw/device/lib/crypto/impl/aes_gcm/aes_gcm.h @@ -12,6 +12,7 @@ #include "sw/device/lib/base/macros.h" #include "sw/device/lib/crypto/drivers/aes.h" #include "sw/device/lib/crypto/impl/aes_gcm/ghash.h" +#include "sw/device/lib/crypto/include/datatypes.h" #ifdef __cplusplus extern "C" { @@ -29,6 +30,10 @@ typedef struct aes_gcm_context { * Underlying AES-CTR key. */ aes_key_t key; + /** + * Security level of the underlying AES-CTR key. + */ + otcrypto_key_security_level_t security_level; /** * Initial counter block (J0 in the spec). */ diff --git a/sw/device/lib/crypto/include/aes_gcm.h b/sw/device/lib/crypto/include/aes_gcm.h index e19354bf16c03..f456ad399069a 100644 --- a/sw/device/lib/crypto/include/aes_gcm.h +++ b/sw/device/lib/crypto/include/aes_gcm.h @@ -39,7 +39,7 @@ typedef enum otcrypto_aes_gcm_tag_len { * change.  */ typedef struct otcrypto_aes_gcm_context { - uint32_t data[100]; + uint32_t data[102]; } otcrypto_aes_gcm_context_t; /** From 3188a266de3a188d54a9e491c7f05acc41fb8a4f Mon Sep 17 00:00:00 2001 From: Pascal Nasahl Date: Tue, 15 Jul 2025 15:53:20 +0200 Subject: [PATCH 40/51] [cryptotest] Set kOtcryptoKeySecurityLevelHigh for AES-GCM Replace kOtcryptoKeySecurityLevelLow with kOtcryptoKeySecurityLevelHigh to make sure that the AES-GCM FI protection is tested as well. Not testing security_level low is fine as low is a subset of high. Signed-off-by: Pascal Nasahl (cherry picked from commit c8503b7cf8fe5efd72a4acccfe8db8f2328d852a) --- sw/device/tests/crypto/aes_gcm_testutils.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sw/device/tests/crypto/aes_gcm_testutils.c b/sw/device/tests/crypto/aes_gcm_testutils.c index e5df22325e72f..fb2a335619121 100644 --- a/sw/device/tests/crypto/aes_gcm_testutils.c +++ b/sw/device/tests/crypto/aes_gcm_testutils.c @@ -129,7 +129,7 @@ status_t aes_gcm_testutils_encrypt(const aes_gcm_test_t *test, bool streaming, .key_mode = kOtcryptoKeyModeAesGcm, .key_length = test->key_len * sizeof(uint32_t), .hw_backed = kHardenedBoolFalse, - .security_level = kOtcryptoKeySecurityLevelLow, + .security_level = kOtcryptoKeySecurityLevelHigh, }; // Construct blinded key from the key and testing mask. @@ -226,7 +226,7 @@ status_t aes_gcm_testutils_decrypt(const aes_gcm_test_t *test, .key_mode = kOtcryptoKeyModeAesGcm, .key_length = test->key_len * sizeof(uint32_t), .hw_backed = kHardenedBoolFalse, - .security_level = kOtcryptoKeySecurityLevelLow, + .security_level = kOtcryptoKeySecurityLevelHigh, }; // Construct blinded key from the key and testing mask. From 3e17364215dee2f4d10b63fd2d5b94be88fa4edc Mon Sep 17 00:00:00 2001 From: Pascal Nasahl Date: Tue, 15 Jul 2025 15:55:49 +0200 Subject: [PATCH 41/51] [pentest] se kOtcryptoKeySecurityLevelHigh for AES-GCM By setting kOtcryptoKeySecurityLevelHigh, the CL will use countermeasures to protect the AES-GCM computation against FI. Enable this protection for the CL AES-GCM pentesting. Signed-off-by: Pascal Nasahl (cherry picked from commit 4d12b44cae8915281b5261a67778e1278c69223a) --- .../tests/penetrationtests/firmware/fi/cryptolib_fi_sym_impl.c | 2 +- .../penetrationtests/firmware/sca/cryptolib_sca_sym_impl.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sw/device/tests/penetrationtests/firmware/fi/cryptolib_fi_sym_impl.c b/sw/device/tests/penetrationtests/firmware/fi/cryptolib_fi_sym_impl.c index 2d81f9792ae6c..2e9c48d9f4fc1 100644 --- a/sw/device/tests/penetrationtests/firmware/fi/cryptolib_fi_sym_impl.c +++ b/sw/device/tests/penetrationtests/firmware/fi/cryptolib_fi_sym_impl.c @@ -216,7 +216,7 @@ status_t cryptolib_fi_gcm_impl(cryptolib_fi_sym_gcm_in_t uj_input, .key_mode = kOtcryptoKeyModeAesGcm, .key_length = uj_input.key_len, .hw_backed = kHardenedBoolFalse, - .security_level = kOtcryptoKeySecurityLevelLow, + .security_level = kOtcryptoKeySecurityLevelHigh, }; // Construct blinded key from the key and testing mask. diff --git a/sw/device/tests/penetrationtests/firmware/sca/cryptolib_sca_sym_impl.c b/sw/device/tests/penetrationtests/firmware/sca/cryptolib_sca_sym_impl.c index 11b32e4e004e6..3c6af084f792c 100644 --- a/sw/device/tests/penetrationtests/firmware/sca/cryptolib_sca_sym_impl.c +++ b/sw/device/tests/penetrationtests/firmware/sca/cryptolib_sca_sym_impl.c @@ -233,7 +233,7 @@ status_t cryptolib_sca_gcm_impl( .key_mode = kOtcryptoKeyModeAesGcm, .key_length = key_len, .hw_backed = kHardenedBoolFalse, - .security_level = kOtcryptoKeySecurityLevelLow, + .security_level = kOtcryptoKeySecurityLevelHigh, }; // Construct blinded key from the key and mask. From 185f677ddfed19d68f402cec1593551fe59cfd7c Mon Sep 17 00:00:00 2001 From: Pascal Nasahl Date: Mon, 1 Sep 2025 16:35:08 +0200 Subject: [PATCH 42/51] [crypto] Check HMAC key integrity When creating the HMAC key, store the checksum into the HMAC key struct. After writing the key to the HMAC block, recompute the checksum over the key and compare it to the stored checksum value. Signed-off-by: Pascal Nasahl (cherry picked from commit deee829d895f7d85a40ce79d7718ececdc2949bf) --- sw/device/lib/crypto/drivers/BUILD | 1 + sw/device/lib/crypto/drivers/hmac.c | 149 +++++++++++++++----------- sw/device/lib/crypto/drivers/hmac.h | 89 +++++++++++----- sw/device/lib/crypto/impl/hmac.c | 155 ++++++++++++++++------------ sw/device/lib/crypto/include/sha2.h | 2 +- 5 files changed, 241 insertions(+), 155 deletions(-) diff --git a/sw/device/lib/crypto/drivers/BUILD b/sw/device/lib/crypto/drivers/BUILD index 22bdca2704e9f..0d36e4269622a 100644 --- a/sw/device/lib/crypto/drivers/BUILD +++ b/sw/device/lib/crypto/drivers/BUILD @@ -202,6 +202,7 @@ cc_library( "//hw/top_earlgrey/sw/autogen:top_earlgrey", "//sw/device/lib/base:abs_mmio", "//sw/device/lib/base:bitfield", + "//sw/device/lib/base:crc32", "//sw/device/lib/base:hardened", "//sw/device/lib/base:hardened_memory", "//sw/device/lib/base:macros", diff --git a/sw/device/lib/crypto/drivers/hmac.c b/sw/device/lib/crypto/drivers/hmac.c index 64e0c2d62b892..9b3fc9e055795 100644 --- a/sw/device/lib/crypto/drivers/hmac.c +++ b/sw/device/lib/crypto/drivers/hmac.c @@ -6,6 +6,7 @@ #include "sw/device/lib/base/abs_mmio.h" #include "sw/device/lib/base/bitfield.h" +#include "sw/device/lib/base/crc32.h" #include "sw/device/lib/base/hardened.h" #include "sw/device/lib/base/hardened_memory.h" #include "sw/device/lib/base/memory.h" @@ -180,16 +181,32 @@ static status_t clear(void) { * caller must ensure that HMAC HWIP is in an idle state that accepts writing * key words. * - * The key may be NULL if `key_wordlen` is zero; in that case this function is - * a no-op. + * The key may be NULL. * - * @param key The buffer that points to the key. - * @param key_wordlen The length of the key in words. - * @return OK or error. + * @param key The buffer that points to the hmac_key_t key structure. + * @return Result of the operation. */ -static status_t key_write(const uint32_t *key, size_t key_wordlen) { - uint32_t key_reg = kHmacBaseAddr + HMAC_KEY_0_REG_OFFSET; - return hardened_memcpy((uint32_t *)key_reg, key, key_wordlen); +static status_t key_write(const hmac_key_t *key) { + if (key != NULL) { + uint32_t key_reg = kHmacBaseAddr + HMAC_KEY_0_REG_OFFSET; + HARDENED_TRY( + hardened_memcpy((uint32_t *)key_reg, key->key_block, key->key_len)); + // We only check the integrity of the key when entering the CryptoLib. This + // check here will catch any manipulations of the key or the pointer to the + // key that might have happend in the meanwhile. We do it at this point as + // the key just got written into the HMAC core in the `hardened_memcpy()` + // above. + if (launder32(key->key_len) != 0) { + HARDENED_CHECK_EQ(hmac_key_integrity_checksum_check(key), + kHardenedBoolTrue); + } else { + HARDENED_CHECK_EQ(key->key_len, 0); + } + } else { + HARDENED_CHECK_EQ(key, NULL); + } + + return OTCRYPTO_OK; } /** @@ -229,7 +246,7 @@ static status_t context_restore(hmac_ctx_t *ctx) { // Write to KEY registers for HMAC operations. If the operation is SHA-2, // `key_wordlen` is set to 0 during `ctx` initialization. - HARDENED_TRY(key_write(ctx->key, ctx->key_wordlen)); + HARDENED_TRY(key_write(&ctx->key)); uint32_t cmd = HMAC_CMD_REG_RESVAL; // Decide if we need to invoke `start` or `continue` command. @@ -296,13 +313,14 @@ static status_t hmac_context_wipe(hmac_ctx_t *ctx) { HARDENED_TRY(entropy_complex_check()); // Randomize sensitive data. - HARDENED_TRY(hardened_memshred(ctx->key, kHmacMaxBlockWords)); + HARDENED_TRY(hardened_memshred(ctx->key.key_block, kHmacMaxBlockWords)); HARDENED_TRY(hardened_memshred(ctx->H, kHmacMaxDigestWords)); HARDENED_TRY(hardened_memshred((uint32_t *)(ctx->partial_block), kHmacMaxBlockBytes / sizeof(uint32_t))); // Zero the remaining ctx fields. ctx->cfg_reg = 0; - ctx->key_wordlen = 0; + ctx->key.key_len = 0; + ctx->key.checksum = 0; ctx->msg_block_wordlen = 0; ctx->digest_wordlen = 0; ctx->lower = 0; @@ -431,14 +449,13 @@ static status_t ensure_idle(void) { * * @param cfg HMAC block configuration register. * @param key Key input for HMAC (may be NULL if `key_wordlen` is 0). - * @param key_wordlen Length of key input in words. * @param msg Message data. * @param msg_len Length of message data in bytes. * @param digest_wordlen Digest length in 32-bit words. * @param[out] digest Buffer for the digest. */ -static status_t oneshot(const uint32_t cfg, const uint32_t *key, - size_t key_wordlen, const uint8_t *msg, size_t msg_len, +static status_t oneshot(const uint32_t cfg, const hmac_key_t *key, + const uint8_t *msg, size_t msg_len, size_t digest_wordlen, uint32_t *digest) { // Check that the block is idle. HARDENED_TRY(ensure_idle()); @@ -447,7 +464,7 @@ static status_t oneshot(const uint32_t cfg, const uint32_t *key, abs_mmio_write32(kHmacBaseAddr + HMAC_CFG_REG_OFFSET, cfg); // Write the key (no-op if the key length is 0, e.g. for hashing). - HARDENED_TRY(key_write(key, key_wordlen)); + HARDENED_TRY(key_write(key)); // Read back the HMAC configuration and compare to the expected configuration. HARDENED_CHECK_EQ(abs_mmio_read32(kHmacBaseAddr + HMAC_CFG_REG_OFFSET), @@ -477,37 +494,35 @@ status_t hmac_hash_sha256(const uint8_t *msg, size_t msg_len, uint32_t *digest) { uint32_t cfg = cfg_get(/*hmac_en=*/false, kDigestLengthSha256, kKeyLengthNone); - return oneshot(cfg, /*key=*/NULL, /*key_wordlen=*/0, msg, msg_len, - kHmacSha256DigestWords, digest); + return oneshot(cfg, /*key=*/NULL, msg, msg_len, kHmacSha256DigestWords, + digest); } status_t hmac_hash_sha384(const uint8_t *msg, size_t msg_len, uint32_t *digest) { uint32_t cfg = cfg_get(/*hmac_en=*/false, kDigestLengthSha384, kKeyLengthNone); - return oneshot(cfg, /*key=*/NULL, /*key_wordlen=*/0, msg, msg_len, - kHmacSha384DigestWords, digest); + return oneshot(cfg, /*key=*/NULL, msg, msg_len, kHmacSha384DigestWords, + digest); } status_t hmac_hash_sha512(const uint8_t *msg, size_t msg_len, uint32_t *digest) { uint32_t cfg = cfg_get(/*hmac_en=*/false, kDigestLengthSha512, kKeyLengthNone); - return oneshot(cfg, /*key=*/NULL, /*key_wordlen=*/0, msg, msg_len, - kHmacSha512DigestWords, digest); + return oneshot(cfg, /*key=*/NULL, msg, msg_len, kHmacSha512DigestWords, + digest); } -status_t hmac_hmac_sha256_cl(const uint32_t *key_block, const uint8_t *msg, - size_t msg_len, uint32_t *tag) { +status_t hmac_hmac_sha256_cl(const hmac_key_t *key, const uint8_t *msg, + size_t msg_len, uint32_t *tag) { // Always configure the key length as the underlying message block size. uint32_t cfg = cfg_get(/*hmac_en=*/true, kDigestLengthSha256, kKeyLength512); - return oneshot(cfg, key_block, kHmacSha256BlockWords, msg, msg_len, - kHmacSha256DigestWords, tag); + return oneshot(cfg, key, msg, msg_len, kHmacSha256DigestWords, tag); } -status_t hmac_hmac_sha256_redundant(const uint32_t *key_block, - const uint8_t *msg, size_t msg_len, - uint32_t *tag) { +status_t hmac_hmac_sha256_redundant(const hmac_key_t *key, const uint8_t *msg, + size_t msg_len, uint32_t *tag) { uint32_t o_key_pad[kHmacSha256BlockWords]; uint32_t i_key_pad[kHmacSha256BlockWords]; memset(o_key_pad, 0, kHmacSha256BlockBytes); @@ -515,8 +530,8 @@ status_t hmac_hmac_sha256_redundant(const uint32_t *key_block, // XOR the key K with the outer (opad) and inner (ipad) padding. for (size_t it = 0; it < kHmacSha256BlockWords; it++) { - o_key_pad[it] = key_block[it] ^ 0x5c5c5c5c; - i_key_pad[it] = key_block[it] ^ 0x36363636; + o_key_pad[it] = key->key_block[it] ^ 0x5c5c5c5c; + i_key_pad[it] = key->key_block[it] ^ 0x36363636; } // Concatenate the message with the inner padded key. @@ -543,17 +558,15 @@ status_t hmac_hmac_sha256_redundant(const uint32_t *key_block, kHmacSha256BlockBytes + kHmacSha256DigestBytes, tag); } -status_t hmac_hmac_sha384(const uint32_t *key_block, const uint8_t *msg, +status_t hmac_hmac_sha384(const hmac_key_t *key, const uint8_t *msg, size_t msg_len, uint32_t *tag) { // Always configure the key length as the underlying message block size. uint32_t cfg = cfg_get(/*hmac_en=*/true, kDigestLengthSha384, kKeyLength1024); - return oneshot(cfg, key_block, kHmacSha384BlockWords, msg, msg_len, - kHmacSha384DigestWords, tag); + return oneshot(cfg, key, msg, msg_len, kHmacSha384DigestWords, tag); } -status_t hmac_hmac_sha384_redundant(const uint32_t *key_block, - const uint8_t *msg, size_t msg_len, - uint32_t *tag) { +status_t hmac_hmac_sha384_redundant(const hmac_key_t *key, const uint8_t *msg, + size_t msg_len, uint32_t *tag) { uint32_t o_key_pad[kHmacSha384BlockWords]; uint32_t i_key_pad[kHmacSha384BlockWords]; memset(o_key_pad, 0, kHmacSha384BlockBytes); @@ -561,8 +574,8 @@ status_t hmac_hmac_sha384_redundant(const uint32_t *key_block, // XOR the key K with the outer (opad) and inner (ipad) padding. for (size_t it = 0; it < kHmacSha384BlockWords; it++) { - o_key_pad[it] = key_block[it] ^ 0x5c5c5c5c; - i_key_pad[it] = key_block[it] ^ 0x36363636; + o_key_pad[it] = key->key_block[it] ^ 0x5c5c5c5c; + i_key_pad[it] = key->key_block[it] ^ 0x36363636; } // Concatenate the message with the inner padded key. @@ -589,17 +602,15 @@ status_t hmac_hmac_sha384_redundant(const uint32_t *key_block, kHmacSha384BlockBytes + kHmacSha384DigestBytes, tag); } -status_t hmac_hmac_sha512(const uint32_t *key_block, const uint8_t *msg, +status_t hmac_hmac_sha512(const hmac_key_t *key, const uint8_t *msg, size_t msg_len, uint32_t *tag) { // Always configure the key length as the underlying message block size. uint32_t cfg = cfg_get(/*hmac_en=*/true, kDigestLengthSha512, kKeyLength1024); - return oneshot(cfg, key_block, kHmacSha512BlockWords, msg, msg_len, - kHmacSha512DigestWords, tag); + return oneshot(cfg, key, msg, msg_len, kHmacSha512DigestWords, tag); } -status_t hmac_hmac_sha512_redundant(const uint32_t *key_block, - const uint8_t *msg, size_t msg_len, - uint32_t *tag) { +status_t hmac_hmac_sha512_redundant(const hmac_key_t *key, const uint8_t *msg, + size_t msg_len, uint32_t *tag) { uint32_t o_key_pad[kHmacSha512BlockWords]; uint32_t i_key_pad[kHmacSha512BlockWords]; memset(o_key_pad, 0, kHmacSha512BlockBytes); @@ -607,8 +618,8 @@ status_t hmac_hmac_sha512_redundant(const uint32_t *key_block, // XOR the key K with the outer (opad) and inner (ipad) padding. for (size_t it = 0; it < kHmacSha512BlockWords; it++) { - o_key_pad[it] = key_block[it] ^ 0x5c5c5c5c; - i_key_pad[it] = key_block[it] ^ 0x36363636; + o_key_pad[it] = key->key_block[it] ^ 0x5c5c5c5c; + i_key_pad[it] = key->key_block[it] ^ 0x36363636; } // Concatenate the message with the inner padded key. @@ -648,7 +659,8 @@ status_t hmac_hmac_sha512_redundant(const uint32_t *key_block, */ static void sha2_init(hmac_digest_length_t digest_len, hmac_ctx_t *ctx) { ctx->cfg_reg = cfg_get(/*hmac_en=*/false, digest_len, kKeyLengthNone); - ctx->key_wordlen = 0; + ctx->key.key_len = 0; + ctx->key.checksum = 0; ctx->lower = 0; ctx->upper = 0; ctx->partial_block_bytelen = 0; @@ -690,30 +702,49 @@ void hmac_hash_sha512_init(hmac_ctx_t *ctx) { sha2_init(kDigestLengthSha512, ctx); } -void hmac_hmac_sha256_init_cl(const uint32_t *key_block, hmac_ctx_t *ctx) { +void hmac_hmac_sha256_init_cl(const hmac_key_t key, hmac_ctx_t *ctx) { ctx->msg_block_wordlen = kHmacSha256BlockWords, - ctx->digest_wordlen = kHmacSha256DigestWords, - ctx->key_wordlen = kHmacSha256BlockWords; - memcpy(ctx->key, key_block, ctx->key_wordlen * sizeof(uint32_t)); + ctx->digest_wordlen = kHmacSha256DigestWords; + ctx->key.key_len = key.key_len; + ctx->key.checksum = key.checksum; + hardened_memcpy(ctx->key.key_block, key.key_block, key.key_len); hmac_init(kKeyLength512, kDigestLengthSha256, ctx); } -void hmac_hmac_sha384_init(const uint32_t *key_block, hmac_ctx_t *ctx) { +void hmac_hmac_sha384_init(const hmac_key_t key, hmac_ctx_t *ctx) { ctx->msg_block_wordlen = kHmacSha384BlockWords, - ctx->digest_wordlen = kHmacSha384DigestWords, - ctx->key_wordlen = kHmacSha384BlockWords; - memcpy(ctx->key, key_block, ctx->key_wordlen * sizeof(uint32_t)); + ctx->digest_wordlen = kHmacSha384DigestWords; + ctx->key.key_len = key.key_len; + ctx->key.checksum = key.checksum; + hardened_memcpy(ctx->key.key_block, key.key_block, key.key_len); hmac_init(kKeyLength1024, kDigestLengthSha384, ctx); } -void hmac_hmac_sha512_init(const uint32_t *key_block, hmac_ctx_t *ctx) { +void hmac_hmac_sha512_init(const hmac_key_t key, hmac_ctx_t *ctx) { ctx->msg_block_wordlen = kHmacSha512BlockWords, - ctx->digest_wordlen = kHmacSha512DigestWords, - ctx->key_wordlen = kHmacSha512BlockWords; - memcpy(ctx->key, key_block, ctx->key_wordlen * sizeof(uint32_t)); + ctx->digest_wordlen = kHmacSha512DigestWords; + ctx->key.key_len = key.key_len; + ctx->key.checksum = key.checksum; + hardened_memcpy(ctx->key.key_block, key.key_block, key.key_len); hmac_init(kKeyLength1024, kDigestLengthSha512, ctx); } +uint32_t hmac_key_integrity_checksum(const hmac_key_t *key) { + uint32_t ctx; + crc32_init(&ctx); + crc32_add32(&ctx, key->key_len); + crc32_add(&ctx, (unsigned char *)key->key_block, key->key_len); + return crc32_finish(&ctx); +} + +hardened_bool_t hmac_key_integrity_checksum_check(const hmac_key_t *key) { + if (key->checksum == launder32(hmac_key_integrity_checksum(key))) { + HARDENED_CHECK_EQ(key->checksum, hmac_key_integrity_checksum(key)); + return kHardenedBoolTrue; + } + return kHardenedBoolFalse; +} + status_t hmac_update(hmac_ctx_t *ctx, const uint8_t *data, size_t len) { // If we don't have enough new bytes to fill a block, just update the partial // block and return. diff --git a/sw/device/lib/crypto/drivers/hmac.h b/sw/device/lib/crypto/drivers/hmac.h index dca815e57aef3..32bf8cd6ef80e 100644 --- a/sw/device/lib/crypto/drivers/hmac.h +++ b/sw/device/lib/crypto/drivers/hmac.h @@ -52,6 +52,24 @@ enum { kHmacMaxBlockWords = kHmacMaxBlockBytes / sizeof(uint32_t), }; +/** + * The HMAC key structure. + */ +typedef struct hmac_key { + /** + * The length of the key (in 32-bit words). + */ + size_t key_len; + /** + * Key storage buffer. + */ + uint32_t key_block[kHmacMaxBlockWords]; + /** + * Checksum of this HMAC key structure. + */ + uint32_t checksum; +} hmac_key_t; + /** * A context struct maintained for streaming operations. */ @@ -59,8 +77,7 @@ typedef struct hmac_ctx { // A copy of `CFG` register used during resumption. uint32_t cfg_reg; // A copy of `KEY` to be used during start or resumption. - uint32_t key[kHmacMaxBlockWords]; - size_t key_wordlen; + hmac_key_t key; // The internal (message) block size of SHA-2 for this operation. size_t msg_block_wordlen; size_t digest_wordlen; @@ -112,15 +129,15 @@ status_t hmac_hash_sha512(const uint8_t *msg, size_t msg_len, uint32_t *digest); * The key should be pre-processed into a buffer the size of a full message * block, according to FIPS 198-1, section 4. * - * @param key_block Input key block (`kHmacSha256BlockWords` words). + * @param key HMAC key. * @param msg Input message. * @param msg_len Message length in bytes. * @param[out] tag Authentication tag (`kHmacSha256DigestWords` bytes). * @return OK or error. */ OT_WARN_UNUSED_RESULT -status_t hmac_hmac_sha256_cl(const uint32_t *key_block, const uint8_t *msg, - size_t msg_len, uint32_t *tag); +status_t hmac_hmac_sha256_cl(const hmac_key_t *key, const uint8_t *msg, + size_t msg_len, uint32_t *tag); /** * Redundant implementation for a one-shot HMAC-SHA256 hash computation. @@ -134,16 +151,15 @@ status_t hmac_hmac_sha256_cl(const uint32_t *key_block, const uint8_t *msg, * implementations, injecting two identical faults affect different parts during * the HMAC compuation, which can be detected. * - * @param key_block Input key block (`kHmacSha256BlockWords` words). + * @param key HMAC key. * @param msg Input message. * @param msg_len Message length in bytes. * @param[out] tag Authentication tag (`kHmacSha256DigestWords` bytes). * @return OK or error. */ OT_WARN_UNUSED_RESULT -status_t hmac_hmac_sha256_redundant(const uint32_t *key_block, - const uint8_t *msg, size_t msg_len, - uint32_t *tag); +status_t hmac_hmac_sha256_redundant(const hmac_key_t *key, const uint8_t *msg, + size_t msg_len, uint32_t *tag); /** * One-shot HMAC-SHA384 hash computation. @@ -151,14 +167,14 @@ status_t hmac_hmac_sha256_redundant(const uint32_t *key_block, * The key should be pre-processed into a buffer the size of a full message * block, according to FIPS 198-1, section 4. * - * @param key_block Input key block (`kHmacSha384BlockWords` words). + * @param key HMAC key. * @param msg Input message. * @param msg_len Message length in bytes. * @param[out] tag Authentication tag (`kHmacSha384DigestWords` bytes). * @return OK or error. */ OT_WARN_UNUSED_RESULT -status_t hmac_hmac_sha384(const uint32_t *key_block, const uint8_t *msg, +status_t hmac_hmac_sha384(const hmac_key_t *key, const uint8_t *msg, size_t msg_len, uint32_t *tag); /** @@ -173,16 +189,15 @@ status_t hmac_hmac_sha384(const uint32_t *key_block, const uint8_t *msg, * implementations, injecting two identical faults affect different parts during * the HMAC compuation, which can be detected. * - * @param key_block Input key block (`kHmacSha384BlockWords` words). + * @param key HMAC key. * @param msg Input message. * @param msg_len Message length in bytes. * @param[out] tag Authentication tag (`kHmacSha384DigestWords` bytes). * @return OK or error. */ OT_WARN_UNUSED_RESULT -status_t hmac_hmac_sha384_redundant(const uint32_t *key_block, - const uint8_t *msg, size_t msg_len, - uint32_t *tag); +status_t hmac_hmac_sha384_redundant(const hmac_key_t *key, const uint8_t *msg, + size_t msg_len, uint32_t *tag); /** * One-shot HMAC-SHA512 hash computation. @@ -190,14 +205,14 @@ status_t hmac_hmac_sha384_redundant(const uint32_t *key_block, * The key should be pre-processed into a buffer the size of a full message * block, according to FIPS 198-1, section 4. * - * @param key_block Input key block (`kHmacSha512BlockWords` words). + * @param key HMAC key. * @param msg Input message. * @param msg_len Message length in bytes. * @param[out] tag Authentication tag (`kHmacSha512DigestWords` bytes). * @return OK or error. */ OT_WARN_UNUSED_RESULT -status_t hmac_hmac_sha512(const uint32_t *key_block, const uint8_t *msg, +status_t hmac_hmac_sha512(const hmac_key_t *key, const uint8_t *msg, size_t msg_len, uint32_t *tag); /** @@ -212,16 +227,15 @@ status_t hmac_hmac_sha512(const uint32_t *key_block, const uint8_t *msg, * implementations, injecting two identical faults affect different parts during * the HMAC compuation, which can be detected. * - * @param key_block Input key block (`kHmacSha512BlockWords` words). + * @param key HMAC key. * @param msg Input message. * @param msg_len Message length in bytes. * @param[out] tag Authentication tag (`kHmacSha512DigestWords` bytes). * @return OK or error. */ OT_WARN_UNUSED_RESULT -status_t hmac_hmac_sha512_redundant(const uint32_t *key_block, - const uint8_t *msg, size_t msg_len, - uint32_t *tag); +status_t hmac_hmac_sha512_redundant(const hmac_key_t *key, const uint8_t *msg, + size_t msg_len, uint32_t *tag); /** * Initializes the context for a streaming SHA256 hash computation. @@ -250,10 +264,10 @@ void hmac_hash_sha512_init(hmac_ctx_t *ctx); * The key should be pre-processed into a buffer the size of a full message * block, according to FIPS 198-1, section 4. * - * @param key_block Input key block (`kHmacSha256BlockWords` words). + * @param key The key used for HMAC. * @param[out] ctx Initialized context object. */ -void hmac_hmac_sha256_init_cl(const uint32_t *key_block, hmac_ctx_t *ctx); +void hmac_hmac_sha256_init_cl(const hmac_key_t key, hmac_ctx_t *ctx); /** * Initializes the context for a streaming HMAC-SHA384 computation. @@ -261,10 +275,10 @@ void hmac_hmac_sha256_init_cl(const uint32_t *key_block, hmac_ctx_t *ctx); * The key should be pre-processed into a buffer the size of a full message * block, according to FIPS 198-1, section 4. * - * @param key_block Input key block (`kHmacSha384BlockWords` words). + * @param key The key used for HMAC. * @param[out] ctx Initialized context object. */ -void hmac_hmac_sha384_init(const uint32_t *key_block, hmac_ctx_t *ctx); +void hmac_hmac_sha384_init(const hmac_key_t key, hmac_ctx_t *ctx); /** * Initializes the context for a streaming HMAC-SHA512 computation. @@ -272,10 +286,31 @@ void hmac_hmac_sha384_init(const uint32_t *key_block, hmac_ctx_t *ctx); * The key should be pre-processed into a buffer the size of a full message * block, according to FIPS 198-1, section 4. * - * @param key_block Input key block (`kHmacSha512BlockWords` words). + * @param key The key used for HMAC. * @param[out] ctx Initialized context object. */ -void hmac_hmac_sha512_init(const uint32_t *key_block, hmac_ctx_t *ctx); +void hmac_hmac_sha512_init(const hmac_key_t key, hmac_ctx_t *ctx); + +/** + * Compute the checksum of an HMAC key. + * + * Call this routine after creating or modifying the HMAC key structure. + * + * @param key HMAC key. + * @returns Checksum value. + */ +uint32_t hmac_key_integrity_checksum(const hmac_key_t *key); + +/** + * Perform an integrity check on the HMAC key. + * + * Returns `kHardenedBoolTrue` if the check passed and `kHardenedBoolFalse` + * otherwise. + * + * @param key HMAC key. + * @returns Whether the integrity check passed. + */ +hardened_bool_t hmac_key_integrity_checksum_check(const hmac_key_t *key); /** * Update the context with additional messsage data. diff --git a/sw/device/lib/crypto/impl/hmac.c b/sw/device/lib/crypto/impl/hmac.c index b6a7f7d2a2059..bb7eccf89350c 100644 --- a/sw/device/lib/crypto/impl/hmac.c +++ b/sw/device/lib/crypto/impl/hmac.c @@ -30,21 +30,24 @@ static_assert(sizeof(hmac_ctx_t) % sizeof(uint32_t) == 0, "`hardened_memcpy()`"); /** - * Compute the key block (see FIPS 198-1, Section 4, Steps 1-3). + * Compute the key block (see FIPS 198-1, Section 4, Steps 1-3) together with + * its checksum. * * Adds padding and in some cases pre-hashes the HMAC key to get a value the - * length of the underlying message block size. + * length of the underlying message block size. This length is then set in + * the key_len field of hmac_key_t. * * The caller must ensure that at least `key_block_wordlen` 32-bit words of * space is allocated at the destination `key_block` buffer. * * @param key The blinded input key. * @param key_block_wordlen Block size in 32-bit words. - * @param[out] key_block Destination buffer for the key block. + * @param[out] hmac_key Destination of the HMAC key struct. * @return Result of the operation. */ -static status_t key_block_get(const otcrypto_blinded_key_t *key, - size_t key_block_wordlen, uint32_t *key_block) { +static status_t hmac_key_construct(const otcrypto_blinded_key_t *key, + size_t key_block_wordlen, + hmac_key_t *hmac_key) { // HMAC HWIP does not support masking, so we need to unmask the key. size_t unmasked_key_len = keyblob_share_num_words(key->config); uint32_t unmasked_key[unmasked_key_len]; @@ -52,42 +55,61 @@ static status_t key_block_get(const otcrypto_blinded_key_t *key, // Pre-populate with 0s, in order to pad keys smaller than the internal // block size, according to FIPS 198-1, Section 4. - memset(key_block, 0, key_block_wordlen * sizeof(uint32_t)); + memset(hmac_key->key_block, 0, key_block_wordlen * sizeof(uint32_t)); // If the key is larger than the internal block size, we need to hash it // according to FIPS 198-1, Section 4, Step 2. if (launder32(key->config.key_length) > key_block_wordlen * sizeof(uint32_t)) { + otcrypto_hmac_key_mode_t used_key_mode; switch (key->config.key_mode) { case kOtcryptoKeyModeHmacSha256: - return hmac_hash_sha256((unsigned char *)unmasked_key, - key->config.key_length, key_block); + HARDENED_TRY(hmac_hash_sha256((unsigned char *)unmasked_key, + key->config.key_length, + hmac_key->key_block)); + used_key_mode = launder32(kOtcryptoKeyModeHmacSha256); + break; case kOtcryptoKeyModeHmacSha384: - return hmac_hash_sha384((unsigned char *)unmasked_key, - key->config.key_length, key_block); + HARDENED_TRY(hmac_hash_sha384((unsigned char *)unmasked_key, + key->config.key_length, + hmac_key->key_block)); + used_key_mode = launder32(kOtcryptoKeyModeHmacSha384); + break; case kOtcryptoKeyModeHmacSha512: - return hmac_hash_sha512((unsigned char *)unmasked_key, - key->config.key_length, key_block); + HARDENED_TRY(hmac_hash_sha512((unsigned char *)unmasked_key, + key->config.key_length, + hmac_key->key_block)); + used_key_mode = launder32(kOtcryptoKeyModeHmacSha512); + break; default: return OTCRYPTO_BAD_ARGS; } - // Should be unreachable. - HARDENED_TRAP(); - return OTCRYPTO_FATAL_ERR; + HARDENED_CHECK_EQ(used_key_mode, key->config.key_mode); } else { HARDENED_CHECK_LE(key->config.key_length, key_block_wordlen * sizeof(uint32_t)); - HARDENED_TRY(hardened_memcpy(key_block, unmasked_key, unmasked_key_len)); + HARDENED_TRY( + hardened_memcpy(hmac_key->key_block, unmasked_key, unmasked_key_len)); // If the key size isn't a multiple of the word size, zero the last few // bytes. size_t offset = key->config.key_length % sizeof(uint32_t); if (offset != 0) { unsigned char *key_end_ptr = - (unsigned char *)(&key_block[unmasked_key_len]); + (unsigned char *)(&hmac_key->key_block[unmasked_key_len]); size_t num_zero_bytes = sizeof(uint32_t) - offset; memset(key_end_ptr - num_zero_bytes, 0, num_zero_bytes); } } + // Set the key length to the key block word length. + hmac_key->key_len = key_block_wordlen; + + // Create the checksum of the key and store it in the key structure. + if (launder32(hmac_key->key_len) > 0) { + hmac_key->checksum = hmac_key_integrity_checksum(hmac_key); + } else { + HARDENED_CHECK_EQ(hmac_key->key_len, 0); + } + return OTCRYPTO_OK; } @@ -135,18 +157,18 @@ otcrypto_status_t otcrypto_hmac(const otcrypto_blinded_key_t *key, } // Call the appropriate function from the HMAC driver. + hmac_key_t hmac_key; switch (launder32(key->config.key_mode)) { case kOtcryptoKeyModeHmacSha256: { HARDENED_CHECK_EQ(key->config.key_mode, kOtcryptoKeyModeHmacSha256); - uint32_t key_block[kHmacSha256BlockWords]; - HARDENED_TRY(key_block_get(key, ARRAYSIZE(key_block), key_block)); + HARDENED_TRY(hmac_key_construct(key, kHmacSha256BlockWords, &hmac_key)); if (launder32(key->config.security_level) == kOtcryptoKeySecurityLevelLow) { // No protection against FI. HARDENED_CHECK_EQ(key->config.security_level, kOtcryptoKeySecurityLevelLow); - return hmac_hmac_sha256_cl(key_block, input_message.data, - input_message.len, tag.data); + return hmac_hmac_sha256_cl(&hmac_key, input_message.data, + input_message.len, tag.data); } else if (launder32(key->config.security_level) == kOtcryptoKeySecurityLevelMedium) { // Call the HMAC core twice and compare both tags. This serves as a FI @@ -154,16 +176,15 @@ otcrypto_status_t otcrypto_hmac(const otcrypto_blinded_key_t *key, // First HMAC computation using the HMAC core. HARDENED_CHECK_EQ(key->config.security_level, kOtcryptoKeySecurityLevelMedium); - HARDENED_TRY(hmac_hmac_sha256_cl(key_block, input_message.data, - input_message.len, tag.data)); + HARDENED_TRY(hmac_hmac_sha256_cl(&hmac_key, input_message.data, + input_message.len, tag.data)); // Second HMAC computation using the HMAC core. uint32_t tag_redundant[tag.len]; - uint32_t key_block_redundant[kHmacSha256BlockWords]; - HARDENED_TRY(key_block_get(key, ARRAYSIZE(key_block_redundant), - key_block_redundant)); - HARDENED_TRY(hmac_hmac_sha256_cl(key_block_redundant, - input_message.data, input_message.len, - tag_redundant)); + hmac_key_t hmac_key_redundant; + HARDENED_TRY(hmac_key_construct(key, kHmacSha256BlockWords, + &hmac_key_redundant)); + HARDENED_TRY(hmac_hmac_sha256_cl(&hmac_key_redundant, input_message.data, + input_message.len, tag_redundant)); // Comparison of both tags. HARDENED_CHECK_EQ( hardened_memeq(&tag.data[0], &tag_redundant[0], tag.len), @@ -176,15 +197,15 @@ otcrypto_status_t otcrypto_hmac(const otcrypto_blinded_key_t *key, HARDENED_CHECK_EQ(key->config.security_level, kOtcryptoKeySecurityLevelHigh); // First HMAC computation using the HMAC core. - HARDENED_TRY(hmac_hmac_sha256_cl(key_block, input_message.data, - input_message.len, tag.data)); + HARDENED_TRY(hmac_hmac_sha256_cl(&hmac_key, input_message.data, + input_message.len, tag.data)); // Second HMAC computation without using the HMAC core. uint32_t tag_redundant[tag.len]; - uint32_t key_block_redundant[kHmacSha256BlockWords]; - HARDENED_TRY(key_block_get(key, ARRAYSIZE(key_block_redundant), - key_block_redundant)); + hmac_key_t hmac_key_redundant; + HARDENED_TRY(hmac_key_construct(key, kHmacSha256BlockWords, + &hmac_key_redundant)); HARDENED_TRY( - hmac_hmac_sha256_redundant(key_block_redundant, input_message.data, + hmac_hmac_sha256_redundant(&hmac_key_redundant, input_message.data, input_message.len, tag_redundant)); // Comparison of both tags. HARDENED_CHECK_EQ( @@ -195,13 +216,12 @@ otcrypto_status_t otcrypto_hmac(const otcrypto_blinded_key_t *key, } case kOtcryptoKeyModeHmacSha384: { HARDENED_CHECK_EQ(key->config.key_mode, kOtcryptoKeyModeHmacSha384); - uint32_t key_block[kHmacSha384BlockWords]; - HARDENED_TRY(key_block_get(key, ARRAYSIZE(key_block), key_block)); + HARDENED_TRY(hmac_key_construct(key, kHmacSha384BlockWords, &hmac_key)); if (launder32(key->config.security_level) == kOtcryptoKeySecurityLevelLow) { HARDENED_CHECK_EQ(key->config.security_level, kOtcryptoKeySecurityLevelLow); - return hmac_hmac_sha384(key_block, input_message.data, + return hmac_hmac_sha384(&hmac_key, input_message.data, input_message.len, tag.data); } else if (launder32(key->config.security_level) == kOtcryptoKeySecurityLevelMedium) { @@ -210,14 +230,14 @@ otcrypto_status_t otcrypto_hmac(const otcrypto_blinded_key_t *key, // First HMAC computation using the HMAC core. HARDENED_CHECK_EQ(key->config.security_level, kOtcryptoKeySecurityLevelMedium); - HARDENED_TRY(hmac_hmac_sha384(key_block, input_message.data, + HARDENED_TRY(hmac_hmac_sha384(&hmac_key, input_message.data, input_message.len, tag.data)); // Second HMAC computation using the HMAC core. uint32_t tag_redundant[tag.len]; - uint32_t key_block_redundant[kHmacSha384BlockWords]; - HARDENED_TRY(key_block_get(key, ARRAYSIZE(key_block_redundant), - key_block_redundant)); - HARDENED_TRY(hmac_hmac_sha384(key_block_redundant, input_message.data, + hmac_key_t hmac_key_redundant; + HARDENED_TRY(hmac_key_construct(key, kHmacSha384BlockWords, + &hmac_key_redundant)); + HARDENED_TRY(hmac_hmac_sha384(&hmac_key_redundant, input_message.data, input_message.len, tag_redundant)); // Comparison of both tags. HARDENED_CHECK_EQ( @@ -231,15 +251,15 @@ otcrypto_status_t otcrypto_hmac(const otcrypto_blinded_key_t *key, HARDENED_CHECK_EQ(key->config.security_level, kOtcryptoKeySecurityLevelHigh); // First HMAC computation using the HMAC core. - HARDENED_TRY(hmac_hmac_sha384(key_block, input_message.data, + HARDENED_TRY(hmac_hmac_sha384(&hmac_key, input_message.data, input_message.len, tag.data)); // Second HMAC computation without using the HMAC core. uint32_t tag_redundant[tag.len]; - uint32_t key_block_redundant[kHmacSha384BlockWords]; - HARDENED_TRY(key_block_get(key, ARRAYSIZE(key_block_redundant), - key_block_redundant)); + hmac_key_t hmac_key_redundant; + HARDENED_TRY(hmac_key_construct(key, kHmacSha384BlockWords, + &hmac_key_redundant)); HARDENED_TRY( - hmac_hmac_sha384_redundant(key_block_redundant, input_message.data, + hmac_hmac_sha384_redundant(&hmac_key_redundant, input_message.data, input_message.len, tag_redundant)); // Comparison of both tags. HARDENED_CHECK_EQ( @@ -250,13 +270,12 @@ otcrypto_status_t otcrypto_hmac(const otcrypto_blinded_key_t *key, } case kOtcryptoKeyModeHmacSha512: { HARDENED_CHECK_EQ(key->config.key_mode, kOtcryptoKeyModeHmacSha512); - uint32_t key_block[kHmacSha512BlockWords]; - HARDENED_TRY(key_block_get(key, ARRAYSIZE(key_block), key_block)); + HARDENED_TRY(hmac_key_construct(key, kHmacSha512BlockWords, &hmac_key)); if (launder32(key->config.security_level) == kOtcryptoKeySecurityLevelLow) { HARDENED_CHECK_EQ(key->config.security_level, kOtcryptoKeySecurityLevelLow); - return hmac_hmac_sha512(key_block, input_message.data, + return hmac_hmac_sha512(&hmac_key, input_message.data, input_message.len, tag.data); } else if (launder32(key->config.security_level) == kOtcryptoKeySecurityLevelMedium) { @@ -265,14 +284,14 @@ otcrypto_status_t otcrypto_hmac(const otcrypto_blinded_key_t *key, // First HMAC computation using the HMAC core. HARDENED_CHECK_EQ(key->config.security_level, kOtcryptoKeySecurityLevelMedium); - HARDENED_TRY(hmac_hmac_sha512(key_block, input_message.data, + HARDENED_TRY(hmac_hmac_sha512(&hmac_key, input_message.data, input_message.len, tag.data)); // Second HMAC computation using the HMAC core. uint32_t tag_redundant[tag.len]; - uint32_t key_block_redundant[kHmacSha512BlockWords]; - HARDENED_TRY(key_block_get(key, ARRAYSIZE(key_block_redundant), - key_block_redundant)); - HARDENED_TRY(hmac_hmac_sha512(key_block_redundant, input_message.data, + hmac_key_t hmac_key_redundant; + HARDENED_TRY(hmac_key_construct(key, kHmacSha512BlockWords, + &hmac_key_redundant)); + HARDENED_TRY(hmac_hmac_sha512(&hmac_key_redundant, input_message.data, input_message.len, tag_redundant)); // Comparison of both tags. HARDENED_CHECK_EQ( @@ -286,15 +305,15 @@ otcrypto_status_t otcrypto_hmac(const otcrypto_blinded_key_t *key, HARDENED_CHECK_EQ(key->config.security_level, kOtcryptoKeySecurityLevelHigh); // First HMAC computation using the HMAC core. - HARDENED_TRY(hmac_hmac_sha512(key_block, input_message.data, + HARDENED_TRY(hmac_hmac_sha512(&hmac_key, input_message.data, input_message.len, tag.data)); // Second HMAC computation without using the HMAC core. uint32_t tag_redundant[tag.len]; - uint32_t key_block_redundant[kHmacSha512BlockWords]; - HARDENED_TRY(key_block_get(key, ARRAYSIZE(key_block_redundant), - key_block_redundant)); + hmac_key_t hmac_key_redundant; + HARDENED_TRY(hmac_key_construct(key, kHmacSha512BlockWords, + &hmac_key_redundant)); HARDENED_TRY( - hmac_hmac_sha512_redundant(key_block_redundant, input_message.data, + hmac_hmac_sha512_redundant(&hmac_key_redundant, input_message.data, input_message.len, tag_redundant)); // Comparison of both tags. HARDENED_CHECK_EQ( @@ -328,22 +347,22 @@ otcrypto_status_t otcrypto_hmac_init(otcrypto_hmac_context_t *ctx, // Call the appropriate function from the HMAC driver. hmac_ctx_t hmac_ctx; - uint32_t key_block[kHmacMaxBlockWords]; + hmac_key_t hmac_key; switch (launder32(key->config.key_mode)) { case kOtcryptoKeyModeHmacSha256: HARDENED_CHECK_EQ(key->config.key_mode, kOtcryptoKeyModeHmacSha256); - HARDENED_TRY(key_block_get(key, kHmacSha256BlockWords, key_block)); - hmac_hmac_sha256_init_cl(key_block, &hmac_ctx); + HARDENED_TRY(hmac_key_construct(key, kHmacSha256BlockWords, &hmac_key)); + hmac_hmac_sha256_init_cl(hmac_key, &hmac_ctx); break; case kOtcryptoKeyModeHmacSha384: HARDENED_CHECK_EQ(key->config.key_mode, kOtcryptoKeyModeHmacSha384); - HARDENED_TRY(key_block_get(key, kHmacSha384BlockWords, key_block)); - hmac_hmac_sha384_init(key_block, &hmac_ctx); + HARDENED_TRY(hmac_key_construct(key, kHmacSha384BlockWords, &hmac_key)); + hmac_hmac_sha384_init(hmac_key, &hmac_ctx); break; case kOtcryptoKeyModeHmacSha512: HARDENED_CHECK_EQ(key->config.key_mode, kOtcryptoKeyModeHmacSha512); - HARDENED_TRY(key_block_get(key, kHmacSha512BlockWords, key_block)); - hmac_hmac_sha512_init(key_block, &hmac_ctx); + HARDENED_TRY(hmac_key_construct(key, kHmacSha512BlockWords, &hmac_key)); + hmac_hmac_sha512_init(hmac_key, &hmac_ctx); break; default: return OTCRYPTO_BAD_ARGS; diff --git a/sw/device/lib/crypto/include/sha2.h b/sw/device/lib/crypto/include/sha2.h index ae98cb6fa1c31..f9491652d04b6 100644 --- a/sw/device/lib/crypto/include/sha2.h +++ b/sw/device/lib/crypto/include/sha2.h @@ -22,7 +22,7 @@ enum { * We assert that this value is large enough to host the internal HMAC driver * struct. */ - kOtcryptoSha2CtxStructWords = 87, + kOtcryptoSha2CtxStructWords = 88, }; /** From 9a3d92d6634bd9dd609eec2b204da57da490068a Mon Sep 17 00:00:00 2001 From: Pascal Nasahl Date: Wed, 20 Aug 2025 14:02:22 +0200 Subject: [PATCH 43/51] [crypto] AES-GCM SCA hardening This commit hardens the GHASH function against SCA. The masking scheme follows the approach highlighted in lowRISC/opentitan#27258. In summary, this masking schemes processes the shares of the hash subkey H = H0 + H1 and the encrypted intial counter block S = S0 + S1 independently. Signed-off-by: Pascal Nasahl (cherry picked from commit 996a7b47cf441ede4dac729ecb62ec102aedf2df) --- sw/device/lib/crypto/impl/aes_gcm/BUILD | 2 + sw/device/lib/crypto/impl/aes_gcm/aes_gcm.c | 93 ++++++++++--- sw/device/lib/crypto/impl/aes_gcm/ghash.c | 128 +++++++++++++++--- sw/device/lib/crypto/impl/aes_gcm/ghash.h | 59 +++++++- .../lib/crypto/impl/aes_gcm/ghash_unittest.cc | 38 ++++-- sw/device/lib/crypto/include/aes_gcm.h | 2 +- 6 files changed, 267 insertions(+), 55 deletions(-) diff --git a/sw/device/lib/crypto/impl/aes_gcm/BUILD b/sw/device/lib/crypto/impl/aes_gcm/BUILD index 12d5e4194685a..1f67983f4a02a 100644 --- a/sw/device/lib/crypto/impl/aes_gcm/BUILD +++ b/sw/device/lib/crypto/impl/aes_gcm/BUILD @@ -24,8 +24,10 @@ cc_library( srcs = ["ghash.c"], hdrs = ["ghash.h"], deps = [ + "//sw/device/lib/base:hardened_memory", "//sw/device/lib/base:macros", "//sw/device/lib/base:memory", + "//sw/device/lib/crypto/drivers:rv_core_ibex", ], ) diff --git a/sw/device/lib/crypto/impl/aes_gcm/aes_gcm.c b/sw/device/lib/crypto/impl/aes_gcm/aes_gcm.c index c58279b2610a9..e1be9e291143e 100644 --- a/sw/device/lib/crypto/impl/aes_gcm/aes_gcm.c +++ b/sw/device/lib/crypto/impl/aes_gcm/aes_gcm.c @@ -220,8 +220,22 @@ static status_t aes_gcm_hash_subkey( HARDENED_TRY( aes_encrypt_block(key, &zero, &zero, &hash_subkey, security_level)); + // Create two shares of the hash subkey. + aes_block_t hash_subkey_share0; + aes_block_t hash_subkey_share1; + + // Share 0: random data. + hardened_memshred(hash_subkey_share0.data, kAesBlockNumWords); + + // Share 1: hash_subkey ^ hash_subkey_share0 + hardened_memcpy(hash_subkey_share1.data, hash_subkey_share0.data, + kAesBlockNumWords); + // TODO(#28008): make sure that we do not override shares. + hardened_xor(hash_subkey_share1.data, hash_subkey.data, kAesBlockNumWords); + // Set the key for the GHASH context. - ghash_init_subkey(hash_subkey.data, ctx); + ghash_init_subkey(hash_subkey_share0.data, ctx->tbl0); + ghash_init_subkey(hash_subkey_share1.data, ctx->tbl1); return OTCRYPTO_OK; } @@ -249,6 +263,29 @@ static status_t aes_gcm_counter(const size_t iv_len, const uint32_t *iv, } else if (iv_len == 4) { // If the IV is 128 bits, then J0 = GHASH(H, IV || {0}^120 || 0x80), where // {0}^120 means 120 zero bits (15 0x00 bytes). + + // As the encrypted initial counter block S only can be computed AFTER + // aes_gcm_counter(), set S0 and S1 to random. + aes_block_t enc_initial_counter_block; + hardened_memshred(enc_initial_counter_block.data, kAesBlockNumWords); + + // Split the initial counter block S into two shares S0 and S1. + // S0: random data. + aes_block_t enc_initial_counter_block0; + hardened_memshred(enc_initial_counter_block0.data, kAesBlockNumWords); + + // S1: S ^ S0 + aes_block_t enc_initial_counter_block1; + hardened_memcpy(enc_initial_counter_block1.data, + enc_initial_counter_block0.data, kAesBlockNumWords); + hardened_xor(enc_initial_counter_block1.data, + enc_initial_counter_block.data, kAesBlockNumWords); + + // Calculate the masking correction terms and store the encrypted initial + // counter blocks S0 and S1. + ghash_handle_enc_initial_counter_block( + enc_initial_counter_block0.data, enc_initial_counter_block1.data, ctx); + ghash_init(ctx); ghash_update(ctx, iv_len * sizeof(uint32_t), (unsigned char *)iv); uint8_t buffer[kAesBlockNumBytes]; @@ -256,6 +293,10 @@ static status_t aes_gcm_counter(const size_t iv_len, const uint32_t *iv, buffer[kAesBlockNumBytes - 1] = 0x80; ghash_update(ctx, kAesBlockNumBytes, buffer); ghash_final(ctx, j0->data); + // In the masking scheme, the GHASH function now actually XORs the initial + // counter block S to the output. As we do not want to have this for J0, + // correct the output. + hardened_xor(j0->data, enc_initial_counter_block.data, kAesBlockNumWords); } else { // Should not happen; invalid IV length. return OTCRYPTO_BAD_ARGS; @@ -285,29 +326,16 @@ static status_t aes_gcm_get_tag(aes_gcm_context_t *ctx, size_t tag_len, __builtin_bswap64(((uint64_t)ctx->input_len) * 8), }; - // Finish computing S by appending (len64(A) || len64(C)). + // Append (len64(A) || len64(C)) and XOR the result with S1 to get the final + // tag. ghash_update(&ctx->ghash_ctx, kAesBlockNumBytes, (unsigned char *)last_block); - aes_block_t s; - ghash_final(&ctx->ghash_ctx, s.data); - - // Compute the tag T = GCTR(K, J0, S). - uint32_t full_tag[kAesBlockNumWords]; - size_t full_tag_len; - aes_block_t empty = {.data = {0}}; - HARDENED_TRY(aes_gcm_gctr(ctx->key, &ctx->initial_counter_block, - /*partial_len=*/0, &empty, kAesBlockNumBytes, - (unsigned char *)s.data, ctx->security_level, - &full_tag_len, (unsigned char *)full_tag)); - - // Sanity check. - if (full_tag_len != kAesBlockNumBytes) { - return OTCRYPTO_FATAL_ERR; - } + aes_block_t full_tag; + ghash_final(&ctx->ghash_ctx, full_tag.data); // Truncate the tag if needed. NIST requires we take the most significant // bits in big-endian representation, which corresponds to the least // significant bits in Ibex's little-endian representation. - HARDENED_TRY(hardened_memcpy(tag, full_tag, tag_len)); + HARDENED_TRY(hardened_memcpy(tag, full_tag.data, tag_len)); return OTCRYPTO_OK; } @@ -353,6 +381,33 @@ static status_t aes_gcm_init(const aes_key_t key, const size_t iv_len, HARDENED_TRY(aes_gcm_counter(iv_len, iv, &ctx->ghash_ctx, &ctx->initial_counter_block)); + // Create the encrypted initial counter block S. + aes_block_t zero; + memset(zero.data, 0, kAesBlockNumBytes); + aes_block_t enc_initial_counter_block; + HARDENED_TRY(aes_encrypt_block(key, &ctx->initial_counter_block, &zero, + &enc_initial_counter_block, + ctx->security_level)); + + // Split the initial counter block S into two shares S0 and S1. + // S0: random data. + aes_block_t enc_initial_counter_block0; + hardened_memshred(enc_initial_counter_block0.data, kAesBlockNumWords); + + // S1: S ^ S0 + aes_block_t enc_initial_counter_block1; + hardened_memcpy(enc_initial_counter_block1.data, + enc_initial_counter_block0.data, kAesBlockNumWords); + // TODO(#28008): make sure that we do not override shares. + hardened_xor(enc_initial_counter_block1.data, enc_initial_counter_block.data, + kAesBlockNumWords); + + // Calculate the masking correction terms and store the encrypted initial + // counter blocks. + ghash_handle_enc_initial_counter_block(enc_initial_counter_block0.data, + enc_initial_counter_block1.data, + &ctx->ghash_ctx); + // Set the initial IV for GCTR to inc32(J0). // The eventual ciphertext is C = GCTR(K, inc32(J0), plaintext). memcpy(ctx->gctr_iv.data, ctx->initial_counter_block.data, kAesBlockNumBytes); diff --git a/sw/device/lib/crypto/impl/aes_gcm/ghash.c b/sw/device/lib/crypto/impl/aes_gcm/ghash.c index 0d4426d328ba3..32da1cf1eadab 100644 --- a/sw/device/lib/crypto/impl/aes_gcm/ghash.c +++ b/sw/device/lib/crypto/impl/aes_gcm/ghash.c @@ -4,6 +4,7 @@ #include "sw/device/lib/crypto/impl/aes_gcm/ghash.h" +#include "sw/device/lib/base/hardened_memory.h" #include "sw/device/lib/base/macros.h" #include "sw/device/lib/base/memory.h" @@ -143,11 +144,11 @@ static uint8_t reverse_bits(uint8_t byte) { return out; } -void ghash_init_subkey(const uint32_t *hash_subkey, ghash_context_t *ctx) { +void ghash_init_subkey(const uint32_t *hash_subkey, ghash_block_t *tbl) { // Initialize 0 * H = 0. - memset(ctx->tbl[0].data, 0, kGhashBlockNumBytes); + memset(tbl[0].data, 0, kGhashBlockNumBytes); // Initialize 1 * H = H. - memcpy(ctx->tbl[0x8].data, hash_subkey, kGhashBlockNumBytes); + memcpy(tbl[0x8].data, hash_subkey, kGhashBlockNumBytes); // To get remaining entries, we use a variant of "shift and add"; in // polynomial terms, a shift is a multiplication by x. Note that, because the @@ -157,16 +158,19 @@ void ghash_init_subkey(const uint32_t *hash_subkey, ghash_context_t *ctx) { for (uint8_t i = 2; i < 16; i += 2) { // Find the product corresponding to (i >> 1) * H and multiply by x to // shift 1; this will be i * H. - galois_mulx(&ctx->tbl[reverse_bits(i >> 1)], &ctx->tbl[reverse_bits(i)]); + galois_mulx(&tbl[reverse_bits(i >> 1)], &tbl[reverse_bits(i)]); // Add H to i * H to get (i + 1) * H. - block_xor(&ctx->tbl[reverse_bits(i)], &ctx->tbl[0x8], - &ctx->tbl[reverse_bits(i + 1)]); + block_xor(&tbl[reverse_bits(i)], &tbl[0x8], &tbl[reverse_bits(i + 1)]); } } void ghash_init(ghash_context_t *ctx) { - memset(ctx->state.data, 0, kGhashBlockNumBytes); + // Randomize the initial state. + hardened_memshred(ctx->state0.data, kGhashBlockNumWords); + hardened_memshred(ctx->state1.data, kGhashBlockNumWords); + // Initialize the ghash block counter. + ctx->ghash_block_cnt = 0; } /** @@ -182,9 +186,12 @@ void ghash_init(ghash_context_t *ctx) { * This operation corresponds to multiplication in the Galois field with order * 2^128, modulo the polynomial x^128 + x^8 + x^2 + x + 1 * - * @param ctx GHASH context, updated in place. + * @param state GHASH state. + * @param tbl Product table for the masked hash subkey. + * @return Multiplication of the state and the hash subkey. */ -static void galois_mul_state_key(ghash_context_t *ctx) { +static ghash_block_t galois_mul_state_key(ghash_block_t state, + ghash_block_t tbl[16]) { // Initialize the multiplication result to 0. ghash_block_t result; memset(result.data, 0, kGhashBlockNumBytes); @@ -217,7 +224,7 @@ static void galois_mul_state_key(ghash_context_t *ctx) { // Add the product of the next window and H to `result`. We process the // windows starting with the most significant polynomial terms, which means // starting from the last byte and proceeding to the first. - uint8_t tbl_index = block_byte_get(&ctx->state, (kNumWindows - 1 - i) >> 1); + uint8_t tbl_index = block_byte_get(&state, (kNumWindows - 1 - i) >> 1); // Select the less significant 4 bits if i is even, or the more significant // 4 bits if i is odd. This does not need to be constant time, since the @@ -227,10 +234,9 @@ static void galois_mul_state_key(ghash_context_t *ctx) { } else { tbl_index &= 0x0f; } - block_xor(&result, &ctx->tbl[tbl_index], &result); + block_xor(&result, &tbl[tbl_index], &result); } - - memcpy(ctx->state.data, result.data, kGhashBlockNumBytes); + return result; } /** @@ -240,10 +246,67 @@ static void galois_mul_state_key(ghash_context_t *ctx) { * @param block Block to incorporate. */ static void ghash_process_block(ghash_context_t *ctx, ghash_block_t *block) { - // XOR `state` with the next input block. - block_xor(&ctx->state, block, &ctx->state); - // Multiply state by H in-place. - galois_mul_state_key(ctx); + ghash_block_t s0_tmp; + ghash_block_t s1_tmp; + if (ctx->ghash_block_cnt == 0) { + // Process share 0. + // share0_tmp = (S0 + T0) * H0 + hardened_memcpy(s0_tmp.data, block->data, kGhashBlockNumWords); + hardened_xor(s0_tmp.data, ctx->enc_initial_counter_block0.data, + kGhashBlockNumWords); + s0_tmp = galois_mul_state_key(s0_tmp, ctx->tbl0); + + // Apply the correction terms for state share 0. + // share0 = share0_tmp + (S0*(H0+1)) + hardened_memcpy(ctx->state0.data, s0_tmp.data, kGhashBlockNumWords); + hardened_xor(ctx->state0.data, ctx->correction_term0.data, + kGhashBlockNumWords); + + // TODO(#28013): randomize register file content before processing the + // second share. + + // Process share 1. + // share1_tmp = (S1 + T0) * H1 + hardened_memcpy(s1_tmp.data, block->data, kGhashBlockNumWords); + hardened_xor(s1_tmp.data, ctx->enc_initial_counter_block1.data, + kGhashBlockNumWords); + s1_tmp = galois_mul_state_key(s1_tmp, ctx->tbl1); + + // Apply the correction terms for state share 1. + // share1 = share1_tmp + correction_term1 + hardened_memcpy(ctx->state1.data, s1_tmp.data, kGhashBlockNumWords); + hardened_xor(ctx->state1.data, ctx->correction_term1_init.data, + kGhashBlockNumWords); + } else { + // Process share 0. + // tmp = (share0+TN-1)+share1 + ghash_block_t tmp; + hardened_memcpy(tmp.data, block->data, kGhashBlockNumWords); + hardened_xor(tmp.data, ctx->state0.data, kGhashBlockNumWords); + hardened_xor(tmp.data, ctx->state1.data, kGhashBlockNumWords); + + // s0_tmp = tmp * H0 + s0_tmp = galois_mul_state_key(tmp, ctx->tbl0); + + // Apply the correction terms for state share 0. + // share0 = share0_tmp + (S0*(H0+1)) + hardened_memcpy(ctx->state0.data, s0_tmp.data, kGhashBlockNumWords); + hardened_xor(ctx->state0.data, ctx->correction_term0.data, + kGhashBlockNumWords); + + // Process share 1. + // share1_tmp = tmp * H1 + s1_tmp = galois_mul_state_key(tmp, ctx->tbl1); + + // Apply the correction terms for state share 1. + // share1 = share1_tmp + (S0*H0) + hardened_memcpy(ctx->state1.data, s1_tmp.data, kGhashBlockNumWords); + hardened_xor(ctx->state1.data, ctx->correction_term1.data, + kGhashBlockNumWords); + } + + // Increment the number of processed ghash block counter. + ctx->ghash_block_cnt++; } void ghash_process_full_blocks(ghash_context_t *ctx, size_t partial_len, @@ -293,6 +356,35 @@ void ghash_update(ghash_context_t *ctx, size_t input_len, } } +void ghash_handle_enc_initial_counter_block( + const uint32_t *enc_initial_counter_block0, + const uint32_t *enc_initial_counter_block1, ghash_context_t *ctx) { + // correction_term0 = S0 * (H0 + 1). + ghash_block_t s0; + hardened_memcpy(s0.data, enc_initial_counter_block0, kGhashBlockNumWords); + ghash_block_t mul_tmp = galois_mul_state_key(s0, ctx->tbl0); + block_xor(&mul_tmp, &s0, &ctx->correction_term0); + + // correction_term1 = S0 * H1. + ctx->correction_term1 = galois_mul_state_key(s0, ctx->tbl1); + + // correction_term1_init = S1 * H1. + ghash_block_t s1; + hardened_memcpy(s1.data, enc_initial_counter_block1, kGhashBlockNumWords); + ctx->correction_term1_init = galois_mul_state_key(s1, ctx->tbl1); + + // Save the encrypted initial counter blocks into the ghash context as we + // need them throughout the ghash computations. + hardened_memcpy(ctx->enc_initial_counter_block0.data, + enc_initial_counter_block0, kGhashBlockNumWords); + hardened_memcpy(ctx->enc_initial_counter_block1.data, + enc_initial_counter_block1, kGhashBlockNumWords); +} + void ghash_final(ghash_context_t *ctx, uint32_t *result) { - memcpy(result, ctx->state.data, kGhashBlockNumBytes); + // Tag = (state0 + state1) + S1 + ghash_block_t final_block; + block_xor(&ctx->state0, &ctx->state1, &final_block); + block_xor(&final_block, &ctx->enc_initial_counter_block1, &final_block); + memcpy(result, final_block.data, kGhashBlockNumBytes); } diff --git a/sw/device/lib/crypto/impl/aes_gcm/ghash.h b/sw/device/lib/crypto/impl/aes_gcm/ghash.h index d7f05638c910e..688cd2c7a9d1a 100644 --- a/sw/device/lib/crypto/impl/aes_gcm/ghash.h +++ b/sw/device/lib/crypto/impl/aes_gcm/ghash.h @@ -32,13 +32,45 @@ typedef struct ghash_block { typedef struct ghash_context { /** - * Precomputed product table for the hash subkey. + * Precomputed product table for the hash subkey share 0. */ - ghash_block_t tbl[16]; + ghash_block_t tbl0[16]; /** - * Cipher block representing the current GHASH state. + * Precomputed product table for the hash subkey share 1. */ - ghash_block_t state; + ghash_block_t tbl1[16]; + /** + * Cipher block representing the current GHASH state for share 0. + */ + ghash_block_t state0; + /** + * Cipher block representing the current GHASH state for share 1. + */ + ghash_block_t state1; + /** + * Precomputed correction term (S0 * (H0+1)) for state share 0. + */ + ghash_block_t correction_term0; + /** + * Precomputed correction term (S0 * H1) for state share 1. + */ + ghash_block_t correction_term1; + /** + * Precomputed initial correction term (S1 * H1) for state share 1. + */ + ghash_block_t correction_term1_init; + /** + * Encrypted initial counter block share 0. + */ + ghash_block_t enc_initial_counter_block0; + /** + * Encrypted initial counter block share 1. + */ + ghash_block_t enc_initial_counter_block1; + /** + * Number of processed ghash blocks. + */ + size_t ghash_block_cnt; } ghash_context_t; /** @@ -55,9 +87,9 @@ typedef struct ghash_context { * * @param hash_subkey Subkey for the GHASH operation (`kGhashBlockNumWords` * words). - * @param[out] ctx Context object with product table populated. + * @param[out] tbl The populated product table. */ -void ghash_init_subkey(const uint32_t *hash_subkey, ghash_context_t *ctx); +void ghash_init_subkey(const uint32_t *hash_subkey, ghash_block_t *tbl); /** * Start a GHASH operation. @@ -106,6 +138,21 @@ void ghash_process_full_blocks(ghash_context_t *ctx, size_t partial_len, */ void ghash_update(ghash_context_t *ctx, size_t input_len, const uint8_t *input); +/** + * Computes the correction terms needed for the masking scheme. + * + * correction_term0 = S0 * (H0 + 1). + * correction_term1 = S0 * H1. + * correction_term1_init = S1 * H1. + * + * @param enc_initial_counter_block0 Pointer to S0. + * @param enc_initial_counter_block1 Pointer to S1. + * @param ctx Context object. + */ +void ghash_handle_enc_initial_counter_block( + const uint32_t *enc_initial_counter_block0, + const uint32_t *enc_initial_counter_block1, ghash_context_t *ctx); + /** * Update the state of a GHASH operation. * diff --git a/sw/device/lib/crypto/impl/aes_gcm/ghash_unittest.cc b/sw/device/lib/crypto/impl/aes_gcm/ghash_unittest.cc index cb6061de0f31a..456dfebc86470 100644 --- a/sw/device/lib/crypto/impl/aes_gcm/ghash_unittest.cc +++ b/sw/device/lib/crypto/impl/aes_gcm/ghash_unittest.cc @@ -13,6 +13,14 @@ namespace ghash_unittest { namespace { using ::testing::ElementsAreArray; +// Zero block. +std::array Zero = { + 0x0, + 0x0, + 0x0, + 0x0, +}; + TEST(Ghash, McGrawViegaTestCase1) { // GHASH computation from test case 1 of: // https://csrc.nist.rip/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-spec.pdf @@ -31,7 +39,9 @@ TEST(Ghash, McGrawViegaTestCase1) { // Compute GHASH(H, A, C). ghash_context_t ctx; - ghash_init_subkey(H.data(), &ctx); + ghash_init_subkey(H.data(), ctx.tbl0); + ghash_init_subkey(Zero.data(), ctx.tbl1); + ghash_handle_enc_initial_counter_block(Zero.data(), Zero.data(), &ctx); ghash_init(&ctx); uint32_t result[kGhashBlockNumWords]; ghash_final(&ctx, result); @@ -59,11 +69,11 @@ TEST(Ghash, ProcessFullBlocksOneByte) { // Initialize context. ghash_context_t ctx; - ghash_init_subkey(H.data(), &ctx); + ghash_init_subkey(H.data(), ctx.tbl0); + ghash_init_subkey(Zero.data(), ctx.tbl1); + ghash_handle_enc_initial_counter_block(Zero.data(), Zero.data(), &ctx); ghash_init(&ctx); - EXPECT_THAT(ctx.state.data, testing::ElementsAreArray(zero_block)); ghash_process_full_blocks(&ctx, partial_len, &partial, input_len, input); - EXPECT_THAT(ctx.state.data, testing::ElementsAreArray(zero_block)); EXPECT_EQ(partial.data[0], input_word); EXPECT_EQ(partial.data[1], 0); EXPECT_EQ(partial.data[2], 0); @@ -85,9 +95,10 @@ TEST(Ghash, Mul1) { uint8_t one = 0x80; ghash_context_t ctx; - ghash_init_subkey(H.data(), &ctx); + ghash_init_subkey(H.data(), ctx.tbl0); + ghash_init_subkey(Zero.data(), ctx.tbl1); + ghash_handle_enc_initial_counter_block(Zero.data(), Zero.data(), &ctx); ghash_init(&ctx); - EXPECT_THAT(ctx.state.data, testing::ElementsAreArray(zero)); ghash_block_t partial = {.data = {0}}; size_t partial_len = 0; @@ -96,10 +107,9 @@ TEST(Ghash, Mul1) { ghash_process_full_blocks(&ctx, partial_len, &partial, input_len, input); EXPECT_LT(input_len, kGhashBlockNumBytes - partial_len); EXPECT_EQ(partial.data[0], one); - EXPECT_THAT(ctx.state.data, testing::ElementsAreArray(zero)); ghash_update(&ctx, 1, &one); - EXPECT_THAT(ctx.state.data, testing::ElementsAreArray(H)); + EXPECT_THAT(ctx.state0.data, testing::ElementsAreArray(H)); uint32_t result[kGhashBlockNumWords]; ghash_final(&ctx, result); @@ -144,7 +154,9 @@ TEST(Ghash, McGrawViegaTestCase2) { // Compute GHASH(H, A, C). ghash_context_t ctx; - ghash_init_subkey(H.data(), &ctx); + ghash_init_subkey(H.data(), ctx.tbl0); + ghash_init_subkey(Zero.data(), ctx.tbl1); + ghash_handle_enc_initial_counter_block(Zero.data(), Zero.data(), &ctx); ghash_init(&ctx); ghash_update(&ctx, A.size() * sizeof(uint32_t), (unsigned char *)A.data()); ghash_update(&ctx, C.size() * sizeof(uint32_t), (unsigned char *)C.data()); @@ -190,7 +202,9 @@ TEST(Ghash, ContextReset) { // Initialize the hash subkey (should only need to do this once). ghash_context_t ctx; - ghash_init_subkey(H.data(), &ctx); + ghash_init_subkey(H.data(), ctx.tbl0); + ghash_init_subkey(Zero.data(), ctx.tbl1); + ghash_handle_enc_initial_counter_block(Zero.data(), Zero.data(), &ctx); // Compute GHASH(H, A, C). ghash_init(&ctx); @@ -254,7 +268,9 @@ TEST(Ghash, McGrawViegaTestCase18) { // Compute GHASH(H, A, C). ghash_context_t ctx; - ghash_init_subkey(H.data(), &ctx); + ghash_init_subkey(H.data(), ctx.tbl0); + ghash_init_subkey(Zero.data(), ctx.tbl1); + ghash_handle_enc_initial_counter_block(Zero.data(), Zero.data(), &ctx); ghash_init(&ctx); ghash_update(&ctx, A.size() * sizeof(uint32_t), (unsigned char *)A.data()); ghash_update(&ctx, C.size() * sizeof(uint32_t), (unsigned char *)C.data()); diff --git a/sw/device/lib/crypto/include/aes_gcm.h b/sw/device/lib/crypto/include/aes_gcm.h index f456ad399069a..291e8f94c7ed1 100644 --- a/sw/device/lib/crypto/include/aes_gcm.h +++ b/sw/device/lib/crypto/include/aes_gcm.h @@ -39,7 +39,7 @@ typedef enum otcrypto_aes_gcm_tag_len { * change.  */ typedef struct otcrypto_aes_gcm_context { - uint32_t data[102]; + uint32_t data[192]; } otcrypto_aes_gcm_context_t; /** From 10c6bbd326ec920c66fd36c0e026df74cedf9a86 Mon Sep 17 00:00:00 2001 From: Pascal Nasahl Date: Fri, 29 Aug 2025 14:20:38 +0200 Subject: [PATCH 44/51] [crypto] Rename hardened_xor Rename this function to hardend_xor_in_place as the next commit introduces a hardened_xor that does not store the result in-place. Signed-off-by: Pascal Nasahl (cherry picked from commit 542beff0988afcfc65f8fb501beed186600a8544) --- sw/device/lib/base/hardened_memory.c | 4 +-- sw/device/lib/base/hardened_memory.h | 6 ++--- sw/device/lib/crypto/impl/aes_gcm.c | 6 +++-- sw/device/lib/crypto/impl/aes_gcm/aes_gcm.c | 14 ++++++----- sw/device/lib/crypto/impl/aes_gcm/ghash.c | 28 ++++++++++----------- sw/device/lib/crypto/impl/keyblob.c | 4 +-- 6 files changed, 33 insertions(+), 29 deletions(-) diff --git a/sw/device/lib/base/hardened_memory.c b/sw/device/lib/base/hardened_memory.c index c8965ef793e55..12696d679c0c1 100644 --- a/sw/device/lib/base/hardened_memory.c +++ b/sw/device/lib/base/hardened_memory.c @@ -182,8 +182,8 @@ hardened_bool_t hardened_memeq(const uint32_t *lhs, const uint32_t *rhs, return kHardenedBoolFalse; } -status_t hardened_xor(uint32_t *restrict x, const uint32_t *restrict y, - size_t word_len) { +status_t hardened_xor_in_place(uint32_t *restrict x, const uint32_t *restrict y, + size_t word_len) { // Generate a random ordering. random_order_t order; random_order_init(&order, word_len); diff --git a/sw/device/lib/base/hardened_memory.h b/sw/device/lib/base/hardened_memory.h index ae1ad3b9bcae5..fdbceca436c29 100644 --- a/sw/device/lib/base/hardened_memory.h +++ b/sw/device/lib/base/hardened_memory.h @@ -94,7 +94,7 @@ hardened_bool_t hardened_memeq(const uint32_t *lhs, const uint32_t *rhs, size_t word_len); /** - * Combines two word buffers with XOR. + * Combines two word buffers with XOR in-place. * * Callers should ensure the entropy complex is up before calling this * function. The implementation uses random-order hardening primitives for @@ -105,8 +105,8 @@ hardened_bool_t hardened_memeq(const uint32_t *lhs, const uint32_t *rhs, * @param word_len Length in words of each operand. * @return OK or error. */ -status_t hardened_xor(uint32_t *OT_RESTRICT x, const uint32_t *OT_RESTRICT y, - size_t word_len); +status_t hardened_xor_in_place(uint32_t *OT_RESTRICT x, + const uint32_t *OT_RESTRICT y, size_t word_len); #ifdef __cplusplus } // extern "C" diff --git a/sw/device/lib/crypto/impl/aes_gcm.c b/sw/device/lib/crypto/impl/aes_gcm.c index 36af807c06892..b957c49d5e7d1 100644 --- a/sw/device/lib/crypto/impl/aes_gcm.c +++ b/sw/device/lib/crypto/impl/aes_gcm.c @@ -85,9 +85,11 @@ status_t gcm_remask_key(aes_gcm_context_t *internal_ctx) { HARDENED_TRY(hardened_memshred(mask, internal_ctx->key.key_len)); // XOR each share with the mask. - HARDENED_TRY(hardened_xor((uint32_t *)internal_ctx->key.key_shares[0], mask, + HARDENED_TRY( + hardened_xor_in_place((uint32_t *)internal_ctx->key.key_shares[0], mask, internal_ctx->key.key_len)); - HARDENED_TRY(hardened_xor((uint32_t *)internal_ctx->key.key_shares[1], mask, + HARDENED_TRY( + hardened_xor_in_place((uint32_t *)internal_ctx->key.key_shares[1], mask, internal_ctx->key.key_len)); // Update the checksum. internal_ctx->key.checksum = aes_key_integrity_checksum(&internal_ctx->key); diff --git a/sw/device/lib/crypto/impl/aes_gcm/aes_gcm.c b/sw/device/lib/crypto/impl/aes_gcm/aes_gcm.c index e1be9e291143e..a7a6455a09b7f 100644 --- a/sw/device/lib/crypto/impl/aes_gcm/aes_gcm.c +++ b/sw/device/lib/crypto/impl/aes_gcm/aes_gcm.c @@ -231,7 +231,8 @@ static status_t aes_gcm_hash_subkey( hardened_memcpy(hash_subkey_share1.data, hash_subkey_share0.data, kAesBlockNumWords); // TODO(#28008): make sure that we do not override shares. - hardened_xor(hash_subkey_share1.data, hash_subkey.data, kAesBlockNumWords); + hardened_xor_in_place(hash_subkey_share1.data, hash_subkey.data, + kAesBlockNumWords); // Set the key for the GHASH context. ghash_init_subkey(hash_subkey_share0.data, ctx->tbl0); @@ -278,8 +279,8 @@ static status_t aes_gcm_counter(const size_t iv_len, const uint32_t *iv, aes_block_t enc_initial_counter_block1; hardened_memcpy(enc_initial_counter_block1.data, enc_initial_counter_block0.data, kAesBlockNumWords); - hardened_xor(enc_initial_counter_block1.data, - enc_initial_counter_block.data, kAesBlockNumWords); + hardened_xor_in_place(enc_initial_counter_block1.data, + enc_initial_counter_block.data, kAesBlockNumWords); // Calculate the masking correction terms and store the encrypted initial // counter blocks S0 and S1. @@ -296,7 +297,8 @@ static status_t aes_gcm_counter(const size_t iv_len, const uint32_t *iv, // In the masking scheme, the GHASH function now actually XORs the initial // counter block S to the output. As we do not want to have this for J0, // correct the output. - hardened_xor(j0->data, enc_initial_counter_block.data, kAesBlockNumWords); + hardened_xor_in_place(j0->data, enc_initial_counter_block.data, + kAesBlockNumWords); } else { // Should not happen; invalid IV length. return OTCRYPTO_BAD_ARGS; @@ -399,8 +401,8 @@ static status_t aes_gcm_init(const aes_key_t key, const size_t iv_len, hardened_memcpy(enc_initial_counter_block1.data, enc_initial_counter_block0.data, kAesBlockNumWords); // TODO(#28008): make sure that we do not override shares. - hardened_xor(enc_initial_counter_block1.data, enc_initial_counter_block.data, - kAesBlockNumWords); + hardened_xor_in_place(enc_initial_counter_block1.data, + enc_initial_counter_block.data, kAesBlockNumWords); // Calculate the masking correction terms and store the encrypted initial // counter blocks. diff --git a/sw/device/lib/crypto/impl/aes_gcm/ghash.c b/sw/device/lib/crypto/impl/aes_gcm/ghash.c index 32da1cf1eadab..997f2acbb42bf 100644 --- a/sw/device/lib/crypto/impl/aes_gcm/ghash.c +++ b/sw/device/lib/crypto/impl/aes_gcm/ghash.c @@ -252,15 +252,15 @@ static void ghash_process_block(ghash_context_t *ctx, ghash_block_t *block) { // Process share 0. // share0_tmp = (S0 + T0) * H0 hardened_memcpy(s0_tmp.data, block->data, kGhashBlockNumWords); - hardened_xor(s0_tmp.data, ctx->enc_initial_counter_block0.data, - kGhashBlockNumWords); + hardened_xor_in_place(s0_tmp.data, ctx->enc_initial_counter_block0.data, + kGhashBlockNumWords); s0_tmp = galois_mul_state_key(s0_tmp, ctx->tbl0); // Apply the correction terms for state share 0. // share0 = share0_tmp + (S0*(H0+1)) hardened_memcpy(ctx->state0.data, s0_tmp.data, kGhashBlockNumWords); - hardened_xor(ctx->state0.data, ctx->correction_term0.data, - kGhashBlockNumWords); + hardened_xor_in_place(ctx->state0.data, ctx->correction_term0.data, + kGhashBlockNumWords); // TODO(#28013): randomize register file content before processing the // second share. @@ -268,22 +268,22 @@ static void ghash_process_block(ghash_context_t *ctx, ghash_block_t *block) { // Process share 1. // share1_tmp = (S1 + T0) * H1 hardened_memcpy(s1_tmp.data, block->data, kGhashBlockNumWords); - hardened_xor(s1_tmp.data, ctx->enc_initial_counter_block1.data, - kGhashBlockNumWords); + hardened_xor_in_place(s1_tmp.data, ctx->enc_initial_counter_block1.data, + kGhashBlockNumWords); s1_tmp = galois_mul_state_key(s1_tmp, ctx->tbl1); // Apply the correction terms for state share 1. // share1 = share1_tmp + correction_term1 hardened_memcpy(ctx->state1.data, s1_tmp.data, kGhashBlockNumWords); - hardened_xor(ctx->state1.data, ctx->correction_term1_init.data, - kGhashBlockNumWords); + hardened_xor_in_place(ctx->state1.data, ctx->correction_term1_init.data, + kGhashBlockNumWords); } else { // Process share 0. // tmp = (share0+TN-1)+share1 ghash_block_t tmp; hardened_memcpy(tmp.data, block->data, kGhashBlockNumWords); - hardened_xor(tmp.data, ctx->state0.data, kGhashBlockNumWords); - hardened_xor(tmp.data, ctx->state1.data, kGhashBlockNumWords); + hardened_xor_in_place(tmp.data, ctx->state0.data, kGhashBlockNumWords); + hardened_xor_in_place(tmp.data, ctx->state1.data, kGhashBlockNumWords); // s0_tmp = tmp * H0 s0_tmp = galois_mul_state_key(tmp, ctx->tbl0); @@ -291,8 +291,8 @@ static void ghash_process_block(ghash_context_t *ctx, ghash_block_t *block) { // Apply the correction terms for state share 0. // share0 = share0_tmp + (S0*(H0+1)) hardened_memcpy(ctx->state0.data, s0_tmp.data, kGhashBlockNumWords); - hardened_xor(ctx->state0.data, ctx->correction_term0.data, - kGhashBlockNumWords); + hardened_xor_in_place(ctx->state0.data, ctx->correction_term0.data, + kGhashBlockNumWords); // Process share 1. // share1_tmp = tmp * H1 @@ -301,8 +301,8 @@ static void ghash_process_block(ghash_context_t *ctx, ghash_block_t *block) { // Apply the correction terms for state share 1. // share1 = share1_tmp + (S0*H0) hardened_memcpy(ctx->state1.data, s1_tmp.data, kGhashBlockNumWords); - hardened_xor(ctx->state1.data, ctx->correction_term1.data, - kGhashBlockNumWords); + hardened_xor_in_place(ctx->state1.data, ctx->correction_term1.data, + kGhashBlockNumWords); } // Increment the number of processed ghash block counter. diff --git a/sw/device/lib/crypto/impl/keyblob.c b/sw/device/lib/crypto/impl/keyblob.c index 410148eec8eb7..de3efa0a8107b 100644 --- a/sw/device/lib/crypto/impl/keyblob.c +++ b/sw/device/lib/crypto/impl/keyblob.c @@ -228,8 +228,8 @@ status_t keyblob_remask(otcrypto_blinded_key_t *key) { HARDENED_TRY(hardened_memshred(mask, key_share_words)); // XOR each share with the mask. - HARDENED_TRY(hardened_xor(share0, mask, key_share_words)); - HARDENED_TRY(hardened_xor(share1, mask, key_share_words)); + HARDENED_TRY(hardened_xor_in_place(share0, mask, key_share_words)); + HARDENED_TRY(hardened_xor_in_place(share1, mask, key_share_words)); // Update the key checksum. key->checksum = integrity_blinded_checksum(key); From 4da01063b568aec93b40720387af1f7250feba44 Mon Sep 17 00:00:00 2001 From: Pascal Nasahl Date: Fri, 29 Aug 2025 15:02:25 +0200 Subject: [PATCH 45/51] [crypto] Add improved hardened_xor function Takes `x` and `y` and writes `x ^ y` to the `dest` output buffer. To avoid combining `x` and `y` in the XOR operation, the function actually performs: `dest = ((rand ^ x) ^ y) ^ rand` Closes lowRISC/opentitan#28008 Signed-off-by: Pascal Nasahl (cherry picked from commit 54116843ab4dc031f04e6cfc3e2452fff6980a74) --- sw/device/lib/base/hardened_memory.c | 52 ++++++++++++++++++++++++++++ sw/device/lib/base/hardened_memory.h | 20 +++++++++++ 2 files changed, 72 insertions(+) diff --git a/sw/device/lib/base/hardened_memory.c b/sw/device/lib/base/hardened_memory.c index 12696d679c0c1..1ebc314c3a00c 100644 --- a/sw/device/lib/base/hardened_memory.c +++ b/sw/device/lib/base/hardened_memory.c @@ -182,6 +182,58 @@ hardened_bool_t hardened_memeq(const uint32_t *lhs, const uint32_t *rhs, return kHardenedBoolFalse; } +status_t hardened_xor(const uint32_t *restrict x, const uint32_t *restrict y, + size_t word_len, uint32_t *restrict dest) { + // Randomize the content of the output buffer before writing to it. + hardened_memshred(dest, word_len); + + // Create a random variable rand. + uint32_t rand[word_len]; + hardened_memshred(rand, word_len); + + // Cast pointers to `uintptr_t` to erase their provenance. + uintptr_t x_addr = (uintptr_t)x; + uintptr_t y_addr = (uintptr_t)y; + uintptr_t dest_addr = (uintptr_t)dest; + uintptr_t rand_addr = (uintptr_t)&rand; + + // Generate a random ordering. + random_order_t order; + random_order_init(&order, word_len); + size_t count = 0; + size_t expected_count = random_order_len(&order); + + // XOR the mask with the first share. This loop is modelled off the one in + // `hardened_memcpy`; see the comments there for more details. + for (; launderw(count) < expected_count; count = launderw(count) + 1) { + size_t byte_idx = launderw(random_order_advance(&order)) * sizeof(uint32_t); + + // Prevent the compiler from re-ordering the loop. + barrierw(byte_idx); + + // Calculate pointers. + uintptr_t xp = x_addr + byte_idx; + uintptr_t yp = y_addr + byte_idx; + uintptr_t destp = dest_addr + byte_idx; + uintptr_t randp = rand_addr + byte_idx; + + // Set the pointers. + void *xv = (void *)launderw(xp); + void *yv = (void *)launderw(yp); + void *destv = (void *)launderw(destp); + void *randv = (void *)launderw(randp); + + // Perform the XORs: dest = ((x ^ rand) ^ y) ^ rand + write_32(read_32(xv) ^ read_32(randv), destv); + write_32(read_32(destv) ^ read_32(yv), destv); + write_32(read_32(destv) ^ read_32(randv), destv); + } + RANDOM_ORDER_HARDENED_CHECK_DONE(order); + HARDENED_CHECK_EQ(count, expected_count); + + return OTCRYPTO_OK; +} + status_t hardened_xor_in_place(uint32_t *restrict x, const uint32_t *restrict y, size_t word_len) { // Generate a random ordering. diff --git a/sw/device/lib/base/hardened_memory.h b/sw/device/lib/base/hardened_memory.h index fdbceca436c29..1edeb9f7f4801 100644 --- a/sw/device/lib/base/hardened_memory.h +++ b/sw/device/lib/base/hardened_memory.h @@ -93,6 +93,26 @@ status_t hardened_memshred(uint32_t *dest, size_t word_len); hardened_bool_t hardened_memeq(const uint32_t *lhs, const uint32_t *rhs, size_t word_len); +/** + * Combines two word buffers with XOR and store the result in the dest. buffer. + * + * Performs dest = ((rand ^ x) ^ y) ^ rand + * + * Callers should ensure the entropy complex is up before calling this + * function. The implementation uses random-order hardening primitives for + * side-channel defense. Moreover, calles should ensure that the dest. buffer + * is different from the source buffers. + * + * @param x Pointer to the first operand. + * @param y Pointer to the second operand. + * @param word_len Length in words of each operand. + * @param dest[out] Pointer to the output buffer. + * @return OK or error. + */ +status_t hardened_xor(const uint32_t *OT_RESTRICT x, + const uint32_t *OT_RESTRICT y, size_t word_len, + uint32_t *OT_RESTRICT dest); + /** * Combines two word buffers with XOR in-place. * From 3ba3872f25a2cd5c9da71427c62920f1269a323c Mon Sep 17 00:00:00 2001 From: Pascal Nasahl Date: Fri, 29 Aug 2025 15:38:49 +0200 Subject: [PATCH 46/51] [crypto] Avoid overriding shares in AES-GCM With the previous hardend_xor implementation, we were overriding share0 with share1, which leaks. By using the improved hardened_xor, we avoiding this issue. Signed-off-by: Pascal Nasahl (cherry picked from commit b126c26037688f186230539c47165d4f7fb6b9b3) --- sw/device/lib/crypto/impl/aes_gcm/aes_gcm.c | 21 +++++++-------------- sw/device/lib/crypto/impl/aes_gcm/ghash.c | 8 ++++++-- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/sw/device/lib/crypto/impl/aes_gcm/aes_gcm.c b/sw/device/lib/crypto/impl/aes_gcm/aes_gcm.c index a7a6455a09b7f..e97c4f21ee44b 100644 --- a/sw/device/lib/crypto/impl/aes_gcm/aes_gcm.c +++ b/sw/device/lib/crypto/impl/aes_gcm/aes_gcm.c @@ -228,11 +228,8 @@ static status_t aes_gcm_hash_subkey( hardened_memshred(hash_subkey_share0.data, kAesBlockNumWords); // Share 1: hash_subkey ^ hash_subkey_share0 - hardened_memcpy(hash_subkey_share1.data, hash_subkey_share0.data, - kAesBlockNumWords); - // TODO(#28008): make sure that we do not override shares. - hardened_xor_in_place(hash_subkey_share1.data, hash_subkey.data, - kAesBlockNumWords); + hardened_xor(hash_subkey_share0.data, hash_subkey.data, kAesBlockNumWords, + hash_subkey_share1.data); // Set the key for the GHASH context. ghash_init_subkey(hash_subkey_share0.data, ctx->tbl0); @@ -277,10 +274,9 @@ static status_t aes_gcm_counter(const size_t iv_len, const uint32_t *iv, // S1: S ^ S0 aes_block_t enc_initial_counter_block1; - hardened_memcpy(enc_initial_counter_block1.data, - enc_initial_counter_block0.data, kAesBlockNumWords); - hardened_xor_in_place(enc_initial_counter_block1.data, - enc_initial_counter_block.data, kAesBlockNumWords); + hardened_xor(enc_initial_counter_block0.data, + enc_initial_counter_block.data, kAesBlockNumWords, + enc_initial_counter_block1.data); // Calculate the masking correction terms and store the encrypted initial // counter blocks S0 and S1. @@ -398,11 +394,8 @@ static status_t aes_gcm_init(const aes_key_t key, const size_t iv_len, // S1: S ^ S0 aes_block_t enc_initial_counter_block1; - hardened_memcpy(enc_initial_counter_block1.data, - enc_initial_counter_block0.data, kAesBlockNumWords); - // TODO(#28008): make sure that we do not override shares. - hardened_xor_in_place(enc_initial_counter_block1.data, - enc_initial_counter_block.data, kAesBlockNumWords); + hardened_xor(enc_initial_counter_block0.data, enc_initial_counter_block.data, + kAesBlockNumWords, enc_initial_counter_block1.data); // Calculate the masking correction terms and store the encrypted initial // counter blocks. diff --git a/sw/device/lib/crypto/impl/aes_gcm/ghash.c b/sw/device/lib/crypto/impl/aes_gcm/ghash.c index 997f2acbb42bf..d3f510ebb25e6 100644 --- a/sw/device/lib/crypto/impl/aes_gcm/ghash.c +++ b/sw/device/lib/crypto/impl/aes_gcm/ghash.c @@ -383,8 +383,12 @@ void ghash_handle_enc_initial_counter_block( void ghash_final(ghash_context_t *ctx, uint32_t *result) { // Tag = (state0 + state1) + S1 + ghash_block_t tmp_block; ghash_block_t final_block; - block_xor(&ctx->state0, &ctx->state1, &final_block); - block_xor(&final_block, &ctx->enc_initial_counter_block1, &final_block); + hardened_xor(ctx->state0.data, ctx->state1.data, kGhashBlockNumWords, + tmp_block.data); + hardened_xor(tmp_block.data, ctx->enc_initial_counter_block1.data, + kGhashBlockNumWords, final_block.data); + memcpy(result, final_block.data, kGhashBlockNumBytes); } From b76af53197560e5bdb5929795cd727c6f5ea6ad0 Mon Sep 17 00:00:00 2001 From: Pascal Nasahl Date: Tue, 22 Jul 2025 18:27:04 +0200 Subject: [PATCH 47/51] [crypto] Add functions to disable/enable iCache As code in CL should be executed in constant time, this commit adds new functions that allow the CL to disable and restore the state of the Ibex instruction cache. Signed-off-by: Pascal Nasahl (cherry picked from commit 7845666a5e408c1b8c1aa8704866a8cccb416ce4) --- sw/device/lib/crypto/drivers/BUILD | 2 ++ sw/device/lib/crypto/drivers/rv_core_ibex.c | 36 +++++++++++++++++++++ sw/device/lib/crypto/drivers/rv_core_ibex.h | 23 +++++++++++++ 3 files changed, 61 insertions(+) diff --git a/sw/device/lib/crypto/drivers/BUILD b/sw/device/lib/crypto/drivers/BUILD index 0d36e4269622a..469e7d3c07aaa 100644 --- a/sw/device/lib/crypto/drivers/BUILD +++ b/sw/device/lib/crypto/drivers/BUILD @@ -228,6 +228,8 @@ dual_cc_library( "//hw/ip/rv_core_ibex/data:rv_core_ibex_c_regs", "//sw/device/lib/base:abs_mmio", "//sw/device/lib/base:csr", + "//sw/device/lib/base:hardened", + "//sw/device/lib/crypto/impl:status", ], ), ) diff --git a/sw/device/lib/crypto/drivers/rv_core_ibex.c b/sw/device/lib/crypto/drivers/rv_core_ibex.c index c4a8a38991968..5c3977c139ff6 100644 --- a/sw/device/lib/crypto/drivers/rv_core_ibex.c +++ b/sw/device/lib/crypto/drivers/rv_core_ibex.c @@ -6,12 +6,19 @@ #include "sw/device/lib/base/abs_mmio.h" #include "sw/device/lib/base/bitfield.h" +#include "sw/device/lib/base/csr.h" +#include "sw/device/lib/base/hardened.h" +#include "sw/device/lib/crypto/impl/status.h" #include "hw/top_earlgrey/sw/autogen/top_earlgrey.h" #include "rv_core_ibex_regs.h" enum { kBaseAddr = TOP_EARLGREY_RV_CORE_IBEX_CFG_BASE_ADDR, + /** + * CSR_REG_CPUCTRL[0] is the iCache configuration field. + */ + kCpuctrlICacheMask = 1, }; /** @@ -27,6 +34,35 @@ static void wait_rnd_valid(void) { } } +status_t ibex_disable_icache(hardened_bool_t *icache_enabled) { + // Check if the instruction cache is already disabled. + uint32_t csr; + CSR_READ(CSR_REG_CPUCTRL, &csr); + if ((csr & kCpuctrlICacheMask) == 1) { + *icache_enabled = kHardenedBoolTrue; + } else { + *icache_enabled = kHardenedBoolFalse; + HARDENED_CHECK_EQ(launder32((csr & kCpuctrlICacheMask)), 0); + } + + // If the instruction cache is enabled, disable it. + if (*icache_enabled == kHardenedBoolTrue) { + CSR_CLEAR_BITS(CSR_REG_CPUCTRL, kCpuctrlICacheMask); + } else { + HARDENED_CHECK_EQ(launder32(*icache_enabled), kHardenedBoolFalse); + } + + return OTCRYPTO_OK; +} + +void ibex_restore_icache(hardened_bool_t icache_enabled) { + // If the instruction cache was enabled before the CL disabled it, enable it + // again. + if (icache_enabled == kHardenedBoolTrue) { + CSR_SET_BITS(CSR_REG_CPUCTRL, kCpuctrlICacheMask); + } +} + uint32_t ibex_rnd32_read(void) { wait_rnd_valid(); return abs_mmio_read32(kBaseAddr + RV_CORE_IBEX_RND_DATA_REG_OFFSET); diff --git a/sw/device/lib/crypto/drivers/rv_core_ibex.h b/sw/device/lib/crypto/drivers/rv_core_ibex.h index 5fe43bdc472fd..68e64a01ab7e2 100644 --- a/sw/device/lib/crypto/drivers/rv_core_ibex.h +++ b/sw/device/lib/crypto/drivers/rv_core_ibex.h @@ -8,6 +8,29 @@ #include #include +#include "sw/device/lib/base/hardened.h" +#include "sw/device/lib/crypto/impl/status.h" + +/** + * Disable the Ibex Instruction Cache if it is enabled. + * + * Reads out the current state of the instruction cache. If it is enabled, + * disable it for the crypto lib. + * + * @param[out] icache_enabled kHardenedBoolTrue if the iCache was enabled before + * we disabled it. + * @return Error status. + */ +status_t ibex_disable_icache(hardened_bool_t *icache_enabled); + +/** + * Enables the Ibex Instruction Cache if icache_enabled is set. + * + * If icache_enabled == kHardenedBoolTrue, this function enables the iCache by + * writing to CPUCTRL. + */ +void ibex_restore_icache(hardened_bool_t icache_enabled); + /** * Get random data from the EDN0 interface. * From 21c2b12d87b6861f060f6838dc8c4b4a48c45fbd Mon Sep 17 00:00:00 2001 From: Pascal Nasahl Date: Tue, 22 Jul 2025 18:29:47 +0200 Subject: [PATCH 48/51] [crypto] Disable iCache for AES-GCM When entering the CL over the API, disable the iCache if it is enabled. When leaving the CL, restore the previous state of the iCache (enabled or disabled). Signed-off-by: Pascal Nasahl (cherry picked from commit 0602467cd18f1bd6209023ea49c583f35da691e7) --- sw/device/lib/crypto/impl/aes_gcm.c | 64 +++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/sw/device/lib/crypto/impl/aes_gcm.c b/sw/device/lib/crypto/impl/aes_gcm.c index b957c49d5e7d1..f797ce6fcf75a 100644 --- a/sw/device/lib/crypto/impl/aes_gcm.c +++ b/sw/device/lib/crypto/impl/aes_gcm.c @@ -310,6 +310,10 @@ otcrypto_status_t otcrypto_aes_gcm_encrypt(otcrypto_blinded_key_t *key, // Check the tag length. HARDENED_TRY(aes_gcm_check_tag_length(auth_tag.len, tag_len)); + // Store the iCache state (on or off) and disable it when it is on. + hardened_bool_t icache_saved_state; + HARDENED_TRY(ibex_disable_icache(&icache_saved_state)); + // Construct the AES key. aes_key_t aes_key; HARDENED_TRY(aes_gcm_key_construct(key, &aes_key)); @@ -321,6 +325,10 @@ otcrypto_status_t otcrypto_aes_gcm_encrypt(otcrypto_blinded_key_t *key, auth_tag.data, ciphertext.data)); HARDENED_TRY(clear_key_if_sideloaded(aes_key)); + + // Enable the iCache if it was previously enabled. + ibex_restore_icache(icache_saved_state); + return OTCRYPTO_OK; } @@ -346,6 +354,10 @@ otcrypto_status_t otcrypto_aes_gcm_decrypt( // Ensure entropy complex is initialized. HARDENED_TRY(entropy_complex_check()); + // Store the iCache state (on or off) and disable it when it is on. + hardened_bool_t icache_saved_state; + HARDENED_TRY(ibex_disable_icache(&icache_saved_state)); + // Construct the AES key. aes_key_t aes_key; HARDENED_TRY(aes_gcm_key_construct(key, &aes_key)); @@ -366,6 +378,10 @@ otcrypto_status_t otcrypto_aes_gcm_decrypt( auth_tag.data, plaintext.data, success)); HARDENED_TRY(clear_key_if_sideloaded(aes_key)); + + // Enable the iCache if it was previously enabled. + ibex_restore_icache(icache_saved_state); + return OTCRYPTO_OK; } @@ -379,6 +395,10 @@ otcrypto_status_t otcrypto_aes_gcm_encrypt_init( // Ensure entropy complex is initialized. HARDENED_TRY(entropy_complex_check()); + // Store the iCache state (on or off) and disable it when it is on. + hardened_bool_t icache_saved_state; + HARDENED_TRY(ibex_disable_icache(&icache_saved_state)); + // Construct the AES key. aes_key_t aes_key; HARDENED_TRY(aes_gcm_key_construct(key, &aes_key)); @@ -392,6 +412,10 @@ otcrypto_status_t otcrypto_aes_gcm_encrypt_init( // Save the context and clear the key if needed. HARDENED_TRY(gcm_context_save(&internal_ctx, ctx)); HARDENED_TRY(clear_key_if_sideloaded(internal_ctx.key)); + + // Enable the iCache if it was previously enabled. + ibex_restore_icache(icache_saved_state); + return OTCRYPTO_OK; } @@ -405,6 +429,10 @@ otcrypto_status_t otcrypto_aes_gcm_decrypt_init( // Ensure entropy complex is initialized. HARDENED_TRY(entropy_complex_check()); + // Store the iCache state (on or off) and disable it when it is on. + hardened_bool_t icache_saved_state; + HARDENED_TRY(ibex_disable_icache(&icache_saved_state)); + // Construct the AES key. aes_key_t aes_key; HARDENED_TRY(aes_gcm_key_construct(key, &aes_key)); @@ -418,6 +446,10 @@ otcrypto_status_t otcrypto_aes_gcm_decrypt_init( // Save the context and clear the key if needed. HARDENED_TRY(gcm_context_save(&internal_ctx, ctx)); HARDENED_TRY(clear_key_if_sideloaded(internal_ctx.key)); + + // Enable the iCache if it was previously enabled. + ibex_restore_icache(icache_saved_state); + return OTCRYPTO_OK; } @@ -435,6 +467,10 @@ otcrypto_status_t otcrypto_aes_gcm_update_aad(otcrypto_aes_gcm_context_t *ctx, return OTCRYPTO_OK; } + // Store the iCache state (on or off) and disable it when it is on. + hardened_bool_t icache_saved_state; + HARDENED_TRY(ibex_disable_icache(&icache_saved_state)); + // Restore the AES-GCM context object and load the key if needed. aes_gcm_context_t internal_ctx; HARDENED_TRY(gcm_context_restore(ctx, &internal_ctx)); @@ -446,6 +482,10 @@ otcrypto_status_t otcrypto_aes_gcm_update_aad(otcrypto_aes_gcm_context_t *ctx, // Save the context and clear the key if needed. HARDENED_TRY(gcm_context_save(&internal_ctx, ctx)); HARDENED_TRY(clear_key_if_sideloaded(internal_ctx.key)); + + // Enable the iCache if it was previously enabled. + ibex_restore_icache(icache_saved_state); + return OTCRYPTO_OK; } @@ -466,6 +506,10 @@ otcrypto_status_t otcrypto_aes_gcm_update_encrypted_data( return OTCRYPTO_OK; } + // Store the iCache state (on or off) and disable it when it is on. + hardened_bool_t icache_saved_state; + HARDENED_TRY(ibex_disable_icache(&icache_saved_state)); + // Restore the AES-GCM context object and load the key if needed. aes_gcm_context_t internal_ctx; HARDENED_TRY(gcm_context_restore(ctx, &internal_ctx)); @@ -493,6 +537,10 @@ otcrypto_status_t otcrypto_aes_gcm_update_encrypted_data( // Save the context and clear the key if needed. HARDENED_TRY(gcm_context_save(&internal_ctx, ctx)); HARDENED_TRY(clear_key_if_sideloaded(internal_ctx.key)); + + // Enable the iCache if it was previously enabled. + ibex_restore_icache(icache_saved_state); + return OTCRYPTO_OK; } @@ -512,6 +560,10 @@ otcrypto_status_t otcrypto_aes_gcm_encrypt_final( // Ensure entropy complex is initialized. HARDENED_TRY(entropy_complex_check()); + // Store the iCache state (on or off) and disable it when it is on. + hardened_bool_t icache_saved_state; + HARDENED_TRY(ibex_disable_icache(&icache_saved_state)); + // Check the tag length. HARDENED_TRY(aes_gcm_check_tag_length(auth_tag.len, tag_len)); @@ -537,6 +589,10 @@ otcrypto_status_t otcrypto_aes_gcm_encrypt_final( // Clear the context and the key if needed. HARDENED_TRY(hardened_memshred(ctx->data, ARRAYSIZE(ctx->data))); HARDENED_TRY(clear_key_if_sideloaded(internal_ctx.key)); + + // Enable the iCache if it was previously enabled. + ibex_restore_icache(icache_saved_state); + return OTCRYPTO_OK; } @@ -557,6 +613,10 @@ otcrypto_status_t otcrypto_aes_gcm_decrypt_final( // Entropy complex needs to be initialized for `memshred`. HARDENED_TRY(entropy_complex_check()); + // Store the iCache state (on or off) and disable it when it is on. + hardened_bool_t icache_saved_state; + HARDENED_TRY(ibex_disable_icache(&icache_saved_state)); + // Check the tag length. HARDENED_TRY(aes_gcm_check_tag_length(auth_tag.len, tag_len)); @@ -582,5 +642,9 @@ otcrypto_status_t otcrypto_aes_gcm_decrypt_final( // Clear the context and the key if needed. HARDENED_TRY(hardened_memshred(ctx->data, ARRAYSIZE(ctx->data))); HARDENED_TRY(clear_key_if_sideloaded(internal_ctx.key)); + + // Enable the iCache if it was previously enabled. + ibex_restore_icache(icache_saved_state); + return OTCRYPTO_OK; } From 1759e5d4fc06de8ea49fc52d2d70adc745eed0b6 Mon Sep 17 00:00:00 2001 From: Pascal Nasahl Date: Fri, 19 Sep 2025 09:11:42 +0800 Subject: [PATCH 49/51] [crypto] Use hardened_xor in HMAC As we are doing an operation on the key, use the hardened_xor instead of the plain XOR. Signed-off-by: Pascal Nasahl (cherry picked from commit 273a347ead37eaa958f36b9ea2c806e150db43b2) --- sw/device/lib/crypto/drivers/hmac.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/sw/device/lib/crypto/drivers/hmac.c b/sw/device/lib/crypto/drivers/hmac.c index 9b3fc9e055795..108f36b48b51f 100644 --- a/sw/device/lib/crypto/drivers/hmac.c +++ b/sw/device/lib/crypto/drivers/hmac.c @@ -529,10 +529,12 @@ status_t hmac_hmac_sha256_redundant(const hmac_key_t *key, const uint8_t *msg, memset(i_key_pad, 0, kHmacSha256BlockBytes); // XOR the key K with the outer (opad) and inner (ipad) padding. - for (size_t it = 0; it < kHmacSha256BlockWords; it++) { - o_key_pad[it] = key->key_block[it] ^ 0x5c5c5c5c; - i_key_pad[it] = key->key_block[it] ^ 0x36363636; - } + uint32_t opad[kHmacSha256BlockWords]; + uint32_t ipad[kHmacSha256BlockWords]; + memset(opad, 0x5c5c5c5c, kHmacSha256BlockBytes); + memset(ipad, 0x36363636, kHmacSha256BlockBytes); + TRY(hardened_xor(key->key_block, opad, kHmacSha256BlockWords, o_key_pad)); + TRY(hardened_xor(key->key_block, ipad, kHmacSha256BlockWords, i_key_pad)); // Concatenate the message with the inner padded key. uint8_t i_key_pad_msg[kHmacSha256BlockBytes + msg_len]; From 0b7678d31dd8cdc238e9f14772e09c599da74279 Mon Sep 17 00:00:00 2001 From: Pascal Nasahl Date: Fri, 22 Aug 2025 14:55:55 +0200 Subject: [PATCH 50/51] [crypto] Remove decoys in hardened_* functions In the previous implementation of random_order, the length returned was larger than the length of the buffer we want to traverse. Hence, decoys were introduced and they were used for the additional iterations. lowRISC/opentitan#27875 modified the random_order function. Now, the length returned is identical to the length of the buffer we want to traverse. Hence, no additional iterations are executed - so we can remove the decoys. As described in lowRISC/opentitan#27638 decoys are anyways not particularly as a SCA countermeasure. Closes lowRISC/opentitan#27638 Signed-off-by: Pascal Nasahl (cherry picked from commit 1136ecb45e11d9c355fc3ca05969ddc89d54224a) --- sw/device/lib/base/hardened_memory.c | 98 ++++------------------------ 1 file changed, 13 insertions(+), 85 deletions(-) diff --git a/sw/device/lib/base/hardened_memory.c b/sw/device/lib/base/hardened_memory.c index 1ebc314c3a00c..a720a094acb83 100644 --- a/sw/device/lib/base/hardened_memory.c +++ b/sw/device/lib/base/hardened_memory.c @@ -24,19 +24,8 @@ status_t hardened_memcpy(uint32_t *restrict dest, const uint32_t *restrict src, uintptr_t src_addr = (uintptr_t)src; uintptr_t dest_addr = (uintptr_t)dest; - // `decoys` is a small stack array that is filled with uninitialized memory. - // It is scratch space for us to do "extra" operations, when the number of - // iteration indices the chosen random order is different from `word_len`. - // - // These extra operations also introduce noise that an attacker must do work - // to filter, such as by applying side-channel analysis to obtain an address - // trace. - uint32_t decoys[8]; - uintptr_t decoy_addr = (uintptr_t)&decoys; - // We need to launder `count`, so that the SW.LOOP-COMPLETION check is not // deleted by the compiler. - size_t byte_len = word_len * sizeof(uint32_t); for (; launderw(count) < expected_count; count = launderw(count) + 1) { // The order values themselves are in units of words, but we need `byte_idx` // to be in units of bytes. @@ -49,28 +38,9 @@ status_t hardened_memcpy(uint32_t *restrict dest, const uint32_t *restrict src, // happens-before among indices consistent with `order`. barrierw(byte_idx); - // Compute putative offsets into `src`, `dest`, and `decoys`. Some of these - // may go off the end of `src` and `dest`, but they will not be cast to - // pointers in that case. (Note that casting out-of-range addresses to - // pointers is UB.) - uintptr_t srcp = src_addr + byte_idx; - uintptr_t destp = dest_addr + byte_idx; - uintptr_t decoy1 = decoy_addr + (byte_idx % sizeof(decoys)); - uintptr_t decoy2 = - decoy_addr + - ((byte_idx + (sizeof(decoys) / 2) + sizeof(uint32_t)) % sizeof(decoys)); - - // Branchlessly select whether to do a "real" copy or a decoy copy, - // depending on whether we've gone off the end of the array or not. - // - // Pretty much everything needs to be laundered: we need to launder - // `byte_idx` for obvious reasons, and we need to launder the result of the - // select, so that the compiler cannot delete the resulting loads and - // stores. This is similar to having used `volatile uint32_t *`. - void *src = (void *)launderw( - ct_cmovw(ct_sltuw(launderw(byte_idx), byte_len), srcp, decoy1)); - void *dest = (void *)launderw( - ct_cmovw(ct_sltuw(launderw(byte_idx), byte_len), destp, decoy2)); + // Calculate pointers. + void *src = (void *)launderw(src_addr + byte_idx); + void *dest = (void *)launderw(dest_addr + byte_idx); // Perform the copy, without performing a typed dereference operation. write_32(read_32(src), dest); @@ -90,19 +60,12 @@ status_t hardened_memshred(uint32_t *dest, size_t word_len) { uintptr_t data_addr = (uintptr_t)dest; - uint32_t decoys[8]; - uintptr_t decoy_addr = (uintptr_t)&decoys; - - size_t byte_len = word_len * sizeof(uint32_t); for (; count < expected_count; count = launderw(count) + 1) { size_t byte_idx = launderw(random_order_advance(&order)) * sizeof(uint32_t); barrierw(byte_idx); - uintptr_t datap = data_addr + byte_idx; - uintptr_t decoy = decoy_addr + (byte_idx % sizeof(decoys)); - - void *data = (void *)launderw( - ct_cmovw(ct_sltuw(launderw(byte_idx), byte_len), datap, decoy)); + // Calculate pointer. + void *data = (void *)launderw(data_addr + byte_idx); // Write a freshly-generated random word to `*data`. write_32(hardened_memshred_random_word(), data); @@ -125,36 +88,18 @@ hardened_bool_t hardened_memeq(const uint32_t *lhs, const uint32_t *rhs, uintptr_t lhs_addr = (uintptr_t)lhs; uintptr_t rhs_addr = (uintptr_t)rhs; - // `decoys` needs to be filled with equal values this time around. It - // should be filled with values with a Hamming weight of around 16, which is - // the most common hamming weight among 32-bit words. - uint32_t decoys[8] = { - 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, - 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, - }; - uintptr_t decoy_addr = (uintptr_t)&decoys; - uint32_t zeros = 0; uint32_t ones = UINT32_MAX; // The loop is almost token-for-token the one above, but the copy is // replaced with something else. - size_t byte_len = word_len * sizeof(uint32_t); for (; count < expected_count; count = launderw(count) + 1) { size_t byte_idx = launderw(random_order_advance(&order)) * sizeof(uint32_t); barrierw(byte_idx); - uintptr_t ap = lhs_addr + byte_idx; - uintptr_t bp = rhs_addr + byte_idx; - uintptr_t decoy1 = decoy_addr + (byte_idx % sizeof(decoys)); - uintptr_t decoy2 = - decoy_addr + - ((byte_idx + (sizeof(decoys) / 2) + sizeof(uint32_t)) % sizeof(decoys)); - - void *av = (void *)launderw( - ct_cmovw(ct_sltuw(launderw(byte_idx), byte_len), ap, decoy1)); - void *bv = (void *)launderw( - ct_cmovw(ct_sltuw(launderw(byte_idx), byte_len), bp, decoy2)); + // Calculate pointers. + void *av = (void *)launderw(lhs_addr + byte_idx); + void *bv = (void *)launderw(rhs_addr + byte_idx); uint32_t a = read_32(av); uint32_t b = read_32(bv); @@ -242,40 +187,23 @@ status_t hardened_xor_in_place(uint32_t *restrict x, const uint32_t *restrict y, size_t count = 0; size_t expected_count = random_order_len(&order); - // Create some random values for decoy operations. - uint32_t decoys[8]; - hardened_memshred(decoys, ARRAYSIZE(decoys)); - // Cast pointers to `uintptr_t` to erase their provenance. uintptr_t x_addr = (uintptr_t)x; uintptr_t y_addr = (uintptr_t)y; - uintptr_t decoy_addr = (uintptr_t)&decoys; // XOR the mask with the first share. This loop is modelled off the one in // `hardened_memcpy`; see the comments there for more details. - size_t byte_len = word_len * sizeof(uint32_t); for (; launderw(count) < expected_count; count = launderw(count) + 1) { size_t byte_idx = launderw(random_order_advance(&order)) * sizeof(uint32_t); // Prevent the compiler from re-ordering the loop. barrierw(byte_idx); - // Calculate pointers. The x and y pointers might not be valid, but in this - // case they will not be selected. - uintptr_t xp = x_addr + byte_idx; - uintptr_t yp = y_addr + byte_idx; - uintptr_t decoy1 = decoy_addr + (byte_idx % sizeof(decoys)); - uintptr_t decoy2 = - decoy_addr + - ((byte_idx + (sizeof(decoys) / 2) + sizeof(uint32_t)) % sizeof(decoys)); - - // Select in constant-time either the real pointers or decoys. - void *xv = (void *)launderw( - ct_cmovw(ct_sltuw(launderw(byte_idx), byte_len), xp, decoy1)); - void *yv = (void *)launderw( - ct_cmovw(ct_sltuw(launderw(byte_idx), byte_len), yp, decoy2)); - - // Perform an XOR in either the decoy array or the real array. + // Calculate pointers. + void *xv = (void *)launderw(x_addr + byte_idx); + void *yv = (void *)launderw(y_addr + byte_idx); + + // Perform an XOR in the array. write_32(read_32(xv) ^ read_32(yv), xv); } RANDOM_ORDER_HARDENED_CHECK_DONE(order); From f85abb4d31f36b63d328d2e9834aa73930c67833 Mon Sep 17 00:00:00 2001 From: Pascal Nasahl Date: Thu, 2 Oct 2025 17:27:36 +0200 Subject: [PATCH 51/51] [crypto] eg100 specific fixes Signed-off-by: Pascal Nasahl (commit is original to earlgrey_1.0.0) --- sw/device/lib/crypto/drivers/BUILD | 3 +++ sw/device/lib/crypto/drivers/hmac.c | 2 +- sw/device/lib/crypto/drivers/hmac.h | 2 +- sw/device/lib/crypto/impl/hmac.c | 11 ++++++----- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/sw/device/lib/crypto/drivers/BUILD b/sw/device/lib/crypto/drivers/BUILD index 469e7d3c07aaa..a5c9e636f845b 100644 --- a/sw/device/lib/crypto/drivers/BUILD +++ b/sw/device/lib/crypto/drivers/BUILD @@ -53,6 +53,7 @@ opentitan_test( ), deps = [ ":aes", + ":rv_core_ibex", "//sw/device/lib/base:macros", "//sw/device/lib/base:memory", "//sw/device/lib/crypto/impl:status", @@ -228,6 +229,8 @@ dual_cc_library( "//hw/ip/rv_core_ibex/data:rv_core_ibex_c_regs", "//sw/device/lib/base:abs_mmio", "//sw/device/lib/base:csr", + ], + shared = [ "//sw/device/lib/base:hardened", "//sw/device/lib/crypto/impl:status", ], diff --git a/sw/device/lib/crypto/drivers/hmac.c b/sw/device/lib/crypto/drivers/hmac.c index 108f36b48b51f..b4842d4971a05 100644 --- a/sw/device/lib/crypto/drivers/hmac.c +++ b/sw/device/lib/crypto/drivers/hmac.c @@ -515,7 +515,7 @@ status_t hmac_hash_sha512(const uint8_t *msg, size_t msg_len, } status_t hmac_hmac_sha256_cl(const hmac_key_t *key, const uint8_t *msg, - size_t msg_len, uint32_t *tag) { + size_t msg_len, uint32_t *tag) { // Always configure the key length as the underlying message block size. uint32_t cfg = cfg_get(/*hmac_en=*/true, kDigestLengthSha256, kKeyLength512); return oneshot(cfg, key, msg, msg_len, kHmacSha256DigestWords, tag); diff --git a/sw/device/lib/crypto/drivers/hmac.h b/sw/device/lib/crypto/drivers/hmac.h index 32bf8cd6ef80e..17bcc0f262e70 100644 --- a/sw/device/lib/crypto/drivers/hmac.h +++ b/sw/device/lib/crypto/drivers/hmac.h @@ -137,7 +137,7 @@ status_t hmac_hash_sha512(const uint8_t *msg, size_t msg_len, uint32_t *digest); */ OT_WARN_UNUSED_RESULT status_t hmac_hmac_sha256_cl(const hmac_key_t *key, const uint8_t *msg, - size_t msg_len, uint32_t *tag); + size_t msg_len, uint32_t *tag); /** * Redundant implementation for a one-shot HMAC-SHA256 hash computation. diff --git a/sw/device/lib/crypto/impl/hmac.c b/sw/device/lib/crypto/impl/hmac.c index bb7eccf89350c..81eb60583fcee 100644 --- a/sw/device/lib/crypto/impl/hmac.c +++ b/sw/device/lib/crypto/impl/hmac.c @@ -168,7 +168,7 @@ otcrypto_status_t otcrypto_hmac(const otcrypto_blinded_key_t *key, HARDENED_CHECK_EQ(key->config.security_level, kOtcryptoKeySecurityLevelLow); return hmac_hmac_sha256_cl(&hmac_key, input_message.data, - input_message.len, tag.data); + input_message.len, tag.data); } else if (launder32(key->config.security_level) == kOtcryptoKeySecurityLevelMedium) { // Call the HMAC core twice and compare both tags. This serves as a FI @@ -177,14 +177,15 @@ otcrypto_status_t otcrypto_hmac(const otcrypto_blinded_key_t *key, HARDENED_CHECK_EQ(key->config.security_level, kOtcryptoKeySecurityLevelMedium); HARDENED_TRY(hmac_hmac_sha256_cl(&hmac_key, input_message.data, - input_message.len, tag.data)); + input_message.len, tag.data)); // Second HMAC computation using the HMAC core. uint32_t tag_redundant[tag.len]; hmac_key_t hmac_key_redundant; HARDENED_TRY(hmac_key_construct(key, kHmacSha256BlockWords, &hmac_key_redundant)); - HARDENED_TRY(hmac_hmac_sha256_cl(&hmac_key_redundant, input_message.data, - input_message.len, tag_redundant)); + HARDENED_TRY(hmac_hmac_sha256_cl(&hmac_key_redundant, + input_message.data, input_message.len, + tag_redundant)); // Comparison of both tags. HARDENED_CHECK_EQ( hardened_memeq(&tag.data[0], &tag_redundant[0], tag.len), @@ -198,7 +199,7 @@ otcrypto_status_t otcrypto_hmac(const otcrypto_blinded_key_t *key, kOtcryptoKeySecurityLevelHigh); // First HMAC computation using the HMAC core. HARDENED_TRY(hmac_hmac_sha256_cl(&hmac_key, input_message.data, - input_message.len, tag.data)); + input_message.len, tag.data)); // Second HMAC computation without using the HMAC core. uint32_t tag_redundant[tag.len]; hmac_key_t hmac_key_redundant;