diff --git a/tools/__init__.py b/tools/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tools/flash_device.py b/tools/flash_device.py index f6fee27..bc4724b 100755 --- a/tools/flash_device.py +++ b/tools/flash_device.py @@ -28,6 +28,25 @@ NVS_NAMESPACE = "doorcounter" NVS_PARTITION_OFFSET = "0x9000" NVS_PARTITION_SIZE = "0x5000" # matches firmware partition table (20KB) +# Characters that would change the field/row structure of the NVS-CSV format +# (key,type,encoding,value). A value containing any of these would either +# split into more fields or add rows, silently provisioning the wrong keys. +_CSV_FORBIDDEN = (",", '"', "\n", "\r") + + +def _reject_csv_metacharacters(name, value): + """Exit with an error if value contains a character that would corrupt + the NVS CSV. Used for operator-supplied strings (device id, location id, + WiFi credentials).""" + for c in _CSV_FORBIDDEN: + if c in value: + print( + f"Error: --{name} contains forbidden character {c!r}; " + f"this would corrupt the NVS partition CSV.", + file=sys.stderr, + ) + sys.exit(1) + def build_nvs_csv(device_id, location_id, hmac_secret, wifi_ssid=None, wifi_pass=None, line_offset=50): @@ -78,6 +97,13 @@ def main(): print("Error: --line-offset must be 0-100", file=sys.stderr) sys.exit(1) + _reject_csv_metacharacters("device-id", args.device_id) + _reject_csv_metacharacters("location-id", args.location_id) + if args.wifi_ssid is not None: + _reject_csv_metacharacters("wifi-ssid", args.wifi_ssid) + if args.wifi_password is not None: + _reject_csv_metacharacters("wifi-password", args.wifi_password) + with tempfile.TemporaryDirectory() as tmp: csv_path = os.path.join(tmp, "nvs.csv") bin_path = os.path.join(tmp, "nvs.bin") diff --git a/tools/test_flash_device.py b/tools/test_flash_device.py new file mode 100644 index 0000000..45e29e2 --- /dev/null +++ b/tools/test_flash_device.py @@ -0,0 +1,17 @@ +import pytest + +from tools.flash_device import _reject_csv_metacharacters + + +def test_clean_value_accepted(): + """A value with no metacharacters should pass without exiting.""" + _reject_csv_metacharacters("device-id", "dc-0042") + _reject_csv_metacharacters("location-id", "retailer-123") + _reject_csv_metacharacters("wifi-ssid", "StoreWiFi-2.4GHz") + _reject_csv_metacharacters("wifi-password", "p@ssw0rd!~#$%^&*()_+-=:;<>?/") + + +@pytest.mark.parametrize("bad", ["Home,Network", 'pa"ss', "ssid\nfoo", "name\rbar"]) +def test_metacharacter_rejected(bad): + with pytest.raises(SystemExit): + _reject_csv_metacharacters("wifi-ssid", bad)