コード例 #1
0
ファイル: usb.py プロジェクト: Coldcard/firmware
    async def handle_download(self, offset, length, file_number):
        # let them read from where we store the signed txn
        # - filenumber can be 0 or 1: uploaded txn, or result
        from sflash import SF

        # limiting memory use here, should be MAX_BLK_LEN really
        length = min(length, MAX_BLK_LEN)

        assert 0 <= file_number < 2, 'bad fnum'
        assert 0 <= offset <= MAX_TXN_LEN, "bad offset"
        assert 1 <= length, 'len'

        # maintain a running SHA256 over what's sent
        if offset == 0:
            self.file_checksum = sha256()

        pos = (MAX_TXN_LEN * file_number) + offset

        resp = bytearray(4 + length)
        resp[0:4] = b'biny'
        SF.read(pos, memoryview(resp)[4:])

        self.file_checksum.update(memoryview(resp)[4:])

        return resp
コード例 #2
0
def clean_shutdown(style=0):
    # wipe SPI flash and shutdown (wiping main memory)
    import callgate
    from sflash import SF

    try:
        SF.wipe_most()
    except: pass

    callgate.show_logout(style)
コード例 #3
0
ファイル: nvram.py プロジェクト: ramsemune/firmware
def count_busy():
    from nvstore import SLOTS
    from sflash import SF

    busy = 0
    b = bytearray(4096)
    for pos in SLOTS:
        SF.read(pos, b)
        if len(set(b)) > 200:
            busy += 1
    return busy
コード例 #4
0
ファイル: sffile.py プロジェクト: ramsemune/firmware
    def read_into(self, b):
        # limitation: this will read past end of file, but not tell the caller
        actual = min(self.length - self.pos, len(b))
        if actual <= 0:
            return 0

        SF.read(self.start + self.pos, b)

        self.pos += actual

        return actual
コード例 #5
0
    def blank(self):
        # erase current copy of values in nvram; older ones may exist still
        # - use when clearing the seed value
        if self.my_pos:
            SF.wait_done()
            SF.sector_erase(self.my_pos)
            self.my_pos = 0

        # act blank too, just in case.
        self.current.clear()
        self.overrides.clear()
        self.is_dirty = 0
        self.capacity = 0
コード例 #6
0
ファイル: sffile.py プロジェクト: ramsemune/firmware
    async def erase(self):
        # must be used by caller before writing any bytes
        assert not self.readonly
        assert self.length == 0  # 'already wrote?'

        for i in range(0, self.max_size, blksize):
            SF.block_erase(self.start + i)

            if i and self.message:
                from glob import dis
                dis.progress_bar_show(i / self.max_size)

            # expect block erase to take up to 2 seconds
            while SF.is_busy():
                await sleep_ms(50)
コード例 #7
0
ファイル: auth.py プロジェクト: Coldcard/firmware
    async def interact(self):
        from version import decode_firmware_header
        from sflash import SF

        date, version, _ = decode_firmware_header(self.hdr)

        msg = '''\
Install this new firmware?

  {version}
  {built}

Binary checksum and signature will be further verified before any changes are made.
'''.format(version=version, built=date)

        try:
            ch = await ux_show_story(msg)

            if ch == 'y':
                # Accepted:
                # - write final file header, so bootloader will see it
                # - reboot to start process
                from glob import dis
                import callgate
                SF.write(self.length, self.hdr)

                dis.fullscreen('Upgrading...', percent=1)

                callgate.show_logout(2)
            else:
                # they don't want to!
                self.refused = True
                SF.block_erase(0)           # just in case, but not required
                await ux_dramatic_pause("Refused.", 2)

        except BaseException as exc:
            self.failed = "Exception"
            sys.print_exception(exc)
        finally:
            UserAuthorizedAction.cleanup()      # because no results to store
            self.pop_menu()
コード例 #8
0
ファイル: sffile.py プロジェクト: ramsemune/firmware
    def write(self, b):
        # immediate write, no buffering
        assert not self.readonly
        assert self.pos == self.length  # "can only append"
        assert self.pos + len(
            b
        ) <= self.max_size  # "past end: %r" % [self.pos, len(b), self.max_size]

        left = len(b)

        # must perform page-aligned (256) writes, but can start
        # anywhere in the page, and can write just one byte
        sofar = 0

        while left:
            if (self.pos + sofar) % 256 != 0:
                # start is unaligned, do a partial write to align
                assert sofar == 0  #, (sofar, (self.pos+sofar))       # can only happen on first page
                runt = min(left, 256 - (self.pos % 256))
                here = memoryview(b)[0:runt]
                assert len(here) == runt
            else:
                # write full pages, or final runt
                here = memoryview(b)[sofar:sofar + 256]
                assert 1 <= len(here) <= 256

            self.wait_writable()

            SF.write(self.start + self.pos + sofar, here)

            left -= len(here)
            sofar += len(here)
            self.checksum.update(here)

            assert left >= 0

        assert sofar == len(b)
        self.pos += sofar
        self.length = self.pos

        return sofar
