Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
44b6a25
Accept a CCID device instead of a ctaphid device
sosthene-nitrokey Nov 10, 2025
925f576
isinstance doesn't work because of decorator
sosthene-nitrokey Nov 21, 2025
4f794ae
Forward command through ccid
sosthene-nitrokey Nov 24, 2025
6d9bc6e
Fix comparison
sosthene-nitrokey Nov 24, 2025
4b80b3f
Use lists instead of bytes
sosthene-nitrokey Nov 24, 2025
f1cf386
WIP
sosthene-nitrokey Nov 25, 2025
29e85d4
Make path optional
sosthene-nitrokey Nov 25, 2025
9110841
Return bytes instead of lists of ints
sosthene-nitrokey Nov 25, 2025
8398e65
Expect card connection exception after reboot
sosthene-nitrokey Nov 25, 2025
768c0b1
Fix logger bug
sosthene-nitrokey Nov 26, 2025
ffcdbd0
Use 0 class to fix secrets app
sosthene-nitrokey Nov 26, 2025
bcd475c
CCID: handle responses with body to select
sosthene-nitrokey Nov 26, 2025
9d8fa39
Fix secret app over CCID expecting status bytes to be part of the result
sosthene-nitrokey Nov 26, 2025
450893d
Fix class (Secrets app returns SecureMessagingNotSupported otherwise)
sosthene-nitrokey Nov 26, 2025
150e84d
Add nkpk ATR
sosthene-nitrokey Nov 26, 2025
4ecab9b
Fix recursion caused by list function overriding the builtin list con…
sosthene-nitrokey Nov 26, 2025
0b47541
Handle missing confirmations
sosthene-nitrokey Nov 27, 2025
3c8280f
Make pcsc optional
sosthene-nitrokey Nov 28, 2025
102b863
Fix type checking attempt 1
sosthene-nitrokey Nov 28, 2025
f96253e
Add should_default_ccid helper
sosthene-nitrokey Dec 1, 2025
eed8488
Fix formatting
sosthene-nitrokey Dec 18, 2025
66254f6
Fix ruff lint
sosthene-nitrokey Dec 18, 2025
2436e61
list: allow listing through CCID
sosthene-nitrokey Dec 19, 2025
3a4d82a
Move should_default_ccid to the trussed module
sosthene-nitrokey Dec 19, 2025
a39abcb
Make device cloneable
sosthene-nitrokey Jan 12, 2026
f9559fd
Switch to exclusive transmit
sosthene-nitrokey Jan 13, 2026
a841f9f
Allow configuring exclusivity of connections
sosthene-nitrokey Jan 14, 2026
763f7ea
Better handle already opened devices
sosthene-nitrokey Jan 28, 2026
9fce85f
Remove `clone` method not used by nitrokey-app2 in the end
sosthene-nitrokey Jan 30, 2026
43b525b
Follow review recommendations
sosthene-nitrokey Jan 30, 2026
12a3733
fixup! Remove `clone` method not used by nitrokey-app2 in the end
sosthene-nitrokey Jan 30, 2026
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: 37 additions & 1 deletion poetry.lock
Original file line number Diff line number Diff line change
Expand Up @@ -937,6 +937,39 @@ files = [
[package.extras]
windows-terminal = ["colorama (>=0.4.6)"]

[[package]]
name = "pyscard"
version = "2.3.1"
description = "Smartcard module for Python."
optional = true
python-versions = ">=3.9"
groups = ["main"]
markers = "extra == \"pcsc\""
files = [
{file = "pyscard-2.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5bd277142644441cba8fe71bc991fe83108147f3ba22c01e8b9a5a3f41a31f69"},
{file = "pyscard-2.3.1-cp310-cp310-win32.whl", hash = "sha256:717ea958ee77d7d4514ff0a6eb238082f9437e7882479ee6f742bb9db54419d4"},
{file = "pyscard-2.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:91b27548eddcd6c21f115d5e6151ced9b348aae989b0e4fcc1ad4c479e61610c"},
{file = "pyscard-2.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:341eca9151318093d5543fa812736546de19020d214e01aff9bbc231d1429949"},
{file = "pyscard-2.3.1-cp311-cp311-win32.whl", hash = "sha256:be78734964621b59f8b63c90b822169dc804571df57f574733ccf585a31769af"},
{file = "pyscard-2.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:52da45a3becfb6807dcd9427d763aeb154c9c9e9ad324a13a2bf322fa31baca5"},
{file = "pyscard-2.3.1-cp311-cp311-win_arm64.whl", hash = "sha256:e228c78e028b74e5411a604c765852bdc8fd04321f03e62d25cad8e90de27597"},
{file = "pyscard-2.3.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:acb9f4842cb08110d916e7e37ef225c755a1da627017364c55df547b6f187dcf"},
{file = "pyscard-2.3.1-cp312-cp312-macosx_15_0_x86_64.whl", hash = "sha256:abf5091d5067ac4945eb2a283b60bddce7530595e585868449da12d3b9e885ee"},
{file = "pyscard-2.3.1-cp312-cp312-win32.whl", hash = "sha256:c5d9fde122ffd41a74af72364166e8b23760230163e20c4c130aa0616bf4a786"},
{file = "pyscard-2.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:561889413866141b3a44099f043d10344ee64b0604d2fecb275975b54aa584c6"},
{file = "pyscard-2.3.1-cp312-cp312-win_arm64.whl", hash = "sha256:06ae5e5421a5dd380e8c80046b4242e7b98ed381247117f4cf2c1c8328f74bce"},
{file = "pyscard-2.3.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c11a596407e18cdcf16a4ccd8cdeaa55846d4f7ec2eefc529483e201f6906658"},
{file = "pyscard-2.3.1-cp313-cp313-macosx_15_0_x86_64.whl", hash = "sha256:72b1ab922fad5e050144ec72762e36741271b09d2389cda9b976b61ee0564e71"},
{file = "pyscard-2.3.1-cp313-cp313-win32.whl", hash = "sha256:a0b59d1961ff9fb15d980ad64edae13e4512b7e641ea8959e86133f34091aa5c"},
{file = "pyscard-2.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:df2b256bc719b701807114bdd179f7b303f309a954d5689188b088adfc33ead2"},
{file = "pyscard-2.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:f58b46cd78455a29a0abceabff21b37da81a385a737b29e6dd5e25acb7e3f3da"},
{file = "pyscard-2.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a36bab071c6f4b1d74a8778a784ccf3cc04bf35a6c677bce15e70fdbeac5b932"},
{file = "pyscard-2.3.1.tar.gz", hash = "sha256:a24356f57a0a950740b6e54f51f819edd5296ee8892a6625b0da04724e9e6c13"},
]

[package.extras]
gui = ["wxPython"]

[[package]]
name = "pyserial"
version = "3.5"
Expand Down Expand Up @@ -1499,7 +1532,10 @@ files = [
{file = "wrapt-1.17.2.tar.gz", hash = "sha256:41388e9d4d1522446fe79d3213196bd9e3b301a336965b9e27ca2788ebd122f3"},
]

[extras]
pcsc = ["pyscard"]

[metadata]
lock-version = "2.1"
python-versions = ">=3.10, <4"
content-hash = "1a20a18c696c423e7b3333b9218c5ca13d6f16ac69168fed5c72a6e0fc362674"
content-hash = "05d20e23bd3bdaefcb419d104f058bba9fdde81e849fa9426c814127a2032f76"
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ classifiers = [
"Intended Audience :: Developers",
]

[project.optional-dependencies]
pcsc = ["pyscard >=2, <3"]

[tool.poetry.group.dev]
optional = true

Expand Down
8 changes: 6 additions & 2 deletions src/nitrokey/nk3/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,14 @@
)


