예제 #1
0
    async def failure(self, msg, exc=None, title='Failure'):
        self.failed = msg
        self.done()

        # show line number and/or simple text about error
        if exc:
            print("%s:" % msg)
            sys.print_exception(exc)

            msg += '\n\n'
            em = str(exc)
            if em:
                msg += em
                msg += '\n\n'
            msg += problem_file_line(exc)
        
        from main import hsm_active, dis

        # do nothing more for HSM case: msg will be available over USB
        if hsm_active:
            dis.progress_bar(1)     # finish the Validating... or whatever was up
            return

        # may be a user-abort waiting, but we want to see error msg; so clear it
        ux_clear_keys(True)

        return await ux_show_story(msg, title)
예제 #2
0
파일: auth.py 프로젝트: syscoin/firmware
    async def failure(self, msg, exc=None, title='Failure'):
        self.failed = msg
        self.done()

        if exc:
            print("%s:" % msg)
            sys.print_exception(exc)
            msg += "\n\n(%s)" % problem_file_line(exc)

        # may be a user-abort waiting, but we want to see error msg; so clear it
        ux_clear_keys(True)

        return await ux_show_story(msg, title)
예제 #3
0
async def start_hsm_approval(sf_len=0, usb_mode=False, startup_mode=False):
    # Show details of the proposed HSM policy (or saved one)
    # If approved, go into HSM mode and never come back to normal.

    UserAuthorizedAction.cleanup()

    is_new = True

    if sf_len:
        with SFFile(0, length=sf_len) as fd:
            json = fd.read(sf_len).decode()
    else:
        try:
            json = open(POLICY_FNAME, 'rt').read()
        except:
            raise ValueError("No existing policy")

        is_new = False

    # parse as JSON
    cant_fail = False
    try:
        try:
            js_policy = ujson.loads(json)
        except:
            raise ValueError("JSON parse fail")

        cant_fail = bool(js_policy.get('boot_to_hsm', False))

        # parse the policy
        policy = HSMPolicy()
        policy.load(js_policy)
    except BaseException as exc:
        err = "HSM Policy invalid: %s: %s" % (problem_file_line(exc), str(exc))
        if usb_mode:
            raise ValueError(err)

        # What to do in a menu case? Shouldn't happen anyway, but
        # maybe they upgraded the firmware, and so old policy file
        # isn't suitable anymore.
        # - or maybe the settings have been f-ed with.
        print(err)

        if startup_mode and cant_fail:
            # die as a brick here, not safe to proceed w/o HSM active
            import callgate, ux
            ux.show_fatal_error(err.replace(': ', ':\n '))
            callgate.show_logout(1)     # die w/ it visible
            # not reached

        await ux_show_story("Cannot start HSM.\n\n%s" % err)
        return

    # Boot-to-HSM feature: don't ask, just start policy immediately
    if startup_mode and policy.boot_to_hsm:
        msg = uio.StringIO()
        policy.explain(msg)
        policy.activate(False)
        the_ux.reset(hsm_ux_obj)
        return None
        

    ar = ApproveHSMPolicy(policy, is_new)
    UserAuthorizedAction.active_request = ar

    if startup_mode:
        return ar

    if usb_mode:
        # for USB case, kill any menu stack, and put our thing at the top
        abort_and_goto(UserAuthorizedAction.active_request)
    else:
        # menu item case: add to stack, so we can still back out
        from ux import the_ux
        the_ux.push(UserAuthorizedAction.active_request)

    return ar
예제 #4
0
파일: usb.py 프로젝트: jimmysong/firmware
    async def usb_hid_recv(self):
        # blocks and builds up a full-length command packet in memory
        # - calls self.handle() once complete msg on hand
        msg_len = 0

        while 1:
            yield IORead(self.blockable)

            try:
                here, is_last, is_encrypted = self.get_packet()

                #print('Rx[%d]' % len(here))
                if here:
                    lh = len(here)
                    if msg_len + lh > MAX_MSG_LEN:
                        raise FramingError('xlong')

                    self.msg[msg_len:msg_len + lh] = here
                    msg_len += lh
                else:
                    # treat zero-length packets as a reset request
                    # do not echo anything back on link.. used to resync connection
                    msg_len = 0
                    continue

                if not is_last:
                    # need more content
                    continue

                if not (4 <= msg_len <= MAX_MSG_LEN):
                    raise FramingError('badsz')

                if is_encrypted:
                    if self.decrypt is None:
                        raise FramingError('no key')

                    self.encrypted_req = True
                    self.decrypt_inplace(msg_len)
                else:
                    self.encrypted_req = False

                # process request
                try:
                    # this saves memory over a simple slice (confirmed)
                    args = memoryview(self.msg)[4:msg_len]
                    resp = await self.handle(self.msg[0:4], args)
                    msg_len = 0
                except CCBusyError:
                    # auth UX is doing something else
                    resp = b'busy'
                    msg_len = 0
                except HSMDenied:
                    resp = b'err_Not allowed in HSM mode'
                    msg_len = 0
                except (ValueError, AssertionError) as exc:
                    # some limited invalid args feedback
                    #print("USB request caused assert: ", end='')
                    #sys.print_exception(exc)
                    msg = str(exc)
                    if not msg:
                        msg = 'Assertion ' + problem_file_line(exc)
                    resp = b'err_' + msg.encode()[0:80]
                    msg_len = 0
                except Exception as exc:
                    # catch bugs and fuzzing too
                    print("USB request caused this: ", end='')
                    sys.print_exception(exc)
                    resp = b'err_Confused ' + problem_file_line(exc)
                    msg_len = 0

                # aways send a reply if they get this far
                await self.send_response(resp)

            except FramingError as exc:
                reason = exc.args[0]
                print("Framing: %s" % reason)
                self.framing_error(reason)
                msg_len = 0

            except BaseException as exc:
                # recover from general issues/keep going
                print("USB!")
                sys.print_exception(exc)
                msg_len = 0
예제 #5
0
    async def approve_transaction(self, psbt, psbt_sha, story):
        # Approve or don't a transaction. Catch assertions and other
        # reasons for failing/rejecting into the log.
        # - return 'y' or 'x'
        chain = chains.current_chain()
        assert psbt_sha and len(psbt_sha) == 32
        self.get_time_left()

        with AuditLogger('psbt', psbt_sha, self.never_log) as log:

            if self.must_log and log.is_unsaved:
                self.refuse(log, "Could not log details, and must_log is set")
                return 'x'

            log.info('Transaction signing requested:')
            log.info('SHA256(PSBT) = ' + b2a_hex(psbt_sha).decode('ascii'))
            log.info('-vvv-\n%s\n-^^^-' % story)

            # reset pending auth list and "consume" it now
            auth = self.pending_auth
            self.pending_auth = {}

            try:
                # do this super early so always cleared even if other issues
                local_ok = self.consume_local_code(psbt_sha)

                if not self.rules:
                    raise ValueError("no txn signing allowed")

                # reject anything with warning, probably
                if psbt.warnings:
                    if self.warnings_ok:
                        log.info(
                            "Txn has warnings, but policy is to accept anyway."
                        )
                    else:
                        raise ValueError("has %d warning(s)" %
                                         len(psbt.warnings))

                # See who has entered creditials already (all must be valid).
                users = []
                for u, (token, counter) in auth.items():
                    problem = Users.auth_okay(u,
                                              token,
                                              totp_time=counter,
                                              psbt_hash=psbt_sha)
                    if problem:
                        self.refuse(
                            log, "User '%s' gave wrong auth value: %s" %
                            (u, problem))
                        return 'x'
                    users.append(u)

                # was right code provided locally? (also resets for next attempt)
                if local_ok:
                    log.info("Local operator gave correct code.")
                if users:
                    log.info("These users gave correct auth codes: " +
                             ', '.join(users))

                # Where is it going?
                total_out = 0
                dests = []
                for idx, tx_out in psbt.output_iter():
                    if not psbt.outputs[idx].is_change:
                        total_out += tx_out.nValue
                        dests.append(chain.render_address(tx_out.scriptPubKey))

                # Pick a rule to apply to this specific txn
                reasons = []
                for rule in self.rules:
                    try:
                        if rule.matches_transaction(psbt, users, total_out,
                                                    dests, local_ok):
                            break
                    except BaseException as exc:
                        # let's not share these details, except for debug; since
                        # they are not errors, just picking best rule in priority order
                        r = "rule #%d: %s" % (rule.index, str(exc)
                                              or problem_file_line(exc))
                        reasons.append(r)
                        print(r)
                else:
                    err = "Rejected: " + ', '.join(reasons)
                    self.refuse(log, err)
                    return 'x'

                if users:
                    msg = ', '.join(auth.keys())
                    if local_ok:
                        msg += ', and the local operator.' if msg else 'local operator'

                # looks good, do it
                self.approve(log, "Acceptable by rule #%d" % rule.index)

                if rule.per_period is not None:
                    self.record_spend(rule, total_out)

                return 'y'
            except BaseException as exc:
                sys.print_exception(exc)
                err = "Rejected: " + (str(exc) or problem_file_line(exc))
                self.refuse(log, err)

                return 'x'
