| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| |
| #include "sw/device/lib/base/abs_mmio.h" |
| #include "sw/device/lib/base/memory.h" |
| #include "sw/device/lib/crypto/drivers/otbn.h" |
| #include "sw/device/lib/runtime/ibex.h" |
| #include "sw/device/lib/runtime/log.h" |
| #include "sw/device/lib/testing/entropy_testutils.h" |
| #include "sw/device/lib/testing/test_framework/ottf_main.h" |
| #include "sw/device/lib/testing/test_framework/ottf_test_config.h" |
| #include "sw/device/sca/lib/prng.h" |
| #include "sw/device/sca/lib/sca.h" |
| #include "sw/device/sca/lib/simple_serial.h" |
| |
| #include "hw/top_earlgrey/sw/autogen/top_earlgrey.h" |
| #include "otbn_regs.h" |
| |
| /** |
| * OpenTitan program for OTBN ECDSA-P256 side-channel analysis. |
| * |
| * This program implements the following simple serial commands: |
| * - Set seed ('x'), |
| * - Secret key generation ('k'), |
| * - Keypair generation ('p'), |
| * - Get version ('v') (implemented in simpleserial library), |
| * - Seed PRNG ('s') (implemented in simpleserial library), |
| * See https://wiki.newae.com/SimpleSerial for details on the protocol. |
| */ |
| |
| OTTF_DEFINE_TEST_CONFIG(); |
| |
| enum { |
| /** |
| * Number of bytes for ECDSA P-256 seeds and masked private keys. |
| */ |
| kEcc256SeedNumBytes = 320 / 8, |
| /** |
| * Number of 32b words for ECDSA P-256 seeds and masked private keys. |
| */ |
| kEcc256SeedNumWords = kEcc256SeedNumBytes / sizeof(uint32_t), |
| /** |
| * Number of bytes for ECDSA P-256 point coordinates. |
| */ |
| kEcc256CoordNumBytes = 256 / 8, |
| /** |
| * Number of 32b words for ECDSA P-256 point coordinates. |
| */ |
| kEcc256CoordNumWords = kEcc256CoordNumBytes / sizeof(uint32_t), |
| /** |
| * Mode option for the ECDSA keygen app (generates the private key only). |
| */ |
| kEcc256ModePrivateKeyOnly = 1, |
| /** |
| * Mode option for the ECDSA keygen app (generates the full keypair). |
| */ |
| kEcc256ModeKeypair = 2, |
| /** |
| * Max number of traces per batch. |
| */ |
| kNumBatchOpsMax = 256, |
| }; |
| |
| /** |
| * An array of seeds to be used in a batch |
| */ |
| uint32_t batch_seeds[kNumBatchOpsMax][kEcc256SeedNumWords]; |
| |
| /** |
| * An array of masks to be used in a batch |
| */ |
| uint32_t batch_masks[kNumBatchOpsMax][kEcc256SeedNumWords]; |
| |
| /** |
| * Arrays for first and second share of masked private key d to be used in a |
| * batch |
| */ |
| uint32_t d0_batch[kEcc256SeedNumWords]; |
| uint32_t d1_batch[kEcc256SeedNumWords]; |
| |
| /** |
| * Fixed-message indicator. |
| * |
| * Used in the 'b' (batch capture) command for indicating whether to use fixed |
| * or random message. |
| */ |
| static bool run_fixed = true; |
| |
| /** |
| * Masking indicator. |
| * |
| * Used in the 'b' (batch capture) command for indicating whether to use masks. |
| */ |
| static bool en_masks = false; |
| |
| OTBN_DECLARE_APP_SYMBOLS(p256_key_from_seed_sca); |
| |
| OTBN_DECLARE_SYMBOL_ADDR(p256_key_from_seed_sca, mode); |
| OTBN_DECLARE_SYMBOL_ADDR(p256_key_from_seed_sca, seed0); |
| OTBN_DECLARE_SYMBOL_ADDR(p256_key_from_seed_sca, seed1); |
| OTBN_DECLARE_SYMBOL_ADDR(p256_key_from_seed_sca, d0); |
| OTBN_DECLARE_SYMBOL_ADDR(p256_key_from_seed_sca, d1); |
| OTBN_DECLARE_SYMBOL_ADDR(p256_key_from_seed_sca, x); |
| OTBN_DECLARE_SYMBOL_ADDR(p256_key_from_seed_sca, y); |
| |
| static const otbn_app_t kOtbnAppP256KeyFromSeed = |
| OTBN_APP_T_INIT(p256_key_from_seed_sca); |
| |
| static const otbn_addr_t kOtbnVarMode = |
| OTBN_ADDR_T_INIT(p256_key_from_seed_sca, mode); |
| static const otbn_addr_t kOtbnVarSeed0 = |
| OTBN_ADDR_T_INIT(p256_key_from_seed_sca, seed0); |
| static const otbn_addr_t kOtbnVarSeed1 = |
| OTBN_ADDR_T_INIT(p256_key_from_seed_sca, seed1); |
| static const otbn_addr_t kOtbnVarD0 = |
| OTBN_ADDR_T_INIT(p256_key_from_seed_sca, d0); |
| static const otbn_addr_t kOtbnVarD1 = |
| OTBN_ADDR_T_INIT(p256_key_from_seed_sca, d1); |
| static const otbn_addr_t kOtbnVarX = |
| OTBN_ADDR_T_INIT(p256_key_from_seed_sca, x); |
| static const otbn_addr_t kOtbnVarY = |
| OTBN_ADDR_T_INIT(p256_key_from_seed_sca, y); |
| |
| /** |
| * Seed value. |
| * |
| * The default value corresponds to the test data in |
| * sw/otbn/crypto/test/p256_key_from_seed_test.s |
| * |
| * This default value can be overwritten via the simpleserial command `x` |
| * (see ecc256_set_seed) |
| */ |
| uint32_t ecc256_seed[kEcc256SeedNumWords] = { |
| 0x016064e9, 0x11e3f4d6, 0xac3a6fa7, 0xaba11a1b, 0x8f9271d1, |
| 0x22b79d5f, 0x1176f31d, 0xb5ac3a51, 0x99a082d7, 0x484eb366, |
| }; |
| |
| /** |
| * Simple serial 'm' (set masks enable) command handler. |
| * |
| * This can be used for batch mode. |
| * |
| * @param enable 1 => masks enabled, 0 => masks disabled. |
| * @param enable_len Length of sent enable value. |
| */ |
| static void ecc256_en_masks(const uint8_t *enable, size_t enable_len) { |
| SS_CHECK(enable_len == 1); |
| if (*enable) { |
| en_masks = true; |
| } else { |
| en_masks = false; |
| } |
| } |
| |
| /** |
| * Simple serial 'x' (set seed) command handler. |
| * |
| * The seed must be `kEcc256SeedNumBytes` bytes long. |
| * |
| * @param seed Value for seed share. |
| * @param seed_len Length of seed share. |
| */ |
| static void ecc256_set_seed(const uint8_t *seed, size_t seed_len) { |
| SS_CHECK(seed_len == kEcc256SeedNumBytes); |
| memcpy(ecc256_seed, seed, seed_len); |
| } |
| |
| /** |
| * Runs the OTBN key generation program. |
| * |
| * The seed shares must be `kEcc256SeedNumWords` words long. |
| * |
| * @param[in] mode Mode parameter (private key only or full keypair). |
| * @param[in] seed Seed for key generation. |
| * @param[in] mask Mask for seed. |
| */ |
| static void p256_run_keygen(uint32_t mode, const uint32_t *seed, |
| const uint32_t *mask) { |
| // Write mode. |
| SS_CHECK_STATUS_OK(otbn_dmem_write(/*num_words=*/1, &mode, kOtbnVarMode)); |
| |
| // Compute first share of seed (seed ^ mask). |
| uint32_t seed0[kEcc256SeedNumWords]; |
| for (size_t i = 0; i < kEcc256SeedNumWords; i++) { |
| seed0[i] = seed[i] ^ mask[i]; |
| } |
| |
| // Write seed shares. |
| SS_CHECK_STATUS_OK( |
| otbn_dmem_write(kEcc256SeedNumWords, seed0, kOtbnVarSeed0)); |
| SS_CHECK_STATUS_OK(otbn_dmem_write(kEcc256SeedNumWords, mask, kOtbnVarSeed1)); |
| |
| // Execute program. |
| sca_set_trigger_high(); |
| SS_CHECK_STATUS_OK(otbn_execute()); |
| SS_CHECK_STATUS_OK(otbn_busy_wait_for_done()); |
| sca_set_trigger_low(); |
| } |
| |
| static void ecc256_ecdsa_secret_keygen_batch(const uint8_t *data, |
| size_t data_len) { |
| uint32_t num_traces = 0; |
| uint32_t out[kEcc256SeedNumWords]; |
| uint32_t batch_digest[kEcc256SeedNumWords]; |
| uint8_t dummy[kEcc256SeedNumBytes]; |
| SS_CHECK(data_len == sizeof(num_traces)); |
| num_traces = read_32(data); |
| |
| if (num_traces > kNumBatchOpsMax) { |
| LOG_ERROR("Too many traces for one batch."); |
| return; |
| } |
| |
| // zero the batch digest |
| for (uint32_t j = 0; j < kEcc256SeedNumWords; ++j) { |
| batch_digest[j] = 0; |
| } |
| |
| for (uint32_t i = 0; i < num_traces; ++i) { |
| if (run_fixed) { |
| memcpy(batch_seeds[i], ecc256_seed, kEcc256SeedNumBytes); |
| } else { |
| prng_rand_bytes((unsigned char *)batch_seeds[i], kEcc256SeedNumBytes); |
| } |
| if (en_masks) { |
| prng_rand_bytes((unsigned char *)batch_masks[i], kEcc256SeedNumBytes); |
| } else { |
| for (uint32_t j = 0; j < kEcc256SeedNumWords; ++j) { |
| batch_masks[i][j] = 0; |
| } |
| } |
| // Another PRNG run to determine 'run_fixed' for the next cycle. |
| prng_rand_bytes(dummy, kEcc256SeedNumBytes); |
| run_fixed = dummy[0] & 0x1; |
| } |
| |
| for (uint32_t i = 0; i < num_traces; ++i) { |
| p256_run_keygen(kEcc256ModePrivateKeyOnly, batch_seeds[i], batch_masks[i]); |
| |
| // Read results. |
| SS_CHECK_STATUS_OK( |
| otbn_dmem_read(kEcc256SeedNumWords, kOtbnVarD0, d0_batch)); |
| SS_CHECK_STATUS_OK( |
| otbn_dmem_read(kEcc256SeedNumWords, kOtbnVarD1, d1_batch)); |
| |
| // The correctness of each batch is verified by computing and sending |
| // the batch digest. This digest is computed by XORing all d0 shares of |
| // the batch. |
| for (uint32_t j = 0; j < kEcc256SeedNumWords; ++j) { |
| batch_digest[j] ^= d0_batch[j]; |
| } |
| } |
| |
| // Send the batch digest to the host for verification. |
| simple_serial_send_packet('r', (uint8_t *)batch_digest, |
| kEcc256SeedNumWords * 4); |
| } |
| |
| /** |
| * Generates a secret key from a masked seed. |
| * |
| * The seed shares must be `kEcc256SeedNumWords` words long, and the caller |
| * must provide pre-allocated buffers of the same length for the private key |
| * shares. |
| * |
| * @param[in] seed Seed for key generation. |
| * @param[in] mask Mask for seed. |
| * @param[out] d0 First share of masked private key d. |
| * @param[out] d1 Second share of masked private key d. |
| */ |
| static void p256_ecdsa_gen_secret_key(const uint32_t *seed, |
| const uint32_t *mask, uint32_t *d0, |
| uint32_t *d1) { |
| // Run the key generation program. |
| p256_run_keygen(kEcc256ModePrivateKeyOnly, seed, mask); |
| |
| // Read results. |
| SS_CHECK_STATUS_OK(otbn_dmem_read(kEcc256SeedNumWords, kOtbnVarD0, d0)); |
| SS_CHECK_STATUS_OK(otbn_dmem_read(kEcc256SeedNumWords, kOtbnVarD1, d1)); |
| } |
| |
| /** |
| * Generates a keypair from a masked seed. |
| * |
| * The seed shares must be `kEcc256SeedNumWords` words long, and the caller |
| * must provide pre-allocated buffers of the same length for the private key |
| * shares and of length `kEcc256CoordNumWords` for the public key coordinates. |
| * |
| * @param[in] seed Seed for key generation. |
| * @param[in] mask Mask for seed. |
| * @param[out] d0 First share of masked private key d. |
| * @param[out] d1 Second share of masked private key d. |
| * @param[out] x x-coordinate of public key Q. |
| * @param[out] y y-coordinate of public key Q. |
| */ |
| static void p256_ecdsa_gen_keypair(const uint32_t *seed, const uint32_t *mask, |
| uint32_t *d0, uint32_t *d1, uint32_t *x, |
| uint32_t *y) { |
| // Run the key generation program. |
| p256_run_keygen(kEcc256ModeKeypair, seed, mask); |
| |
| // Read results. |
| SS_CHECK_STATUS_OK(otbn_dmem_read(kEcc256SeedNumWords, kOtbnVarD0, d0)); |
| SS_CHECK_STATUS_OK(otbn_dmem_read(kEcc256SeedNumWords, kOtbnVarD1, d1)); |
| SS_CHECK_STATUS_OK(otbn_dmem_read(kEcc256CoordNumWords, kOtbnVarX, x)); |
| SS_CHECK_STATUS_OK(otbn_dmem_read(kEcc256CoordNumWords, kOtbnVarY, y)); |
| } |
| |
| /** |
| * Simple serial 'k' (secret keygen) command handler. |
| * |
| * Takes the mask value from the simple serial UART and triggers an OTBN |
| * secret key generation operation. The mask must be `kEcc256SeedNumBytes` |
| * bytes long. |
| * |
| * Uses a fixed seed. To overwrite the seed, use the simpleserial command 's'. |
| * |
| * @param[in] mask The mask provided by the simpleserial UART. |
| * @param[in] mask_len Length of the mask. |
| */ |
| static void ecc256_ecdsa_secret_keygen(const uint8_t *mask, size_t mask_len) { |
| if (mask_len != kEcc256SeedNumBytes) { |
| LOG_ERROR("Invalid mask length %hu", (uint8_t)mask_len); |
| return; |
| } |
| |
| // Copy mask to an aligned buffer. |
| uint32_t ecc256_mask[kEcc256SeedNumWords]; |
| memcpy(ecc256_mask, mask, kEcc256SeedNumBytes); |
| |
| uint32_t ecc256_d0[kEcc256SeedNumWords]; |
| uint32_t ecc256_d1[kEcc256SeedNumWords]; |
| p256_ecdsa_gen_secret_key(ecc256_seed, ecc256_mask, ecc256_d0, ecc256_d1); |
| |
| simple_serial_send_packet('r', (unsigned char *)ecc256_d0, |
| kEcc256SeedNumBytes); |
| simple_serial_send_packet('r', (unsigned char *)ecc256_d1, |
| kEcc256SeedNumBytes); |
| } |
| |
| /** |
| * Simple serial 'p' (keypair generation) command handler. |
| * |
| * Takes the mask value from the simple serial UART and triggers an OTBN |
| * secret key generation operation. The mask must be `kEcc256SeedNumBytes` |
| * bytes long. |
| * |
| * Uses a fixed seed. To overwrite the seed, use the simpleserial command 's'. |
| * |
| * @param[in] mask The mask provided by the simpleserial UART. |
| * @param[in] mask_len Length of the mask. |
| */ |
| static void ecc256_ecdsa_gen_keypair(const uint8_t *mask, size_t mask_len) { |
| if (mask_len != kEcc256SeedNumBytes) { |
| LOG_ERROR("Invalid mask length %hu", (uint8_t)mask_len); |
| return; |
| } |
| |
| // Copy mask to an aligned buffer. |
| uint32_t ecc256_mask[kEcc256SeedNumWords]; |
| memcpy(ecc256_mask, mask, kEcc256SeedNumBytes); |
| |
| uint32_t ecc256_d0[kEcc256SeedNumWords]; |
| uint32_t ecc256_d1[kEcc256SeedNumWords]; |
| uint32_t ecc256_x[kEcc256CoordNumWords]; |
| uint32_t ecc256_y[kEcc256CoordNumWords]; |
| p256_ecdsa_gen_keypair(ecc256_seed, ecc256_mask, ecc256_d0, ecc256_d1, |
| ecc256_x, ecc256_y); |
| |
| simple_serial_send_packet('r', (unsigned char *)ecc256_d0, |
| kEcc256SeedNumBytes); |
| simple_serial_send_packet('r', (unsigned char *)ecc256_d1, |
| kEcc256SeedNumBytes); |
| simple_serial_send_packet('r', (unsigned char *)ecc256_x, |
| kEcc256CoordNumBytes); |
| simple_serial_send_packet('r', (unsigned char *)ecc256_y, |
| kEcc256CoordNumBytes); |
| } |
| |
| /** |
| * Initializes peripherals and processes simple serial packets received over |
| * UART. |
| */ |
| static void simple_serial_main(void) { |
| entropy_testutils_auto_mode_init(); |
| |
| sca_init(kScaTriggerSourceOtbn, kScaPeripheralEntropy | kScaPeripheralIoDiv4 | |
| kScaPeripheralOtbn | kScaPeripheralCsrng | |
| kScaPeripheralEdn | kScaPeripheralHmac); |
| |
| LOG_INFO("Running ECC serial"); |
| LOG_INFO("Initializing simple serial interface to capture board."); |
| |
| simple_serial_init(sca_get_uart()); |
| SS_CHECK(simple_serial_register_handler( |
| 'b', ecc256_ecdsa_secret_keygen_batch) == kSimpleSerialOk); |
| SS_CHECK(simple_serial_register_handler('k', ecc256_ecdsa_secret_keygen) == |
| kSimpleSerialOk); |
| SS_CHECK(simple_serial_register_handler('p', ecc256_ecdsa_gen_keypair) == |
| kSimpleSerialOk); |
| SS_CHECK(simple_serial_register_handler('x', ecc256_set_seed) == |
| kSimpleSerialOk); |
| SS_CHECK(simple_serial_register_handler('m', ecc256_en_masks) == |
| kSimpleSerialOk); |
| |
| LOG_INFO("Load p256 keygen from seed app into OTBN"); |
| SS_CHECK_STATUS_OK(otbn_load_app(kOtbnAppP256KeyFromSeed)); |
| |
| LOG_INFO("Starting simple serial packet handling."); |
| while (true) { |
| simple_serial_process_packet(); |
| } |
| } |
| |
| bool test_main(void) { |
| simple_serial_main(); |
| return true; |
| } |