def list() -> List[Union[NK3, NK3Bootloader]]:
def list(use_ccid: bool = False, exclusive: bool = True) -> List[Union[NK3, NK3Bootloader]]:
devices: List[Union[NK3, NK3Bootloader]] = []
devices.extend(NK3Bootloader.list())
devices.extend(NK3.list())
if use_ccid:
devices.extend(NK3.list_ccid(exclusive))
else:
devices.extend(NK3.list_ctaphid())

return devices


Expand Down
30 changes: 27 additions & 3 deletions src/nitrokey/nk3/_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,18 @@

from fido2.hid import CtapHidDevice

try:
from smartcard.ExclusiveConnectCardConnection import ExclusiveConnectCardConnection
from smartcard.ExclusiveTransmitCardConnection import ExclusiveTransmitCardConnection
except ModuleNotFoundError:

class ExclusiveTransmitCardConnection: # type: ignore[no-redef]
pass

class ExclusiveConnectCardConnection: # type: ignore[no-redef]
pass


from nitrokey import _VID_NITROKEY
from nitrokey.trussed import Fido2Certs, Model, TrussedDevice, Version

Expand All @@ -31,7 +43,10 @@
class NK3(TrussedDevice):
"""A Nitrokey 3 device running the firmware."""

def __init__(self, device: CtapHidDevice) -> None:
def __init__(
self,
device: CtapHidDevice | ExclusiveTransmitCardConnection | ExclusiveConnectCardConnection,
) -> None:
super().__init__(device, FIDO2_CERTS)

@property
Expand All @@ -49,11 +64,20 @@ def name(self) -> str:
return "Nitrokey 3"

@classmethod
def from_device(cls, device: CtapHidDevice) -> "NK3":
def from_device(
cls,
device: CtapHidDevice | ExclusiveTransmitCardConnection | ExclusiveConnectCardConnection,
) -> "NK3":
return cls(device)

