Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 24 additions & 14 deletions nitrokeyapp/device_data.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import logging
from typing import List, Optional

from nitrokey import nk3
from nitrokey.nk3 import NK3, NK3Bootloader
from nitrokey.trussed import TrussedBase, TrussedDevice, Uuid, Version
from nitrokey import nk3, nkpk
from nitrokey.nk3 import NK3
from nitrokey.nkpk import NKPK
from nitrokey.trussed import Model, TrussedBase, TrussedBootloader, TrussedDevice, Uuid, Version
from nitrokey.trussed.admin_app import Status

from nitrokeyapp.update import Nk3Context, UpdateGUI, UpdateResult, UpdateStatus
from nitrokeyapp.update import UpdateContext, UpdateGUI, UpdateResult, UpdateStatus

logger = logging.getLogger(__name__)


class DeviceData:
def __init__(self, device: TrussedBase) -> None:
self.path = device.path
self.model = device.model
self.updating = False

self._status: Optional[Status] = None
Expand All @@ -35,18 +37,21 @@ def __repr__(self) -> str:

@classmethod
def list(cls) -> List["DeviceData"]:
return [cls(dev) for dev in nk3.list()]
nk3_devices = [cls(dev) for dev in nk3.list()]
nkpk_devices = [cls(dev) for dev in nkpk.list()]
return nk3_devices + nkpk_devices

@property
def name(self) -> str:
if self.is_bootloader:
# desc = self.path.split("/")[-1]
return "Nitrokey 3 (BL)"
return f"Nitrokey 3: {self.uuid_prefix}"
return f"{self.model} (BL)"
else:
return f"{self.model}: {self.uuid_prefix}"

@property
def is_bootloader(self) -> bool:
return isinstance(self._device, NK3Bootloader)
return isinstance(self._device, TrussedBootloader)

@property
def is_too_old(self) -> bool:
Expand Down Expand Up @@ -94,20 +99,25 @@ def uuid_prefix(self) -> str:
assert isinstance(self._device, TrussedDevice)
return str(self.uuid)[:5]

def open(self) -> NK3:
device = NK3.open(self.path)
def open(self) -> TrussedDevice:
device: Optional[TrussedDevice] = None
if self.model == Model.NK3:
device = NK3.open(self.path)
elif self.model == Model.NKPK:
device = NKPK.open(self.path)

if device:
return device
else:
# TODO: improve error handling
raise RuntimeError(f"Failed to open device {self.uuid} at {self.path}")
raise RuntimeError(f"Failed to open {self.model} device {self.uuid} at {self.path}")

def update(self, ui: UpdateGUI, image: Optional[str] = None) -> UpdateResult:
self.updating = True
result = Nk3Context(self.path).update(ui, image)
result = UpdateContext(self.path, self.model).update(ui, image)
if result.status == UpdateStatus.SUCCESS:
logger.info("Nitrokey 3 successfully updated")
logger.info(f"{self.model} successfully updated")
else:
logger.error(f"Nitrokey 3 update failed: {result}")
logger.error(f"{self.model} update failed: {result}")
self.updating = False
return result
4 changes: 3 additions & 1 deletion nitrokeyapp/gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from types import TracebackType
from typing import Dict, Optional, Type

from nitrokey.trussed import Model
from PySide6 import QtWidgets
from PySide6.QtCore import QEvent, Qt, Signal, Slot
from PySide6.QtGui import QCursor
Expand Down Expand Up @@ -251,7 +252,8 @@ def show_device(self, data: DeviceData) -> None:
self.tabs.setTabEnabled(1, False)
self.tabs.setTabEnabled(2, False)
else:
self.tabs.setTabEnabled(1, True)
has_secrets = self.selected_device.model == Model.NK3
self.tabs.setTabEnabled(1, has_secrets)
self.tabs.setTabEnabled(2, True)

self.show_navigation()
Expand Down
18 changes: 8 additions & 10 deletions nitrokeyapp/overview_tab/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ def refresh(self, data: DeviceData, force: bool = False) -> None:
self.ui.status_label.hide()
self.ui.nk3_status.hide()
self.ui.more_info.hide()
self.ui.nk3_label.setText("Nitrokey 3 Bootloader")
self.ui.nk3_label.setText(f"{data.model} Bootloader")
self.status_error(InitStatus(0))

else:
Expand All @@ -105,7 +105,7 @@ def refresh(self, data: DeviceData, force: bool = False) -> None:
str(data.status.variant.name),
str(data.status.init_status),
)
self.ui.nk3_label.setText("Nitrokey 3")
self.ui.nk3_label.setText(str(data.model))
if data.status.init_status is None:
self.ui.status_label.hide()
self.ui.nk3_status.hide()
Expand Down Expand Up @@ -133,14 +133,12 @@ def status_error(self, init: InitStatus) -> None:

