1
0
mirror of https://review.coreboot.org/flashrom.git synced 2025-04-26 22:52:34 +02:00
flashrom/rpmc.c
Antonio Vázquez Blanco ce825859c4 Move SPI declarations from flash.h to spi.h
As a consecuence, some of the files that used to include flash.h no
longer need to do so. For this reason, flash.h includes are also deleted
in this commit.

Change-Id: I794a71536a3b85fde39f83c802fa0f5dd8d428e0
Signed-off-by: Antonio Vázquez Blanco <antoniovazquezblanco@gmail.com>
Reviewed-on: https://review.coreboot.org/c/flashrom/+/85539
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Anastasia Klimchuk <aklm@chromium.org>
Reviewed-by: Peter Marheine <pmarheine@chromium.org>
Reviewed-by: David Reguera Garcia (Dreg) <regueragarciadavid@gmail.com>
Reviewed-by: Matti Finder <matti.finder@gmail.com>
2025-02-21 07:17:57 +00:00

496 lines
16 KiB
C

/*
* This file is part of the flashrom project.
*
* Copyright (C) 2024 Matti Finder
* (written by Matti Finder <matti.finder@gmail.com>)
*
* 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 <stdint.h>
#include <stddef.h>
#include <unistd.h>
#include <openssl/hmac.h>
#include <openssl/evp.h>
#include <openssl/rand.h>
#include <string.h>
#include "spi.h"
// 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";
}
}