Remove risky patching of signed firmware headers
This commit is contained in:
parent
6d5cdb47a6
commit
761a1523a4
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user