コード例 #9
0
ファイル: sffile.py プロジェクト: ramsemune/firmware
    def read(self, ll=None):
        if ll == 0:
            return b''
        elif ll is None:
            ll = self.length - self.pos
        else:
            ll = min(ll, self.length - self.pos)

        if ll <= 0:
            # at EOF
            return b''

        rv = bytearray(ll)
        SF.read(self.start + self.pos, rv)

        self.pos += ll

        if self.message and ll > 1:
            from glob import dis
            dis.progress_bar_show(self.pos / self.length)

        # altho tempting to return a bytearray (which we already have) many
        # callers expect return to be bytes and have those methods, like "find"
        return bytes(rv)
コード例 #10
0
    def save(self):
        # render as JSON, encrypt and write it.

        self.current['_age'] = self.current.get('_age', 1) + 1

        pos = self.find_spot(self.my_pos)

        aes = self.get_aes(pos).cipher

        with SFFile(pos, max_size=4096, pre_erased=True) as fd:
            chk = sha256()

            # first the json data
            d = ujson.dumps(self.current)

            # pad w/ zeros
            dat_len = len(d)
            pad_len = (4096 - 32) - dat_len
            assert pad_len >= 0, 'too big'

            self.capacity = dat_len / 4096

            fd.write(aes(d))
            chk.update(d)
            del d

            while pad_len > 0:
                here = min(32, pad_len)

                pad = bytes(here)
                fd.write(aes(pad))
                chk.update(pad)

                pad_len -= here

            fd.write(aes(chk.digest()))
            assert fd.tell() == 4096

        # erase old copy of data
        if self.my_pos and self.my_pos != pos:
            SF.wait_done()
            SF.sector_erase(self.my_pos)
            SF.wait_done()

        self.my_pos = pos
        self.is_dirty = 0
コード例 #11
0
    def find_spot(self, not_here=0):
        # search for a blank sector to use
        # - check randomly and pick first blank one (wear leveling, deniability)
        # - we will write and then erase old slot
        # - if "full", blow away a random one
        options = [s for s in SLOTS if s != not_here]
        shuffle(options)

        buf = bytearray(16)
        for pos in options:
            SF.read(pos, buf)
            if set(buf) == {0xff}:
                # blank
                return pos

        # No where to write! (probably a bug because we have lots of slots)
        # ... so pick a random slot and kill what it had
        #print("ERROR: nvram full?")

        victem = options[0]
        SF.sector_erase(victem)
        SF.wait_done()

        return victem
コード例 #12
0
    def load(self, dis=None):
        # Search all slots for any we can read, decrypt that,
        # and pick the newest one (in unlikely case of dups)
        # reset
        self.current.clear()
        self.overrides.clear()
        self.my_pos = 0
        self.is_dirty = 0
        self.capacity = 0

        # 4k, but last 32 bytes are a SHA (itself encrypted)
        global _tmp

        buf = bytearray(4)
        empty = 0
        for pos in SLOTS:
            if dis:
                dis.progress_bar_show(
                    (pos - SLOTS.start) / (SLOTS.stop - SLOTS.start))
            gc.collect()

            SF.read(pos, buf)
            if buf[0] == buf[1] == buf[2] == buf[3] == 0xff:
                # erased (probably)
                empty += 1
                continue

            # check if first 2 bytes makes sense for JSON
            aes = self.get_aes(pos)
            chk = aes.copy().cipher(b'{"')

            if chk != buf[0:2]:
                # doesn't look like JSON meant for me
                continue

            # probably good, read it
            chk = sha256()
            aes = aes.cipher
            expect = None

            with SFFile(pos, length=4096, pre_erased=True) as fd:
                for i in range(4096 / 32):
                    b = aes(fd.read(32))
                    if i != 127:
                        _tmp[i * 32:(i * 32) + 32] = b
                        chk.update(b)
                    else:
                        expect = b

            try:
                # verify checksum in last 32 bytes
                assert expect == chk.digest()

                # loads() can't work from a byte array, and converting to
                # bytes here would copy it; better to use file emulation.
                fd = BytesIO(_tmp)
                d = ujson.load(fd)
                self.capacity = fd.seek(0, 1) / 4096  # .tell() is missing
            except:
                # One in 65k or so chance to come here w/ garbage decoded, so
                # not an error.
                continue

            got_age = d.get('_age', 0)
            if got_age > self.current.get('_age', -1):
                # likely winner
                self.current = d
                self.my_pos = pos
                #print("NV: data @ %d w/ age=%d" % (pos, got_age))
            else:
                # stale data seen; clean it up.
                assert self.current['_age'] > 0
                #print("NV: cleanup @ %d" % pos)
                SF.sector_erase(pos)
                SF.wait_done()

        # 4k is a large object, sigh, for us right now. cleanup
        gc.collect()

        # done, if we found something
        if self.my_pos:
            return

        # nothing found.
        self.my_pos = 0
        self.current = self.default_values()

        if empty == len(SLOTS):
            # Whole thing is blank. Bad for plausible deniability. Write 3 slots
            # with garbage. They will be wasted space until it fills.
            blks = list(SLOTS)
            shuffle(blks)

            for pos in blks[0:3]:
                for i in range(0, 4096, 256):
                    h = ngu.random.bytes(256)
                    SF.wait_done()
                    SF.write(pos + i, h)
コード例 #13
0
ファイル: selftest.py プロジェクト: ramsemune/firmware
async def test_sflash():
    dis.clear()
    dis.text(None, 18, 'Serial Flash')
    dis.show()

    from sflash import SF
    from ustruct import pack
    import ngu

    msize = 1024 * 1024
    SF.chip_erase()

    for phase in [0, 1]:
        steps = 7 * 4
        for i in range(steps):
            dis.progress_bar(i / steps)
            dis.show()
            await sleep_ms(250)
            if not SF.is_busy(): break

        assert not SF.is_busy()  # "didn't finish"

        # leave chip blank
        if phase == 1: break

        buf = bytearray(32)
        for addr in range(0, msize, 1024):
            SF.read(addr, buf)
            assert set(buf) == {255}  # "not blank"

            rnd = ngu.hash.sha256s(pack('I', addr))
            SF.write(addr, rnd)
            SF.read(addr, buf)
            assert buf == rnd  #  "write failed"

            dis.progress_bar_show(addr / msize)

        # check no aliasing, also right size part
        for addr in range(0, msize, 1024):
            expect = ngu.hash.sha256s(pack('I', addr))
            SF.read(addr, buf)
            assert buf == expect  # "readback failed"

            dis.progress_bar_show(addr / msize)
コード例 #14
0
ファイル: nvram.py プロジェクト: ramsemune/firmware
#
# Unit test for shared/nvstore.py
#
# this will run on the simulator
# run manually with:
#   execfile('../../testing/devtest/nvram.py')

from ubinascii import hexlify as b2a_hex
from ubinascii import unhexlify as a2b_hex

import ustruct
from sflash import SF
from nvstore import SLOTS, settings

# reset whatever's there
SF.chip_erase()
settings.load()

for v in [123, 'hello', 34.56, dict(a=45)]:
    settings.set('abc', v)
    assert settings.get('abc') == v

a = settings.get('_age', -1)
settings.save()
assert settings.get('_age') >= a+1, [settings.get('_age'), a+1]

chk = dict(settings.current)
settings.load()

# some minor differences in values: bytes vs. strings, so just check keys
assert sorted(list(chk)) == sorted(list(settings.current)), \
コード例 #15
0
ファイル: sffile.py プロジェクト: ramsemune/firmware
 def wait_writable(self):
     # TODO: timeouts here
     while SF.is_busy():
         pass
