def read_file_encrypted(
        file_name: str, ret_attrs: dict,
        hw_session: HwSessionInfo) -> Generator[bytes, None, None]:
    ret_attrs['encrypted'] = False

    try:
        hw_session.save_state()
        with open(file_name, 'rb') as f_ptr:
            data = f_ptr.read(len(DMT_ENCRYPTED_DATA_PREFIX))
            if data == DMT_ENCRYPTED_DATA_PREFIX:
                ret_attrs['encrypted'] = True

                protocol = read_varint_from_file(f_ptr)
                if protocol == 1:  # with Trezor method + Fernet

                    hw_type_bin = read_varint_from_file(f_ptr)
                    hw_type = {
                        1: HWType.trezor,
                        2: HWType.keepkey,
                        3: HWType.ledger_nano
                    }.get(hw_type_bin)

                    if hw_type:
                        # connect hardware wallet, choosing the type compatible with the type read from
                        # the encrypted file
                        if hw_session.hw_client:
                            if (hw_type in (HWType.trezor, HWType.keepkey) and
                                hw_session.hw_type not in (HWType.trezor, HWType.keepkey)) or \
                                    (hw_type == HWType.ledger_nano and hw_type != hw_session.hw_type):
                                # if the currently connected hardware wallet type is not compatible with the
                                # type from the encrypted file, disconnect it to give a user a chance to choose
                                # the correct one in the code below
                                hw_session.disconnect_hardware_wallet()

                        if not hw_session.hw_client:
                            if hw_type in (HWType.trezor, HWType.keepkey):
                                hw_session.set_hw_types_allowed(
                                    (HWType.trezor, HWType.keepkey))
                            else:
                                hw_session.set_hw_types_allowed((hw_type, ))
                            if not hw_session.connect_hardware_wallet():
                                raise HWNotConnectedException(
                                    f'This file was encrypted with {HWType.get_desc(hw_type)} hardware wallet, '
                                    f'which has to be connected to the computer decrypt the file.'
                                )

                        data_label_bin = read_bytes_from_file(f_ptr)
                        label = base64.urlsafe_b64decode(
                            data_label_bin).decode('utf-8')

                        encrypted_key_bin = read_bytes_from_file(f_ptr)
                        bip32_path_n = read_int_list_from_file(f_ptr)
                        pub_key_hash_hdr = read_bytes_from_file(f_ptr)

                        while True:
                            if not hw_session.hw_client:
                                raise HWNotConnectedException(
                                    f'This file was encrypted with {HWType.get_desc(hw_type)} hardware wallet, '
                                    f'which has to be connected to the computer decrypt the file.'
                                )

                            if hw_session.hw_type in (HWType.trezor,
                                                      HWType.keepkey):
                                key_bin, pub_key = hw_session.hw_decrypt_value(
                                    bip32_path_n,
                                    label=label,
                                    value=encrypted_key_bin)
                            elif hw_session.hw_type == HWType.ledger_nano:
                                display_label = f'<b>Click the sign message confirmation button on the <br>' \
                                                f'hardware wallet to decrypt \'{label}\'.</b>'
                                bip32_path_str = bip32_path_n_to_string(
                                    bip32_path_n)
                                sig = hw_sign_message(
                                    hw_session,
                                    'Dash',
                                    bip32_path_str,
                                    encrypted_key_bin.hex(),
                                    display_label=display_label)
                                adr_pk = get_address_and_pubkey(
                                    hw_session, 'Dash', bip32_path_str)

                                pub_key = adr_pk.get('publicKey')
                                key_bin = SHA256.new(sig.signature).digest()
                            else:
                                raise Exception(
                                    'Invalid hardware wallet type.')

                            pub_key_hash = SHA256.new(pub_key).digest()

                            if pub_key_hash_hdr == pub_key_hash:
                                break

                            url = get_note_url('DMT0003')
                            if WndUtils.query_dlg(
                                    message=
                                    'Inconsistency between encryption and decryption keys.\n\n'
                                    'The reason may be using a different passphrase than it was used '
                                    'for encryption or running another application communicating with the '
                                    'device simultaneously, like Trezor web wallet (see <a href="{url}">'
                                    'here</a>).\n\n'
                                    'Do you want to try again?',
                                    buttons=QMessageBox.Yes
                                    | QMessageBox.Cancel,
                                    default_button=QMessageBox.Cancel,
                                    icon=QMessageBox.Warning
                            ) == QMessageBox.Cancel:
                                raise CancelException('User cancelled.')
                            hw_session.disconnect_hardware_wallet()
                            hw_session.connect_hardware_wallet()

                        key = base64.urlsafe_b64encode(key_bin)
                        fer = Fernet(key)

                        while True:
                            # data is written in blocks; if front of each block there is a block size value
                            data_bin = f_ptr.read(8)
                            if len(data_bin) == 0:
                                break  # end of file
                            elif len(data_bin) < 8:
                                raise ValueError(
                                    'File end before read completed.')

                            data_chunk_size = int.from_bytes(
                                data_bin, byteorder='little')
                            if data_chunk_size < 0 or data_chunk_size > 2000000000:
                                raise ValueError(
                                    'Data corrupted: invalid data chunk size.')

                            data_bin = f_ptr.read(data_chunk_size)
                            if data_chunk_size != len(data_bin):
                                raise ValueError(
                                    'File end before read completed.')
                            data_base64 = base64.urlsafe_b64encode(data_bin)
                            try:
                                data_decr = fer.decrypt(data_base64)
                            except InvalidToken:
                                raise Exception(
                                    'Couldn\'t decrypt file (InvalidToken error). The file is probably '
                                    'corrupted or is encrypted with a different encryption method.'
                                )
                            yield data_decr
                    else:
                        raise ValueError('Invalid hardware wallet type value.')
                else:
                    raise ValueError('Invalid protocol value.')
            else:
                # the data inside the file isn't encrypted

                # read and yield raw data
                while True:
                    # data is written in blocks; if front of each block there is a block size value
                    data += f_ptr.read(ENC_FILE_BLOCK_SIZE)
                    if not len(data):
                        break
                    yield data
                    data = bytes()
    finally:
        hw_session.restore_state()
