From 9ec48c831becb9c81b63b5bbda8156da37e4255f Mon Sep 17 00:00:00 2001 From: davide3011 Date: Mon, 16 Feb 2026 15:58:52 +0100 Subject: [PATCH] network/gui: add unified reset for cached server SSL certificates - add Network.clear_pinned_server_certs() to clear cached files in config.path/certs - close active interfaces after cleanup so certificates are fetched again on reconnect - expose clearPinnedServerCertificates() in the QML network bridge - add a confirmed 'Reset SSL certificates' action in QML NetworkOverview - add the same confirmed action in the Qt Network dialog (Server tab) --- .../gui/qml/components/NetworkOverview.qml | 28 ++++++++++++++++ electrum/gui/qml/qenetwork.py | 8 +++++ electrum/gui/qt/network_dialog.py | 33 ++++++++++++++++++- electrum/network.py | 21 ++++++++++++ 4 files changed, 89 insertions(+), 1 deletion(-) diff --git a/electrum/gui/qml/components/NetworkOverview.qml b/electrum/gui/qml/components/NetworkOverview.qml index 7e9b21f2b..01372122c 100644 --- a/electrum/gui/qml/components/NetworkOverview.qml +++ b/electrum/gui/qml/components/NetworkOverview.qml @@ -296,6 +296,34 @@ Pane { dialog.open() } } + + FlatButton { + Layout.fillWidth: true + Layout.preferredWidth: 1 + text: qsTr('Reset SSL certificates'); + icon.source: '../../icons/network.png' + onClicked: { + var dialog = app.messageDialog.createObject(app, { + title: qsTr('Are you sure?'), + text: qsTr('This will remove cached SSL certificates for servers and reconnect to fetch them again.'), + yesno: true + }) + dialog.accepted.connect(function() { + var removed = Network.clearPinnedServerCertificates() + var msg = removed < 0 + ? qsTr('Failed to reset SSL certificates.') + : removed > 0 + ? qsTr('%1 certificate files were removed.').arg(removed) + : qsTr('No cached certificate files were found.') + var infoDialog = app.messageDialog.createObject(app, { + title: qsTr('Reset SSL certificates'), + text: msg + }) + infoDialog.open() + }) + dialog.open() + } + } } } diff --git a/electrum/gui/qml/qenetwork.py b/electrum/gui/qml/qenetwork.py index 099e21a80..84707b8c6 100644 --- a/electrum/gui/qml/qenetwork.py +++ b/electrum/gui/qml/qenetwork.py @@ -306,3 +306,11 @@ class QENetwork(QObject, QtEventListener): @pyqtSlot() def probeTor(self): ProxySettings.probe_tor(self.torProbeFinished.emit) # via signal + + @pyqtSlot(result=int) + def clearPinnedServerCertificates(self): + try: + return self.network.run_from_another_thread(self.network.clear_pinned_server_certs()) + except Exception: + self._logger.exception("failed to clear pinned server certificates") + return -1 diff --git a/electrum/gui/qt/network_dialog.py b/electrum/gui/qt/network_dialog.py index 4d7c68008..eebdb4763 100644 --- a/electrum/gui/qt/network_dialog.py +++ b/electrum/gui/qt/network_dialog.py @@ -29,7 +29,7 @@ from PyQt6.QtCore import Qt, pyqtSignal, pyqtSlot from PyQt6.QtWidgets import ( QTreeWidget, QTreeWidgetItem, QMenu, QGridLayout, QComboBox, QLineEdit, QDialog, QVBoxLayout, QHeaderView, QCheckBox, QTabWidget, QWidget, QLabel, QPushButton, QHBoxLayout, - QListWidget, QListWidgetItem, + QListWidget, QListWidgetItem, QMessageBox, ) from PyQt6.QtGui import QIntValidator @@ -444,6 +444,13 @@ class ServerWidget(QWidget, QtEventListener): self.layout().addWidget(self.nodes_list_widget) self.nodes_list_widget.update() + self.clear_certs_button = QPushButton(_('Reset SSL certificates')) + self.clear_certs_button.clicked.connect(self.clear_pinned_server_certs) + buttons = QHBoxLayout() + buttons.addStretch(1) + buttons.addWidget(self.clear_certs_button) + self.layout().addLayout(buttons) + self.register_callbacks() self.destroyed.connect(lambda: self.unregister_callbacks()) @@ -584,6 +591,30 @@ class ServerWidget(QWidget, QtEventListener): _logger.debug(f"set_server: {new_net_params=}") self.network.run_from_another_thread(self.network.set_parameters(new_net_params)) + def clear_pinned_server_certs(self): + result = QMessageBox.question( + self, + _('Reset SSL certificates'), + _('This will remove cached SSL certificates for servers and reconnect to fetch them again.') + + '\n\n' + + _('Do you want to continue?'), + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, + QMessageBox.StandardButton.No, + ) + if result != QMessageBox.StandardButton.Yes: + return + try: + removed = self.network.run_from_another_thread(self.network.clear_pinned_server_certs()) + except Exception as e: + _logger.exception("failed to clear pinned server certificates") + QMessageBox.critical(self, _('Reset SSL certificates'), str(e)) + return + if removed > 0: + msg = _('{} certificate files were removed.').format(removed) + else: + msg = _('No cached certificate files were found.') + QMessageBox.information(self, _('Reset SSL certificates'), msg) + class NostrWidget(QWidget, QtEventListener): diff --git a/electrum/network.py b/electrum/network.py index 966a31e6f..3a11514da 100644 --- a/electrum/network.py +++ b/electrum/network.py @@ -408,6 +408,27 @@ class Network(Logger, NetworkRetryManager[ServerAddr]): """Our guess whether the device has Internet-connectivity.""" return self._has_ever_managed_to_connect_to_server + async def clear_pinned_server_certs(self) -> int: + """Delete cached server SSL certs and reconnect interfaces.""" + certs_dir = os.path.join(self.config.path, 'certs') + util.make_dir(certs_dir) + removed = 0 + for entry in os.scandir(certs_dir): + if not entry.is_file(): + continue + try: + os.unlink(entry.path) + removed += 1 + except OSError as e: + self.logger.warning(f"failed to delete cert file {entry.path!r}: {e!r}") + self.logger.info(f"removed {removed} cached server cert(s)") + if removed: + with self.interfaces_lock: + interfaces = list(self.interfaces.values()) + for iface in interfaces: + await self._close_interface(iface) + return removed + def has_channel_db(self): return self.channel_db is not None