diff --git a/sw/device/tests/penetrationtests/firmware/sca/BUILD b/sw/device/tests/penetrationtests/firmware/sca/BUILD index adda886fd0039..91bc59563efeb 100644 --- a/sw/device/tests/penetrationtests/firmware/sca/BUILD +++ b/sw/device/tests/penetrationtests/firmware/sca/BUILD @@ -161,6 +161,7 @@ cc_library( "//sw/device/tests/penetrationtests/firmware/sca/otbn:otbn_insn_carry_flag", "//sw/device/tests/penetrationtests/firmware/sca/otbn:otbn_key_sideload_sca", "//sw/device/tests/penetrationtests/json:otbn_sca_commands", + "//sw/otbn/crypto:p256_ecdsa_sca", "//sw/otbn/crypto:rsa", ], ) diff --git a/sw/device/tests/penetrationtests/firmware/sca/otbn_sca.c b/sw/device/tests/penetrationtests/firmware/sca/otbn_sca.c index 465a5719788bf..d52ebbbbafa9e 100644 --- a/sw/device/tests/penetrationtests/firmware/sca/otbn_sca.c +++ b/sw/device/tests/penetrationtests/firmware/sca/otbn_sca.c @@ -36,6 +36,20 @@ static dif_kmac_t kmac; enum { kKeySideloadNumIt = 16, + /** + * Number of bytes for ECDSA P-256 private keys, message digests, and point + * coordinates. + */ + kEcc256NumBytes = 256 / 8, + /** + * Number of 32b words for ECDSA P-256 private keys, message digests, and + * point coordinates. + */ + kEcc256NumWords = kEcc256NumBytes / sizeof(uint32_t), + /** + * Max number of traces per batch. + */ + kNumBatchOpsMax = 256, }; // Data structs for key sideloading test. @@ -76,6 +90,32 @@ static const otbn_addr_t kOtbnVarRsaInOut = OTBN_ADDR_T_INIT(rsa, inout); static const otbn_addr_t kOtbnVarRsaModulus = OTBN_ADDR_T_INIT(rsa, modulus); static const otbn_addr_t kOtbnVarRsaExp = OTBN_ADDR_T_INIT(rsa, exp); +// p256_ecdsa_sca has randomization removed. +OTBN_DECLARE_APP_SYMBOLS(p256_ecdsa_sca); + +OTBN_DECLARE_SYMBOL_ADDR(p256_ecdsa_sca, mode); +OTBN_DECLARE_SYMBOL_ADDR(p256_ecdsa_sca, msg); +OTBN_DECLARE_SYMBOL_ADDR(p256_ecdsa_sca, r); +OTBN_DECLARE_SYMBOL_ADDR(p256_ecdsa_sca, s); +OTBN_DECLARE_SYMBOL_ADDR(p256_ecdsa_sca, x); +OTBN_DECLARE_SYMBOL_ADDR(p256_ecdsa_sca, y); +OTBN_DECLARE_SYMBOL_ADDR(p256_ecdsa_sca, d0); +OTBN_DECLARE_SYMBOL_ADDR(p256_ecdsa_sca, d1); +OTBN_DECLARE_SYMBOL_ADDR(p256_ecdsa_sca, k0); +OTBN_DECLARE_SYMBOL_ADDR(p256_ecdsa_sca, k1); +OTBN_DECLARE_SYMBOL_ADDR(p256_ecdsa_sca, x_r); + +static const otbn_app_t kOtbnAppP256Ecdsa = OTBN_APP_T_INIT(p256_ecdsa_sca); + +static const otbn_addr_t kOtbnVarMode = OTBN_ADDR_T_INIT(p256_ecdsa_sca, mode); +static const otbn_addr_t kOtbnVarMsg = OTBN_ADDR_T_INIT(p256_ecdsa_sca, msg); +static const otbn_addr_t kOtbnVarR = OTBN_ADDR_T_INIT(p256_ecdsa_sca, r); +static const otbn_addr_t kOtbnVarS = OTBN_ADDR_T_INIT(p256_ecdsa_sca, s); +static const otbn_addr_t kOtbnVarD0 = OTBN_ADDR_T_INIT(p256_ecdsa_sca, d0); +static const otbn_addr_t kOtbnVarD1 = OTBN_ADDR_T_INIT(p256_ecdsa_sca, d1); +static const otbn_addr_t kOtbnVarK0 = OTBN_ADDR_T_INIT(p256_ecdsa_sca, k0); +static const otbn_addr_t kOtbnVarK1 = OTBN_ADDR_T_INIT(p256_ecdsa_sca, k1); + /** * Clears the OTBN DMEM and IMEM. * @@ -89,6 +129,349 @@ static status_t clear_otbn(void) { return OK_STATUS(); } +/** + * Generate masked shared. + * + * If mask_en is set, generate a random share. + * If mask_en is not set, set share to 0. + * + * @param dest_array Destination array. + * @param mask_en Masking enabled or disabled. + * @param len Length of the array. + */ +void gen_mask_data(uint32_t *dest_array, bool mask_en, size_t len) { + if (mask_en) { + for (size_t j = 0; j < len; ++j) { + dest_array[j] = prng_rand_uint32(); + } + } else { + memset(dest_array, 0, len * sizeof(dest_array[0])); + } +} + +/** + * Generate a FvsR data set. + * + * If fixed is set, copy src_fixed_array into dest_array. + * If fixed is not set, generate random data. + * + * @param dest_array Destination array. + * @param fixed Fixed or random set. + * @param src_fixed_array Source fixed array. + * @param len Length of the array. + */ +void gen_fvsr_data(uint32_t *dest_array, bool fixed, uint32_t *src_fixed_array, + size_t len) { + if (fixed) { + memcpy(dest_array, src_fixed_array, len * sizeof(src_fixed_array[0])); + } else { + for (size_t j = 0; j < len; ++j) { + dest_array[j] = prng_rand_uint32(); + } + } +} + +/** + * Signs a message with ECDSA using the P-256 curve. + * + * R = k*G + * r = x-coordinate of R + * s = k^(-1)(msg + r*d) mod n + * + * @param otbn_ctx The OTBN context object. + * @param msg The message to sign, msg (32B). + * @param private_key_d The private key, d (32B). + * @param k The ephemeral key, k (random scalar) (32B). + * @param[out] signature_r Signature component r (the x-coordinate of R). + * Provide a pre-allocated 32B buffer. + * @param[out] signature_s Signature component s (the proof). + * Provide a pre-allocated 32B buffer. + */ +static status_t p256_ecdsa_sign(const uint32_t *msg, + const uint32_t *private_key_d, + uint32_t *signature_r, uint32_t *signature_s, + const uint32_t *k) { + uint32_t mode = 1; // mode 1 => sign + // Send operation mode to OTBN + TRY(otbn_dmem_write(/*num_words=*/1, &mode, kOtbnVarMode)); + // Send Msg to OTBN + TRY(otbn_dmem_write(kEcc256NumWords, msg, kOtbnVarMsg)); + // Send two shares of private_key_d to OTBN + TRY(otbn_dmem_write(kEcc256NumWords, private_key_d, kOtbnVarD0)); + TRY(otbn_dmem_write(kEcc256NumWords, private_key_d + kEcc256NumWords, + kOtbnVarD1)); + // Send two shares of secret_k to OTBN + TRY(otbn_dmem_write(kEcc256NumWords, k, kOtbnVarK0)); + TRY(otbn_dmem_write(kEcc256NumWords, k + kEcc256NumWords, kOtbnVarK1)); + + // Start OTBN execution + pentest_set_trigger_high(); + // Give the trigger time to rise. + asm volatile(NOP30); + otbn_execute(); + otbn_busy_wait_for_done(); + pentest_set_trigger_low(); + + // Read the results back (sig_r, sig_s) + TRY(otbn_dmem_read(kEcc256NumWords, kOtbnVarR, signature_r)); + TRY(otbn_dmem_read(kEcc256NumWords, kOtbnVarS, signature_s)); + + return OK_STATUS(); +} + +status_t handle_otbn_sca_ecdsa_p256_sign(ujson_t *uj) { + // Get masks off or on. + penetrationtest_otbn_sca_en_masks_t uj_data_masks; + + // Get message and key. + penetrationtest_otbn_sca_ecdsa_p256_sign_t uj_data; + TRY(ujson_deserialize_penetrationtest_otbn_sca_ecdsa_p256_sign_t(uj, + &uj_data)); + + // Set of share d1 for masking. + uint32_t ecc256_private_key_d1[kEcc256NumWords]; + memset(ecc256_private_key_d1, 0, sizeof(ecc256_private_key_d1)); + // If masking is activated, generate random share d1. + if (uj_data_masks.en_masks) { + for (size_t j = 0; j < kEcc256NumWords; j++) { + ecc256_private_key_d1[j] = prng_rand_uint32(); + } + } + + // Set of share k1 for masking. + uint32_t ecc256_secret_k1[kEcc256NumWords]; + memset(ecc256_secret_k1, 0, sizeof(ecc256_secret_k1)); + // If masking is activated, generate random share d1. + if (uj_data_masks.en_masks) { + for (size_t j = 0; j < kEcc256NumWords; j++) { + ecc256_secret_k1[j] = prng_rand_uint32(); + } + } + + // Combine D0 and D1 into the private key. + uint32_t ecc256_private_key_d[2 * kEcc256NumWords]; + memset(ecc256_private_key_d, 0, sizeof(ecc256_private_key_d)); + memcpy(ecc256_private_key_d, uj_data.d0, sizeof(uj_data.d0)); + memcpy(ecc256_private_key_d + kEcc256NumWords, ecc256_private_key_d1, + sizeof(ecc256_private_key_d1)); + + // Combine K0 and K1 into the secret key. + uint32_t ecc256_secret_k[2 * kEcc256NumWords]; + memset(ecc256_secret_k, 0, sizeof(ecc256_secret_k)); + memcpy(ecc256_secret_k, uj_data.k0, sizeof(uj_data.k0)); + memcpy(ecc256_secret_k + kEcc256NumWords, ecc256_secret_k1, + sizeof(ecc256_secret_k1)); + + otbn_load_app(kOtbnAppP256Ecdsa); + + // Signature output. + uint32_t ecc256_signature_r[kEcc256NumWords]; + uint32_t ecc256_signature_s[kEcc256NumWords]; + + // Start the operation. + p256_ecdsa_sign(uj_data.msg, ecc256_private_key_d, ecc256_signature_r, + ecc256_signature_s, ecc256_secret_k); + + // Send back signature to host. + penetrationtest_otbn_sca_ecdsa_p256_signature_t uj_output; + memcpy(uj_output.r, ecc256_signature_r, sizeof(ecc256_signature_r)); + memcpy(uj_output.s, ecc256_signature_s, sizeof(ecc256_signature_s)); + RESP_OK(ujson_serialize_penetrationtest_otbn_sca_ecdsa_p256_signature_t, uj, + &uj_output); + + // Clear OTBN memory + TRY(clear_otbn()); + + return OK_STATUS(); +} + +status_t handle_otbn_sca_ecdsa_p256_sign_batch(ujson_t *uj) { + // Get number of traces. + penetrationtest_otbn_sca_num_traces_t uj_data_num_traces; + TRY(ujson_deserialize_penetrationtest_otbn_sca_num_traces_t( + uj, &uj_data_num_traces)); + + if (uj_data_num_traces.num_traces > kNumBatchOpsMax) { + return OUT_OF_RANGE(); + } + + // Get masks off or on. + penetrationtest_otbn_sca_en_masks_t uj_data_masks; + TRY(ujson_deserialize_penetrationtest_otbn_sca_en_masks_t(uj, + &uj_data_masks)); + + // Create random message, k, and d. + uint32_t ecc256_message_batch[kNumBatchOpsMax][kEcc256NumWords]; + + uint32_t ecc256_private_key_d0_batch[kNumBatchOpsMax][kEcc256NumWords]; + uint32_t ecc256_private_key_d1_batch[kNumBatchOpsMax][kEcc256NumWords]; + uint32_t ecc256_private_key_d_batch[kNumBatchOpsMax][2 * kEcc256NumWords]; + + uint32_t ecc256_secret_key_k0_batch[kNumBatchOpsMax][kEcc256NumWords]; + uint32_t ecc256_secret_key_k1_batch[kNumBatchOpsMax][kEcc256NumWords]; + uint32_t ecc256_secret_key_k_batch[kNumBatchOpsMax][2 * kEcc256NumWords]; + + // Generate the FvsR data set. + for (size_t i = 0; i < uj_data_num_traces.num_traces; ++i) { + // Generate random message. + gen_fvsr_data(ecc256_message_batch[i], false, NULL, kEcc256NumWords); + + // Set random private key d0. + gen_fvsr_data(ecc256_private_key_d0_batch[i], false, NULL, kEcc256NumWords); + + // When masks are on, set random private key d1. If masks are off, set to 0. + gen_mask_data(ecc256_private_key_d1_batch[i], uj_data_masks.en_masks, + kEcc256NumWords); + + // Combine both shares d0 and d1 to d. + memcpy(ecc256_private_key_d_batch[i], ecc256_private_key_d0_batch[i], + sizeof(ecc256_private_key_d0_batch[i])); + memcpy(ecc256_private_key_d_batch[i] + kEcc256NumWords, + ecc256_private_key_d1_batch[i], + sizeof(ecc256_private_key_d1_batch[i])); + + // Set random secret key k0. + gen_fvsr_data(ecc256_secret_key_k0_batch[i], false, NULL, kEcc256NumWords); + + // When masks are on, set random secret key k1. If masks are off, set to 0. + gen_mask_data(ecc256_secret_key_k1_batch[i], uj_data_masks.en_masks, + kEcc256NumWords); + + // Combine both shares k0 and k1 to k. + memcpy(ecc256_secret_key_k_batch[i], ecc256_secret_key_k0_batch[i], + sizeof(ecc256_secret_key_k0_batch[i])); + memcpy(ecc256_secret_key_k_batch[i] + kEcc256NumWords, + ecc256_secret_key_k1_batch[i], + sizeof(ecc256_secret_key_k1_batch[i])); + } + + // Last signature output. + uint32_t ecc256_signature_r[kEcc256NumWords]; + uint32_t ecc256_signature_s[kEcc256NumWords]; + // Run num_traces ECDSA operations. + for (size_t i = 0; i < uj_data_num_traces.num_traces; ++i) { + otbn_load_app(kOtbnAppP256Ecdsa); + + // Start the operation. + p256_ecdsa_sign(ecc256_message_batch[i], ecc256_private_key_d_batch[i], + ecc256_signature_r, ecc256_signature_s, + ecc256_secret_key_k_batch[i]); + } + + // Send back the last signature to host. + penetrationtest_otbn_sca_ecdsa_p256_signature_t uj_output; + memcpy(uj_output.r, ecc256_signature_r, sizeof(ecc256_signature_r)); + memcpy(uj_output.s, ecc256_signature_s, sizeof(ecc256_signature_s)); + RESP_OK(ujson_serialize_penetrationtest_otbn_sca_ecdsa_p256_signature_t, uj, + &uj_output); + + // Clear OTBN memory + TRY(clear_otbn()); + + return OK_STATUS(); +} + +status_t handle_otbn_sca_ecdsa_p256_sign_fvsr_batch(ujson_t *uj) { + // Get number of traces. + penetrationtest_otbn_sca_num_traces_t uj_data_num_traces; + TRY(ujson_deserialize_penetrationtest_otbn_sca_num_traces_t( + uj, &uj_data_num_traces)); + + if (uj_data_num_traces.num_traces > kNumBatchOpsMax) { + return OUT_OF_RANGE(); + } + + // Get masks off or on. + penetrationtest_otbn_sca_en_masks_t uj_data_masks; + TRY(ujson_deserialize_penetrationtest_otbn_sca_en_masks_t(uj, + &uj_data_masks)); + + // Get fixed message and key. + penetrationtest_otbn_sca_ecdsa_p256_sign_t uj_data; + TRY(ujson_deserialize_penetrationtest_otbn_sca_ecdsa_p256_sign_t(uj, + &uj_data)); + + uint32_t ecc256_message_batch[kNumBatchOpsMax][kEcc256NumWords]; + + uint32_t ecc256_private_key_d0_batch[kNumBatchOpsMax][kEcc256NumWords]; + uint32_t ecc256_private_key_d1_batch[kNumBatchOpsMax][kEcc256NumWords]; + uint32_t ecc256_private_key_d_batch[kNumBatchOpsMax][2 * kEcc256NumWords]; + + uint32_t ecc256_secret_key_k0_batch[kNumBatchOpsMax][kEcc256NumWords]; + uint32_t ecc256_secret_key_k1_batch[kNumBatchOpsMax][kEcc256NumWords]; + uint32_t ecc256_secret_key_k_batch[kNumBatchOpsMax][2 * kEcc256NumWords]; + + // Generate the FvsR data set. For each trace, message, k, and d is either set + // to fixed received from the host over uJSON or random. + bool run_fixed = true; + for (size_t i = 0; i < uj_data_num_traces.num_traces; ++i) { + // Set message. + gen_fvsr_data(ecc256_message_batch[i], run_fixed, uj_data.msg, + kEcc256NumWords); + + // If the run is fixed, take the private key received over uJSON. Else, + // generate a random private key. + gen_fvsr_data(ecc256_private_key_d0_batch[i], run_fixed, uj_data.d0, + kEcc256NumWords); + + // When masks are on, set fixed or random private key d1. If masks are off, + // set to 0. + gen_mask_data(ecc256_private_key_d1_batch[i], uj_data_masks.en_masks, + kEcc256NumWords); + + // Combine both shares + memcpy(ecc256_private_key_d_batch[i], ecc256_private_key_d0_batch[i], + sizeof(ecc256_private_key_d0_batch[i])); + memcpy(ecc256_private_key_d_batch[i] + kEcc256NumWords, + ecc256_private_key_d1_batch[i], + sizeof(ecc256_private_key_d1_batch[i])); + + // Set random secret key k0. + // If the run is fixed, take the private key received over uJSON. Else, + // generate a random private key. + gen_fvsr_data(ecc256_secret_key_k0_batch[i], run_fixed, uj_data.k0, + kEcc256NumWords); + + // When masks are on, set random secret key k1. If masks are off, set to 0. + gen_mask_data(ecc256_secret_key_k1_batch[i], uj_data_masks.en_masks, + kEcc256NumWords); + + // Combine both shares k0 and k1 to k. + memcpy(ecc256_secret_key_k_batch[i], ecc256_secret_key_k0_batch[i], + sizeof(ecc256_secret_key_k0_batch[i])); + memcpy(ecc256_secret_key_k_batch[i] + kEcc256NumWords, + ecc256_secret_key_k1_batch[i], + sizeof(ecc256_secret_key_k1_batch[i])); + + run_fixed = prng_rand_uint32() & 0x1; + } + + // Last signature output. + uint32_t ecc256_signature_r[kEcc256NumWords]; + uint32_t ecc256_signature_s[kEcc256NumWords]; + // Run num_traces ECDSA operations. + for (size_t i = 0; i < uj_data_num_traces.num_traces; ++i) { + otbn_load_app(kOtbnAppP256Ecdsa); + + // Start the operation. + p256_ecdsa_sign(uj_data.msg, ecc256_private_key_d_batch[i], + ecc256_signature_r, ecc256_signature_s, + ecc256_secret_key_k_batch[i]); + } + + // Send back the last signature to host. + penetrationtest_otbn_sca_ecdsa_p256_signature_t uj_output; + memcpy(uj_output.r, ecc256_signature_r, sizeof(ecc256_signature_r)); + memcpy(uj_output.s, ecc256_signature_s, sizeof(ecc256_signature_s)); + RESP_OK(ujson_serialize_penetrationtest_otbn_sca_ecdsa_p256_signature_t, uj, + &uj_output); + + // Clear OTBN memory + TRY(clear_otbn()); + + return OK_STATUS(); +} + status_t handle_otbn_pentest_init(ujson_t *uj) { // Configure the entropy complex for OTBN. Set the reseed interval to max // to avoid a non-constant trigger window. @@ -293,6 +676,12 @@ status_t handle_otbn_sca(ujson_t *uj) { return handle_otbn_sca_ecc256_set_c(uj); case kOtbnScaSubcommandEcc256SetSeed: return handle_otbn_sca_ecc256_set_seed(uj); + case kOtbnScaSubcommandEcdsaP256Sign: + return handle_otbn_sca_ecdsa_p256_sign(uj); + case kOtbnScaSubcommandEcdsaP256SignBatch: + return handle_otbn_sca_ecdsa_p256_sign_batch(uj); + case kOtbnScaSubcommandEcdsaP256SignFvsrBatch: + return handle_otbn_sca_ecdsa_p256_sign_fvsr_batch(uj); case kOtbnScaSubcommandInit: return handle_otbn_pentest_init(uj); case kOtbnScaSubcommandInitKeyMgr: diff --git a/sw/device/tests/penetrationtests/firmware/sca/otbn_sca.h b/sw/device/tests/penetrationtests/firmware/sca/otbn_sca.h index d067591d4bb12..462c3a9fc19a3 100644 --- a/sw/device/tests/penetrationtests/firmware/sca/otbn_sca.h +++ b/sw/device/tests/penetrationtests/firmware/sca/otbn_sca.h @@ -64,6 +64,39 @@ status_t handle_otbn_sca_ecc256_set_c(ujson_t *uj); */ status_t handle_otbn_sca_ecc256_set_seed(ujson_t *uj); +/** + * otbn.sca.ecdsa256.sign command handler. + * + * Runs a ECDSA 256 sign operation, used to measure whether the operation + * leakes secret information. + * + * @param uj An initialized uJSON context. + * @return OK or error. + */ +status_t handle_otbn_sca_ecdsa_p256_sign(ujson_t *uj); + +/** + * otbn.sca.ecdsa256.sign_batch command handler. + * + * Same as otbn.sca.ecdsa256.sign but in batch mode. Random message, random + * key, and random secret is used. + * + * @param uj An initialized uJSON context. + * @return OK or error. + */ +status_t handle_otbn_sca_ecdsa_p256_sign_batch(ujson_t *uj); + +/** + * otbn.sca.ecdsa256.sign_fvsr_batch command handler. + * + * Same as otbn.sca.ecdsa256.sign but in batch mode. Fixed or random message, + * fixed or random key, and fixed or random secret is used. + * + * @param uj An initialized uJSON context. + * @return OK or error. + */ +status_t handle_otbn_sca_ecdsa_p256_sign_fvsr_batch(ujson_t *uj); + /** * Initializes the OTBN SCA test on the device. * diff --git a/sw/device/tests/penetrationtests/json/otbn_sca_commands.h b/sw/device/tests/penetrationtests/json/otbn_sca_commands.h index 3441f141f9fbe..25b828d5ff057 100644 --- a/sw/device/tests/penetrationtests/json/otbn_sca_commands.h +++ b/sw/device/tests/penetrationtests/json/otbn_sca_commands.h @@ -22,6 +22,9 @@ extern "C" { value(_, Ecc256EnMasks) \ value(_, Ecc256SetC) \ value(_, Ecc256SetSeed) \ + value(_, EcdsaP256Sign) \ + value(_, EcdsaP256SignBatch) \ + value(_, EcdsaP256SignFvsrBatch) \ value(_, Init) \ value(_, InitKeyMgr) \ value(_, InsnCarryFlag) \ @@ -72,6 +75,17 @@ UJSON_SERDE_STRUCT(PenetrationtestOtbnScaRsa512DecOut, penetrationtest_otbn_sca_ field(big_num, uint32_t, 8) UJSON_SERDE_STRUCT(PenetrationtestOtbnScaBigNum, penetrationtest_otbn_sca_big_num_t, OTBN_SCA_BIG_NUM); +#define OTBN_SCA_ECDSA_P256_SIGN(field, string) \ + field(msg, uint32_t, 8) \ + field(d0, uint32_t, 10) \ + field(k0, uint32_t, 10) +UJSON_SERDE_STRUCT(PenetrationtestOtbnScaEcdsaP256Sign, penetrationtest_otbn_sca_ecdsa_p256_sign_t, OTBN_SCA_ECDSA_P256_SIGN); + +#define OTBN_SCA_ECDSA_P256_SIGNATURE(field, string) \ + field(r, uint8_t, 32) \ + field(s, uint8_t, 32) +UJSON_SERDE_STRUCT(PenetrationtestOtbnScaEcdsaP256Signature, penetrationtest_otbn_sca_ecdsa_p256_signature_t, OTBN_SCA_ECDSA_P256_SIGNATURE); + // clang-format on #ifdef __cplusplus