first init
This commit is contained in:
@@ -0,0 +1,360 @@
|
||||
"""PCSCCardConnection class manages connections thru a PCSC reader.
|
||||
|
||||
__author__ = "https://www.gemalto.com/"
|
||||
|
||||
Copyright 2001-2012 gemalto
|
||||
Author: Jean-Daniel Aussel, mailto:jean-daniel.aussel@gemalto.com
|
||||
|
||||
This file is part of pyscard.
|
||||
|
||||
pyscard is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyscard is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with pyscard; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
"""
|
||||
|
||||
from smartcard.CardConnection import CardConnection
|
||||
from smartcard.Exceptions import (
|
||||
CardConnectionException,
|
||||
NoCardException,
|
||||
SmartcardException,
|
||||
)
|
||||
from smartcard.scard import (
|
||||
SCARD_E_INVALID_VALUE,
|
||||
SCARD_E_NO_SMARTCARD,
|
||||
SCARD_PCI_RAW,
|
||||
SCARD_PCI_T0,
|
||||
SCARD_PCI_T1,
|
||||
SCARD_PROTOCOL_RAW,
|
||||
SCARD_PROTOCOL_T0,
|
||||
SCARD_PROTOCOL_T1,
|
||||
SCARD_PROTOCOL_T15,
|
||||
SCARD_RESET_CARD,
|
||||
SCARD_S_SUCCESS,
|
||||
SCARD_SCOPE_USER,
|
||||
SCARD_SHARE_SHARED,
|
||||
SCARD_UNPOWER_CARD,
|
||||
SCARD_W_REMOVED_CARD,
|
||||
SCardConnect,
|
||||
SCardControl,
|
||||
SCardDisconnect,
|
||||
SCardEstablishContext,
|
||||
SCardGetAttrib,
|
||||
SCardGetErrorMessage,
|
||||
SCardReconnect,
|
||||
SCardReleaseContext,
|
||||
SCardStatus,
|
||||
SCardTransmit,
|
||||
)
|
||||
|
||||
|
||||
def translateprotocolmask(protocol):
|
||||
"""Translate L{CardConnection} protocol mask into PCSC protocol mask."""
|
||||
pcscprotocol = 0
|
||||
if protocol is not None:
|
||||
if CardConnection.T0_protocol & protocol:
|
||||
pcscprotocol |= SCARD_PROTOCOL_T0
|
||||
if CardConnection.T1_protocol & protocol:
|
||||
pcscprotocol |= SCARD_PROTOCOL_T1
|
||||
if CardConnection.RAW_protocol & protocol:
|
||||
pcscprotocol |= SCARD_PROTOCOL_RAW
|
||||
if CardConnection.T15_protocol & protocol:
|
||||
pcscprotocol |= SCARD_PROTOCOL_T15
|
||||
return pcscprotocol
|
||||
|
||||
|
||||
def translateprotocolheader(protocol):
|
||||
"""Translate protocol into PCSC protocol header."""
|
||||
pcscprotocol = 0
|
||||
if protocol is not None:
|
||||
if CardConnection.T0_protocol == protocol:
|
||||
pcscprotocol = SCARD_PCI_T0
|
||||
if CardConnection.T1_protocol == protocol:
|
||||
pcscprotocol = SCARD_PCI_T1
|
||||
if CardConnection.RAW_protocol == protocol:
|
||||
pcscprotocol = SCARD_PCI_RAW
|
||||
return pcscprotocol
|
||||
|
||||
|
||||
dictProtocolHeader = {SCARD_PCI_T0: "T0", SCARD_PCI_T1: "T1", SCARD_PCI_RAW: "RAW"}
|
||||
dictProtocol = {
|
||||
SCARD_PROTOCOL_T0: "T0",
|
||||
SCARD_PROTOCOL_T1: "T1",
|
||||
SCARD_PROTOCOL_RAW: "RAW",
|
||||
SCARD_PROTOCOL_T15: "T15",
|
||||
SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1: "T0 or T1",
|
||||
}
|
||||
|
||||
|
||||
class PCSCCardConnection(CardConnection):
|
||||
"""PCSCCard connection class. Handles connection with a card thru a
|
||||
PCSC reader."""
|
||||
|
||||
def __init__(self, reader):
|
||||
"""Construct a new PCSC card connection.
|
||||
|
||||
@param reader: the reader in which the smartcard to connect to is located.
|
||||
"""
|
||||
CardConnection.__init__(self, reader)
|
||||
self.hcard = None
|
||||
self.disposition = None
|
||||
hresult, self.hcontext = SCardEstablishContext(SCARD_SCOPE_USER)
|
||||
if hresult != SCARD_S_SUCCESS:
|
||||
raise CardConnectionException(
|
||||
"Failed to establish context : " + SCardGetErrorMessage(hresult),
|
||||
hresult=hresult,
|
||||
)
|
||||
|
||||
def __del__(self):
|
||||
"""Destructor. Clean PCSC connection resources."""
|
||||
# race condition: module CardConnection
|
||||
# can disappear before __del__ is called
|
||||
self.release()
|
||||
|
||||
def release(self):
|
||||
"""explicit release"""
|
||||
if self.hcontext is not None:
|
||||
CardConnection.release(self)
|
||||
self.disconnect()
|
||||
hresult = SCardReleaseContext(self.hcontext)
|
||||
if hresult not in (SCARD_S_SUCCESS, SCARD_E_INVALID_VALUE):
|
||||
raise CardConnectionException(
|
||||
"Failed to release context: " + SCardGetErrorMessage(hresult),
|
||||
hresult=hresult,
|
||||
)
|
||||
self.hcontext = None
|
||||
CardConnection.__del__(self)
|
||||
|
||||
def connect(self, protocol=None, mode=None, disposition=None):
|
||||
"""Connect to the card.
|
||||
|
||||
If protocol is not specified, connect with the default
|
||||
connection protocol.
|
||||
|
||||
If mode is not specified, connect with
|
||||
C{smartcard.scard.SCARD_SHARE_SHARED}."""
|
||||
CardConnection.connect(self, protocol)
|
||||
pcscprotocol = translateprotocolmask(protocol)
|
||||
if 0 == pcscprotocol:
|
||||
pcscprotocol = self.getProtocol()
|
||||
|
||||
if mode is None:
|
||||
mode = SCARD_SHARE_SHARED
|
||||
|
||||
# store the way to dispose the card
|
||||
if disposition is None:
|
||||
disposition = SCARD_UNPOWER_CARD
|
||||
self.disposition = disposition
|
||||
|
||||
if self.hcontext is None:
|
||||
raise CardConnectionException("Context already released")
|
||||
|
||||
hresult, self.hcard, dwActiveProtocol = SCardConnect(
|
||||
self.hcontext, str(self.reader), mode, pcscprotocol
|
||||
)
|
||||
if hresult != SCARD_S_SUCCESS:
|
||||
self.hcard = None
|
||||
if hresult in (SCARD_W_REMOVED_CARD, SCARD_E_NO_SMARTCARD):
|
||||
raise NoCardException("Unable to connect", hresult=hresult)
|
||||
|
||||
raise CardConnectionException(
|
||||
"Unable to connect with protocol: "
|
||||
+ dictProtocol[pcscprotocol]
|
||||
+ ". "
|
||||
+ SCardGetErrorMessage(hresult),
|
||||
hresult=hresult,
|
||||
)
|
||||
|
||||
protocol = 0
|
||||
if dwActiveProtocol == SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1:
|
||||
# special case for T0 | T1
|
||||
# this happens when mode=SCARD_SHARE_DIRECT and no protocol is
|
||||
# then negotiated with the card
|
||||
protocol = CardConnection.T0_protocol | CardConnection.T1_protocol
|
||||
else:
|
||||
for p, p_name in dictProtocol.items():
|
||||
if p == dwActiveProtocol:
|
||||
protocol = getattr(CardConnection, f"{p_name}_protocol")
|
||||
PCSCCardConnection.setProtocol(self, protocol)
|
||||
|
||||
def reconnect(self, protocol=None, mode=None, disposition=None):
|
||||
"""Reconnect to the card.
|
||||
|
||||
If protocol is not specified, connect with the default
|
||||
connection protocol.
|
||||
|
||||
If mode is not specified, connect with
|
||||
C{smartcard.scard.SCARD_SHARE_SHARED}.
|
||||
|
||||
If disposition is not specified, do a warm reset
|
||||
(C{smartcard.scard.SCARD_RESET_CARD})"""
|
||||
CardConnection.reconnect(self, protocol)
|
||||
if self.hcard is None:
|
||||
raise CardConnectionException("Card not connected")
|
||||
|
||||
pcscprotocol = translateprotocolmask(protocol)
|
||||
if 0 == pcscprotocol:
|
||||
pcscprotocol = self.getProtocol()
|
||||
|
||||
if mode is None:
|
||||
mode = SCARD_SHARE_SHARED
|
||||
|
||||
# store the way to dispose the card
|
||||
if disposition is None:
|
||||
disposition = SCARD_RESET_CARD
|
||||
self.disposition = disposition
|
||||
|
||||
hresult, dwActiveProtocol = SCardReconnect(
|
||||
self.hcard, mode, pcscprotocol, self.disposition
|
||||
)
|
||||
if hresult != SCARD_S_SUCCESS:
|
||||
self.hcard = None
|
||||
if hresult in (SCARD_W_REMOVED_CARD, SCARD_E_NO_SMARTCARD):
|
||||
raise NoCardException("Unable to reconnect", hresult=hresult)
|
||||
|
||||
raise CardConnectionException(
|
||||
"Unable to reconnect with protocol: "
|
||||
+ dictProtocol[pcscprotocol]
|
||||
+ ". "
|
||||
+ SCardGetErrorMessage(hresult),
|
||||
hresult=hresult,
|
||||
)
|
||||
|
||||
protocol = 0
|
||||
if dwActiveProtocol == SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1:
|
||||
# special case for T0 | T1
|
||||
# this happens when mode=SCARD_SHARE_DIRECT and no protocol is
|
||||
# then negotiated with the card
|
||||
protocol = CardConnection.T0_protocol | CardConnection.T1_protocol
|
||||
else:
|
||||
for p, p_name in dictProtocol.items():
|
||||
if p == dwActiveProtocol:
|
||||
protocol = getattr(CardConnection, f"{p_name}_protocol")
|
||||
PCSCCardConnection.setProtocol(self, protocol)
|
||||
|
||||
def disconnect(self):
|
||||
"""Disconnect from the card."""
|
||||
|
||||
if self.hcard is not None:
|
||||
# when __del__() is invoked in response to a module being
|
||||
# deleted, e.g., when execution of the program is done,
|
||||
# other globals referenced by the __del__() method may
|
||||
# already have been deleted. this causes
|
||||
# CardConnection.disconnect to except with a TypeError
|
||||
try:
|
||||
CardConnection.disconnect(self)
|
||||
except TypeError:
|
||||
pass
|
||||
hresult = SCardDisconnect(self.hcard, self.disposition)
|
||||
self.hcard = None
|
||||
if hresult != SCARD_S_SUCCESS:
|
||||
raise CardConnectionException(
|
||||
"Failed to disconnect: " + SCardGetErrorMessage(hresult),
|
||||
hresult=hresult,
|
||||
)
|
||||
|
||||
def getATR(self):
|
||||
"""Return card ATR"""
|
||||
CardConnection.getATR(self)
|
||||
if self.hcard is None:
|
||||
raise CardConnectionException("Card not connected")
|
||||
hresult, _reader, _state, _protocol, atr = SCardStatus(self.hcard)
|
||||
if hresult != SCARD_S_SUCCESS:
|
||||
raise CardConnectionException(
|
||||
"Failed to get status: " + SCardGetErrorMessage(hresult),
|
||||
hresult=hresult,
|
||||
)
|
||||
return atr
|
||||
|
||||
def doTransmit(self, command, protocol=None):
|
||||
"""Transmit an apdu to the card and return response apdu.
|
||||
|
||||
@param command: command apdu to transmit (list of bytes)
|
||||
|
||||
@param protocol: the transmission protocol, from
|
||||
L{CardConnection.T0_protocol}, L{CardConnection.T1_protocol}, or
|
||||
L{CardConnection.RAW_protocol}
|
||||
|
||||
@return: a tuple (response, sw1, sw2) where
|
||||
- response are the response bytes excluding status words
|
||||
- sw1 is status word 1, e.g. 0x90
|
||||
- sw2 is status word 2, e.g. 0x1A
|
||||
"""
|
||||
if protocol is None:
|
||||
protocol = self.getProtocol()
|
||||
CardConnection.doTransmit(self, command, protocol)
|
||||
pcscprotocolheader = translateprotocolheader(protocol)
|
||||
if 0 == pcscprotocolheader:
|
||||
raise CardConnectionException(
|
||||
"Invalid protocol in transmit: must be "
|
||||
+ "CardConnection.T0_protocol, "
|
||||
+ "CardConnection.T1_protocol, or "
|
||||
+ "CardConnection.RAW_protocol"
|
||||
)
|
||||
if self.hcard is None:
|
||||
raise CardConnectionException("Card not connected")
|
||||
hresult, response = SCardTransmit(self.hcard, pcscprotocolheader, command)
|
||||
if hresult != SCARD_S_SUCCESS:
|
||||
raise CardConnectionException(
|
||||
"Failed to transmit with protocol "
|
||||
+ dictProtocolHeader[pcscprotocolheader]
|
||||
+ ". "
|
||||
+ SCardGetErrorMessage(hresult),
|
||||
hresult=hresult,
|
||||
)
|
||||
|
||||
if len(response) < 2:
|
||||
raise CardConnectionException(
|
||||
"Card returned no valid response", hresult=hresult
|
||||
)
|
||||
|
||||
sw1 = (response[-2] + 256) % 256
|
||||
sw2 = (response[-1] + 256) % 256
|
||||
|
||||
data = [(x + 256) % 256 for x in response[:-2]]
|
||||
return list(data), sw1, sw2
|
||||
|
||||
def doControl(self, controlCode, command=None):
|
||||
"""Transmit a control command to the reader and return response.
|
||||
|
||||
@param controlCode: control command
|
||||
|
||||
@param command: command data to transmit (list of bytes)
|
||||
|
||||
@return: response are the response bytes (if any)
|
||||
"""
|
||||
if command is None:
|
||||
command = []
|
||||
CardConnection.doControl(self, controlCode, command)
|
||||
hresult, response = SCardControl(self.hcard, controlCode, command)
|
||||
if hresult != SCARD_S_SUCCESS:
|
||||
raise SmartcardException(
|
||||
"Failed to control " + SCardGetErrorMessage(hresult), hresult=hresult
|
||||
)
|
||||
|
||||
data = [(x + 256) % 256 for x in response]
|
||||
return list(data)
|
||||
|
||||
def doGetAttrib(self, attribId):
|
||||
"""get an attribute
|
||||
|
||||
@param attribId: Identifier for the attribute to get
|
||||
|
||||
@return: response are the attribute byte array
|
||||
"""
|
||||
CardConnection.doGetAttrib(self, attribId)
|
||||
hresult, response = SCardGetAttrib(self.hcard, attribId)
|
||||
if hresult != SCARD_S_SUCCESS:
|
||||
raise SmartcardException(
|
||||
"Failed to getAttrib " + SCardGetErrorMessage(hresult), hresult=hresult
|
||||
)
|
||||
return response
|
||||
@@ -0,0 +1,490 @@
|
||||
"""PCSC Smartcard request.
|
||||
|
||||
__author__ = "https://www.gemalto.com/"
|
||||
|
||||
Copyright 2001-2012 gemalto
|
||||
Author: Jean-Daniel Aussel, mailto:jean-daniel.aussel@gemalto.com
|
||||
|
||||
This file is part of pyscard.
|
||||
|
||||
pyscard is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyscard is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with pyscard; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
"""
|
||||
|
||||
import threading
|
||||
from datetime import datetime
|
||||
|
||||
from smartcard import Card
|
||||
from smartcard.AbstractCardRequest import AbstractCardRequest
|
||||
from smartcard.Exceptions import (
|
||||
CardConnectionException,
|
||||
CardRequestException,
|
||||
CardRequestTimeoutException,
|
||||
ListReadersException,
|
||||
)
|
||||
from smartcard.pcsc.PCSCReader import PCSCReader
|
||||
from smartcard.scard import (
|
||||
INFINITE,
|
||||
SCARD_E_CANCELLED,
|
||||
SCARD_E_NO_READERS_AVAILABLE,
|
||||
SCARD_E_NO_SERVICE,
|
||||
SCARD_E_SERVICE_STOPPED,
|
||||
SCARD_E_SYSTEM_CANCELLED,
|
||||
SCARD_E_TIMEOUT,
|
||||
SCARD_E_UNKNOWN_READER,
|
||||
SCARD_S_SUCCESS,
|
||||
SCARD_SCOPE_USER,
|
||||
SCARD_STATE_CHANGED,
|
||||
SCARD_STATE_PRESENT,
|
||||
SCARD_STATE_UNAWARE,
|
||||
SCardCancel,
|
||||
SCardEstablishContext,
|
||||
SCardGetErrorMessage,
|
||||
SCardGetStatusChange,
|
||||
SCardListReaders,
|
||||
SCardReleaseContext,
|
||||
)
|
||||
|
||||
|
||||
class PCSCCardRequest(AbstractCardRequest):
|
||||
"""PCSC CardRequest class."""
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
# pylint: disable=too-many-positional-arguments
|
||||
def __init__(
|
||||
self,
|
||||
newcardonly=False,
|
||||
readers=None,
|
||||
cardType=None,
|
||||
cardServiceClass=None,
|
||||
timeout=1,
|
||||
):
|
||||
"""Construct new PCSCCardRequest.
|
||||
|
||||
@param newcardonly: if C{True}, request a new card. default is
|
||||
C{False}, i.e. accepts cards already inserted
|
||||
|
||||
@param readers: the list of readers to consider for requesting a
|
||||
card default is to consider all readers
|
||||
|
||||
@param cardType: the L{CardType} class to wait for; default is
|
||||
L{AnyCardType}, i.e. the request will returns with new or already
|
||||
inserted cards
|
||||
|
||||
@param cardServiceClass: the specific card service class to
|
||||
create and bind to the card default is to create and bind a
|
||||
L{PassThruCardService}
|
||||
|
||||
@param timeout: the time in seconds we are ready to wait for
|
||||
connecting to the requested card. default is to wait one second
|
||||
to wait forever, set timeout to C{None}
|
||||
"""
|
||||
AbstractCardRequest.__init__(
|
||||
self, newcardonly, readers, cardType, cardServiceClass, timeout
|
||||
)
|
||||
|
||||
# if timeout is None, translate to scard.INFINITE
|
||||
if self.timeout is None:
|
||||
self.timeout = INFINITE
|
||||
# otherwise, from seconds to milliseconds
|
||||
else:
|
||||
self.timeout = int(self.timeout * 1000)
|
||||
|
||||
hresult, self.hcontext = SCardEstablishContext(SCARD_SCOPE_USER)
|
||||
if hresult != SCARD_S_SUCCESS:
|
||||
self.hcontext = None
|
||||
raise CardConnectionException(hresult=hresult)
|
||||
self.evt = threading.Event()
|
||||
self.hresult = SCARD_S_SUCCESS
|
||||
self.readerstates = {}
|
||||
self.newstates = []
|
||||
self.timeout_init = self.timeout
|
||||
|
||||
def __del__(self):
|
||||
self.release()
|
||||
|
||||
def release(self):
|
||||
"""Release the PCSC context"""
|
||||
if self.hcontext is not None:
|
||||
hresult = SCardReleaseContext(self.hcontext)
|
||||
if hresult != SCARD_S_SUCCESS:
|
||||
raise CardConnectionException(hresult=hresult)
|
||||
self.hcontext = None
|
||||
|
||||
def getReaderNames(self):
|
||||
"""Returns the list of PCSC readers on which to wait for cards."""
|
||||
|
||||
# get inserted readers
|
||||
hresult, pcscreaders = SCardListReaders(self.hcontext, [])
|
||||
|
||||
# renew the context in case PC/SC was stopped
|
||||
# this happens on Windows when the last reader is disconnected
|
||||
if hresult in (SCARD_E_SERVICE_STOPPED, SCARD_E_NO_SERVICE):
|
||||
hresult = SCardReleaseContext(self.hcontext)
|
||||
if hresult != SCARD_S_SUCCESS:
|
||||
raise CardConnectionException(hresult=hresult)
|
||||
hresult, self.hcontext = SCardEstablishContext(SCARD_SCOPE_USER)
|
||||
if hresult != SCARD_S_SUCCESS:
|
||||
raise CardConnectionException(hresult=hresult)
|
||||
hresult, pcscreaders = SCardListReaders(self.hcontext, [])
|
||||
if SCARD_E_NO_READERS_AVAILABLE == hresult:
|
||||
return []
|
||||
if SCARD_S_SUCCESS != hresult:
|
||||
raise ListReadersException(hresult)
|
||||
|
||||
readers = []
|
||||
|
||||
# if no readers asked, use all inserted readers
|
||||
if self.readersAsked is None:
|
||||
readers = pcscreaders
|
||||
|
||||
# otherwise use only the asked readers that are inserted
|
||||
else:
|
||||
for reader in self.readersAsked:
|
||||
if not isinstance(reader, str):
|
||||
reader = str(reader)
|
||||
if reader in pcscreaders:
|
||||
readers = readers + [reader]
|
||||
|
||||
return readers
|
||||
|
||||
# thread waiting for a change
|
||||
# the main thread will handle a possible KeyboardInterrupt
|
||||
def __getStatusChange(self):
|
||||
self.hresult, self.newstates = SCardGetStatusChange(
|
||||
self.hcontext, self.timeout, list(self.readerstates.values())
|
||||
)
|
||||
self.evt.set()
|
||||
|
||||
def waitforcard(self):
|
||||
"""Wait for card insertion and returns a card service."""
|
||||
|
||||
# pylint: disable=too-many-statements
|
||||
# pylint: disable=too-many-locals
|
||||
# pylint: disable=too-many-branches
|
||||
|
||||
AbstractCardRequest.waitforcard(self)
|
||||
cardfound = False
|
||||
|
||||
# create a dictionary entry for new readers
|
||||
readerstates = {}
|
||||
readernames = self.getReaderNames()
|
||||
# add PnP special reader
|
||||
readernames.append("\\\\?PnP?\\Notification")
|
||||
|
||||
for reader in readernames:
|
||||
if reader not in readerstates:
|
||||
readerstates[reader] = (reader, SCARD_STATE_UNAWARE)
|
||||
|
||||
# call SCardGetStatusChange only if we have some readers
|
||||
if readerstates:
|
||||
hresult, newstates = SCardGetStatusChange(
|
||||
self.hcontext, 0, list(readerstates.values())
|
||||
)
|
||||
else:
|
||||
hresult = SCARD_S_SUCCESS
|
||||
newstates = []
|
||||
|
||||
# we can expect normally time-outs or reader
|
||||
# disappearing just before the call
|
||||
# otherwise, raise exception on error
|
||||
if hresult not in (SCARD_S_SUCCESS, SCARD_E_TIMEOUT, SCARD_E_UNKNOWN_READER):
|
||||
raise CardRequestException(
|
||||
"Failed to SCardGetStatusChange " + SCardGetErrorMessage(hresult),
|
||||
hresult=hresult,
|
||||
)
|
||||
|
||||
# update readerstate
|
||||
for state in newstates:
|
||||
readername, eventstate, atr = state
|
||||
readerstates[readername] = (readername, eventstate)
|
||||
|
||||
# if a new card is not requested, just return the first available
|
||||
if not self.newcardonly:
|
||||
for state in newstates:
|
||||
readername, eventstate, atr = state
|
||||
if eventstate & SCARD_STATE_PRESENT:
|
||||
reader = PCSCReader(readername)
|
||||
if self.cardType.matches(atr, reader):
|
||||
if self.cardServiceClass.supports("dummy"):
|
||||
cardfound = True
|
||||
return self.cardServiceClass(reader.createConnection())
|
||||
|
||||
startDate = datetime.now()
|
||||
self.timeout = self.timeout_init
|
||||
while not cardfound:
|
||||
|
||||
# create a dictionary entry for new readers
|
||||
readernames = self.getReaderNames()
|
||||
|
||||
if self.readersAsked is None:
|
||||
# add PnP special reader
|
||||
readernames.append("\\\\?PnP?\\Notification")
|
||||
|
||||
for reader in readernames:
|
||||
if reader not in readerstates:
|
||||
readerstates[reader] = (reader, SCARD_STATE_UNAWARE)
|
||||
|
||||
# remove dictionary entry for readers that disappeared
|
||||
for oldreader in list(readerstates.keys()):
|
||||
if oldreader not in readernames:
|
||||
del readerstates[oldreader]
|
||||
|
||||
# wait for card insertion
|
||||
self.readerstates = readerstates
|
||||
waitThread = threading.Thread(target=self.__getStatusChange)
|
||||
waitThread.start()
|
||||
|
||||
# the main thread handles a possible KeyboardInterrupt
|
||||
try:
|
||||
waitThread.join()
|
||||
except KeyboardInterrupt as exc:
|
||||
hresult = SCardCancel(self.hcontext)
|
||||
if hresult != SCARD_S_SUCCESS:
|
||||
raise CardRequestException(
|
||||
"Failed to SCardCancel " + SCardGetErrorMessage(hresult),
|
||||
hresult=hresult,
|
||||
) from exc
|
||||
|
||||
# wait for the thread to finish in case of KeyboardInterrupt
|
||||
self.evt.wait(timeout=None)
|
||||
|
||||
# get values set in the __getStatusChange thread
|
||||
hresult = self.hresult
|
||||
newstates = self.newstates
|
||||
|
||||
# compute remaining timeout
|
||||
if self.timeout != INFINITE:
|
||||
delta = datetime.now() - startDate
|
||||
self.timeout -= int(delta.total_seconds() * 1000)
|
||||
# timeout cant be < 0
|
||||
self.timeout = max(self.timeout, 0)
|
||||
|
||||
# time-out
|
||||
if hresult in (SCARD_E_TIMEOUT, SCARD_E_CANCELLED):
|
||||
raise CardRequestTimeoutException(hresult=hresult)
|
||||
|
||||
# reader vanished before or during the call
|
||||
if SCARD_E_UNKNOWN_READER == hresult:
|
||||
pass
|
||||
|
||||
# this happens on Windows when the last reader is disconnected
|
||||
elif hresult in (SCARD_E_SYSTEM_CANCELLED, SCARD_E_NO_SERVICE):
|
||||
pass
|
||||
|
||||
# some error happened
|
||||
elif SCARD_S_SUCCESS != hresult:
|
||||
raise CardRequestException(
|
||||
"Failed to get status change " + SCardGetErrorMessage(hresult),
|
||||
hresult=hresult,
|
||||
)
|
||||
|
||||
# something changed!
|
||||
else:
|
||||
|
||||
# check if we have to return a match, i.e.
|
||||
# if no new card in inserted and there is a card found
|
||||
# or if a new card is requested, and there is a change+present
|
||||
for state in newstates:
|
||||
readername, eventstate, atr = state
|
||||
_, oldstate = readerstates[readername]
|
||||
|
||||
# the status can change on a card already inserted, e.g.
|
||||
# unpowered, in use, ...
|
||||
# if a new card is requested, clear the state changed bit
|
||||
# if the card was already inserted and is still inserted
|
||||
if self.newcardonly:
|
||||
if oldstate & SCARD_STATE_PRESENT and eventstate & (
|
||||
SCARD_STATE_CHANGED | SCARD_STATE_PRESENT
|
||||
):
|
||||
eventstate = eventstate & (0xFFFFFFFF ^ SCARD_STATE_CHANGED)
|
||||
|
||||
if (
|
||||
self.newcardonly
|
||||
and eventstate & SCARD_STATE_PRESENT
|
||||
and eventstate & SCARD_STATE_CHANGED
|
||||
) or (not self.newcardonly and eventstate & SCARD_STATE_PRESENT):
|
||||
reader = PCSCReader(readername)
|
||||
if self.cardType.matches(atr, reader):
|
||||
if self.cardServiceClass.supports("dummy"):
|
||||
cardfound = True
|
||||
return self.cardServiceClass(reader.createConnection())
|
||||
|
||||
# update state dictionary
|
||||
readerstates[readername] = (readername, eventstate)
|
||||
|
||||
# should not happen
|
||||
return None
|
||||
|
||||
def waitforcardevent(self):
|
||||
"""Wait for card insertion or removal."""
|
||||
|
||||
# pylint: disable=too-many-statements
|
||||
# pylint: disable=too-many-locals
|
||||
# pylint: disable=too-many-branches
|
||||
|
||||
AbstractCardRequest.waitforcardevent(self)
|
||||
presentcards = []
|
||||
|
||||
startDate = datetime.now()
|
||||
eventfound = False
|
||||
self.timeout = self.timeout_init
|
||||
while not eventfound:
|
||||
|
||||
# get states from previous run
|
||||
readerstates = self.readerstates
|
||||
|
||||
# reinitialize at each iteration just in case a new reader appeared
|
||||
_readernames = self.getReaderNames()
|
||||
readernames = _readernames
|
||||
|
||||
if self.readersAsked is None:
|
||||
# add PnP special reader
|
||||
readernames.append("\\\\?PnP?\\Notification")
|
||||
|
||||
# first call?
|
||||
if len(readerstates) == 0:
|
||||
# init
|
||||
for reader in readernames:
|
||||
# create a dictionary entry for new readers
|
||||
readerstates[reader] = (reader, SCARD_STATE_UNAWARE)
|
||||
|
||||
# check if a new reader with a card has just been connected
|
||||
for reader in _readernames:
|
||||
# is the reader a new one?
|
||||
if reader not in readerstates:
|
||||
# create a dictionary entry for new reader
|
||||
readerstates[reader] = (reader, SCARD_STATE_UNAWARE)
|
||||
|
||||
hresult, newstates = SCardGetStatusChange(
|
||||
self.hcontext, 0, list(readerstates.values())
|
||||
)
|
||||
|
||||
# added reader is the last one (index is -1)
|
||||
_, state, _ = newstates[-1]
|
||||
if state & SCARD_STATE_PRESENT:
|
||||
eventfound = True
|
||||
|
||||
# check if a reader has been removed
|
||||
to_remove = []
|
||||
for reader in readerstates:
|
||||
if reader not in readernames:
|
||||
_, state = readerstates[reader]
|
||||
# was the card present?
|
||||
if state & SCARD_STATE_PRESENT:
|
||||
eventfound = True
|
||||
|
||||
to_remove.append(reader)
|
||||
|
||||
if to_remove:
|
||||
for reader in to_remove:
|
||||
# remove reader
|
||||
del readerstates[reader]
|
||||
|
||||
# get newstates with new reader list
|
||||
hresult, newstates = SCardGetStatusChange(
|
||||
self.hcontext, 0, list(readerstates.values())
|
||||
)
|
||||
|
||||
if eventfound:
|
||||
break
|
||||
|
||||
# wait for card insertion
|
||||
self.readerstates = readerstates
|
||||
waitThread = threading.Thread(target=self.__getStatusChange)
|
||||
waitThread.start()
|
||||
|
||||
# the main thread handles a possible KeyboardInterrupt
|
||||
try:
|
||||
waitThread.join()
|
||||
except KeyboardInterrupt as exc:
|
||||
hresult = SCardCancel(self.hcontext)
|
||||
if hresult != SCARD_S_SUCCESS:
|
||||
raise CardRequestException(
|
||||
"Failed to SCardCancel " + SCardGetErrorMessage(hresult),
|
||||
hresult=hresult,
|
||||
) from exc
|
||||
|
||||
# wait for the thread to finish in case of KeyboardInterrupt
|
||||
self.evt.wait(timeout=None)
|
||||
|
||||
# get values set in the __getStatusChange thread
|
||||
hresult = self.hresult
|
||||
newstates = self.newstates
|
||||
|
||||
# compute remaining timeout
|
||||
if self.timeout != INFINITE:
|
||||
delta = datetime.now() - startDate
|
||||
self.timeout -= int(delta.total_seconds() * 1000)
|
||||
# timeout cant be < 0
|
||||
self.timeout = max(self.timeout, 0)
|
||||
|
||||
# time-out
|
||||
if hresult in (SCARD_E_TIMEOUT, SCARD_E_CANCELLED):
|
||||
raise CardRequestTimeoutException(hresult=hresult)
|
||||
|
||||
# the reader was unplugged during the loop
|
||||
if SCARD_E_UNKNOWN_READER == hresult:
|
||||
pass
|
||||
|
||||
# this happens on Windows when the last reader is disconnected
|
||||
elif hresult in (SCARD_E_SYSTEM_CANCELLED, SCARD_E_NO_SERVICE):
|
||||
pass
|
||||
|
||||
# some error happened
|
||||
elif SCARD_S_SUCCESS != hresult:
|
||||
raise CardRequestException(
|
||||
"Failed to get status change " + SCardGetErrorMessage(hresult),
|
||||
hresult=hresult,
|
||||
)
|
||||
|
||||
# something changed!
|
||||
else:
|
||||
for state in newstates:
|
||||
readername, eventstate, atr = state
|
||||
|
||||
# ignore PnP reader
|
||||
if readername == "\\\\?PnP?\\Notification":
|
||||
continue
|
||||
|
||||
if eventstate & SCARD_STATE_CHANGED:
|
||||
eventfound = True
|
||||
|
||||
# update readerstates for next SCardGetStatusChange() call
|
||||
self.readerstates = {}
|
||||
for reader, state, atr in newstates:
|
||||
self.readerstates[reader] = (reader, state)
|
||||
|
||||
# return all the cards present
|
||||
for state in newstates:
|
||||
readername, eventstate, atr = state
|
||||
if readername == "\\\\?PnP?\\Notification":
|
||||
continue
|
||||
if eventstate & SCARD_STATE_PRESENT:
|
||||
presentcards.append(Card.Card(readername, atr))
|
||||
|
||||
return presentcards
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Small sample illustrating the use of PCSCCardRequest.py.
|
||||
|
||||
from smartcard.util import toHexString
|
||||
|
||||
print("Insert a new card within 10 seconds")
|
||||
cr = PCSCCardRequest(timeout=10, newcardonly=True)
|
||||
cs = cr.waitforcard()
|
||||
cs.connection.connect()
|
||||
print(cs.connection.getReader() + " " + toHexString(cs.connection.getATR()))
|
||||
cs.connection.disconnect()
|
||||
@@ -0,0 +1,125 @@
|
||||
"""Smartcard module exceptions.
|
||||
|
||||
This module defines the exceptions raised by the smartcard.pcsc modules.
|
||||
|
||||
__author__ = "https://www.gemalto.com/"
|
||||
|
||||
Copyright 2001-2012 gemalto
|
||||
Author: Jean-Daniel Aussel, mailto:jean-daniel.aussel@gemalto.com
|
||||
|
||||
This file is part of pyscard.
|
||||
|
||||
pyscard is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyscard is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with pyscard; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
"""
|
||||
|
||||
# gemalto scard library
|
||||
import smartcard.scard
|
||||
|
||||
|
||||
class BaseSCardException(Exception):
|
||||
"""Base class for scard (aka PCSC) exceptions.
|
||||
|
||||
scard exceptions are raised by the scard module, i.e.
|
||||
low-level PCSC access to readers and cards.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, *args, hresult=-1, message=""):
|
||||
"""Constructor that stores the pcsc error status."""
|
||||
if not message:
|
||||
message = "scard exception"
|
||||
if -1 == hresult and len(args) > 0:
|
||||
hresult = args[0]
|
||||
args = args[1:]
|
||||
super().__init__(message, *args)
|
||||
self.message = message
|
||||
self.hresult = hresult
|
||||
|
||||
def __str__(self):
|
||||
"""Returns a string representation of the exception."""
|
||||
text = super().__str__()
|
||||
if self.hresult != -1:
|
||||
hresult = self.hresult
|
||||
if hresult < 0:
|
||||
# convert 0x-7FEFFFE3 into 0x8010001D
|
||||
hresult += 0x100000000
|
||||
text += f": {smartcard.scard.SCardGetErrorMessage(self.hresult)} (0x{hresult:08X})"
|
||||
return text
|
||||
|
||||
|
||||
class AddReaderToGroupException(BaseSCardException):
|
||||
"""Raised when scard fails to add a new reader to a PCSC reader group."""
|
||||
|
||||
def __init__(self, hresult, readername="", groupname=""):
|
||||
super().__init__(
|
||||
message="Failed to add reader: " + readername + " to group: " + groupname,
|
||||
hresult=hresult,
|
||||
)
|
||||
self.readername = readername
|
||||
self.groupname = groupname
|
||||
|
||||
|
||||
class EstablishContextException(BaseSCardException):
|
||||
"""Raised when scard failed to establish context with PCSC."""
|
||||
|
||||
def __init__(self, hresult):
|
||||
super().__init__(message="Failed to establish context", hresult=hresult)
|
||||
|
||||
|
||||
class ListReadersException(BaseSCardException):
|
||||
"""Raised when scard failed to list readers."""
|
||||
|
||||
def __init__(self, hresult):
|
||||
super().__init__(message="Failed to list readers", hresult=hresult)
|
||||
|
||||
|
||||
class IntroduceReaderException(BaseSCardException):
|
||||
"""Raised when scard fails to introduce a new reader to PCSC."""
|
||||
|
||||
def __init__(self, hresult, readername=""):
|
||||
super().__init__(
|
||||
message="Failed to introduce a new reader: " + readername, hresult=hresult
|
||||
)
|
||||
self.readername = readername
|
||||
|
||||
|
||||
class ReleaseContextException(BaseSCardException):
|
||||
"""Raised when scard failed to release PCSC context."""
|
||||
|
||||
def __init__(self, hresult):
|
||||
super().__init__(message="Failed to release context", hresult=hresult)
|
||||
|
||||
|
||||
class RemoveReaderFromGroupException(BaseSCardException):
|
||||
"""Raised when scard fails to remove a reader from a PCSC reader group."""
|
||||
|
||||
def __init__(self, hresult, readername="", groupname=""):
|
||||
BaseSCardException.__init__(self, hresult)
|
||||
self.readername = readername
|
||||
self.groupname = groupname
|
||||
super().__init__(
|
||||
message="Failed to remove reader: "
|
||||
+ readername
|
||||
+ " from group: "
|
||||
+ groupname,
|
||||
hresult=hresult,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
raise EstablishContextException(smartcard.scard.SCARD_E_NO_MEMORY)
|
||||
except BaseSCardException as exc:
|
||||
print(exc)
|
||||
@@ -0,0 +1,274 @@
|
||||
"""PCSCPart10: PC/SC Part 10 (pinpad)
|
||||
|
||||
__author__ = "Ludovic Rousseau"
|
||||
|
||||
Copyright 2009-2010 Ludovic Rosseau
|
||||
Author: Ludovic Rousseau, mailto:ludovic.rousseau@free.fr
|
||||
|
||||
This file is part of pyscard.
|
||||
|
||||
pyscard is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyscard is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with pyscard; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
"""
|
||||
|
||||
from smartcard.scard import SCARD_CTL_CODE, SCARD_SHARE_DIRECT
|
||||
|
||||
# constants defined in PC/SC v2 Part 10
|
||||
CM_IOCTL_GET_FEATURE_REQUEST = SCARD_CTL_CODE(3400)
|
||||
|
||||
FEATURE_VERIFY_PIN_START = 0x01
|
||||
FEATURE_VERIFY_PIN_FINISH = 0x02
|
||||
FEATURE_MODIFY_PIN_START = 0x03
|
||||
FEATURE_MODIFY_PIN_FINISH = 0x04
|
||||
FEATURE_GET_KEY_PRESSED = 0x05
|
||||
FEATURE_VERIFY_PIN_DIRECT = 0x06
|
||||
FEATURE_MODIFY_PIN_DIRECT = 0x07
|
||||
FEATURE_MCT_READER_DIRECT = 0x08
|
||||
FEATURE_MCT_UNIVERSAL = 0x09
|
||||
FEATURE_IFD_PIN_PROPERTIES = 0x0A
|
||||
FEATURE_ABORT = 0x0B
|
||||
FEATURE_SET_SPE_MESSAGE = 0x0C
|
||||
FEATURE_VERIFY_PIN_DIRECT_APP_ID = 0x0D
|
||||
FEATURE_MODIFY_PIN_DIRECT_APP_ID = 0x0E
|
||||
FEATURE_WRITE_DISPLAY = 0x0F
|
||||
FEATURE_GET_KEY = 0x10
|
||||
FEATURE_IFD_DISPLAY_PROPERTIES = 0x11
|
||||
FEATURE_GET_TLV_PROPERTIES = 0x12
|
||||
FEATURE_CCID_ESC_COMMAND = 0x13
|
||||
|
||||
Features = {
|
||||
"FEATURE_VERIFY_PIN_START": FEATURE_VERIFY_PIN_START,
|
||||
"FEATURE_VERIFY_PIN_FINISH": FEATURE_VERIFY_PIN_FINISH,
|
||||
"FEATURE_MODIFY_PIN_START": FEATURE_MODIFY_PIN_START,
|
||||
"FEATURE_MODIFY_PIN_FINISH": FEATURE_MODIFY_PIN_FINISH,
|
||||
"FEATURE_GET_KEY_PRESSED": FEATURE_GET_KEY_PRESSED,
|
||||
"FEATURE_VERIFY_PIN_DIRECT": FEATURE_VERIFY_PIN_DIRECT,
|
||||
"FEATURE_MODIFY_PIN_DIRECT": FEATURE_MODIFY_PIN_DIRECT,
|
||||
"FEATURE_MCT_READER_DIRECT": FEATURE_MCT_READER_DIRECT,
|
||||
"FEATURE_MCT_UNIVERSAL": FEATURE_MCT_UNIVERSAL,
|
||||
"FEATURE_IFD_PIN_PROPERTIES": FEATURE_IFD_PIN_PROPERTIES,
|
||||
"FEATURE_ABORT": FEATURE_ABORT,
|
||||
"FEATURE_SET_SPE_MESSAGE": FEATURE_SET_SPE_MESSAGE,
|
||||
"FEATURE_VERIFY_PIN_DIRECT_APP_ID": FEATURE_VERIFY_PIN_DIRECT_APP_ID,
|
||||
"FEATURE_MODIFY_PIN_DIRECT_APP_ID": FEATURE_MODIFY_PIN_DIRECT_APP_ID,
|
||||
"FEATURE_WRITE_DISPLAY": FEATURE_WRITE_DISPLAY,
|
||||
"FEATURE_GET_KEY": FEATURE_GET_KEY,
|
||||
"FEATURE_IFD_DISPLAY_PROPERTIES": FEATURE_IFD_DISPLAY_PROPERTIES,
|
||||
"FEATURE_GET_TLV_PROPERTIES": FEATURE_GET_TLV_PROPERTIES,
|
||||
"FEATURE_CCID_ESC_COMMAND": FEATURE_CCID_ESC_COMMAND,
|
||||
}
|
||||
|
||||
# properties returned by FEATURE_GET_TLV_PROPERTIES
|
||||
PCSCv2_PART10_PROPERTY_wLcdLayout = 1
|
||||
PCSCv2_PART10_PROPERTY_bEntryValidationCondition = 2
|
||||
PCSCv2_PART10_PROPERTY_bTimeOut2 = 3
|
||||
PCSCv2_PART10_PROPERTY_wLcdMaxCharacters = 4
|
||||
PCSCv2_PART10_PROPERTY_wLcdMaxLines = 5
|
||||
PCSCv2_PART10_PROPERTY_bMinPINSize = 6
|
||||
PCSCv2_PART10_PROPERTY_bMaxPINSize = 7
|
||||
PCSCv2_PART10_PROPERTY_sFirmwareID = 8
|
||||
PCSCv2_PART10_PROPERTY_bPPDUSupport = 9
|
||||
PCSCv2_PART10_PROPERTY_dwMaxAPDUDataSize = 10
|
||||
PCSCv2_PART10_PROPERTY_wIdVendor = 11
|
||||
PCSCv2_PART10_PROPERTY_wIdProduct = 12
|
||||
|
||||
Properties = {
|
||||
"PCSCv2_PART10_PROPERTY_wLcdLayout": PCSCv2_PART10_PROPERTY_wLcdLayout,
|
||||
"PCSCv2_PART10_PROPERTY_bEntryValidationCondition": PCSCv2_PART10_PROPERTY_bEntryValidationCondition, # pylint: disable=line-too-long
|
||||
"PCSCv2_PART10_PROPERTY_bTimeOut2": PCSCv2_PART10_PROPERTY_bTimeOut2,
|
||||
"PCSCv2_PART10_PROPERTY_wLcdMaxCharacters": PCSCv2_PART10_PROPERTY_wLcdMaxCharacters,
|
||||
"PCSCv2_PART10_PROPERTY_wLcdMaxLines": PCSCv2_PART10_PROPERTY_wLcdMaxLines,
|
||||
"PCSCv2_PART10_PROPERTY_bMinPINSize": PCSCv2_PART10_PROPERTY_bMinPINSize,
|
||||
"PCSCv2_PART10_PROPERTY_bMaxPINSize": PCSCv2_PART10_PROPERTY_bMaxPINSize,
|
||||
"PCSCv2_PART10_PROPERTY_sFirmwareID": PCSCv2_PART10_PROPERTY_sFirmwareID,
|
||||
"PCSCv2_PART10_PROPERTY_bPPDUSupport": PCSCv2_PART10_PROPERTY_bPPDUSupport,
|
||||
"PCSCv2_PART10_PROPERTY_dwMaxAPDUDataSize": PCSCv2_PART10_PROPERTY_dwMaxAPDUDataSize,
|
||||
"PCSCv2_PART10_PROPERTY_wIdVendor": PCSCv2_PART10_PROPERTY_wIdVendor,
|
||||
"PCSCv2_PART10_PROPERTY_wIdProduct": PCSCv2_PART10_PROPERTY_wIdProduct,
|
||||
}
|
||||
|
||||
# we already have: Features['FEATURE_x'] = FEATURE_x
|
||||
# we will now also have: Features[FEATURE_x] = 'FEATURE_x'
|
||||
for k in list(Features.keys()):
|
||||
Features[Features[k]] = k
|
||||
|
||||
for k in list(Properties.keys()):
|
||||
Properties[Properties[k]] = k
|
||||
|
||||
|
||||
def parseFeatureRequest(response):
|
||||
"""Get the list of Part10 features supported by the reader.
|
||||
|
||||
@param response: result of L{CM_IOCTL_GET_FEATURE_REQUEST} command
|
||||
|
||||
@rtype: list
|
||||
@return: a list of list C{[[tag1, value1], [tag2, value2]]}
|
||||
"""
|
||||
features = []
|
||||
while len(response) > 0:
|
||||
tag = response[0]
|
||||
control = (
|
||||
((((response[2] << 8) + response[3]) << 8) + response[4]) << 8
|
||||
) + response[5]
|
||||
try:
|
||||
features.append([Features[tag], control])
|
||||
except KeyError:
|
||||
pass
|
||||
del response[:6]
|
||||
return features
|
||||
|
||||
|
||||
def getFeatureRequest(cardConnection):
|
||||
"""Get the list of Part10 features supported by the reader.
|
||||
|
||||
@param cardConnection: L{CardConnection} object
|
||||
|
||||
@rtype: list
|
||||
@return: a list of list C{[[tag1, value1], [tag2, value2]]}
|
||||
"""
|
||||
response = cardConnection.control(CM_IOCTL_GET_FEATURE_REQUEST, [])
|
||||
return parseFeatureRequest(response)
|
||||
|
||||
|
||||
def hasFeature(featureList, feature):
|
||||
"""return the controlCode for a feature or None
|
||||
|
||||
@param feature: feature to look for
|
||||
@param featureList: feature list as returned by L{getFeatureRequest()}
|
||||
|
||||
@return: feature value or None
|
||||
"""
|
||||
for f in featureList:
|
||||
if feature in (f[0], Features[f[0]]):
|
||||
return f[1]
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def getPinProperties(cardConnection, featureList=None, controlCode=None):
|
||||
"""return the C{PIN_PROPERTIES} structure
|
||||
|
||||
@param cardConnection: L{CardConnection} object
|
||||
@param featureList: feature list as returned by L{getFeatureRequest()}
|
||||
@param controlCode: control code for L{FEATURE_IFD_PIN_PROPERTIES}
|
||||
|
||||
@rtype: dict
|
||||
@return: a dict"""
|
||||
if controlCode is None:
|
||||
if featureList is None:
|
||||
featureList = getFeatureRequest(cardConnection)
|
||||
controlCode = hasFeature(featureList, FEATURE_IFD_PIN_PROPERTIES)
|
||||
|
||||
if controlCode is None:
|
||||
return {"raw": []}
|
||||
|
||||
response = cardConnection.control(controlCode, [])
|
||||
d = {
|
||||
"raw": response,
|
||||
"LcdLayoutX": response[0],
|
||||
"LcdLayoutY": response[1],
|
||||
"EntryValidationCondition": response[2],
|
||||
"TimeOut2": response[3],
|
||||
}
|
||||
|
||||
return d
|
||||
|
||||
|
||||
def getTlvProperties(cardConnection, featureList=None, controlCode=None):
|
||||
"""return the C{GET_TLV_PROPERTIES} structure
|
||||
|
||||
@param cardConnection: L{CardConnection} object
|
||||
@param featureList: feature list as returned by L{getFeatureRequest()}
|
||||
@param controlCode: control code for L{FEATURE_GET_TLV_PROPERTIES}
|
||||
|
||||
@rtype: dict
|
||||
@return: a dict"""
|
||||
if controlCode is None:
|
||||
if featureList is None:
|
||||
featureList = getFeatureRequest(cardConnection)
|
||||
controlCode = hasFeature(featureList, FEATURE_GET_TLV_PROPERTIES)
|
||||
|
||||
if controlCode is None:
|
||||
return {"raw": []}
|
||||
|
||||
response = cardConnection.control(controlCode, [])
|
||||
return parseTlvProperties(response)
|
||||
|
||||
|
||||
def parseTlvProperties(response):
|
||||
"""return the GET_TLV_PROPERTIES structure
|
||||
|
||||
@param response: result of L{FEATURE_GET_TLV_PROPERTIES}
|
||||
|
||||
@rtype: dict
|
||||
@return: a dict"""
|
||||
d = {
|
||||
"raw": response,
|
||||
}
|
||||
|
||||
# create a new list to consume it
|
||||
tmp = list(response)
|
||||
while tmp:
|
||||
tag = tmp[0]
|
||||
length = tmp[1]
|
||||
data = tmp[2 : 2 + length]
|
||||
|
||||
if PCSCv2_PART10_PROPERTY_sFirmwareID == tag:
|
||||
# convert to a string
|
||||
data = "".join([chr(c) for c in data])
|
||||
# we now suppose the value is an integer
|
||||
elif 1 == length:
|
||||
# byte
|
||||
data = data[0]
|
||||
elif 2 == length:
|
||||
# 16 bits value
|
||||
data = data[1] * 256 + data[0]
|
||||
elif 4 == length:
|
||||
# 32 bits value
|
||||
data = ((data[3] * 256 + data[2]) * 256 + data[1]) * 256 + data[0]
|
||||
|
||||
# store the value in the dictionary
|
||||
try:
|
||||
d[Properties[tag]] = data
|
||||
except KeyError:
|
||||
d["UNKNOWN"] = data
|
||||
|
||||
del tmp[0 : 2 + length]
|
||||
|
||||
return d
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Small sample illustrating the use of PCSCPart10.
|
||||
from smartcard.pcsc.PCSCReader import PCSCReader
|
||||
|
||||
cc = PCSCReader.readers()[0].createConnection()
|
||||
cc.connect(mode=SCARD_SHARE_DIRECT)
|
||||
|
||||
# print(cc.control(CM_IOCTL_GET_FEATURE_REQUEST))
|
||||
_features = getFeatureRequest(cc)
|
||||
print(_features)
|
||||
|
||||
print(hasFeature(_features, FEATURE_VERIFY_PIN_START))
|
||||
print(hasFeature(_features, FEATURE_VERIFY_PIN_DIRECT))
|
||||
|
||||
properties = getPinProperties(cc)
|
||||
print("\nPinProperties:")
|
||||
for k, v in list(properties.items()):
|
||||
print(f" {k}: {v}")
|
||||
|
||||
print("\nTlvProperties:")
|
||||
properties = getTlvProperties(cc)
|
||||
for k, v in list(properties.items()):
|
||||
print(f" {k}: {v}")
|
||||
@@ -0,0 +1,172 @@
|
||||
"""PCSCReader: concrete reader class for PCSC Readers
|
||||
|
||||
__author__ = "gemalto https://www.gemalto.com/"
|
||||
|
||||
Copyright 2001-2012 gemalto
|
||||
Author: Jean-Daniel Aussel, mailto:jean-daniel.aussel@gemalto.com
|
||||
|
||||
This file is part of pyscard.
|
||||
|
||||
pyscard is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyscard is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with pyscard; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
"""
|
||||
|
||||
from smartcard.CardConnectionDecorator import CardConnectionDecorator
|
||||
from smartcard.Exceptions import (
|
||||
CardServiceNotFoundException,
|
||||
CardServiceStoppedException,
|
||||
NoCardException,
|
||||
)
|
||||
from smartcard.pcsc.PCSCCardConnection import PCSCCardConnection
|
||||
from smartcard.pcsc.PCSCExceptions import (
|
||||
AddReaderToGroupException,
|
||||
EstablishContextException,
|
||||
IntroduceReaderException,
|
||||
ListReadersException,
|
||||
ReleaseContextException,
|
||||
RemoveReaderFromGroupException,
|
||||
)
|
||||
from smartcard.reader.Reader import Reader
|
||||
from smartcard.scard import (
|
||||
SCARD_E_DUPLICATE_READER,
|
||||
SCARD_E_NO_READERS_AVAILABLE,
|
||||
SCARD_E_NO_SERVICE,
|
||||
SCARD_E_SERVICE_STOPPED,
|
||||
SCARD_S_SUCCESS,
|
||||
SCARD_SCOPE_USER,
|
||||
SCardAddReaderToGroup,
|
||||
SCardEstablishContext,
|
||||
SCardIntroduceReader,
|
||||
SCardListReaders,
|
||||
SCardReleaseContext,
|
||||
SCardRemoveReaderFromGroup,
|
||||
)
|
||||
|
||||
|
||||
def __PCSCreaders__(hcontext, groups=None):
|
||||
"""Returns the list of PCSC smartcard readers in PCSC group.
|
||||
|
||||
If group is not specified, returns the list of all PCSC smartcard readers.
|
||||
"""
|
||||
|
||||
if groups is None:
|
||||
groups = []
|
||||
elif isinstance(groups, str):
|
||||
groups = [groups]
|
||||
hresult, readers = SCardListReaders(hcontext, groups)
|
||||
if hresult != SCARD_S_SUCCESS:
|
||||
if hresult == SCARD_E_NO_READERS_AVAILABLE:
|
||||
readers = []
|
||||
elif hresult == SCARD_E_SERVICE_STOPPED:
|
||||
raise CardServiceStoppedException(hresult=hresult)
|
||||
elif hresult == SCARD_E_NO_SERVICE:
|
||||
raise CardServiceNotFoundException(hresult=hresult)
|
||||
else:
|
||||
raise ListReadersException(hresult)
|
||||
|
||||
return readers
|
||||
|
||||
|
||||
class PCSCReader(Reader):
|
||||
"""PCSC reader class."""
|
||||
|
||||
def __init__(self, readername):
|
||||
"""Constructs a new PCSC reader."""
|
||||
Reader.__init__(self, readername)
|
||||
|
||||
def addtoreadergroup(self, groupname):
|
||||
"""Add reader to a reader group."""
|
||||
|
||||
hresult, hcontext = SCardEstablishContext(SCARD_SCOPE_USER)
|
||||
if SCARD_S_SUCCESS != hresult:
|
||||
raise EstablishContextException(hresult)
|
||||
try:
|
||||
hresult = SCardIntroduceReader(hcontext, self.name, self.name)
|
||||
if hresult not in (SCARD_S_SUCCESS, SCARD_E_DUPLICATE_READER):
|
||||
raise IntroduceReaderException(hresult, self.name)
|
||||
hresult = SCardAddReaderToGroup(hcontext, self.name, groupname)
|
||||
if SCARD_S_SUCCESS != hresult:
|
||||
raise AddReaderToGroupException(hresult, self.name, groupname)
|
||||
finally:
|
||||
hresult = SCardReleaseContext(hcontext)
|
||||
if SCARD_S_SUCCESS != hresult:
|
||||
raise ReleaseContextException(hresult)
|
||||
|
||||
def removefromreadergroup(self, groupname):
|
||||
"""Remove a reader from a reader group"""
|
||||
|
||||
hresult, hcontext = SCardEstablishContext(SCARD_SCOPE_USER)
|
||||
if SCARD_S_SUCCESS != hresult:
|
||||
raise EstablishContextException(hresult)
|
||||
try:
|
||||
hresult = SCardRemoveReaderFromGroup(hcontext, self.name, groupname)
|
||||
if SCARD_S_SUCCESS != hresult:
|
||||
raise RemoveReaderFromGroupException(hresult, self.name, groupname)
|
||||
finally:
|
||||
hresult = SCardReleaseContext(hcontext)
|
||||
if SCARD_S_SUCCESS != hresult:
|
||||
raise ReleaseContextException(hresult)
|
||||
|
||||
def createConnection(self):
|
||||
"""Return a card connection thru PCSC reader."""
|
||||
return CardConnectionDecorator(PCSCCardConnection(self.name))
|
||||
|
||||
class Factory:
|
||||
"""Factory to create PCSCReader objects"""
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
|
||||
@staticmethod
|
||||
def create(readername):
|
||||
"""Return a PCSCReader object"""
|
||||
return PCSCReader(readername)
|
||||
|
||||
@staticmethod
|
||||
def readers(groups=None):
|
||||
"""Return the list of readers"""
|
||||
if groups is None:
|
||||
groups = []
|
||||
creaders = []
|
||||
hresult, hcontext = SCardEstablishContext(SCARD_SCOPE_USER)
|
||||
if SCARD_S_SUCCESS != hresult:
|
||||
raise EstablishContextException(hresult)
|
||||
try:
|
||||
pcsc_readers = __PCSCreaders__(hcontext, groups)
|
||||
finally:
|
||||
hresult = SCardReleaseContext(hcontext)
|
||||
if SCARD_S_SUCCESS != hresult:
|
||||
raise ReleaseContextException(hresult)
|
||||
|
||||
for reader in pcsc_readers:
|
||||
creaders.append(PCSCReader.Factory.create(reader))
|
||||
return creaders
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from smartcard.util import toHexString
|
||||
|
||||
SELECT = [0xA0, 0xA4, 0x00, 0x00, 0x02]
|
||||
DF_TELECOM = [0x7F, 0x10]
|
||||
|
||||
_creaders = PCSCReader.readers()
|
||||
for _reader in _creaders:
|
||||
try:
|
||||
print(_reader.name)
|
||||
connection = _reader.createConnection()
|
||||
connection.connect()
|
||||
print(toHexString(connection.getATR()))
|
||||
data, sw1, sw2 = connection.transmit(SELECT + DF_TELECOM)
|
||||
print(f"{sw1:02X} {sw2:02X}")
|
||||
except NoCardException:
|
||||
print("no card in reader")
|
||||
@@ -0,0 +1,128 @@
|
||||
"""PCSCReaderGroups organizes smartcard readers as groups.
|
||||
|
||||
__author__ = "gemalto https://www.gemalto.com/"
|
||||
|
||||
Copyright 2001-2012 gemalto
|
||||
Author: Jean-Daniel Aussel, mailto:jean-daniel.aussel@gemalto.com
|
||||
|
||||
This file is part of pyscard.
|
||||
|
||||
pyscard is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyscard is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with pyscard; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
"""
|
||||
|
||||
from smartcard.pcsc.PCSCExceptions import (
|
||||
EstablishContextException,
|
||||
ListReadersException,
|
||||
ReleaseContextException,
|
||||
)
|
||||
from smartcard.reader.ReaderGroups import innerreadergroups, readergroups
|
||||
from smartcard.scard import (
|
||||
SCARD_S_SUCCESS,
|
||||
SCARD_SCOPE_USER,
|
||||
SCardEstablishContext,
|
||||
SCardForgetReaderGroup,
|
||||
SCardGetErrorMessage,
|
||||
SCardIntroduceReaderGroup,
|
||||
SCardListReaderGroups,
|
||||
SCardReleaseContext,
|
||||
error,
|
||||
)
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
|
||||
|
||||
class pcscinnerreadergroups(innerreadergroups):
|
||||
"""Smartcard PCSC readers groups inner class.
|
||||
|
||||
The PCSCReaderGroups singleton manages the creation of the unique
|
||||
instance of this class.
|
||||
"""
|
||||
|
||||
def __init__(self, initlist=None):
|
||||
"""Constructor."""
|
||||
innerreadergroups.__init__(self, initlist)
|
||||
self.unremovablegroups = ["SCard$DefaultReaders"]
|
||||
|
||||
def getreadergroups(self):
|
||||
"""Returns the list of smartcard reader groups."""
|
||||
innerreadergroups.getreadergroups(self)
|
||||
|
||||
hresult, hcontext = SCardEstablishContext(SCARD_SCOPE_USER)
|
||||
if hresult != SCARD_S_SUCCESS:
|
||||
raise EstablishContextException(hresult)
|
||||
hresult, readers = SCardListReaderGroups(hcontext)
|
||||
if hresult != SCARD_S_SUCCESS:
|
||||
raise ListReadersException(hresult)
|
||||
hresult = SCardReleaseContext(hcontext)
|
||||
if hresult != SCARD_S_SUCCESS:
|
||||
raise ReleaseContextException(hresult)
|
||||
return readers
|
||||
|
||||
def addreadergroup(self, newgroup):
|
||||
"""Add a reader group"""
|
||||
|
||||
hresult, hcontext = SCardEstablishContext(SCARD_SCOPE_USER)
|
||||
if SCARD_S_SUCCESS != hresult:
|
||||
raise error("Failed to establish context: " + SCardGetErrorMessage(hresult))
|
||||
try:
|
||||
hresult = SCardIntroduceReaderGroup(hcontext, newgroup)
|
||||
if SCARD_S_SUCCESS != hresult:
|
||||
raise error(
|
||||
"Unable to introduce reader group: " + SCardGetErrorMessage(hresult)
|
||||
)
|
||||
|
||||
innerreadergroups.addreadergroup(self, newgroup)
|
||||
|
||||
finally:
|
||||
hresult = SCardReleaseContext(hcontext)
|
||||
if SCARD_S_SUCCESS != hresult:
|
||||
raise error(
|
||||
"Failed to release context: " + SCardGetErrorMessage(hresult)
|
||||
)
|
||||
|
||||
def removereadergroup(self, group):
|
||||
"""Remove a reader group"""
|
||||
|
||||
hresult, hcontext = SCardEstablishContext(SCARD_SCOPE_USER)
|
||||
if SCARD_S_SUCCESS != hresult:
|
||||
raise error("Failed to establish context: " + SCardGetErrorMessage(hresult))
|
||||
try:
|
||||
hresult = SCardForgetReaderGroup(hcontext, group)
|
||||
if hresult != SCARD_S_SUCCESS:
|
||||
raise error(
|
||||
"Unable to forget reader group: " + SCardGetErrorMessage(hresult)
|
||||
)
|
||||
|
||||
innerreadergroups.removereadergroup(self, group)
|
||||
|
||||
finally:
|
||||
hresult = SCardReleaseContext(hcontext)
|
||||
if SCARD_S_SUCCESS != hresult:
|
||||
raise error(
|
||||
"Failed to release context: " + SCardGetErrorMessage(hresult)
|
||||
)
|
||||
|
||||
|
||||
class PCSCReaderGroups(readergroups):
|
||||
"""PCSC readers groups."""
|
||||
|
||||
def __init__(self, initlist=None):
|
||||
"""Create a single instance of pcscinnerreadergroups on first call"""
|
||||
self.innerclazz = pcscinnerreadergroups
|
||||
readergroups.__init__(self, initlist)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(PCSCReaderGroups().getreadergroups())
|
||||
Reference in New Issue
Block a user