LudovicRousseau / pyscard

pyscard smartcard library for python
http://pyscard.sourceforge.net/
GNU Lesser General Public License v2.1
397 stars 113 forks source link

pyscard 2.1.1 - Failed to get status change Cannot find a smart card reader. : Cannot find a smart card reader. (0x8010002E) #207

Open deweydb opened 1 day ago

deweydb commented 1 day ago

Your system information

Please describe your issue in as much detail as possible:

After upgrading from version 2.0.10 to 2.1.1 we have an issue with our Omnikey 5022 USB Reader we receive the following error:

Traceback (most recent call last):
  File "C:\Users\Adrian\AppData\Local\Programs\Python\Python312\Lib\site-packages\smartcard\CardMonitoring.py", line 165, in run
    currentcards = self.cardrequest.waitforcardevent()
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Adrian\AppData\Local\Programs\Python\Python312\Lib\site-packages\smartcard\CardRequest.py", line 72, in waitforcardevent
    return self.pcsccardrequest.waitforcardevent()
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Adrian\AppData\Local\Programs\Python\Python312\Lib\site-packages\smartcard\pcsc\PCSCCardRequest.py", line 291, in waitforcardevent
    readernames = self.getReaderNames()
                  ^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Adrian\AppData\Local\Programs\Python\Python312\Lib\site-packages\smartcard\pcsc\PCSCCardRequest.py", line 99, in getReaderNames
    raise ListReadersException(hresult)
