diff --git a/pynitrokey/cli/nk3/__init__.py b/pynitrokey/cli/nk3/__init__.py index a15ff80d..912d93e2 100644 --- a/pynitrokey/cli/nk3/__init__.py +++ b/pynitrokey/cli/nk3/__init__.py @@ -135,7 +135,7 @@ def list() -> None: with device as device: uuid = device.uuid() if uuid: - local_print(f"{device.path}: {device.name} {device.uuid():032X}") + local_print(f"{device.path}: {device.name} {uuid}") else: local_print(f"{device.path}: {device.name}") diff --git a/pynitrokey/cli/nk3/test.py b/pynitrokey/cli/nk3/test.py index 7646a5c4..f0d8d128 100644 --- a/pynitrokey/cli/nk3/test.py +++ b/pynitrokey/cli/nk3/test.py @@ -23,7 +23,7 @@ from pynitrokey.helpers import local_print from pynitrokey.nk3.base import Nitrokey3Base from pynitrokey.nk3.device import Nitrokey3Device -from pynitrokey.nk3.utils import Version +from pynitrokey.nk3.utils import Uuid, Version logger = logging.getLogger(__name__) @@ -149,7 +149,7 @@ def log_system() -> None: @test_case("uuid", "UUID query") def test_uuid_query(ctx: TestContext, device: Nitrokey3Base) -> TestResult: uuid = device.uuid() - uuid_str = f"{uuid:032X}" if uuid else "[not supported]" + uuid_str = str(uuid) if uuid else "[not supported]" return TestResult(TestStatus.SUCCESS, uuid_str) @@ -179,7 +179,7 @@ def test_bootloader_configuration( from smartcard.CardConnection import CardConnection from smartcard.Exceptions import NoCardException - def find_smartcard(uuid: int) -> CardConnection: + def find_smartcard(uuid: Uuid) -> CardConnection: for reader in System.readers(): conn = reader.createConnection() try: @@ -193,10 +193,10 @@ def find_smartcard(uuid: int) -> CardConnection: continue if len(data) != 16: continue - if uuid != int.from_bytes(data, "big"): + if uuid != Uuid(int.from_bytes(data, byteorder="big")): continue return conn - raise Exception(f"No smartcard with UUID {uuid:032X} found") + raise Exception(f"No smartcard with UUID {uuid} found") def select(conn: CardConnection, aid: list[int]) -> bool: apdu = [0x00, 0xA4, 0x04, 0x00] diff --git a/pynitrokey/nk3/base.py b/pynitrokey/nk3/base.py index 8d175577..08fe93d7 100644 --- a/pynitrokey/nk3/base.py +++ b/pynitrokey/nk3/base.py @@ -10,6 +10,8 @@ from abc import ABC, abstractmethod from typing import Optional, TypeVar +from .utils import Uuid + T = TypeVar("T", bound="Nitrokey3Base") @@ -41,5 +43,5 @@ def reboot(self) -> bool: ... @abstractmethod - def uuid(self) -> Optional[int]: + def uuid(self) -> Optional[Uuid]: ... diff --git a/pynitrokey/nk3/bootloader/lpc55.py b/pynitrokey/nk3/bootloader/lpc55.py index 78a6ef75..f0b48618 100644 --- a/pynitrokey/nk3/bootloader/lpc55.py +++ b/pynitrokey/nk3/bootloader/lpc55.py @@ -19,7 +19,7 @@ from spsdk.sbfile.sb2.images import BootImageV21 from spsdk.utils.usbfilter import USBDeviceFilter -from ..utils import Version +from ..utils import Uuid, Version from . import FirmwareMetadata, Nitrokey3Bootloader, ProgressCallback, Variant RKHT = bytes.fromhex("050aad3e77791a81e59c5b2ba5a158937e9460ee325d8ccba09734b8fdebb171") @@ -81,7 +81,7 @@ def reboot(self) -> bool: raise Exception("Failed to reboot Nitrokey 3 bootloader") return True - def uuid(self) -> Optional[int]: + def uuid(self) -> Optional[Uuid]: uuid = self.device.get_property(PropertyTag.UNIQUE_DEVICE_IDENT) # type: ignore[arg-type] if not uuid: raise ValueError("Missing response for UUID property query") @@ -92,7 +92,7 @@ def uuid(self) -> Optional[int]: # https://github.com/lpc55/lpc55-host/blob/main/src/bootloader/property.rs#L222 wrong_endian = (uuid[3] << 96) + (uuid[2] << 64) + (uuid[1] << 32) + uuid[0] right_endian = wrong_endian.to_bytes(16, byteorder="little") - return int.from_bytes(right_endian, byteorder="big") + return Uuid(int.from_bytes(right_endian, byteorder="big")) def update( self, diff --git a/pynitrokey/nk3/bootloader/nrf52.py b/pynitrokey/nk3/bootloader/nrf52.py index 211d4280..a488afdc 100644 --- a/pynitrokey/nk3/bootloader/nrf52.py +++ b/pynitrokey/nk3/bootloader/nrf52.py @@ -26,7 +26,7 @@ from nordicsemi.dfu.package import Package from nordicsemi.lister.device_lister import DeviceLister -from ..utils import Version +from ..utils import Uuid, Version from . import FirmwareMetadata, Nitrokey3Bootloader, ProgressCallback, Variant logger = logging.getLogger(__name__) @@ -153,8 +153,8 @@ def close(self) -> None: def reboot(self) -> bool: return False - def uuid(self) -> Optional[int]: - return self._uuid + def uuid(self) -> Optional[Uuid]: + return Uuid(self._uuid) def update(self, data: bytes, callback: Optional[ProgressCallback] = None) -> None: # based on https://github.com/NordicSemiconductor/pc-nrfutil/blob/1caa347b1cca3896f4695823f48abba15fbef76b/nordicsemi/dfu/dfu.py diff --git a/pynitrokey/nk3/device.py b/pynitrokey/nk3/device.py index 36f4baad..e0d0e105 100644 --- a/pynitrokey/nk3/device.py +++ b/pynitrokey/nk3/device.py @@ -20,7 +20,7 @@ from .base import Nitrokey3Base from .exceptions import TimeoutException -from .utils import Version +from .utils import Uuid, Version RNG_LEN = 57 UUID_LEN = 16 @@ -96,14 +96,14 @@ def reboot(self, mode: BootMode = BootMode.FIRMWARE) -> bool: self.logger.debug("ignoring OSError after reboot", exc_info=e) return True - def uuid(self) -> Optional[int]: + def uuid(self) -> Optional[Uuid]: uuid = self._call(Command.UUID) if len(uuid) == 0: # Firmware version 1.0.0 does not support querying the UUID return None if len(uuid) != UUID_LEN: raise ValueError(f"UUID response has invalid length {len(uuid)}") - return int.from_bytes(uuid, "big") + return Uuid(int.from_bytes(uuid, byteorder="big")) def version(self) -> Version: version_bytes = self._call(Command.VERSION, response_len=VERSION_LEN) diff --git a/pynitrokey/nk3/utils.py b/pynitrokey/nk3/utils.py index 6f7028b7..7a73d95d 100644 --- a/pynitrokey/nk3/utils.py +++ b/pynitrokey/nk3/utils.py @@ -10,9 +10,20 @@ from functools import total_ordering from typing import Tuple +from dataclasses import dataclass from spsdk.sbfile.misc import BcdVersion3 +@dataclass(order=True, frozen=True) +class Uuid: + """UUID of a Nitrokey 3 device.""" + + value: int + + def __str__(self) -> str: + return f"{self.value:032X}" + + @total_ordering class Version: def __init__(self, major: int, minor: int, patch: int) -> None: