Exemplo n.º 1
0
    async def handle_upload(self, offset, total_size, data):
        from main import dis, sf, 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 = tcc.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)

                while sf.is_busy():
                    await sleep_ms(10)

            # 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
Exemplo n.º 2
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
        from main import sf

        options = [s for s in SLOTS if s != not_here]
        tcc.random.shuffle(options)

        buf = bytearray(16)
        for pos in options:
            sf.read(pos, buf)
            if set(buf) == {0xff}:
                # blank
                return sf, 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 sf, victem
Exemplo n.º 3
0
    def blank(self):
        # erase current copy of values in nvram; older ones may exist still
        # - use when clearing the seed value
        from main import sf

        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 = {}
        self.is_dirty = 0
Exemplo n.º 4
0
    def save(self):
        # render as JSON, encrypt and write it.

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

        sf, pos = self.find_spot(self.my_pos)

        aes = self.get_aes(tcc.AES.Encrypt, pos)

        with SFFile(pos, max_size=4096, pre_erased=True) as fd:
            chk = tcc.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.update(d))
            chk.update(d)
            del d

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

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

                pad_len -= here

            fd.write(aes.update(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
Exemplo n.º 5
0
    async def handle_upload(self, offset, total_size, data):
        from main import dis, sf

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

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

        rb = bytearray(256)
        for pos in range(offset, offset + len(data), 256):
            if pos % 4096 == 0:
                # erase here
                sf.sector_erase(pos)

                dis.fullscreen("Receiving...")
                dis.progress_bar_show(offset / total_size)

                while sf.is_busy():
                    await sleep_ms(10)

            # write up to 256 bytes
            here = data[pos - offset:pos - offset + 256]
            sf.write(pos, here)

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

            # use actual read back for verify
            sf.read(pos, rb)
            self.file_checksum.update(rb[0:len(here)])

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

        return offset
Exemplo n.º 6
0
    def load(self):
        # Search all slots for any we can read, decrypt that,
        # and pick the newest one (in unlikely case of dups)
        from main import sf

        # reset
        self.current = {}
        self.my_pos = 0
        self.is_dirty = 0

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

        buf = bytearray(4)
        empty = 0
        for pos in SLOTS:
            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(tcc.AES.Encrypt, pos)
            chk = aes.update(b'{"')

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

            # probably good, read it
            aes = self.get_aes(tcc.AES.Encrypt, pos)

            chk = tcc.sha256()
            expect = None

            with SFFile(pos, length=4096, pre_erased=True) as fd:
                for i in range(4096 / 32):
                    b = aes.update(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.
                d = ujson.load(BytesIO(_tmp))
            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)
            tcc.random.shuffle(blks)

            for pos in blks[0:3]:
                for i in range(0, 4096, 256):
                    h = tcc.random.bytes(256)
                    sf.wait_done()
                    sf.write(pos + i, h)
Exemplo n.º 7
0
async def microsd_upgrade(*a):
    # Upgrade vis MicroSD card
    # - search for a particular file
    # - verify it lightly
    # - erase serial flash
    # - copy it over (slow)
    # - reboot into bootloader, which finishes install

    fn = await file_picker('Pick firmware image to use (.DFU)',
                           suffix='.dfu',
                           min_size=0x7800)

    if not fn: return

    failed = None

    with CardSlot() as card:
        with open(fn, 'rb') as fp:
            from main import sf, dis
            from files import dfu_parse
            from ustruct import unpack_from

            offset, size = dfu_parse(fp)

            # get a copy of special signed heaer at the end of the flash as well
            from sigheader import FW_HEADER_OFFSET, FW_HEADER_SIZE, FW_HEADER_MAGIC, FWH_PY_FORMAT
            hdr = bytearray(FW_HEADER_SIZE)
            fp.seek(offset + FW_HEADER_OFFSET)

            # basic checks only: for confused customers, not attackers.
            try:
                rv = fp.readinto(hdr)
                assert rv == FW_HEADER_SIZE

                magic_value, timestamp, version_string, pk, fw_size = \
                                unpack_from(FWH_PY_FORMAT, hdr)[0:5]
                assert magic_value == FW_HEADER_MAGIC
                assert fw_size == size

                # TODO: maybe show the version string? Warn them that downgrade doesn't work?

            except Exception as exc:
                failed = "Sorry! That does not look like a firmware " \
                            "file we would want to use.\n\n\n%s" % exc

            if not failed:

                # copy binary into serial flash
                fp.seek(offset)

                buf = bytearray(256)  # must be flash page size
                pos = 0
                dis.fullscreen("Loading...")
                while pos <= size + FW_HEADER_SIZE:
                    dis.progress_bar_show(pos / size)

                    if pos == size:
                        # save an extra copy of the header (also means we got done)
                        buf = hdr
                    else:
                        here = fp.readinto(buf)
                        if not here: break

                    if pos % 4096 == 0:
                        # erase here
                        sf.sector_erase(pos)
                        while sf.is_busy():
                            await sleep_ms(10)

                    sf.write(pos, buf)

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

                    pos += here

    if failed:
        await ux_show_story(failed, title='Corrupt')
        return

    # continue process...
    import machine
    machine.reset()
Exemplo n.º 8
0
async def microsd_upgrade(*a):
    # Upgrade vis MicroSD card
    # - search for a particular file
    # - verify it lightly
    # - erase serial flash
    # - copy it over (slow)
    # - reboot into bootloader, which finishes install

    fn = await file_picker('Pick firmware image to use (.DFU)', suffix='.dfu', min_size=0x7800)

    if not fn: return

    failed = None

    with CardSlot() as card:
        with open(fn, 'rb') as fp:
            from main import sf, dis
            from files import dfu_parse
            from utils import check_firmware_hdr

            offset, size = dfu_parse(fp)

            # we also put a copy of special signed heaer at the end of the flash
            from sigheader import FW_HEADER_OFFSET, FW_HEADER_SIZE

            # read just the signature header
            hdr = bytearray(FW_HEADER_SIZE)
            fp.seek(offset + FW_HEADER_OFFSET)
            rv = fp.readinto(hdr)
            assert rv == FW_HEADER_SIZE

            # check header values
            failed = check_firmware_hdr(hdr, size)

            if not failed:
                patched = 0
            
                # copy binary into serial flash
                fp.seek(offset)

                buf = bytearray(256)        # must be flash page size
                pos = 0
                dis.fullscreen("Loading...")
                while pos <= size + FW_HEADER_SIZE:
                    dis.progress_bar_show(pos/size)

                    if pos == size:
                        # save an extra copy of the header (also means we got done)
                        buf = hdr
                        patched += 1
                    else:
                        here = fp.readinto(buf)
                        if not here: break

                    if pos == (FW_HEADER_OFFSET & ~255):
                        # update w/ our patched version of hdr
                        buf[128:FW_HEADER_SIZE+128] = hdr
                        patched += 1

                    if pos % 4096 == 0:
                        # erase here
                        sf.sector_erase(pos)
                        while sf.is_busy():
                            await sleep_ms(10)

                    sf.write(pos, buf)

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

                    pos += here

                assert patched == 2

    if failed:
        await ux_show_story(failed, title='Sorry!')
        return

    # continue process...
    import machine
    machine.reset()