implement device signing binary frames and sign() command for on-device signing.

This commit is contained in:
agessaman 2025-12-14 22:23:33 -08:00
parent 6579efe6b4
commit c42c30c25d
4 changed files with 167 additions and 0 deletions

View file

@ -211,6 +211,61 @@ class DeviceCommands(CommandHandlerBase):
data = b"\x18" + key
return await self.send(data, [EventType.OK, EventType.ERROR])
async def sign_start(self) -> Event:
"""
Initialize a signing session on the device.
Returns the available buffer size for signing data.
"""
logger.debug("Starting signing session on device")
return await self.send(b"\x21", [EventType.SIGN_START, EventType.ERROR])
async def sign_data(self, chunk: bytes) -> Event:
"""
Send a chunk of data to be included in the device-side signature.
The device accepts up to 8KB total across chunks; caller is responsible
for chunking appropriately.
"""
if not isinstance(chunk, (bytes, bytearray)):
raise TypeError("chunk must be bytes-like")
logger.debug(f"Sending signing data chunk ({len(chunk)} bytes)")
return await self.send(b"\x22" + bytes(chunk), [EventType.OK, EventType.ERROR])
async def sign_finish(self) -> Event:
"""
Finalize signing and retrieve the signature produced by the device.
"""
logger.debug("Finalizing signing session on device")
return await self.send(b"\x23", [EventType.SIGNATURE, EventType.ERROR])
async def sign(self, data: bytes, chunk_size: int = 512) -> Event:
"""
Convenience: sign the given data on device, handling chunking.
Returns the signature event or an error event.
"""
if not isinstance(data, (bytes, bytearray)):
raise TypeError("data must be bytes-like")
if chunk_size <= 0:
raise ValueError("chunk_size must be > 0")
start_evt = await self.sign_start()
if start_evt.type == EventType.ERROR:
return start_evt
max_len = start_evt.payload.get("max_length", 0)
if max_len and len(data) > max_len:
return Event(EventType.ERROR, {"reason": "data_too_large", "max_length": max_len, "len": len(data)})
for idx in range(0, len(data), chunk_size):
chunk = data[idx : idx + chunk_size]
evt = await self.sign_data(chunk)
if evt.type == EventType.ERROR:
return evt
return await self.sign_finish()
async def get_stats_core(self) -> Event:
logger.debug("Getting core statistics")
# CMD_GET_STATS (56) + STATS_TYPE_CORE (0)

View file

@ -50,6 +50,8 @@ class EventType(Enum):
CONTROL_DATA = "control_data"
DISCOVER_RESPONSE = "discover_response"
NEIGHBOURS_RESPONSE = "neighbours_response"
SIGN_START = "sign_start"
SIGNATURE = "signature"
# Command response types
OK = "command_ok"

View file

@ -710,6 +710,20 @@ class MessageReader:
else:
logger.error(f"Invalid private key response length: {len(data)}")
elif packet_type_value == PacketType.SIGN_START.value:
logger.debug(f"Received sign start response: {data.hex()}")
# Payload: 1 reserved byte, 4-byte max length
dbuf.read(1)
max_len = int.from_bytes(dbuf.read(4), "little")
res = {"max_length": max_len}
await self.dispatcher.dispatch(Event(EventType.SIGN_START, res))
elif packet_type_value == PacketType.SIGNATURE.value:
logger.debug(f"Received signature: {data.hex()}")
signature = dbuf.read()
res = {"signature": signature}
await self.dispatcher.dispatch(Event(EventType.SIGNATURE, res))
elif packet_type_value == PacketType.DISABLED.value:
logger.debug("Received disabled response")
res = {"reason": "private_key_export_disabled"}