File syn_fwupdate.c¶
File List > src > syntropic > system > syn_fwupdate.c
Go to the documentation of this file
#if __has_include("syn_config.h")
#include "syn_config.h"
#endif
#if !defined(SYN_USE_BOOT) || SYN_USE_BOOT
#include "../port/syn_port_flash.h"
#include "../util/syn_assert.h"
#include "../util/syn_crc.h"
#include "syn_fwimage.h"
#include "syn_fwupdate.h"
#if defined(SYN_FW_USE_ED25519) && SYN_FW_USE_ED25519
#include "../crypto/syn_ed25519.h"
#endif
#include <string.h>
/* ── Helpers ────────────────────────────────────────────────────────────── */
static SYN_Status flush_page(SYN_FwUpdate *upd)
{
if (upd->page_buf_used == 0)
return SYN_OK;
uint32_t addr = upd->data_addr + upd->bytes_written;
/* Check if we need to erase a new sector */
uint32_t sector_size = syn_port_flash_sector_size(addr);
if (sector_size > 0) {
uint32_t sector_start = addr - (addr % sector_size);
/* LCOV_EXCL_START: Hardware flash sector boundary erase */
if (addr == sector_start && upd->bytes_written > 0) {
SYN_Status st = syn_port_flash_erase(sector_start);
if (st != SYN_OK)
return st;
}
/* LCOV_EXCL_STOP */
}
/* Write the buffered data */
SYN_Status st = syn_port_flash_write(addr, upd->page_buf, upd->page_buf_used);
if (st != SYN_OK)
return st;
upd->bytes_written += upd->page_buf_used;
upd->page_buf_used = 0;
return SYN_OK;
}
/* ── API ────────────────────────────────────────────────────────────────── */
SYN_Status syn_fwupdate_begin(SYN_FwUpdate *upd, uint32_t slot_addr, uint32_t max_size,
uint8_t *page_buf, uint16_t page_buf_size)
{
SYN_ASSERT(upd != NULL);
SYN_ASSERT(page_buf != NULL);
SYN_ASSERT(page_buf_size > 0);
if (upd == NULL || page_buf == NULL || page_buf_size == 0) {
return SYN_INVALID_PARAM; /* LCOV_EXCL_LINE: Defensive NULL check after SYN_ASSERT macro in
release mode */
}
memset(upd, 0, sizeof(*upd));
upd->slot_addr = slot_addr;
upd->data_addr = slot_addr + (uint32_t)sizeof(SYN_FwImageHeader);
upd->max_size = max_size;
upd->page_buf = page_buf;
upd->page_buf_size = page_buf_size;
upd->crc_state = SYN_CRC32_INIT;
upd->active = true;
#if defined(SYN_FW_USE_HMAC) && SYN_FW_USE_HMAC
/* HMAC is initialized lazily in set_key(); key_set starts false
* because memset above zeroed the struct. */
#endif
/* Erase the first sector (contains the header area) */
SYN_Status st = syn_port_flash_erase(slot_addr);
if (st != SYN_OK) {
upd->error = true;
upd->active = false;
return st;
}
return SYN_OK;
}
SYN_Status syn_fwupdate_write(SYN_FwUpdate *upd, const uint8_t *data, size_t len)
{
SYN_ASSERT(upd != NULL);
if (!upd->active || upd->error)
return SYN_ERROR;
if (data == NULL || len == 0)
return SYN_OK;
/* Check size limit */
if (upd->bytes_written + upd->page_buf_used + len > upd->max_size) {
upd->error = true;
return SYN_ERROR;
}
/* Update running CRC */
#if defined(SYN_FW_USE_AES_GCM) && SYN_FW_USE_AES_GCM
if (upd->gcm_key_set) {
uint8_t dec_chunk[256];
size_t ct_offset = 0;
while (ct_offset < len) {
size_t chunk_len = len - ct_offset;
if (chunk_len > sizeof(dec_chunk)) {
chunk_len = sizeof(dec_chunk);
}
for (size_t i = 0; i < chunk_len; i++) {
uint8_t ct_byte = data[ct_offset + i];
/* GHASH block accumulation */
upd->gcm_partial_ct[upd->gcm_partial_used++] = ct_byte;
if (upd->gcm_partial_used == 16U) {
for (int j = 0; j < 16; j++) {
upd->gcm_s[j] ^= upd->gcm_partial_ct[j];
}
syn_aes_ghash_mult(upd->gcm_s, upd->gcm_h, upd->gcm_s);
upd->gcm_partial_used = 0;
}
/* CTR keystream */
if (upd->gcm_stream_used == 0 || upd->gcm_stream_used == 16U) {
syn_aes_encrypt_block(&upd->gcm_aes, upd->gcm_ctr, upd->gcm_stream_buf);
for (int j = 15; j >= 12; j--) {
upd->gcm_ctr[j]++;
if (upd->gcm_ctr[j] != 0U) {
break;
}
}
upd->gcm_stream_used = 0;
}
dec_chunk[i] = (uint8_t)(ct_byte ^ upd->gcm_stream_buf[upd->gcm_stream_used++]);
upd->gcm_total_bytes++;
}
/* Update CRC and HMAC on decrypted plaintext */
upd->crc_state = syn_crc32_update(upd->crc_state, dec_chunk, chunk_len);
#if defined(SYN_FW_USE_HMAC) && SYN_FW_USE_HMAC
if (upd->key_set) {
syn_hmac_sha256_update(&upd->hmac_ctx, dec_chunk, chunk_len);
}
#endif
/* Write decrypted plaintext into page buffer */
size_t p_offset = 0;
while (p_offset < chunk_len) {
size_t space = (size_t)(upd->page_buf_size - upd->page_buf_used);
size_t to_copy = chunk_len - p_offset;
if (to_copy > space)
to_copy = space;
memcpy(upd->page_buf + upd->page_buf_used, dec_chunk + p_offset, to_copy);
upd->page_buf_used += (uint16_t)to_copy;
p_offset += to_copy;
if (upd->page_buf_used >= upd->page_buf_size) {
SYN_Status st = flush_page(upd);
if (st != SYN_OK) {
upd->error = true;
return st;
}
}
}
ct_offset += chunk_len;
}
return SYN_OK;
}
#endif
upd->crc_state = syn_crc32_update(upd->crc_state, data, len);
#if defined(SYN_FW_USE_HMAC) && SYN_FW_USE_HMAC
/* Update running HMAC if key was provided */
if (upd->key_set) {
syn_hmac_sha256_update(&upd->hmac_ctx, data, len);
}
#endif
/* Buffer data and flush full pages */
size_t offset = 0;
while (offset < len) {
size_t space = (size_t)(upd->page_buf_size - upd->page_buf_used);
size_t to_copy = len - offset;
if (to_copy > space)
to_copy = space;
memcpy(upd->page_buf + upd->page_buf_used, data + offset, to_copy);
upd->page_buf_used += (uint16_t)to_copy;
offset += to_copy;
/* Flush if buffer is full */
if (upd->page_buf_used >= upd->page_buf_size) {
SYN_Status st = flush_page(upd);
if (st != SYN_OK) {
upd->error = true;
return st;
}
}
}
return SYN_OK;
}
SYN_Status syn_fwupdate_finish(SYN_FwUpdate *upd, uint32_t expected_crc,
#if defined(SYN_FW_USE_HMAC) && SYN_FW_USE_HMAC
const uint8_t *expected_hmac,
#endif
#if defined(SYN_FW_USE_ED25519) && SYN_FW_USE_ED25519
const uint8_t *expected_sig,
#endif
uint32_t version_code)
{
SYN_ASSERT(upd != NULL);
if (!upd->active || upd->error)
return SYN_ERROR;
/* Flush remaining data */
SYN_Status st = flush_page(upd);
if (st != SYN_OK) {
upd->error = true;
return st;
}
/* Finalize CRC */
uint32_t computed_crc = syn_crc32_final(upd->crc_state);
#if defined(SYN_FW_USE_HMAC) && SYN_FW_USE_HMAC
/* Finalize HMAC before CRC check so the digest is available for
* both verification and storage in the header. */
uint8_t computed_hmac[32] = {0};
if (upd->key_set) {
syn_hmac_sha256_final(&upd->hmac_ctx, computed_hmac);
}
#endif
if (computed_crc != expected_crc) {
/* CRC mismatch — mark slot invalid */
syn_fwupdate_abort(upd);
return SYN_ERROR;
}
#if defined(SYN_FW_USE_HMAC) && SYN_FW_USE_HMAC
/* Verify HMAC if caller provided an expected value */
if (expected_hmac != NULL && upd->key_set) {
/* Constant-time compare to prevent timing side channels */
uint8_t diff = 0;
size_t i;
for (i = 0; i < 32; i++) {
diff |= computed_hmac[i] ^ expected_hmac[i];
}
if (diff != 0) {
syn_fwupdate_abort(upd);
return SYN_ERROR;
}
}
#endif
#if defined(SYN_FW_USE_ED25519) && SYN_FW_USE_ED25519
/* Verify Ed25519 digital signature if public key was set and expected signature provided */
if (expected_sig != NULL && upd->pubkey_set) {
SYN_FwImageHeader test_hdr;
memset(&test_hdr, 0, sizeof(test_hdr));
test_hdr.magic = SYN_FW_MAGIC;
test_hdr.version_code = version_code;
test_hdr.image_size = upd->bytes_written;
test_hdr.image_crc = computed_crc;
test_hdr.state = SYN_FW_STATE_NEW;
memcpy(test_hdr.image_sig, expected_sig, 64);
syn_fwimage_seal_header(&test_hdr);
if (!syn_fwimage_verify_signature(&test_hdr, upd->slot_addr, upd->public_key)) {
syn_fwupdate_abort(upd);
return SYN_ERROR;
}
}
#endif
/* Write the final header with state = NEW */
SYN_FwImageHeader hdr;
memset(&hdr, 0, sizeof(hdr));
hdr.magic = SYN_FW_MAGIC;
hdr.version_code = version_code;
hdr.image_size = upd->bytes_written;
hdr.image_crc = computed_crc;
hdr.state = SYN_FW_STATE_NEW;
#if defined(SYN_FW_USE_HMAC) && SYN_FW_USE_HMAC
/* Store HMAC in header if key was set */
if (upd->key_set) {
memcpy(hdr.image_hmac, computed_hmac, 32);
}
#endif
#if defined(SYN_FW_USE_ED25519) && SYN_FW_USE_ED25519
/* Store Ed25519 signature in header if provided */
if (expected_sig != NULL && upd->pubkey_set) {
memcpy(hdr.image_sig, expected_sig, 64);
}
#endif
syn_fwimage_seal_header(&hdr);
/* Write the final header directly into the unprogrammed slot header area */
st = syn_port_flash_write(upd->slot_addr, &hdr, sizeof(hdr));
if (st != SYN_OK) {
upd->error = true;
syn_fwupdate_abort(upd);
return st;
}
upd->active = false;
return SYN_OK;
}
void syn_fwupdate_abort(SYN_FwUpdate *upd)
{
SYN_ASSERT(upd != NULL);
if (!upd->active)
return;
/* Write an INVALID header */
SYN_FwImageHeader hdr;
memset(&hdr, 0, sizeof(hdr));
hdr.magic = SYN_FW_MAGIC;
hdr.state = SYN_FW_STATE_INVALID;
syn_fwimage_seal_header(&hdr);
/* Best-effort — ignore errors during abort */
syn_port_flash_erase(upd->slot_addr);
syn_port_flash_write(upd->slot_addr, &hdr, sizeof(hdr));
upd->active = false;
upd->error = true;
}
#if defined(SYN_FW_USE_HMAC) && SYN_FW_USE_HMAC
void syn_fwupdate_set_key(SYN_FwUpdate *upd, const void *key, size_t key_len)
{
SYN_ASSERT(upd != NULL);
SYN_ASSERT(key != NULL);
syn_hmac_sha256_init(&upd->hmac_ctx, key, key_len);
upd->key_set = true;
}
#endif /* SYN_FW_USE_HMAC */
#if defined(SYN_FW_USE_ED25519) && SYN_FW_USE_ED25519
void syn_fwupdate_set_public_key(SYN_FwUpdate *upd, const uint8_t *public_key)
{
SYN_ASSERT(upd != NULL);
SYN_ASSERT(public_key != NULL);
memcpy(upd->public_key, public_key, 32);
upd->pubkey_set = true;
}
bool syn_fwimage_verify_signature(const SYN_FwImageHeader *hdr, uint32_t slot_addr,
const uint8_t *public_key)
{
if (hdr == NULL || public_key == NULL || !syn_fwimage_header_valid(hdr)) {
return false;
}
uint32_t data_addr = slot_addr + (uint32_t)sizeof(SYN_FwImageHeader);
uint32_t remaining = hdr->image_size;
SYN_SHA512_Ctx hash_ctx;
syn_sha512_init(&hash_ctx);
syn_sha512_update(&hash_ctx, hdr->image_sig, 32U);
syn_sha512_update(&hash_ctx, public_key, 32U);
uint8_t chunk[256];
uint32_t offset = 0;
while (remaining > 0) {
uint32_t to_read =
(remaining > (uint32_t)sizeof(chunk)) ? (uint32_t)sizeof(chunk) : remaining;
SYN_Status st = syn_port_flash_read(data_addr + offset, chunk, to_read);
if (st != SYN_OK) {
return false;
}
syn_sha512_update(&hash_ctx, chunk, to_read);
offset += to_read;
remaining -= to_read;
}
uint8_t h[64];
syn_sha512_final(&hash_ctx, h);
return syn_ed25519_verify_hash(hdr->image_sig, h, public_key);
}
#endif /* SYN_FW_USE_ED25519 */
#if defined(SYN_FW_USE_AES_GCM) && SYN_FW_USE_AES_GCM
SYN_Status syn_fwupdate_set_aes_gcm_key(SYN_FwUpdate *upd, const uint8_t *key, size_t key_len,
const uint8_t *iv, size_t iv_len)
{
if (upd == NULL || key == NULL || iv == NULL || iv_len != 12U) {
return SYN_INVALID_PARAM;
}
if (key_len != 16U && key_len != 24U && key_len != 32U) {
return SYN_INVALID_PARAM;
}
(void)syn_aes_init(&upd->gcm_aes, key, key_len);
/* Compute H = AES_K(0^128) */
uint8_t zero[16] = {0};
syn_aes_encrypt_block(&upd->gcm_aes, zero, upd->gcm_h);
/* Construct J0 = IV || 0x00000001 */
memcpy(upd->gcm_j0, iv, 12);
upd->gcm_j0[12] = 0x00;
upd->gcm_j0[13] = 0x00;
upd->gcm_j0[14] = 0x00;
upd->gcm_j0[15] = 0x01;
/* CTR counter starts at J0 + 1 */
memcpy(upd->gcm_ctr, upd->gcm_j0, 16);
upd->gcm_ctr[15] = 0x02;
memset(upd->gcm_s, 0, 16);
memset(upd->gcm_partial_ct, 0, 16);
upd->gcm_partial_used = 0;
memset(upd->gcm_stream_buf, 0, 16);
upd->gcm_stream_used = 0;
upd->gcm_total_bytes = 0;
upd->gcm_key_set = true;
return SYN_OK;
}
SYN_Status syn_fwupdate_finish_gcm(SYN_FwUpdate *upd, const uint8_t expected_tag[16],
uint32_t version_code)
{
if (upd == NULL || expected_tag == NULL || !upd->active || upd->error || !upd->gcm_key_set) {
return SYN_ERROR;
}
/* Flush remaining page buffer to flash */
SYN_Status st = flush_page(upd);
if (st != SYN_OK) {
upd->error = true;
return st;
}
/* Finalize partial GHASH block if any */
if (upd->gcm_partial_used > 0) {
for (uint8_t i = upd->gcm_partial_used; i < 16U; i++) {
upd->gcm_partial_ct[i] = 0;
}
for (int j = 0; j < 16; j++) {
upd->gcm_s[j] ^= upd->gcm_partial_ct[j];
}
syn_aes_ghash_mult(upd->gcm_s, upd->gcm_h, upd->gcm_s);
upd->gcm_partial_used = 0;
}
/* Length block: [len(AAD)*8 = 0 (64-bit)] || [len(CT)*8 (64-bit)] */
uint8_t len_block[16] = {0};
uint64_t ct_bits = upd->gcm_total_bytes * 8ULL;
for (int i = 0; i < 8; i++) {
len_block[15 - i] = (uint8_t)((ct_bits >> (i * 8)) & 0xFF);
}
for (int j = 0; j < 16; j++) {
upd->gcm_s[j] ^= len_block[j];
}
syn_aes_ghash_mult(upd->gcm_s, upd->gcm_h, upd->gcm_s);
/* Encrypt J0 for tag mask */
uint8_t tag_mask[16];
syn_aes_encrypt_block(&upd->gcm_aes, upd->gcm_j0, tag_mask);
uint8_t computed_tag[16];
uint8_t diff = 0;
for (int j = 0; j < 16; j++) {
computed_tag[j] = (uint8_t)(upd->gcm_s[j] ^ tag_mask[j]);
diff |= (uint8_t)(computed_tag[j] ^ expected_tag[j]);
}
if (diff != 0) {
syn_fwupdate_abort(upd);
return SYN_ERROR;
}
/* Write final image header */
SYN_FwImageHeader hdr;
memset(&hdr, 0, sizeof(hdr));
hdr.magic = SYN_FW_MAGIC;
hdr.version_code = version_code;
hdr.image_size = upd->bytes_written;
hdr.image_crc = syn_crc32_final(upd->crc_state);
hdr.state = SYN_FW_STATE_NEW;
syn_fwimage_seal_header(&hdr);
st = syn_port_flash_write(upd->slot_addr, &hdr, sizeof(hdr));
if (st != SYN_OK) {
upd->error = true;
return st;
}
upd->active = false;
return SYN_OK;
}
#endif /* SYN_FW_USE_AES_GCM */
#endif /* SYN_USE_BOOT */