Skip to content

feat(ota): add firmware digest verification (MD5/HMAC-SHA256) - #17

Merged
heshaoqiong-tuya merged 1 commit into
masterfrom
feat/ota-firmware-digest-verification
Aug 20, 2026
Merged

feat(ota): add firmware digest verification (MD5/HMAC-SHA256)#17
heshaoqiong-tuya merged 1 commit into
masterfrom
feat/ota-firmware-digest-verification

Conversation

@sedawwk

@sedawwk sedawwk commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

Add streaming verifier API (iot_ota_verify_init/update/finish/abort) that validates downloaded OTA firmware against the cloud-provided md5/hmac digest. The cloud already returns these fields via iot_ota_check_upgrade(), but nothing validated the downloaded bytes against them.

Algorithm matches TuyaOpen's tuya_ota.c: when hmac is present, the expected value is HMAC-SHA256(device secret_key,
UPPERCASE_hex(SHA-256(image))) — the HMAC message is the 64-char uppercase hex string of the SHA-256 digest (matching TuyaOpen's hex2str). When only md5 is present, falls back to plain MD5. Comparison is case-insensitive with constant-time hex compare.

New error code OPRT_OTA_VERIFY_FAILED (-0x000D) — skips -0x0008..-0x000C already used by iot_dp.h. Both OTA demos (ESP-IDF + POSIX) now verify the digest before completing the upgrade. Unit tests with Python-generated known-answer vectors cover match/mismatch/streaming/edge cases.

Verified end-to-end against a real Tuya cloud OTA task (firmware v2.2.8, 3254320 bytes): hmac(sec_key, SHA-256_hex_UPPER) matches the cloud value.

@heshaoqiong-tuya
heshaoqiong-tuya force-pushed the feat/ota-firmware-digest-verification branch from e242ac1 to 81096df Compare August 20, 2026 09:41
Add streaming verifier API (iot_ota_verify_init/update/finish/abort) that
validates downloaded OTA firmware against the cloud-provided md5/hmac
digest. The cloud already returns these fields via
iot_ota_check_upgrade(), but nothing validated the downloaded bytes
against them.

Algorithm matches TuyaOpen's tuya_ota.c: when hmac is present, the
expected value is HMAC-SHA256(device secret_key,
UPPERCASE_hex(SHA-256(image))) — the HMAC message is the 64-char
uppercase hex string of the SHA-256 digest (matching TuyaOpen's hex2str).
When only md5 is present, falls back to plain MD5. Comparison is
case-insensitive with constant-time hex compare.

Digest selection treats an empty string as absent rather than as a
malformed digest: the cloud sends `"hmac": ""` for an algorithm it has
not configured, so keying off non-NULL alone would make init return
OPRT_INVALID_PARAMETER and leave the md5 branch unreachable, failing the
upgrade outright on any device configured with md5 only. A non-empty
digest of the wrong length stays a hard error — a malformed hmac must
never silently downgrade to the weaker md5.

New error code OPRT_OTA_VERIFY_FAILED (-0x000D) — skips -0x0008..-0x000C
already used by iot_dp.h. init does not write *ctx_out on any error path,
so the skip path for OPRT_NOT_SUPPORTED is `if (ctx != NULL)` around both
update and finish; finish(NULL) is a parameter error, not "skip". The
guide spells this out because getting it wrong fails open: an application
that drops init's return value flashes and boots an unverified image.

Both OTA demos verify before completing the upgrade — the ESP-IDF demo
checks the digest before esp_ota_set_boot_partition, the POSIX demo
re-reads the downloaded file and checks ferror() so an I/O error is not
reported as a digest mismatch.

Unit tests with Python-generated known-answer vectors cover match,
mismatch, streamed chunking, uppercase expected, md5 fallback, empty and
malformed digests, NULL guards and abort (iot_ota_verify_test 12/12).
iot_ota_test 9/9, posix examples build clean.

Verified end-to-end against a real Tuya cloud OTA task (firmware v2.2.8,
3254320 bytes): hmac(sec_key, SHA-256_hex_UPPER) matches the cloud value.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@heshaoqiong-tuya
heshaoqiong-tuya force-pushed the feat/ota-firmware-digest-verification branch from 81096df to e09d502 Compare August 20, 2026 09:46
@heshaoqiong-tuya
heshaoqiong-tuya merged commit b34df2f into master Aug 20, 2026
4 checks passed
@sedawwk
sedawwk deleted the feat/ota-firmware-digest-verification branch August 20, 2026 11:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants