You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

399 lines
13 KiB

// SPDX-FileCopyrightText: 2018 Coinkite, Inc. <coldcardwallet.com>
// SPDX-License-Identifier: GPL-3.0-only
//
/*
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
* and is covered by GPLv3 license found in COPYING.
*
* update.c -- firmware update processing
*
*/
#include <string.h>
#include <stdlib.h>
#include "display.h"
#include "fwheader.h"
#include "hash.h"
#include "se-config.h"
#include "sha256.h"
#include "spiflash.h"
#include "splash.h"
#include "utils.h"
#include "se-atecc608a.h"
#include "verify.h"
#include "flash.h"
#include "update.h"
#include "ui.h"
#include "gpio.h"
#include "firmware-keys.h"
// Global so we can compare with it later in do_update()
static uint8_t spi_hdr_hash[HASH_LEN] = {0};
static void clear_update_from_spi_flash()
{
uint8_t zeros[FW_HEADER_SIZE] = {0};
spi_write(0, 256, zeros);
spi_write(256, sizeof(zeros), zeros);
}
static void calculate_spi_hash(
passport_firmware_header_t *hdr,
uint8_t *hash,
uint8_t hashlen
)
{
SHA256_CTX ctx;
uint32_t pos = FW_HEADER_SIZE + 256; // Skip over the update hash page
uint32_t remaining = hdr->info.fwlength;
uint8_t *buf = (uint8_t *)D1_AXISRAM_BASE; /* Working memory */
sha256_init(&ctx);
sha256_update(&ctx, (uint8_t *)&hdr->info, sizeof(fw_info_t));
while (remaining > 0)
{
size_t bufsize;
if (remaining >= 8192)
bufsize = 8192;
else
bufsize = remaining;
if (spi_read(pos, bufsize, buf) != HAL_OK)
goto out;
sha256_update(&ctx, buf, bufsize);
remaining -= bufsize;
pos += bufsize;
}
sha256_final(&ctx, hash);
/* double SHA256 */
sha256_init(&ctx);
sha256_update(&ctx, hash, hashlen);
sha256_final(&ctx, hash);
out:
return;
}
static void calculate_spi_hdr_hash(
passport_firmware_header_t *hdr,
uint8_t *hash,
uint8_t hashlen
)
{
SHA256_CTX ctx;
sha256_init(&ctx);
sha256_update(&ctx, (uint8_t *)hdr, sizeof(passport_firmware_header_t));
sha256_final(&ctx, hash);
/* double SHA256 */
sha256_init(&ctx);
sha256_update(&ctx, hash, hashlen);
sha256_final(&ctx, hash);
}
// Hash the spi hash with the device hash value -- used to prevent external attacker from beign able to insert a firmware
// update directly in external SPI flash. They won't be able to replicate this hash.
static void calculate_update_hash(
uint8_t *spi_hash,
uint8_t spi_hashlen,
uint8_t *update_hash,
uint8_t update_hashlen
)
{
SHA256_CTX ctx;
uint8_t device_hash[HASH_LEN];
get_device_hash(device_hash);
sha256_init(&ctx);
sha256_update(&ctx, (uint8_t *)spi_hash, spi_hashlen);
sha256_update(&ctx, device_hash, sizeof(device_hash));
sha256_final(&ctx, update_hash);
}
static int do_update(uint32_t size)
{
int rc;
uint8_t flash_word_len = sizeof(uint32_t) * FLASH_NB_32BITWORD_IN_FLASHWORD;
uint32_t pos;
uint32_t addr;
uint32_t data[FLASH_NB_32BITWORD_IN_FLASHWORD] __attribute__((aligned(8)));
uint32_t total = FW_END - FW_START;
uint8_t percent_done = 0;
uint8_t last_percent_done = 255;
uint8_t curr_spi_hdr_hash[HASH_LEN] = {0};
uint32_t remaining_bytes_to_hash = sizeof(passport_firmware_header_t);
secresult not_checked = SEC_TRUE;
SHA256_CTX ctx;
sha256_init(&ctx);
flash_unlock();
// Make sure header still fits in one page or this check will be more complex.
if (sizeof(passport_firmware_header_t) > 256) {
clear_update_from_spi_flash();
ui_show_fatal_error("sizeof(passport_firmware_header_t) > 256");
}
for (pos = 0, addr = FW_START; pos < size; pos += flash_word_len, addr += flash_word_len)
{
// We read starting 256 bytes in as the first page holds the update request hash
if (spi_read(pos + 256, sizeof(data), (uint8_t *)data) != HAL_OK)
{
rc = -1;
break;
}
// TOCTOU check by hashing the header again and comparing to the hash we took earlier when we verified it.
if (remaining_bytes_to_hash > 0) {
// Calculate the running hash 32 bytes at a time until we reach sizeof(passport_firmware_header_t)
size_t hash_size = MIN(remaining_bytes_to_hash, flash_word_len);
sha256_update(&ctx, (uint8_t *)data, hash_size);
remaining_bytes_to_hash -= hash_size;
}
if (not_checked == SEC_TRUE && remaining_bytes_to_hash == 0) {
// Finalize the hash and check it
sha256_final(&ctx, curr_spi_hdr_hash);
/* double SHA256 */
sha256_init(&ctx);
sha256_update(&ctx, curr_spi_hdr_hash, HASH_LEN);
sha256_final(&ctx, curr_spi_hdr_hash);
// ui_show_hex_buffer("Prev Hash", spi_hdr_hash, HASH_LEN);
// ui_show_hex_buffer("TOCTOU Hash", curr_spi_hdr_hash, HASH_LEN);
// Compare the hashes
if (memcmp(curr_spi_hdr_hash, spi_hdr_hash, HASH_LEN) != 0) {
// Someone may be hacking on the SPI flash!
clear_update_from_spi_flash();
ui_show_fatal_error("\nSPI flash appears to have been actively modified during firmware update.");
}
not_checked = SEC_FALSE;
}
if (addr % FLASH_SECTOR_SIZE == 0)
{
rc = flash_sector_erase(addr);
if (rc < 0)
break;
}
rc = flash_burn(addr, (uint32_t)data);
if (rc < 0)
break;
/* Update the progress bar only if the percentage changed */
percent_done = (uint8_t)((float)pos/(float)total * 100.0f);
if (percent_done != last_percent_done)
{
display_progress_bar(PROGRESS_BAR_MARGIN, PROGRESS_BAR_Y, SCREEN_WIDTH - (PROGRESS_BAR_MARGIN * 2), PROGRESS_BAR_HEIGHT, percent_done);
/* Showing just the lines that changed is much faster and avoids full-screen flicker */
display_show_lines(PROGRESS_BAR_Y, PROGRESS_BAR_Y + PROGRESS_BAR_HEIGHT);
last_percent_done = percent_done;
}
}
/* Clear the remainder of flash */
memset(data, 0, sizeof(data));
for (; addr < FW_END; pos += flash_word_len, addr += flash_word_len)
{
if (addr % FLASH_SECTOR_SIZE == 0)
{
rc = flash_sector_erase(addr);
if (rc < 0)
break;
}
rc = flash_burn(addr, (uint32_t)data);
if (rc < 0)
break;
/* Update the progress bar only if the percentage changed */
percent_done = (uint8_t)((float)pos/(float)total * 100.0f);
if (percent_done != last_percent_done)
{
display_progress_bar(PROGRESS_BAR_MARGIN, PROGRESS_BAR_Y, SCREEN_WIDTH - (PROGRESS_BAR_MARGIN * 2), PROGRESS_BAR_HEIGHT, percent_done);
/* Showing just the lines that changed is much faster and avoids full-screen flicker */
display_show_lines(PROGRESS_BAR_Y, PROGRESS_BAR_Y + PROGRESS_BAR_HEIGHT);
last_percent_done = percent_done;
}
}
/* Make sure the progress bar goes to 100 */
display_progress_bar(PROGRESS_BAR_MARGIN, PROGRESS_BAR_Y, SCREEN_WIDTH - (PROGRESS_BAR_MARGIN * 2), PROGRESS_BAR_HEIGHT, 100);
display_show_lines(PROGRESS_BAR_Y, PROGRESS_BAR_Y + PROGRESS_BAR_HEIGHT);
flash_lock();
return rc;
}
secresult is_firmware_update_present(void)
{
passport_firmware_header_t hdr = {};
if (spi_setup() != HAL_OK)
return SEC_FALSE;
// Skip first page of flash
if (spi_read(256, sizeof(hdr), (void *)&hdr) != HAL_OK)
return SEC_FALSE;
if (!verify_header(&hdr))
return SEC_FALSE;
return SEC_TRUE;
}
void update_firmware(void)
{
int rc;
passport_firmware_header_t *internalhdr = FW_HDR;
passport_firmware_header_t spihdr = {0};
uint8_t internal_fw_hash[HASH_LEN] = {0};
uint8_t spi_fw_hash[HASH_LEN] = {0};
uint8_t current_board_hash[HASH_LEN] = {0};
uint8_t new_board_hash[HASH_LEN] = {0};
uint8_t actual_update_hash[HASH_LEN] = {0};
uint8_t expected_update_hash[HASH_LEN] = {0};
/*
* If we fail to either setup the SPI bus or read the SPI flash
* then just return...something is wrong in hardware but maybe it's
* temporary.
*/
if (spi_setup() != HAL_OK)
return;
// If the update was requested by the user, then there will be a hash in the first 32 bytes that combines
// the firmware hash with the device hash.
if (spi_read(0, HASH_LEN, (void *)&actual_update_hash) != HAL_OK)
return;
// Start reading one page in as there is a 32-byte hash in the first page
if (spi_read(256, sizeof(spihdr), (void *)&spihdr) != HAL_OK)
return;
// ui_show_hex_buffer("SPI Hdr 1", (uint8_t*)&spihdr, 170);
calculate_spi_hdr_hash(&spihdr, spi_hdr_hash, HASH_LEN);
// ui_show_hex_buffer("SPI Hdr Hash", spi_hdr_hash, HASH_LEN);
calculate_update_hash(spi_hdr_hash, sizeof(spi_hdr_hash), expected_update_hash, sizeof(expected_update_hash));
// Ensure that the hashes match!
if (memcmp(expected_update_hash, actual_update_hash, sizeof(expected_update_hash)) != 0) {
// This looks like an unrequested update (i.e., a possible attack)
goto out;
}
/* Verify firmware header in SPI flash and bail if it fails */
if (!verify_header(&spihdr))
{
if (ui_show_message("Update Error", "The firmware update you chose has an invalid header and will not be installed.", "SHUTDOWN", "OK", true)){
goto out;
} else {
display_clean_shutdown();
}
}
/*
* If the current firmeware verification passes then compare
* timestamps and don't allow an earlier version. However, if the
* internal firmware header verification fails then proceed with the
* update...maybe the previous update attempt failed because we lost
* power.
*
* We also allow going back and forth between user-signed firmware and Foundation-signed firmware.
*/
if (verify_current_firmware(true) == SEC_TRUE)
{
if ((spihdr.signature.pubkey1 != FW_USER_KEY && internalhdr->signature.pubkey1 != FW_USER_KEY) &&
(spihdr.info.timestamp < internalhdr->info.timestamp))
{
if (ui_show_message("Update Error", "This firmware update is older than the current firmware and will not be installed.", "SHUTDOWN", "OK", true))
goto out;
else
display_clean_shutdown();
}
// Handle the firmware hash update
uint8_t *fwptr = (uint8_t *)internalhdr + FW_HEADER_SIZE;
hash_fw(&internalhdr->info, fwptr, internalhdr->info.fwlength, internal_fw_hash, sizeof(internal_fw_hash));
hash_board(internal_fw_hash, sizeof(internal_fw_hash), current_board_hash, sizeof(current_board_hash));
calculate_spi_hash(&spihdr, spi_fw_hash, sizeof(spi_fw_hash));
/* Verify the signature and bail if it fails */
if (verify_signature(&spihdr, spi_fw_hash, sizeof(spi_fw_hash)) == SEC_FALSE)
{
if (ui_show_message("Update Error", "The firmware update does not appear to be properly signed and will not be installed.\n\nThis can also occur if you lost power during a firmware update.", "SHUTDOWN", "OK", true))
goto out;
else
display_clean_shutdown();
}
/*
* Calculate a new board hash based on the SPI firmware and then
* reprogram the board hash in the SE. If the update fails it
* will be retried until it succeeds or the board is declared dead.
*/
hash_board(spi_fw_hash, sizeof(spi_fw_hash), new_board_hash, sizeof(new_board_hash));
#ifdef CONVERSION_BUILD
/*
* Conversion build is temporary and used to get current demo
* boards which have 0's programmed for the board hash to be
* properly programmed with a real board hash. Thereafter they
* will only be able to update via SD card.
* Delete this code once this has been done.
*/
memset(current_board_hash, 0, sizeof(current_board_hash));
#endif /* CONVERSION_BUILD */
rc = se_program_board_hash(current_board_hash, new_board_hash, sizeof(new_board_hash));
if (rc < 0) {
if (ui_show_message("Update Error", "Unable to update the firmware hash in the Secure Element. Update will continue, but may not be successful.", "SHUTDOWN", "OK", true)){
// Nothing to do
} else {
display_clean_shutdown();
}
}
}
// Draw the logo and message - progress bar gets drawn and updated periodically in do_update()
show_splash("Updating Firmware...");
rc = do_update(FW_HEADER_SIZE + spihdr.info.fwlength);
if (rc < 0)
{
if (ui_show_message("Update Error", "Failed to install the firmware update.", "SHUTDOWN", "RESTART", true))
passport_reset();
else
// TODO: Should we have an option here to clear the SPI flash and restart (we could run a verify_current_firmware() first to make sure it's safe to boot there
display_clean_shutdown();
}
out:
clear_update_from_spi_flash();
}
secresult is_user_signed_firmware_installed(void)
{
passport_firmware_header_t *hdr = FW_HDR;
return (hdr->signature.pubkey1 == FW_USER_KEY && hdr->signature.pubkey2 == 0) ? SEC_TRUE : SEC_FALSE;
}