smartcard.Exceptions.ListReadersException: Failed to list readers: The action was cancelled by an SCardCancel request.  (0x80100002)
Traceback (most recent call last):
  File "C:\Users\Adrian\AppData\Local\Programs\Python\Python312\Lib\site-packages\smartcard\CardMonitoring.py", line 165, in run
    currentcards = self.cardrequest.waitforcardevent()
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Adrian\AppData\Local\Programs\Python\Python312\Lib\site-packages\smartcard\CardRequest.py", line 72, in waitforcardevent
    return self.pcsccardrequest.waitforcardevent()
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Adrian\AppData\Local\Programs\Python\Python312\Lib\site-packages\smartcard\pcsc\PCSCCardRequest.py", line 322, in waitforcardevent
    raise CardRequestException(
smartcard.Exceptions.CardRequestException: Failed to get status change Cannot find a smart card reader. : Cannot find a smart card reader.  (0x8010002E)

In an effort to try to understand what is going wrong i printed out hresult right before this exception is called and got: -2146435070

Reverting to pyscard 2.0.10 seems to resolve the issue. Please let me know if I can assist in any further tests or debugging of the issue.

LudovicRousseau commented 23 hours ago

can you provide a sample code to reproduce the issue?

deweydb commented 16 hours ago

Hello, the application is a bit complex, but I have tried to extract out the relevant portion to a single file which reproduces this bug, while removing as much superfluous code as possible. Running the following code on version 2.1.1 results in the error:

Traceback (most recent call last):
  File "C:\Users\Adrian\AppData\Local\Programs\Python\Python312\Lib\site-packages\smartcard\CardMonitoring.py", line 165, in run
    currentcards = self.cardrequest.waitforcardevent()
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Adrian\AppData\Local\Programs\Python\Python312\Lib\site-packages\smartcard\CardRequest.py", line 72, in waitforcardevent
    return self.pcsccardrequest.waitforcardevent()
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Adrian\AppData\Local\Programs\Python\Python312\Lib\site-packages\smartcard\pcsc\PCSCCardRequest.py", line 321, in waitforcardevent
    raise CardRequestException(
smartcard.Exceptions.CardRequestException: Failed to get status change Cannot find a smart card reader. : Cannot find a smart card reader.  (0x8010002E)

Running it on version 2.0.10 does not result in such an error.

from smartcard.CardMonitoring import CardObserver, CardMonitor
from smartcard.util import toBytes, toHexString
from smartcard.CardType import ATRCardType
from smartcard.CardRequest import CardRequest
from smartcard.Exceptions import CardRequestTimeoutException, CardConnectionException
from functools import partial
import threading

DEFAULT_WRITER = print
DEFAULT_CARD_ATR = "3B 81 80 01 80 80"
DEFAULT_TIMEOUT = 5.0

class Response:
    def __init__(self, raw_response: list):
        self.data, sw1, sw2 = raw_response
        self.status = sw1, sw2

    def is_successful(self):
        return self.status in [(0x91, 0x00), (0x90, 0x00), (0x91, 0xAF)]

    def to_bytes(self):
        return bytearray(self.data)

    def to_string(self):
        return self.to_bytes().decode("utf-8")

    def to_hex_string(self, *args, **kwargs):
        return toHexString(self.data, *args, **kwargs)

def dispatch(command: list, service) -> Response:
    raw_response = service.connection.transmit(command)
    response = Response(raw_response)
    return response

class Observer(CardObserver):
    def __init__(self, callback=None, auth: dict | None = None, threading_event=None):
        self.callback = callback
        self.auth = auth
        self.threading_event = threading_event
        self.send = None
        card_type = ATRCardType(toBytes(DEFAULT_CARD_ATR))
        card_request = CardRequest(timeout=DEFAULT_TIMEOUT, cardType=card_type)
        self.result = None

        try:
            self.service = card_request.waitforcard()
            self.service.connection.connect()
        except (CardRequestTimeoutException, CardConnectionException) as e:
            return None

    def update(self, _, handlers):
        added, _ = handlers
        if added:
            try:
                self.send = partial(dispatch, service=self.service)
                session_key = self.pre_auth()
                self.result = self.callback(
                    send=self.send, session_key=session_key)
                self.service.connection.disconnect()
                self.threading_event(self.result)
            except CardConnectionException as e:
                return None

    def pre_auth(self) -> list | None:
        if not self.auth:
            return None
        try:
            print("placeholder...")
            return "redacted"
        except Exception as e:
            print(e)

class Omnikey(CardMonitor):
    def __init__(self, config=None):
        super().__init__()
        self.config = config
        self.scan_event = threading.Event()

    def threading_event_callback(self, result):
        self.observer_result = result
        self.scan_event.set()

    def basic(self, *args, **kwargs):
        def decorator(func):
            def wrapper():
                callback = partial(func, *args, **kwargs)
                observer = Observer(
                    callback=callback, threading_event=self.threading_event_callback
                )
                self.addObserver(observer)
                self.deleteObserver(observer)
                return observer.result

            return wrapper

        return decorator

    def authenticate(self, *args, **kwargs):
        def decorator(func):
            def wrapper():
                callback = partial(func, *args, **kwargs)
                observer = Observer(
                    callback=callback,
                    auth=self.config,
                    threading_event=self.threading_event_callback,
                )
                self.addObserver(observer)
                self.deleteObserver(observer)
                return observer.result

            return wrapper

        return decorator

omnikey = Omnikey()

@omnikey.basic()
def create_card(**kwargs):
    pass

create_card()

I should also add that this bug does not seem to happen on Mac OS, it seems to be exclusive to windows.

LudovicRousseau commented 1 hour ago

I can't reproduce the problem on Windows 10 with the current/unreleased code. I modified a bit your code to add some debug and make it worl with any card.

$ diff -u bug_orig.py bug2.py
--- bug_orig.py 2024-10-09 11:49:54.076068500 +0200
+++ bug2.py     2024-10-09 11:53:37.586248300 +0200
@@ -1,13 +1,11 @@
 from smartcard.CardMonitoring import CardObserver, CardMonitor
 from smartcard.util import toBytes, toHexString
-from smartcard.CardType import ATRCardType
+from smartcard.CardType import AnyCardType
 from smartcard.CardRequest import CardRequest
 from smartcard.Exceptions import CardRequestTimeoutException, CardConnectionException
 from functools import partial
 import threading

-DEFAULT_WRITER = print
-DEFAULT_CARD_ATR = "3B 81 80 01 80 80"
 DEFAULT_TIMEOUT = 5.0

 class Response:
@@ -28,6 +26,7 @@
         return toHexString(self.data, *args, **kwargs)

 def dispatch(command: list, service) -> Response:
+    print("dispatch")
     raw_response = service.connection.transmit(command)
     response = Response(raw_response)
     return response
@@ -39,7 +38,7 @@
         self.auth = auth
         self.threading_event = threading_event
         self.send = None
-        card_type = ATRCardType(toBytes(DEFAULT_CARD_ATR))
+        card_type = AnyCardType
         card_request = CardRequest(timeout=DEFAULT_TIMEOUT, cardType=card_type)
         self.result = None

@@ -50,6 +49,7 @@
             return None

     def update(self, _, handlers):
+        print("update:", handlers)
         added, _ = handlers
         if added:
             try:
@@ -63,6 +63,7 @@
                 return None

     def pre_auth(self) -> list | None:
+        print("pre_auth")
         if not self.auth:
             return None
         try:

If I start the program with a card already inserted I immediately get:

update: ([], [])

If I start the program with no card inserted and I insert a card I get:

update: ([3B 6F 00 00 80 5A 28 13 02 10 12 2B 75 11 8E 47 82 90 00 / Alcor Micro USB Smart Card Reader 0], [])
pre_auth

If I start the program with no card inserted and I just wait I get, after 5 seconds:

update: ([], [])

I guess that is the expected behaviour.

Can you install the current code from git and try on your side?

LudovicRousseau commented 1 hour ago

With PySCard 2.1.1 I reproduce your problem. I also see that if I start the test program with a card already inserted I get

update: ([3B 6F 00 00 80 5A 28 13 02 10 12 2B 75 11 8E 47 82 90 00 / Alcor Micro USB Smart Card Reader 0], [])
pre_auth

Instead of an empty added variable. I will fix that.