Esempio n. 1
0
    def sign_message(self, sequence, message, password):

        message_byte = message.encode('utf8')
        message_hash = hashlib.sha256(message_byte).hexdigest().upper()
        client = self.get_client()
        address_path = self.get_derivation()[2:] + "/%d/%d"%sequence
        self.print_error('debug: sign_message: path: '+address_path)
        self.handler.show_message("Signing message ...\r\nMessage hash: "+message_hash)

        # check if 2FA is required
        hmac=b''
        if (client.cc.needs_2FA==None):
            (response, sw1, sw2, d)=client.cc.card_get_status()
        if client.cc.needs_2FA:
            # challenge based on sha256(btcheader+msg)
            # format & encrypt msg
            import json
            msg= {'action':"sign_msg", 'msg':message}
            msg=  json.dumps(msg)
            (id_2FA, msg_out)= client.cc.card_crypt_transaction_2FA(msg, True)

            d={}
            d['msg_encrypt']= msg_out
            d['id_2FA']= id_2FA
            # print_error("encrypted message: "+msg_out)
            self.print_error("id_2FA: "+id_2FA)

            #do challenge-response with 2FA device...
            self.handler.show_message('2FA request sent! Approve or reject request on your second device.')
            Satochip2FA.do_challenge_response(d)
            # decrypt and parse reply to extract challenge response
            try:
                reply_encrypt= d['reply_encrypt']
            except Exception as e:
                # Note the below give_error call will itself raise Message. :/
                self.give_error("No response received from 2FA!", True)
                return
            reply_decrypt= client.cc.card_crypt_transaction_2FA(reply_encrypt, False)
            self.print_error("challenge:response= "+ reply_decrypt)
            reply_decrypt= reply_decrypt.split(":")
            chalresponse=reply_decrypt[1]
            hmac= bytes.fromhex(chalresponse)

        try:
            keynbr= 0xFF #for extended key
            (depth, bytepath)= bip32path2bytes(address_path)
            (pubkey, chaincode)=client.cc.card_bip32_get_extendedkey(bytepath)
            (response2, sw1, sw2, compsig) = client.cc.card_sign_message(keynbr, pubkey, message_byte, hmac)
            if (compsig==b''):
                self.handler.show_error(_("Wrong signature!\nThe 2FA device may have rejected the action."))

        except Exception as e:
            self.give_error(e, True)
        finally:
            self.handler.finished()

        return compsig
Esempio n. 2
0
    def reset_2FA(self, client):
        if client.cc.needs_2FA:
            # challenge based on ID_2FA
            # format & encrypt msg
            import json
            msg = {'action': "reset_2FA"}
            msg = json.dumps(msg)
            (id_2FA, msg_out) = client.cc.card_crypt_transaction_2FA(msg, True)
            d = {}
            d['msg_encrypt'] = msg_out
            d['id_2FA'] = id_2FA
            # print_error("encrypted message: "+msg_out)
            print_error("id_2FA: " + id_2FA)

            #do challenge-response with 2FA device...
            client.handler.show_message(
                '2FA request sent! Approve or reject request on your second device.'
            )
            Satochip2FA.do_challenge_response(d)
            # decrypt and parse reply to extract challenge response
            try:
                reply_encrypt = d['reply_encrypt']
            except Exception as e:
                self.give_error("No response received from 2FA!", True)
            reply_decrypt = client.cc.card_crypt_transaction_2FA(
                reply_encrypt, False)
            print_error("challenge:response= " + reply_decrypt)
            reply_decrypt = reply_decrypt.split(":")
            chalresponse = reply_decrypt[1]
            hmac = list(bytes.fromhex(chalresponse))

            # send request
            (response, sw1, sw2) = client.cc.card_reset_2FA_key(hmac)
            if (sw1 == 0x90 and sw2 == 0x00):
                msg = _("2FA reset successfully!")
                client.cc.needs_2FA = False
                client.handler.show_message(msg)
            elif (sw1 == 0x9c and sw2 == 0x17):
                msg = _(
                    f"Failed to reset 2FA: \nyou must reset the seed first (error code {hex(256*sw1+sw2)})"
                )
                client.handler.show_error(msg)
            elif (sw1 == 0x9c and sw2 == 0x0b):
                msg = _(
                    f"Failed to reset 2FA: \nrequest rejected by 2FA device (error code: {hex(256*sw1+sw2)})"
                )
                client.handler.show_message(msg)
            else:
                msg = _(
                    f"Failed to reset 2FA with error code: {hex(256*sw1+sw2)}")
                client.handler.show_error(msg)
        else:
            msg = _(f"2FA is already disabled!")
            client.handler.show_error(msg)
