KeepKey under the hood.

RTM KeepKey vs Developer KeepKey

In followup of Stellaw’s mighty first look teardown, and my own second look, I would like to post a few observations on the differences between the Release to Manufacturing KeepKeys over the Developer preview KeepKeys.

Both arrived in the same well protected cardboard box with FedEx tracking.

The RTM keepkey box was shrink wrapped and with 2 tamper proof holographic stickers.

(This in contrast to how my First Edition TREZOR arrived)

Inside was a stitched (?p)leather sleeve for storing the recovery card, instead of the cardboard sleeve from the developer edition, as well a Chrome extension quickstart guide and Warranty pamphlet. The green tray has been upgraded to thicker cardboard.

The biggest improvement is in the top button. Apparently the metal buttons were not ready in time for the developer units, so they shipped with a plastic button. As a result, on the top/right sides the developer units had a small compressible gap between the front and rear shells. This has been eliminated in the RTM production run.

Under the Hood

Unlike TREZOR, Keepkey have not released their board schematics.

TREZOR uses :

  • 1x OLED display 128x64 UG-2864HSWEG01
  • 1x STM32F205RE

KeepKey uses :

Thus KeepKey has 1MB flash vs 512kB flash for TREZOR

Flash Memory Layout

TREZOR

flash memory layout: 
name | range | size | function
— — — — — -+ — — — — — — — — — — — — -+ — — — — -+ — — — — — — — — — Sector 0 | 0x08000000–0x08003FFF | 16 KiB | bootloader code
Sector 1 | 0x08004000–0x08007FFF | 16 KiB | bootloader code
— — — — — -+ — — — — — — — — — — — — -+ — — — — -+ — — — — — — — — — Sector 2 | 0x08008000–0x0800BFFF | 16 KiB | metadata area
Sector 3 | 0x0800C000–0x0800FFFF | 16 KiB | metadata area
— — — — — -+ — — — — — — — — — — — — -+ — — — — -+ — — — — — — — — — Sector 4 | 0x08010000–0x0801FFFF | 64 KiB | application code
Sector 5 | 0x08020000–0x0803FFFF | 128 KiB | application code
Sector 6 | 0x08040000–0x0805FFFF | 128 KiB | application code
Sector 7 | 0x08060000–0x0807FFFF | 128 KiB | application code
===========+=========================+============================ Sector 8 | 0x08080000–0x0809FFFF | 128 KiB | N/A
Sector 9 | 0x080A0000–0x080BFFFF | 128 KiB | N/A
Sector 10 | 0x080C0000–0x080DFFFF | 128 KiB | N/A
Sector 11 | 0x080E0000–0x080FFFFF | 128 KiB | N/A

Interestingly Trezor’s firmware v1.3.3 is only 172kB, whereas v1.3.4 firmware is 262kB, still leaving over 180kB spare

KeepKey

flash memory layout: 
— — — — — — — — — —
name | range | size | function
— — — — — -+ — — — — — — — — — — — — -+ — — — — -+ — — — — — — — — — Sector 0 | 0x08000000–0x08003FFF | 16 KiB | bootstrap code (Read Only)
Sector 1 | 0x08004000–0x08007FFF | 16 KiB | empty(Read/Write)
— — — — — -+ — — — — — — — — — — — — -+ — — — — -+ — — — — — — — — — Sector 2 | 0x08008000–0x0800BFFF | 16 KiB | empty (Read/Write) Sector 3 | 0x0800C000–0x0800FFFF | 16 KiB | storage/config (Read/Write)
— — — — — -+ — — — — — — — — — — — — -+ — — — — -+ — — — — — — — — — Sector 4 | 0x08010000–0x0801FFFF | 64 KiB | empty (Read/Write) Sector 5 | 0x08020000–0x0803FFFF | 128 KiB | bootloader code (Read Only)
Sector 6 | 0x08040000–0x0805FFFF | 128 KiB | bootloader code (Read Only)
Sector 7 | 0x08060000–0x0807FFFF | 128 KiB | application code(Read/Write)
===========+=========================+============================ Sector 8 | 0x08080000–0x0809FFFF | 128 KiB | application code (Read/Write)
Sector 9 | 0x080A0000–0x080BFFFF | 128 KiB | application code (Read/Write)
Sector 10 | 0x080C0000–0x080DFFFF | 128 KiB | application code (Read/Write)
Sector 11 | 0x080E0000–0x080FFFFF | 128 KiB | application code (Read/Write)

KeepKey’s firmware v1.0.3 is 302kB, plenty of room for fun additions. KeepKey does rotate the storage segment between Sectors 1,2 and 3 for flash wear levelling.

Open source deterministic bootloader and firmware.

KeepKey’s codebase is a fork of TREZOR’s. The factory installed bootstrap and bootloader are in write protected segments 0, 5 and 6.

The bootloader checks the firmware’s header and code signature then loads the firmware. The bootloader can load unsigned firmware, after first displaying a warning that requires user confirmation each power cycle.

The firmware itself calculates the bootloader’s SHA256 hash, which is viewable with get_features

$ ./cmdkk.py get_features
vendor: “keepkey.com”
major_version: 1
minor_version: 0
patch_version: 3
….
initialized: false
revision: “hex(086041a2cd4c9cdb814cd71b1c22801d7db68da1)”
bootloader_hash: “hex(6465bc505586700a8111c4bf7db6f40af73e720f9e488d20db56135e5a690c4f)”
imported: false
pin_cached: false
passphrase_cached: false

The entire flash can be dumped by custom firmware, including bootloader and firmware code segments, and the storage sector.

Downgrading from firmware v1.0.3 or flashing unsigned firmware wipes the storage sector.