예제 #6
0
    async def interact(self):
        # Prompt user w/ details and get approval
        from main import dis

        # step 1: parse PSBT from sflash into in-memory objects.
        dis.fullscreen("Validating...")

        try:
            with SFFile(TXN_INPUT_OFFSET, length=self.psbt_len) as fd:
                self.psbt = psbtObject.read_psbt(fd)
        except BaseException as exc:
            sys.print_exception(exc)
            if isinstance(exc, MemoryError):
                msg = "Transaction is too complex."
            else:
                msg = "PSBT parse failed"

            return await self.failure(msg)

        # Do some analysis/ validation
        try:
            await self.psbt.validate()      # might do UX: accept multisig import
            self.psbt.consider_inputs()
            self.psbt.consider_keys()
            self.psbt.consider_outputs()
        except FraudulentChangeOutput as exc:
            print('FraudulentChangeOutput: ' + exc.args[0])
            return await self.failure(exc.args[0], title='Change Fraud')
        except FatalPSBTIssue as exc:
            print('FatalPSBTIssue: ' + exc.args[0])
            return await self.failure(exc.args[0])
        except BaseException as exc:
            sys.print_exception(exc)
            del self.psbt
            gc.collect()

            if isinstance(exc, MemoryError):
                msg = "Transaction is too complex."
            else:
                msg = "Invalid PSBT: %s (%s)" % (exc, problem_file_line(exc))

            return await self.failure(msg, exc)

        # step 2: figure out what we are approving, so we can get sign-off
        # - outputs, amounts
        # - fee 
        #
        # notes: 
        # - try to handle lots of outputs
        # - cannot calc fee as sat/byte, only as percent
        # - somethings are 'warnings':
        #       - fee too big
        #       - inputs we can't sign (no key)
        #
        try:
            msg = uio.StringIO()

            # mention warning at top
            wl= len(self.psbt.warnings)
            if wl == 1:
                msg.write('(1 warning below)\n\n')
            elif wl >= 2:
                msg.write('(%d warnings below)\n\n' % wl)

            self.output_summary_text(msg)
            gc.collect()

            fee = self.psbt.calculate_fee()
            if fee is not None:
                msg.write("\nNetwork fee:\n%s %s\n" % self.chain.render_value(fee))

            if self.psbt.warnings:
                msg.write('\n---WARNING---\n\n')

                for label,m in self.psbt.warnings:
                    msg.write('- %s: %s\n\n' % (label, m))

            msg.write("\nPress OK to approve and sign transaction. X to abort.")

            ch = await ux_show_story(msg, title="OK TO SEND?")
        except MemoryError as exc:
            # recovery? maybe.
            try:
                del self.psbt
                del msg
            except: pass        # might be NameError since we don't know how far we got
            gc.collect()

            msg = "Transaction is too complex."
            return await self.failure(msg, exc)

        if ch != 'y':
            # they don't want to!
            self.refused = True
            await ux_dramatic_pause("Refused.", 1)

            del self.psbt

            self.done()
            return

        # do the actual signing.
        try:
            gc.collect()
            self.psbt.sign_it()
        except FraudulentChangeOutput as exc:
            print('FraudulentChangeOutput: ' + exc.args[0])
            return await self.failure(exc.args[0], title='Change Fraud')
        except MemoryError as exc:
            msg = "Transaction is too complex."
            return await self.failure(msg, exc)
        except BaseException as exc:
            sys.print_exception(exc)
            return await self.failure("Signing failed late: %s" % exc)

        if self.approved_cb:
            # for micro sd case
            await self.approved_cb(self.psbt)
            self.done()
            return

        try:
            # re-serialize the PSBT back out
            with SFFile(TXN_OUTPUT_OFFSET, max_size=MAX_TXN_LEN, message="Saving...") as fd:
                await fd.erase()

                if self.do_finalize:
                    self.psbt.finalize(fd)
                else:
                    self.psbt.serialize(fd)

                self.result = (fd.tell(), fd.checksum.digest())

            self.done()

        except BaseException as exc:
            self.failed = "PSBT output failed"
            print("PSBT output failure: ")
            sys.print_exception(exc)
            self.done()
            return