New metadata format (#539)

* new metadata format is used

* Update bootloader/src/main.rs

Co-authored-by: ztoked <zhalvorsen@google.com>

* splits the metadata signed and unsigned parts evenly

* fixes pylint

Co-authored-by: ztoked <zhalvorsen@google.com>
This commit is contained in:
kaczmarczyck
2022-08-31 14:35:45 +02:00
committed by GitHub
parent 932924ea85
commit 598c21071e
7 changed files with 190 additions and 260 deletions

View File

@@ -20,7 +20,6 @@ from __future__ import division
from __future__ import print_function
import argparse
import datetime
import hashlib
import os
import struct
@@ -44,6 +43,7 @@ OPENSK_VID_PID = (0x1915, 0x521F)
OPENSK_VENDOR_UPGRADE = 0x42
OPENSK_VENDOR_UPGRADE_INFO = 0x43
PAGE_SIZE = 0x1000
METADATA_SIGN_OFFSET = 0x800
KERNEL_SIZE = 0x20000
APP_SIZE = 0x20000
PARTITION_ADDRESS = {
@@ -54,7 +54,15 @@ ES256_ALGORITHM = -7
ARCH = "thumbv7em-none-eabi"
def create_metadata(firmware_image: bytes, partition_address: int) -> bytes:
def hash_message(message: bytes) -> bytes:
"""Uses SHA256 to hash a message."""
sha256_hash = hashlib.sha256()
sha256_hash.update(message)
return sha256_hash.digest()
def create_metadata(firmware_image: bytes, partition_address: int, version: int,
priv_key: Any) -> bytes:
"""Creates the matching metadata for the given firmware.
The metadata consists of a timestamp, the expected address and a hash of
@@ -65,25 +73,26 @@ def create_metadata(firmware_image: bytes, partition_address: int) -> bytes:
partition_address: The address to be written as a metadata property.
Returns:
A byte array consisting of 32B hash, 4B timestamp and 4B partition address
in little endian encoding.
A byte array of page size, consisting of
- 32 B hash,
- 64 B signature,
at the beginning and
- 8 B version and
- 4 B partition address in little endian encoding
after METADATA_SIGN_OFFSET. All other bytes are 0xFF.
"""
t = datetime.datetime.utcnow().timestamp()
timestamp = struct.pack("<I", int(t))
if version < 0 or version >= 2**63:
fatal("The version must fit into an unsigned integer with 63 bit.\n"
"Please pass it using --version")
version_bytes = struct.pack("<Q", version)
partition_start = struct.pack("<I", partition_address)
sha256_hash = hashlib.sha256()
sha256_hash.update(firmware_image)
sha256_hash.update(timestamp)
sha256_hash.update(partition_start)
checksum = sha256_hash.digest()
return checksum + timestamp + partition_start
def hash_message(message: bytes) -> bytes:
"""Uses SHA256 to hash a message."""
sha256_hash = hashlib.sha256()
sha256_hash.update(message)
return sha256_hash.digest()
# Prefix sizes that are a multiple of 64 suit our bootloader's SHA.
signed_metadata = pad_to(version_bytes + partition_start,
PAGE_SIZE - METADATA_SIGN_OFFSET)
signed_data = signed_metadata + firmware_image
checksum = hash_message(signed_data)
signature = sign_firmware(signed_data, priv_key)
return pad_to(checksum + signature, METADATA_SIGN_OFFSET) + signed_metadata
def check_info(partition_address: int, authenticator: Any):
@@ -95,7 +104,8 @@ def check_info(partition_address: int, authenticator: Any):
data={},
)
if result[0x01] != partition_address:
fatal("Identifiers do not match.")
fatal(f"Identifiers do not match, received 0x{result[0x01]:0x}, "
f"expected 0x{partition_address:0x}.")
except ctap.CtapError as ex:
fatal(f"Failed to read OpenSK upgrade info (error: {ex})")
@@ -163,19 +173,14 @@ def sign_firmware(data: bytes, priv_key: Any) -> bytes:
def main(args):
colorama.init()
if not args.priv_key:
fatal("Please pass in a private key file using --private-key.")
firmware_image = generate_firmware_image(args.board)
partition_address = PARTITION_ADDRESS[args.board]
metadata = create_metadata(firmware_image, partition_address)
if not args.priv_key:
fatal("Please pass in a private key file using --private-key.")
priv_key = load_priv_key(args.priv_key)
signed_data = firmware_image + metadata[32:40]
signature = {
"alg": ES256_ALGORITHM,
"signature": sign_firmware(signed_data, priv_key)
}
metadata = create_metadata(firmware_image, partition_address, args.version,
priv_key)
if args.use_vendor_hid:
patcher = patch.object(hid.base, "FIDO_USAGE_PAGE", 0xFF00)
@@ -206,11 +211,13 @@ def main(args):
)
info("Writing metadata...")
cbor_data = {2: metadata, 3: hash_message(metadata), 4: signature}
# TODO Write the correct address when the metadata is transparent.
cbor_data = {2: metadata, 3: hash_message(metadata)}
authenticator.send_cbor(
OPENSK_VENDOR_UPGRADE,
data=cbor_data,
)
except ctap.CtapError as ex:
message = "Failed to upgrade OpenSK"
if ex.code.value == ctap.CtapError.ERR.INVALID_COMMAND:
@@ -263,4 +270,10 @@ if __name__ == "__main__":
dest="use_vendor_hid",
help=("Whether to upgrade the device using the Vendor HID interface."),
)
parser.add_argument(
"--version",
type=int,
dest="version",
help=("Firmware version that is built."),
)
main(parser.parse_args())