Ejemplo n.º 2
0
    def setupUi(self):
        Ui_ConfigDlg.setupUi(self, self)
        self.resize(
            app_cache.get_value('ConfigDlg_Width',
                                self.size().width(), int),
            app_cache.get_value('ConfigDlg_Height',
                                self.size().height(), int))

        self.setWindowTitle("Configuration")
        self.splitter.setStretchFactor(0, 0)
        self.splitter.setStretchFactor(1, 1)
        self.accepted.connect(self.on_accepted)
        self.tabWidget.setCurrentIndex(0)

        self.disable_cfg_update = True

        layout_details = self.detailsFrame.layout()
        self.chbConnEnabled = QCheckBox("Enabled")
        self.chbConnEnabled.toggled.connect(self.on_chbConnEnabled_toggled)
        layout_details.addWidget(self.chbConnEnabled)
        self.chbUseSshTunnel = QCheckBox("Use SSH tunnel")
        self.chbUseSshTunnel.toggled.connect(self.on_chbUseSshTunnel_toggled)
        layout_details.addWidget(self.chbUseSshTunnel)
        self.ssh_tunnel_widget = SshConnectionWidget(self)
        layout_details.addWidget(self.ssh_tunnel_widget)

        # layout for button for reading RPC configuration from remote host over SSH:
        hl = QHBoxLayout()
        self.btnSshReadRpcConfig = QPushButton(
            "\u2B07 Read RPC configuration from SSH host \u2B07")
        self.btnSshReadRpcConfig.clicked.connect(
            self.on_btnSshReadRpcConfig_clicked)
        hl.addWidget(self.btnSshReadRpcConfig)
        hl.addStretch()
        layout_details.addLayout(hl)

        # add connection-editing controls widget:
        self.rpc_cfg_widget = RpcConnectionWidget(self.detailsFrame)
        layout_details.addWidget(self.rpc_cfg_widget)

        # layout for controls related to setting up an additional encryption
        hl = QHBoxLayout()
        self.btnEncryptionPublicKey = QPushButton("RPC encryption public key")
        self.btnEncryptionPublicKey.clicked.connect(
            self.on_btnEncryptionPublicKey_clicked)
        hl.addWidget(self.btnEncryptionPublicKey)
        self.lblEncryptionPublicKey = QLabel(self)
        self.lblEncryptionPublicKey.setText('')
        hl.addWidget(self.lblEncryptionPublicKey)
        hl.addStretch()
        layout_details.addLayout(hl)

        # layout for the 'test connection' button:
        hl = QHBoxLayout()
        self.btnTestConnection = QPushButton("\u2705 Test connection")
        self.btnTestConnection.clicked.connect(
            self.on_btnTestConnection_clicked)
        sp = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum,
                                   QtWidgets.QSizePolicy.Minimum)
        sp.setHorizontalStretch(0)
        sp.setVerticalStretch(0)
        sp.setHeightForWidth(
            self.btnTestConnection.sizePolicy().hasHeightForWidth())
        self.btnTestConnection.setSizePolicy(sp)
        hl.addWidget(self.btnTestConnection)
        hl.addStretch()
        layout_details.addLayout(hl)
        layout_details.addStretch()

        self.rpc_cfg_widget.edtRpcHost.textEdited.connect(
            self.on_edtRpcHost_textEdited)
        self.rpc_cfg_widget.edtRpcPort.textEdited.connect(
            self.on_edtRpcPort_textEdited)
        self.rpc_cfg_widget.edtRpcUsername.textEdited.connect(
            self.on_edtRpcUsername_textEdited)
        self.rpc_cfg_widget.edtRpcPassword.textEdited.connect(
            self.on_edtRpcPassword_textEdited)
        self.rpc_cfg_widget.chbRpcSSL.toggled.connect(self.chbRpcSSL_toggled)
        self.ssh_tunnel_widget.edtSshHost.textEdited.connect(
            self.on_edtSshHost_textEdited)
        self.ssh_tunnel_widget.edtSshPort.textEdited.connect(
            self.on_edtSshPort_textEdited)
        self.ssh_tunnel_widget.edtSshUsername.textEdited.connect(
            self.on_edtSshUsername_textEdited)
        self.ssh_tunnel_widget.cboAuthentication.currentIndexChanged.connect(
            self.on_cboSshAuthentication_currentIndexChanged)
        self.ssh_tunnel_widget.edtPrivateKeyPath.textChanged.connect(
            self.on_edtSshPrivateKeyPath_textChanged)

        self.lstConns.setContextMenuPolicy(Qt.CustomContextMenu)
        self.popMenu = QMenu(self)

        self.action_new_connection = self.popMenu.addAction(
            "Add new connection")
        self.action_new_connection.triggered.connect(
            self.on_action_new_connection_triggered)
        self.setIcon(self.action_new_connection, '*****@*****.**')
        self.btnNewConn.setDefaultAction(self.action_new_connection)

        self.action_delete_connections = self.popMenu.addAction(
            "Delete selected connection(s)")
        self.action_delete_connections.triggered.connect(
            self.on_action_delete_connections_triggered)
        self.setIcon(self.action_delete_connections, '*****@*****.**')
        self.btnDeleteConn.setDefaultAction(self.action_delete_connections)

        self.action_copy_connections = self.popMenu.addAction(
            "Copy connection(s) to clipboard",
            self.on_action_copy_connections_triggered, QKeySequence("Ctrl+C"))
        self.setIcon(self.action_copy_connections, '*****@*****.**')
        self.addAction(self.action_copy_connections)

        self.action_paste_connections = self.popMenu.addAction(
            "Paste connection(s) from clipboard",
            self.on_action_paste_connections_triggered, QKeySequence("Ctrl+V"))
        self.setIcon(self.action_paste_connections, '*****@*****.**')
        self.addAction(self.action_paste_connections)

        self.btnNewConn.setText("")
        self.btnDeleteConn.setText("")
        self.btnMoveDownConn.setText("")
        self.btnMoveUpConn.setText("")
        self.btnRestoreDefault.setText("")
        self.setIcon(self.btnMoveDownConn, "*****@*****.**")
        self.setIcon(self.btnMoveUpConn, "*****@*****.**", rotate=180)
        self.setIcon(self.btnRestoreDefault, "*****@*****.**")
        self.setIcon(self.rpc_cfg_widget.btnShowPassword, "*****@*****.**")

        self.rpc_cfg_widget.btnShowPassword.setText("")
        self.rpc_cfg_widget.btnShowPassword.pressed.connect(
            lambda: self.rpc_cfg_widget.edtRpcPassword.setEchoMode(QLineEdit.
                                                                   Normal))
        self.rpc_cfg_widget.btnShowPassword.released.connect(
            lambda: self.rpc_cfg_widget.edtRpcPassword.setEchoMode(QLineEdit.
                                                                   Password))

        if self.local_config.is_mainnet():
            self.cboHatchNetwork.setCurrentIndex(0)
            self.connections_current = self.connections_mainnet
        else:
            self.cboHatchNetwork.setCurrentIndex(1)
            self.connections_current = self.connections_testnet
        for cfg in self.local_config.hatch_net_configs:
            if cfg.testnet:
                self.connections_testnet.append(cfg)
            else:
                self.connections_mainnet.append(cfg)

        if self.local_config.hw_type == HWType.trezor:
            self.chbHwTrezor.setChecked(True)
        elif self.local_config.hw_type == HWType.keepkey:
            self.chbHwKeepKey.setChecked(True)
        else:
            self.chbHwLedgerNanoS.setChecked(True)

        if self.local_config.hw_keepkey_psw_encoding == 'NFC':
            self.cboKeepkeyPassEncoding.setCurrentIndex(0)
        else:
            self.cboKeepkeyPassEncoding.setCurrentIndex(1)
        note_url = get_note_url('HMTN0001')
        self.lblKeepkeyPassEncoding.setText(
            f'KepKey passphrase encoding (<a href="{note_url}">see</a>)')

        self.chbCheckForUpdates.setChecked(self.local_config.check_for_updates)
        self.chbBackupConfigFile.setChecked(
            self.local_config.backup_config_file)
        self.chbDownloadProposalExternalData.setChecked(
            self.local_config.read_proposals_external_attributes)
        self.chbDontUseFileDialogs.setChecked(
            self.local_config.dont_use_file_dialogs)
        self.chbConfirmWhenVoting.setChecked(
            self.local_config.confirm_when_voting)
        self.chbAddRandomOffsetToVotingTime.setChecked(
            self.local_config.add_random_offset_to_vote_time)
        self.chbEncryptConfigFile.setChecked(
            self.local_config.encrypt_config_file)

        idx = {
            'CRITICAL': 0,
            'ERROR': 1,
            'WARNING': 2,
            'INFO': 3,
            'DEBUG': 4,
            'NOTSET': 5
        }.get(self.local_config.log_level_str, 2)
        self.cboLogLevel.setCurrentIndex(idx)

        self.display_connection_list()
        if len(self.local_config.hatch_net_configs):
            self.lstConns.setCurrentRow(0)

        self.update_keepkey_pass_encoding_ui()
        self.update_connection_details_ui()
        self.disable_cfg_update = False
        self.splitter.setSizes(
            app_cache.get_value('ConfigDlg_ConnectionSplitter_Sizes',
                                [100, 100], list))