def set_update_enabled(self, enabled: bool) -> None:
tooltip = ""
if enabled:
...
else:
if not enabled:
self.hide_more_options()
self.common_ui.info.info.emit(
"Please remove all Nitrokey 3 devices except the one you want to update."
"Please remove all Nitrokey devices except the one you want to update."
)
tooltip = "Please remove all Nitrokey 3 devices except the one you want to update."
tooltip = "Please remove all Nitrokey devices except the one you want to update."

self.ui.btn_update.setEnabled(enabled)
self.ui.btn_update.setToolTip(tooltip)
Expand Down Expand Up @@ -200,11 +198,11 @@ def device_updated(self, result: UpdateResult) -> None:
msg = ": " + result.message

if result.status == UpdateStatus.SUCCESS:
self.common_ui.info.info.emit(f"Nitrokey 3 successfully updated{msg}")
self.common_ui.info.info.emit(f"{result.model} successfully updated{msg}")
elif result.status == UpdateStatus.ERROR:
self.common_ui.info.error.emit(f"Nitrokey 3 update failed{msg}")
self.common_ui.info.error.emit(f"{result.model} update failed{msg}")
elif result.status == UpdateStatus.ABORTED:
self.common_ui.info.error.emit(f"Nitrokey 3 update aborted{msg}")
self.common_ui.info.error.emit(f"{result.model} update aborted{msg}")
else:
self.common_ui.info.error.emit(f"Unexpected update result: {result.status}{msg}")

Expand Down
2 changes: 1 addition & 1 deletion nitrokeyapp/overview_tab/worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def __init__(self, common_ui: CommonUi, data: DeviceData, is_qubesos: bool) -> N

self.device_updated.connect(lambda _: self.finished.emit())

self.update_gui = UpdateGUI(self.common_ui, self.is_qubesos)
self.update_gui = UpdateGUI(self.common_ui, data.model, self.is_qubesos)
self.common_ui.prompt.confirmed.connect(self.cancel_busy_wait)

def run(self) -> None:
Expand Down
25 changes: 25 additions & 0 deletions nitrokeyapp/secrets_tab/worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from datetime import datetime
from typing import Dict, Optional

from nitrokey.nk3 import NK3
from nitrokey.nk3.secrets_app import SecretsApp, SecretsAppException
from nitrokey.trussed import Uuid
from PySide6.QtCore import QObject, Signal, Slot
Expand Down Expand Up @@ -64,6 +65,8 @@ def run(self) -> None:
compatible = False
try:
with self.data.open() as device:
if not isinstance(device, NK3):
return
secrets = SecretsApp(device)
try:
compatible = secrets._semver_equal_or_newer("4.11.0")
Expand Down Expand Up @@ -112,6 +115,8 @@ def cleanup(self) -> None:

def run(self) -> None:
with self.data.open() as device:
if not isinstance(device, NK3):
return
secrets = SecretsApp(device)
select = secrets.select()

Expand All @@ -129,6 +134,8 @@ def run(self) -> None:
@Slot(str)
def pin_queried(self, pin: str) -> None:
with self.data.open() as device:
if not isinstance(device, NK3):
return
secrets = SecretsApp(device)
try:
with self.touch_prompt():
Expand All @@ -145,6 +152,8 @@ def pin_queried(self, pin: str) -> None:
@Slot(str)
def pin_chosen(self, pin: str) -> None:
with self.data.open() as device:
if not isinstance(device, NK3):
return
secrets = SecretsApp(device)
with self.touch_prompt():
secrets.set_pin_raw(pin)
Expand Down Expand Up @@ -281,6 +290,8 @@ def temp_rename_credential(self, from_cred_id: bytes) -> bytes:
new_cred_id += b"_"

