diff --git a/MAINTAINERS b/MAINTAINERS index c54eceb77..612403bd0 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -102,6 +102,12 @@ M: Peter Marheine S: Supported F: i2c_helper_linux.c +REPLAY PROTECTED MONOTONIC COUNTER +M: Matti Finder +S: Maintained +F: rpmc.c +F: include/rpmc.h + WRITEPROTECT M: Nikolai Artemiev M: Sergii Dmytruk diff --git a/include/flash.h b/include/flash.h index f13d20288..a1f15514e 100644 --- a/include/flash.h +++ b/include/flash.h @@ -172,6 +172,11 @@ enum write_granularity { /* Whether chip has configuration register (RDCR/WRSR_EXT2 commands) */ #define FEATURE_CFGR (1 << 25) +/* + * Whether the chip supports serial flash hardening specified in JESD260 + */ +#define FEATURE_FLASH_HARDENING (1 << 26) + #define ERASED_VALUE(flash) (((flash)->chip->feature_bits & FEATURE_ERASED_ZERO) ? 0x00 : 0xff) #define UNERASED_VALUE(flash) (((flash)->chip->feature_bits & FEATURE_ERASED_ZERO) ? 0xff : 0x00) @@ -543,6 +548,32 @@ struct flashchip { * and determines what protection range they select. */ enum decode_range_func decode_range; + + struct rpmc_config { + uint8_t op1_opcode; + uint8_t op2_opcode; + + unsigned int num_counters; + + /* + * Busy Polling Method : + * ‘0’: Poll for OP1 busy using OP2 Extended Status[0]. + * No OP1 Suspended State Support. + * ‘1’: Poll for OP1 busy using Read Status (05H). + * Suspended State is supported. + */ + enum busy_polling_methods { + POLL_OP2_EXTENDED_STATUS = 0, + POLL_READ_STATUS = 1 + } busy_polling_method; + + unsigned int update_rate; + + /* All times in microsecond (us) */ + unsigned int polling_delay_read_counter_us; + unsigned int polling_short_delay_write_counter_us; + unsigned int polling_long_delay_write_counter_us; + } rpmc_ctx; }; typedef int (*chip_restore_fn_cb_t)(struct flashctx *flash, void *data); diff --git a/include/rpmc.h b/include/rpmc.h new file mode 100644 index 000000000..271aaef42 --- /dev/null +++ b/include/rpmc.h @@ -0,0 +1,165 @@ +/* + * This file is part of the flashrom project. + * + * Copyright (C) 2024 Matti Finder + * (written by Matti Finder ) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __RPMC_H__ +#define __RPMC_H__ 1 + +#include +#include "flash.h" // for flashctx + +/** + * @defgroup flashrom-rpmc RPMC operations + * @{ + */ + +#define RPMC_OP1_MSG_HEADER_LENGTH 4 +#define RPMC_SIGNATURE_LENGTH 32 +#define RPMC_COUNTER_LENGTH 4 +#define RPMC_KEY_DATA_LENGTH 4 +#define RPMC_TAG_LENGTH 12 +#define RPMC_HMAC_KEY_LENGTH 32 +#define RPMC_TRUNCATED_SIG_LENGTH 28 + +enum rpmc_result { + RPMC_SUCCESS = 0, + RPMC_ERROR_SPI_TRANSMISSION, + RPMC_ERROR_OPENSSL, + RPMC_ERROR_TAG_MISMATCH, + RPMC_ERROR_SIGNATURE_MISMATCH, + RPMC_ERROR_INTERNAL, + RPMC_ERROR_KEY_READ, + RPMC_ERROR_HARDENING_UNSUPPORTED, + RPMC_ERROR_COUNTER_OUT_OF_RANGE, + RPMC_ERROR_ROOT_KEY_OVERWRITE, + RPMC_ERROR_COUNTER_UNINITIALIZED, + RPMC_ERROR_COUNTER_DATA_MISMATCH, + RPMC_ERROR_HMAC_KEY_REGISTER_UNINITIALIZED, + RPMC_ERROR_WRONG_SIGNATURE +}; + +struct rpmc_status_register { + /* + * Values: + * 0b00000000 -> Power on state + * 0b10000000 -> Success + * 0b0xxxxxx1 -> Busy + * 0b0xxxxx1x -> Error: Root key register overwrite, + * counter Address out of range, + * truncated signature mismatch + * or monotonic counter uninitialized + * 0b0xxxx1xx -> Error: Signature mismatch, + * counter address out of range, + * cmdtype out of range + * or incorrect payload size + * 0b0xxx1xxx -> Error: Hmac key register uninitialized + * 0b0xx1xxxx -> Error: Counter data mismatch + * 0b0x1xxxxx -> Fatal device error + * + * Some bits might exclude others or their meaning might be dependent + * on previous commands. + * Read JESD260 or your device's data sheet for more details. + */ + uint8_t status; + unsigned char tag[RPMC_TAG_LENGTH]; + uint32_t counter_data; + unsigned char signature[RPMC_SIGNATURE_LENGTH]; +}; + +/** + * @brief Write root key on flashchip + * + * @param[in] flash Flash context which rpmc options will be used + * @param[in] keyfile Location of 32-byte key to use + * @param[in] counter_address Address of counter (starts at 0) + * + * @return The result of the operation + */ +enum rpmc_result rpmc_write_root_key(struct flashrom_flashctx *flash, + const char *keyfile, + unsigned int counter_address); + +/** + * @brief Update hmac key register + * + * @param[in] flash Flash context which rpmc options will be used + * @param[in] keyfile Location of 32-byte key to use + * @param[in] key_data 4-bytes of data to use as key data + * @param[in] counter_address Address of counter (starts at 0) + * + * @return The result of the operation + */ +enum rpmc_result rpmc_update_hmac_key(struct flashrom_flashctx *flash, + const char *keyfile, + uint32_t key_data, + unsigned int counter_address); + +/** + * @brief Increment monotonic counter value + * + * @param[in] flash Flash context which rpmc options will be used + * @param[in] keyfile Location of 32-byte key to use + * @param[in] key_data 4-bytes of data to use as key data + * @param[in] counter_address Address of counter (starts at 0) + * @param[in] previous_value Previous value of counter + * + * @return The result of the operation + */ +enum rpmc_result rpmc_increment_counter(struct flashrom_flashctx *flash, + const char *keyfile, + uint32_t key_data, + unsigned int counter_address, + uint32_t previous_value); + +/** + * @brief Get monotonic counter value + * + * @param[in] flash Flash context which rpmc options will be used + * @param[in] keyfile Location of 32-byte key to use + * @param[in] key_data 4-bytes of data to use as key data + * @param[in] counter_address Address of counter (starts at 0) + * @param[out] counter_value Pointer to write the counter value to + * + * @return The result of the operation + */ +enum rpmc_result rpmc_get_monotonic_counter(struct flashrom_flashctx *flash, + const char *keyfile, + uint32_t key_data, + unsigned int counter_address, + uint32_t *counter_value); + +/** + * @brief Read the full JESD260 extended status register + * + * @param[in] flash Flash context which rpmc options will be used + * @param[out] status Status register to write data into + * + * @return The result of the operation + */ +enum rpmc_result rpmc_read_data(struct flashrom_flashctx *flash, + struct rpmc_status_register *status); + +/** + * @brief Get a string description for rpmc result + * + * @param[in] value A rpmc result + * + * @return String description + */ +const char * rpmc_describe_result(enum rpmc_result value); + +/** @} */ /* end flashrom-rpmc */ + +#endif /* !__RPMC_H__ */ diff --git a/meson.build b/meson.build index 26d96d932..6c8d3d361 100644 --- a/meson.build +++ b/meson.build @@ -164,6 +164,7 @@ libpci = dependency('libpci', required : group_pci, version : '>=2.2.0', libusb1 = dependency('libusb-1.0', required : group_usb) libftdi1 = dependency('libftdi1', required : group_ftdi) libjaylink = dependency('libjaylink', required : group_jlink, version : '>=0.3.0') +libcrypto = dependency('libcrypto', required : get_option('rpmc'), version : '>=3.0.0') # ECAM is supported in libpci after 3.13.0 if libpci.version().version_compare('>=3.13.0') @@ -172,6 +173,13 @@ else add_project_arguments('-DCONFIG_USE_LIBPCI_ECAM=0', language: 'c') endif +# Support additional rpmc commands if libcrypto is installed +if (get_option('rpmc').auto() or get_option('rpmc').enabled()) and libcrypto.found() + add_project_arguments('-DCONFIG_RPMC_ENABLED=1', language : 'c') + srcs += 'rpmc.c' + deps += libcrypto +endif + if host_machine.system() == 'windows' # Specifying an include_path that doesn't exist is an error, # but we only use this if the library is found in the same directory. diff --git a/meson_options.txt b/meson_options.txt index 6df95ba81..87456a9be 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -24,3 +24,4 @@ option('ni845x_search_path', type : 'string', value : 'C:\Program Files (x86)\Na option('delay_minimum_sleep_us', type : 'integer', min : 0, value : 100, description : 'Minimum time in microseconds to suspend execution for (rather than polling) when a delay is required.' + ' Larger values may perform better on machines with low timer resolution, at the cost of increased power.') +option('rpmc', type : 'feature', value : 'auto', description : 'Support for Replay Protected Monotonic Counter (RPMC) commands as specified by JESD260') diff --git a/rpmc.c b/rpmc.c new file mode 100644 index 000000000..72b0f2f74 --- /dev/null +++ b/rpmc.c @@ -0,0 +1,495 @@ +/* + * This file is part of the flashrom project. + * + * Copyright (C) 2024 Matti Finder + * (written by Matti Finder ) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "rpmc.h" +#include "flash.h" +#include +#include +#include +#include +#include +#include +#include + +// OP1 commands +#define RPMC_WRITE_ROOT_KEY_MSG_LENGTH (RPMC_OP1_MSG_HEADER_LENGTH + RPMC_HMAC_KEY_LENGTH + RPMC_TRUNCATED_SIG_LENGTH) +#define RPMC_UPDATE_HMAC_KEY_MSG_LENGTH (RPMC_OP1_MSG_HEADER_LENGTH + RPMC_KEY_DATA_LENGTH + RPMC_SIGNATURE_LENGTH) +#define RPMC_INCREMENT_MONOTONIC_COUNTER_MSG_LENGTH (RPMC_OP1_MSG_HEADER_LENGTH + RPMC_COUNTER_LENGTH + RPMC_SIGNATURE_LENGTH) +#define RPMC_GET_MONOTONIC_COUNTER_MSG_LENGTH (RPMC_OP1_MSG_HEADER_LENGTH + RPMC_TAG_LENGTH + RPMC_SIGNATURE_LENGTH) + +// OP2 commands +#define RPMC_READ_DATA_MSG_LENGTH 2 +#define RPMC_READ_DATA_ANSWER_LENGTH (1 + RPMC_TAG_LENGTH + RPMC_COUNTER_LENGTH + RPMC_SIGNATURE_LENGTH) + +static enum rpmc_result rpmc_get_extended_status(struct flashrom_flashctx *flash, uint8_t *status) +{ + const unsigned char extended_status_msg[RPMC_READ_DATA_MSG_LENGTH] = { + flash->chip->rpmc_ctx.op2_opcode, + 0 // dummy + }; + + if (spi_send_command(flash, RPMC_READ_DATA_MSG_LENGTH, 1, extended_status_msg, status)) { + msg_gerr("Reading extended status failed\n"); + return RPMC_ERROR_SPI_TRANSMISSION; + } + + return RPMC_SUCCESS; +} + +static enum rpmc_result rpmc_get_extended_status_long(struct flashrom_flashctx *flash, + struct rpmc_status_register *status, + // optional to check values tag and signature against + const unsigned char *const tag, + const unsigned char *const key) +{ + const unsigned int tag_offset = 1; + const unsigned int counter_data_offset = tag_offset + RPMC_TAG_LENGTH; + const unsigned int signature_offset = counter_data_offset + RPMC_COUNTER_LENGTH; + const unsigned char cmd[RPMC_READ_DATA_MSG_LENGTH] = { + flash->chip->rpmc_ctx.op2_opcode, + 0 // dummy + }; + unsigned char answer[RPMC_READ_DATA_ANSWER_LENGTH]; + + if (spi_send_command(flash, RPMC_READ_DATA_MSG_LENGTH, RPMC_READ_DATA_ANSWER_LENGTH, cmd, answer)) { + msg_gerr("reading extended status failed\n"); + return RPMC_ERROR_SPI_TRANSMISSION; + } + + status->status = answer[0]; + + memcpy(status->tag, answer + tag_offset, RPMC_TAG_LENGTH); + + status->counter_data = answer[counter_data_offset]; + status->counter_data = (status->counter_data << 8) | answer[counter_data_offset + 1]; + status->counter_data = (status->counter_data << 8) | answer[counter_data_offset + 2]; + status->counter_data = (status->counter_data << 8) | answer[counter_data_offset + 3]; + + memcpy(status->signature, answer + signature_offset, RPMC_SIGNATURE_LENGTH); + + if (tag != NULL) { + if (memcmp(tag, status->tag, RPMC_TAG_LENGTH) != 0) { + msg_gwarn("Tag doesn't match counter might be false\n"); + return RPMC_ERROR_TAG_MISMATCH; + } + } + + if (key != NULL) { + unsigned char signature_buffer[RPMC_SIGNATURE_LENGTH]; + if (HMAC(EVP_sha256(), + key, RPMC_HMAC_KEY_LENGTH, + answer + tag_offset, RPMC_TAG_LENGTH + RPMC_COUNTER_LENGTH, + signature_buffer, NULL) == NULL) { + msg_gerr("Could not generate signature\n"); + return RPMC_ERROR_OPENSSL; + } + + if (memcmp(signature_buffer, status->signature, RPMC_SIGNATURE_LENGTH) != 0) { + msg_gwarn("Signature doesn't match, counter might be false\n"); + return RPMC_ERROR_SIGNATURE_MISMATCH; + } + } + + return RPMC_SUCCESS; +} + +static enum rpmc_result rpmc_poll_until_finished(struct flashrom_flashctx *flash) +{ + unsigned char poll_response; + + do { + const unsigned char status_poll_msg = 0x05; + + // since we aren't really a time critical application we just sleep for the longest time + programmer_delay(flash, flash->chip->rpmc_ctx.polling_long_delay_write_counter_us); + + switch (flash->chip->rpmc_ctx.busy_polling_method) { + case POLL_READ_STATUS: + if (spi_send_command(flash, 1, 1, &status_poll_msg, &poll_response)) { + msg_gerr("Polling Status-Register-1 failed\n"); + return RPMC_ERROR_SPI_TRANSMISSION; + } + break; + case POLL_OP2_EXTENDED_STATUS: + { + enum rpmc_result res = rpmc_get_extended_status(flash, &poll_response); + if (res != RPMC_SUCCESS) + return res; + break; + } + default: + msg_gerr("Unknown busy polling method found, this should not happen. Exiting...\n"); + return RPMC_ERROR_INTERNAL; + } + } while ((poll_response & 1) != 0); + + return RPMC_SUCCESS; +} + +static enum rpmc_result rpmc_calculate_hmac_key_register(const char *const keyfile, + const uint32_t key_data, + unsigned char *const hmac_key_register) +{ + unsigned char key[RPMC_HMAC_KEY_LENGTH]; + unsigned char key_data_buf[RPMC_KEY_DATA_LENGTH] = { + (key_data >> 24) & 0xff, + (key_data >> 16) & 0xff, + (key_data >> 8) & 0xff, + key_data & 0xff + }; + + if (keyfile == NULL || read_buf_from_file(key, RPMC_HMAC_KEY_LENGTH, keyfile) != 0) + return RPMC_ERROR_KEY_READ; + + if (HMAC(EVP_sha256(), + key, RPMC_HMAC_KEY_LENGTH, + key_data_buf, RPMC_KEY_DATA_LENGTH, + hmac_key_register, NULL) == NULL) { + msg_gerr("Could not calculate HMAC signature for hmac storage\n"); + return RPMC_ERROR_OPENSSL; + } + + return RPMC_SUCCESS; +} + +static enum rpmc_result rpmc_basic_checks(struct flashrom_flashctx *flash, const unsigned int counter_address) +{ + if ((flash->chip->feature_bits & FEATURE_FLASH_HARDENING) == 0) { + msg_gerr("Flash hardening is not supported on this chip, aborting.\n"); + return RPMC_ERROR_HARDENING_UNSUPPORTED; + } + + if (counter_address >= flash->chip->rpmc_ctx.num_counters) { + msg_gerr("Counter address is not in range, should be between 0 and %d.\n", + flash->chip->rpmc_ctx.num_counters - 1); + return RPMC_ERROR_COUNTER_OUT_OF_RANGE; + } + + return RPMC_SUCCESS; +} + +static enum rpmc_result rpmc_send_and_wait(struct flashrom_flashctx *flash, + const unsigned char *const msg, + const size_t length) +{ + if (spi_send_command(flash, length, 0, msg, NULL)) + return RPMC_ERROR_SPI_TRANSMISSION; + + // check operation status + return rpmc_poll_until_finished(flash); +} + +static enum rpmc_result rpmc_sign_send_wait_check(struct flashrom_flashctx *flash, + unsigned char *const msg, + const size_t msg_length, + const size_t signature_offset, + const char *const keyfile, + const uint32_t key_data, + uint8_t *return_status) +{ + unsigned char hmac_key_register[RPMC_HMAC_KEY_LENGTH]; + + enum rpmc_result ret = rpmc_calculate_hmac_key_register(keyfile, key_data, hmac_key_register); + if (ret != RPMC_SUCCESS) + return ret; + + if (HMAC(EVP_sha256(), + hmac_key_register, RPMC_HMAC_KEY_LENGTH, + msg, signature_offset, + msg + signature_offset, NULL) == NULL) { + msg_gerr("Could not generate HMAC signature\n"); + return RPMC_ERROR_OPENSSL; + } + + ret = rpmc_send_and_wait(flash, msg, msg_length); + if (ret != RPMC_SUCCESS) + return ret; + + ret = rpmc_get_extended_status(flash, return_status); + if (ret != RPMC_SUCCESS) + return ret; + + return RPMC_SUCCESS; +} + +enum rpmc_result rpmc_write_root_key(struct flashrom_flashctx *flash, + const char *const keyfile, + const unsigned int counter_address) +{ + const unsigned int key_offset = RPMC_OP1_MSG_HEADER_LENGTH; + const unsigned int signature_offset = key_offset + RPMC_HMAC_KEY_LENGTH; + const unsigned int signature_cutoff = RPMC_SIGNATURE_LENGTH - RPMC_TRUNCATED_SIG_LENGTH; + + unsigned char msg[RPMC_WRITE_ROOT_KEY_MSG_LENGTH] = { + flash->chip->rpmc_ctx.op1_opcode, // Opcode + 0x00, // CmdType + counter_address, // CounterAddr + 0 //Reserved + }; + + enum rpmc_result ret = rpmc_basic_checks(flash, counter_address); + if (ret != RPMC_SUCCESS) + return ret; + + if (keyfile == NULL || read_buf_from_file(msg + key_offset, RPMC_HMAC_KEY_LENGTH, keyfile) != 0) + return RPMC_ERROR_KEY_READ; + + unsigned char signature_buffer[RPMC_SIGNATURE_LENGTH]; + if (HMAC(EVP_sha256(), + msg + key_offset, RPMC_HMAC_KEY_LENGTH, + msg, RPMC_OP1_MSG_HEADER_LENGTH, + signature_buffer, NULL) == NULL) { + msg_gerr("Could not calculate HMAC signature for message\n"); + return RPMC_ERROR_OPENSSL; + } + + // need to truncate the signature a bit + memcpy(msg + signature_offset, signature_buffer + signature_cutoff, RPMC_TRUNCATED_SIG_LENGTH); + + ret = rpmc_send_and_wait(flash, msg, RPMC_WRITE_ROOT_KEY_MSG_LENGTH); + if (ret != RPMC_SUCCESS) + return ret; + + uint8_t status; + ret = rpmc_get_extended_status(flash, &status); + if (ret != RPMC_SUCCESS) + return ret; + + if (status & (1 << 1)) { + return RPMC_ERROR_ROOT_KEY_OVERWRITE; + } else if (status != 0x80) { + // Incorrect payload size received or we have an unexpected bit set + // This should not happen, if we wrote the code correctly + return RPMC_ERROR_INTERNAL; + } + + return RPMC_SUCCESS; +} + +enum rpmc_result rpmc_update_hmac_key(struct flashrom_flashctx *flash, + const char *const keyfile, + const uint32_t key_data, + const unsigned int counter_address) +{ + const unsigned int signature_offset = RPMC_OP1_MSG_HEADER_LENGTH + RPMC_KEY_DATA_LENGTH; + unsigned char msg[RPMC_UPDATE_HMAC_KEY_MSG_LENGTH] = { + flash->chip->rpmc_ctx.op1_opcode, // Opcode + 0x01, // CmdType + counter_address, // CounterAddr + 0, // Reserved + (key_data >> 24) & 0xff, + (key_data >> 16) & 0xff, + (key_data >> 8) & 0xff, + key_data & 0xff + }; + + enum rpmc_result ret = rpmc_basic_checks(flash, counter_address); + if (ret != RPMC_SUCCESS) + return ret; + + uint8_t status; + ret = rpmc_sign_send_wait_check(flash, + msg, + RPMC_UPDATE_HMAC_KEY_MSG_LENGTH, + signature_offset, + keyfile, + key_data, + &status); + if (ret != RPMC_SUCCESS) + return ret; + + if (status & (1 << 1)) { + return RPMC_ERROR_COUNTER_UNINITIALIZED; + } else if (status & (1 << 2)) { + // Counter address out of range or incorrect payload size received + // also possible but we check those in the code + return RPMC_ERROR_WRONG_SIGNATURE; + } else if (status != 0x80) { + // Unexpected bit is set + // This should not happen, if we wrote the code correctly + return RPMC_ERROR_INTERNAL; + } + + return RPMC_SUCCESS; +} + +enum rpmc_result rpmc_increment_counter(struct flashrom_flashctx *flash, + const char *const keyfile, + const uint32_t key_data, + const unsigned int counter_address, + const uint32_t previous_value) +{ + const unsigned int signature_offset = RPMC_OP1_MSG_HEADER_LENGTH + RPMC_COUNTER_LENGTH; + unsigned char msg[RPMC_INCREMENT_MONOTONIC_COUNTER_MSG_LENGTH] = { + flash->chip->rpmc_ctx.op1_opcode, // Opcode + 0x02, // CmdType + counter_address, // CounterAddr + 0, // Reserved + (previous_value >> 24) & 0xff, + (previous_value >> 16) & 0xff, + (previous_value >> 8) & 0xff, + previous_value & 0xff + }; + + enum rpmc_result ret = rpmc_basic_checks(flash, counter_address); + if (ret != RPMC_SUCCESS) + return ret; + + uint8_t status; + ret = rpmc_sign_send_wait_check(flash, + msg, + RPMC_INCREMENT_MONOTONIC_COUNTER_MSG_LENGTH, + signature_offset, + keyfile, + key_data, + &status); + if (ret != RPMC_SUCCESS) + return ret; + + if (status & (1 << 4)) { + return RPMC_ERROR_COUNTER_DATA_MISMATCH; + } else if (status & (1 << 3)) { + return RPMC_ERROR_HMAC_KEY_REGISTER_UNINITIALIZED; + } else if (status & (1 << 2)) { + // Counter address out of range or incorrect payload size received + // also possible but we check those in the code + return RPMC_ERROR_WRONG_SIGNATURE; + } else if (status != 0x80) { + // Unexpected bit is set + // This should not happen, if we wrote the code correctly + return RPMC_ERROR_INTERNAL; + } + + return RPMC_SUCCESS; +} + +enum rpmc_result rpmc_get_monotonic_counter(struct flashrom_flashctx *flash, + const char *const keyfile, + const uint32_t key_data, + const unsigned int counter_address, + uint32_t *const counter_value) +{ + unsigned char hmac_key_register[RPMC_HMAC_KEY_LENGTH]; + const unsigned int tag_offset = RPMC_OP1_MSG_HEADER_LENGTH; + const unsigned int signature_offset = tag_offset + RPMC_TAG_LENGTH; + unsigned char msg[RPMC_GET_MONOTONIC_COUNTER_MSG_LENGTH] = { + flash->chip->rpmc_ctx.op1_opcode, // Opcode + 0x03, // CmdType + counter_address, // CounterAddr + 0, // Reserved + }; + + enum rpmc_result ret = rpmc_basic_checks(flash, counter_address); + if (ret != RPMC_SUCCESS) + return ret; + + if (RAND_bytes(msg + tag_offset, RPMC_TAG_LENGTH) != 1) { + msg_gerr("Could not generate random tag.\n"); + return RPMC_ERROR_OPENSSL; + } + + msg_gdbg("Random tag is:"); + for (size_t i = 0; i < RPMC_TAG_LENGTH; i++) { + msg_gdbg(" 0x%02x", msg[tag_offset + i]); + } + msg_gdbg("\n"); + + ret = rpmc_calculate_hmac_key_register(keyfile, key_data, hmac_key_register); + if (ret != RPMC_SUCCESS) + return ret; + + if (HMAC(EVP_sha256(), + hmac_key_register, RPMC_HMAC_KEY_LENGTH, + msg, signature_offset, + msg + signature_offset, NULL) == NULL) { + msg_gerr("Could not generate HMAC signature\n"); + return RPMC_ERROR_OPENSSL; + } + + ret = rpmc_send_and_wait(flash, msg, RPMC_GET_MONOTONIC_COUNTER_MSG_LENGTH); + if (ret != RPMC_SUCCESS) + return ret; + + struct rpmc_status_register status; + ret = rpmc_get_extended_status_long(flash, &status, msg + tag_offset, hmac_key_register); + if (!(ret == RPMC_ERROR_TAG_MISMATCH || ret == RPMC_ERROR_SIGNATURE_MISMATCH || ret == RPMC_SUCCESS)) + return ret; + + if (status.status & (1 << 3)) { + return RPMC_ERROR_HMAC_KEY_REGISTER_UNINITIALIZED; + } else if (status.status & (1 << 2)) { + // Counter address out of range or incorrect payload size received + // also possible but we check those in the code + return RPMC_ERROR_WRONG_SIGNATURE; + } else if (status.status != 0x80) { + // Unexpected bit is set + // This should not happen, if we wrote the code correctly + return RPMC_ERROR_INTERNAL; + } + + *counter_value = status.counter_data; + return ret; +} + +enum rpmc_result rpmc_read_data(struct flashrom_flashctx *flash, struct rpmc_status_register *status) +{ + // hack around not having a counter address + enum rpmc_result ret = rpmc_basic_checks(flash, 0); + if (ret != RPMC_SUCCESS) + return ret; + + ret = rpmc_get_extended_status_long(flash, status, NULL, NULL); + if (ret != RPMC_SUCCESS) + return ret; + + return RPMC_SUCCESS; +} + +const char *rpmc_describe_result(const enum rpmc_result value) +{ + switch (value) { + case RPMC_SUCCESS: + return "Success\n"; + case RPMC_ERROR_SPI_TRANSMISSION: + return "Error: Sending spi command failed\n"; + case RPMC_ERROR_OPENSSL: + return "Error: Failure while calling into openssl\n"; + case RPMC_ERROR_TAG_MISMATCH: + return "Error: The recieved tag doesn't match the one that was sent\n"; + case RPMC_ERROR_SIGNATURE_MISMATCH: + return "Error: The recieved signature doesn't match the expected one\n"; + case RPMC_ERROR_INTERNAL: + return "Internal error: Unexpected state reached, please inform the maintainers\n"; + case RPMC_ERROR_KEY_READ: + return "Error: Failed to read the key from keyfile\n"; + case RPMC_ERROR_HARDENING_UNSUPPORTED: + return "Error: RPMC commands are not supported on this device\n"; + case RPMC_ERROR_COUNTER_OUT_OF_RANGE: + return "Error: Given counter address not in range for this device\n"; + case RPMC_ERROR_ROOT_KEY_OVERWRITE: + return "Error: Root key for this counter address can't be overwritten\n"; + case RPMC_ERROR_COUNTER_UNINITIALIZED: + return "Error: Root key for this counter is not initialized\n"; + case RPMC_ERROR_COUNTER_DATA_MISMATCH: + return "Error: Previous value of this counter is not correct\n"; + case RPMC_ERROR_HMAC_KEY_REGISTER_UNINITIALIZED: + return "Error: Hmac key register is not initialized\n"; + case RPMC_ERROR_WRONG_SIGNATURE: + return "Error: The signature doesn't match (root key or key data is wrong)\n"; + default: + return "Unknown internal error: Error code not recognised\n"; + } +} diff --git a/sfdp.c b/sfdp.c index 868377933..df05d973e 100644 --- a/sfdp.c +++ b/sfdp.c @@ -252,6 +252,88 @@ done: return 0; } +static unsigned int bits_to_counter_delay(const uint8_t bits) +{ + unsigned int value = bits & 0xf; + + switch ((bits & (0b11 << 4)) >> 4) { + case 0b00: + value *= 1; + break; + case 0b01: + value *= 16; + break; + case 0b10: + value *= 128; + break; + case 0b11: + value *= 1000; + break; + } + + return value; +} + +static int parse_rpmc_parameter_table(struct flashchip *const chip, const uint8_t *const buf, const uint16_t len) +{ + if (len != 2 * 4) { + msg_cdbg("Length of RPMC parameter table is wrong, skipping it\n"); + return 1; + } + + msg_cdbg("Parsing rpmc parameter table...\n"); + + // first dword + uint32_t first_dword = ((unsigned int)buf[(4 * 0) + 0]); + first_dword |= ((unsigned int)buf[(4 * 0) + 1]) << 8; + first_dword |= ((unsigned int)buf[(4 * 0) + 2]) << 16; + first_dword |= ((unsigned int)buf[(4 * 0) + 3]) << 24; + + if ((first_dword & 0b1) != 0) { + // flash hardening is not supported + msg_cdbg("Flash Hardening not supported\n"); + goto done; + } + + chip->feature_bits |= FEATURE_FLASH_HARDENING; + + chip->rpmc_ctx.busy_polling_method = (first_dword & (1 << 2)) >> 2; + msg_cspew("Busy polling method: %u\n", chip->rpmc_ctx.busy_polling_method); + + chip->rpmc_ctx.num_counters = ((first_dword & (0xf << 4)) >> 4) + 1; + msg_cspew("Number of counters: %u\n", chip->rpmc_ctx.num_counters); + + chip->rpmc_ctx.op1_opcode = (first_dword & (0xff << 8)) >> 8; + msg_cspew("OP1 opcode: 0x%02x\n", chip->rpmc_ctx.op1_opcode); + + chip->rpmc_ctx.op2_opcode = (first_dword & (0xff << 16)) >> 16; + msg_cspew("OP2 opcode: 0x%02x\n", chip->rpmc_ctx.op2_opcode); + + chip->rpmc_ctx.update_rate = 5 * (1 << ((first_dword & (0xf << 24)) >> 24)); + msg_cspew("Update rate: %u seconds\n", chip->rpmc_ctx.update_rate); + + // second dword + uint32_t second_dword = ((unsigned int)buf[(4 * 1) + 0]); + second_dword |= ((unsigned int)buf[(4 * 1) + 1]) << 8; + second_dword |= ((unsigned int)buf[(4 * 1) + 2]) << 16; + second_dword |= ((unsigned int)buf[(4 * 1) + 3]) << 24; + + chip->rpmc_ctx.polling_delay_read_counter_us = bits_to_counter_delay(second_dword & 0xf); + msg_cspew("Read counter polling delay: %u us\n", chip->rpmc_ctx.polling_delay_read_counter_us); + + chip->rpmc_ctx.polling_short_delay_write_counter_us = bits_to_counter_delay((second_dword >> 8) & 0xf); + msg_cspew("Write counter short polling delay: %u us\n", + chip->rpmc_ctx.polling_short_delay_write_counter_us); + + chip->rpmc_ctx.polling_long_delay_write_counter_us = bits_to_counter_delay((second_dword >> 16) & 0xf) * 1000; + msg_cspew("Write counter long polling delay: %u us\n", + chip->rpmc_ctx.polling_long_delay_write_counter_us); + +done: + msg_cdbg("done.\n"); + return 0; +} + int probe_spi_sfdp(struct flashctx *flash) { int ret = 0; @@ -359,23 +441,44 @@ int probe_spi_sfdp(struct flashctx *flash) } msg_cspew("\n"); - if (i == 0) { /* Mandatory JEDEC SFDP parameter table */ - if (hdrs[i].id != 0) - msg_cdbg("ID of the mandatory JEDEC SFDP " - "parameter table is not 0 as demanded " - "by JESD216 (warning only).\n"); - - if (hdrs[i].v_major != 0x01) { + if (i == 0) { + if (hdrs[i].id != 0) { + msg_cerr("ID of the mandatory JEDEC SFDP " + "parameter table is not 0 as demanded " + "by JESD216.\n"); + } else if (hdrs[i].v_major != 0x01) { msg_cdbg("The chip contains an unknown " - "version of the JEDEC flash " - "parameters table, skipping it.\n"); + "version of the JEDEC flash " + "parameters table (Version: %u.%u), skipping it.\n", + hdrs[i].v_major, hdrs[i].v_minor); } else if (len != 4 * 4 && len < 9 * 4) { msg_cdbg("Length of the mandatory JEDEC SFDP " "parameter table is wrong (%d B), " "skipping it.\n", len); - } else if (sfdp_fill_flash(flash->chip, tbuf, len) == 0) + } else if (sfdp_fill_flash(flash->chip, tbuf, len) == 0) { ret = 1; + } + } else { + /* TODO: implement parsing for other pages */ + switch (hdrs[i].id){ + case 0x03: /* RPMC parameter table as specified in JESD260 */ + if (hdrs[i].v_major != 0x01 || hdrs[i].v_minor != 0x0) { + msg_cdbg("The chip contains an unknown " + "version of the JEDEC RPMC " + "parameters table (Version: %u.%u), skipping it.\n", + hdrs[i].v_major, hdrs[i].v_minor); + } else { + parse_rpmc_parameter_table(flash->chip, tbuf, len); + } + break; + default: + msg_cdbg("Support for SFDP Page with ID 0x%02x not implemented" + ", skipping it.\n", + hdrs[i].id); + break; + } } + free(tbuf); }