1414from typing import List , Optional , Sequence , TypeVar , Union
1515
1616from fido2 .hid import CtapHidDevice , list_descriptors , open_device
17+ from smartcard .CardConnection import CardConnection
18+ from smartcard .Exceptions import NoCardException
19+ from smartcard .System import readers
1720
1821from ._base import TrussedBase
1922from ._utils import Fido2Certs , Uuid
@@ -34,14 +37,19 @@ class App(Enum):
3437
3538class TrussedDevice (TrussedBase ):
3639 def __init__ (
37- self , device : CtapHidDevice , fido2_certs : Sequence [Fido2Certs ]
40+ self ,
41+ device : Optional [CtapHidDevice ],
42+ fido2_certs : Sequence [Fido2Certs ],
43+ device_ccid : Optional [CardConnection ] = None ,
3844 ) -> None :
39- self ._validate_vid_pid (device .descriptor .vid , device .descriptor .pid )
45+ if device is not None :
46+ self ._validate_vid_pid (device .descriptor .vid , device .descriptor .pid )
47+ self ._path = _device_path_to_str (device .descriptor .path )
48+ self ._logger = logger .getChild (self ._path )
4049
4150 self .device = device
51+ self .device_ccid = device_ccid
4252 self .fido2_certs = fido2_certs
43- self ._path = _device_path_to_str (device .descriptor .path )
44- self ._logger = logger .getChild (self ._path )
4553
4654 from .admin_app import AdminApp
4755
@@ -53,7 +61,11 @@ def path(self) -> str:
5361 return self ._path
5462
5563 def close (self ) -> None :
56- self .device .close ()
64+ if self .device is not None :
65+ self .device .close ()
66+ if self .device_ccid is not None :
67+ self .device_ccid .disconnect ()
68+ self .device_ccid .release ()
5769
5870 def reboot (self ) -> bool :
5971 from .admin_app import BootMode
@@ -64,7 +76,8 @@ def uuid(self) -> Optional[Uuid]:
6476 return self .admin .uuid ()
6577
6678 def wink (self ) -> None :
67- self .device .wink ()
79+ if self .device is not None :
80+ self .device .wink ()
6881
6982 def _call (
7083 self ,
@@ -73,7 +86,14 @@ def _call(
7386 response_len : Optional [int ] = None ,
7487 data : bytes = b"" ,
7588 ) -> bytes :
76- response = self .device .call (command , data = data )
89+ if self .device_ccid is not None :
90+ response = _call_ccid (self .device_ccid , command , data )
91+ elif self .device is not None :
92+ response = self .device .call (command , data = data )
93+ else :
94+ raise RuntimeError (
95+ "Nitrokey device needs either a valid CCID device or a valid CTAPHID device"
96+ )
7797 if response_len is not None and response_len != len (response ):
7898 raise ValueError (
7999 f"The response for the CTAPHID { command_name } command has an unexpected length "
@@ -91,7 +111,11 @@ def _call_app(
91111
92112 @classmethod
93113 @abstractmethod
94- def from_device (cls : type [T ], device : CtapHidDevice ) -> T : ...
114+ def from_device (
115+ cls : type [T ],
116+ device : Optional [CtapHidDevice ],
117+ device_ccid : Optional [CardConnection ] = None ,
118+ ) -> T : ...
95119
96120 @classmethod
97121 def open (cls : type [T ], path : str ) -> Optional [T ]:
@@ -104,7 +128,7 @@ def open(cls: type[T], path: str) -> Optional[T]:
104128 logger .warn (f"No CTAPHID device at path { path } " , exc_info = sys .exc_info ())
105129 return None
106130 try :
107- return cls .from_device (device )
131+ return cls .from_device (device , device_ccid = None )
108132 except ValueError :
109133 logger .warn (f"No Nitrokey device at path { path } " , exc_info = sys .exc_info ())
110134 return None
@@ -120,7 +144,36 @@ def _list_vid_pid(cls: type[T], vid: int, pid: int) -> List[T]:
120144 for desc in list_descriptors () # type: ignore
121145 if desc .vid == vid and desc .pid == pid
122146 ]
123- return [cls .from_device (open_device (desc .path )) for desc in descriptors ]
147+ return [
148+ cls .from_device (open_device (desc .path ), device_ccid = None )
149+ for desc in descriptors
150+ ]
151+
152+ @classmethod
153+ def _list_pcsc_atr (cls : type [T ], atr : List [int ]) -> List [T ]:
154+ devices = []
155+ for r in readers ():
156+ connection = r .createConnection ()
157+ try :
158+ connection .connect ()
159+ except NoCardException :
160+ continue
161+ if atr == connection .getATR ():
162+ connection .disconnect ()
163+ connection .release ()
164+ continue
165+ devices .append (cls .from_device (None , device_ccid = connection ))
166+
167+ return devices
168+
169+
170+ def _call_ccid (
171+ device : CardConnection ,
172+ command : int ,
173+ data : bytes = b"" ,
174+ response_len : Optional [int ] = None ,
175+ ) -> bytes :
176+ raise NotImplementedError ("TODO" )
124177
125178
126179def _device_path_to_str (path : Union [bytes , str ]) -> str :
0 commit comments