Esempio n. 3
0
    def reset_seed(self, client):
        print_error("In reset_seed")
        # pin
        msg = ''.join([
            _("WARNING!\n"),
            _("You are about to reset the seed of your Satochip. This process is irreversible!\n"),
            _("Please be sure that your wallet is empty and that you have a backup of the seed as a precaution.\n\n"),
            _("To proceed, enter the PIN for your Satochip:")
        ])
        (password, reset_2FA)= self.reset_seed_dialog(msg)
        if (password is None):
            return
        pin = password.encode('utf8')
        pin= list(pin)

        # if 2FA is enabled, get challenge-response
        hmac=[]
        if (client.cc.needs_2FA==None):
            (response, sw1, sw2, d)=client.cc.card_get_status()
        if client.cc.needs_2FA:
            # challenge based on authentikey
            authentikeyx= bytearray(client.cc.parser.authentikey_coordx).hex()

            # format & encrypt msg
            import json
            msg= {'action':"reset_seed", 'authentikeyx':authentikeyx}
            msg=  json.dumps(msg)
            (id_2FA, msg_out)= client.cc.card_crypt_transaction_2FA(msg, True)
            d={}
            d['msg_encrypt']= msg_out
            d['id_2FA']= id_2FA
            # print_error("encrypted message: "+msg_out)
            #print_error("id_2FA: "+id_2FA)

            #do challenge-response with 2FA device...
            client.handler.show_message('2FA request sent! Approve or reject request on your second device.')
            Satochip2FA.do_challenge_response(d)
            # decrypt and parse reply to extract challenge response
            try:
                reply_encrypt= d['reply_encrypt']
            except Exception as e:
                self.give_error("No response received from 2FA.\nPlease ensure that the Satochip-2FA plugin is enabled in Tools>Optional Features", True)
            reply_decrypt= client.cc.card_crypt_transaction_2FA(reply_encrypt, False)
            print_error("challenge:response= "+ reply_decrypt)
            reply_decrypt= reply_decrypt.split(":")
            chalresponse=reply_decrypt[1]
            hmac= list(bytes.fromhex(chalresponse))

        # send request
        (response, sw1, sw2) = client.cc.card_reset_seed(pin, hmac)
        if (sw1==0x90 and sw2==0x00):
            msg= _("Seed reset successfully!\nYou should close this wallet and launch the wizard to generate a new wallet.")
            client.handler.show_message(msg)
            #to do: close client?
        else:
            msg= _(f"Failed to reset seed with error code: {hex(sw1)}{hex(sw2)}")
            client.handler.show_error(msg)

        if reset_2FA and client.cc.needs_2FA:
            # challenge based on ID_2FA
            # format & encrypt msg
            import json
            msg= {'action':"reset_2FA"}
            msg=  json.dumps(msg)
            (id_2FA, msg_out)= client.cc.card_crypt_transaction_2FA(msg, True)
            d={}
            d['msg_encrypt']= msg_out
            d['id_2FA']= id_2FA
            # print_error("encrypted message: "+msg_out)
            print_error("id_2FA: "+id_2FA)

            #do challenge-response with 2FA device...
            client.handler.show_message('2FA request sent! Approve or reject request on your second device.')
            Satochip2FA.do_challenge_response(d)
            # decrypt and parse reply to extract challenge response
            try:
                reply_encrypt= d['reply_encrypt']
            except Exception as e:
                self.give_error("No response received from 2FA.\nPlease ensure that the Satochip-2FA plugin is enabled in Tools>Optional Features", True)
            reply_decrypt= client.cc.card_crypt_transaction_2FA(reply_encrypt, False)
            print_error("challenge:response= "+ reply_decrypt)
            reply_decrypt= reply_decrypt.split(":")
            chalresponse=reply_decrypt[1]
            hmac= list(bytes.fromhex(chalresponse))

            # send request
            (response, sw1, sw2) = client.cc.card_reset_2FA_key(hmac)
            if (sw1==0x90 and sw2==0x00):
                msg= _("2FA reset successfully!")
                client.cc.needs_2FA= False
                client.handler.show_message(msg)
            else:
                msg= _(f"Failed to reset 2FA with error code: {hex(sw1)}{hex(sw2)}")
                client.handler.show_error(msg)
