From 345c90ccba1719067859becfd1bcbf1a50d13b35 Mon Sep 17 00:00:00 2001 From: Martin Milata Date: Mon, 15 Nov 2021 17:40:56 +0100 Subject: [PATCH] docs(common): add section on reproducible builds --- docs/SUMMARY.md | 2 + docs/common/reproducible-build.md | 81 ++++++++++++++++++++++ docs/hardware/model-one/firmware-format.md | 79 +++++++++++++++++++++ 3 files changed, 162 insertions(+) create mode 100644 docs/common/reproducible-build.md create mode 100644 docs/hardware/model-one/firmware-format.md diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 5882198b1..78828a07f 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -21,12 +21,14 @@ - [Boot stages](hardware/model-t/boot.md) - [Memory layout](hardware/model-t/memory.md) - [Model One](hardware/model-one/index.md) + - [Firmware format](hardware/model-one/firmware-format.md) - [Common](common/index.md) - [Communication](common/communication/index.md) - [Sessions](common/communication/sessions.md) - [Passphrase](common/communication/passphrase.md) - [Migration](common/communication/passphrase-redesign-migration.md) - [Bitcoin signing](common/communication/bitcoin-signing.md) + - [Reproducible builds](common/reproducible-build.md) - [Storage](storage/index.md) - [Tests](tests/index.md) - [Device Tests](tests/device-tests.md) diff --git a/docs/common/reproducible-build.md b/docs/common/reproducible-build.md new file mode 100644 index 000000000..24d2e15cf --- /dev/null +++ b/docs/common/reproducible-build.md @@ -0,0 +1,81 @@ +# Reproducible build + +We want to invite the wider community to participate in the verification of +the firmware built by SatoshiLabs. With reasonable effort you should be able to +build the firmware and verify that it's identical to the official firmware. + +Trezor Firmware uses [Nix](https://nixos.org/), [Poetry](https://python-poetry.org/) +and [Cargo](https://doc.rust-lang.org/cargo/) to make the build environment +deterministic. We also provide a Docker-based script so that the build can be +performed with a single command on usual x86 Linux system. + +## Building + +First you need to determine which *version tag* you want to build: +* for Trezor One it is `legacy/vX.Y.Z`, e.g. `legacy/v1.10.3`, +* for Trezor Model T it is `core/vX.Y.Z`, e.g. `core/v2.4.2`. + +Assuming you want to build `core/v2.4.2`: + +1. install [Docker](https://www.docker.com/) +2. clone the firmware repository: `git clone https://github.com/trezor/trezor-firmware.git` +3. go into the firmware directory: `cd trezor-firmware` +4. checkout the version tag: `git checkout core/v2.4.2` +5. run: `bash build-docker.sh core/v2.4.2` + +After the build finishes the firmware images are located in: +* `build/legacy/firmware/firmware.bin` and `build/legacy-bitcoinonly/firmware/firmware.bin` for Trezor One, +* `build/core/firmware/firmware.bin` and `build/core-bitcoinonly/firmware/firmware.bin` for Trezor Model T. + +## Verifying + +The result won't be bit-by-bit identical with the official images because the +official images are signed while local builds aren't. Official release of +Trezor One firmware also has additional 256-byte legacy header that needs to be +removed first. + +### Trezor T + +The [firmware header](../hardware/model-t/boot.md#firmware-header) contains 65 +bytes of signature data at offset 0x15bf. After overwriting it by zeros in +official release the binaries should become identical. + +``` +wget https://data.trezor.io/firmware/2/trezor-2.4.2.bin + +# the following line removes 65 bytes of signature data from the official firmware +dd if=/dev/zero of=trezor-2.4.2.bin bs=1 seek=5567 count=65 conv=notrunc + +# the following two lines print out the hashes of the firmwares +sha256sum trezor-2.4.2.bin +sha256sum build/core/firmware/firmware.bin +``` + +### Trezor One + +Official T1 firmware starts with [256-byte legacy header](../hardware/model-one/firmware-format.md) +used for compatibility with old bootloaders. Locally built firmware doesn't have this header. + +``` +wget https://data.trezor.io/firmware/1/trezor-1.10.3.bin + +# strip legacy header +tail -c +257 trezor-1.10.3.bin > trezor-1.10.3-nolegacyhdr.bin +``` + +The [v2 header](../hardware/model-one/firmware-format.md#v2-header) has 3x65 +bytes of signature data at offset 0x220. Overwrite by zeros to obtain image +identical to the one built locally. + +``` +dd if=/dev/zero of=trezor-1.10.3-nolegacyhdr.bin bs=1 seek=544 count=195 conv=notrunc + +sha256sum trezor-1.10.3-nolegacyhdr.bin +sha256sum build/legacy/firmware/firmware.bin +``` + +_Note: Fingerprints displayed for T1 at the end of `build-docker.sh` do not match fingerprints of +official firmware due to the legacy header._ + +_Note: T1 firmware built this way won't boot because unsigned firmware needs to be built with +[`MEMORY_PROTECT=0`](../legacy/index.md#combining-bootloader-and-firmware-with-various-memory_protect-settings-signedunsigned)._ diff --git a/docs/hardware/model-one/firmware-format.md b/docs/hardware/model-one/firmware-format.md new file mode 100644 index 000000000..e8421708e --- /dev/null +++ b/docs/hardware/model-one/firmware-format.md @@ -0,0 +1,79 @@ +# Trezor One firmware format + +Historically Trezor One has been using 256-byte header (w/ `TRZR` magic string) followed by the +actual firmware. Since version 1.8.0, different 1024-byte header (w/ `TRZF` magic string) is in use, +and building firmware from this repository produces firmware image containing such header followed +by firmware code. + +Official release firmware contains both these headers for compatibility with old bootloaders. That +means there is a 256-byte `TRZR` header followed by 1024-byte `TRZF` header followed by code. + +* Hash function used for computing data digest for signatures is SHA256. +* Signature system is ECDSA over SECP256k1. +* All multibyte integer values are little endian. + +## Legacy Header + +Total length of legacy header is always 256 bytes. + +| offset | length | name | description | +|-------:|-------:|------|-------------| +| 0x0000 | 4 | magic | firmware magic `TRZR` | +| 0x0004 | 4 | codelen | length of V2 header + code (length of code before 1.8.0) | +| 0x0008 | 1 | sigindex1 | index of key for `sig1` | +| 0x0009 | 1 | sigindex2 | index of key for `sig2` | +| 0x000A | 1 | sigindex3 | index of key for `sig3` | +| 0x000B | 1 | flags | unused since 1.8.0 (zeroed) | +| 0x000C | 52 | reserved | not used yet (zeroed) | +| 0x0040 | 64 | sig1 | signature #1 | +| 0x0080 | 64 | sig2 | signature #2 | +| 0x00C0 | 64 | sig3 | signature #3 | + +Signature verification: + +* Calculate SHA256 digest of firmware without this header. +* Verify signature `sig1` of the digest against public key with index `sigindex1` in [`V1_BOOTLOADER_KEYS`](../../../python/src/trezorlib/firmware.py). +* Repeat for `sig2` and `sig3`. Indexes must be distinct. + +## V2 Header + +This header has the same format as [Model T Firmware Header](../model-t/boot.md#firmware-header), +however due to different signature scheme the `sigmask` and `sig` fields are zeroed and part of the +reserved space is used for T1-specific fields `sig1`-`sig3`, `sigindex1-sigindex3`. Total length of +v2 header is always 1024 bytes. + +| offset | length | name | description | +|-------:|-------:|------|-------------| +| 0x0000 | 4 | magic | firmware magic `TRZF` | +| 0x0004 | 4 | hdrlen | length of the firmware header | +| 0x0008 | 4 | expiry | valid until timestamp (0=infinity) | +| 0x000C | 4 | codelen | length of the firmware code (without the header) | +| 0x0010 | 1 | vmajor | version (major) | +| 0x0011 | 1 | vminor | version (minor) | +| 0x0012 | 1 | vpatch | version (patch) | +| 0x0013 | 1 | vbuild | version (build) | +| 0x0014 | 1 | fix_vmajor | version of last critical bugfix (major) | +| 0x0015 | 1 | fix_vminor | version of last critical bugfix (minor) | +| 0x0016 | 1 | fix_vpatch | version of last critical bugfix (patch) | +| 0x0017 | 1 | fix_vbuild | version of last critical bugfix (build) | +| 0x0018 | 8 | reserved | not used yet (zeroed) | +| 0x0020 | 32 | hash1 | hash of the first code chunk excluding both the legacy and the v2 header (129792 B) | +| 0x0040 | 32 | hash2 | hash of the second code chunk (128 KiB), zeroed if unused | +| ... | ... | ... | ... | +| 0x0200 | 32 | hash16 | hash of the last possible code chunk (128 KiB), zeroed if unused | +| 0x0220 | 64 | sig1 | signature #1 | +| 0x0260 | 64 | sig2 | signature #2 | +| 0x02A0 | 64 | sig3 | signature #3 | +| 0x02E0 | 1 | sigindex1 | index of key for `sig1` | +| 0x02E1 | 1 | sigindex2 | index of key for `sig2` | +| 0x02E2 | 1 | sigindex3 | index of key for `sig3` | +| 0x02E3 | 220 | reserved | not used yet (zeroed) | +| 0x03BF | 1 | reserved_sigmask | unused in T1 (zeroed) | +| 0x03C0 | 64 | reserved_sig | unused in T1 (zeroed) | + +Signature verification: + +* Calculate SHA256 digest of the entire header with `sig1`-`sig3` and `sigindex1`-`sigindex3` zeroed + out. +* Verify signature `sig1` of the digest against public key with index `sigindex1` in [`V1_BOOTLOADER_KEYS`](../../../python/src/trezorlib/firmware.py). +* Repeat for `sig2` and `sig3`. Indexes must be distinct.