From aea85aacb3fd28b22fc2472a29ff8b63b29f63f1 Mon Sep 17 00:00:00 2001 From: iwanicki92 Date: Mon, 28 Sep 2026 23:22:48 +0200 Subject: [PATCH] key_store_file: accept signature type 0 in key store headers Key stores in early ThinkPad T14/X13 Gen 1 AMD (Renoir) releases have no $PS1 magic and a signature type of 0 at 0x34, next to a 0x200 byte signature that verifies against the certifying key. The header check required 2 there and the resulting AssertionError aborted the whole ROM. Name the two dwords at 0x30 and 0x34 as in HeaderFile (signed flag and signature type) and accept signature type 0 or 2. The signature size is still derived from the header sizes, so it stays 0x200 for these key stores. Signed-off-by: iwanicki92 --- psptool/key_store_file.py | 21 ++++--- tests/unit/test_key_store_file.py | 91 +++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 8 deletions(-) create mode 100644 tests/unit/test_key_store_file.py diff --git a/psptool/key_store_file.py b/psptool/key_store_file.py index fe17c96..523a22e 100644 --- a/psptool/key_store_file.py +++ b/psptool/key_store_file.py @@ -81,9 +81,13 @@ def __init__(self, file): self.certifying_id = KeyId(self, 0x10, buffer_offset=0x38) - self._unknown_constant_1 = NestedBuffer(self, 0x4, buffer_offset=0x30) - self._unknown_constant_2 = NestedBuffer(self, 0x4, buffer_offset=0x34) - assert self.unknown_constants == (b'\1\0\0\0', b'\2\0\0\0') + # Same fields as in a HeaderFile. Early Renoir key stores leave the + # signature type at 0 next to a 0x200 byte signature, so + # signature_size is derived from the sizes instead. + self._signed = NestedBuffer(self, 0x4, buffer_offset=0x30) + self._signature_type = NestedBuffer(self, 0x4, buffer_offset=0x34) + assert self.signed == 1 + assert self.signature_type in {0, 2} self._keystore_type = NestedBuffer(self, 0x4, buffer_offset=0x7c) assert self.keystore_type in File.KEY_STORE_TYPES or self.keystore_type == 0 @@ -130,11 +134,12 @@ def signature_size(self) -> int: return self.parent_buffer.buffer_size - self.body_size - self.HEADER_SIZE @property - def unknown_constants(self) -> (bytes, bytes, bytes): - return ( - self._unknown_constant_1.get_bytes(), - self._unknown_constant_2.get_bytes(), - ) + def signed(self) -> int: + return int.from_bytes(self._signed.get_bytes(), 'little') + + @property + def signature_type(self) -> int: + return int.from_bytes(self._signature_type.get_bytes(), 'little') class KeyStore(NestedBuffer): diff --git a/tests/unit/test_key_store_file.py b/tests/unit/test_key_store_file.py new file mode 100644 index 0000000..875c90b --- /dev/null +++ b/tests/unit/test_key_store_file.py @@ -0,0 +1,91 @@ +# PSPTool - Display, extract and manipulate PSP firmware inside UEFI images +# Copyright (C) 2026 contributors +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +"""Synthetic ROMs holding a single 0x50 key store. + +Key stores in early ThinkPad T14/X13 Gen 1 AMD releases have no $PS1 +magic and signature type 0 next to a 0x200 byte signature. +""" + +import contextlib +import io +import os +import struct +import tempfile +import unittest + +from psptool import PSPTool +from psptool.blob import Blob +from psptool.utils import fletcher32 + +ROM_SIZE = 8 * 1024 * 1024 +FET_OFFSET = Blob.POSSIBLE_FET_OFFSETS[0] +PSP_DIR_OFFSET = FET_OFFSET + 0x1000 +KEY_STORE_OFFSET = FET_OFFSET + 0x2000 +HEADER_FILE_SIZE = 0x100 + +# Directory additional_info: version bit set, address mode 1 (flash offset) +FLASH_OFFSET_MODE = (1 << 31) | (1 << 24) + + +def key_store_file(magic: bytes, signature_type: int) -> bytes: + modulus_size = 0x100 + key = struct.pack(' bytes: + blob = bytearray(ROM_SIZE) + blob[FET_OFFSET - 4:FET_OFFSET] = b'\xff' * 4 + blob[FET_OFFSET:FET_OFFSET + 4] = Blob._FIRMWARE_ENTRY_MAGIC + blob[FET_OFFSET + 4:FET_OFFSET + 8] = struct.pack('