コード例 #16
0
ファイル: usb.py プロジェクト: Coldcard/firmware
    async def handle_upload(self, offset, total_size, data):
        from sflash import SF
        from glob import dis, hsm_active
        from utils import check_firmware_hdr
        from sigheader import FW_HEADER_OFFSET, FW_HEADER_SIZE, FW_HEADER_MAGIC

        # maintain a running SHA256 over what's received
        if offset == 0:
            self.file_checksum = sha256()
            self.is_fw_upgrade = False

        assert offset % 256 == 0, 'alignment'
        assert offset + len(data) <= total_size <= MAX_UPLOAD_LEN, 'long'

        if hsm_active:
            # additional restrictions in HSM mode
            assert offset + len(data) <= total_size <= MAX_TXN_LEN, 'psbt'
            if offset == 0:
                assert data[0:5] == b'psbt\xff', 'psbt'

        for pos in range(offset, offset + len(data), 256):
            if pos % 4096 == 0:
                # erase here
                dis.fullscreen("Receiving...", offset / total_size)

                SF.sector_erase(pos)

                # expect 10-22 ms delay here
                await sleep_ms(12)
                while SF.is_busy():
                    await sleep_ms(2)

            # write up to 256 bytes
            here = data[pos - offset:pos - offset + 256]
            self.file_checksum.update(here)

            # Very special case for firmware upgrades: intercept and modify
            # header contents on the fly, and also fail faster if wouldn't work
            # on this specific hardware.
            # - workaround: ckcc-protocol upgrade process understates the file
            #   length and appends hdr, but that's kinda a bug, so support both
            is_trailer = (pos == (total_size - FW_HEADER_SIZE)
                          or pos == total_size)

            if pos == (FW_HEADER_OFFSET & ~255):
                hdr = memoryview(here)[-128:]
                magic, = unpack_from('<I', hdr[0:4])
                if magic == FW_HEADER_MAGIC:
                    self.is_fw_upgrade = bytes(hdr)

                    prob = check_firmware_hdr(hdr, total_size)
                    if prob:
                        raise ValueError(prob)

            if is_trailer and self.is_fw_upgrade:
                # expect the trailer to exactly match the original one
                assert len(here) == 128  # == FW_HEADER_SIZE
                hdr = memoryview(here)[-128:]
                assert hdr == self.is_fw_upgrade  # indicates hacking

                # but don't write it, instead offer user a chance to abort
                from auth import authorize_upgrade
                authorize_upgrade(self.is_fw_upgrade, pos)

                # pretend we wrote it, so ckcc-protocol or whatever gives normal feedback
                return offset

            SF.write(pos, here)

            # full page write: 0.6 to 3ms
            while SF.is_busy():
                await sleep_ms(1)

        if offset + len(data) >= total_size and not hsm_active:
            # probably done
            dis.progress_bar_show(1.0)
            ux.restore_menu()

        return offset
コード例 #17
0
            with SFFile(0, ll) as fd:
                numpad.inject('y')  # for 'success' message
                await restore_complete_doit(fd, words)

                assert settings.get('check') == today, \
                            (settings.get('check'), '!=',  today)
                assert settings.get('chain') == chain, \
                            (settings.get('chain'), '!=',  chain)

                if version.has_608:
                    assert pa.ls_fetch() == ls

            if had_policy:
                assert had_policy == hsm.hsm_policy_available()

            today += 3

            import ux
            ux.restore_menu()


import uasyncio
uasyncio.get_event_loop().run_until_complete(test_7z())

# test recovery/reset
from sflash import SF
SF.chip_erase()
settings.load()

# EOF
コード例 #18
0
    async def handle_upload(self, offset, total_size, data):
        from sflash import SF
        from glob import dis, hsm_active
        from utils import check_firmware_hdr
        from sigheader import FW_HEADER_OFFSET, FW_HEADER_SIZE

        # maintain a running SHA256 over what's received
        if offset == 0:
            self.file_checksum = sha256()

        assert offset % 256 == 0, 'alignment'
        assert offset + len(data) <= total_size <= MAX_UPLOAD_LEN, 'long'

        if hsm_active:
            # additional restrictions in HSM mode
            assert offset + len(data) <= total_size <= MAX_TXN_LEN, 'psbt'
            if offset == 0:
                assert data[0:5] == b'psbt\xff', 'psbt'

        for pos in range(offset, offset + len(data), 256):
            if pos % 4096 == 0:
                # erase here
                dis.fullscreen("Receiving...", offset / total_size)

                SF.sector_erase(pos)

                # expect 10-22 ms delay here
                await sleep_ms(12)
                while SF.is_busy():
                    await sleep_ms(2)

            # write up to 256 bytes
            here = data[pos - offset:pos - offset + 256]

            self.file_checksum.update(here)

            # Very special case for firmware upgrades: intercept and modify
            # header contents on the fly, and also fail faster if wouldn't work
            # on this specific hardware.
            # - workaround: ckcc-protocol upgrade process understates the file
            #   length and appends hdr, but that's kinda a bug, so support both
            if (pos == (FW_HEADER_OFFSET & ~255)
                    or pos == (total_size - FW_HEADER_SIZE)
                    or pos == total_size):

                prob = check_firmware_hdr(memoryview(here)[-128:],
                                          None,
                                          bad_magic_ok=True)
                if prob:
                    raise ValueError(prob)

            SF.write(pos, here)

            # full page write: 0.6 to 3ms
            while SF.is_busy():
                await sleep_ms(1)

        if offset + len(data) >= total_size and not hsm_active:
            # probably done
            dis.progress_bar_show(1.0)
            ux.restore_menu()

        return offset