mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-11-15 20:19:23 +00:00
python/firmware: clarify firmware image types
This commit is contained in:
parent
ab82382b1e
commit
941087179f
@ -36,18 +36,18 @@ def validate_firmware(version, fw, expected_fingerprint=None):
|
|||||||
if version == firmware.FirmwareFormat.TREZOR_ONE:
|
if version == firmware.FirmwareFormat.TREZOR_ONE:
|
||||||
if fw.embedded_onev2:
|
if fw.embedded_onev2:
|
||||||
click.echo("Trezor One firmware with embedded v2 image (1.8.0 or later)")
|
click.echo("Trezor One firmware with embedded v2 image (1.8.0 or later)")
|
||||||
_print_version(fw.embedded_onev2.firmware_header.version)
|
_print_version(fw.embedded_onev2.header.version)
|
||||||
else:
|
else:
|
||||||
click.echo("Trezor One firmware image.")
|
click.echo("Trezor One firmware image.")
|
||||||
elif version == firmware.FirmwareFormat.TREZOR_ONE_V2:
|
elif version == firmware.FirmwareFormat.TREZOR_ONE_V2:
|
||||||
click.echo("Trezor One v2 firmware (1.8.0 or later)")
|
click.echo("Trezor One v2 firmware (1.8.0 or later)")
|
||||||
_print_version(fw.firmware_header.version)
|
_print_version(fw.header.version)
|
||||||
elif version == firmware.FirmwareFormat.TREZOR_T:
|
elif version == firmware.FirmwareFormat.TREZOR_T:
|
||||||
click.echo("Trezor T firmware image.")
|
click.echo("Trezor T firmware image.")
|
||||||
vendor = fw.vendor_header.vendor_string
|
vendor = fw.vendor_header.vendor_string
|
||||||
vendor_version = "{major}.{minor}".format(**fw.vendor_header.version)
|
vendor_version = "{major}.{minor}".format(**fw.vendor_header.version)
|
||||||
click.echo("Vendor header from {}, version {}".format(vendor, vendor_version))
|
click.echo("Vendor header from {}, version {}".format(vendor, vendor_version))
|
||||||
_print_version(fw.firmware_header.version)
|
_print_version(fw.image.header.version)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
firmware.validate(version, fw, allow_unsigned=False)
|
firmware.validate(version, fw, allow_unsigned=False)
|
||||||
@ -198,17 +198,18 @@ def firmware_update(
|
|||||||
click.echo("Please switch your device to bootloader mode.")
|
click.echo("Please switch your device to bootloader mode.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
# bootloader for T1 does not export 'model', so we rely on major_version
|
||||||
f = client.features
|
f = client.features
|
||||||
bootloader_onev2 = f.major_version == 1 and f.minor_version >= 8
|
bootloader_version = (f.major_version, f.minor_version, f.patch_version)
|
||||||
|
bootloader_onev2 = f.major_version == 1 and bootloader_version >= (1, 8, 0)
|
||||||
|
|
||||||
if filename:
|
if filename:
|
||||||
data = open(filename, "rb").read()
|
data = open(filename, "rb").read()
|
||||||
else:
|
else:
|
||||||
if not url:
|
if not url:
|
||||||
bootloader_version = [f.major_version, f.minor_version, f.patch_version]
|
|
||||||
version_list = [int(x) for x in version.split(".")] if version else None
|
version_list = [int(x) for x in version.split(".")] if version else None
|
||||||
url, fp = find_best_firmware_version(
|
url, fp = find_best_firmware_version(
|
||||||
bootloader_version, version_list, beta, bitcoin_only
|
list(bootloader_version), version_list, beta, bitcoin_only
|
||||||
)
|
)
|
||||||
if not fingerprint:
|
if not fingerprint:
|
||||||
fingerprint = fp
|
fingerprint = fp
|
||||||
|
@ -198,24 +198,37 @@ FirmwareHeader = c.Struct(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
Firmware = c.Struct(
|
"""Raw firmware image.
|
||||||
|
|
||||||
|
Consists of firmware header and code block.
|
||||||
|
This is the expected format of firmware binaries for Trezor One, or bootloader images
|
||||||
|
for Trezor T."""
|
||||||
|
FirmwareImage = c.Struct(
|
||||||
|
"header" / FirmwareHeader,
|
||||||
|
"_code_offset" / c.Tell,
|
||||||
|
"code" / c.Bytes(c.this.header.code_length),
|
||||||
|
c.Terminated,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
"""Firmware image prefixed by a vendor header.
|
||||||
|
|
||||||
|
This is the expected format of firmware binaries for Trezor T."""
|
||||||
|
VendorFirmware = c.Struct(
|
||||||
"vendor_header" / VendorHeader,
|
"vendor_header" / VendorHeader,
|
||||||
"firmware_header" / FirmwareHeader,
|
"image" / FirmwareImage,
|
||||||
"_code_offset" / c.Tell,
|
|
||||||
"code" / c.Bytes(c.this.firmware_header.code_length),
|
|
||||||
c.Terminated,
|
c.Terminated,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
FirmwareOneV2 = c.Struct(
|
"""Legacy firmware image.
|
||||||
"firmware_header" / FirmwareHeader,
|
Consists of a custom header and code block.
|
||||||
"_code_offset" / c.Tell,
|
This is the expected format of firmware binaries for Trezor One pre-1.8.0.
|
||||||
"code" / c.Bytes(c.this.firmware_header.code_length),
|
|
||||||
c.Terminated,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
The code block can optionally be interpreted as a new-style firmware image. That is the
|
||||||
FirmwareOne = c.Struct(
|
expected format of firmware binary for Trezor One version 1.8.0, which can be installed
|
||||||
|
by both the older and the newer bootloader."""
|
||||||
|
LegacyFirmware = c.Struct(
|
||||||
"magic" / c.Const(b"TRZR"),
|
"magic" / c.Const(b"TRZR"),
|
||||||
"code_length" / c.Rebuild(c.Int32ul, c.len_(c.this.code)),
|
"code_length" / c.Rebuild(c.Int32ul, c.len_(c.this.code)),
|
||||||
"key_indexes" / c.Int8ul[V1_SIGNATURE_SLOTS], # pylint: disable=E1136
|
"key_indexes" / c.Int8ul[V1_SIGNATURE_SLOTS], # pylint: disable=E1136
|
||||||
@ -228,7 +241,7 @@ FirmwareOne = c.Struct(
|
|||||||
"code" / c.Bytes(c.this.code_length),
|
"code" / c.Bytes(c.this.code_length),
|
||||||
c.Terminated,
|
c.Terminated,
|
||||||
|
|
||||||
"embedded_onev2" / c.RestreamData(c.this.code, c.Optional(FirmwareOneV2)),
|
"embedded_onev2" / c.RestreamData(c.this.code, c.Optional(FirmwareImage)),
|
||||||
)
|
)
|
||||||
|
|
||||||
# fmt: on
|
# fmt: on
|
||||||
@ -240,20 +253,19 @@ class FirmwareFormat(Enum):
|
|||||||
TREZOR_ONE_V2 = 3
|
TREZOR_ONE_V2 = 3
|
||||||
|
|
||||||
|
|
||||||
FirmwareType = NewType("FirmwareType", c.Container)
|
ParsedFirmware = Tuple[FirmwareFormat, c.Container]
|
||||||
ParsedFirmware = Tuple[FirmwareFormat, FirmwareType]
|
|
||||||
|
|
||||||
|
|
||||||
def parse(data: bytes) -> ParsedFirmware:
|
def parse(data: bytes) -> ParsedFirmware:
|
||||||
if data[:4] == b"TRZR":
|
if data[:4] == b"TRZR":
|
||||||
version = FirmwareFormat.TREZOR_ONE
|
version = FirmwareFormat.TREZOR_ONE
|
||||||
cls = FirmwareOne
|
cls = LegacyFirmware
|
||||||
elif data[:4] == b"TRZV":
|
elif data[:4] == b"TRZV":
|
||||||
version = FirmwareFormat.TREZOR_T
|
version = FirmwareFormat.TREZOR_T
|
||||||
cls = Firmware
|
cls = VendorFirmware
|
||||||
elif data[:4] == b"TRZF":
|
elif data[:4] == b"TRZF":
|
||||||
version = FirmwareFormat.TREZOR_ONE_V2
|
version = FirmwareFormat.TREZOR_ONE_V2
|
||||||
cls = FirmwareOneV2
|
cls = FirmwareImage
|
||||||
else:
|
else:
|
||||||
raise ValueError("Unrecognized firmware image type")
|
raise ValueError("Unrecognized firmware image type")
|
||||||
|
|
||||||
@ -261,10 +273,10 @@ def parse(data: bytes) -> ParsedFirmware:
|
|||||||
fw = cls.parse(data)
|
fw = cls.parse(data)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise FirmwareIntegrityError("Invalid firmware image") from e
|
raise FirmwareIntegrityError("Invalid firmware image") from e
|
||||||
return version, FirmwareType(fw)
|
return version, fw
|
||||||
|
|
||||||
|
|
||||||
def digest_onev1(fw: FirmwareType) -> bytes:
|
def digest_onev1(fw: c.Container) -> bytes:
|
||||||
return hashlib.sha256(fw.code).digest()
|
return hashlib.sha256(fw.code).digest()
|
||||||
|
|
||||||
|
|
||||||
@ -286,7 +298,7 @@ def check_sig_v1(
|
|||||||
key_idx = key_indexes[i] - 1
|
key_idx = key_indexes[i] - 1
|
||||||
signature = signatures[i]
|
signature = signatures[i]
|
||||||
|
|
||||||
if key_idx not in V1_BOOTLOADER_KEYS:
|
if key_idx >= len(V1_BOOTLOADER_KEYS):
|
||||||
# unknown pubkey
|
# unknown pubkey
|
||||||
raise InvalidSignatureError("Unknown key in slot {}".format(i))
|
raise InvalidSignatureError("Unknown key in slot {}".format(i))
|
||||||
|
|
||||||
@ -310,60 +322,75 @@ def header_digest(
|
|||||||
return hash_function(header_bytes).digest()
|
return hash_function(header_bytes).digest()
|
||||||
|
|
||||||
|
|
||||||
def digest_v2(fw: FirmwareType) -> bytes:
|
def digest_v2(fw: c.Container) -> bytes:
|
||||||
return header_digest(fw.image.header, FirmwareHeader, blake2s)
|
return header_digest(fw.image.header, FirmwareHeader, blake2s)
|
||||||
|
|
||||||
|
|
||||||
def digest_onev2(fw: FirmwareType) -> bytes:
|
def digest_onev2(fw: c.Container) -> bytes:
|
||||||
return header_digest(fw.header, FirmwareHeader, hashlib.sha256)
|
return header_digest(fw.header, FirmwareHeader, hashlib.sha256)
|
||||||
|
|
||||||
|
|
||||||
def validate_code_hashes(
|
def calculate_code_hashes(
|
||||||
fw: FirmwareType,
|
code: bytes,
|
||||||
|
code_offset: int,
|
||||||
hash_function: Callable = blake2s,
|
hash_function: Callable = blake2s,
|
||||||
chunk_size: int = V2_CHUNK_SIZE,
|
chunk_size: int = V2_CHUNK_SIZE,
|
||||||
padding_byte: bytes = None,
|
padding_byte: bytes = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
for i, expected_hash in enumerate(fw.firmware_header.hashes):
|
hashes = []
|
||||||
|
for i in range(16):
|
||||||
if i == 0:
|
if i == 0:
|
||||||
# Because first chunk is sent along with headers, there is less code in it.
|
# Because first chunk is sent along with headers, there is less code in it.
|
||||||
chunk = fw.code[: chunk_size - fw._code_offset]
|
chunk = code[: chunk_size - code_offset]
|
||||||
else:
|
else:
|
||||||
# Subsequent chunks are shifted by the "missing header" size.
|
# Subsequent chunks are shifted by the "missing header" size.
|
||||||
ptr = i * chunk_size - fw._code_offset
|
ptr = i * chunk_size - code_offset
|
||||||
chunk = fw.code[ptr : ptr + chunk_size]
|
chunk = code[ptr : ptr + chunk_size]
|
||||||
|
|
||||||
# padding for last chunk
|
# padding for last chunk
|
||||||
if padding_byte is not None and i > 1 and chunk and len(chunk) < chunk_size:
|
if padding_byte is not None and i > 1 and chunk and len(chunk) < chunk_size:
|
||||||
chunk += padding_byte[0:1] * (chunk_size - len(chunk))
|
chunk += padding_byte[0:1] * (chunk_size - len(chunk))
|
||||||
|
|
||||||
if not chunk and expected_hash == b"\0" * 32:
|
if not chunk:
|
||||||
continue
|
hashes.append(b"\0" * 32)
|
||||||
chunk_hash = hash_function(chunk).digest()
|
else:
|
||||||
if chunk_hash != expected_hash:
|
hashes.append(hash_function(chunk).digest())
|
||||||
raise FirmwareIntegrityError("Invalid firmware data.")
|
|
||||||
|
return hashes
|
||||||
|
|
||||||
|
|
||||||
def validate_onev2(fw: FirmwareType, allow_unsigned: bool = False) -> None:
|
def validate_code_hashes(fw: c.Container, version: FirmwareFormat) -> None:
|
||||||
|
if version == FirmwareFormat.TREZOR_ONE_V2:
|
||||||
|
image = fw
|
||||||
|
hash_function = hashlib.sha256
|
||||||
|
chunk_size = ONEV2_CHUNK_SIZE
|
||||||
|
padding_byte = b"\xff"
|
||||||
|
else:
|
||||||
|
image = fw.image
|
||||||
|
hash_function = blake2s
|
||||||
|
chunk_size = V2_CHUNK_SIZE
|
||||||
|
padding_byte = None
|
||||||
|
|
||||||
|
expected_hashes = calculate_code_hashes(
|
||||||
|
image.code, image._code_offset, hash_function, chunk_size, padding_byte
|
||||||
|
)
|
||||||
|
if expected_hashes != image.header.hashes:
|
||||||
|
raise FirmwareIntegrityError("Invalid firmware data.")
|
||||||
|
|
||||||
|
|
||||||
|
def validate_onev2(fw: c.Container, allow_unsigned: bool = False) -> None:
|
||||||
try:
|
try:
|
||||||
check_sig_v1(
|
check_sig_v1(
|
||||||
digest_onev2(fw),
|
digest_onev2(fw), fw.header.v1_key_indexes, fw.header.v1_signatures,
|
||||||
fw.firmware_header.v1_key_indexes,
|
|
||||||
fw.firmware_header.v1_signatures,
|
|
||||||
)
|
)
|
||||||
except Unsigned:
|
except Unsigned:
|
||||||
if not allow_unsigned:
|
if not allow_unsigned:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
validate_code_hashes(
|
validate_code_hashes(fw, FirmwareFormat.TREZOR_ONE_V2)
|
||||||
fw,
|
|
||||||
hash_function=hashlib.sha256,
|
|
||||||
chunk_size=ONEV2_CHUNK_SIZE,
|
|
||||||
padding_byte=b"\xFF",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def validate_onev1(fw: FirmwareType, allow_unsigned: bool = False) -> None:
|
def validate_onev1(fw: c.Container, allow_unsigned: bool = False) -> None:
|
||||||
try:
|
try:
|
||||||
check_sig_v1(digest_onev1(fw), fw.key_indexes, fw.signatures)
|
check_sig_v1(digest_onev1(fw), fw.key_indexes, fw.signatures)
|
||||||
except Unsigned:
|
except Unsigned:
|
||||||
@ -373,7 +400,7 @@ def validate_onev1(fw: FirmwareType, allow_unsigned: bool = False) -> None:
|
|||||||
validate_onev2(fw.embedded_onev2, allow_unsigned)
|
validate_onev2(fw.embedded_onev2, allow_unsigned)
|
||||||
|
|
||||||
|
|
||||||
def validate_v2(fw: FirmwareType, skip_vendor_header: bool = False) -> None:
|
def validate_v2(fw: c.Container, skip_vendor_header: bool = False) -> None:
|
||||||
vendor_fingerprint = header_digest(fw.vendor_header, VendorHeader)
|
vendor_fingerprint = header_digest(fw.vendor_header, VendorHeader)
|
||||||
fingerprint = digest_v2(fw)
|
fingerprint = digest_v2(fw)
|
||||||
|
|
||||||
@ -399,23 +426,23 @@ def validate_v2(fw: FirmwareType, skip_vendor_header: bool = False) -> None:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
cosi.verify_m_of_n(
|
cosi.verify_m_of_n(
|
||||||
fw.firmware_header.signature,
|
fw.image.header.signature,
|
||||||
fingerprint,
|
fingerprint,
|
||||||
fw.vendor_header.vendor_sigs_required,
|
fw.vendor_header.vendor_sigs_required,
|
||||||
fw.vendor_header.vendor_sigs_n,
|
fw.vendor_header.vendor_sigs_n,
|
||||||
fw.firmware_header.sigmask,
|
fw.image.header.sigmask,
|
||||||
fw.vendor_header.pubkeys,
|
fw.vendor_header.pubkeys,
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
raise InvalidSignatureError("Invalid firmware signature.")
|
raise InvalidSignatureError("Invalid firmware signature.")
|
||||||
|
|
||||||
# XXX expiry is not used now
|
# XXX expiry is not used now
|
||||||
# if time.gmtime(fw.firmware_header.expiry) < now:
|
# if time.gmtime(fw.image.header.expiry) < now:
|
||||||
# raise ValueError("Firmware header expired.")
|
# raise ValueError("Firmware header expired.")
|
||||||
validate_code_hashes(fw)
|
validate_code_hashes(fw, FirmwareFormat.TREZOR_T)
|
||||||
|
|
||||||
|
|
||||||
def digest(version: FirmwareFormat, fw: FirmwareType) -> bytes:
|
def digest(version: FirmwareFormat, fw: c.Container) -> bytes:
|
||||||
if version == FirmwareFormat.TREZOR_ONE:
|
if version == FirmwareFormat.TREZOR_ONE:
|
||||||
return digest_onev1(fw)
|
return digest_onev1(fw)
|
||||||
elif version == FirmwareFormat.TREZOR_ONE_V2:
|
elif version == FirmwareFormat.TREZOR_ONE_V2:
|
||||||
@ -427,7 +454,7 @@ def digest(version: FirmwareFormat, fw: FirmwareType) -> bytes:
|
|||||||
|
|
||||||
|
|
||||||
def validate(
|
def validate(
|
||||||
version: FirmwareFormat, fw: FirmwareType, allow_unsigned: bool = False
|
version: FirmwareFormat, fw: c.Container, allow_unsigned: bool = False
|
||||||
) -> None:
|
) -> None:
|
||||||
if version == FirmwareFormat.TREZOR_ONE:
|
if version == FirmwareFormat.TREZOR_ONE:
|
||||||
return validate_onev1(fw, allow_unsigned)
|
return validate_onev1(fw, allow_unsigned)
|
||||||
|
Loading…
Reference in New Issue
Block a user