with self.data.open() as device:
if not isinstance(device, NK3):
return from_cred_id
secrets = SecretsApp(device)
with self.touch_prompt():
secrets.update_credential(cred_id=from_cred_id, new_name=new_cred_id)
Expand All @@ -290,6 +301,8 @@ def temp_rename_credential(self, from_cred_id: bytes) -> bytes:
@Slot()
def edit_credential_final(self) -> None:
with self.data.open() as device:
if not isinstance(device, NK3):
return
secrets = SecretsApp(device)
with self.touch_prompt():
reg_data = {
Expand Down Expand Up @@ -370,6 +383,8 @@ def add_credential(self, successful: bool = True) -> None:
return

with self.data.open() as device:
if not isinstance(device, NK3):
return
secrets = SecretsApp(device)
with self.touch_prompt():
reg_data = {
Expand Down Expand Up @@ -435,6 +450,8 @@ def run(self) -> None:
@Slot()
def delete_credential(self) -> None:
with self.data.open() as device:
if not isinstance(device, NK3):
return
secrets = SecretsApp(device)
try:
secrets.delete(self.credential.id)
Expand Down Expand Up @@ -478,6 +495,8 @@ def run(self) -> None:
@Slot()
def generate_otp(self) -> None:
with self.data.open() as device:
if not isinstance(device, NK3):
return
secrets = SecretsApp(device)

challenge = None
Expand Down Expand Up @@ -532,6 +551,8 @@ def run(self) -> None:
self.spawn(verify_pin_job)
else:
with self.data.open() as device:
if not isinstance(device, NK3):
return
secrets = SecretsApp(device)
credentials = Credential.list(secrets)
self.credentials_listed.emit(credentials)
Expand All @@ -543,6 +564,8 @@ def list_protected_credentials(self, successful: bool) -> None:
self.uncheck_checkbox.emit(True)

with self.data.open() as device:
if not isinstance(device, NK3):
return
secrets = SecretsApp(device)
for credential in Credential.list(secrets):
credentials.append(credential)
Expand Down Expand Up @@ -584,6 +607,8 @@ def run(self) -> None:
@Slot()
def get_credential(self) -> None:
with self.data.open() as device:
if not isinstance(device, NK3):
return
secrets = SecretsApp(device)
try:
pse = secrets.get_credential(self.credential.id)
Expand Down
7 changes: 6 additions & 1 deletion nitrokeyapp/settings_tab/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from fido2.ctap2.base import Info
from nitrokey.nk3.secrets_app import SelectResponse
from nitrokey.trussed import Model
from PySide6.QtCore import QThread, Signal, Slot
from PySide6.QtWidgets import QLineEdit, QTreeWidgetItem, QWidget

Expand Down Expand Up @@ -180,6 +181,10 @@ def refresh(self, data: DeviceData, force: bool = False) -> None:
return
self.reset()
self.data = data
has_passwords = self.data.model == Model.NK3
self.items[State.Passwords].setDisabled(not has_passwords)
self.items[State.PasswordsPin].setDisabled(not has_passwords)
self.items[State.PasswordsReset].setDisabled(not has_passwords)

def field_btn(self) -> None:
icon_visibility = self.get_qicon("visibility_off.svg")
Expand Down Expand Up @@ -212,7 +217,7 @@ def field_btn(self) -> None:

def show_widget(self, item: Optional[QTreeWidgetItem]) -> None:
self.active_item = item
if item is None:
if item is None or item.isDisabled():
self.view_settings_empty()
return

Expand Down
9 changes: 9 additions & 0 deletions nitrokeyapp/settings_tab/worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from fido2.ctap2.base import Ctap2, Info
from fido2.ctap2.pin import ClientPin
from nitrokey.nk3 import NK3
from nitrokey.nk3.secrets_app import SecretsApp, SecretsAppException, SelectResponse
from PySide6.QtCore import Signal, Slot

Expand Down Expand Up @@ -50,6 +51,8 @@ def __init__(self, common_ui: CommonUi, data: DeviceData) -> None:
def run(self) -> None:
pin_status: bool = False
with self.data.open() as device:
if not isinstance(device, NK3):
return
secrets = SecretsApp(device)
status = secrets.select()
if status.pin_attempt_counter is not None:
Expand Down Expand Up @@ -111,6 +114,8 @@ def __init__(self, common_ui: CommonUi, data: DeviceData, old_pin: str, new_pin:
def check(self) -> bool:
pin_status: bool = False
with self.data.open() as device:
if not isinstance(device, NK3):
return pin_status
secrets = SecretsApp(device)
status = secrets.select()
if status.pin_attempt_counter is not None:
Expand All @@ -123,6 +128,8 @@ def run(self) -> None:
passwords_state = self.check()
with self.touch_prompt():
with self.data.open() as device:
if not isinstance(device, NK3):
return
secrets = SecretsApp(device)
try:
if passwords_state:
Expand Down Expand Up @@ -181,6 +188,8 @@ def __init__(self, common_ui: CommonUi, data: DeviceData) -> None:

def run(self) -> None:
with self.data.open() as device:
if not isinstance(device, NK3):
return
secrets = SecretsApp(device)
try:
with self.touch_prompt():
Expand Down
Loading