diff --git a/electrum/plugins/satochip/qt.py b/electrum/plugins/satochip/qt.py index cd8244736ebe..26620bef5d6a 100644 --- a/electrum/plugins/satochip/qt.py +++ b/electrum/plugins/satochip/qt.py @@ -4,7 +4,7 @@ from electrum.gui.qt.util import (EnterButton, Buttons, CloseButton, OkButton, CancelButton, WindowModalDialog, WWLabel) from electrum.gui.qt.qrcodewidget import QRCodeWidget, QRDialog, QRDialogCancellable from PyQt5.QtCore import Qt, pyqtSignal -from PyQt5.QtWidgets import (QPushButton, QLabel, QVBoxLayout, QWidget, QGridLayout, QLineEdit, QCheckBox) +from PyQt5.QtWidgets import (QPushButton, QLabel, QVBoxLayout, QHBoxLayout, QWidget, QGridLayout, QComboBox, QLineEdit, QCheckBox) from functools import partial from os import urandom @@ -14,7 +14,7 @@ #pysatochip from pysatochip.CardConnector import CardConnector, UnexpectedSW12Error, CardError, CardNotPresentError -from pysatochip.Satochip2FA import Satochip2FA +from pysatochip.Satochip2FA import Satochip2FA, SERVER_LIST from pysatochip.version import SATOCHIP_PROTOCOL_MAJOR_VERSION, SATOCHIP_PROTOCOL_MINOR_VERSION _logger = get_logger(__name__) @@ -150,6 +150,11 @@ def _reset_2FA(): thread.add(connect_and_doit, on_success=self.show_values) reset_2FA_btn.clicked.connect(_reset_2FA) + change_2FA_server_btn = QPushButton('Select 2FA server') + def _change_2FA_server(): + thread.add(connect_and_doit, on_success=self.change_2FA_server) + change_2FA_server_btn.clicked.connect(_change_2FA_server) + verify_card_btn = QPushButton('Verify card') def _verify_card(): thread.add(connect_and_doit, on_success=self.verify_card) @@ -170,6 +175,8 @@ def _change_card_label(): y += 2 grid.addWidget(reset_2FA_btn, y, 0, 1, 2, Qt.AlignHCenter) y += 2 + grid.addWidget(change_2FA_server_btn, y, 0, 1, 2, Qt.AlignHCenter) + y += 2 grid.addWidget(verify_card_btn, y, 0, 1, 2, Qt.AlignHCenter) y += 2 grid.addWidget(change_card_label_btn, y, 0, 1, 2, Qt.AlignHCenter) @@ -404,7 +411,17 @@ def reset_2FA(self, client): else: msg= _(f"2FA is already disabled!") self.window.show_error(msg) - + + def change_2FA_server(self, client): + _logger.info("in change_2FA_server") + config = SimpleConfig() + help_txt="Select 2FA server in the list:" + option_name= "satochip_2FA_server" + options= SERVER_LIST #["server1", "server2", "server3"] + title= "Select 2FA server" + d = SelectOptionsDialog(option_name = option_name, options = options, parent=None, title=title, help_text=help_txt, config=config) + result=d.exec_() # result should be 0 or 1 + def verify_card(self, client): is_authentic, txt_ca, txt_subca, txt_device, txt_error = self.card_verify_authenticity(client) @@ -499,5 +516,53 @@ def change_card_label_dialog(self, client, msg): self.window.show_error(_("Card label should not be longer than 64 chars!")) - +class SelectOptionsDialog(WindowModalDialog): + + def __init__( + self, + *, + option_name, + options=None, + parent=None, + title="", + help_text=None, + config: SimpleConfig, + ): + WindowModalDialog.__init__(self, parent, title) + self.config = config + + vbox = QVBoxLayout() + if help_text: + text_label = WWLabel() + text_label.setText(help_text) + vbox.addWidget(text_label) + + def set_option(): + _logger.info(f"New 2FA server: {options_combo.currentText()}") + # save in config + config.set_key(option_name, options_combo.currentText(), save=True) + _logger.info("config changed!") + + default= config.get(option_name, default= SERVER_LIST[0]) + options_combo = QComboBox() + options_combo.addItems(options) + options_combo.setCurrentText(default) + options_combo.currentIndexChanged.connect(set_option) + vbox.addWidget(options_combo) + + hbox = QHBoxLayout() + hbox.addStretch(1) + + b = QPushButton(_("Ok")) + hbox.addWidget(b) + b.clicked.connect(self.accept) + b.setDefault(True) + + vbox.addLayout(hbox) + self.setLayout(vbox) + + # note: the word-wrap on the text_label is causing layout sizing issues. + # see https://stackoverflow.com/a/25661985 and https://bugreports.qt.io/browse/QTBUG-37673 + # workaround: + self.setMinimumSize(self.sizeHint()) \ No newline at end of file diff --git a/electrum/plugins/satochip/satochip.py b/electrum/plugins/satochip/satochip.py index 3dcefa5a56a0..af74696db4d6 100644 --- a/electrum/plugins/satochip/satochip.py +++ b/electrum/plugins/satochip/satochip.py @@ -17,6 +17,7 @@ from electrum.ecc import CURVE_ORDER, der_sig_from_r_and_s, get_r_and_s_from_der_sig, ECPubkey from electrum.bip32 import BIP32Node, convert_bip32_strpath_to_intpath, convert_bip32_intpath_to_strpath from electrum.logging import get_logger +from electrum.simple_config import SimpleConfig from electrum.gui.qt.qrcodewidget import QRCodeWidget, QRDialog from ..hw_wallet import HW_PluginBase, HardwareClientBase @@ -421,20 +422,20 @@ def do_challenge_response(self, msg): _logger.info("id_2FA: "+id_2FA) reply_encrypt= None - hmac= 20*"00" #bytes.fromhex(20*"00") # default response (reject) + hmac= 20*"00" # default response (reject) status_msg="" - for server in SERVER_LIST: - status_msg += f"2FA request sent to '{server}' \nApprove or reject request on your second device." + + config = SimpleConfig() + server_2FA = config.get("satochip_2FA_server", default= SERVER_LIST[0]) + status_msg += f"2FA request sent to '{server_2FA}' \nApprove or reject request on your second device." + self.handler.show_message(status_msg) + try: + Satochip2FA.do_challenge_response(d, server_name= server_2FA) + # decrypt and parse reply to extract challenge response + reply_encrypt= d['reply_encrypt'] + except Exception as e: + status_msg += f"\nFailed to contact cosigner! \n=> Select another 2FA server in Satochip settings\n\n" self.handler.show_message(status_msg) - try: - Satochip2FA.do_challenge_response(d, server_name= server) - # decrypt and parse reply to extract challenge response - reply_encrypt= d['reply_encrypt'] - break - except Exception as e: - status_msg += f"\nFailed to contact cosigner! \n=>trying another server\n\n" - self.handler.show_message(status_msg) - #self.handler.show_error(f"No response received from '{server}', trying another server") if reply_encrypt is not None: reply_decrypt= client.cc.card_crypt_transaction_2FA(reply_encrypt, False) _logger.info("challenge:response= "+ reply_decrypt)