Skip to content

Commit e1a5941

Browse files
committed
Implement OTA checksum verification
Add capability to verify the checksum of the OTA file while downloading it.
1 parent 09a4469 commit e1a5941

File tree

2 files changed

+50
-5
lines changed

2 files changed

+50
-5
lines changed

matter_server/server/ota/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
"softwareVersionString": "2.0",
1212
"cdVersionNumber": 1,
1313
"softwareVersionValid": True,
14+
"otaChecksum": "7qcyvg2kPmKZaDLIk8C7Vyteqf4DI73x0tFZkmPALCo=",
15+
"otaChecksumType": 1,
1416
"minApplicableSoftwareVersion": 1,
1517
"maxApplicableSoftwareVersion": 1,
1618
"otaUrl": "https://github.com/agners/matter-linux-example-apps/releases/download/v1.3.0.0/chip-ota-requestor-app-x86-64.ota",

matter_server/server/ota/provider.py

+48-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
"""Handling Matter OTA provider."""
22

33
import asyncio
4+
from base64 import b64encode
45
from dataclasses import asdict, dataclass
56
import functools
7+
import hashlib
68
import json
79
import logging
810
from pathlib import Path
@@ -12,6 +14,7 @@
1214

1315
from aiohttp import ClientError, ClientSession
1416

17+
from matter_server.common.errors import UpdateError
1518
from matter_server.common.helpers.util import dataclass_from_dict
1619

1720
if TYPE_CHECKING:
@@ -21,6 +24,22 @@
2124

2225
DEFAULT_UPDATES_PATH: Final[Path] = Path("updates")
2326

27+
# From Matter SDK src/app/ota_image_tool.py
28+
CHECHKSUM_TYPES: Final[dict[int, str]] = {
29+
1: "sha256",
30+
2: "sha256_128",
31+
3: "sha256_120",
32+
4: "sha256_96",
33+
5: "sha256_64",
34+
6: "sha256_32",
35+
7: "sha384",
36+
8: "sha512",
37+
9: "sha3_224",
38+
10: "sha3_256",
39+
11: "sha3_384",
40+
12: "sha3_512",
41+
}
42+
2443

2544
@dataclass
2645
class DeviceSoftwareVersionModel: # pylint: disable=C0103
@@ -231,11 +250,22 @@ async def download_update(self, update_desc: dict) -> None:
231250
)
232251

233252
file_path = DEFAULT_UPDATES_PATH / file_name
234-
if await loop.run_in_executor(None, file_path.exists):
235-
LOGGER.info("File '%s' exists already, skipping download.", file_name)
236-
return
237253

238254
try:
255+
checksum_alg = None
256+
if (
257+
"otaChecksum" in update_desc
258+
and "otaChecksumType" in update_desc
259+
and update_desc["otaChecksumType"] in CHECHKSUM_TYPES
260+
):
261+
checksum_alg = hashlib.new(
262+
CHECHKSUM_TYPES[update_desc["otaChecksumType"]]
263+
)
264+
else:
265+
LOGGER.warning(
266+
"No OTA checksum type or not supported, OTA will not be checked."
267+
)
268+
239269
async with ClientSession(raise_for_status=True) as session:
240270
# fetch the paa certificates list
241271
LOGGER.debug("Download update from '%s'.", url)
@@ -246,8 +276,21 @@ async def download_update(self, update_desc: dict) -> None:
246276
if not chunk:
247277
break
248278
await loop.run_in_executor(None, f.write, chunk)
249-
250-
# TODO: Check against otaChecksum
279+
if checksum_alg:
280+
checksum_alg.update(chunk)
281+
282+
# Download finished, check checksum if necessary
283+
if checksum_alg:
284+
checksum = b64encode(checksum_alg.digest()).decode("ascii")
285+
if checksum != update_desc["otaChecksum"]:
286+
LOGGER.error(
287+
"Checksum mismatch for file '%s', expected: %s, got: %s",
288+
file_name,
289+
update_desc["otaChecksum"],
290+
checksum,
291+
)
292+
await loop.run_in_executor(None, file_path.unlink)
293+
raise UpdateError("Checksum mismatch!")
251294

252295
LOGGER.info(
253296
"File '%s' downloaded to '%s'", file_name, DEFAULT_UPDATES_PATH

0 commit comments

Comments
 (0)