Esempio n. 4
0
    def system_tray(self, card_present):
        logger.debug('In system_tray')
        self.menu_def = [
            'BLANK',
            [
                '&Setup new Satochip', '&Change PIN', '&Reset seed',
                '&Enable 2FA', '&About', '&Quit'
            ]
        ]

        if card_present:
            self.tray = sg.SystemTray(menu=self.menu_def,
                                      filename=self.satochip_icon)
        else:
            self.tray = sg.SystemTray(menu=self.menu_def,
                                      filename=self.satochip_unpaired_icon)

        while True:
            menu_item = self.tray.Read(timeout=1)
            if menu_item != '__TIMEOUT__':
                logger.debug('Menu item: ' + menu_item)

            ## Setup new Satochip ##
            if menu_item == 'Setup new Satochip':
                self.client.card_init_connect()

            ## Change PIN ##
            elif menu_item == 'Change PIN':
                msg_oldpin = ("Enter the current PIN for your Satochip:")
                msg_newpin = ("Enter a new PIN for your Satochip:")
                msg_confirm = ("Please confirm the new PIN for your Satochip:")
                msg_error = (
                    "The PIN values do not match! Please type PIN again!")
                msg_cancel = ("PIN change cancelled!")
                (is_PIN, oldpin, newpin) = self.client.PIN_change_dialog(
                    msg_oldpin, msg_newpin, msg_confirm, msg_error, msg_cancel)
                if not is_PIN:
                    continue
                else:
                    oldpin = list(oldpin)
                    newpin = list(newpin)
                    (response, sw1,
                     sw2) = self.client.cc.card_change_PIN(0, oldpin, newpin)
                    if (sw1 == 0x90 and sw2 == 0x00):
                        msg = ("PIN changed successfully!")
                        self.show_success(msg)
                    else:
                        msg = (
                            f"Failed to change PIN with error code: {hex(sw1)}{hex(sw2)}"
                        )
                        self.show_error(msg)

            ## Reset seed ##
            elif menu_item == 'Reset seed':
                msg = ''.join([
                    ("WARNING!\n"),
                    ("You are about to reset the seed of your Satochip. This process is irreversible!\n"
                     ),
                    ("Please be sure that your wallet is empty and that you have a backup of the seed as a precaution.\n\n"
                     ), ("To proceed, enter the PIN for your Satochip:")
                ])
                (event, values) = self.reset_seed_dialog(msg)
                if event == 'Cancel':
                    msg = ("Seed reset cancelled!")
                    self.show_message(msg)
                    continue

                pin = values['pin']
                reset_2FA = values['reset_2FA']
                pin = list(pin.encode('utf8'))

                # if 2FA is enabled, get challenge-response
                hmac = []
                try:  # todo: check if is_seeded
                    self.client.cc.card_bip32_get_authentikey()
                    self.client.cc.is_seeded = True
                except UninitializedSeedError:
                    self.client.cc.is_seeded = False
                if self.client.cc.needs_2FA and self.client.cc.is_seeded:
                    # challenge based on authentikey
                    authentikeyx = bytearray(
                        self.client.cc.parser.authentikey_coordx).hex()

                    # format & encrypt msg
                    import json
                    msg = {
                        'action': "reset_seed",
                        'authentikeyx': authentikeyx
                    }
                    msg = json.dumps(msg)
                    (id_2FA,
                     msg_out) = self.client.cc.card_crypt_transaction_2FA(
                         msg, True)
                    d = {}
                    d['msg_encrypt'] = msg_out
                    d['id_2FA'] = id_2FA
                    # logger.debug("encrypted message: "+msg_out)

                    #do challenge-response with 2FA device...
                    self.show_message(
                        '2FA request sent! Approve or reject request on your second device.'
                    )
                    Satochip2FA.do_challenge_response(d)
                    # decrypt and parse reply to extract challenge response
                    try:
                        reply_encrypt = d['reply_encrypt']
                    except Exception as e:
                        self.show_error("No response received from 2FA...")
                        continue
                    reply_decrypt = self.client.cc.card_crypt_transaction_2FA(
                        reply_encrypt, False)
                    logger.debug("challenge:response= " + reply_decrypt)
                    reply_decrypt = reply_decrypt.split(":")
                    chalresponse = reply_decrypt[1]
                    hmac = list(bytes.fromhex(chalresponse))

                # send request
                (response, sw1,
                 sw2) = self.client.cc.card_reset_seed(pin, hmac)
                if (sw1 == 0x90 and sw2 == 0x00):
                    msg = (
                        "Seed reset successfully!\nYou can launch the wizard to setup your Satochip"
                    )
                    self.show_success(msg)
                else:
                    msg = (
                        f"Failed to reset seed with error code: {hex(sw1)}{hex(sw2)}"
                    )
                    self.show_error(msg)

                # reset 2FA
                if reset_2FA and self.client.cc.needs_2FA:
                    # challenge based on ID_2FA
                    # format & encrypt msg
                    import json
                    msg = {'action': "reset_2FA"}
                    msg = json.dumps(msg)
                    (id_2FA,
                     msg_out) = self.client.cc.card_crypt_transaction_2FA(
                         msg, True)
                    d = {}
                    d['msg_encrypt'] = msg_out
                    d['id_2FA'] = id_2FA
                    # _logger.info("encrypted message: "+msg_out)

                    #do challenge-response with 2FA device...
                    self.client.handler.show_message(
                        '2FA request sent! Approve or reject request on your second device.'
                    )
                    Satochip2FA.do_challenge_response(d)
                    # decrypt and parse reply to extract challenge response
                    try:
                        reply_encrypt = d['reply_encrypt']
                    except Exception as e:
                        self.show_error("No response received from 2FA...")
                    reply_decrypt = self.client.cc.card_crypt_transaction_2FA(
                        reply_encrypt, False)
                    logger.debug("challenge:response= " + reply_decrypt)
                    reply_decrypt = reply_decrypt.split(":")
                    chalresponse = reply_decrypt[1]
                    hmac = list(bytes.fromhex(chalresponse))

                    # send request
                    (response, sw1,
                     sw2) = self.client.cc.card_reset_2FA_key(hmac)
                    if (sw1 == 0x90 and sw2 == 0x00):
                        self.client.cc.needs_2FA = False
                        msg = ("2FA reset successfully!")
                        self.show_success(msg)
                    else:
                        msg = (
                            f"Failed to reset 2FA with error code: {hex(sw1)}{hex(sw2)}"
                        )
                        self.show_error(msg)

            ## Enable 2FA ##
            elif menu_item == 'Enable 2FA':
                self.client.init_2FA()
                continue

            ## About ##
            elif menu_item == 'About':
                #copyright
                msg_copyright = ''.join([
                    '(c)2020 - Satochip by Toporin - https://github.com/Toporin/ \n',
                    "This program is licensed under the GNU Lesser General Public License v3.0 \n",
                    "This software is provided 'as-is', without any express or implied warranty.\n",
                    "In no event will the authors be held liable for any damages arising from \n"
                    "the use of this software."
                ])
                #sw version
                # v_supported= (CardConnector.SATOCHIP_PROTOCOL_MAJOR_VERSION<<8)+CardConnector.SATOCHIP_PROTOCOL_MINOR_VERSION
                # sw_rel= str(CardConnector.SATOCHIP_PROTOCOL_MAJOR_VERSION) +'.'+ str(CardConnector.SATOCHIP_PROTOCOL_MINOR_VERSION)
                v_supported = (SATOCHIP_PROTOCOL_MAJOR_VERSION <<
                               8) + SATOCHIP_PROTOCOL_MINOR_VERSION
                sw_rel = str(SATOCHIP_PROTOCOL_MAJOR_VERSION) + '.' + str(
                    SATOCHIP_PROTOCOL_MINOR_VERSION)
                fw_rel = "N/A"
                is_seeded = "N/A"
                needs_2FA = "N/A"
                needs_SC = "N/A"
                msg_status = (
                    "Card is not initialized! \nClick on 'Setup new Satochip' in the menu to start configuration."
                )

                (response, sw1, sw2, status) = self.client.cc.card_get_status()
                if (sw1 == 0x90 and sw2 == 0x00):
                    #hw version
                    v_applet = (status["protocol_major_version"] <<
                                8) + status["protocol_minor_version"]
                    fw_rel = str(status["protocol_major_version"]) + '.' + str(
                        status["protocol_minor_version"])
                    # status
                    if (v_supported < v_applet):
                        msg_status = (
                            'The version of your Satochip is higher than supported. \nYou should update Satochip-Bridge!'
                        )
                    else:
                        msg_status = 'Satochip-Bridge is up-to-date'
                    # needs2FA?
                    if len(response) >= 9 and response[8] == 0X01:
                        needs_2FA = "yes"
                    elif len(response) >= 9 and response[8] == 0X00:
                        needs_2FA = "no"
                    else:
                        needs_2FA = "unknown"
                    #is_seeded?
                    if len(response) >= 10:
                        is_seeded = "yes" if status["is_seeded"] else "no"
                    else:  #for earlier versions
                        try:
                            self.client.cc.card_bip32_get_authentikey()
                            is_seeded = "yes"
                        except UninitializedSeedError:
                            is_seeded = "no"
                        except Exception:
                            is_seeded = "unknown"
                    # secure channel
                    if status["needs_secure_channel"]:
                        needs_SC = "yes"
                    else:
                        needs_SC = "no"
                else:
                    msg_status = 'No card found! please insert card!'

                frame_layout1 = [[
                    sg.Text('Supported Version: ', size=(20, 1)),
                    sg.Text(sw_rel)
                ],
                                 [
                                     sg.Text('Firmware Version: ',
                                             size=(20, 1)),
                                     sg.Text(fw_rel)
                                 ],
                                 [
                                     sg.Text('Wallet is seeded: ',
                                             size=(20, 1)),
                                     sg.Text(is_seeded)
                                 ],
                                 [
                                     sg.Text('Requires 2FA: ', size=(20, 1)),
                                     sg.Text(needs_2FA)
                                 ],
                                 [
                                     sg.Text('Uses Secure Channel: ',
                                             size=(20, 1)),
                                     sg.Text(needs_SC)
                                 ]]
                frame_layout2 = [[
                    sg.Text(msg_status,
                            justification='center',
                            relief=sg.RELIEF_SUNKEN)
                ]]
                frame_layout3 = [[
                    sg.Text(msg_copyright,
                            justification='center',
                            relief=sg.RELIEF_SUNKEN)
                ]]
                layout = [[
                    sg.Frame('Satochip',
                             frame_layout1,
                             font='Any 12',
                             title_color='blue')
                ],
                          [
                              sg.Frame('Satochip status',
                                       frame_layout2,
                                       font='Any 12',
                                       title_color='blue')
                          ],
                          [
                              sg.Frame('About Satochip-Bridge',
                                       frame_layout3,
                                       font='Any 12',
                                       title_color='blue')
                          ], [sg.Button('Ok')]]

                window = sg.Window('Satochip-Bridge: About',
                                   layout,
                                   icon=self.satochip_icon)
                event, value = window.read()
                window.close()
                del window
                continue

            ## Quit ##
            elif menu_item in (None, 'Quit'):
                break

            # check for handle requests from client through the queue
            self.reply()

        # exit after leaving the loop
        #sys.exit() # does not finish background thread
        os._exit(
            0
        )  # kill background thread but doesn't let the interpreter do any cleanup before the process dies
