blob: c9d0366fbde8c1b9623a31a025738d5bcbbe211b [file] [log] [blame]
// 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/dif/dif_gpio.h"
#include "sw/device/lib/dif/dif_rv_timer.h"
#include "sw/device/lib/dif/dif_uart.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/runtime/log.h"
#include "sw/device/lib/runtime/print.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;
static dif_uart_t uart;
enum {
kAesKeySize = 16,
/**
* Number of cycles (at `kClockFreqCpuHz`) that Ibex should sleep to minimize
* noise during AES operations. Caution: This number should be chosen to
* provide enough time. Otherwise, Ibex might wake up while AES is still busy
* and disturb the capture. Currently, we use a start trigger delay of 40
* clock cycles and the scope captures 18 clock cycles at kClockFreqCpuHz (180
* samples). The latter number will likely increase as we improve the security
* hardening.
*/
kIbexAesSleepCycles = 200,
/**
* GPIO capture trigger values.
*/
kGpioCaptureTriggerHigh = 0x08200,
kGpioCaptureTriggerLow = 0x00000,
/**
* RV timer settings.
*/
kRvTimerComparator = 0,
kRvTimerHart = kTopEarlgreyPlicTargetIbex0,
/**
* UART constants.
*/
kUartMaxRxSizeText = 128,
kUartMaxRxSizeBin = 64,
/**
* Simple serial protocol version 1.1.
*/
kSimpleSerialProtocolVersion = 1,
};
// Simple serial status codes
typedef enum simple_serial_result {
kSimpleSerialOk = 0,
kSimpleSerialError = 1
} simple_serial_result_t;
// 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[out] to output value in hex format.
*
* @return kSimpleSerialOk on success, kSimpleSerialError otherwise.
*/
static simple_serial_result_t hex_value(uint8_t from, uint8_t *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[out] to output buffer for binary.
* @param num length of output buffer.
*
* @return kSimpleSerialOk on success, kSimpleSerialError otherwise.
*/
static simple_serial_result_t a2b_hex(const uint8_t *from, uint8_t *to,
size_t num) {
for (int i = 0; i < num; ++i) {
uint8_t 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 uint8_t cmd_tag, const uint8_t *data,
size_t data_len) {
base_printf("%c", cmd_tag);
for (int i = 0; i < data_len; ++i) {
base_printf("%2x", (uint32_t)data[i]);
}
base_printf("\n");
}
/**
* 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 uint8_t *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 plaintext plain text to be encrypted.
* @param plaintext_len plain text length.
*
* @return kSimpleSerialOk on success, kSimpleSerialError otherwise.
*/
static simple_serial_result_t simple_serial_trigger_encryption(
const uint8_t *plaintext, size_t plaintext_len) {
if (plaintext_len > kUartMaxRxSizeBin) {
return kSimpleSerialError;
}
uint8_t ciphertext[64];
if (plaintext_len != kAesKeySize) {
return kSimpleSerialError;
}
// Provide input data to AES.
aes_data_put_wait(plaintext);
// Arm capture trigger. The actual trigger signal used for capture is a
// combination (logical AND) of:
// - GPIO15 enabled here, and
// - the busy signal of the AES unit. This signal will go high 40 cycles
// after aes_manual_trigger() is executed below and remain high until
// the operation has finished.
SS_CHECK(dif_gpio_write_all(&gpio, kGpioCaptureTriggerHigh) == kDifGpioOk);
// Start timer to wake up Ibex after AES is done.
uint64_t current_time;
SS_CHECK(dif_rv_timer_counter_read(&timer, kRvTimerHart, &current_time) ==
kDifRvTimerOk);
SS_CHECK(dif_rv_timer_arm(&timer, kRvTimerHart, kRvTimerComparator,
current_time + kIbexAesSleepCycles) ==
kDifRvTimerOk);
SS_CHECK(dif_rv_timer_counter_set_enabled(
&timer, kRvTimerHart, kDifRvTimerEnabled) == kDifRvTimerOk);
// Start AES operation (this triggers the capture) and go to sleep.
// Using the SecAesStartTriggerDelay hardware parameter, the AES unit is
// configured to start operation 40 cycles after receiving the start trigger.
// This allows Ibex to go to sleep in order to not disturb the capture.
aes_manual_trigger();
wait_for_interrupt();
// Disable capture trigger.
SS_CHECK(dif_gpio_write_all(&gpio, kGpioCaptureTriggerLow) == kDifGpioOk);
// Retrieve output data from AES.
aes_data_get_wait(ciphertext);
print_cmd_response('r', ciphertext, plaintext_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 uint8_t *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 >= kUartMaxRxSizeBin) {
return;
}
uint8_t 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', (uint8_t[1]){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 uint8_t *)&result, /*data_len=*/1);
}
int main(void) {
CHECK(dif_uart_init(
(dif_uart_params_t){
.base_addr = mmio_region_from_addr(TOP_EARLGREY_UART_BASE_ADDR),
},
&uart) == kDifUartOk);
CHECK(dif_uart_configure(&uart, (dif_uart_config_t){
.baudrate = kUartBaudrate,
.clk_freq_hz = kClockFreqPeripheralHz,
.parity_enable = kDifUartToggleDisabled,
.parity = kDifUartParityEven,
}) == kDifUartConfigOk);
base_uart_stdout(&uart);
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(kClockFreqPeripheralHz,
kClockFreqCpuHz, &tick_params) ==
kDifRvTimerApproximateTickParamsOk);
CHECK(dif_rv_timer_set_tick_params(&timer, kRvTimerHart, tick_params) ==
kDifRvTimerOk);
CHECK(dif_rv_timer_irq_enable(&timer, kRvTimerHart, kRvTimerComparator,
kDifRvTimerEnabled) == kDifRvTimerOk);
dif_gpio_params_t gpio_params = {
.base_addr = mmio_region_from_addr(TOP_EARLGREY_GPIO_BASE_ADDR)};
CHECK(dif_gpio_init(gpio_params, &gpio) == kDifGpioOk);
CHECK(dif_gpio_output_set_enabled_all(&gpio, 0x08200) == kDifGpioOk);
CHECK(dif_gpio_write_all(&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;
uint8_t text[128] = {0};
size_t pos = 0;
while (true) {
size_t chars_read;
if (dif_uart_bytes_receive(&uart, 1, (uint8_t *)&text[pos], &chars_read) !=
kDifUartOk ||
chars_read == 0) {
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) % kUartMaxRxSizeText;
}
}
}
void handler_irq_timer(void) {
bool irq_flag;
CHECK(dif_rv_timer_irq_get(&timer, kRvTimerHart, kRvTimerComparator,
&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, kRvTimerHart,
kDifRvTimerDisabled) == kDifRvTimerOk);
CHECK(dif_rv_timer_irq_clear(&timer, kRvTimerHart, kRvTimerComparator) ==
kDifRvTimerOk);
}