2021-11-26 14:50:43 +00:00
|
|
|
# This file is part of the Trezor project.
|
|
|
|
#
|
|
|
|
# Copyright (C) 2012-2022 SatoshiLabs and contributors
|
|
|
|
#
|
|
|
|
# This library is free software: you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU Lesser General Public License version 3
|
|
|
|
# as published by the Free Software Foundation.
|
|
|
|
#
|
|
|
|
# This library is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU Lesser General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the License along with this library.
|
|
|
|
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
|
|
|
|
|
2022-09-06 09:18:05 +00:00
|
|
|
import typing as t
|
|
|
|
from copy import copy
|
|
|
|
from dataclasses import asdict
|
2020-01-03 12:16:33 +00:00
|
|
|
from enum import Enum
|
|
|
|
|
|
|
|
import click
|
|
|
|
import construct as c
|
2022-09-06 09:18:05 +00:00
|
|
|
from construct_classes import Struct
|
|
|
|
from typing_extensions import Protocol, Self, runtime_checkable
|
2020-01-03 12:16:33 +00:00
|
|
|
|
2020-09-01 09:04:20 +00:00
|
|
|
from .. import cosi, firmware
|
2022-12-19 15:55:34 +00:00
|
|
|
from ..firmware import models as fw_models
|
2020-01-03 12:16:33 +00:00
|
|
|
|
|
|
|
SYM_OK = click.style("\u2714", fg="green")
|
|
|
|
SYM_FAIL = click.style("\u274c", fg="red")
|
|
|
|
|
|
|
|
|
|
|
|
class Status(Enum):
|
|
|
|
VALID = click.style("VALID", fg="green", bold=True)
|
|
|
|
INVALID = click.style("INVALID", fg="red", bold=True)
|
|
|
|
MISSING = click.style("MISSING", fg="blue", bold=True)
|
|
|
|
DEVEL = click.style("DEVEL", fg="red", bold=True)
|
|
|
|
|
feat(python): add full type information
WIP - typing the trezorctl apps
typing functions trezorlib/cli
addressing most of mypy issue for trezorlib apps and _internal folder
fixing broken device tests by changing asserts in debuglink.py
addressing most of mypy issues in trezorlib/cli folder
adding types to some untyped functions, mypy section in setup.cfg
typing what can be typed, some mypy fixes, resolving circular import issues
importing type objects in "if TYPE_CHECKING:" branch
fixing CI by removing assert in emulator, better ignore comments
CI assert fix, style fixes, new config options
fixup! CI assert fix, style fixes, new config options
type fixes after rebasing on master
fixing python3.6 and 3.7 unittests by importing Literal from typing_extensions
couple mypy and style fixes
fixes and improvements from code review
silencing all but one mypy issues
trial of typing the tools.expect function
fixup! trial of typing the tools.expect function
@expect and @session decorators correctly type-checked
Optional args in CLI where relevant, not using general list/tuple/dict where possible
python/Makefile commands, adding them into CI, ignoring last mypy issue
documenting overload for expect decorator, two mypy fixes coming from that
black style fix
improved typing of decorators, pyright config file
addressing or ignoring pyright errors, replacing mypy in CI by pyright
fixing incomplete assert causing device tests to fail
pyright issue that showed in CI but not locally, printing pyright version in CI
fixup! pyright issue that showed in CI but not locally, printing pyright version in CI
unifying type:ignore statements for pyright usage
resolving PIL.Image issues, pyrightconfig not excluding anything
replacing couple asserts with TypeGuard on safe_issubclass
better error handling of usb1 import for webusb
better error handling of hid import
small typing details found out by strict pyright mode
improvements from code review
chore(python): changing List to Sequence for protobuf messages
small code changes to reflect the protobuf change to Sequence
importing TypedDict from typing_extensions to support 3.6 and 3.7
simplify _format_access_list function
fixup! simplify _format_access_list function
typing tools folder
typing helper-scripts folder
some click typing
enforcing all functions to have typed arguments
reverting the changed argument name in tools
replacing TransportType with Transport
making PinMatrixRequest.type protobuf attribute required
reverting the protobuf change, making argument into get_pin Optional
small fixes in asserts
solving the session decorator type issues
fixup! solving the session decorator type issues
improvements from code review
fixing new pyright errors introduced after version increase
changing -> Iterable to -> Sequence in enumerate_devices, change in wait_for_devices
style change in debuglink.py
chore(python): adding type annotation to Sequences in messages.py
better "self and cls" types on Transport
fixup! better "self and cls" types on Transport
fixing some easy things from strict pyright run
2021-11-03 22:12:53 +00:00
|
|
|
def is_ok(self) -> bool:
|
2020-01-03 12:16:33 +00:00
|
|
|
return self is Status.VALID or self is Status.DEVEL
|
|
|
|
|
|
|
|
|
|
|
|
VHASH_DEVEL = bytes.fromhex(
|
|
|
|
"c5b4d40cb76911392122c8d1c277937e49c69b2aaf818001ec5c7663fcce258f"
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2022-09-06 09:18:05 +00:00
|
|
|
def _make_dev_keys(*key_bytes: bytes) -> t.Sequence[bytes]:
|
2020-01-03 12:16:33 +00:00
|
|
|
return [k * 32 for k in key_bytes]
|
|
|
|
|
|
|
|
|
|
|
|
def all_zero(data: bytes) -> bool:
|
|
|
|
return all(b == 0 for b in data)
|
|
|
|
|
|
|
|
|
2022-12-15 13:36:04 +00:00
|
|
|
def _check_signature_any(fw: "SignableImageProto", is_devel: bool = False) -> Status:
|
2022-09-06 09:18:05 +00:00
|
|
|
if not fw.signature_present():
|
2020-01-03 12:16:33 +00:00
|
|
|
return Status.MISSING
|
|
|
|
try:
|
2022-09-06 09:18:05 +00:00
|
|
|
fw.verify()
|
2020-01-03 12:16:33 +00:00
|
|
|
return Status.VALID if not is_devel else Status.DEVEL
|
2022-12-15 13:36:04 +00:00
|
|
|
except Exception:
|
|
|
|
pass
|
|
|
|
|
|
|
|
try:
|
2022-12-19 15:55:34 +00:00
|
|
|
fw.verify(dev_keys=True)
|
2022-12-15 13:36:04 +00:00
|
|
|
return Status.DEVEL
|
2020-01-03 12:16:33 +00:00
|
|
|
except Exception:
|
|
|
|
return Status.INVALID
|
|
|
|
|
|
|
|
|
|
|
|
# ====================== formatting functions ====================
|
|
|
|
|
|
|
|
|
|
|
|
class LiteralStr(str):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
def _format_container(
|
2022-09-06 09:18:05 +00:00
|
|
|
pb: t.Union[c.Container, Struct, dict],
|
2020-01-03 12:16:33 +00:00
|
|
|
indent: int = 0,
|
|
|
|
sep: str = " " * 4,
|
2022-09-06 09:18:05 +00:00
|
|
|
truncate_after: t.Optional[int] = 64,
|
|
|
|
truncate_to: t.Optional[int] = 32,
|
2020-01-03 12:16:33 +00:00
|
|
|
) -> str:
|
|
|
|
def mostly_printable(bytes: bytes) -> bool:
|
|
|
|
if not bytes:
|
|
|
|
return True
|
|
|
|
printable = sum(1 for byte in bytes if 0x20 <= byte <= 0x7E)
|
|
|
|
return printable / len(bytes) > 0.8
|
|
|
|
|
2022-09-06 09:18:05 +00:00
|
|
|
def pformat(value: t.Any, indent: int) -> str:
|
2020-01-03 12:16:33 +00:00
|
|
|
level = sep * indent
|
|
|
|
leadin = sep * (indent + 1)
|
|
|
|
|
|
|
|
if isinstance(value, LiteralStr):
|
|
|
|
return value
|
|
|
|
|
|
|
|
if isinstance(value, list):
|
|
|
|
# short list of simple values
|
feat(python): add full type information
WIP - typing the trezorctl apps
typing functions trezorlib/cli
addressing most of mypy issue for trezorlib apps and _internal folder
fixing broken device tests by changing asserts in debuglink.py
addressing most of mypy issues in trezorlib/cli folder
adding types to some untyped functions, mypy section in setup.cfg
typing what can be typed, some mypy fixes, resolving circular import issues
importing type objects in "if TYPE_CHECKING:" branch
fixing CI by removing assert in emulator, better ignore comments
CI assert fix, style fixes, new config options
fixup! CI assert fix, style fixes, new config options
type fixes after rebasing on master
fixing python3.6 and 3.7 unittests by importing Literal from typing_extensions
couple mypy and style fixes
fixes and improvements from code review
silencing all but one mypy issues
trial of typing the tools.expect function
fixup! trial of typing the tools.expect function
@expect and @session decorators correctly type-checked
Optional args in CLI where relevant, not using general list/tuple/dict where possible
python/Makefile commands, adding them into CI, ignoring last mypy issue
documenting overload for expect decorator, two mypy fixes coming from that
black style fix
improved typing of decorators, pyright config file
addressing or ignoring pyright errors, replacing mypy in CI by pyright
fixing incomplete assert causing device tests to fail
pyright issue that showed in CI but not locally, printing pyright version in CI
fixup! pyright issue that showed in CI but not locally, printing pyright version in CI
unifying type:ignore statements for pyright usage
resolving PIL.Image issues, pyrightconfig not excluding anything
replacing couple asserts with TypeGuard on safe_issubclass
better error handling of usb1 import for webusb
better error handling of hid import
small typing details found out by strict pyright mode
improvements from code review
chore(python): changing List to Sequence for protobuf messages
small code changes to reflect the protobuf change to Sequence
importing TypedDict from typing_extensions to support 3.6 and 3.7
simplify _format_access_list function
fixup! simplify _format_access_list function
typing tools folder
typing helper-scripts folder
some click typing
enforcing all functions to have typed arguments
reverting the changed argument name in tools
replacing TransportType with Transport
making PinMatrixRequest.type protobuf attribute required
reverting the protobuf change, making argument into get_pin Optional
small fixes in asserts
solving the session decorator type issues
fixup! solving the session decorator type issues
improvements from code review
fixing new pyright errors introduced after version increase
changing -> Iterable to -> Sequence in enumerate_devices, change in wait_for_devices
style change in debuglink.py
chore(python): adding type annotation to Sequences in messages.py
better "self and cls" types on Transport
fixup! better "self and cls" types on Transport
fixing some easy things from strict pyright run
2021-11-03 22:12:53 +00:00
|
|
|
if not value or isinstance(value[0], (int, bool, Enum)):
|
2020-01-03 12:16:33 +00:00
|
|
|
return repr(value)
|
|
|
|
|
|
|
|
# long list, one line per entry
|
|
|
|
lines = ["[", level + "]"]
|
|
|
|
lines[1:1] = [leadin + pformat(x, indent + 1) for x in value]
|
|
|
|
return "\n".join(lines)
|
|
|
|
|
2022-09-06 09:18:05 +00:00
|
|
|
if isinstance(value, Struct):
|
|
|
|
value = asdict(value)
|
|
|
|
|
2020-01-03 12:16:33 +00:00
|
|
|
if isinstance(value, dict):
|
|
|
|
lines = ["{"]
|
|
|
|
for key, val in value.items():
|
|
|
|
if key.startswith("_"):
|
|
|
|
continue
|
|
|
|
if val is None or val == []:
|
|
|
|
continue
|
|
|
|
lines.append(leadin + key + ": " + pformat(val, indent + 1))
|
|
|
|
lines.append(level + "}")
|
|
|
|
return "\n".join(lines)
|
|
|
|
|
|
|
|
if isinstance(value, (bytes, bytearray)):
|
|
|
|
length = len(value)
|
|
|
|
suffix = ""
|
|
|
|
if truncate_after and length > truncate_after:
|
|
|
|
suffix = "..."
|
|
|
|
value = value[: truncate_to or 0]
|
|
|
|
if mostly_printable(value):
|
|
|
|
output = repr(value)
|
|
|
|
else:
|
|
|
|
output = value.hex()
|
2021-09-27 10:13:51 +00:00
|
|
|
return f"{length} bytes {output}{suffix}"
|
2020-01-03 12:16:33 +00:00
|
|
|
|
|
|
|
if isinstance(value, Enum):
|
|
|
|
return str(value)
|
|
|
|
|
|
|
|
return repr(value)
|
|
|
|
|
|
|
|
return pformat(pb, indent)
|
|
|
|
|
|
|
|
|
2022-09-06 09:18:05 +00:00
|
|
|
def _format_version(version: t.Tuple[int, ...]) -> str:
|
|
|
|
return ".".join(str(i) for i in version)
|
|
|
|
|
|
|
|
|
|
|
|
def format_header(
|
|
|
|
header: firmware.core.FirmwareHeader,
|
|
|
|
code_hashes: t.Sequence[bytes],
|
|
|
|
digest: bytes,
|
|
|
|
sig_status: Status,
|
|
|
|
) -> str:
|
|
|
|
header_dict = asdict(header)
|
|
|
|
header_out = header_dict.copy()
|
|
|
|
|
|
|
|
for key, val in header_out.items():
|
|
|
|
if "version" in key:
|
|
|
|
header_out[key] = LiteralStr(_format_version(val))
|
|
|
|
|
|
|
|
hashes_out = []
|
|
|
|
for expected, actual in zip(header.hashes, code_hashes):
|
|
|
|
status = SYM_OK if expected == actual else SYM_FAIL
|
|
|
|
hashes_out.append(LiteralStr(f"{status} {expected.hex()}"))
|
|
|
|
|
|
|
|
if all(all_zero(h) for h in header.hashes):
|
|
|
|
hash_status = Status.MISSING
|
|
|
|
elif header.hashes != code_hashes:
|
|
|
|
hash_status = Status.INVALID
|
|
|
|
else:
|
|
|
|
hash_status = Status.VALID
|
|
|
|
|
|
|
|
header_out["hashes"] = hashes_out
|
|
|
|
|
|
|
|
all_ok = SYM_OK if hash_status.is_ok() and sig_status.is_ok() else SYM_FAIL
|
|
|
|
|
|
|
|
output = [
|
|
|
|
"Firmware Header " + _format_container(header_out),
|
|
|
|
f"Fingerprint: {click.style(digest.hex(), bold=True)}",
|
|
|
|
f"{all_ok} Signature is {sig_status.value}, hashes are {hash_status.value}",
|
|
|
|
]
|
|
|
|
|
|
|
|
return "\n".join(output)
|
2020-01-03 12:16:33 +00:00
|
|
|
|
|
|
|
|
|
|
|
# =========================== functionality implementations ===============
|
|
|
|
|
|
|
|
|
2022-09-06 09:18:05 +00:00
|
|
|
class SignableImageProto(Protocol):
|
|
|
|
NAME: t.ClassVar[str]
|
2020-01-03 12:16:33 +00:00
|
|
|
|
2022-09-06 09:18:05 +00:00
|
|
|
@classmethod
|
|
|
|
def parse(cls, data: bytes) -> Self:
|
2022-12-19 15:55:34 +00:00
|
|
|
"""Parse binary data into an image of this type."""
|
2022-09-06 09:18:05 +00:00
|
|
|
...
|
2020-01-03 12:16:33 +00:00
|
|
|
|
|
|
|
def digest(self) -> bytes:
|
2022-12-19 15:55:34 +00:00
|
|
|
"""Calculate digest that will be signed / verified."""
|
2022-09-06 09:18:05 +00:00
|
|
|
...
|
2020-01-03 12:16:33 +00:00
|
|
|
|
2022-12-19 15:55:34 +00:00
|
|
|
def verify(self, dev_keys: bool = False) -> None:
|
|
|
|
"""Verify signature of the image.
|
|
|
|
|
|
|
|
If dev_keys is True, verify using development keys. If selected, a production
|
|
|
|
image will fail verification.
|
|
|
|
"""
|
2022-09-06 09:18:05 +00:00
|
|
|
...
|
2020-01-03 12:16:33 +00:00
|
|
|
|
2022-09-06 09:18:05 +00:00
|
|
|
def build(self) -> bytes:
|
2022-12-19 15:55:34 +00:00
|
|
|
"""Reconstruct binary representation of the image."""
|
2022-09-06 09:18:05 +00:00
|
|
|
...
|
|
|
|
|
|
|
|
def format(self, verbose: bool = False) -> str:
|
2022-12-19 15:55:34 +00:00
|
|
|
"""Generate printable information about the image."""
|
2022-09-06 09:18:05 +00:00
|
|
|
...
|
|
|
|
|
|
|
|
def signature_present(self) -> bool:
|
2022-12-19 15:55:34 +00:00
|
|
|
"""Check if the image has a signature."""
|
2022-09-06 09:18:05 +00:00
|
|
|
...
|
|
|
|
|
2022-12-15 13:36:04 +00:00
|
|
|
def public_keys(self, dev_keys: bool = False) -> t.Sequence[bytes]:
|
2022-12-19 15:55:34 +00:00
|
|
|
"""Return public keys that should be used to sign the image.
|
|
|
|
|
|
|
|
This does _not_ return the keys with which the image is actually signed.
|
|
|
|
In particular, `image.public_keys()` will return the production
|
|
|
|
keys even if the image is signed with development keys.
|
|
|
|
|
|
|
|
If dev_keys is True, return development keys.
|
|
|
|
"""
|
2022-09-06 09:18:05 +00:00
|
|
|
...
|
|
|
|
|
|
|
|
|
|
|
|
@runtime_checkable
|
|
|
|
class CosiSignedImage(SignableImageProto, Protocol):
|
|
|
|
DEV_KEYS: t.ClassVar[t.Sequence[bytes]] = []
|
2020-01-03 12:16:33 +00:00
|
|
|
|
2024-02-28 14:11:47 +00:00
|
|
|
def insert_signature(self, signature: bytes, sigmask: int) -> None: ...
|
2022-09-06 09:18:05 +00:00
|
|
|
|
|
|
|
|
|
|
|
@runtime_checkable
|
|
|
|
class LegacySignedImage(SignableImageProto, Protocol):
|
2024-02-28 14:11:47 +00:00
|
|
|
def slots(self) -> t.Iterable[int]: ...
|
2020-01-03 12:16:33 +00:00
|
|
|
|
2024-02-28 14:11:47 +00:00
|
|
|
def insert_signature(self, slot: int, key_index: int, signature: bytes) -> None: ...
|
2020-01-03 12:16:33 +00:00
|
|
|
|
2022-12-19 15:55:34 +00:00
|
|
|
def public_keys(
|
|
|
|
self, dev_keys: bool = False, signature_version: int = 3
|
|
|
|
) -> t.Sequence[bytes]:
|
|
|
|
"""Return public keys that should be used to sign the image.
|
|
|
|
|
|
|
|
This does _not_ return the keys with which the image is actually signed.
|
|
|
|
In particular, `image.public_keys()` will return the production
|
|
|
|
keys even if the image is signed with development keys.
|
|
|
|
|
|
|
|
If dev_keys is True, return development keys.
|
|
|
|
|
|
|
|
Specifying signature_version allows to return keys for a different signature
|
|
|
|
scheme version. The default is the newest version 3.
|
|
|
|
"""
|
|
|
|
...
|
|
|
|
|
2020-01-03 12:16:33 +00:00
|
|
|
|
2022-09-06 09:18:05 +00:00
|
|
|
class CosiSignatureHeaderProto(Protocol):
|
2023-05-31 12:43:20 +00:00
|
|
|
hw_model: t.Union[fw_models.Model, bytes]
|
2022-09-06 09:18:05 +00:00
|
|
|
signature: bytes
|
|
|
|
sigmask: int
|
2020-01-03 12:16:33 +00:00
|
|
|
|
2022-09-06 09:18:05 +00:00
|
|
|
|
|
|
|
class CosiSignedMixin:
|
|
|
|
def signature_present(self) -> bool:
|
|
|
|
header = self.get_header()
|
|
|
|
return not all_zero(header.signature) or header.sigmask != 0
|
|
|
|
|
|
|
|
def insert_signature(self, signature: bytes, sigmask: int) -> None:
|
|
|
|
self.get_header().signature = signature
|
|
|
|
self.get_header().sigmask = sigmask
|
|
|
|
|
|
|
|
def get_header(self) -> CosiSignatureHeaderProto:
|
|
|
|
raise NotImplementedError
|
|
|
|
|
2023-05-31 12:43:20 +00:00
|
|
|
def get_model_keys(self, dev_keys: bool) -> fw_models.ModelKeys:
|
|
|
|
hw_model = self.get_header().hw_model
|
|
|
|
model = fw_models.Model.from_hw_model(hw_model)
|
|
|
|
return model.model_keys(dev_keys)
|
|
|
|
|
2022-09-06 09:18:05 +00:00
|
|
|
|
|
|
|
class VendorHeader(firmware.VendorHeader, CosiSignedMixin):
|
2022-12-19 15:55:34 +00:00
|
|
|
NAME: t.ClassVar[str] = "vendorheader"
|
2020-01-03 12:16:33 +00:00
|
|
|
DEV_KEYS = _make_dev_keys(b"\x44", b"\x45")
|
|
|
|
|
2022-09-06 09:18:05 +00:00
|
|
|
SUBCON = c.Struct(*firmware.VendorHeader.SUBCON.subcons, c.Terminated)
|
2020-01-03 12:16:33 +00:00
|
|
|
|
2022-09-06 09:18:05 +00:00
|
|
|
def get_header(self) -> CosiSignatureHeaderProto:
|
|
|
|
return self
|
2020-01-03 12:16:33 +00:00
|
|
|
|
|
|
|
def _format(self, terse: bool) -> str:
|
|
|
|
if not terse:
|
|
|
|
output = [
|
2022-09-06 09:18:05 +00:00
|
|
|
"Vendor Header " + _format_container(self),
|
|
|
|
f"Pubkey bundle hash: {self.vhash().hex()}",
|
2020-01-03 12:16:33 +00:00
|
|
|
]
|
|
|
|
else:
|
|
|
|
output = [
|
|
|
|
"Vendor Header for {vendor} version {version} ({size} bytes)".format(
|
2022-09-06 09:18:05 +00:00
|
|
|
vendor=click.style(self.text, bold=True),
|
|
|
|
version=_format_version(self.version),
|
|
|
|
size=self.header_len,
|
2020-01-03 12:16:33 +00:00
|
|
|
),
|
|
|
|
]
|
|
|
|
|
|
|
|
if not terse:
|
2022-09-06 09:18:05 +00:00
|
|
|
output.append(f"Fingerprint: {click.style(self.digest().hex(), bold=True)}")
|
2020-01-03 12:16:33 +00:00
|
|
|
|
2022-12-15 13:36:04 +00:00
|
|
|
sig_status = _check_signature_any(self)
|
2020-01-03 12:16:33 +00:00
|
|
|
sym = SYM_OK if sig_status.is_ok() else SYM_FAIL
|
2021-09-27 10:13:51 +00:00
|
|
|
output.append(f"{sym} Signature is {sig_status.value}")
|
2020-01-03 12:16:33 +00:00
|
|
|
|
|
|
|
return "\n".join(output)
|
|
|
|
|
|
|
|
def format(self, verbose: bool = False) -> str:
|
|
|
|
return self._format(terse=False)
|
|
|
|
|
2022-12-15 13:36:04 +00:00
|
|
|
def public_keys(self, dev_keys: bool = False) -> t.Sequence[bytes]:
|
2023-05-31 12:43:20 +00:00
|
|
|
return self.get_model_keys(dev_keys).bootloader_keys
|
2022-09-06 09:18:05 +00:00
|
|
|
|
|
|
|
|
|
|
|
class VendorFirmware(firmware.VendorFirmware, CosiSignedMixin):
|
2022-12-19 15:55:34 +00:00
|
|
|
NAME: t.ClassVar[str] = "firmware"
|
2022-09-06 09:18:05 +00:00
|
|
|
DEV_KEYS = _make_dev_keys(b"\x47", b"\x48")
|
|
|
|
|
|
|
|
def get_header(self) -> CosiSignatureHeaderProto:
|
|
|
|
return self.firmware.header
|
2020-01-03 12:16:33 +00:00
|
|
|
|
2022-09-06 09:18:05 +00:00
|
|
|
def format(self, verbose: bool = False) -> str:
|
|
|
|
vh = copy(self.vendor_header)
|
|
|
|
vh.__class__ = VendorHeader
|
|
|
|
assert isinstance(vh, VendorHeader)
|
|
|
|
|
|
|
|
is_devel = self.vendor_header.vhash() == VHASH_DEVEL
|
|
|
|
return (
|
|
|
|
vh._format(terse=not verbose)
|
|
|
|
+ "\n"
|
|
|
|
+ format_header(
|
|
|
|
self.firmware.header,
|
|
|
|
self.firmware.code_hashes(),
|
|
|
|
self.digest(),
|
|
|
|
_check_signature_any(self, is_devel),
|
|
|
|
)
|
2020-01-03 12:16:33 +00:00
|
|
|
)
|
|
|
|
|
2022-12-15 13:36:04 +00:00
|
|
|
def public_keys(self, dev_keys: bool = False) -> t.Sequence[bytes]:
|
2022-12-19 15:55:34 +00:00
|
|
|
"""Return public keys that should be used to sign the image.
|
|
|
|
|
|
|
|
In vendor firmware, the public keys are stored in the vendor header.
|
|
|
|
There is no choice of development keys. If that is required, you need to create
|
|
|
|
an image with a development vendor header.
|
|
|
|
"""
|
2022-09-06 09:18:05 +00:00
|
|
|
return self.vendor_header.pubkeys
|
2020-01-03 12:16:33 +00:00
|
|
|
|
|
|
|
|
2022-09-06 09:18:05 +00:00
|
|
|
class BootloaderImage(firmware.FirmwareImage, CosiSignedMixin):
|
2022-12-19 15:55:34 +00:00
|
|
|
NAME: t.ClassVar[str] = "bootloader"
|
2022-09-06 09:18:05 +00:00
|
|
|
DEV_KEYS = _make_dev_keys(b"\x41", b"\x42")
|
2020-01-03 12:16:33 +00:00
|
|
|
|
2022-09-06 09:18:05 +00:00
|
|
|
def get_header(self) -> CosiSignatureHeaderProto:
|
|
|
|
return self.header
|
2020-01-03 12:16:33 +00:00
|
|
|
|
2022-09-06 09:18:05 +00:00
|
|
|
def format(self, verbose: bool = False) -> str:
|
|
|
|
return format_header(
|
|
|
|
self.header,
|
|
|
|
self.code_hashes(),
|
|
|
|
self.digest(),
|
2022-12-15 13:36:04 +00:00
|
|
|
_check_signature_any(self),
|
2022-09-06 09:18:05 +00:00
|
|
|
)
|
2020-01-03 12:16:33 +00:00
|
|
|
|
2022-12-19 15:55:34 +00:00
|
|
|
def verify(self, dev_keys: bool = False) -> None:
|
2022-09-06 09:18:05 +00:00
|
|
|
self.validate_code_hashes()
|
2022-12-19 15:55:34 +00:00
|
|
|
public_keys = self.public_keys(dev_keys)
|
2022-09-06 09:18:05 +00:00
|
|
|
try:
|
|
|
|
cosi.verify(
|
|
|
|
self.header.signature,
|
|
|
|
self.digest(),
|
2022-12-19 15:55:34 +00:00
|
|
|
self.get_model_keys(dev_keys).boardloader_sigs_needed,
|
2022-12-15 13:36:04 +00:00
|
|
|
public_keys,
|
2022-09-06 09:18:05 +00:00
|
|
|
self.header.sigmask,
|
|
|
|
)
|
|
|
|
except Exception:
|
|
|
|
raise firmware.InvalidSignatureError("Invalid bootloader signature")
|
|
|
|
|
2022-12-15 13:36:04 +00:00
|
|
|
def public_keys(self, dev_keys: bool = False) -> t.Sequence[bytes]:
|
2022-12-19 15:55:34 +00:00
|
|
|
return self.get_model_keys(dev_keys).boardloader_keys
|
2022-09-06 09:18:05 +00:00
|
|
|
|
|
|
|
|
|
|
|
class LegacyFirmware(firmware.LegacyFirmware):
|
2022-12-19 15:55:34 +00:00
|
|
|
NAME: t.ClassVar[str] = "legacy_firmware_v1"
|
2022-09-06 09:18:05 +00:00
|
|
|
|
|
|
|
def signature_present(self) -> bool:
|
|
|
|
return any(i != 0 for i in self.key_indexes) or any(
|
|
|
|
not all_zero(sig) for sig in self.signatures
|
|
|
|
)
|
2020-01-03 12:16:33 +00:00
|
|
|
|
2022-09-06 09:18:05 +00:00
|
|
|
def insert_signature(self, slot: int, key_index: int, signature: bytes) -> None:
|
|
|
|
if not 0 <= slot < firmware.V1_SIGNATURE_SLOTS:
|
|
|
|
raise ValueError("Invalid slot number")
|
2023-10-16 10:38:24 +00:00
|
|
|
if not 0 < key_index <= len(fw_models.LEGACY_V1V2.firmware_keys):
|
2022-09-06 09:18:05 +00:00
|
|
|
raise ValueError("Invalid key index")
|
|
|
|
self.key_indexes[slot] = key_index
|
|
|
|
self.signatures[slot] = signature
|
2020-01-03 12:16:33 +00:00
|
|
|
|
2022-09-06 09:18:05 +00:00
|
|
|
def format(self, verbose: bool = False) -> str:
|
|
|
|
contents = asdict(self).copy()
|
|
|
|
del contents["embedded_v2"]
|
|
|
|
if self.embedded_v2:
|
|
|
|
em = copy(self.embedded_v2)
|
|
|
|
em.__class__ = LegacyV2Firmware
|
|
|
|
assert isinstance(em, LegacyV2Firmware)
|
|
|
|
embedded_content = "\nEmbedded V2 header: " + em.format(verbose=verbose)
|
2020-01-03 12:16:33 +00:00
|
|
|
else:
|
2022-09-06 09:18:05 +00:00
|
|
|
embedded_content = ""
|
2020-01-03 12:16:33 +00:00
|
|
|
|
2022-09-06 09:18:05 +00:00
|
|
|
return _format_container(contents) + embedded_content
|
2020-01-03 12:16:33 +00:00
|
|
|
|
2022-12-19 15:55:34 +00:00
|
|
|
def public_keys(
|
|
|
|
self, dev_keys: bool = False, signature_version: int = 2
|
|
|
|
) -> t.Sequence[bytes]:
|
|
|
|
if dev_keys:
|
2023-10-16 10:38:24 +00:00
|
|
|
return fw_models.LEGACY_V1V2_DEV.firmware_keys
|
2022-12-19 15:55:34 +00:00
|
|
|
else:
|
2023-10-16 10:38:24 +00:00
|
|
|
return fw_models.LEGACY_V1V2.firmware_keys
|
2020-01-03 12:16:33 +00:00
|
|
|
|
2022-09-06 09:18:05 +00:00
|
|
|
def slots(self) -> t.Iterable[int]:
|
|
|
|
return self.key_indexes
|
2020-01-03 12:16:33 +00:00
|
|
|
|
|
|
|
|
2022-09-06 09:18:05 +00:00
|
|
|
class LegacyV2Firmware(firmware.LegacyV2Firmware):
|
2022-12-19 15:55:34 +00:00
|
|
|
NAME: t.ClassVar[str] = "legacy_firmware_v2"
|
2020-01-03 12:16:33 +00:00
|
|
|
|
2022-09-06 09:18:05 +00:00
|
|
|
def signature_present(self) -> bool:
|
|
|
|
return any(i != 0 for i in self.header.v1_key_indexes) or any(
|
|
|
|
not all_zero(sig) for sig in self.header.v1_signatures
|
2020-01-03 12:16:33 +00:00
|
|
|
)
|
|
|
|
|
2022-09-06 09:18:05 +00:00
|
|
|
def insert_signature(self, slot: int, key_index: int, signature: bytes) -> None:
|
|
|
|
if not 0 <= slot < firmware.V1_SIGNATURE_SLOTS:
|
|
|
|
raise ValueError("Invalid slot number")
|
2022-11-03 15:38:56 +00:00
|
|
|
if not 0 < key_index <= len(firmware.V1_BOOTLOADER_KEYS):
|
2022-09-06 09:18:05 +00:00
|
|
|
raise ValueError("Invalid key index")
|
|
|
|
if not isinstance(self.header.v1_key_indexes, list):
|
|
|
|
self.header.v1_key_indexes = list(self.header.v1_key_indexes)
|
|
|
|
if not isinstance(self.header.v1_signatures, list):
|
|
|
|
self.header.v1_signatures = list(self.header.v1_signatures)
|
|
|
|
self.header.v1_key_indexes[slot] = key_index
|
|
|
|
self.header.v1_signatures[slot] = signature
|
|
|
|
|
2020-01-03 12:16:33 +00:00
|
|
|
def format(self, verbose: bool = False) -> str:
|
2022-09-06 09:18:05 +00:00
|
|
|
return format_header(
|
|
|
|
self.header,
|
|
|
|
self.code_hashes(),
|
|
|
|
self.digest(),
|
2022-12-15 13:36:04 +00:00
|
|
|
_check_signature_any(self),
|
2020-01-03 12:16:33 +00:00
|
|
|
)
|
|
|
|
|
2022-12-19 15:55:34 +00:00
|
|
|
def public_keys(
|
|
|
|
self, dev_keys: bool = False, signature_version: int = 3
|
|
|
|
) -> t.Sequence[bytes]:
|
|
|
|
keymap: t.Dict[t.Tuple[int, bool], fw_models.ModelKeys] = {
|
2023-10-16 10:38:24 +00:00
|
|
|
(3, False): fw_models.LEGACY_V3,
|
|
|
|
(3, True): fw_models.LEGACY_V3_DEV,
|
|
|
|
(2, False): fw_models.LEGACY_V1V2,
|
|
|
|
(2, True): fw_models.LEGACY_V1V2_DEV,
|
2022-12-19 15:55:34 +00:00
|
|
|
}
|
|
|
|
if not (signature_version, dev_keys) in keymap:
|
|
|
|
raise ValueError("Unsupported signature version")
|
|
|
|
return keymap[signature_version, dev_keys].firmware_keys
|
2020-01-03 12:16:33 +00:00
|
|
|
|
2022-09-06 09:18:05 +00:00
|
|
|
def slots(self) -> t.Iterable[int]:
|
|
|
|
return self.header.v1_key_indexes
|
2020-01-03 12:16:33 +00:00
|
|
|
|
|
|
|
|
2022-09-06 09:18:05 +00:00
|
|
|
def parse_image(image: bytes) -> SignableImageProto:
|
|
|
|
try:
|
|
|
|
return VendorFirmware.parse(image)
|
|
|
|
except c.ConstructError:
|
|
|
|
pass
|
2020-01-03 12:16:33 +00:00
|
|
|
|
2022-09-06 09:18:05 +00:00
|
|
|
try:
|
|
|
|
return VendorHeader.parse(image)
|
|
|
|
except c.ConstructError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
try:
|
|
|
|
firmware_img = firmware.core.FirmwareImage.parse(image)
|
|
|
|
if firmware_img.header.magic == firmware.core.HeaderType.BOOTLOADER:
|
|
|
|
return BootloaderImage.parse(image)
|
|
|
|
if firmware_img.header.magic == firmware.core.HeaderType.FIRMWARE:
|
|
|
|
return LegacyV2Firmware.parse(image)
|
|
|
|
raise ValueError("Unrecognized firmware header magic")
|
|
|
|
except c.ConstructError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
try:
|
|
|
|
return LegacyFirmware.parse(image)
|
|
|
|
except c.ConstructError:
|
|
|
|
pass
|
2020-01-03 12:16:33 +00:00
|
|
|
|
2022-09-06 09:18:05 +00:00
|
|
|
raise ValueError("Unrecognized firmware type")
|