Remove risky patching of signed firmware headers

This commit is contained in:
Peter D. Gray 2020-02-20 08:56:06 -05:00
parent 6d5cdb47a6
commit 761a1523a4
8 changed files with 26 additions and 60 deletions

View File

@ -108,8 +108,7 @@ def show_version(fname):
@main.command('check')
@click.argument('fname', default='firmware-signed.bin')
@click.option('--no-patch', '-x', is_flag=True, help='Dont mask-out hw_compat field')
def readback(fname, no_patch=False):
def readback(fname):
"Verify pubkey and signature used in binary file"
data = open(fname, 'rb').read()
@ -134,13 +133,6 @@ def readback(fname, no_patch=False):
nv += ' HIGH_WATER'
v = nv
elif fld == 'hw_compat':
if v and not no_patch:
# mask it out for signature purposes
assert FWH_HWC_NUM_OFFSET == FW_HEADER_SIZE - 64 - 28 - 4
xo = FW_HEADER_OFFSET+FWH_HWC_NUM_OFFSET
data = bytearray(data)
data[xo:xo+4] = b'\0\0\0\0'
nv = '0x%x => ' % v
d = []
if v & MK_1_OK: d.append('Mk1')
@ -149,8 +141,6 @@ def readback(fname, no_patch=False):
if v & ~(MK_1_OK | MK_2_OK | MK_3_OK):
d.append('?other?')
v = nv + '+'.join(d)
if not no_patch:
v += " (masked out of sig)"
elif fld == 'timestamp':
v = str(b2a_hex(v), 'ascii')
nv = '20' + '-'.join(v[i:i+2] for i in range(0, 6, 2)) + ' '
@ -211,14 +201,11 @@ def doit(keydir, outfn=None, build_dir='l-port/build-COLDCARD', high_water=False
assert len(vectors) <= FW_HEADER_OFFSET, "isr vectors area is too big!"
assert len(body) >= FW_MIN_LENGTH, "main firmware is too small: %d" % len(body)
if version == '3.0.7':
# transition version: does not specify hw compact but can check it for future
hw_compat = 0
else:
hw_compat = CURRENT_HARDWARE
if force_hw_compat is not None:
hw_compat = int(force_hw_compat, 16)
click.echo("Overriding hw_compat field: 0x%02x" % hw_compat)
else:
hw_compat = CURRENT_HARDWARE
body_len = align_to(len(body), 512)
assert body_len % 512 == 0, body_len
@ -232,17 +219,21 @@ def doit(keydir, outfn=None, build_dir='l-port/build-COLDCARD', high_water=False
version_string=version,
firmware_length=FW_HEADER_OFFSET+FW_HEADER_SIZE+body_len,
install_flags=(FWHIF_HIGH_WATER if high_water else 0x0),
hw_compat=0, # see below
hw_compat=hw_compat,
future=b'\0'*(4*FWH_NUM_FUTURE),
signature=b'\xff'*64,
pubkey_num=pubkey_num,
timestamp=timestamp(backdate) )
assert FW_MIN_LENGTH <= hdr.firmware_length <= FW_MAX_LENGTH, hdr.firmware_length
assert hdr.firmware_length <= 786432-128, \
"too big for our-protocol USB upgrades: %d = %d bytes too big" % (
hdr.firmware_length, hdr.firmware_length-(786432-128))
# actual file length limited by size of SPI flash area reserved to txn data/uploads
USB_MAX_LEN = (786432-128)
assert hdr.firmware_length <= USB_MAX_LEN, \
"too big for our USB upgrades: %d = %d bytes too big" % (
hdr.firmware_length, hdr.firmware_length-USB_MAX_LEN)
print("Remaining flash space: %d bytes" % (USB_MAX_LEN - hdr.firmware_length))
binhdr = struct.pack(FWH_PY_FORMAT, *hdr)
assert len(binhdr) == FW_HEADER_SIZE
@ -265,13 +256,6 @@ def doit(keydir, outfn=None, build_dir='l-port/build-COLDCARD', high_water=False
from ecdsa.util import sigencode_string
sig = sk.sign_digest(fw_hash, sigencode=sigencode_string)
# Final header has non-zero hw_compat field which will break
# signature, if it is ever seen by older code that did not clear
# that field (after checking compatibility)
if hw_compat != 0:
modhdr = hdr._replace(hw_compat=hw_compat)
binhdr = struct.pack(FWH_PY_FORMAT, *modhdr)
assert len(sig) == 64
final = binhdr[:-64] + sig
assert len(final) == FW_HEADER_SIZE

View File

@ -1,22 +1,17 @@
## 3.1.0 - Feb ??, 2020
## 3.1.0 - Feb 20, 2020
- HSM (Hardware Security Module) mode: give Coldcard spending rules, including whitelisted
addresses, velocity limits, subsets of authorizing users ... and Coldcard can sign with
no human present. Requires companion software to setup (ck-bunker or ckcc-protocol) to
enabled and disabled by default, with multi-step on-screen confirmation to enable.
no human present. Requires companion software to setup (ckbunker or ckcc-protocol),
and disabled by default, with multi-step on-screen confirmation required to enable. Mk3 only.
- Enhancement: New "user management" menu. Advanced > User Management shows a menu
with usernames, some details and a 'delete user' command. USB commands must be used to
create user accounts and they are only used to authenticate human approvals in HSM mode.
create user accounts and they are only used to authenticate txn approvals in HSM mode.
- Enhancement: PSBT transaction can be "visualized" over USB, meaning you can view what
the Coldcard will show on the screen during approval process, as text, downloaded over USB.
That text can be signed (always with root key) to prove authenticity.
## 3.0.7 - Feb ??, 2020
- IMPORTANT NOTE: You must upgrade to this version before any subsequent
version (regardless of your hardware).
- Enhancement: Sending large PSBT files, and firmware upgrades over USB should be faster.
- Final version to support Mk1 hardware.
- Enhancement: Sending large PSBT files, and firmware upgrades over USB should be a little faster.
- IMPORTANT: This release is NOT COMPATIBLE with Mk1 hardware. It will brick Mk1 Coldcards.
## 3.0.6 - Dec 19, 2019
@ -33,6 +28,7 @@
- Bugfix: add blank line between addresses shown if sending to multiple destinations.
- Bugfix: multisig outputs were not checked to see if they are change (would have been
shown as regular outputs), if the PSBT did not have XPUB data in globals section.
- NOTE: This is the final version to support Mk1 hardware.
## 3.0.5 - Nov 25, 2019

View File

@ -277,10 +277,9 @@ def check_firmware_hdr(hdr, binary_size=None, bad_magic_ok=False):
# Check basics of new firmware being loaded. Return text of error msg if any.
# - basic checks only: for confused customers, not attackers.
# - hdr must be a bytearray(FW_HEADER_SIZE+more)
# - hdr will be updated in-place, and caller must get that into flash @ FW_HEADER_OFFSET
from sigheader import FW_HEADER_SIZE, FW_HEADER_MAGIC, FWH_PY_FORMAT
from sigheader import MK_1_OK, MK_2_OK, MK_3_OK, FWH_HWC_NUM_OFFSET
from sigheader import MK_1_OK, MK_2_OK, MK_3_OK
from ustruct import unpack_from
from version import hw_label
@ -317,10 +316,6 @@ def check_firmware_hdr(hdr, binary_size=None, bad_magic_ok=False):
if not ok:
return "New firmware doesn't support this version of Coldcard hardware (%s)."%hw_label
# Patch the header, so hw_compat field is zero
# - the signature value is based on those bytes being zero
hdr[FWH_HWC_NUM_OFFSET:FWH_HWC_NUM_OFFSET+4] = b'\0\0\0\0'
return None

View File

@ -42,12 +42,12 @@ firmware-signed.dfu: firmware-signed.bin Makefile
# make the DFU file which is shared for upgrades
dfu: firmware-signed.dfu
# Build a binary, signed w/ production key but w/ hw-compat=0x0 for direct DFU loading
# Build a binary, signed w/ production key
# - always rebuild binary
.PHONY: dev.dfu
dev.dfu: l-port/build-COLDCARD/firmware?.bin
cd $(PORT_TOP) && $(MAKE) $(MAKE_ARGS)
$(SIGNIT) sign $(VERSION_STRING) --force-hw-compat 0x0 -o dev.bin
$(SIGNIT) sign $(VERSION_STRING) -o dev.bin
$(PYTHON_MAKE_DFU) -b $(FIRMWARE_BASE):dev.bin dev.dfu
# This is fast for Coinkite devs, but no DFU support in the wild.

View File

@ -57,9 +57,6 @@ typedef struct {
// offset of pubkey number
#define FWH_PK_NUM_OFFSET 20
// offset of hw_compat field (4 bytes)
#define FWH_HWC_NUM_OFFSET (128 - 64 - 32)
// Bits in install_flags
#define FWHIF_HIGH_WATER 0x01

View File

@ -42,9 +42,6 @@ FWH_NUM_FUTURE = 7
# offset of pubkey number
FWH_PK_NUM_OFFSET = 20
# offset of hw_compat field (4 bytes)
FWH_HWC_NUM_OFFSET = (128 - 64 - 32)
# Bits in install_flags
FWHIF_HIGH_WATER = 0x01

View File

@ -101,7 +101,7 @@ def sim_card_ejected(sim_exec, is_simulator):
if not is_simulator():
# assuming no card on device
if not ejected:
raise pytest.fail('only on simulator')
raise pytest.fail('cant insert on real dev')
else:
return
@ -110,7 +110,8 @@ def sim_card_ejected(sim_exec, is_simulator):
assert sim_exec(cmd) == 'ok'
yield doit
doit(False)
if is_simulator():
doit(False)
@pytest.fixture(scope='module')
def send_ux_abort(simulator):

View File

@ -107,10 +107,6 @@ def test_hacky_upgrade(mode, transport, dev, sim_exec, make_firmware, upload_fil
assert cooked.magic_value == FW_HEADER_MAGIC
assert cooked.firmware_length == len(data)
patched = struct.pack(FWH_PY_FORMAT, *cooked._replace(hw_compat=0))
if mode == 'nocheck':
assert patched == hdr
if mode == 'incompat':
if transport == 'usb':
with pytest.raises(CCProtoError) as ee:
@ -127,11 +123,11 @@ def test_hacky_upgrade(mode, transport, dev, sim_exec, make_firmware, upload_fil
else:
upgrade_by_sd(data)
# check data was patched as uploading (2 spots)
# check data was uploaded verbatim (VERY SLOW)
for pos in range(0, cooked.firmware_length + 128, 128):
a = eval(sim_eval(f'main.sf.array[{pos}:{pos+128}]'))
if pos in [ FW_HEADER_OFFSET, cooked.firmware_length]:
assert a == patched, f"wrong @ {pos}"
assert a == hdr, f"wrong @ {pos}"
else:
assert a == data[pos:pos+128], repr(pos)