[sw/sca] AES-ECB encrypt simple serial
The AES test program is based on ChipWhisperer's simple serial protocol.
The test is configured to work with the CW305 FPGA board and a
ChipWhisperer capture board.
Currently, the test only supports AES-ECB 128bit key encryption.
Signed-off-by: Miguel Osorio <miguelosorio@google.com>
diff --git a/sw/device/meson.build b/sw/device/meson.build
index 7e21754..04734e7 100644
--- a/sw/device/meson.build
+++ b/sw/device/meson.build
@@ -33,6 +33,7 @@
subdir('boot_rom')
subdir('mask_rom')
subdir('examples')
+subdir('sca')
subdir('tests')
subdir('benchmarks')
subdir('tock')
diff --git a/sw/device/sca/aes_serial/aes_serial.c b/sw/device/sca/aes_serial/aes_serial.c
new file mode 100644
index 0000000..a79e5be
--- /dev/null
+++ b/sw/device/sca/aes_serial/aes_serial.c
@@ -0,0 +1,336 @@
+// 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/aes.h"
+#include "sw/device/lib/arch/device.h"
+#include "sw/device/lib/base/log.h"
+#include "sw/device/lib/dif/dif_gpio.h"
+#include "sw/device/lib/dif/dif_rv_timer.h"
+#include "sw/device/lib/handler.h"
+#include "sw/device/lib/irq.h"
+#include "sw/device/lib/pinmux.h"
+#include "sw/device/lib/runtime/hart.h"
+#include "sw/device/lib/uart.h"
+
+#include "hw/top_earlgrey/sw/autogen/top_earlgrey.h"
+
+// This program implements the ChipWhisperer simple serial protocol version 1.1.
+// Only set key ('k'), encrypt ('p') and version ('v') commands are implemented.
+// Encryption is done in AES-ECB-128 mode.
+// See https://wiki.newae.com/SimpleSerial for details.
+
+static dif_gpio_t gpio;
+static dif_rv_timer_t timer;
+
+// UART input maximum buffer sizes
+static const uint32_t kMaxInputLengthText = 128;
+static const uint32_t kMaxInputLengthBin = 64;
+
+// AES Configuration parameters. These values must match the test driving
+// side.
+static const uint32_t kAesBlockSize = 16;
+static const uint32_t kAesKeySize = 16;
+
+// Simple serial status codes
+typedef enum simple_serial_result {
+ kSimpleSerialOk = 0,
+ kSimpleSerialError = 1
+} simple_serial_result_t;
+
+// Simple serial protocol version 1.1
+static const uint32_t kSimpleSerialProtocolVersion = 1;
+
+// GPIO capture trigger values
+static const uint32_t kGpioCaptureTriggerHigh = 0x08200;
+static const uint32_t kGpioCaptureTriggerLow = 0x00000;
+
+// RV Timer settings
+static const uint32_t kHart = (uint32_t)kTopEarlgreyPlicTargetIbex0;
+static const uint32_t kComparator = 0;
+static const uint64_t kTickFreqHz = 1000 * 1000; // 1 MHz.
+static const uint64_t kDeadline = 500; // 500us.
+
+// TODO: Remove once there is a CHECK version that does not require test
+// library dependencies.
+#define CHECK(condition, ...) \
+ do { \
+ if (!(condition)) { \
+ /* NOTE: because the condition in this if \
+ statement can be statically determined, \
+ only one of the below string constants \
+ will be included in the final binary.*/ \
+ if (GET_NUM_VARIABLE_ARGS(_, ##__VA_ARGS__) == 0) { \
+ LOG_ERROR("CHECK-fail: " #condition); \
+ } else { \
+ LOG_ERROR("CHECK-fail: " __VA_ARGS__); \
+ } \
+ } \
+ } while (false)
+
+/** Returns kSimpleSerialError if the condition evaluates to false */
+#define SS_CHECK(condition) \
+ do { \
+ if (!(condition)) { \
+ return kSimpleSerialError; \
+ } \
+ } while (false)
+
+/**
+ * Convert `from` binary `to` hex.
+ *
+ * @param from input value in binary format.
+ * @param to output value in hex format.
+ *
+ * @return kSimpleSerialOk on success, kSimpleSerialError otherwise.
+ */
+static simple_serial_result_t hex_value(char from, char *to) {
+ if (from >= '0' && from <= '9') {
+ *to = from - '0';
+ } else if (from >= 'A' && from <= 'F') {
+ *to = from - 'A' + 10;
+ } else if (from >= 'a' && from <= 'f') {
+ *to = from - 'a' + 10;
+ } else {
+ return kSimpleSerialError;
+ }
+ return kSimpleSerialOk;
+}
+
+/**
+ * Convert `num` bytes `from` hex `to` binary format.
+ *
+ * `from` size is expected to by twice as big as `to`.
+ *
+ * @param from input buffer for hex formatted characters.
+ * @param to output binary buffer.
+ * @param num number of characters in output buffer.
+ *
+ * @return kSimpleSerialOk on success, kSimpleSerialError otherwise.
+ */
+static simple_serial_result_t a2b_hex(const char *from, char *to, size_t num) {
+ for (int i = 0; i < num; ++i) {
+ char hi, lo;
+ if (hex_value(from[i * 2], &hi) || hex_value(from[i * 2 + 1], &lo)) {
+ return kSimpleSerialError;
+ }
+ to[i] = ((hi & 0xFF) << 4) | (lo & 0xFF);
+ }
+ return kSimpleSerialOk;
+}
+
+/**
+ * Send simple serial command response via UART.
+ *
+ * @param cmd_tag Simple serial command tag.
+ * @param data Response data. Converted to hex format by this function.
+ * @param data_len data length.
+ */
+static void print_cmd_response(const char cmd_tag, const char *data,
+ size_t data_len) {
+ static const char b2a_hex_values[16] = "0123456789ABCDEF";
+
+ // TODO: Switch to sw/device/lib/base/print.h
+ uart_send_char(cmd_tag);
+ for (int i = 0; i < data_len; ++i) {
+ uart_send_char(b2a_hex_values[data[i] >> 4]);
+ uart_send_char(b2a_hex_values[data[i] & 0xF]);
+ }
+ uart_send_char('\n');
+}
+
+/**
+ * Return number of AES blocks from `plain_text_len`.
+ */
+static size_t get_num_blocks(size_t plain_text_len) {
+ size_t num_blocks = plain_text_len / kAesBlockSize;
+ if ((plain_text_len - (num_blocks * kAesBlockSize)) > 0) {
+ ++num_blocks;
+ }
+ return num_blocks;
+}
+
+/**
+ * Simple serial set AES key command.
+ *
+ * @param aes_cfg AES configuration object.
+ * @param key AES key.
+ * @param key_len AES key length.
+ *
+ * @return kSimpleSerialOk on success, kSimpleSerialError otherwise.
+ */
+static simple_serial_result_t simple_serial_set_key(const aes_cfg_t *aes_cfg,
+ const char *key_share0,
+ size_t key_len) {
+ // The implementation does not use key shares to simplify AES attacks.
+ static const uint8_t kKeyShare1[32] = {0};
+
+ if (key_len != kAesKeySize) {
+ return kSimpleSerialError;
+ }
+
+ aes_clear();
+ while (!aes_idle()) {
+ ;
+ }
+ aes_init(*aes_cfg);
+
+ aes_key_put(key_share0, kKeyShare1, aes_cfg->key_len);
+ return kSimpleSerialOk;
+}
+
+/*
+ * Simple serial trigger AES encryption command.
+ *
+ * @param aes_cfg AES configuration object.
+ * @param plain_text plain text to be encrypted.
+ * @param plain_text_len plain text length.
+ *
+ * @return kSimpleSerialOk on success, kSimpleSerialError otherwise.
+ */
+static simple_serial_result_t simple_serial_trigger_encryption(
+ const char *plain_text, size_t plain_text_len) {
+ if (plain_text_len > kMaxInputLengthBin) {
+ return kSimpleSerialError;
+ }
+
+ char cipher_text[64];
+ size_t num_blocks = get_num_blocks(plain_text_len);
+ if (num_blocks != 1) {
+ return kSimpleSerialError;
+ }
+
+ // start timer to wake up Ibex after AES is done.
+ uint64_t current_time;
+ SS_CHECK(dif_rv_timer_counter_read(&timer, kHart, ¤t_time) ==
+ kDifRvTimerOk);
+ SS_CHECK(dif_rv_timer_arm(&timer, kHart, kComparator,
+ current_time + kDeadline) == kDifRvTimerOk);
+ SS_CHECK(dif_rv_timer_counter_set_enabled(
+ &timer, kHart, kDifRvTimerEnabled) == kDifRvTimerOk);
+
+ aes_data_put_wait(plain_text);
+ SS_CHECK(dif_gpio_all_write(&gpio, kGpioCaptureTriggerHigh) == kDifGpioOk);
+
+ aes_manual_trigger();
+ wait_for_interrupt();
+
+ SS_CHECK(dif_gpio_all_write(&gpio, kGpioCaptureTriggerLow) == kDifGpioOk);
+ aes_data_get_wait(cipher_text);
+
+ print_cmd_response('r', cipher_text, plain_text_len);
+ return kSimpleSerialOk;
+}
+
+/**
+ * Handle simple serial command.
+ *
+ * @param aes_cfg AES configuration object.
+ * @param cmd input command buffer.
+ * @param cmd_len number of characters set in input buffer.
+ */
+static void simple_serial_handle_command(const aes_cfg_t *aes_cfg,
+ const char *cmd, size_t cmd_len) {
+ // Data length is half the size of the hex encoded string.
+ const size_t data_len = cmd_len >> 1;
+ if (data_len >= kMaxInputLengthBin) {
+ return;
+ }
+ char data[64] = {0};
+
+ // The simple serial protocol does not expect return status codes for invalid
+ // data, thus it is ok to return early.
+ if (a2b_hex(&cmd[1], data, data_len) == kSimpleSerialError) {
+ return;
+ }
+
+ if (cmd[0] == 'v') {
+ print_cmd_response('z', (const char *)&kSimpleSerialProtocolVersion,
+ /*data_len=*/1);
+ return;
+ }
+
+ simple_serial_result_t result = kSimpleSerialOk;
+ switch (cmd[0]) {
+ case 'k':
+ result = simple_serial_set_key(aes_cfg, data, data_len);
+ break;
+ case 'p':
+ result = simple_serial_trigger_encryption(data, data_len);
+ break;
+ default:
+ // Unknown command.
+ result = kSimpleSerialError;
+ }
+
+ // This protocol version expects a 1 byte return status.
+ print_cmd_response('z', (const char *)&result, /*data_len=*/1);
+}
+
+int main(int argc, char **argv) {
+ uart_init(kUartBaudrate);
+ base_set_stdout(uart_stdout);
+ pinmux_init();
+
+ irq_global_ctrl(true);
+ irq_timer_ctrl(true);
+
+ CHECK(dif_rv_timer_init(
+ mmio_region_from_addr(TOP_EARLGREY_RV_TIMER_BASE_ADDR),
+ (dif_rv_timer_config_t){.hart_count = 1, .comparator_count = 1},
+ &timer) == kDifRvTimerOk);
+ dif_rv_timer_tick_params_t tick_params;
+ CHECK(dif_rv_timer_approximate_tick_params(kClockFreqHz, kTickFreqHz,
+ &tick_params) ==
+ kDifRvTimerApproximateTickParamsOk);
+ CHECK(dif_rv_timer_set_tick_params(&timer, kHart, tick_params) ==
+ kDifRvTimerOk);
+ CHECK(dif_rv_timer_irq_enable(&timer, kHart, kComparator,
+ kDifRvTimerEnabled) == kDifRvTimerOk);
+
+ dif_gpio_config_t gpio_config = {.base_addr =
+ mmio_region_from_addr(0x40010000)};
+ CHECK(dif_gpio_init(&gpio_config, &gpio) == kDifGpioOk);
+ CHECK(dif_gpio_output_mode_all_set(&gpio, 0x08200) == kDifGpioOk);
+ CHECK(dif_gpio_all_write(&gpio, kGpioCaptureTriggerLow) == kDifGpioOk);
+
+ aes_cfg_t aes_cfg;
+ aes_cfg.key_len = kAes128;
+ aes_cfg.operation = kAesEnc;
+ aes_cfg.mode = kAesEcb;
+ aes_cfg.manual_operation = true;
+
+ char text[128] = {0};
+ size_t pos = 0;
+ while (true) {
+ if (uart_rcv_char(&text[pos]) == -1) {
+ usleep(50);
+ continue;
+ }
+ if (text[pos] == '\n' || text[pos] == '\r') {
+ // Continue to override line feed (\n) or carriage return (\r). This
+ // way we don't pass empty lines to the handle_command function.
+ if (pos != 0) {
+ simple_serial_handle_command(&aes_cfg, text, pos - 1);
+ }
+ // Reset `pos` for the next command.
+ pos = 0;
+ } else {
+ // Going over the maxium buffer size will result in corrupting the input
+ // buffer. This is okay in this case since we control the script used
+ // to drive the UART input.
+ pos = (pos + 1) % kMaxInputLengthText;
+ }
+ }
+}
+
+void handler_irq_timer(void) {
+ bool irq_flag;
+ CHECK(dif_rv_timer_irq_get(&timer, kHart, kComparator, &irq_flag) ==
+ kDifRvTimerOk);
+ CHECK(irq_flag, "Entered IRQ handler but the expected IRQ flag wasn't set!");
+
+ CHECK(dif_rv_timer_counter_set_enabled(&timer, kHart, kDifRvTimerDisabled) ==
+ kDifRvTimerOk);
+ CHECK(dif_rv_timer_irq_clear(&timer, kHart, kComparator) == kDifRvTimerOk);
+}
diff --git a/sw/device/sca/aes_serial/meson.build b/sw/device/sca/aes_serial/meson.build
new file mode 100644
index 0000000..ecd6c5a
--- /dev/null
+++ b/sw/device/sca/aes_serial/meson.build
@@ -0,0 +1,42 @@
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+
+foreach device_name, device_lib : sw_lib_arch_core_devices
+ aes_serial_elf = executable(
+ 'aes_serial_' + device_name,
+ sources: ['aes_serial.c'],
+ name_suffix: 'elf',
+ dependencies: [
+ device_lib,
+ dif_rv_timer,
+ riscv_crt,
+ sw_lib_aes,
+ sw_lib_base_log,
+ sw_lib_dif_gpio,
+ sw_lib_irq_handlers,
+ sw_lib_irq,
+ sw_lib_mmio,
+ sw_lib_pinmux,
+ sw_lib_runtime_hart,
+ sw_lib_uart,
+ ],
+ )
+
+ aes_serial_embedded = custom_target(
+ 'aes_serial_' + device_name,
+ command: make_embedded_target,
+ input: aes_serial_elf,
+ output: make_embedded_target_outputs,
+ build_by_default: true,
+ )
+
+ custom_target(
+ 'aes_serial_export_' + device_name,
+ command: export_embedded_target,
+ input: [aes_serial_elf, aes_serial_embedded],
+ output: 'aes_serial_export_' + device_name,
+ build_always_stale: true,
+ build_by_default: true,
+ )
+endforeach
diff --git a/sw/device/sca/meson.build b/sw/device/sca/meson.build
new file mode 100644
index 0000000..9511693
--- /dev/null
+++ b/sw/device/sca/meson.build
@@ -0,0 +1,5 @@
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+
+subdir('aes_serial')