#!/usr/bin/python
import argparse
import hashlib
import struct
import binascii
import ecdsa
x = open(‘bootloader.RTM.bin’, ‘r’).read()
h = hashlib.sha256()
h.update(x)
x = h.digest()
h = hashlib.sha256()
h.update(x)
print h.hexdigest()

With this python script, our dumped segments 5+6 (0x08020000–0x0805FFFF) produce the same SHA256 hash.

$ ./bootloader_hash_KeepKey.py
6465bc505586700a8111c4bf7db6f40af73e720f9e488d20db56135e5a690c4f

KeepKey allows for Docker deterministic reproducible builds, the same principle as the tor project. The provided OSX instructions were very simple.

Ok, here are OS X instructions.
First, download the Docker Toolbox at Kitematic: https://kitematic.com. This is a newish tool Docker released that combines everything to make Docker setup a lot easier for the Mac. Once that toolbox is installed (it installs several Apps to make Docker work), run “Kinematic (BETA)” App. The first time it will take a bit to load as it sets up the virtual environment.
Then, in the File menu of Kitematice, click the “Open Docker Command Line Terminal”. In that terminal navigate to cheeky firmware.
$ docker build -t keepkey/firmware .
This builds the docker image we will build in, and the toolchain used for compiling. When that is complete, you can go ahead and compile using:
./docker_build_release.sh
If you can get it successfully compiled using those commands, the next step is to make sure you have the correct tag checked out of the firmware that you are trying to match (ie. git checkout v1.0.2). Then simply build again:
$ ./docker_build_release.sh

With Kitematic I was able to reproducibly build firmware (and bootloaders) for v1.0.2

Build succeeded.
******************************************************************** * KeepKey Application Fingerprint * ********************************************************************* e56f6ec523249ef67928a01aef529d36e952ef317a5a9944ebeb480ae251b59b -

and v1.0.3

Build succeeded. ********************************************************************* * KeepKey Application Fingerprint * ********************************************************************* 52f3db3f12f6c6de1da11ce0186dcf072a49f15e4d734daaa9a40e5d68748e4d -

The official signed firmware can be found here.

A modified TREZOR firmware signing script, with KPKY magic and public keys, confirms the same firmware fingerprints as our docker deterministic builds.

$ python firmware_signKeepKey.py -v -f “keepkey_main(1.02).bin”
Firmware size 301408 bytes
Firmware fingerprint: e56f6ec523249ef67928a01aef529d36e952ef317a5a9944ebeb480ae251b59b
Slot #1 signature: VALID 866cca1d0c346921be2ec0316f894c17b473743985bde1bdeb77268c844f9562ab678a71c2d9f58273500924f0e8dda237099aa25820e017c39d213c33d3f98c
Slot #2 signature: VALID a2de0f63734ed59661c0823b8e62b3cf71ba2aa242f0079822ae1442867f737120218a4287c3bcc3f5a85fea6daab1ea5954bff6e7bd5b5c89cf3dca877b060e
Slot #3 signature: VALID eb214c595a528eb617abf104a882578c1e9d706a97a10ed28f139723943dfb72e6ad1dd96643c924ba0d70a11012eb3bc69bfa7c91132861e297c5c91239ba95
$ python firmware_signKeepKey.py -v -f “keepkey_main(1.03).bin” 
Firmware size 301760 bytes
Firmware fingerprint: 52f3db3f12f6c6de1da11ce0186dcf072a49f15e4d734daaa9a40e5d68748e4d Slot #1 signature: VALID 329b60b688b184805d8443db5bd8da43a8f98ba294c4add0f466b69c0d35e8a0302ef6a02e93901c296ed2c9c7f2abebebe50060a00245c863f9ed805d451185 Slot #2 signature: VALID c32a19006d6391fe03ec95874c14a97956bb536053059c4df26dbdd06d674e862a5f58364ee2000035b874363b16620f29ed923e9b6b111c5e7b48c95d06bcfe Slot #3 signature: VALID c4a5b2d5b53922329d086ddbb11ce8d522586b9ab9d3beb3fe1e4bc17dd533e9ad12e47caac3286ac34cd612399e1a2733c565eec3ea93327e2d56be871333c8

The factory installed bootloader, dumped then cropped to size, is identical to the v1.0.3 docker built bootloader.

$ md5 cropped_RTM_bootloader.bin bootloader_main.bin
MD5 (cropped_RTM_bootloader.bin) = 79f7c2f3b76bba4d751ca5545b7b266c
MD5 (bootloader_main.bin) = 79f7c2f3b76bba4d751ca5545b7b266c

Lastly, dumping the application segment (0x08060000–0x080FFFFF) of our custom firmware when cropped to size produces the identical md5 as that of our built custom firmware loaded on KeepKey with firmware_update.

In Conclusion …

  • The bootloader source code shows no obvious backdoors
  • The firmware calculates the bootloader’s SHA256 hash
  • Custom firmware loaded on KeepKey is unaltered by ./cmdkk.py firmware_update, and calculates the same bootloader SHA256 hash as official signed firmware does.
  • A Deterministically built bootloader v1.0.3 is identical the the factory installed bootloader
  • Deterministically built firmware v1.0.2 and v1.0.3 is identical to the official release firmware (excluding the signature header)
  • Dumping and analyzing the “empty” segments and free space of the “code” segments reveals them to be blank “FF” with no hidden code (apart from the rotating storage segment)
  • Reverse engineering the dumped bootloader in IDA finds no apparent hidden back doors.
  • KeepKey ships in a FedEx trackable, signature on delivery, shrink wrapped, tamper-evident-holographic-sticker-secured box. KeepKey is not open hardware, but stellaw has published a teardown of a developer unit.
One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.