OTA step 1: Update File format and streaming parser (host-tested)

lib/ota: a 160-byte header (magic, format, image size and SHA-256,
version, ECDSA signature over the first 80 bytes) then the image.
UpdateParser checks the header and signature before writing anything,
hashes the image as it streams into an UpdateSink, and only finishes
the sink when the hash matches. Downgrades are flagged, not refused.
Includes a dependency-free SHA-256 and semver comparison.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
This commit is contained in:
2026-10-03 21:30:13 +02:00
co-authored by Claude Opus 5.5
parent e9efbcca4d
commit 326e864264
6 changed files with 574 additions and 0 deletions
+85
View File
@@ -0,0 +1,85 @@
#include "sha256.h"
#include <cstring>
namespace roro {
namespace {
const uint32_t K[64] = {
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2};
inline uint32_t rotr(uint32_t x, int n) { return (x >> n) | (x << (32 - n)); }
} // namespace
Sha256::Sha256() {
const uint32_t init[8] = {0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19};
std::memcpy(h_, init, sizeof(h_));
}
void Sha256::block(const uint8_t* p) {
uint32_t w[64];
for (int i = 0; i < 16; i++)
w[i] = (uint32_t(p[4 * i]) << 24) | (uint32_t(p[4 * i + 1]) << 16) | (uint32_t(p[4 * i + 2]) << 8) | p[4 * i + 3];
for (int i = 16; i < 64; i++) {
uint32_t s0 = rotr(w[i - 15], 7) ^ rotr(w[i - 15], 18) ^ (w[i - 15] >> 3);
uint32_t s1 = rotr(w[i - 2], 17) ^ rotr(w[i - 2], 19) ^ (w[i - 2] >> 10);
w[i] = w[i - 16] + s0 + w[i - 7] + s1;
}
uint32_t a = h_[0], b = h_[1], c = h_[2], d = h_[3], e = h_[4], f = h_[5], g = h_[6], h = h_[7];
for (int i = 0; i < 64; i++) {
uint32_t t1 = h + (rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25)) + ((e & f) ^ (~e & g)) + K[i] + w[i];
uint32_t t2 = (rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22)) + ((a & b) ^ (a & c) ^ (b & c));
h = g;
g = f;
f = e;
e = d + t1;
d = c;
c = b;
b = a;
a = t1 + t2;
}
h_[0] += a; h_[1] += b; h_[2] += c; h_[3] += d;
h_[4] += e; h_[5] += f; h_[6] += g; h_[7] += h;
}
void Sha256::update(const uint8_t* data, size_t len) {
bits_ += static_cast<uint64_t>(len) * 8;
while (len > 0) {
size_t take = 64 - used_ < len ? 64 - used_ : len;
std::memcpy(buf_ + used_, data, take);
used_ += take;
data += take;
len -= take;
if (used_ == 64) {
block(buf_);
used_ = 0;
}
}
}
void Sha256::finish(uint8_t out[32]) {
uint64_t bits = bits_;
uint8_t pad = 0x80;
update(&pad, 1);
uint8_t zero = 0;
while (used_ != 56) update(&zero, 1);
uint8_t len[8];
for (int i = 0; i < 8; i++) len[i] = static_cast<uint8_t>(bits >> (56 - 8 * i));
update(len, 8);
for (int i = 0; i < 8; i++) {
out[4 * i] = h_[i] >> 24;
out[4 * i + 1] = h_[i] >> 16;
out[4 * i + 2] = h_[i] >> 8;
out[4 * i + 3] = h_[i];
}
}
} // namespace roro