feat: verify CRC32 checksum for font files (#1904)
## Summary * **What is the goal of this PR?** Add end-to-end integrity verification for downloaded font files by including CRC32 checksums in the font manifest and validating downloaded `.cpfont` files on device. * **What changes are included?** - `generate-font-manifest.py`: compute and include `crc32` for each `.cpfont` asset in the generated `fonts.json` manifest. - `FontDownloadActivity.h`: extend manifest file metadata with `crc32` and declare checksum helper. - `FontDownloadActivity.cpp`: parse `crc32` from manifest, compute CRC32 of downloaded files using `esp_rom_crc32_le`. --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**YES**_
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
#include <I18n.h>
|
||||
#include <Logging.h>
|
||||
#include <WiFi.h>
|
||||
#include <esp_rom_crc.h>
|
||||
|
||||
#include "MappedInputManager.h"
|
||||
#include "SdCardFontGlobals.h"
|
||||
@@ -123,6 +124,14 @@ bool FontDownloadActivity::fetchAndParseManifest() {
|
||||
ManifestFile file;
|
||||
file.name = fileObj["name"] | "";
|
||||
file.size = fileObj["size"] | 0;
|
||||
|
||||
if (!fileObj["crc32"].is<uint32_t>()) {
|
||||
LOG_ERR("FONT", "Malformed manifest file entry: missing or invalid crc32 for %s", file.name.c_str());
|
||||
errorMessage_ = "Invalid font manifest";
|
||||
return false;
|
||||
}
|
||||
file.crc32 = fileObj["crc32"].as<uint32_t>();
|
||||
|
||||
family.totalSize += file.size;
|
||||
family.files.push_back(std::move(file));
|
||||
}
|
||||
@@ -181,6 +190,24 @@ size_t FontDownloadActivity::totalUninstalledSize() const {
|
||||
return total;
|
||||
}
|
||||
|
||||
// Standard CRC32 matching zlib/Python zlib.crc32().
|
||||
bool FontDownloadActivity::computeFileCrc32(const char* path, uint32_t& outCrc) {
|
||||
FsFile f;
|
||||
if (!Storage.openFileForRead("FONT", path, f)) {
|
||||
return false;
|
||||
}
|
||||
constexpr size_t BUF_SIZE = 128;
|
||||
uint8_t buf[BUF_SIZE];
|
||||
uint32_t crc = 0;
|
||||
while (f.available()) {
|
||||
const int n = f.read(buf, BUF_SIZE);
|
||||
if (n <= 0) break;
|
||||
crc = esp_rom_crc32_le(crc, buf, static_cast<uint32_t>(n));
|
||||
}
|
||||
outCrc = crc;
|
||||
return true;
|
||||
}
|
||||
|
||||
void FontDownloadActivity::downloadFamily(ManifestFamily& family) {
|
||||
{
|
||||
RenderLock lock(*this);
|
||||
@@ -233,6 +260,29 @@ void FontDownloadActivity::downloadFamily(ManifestFamily& family) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t actualCrc = 0;
|
||||
if (!computeFileCrc32(destPath, actualCrc)) {
|
||||
LOG_ERR("FONT", "Failed to open file for CRC check: %s", destPath);
|
||||
fontInstaller_.deleteFamily(family.name.c_str());
|
||||
family.installed = false;
|
||||
family.hasUpdate = false;
|
||||
RenderLock lock(*this);
|
||||
state_ = ERROR;
|
||||
errorMessage_ = "Failed to compute checksum: " + file.name;
|
||||
return;
|
||||
}
|
||||
if (actualCrc != file.crc32) {
|
||||
LOG_ERR("FONT", "CRC32 mismatch for %s: got %08x expected %08x", file.name.c_str(), actualCrc, file.crc32);
|
||||
fontInstaller_.deleteFamily(family.name.c_str());
|
||||
family.installed = false;
|
||||
family.hasUpdate = false;
|
||||
RenderLock lock(*this);
|
||||
state_ = ERROR;
|
||||
errorMessage_ = "Checksum mismatch: " + file.name;
|
||||
return;
|
||||
}
|
||||
LOG_DBG("FONT", "Downloaded %s (size=%zu crc32=%08x)", file.name.c_str(), file.size, actualCrc);
|
||||
|
||||
if (!fontInstaller_.validateCpfontFile(destPath)) {
|
||||
LOG_ERR("FONT", "Invalid .cpfont: %s", destPath);
|
||||
fontInstaller_.deleteFamily(family.name.c_str());
|
||||
|
||||
Reference in New Issue
Block a user