From 7f0844242d7d0f548b40526d0b43bab45425fba0 Mon Sep 17 00:00:00 2001 From: jj-so Date: Mon, 29 Apr 2024 13:40:45 +0200 Subject: [PATCH 01/14] Settings: add ui --- nitrokeyapp/gui.py | 8 +- nitrokeyapp/settings_tab/__init__.py | 82 ++++ nitrokeyapp/settings_tab/worker.py | 68 +++ nitrokeyapp/ui/settings_tab.ui | 594 +++++++++++++++++++++++++++ 4 files changed, 751 insertions(+), 1 deletion(-) create mode 100644 nitrokeyapp/settings_tab/__init__.py create mode 100644 nitrokeyapp/settings_tab/worker.py create mode 100644 nitrokeyapp/ui/settings_tab.ui diff --git a/nitrokeyapp/gui.py b/nitrokeyapp/gui.py index 93cf60f4..d020edc6 100644 --- a/nitrokeyapp/gui.py +++ b/nitrokeyapp/gui.py @@ -22,6 +22,7 @@ from nitrokeyapp.prompt_box import PromptBox from nitrokeyapp.qt_utils_mix_in import QtUtilsMixIn from nitrokeyapp.secrets_tab import SecretsTab +from nitrokeyapp.settings_tab import SettingsTab from nitrokeyapp.touch import TouchIndicator # import wizards and stuff @@ -91,8 +92,13 @@ def __init__(self, qt_app: QtWidgets.QApplication, log_file: str): self.overview_tab = OverviewTab(self) self.secrets_tab = SecretsTab(self) + self.settings_tab = SettingsTab(self) - self.views: list[DeviceView] = [self.overview_tab, self.secrets_tab] + self.views: list[DeviceView] = [ + self.overview_tab, + self.secrets_tab, + self.settings_tab, + ] for view in self.views: if view.worker: diff --git a/nitrokeyapp/settings_tab/__init__.py b/nitrokeyapp/settings_tab/__init__.py new file mode 100644 index 00000000..56ff7b7a --- /dev/null +++ b/nitrokeyapp/settings_tab/__init__.py @@ -0,0 +1,82 @@ +import binascii +import logging +from base64 import b32decode, b32encode +from enum import Enum +from random import randbytes +from typing import Callable, Optional + +from PySide6.QtCore import Qt, QThread, QTimer, Signal, Slot +from PySide6.QtGui import QGuiApplication +from PySide6.QtWidgets import QLineEdit, QListWidgetItem, QWidget + +from nitrokeyapp.common_ui import CommonUi +from nitrokeyapp.device_data import DeviceData +from nitrokeyapp.qt_utils_mix_in import QtUtilsMixIn +from nitrokeyapp.worker import Worker + +from .worker import SettingsWorker + +# logger = logging.getLogger(__name__) + +# class SettingsTabState(Enum): +# Initial = 0 +# ShowCred = 1 +# AddCred = 2 +# EditCred = 3 +# +# NotAvailable = 99 + + +class SettingsTab(QtUtilsMixIn, QWidget): + # standard UI + # busy_state_changed = Signal(bool) + # error = Signal(str, Exception) + # start_touch = Signal() + # stop_touch = Signal() + + # worker triggers + # trigger_add_credential = Signal(DeviceData, Credential, bytes) + + def __init__(self, parent: Optional[QWidget] = None) -> None: + QWidget.__init__(self, parent) + QtUtilsMixIn.__init__(self) + + self.data: Optional[DeviceData] = None + self.common_ui = CommonUi() + + self.worker_thread = QThread() + self._worker = SettingsWorker(self.common_ui) + self._worker.moveToThread(self.worker_thread) + self.worker_thread.start() + + self.ui = self.load_ui("settings_tab.ui", self) + + @property + def title(self) -> str: + return "Settings" + + @property + def widget(self) -> QWidget: + return self.ui + + @property + def worker(self) -> Optional[Worker]: + return self._worker + + def reset(self) -> None: + self.data = None + + def refresh(self, data: DeviceData, force: bool = False) -> None: + if data == self.data and not force: + return + self.reset() + self.data = data + + def set_device_data( + self, path: str, uuid: str, version: str, variant: str, init_status: str + ) -> None: + self.ui.nk3_path.setText(path) + self.ui.nk3_uuid.setText(uuid) + self.ui.nk3_version.setText(version) + self.ui.nk3_variant.setText(variant) + self.ui.nk3_status.setText(init_status) diff --git a/nitrokeyapp/settings_tab/worker.py b/nitrokeyapp/settings_tab/worker.py new file mode 100644 index 00000000..8b7c9976 --- /dev/null +++ b/nitrokeyapp/settings_tab/worker.py @@ -0,0 +1,68 @@ +import logging +from typing import Optional + +from PySide6.QtCore import Signal, Slot + +from nitrokeyapp.common_ui import CommonUi +from nitrokeyapp.device_data import DeviceData +from nitrokeyapp.update import UpdateGUI +from nitrokeyapp.worker import Job, Worker + +logger = logging.getLogger(__name__) + + +class UpdateDevice(Job): + device_updated = Signal(bool) + + def __init__( + self, + common_ui: CommonUi, + data: DeviceData, + ) -> None: + super().__init__(common_ui) + + self.data = data + + self.image: Optional[str] = None + + self.device_updated.connect(lambda _: self.finished.emit()) + + self.update_gui = UpdateGUI(self.common_ui) + self.common_ui.prompt.confirmed.connect(self.cancel_busy_wait) + + def run(self) -> None: + if not self.image: + success = self.data.update(self.update_gui) + else: + success = self.data.update(self.update_gui, self.image) + + self.device_updated.emit(success) + + @Slot() + def cleanup(self) -> None: + self.common_ui.prompt.confirmed.disconnect() + + @Slot(bool) + def cancel_busy_wait(self, confirmed: bool) -> None: + self.update_gui.await_confirmation = confirmed + + +class SettingsWorker(Worker): + # TODO: remove DeviceData from signatures + device_updated = Signal(bool) + + def __init__(self, common_ui: CommonUi) -> None: + super().__init__(common_ui) + + @Slot(DeviceData) + def update_device(self, data: DeviceData) -> None: + job = UpdateDevice(self.common_ui, data) + job.device_updated.connect(self.device_updated) + self.run(job) + + @Slot(DeviceData, str) + def update_device_file(self, data: DeviceData, filename: str) -> None: + job = UpdateDevice(self.common_ui, data) + job.image = filename + job.device_updated.connect(self.device_updated) + self.run(job) diff --git a/nitrokeyapp/ui/settings_tab.ui b/nitrokeyapp/ui/settings_tab.ui new file mode 100644 index 00000000..08484f13 --- /dev/null +++ b/nitrokeyapp/ui/settings_tab.ui @@ -0,0 +1,594 @@ + + + SettingsTab + + + + 0 + 0 + 811 + 527 + + + + + 0 + 0 + + + + Form + + + + + + + 0 + + + + + 0 + + + + + 0 + 0 + + + + + 9 + + + 0 + + + 9 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + Abort + + + + icons/close.svgicons/close.svg + + + + + + + + 0 + 0 + + + + Reset + + + + icons/delete.svgicons/delete.svg + + + + + + + + 0 + 0 + + + + Save + + + + icons/save.svgicons/save.svg + + + + + + + + 0 + 0 + + + + Qt::LeftToRight + + + Edit + + + + icons/edit.svgicons/edit.svg + + + + + + + + + + + 0 + 0 + + + + + 60 + 0 + + + + + Pin Settings + + + + true + + + + + icons/dialpad.svgicons/dialpad.svg + + + + + FIDO2 + + + + + PasswordManager + + + + + OTP + + + + + + + + + 20 + 0 + + + + 1 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + 0 + 0 + + + + + 0 + 30 + + + + + 11 + false + true + + + + dummy + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 0 + + + + + + + Current Password: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 8 + 0 + + + + QLineEdit::Normal + + + false + + + <empty> + + + false + + + + + + + New Password: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + QLineEdit::Password + + + true + + + <empty> + + + false + + + + + + + Qt::LeftToRight + + + Repeat New Password: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + <empty> + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 13 + 184 + + + + + + + + Qt::Horizontal + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + 0 + 0 + 510 + 479 + + + + + 0 + 0 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + 0 + 0 + + + + + 0 + 30 + + + + + 11 + false + true + + + + dummy + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 0 + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 13 + 184 + + + + + + + + + + + + + + + + + 0 + 0 + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 63 + 20 + + + + + + + + + + + + + 0 + + + + + Please update the firmware on the device to use this feature. + + + true + + + + + + + Qt::Vertical + + + + 20 + 527 + + + + + + + + + + + + + + From f9f8c88ea858e4a999dc13a3561efcea322666f1 Mon Sep 17 00:00:00 2001 From: jj-so Date: Mon, 29 Apr 2024 15:53:47 +0200 Subject: [PATCH 02/14] Settings: add buttons --- nitrokeyapp/settings_tab/__init__.py | 63 ++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/nitrokeyapp/settings_tab/__init__.py b/nitrokeyapp/settings_tab/__init__.py index 56ff7b7a..519ee659 100644 --- a/nitrokeyapp/settings_tab/__init__.py +++ b/nitrokeyapp/settings_tab/__init__.py @@ -51,6 +51,69 @@ def __init__(self, parent: Optional[QWidget] = None) -> None: self.ui = self.load_ui("settings_tab.ui", self) + icon_visibility = self.get_qicon("visibility_off.svg") + icon_check = self.get_qicon("done.svg") + icon_false = self.get_qicon("close.svg") + + loc = QLineEdit.ActionPosition.TrailingPosition + self.action_current_password_show = self.ui.current_password.addAction(icon_visibility, loc) + self.action_current_password_show.triggered.connect(self.act_current_password_show) + + self.action_new_password_show = self.ui.new_password.addAction(icon_visibility, loc) + self.action_new_password_show.triggered.connect(self.act_new_password_show) + + self.action_repeat_password_show = self.ui.repeat_password.addAction(icon_visibility, loc) + self.action_repeat_password_show.triggered.connect(self.act_repeat_password_show) + + self.show_current_password_check = self.ui.current_password.addAction(icon_check, loc) + self.show_current_password_false = self.ui.current_password.addAction(icon_false, loc) + + self.show_repeat_password_check = self.ui.repeat_password.addAction(icon_check, loc) + self.show_repeat_password_false = self.ui.repeat_password.addAction(icon_false, loc) + + + # self.line_actions = [ + # self.action_current_password_show, + # self.show_current_password_check, + # self.show_current_password_false, + # self.action_new_password_show, + # self.action_repeat_password_show, + # ] + + def act_current_password_show(self) -> None: + self.set_current_password_show(self.ui.current_password.echoMode() == QLineEdit.Password, ) # type: ignore [attr-defined] + + def act_new_password_show(self) -> None: + self.set_new_password_show(self.ui.new_password.echoMode() == QLineEdit.Password) # type: ignore [attr-defined] + + def act_repeat_password_show(self) -> None: + self.set_repeat_password_show(self.ui.repeat_password.echoMode() == QLineEdit.Password) # type: ignore [attr-defined] + + + def set_current_password_show(self, show: bool = True) -> None: + icon_show = self.get_qicon("visibility.svg") + icon_hide = self.get_qicon("visibility_off.svg") + icon = icon_show if show else icon_hide + mode = QLineEdit.Normal if show else QLineEdit.Password # type: ignore [attr-defined] + self.ui.current_password.setEchoMode(mode) + self.action_current_password_show.setIcon(icon) + + def set_new_password_show(self, show: bool = True) -> None: + icon_show = self.get_qicon("visibility.svg") + icon_hide = self.get_qicon("visibility_off.svg") + icon = icon_show if show else icon_hide + mode = QLineEdit.Normal if show else QLineEdit.Password # type: ignore [attr-defined] + self.ui.new_password.setEchoMode(mode) + self.action_new_password_show.setIcon(icon) + + def set_repeat_password_show(self, show: bool = True) -> None: + icon_show = self.get_qicon("visibility.svg") + icon_hide = self.get_qicon("visibility_off.svg") + icon = icon_show if show else icon_hide + mode = QLineEdit.Normal if show else QLineEdit.Password # type: ignore [attr-defined] + self.ui.repeat_password.setEchoMode(mode) + self.action_repeat_password_show.setIcon(icon) + @property def title(self) -> str: return "Settings" From 8712d3ec3d3f15593735b50f1bba78183918aaeb Mon Sep 17 00:00:00 2001 From: jj-so Date: Mon, 6 May 2024 16:28:50 +0200 Subject: [PATCH 03/14] Settings: connect buttons --- nitrokeyapp/settings_tab/__init__.py | 62 +++++++- nitrokeyapp/ui/settings_tab.ui | 207 +++++++++++++-------------- 2 files changed, 161 insertions(+), 108 deletions(-) diff --git a/nitrokeyapp/settings_tab/__init__.py b/nitrokeyapp/settings_tab/__init__.py index 519ee659..dd16258e 100644 --- a/nitrokeyapp/settings_tab/__init__.py +++ b/nitrokeyapp/settings_tab/__init__.py @@ -7,7 +7,7 @@ from PySide6.QtCore import Qt, QThread, QTimer, Signal, Slot from PySide6.QtGui import QGuiApplication -from PySide6.QtWidgets import QLineEdit, QListWidgetItem, QWidget +from PySide6.QtWidgets import QLineEdit, QListWidgetItem, QWidget, QTreeWidgetItem, QPushButton from nitrokeyapp.common_ui import CommonUi from nitrokeyapp.device_data import DeviceData @@ -51,6 +51,22 @@ def __init__(self, parent: Optional[QWidget] = None) -> None: self.ui = self.load_ui("settings_tab.ui", self) + #Tree + parent_item = QTreeWidgetItem(self.ui.settings_tree, [""]) + btn_fido2 = QPushButton('Fido2') + btn_fido2.pressed.connect(lambda: self.show_pin("FIDO2")) + self.ui.settings_tree.setItemWidget(parent_item, 0, btn_fido2) + + parent_item = QTreeWidgetItem(self.ui.settings_tree, [""]) + btn_pwManager = QPushButton('PasswordManager') + btn_pwManager.pressed.connect(lambda: self.show_pin("Password Manager")) + self.ui.settings_tree.setItemWidget(parent_item, 0, btn_pwManager) + + parent_item = QTreeWidgetItem(self.ui.settings_tree, [""]) + btn_otp = QPushButton('OTP') + btn_otp.pressed.connect(lambda: self.show_pin("OTP")) + self.ui.settings_tree.setItemWidget(parent_item, 0, btn_otp) + icon_visibility = self.get_qicon("visibility_off.svg") icon_check = self.get_qicon("done.svg") icon_false = self.get_qicon("close.svg") @@ -71,6 +87,7 @@ def __init__(self, parent: Optional[QWidget] = None) -> None: self.show_repeat_password_check = self.ui.repeat_password.addAction(icon_check, loc) self.show_repeat_password_false = self.ui.repeat_password.addAction(icon_false, loc) + self.reset() # self.line_actions = [ # self.action_current_password_show, @@ -80,6 +97,39 @@ def __init__(self, parent: Optional[QWidget] = None) -> None: # self.action_repeat_password_show, # ] + def show_pin(self, pintype) -> None: + self.ui.settings_empty.hide() + self.ui.pinsettings_edit.hide() + self.ui.pinsettings_desc.show() + + self.ui.btn_abort.hide() + self.ui.btn_reset.hide() + self.ui.btn_save.hide() + self.ui.btn_edit.show() + + self.ui.btn_edit.pressed.connect(lambda: self.edit_pin(pintype)) + + self.ui.pin_name.setText(pintype) + + print(pintype) + + def edit_pin(self, pintype) -> None: + self.ui.settings_empty.hide() + self.ui.pinsettings_desc.hide() + self.ui.pinsettings_edit.show() + + self.ui.btn_edit.hide() + self.ui.btn_abort.show() + self.ui.btn_reset.show() + self.ui.btn_save.show() + + self.ui.btn_abort.pressed.connect(lambda: self.show_pin(pintype)) + self.ui.btn_save.pressed.connect(lambda: self.save_pin(pintype)) + self.ui.btn_reset.pressed.connect(lambda: self.reset_pin(pintype)) + + + self.ui.password_label.setText(pintype) + def act_current_password_show(self) -> None: self.set_current_password_show(self.ui.current_password.echoMode() == QLineEdit.Password, ) # type: ignore [attr-defined] @@ -127,7 +177,15 @@ def worker(self) -> Optional[Worker]: return self._worker def reset(self) -> None: - self.data = None +# self.data = None + self.ui.settings_empty.show() + self.ui.pinsettings_edit.hide() + self.ui.pinsettings_desc.hide() + + self.ui.btn_abort.hide() + self.ui.btn_reset.hide() + self.ui.btn_save.hide() + self.ui.btn_edit.hide() def refresh(self, data: DeviceData, force: bool = False) -> None: if data == self.data and not force: diff --git a/nitrokeyapp/ui/settings_tab.ui b/nitrokeyapp/ui/settings_tab.ui index 08484f13..db772dd5 100644 --- a/nitrokeyapp/ui/settings_tab.ui +++ b/nitrokeyapp/ui/settings_tab.ui @@ -178,21 +178,6 @@ icons/dialpad.svgicons/dialpad.svg - - - FIDO2 - - - - - PasswordManager - - - - - OTP - - @@ -222,26 +207,113 @@ - + - + 0 0 - - 0 - - - 0 - - - 0 - - - 0 - + + + + + 0 + 0 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + 0 + 0 + + + + + 0 + 30 + + + + + 11 + false + true + + + + dummy + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 0 + + + + + + + Status + + + + + + + Qt::Vertical + + + + 20 + 37 + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 13 + 184 + + + + + + + + + + + + + 0 + 0 + + + @@ -433,83 +505,6 @@ - - - - - 0 - 0 - 510 - 479 - - - - - 0 - 0 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - - 0 - 0 - - - - - 0 - 30 - - - - - 11 - false - true - - - - dummy - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - 0 - - - - - - - Qt::Vertical - - - QSizePolicy::Expanding - - - - 13 - 184 - - - - - - - - - - From 4ddb9060669feebb35a6cb1503f13bd29e36cf60 Mon Sep 17 00:00:00 2001 From: jj-so Date: Wed, 8 May 2024 15:24:34 +0200 Subject: [PATCH 04/14] Settings: rebuild tree --- nitrokeyapp/settings_tab/__init__.py | 115 +++++++++++++++++++-------- nitrokeyapp/settings_tab/data.py | 8 ++ nitrokeyapp/ui/settings_tab.ui | 46 +++-------- 3 files changed, 101 insertions(+), 68 deletions(-) create mode 100644 nitrokeyapp/settings_tab/data.py diff --git a/nitrokeyapp/settings_tab/__init__.py b/nitrokeyapp/settings_tab/__init__.py index dd16258e..245ec3d3 100644 --- a/nitrokeyapp/settings_tab/__init__.py +++ b/nitrokeyapp/settings_tab/__init__.py @@ -7,7 +7,7 @@ from PySide6.QtCore import Qt, QThread, QTimer, Signal, Slot from PySide6.QtGui import QGuiApplication -from PySide6.QtWidgets import QLineEdit, QListWidgetItem, QWidget, QTreeWidgetItem, QPushButton +from PySide6.QtWidgets import QLineEdit, QListWidgetItem, QWidget, QTreeWidgetItem from nitrokeyapp.common_ui import CommonUi from nitrokeyapp.device_data import DeviceData @@ -18,13 +18,14 @@ # logger = logging.getLogger(__name__) -# class SettingsTabState(Enum): -# Initial = 0 -# ShowCred = 1 -# AddCred = 2 -# EditCred = 3 -# -# NotAvailable = 99 +class SettingsTabState(Enum): + Initial = 0 + Fido = 1 + FidoPw = 2 + otp = 3 + otpPw = 4 + + NotAvailable = 99 class SettingsTab(QtUtilsMixIn, QWidget): @@ -52,21 +53,57 @@ def __init__(self, parent: Optional[QWidget] = None) -> None: self.ui = self.load_ui("settings_tab.ui", self) #Tree - parent_item = QTreeWidgetItem(self.ui.settings_tree, [""]) - btn_fido2 = QPushButton('Fido2') - btn_fido2.pressed.connect(lambda: self.show_pin("FIDO2")) - self.ui.settings_tree.setItemWidget(parent_item, 0, btn_fido2) + pin_icon = self.get_qicon("dialpad.svg") + + fido = QTreeWidgetItem(self.ui.settings_tree) + pintype = SettingsTabState.Fido + name = "FIDO2" + desc = "FIDO2 is an authentication standard that enables secure and passwordless access to online services. It uses public key cryptography to provide strong authentication and protect against phishing and other security threats." + + fido.setText(0, name) + fido.setData(1, 0, pintype) + fido.setData(2, 0, name) + fido.setData(3, 0, desc) + + + fido_pin = QTreeWidgetItem() + pintype = SettingsTabState.FidoPw + name = "FIDO2 Pin Settings" + + fido_pin.setIcon(0, pin_icon) + fido.addChild(fido_pin) + + fido_pin.setText(0, name) + fido_pin.setData(1, 0, pintype) + fido_pin.setData(2, 0, name) + + + otp = QTreeWidgetItem(self.ui.settings_tree) + pintype = SettingsTabState.otp + name = "OTP" + desc = "One-Time Password (OTP) is a security mechanism that generates a unique password for each login session. This password is typically valid for only one login attempt or for a short period of time, adding an extra layer of security to the authentication process. OTPs are commonly used in two-factor authentication systems to verify the identity of users." + + otp.setText(0, name) + otp.setData(1, 0, pintype) + otp.setData(2, 0, name) + otp.setData(3, 0, desc) + + otp_pin = QTreeWidgetItem() + pintype = SettingsTabState.otpPw + name = "OTP Pin Settings" + + otp_pin.setText(0, name) + otp_pin.setData(1, 0, pintype) + otp_pin.setData(2, 0, name) - parent_item = QTreeWidgetItem(self.ui.settings_tree, [""]) - btn_pwManager = QPushButton('PasswordManager') - btn_pwManager.pressed.connect(lambda: self.show_pin("Password Manager")) - self.ui.settings_tree.setItemWidget(parent_item, 0, btn_pwManager) + otp_pin.setIcon(0, pin_icon) + otp.addChild(otp_pin) - parent_item = QTreeWidgetItem(self.ui.settings_tree, [""]) - btn_otp = QPushButton('OTP') - btn_otp.pressed.connect(lambda: self.show_pin("OTP")) - self.ui.settings_tree.setItemWidget(parent_item, 0, btn_otp) + self.ui.settings_tree.itemClicked.connect(self.show) + + self.reset() + def field_btn(self) -> None: icon_visibility = self.get_qicon("visibility_off.svg") icon_check = self.get_qicon("done.svg") icon_false = self.get_qicon("close.svg") @@ -87,7 +124,6 @@ def __init__(self, parent: Optional[QWidget] = None) -> None: self.show_repeat_password_check = self.ui.repeat_password.addAction(icon_check, loc) self.show_repeat_password_false = self.ui.repeat_password.addAction(icon_false, loc) - self.reset() # self.line_actions = [ # self.action_current_password_show, @@ -97,7 +133,16 @@ def __init__(self, parent: Optional[QWidget] = None) -> None: # self.action_repeat_password_show, # ] - def show_pin(self, pintype) -> None: + def show(self, item) -> None: + print("show") + pintype = item.data(1, 0) + if pintype == SettingsTabState.Fido or pintype == SettingsTabState.otp: + self.show_pin(item) + else: + self.edit_pin(item) + + + def show_pin(self, item) -> None: self.ui.settings_empty.hide() self.ui.pinsettings_edit.hide() self.ui.pinsettings_desc.show() @@ -105,30 +150,34 @@ def show_pin(self, pintype) -> None: self.ui.btn_abort.hide() self.ui.btn_reset.hide() self.ui.btn_save.hide() - self.ui.btn_edit.show() - self.ui.btn_edit.pressed.connect(lambda: self.edit_pin(pintype)) + pintype = item.data(1, 0) + name = item.data(2, 0) + desc = item.data(3, 0) - self.ui.pin_name.setText(pintype) + self.ui.pin_name.setText(name) + self.ui.pin_description.setText(desc) + self.ui.pin_description.setReadOnly(True) - print(pintype) - def edit_pin(self, pintype) -> None: + def edit_pin(self, item) -> None: self.ui.settings_empty.hide() self.ui.pinsettings_desc.hide() self.ui.pinsettings_edit.show() - self.ui.btn_edit.hide() self.ui.btn_abort.show() self.ui.btn_reset.show() self.ui.btn_save.show() - self.ui.btn_abort.pressed.connect(lambda: self.show_pin(pintype)) - self.ui.btn_save.pressed.connect(lambda: self.save_pin(pintype)) - self.ui.btn_reset.pressed.connect(lambda: self.reset_pin(pintype)) + self.ui.btn_abort.pressed.connect(lambda: self.show_pin(item)) + self.ui.btn_save.pressed.connect(lambda: self.save_pin(item)) + self.ui.btn_reset.pressed.connect(lambda: self.reset_pin(item)) + + name = item.data(2, 0) + self.ui.password_label.setText(name) - self.ui.password_label.setText(pintype) + self.field_btn() def act_current_password_show(self) -> None: self.set_current_password_show(self.ui.current_password.echoMode() == QLineEdit.Password, ) # type: ignore [attr-defined] @@ -177,7 +226,6 @@ def worker(self) -> Optional[Worker]: return self._worker def reset(self) -> None: -# self.data = None self.ui.settings_empty.show() self.ui.pinsettings_edit.hide() self.ui.pinsettings_desc.hide() @@ -185,7 +233,6 @@ def reset(self) -> None: self.ui.btn_abort.hide() self.ui.btn_reset.hide() self.ui.btn_save.hide() - self.ui.btn_edit.hide() def refresh(self, data: DeviceData, force: bool = False) -> None: if data == self.data and not force: diff --git a/nitrokeyapp/settings_tab/data.py b/nitrokeyapp/settings_tab/data.py new file mode 100644 index 00000000..963a2b94 --- /dev/null +++ b/nitrokeyapp/settings_tab/data.py @@ -0,0 +1,8 @@ +from dataclasses import dataclass + +@dataclass +class Pin: + id: bytes + pintype: Optional[item.data(1, 0)] + name: str + desc: str \ No newline at end of file diff --git a/nitrokeyapp/ui/settings_tab.ui b/nitrokeyapp/ui/settings_tab.ui index db772dd5..23155f94 100644 --- a/nitrokeyapp/ui/settings_tab.ui +++ b/nitrokeyapp/ui/settings_tab.ui @@ -7,7 +7,7 @@ 0 0 811 - 527 + 570 @@ -127,26 +127,6 @@ - - - - - 0 - 0 - - - - Qt::LeftToRight - - - Edit - - - - icons/edit.svgicons/edit.svg - - - @@ -166,16 +146,11 @@ - Pin Settings - - - - true - + Settings - icons/dialpad.svgicons/dialpad.svg + icons/settings.svgicons/settings.svg @@ -183,7 +158,7 @@ - + 20 0 @@ -218,7 +193,7 @@ - + 0 0 @@ -274,16 +249,19 @@ Qt::Vertical + + QSizePolicy::MinimumExpanding + 20 - 37 + 40 - + @@ -308,7 +286,7 @@ - + 0 0 @@ -317,7 +295,7 @@ - + 0 0 From 73b8801f87c66d6de794d176fab36109cf2545ad Mon Sep 17 00:00:00 2001 From: jj-so Date: Tue, 14 May 2024 09:32:42 +0200 Subject: [PATCH 05/14] Settings: continued ui --- nitrokeyapp/settings_tab/__init__.py | 72 +++++++++++++++++++++++++++- nitrokeyapp/settings_tab/data.py | 28 ++++++++--- nitrokeyapp/ui/settings_tab.ui | 4 +- 3 files changed, 94 insertions(+), 10 deletions(-) diff --git a/nitrokeyapp/settings_tab/__init__.py b/nitrokeyapp/settings_tab/__init__.py index 245ec3d3..11ef108d 100644 --- a/nitrokeyapp/settings_tab/__init__.py +++ b/nitrokeyapp/settings_tab/__init__.py @@ -13,6 +13,7 @@ from nitrokeyapp.device_data import DeviceData from nitrokeyapp.qt_utils_mix_in import QtUtilsMixIn from nitrokeyapp.worker import Worker +#from .data import pin_check from .worker import SettingsWorker @@ -57,6 +58,7 @@ def __init__(self, parent: Optional[QWidget] = None) -> None: fido = QTreeWidgetItem(self.ui.settings_tree) pintype = SettingsTabState.Fido + fido.setExpanded(False) name = "FIDO2" desc = "FIDO2 is an authentication standard that enables secure and passwordless access to online services. It uses public key cryptography to provide strong authentication and protect against phishing and other security threats." @@ -80,6 +82,7 @@ def __init__(self, parent: Optional[QWidget] = None) -> None: otp = QTreeWidgetItem(self.ui.settings_tree) pintype = SettingsTabState.otp + otp.setExpanded(False) name = "OTP" desc = "One-Time Password (OTP) is a security mechanism that generates a unique password for each login session. This password is typically valid for only one login attempt or for a short period of time, adding an extra layer of security to the authentication process. OTPs are commonly used in two-factor authentication systems to verify the identity of users." @@ -101,6 +104,10 @@ def __init__(self, parent: Optional[QWidget] = None) -> None: self.ui.settings_tree.itemClicked.connect(self.show) + self.ui.current_password.textChanged.connect(self.check_credential) + self.ui.new_password.textChanged.connect(self.check_credential) + self.ui.repeat_password.textChanged.connect(self.check_credential) + self.reset() def field_btn(self) -> None: @@ -124,6 +131,15 @@ def field_btn(self) -> None: self.show_repeat_password_check = self.ui.repeat_password.addAction(icon_check, loc) self.show_repeat_password_false = self.ui.repeat_password.addAction(icon_false, loc) + self.action_current_password_show.setVisible(False) + self.action_new_password_show.setVisible(False) + self.action_repeat_password_show.setVisible(False) + self.show_current_password_check.setVisible(False) + self.show_current_password_false.setVisible(False) + self.show_repeat_password_check.setVisible(False) + self.show_repeat_password_false.setVisible(False) + + # self.line_actions = [ # self.action_current_password_show, @@ -134,13 +150,21 @@ def field_btn(self) -> None: # ] def show(self, item) -> None: - print("show") pintype = item.data(1, 0) if pintype == SettingsTabState.Fido or pintype == SettingsTabState.otp: self.show_pin(item) + self.collapse_all_except(item) + item.setExpanded(True) else: self.edit_pin(item) + def collapse_all_except(self, item): + top_level_items = self.settings_tree.invisibleRootItem().takeChildren() + for top_level_item in top_level_items: + if top_level_item is not item.parent(): + top_level_item.setExpanded(False) + self.settings_tree.invisibleRootItem().addChildren(top_level_items) + def show_pin(self, item) -> None: self.ui.settings_empty.hide() @@ -165,11 +189,15 @@ def edit_pin(self, item) -> None: self.ui.pinsettings_desc.hide() self.ui.pinsettings_edit.show() + self.ui.current_password.clear() + self.ui.new_password.clear() + self.ui.repeat_password.clear() + self.ui.btn_abort.show() self.ui.btn_reset.show() self.ui.btn_save.show() - self.ui.btn_abort.pressed.connect(lambda: self.show_pin(item)) + self.ui.btn_abort.pressed.connect(lambda: self.abort(item)) self.ui.btn_save.pressed.connect(lambda: self.save_pin(item)) self.ui.btn_reset.pressed.connect(lambda: self.reset_pin(item)) @@ -179,6 +207,15 @@ def edit_pin(self, item) -> None: self.field_btn() + def abort(self, item) -> None: + p_item = item.parent() + self.show(p_item) + + # def reset(self) -> None: + + # def save(self) -> None: + + def act_current_password_show(self) -> None: self.set_current_password_show(self.ui.current_password.echoMode() == QLineEdit.Password, ) # type: ignore [attr-defined] @@ -248,3 +285,34 @@ def set_device_data( self.ui.nk3_version.setText(version) self.ui.nk3_variant.setText(variant) self.ui.nk3_status.setText(init_status) + + @Slot() + def check_credential(self) -> None: + current_password = self.ui.current_password.text() + new_password = self.ui.new_password.text() + repeat_password = self.ui.repeat_password.text() + + if len(current_password) > 0: + self.action_current_password_show.setVisible(True) + else: + self.action_current_password_show.setVisible(False) + + if len(new_password) > 0: + self.action_new_password_show.setVisible(True) + else: + self.action_new_password_show.setVisible(False) + + if len(repeat_password) >0: + self.action_repeat_password_show.setVisible(True) + else: + self.action_repeat_password_show.setVisible(False) + + if len(repeat_password) > 0 and new_password == repeat_password: + self.show_repeat_password_check.setVisible(True) + self.show_repeat_password_false.setVisible(False) + elif len(repeat_password) == 0 and len(new_password) == 0: + self.show_repeat_password_false.setVisible(False) + self.show_repeat_password_check.setVisible(False) + else: + self.show_repeat_password_false.setVisible(True) + self.show_repeat_password_check.setVisible(False) diff --git a/nitrokeyapp/settings_tab/data.py b/nitrokeyapp/settings_tab/data.py index 963a2b94..c55d8616 100644 --- a/nitrokeyapp/settings_tab/data.py +++ b/nitrokeyapp/settings_tab/data.py @@ -1,8 +1,24 @@ from dataclasses import dataclass +from enum import Enum, auto, unique -@dataclass -class Pin: - id: bytes - pintype: Optional[item.data(1, 0)] - name: str - desc: str \ No newline at end of file +from pynitrokey.nk3.secrets_app import SecretsApp, SelectResponse, CCIDInstruction + +class pin_check: + + def check(self) -> SelectResponse: + check = SecretsApp.select() + return check + + + # @classmethod + # def check(cls, CCID: CCIDInstruction) -> SelectResponse: + # SecretsApp.select(cls, CCID) + +#@dataclass +#class Pin: +# id: bytes +# pintype: Optional[item.data(1, 0)] +# name: str +# desc: str + +#cls, secrets: SelectResponse \ No newline at end of file diff --git a/nitrokeyapp/ui/settings_tab.ui b/nitrokeyapp/ui/settings_tab.ui index 23155f94..ef7df91b 100644 --- a/nitrokeyapp/ui/settings_tab.ui +++ b/nitrokeyapp/ui/settings_tab.ui @@ -164,7 +164,7 @@ - 1 + 2 @@ -358,7 +358,7 @@ - QLineEdit::Normal + QLineEdit::Password false From fa3382fb9ea67ac46e12ad410574e891e5ea540e Mon Sep 17 00:00:00 2001 From: jj-so Date: Thu, 16 May 2024 17:08:02 +0200 Subject: [PATCH 06/14] Settings: connect pynitrokey --- nitrokeyapp/settings_tab/__init__.py | 55 +++++++++++++++++++++++++++- nitrokeyapp/ui/settings_tab.ui | 3 ++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/nitrokeyapp/settings_tab/__init__.py b/nitrokeyapp/settings_tab/__init__.py index 11ef108d..1667ab2f 100644 --- a/nitrokeyapp/settings_tab/__init__.py +++ b/nitrokeyapp/settings_tab/__init__.py @@ -5,6 +5,10 @@ from random import randbytes from typing import Callable, Optional +from pynitrokey.fido2 import find +from fido2.ctap2.pin import ClientPin, PinProtocol +from pynitrokey.nk3.secrets_app import SecretsApp + from PySide6.QtCore import Qt, QThread, QTimer, Signal, Slot from PySide6.QtGui import QGuiApplication from PySide6.QtWidgets import QLineEdit, QListWidgetItem, QWidget, QTreeWidgetItem @@ -183,6 +187,11 @@ def show_pin(self, item) -> None: self.ui.pin_description.setText(desc) self.ui.pin_description.setReadOnly(True) + if pintype == SettingsTabState.Fido: + self.fido_status() + elif pintype == SettingsTabState.otp: + self.otp_status() + def edit_pin(self, item) -> None: self.ui.settings_empty.hide() @@ -207,13 +216,57 @@ def edit_pin(self, item) -> None: self.field_btn() + def fido_status(self) -> None: + ctaphid_raw_dev = self.data._device.device + fido2_client = find(raw_device=ctaphid_raw_dev) + self.ui.status_label.setText("pin ja" if fido2_client.has_pin() else "pin nein") + + def otp_status(self) -> None: + secrets = SecretsApp(self.data._device) + status = secrets.select() + status_txt = str(status) + self.ui.status_label.setText(status_txt) + def abort(self, item) -> None: p_item = item.parent() self.show(p_item) + + # def reset(self) -> None: - # def save(self) -> None: + def save_pin(self, item) -> None: + pintype = item.data(1, 0) + if pintype == SettingsTabState.FidoPw: + print(pintype) + ctaphid_raw_dev = self.data._device.device + fido2_client = find(raw_device=ctaphid_raw_dev) + client = fido2_client.client + # assert isinstance(fido2_client.ctap2, Ctap2) + client_pin = ClientPin(fido2_client.ctap2) + old_pin = self.ui.current_password.text() + new_pin = self.ui.repeat_password.text() + print(old_pin) + print(new_pin) + + client_pin.change_pin(old_pin, new_pin) + else: + secrets = SecretsApp(self.data._device) + print(secrets) + old_pin = self.ui.current_password.text() + new_pin = self.ui.repeat_password.text() + print(type(old_pin)) + print(type(new_pin)) + print(old_pin) + print(new_pin) + secrets.change_pin_raw(old_pin, new_pin) + print(old_pin) + print(new_pin) + + + + + # or pintype == SettingsTabState.otp: def act_current_password_show(self) -> None: diff --git a/nitrokeyapp/ui/settings_tab.ui b/nitrokeyapp/ui/settings_tab.ui index ef7df91b..f80811c1 100644 --- a/nitrokeyapp/ui/settings_tab.ui +++ b/nitrokeyapp/ui/settings_tab.ui @@ -424,6 +424,9 @@ 0 + + QLineEdit::Password + <empty> From fc6e5d1014611ae44cc1146cace7445c61c0795f Mon Sep 17 00:00:00 2001 From: jj-so Date: Fri, 17 May 2024 12:27:11 +0200 Subject: [PATCH 07/14] Settings: add info --- nitrokeyapp/settings_tab/__init__.py | 119 ++++++++++++++++++++------- nitrokeyapp/settings_tab/worker.py | 92 ++++++++++----------- nitrokeyapp/ui/settings_tab.ui | 2 +- 3 files changed, 138 insertions(+), 75 deletions(-) diff --git a/nitrokeyapp/settings_tab/__init__.py b/nitrokeyapp/settings_tab/__init__.py index 1667ab2f..091921b8 100644 --- a/nitrokeyapp/settings_tab/__init__.py +++ b/nitrokeyapp/settings_tab/__init__.py @@ -194,21 +194,26 @@ def show_pin(self, item) -> None: def edit_pin(self, item) -> None: + pintype = item.data(1, 0) self.ui.settings_empty.hide() self.ui.pinsettings_desc.hide() self.ui.pinsettings_edit.show() - self.ui.current_password.clear() - self.ui.new_password.clear() - self.ui.repeat_password.clear() + self.field_clear() + + if self.fido_status() or self.otp_status(): + self.show_current_password(True) + else: + self.show_current_password(False) self.ui.btn_abort.show() - self.ui.btn_reset.show() + self.ui.btn_reset.hide() self.ui.btn_save.show() + self.ui.btn_save.setEnabled(False) self.ui.btn_abort.pressed.connect(lambda: self.abort(item)) self.ui.btn_save.pressed.connect(lambda: self.save_pin(item)) - self.ui.btn_reset.pressed.connect(lambda: self.reset_pin(item)) + name = item.data(2, 0) @@ -216,16 +221,27 @@ def edit_pin(self, item) -> None: self.field_btn() - def fido_status(self) -> None: + def fido_status(self) -> bool: + pin_status = bool ctaphid_raw_dev = self.data._device.device fido2_client = find(raw_device=ctaphid_raw_dev) + pin_status = fido2_client.has_pin() self.ui.status_label.setText("pin ja" if fido2_client.has_pin() else "pin nein") + return pin_status - def otp_status(self) -> None: + def otp_status(self) -> bool: + pin_status = bool secrets = SecretsApp(self.data._device) status = secrets.select() + if status.pin_attempt_counter is not None: + pin_status = True + print(pin_status) + else: + pin_status = False + print(pin_status) status_txt = str(status) self.ui.status_label.setText(status_txt) + return pin_status def abort(self, item) -> None: p_item = item.parent() @@ -238,7 +254,7 @@ def abort(self, item) -> None: def save_pin(self, item) -> None: pintype = item.data(1, 0) if pintype == SettingsTabState.FidoPw: - print(pintype) + ctaphid_raw_dev = self.data._device.device fido2_client = find(raw_device=ctaphid_raw_dev) client = fido2_client.client @@ -246,27 +262,20 @@ def save_pin(self, item) -> None: client_pin = ClientPin(fido2_client.ctap2) old_pin = self.ui.current_password.text() new_pin = self.ui.repeat_password.text() - print(old_pin) - print(new_pin) - - client_pin.change_pin(old_pin, new_pin) + if self.fido_status(): + client_pin.change_pin(old_pin, new_pin) + else: + client_pin.set_pin(new_pin) + self.field_clear() else: secrets = SecretsApp(self.data._device) - print(secrets) old_pin = self.ui.current_password.text() new_pin = self.ui.repeat_password.text() - print(type(old_pin)) - print(type(new_pin)) - print(old_pin) - print(new_pin) - secrets.change_pin_raw(old_pin, new_pin) - print(old_pin) - print(new_pin) - - - - - # or pintype == SettingsTabState.otp: + if self.fido_status(): + secrets.change_pin_raw(old_pin, new_pin) + else: + secrets.set_pin_raw(new_pin) + self.field_clear() def act_current_password_show(self) -> None: @@ -339,21 +348,60 @@ def set_device_data( self.ui.nk3_variant.setText(variant) self.ui.nk3_status.setText(init_status) + def field_clear(self) -> None: + self.ui.current_password.clear() + self.ui.new_password.clear() + self.ui.repeat_password.clear() + + def show_current_password(self, show: bool) -> None: + if show: + self.ui.current_password.show() + self.ui.current_password_label.show() + else: + self.ui.current_password.hide() + self.ui.current_password_label.hide() + + @Slot() - def check_credential(self) -> None: + def check_credential(self, new: bool) -> None: + self.common_ui.info.info.emit("") + + tool_Tip = "Credeantial cannot be saved:" + can_save = True + current_password = self.ui.current_password.text() new_password = self.ui.new_password.text() repeat_password = self.ui.repeat_password.text() - if len(current_password) > 0: + if len(current_password) > 0 and len(current_password) <= 3: + can_save = False self.action_current_password_show.setVisible(True) + self.common_ui.info.info.emit("Current Password is too short") + tool_Tip = tool_Tip + "\n- Current Password is too short" + elif len(current_password) >= 4: + self.action_current_password_show.setVisible(True) + self.common_ui.info.info.emit("") + tool_Tip = tool_Tip + "" else: + can_save = False self.action_current_password_show.setVisible(False) + self.common_ui.info.info.emit("Enter your Current Password") + tool_Tip = tool_Tip + "\n- Enter your Current Password" - if len(new_password) > 0: + if len(new_password) > 0 and len(new_password) <= 3: + can_save = False + self.action_new_password_show.setVisible(True) + self.common_ui.info.info.emit("New Password is too short") + tool_Tip = tool_Tip + "\n- New Password is too short" + elif len(new_password) >= 4: self.action_new_password_show.setVisible(True) + self.common_ui.info.info.emit("") + tool_Tip = tool_Tip + "" else: + can_save = False self.action_new_password_show.setVisible(False) + self.common_ui.info.info.emit("Enter your New Password") + tool_Tip = tool_Tip + "\n- Enter your New Password" if len(repeat_password) >0: self.action_repeat_password_show.setVisible(True) @@ -364,8 +412,23 @@ def check_credential(self) -> None: self.show_repeat_password_check.setVisible(True) self.show_repeat_password_false.setVisible(False) elif len(repeat_password) == 0 and len(new_password) == 0: + can_save = False self.show_repeat_password_false.setVisible(False) self.show_repeat_password_check.setVisible(False) + elif len(repeat_password) > 0 and new_password != repeat_password: + can_save = False + self.show_repeat_password_false.setVisible(True) + self.show_repeat_password_check.setVisible(False) + self.common_ui.info.info.emit("New Password and Repeat Password are not equal") + tool_Tip = tool_Tip + "\n- New Password and Repeat Password are not equal" else: + can_save = False self.show_repeat_password_false.setVisible(True) self.show_repeat_password_check.setVisible(False) + + + self.ui.btn_save.setEnabled(can_save) + if can_save: + tool_Tip = "Credential Save" + + self.ui.btn_save.setToolTip(tool_Tip) diff --git a/nitrokeyapp/settings_tab/worker.py b/nitrokeyapp/settings_tab/worker.py index 8b7c9976..49a3c37a 100644 --- a/nitrokeyapp/settings_tab/worker.py +++ b/nitrokeyapp/settings_tab/worker.py @@ -11,40 +11,40 @@ logger = logging.getLogger(__name__) -class UpdateDevice(Job): - device_updated = Signal(bool) - - def __init__( - self, - common_ui: CommonUi, - data: DeviceData, - ) -> None: - super().__init__(common_ui) - - self.data = data - - self.image: Optional[str] = None - - self.device_updated.connect(lambda _: self.finished.emit()) - - self.update_gui = UpdateGUI(self.common_ui) - self.common_ui.prompt.confirmed.connect(self.cancel_busy_wait) - - def run(self) -> None: - if not self.image: - success = self.data.update(self.update_gui) - else: - success = self.data.update(self.update_gui, self.image) - - self.device_updated.emit(success) - - @Slot() - def cleanup(self) -> None: - self.common_ui.prompt.confirmed.disconnect() - - @Slot(bool) - def cancel_busy_wait(self, confirmed: bool) -> None: - self.update_gui.await_confirmation = confirmed +#class UpdateDevice(Job): +# device_updated = Signal(bool) +# +# def __init__( +# self, +# common_ui: CommonUi, +# data: DeviceData, +# ) -> None: +# super().__init__(common_ui) +# +# self.data = data +# +# self.image: Optional[str] = None +# +# self.device_updated.connect(lambda _: self.finished.emit()) +# +# self.update_gui = UpdateGUI(self.common_ui) +# self.common_ui.prompt.confirmed.connect(self.cancel_busy_wait) +# +# def run(self) -> None: +# if not self.image: +# success = self.data.update(self.update_gui) +# else: +# success = self.data.update(self.update_gui, self.image) +# +# self.device_updated.emit(success) +# +# @Slot() +# def cleanup(self) -> None: +# self.common_ui.prompt.confirmed.disconnect() +# +# @Slot(bool) +# def cancel_busy_wait(self, confirmed: bool) -> None: +# self.update_gui.await_confirmation = confirmed class SettingsWorker(Worker): @@ -54,15 +54,15 @@ class SettingsWorker(Worker): def __init__(self, common_ui: CommonUi) -> None: super().__init__(common_ui) - @Slot(DeviceData) - def update_device(self, data: DeviceData) -> None: - job = UpdateDevice(self.common_ui, data) - job.device_updated.connect(self.device_updated) - self.run(job) - - @Slot(DeviceData, str) - def update_device_file(self, data: DeviceData, filename: str) -> None: - job = UpdateDevice(self.common_ui, data) - job.image = filename - job.device_updated.connect(self.device_updated) - self.run(job) + # @Slot(DeviceData) + # def update_device(self, data: DeviceData) -> None: + # job = UpdateDevice(self.common_ui, data) + # job.device_updated.connect(self.device_updated) + # self.run(job) +# + # @Slot(DeviceData, str) + # def update_device_file(self, data: DeviceData, filename: str) -> None: + # job = UpdateDevice(self.common_ui, data) + # job.image = filename + # job.device_updated.connect(self.device_updated) + # self.run(job) diff --git a/nitrokeyapp/ui/settings_tab.ui b/nitrokeyapp/ui/settings_tab.ui index f80811c1..da9811d9 100644 --- a/nitrokeyapp/ui/settings_tab.ui +++ b/nitrokeyapp/ui/settings_tab.ui @@ -456,7 +456,7 @@ - + Qt::Vertical From 389e61253245a6fde6d68a079ef99391e0b6410f Mon Sep 17 00:00:00 2001 From: jj-so Date: Tue, 21 May 2024 16:50:36 +0200 Subject: [PATCH 08/14] Settings: rebuild info --- nitrokeyapp/settings_tab/__init__.py | 168 ++++++++++++++------------- 1 file changed, 88 insertions(+), 80 deletions(-) diff --git a/nitrokeyapp/settings_tab/__init__.py b/nitrokeyapp/settings_tab/__init__.py index 091921b8..c5452be2 100644 --- a/nitrokeyapp/settings_tab/__init__.py +++ b/nitrokeyapp/settings_tab/__init__.py @@ -198,10 +198,12 @@ def edit_pin(self, item) -> None: self.ui.settings_empty.hide() self.ui.pinsettings_desc.hide() self.ui.pinsettings_edit.show() + self.common_ui.info.info.emit("") + self.field_clear() - if self.fido_status() or self.otp_status(): + if self.fido_status() and pintype == SettingsTabState.FidoPw or self.otp_status() and pintype == SettingsTabState.otpPw: self.show_current_password(True) else: self.show_current_password(False) @@ -210,6 +212,7 @@ def edit_pin(self, item) -> None: self.ui.btn_reset.hide() self.ui.btn_save.show() self.ui.btn_save.setEnabled(False) + self.ui.btn_save.setToolTip("Credeantial cannot be saved") self.ui.btn_abort.pressed.connect(lambda: self.abort(item)) self.ui.btn_save.pressed.connect(lambda: self.save_pin(item)) @@ -222,25 +225,30 @@ def edit_pin(self, item) -> None: self.field_btn() def fido_status(self) -> bool: - pin_status = bool - ctaphid_raw_dev = self.data._device.device - fido2_client = find(raw_device=ctaphid_raw_dev) - pin_status = fido2_client.has_pin() - self.ui.status_label.setText("pin ja" if fido2_client.has_pin() else "pin nein") + pin_status: bool = False + with self.data.open() as device: + ctaphid_raw_dev = device.device + fido2_client = find(raw_device=ctaphid_raw_dev) + pin_status = fido2_client.has_pin() + self.ui.status_label.setText("pin ja" if fido2_client.has_pin() else "pin nein") return pin_status + def otp_status(self) -> bool: - pin_status = bool - secrets = SecretsApp(self.data._device) - status = secrets.select() - if status.pin_attempt_counter is not None: - pin_status = True - print(pin_status) - else: - pin_status = False - print(pin_status) - status_txt = str(status) - self.ui.status_label.setText(status_txt) + pin_status: bool = False + with self.data.open() as device: + secrets = SecretsApp(device) + status = secrets.select() + if status.pin_attempt_counter is not None: + pin_status = True + else: + pin_status = False + + is_set = status.pin_attempt_counter + version = status.version + serial_nr = status.serial_number + status_txt = str(status) + self.ui.status_label.setText(status_txt) return pin_status def abort(self, item) -> None: @@ -254,27 +262,29 @@ def abort(self, item) -> None: def save_pin(self, item) -> None: pintype = item.data(1, 0) if pintype == SettingsTabState.FidoPw: - - ctaphid_raw_dev = self.data._device.device - fido2_client = find(raw_device=ctaphid_raw_dev) - client = fido2_client.client - # assert isinstance(fido2_client.ctap2, Ctap2) - client_pin = ClientPin(fido2_client.ctap2) - old_pin = self.ui.current_password.text() - new_pin = self.ui.repeat_password.text() - if self.fido_status(): - client_pin.change_pin(old_pin, new_pin) - else: - client_pin.set_pin(new_pin) + with self.data.open() as device: + ctaphid_raw_dev = device.device + fido2_client = find(raw_device=ctaphid_raw_dev) + client = fido2_client.client + # assert isinstance(fido2_client.ctap2, Ctap2) + client_pin = ClientPin(fido2_client.ctap2) + old_pin = self.ui.current_password.text() + new_pin = self.ui.repeat_password.text() + if self.fido_status(): + client_pin.change_pin(old_pin, new_pin) + else: + client_pin.set_pin(new_pin) self.field_clear() else: - secrets = SecretsApp(self.data._device) - old_pin = self.ui.current_password.text() - new_pin = self.ui.repeat_password.text() - if self.fido_status(): - secrets.change_pin_raw(old_pin, new_pin) - else: - secrets.set_pin_raw(new_pin) + with self.data.open() as device: + secrets = SecretsApp(device) + #secrets = SecretsApp(self.data._device) + old_pin = self.ui.current_password.text() + new_pin = self.ui.repeat_password.text() + if self.fido_status(): + secrets.change_pin_raw(old_pin, new_pin) + else: + secrets.set_pin_raw(new_pin) self.field_clear() @@ -369,63 +379,61 @@ def check_credential(self, new: bool) -> None: tool_Tip = "Credeantial cannot be saved:" can_save = True - current_password = self.ui.current_password.text() + new_password = self.ui.new_password.text() repeat_password = self.ui.repeat_password.text() + current_password_len = len(self.ui.current_password.text()) + new_password_len = len(new_password) + repeat_password_len = len(repeat_password) - if len(current_password) > 0 and len(current_password) <= 3: - can_save = False - self.action_current_password_show.setVisible(True) - self.common_ui.info.info.emit("Current Password is too short") - tool_Tip = tool_Tip + "\n- Current Password is too short" - elif len(current_password) >= 4: - self.action_current_password_show.setVisible(True) - self.common_ui.info.info.emit("") - tool_Tip = tool_Tip + "" - else: - can_save = False - self.action_current_password_show.setVisible(False) - self.common_ui.info.info.emit("Enter your Current Password") - tool_Tip = tool_Tip + "\n- Enter your Current Password" + self.action_current_password_show.setVisible(False) + self.action_new_password_show.setVisible(False) + self.action_repeat_password_show.setVisible(False) + self.show_repeat_password_check.setVisible(False) + self.show_repeat_password_false.setVisible(False) + - if len(new_password) > 0 and len(new_password) <= 3: + if self.ui.current_password.isHidden(): + pass + else: + if current_password_len <= 3: + can_save = False + if current_password_len == 0: + self.common_ui.info.info.emit("Enter your Current Password") + tool_Tip = tool_Tip + "\n- Enter your Current Password" + if current_password_len >= 1: + self.action_current_password_show.setVisible(True) + if current_password_len >= 1 and current_password_len <=3: + self.common_ui.info.info.emit("Current Password is too short") + tool_Tip = tool_Tip + "\n- Current Password is too short" + + if new_password_len <= 3: can_save = False + if new_password_len == 0: + tool_Tip = tool_Tip + "\n- Enter your New Password" + if new_password_len == 0 and current_password_len >= 4: + self.common_ui.info.info.emit("Enter your New Password") + if new_password_len >=1: self.action_new_password_show.setVisible(True) + if new_password_len >=1 and new_password_len <= 3: + can_save = False self.common_ui.info.info.emit("New Password is too short") tool_Tip = tool_Tip + "\n- New Password is too short" - elif len(new_password) >= 4: - self.action_new_password_show.setVisible(True) - self.common_ui.info.info.emit("") - tool_Tip = tool_Tip + "" - else: - can_save = False - self.action_new_password_show.setVisible(False) - self.common_ui.info.info.emit("Enter your New Password") - tool_Tip = tool_Tip + "\n- Enter your New Password" - if len(repeat_password) >0: - self.action_repeat_password_show.setVisible(True) - else: - self.action_repeat_password_show.setVisible(False) - - if len(repeat_password) > 0 and new_password == repeat_password: - self.show_repeat_password_check.setVisible(True) - self.show_repeat_password_false.setVisible(False) - elif len(repeat_password) == 0 and len(new_password) == 0: + if repeat_password_len == 0: can_save = False - self.show_repeat_password_false.setVisible(False) - self.show_repeat_password_check.setVisible(False) - elif len(repeat_password) > 0 and new_password != repeat_password: + tool_Tip = tool_Tip + "\n- Repeat your New Password" + if repeat_password_len >=1: + self.action_repeat_password_show.setVisible(True) + if repeat_password_len >=1 and repeat_password != new_password: can_save = False - self.show_repeat_password_false.setVisible(True) + self.common_ui.info.info.emit("Repeat Password are not equal") + tool_Tip = tool_Tip + "\n- Repeat Password are not equal" self.show_repeat_password_check.setVisible(False) - self.common_ui.info.info.emit("New Password and Repeat Password are not equal") - tool_Tip = tool_Tip + "\n- New Password and Repeat Password are not equal" - else: - can_save = False self.show_repeat_password_false.setVisible(True) - self.show_repeat_password_check.setVisible(False) - + if repeat_password_len >= 4 and new_password == repeat_password: + self.show_repeat_password_check.setVisible(True) + self.show_repeat_password_false.setVisible(False) self.ui.btn_save.setEnabled(can_save) if can_save: From fcad0ec0e8e6ef9f695f33b16406357976fb273f Mon Sep 17 00:00:00 2001 From: jj-so Date: Wed, 22 May 2024 14:33:53 +0200 Subject: [PATCH 09/14] Settings: key communication --- nitrokeyapp/settings_tab/__init__.py | 107 +++++++++++++++++++-------- nitrokeyapp/settings_tab/worker.py | 60 ++++++++++++--- 2 files changed, 128 insertions(+), 39 deletions(-) diff --git a/nitrokeyapp/settings_tab/__init__.py b/nitrokeyapp/settings_tab/__init__.py index c5452be2..b08bdac8 100644 --- a/nitrokeyapp/settings_tab/__init__.py +++ b/nitrokeyapp/settings_tab/__init__.py @@ -7,6 +7,8 @@ from pynitrokey.fido2 import find from fido2.ctap2.pin import ClientPin, PinProtocol +from fido2.ctap2.base import Ctap2 +from fido2.client import ClientError as Fido2ClientError from pynitrokey.nk3.secrets_app import SecretsApp from PySide6.QtCore import Qt, QThread, QTimer, Signal, Slot @@ -21,7 +23,7 @@ from .worker import SettingsWorker -# logger = logging.getLogger(__name__) +logger = logging.getLogger(__name__) class SettingsTabState(Enum): Initial = 0 @@ -36,12 +38,12 @@ class SettingsTabState(Enum): class SettingsTab(QtUtilsMixIn, QWidget): # standard UI # busy_state_changed = Signal(bool) - # error = Signal(str, Exception) - # start_touch = Signal() - # stop_touch = Signal() + error = Signal(str, Exception) + start_touch = Signal() + stop_touch = Signal() # worker triggers - # trigger_add_credential = Signal(DeviceData, Credential, bytes) +# trigger_otp_change_pw = Signal(DeviceData, str, str) def __init__(self, parent: Optional[QWidget] = None) -> None: QWidget.__init__(self, parent) @@ -51,10 +53,12 @@ def __init__(self, parent: Optional[QWidget] = None) -> None: self.common_ui = CommonUi() self.worker_thread = QThread() - self._worker = SettingsWorker(self.common_ui) + self._worker = SettingsWorker(self.common_ui, self.data) self._worker.moveToThread(self.worker_thread) self.worker_thread.start() +# self.trigger_otp_change_pw.connect(self._worker.otp_change_pw) + self.ui = self.load_ui("settings_tab.ui", self) #Tree @@ -143,16 +147,6 @@ def field_btn(self) -> None: self.show_repeat_password_check.setVisible(False) self.show_repeat_password_false.setVisible(False) - - - # self.line_actions = [ - # self.action_current_password_show, - # self.show_current_password_check, - # self.show_current_password_false, - # self.action_new_password_show, - # self.action_repeat_password_show, - # ] - def show(self, item) -> None: pintype = item.data(1, 0) if pintype == SettingsTabState.Fido or pintype == SettingsTabState.otp: @@ -256,38 +250,93 @@ def abort(self, item) -> None: self.show(p_item) + + + # def reset(self) -> None: + + # def reset(self) -> None: def save_pin(self, item) -> None: pintype = item.data(1, 0) + + if pintype == SettingsTabState.FidoPw: + fido_state = self.fido_status() with self.data.open() as device: ctaphid_raw_dev = device.device fido2_client = find(raw_device=ctaphid_raw_dev) client = fido2_client.client - # assert isinstance(fido2_client.ctap2, Ctap2) + assert isinstance(fido2_client.ctap2, Ctap2) client_pin = ClientPin(fido2_client.ctap2) old_pin = self.ui.current_password.text() new_pin = self.ui.repeat_password.text() - if self.fido_status(): - client_pin.change_pin(old_pin, new_pin) - else: - client_pin.set_pin(new_pin) - self.field_clear() + try: + if fido_state: + client_pin.change_pin(old_pin, new_pin) + self.field_clear() + self.common_ui.info.info.emit("done - please use new pin to verify key") + else: + client_pin.set_pin(new_pin) + except Exception as e: + logger.info(f"fido2 change_pin failed: {e}") +# # error 0x31 +# if "PIN_INVALID" in cause: +# local_critical( +# "your key has a different PIN. Please try to remember it :)", e +# ) +# +# # error 0x34 (power cycle helps) +# if "PIN_AUTH_BLOCKED" in cause: +# local_critical( +# "your key's PIN auth is blocked due to too many incorrect attempts.", +# "please plug it out and in again, then again!", +# "please be careful, after too many incorrect attempts, ", +# " the key will fully block.", +# e, +# ) +# +# # error 0x32 (only reset helps) +# if "PIN_BLOCKED" in cause: +# local_critical( +# "your key's PIN is blocked. ", +# "to use it again, you need to fully reset it.", +# "you can do this using: `nitropy fido2 reset`", +# e, +# ) +# +# # error 0x01 +# if "INVALID_COMMAND" in cause: +# local_critical( +# "error getting credential, is your key in bootloader mode?", +# "try: `nitropy fido2 util program aux leave-bootloader`", +# e, +# ) +# +# # pin required error +# if "PIN required" in str(e): +# local_critical("your key has a PIN set - pass it using `--pin `", e) +# +# local_critical("unexpected Fido2Client (CTAP) error", e) + print(e) else: +# self.trigger_otp_change_pw.emit(DeviceData, old_pin, new_pin) + otp_state = self.otp_status() with self.data.open() as device: secrets = SecretsApp(device) - #secrets = SecretsApp(self.data._device) old_pin = self.ui.current_password.text() new_pin = self.ui.repeat_password.text() - if self.fido_status(): - secrets.change_pin_raw(old_pin, new_pin) - else: - secrets.set_pin_raw(new_pin) + try: + if otp_state: + secrets.change_pin_raw(old_pin, new_pin) + else: + secrets.set_pin_raw(new_pin) + except Exception as e: + logger.info(f"otp change_pin failed: {e}") + print(e) self.field_clear() - def act_current_password_show(self) -> None: self.set_current_password_show(self.ui.current_password.echoMode() == QLineEdit.Password, ) # type: ignore [attr-defined] @@ -399,7 +448,7 @@ def check_credential(self, new: bool) -> None: if current_password_len <= 3: can_save = False if current_password_len == 0: - self.common_ui.info.info.emit("Enter your Current Password") + # self.common_ui.info.info.emit("Enter your Current Password") tool_Tip = tool_Tip + "\n- Enter your Current Password" if current_password_len >= 1: self.action_current_password_show.setVisible(True) diff --git a/nitrokeyapp/settings_tab/worker.py b/nitrokeyapp/settings_tab/worker.py index 49a3c37a..6271f4dc 100644 --- a/nitrokeyapp/settings_tab/worker.py +++ b/nitrokeyapp/settings_tab/worker.py @@ -1,16 +1,56 @@ import logging -from typing import Optional +from dataclasses import dataclass +from datetime import datetime +from typing import Dict, Optional -from PySide6.QtCore import Signal, Slot +from pynitrokey.fido2 import find +from fido2.ctap2.base import Ctap2 +from fido2.ctap2.pin import ClientPin, PinProtocol + +from pynitrokey.nk3.secrets_app import SecretsApp, SecretsAppException +from pynitrokey.trussed.utils import Uuid +from PySide6.QtCore import QObject, Signal, Slot +from PySide6.QtWidgets import QWidget from nitrokeyapp.common_ui import CommonUi from nitrokeyapp.device_data import DeviceData -from nitrokeyapp.update import UpdateGUI from nitrokeyapp.worker import Job, Worker logger = logging.getLogger(__name__) + +class SettingsWorker(Worker): + # TODO: remove DeviceData from signatures + + +#class VerifyPinChangeJob(Job): + + def __init__( + self, + common_ui: CommonUi, + data: DeviceData, + ) -> None: + super().__init__(common_ui) + + self.data = data + + @Slot(DeviceData, str, str) + def otp_change_pw(DeviceData, old_pin: str, new_pin: str) -> None: + print(old_pin, new_pin) + + with self.data.open() as device: + secrets = SecretsApp(device) + try: + with self.touch_prompt(): + secrets.change_pin_raw(old_pin, new_pin) + + except SecretsAppException as e: + self.trigger_error(f"PIN validation failed: {e}") + + + + #class UpdateDevice(Job): # device_updated = Signal(bool) # @@ -47,13 +87,13 @@ # self.update_gui.await_confirmation = confirmed -class SettingsWorker(Worker): - # TODO: remove DeviceData from signatures - device_updated = Signal(bool) - - def __init__(self, common_ui: CommonUi) -> None: - super().__init__(common_ui) - +#class SettingsWorker(Worker): +# # TODO: remove DeviceData from signatures +# device_updated = Signal(bool) +# +# def __init__(self, common_ui: CommonUi) -> None: +# super().__init__(common_ui) +# # @Slot(DeviceData) # def update_device(self, data: DeviceData) -> None: # job = UpdateDevice(self.common_ui, data) From 3f7fb313d24830a60898ee4206b31a43f066b7b3 Mon Sep 17 00:00:00 2001 From: jj-so Date: Thu, 23 May 2024 13:04:09 +0200 Subject: [PATCH 10/14] Settings: worker --- nitrokeyapp/settings_tab/__init__.py | 167 ++++++++++++++++----------- nitrokeyapp/settings_tab/worker.py | 146 ++++++++++++----------- 2 files changed, 173 insertions(+), 140 deletions(-) diff --git a/nitrokeyapp/settings_tab/__init__.py b/nitrokeyapp/settings_tab/__init__.py index b08bdac8..98fd3c1d 100644 --- a/nitrokeyapp/settings_tab/__init__.py +++ b/nitrokeyapp/settings_tab/__init__.py @@ -42,8 +42,15 @@ class SettingsTab(QtUtilsMixIn, QWidget): start_touch = Signal() stop_touch = Signal() + fido_state: bool = None + otp_state: bool = None + # worker triggers -# trigger_otp_change_pw = Signal(DeviceData, str, str) + trigger_fido_status = Signal() + trigger_otp_status = Signal() + + trigger_otp_change_pw = Signal(str, str) + trigger_fido_change_pw = Signal(str, str) def __init__(self, parent: Optional[QWidget] = None) -> None: QWidget.__init__(self, parent) @@ -53,11 +60,14 @@ def __init__(self, parent: Optional[QWidget] = None) -> None: self.common_ui = CommonUi() self.worker_thread = QThread() - self._worker = SettingsWorker(self.common_ui, self.data) + self._worker = SettingsWorker(self.common_ui, self) self._worker.moveToThread(self.worker_thread) self.worker_thread.start() -# self.trigger_otp_change_pw.connect(self._worker.otp_change_pw) + self.trigger_fido_status.connect(self._worker.fido_status) + self.trigger_otp_status.connect(self._worker.otp_status) + self.trigger_otp_change_pw.connect(self._worker.otp_change_pw) + self.trigger_fido_change_pw.connect(self._worker.fido_change_pw) self.ui = self.load_ui("settings_tab.ui", self) @@ -182,9 +192,12 @@ def show_pin(self, item) -> None: self.ui.pin_description.setReadOnly(True) if pintype == SettingsTabState.Fido: - self.fido_status() + self.trigger_fido_status.emit() + self.ui.status_label.setText("pin ja" if self.fido_state else "pin nein") elif pintype == SettingsTabState.otp: - self.otp_status() + self.trigger_otp_status.emit() + # status_txt = str(status) + self.ui.status_label.setText("pin ja" if self.otp_state else "pin nein") def edit_pin(self, item) -> None: @@ -197,7 +210,10 @@ def edit_pin(self, item) -> None: self.field_clear() - if self.fido_status() and pintype == SettingsTabState.FidoPw or self.otp_status() and pintype == SettingsTabState.otpPw: + self.trigger_otp_status.emit() + self.trigger_fido_status.emit() + if { self.fido_state and pintype == SettingsTabState.FidoPw or + self.otp_state and pintype == SettingsTabState.otpPw}: self.show_current_password(True) else: self.show_current_password(False) @@ -218,32 +234,32 @@ def edit_pin(self, item) -> None: self.field_btn() - def fido_status(self) -> bool: - pin_status: bool = False - with self.data.open() as device: - ctaphid_raw_dev = device.device - fido2_client = find(raw_device=ctaphid_raw_dev) - pin_status = fido2_client.has_pin() - self.ui.status_label.setText("pin ja" if fido2_client.has_pin() else "pin nein") - return pin_status - - - def otp_status(self) -> bool: - pin_status: bool = False - with self.data.open() as device: - secrets = SecretsApp(device) - status = secrets.select() - if status.pin_attempt_counter is not None: - pin_status = True - else: - pin_status = False - - is_set = status.pin_attempt_counter - version = status.version - serial_nr = status.serial_number - status_txt = str(status) - self.ui.status_label.setText(status_txt) - return pin_status +# def fido_status(self) -> bool: +# pin_status: bool = False +# with self.data.open() as device: +# ctaphid_raw_dev = device.device +# fido2_client = find(raw_device=ctaphid_raw_dev) +# pin_status = fido2_client.has_pin() +# self.ui.status_label.setText("pin ja" if fido2_client.has_pin() else "pin nein") +# return pin_status + + +# def otp_status(self) -> bool: +# pin_status: bool = False +# with self.data.open() as device: +# secrets = SecretsApp(device) +# status = secrets.select() +# if status.pin_attempt_counter is not None: +# pin_status = True +# else: +# pin_status = False +# +# is_set = status.pin_attempt_counter +# version = status.version +# serial_nr = status.serial_number +# status_txt = str(status) +# self.ui.status_label.setText(status_txt) +# return pin_status def abort(self, item) -> None: p_item = item.parent() @@ -260,27 +276,31 @@ def abort(self, item) -> None: def save_pin(self, item) -> None: pintype = item.data(1, 0) + old_pin = self.ui.current_password.text() + new_pin = self.ui.repeat_password.text() - if pintype == SettingsTabState.FidoPw: - fido_state = self.fido_status() - with self.data.open() as device: - ctaphid_raw_dev = device.device - fido2_client = find(raw_device=ctaphid_raw_dev) - client = fido2_client.client - assert isinstance(fido2_client.ctap2, Ctap2) - client_pin = ClientPin(fido2_client.ctap2) - old_pin = self.ui.current_password.text() - new_pin = self.ui.repeat_password.text() - try: - if fido_state: - client_pin.change_pin(old_pin, new_pin) - self.field_clear() - self.common_ui.info.info.emit("done - please use new pin to verify key") - else: - client_pin.set_pin(new_pin) - except Exception as e: - logger.info(f"fido2 change_pin failed: {e}") + self.trigger_fido_change_pw.emit(old_pin, new_pin) + self.field_clear() + self.common_ui.info.info.emit("done - please use new pin to verify key") + +# fido_state = self.fido_status() +# with self.data.open() as device: +# ctaphid_raw_dev = device.device +# fido2_client = find(raw_device=ctaphid_raw_dev) +# client = fido2_client.client +# assert isinstance(fido2_client.ctap2, Ctap2) +# client_pin = ClientPin(fido2_client.ctap2) +# +# try: +# if fido_state: +# client_pin.change_pin(old_pin, new_pin) +# self.field_clear() +# self.common_ui.info.info.emit("done - please use new pin to verify key") +# else: +# client_pin.set_pin(new_pin) +# except Exception as e: +# logger.info(f"fido2 change_pin failed: {e}") # # error 0x31 # if "PIN_INVALID" in cause: # local_critical( @@ -319,22 +339,23 @@ def save_pin(self, item) -> None: # local_critical("your key has a PIN set - pass it using `--pin `", e) # # local_critical("unexpected Fido2Client (CTAP) error", e) - print(e) +# print(e) else: -# self.trigger_otp_change_pw.emit(DeviceData, old_pin, new_pin) - otp_state = self.otp_status() - with self.data.open() as device: - secrets = SecretsApp(device) - old_pin = self.ui.current_password.text() - new_pin = self.ui.repeat_password.text() - try: - if otp_state: - secrets.change_pin_raw(old_pin, new_pin) - else: - secrets.set_pin_raw(new_pin) - except Exception as e: - logger.info(f"otp change_pin failed: {e}") - print(e) + self.trigger_otp_change_pw.emit(old_pin, new_pin) + # otp_state = self.otp_status() + # with self.data.open() as device: + # secrets = SecretsApp(device) + # old_pin = self.ui.current_password.text() + # new_pin = self.ui.repeat_password.text() + # try: + # if otp_state: + # secrets.change_pin_raw(old_pin, new_pin) + # else: + # secrets.set_pin_raw(new_pin) + # except Exception as e: + # logger.info(f"otp change_pin failed: {e}") + # print(e) + self.common_ui.info.info.emit("done - please use new pin to verify key") self.field_clear() def act_current_password_show(self) -> None: @@ -420,6 +441,20 @@ def show_current_password(self, show: bool) -> None: self.ui.current_password.hide() self.ui.current_password_label.hide() + @Slot(bool) + def status_fido(self, fido_state: bool) -> None: + self.fido_state = fido_state + + @Slot(bool) + def status_otp(self, otp_state: bool) -> None: + self.otp_state = otp_state + + # @Slot() + # def info_otp(self, info: status) -> None: + # is_set = info.pin_attempt_counter + # version = info.version + # serial_nr = info.serial_number + @Slot() def check_credential(self, new: bool) -> None: diff --git a/nitrokeyapp/settings_tab/worker.py b/nitrokeyapp/settings_tab/worker.py index 6271f4dc..6209d238 100644 --- a/nitrokeyapp/settings_tab/worker.py +++ b/nitrokeyapp/settings_tab/worker.py @@ -21,88 +21,86 @@ class SettingsWorker(Worker): + status_fido = Signal(bool) + status_otp = Signal(bool) + info_otp = Signal() # TODO: remove DeviceData from signatures - -#class VerifyPinChangeJob(Job): - def __init__( self, common_ui: CommonUi, - data: DeviceData, + data: DeviceData ) -> None: super().__init__(common_ui) self.data = data - @Slot(DeviceData, str, str) - def otp_change_pw(DeviceData, old_pin: str, new_pin: str) -> None: - print(old_pin, new_pin) - - with self.data.open() as device: - secrets = SecretsApp(device) - try: - with self.touch_prompt(): + @Slot() + def fido_status(self) -> bool: + pin_status: bool = False + with self.data.open() as device: + ctaphid_raw_dev = device.device + fido2_client = find(raw_device=ctaphid_raw_dev) + pin_status = fido2_client.has_pin() + print(pin_status) + self.status_fido.emit(pin_status) + return pin_status + + @Slot() + def otp_status(self) -> bool: + pin_status: bool = False + with self.data.open() as device: + secrets = SecretsApp(device) + status = secrets.select() + if status.pin_attempt_counter is not None: + pin_status = True + else: + pin_status = False + self.status_otp.emit(pin_status) + self.info_otp.emit(status) + return pin_status + + @Slot() + def fido_change_pw( + self, old_pin: str, new_pin: str + ) -> None: + print(old_pin, new_pin) + + fido_state = self.fido_status() + with self.data.open() as device: + ctaphid_raw_dev = device.device + fido2_client = find(raw_device=ctaphid_raw_dev) + client = fido2_client.client + assert isinstance(fido2_client.ctap2, Ctap2) + client_pin = ClientPin(fido2_client.ctap2) + + try: + if fido_state: + client_pin.change_pin(old_pin, new_pin) + else: + client_pin.set_pin(new_pin) + except Exception as e: + self.trigger_error(f"fido2 change_pin failed: {e}") + + + @Slot(str, str) + def otp_change_pw( + self, old_pin: str, new_pin: str + ) -> None: + + print(old_pin, new_pin) + otp_state = self.otp_status() + with self.data.open() as device: + secrets = SecretsApp(device) + try: + with self.touch_prompt(): + if otp_state: secrets.change_pin_raw(old_pin, new_pin) - - except SecretsAppException as e: - self.trigger_error(f"PIN validation failed: {e}") - - - - -#class UpdateDevice(Job): -# device_updated = Signal(bool) -# -# def __init__( -# self, -# common_ui: CommonUi, -# data: DeviceData, -# ) -> None: -# super().__init__(common_ui) -# -# self.data = data -# -# self.image: Optional[str] = None -# -# self.device_updated.connect(lambda _: self.finished.emit()) -# -# self.update_gui = UpdateGUI(self.common_ui) -# self.common_ui.prompt.confirmed.connect(self.cancel_busy_wait) -# -# def run(self) -> None: -# if not self.image: -# success = self.data.update(self.update_gui) -# else: -# success = self.data.update(self.update_gui, self.image) -# -# self.device_updated.emit(success) -# -# @Slot() -# def cleanup(self) -> None: -# self.common_ui.prompt.confirmed.disconnect() -# -# @Slot(bool) -# def cancel_busy_wait(self, confirmed: bool) -> None: -# self.update_gui.await_confirmation = confirmed - - -#class SettingsWorker(Worker): -# # TODO: remove DeviceData from signatures -# device_updated = Signal(bool) -# -# def __init__(self, common_ui: CommonUi) -> None: -# super().__init__(common_ui) -# - # @Slot(DeviceData) - # def update_device(self, data: DeviceData) -> None: - # job = UpdateDevice(self.common_ui, data) - # job.device_updated.connect(self.device_updated) - # self.run(job) -# - # @Slot(DeviceData, str) - # def update_device_file(self, data: DeviceData, filename: str) -> None: - # job = UpdateDevice(self.common_ui, data) - # job.image = filename - # job.device_updated.connect(self.device_updated) - # self.run(job) + else: + secrets.set_pin_raw(new_pin) + except SecretsAppException as e: + self.trigger_error(f"PIN validation failed: {e}") + + @Slot(str) + def trigger_error(self, msg: str) -> None: + self.common_ui.info.error.emit(msg) From 0a46d78a359cd4de2d05ed9be5bd737b69b61bb1 Mon Sep 17 00:00:00 2001 From: jj-so Date: Thu, 23 May 2024 22:02:45 +0200 Subject: [PATCH 11/14] Settings: busy ui --- nitrokeyapp/settings_tab/__init__.py | 309 ++++++++++----------------- nitrokeyapp/settings_tab/data.py | 20 +- nitrokeyapp/settings_tab/worker.py | 94 ++++---- 3 files changed, 168 insertions(+), 255 deletions(-) diff --git a/nitrokeyapp/settings_tab/__init__.py b/nitrokeyapp/settings_tab/__init__.py index 98fd3c1d..66db7878 100644 --- a/nitrokeyapp/settings_tab/__init__.py +++ b/nitrokeyapp/settings_tab/__init__.py @@ -1,56 +1,51 @@ -import binascii import logging -from base64 import b32decode, b32encode +import time from enum import Enum -from random import randbytes -from typing import Callable, Optional +from typing import Optional, Any -from pynitrokey.fido2 import find -from fido2.ctap2.pin import ClientPin, PinProtocol -from fido2.ctap2.base import Ctap2 -from fido2.client import ClientError as Fido2ClientError -from pynitrokey.nk3.secrets_app import SecretsApp - -from PySide6.QtCore import Qt, QThread, QTimer, Signal, Slot -from PySide6.QtGui import QGuiApplication -from PySide6.QtWidgets import QLineEdit, QListWidgetItem, QWidget, QTreeWidgetItem +from pynitrokey.nk3.secrets_app import SelectResponse +from PySide6.QtCore import QThread, Signal, Slot +from PySide6.QtWidgets import QLineEdit, QTreeWidgetItem, QWidget from nitrokeyapp.common_ui import CommonUi from nitrokeyapp.device_data import DeviceData from nitrokeyapp.qt_utils_mix_in import QtUtilsMixIn from nitrokeyapp.worker import Worker -#from .data import pin_check from .worker import SettingsWorker logger = logging.getLogger(__name__) + class SettingsTabState(Enum): - Initial = 0 - Fido = 1 - FidoPw = 2 - otp = 3 - otpPw = 4 + Initial = 0 + Fido = 1 + FidoPw = 2 + otp = 3 + otpPw = 4 - NotAvailable = 99 + NotAvailable = 99 class SettingsTab(QtUtilsMixIn, QWidget): # standard UI - # busy_state_changed = Signal(bool) + busy_state_changed = Signal(bool) error = Signal(str, Exception) start_touch = Signal() stop_touch = Signal() - fido_state: bool = None - otp_state: bool = None + fido_state: bool + otp_state: bool + otp_counter: int + otp_version: str + otp_serial_nr: str # worker triggers - trigger_fido_status = Signal() - trigger_otp_status = Signal() + trigger_fido_status = Signal(DeviceData) + trigger_otp_status = Signal(DeviceData) - trigger_otp_change_pw = Signal(str, str) - trigger_fido_change_pw = Signal(str, str) + trigger_otp_change_pw = Signal(DeviceData, str, str) + trigger_fido_change_pw = Signal(DeviceData, str, str) def __init__(self, parent: Optional[QWidget] = None) -> None: QWidget.__init__(self, parent) @@ -60,7 +55,7 @@ def __init__(self, parent: Optional[QWidget] = None) -> None: self.common_ui = CommonUi() self.worker_thread = QThread() - self._worker = SettingsWorker(self.common_ui, self) + self._worker = SettingsWorker(self.common_ui) self._worker.moveToThread(self.worker_thread) self.worker_thread.start() @@ -69,9 +64,14 @@ def __init__(self, parent: Optional[QWidget] = None) -> None: self.trigger_otp_change_pw.connect(self._worker.otp_change_pw) self.trigger_fido_change_pw.connect(self._worker.fido_change_pw) + self._worker.status_fido.connect(self.handle_status_fido) + self._worker.status_otp.connect(self.handle_status_otp) + self._worker.info_otp.connect(self.handle_info_otp) + + self.ui = self.load_ui("settings_tab.ui", self) - #Tree + # Tree pin_icon = self.get_qicon("dialpad.svg") fido = QTreeWidgetItem(self.ui.settings_tree) @@ -79,13 +79,12 @@ def __init__(self, parent: Optional[QWidget] = None) -> None: fido.setExpanded(False) name = "FIDO2" desc = "FIDO2 is an authentication standard that enables secure and passwordless access to online services. It uses public key cryptography to provide strong authentication and protect against phishing and other security threats." - + fido.setText(0, name) fido.setData(1, 0, pintype) fido.setData(2, 0, name) fido.setData(3, 0, desc) - fido_pin = QTreeWidgetItem() pintype = SettingsTabState.FidoPw name = "FIDO2 Pin Settings" @@ -97,13 +96,12 @@ def __init__(self, parent: Optional[QWidget] = None) -> None: fido_pin.setData(1, 0, pintype) fido_pin.setData(2, 0, name) - otp = QTreeWidgetItem(self.ui.settings_tree) pintype = SettingsTabState.otp otp.setExpanded(False) name = "OTP" desc = "One-Time Password (OTP) is a security mechanism that generates a unique password for each login session. This password is typically valid for only one login attempt or for a short period of time, adding an extra layer of security to the authentication process. OTPs are commonly used in two-factor authentication systems to verify the identity of users." - + otp.setText(0, name) otp.setData(1, 0, pintype) otp.setData(2, 0, name) @@ -120,7 +118,7 @@ def __init__(self, parent: Optional[QWidget] = None) -> None: otp_pin.setIcon(0, pin_icon) otp.addChild(otp_pin) - self.ui.settings_tree.itemClicked.connect(self.show) + self.ui.settings_tree.itemClicked.connect(self.show_widget) self.ui.current_password.textChanged.connect(self.check_credential) self.ui.new_password.textChanged.connect(self.check_credential) @@ -137,17 +135,31 @@ def field_btn(self) -> None: self.action_current_password_show = self.ui.current_password.addAction(icon_visibility, loc) self.action_current_password_show.triggered.connect(self.act_current_password_show) - self.action_new_password_show = self.ui.new_password.addAction(icon_visibility, loc) + self.action_new_password_show = self.ui.new_password.addAction( + icon_visibility, loc + ) self.action_new_password_show.triggered.connect(self.act_new_password_show) - self.action_repeat_password_show = self.ui.repeat_password.addAction(icon_visibility, loc) - self.action_repeat_password_show.triggered.connect(self.act_repeat_password_show) - - self.show_current_password_check = self.ui.current_password.addAction(icon_check, loc) - self.show_current_password_false = self.ui.current_password.addAction(icon_false, loc) - - self.show_repeat_password_check = self.ui.repeat_password.addAction(icon_check, loc) - self.show_repeat_password_false = self.ui.repeat_password.addAction(icon_false, loc) + self.action_repeat_password_show = self.ui.repeat_password.addAction( + icon_visibility, loc + ) + self.action_repeat_password_show.triggered.connect( + self.act_repeat_password_show + ) + + self.show_current_password_check = self.ui.current_password.addAction( + icon_check, loc + ) + self.show_current_password_false = self.ui.current_password.addAction( + icon_false, loc + ) + + self.show_repeat_password_check = self.ui.repeat_password.addAction( + icon_check, loc + ) + self.show_repeat_password_false = self.ui.repeat_password.addAction( + icon_false, loc + ) self.action_current_password_show.setVisible(False) self.action_new_password_show.setVisible(False) @@ -157,7 +169,7 @@ def field_btn(self) -> None: self.show_repeat_password_check.setVisible(False) self.show_repeat_password_false.setVisible(False) - def show(self, item) -> None: + def show_widget(self, item: Any) -> None: pintype = item.data(1, 0) if pintype == SettingsTabState.Fido or pintype == SettingsTabState.otp: self.show_pin(item) @@ -166,15 +178,16 @@ def show(self, item) -> None: else: self.edit_pin(item) - def collapse_all_except(self, item): + def collapse_all_except(self, item: Any): top_level_items = self.settings_tree.invisibleRootItem().takeChildren() for top_level_item in top_level_items: if top_level_item is not item.parent(): top_level_item.setExpanded(False) self.settings_tree.invisibleRootItem().addChildren(top_level_items) - - def show_pin(self, item) -> None: + def show_pin(self, item: Any) -> None: + self.trigger_fido_status.emit(self.data) + self.trigger_otp_status.emit(self.data) self.ui.settings_empty.hide() self.ui.pinsettings_edit.hide() self.ui.pinsettings_desc.show() @@ -182,7 +195,7 @@ def show_pin(self, item) -> None: self.ui.btn_abort.hide() self.ui.btn_reset.hide() self.ui.btn_save.hide() - + pintype = item.data(1, 0) name = item.data(2, 0) desc = item.data(3, 0) @@ -190,30 +203,42 @@ def show_pin(self, item) -> None: self.ui.pin_name.setText(name) self.ui.pin_description.setText(desc) self.ui.pin_description.setReadOnly(True) - + fido_state = self.fido_state + otp_state = self.otp_state if pintype == SettingsTabState.Fido: - self.trigger_fido_status.emit() - self.ui.status_label.setText("pin ja" if self.fido_state else "pin nein") + self.ui.status_label.setText( + "Fido2-Pin is set!" if self.fido_state else "Fido2-Pin is not set!" + ) elif pintype == SettingsTabState.otp: - self.trigger_otp_status.emit() - # status_txt = str(status) - self.ui.status_label.setText("pin ja" if self.otp_state else "pin nein") - - - def edit_pin(self, item) -> None: + if self.otp_state: + pin = "OTP-Pin is set!" + else: + pin = "OTP-Pin is not set!" + self.ui.status_label.setText( + f"\t{pin}\n\n" + f"\tVersion: {self.otp_version}\n" + f"\tPIN attempt counter: {self.otp_counter}\n" + f"\tSerial number: {self.otp_serial_nr}" + ) + + def edit_pin(self, item: Any) -> None: pintype = item.data(1, 0) self.ui.settings_empty.hide() self.ui.pinsettings_desc.hide() self.ui.pinsettings_edit.show() self.common_ui.info.info.emit("") - self.field_clear() - self.trigger_otp_status.emit() - self.trigger_fido_status.emit() - if { self.fido_state and pintype == SettingsTabState.FidoPw or - self.otp_state and pintype == SettingsTabState.otpPw}: + self.trigger_otp_status.emit(self.data) + self.trigger_fido_status.emit(self.data) + + if ( + self.fido_state + and pintype == SettingsTabState.FidoPw + or self.otp_state + and pintype == SettingsTabState.otpPw + ): self.show_current_password(True) else: self.show_current_password(False) @@ -227,135 +252,29 @@ def edit_pin(self, item) -> None: self.ui.btn_abort.pressed.connect(lambda: self.abort(item)) self.ui.btn_save.pressed.connect(lambda: self.save_pin(item)) - name = item.data(2, 0) self.ui.password_label.setText(name) self.field_btn() -# def fido_status(self) -> bool: -# pin_status: bool = False -# with self.data.open() as device: -# ctaphid_raw_dev = device.device -# fido2_client = find(raw_device=ctaphid_raw_dev) -# pin_status = fido2_client.has_pin() -# self.ui.status_label.setText("pin ja" if fido2_client.has_pin() else "pin nein") -# return pin_status - - -# def otp_status(self) -> bool: -# pin_status: bool = False -# with self.data.open() as device: -# secrets = SecretsApp(device) -# status = secrets.select() -# if status.pin_attempt_counter is not None: -# pin_status = True -# else: -# pin_status = False -# -# is_set = status.pin_attempt_counter -# version = status.version -# serial_nr = status.serial_number -# status_txt = str(status) -# self.ui.status_label.setText(status_txt) -# return pin_status - - def abort(self, item) -> None: + def abort(self, item: Any) -> None: p_item = item.parent() - self.show(p_item) - - - - - # def reset(self) -> None: - - - - # def reset(self) -> None: + self.show_widget(p_item) - def save_pin(self, item) -> None: + def save_pin(self, item: Any) -> None: pintype = item.data(1, 0) old_pin = self.ui.current_password.text() new_pin = self.ui.repeat_password.text() - + if pintype == SettingsTabState.FidoPw: - self.trigger_fido_change_pw.emit(old_pin, new_pin) + self.trigger_fido_change_pw.emit(self.data, old_pin, new_pin) self.field_clear() + self.abort(item) self.common_ui.info.info.emit("done - please use new pin to verify key") - -# fido_state = self.fido_status() -# with self.data.open() as device: -# ctaphid_raw_dev = device.device -# fido2_client = find(raw_device=ctaphid_raw_dev) -# client = fido2_client.client -# assert isinstance(fido2_client.ctap2, Ctap2) -# client_pin = ClientPin(fido2_client.ctap2) -# -# try: -# if fido_state: -# client_pin.change_pin(old_pin, new_pin) -# self.field_clear() -# self.common_ui.info.info.emit("done - please use new pin to verify key") -# else: -# client_pin.set_pin(new_pin) -# except Exception as e: -# logger.info(f"fido2 change_pin failed: {e}") -# # error 0x31 -# if "PIN_INVALID" in cause: -# local_critical( -# "your key has a different PIN. Please try to remember it :)", e -# ) -# -# # error 0x34 (power cycle helps) -# if "PIN_AUTH_BLOCKED" in cause: -# local_critical( -# "your key's PIN auth is blocked due to too many incorrect attempts.", -# "please plug it out and in again, then again!", -# "please be careful, after too many incorrect attempts, ", -# " the key will fully block.", -# e, -# ) -# -# # error 0x32 (only reset helps) -# if "PIN_BLOCKED" in cause: -# local_critical( -# "your key's PIN is blocked. ", -# "to use it again, you need to fully reset it.", -# "you can do this using: `nitropy fido2 reset`", -# e, -# ) -# -# # error 0x01 -# if "INVALID_COMMAND" in cause: -# local_critical( -# "error getting credential, is your key in bootloader mode?", -# "try: `nitropy fido2 util program aux leave-bootloader`", -# e, -# ) -# -# # pin required error -# if "PIN required" in str(e): -# local_critical("your key has a PIN set - pass it using `--pin `", e) -# -# local_critical("unexpected Fido2Client (CTAP) error", e) -# print(e) else: - self.trigger_otp_change_pw.emit(old_pin, new_pin) - # otp_state = self.otp_status() - # with self.data.open() as device: - # secrets = SecretsApp(device) - # old_pin = self.ui.current_password.text() - # new_pin = self.ui.repeat_password.text() - # try: - # if otp_state: - # secrets.change_pin_raw(old_pin, new_pin) - # else: - # secrets.set_pin_raw(new_pin) - # except Exception as e: - # logger.info(f"otp change_pin failed: {e}") - # print(e) - self.common_ui.info.info.emit("done - please use new pin to verify key") + self.trigger_otp_change_pw.emit(self.data, old_pin, new_pin) + self.abort(item) self.field_clear() def act_current_password_show(self) -> None: @@ -367,7 +286,6 @@ def act_new_password_show(self) -> None: def act_repeat_password_show(self) -> None: self.set_repeat_password_show(self.ui.repeat_password.echoMode() == QLineEdit.Password) # type: ignore [attr-defined] - def set_current_password_show(self, show: bool = True) -> None: icon_show = self.get_qicon("visibility.svg") icon_hide = self.get_qicon("visibility_off.svg") @@ -442,19 +360,25 @@ def show_current_password(self, show: bool) -> None: self.ui.current_password_label.hide() @Slot(bool) - def status_fido(self, fido_state: bool) -> None: + def handle_status_fido(self, fido_state: bool) -> None: self.fido_state = fido_state @Slot(bool) - def status_otp(self, otp_state: bool) -> None: + def handle_status_otp(self, otp_state: bool) -> None: self.otp_state = otp_state - # @Slot() - # def info_otp(self, info: status) -> None: - # is_set = info.pin_attempt_counter - # version = info.version - # serial_nr = info.serial_number - + @Slot(SelectResponse) + def handle_info_otp(self, status: SelectResponse) -> None: + self.otp_counter = status.pin_attempt_counter + self.otp_version = status.version_str() + self.otp_serial_nr = ( + status.serial_number.hex() + ) + # self.ui.status_label.setText("Fido2-Pin is set!\n" if self.otp_state else "Fido2-Pin is not set !\n" + # f"\tVersion: {version}\n" + # f"\tPIN attempt counter: {is_set}\n" + # f"\tSerial number: {serial_nr}\n") + print(self.otp_counter, self.otp_version, self.otp_serial_nr) @Slot() def check_credential(self, new: bool) -> None: @@ -463,7 +387,6 @@ def check_credential(self, new: bool) -> None: tool_Tip = "Credeantial cannot be saved:" can_save = True - new_password = self.ui.new_password.text() repeat_password = self.ui.repeat_password.text() current_password_len = len(self.ui.current_password.text()) @@ -475,7 +398,6 @@ def check_credential(self, new: bool) -> None: self.action_repeat_password_show.setVisible(False) self.show_repeat_password_check.setVisible(False) self.show_repeat_password_false.setVisible(False) - if self.ui.current_password.isHidden(): pass @@ -483,23 +405,22 @@ def check_credential(self, new: bool) -> None: if current_password_len <= 3: can_save = False if current_password_len == 0: - # self.common_ui.info.info.emit("Enter your Current Password") tool_Tip = tool_Tip + "\n- Enter your Current Password" if current_password_len >= 1: self.action_current_password_show.setVisible(True) - if current_password_len >= 1 and current_password_len <=3: + if current_password_len >= 1 and current_password_len <= 3: self.common_ui.info.info.emit("Current Password is too short") tool_Tip = tool_Tip + "\n- Current Password is too short" - + if new_password_len <= 3: can_save = False if new_password_len == 0: tool_Tip = tool_Tip + "\n- Enter your New Password" if new_password_len == 0 and current_password_len >= 4: self.common_ui.info.info.emit("Enter your New Password") - if new_password_len >=1: + if new_password_len >= 1: self.action_new_password_show.setVisible(True) - if new_password_len >=1 and new_password_len <= 3: + if new_password_len >= 1 and new_password_len <= 3: can_save = False self.common_ui.info.info.emit("New Password is too short") tool_Tip = tool_Tip + "\n- New Password is too short" @@ -507,9 +428,9 @@ def check_credential(self, new: bool) -> None: if repeat_password_len == 0: can_save = False tool_Tip = tool_Tip + "\n- Repeat your New Password" - if repeat_password_len >=1: + if repeat_password_len >= 1: self.action_repeat_password_show.setVisible(True) - if repeat_password_len >=1 and repeat_password != new_password: + if repeat_password_len >= 1 and repeat_password != new_password: can_save = False self.common_ui.info.info.emit("Repeat Password are not equal") tool_Tip = tool_Tip + "\n- Repeat Password are not equal" diff --git a/nitrokeyapp/settings_tab/data.py b/nitrokeyapp/settings_tab/data.py index c55d8616..3a127cc9 100644 --- a/nitrokeyapp/settings_tab/data.py +++ b/nitrokeyapp/settings_tab/data.py @@ -1,24 +1,28 @@ from dataclasses import dataclass from enum import Enum, auto, unique -from pynitrokey.nk3.secrets_app import SecretsApp, SelectResponse, CCIDInstruction +from pynitrokey.nk3.secrets_app import ( + CCIDInstruction, + SecretsApp, + SelectResponse, +) -class pin_check: +class pin_check: def check(self) -> SelectResponse: check = SecretsApp.select() return check - # @classmethod - # def check(cls, CCID: CCIDInstruction) -> SelectResponse: - # SecretsApp.select(cls, CCID) +# @classmethod +# def check(cls, CCID: CCIDInstruction) -> SelectResponse: +# SecretsApp.select(cls, CCID) -#@dataclass -#class Pin: +# @dataclass +# class Pin: # id: bytes # pintype: Optional[item.data(1, 0)] # name: str # desc: str -#cls, secrets: SelectResponse \ No newline at end of file +# cls, secrets: SelectResponse diff --git a/nitrokeyapp/settings_tab/worker.py b/nitrokeyapp/settings_tab/worker.py index 6209d238..0cb20ba7 100644 --- a/nitrokeyapp/settings_tab/worker.py +++ b/nitrokeyapp/settings_tab/worker.py @@ -1,55 +1,47 @@ import logging -from dataclasses import dataclass -from datetime import datetime -from typing import Dict, Optional -from pynitrokey.fido2 import find from fido2.ctap2.base import Ctap2 -from fido2.ctap2.pin import ClientPin, PinProtocol - -from pynitrokey.nk3.secrets_app import SecretsApp, SecretsAppException -from pynitrokey.trussed.utils import Uuid -from PySide6.QtCore import QObject, Signal, Slot -from PySide6.QtWidgets import QWidget +from fido2.ctap2.pin import ClientPin +from pynitrokey.fido2 import find +from pynitrokey.nk3.secrets_app import ( + SecretsApp, + SecretsAppException, + SelectResponse, +) +from PySide6.QtCore import Signal, Slot from nitrokeyapp.common_ui import CommonUi from nitrokeyapp.device_data import DeviceData -from nitrokeyapp.worker import Job, Worker +from nitrokeyapp.worker import Job logger = logging.getLogger(__name__) - -class SettingsWorker(Worker): +class SettingsWorker(Job): + busy_state_changed = Signal(bool) status_fido = Signal(bool) status_otp = Signal(bool) - info_otp = Signal() - # TODO: remove DeviceData from signatures + info_otp = Signal(SelectResponse) + finish = Signal() - def __init__( - self, - common_ui: CommonUi, - data: DeviceData - ) -> None: - super().__init__(common_ui) - self.data = data + def __init__(self, common_ui: CommonUi) -> None: + super().__init__(common_ui) - @Slot() - def fido_status(self) -> bool: + @Slot(DeviceData) + def fido_status(self, data: DeviceData) -> bool: pin_status: bool = False - with self.data.open() as device: + with data.open() as device: ctaphid_raw_dev = device.device fido2_client = find(raw_device=ctaphid_raw_dev) pin_status = fido2_client.has_pin() - print(pin_status) self.status_fido.emit(pin_status) return pin_status - @Slot() - def otp_status(self) -> bool: + @Slot(DeviceData) + def otp_status(self, data: DeviceData) -> bool: pin_status: bool = False - with self.data.open() as device: + with data.open() as device: secrets = SecretsApp(device) status = secrets.select() if status.pin_attempt_counter is not None: @@ -60,46 +52,42 @@ def otp_status(self) -> bool: self.info_otp.emit(status) return pin_status - @Slot() - def fido_change_pw( - self, old_pin: str, new_pin: str - ) -> None: - print(old_pin, new_pin) - - fido_state = self.fido_status() - with self.data.open() as device: + @Slot(DeviceData, str, str) + def fido_change_pw(self, data: DeviceData, old_pin: str, new_pin: str) -> None: + self.busy_state_changed.emit(True) + fido_state = self.fido_status(data) + with data.open() as device: ctaphid_raw_dev = device.device fido2_client = find(raw_device=ctaphid_raw_dev) - client = fido2_client.client assert isinstance(fido2_client.ctap2, Ctap2) client_pin = ClientPin(fido2_client.ctap2) try: - if fido_state: - client_pin.change_pin(old_pin, new_pin) - else: - client_pin.set_pin(new_pin) + if fido_state: + client_pin.change_pin(old_pin, new_pin) + else: + client_pin.set_pin(new_pin) + self.finish.emit() except Exception as e: self.trigger_error(f"fido2 change_pin failed: {e}") + self.busy_state_changed.emit(False) - - @Slot(str, str) - def otp_change_pw( - self, old_pin: str, new_pin: str - ) -> None: - - print(old_pin, new_pin) - otp_state = self.otp_status() - with self.data.open() as device: + @Slot(DeviceData, str, str) + def otp_change_pw(self, data: DeviceData, old_pin: str, new_pin: str) -> None: + self.busy_state_changed.emit(True) + otp_state = self.otp_status(data) + with data.open() as device: secrets = SecretsApp(device) try: with self.touch_prompt(): - if otp_state: + if otp_state: secrets.change_pin_raw(old_pin, new_pin) - else: + else: secrets.set_pin_raw(new_pin) + self.finish.emit() except SecretsAppException as e: self.trigger_error(f"PIN validation failed: {e}") + self.busy_state_changed.emit(False) @Slot(str) def trigger_error(self, msg: str) -> None: From d17839464b5ef72938615929b525404519f4623d Mon Sep 17 00:00:00 2001 From: jj-so Date: Fri, 24 May 2024 12:29:26 +0200 Subject: [PATCH 12/14] Settings: tidy up --- nitrokeyapp/settings_tab/__init__.py | 59 +++--- nitrokeyapp/settings_tab/data.py | 28 --- nitrokeyapp/settings_tab/worker.py | 4 - nitrokeyapp/ui/settings_tab.ui | 262 ++++++++++++--------------- 4 files changed, 147 insertions(+), 206 deletions(-) delete mode 100644 nitrokeyapp/settings_tab/data.py diff --git a/nitrokeyapp/settings_tab/__init__.py b/nitrokeyapp/settings_tab/__init__.py index 66db7878..61f87bc4 100644 --- a/nitrokeyapp/settings_tab/__init__.py +++ b/nitrokeyapp/settings_tab/__init__.py @@ -1,7 +1,6 @@ import logging -import time from enum import Enum -from typing import Optional, Any +from typing import Any, Optional from pynitrokey.nk3.secrets_app import SelectResponse from PySide6.QtCore import QThread, Signal, Slot @@ -34,11 +33,11 @@ class SettingsTab(QtUtilsMixIn, QWidget): start_touch = Signal() stop_touch = Signal() - fido_state: bool - otp_state: bool - otp_counter: int - otp_version: str - otp_serial_nr: str + # fido_state: bool + # otp_state: bool + # otp_counter: int + # otp_version: str + # otp_serial_nr: str # worker triggers trigger_fido_status = Signal(DeviceData) @@ -67,10 +66,15 @@ def __init__(self, parent: Optional[QWidget] = None) -> None: self._worker.status_fido.connect(self.handle_status_fido) self._worker.status_otp.connect(self.handle_status_otp) self._worker.info_otp.connect(self.handle_info_otp) - self.ui = self.load_ui("settings_tab.ui", self) + self.fido_state: bool + self.otp_state: bool + self.otp_counter: Optional[int] + self.otp_version: Optional[str] + self.otp_serial_nr: Optional[str] + # Tree pin_icon = self.get_qicon("dialpad.svg") @@ -132,8 +136,12 @@ def field_btn(self) -> None: icon_false = self.get_qicon("close.svg") loc = QLineEdit.ActionPosition.TrailingPosition - self.action_current_password_show = self.ui.current_password.addAction(icon_visibility, loc) - self.action_current_password_show.triggered.connect(self.act_current_password_show) + self.action_current_password_show = self.ui.current_password.addAction( + icon_visibility, loc + ) + self.action_current_password_show.triggered.connect( + self.act_current_password_show + ) self.action_new_password_show = self.ui.new_password.addAction( icon_visibility, loc @@ -178,12 +186,12 @@ def show_widget(self, item: Any) -> None: else: self.edit_pin(item) - def collapse_all_except(self, item: Any): - top_level_items = self.settings_tree.invisibleRootItem().takeChildren() + def collapse_all_except(self, item: Any) -> None: + top_level_items = self.ui.settings_tree.invisibleRootItem().takeChildren() for top_level_item in top_level_items: if top_level_item is not item.parent(): top_level_item.setExpanded(False) - self.settings_tree.invisibleRootItem().addChildren(top_level_items) + self.ui.settings_tree.invisibleRootItem().addChildren(top_level_items) def show_pin(self, item: Any) -> None: self.trigger_fido_status.emit(self.data) @@ -203,12 +211,14 @@ def show_pin(self, item: Any) -> None: self.ui.pin_name.setText(name) self.ui.pin_description.setText(desc) self.ui.pin_description.setReadOnly(True) - fido_state = self.fido_state - otp_state = self.otp_state + # fido_state = self.fido_state + # otp_state = self.otp_state if pintype == SettingsTabState.Fido: - self.ui.status_label.setText( - "Fido2-Pin is set!" if self.fido_state else "Fido2-Pin is not set!" - ) + if self.fido_state: + pin = "Fido2-Pin is set!" + else: + pin = "Fido2-Pin is not set!" + self.ui.status_label.setText(f"\t{pin}\n\n\n\n") elif pintype == SettingsTabState.otp: if self.otp_state: pin = "OTP-Pin is set!" @@ -278,7 +288,7 @@ def save_pin(self, item: Any) -> None: self.field_clear() def act_current_password_show(self) -> None: - self.set_current_password_show(self.ui.current_password.echoMode() == QLineEdit.Password, ) # type: ignore [attr-defined] + self.set_current_password_show(self.ui.current_password.echoMode() == QLineEdit.Password) # type: ignore [attr-defined] def act_new_password_show(self) -> None: self.set_new_password_show(self.ui.new_password.echoMode() == QLineEdit.Password) # type: ignore [attr-defined] @@ -371,14 +381,9 @@ def handle_status_otp(self, otp_state: bool) -> None: def handle_info_otp(self, status: SelectResponse) -> None: self.otp_counter = status.pin_attempt_counter self.otp_version = status.version_str() - self.otp_serial_nr = ( - status.serial_number.hex() - ) - # self.ui.status_label.setText("Fido2-Pin is set!\n" if self.otp_state else "Fido2-Pin is not set !\n" - # f"\tVersion: {version}\n" - # f"\tPIN attempt counter: {is_set}\n" - # f"\tSerial number: {serial_nr}\n") - print(self.otp_counter, self.otp_version, self.otp_serial_nr) + if status.serial_number is not None: + self.otp_serial_nr = status.serial_number.hex() + # print(self.otp_counter, self.otp_version, self.otp_serial_nr) @Slot() def check_credential(self, new: bool) -> None: diff --git a/nitrokeyapp/settings_tab/data.py b/nitrokeyapp/settings_tab/data.py deleted file mode 100644 index 3a127cc9..00000000 --- a/nitrokeyapp/settings_tab/data.py +++ /dev/null @@ -1,28 +0,0 @@ -from dataclasses import dataclass -from enum import Enum, auto, unique - -from pynitrokey.nk3.secrets_app import ( - CCIDInstruction, - SecretsApp, - SelectResponse, -) - - -class pin_check: - def check(self) -> SelectResponse: - check = SecretsApp.select() - return check - - -# @classmethod -# def check(cls, CCID: CCIDInstruction) -> SelectResponse: -# SecretsApp.select(cls, CCID) - -# @dataclass -# class Pin: -# id: bytes -# pintype: Optional[item.data(1, 0)] -# name: str -# desc: str - -# cls, secrets: SelectResponse diff --git a/nitrokeyapp/settings_tab/worker.py b/nitrokeyapp/settings_tab/worker.py index 0cb20ba7..2bdf5067 100644 --- a/nitrokeyapp/settings_tab/worker.py +++ b/nitrokeyapp/settings_tab/worker.py @@ -22,8 +22,6 @@ class SettingsWorker(Job): status_fido = Signal(bool) status_otp = Signal(bool) info_otp = Signal(SelectResponse) - finish = Signal() - def __init__(self, common_ui: CommonUi) -> None: super().__init__(common_ui) @@ -67,7 +65,6 @@ def fido_change_pw(self, data: DeviceData, old_pin: str, new_pin: str) -> None: client_pin.change_pin(old_pin, new_pin) else: client_pin.set_pin(new_pin) - self.finish.emit() except Exception as e: self.trigger_error(f"fido2 change_pin failed: {e}") self.busy_state_changed.emit(False) @@ -84,7 +81,6 @@ def otp_change_pw(self, data: DeviceData, old_pin: str, new_pin: str) -> None: secrets.change_pin_raw(old_pin, new_pin) else: secrets.set_pin_raw(new_pin) - self.finish.emit() except SecretsAppException as e: self.trigger_error(f"PIN validation failed: {e}") self.busy_state_changed.emit(False) diff --git a/nitrokeyapp/ui/settings_tab.ui b/nitrokeyapp/ui/settings_tab.ui index da9811d9..56606b83 100644 --- a/nitrokeyapp/ui/settings_tab.ui +++ b/nitrokeyapp/ui/settings_tab.ui @@ -23,9 +23,6 @@ - - 0 - @@ -39,97 +36,6 @@ - - 9 - - - 0 - - - 9 - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - Abort - - - - icons/close.svgicons/close.svg - - - - - - - - 0 - 0 - - - - Reset - - - - icons/delete.svgicons/delete.svg - - - - - - - - 0 - 0 - - - - Save - - - - icons/save.svgicons/save.svg - - - - - - @@ -155,7 +61,7 @@ - + @@ -167,19 +73,20 @@ 2 - - - 0 - - - 0 - - - 0 - - - 0 - + + + + + Qt::Vertical + + + + 20 + 483 + + + + @@ -371,7 +278,20 @@ - + + + + Qt::Vertical + + + + 20 + 40 + + + + + New Password: @@ -381,7 +301,7 @@ - + @@ -403,7 +323,7 @@ - + Qt::LeftToRight @@ -416,7 +336,7 @@ - + @@ -432,7 +352,7 @@ - + Qt::Vertical @@ -448,39 +368,6 @@ - - - - Qt::Horizontal - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - @@ -528,6 +415,88 @@ + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + Abort + + + + icons/close.svgicons/close.svg + + + + + + + + 0 + 0 + + + + Reset + + + + icons/delete.svgicons/delete.svg + + + + + + + + 0 + 0 + + + + Save + + + + icons/save.svgicons/save.svg + + + + + + @@ -560,7 +529,6 @@ - From 6c3ea68a8df8223bb7d5272340cf676e11fd01c5 Mon Sep 17 00:00:00 2001 From: jj-so Date: Fri, 24 May 2024 14:40:58 +0200 Subject: [PATCH 13/14] Settings: async --- nitrokeyapp/settings_tab/__init__.py | 92 ++++++++++------------------ nitrokeyapp/settings_tab/worker.py | 6 +- 2 files changed, 36 insertions(+), 62 deletions(-) diff --git a/nitrokeyapp/settings_tab/__init__.py b/nitrokeyapp/settings_tab/__init__.py index 61f87bc4..cd3d29e7 100644 --- a/nitrokeyapp/settings_tab/__init__.py +++ b/nitrokeyapp/settings_tab/__init__.py @@ -1,6 +1,6 @@ import logging from enum import Enum -from typing import Any, Optional +from typing import Optional from pynitrokey.nk3.secrets_app import SelectResponse from PySide6.QtCore import QThread, Signal, Slot @@ -33,12 +33,6 @@ class SettingsTab(QtUtilsMixIn, QWidget): start_touch = Signal() stop_touch = Signal() - # fido_state: bool - # otp_state: bool - # otp_counter: int - # otp_version: str - # otp_serial_nr: str - # worker triggers trigger_fido_status = Signal(DeviceData) trigger_otp_status = Signal(DeviceData) @@ -64,17 +58,10 @@ def __init__(self, parent: Optional[QWidget] = None) -> None: self.trigger_fido_change_pw.connect(self._worker.fido_change_pw) self._worker.status_fido.connect(self.handle_status_fido) - self._worker.status_otp.connect(self.handle_status_otp) self._worker.info_otp.connect(self.handle_info_otp) self.ui = self.load_ui("settings_tab.ui", self) - self.fido_state: bool - self.otp_state: bool - self.otp_counter: Optional[int] - self.otp_version: Optional[str] - self.otp_serial_nr: Optional[str] - # Tree pin_icon = self.get_qicon("dialpad.svg") @@ -177,7 +164,7 @@ def field_btn(self) -> None: self.show_repeat_password_check.setVisible(False) self.show_repeat_password_false.setVisible(False) - def show_widget(self, item: Any) -> None: + def show_widget(self, item: QTreeWidgetItem) -> None: pintype = item.data(1, 0) if pintype == SettingsTabState.Fido or pintype == SettingsTabState.otp: self.show_pin(item) @@ -186,16 +173,14 @@ def show_widget(self, item: Any) -> None: else: self.edit_pin(item) - def collapse_all_except(self, item: Any) -> None: + def collapse_all_except(self, item: QTreeWidgetItem) -> None: top_level_items = self.ui.settings_tree.invisibleRootItem().takeChildren() for top_level_item in top_level_items: if top_level_item is not item.parent(): top_level_item.setExpanded(False) self.ui.settings_tree.invisibleRootItem().addChildren(top_level_items) - def show_pin(self, item: Any) -> None: - self.trigger_fido_status.emit(self.data) - self.trigger_otp_status.emit(self.data) + def show_pin(self, item: QTreeWidgetItem) -> None: self.ui.settings_empty.hide() self.ui.pinsettings_edit.hide() self.ui.pinsettings_desc.show() @@ -211,27 +196,12 @@ def show_pin(self, item: Any) -> None: self.ui.pin_name.setText(name) self.ui.pin_description.setText(desc) self.ui.pin_description.setReadOnly(True) - # fido_state = self.fido_state - # otp_state = self.otp_state if pintype == SettingsTabState.Fido: - if self.fido_state: - pin = "Fido2-Pin is set!" - else: - pin = "Fido2-Pin is not set!" - self.ui.status_label.setText(f"\t{pin}\n\n\n\n") + self.trigger_fido_status.emit(self.data) elif pintype == SettingsTabState.otp: - if self.otp_state: - pin = "OTP-Pin is set!" - else: - pin = "OTP-Pin is not set!" - self.ui.status_label.setText( - f"\t{pin}\n\n" - f"\tVersion: {self.otp_version}\n" - f"\tPIN attempt counter: {self.otp_counter}\n" - f"\tSerial number: {self.otp_serial_nr}" - ) - - def edit_pin(self, item: Any) -> None: + self.trigger_otp_status.emit(self.data) + + def edit_pin(self, item: QTreeWidgetItem) -> None: pintype = item.data(1, 0) self.ui.settings_empty.hide() self.ui.pinsettings_desc.hide() @@ -240,18 +210,10 @@ def edit_pin(self, item: Any) -> None: self.field_clear() - self.trigger_otp_status.emit(self.data) - self.trigger_fido_status.emit(self.data) - - if ( - self.fido_state - and pintype == SettingsTabState.FidoPw - or self.otp_state - and pintype == SettingsTabState.otpPw - ): - self.show_current_password(True) - else: - self.show_current_password(False) + if pintype == SettingsTabState.FidoPw: + self.trigger_fido_status.emit(self.data) + elif pintype == SettingsTabState.otpPw: + self.trigger_otp_status.emit(self.data) self.ui.btn_abort.show() self.ui.btn_reset.hide() @@ -268,11 +230,11 @@ def edit_pin(self, item: Any) -> None: self.field_btn() - def abort(self, item: Any) -> None: + def abort(self, item: QTreeWidgetItem) -> None: p_item = item.parent() self.show_widget(p_item) - def save_pin(self, item: Any) -> None: + def save_pin(self, item: QTreeWidgetItem) -> None: pintype = item.data(1, 0) old_pin = self.ui.current_password.text() new_pin = self.ui.repeat_password.text() @@ -372,18 +334,32 @@ def show_current_password(self, show: bool) -> None: @Slot(bool) def handle_status_fido(self, fido_state: bool) -> None: self.fido_state = fido_state - - @Slot(bool) - def handle_status_otp(self, otp_state: bool) -> None: - self.otp_state = otp_state + if self.fido_state: + pin = "Fido2-Pin is set!" + self.show_current_password(True) + else: + pin = "Fido2-Pin is not set!" + self.show_current_password(False) + self.ui.status_label.setText(f"\t{pin}\n\n\n\n") @Slot(SelectResponse) - def handle_info_otp(self, status: SelectResponse) -> None: + def handle_info_otp(self, otp_state: bool, status: SelectResponse) -> None: + self.otp_state = otp_state self.otp_counter = status.pin_attempt_counter self.otp_version = status.version_str() if status.serial_number is not None: self.otp_serial_nr = status.serial_number.hex() - # print(self.otp_counter, self.otp_version, self.otp_serial_nr) + if self.otp_state: + pin = "OTP-Pin is set!" + self.show_current_password(True) + else: + pin = "OTP-Pin is not set!" + self.show_current_password(False) + self.ui.status_label.setText( + f"\t{pin}\n\n" + f"\tVersion: {self.otp_version}\n" + f"\tPIN attempt counter: {self.otp_counter}\n" + f"\tSerial number: {self.otp_serial_nr}") @Slot() def check_credential(self, new: bool) -> None: diff --git a/nitrokeyapp/settings_tab/worker.py b/nitrokeyapp/settings_tab/worker.py index 2bdf5067..c22aa3cc 100644 --- a/nitrokeyapp/settings_tab/worker.py +++ b/nitrokeyapp/settings_tab/worker.py @@ -20,8 +20,7 @@ class SettingsWorker(Job): busy_state_changed = Signal(bool) status_fido = Signal(bool) - status_otp = Signal(bool) - info_otp = Signal(SelectResponse) + info_otp = Signal(bool, SelectResponse) def __init__(self, common_ui: CommonUi) -> None: super().__init__(common_ui) @@ -46,8 +45,7 @@ def otp_status(self, data: DeviceData) -> bool: pin_status = True else: pin_status = False - self.status_otp.emit(pin_status) - self.info_otp.emit(status) + self.info_otp.emit(pin_status, status) return pin_status @Slot(DeviceData, str, str) From 628a96589d5ca6c0a700e31d3edc614ee8d61f5a Mon Sep 17 00:00:00 2001 From: jj-so Date: Fri, 24 May 2024 16:28:08 +0200 Subject: [PATCH 14/14] Settings: rebuild worker --- nitrokeyapp/settings_tab/__init__.py | 3 +- nitrokeyapp/settings_tab/worker.py | 160 ++++++++++++++++++++++----- 2 files changed, 134 insertions(+), 29 deletions(-) diff --git a/nitrokeyapp/settings_tab/__init__.py b/nitrokeyapp/settings_tab/__init__.py index cd3d29e7..6473e4b9 100644 --- a/nitrokeyapp/settings_tab/__init__.py +++ b/nitrokeyapp/settings_tab/__init__.py @@ -359,7 +359,8 @@ def handle_info_otp(self, otp_state: bool, status: SelectResponse) -> None: f"\t{pin}\n\n" f"\tVersion: {self.otp_version}\n" f"\tPIN attempt counter: {self.otp_counter}\n" - f"\tSerial number: {self.otp_serial_nr}") + f"\tSerial number: {self.otp_serial_nr}" + ) @Slot() def check_credential(self, new: bool) -> None: diff --git a/nitrokeyapp/settings_tab/worker.py b/nitrokeyapp/settings_tab/worker.py index c22aa3cc..0e1ac44a 100644 --- a/nitrokeyapp/settings_tab/worker.py +++ b/nitrokeyapp/settings_tab/worker.py @@ -12,33 +12,52 @@ from nitrokeyapp.common_ui import CommonUi from nitrokeyapp.device_data import DeviceData -from nitrokeyapp.worker import Job +from nitrokeyapp.worker import Job, Worker logger = logging.getLogger(__name__) -class SettingsWorker(Job): - busy_state_changed = Signal(bool) +class CheckFidoPinStatus(Job): status_fido = Signal(bool) - info_otp = Signal(bool, SelectResponse) - def __init__(self, common_ui: CommonUi) -> None: + def __init__( + self, + common_ui: CommonUi, + data: DeviceData, + ) -> None: super().__init__(common_ui) - @Slot(DeviceData) - def fido_status(self, data: DeviceData) -> bool: + self.data = data + + self.status_fido.connect(lambda _: self.finished.emit()) + + def run(self) -> None: pin_status: bool = False - with data.open() as device: + with self.data.open() as device: ctaphid_raw_dev = device.device fido2_client = find(raw_device=ctaphid_raw_dev) pin_status = fido2_client.has_pin() self.status_fido.emit(pin_status) - return pin_status + return - @Slot(DeviceData) - def otp_status(self, data: DeviceData) -> bool: + +class CheckOtpInfo(Job): + info_otp = Signal(bool, SelectResponse) + + def __init__( + self, + common_ui: CommonUi, + data: DeviceData, + ) -> None: + super().__init__(common_ui) + + self.data = data + + self.info_otp.connect(lambda _: self.finished.emit()) + + def run(self) -> None: pin_status: bool = False - with data.open() as device: + with self.data.open() as device: secrets = SecretsApp(device) status = secrets.select() if status.pin_attempt_counter is not None: @@ -46,13 +65,38 @@ def otp_status(self, data: DeviceData) -> bool: else: pin_status = False self.info_otp.emit(pin_status, status) + return + + +class SaveFidoPinJob(Job): + change_pw_fido = Signal() + + def __init__( + self, + common_ui: CommonUi, + data: DeviceData, + old_pin: str, + new_pin: str, + ) -> None: + super().__init__(common_ui) + + self.data = data + self.old_pin = old_pin + self.new_pin = new_pin + + self.change_pw_fido.connect(lambda _: self.finished.emit()) + + def check(self) -> bool: + pin_status: bool = False + with self.data.open() as device: + ctaphid_raw_dev = device.device + fido2_client = find(raw_device=ctaphid_raw_dev) + pin_status = fido2_client.has_pin() return pin_status - @Slot(DeviceData, str, str) - def fido_change_pw(self, data: DeviceData, old_pin: str, new_pin: str) -> None: - self.busy_state_changed.emit(True) - fido_state = self.fido_status(data) - with data.open() as device: + def run(self) -> None: + fido_state = self.check() + with self.data.open() as device: ctaphid_raw_dev = device.device fido2_client = find(raw_device=ctaphid_raw_dev) assert isinstance(fido2_client.ctap2, Ctap2) @@ -60,29 +104,89 @@ def fido_change_pw(self, data: DeviceData, old_pin: str, new_pin: str) -> None: try: if fido_state: - client_pin.change_pin(old_pin, new_pin) + client_pin.change_pin(self.old_pin, self.new_pin) else: - client_pin.set_pin(new_pin) + client_pin.set_pin(self.new_pin) except Exception as e: self.trigger_error(f"fido2 change_pin failed: {e}") - self.busy_state_changed.emit(False) - @Slot(DeviceData, str, str) - def otp_change_pw(self, data: DeviceData, old_pin: str, new_pin: str) -> None: - self.busy_state_changed.emit(True) - otp_state = self.otp_status(data) - with data.open() as device: + +class SaveOtpPinJob(Job): + change_pw_otp = Signal() + + def __init__( + self, + common_ui: CommonUi, + data: DeviceData, + old_pin: str, + new_pin: str, + ) -> None: + super().__init__(common_ui) + + self.data = data + self.old_pin = old_pin + self.new_pin = new_pin + + self.change_pw_otp.connect(lambda _: self.finished.emit()) + + def check(self) -> bool: + pin_status: bool = False + with self.data.open() as device: + secrets = SecretsApp(device) + status = secrets.select() + if status.pin_attempt_counter is not None: + pin_status = True + else: + pin_status = False + return pin_status + + def run(self) -> None: + otp_state = self.check() + with self.data.open() as device: secrets = SecretsApp(device) try: with self.touch_prompt(): if otp_state: - secrets.change_pin_raw(old_pin, new_pin) + secrets.change_pin_raw(self.old_pin, self.new_pin) else: - secrets.set_pin_raw(new_pin) + secrets.set_pin_raw(self.new_pin) except SecretsAppException as e: self.trigger_error(f"PIN validation failed: {e}") - self.busy_state_changed.emit(False) @Slot(str) def trigger_error(self, msg: str) -> None: self.common_ui.info.error.emit(msg) + + +class SettingsWorker(Worker): + change_pw_fido = Signal() + change_pw_otp = Signal() + status_fido = Signal(bool) + info_otp = Signal(bool, SelectResponse) + + def __init__(self, common_ui: CommonUi) -> None: + super().__init__(common_ui) + + @Slot(DeviceData) + def fido_status(self, data: DeviceData) -> None: + job = CheckFidoPinStatus(self.common_ui, data) + job.status_fido.connect(self.status_fido) + self.run(job) + + @Slot(DeviceData) + def otp_status(self, data: DeviceData) -> None: + job = CheckOtpInfo(self.common_ui, data) + job.info_otp.connect(self.info_otp) + self.run(job) + + @Slot(DeviceData, str, str) + def fido_change_pw(self, data: DeviceData, old_pin: str, new_pin: str) -> None: + job = SaveFidoPinJob(self.common_ui, data, old_pin, new_pin) + job.change_pw_fido.connect(self.change_pw_fido) + self.run(job) + + @Slot(DeviceData, str, str) + def otp_change_pw(self, data: DeviceData, old_pin: str, new_pin: str) -> None: + job = SaveOtpPinJob(self.common_ui, data, old_pin, new_pin) + job.change_pw_otp.connect(self.change_pw_otp) + self.run(job)