Esempio n. 5
0
    def sign_transaction(self, tx, password, *, use_cache=False):
        self.print_error('sign_transaction(): tx: '+ str(tx)) #debugSatochip

        client = self.get_client()


        # outputs
        txOutputs= ''.join(tx.serialize_output(o) for o in tx.outputs())
        hashOutputs = bh2u(Hash(bfh(txOutputs)))
        txOutputs = var_int(len(tx.outputs()))+txOutputs
        self.print_error('sign_transaction(): hashOutputs= ', hashOutputs) #debugSatochip
        self.print_error('sign_transaction(): outputs= ', txOutputs) #debugSatochip

        # Fetch inputs of the transaction to sign
        derivations = self.get_tx_derivations(tx)
        for i,txin in enumerate(tx.inputs()):
            self.print_error('sign_transaction(): input =', i) #debugSatochip
            self.print_error('sign_transaction(): input[type]:', txin['type']) #debugSatochip
            if txin['type'] == 'coinbase':
                self.give_error("Coinbase not supported")     # should never happen

            if txin['type'] in ['p2sh']:
                p2shTransaction = True


            pubkeys, x_pubkeys = tx.get_sorted_pubkeys(txin)
            for j, x_pubkey in enumerate(x_pubkeys):
                self.print_error('sign_transaction(): forforloop: j=', j) #debugSatochip
                if tx.is_txin_complete(txin):
                    break

                if x_pubkey in derivations:
                    signingPos = j
                    s = derivations.get(x_pubkey)
                    address_path = "%s/%d/%d" % (self.get_derivation()[2:], s[0], s[1])

                    # get corresponing extended key
                    (depth, bytepath)= bip32path2bytes(address_path)
                    (key, chaincode)=client.cc.card_bip32_get_extendedkey(bytepath)

                    # parse tx
                    pre_tx_hex= tx.serialize_preimage(i)
                    pre_tx= bytes.fromhex(pre_tx_hex)# hex representation => converted to bytes
                    pre_hash = Hash(bfh(pre_tx_hex))
                    pre_hash_hex= pre_hash.hex()
                    self.print_error('sign_transaction(): pre_tx_hex=', pre_tx_hex) #debugSatochip
                    self.print_error('sign_transaction(): pre_hash=', pre_hash_hex) #debugSatochip
                    #(response, sw1, sw2) = client.cc.card_parse_transaction(pre_tx, True) # use 'True' since BCH use BIP143 as in Segwit...
                    #print_error('[satochip] sign_transaction(): response= '+str(response)) #debugSatochip
                    #(tx_hash, needs_2fa) = client.parser.parse_parse_transaction(response)
                    (response, sw1, sw2, tx_hash, needs_2fa) = client.cc.card_parse_transaction(pre_tx, True) # use 'True' since BCH use BIP143 as in Segwit...
                    tx_hash_hex= bytearray(tx_hash).hex()
                    if pre_hash_hex!= tx_hash_hex:
                        raise RuntimeError(f"[Satochip_KeyStore] Tx preimage mismatch: {pre_hash_hex} vs {tx_hash_hex}")


                    # sign tx
                    keynbr= 0xFF #for extended key
                    if needs_2fa:
                        # format & encrypt msg
                        import json
                        coin_type= 145 #see https://github.com/satoshilabs/slips/blob/master/slip-0044.md
                        test_net= networks.net.TESTNET
                        msg= {'action':"sign_tx", 'tx':pre_tx_hex, 'ct':coin_type, 'sw':True, 'tn':test_net, 'txo':txOutputs, 'ty':txin['type']}
                        msg=  json.dumps(msg)
                        (id_2FA, msg_out)= client.cc.card_crypt_transaction_2FA(msg, True)
                        d={}
                        d['msg_encrypt']= msg_out
                        d['id_2FA']= id_2FA
                        # self.print_error("encrypted message: "+msg_out)
                        self.print_error("id_2FA:", id_2FA)

                        #do challenge-response with 2FA device...
                        client.handler.show_message('2FA request sent! Approve or reject request on your second device.')
                        Satochip2FA.do_challenge_response(d)
                        # decrypt and parse reply to extract challenge response
                        try:
                            reply_encrypt= None  # init it in case of exc below
                            reply_encrypt= d['reply_encrypt']
                        except Exception as e:
                            # Note: give_error here will raise again.. :/
                            self.give_error("No response received from 2FA.\nPlease ensure that the Satochip-2FA plugin is enabled in Tools>Optional Features", True)
                            break
                        if reply_encrypt is None:
                            #todo: abort tx
                            break
                        reply_decrypt= client.cc.card_crypt_transaction_2FA(reply_encrypt, False)
                        self.print_error("challenge:response=", reply_decrypt)
                        reply_decrypt= reply_decrypt.split(":")
                        rep_pre_hash_hex= reply_decrypt[0][0:64]
                        if rep_pre_hash_hex!= pre_hash_hex:
                            #todo: abort tx or retry?
                            self.print_error("Abort transaction: tx mismatch:",rep_pre_hash_hex,"!=",pre_hash_hex)
                            break
                        chalresponse=reply_decrypt[1]
                        if chalresponse=="00"*20:
                            #todo: abort tx?
                            self.print_error("Abort transaction: rejected by 2FA!")
                            break
                        chalresponse= list(bytes.fromhex(chalresponse))
                    else:
                        chalresponse= None
                    (tx_sig, sw1, sw2) = client.cc.card_sign_transaction(keynbr, tx_hash, chalresponse)
                    #self.print_error('sign_transaction(): sig=', bytes(tx_sig).hex()) #debugSatochip
                    #todo: check sw1sw2 for error (0x9c0b if wrong challenge-response)
                    # enforce low-S signature (BIP 62)
                    tx_sig = bytearray(tx_sig)
                    r,s= get_r_and_s_from_der_sig(tx_sig)
                    if s > CURVE_ORDER//2:
                        s = CURVE_ORDER - s
                    tx_sig=der_sig_from_r_and_s(r, s)
                    #update tx with signature
                    tx_sig = tx_sig.hex()+'41'
                    #tx.add_signature_to_txin(i,j,tx_sig)
                    txin['signatures'][j] = tx_sig
                    break
            else:
                self.give_error("No matching x_key for sign_transaction") # should never happen

        self.print_error("is_complete", tx.is_complete())
        tx.raw = tx.serialize()
        return