@classmethod
def list(cls) -> List["NK3"]:
def list_ctaphid(cls) -> List["NK3"]:
from . import _PID_NK3_DEVICE

return cls._list_vid_pid(_VID_NITROKEY, _PID_NK3_DEVICE)

@classmethod
def list_ccid(cls, exclusive: bool = True) -> List["NK3"]:
return cls._list_pcsc_atr(
list(bytes.fromhex("3B8F01805D4E6974726F6B657900000000006A")), exclusive
)
38 changes: 33 additions & 5 deletions src/nitrokey/nkpk.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,23 @@
# http://opensource.org/licenses/MIT>, at your option. This file may not be
# copied, modified, or distributed except according to those terms.

import builtins
from typing import List, Optional, Sequence, Union

from fido2.hid import CtapHidDevice

try:
from smartcard.ExclusiveConnectCardConnection import ExclusiveConnectCardConnection
from smartcard.ExclusiveTransmitCardConnection import ExclusiveTransmitCardConnection
except ModuleNotFoundError:

class ExclusiveTransmitCardConnection: # type: ignore[no-redef]
pass

class ExclusiveConnectCardConnection: # type: ignore[no-redef]
pass


from nitrokey import _VID_NITROKEY
from nitrokey.trussed import Fido2Certs, TrussedDevice, Version
from nitrokey.trussed._base import Model
Expand Down Expand Up @@ -44,7 +57,10 @@


class NKPK(TrussedDevice):
def __init__(self, device: CtapHidDevice) -> None:
def __init__(
self,
device: CtapHidDevice | ExclusiveTransmitCardConnection | ExclusiveConnectCardConnection,
) -> None:
super().__init__(device, _FIDO2_CERTS)

@property
Expand All @@ -60,13 +76,22 @@ def name(self) -> str:
return "Nitrokey Passkey"

@classmethod
def from_device(cls, device: CtapHidDevice) -> "NKPK":
def from_device(
cls,
device: CtapHidDevice | ExclusiveTransmitCardConnection | ExclusiveConnectCardConnection,
) -> "NKPK":
return cls(device)

@classmethod
def list(cls) -> List["NKPK"]:
def list_ctaphid(cls) -> List["NKPK"]:
return cls._list_vid_pid(_VID_NITROKEY, _PID_NKPK_DEVICE)

@classmethod
def list_ccid(cls, exclusive: bool = True) -> List["NKPK"]:
return cls._list_pcsc_atr(
builtins.list(bytes.fromhex("3B8F01805D4E6974726F6B657900000000006A")), exclusive
)


class NKPKBootloader(TrussedBootloaderNrf52):
@property
Expand Down Expand Up @@ -94,10 +119,13 @@ def _signature_keys(self) -> Sequence[SignatureKey]:
return _NKPK_DATA.nrf52_signature_keys


def list() -> List[Union[NKPK, NKPKBootloader]]:
def list(use_ccid: bool = False, exclusive: bool = True) -> List[Union[NKPK, NKPKBootloader]]:
devices: List[Union[NKPK, NKPKBootloader]] = []
devices.extend(NKPKBootloader.list())
devices.extend(NKPK.list())
if use_ccid:
devices.extend(NKPK.list_ccid(exclusive))
else:
devices.extend(NKPK.list_ctaphid())
return devices


Expand Down
28 changes: 25 additions & 3 deletions src/nitrokey/trussed/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
# http://opensource.org/licenses/MIT>, at your option. This file may not be
# copied, modified, or distributed except according to those terms.

import ctypes
import sys
from importlib.util import find_spec
from typing import List, Optional

from ._base import Model as Model # noqa: F401
Expand All @@ -23,18 +26,37 @@
from ._utils import Version as Version # noqa: F401


def list(*, model: Optional[Model] = None) -> List[TrussedBase]:
def should_default_ccid() -> bool:
if find_spec("smartcard") is None:
return False

if sys.platform != "win32" and sys.platform != "cygwin":
# Linux or MacOS don't need admin to access with CTAPHID
return False

try:
if ctypes.windll.shell32.IsUserAnAdmin():
return False
else:
return True
except Exception:
return False


def list(
*, use_ccid: bool = False, model: Optional[Model] = None, exclusive: bool = True
) -> List[TrussedBase]:
devices: List[TrussedBase] = []

if model is None or model == Model.NK3:
from nitrokey import nk3

devices.extend(nk3.list())
devices.extend(nk3.list(use_ccid, exclusive))

if model is None or model == Model.NKPK:
from nitrokey import nkpk

devices.extend(nkpk.list())
devices.extend(nkpk.list(use_ccid, exclusive))

return devices

Expand Down
2 changes: 1 addition & 1 deletion src/nitrokey/trussed/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def pid(self) -> int: ...

@property
@abstractmethod
def path(self) -> str: ...
def path(self) -> Optional[str]: ...

@property
@abstractmethod
Expand Down
Loading