Esempio n. 6
0
    def handleMessage(self):
        global cc, status, EXIT_SUCCESS, EXIT_FAILURE, logger
        logger.debug("In handleMessage()")
        logger.debug("Data: "+str(type(self.data))+"  "+self.data)

        # parse msg
        try: 
            msg= json.loads(self.data)          
            action= msg["action"]
        except Exception as e:
            logger.warning("exception: "+repr(e))
            
        try:
            if (action=="get_status"):
                response, sw1, sw2, status = cc.card_get_status()
                status["requestID"]= msg["requestID"]
                status["action"]= msg["action"]
                status['exitstatus']= EXIT_SUCCESS
                reply= json.dumps(status)
                self.sendMessage(reply)
                logger.debug("Reply: "+reply)    
                                
            elif (action=="get_chaincode"):
                path= msg["path"]
                #(depth, bytepath)= parser.bip32path2bytes(path)
                (pubkey, chaincode)= cc.card_bip32_get_extendedkey(path)
                # convert to string
                pubkey= pubkey.get_public_key_hex(False) # non-compressed hexstring
                chaincode= chaincode.hex() # hexstring
                d= {'requestID':msg["requestID"], 'action':msg["action"], 'pubkey':pubkey, 'chaincode':chaincode, 'exitstatus':EXIT_SUCCESS}
                reply= json.dumps(d)
                self.sendMessage(reply)
                logger.debug("Reply: "+reply)    
                
            elif (action=="sign_tx_hash") or (action=="sign_msg_hash"):
                
                # prepare key corresponding to desired path
                path= msg["path"]
                #(depth, bytepath)= parser.bip32path2bytes(path)
                (pubkey, chaincode)= cc.card_bip32_get_extendedkey(path)
                logger.debug("Sign with pubkey: "+ pubkey.get_public_key_bytes(compressed=False).hex())
                logger.debug("Sign hash: "+ msg["hash"])
                keynbr=0xFF
                
                if cc.needs_2FA:
                    #msg2FA= {'action':action, 'msg':message, 'alt':'etherlike'}
                    msg_2FA=  json.dumps(msg)
                    (id_2FA, msg_2FA)= cc.card_crypt_transaction_2FA(msg_2FA, True)
                    d={}
                    d['msg_encrypt']= msg_2FA
                    d['id_2FA']= id_2FA
                    logger.debug("encrypted message: "+msg_2FA)
                    logger.debug("id_2FA: "+ id_2FA)
                    
                    #do challenge-response with 2FA device...
                    notif= '2FA request sent! Approve or reject request on your second device.'
                    cc.client.request('show_notification', notif)
                    #cc.client.request('show_message', notif)
                    Satochip2FA.do_challenge_response(d)
                    # decrypt and parse reply to extract challenge response
                    try: 
                        reply_encrypt= d['reply_encrypt']
                    except Exception as e:
                        cc.client.request('show_error', "No response received from 2FA...")
                    reply_decrypt= cc.card_crypt_transaction_2FA(reply_encrypt, False)
                    logger.debug("challenge:response= "+ reply_decrypt)
                    reply_decrypt= reply_decrypt.split(":")
                    chalresponse=reply_decrypt[1]   
                    hmac= list(bytes.fromhex(chalresponse))
                else:
                    hmac=None
                    logger.debug("Skip confirmation for this action? "+ str(wallets[self]) )
                    if not wallets[self]: #if confirm required
                        request_action= "sign a message" if action=="sign_msg_hash" else "sign a transaction"
                        request_msg= ("A client wants to perform the following on your Satochip:"+
                                                        "\n\tAction: "+ request_action +
                                                        "\n\tAddress:"+ str(self.address)+
                                                        "\n\nApprove action?")
                        (event, values)= cc.client.request('approve_action', request_msg)
                        if event== 'No' or event== 'None':
                            hmac=20*[0] # will trigger reject   
                        else:
                            wallets[self]= values['skip_conf']
                
                if (hmac==20*[0]): # rejected by 2FA or user
                    d= {'requestID':msg["requestID"], 'action':msg["action"], "hash":msg["hash"], 
                        "sig":71*'00', "r":32*'00', "s":32*'00', "v":0 , "pubkey":pubkey.get_public_key_bytes().hex(),
                        'exitstatus':EXIT_FAILURE, 'reason':'Signing request rejected by user'}
                    reply= json.dumps(d)
                    self.sendMessage(reply)
                    logger.debug("Reply: "+reply)    
                else:
                    hash= list(bytes.fromhex(msg["hash"]))
                    (response, sw1, sw2)=cc.card_sign_transaction_hash(keynbr, hash, hmac)
                    
                    # convert sig to rsv format:
                    logger.debug ("Convert sig to rsv format...")
                    try: 
                        #compsig= parser.parse_hash_signature(response, bytes.fromhex(msg["hash"]), pubkey)
                        (r,s,v, sigstring)= cc.parser.parse_rsv_from_dersig(bytes(response), bytes.fromhex(msg["hash"]), pubkey) 
                        # r,s,v:int convert to hex (64-char padded with 0)
                        r= "{0:0{1}x}".format(r,64) 
                        s= "{0:0{1}x}".format(s,64) 
                        logger.debug("sigstring: " + sigstring.hex())
                        logger.debug ("r= " + r)
                        logger.debug ("s= " + s)
                        logger.debug ("v= " + str(v))
                    except Exception as e:
                        logger.warning("Exception in parse_rsv_from_dersig: " + repr(e)) 
                      
                    d= {'requestID':msg["requestID"], 'action':msg["action"], "hash":msg["hash"], 
                                "sig":sigstring.hex(), "r":r, "s":s, "v":v, "pubkey":pubkey.get_public_key_bytes().hex(),
                                'exitstatus':EXIT_SUCCESS}
                    reply= json.dumps(d)
                    self.sendMessage(reply)
                    logger.debug("Reply: "+reply)    
                
            else:
                d= {'requestID':msg['requestID'], 'action':msg['action'], 'exitstatus':EXIT_FAILURE, 'reason':'Action unknown'}
                reply= json.dumps(d)
                self.sendMessage(reply)
                logger.warning("Unknown action: "+action)
                
        except Exception as e:
            logger.warning('Exception: ' + repr(e))
            cc.client.request('show_error','[handleMessage] Exception: '+repr(e))